Processing中毒者の嘔吐物

ProcessingやらopenFrameworksやら色んなプログラミングについて吐きます

Processingで三角関数を使って球体を作る

ジェネラティブ・アートを読まれた方はご存知と思いますが、本の中に三角関数で球体を作るという内容があります。ですがなぜその三角関数の数式で球体が表現できるのか全く解説されていません。多分面倒くさかったんでしょう。ということで代わりに解説していこうと思います。

[普及版]ジェネラティブ・アート―Processingによる実践ガイド

[普及版]ジェネラティブ・アート―Processingによる実践ガイド

  • 作者: マット・ピアソン,Matt Pearson,久保田晃弘,沖啓介
  • 出版社/メーカー: ビー・エヌ・エヌ新社
  • 発売日: 2014/11/21
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (1件) を見る
f:id:P5Aholic:20150615172434j:plain
わかりやすいように、まずはジェネラティブ・アートに載ってるプログラムに変更を加えたものを解説していきます。

Step 1 / とりあえず一個の円を描く

とりあえず、3D空間に1個の円を表示するプログラムが以下です。
f:id:P5Aholic:20150615172152j:plain

void setup(){
  size(960, 540, P3D);
}

void draw(){
  background(0, 15, 30);
  // 原点を画面中心に
  translate(width/2, height/2, 0);
  // X軸を中心に回転
  rotateX(frameCount*0.005);
  // Z軸を中心に回転
  rotateZ(frameCount*0.005);
  
  // 円状に点を描く
  float radius = 200; // 半径
  for(int t = 0; t < 360; t += 10){
    // 角度をラジアンに
    float radianT = radians(t);
    // 点の座標を計算
    float x = radius * cos(radianT);
    float y = radius * sin(radianT);
    // 点を描画
    stroke(0, 128, 128);
    strokeWeight(8);
    point(x, y, 0);
  }
}

中学か高校で習う単位円ってやつに半径200をかけてやると半径200の円が作れます。

Step 2 / z軸方向に円を追加する

次に、この円をz軸方向に複数配置してみます。
f:id:P5Aholic:20150615173559j:plain

void setup(){
  size(960, 540, P3D);
}

void draw(){
  background(0, 15, 30);
  
  translate(width/2, height/2, 0);
  rotateX(frameCount*0.005);
  rotateZ(frameCount*0.005);
  
  float radius = 200;
  for(int s = 0; s <= 180; s += 10){
    float radianS = radians(s);
    // 0 <= s <= 180 なので -1 <= cos(radianS) <= 1
    // よって z は -radius <= z <= radius
    float z = radius * cos(radianS);
    for(int t = 0; t < 360; t += 10){
      float radianT = radians(t);
      float x = radius * cos(radianT);
      float y = radius * sin(radianT);
      stroke(0, 128, 128);
      strokeWeight(8);
      point(x, y, z);
    }
  }
}

ここでちょっとだけ頭を使います。for()を外側にもう一個作って、cos(radianS)が-1~1になるようにします。これに200をかけたものをzとするとzの範囲が-200~200となります。これでひとまず筒状の物体を作ることはできました。筒状になってしまうのは半径が200で固定されているからです。

Step 3 / 半径を調整して球体を仕上げる

では最後に、半径をいい感じに調整して球体を仕上げます。

void setup(){
  size(960, 540, P3D);
}

void draw(){
  background(0, 15, 30);
  
  translate(width/2, height/2, 0);
  rotateX(frameCount*0.005);
  rotateZ(frameCount*0.005);
  
  float radius = 200;
  for(int s = 0; s <= 180; s += 10){
    float radianS = radians(s);
    float z = radius * cos(radianS);
    for(int t = 0; t < 360; t += 10){
      float radianT = radians(t);
      // sin(radianS)は0→1→0の順で変化するので
      // radius * sin(radianS)は0→200→0になる
      float x = radius * sin(radianS) * cos(radianT);
      float y = radius * sin(radianS) * sin(radianT);
      stroke(0, 128, 128);
      strokeWeight(8);
      point(x, y, z);
    }
  }
}

半径を、球体のつむじにに近いほど小さく、球体のお腹に近いほど大きくすればいい感じになります。つまり半径を0→200→0の順で変化させればいいわけです。そのためにradianSをもう一度使います。sin(radianS)は0→1→0の順で変化するので、これに200を掛けてやれば半径を0→200→0に変化させることができるわけです。

まとめると、radianSがz座標と半径の計算に対応して、radianTが円周上の座標の計算に対応しているわけです。

