プログラミングの備忘録

プログラムをつくる過程を残すもの

processingの備忘録 -3次元グラフ-

こんにちは。
今回は、3次元でグラフを描くということをやってみます。


YouTubeにprocessing関連の動画が上がっているのか気になって調べてみたときに「The Coding Train」というチャンネルを見つけました。
その動画をさらっと見てみると自分の興味がありそうなものがたくさんあったので即チャンネル登録しまして、のんびり見てマネしてみているという状態です。
動画は英語なので細かい説明で意味のわからないところはあるかもしれませんが、コード自体は同じ文法なので全くわからないと言うことは無いと思います。ぜひ見てみてください。


その中のひとつで、「Coding Challenge #11: 3D Terrain Generation with Perlin Noise in Processing」という動画があります。
訳すと「コーディングチャレンジ #11:Processingでパーリンノイズを用いた3D地形生成」という感じですが、3次元空間に平面をつくり、「noise()」でなめらかな高低差をつけることで地形つくるという内容です。

全く同じでは無いですが、やってみるとこんな感じになりました。

f:id:taq2777:20211218195325p:plain

パーリンノイズを使った手法は3Dのゲームで自然な地形をつくるときに使われているらしいですが、確かに「Minecraft」や「原神」などを見ているとそんな感じがしますね。


これを見ていて、3次元でのグラフに応用できそうだなと思ったので今回の記事を書いています。
ということでやってみます。


本ブログでの3Dの描画といえばお馴染みのグリグリ動かせるコード。
今回ももれなく使います。
(何のことかわからないという方はこちらを参照してください。)


基礎をつくる

ここでいう基礎というのは、高低差の何もない平面のことです。
平面なら「rect()」で描いても良いですが、一色で高低差がわかりづらいので先に載せた画像のように線を引いておくことにします。

任意の図形を描きたいときは「beginShape()」と「endShape()」そして「vertex()」を使うと便利です。
「beginShape()」で図形を描き始めることをいい、「vertex()」で頂点を指定していきます。最後に「endShape()」で図形を描き終えたことをいって終了です。
また「beginShape()」の引数には「POINTS」「LINES」「QUADS」など様々ありますが、今回は「TRIANGLE_STRIP」を使います。
「TRIANGLE_STRIP」では、「vertex()」で指定された頂点で描かれる図形を三角形で分けてくれます。

以下でコードを書いていきますが、アイデアは動画からもらっていますがそれを元に一から自分で作り直しているので、変数名やつくりは違う部分もあります。
また、ゴチャゴチャするのでグリグリ動かすコードの部分は省いて書いてあります。

int l, w, h;

void setup(){
    size(500, 500, P3D);

    l = 20; //マスの間隔
    w = 1000; //描画する領域の幅
    h = 1000; //描画する領域の高さ
}

void draw(){
    background(255);

    translate(-w/2, -h/2);
    for(int x = 0; x < w; x += l){
        beginShape(TRIANGLE_STRIP);
        for(int y = 0; y < h; y += l){
            vertex(x, y, 0);
            vertex(x+l, y, 0);
        }
        endShape();
    }
}

「translate()」によって、描画領域の真ん中が座標の中心になるようにしています。
考え方としては、(x,y)、(x+l,y)、(x,y+l)、(x+l,y+2*l)、…と点を打っていき、それぞれの点をつないでTRIANGLE_STRIPで三角形状に分けるという感じです。
z軸方向はまだ考えていないので、今は0としてあります。

実行すると、以下のように三角形のタイルで埋められたような平面をつくることができます。
f:id:taq2777:20211218220849p:plain


z軸方向の値を決める

続いて、z軸方向の値(地形の生成の例でいうところの「noise()」で指定する部分)を決めます。

以前万有引力の法則を扱ったものがあるのでそれを利用することにします。
ただし、そのままだと力$F$についてのグラフになります。
それでもいいですが、エネルギー$U$についてのグラフにしてみます。

ここで物理の登場です。
エネルギー$U$は力$F$を積分して-1倍することで求められます。
力$F$は、 $$ F=-G \frac{Mm}{r^{2}} $$ これを積分して-1倍すると、 $$ U=-G \frac{Mm}{r} $$ という風に求められました。

後はこれをプログラムに入れ込めば良いだけです。

int l, w, h;
float G, M, m;
float[][] U;

void setup(){
    size(500, 500, P3D);

    l = 20;
    w = 1000;
    h = 1000;

    G = 100; //万有引力定数
    M = 100; //恒星の質量
    m = 1; //惑星の質量

    //エネルギーUの計算
    U = new float[w/l][h/l];
    for(int x = 0; x < w; x += l){
    for(int y = 0; y < h; y += l){
        float r = sqrt((x-w/2)*(x-w/2) + (y-h/2)*(y-h/2));
        U[x/l][y/l] = -G*m*M/r;
    }
    }
}

void draw(){
    background(255);

    translate(-w/2, -h/2);
    for(int x = 0; x < w; x += l){
        beginShape(TRIANGLE_STRIP);
        for(int y = 0; y < h; y += l){
            vertex(x, y, U[x/l][y/l]);
            vertex(x+l, y, U[(x+l)/l][y/l]);
        }
        endShape();
    }
}

恒星がある位置を描画領域の真ん中(w/2,h/2)として、「vertex()」で打つ各点の位置でのエネルギーを計算して2次元の配列Uに入れています。
「vertex()」のz座標にUの値を入れて、高さの指定を忘れないように。

実行すると、以下のように恒星がある位置に向かってへこんだような曲面をつくることができます。
この曲面に球を転がすとだんだん中心に吸い込まれていくように周回することが予想でき、その動きが実際の惑星のものとだいたい同じになっているというわけです。
f:id:taq2777:20211218224253p:plain


まとめ

というわけで、今回は3次元的なグラフを描く方法について書いてみました。
他の数式についてもグラフ化してみると、その数式に対する理解が深まるかと思います。

「R」や「Mathematica」などでも同じようなことがもっと簡単にできたと記憶していますが、再三書いているかもしれませんがprocessingの練習という意味でブログ化しているので問題ありません。
むしろ他の言語との比較もできて、その言語が何に特化したものなのかを体感することができるかもしれません。