F02 ペンローズタイリング

ペンローズのカイトとダートを使って平面を埋め尽くす方法はいろいろありますが,そのひとつです。ただし,カイト・ダートを増やしていくのではなく,有限な領域を,カイトとダートを縮小して埋めます。

出発点は1本の線分です。この線分からカイトとダートを作り,それを縮小していきます。

アルゴリズムは,「GeoGebra日本」の「GeoGebra実例」「F02 ペンローズタイリング」のものをそのまま借用します。というより,同ページのものをCinderellaに移植する,というほうが正確でしょう。ただ,同ページの手順をそのまま踏んでいくのですが,GeoGebraでそのままやっていっても,何をしているのか分かりにくいところもあるので,適宜補足していきます。また,見出しの表現が若干異なります。

カイトとダートの頂点のリストを返す関数の作成

線分の両端の点を与えると、収縮に用いるカイトとダートのリストを返す関数 defkk(), defkd(), defdk(),defdd() を作ります。 DeflKK1, DeflKD1, DeflDK1, DeflDD1 に相当します。

GeoGebraでツールを作るというのは,CinderellaではCindyscriptのユーザー定義関数を作ることに相当します。

まず,2点 k1,k2 に対し,k1をk2の周りに回転するのですが,Cindyscriptに用意されている rotate() 関数は座標系の回転なので,新たに関数 rot() を作ります。回転は複素数を利用します。

//点p をp0の周りにthだけ回転した点の座標を返す

rot(p,th,p0):=(

z1=complex(p);

z2=complex(p0);

z=z2+(z1-z2)*(cos(th)+i*sin(th));

gauss(z);

);

これを用いて,ツールKK,KD,DK,DD に相当する関数を作ります。なお,こちらは小文字です。

// 2点p1,p2からkk,kd,dk,dd を作る

defkk(k1,k2):=(

k5=rot(k1,-pi/5,k2);

k6=rot(k1,-2*pi/5,k2);

k7=rot(k6,2*pi/5,k5);

[k5,k7,k5,k1];

);

defkd(k1,k2):=(

k5=rot(k1,-pi/5,k2);

k6=rot(k1,-2*pi/5,k2);

[k5,k6];

);

defdk(d1,d2):=(

d4=rot(d1,-3*pi/5,d2);

d5=rot(d2,-pi/5,d4);

d6=rot(d5,-4*pi/5,d2);

[d2,d6];

);

defdd(d1,d2):=(

d4=rot(d1,-3*pi/5,d2);

d5=rot(d2,-pi/5,d4);

DD=[d2,d5];

);

この中から,defkk() の働きを確かめておきましょう。