細かいことを補足しておきます。
内側のfor()では(t < 360)としています。これは一周回りきらないようにするためです。時計でいうと12の針から始まって時計回りに動いて11の針までいくと一週分表示したことになります。(t <= 360)だと、12から12までいって12のところで2つ点が重なることになります。逆に外側のループでは(s <= 180)としています。(s < 180)とすると片方のつむじのてっぺんに点が表示されないのがわかると思います。

300個の点を半径200の球体上にランダムに配置するサンプルも紹介しておきます。

int numPoints = 300;
float[] xPos = new float[numPoints];
float[] yPos = new float[numPoints];
float[] zPos = new float[numPoints];

void setup(){
  size(960, 540, P3D);
  
  // 半径200の球体上に点をランダムに配置
  for(int i = 0; i < numPoints; i++){
    float radianS = radians(random(180));
    float radianT = radians(random(360));
    xPos[i] = 200 * sin(radianS) * cos(radianT);
    yPos[i] = 200 * sin(radianS) * sin(radianT);
    zPos[i] = 200 * cos(radianS);
    /*
    x = sin(radianS) * cos(radianT)
    y = sin(radianS) * sin(radianT)
    z = cos(radianS)
    は、半径1の球体の座標を計算していることになる
    */
  }
}

void draw(){
  background(0, 15, 30);
  
  translate(width/2, height/2, 0);
  rotateX(frameCount*0.005);
  rotateZ(frameCount*0.005);
  
  for(int i = 0; i < numPoints; i++){
    stroke(0, 128, 128);
    strokeWeight(8);
    point(xPos[i], yPos[i], zPos[i]);
  }
}


では、ジェネラティブ・アートに載っている一筆書き版の解説をします。
f:id:P5Aholic:20150615191604j:plain

void setup(){
  size(960, 540, P3D);
}

void draw(){
  background(0, 15, 30);
  
  translate(width/2, height/2, 0);
  rotateX(frameCount*0.01);
  rotateY(frameCount*0.01);
  
  // 一つ前の座標を格納する
  float lastX = 0, lastY = 0, lastZ = 0;
  float radius = 200;
  float s = 0, t = 0;
  
  while(s <= 180){
    float radianS = radians(s);
    float radianT = radians(t);
    // 現在の座標を更新
    float x = radius * sin(radianS) * cos(radianT);
    float y = radius * sin(radianS) * sin(radianT);
    float z = radius * cos(radianS);
    
    stroke(0, 128, 128);
    if(lastX != 0){
      strokeWeight(1);
      // 現在の座標から一つ前の座標に線を引く
      line(x, y, z, lastX, lastY, lastZ);
    }
    strokeWeight(15);
    point(x, y, z);
    
    // 一つ前の座標を更新
    lastX = x;
    lastY = y;
    lastZ = z;
    
    // sとtを同時に更新
    s++;
    t+=10;
  }
}

sを1ずつ更新させ、同時にtも更新することで一筆書きの球体を作ることが出来ます。
tは点と次の点との角度なので、tの更新間隔を変えることで色々な形になります。
t += 30の場合
f:id:P5Aholic:20150615193212j:plain
t += 90の場合
f:id:P5Aholic:20150615193223j:plain
t += 180の場合
f:id:P5Aholic:20150615193229j:plain
これを利用したのが僕がたびたび紹介しているこちらです↓www.youtube.com
ソースはこちら↓
tの更新間隔も更新しています。

float velocity = 0;        // tに足す値
float acceleration = 0.05; // velocityに足す値

void setup(){
  size(960, 540, P3D);
}

void draw(){
  background(0, 15, 30);
  
  translate(width/2, height/2, 0);
  rotateX(frameCount*0.01);
  rotateY(frameCount*0.01);
  
  float lastX = 0, lastY = 0, lastZ = 0;
  float radius = 200;
  float s = 0, t = 0;
  
  while(s <= 180){
    float radianS = radians(s);
    float radianT = radians(t);
    float x = radius * sin(radianS) * cos(radianT);
    float y = radius * sin(radianS) * sin(radianT);
    float z = radius * cos(radianS);
    
    stroke(0, 128, 128);
    if(lastX != 0){
      strokeWeight(1);
      line(x, y, z, lastX, lastY, lastZ);
    }
    strokeWeight(15);
    point(x, y, z);
    
    lastX = x;
    lastY = y;
    lastZ = z;
    
    s++;
    t += velocity;
  }
  velocity += acceleration;
}

お疲れ様でした。解説は以上です。ジェネラティブアートに載ってる数式の解説が無いことに不満な人が多いだろうと思ったので書きました。理解の助けになれば嬉しいです。