エッシャーの絵でトカゲが描かれたものがあります。今回はこれを再現してみたいと思います。書籍「エッシャー・マジック―だまし絵の世界を数理で読み解く」の3.2節(p.44~p.46)を参考にして描いてみました。
トカゲの絵
今回描いてみたトカゲの絵は以下のような図形になりました。それっぽくなったのではないでしょうか。
描き方
こんな絵をそんな簡単に描けるの? と思うかもしれませんが、壁紙アートの記事「基本図形を変形する」のところで解説した考え方を用いれば、意外に簡単に描くことができます。結論としては、P3群の基本図形となる正六角形を変形するだけで、描くことができます。
P3群の基本図形の変形
P3群の基本図形を変形するために、再度隣り合う基本図形同士の辺のラベルと向きをみてみます。
辺\(a\)と辺\(b\)、辺\(c\)と辺\(d\)、および辺\(e\)と辺\(f\)がそれぞれ異なる向きに重なっています。つまり、辺\(a\)を自由に変形したものを用意し、それを辺\(b\)の部分に反対向きに置きます。また辺\(c\)と辺\(d\)、辺\(e\)と辺\(f\)についても同じように変形します。そうすると、隣り合う基本図形同士を重ねることなくP3群の対称性に従って敷き詰めることができます。
辺\(a\)と辺\(b\)の変形
では、実際に辺の形を変形していきます。まず辺\(a\)と辺\(b\)の辺です。
辺\(a\)で変形したものを辺\(b\)の部分に反対向きに置いていることが分かると思います。これがトカゲの左腕と左足の部分になります。
辺\(c\)と辺\(d\)の変形
次に、辺\(c\)と辺\(d\)の辺です。
これがトカゲの尻尾と右足の部分になります。
辺\(e\)と辺\(f\)の変形
最後に、辺\(e\)と辺\(f\)の辺です。
これがトカゲの頭と右腕の部分になります。
トカゲの形の完成
以上の各辺の変形を組み合わせると、基本図形がトカゲの形になります。
トカゲの絵を完成させる
トカゲの形に変形した基本図形をP3群の対称性を考慮して並べていくと、最初に示したような図形を得ることができます。
プログラムコード
最後に、プログラムコードを載せておきます。基本は別記事「渦巻き図形(P3)」に記載しているプログラムコードのうち、makeRecurHexagon関数を下記のmakeLizard関数に置き換えたものになります。なお、トカゲの眼や背中の線、指にあたる部分に線を無理矢理入れてみました。絵の中身に関しては別の方法で入れた方がいいでしょうね。
// 正六角形からトカゲの形に変形する関数(基本図形)
PShape makeLizard(){
PVector[] v = new PVector[6]; // 正六角形の頂点
for (int i=0; i<6; i++){
v[i] = PVector.fromAngle(-i * PI / 3 + PI).mult(scalar / 3.0);
v[i].add( base[1].copy().mult(scalar / 3.0) );
}
// トカゲの形を作っていく
PShape lizard = createShape(GROUP);
PShape lizard_parts = createShape();
lizard_parts.beginShape();
PVector[] auxiliary_point = new PVector[8];
// 辺aを描く
lizard_parts.vertex(v[0].x, v[0].y);
auxiliary_point = getAuxiliaryPoints1(v[0], v[1], v[1].copy().sub(v[3].copy()));
for(int i=0; i<5; i++){
lizard_parts.vertex(auxiliary_point[i].x, auxiliary_point[i].y);
}
// 辺bを描く
lizard_parts.vertex(v[1].x, v[1].y);
auxiliary_point = getAuxiliaryPoints1(v[2], v[1], v[5].copy().sub(v[1].copy()));
for(int i=0; i<5; i++){
lizard_parts.vertex(auxiliary_point[4-i].x, auxiliary_point[4-i].y);
}
// 辺cを描く
lizard_parts.vertex(v[2].x, v[2].y);
auxiliary_point = getAuxiliaryPoints2(v[2], v[3], v[3].copy().sub(v[5].copy()));
for(int i=0; i<8; i++){
lizard_parts.vertex(auxiliary_point[i].x, auxiliary_point[i].y);
}
// 辺dを描く
lizard_parts.vertex(v[3].x, v[3].y);
auxiliary_point = getAuxiliaryPoints2(v[4], v[3], v[1].copy().sub(v[3].copy()));
for(int i=0; i<8; i++){
lizard_parts.vertex(auxiliary_point[7-i].x, auxiliary_point[7-i].y);
}
// 辺eを描く
lizard_parts.vertex(v[4].x, v[4].y);
auxiliary_point = getAuxiliaryPoints3(v[4], v[5], v[5].copy().sub(v[1].copy()));
for(int i=0; i<8; i++){
lizard_parts.vertex(auxiliary_point[i].x, auxiliary_point[i].y);
}
// 辺fを描く
lizard_parts.vertex(v[5].x, v[5].y);
auxiliary_point = getAuxiliaryPoints3(v[0], v[5], v[2].copy().sub(v[0].copy()));
for(int i=0; i<8; i++){
lizard_parts.vertex(auxiliary_point[7-i].x, auxiliary_point[7-i].y);
}
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
fill(0,0,0);
// 背中の線1
lizard_parts = createShape();
lizard_parts.beginShape();
auxiliary_point[0] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(11.0/15.0));
auxiliary_point[1] = v[4].copy().add(v[5].copy().sub(v[4].copy()).mult(3.5/7.0)).add(v[2].copy().sub(v[4].copy()).mult(3/12.0));
auxiliary_point[2] = v[1].copy().add(v[2].copy().sub(v[1].copy()).mult(3.5/7.0)).sub(v[2].copy().sub(v[4].copy()).mult(1/12.0));
auxiliary_point[3] = v[2].copy().add(v[3].copy().sub(v[2].copy()).mult(2.5/7.0)).add(v[2].copy().sub(v[0].copy()).mult(2.5/12.0));
auxiliary_point[4] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(11.2/15.0));
lizard_parts.vertex(auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.bezierVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[2].x, auxiliary_point[2].y, auxiliary_point[3].x, auxiliary_point[3].y);
lizard_parts.bezierVertex(auxiliary_point[2].x, auxiliary_point[2].y, auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[4].x, auxiliary_point[4].y);
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
// 背中の線2
lizard_parts = createShape();
lizard_parts.beginShape();
auxiliary_point[0] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(9.0/15.0));
auxiliary_point[1] = v[4].copy().add(v[5].copy().sub(v[4].copy()).mult(3.5/7.0)).add(v[2].copy().sub(v[4].copy()).mult(3/12.0));
auxiliary_point[2] = v[1].copy().add(v[2].copy().sub(v[1].copy()).mult(3.5/7.0));//.sub(v[2].copy().sub(v[4].copy()).mult(1/12.0));
auxiliary_point[3] = v[2].copy().add(v[3].copy().sub(v[2].copy()).mult(2.5/7.0)).add(v[2].copy().sub(v[0].copy()).mult(2.5/12.0));
auxiliary_point[4] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(8.8/15.0));
lizard_parts.vertex(auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.bezierVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[2].x, auxiliary_point[2].y, auxiliary_point[3].x, auxiliary_point[3].y);
lizard_parts.bezierVertex(auxiliary_point[2].x, auxiliary_point[2].y, auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[4].x, auxiliary_point[4].y);
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
// 頭部の線1
lizard_parts = createShape();
lizard_parts.beginShape();
auxiliary_point[0] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(11.0/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(1.5/12.0));
auxiliary_point[1] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(11.0/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
auxiliary_point[2] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(14.0/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
auxiliary_point[3] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(11.0/15.0)).add(v[2].copy().sub(v[0].copy()).mult(1.0/12.0));
auxiliary_point[4] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(14.1/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
auxiliary_point[5] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(11.1/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
lizard_parts.vertex(auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.bezierVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[2].x, auxiliary_point[2].y, auxiliary_point[3].x, auxiliary_point[3].y);
lizard_parts.bezierVertex(auxiliary_point[4].x, auxiliary_point[4].y, auxiliary_point[5].x, auxiliary_point[5].y, auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
// 頭部の線2
lizard_parts = createShape();
lizard_parts.beginShape();
auxiliary_point[0] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(9.0/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(1.5/12.0));
auxiliary_point[1] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(9.0/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
auxiliary_point[2] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(7.0/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
auxiliary_point[3] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(9.0/15.0)).add(v[2].copy().sub(v[0].copy()).mult(1.0/12.0));
auxiliary_point[4] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(7.1/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
auxiliary_point[5] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(9.1/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
lizard_parts.vertex(auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.bezierVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[2].x, auxiliary_point[2].y, auxiliary_point[3].x, auxiliary_point[3].y);
lizard_parts.bezierVertex(auxiliary_point[4].x, auxiliary_point[4].y, auxiliary_point[5].x, auxiliary_point[5].y, auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
// 眼の線1
lizard_parts = createShape();
lizard_parts.beginShape();
auxiliary_point[0] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(12.0/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
auxiliary_point[1] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(14.0/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
auxiliary_point[2] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(12.0/15.0)).add(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
auxiliary_point[3] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(14.1/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
lizard_parts.vertex(auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.quadraticVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[2].x, auxiliary_point[2].y);
lizard_parts.quadraticVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
// 眼の線2
lizard_parts = createShape();
lizard_parts.beginShape();
auxiliary_point[0] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(8.0/15.0)).sub(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
auxiliary_point[1] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(7.0/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
auxiliary_point[2] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(8.0/15.0)).add(v[2].copy().sub(v[0].copy()).mult(0.5/12.0));
auxiliary_point[3] = v[0].copy().add(v[5].copy().sub(v[0].copy()).mult(7.1/15.0));//.sub(v[2].copy().sub(v[0].copy()).mult(2.0/12.0));
lizard_parts.vertex(auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.quadraticVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[2].x, auxiliary_point[2].y);
lizard_parts.quadraticVertex(auxiliary_point[1].x, auxiliary_point[1].y, auxiliary_point[0].x, auxiliary_point[0].y);
lizard_parts.endShape(CLOSE);
lizard.addChild(lizard_parts);
// 指の線1-1
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(110.0));
auxiliary_point[0] = v[0].copy().add(v[1].copy().sub(v[0].copy()).mult(1.0/7.0)).add(v[4].copy().sub(v[0].copy()).mult(1.0/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線1-2
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(110.0));
auxiliary_point[0] = v[0].copy().add(v[1].copy().sub(v[0].copy()).mult(0.7/7.0)).add(v[4].copy().sub(v[0].copy()).mult(0.7/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線2-1
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(110.0));
auxiliary_point[0] = v[4].copy().add(v[5].copy().sub(v[4].copy()).mult(0.0/7.0)).add(v[5].copy().sub(v[1].copy()).mult(3.2/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線2-2
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(110.0));
auxiliary_point[0] = v[4].copy().add(v[5].copy().sub(v[4].copy()).mult(0.5/7.0)).add(v[5].copy().sub(v[1].copy()).mult(3.5/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線3-1
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(270.0));
auxiliary_point[0] = v[1].copy().add(v[2].copy().sub(v[1].copy()).mult(1.5/7.0)).add(v[1].copy().sub(v[5].copy()).mult(3.3/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線3-2
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(270.0));
auxiliary_point[0] = v[1].copy().add(v[2].copy().sub(v[1].copy()).mult(1.9/7.0)).add(v[1].copy().sub(v[5].copy()).mult(3.3/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線4-1
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(220.0));
auxiliary_point[0] = v[3].copy().add(v[4].copy().sub(v[3].copy()).mult(0.5/7.0)).add(v[4].copy().sub(v[0].copy()).mult(1.0/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
// 指の線4-2
lizard_parts = getLizardFinger();
lizard_parts.rotate(radians(220.0));
auxiliary_point[0] = v[3].copy().add(v[4].copy().sub(v[3].copy()).mult(0.5/7.0)).add(v[4].copy().sub(v[0].copy()).mult(0.5/12.0));
lizard_parts.translate(auxiliary_point[0].x, auxiliary_point[0].y);
lizard.addChild(lizard_parts);
return lizard;
}
// 辺aと辺bを変形するために必要な補助点を算出する関数
PVector[] getAuxiliaryPoints1(PVector start, PVector end, PVector dir){
PVector[] auxiliary_point = new PVector[8];
auxiliary_point[0] = getAuxiliaryPoint(start, end, 2.0/7.0, 1.0/12.0*sqrt(3.0));
auxiliary_point[1] = getAuxiliaryPoint(start, end, 4.0/7.0, 0.0);
auxiliary_point[2] = getAuxiliaryPoint(start, end, 4.0/7.0, -3.0/12.0*sqrt(3.0));
auxiliary_point[3] = getAuxiliaryPoint(start, end, 6.0/7.0, -3.6/12.0*sqrt(3.0));
auxiliary_point[4] = getAuxiliaryPoint(start, end, 5.6/7.0, -1.8/12.0*sqrt(3.0));
return auxiliary_point;
}
// 辺cと辺dを変形するために必要な補助点を算出する関数
PVector[] getAuxiliaryPoints2(PVector start, PVector end, PVector dir){
PVector[] auxiliary_point = new PVector[8];
auxiliary_point[0] = getAuxiliaryPoint(start, end, 1.5/7.0, 3.0/12.0*sqrt(3.0));
auxiliary_point[1] = getAuxiliaryPoint(start, end, 4.0/7.0, 3.0/12.0*sqrt(3.0));
auxiliary_point[2] = getAuxiliaryPoint(start, end, 3.0/7.0, 2.0/12.0*sqrt(3.0));
auxiliary_point[3] = getAuxiliaryPoint(start, end, 2.0/7.0, 0.0);
auxiliary_point[4] = getAuxiliaryPoint(start, end, 3.0/7.0, -1.0/12.0*sqrt(3.0));
auxiliary_point[5] = getAuxiliaryPoint(start, end, 5.0/7.0, -1.0/12.0*sqrt(3.0));
auxiliary_point[6] = getAuxiliaryPoint(start, end, 6.0/7.0, -2.0/12.0*sqrt(3.0));
auxiliary_point[7] = getAuxiliaryPoint(start, end, 6.8/7.0, -2.0/12.0*sqrt(3.0));
return auxiliary_point;
}
// 辺eと辺fを変形するために必要な補助点を算出する関数
PVector[] getAuxiliaryPoints3(PVector start, PVector end, PVector dir){
PVector[] auxiliary_point = new PVector[8];
auxiliary_point[0] = getAuxiliaryPoint(start, end, 0.5/7.0, 2.0/12.0*sqrt(3.0));
auxiliary_point[1] = getAuxiliaryPoint(start, end, -0.5/7.0, 2.0/12.0*sqrt(3.0));
auxiliary_point[2] = getAuxiliaryPoint(start, end, -0.5/7.0, 3.0/12.0*sqrt(3.0));
auxiliary_point[3] = getAuxiliaryPoint(start, end, 1.5/7.0, 4.0/12.0*sqrt(3.0));
auxiliary_point[4] = getAuxiliaryPoint(start, end, 2.5/7.0, 3.5/12.0*sqrt(3.0));
auxiliary_point[5] = getAuxiliaryPoint(start, end, 2.0/7.0, 0.0);
auxiliary_point[6] = getAuxiliaryPoint(start, end, 3.0/7.0, -3.0/12.0*sqrt(3.0));
auxiliary_point[7] = getAuxiliaryPoint(start, end, 4.0/7.0, -3.0/12.0*sqrt(3.0));
return auxiliary_point;
}
// 辺を変形するために必要な補助点を算出する関数
PVector getAuxiliaryPoint(
PVector start,
PVector end,
float parallel_size,
float vertical_size
){
PVector dir_parallel = end.copy().sub(start.copy());
PVector dir_vertical = new PVector(-dir_parallel.y, dir_parallel.x);
PVector auxiliary_point = start.copy().add(dir_parallel.copy().mult(parallel_size)).add(dir_vertical.copy().mult(vertical_size));
return auxiliary_point;
}
// 指の線を描く関数
PShape getLizardFinger(){
PShape lizard_finger = createShape();
lizard_finger.beginShape(QUAD);
lizard_finger.vertex(0.0, 0.0);
lizard_finger.vertex(0.0, 1.0);
lizard_finger.vertex(10.0, 1.0);
lizard_finger.vertex(10.0, 0.0);
lizard_finger.endShape(CLOSE);
return lizard_finger;
}
これを思いつくって、結構すごいですよね。エッシャーはどういう発想しているんでしょうか。なかなかこういう発想はできませんね。