Processingで再帰をアニメーションする

画像1

何をするの?

タイトルのとおりです。普通にメソッドを用いて再帰を描画する場合、だいたい瞬時に再帰が実行されて絵としては味気ないので、今回はオブジェクト指向を利用して再帰のアニメーションを実装してみたいと思います。サンプルコードが動いている様子はyoutubeで見ることができます。

https://www.youtube.com/watch?v=DhMrp-wNrlg&feature=youtu.be

再帰でつくられる図形をインスタンス化することでアニメーションする以外にも再帰の枝同士の当たり判定の実装や、枝を生やした後の移動が高速で行えるなどのメリットがあります。

まずはサンプルコードから

Stems[] stem_arr = {};//配列の生成

void setup() {
 size(1000, 1000);
 colorMode(RGB, 255, 255, 255, 100);
 background(0, 0, 0, 100);
 frameRate(60);
 born(width/2, height, 200, -90, 7);
}

void draw() {
 background(0, 0, 0);

   for (int i=0; i<stem_arr.length; i+=1) {//インスタンスの数だけ実行
     Stems this_stem = stem_arr[i];
     this_stem.calculate();
     this_stem.display();
     this_stem.rec();
   }
}

void born(float x, float y, float _h, float k_angle, int level) {
 Stems this_stem = new Stems(x, y, _h, k_angle, level);
 //生成されるx座標、y座標、枝の長さ、角度、再帰回数
 stem_arr = (Stems[])append(stem_arr, this_stem);//インスタンスを配列に追加
}

class Stems {
 float x, y, h, fangle, xs, ys;
 float frame_c;//変化量
 int maxframe=20;//枝の生える速度
 int lev;
 boolean pro=true;
 Stems(float _x, float _y, float _h, float _angle, int _lev) { //コンストラクタ
   x=_x;
   y=_y;
   h=_h;
   fangle=_angle;
   lev=_lev;
 }

 void calculate() {//枝の終点、途中座標の計算
   if (frame_c<maxframe) {
     frame_c++;

     xs=h*cos(radians(fangle))+x;
     ys=h*sin(radians(fangle))+y;
     
     xs = map(frame_c, 0, maxframe, x, xs);
     ys = map(frame_c, 0, maxframe, y, ys);
   }
 }

 void display() {   
   strokeWeight(1);
   stroke(255, 255, 255);
   line(x, y, xs, ys);
 }

 void rec() {//ここで再帰呼び出し
   if (lev>0 && pro && frame_c==maxframe) {
     float h1=h*0.75;
     born(xs, ys, h1, fangle+20, lev-1);
     born(xs, ys, h1, fangle-20, lev-1);
     pro=false;
   }
 }
}

解説

画像2

簡単な解説図をイラレで作りました。おおよそはこれを見れば理解できると思いますが、詳しく見ていきましょう。


Stems[] stem_arr = {};

これによりインスタンスを動的な配列に入れることができます。
再帰の枝は条件を加えると大幅に増減してしまうのでこのようにしています。


born(width/2, height, 200, -90, 7);

9行目のbornにより最初の枝のインスタンスを作るメソッドbornを呼び出しています。先程の解説の①に相当する部分です。


void born(float x, float y, float _h, float k_angle, int steam) {
 Stems this_stem = new Stems(x, y, _h, k_angle, steam);
 //生成されるx座標、y座標、枝の長さ、角度、再帰回数
 stem_arr = (Stems[])append(stem_arr, this_stem);//インスタンスを配列に追加
}

23行目、今回の関数の中核、bornです。これに枝の発生する座標、長さ、角度などを入れてあげれば枝のインスタンスをstems_arrに追加してくれます。
append(追加先の配列,追加するインスタンス)によって追加されます。
追加する要素は追加先の配列とデータ型が一致していなければなりません。余談ですが、processingには標準で ssplice()を使うことで配列の任意の場所に要素を追加したり、subuset()により削除するコマンドが用意されています。詳しくは公式リファレンスのArray Functionの項をご覧ください。


 void calculate() {//枝の終点、途中座標の計算
   if (frame_c<maxframe) {
     frame_c++;

     xs=h*cos(radians(fangle))+x;
     ys=h*sin(radians(fangle))+y;
     
     xs = map(frame_c, 0, maxframe, x, xs);
     ys = map(frame_c, 0, maxframe, y, ys);
   }
 } 

コンストラクタについてはインスタンスに数値を入れているだけなので、割愛します。43行目のcalculateについて書きます。実にシンプルでインスタンス内部で増加するframe_cとmaxframe(枝の伸びる時間)の数値によって設定した枝の伸びる時間にたいしての経過フレーム数を計測しています。
枝の終端の座標(xs,ys)を計算し、mapを使うことで経過フレームに対する枝の終端座標を出しています。設定したフレーム数に達すると、伸び切った枝は計算を停止します。ここを調整すれば枝の伸び方を変えることができます。


  void rec() {//ここで再帰呼び出し
   if (lev>0 && pro && frame_c==maxframe) {
     float h1=h*0.75;
     born(xs, ys, h1, fangle+20, lev-1);
     born(xs, ys, h1, fangle-20, lev-1);
     pro=false;
   }
 }

displayは特に言うこと無いのでまた飛ばして61行目のrec()の解説です。
枝が伸び切ったところで、ここで解説図の②、bornの呼び出しを行います。なにもしなければ無限にインスタンスを増殖させることになるので、levによって管理しています。一回実行するごとに減っていき0になると関数の実行を停止します。
levが0以上の数値を保持しているインスタンスがrec()を実行するたびにまたインスタンスを作ってしまっては困りますので一度実行したかをチェックするproを設定しています。ここを書き換えれば生成パターンを変えられます。

あとはこの関数が2回bornを呼び出して、bornによって作り出されたインスタンスまたインスタンスを生む…をlevが0になるまでループし続ける仕組みです。

解説は以上になります。ご覧頂きありがとうございました。

おまけ

再帰ではこんな作品を作ることもできます。

画像4

画像4

ランダムと組み合わせるとバリエーションが生まれてたのしいですよ。

質問等あればコメントにてどうぞ。
では。

執筆者
twitter:mirakuma_215
 https://twitter.com/mirakuma_215

この記事が気に入ったらサポートをしてみませんか?