forall([[1,1],[3,2]],draw(#,color->[1,0,0],size->5));

forall(defkk([1,1],[3,2]),draw(#));

2点(1,1) と (3,2) に対してdefkk() で作成される点を表示します。元の点は赤,できた点は緑です。

(1,1) が共通です。

他も同様にできます。

defkd() derdk() defdd()


点の番号は,GeoGebra実例のページの末尾にあるカイトとダートの図に対応します。その中から,収縮に用いるベクトルを表すリストを作っています。

これらの点を結ぶとカイトとダートの図の一部ができます。すべての辺を描いても,並べて描いていくと重複するわけですから,これだけでよいというわけですね。

タイルのリストを収縮する関数の作成

カイトの点のリストとダートの点のリストをリストにして与えると、収縮して現れるカイトのリストとダートのリストを返す関数を作成します。ツール Defl に相当します。

defl(kdlist):=(

klist=kdlist_1;

dlist=kdlist_2;

kks=[];

repeat(length(klist)/2,s,step->2,

kks=kks++defkk(klist_s,klist_(s+1));

);

kds=[];

repeat(length(klist)/2,s,step->2,

kds=kds++defkd(klist_s,klist_(s+1));

);

dks=[];

repeat(length(dlist)/2,s,step->2,

dks=dks++defdk(dlist_s,dlist_(s+1));

);

dds=[];

repeat(length(dlist)/2,s,step->2,

dds=dds++defdd(dlist_s,dlist_(s+1));

);

[kks++dks,kds++dds];

);

GeoGebraの Sequence[] は繰り返しですので,Cindyscriptでは repeat() です。2つずつとるので,step->2 にしています。

GeoGebraでの Join[] はリストの結合なので,Cindyscriptでは演算子 ++ になります。

GeoGebraと異なるのは,はじめに空リストを用意してから,新しくできるリストを ++ でつないでいくことです。

さて,やっていることを確認するために,カイトとダートのリストを

kdlist=[[[2,3],[4,4]],[[2,6],[4,7]]];

として,defkk() のときのように色分けして表示してみます。

赤がカイトの線分の両端,緑ができた点,黄色がダートの両端の線分,黒ができた点です。

さっぱりわからないので,カイトとダートの全体を表示するスクリプトを作ってみましょう。ここでは混乱を避けるためにそのスクリプトは示しません。(この下からダウンロードできるファイルには含まれています。)

また,位置もGeoGebra実例のものとは変えています。

この図と,GeoGebra実例のページの最後にあるアルゴリズムの図を見比べれば,やっていることの意味がわかります。

また,この関数に渡す引数は,「カイトの点のリストとダートの点のリストのリスト」であることに注意が必要です。つまり,複数のカイトとダートの点のリストを,まとめて渡すとまとめて処理されて返されるのです。

タイル1枚を与えるとその線分を描く関数の作成

線分の両端を与えると,カイトとダートの一部をそれぞれ描く関数です。一部を描く意味は,前述の通り,全部描かなくても連続して描けば,結果として全体が描かれることになるからです。このことは,GeoGebra実例にある「仕様」です。

GeoGebraでは,「線分のリストを返す」となっていますが,GeoGebra の Segment[] は実際には線分を描くことになるので,Cindyscriptでは「リストを返す」のではなく,実際に線分を描かなくてはなりません。

// カイトとダートの線分を描く

kseg(k1,k2):=(

k5=rot(k1,-pi/5,k2);

k6=rot(k1,-2*pi/5,k2);

k3=rot(k5,-3*pi/5,k6);

draw(k1,k2);

draw(k2,k3);

);

dseg(d1,d2):=(

d4=rot(d1,-3*pi/5,d2);

draw(d1,d2);

draw(d1,d4);

);

どの部分が描かれるのか,全体と比べてみます。それぞれ,左が全体,右がこれらで描かれるものです。

kseg() dseg()


タイルのリストを与えるとそれらを描く関数の作成

カイトのリストとダートのリストをリストで与えると、それらを描画する関数 (penseg()) を作成します。

このリストはそれぞれ複数のリストです。要は,リストから2つずつをとって,kseg() と dseg() で描いていくだけです。

penseg(ksds):=(

ks=ksds_1;

ds=ksds_2;

repeat(length(ks)/2,s,step->2,

kseg(ks_s,ks_(s+1));

);

repeat(length(ds)/2,s,step->2,

dseg(ds_s,ds_(s+1));

);

);

ペンローズタイリングの関数の作成

2点を与えるとペンローズタイリングを描く関数 (penrose()) を作成します。

GeoGebra実例では,Defl[] を5回適用して描いていますが,Cindyscriptでは繰り返し回数を引数で与える形にします。

2点A,Bをとってからこの関数を使います。(実際には点をとらずに座標を与えてもよい)

まず,Aを中心とする半径ABの円周上に等間隔に10個の点をとります。中心角が pi/5 の扇形の点です。これをカイトの初期の点リストとし,ダートの点リストはなしにします。このカイトとダートの点のリストが,GeoGebra実例ではNですが,ここでは sun にします。

sun に deal() をn回適用します。できたリストを penseg()で描画します。

penrose(p1,p2,n):=(

sun=[apply(1..10,rot(p2,pi/5*#,p1)),[]];

n2=defl(sun);

repeat(n-1,s,

n2=defl(n2)

);

penseg(n2);

);

n=1 のとき penrose(A.xy,B,xy,1) n=5 のときpenrose(A.xy,B,xy,5)


なお,kseg() dseg() ではカイトとダートの一部だけを描いていますが,全体を描くようにすることもできます。その場合はこれより細かい図になります。