4.樹を描く

タートルグラフィクスで樹を描きます。コッホ曲線などとの違いは、「枝分かれする」ことです。

この枝分かれをどうやって描くか。いまのところ、亀に命令できるのは「前へ」だけですから、後戻りができません。もちろん180°向きを変えれば戻れますが・・・・

ここで役に立つのが、CindyScriptのgsave()とgrestore()です。gsave()は現在の座標系の状態を「スタック」という場所に保存します。grestore()はスタックから取り出します。(取り出すとスタックは空になります) 枝分かれするときにその状態を保存し、枝先まで進んだら、前の状態に戻します。戻すのは座標系の状態なので、描かれた線はそのままです。

L-Systemでは、この2つを角カッコ [ と ] で表します。[ が gsave() ] がgrestore() に相当します。プログラミングの一般的用語では、push と pop といいます。L-Systemでもpush / pop といっています。では、これを亀に指令する命令セットの中に加えましょう。それとともに、関数も少し整理しました。Initialization スロットは次のようになります。

---------------------------------------------------------------------------------------------------------------------

// 前に step だけ進む

forward(step):=(

draw((0,0),(step,0));

translate((step,0));

);

// 亀を命令に従って動かす

turtle(command,step,angle):=(

  if(command=="A",forward(step));

  if(command=="B",forward(step));

  if(command=="F",forward(step));

  if(command=="f",translate((step,0)));

  if(command=="+",rotate(angle));

  if(command=="-",rotate(-angle));

  if(command=="[",gsave());

  if(command=="]",grestore());

);

// initiator と generator からコマンド列を作成する

makecom(n):=(

  workstr=initiator;

  repeat(n,

    workstr=replace(workstr,generator);

  );

);

// コマンド列 commandrow にしたがって亀を動かす

tree(commandrow,step,angle):=(

  gsave();

  repeat(length(commandrow),turtle(commandrow_#,step,angle));

  grestore();

);

---------------------------------------------------------------------------------------------------------------------

では、左右に枝分かれのある樹を描くコマンド列を考えましょう。次の図の①から⑩の手順で描きます。

初め、亀は原点Oにいて、x軸の正の方向(矢印の方向)を向いています。

この手順の中で、「⑤ ②の状態に戻る」ためには、②のあとで状態を保存(push)しなければなりません。⑨も同様です。また、①は初めの向きを変えているだけなので、②以降を考えていきます。この手順を亀に指令する文字で表していくと次のようになります。

① 90°向きを変える:直接亀に命令

② F[

③ ー

④ F

⑤ ]

⑥ F[

⑦ +

⑧ F

⑨ ]

⑩ F

②からあとを続けて横に書くと F[ーF]F[+F]F となります。これがジェネレータです。イニシエータはFをひとつだけにします。置き換えはFに対して行なうので、最初にひとつ必要なのです。

drawスロットに次のスクリプトを書きます。

---------------------------------------------------------------------------------------------------------------------

kaku=30°;

nagasa=2;

initiator="F";

generator=[["F","F[-F]F[+F]F"]];

n=1;

rotate(90°);

s=makecom(n);

tree(s,nagasa/n,kaku);

---------------------------------------------------------------------------------------------------------------------

これで、先ほどの図が描けるはずです。やってみましょう。

うまくいったら、nの値を変えてみてください。1歩進む分が、ジェネレータの形にまるまる置き換えられます。わかりやすいように色分けしてみましょう。

n=1のとき F[-F]F[+F]F 

n=2とのき F[-F]F[+F]F[-F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F]F[-F]F[+F]F 

n=3のとき F[-F]F[+F]F[-F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F]F[-F]F[+F]F[-F[-F]F[+F]F[-F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F]F[-F]F[+F]F]F[-F]F[+F]F[-F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F[-F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F]F[-F]F[+F]F]F[-F]F[+F]F[-F[-F]F[+F]F]F[-F]F[+F]F[+F[-F]F[+F]F]F[-F]F[+F]F 

n=1のときのFがジェネレータで置き換えられてn=2の文字列になります。

n=2のときのFがジェネレータで置き換えられてn=3の文字列になります。

急激に文字数が増えますが、n=3のときの図は次のようになるでしょう。

リンデンマイヤーの「The Algorithmic Beauty of Plants」には、次のような例が載っています。角度とジェネレータ、繰り返し回数 n を変えるだけです。やってみましょう。大きくなりすぎるようなら、nagasaを小さくしましょう。

ジェネレータ2つ

ジェネレータを2つにすると、さらにバリエーションのある図が描けます。ただし、亀の動きを追って行くのは結構むずかしくなります。

次のスクリプトで、n=1,2,・・ としたときの、コマンド列(亀に指令する文字列)の変化と図を示します。

---------------------------------------------------------------------------------------------------------------------

kaku=20°;

nagasa=2;

initiator="A";

generator=[["A","B[+A]B[-A]+A"],["B","BB"]];

n=1;

rotate(90°);

s=makecom(n);

tree(s,nagasa/n,kaku);

---------------------------------------------------------------------------------------------------------------------

リンデンマイヤーの「The Algorithmic Beauty of Plants」には、次のような例が載っています。左が、上の例のn=7のときの図です。3つの図のジェネレータ2は変わりません。角度とジェネレータ1、繰り返し回数 n を変えるだけです。やってみましょう。