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 を変えるだけです。やってみましょう。