この記事では書籍「世界一美しい錯視アート」に掲載されている「冷凍蛇の回転」という作品をProcessingを使って再現してみます。
冷凍蛇の回転
今回作成した錯視アート「冷凍蛇の回転」です。
書籍では「円盤が回転して見える。」と解説されています。確かに、回転して見えます。
描き方のポイント
円盤の描き方
この作品は基本的には1つの円盤を描くことができれば、あとはそれを並べていくことで作成することができます。ここでは、1つの円盤を描く手順を示しておきます。
1.白色と黒色のチェック柄の円盤を描く
まず、白色と黒色のチェック柄の円盤を描いていきます。別記事「縮小していく線分を描く」で解説しているやり方を使ってこのチェック柄の円盤を作ることができます。
2.緑色と青色の菱形の列を描く
次に、緑色と青色の菱形の列を上で作成したチェック柄の円盤に描いていきます。菱形は対角線の短い方がチェック柄の1つの形の横幅より少し小さめに取ります。
蛇の舌を描く
この作品は蛇の舌のような形をした図形が描かれています。この図形はベジエ曲線を利用して描きました。
プログラムコード
今回作成した錯視アート「冷凍蛇の回転」のプログラムコードを載せておきます。
void setup() {
size(1000, 1000);
background(255,255,255);
noStroke();
translate(width/2, height/2);
float line_len = width/8.0; // 円盤の半径
int lines_num = 40; // 扇形の個数
// 3×4に並んだ円盤を描画
for(int i=0; i<4; i++){
for(int j=0; j<3; j++){
resetMatrix();
translate(width/8.0+i*width/4.0, height/8.0+width/8.0+j*width/4.0);
drawDisk(line_len, lines_num, i+j);
if( j==0 ){ // 最初の行の円盤に蛇の舌を付与する
translate(line_len * cos(radians(135)+radians(90)*i+(2*(i%2)-1)*radians(360/lines_num)/2.0),
line_len * sin(radians(135)+radians(90)*i+(2*(i%2)-1)*radians(360/lines_num)/2.0) );
rotate(radians(135)+radians(90)*i+(2*(i%2)-1)*radians(360/lines_num)/2.0);
drawSnakeTongue(line_len);
}
if( j==2 ){ // 最後の行の円盤に蛇の舌を付与する
translate(line_len * cos(radians(225)-radians(90)*i+(2*(i%2)-1)*radians(360/lines_num)/2.0),
line_len * sin(radians(225)-radians(90)*i+(2*(i%2)-1)*radians(360/lines_num)/2.0) );
rotate(radians(225)-radians(90)*i+(2*(i%2)-1)*radians(360/lines_num)/2.0);
drawSnakeTongue(line_len);
}
}
}
// 2×3に並んだ円盤を3×4に並んだ円盤に重ねて描画
for(int i=0; i<3; i++){
for(int j=0; j<2; j++){
resetMatrix();
translate(width/4.0+i*width/4.0, height/8.0+width/4.0+j*width/4.0);
drawDisk(line_len, lines_num, i+j);
}
}
}
// 円盤を描く関数
void drawDisk(
float line_len, // 円盤の半径
int lines_num, // 扇形の個数
int inversion // 奇数のとき、黒と白が入れ替わる
){
float segment_len_initial = line_len/5.0; // 線分の長さの初期値
float ratio = 1.0 - segment_len_initial / line_len; // 線分の縮小比率
float theta_arcs = radians(360.0/lines_num); // 扇形の中心角の大きさ
// 扇形から円盤のバックグラウンドとなる黒と白のチェッカー柄を描画
rotate(theta_arcs/2.0);
for(int k=0; k<lines_num; k++){
drawChecker(line_len, segment_len_initial, ratio, theta_arcs, k, inversion); // 収束点に向かって縮小していく扇型を描画
rotate(theta_arcs);
}
// 黒と白のチェッカー柄の上に菱形の列を描画
rotate(-theta_arcs/2.0);
for(int k=0; k<lines_num; k++){
drawRhombus(line_len, segment_len_initial, ratio, theta_arcs, k); // 収束点に向かって縮小していく菱形を描画
rotate(theta_arcs);
}
}
// 黒と白の縞々の扇形を描画する関数
void drawChecker(
float line_len, // 円盤の半径
float segment_len_initial, // 線分の長さの初期値
float ratio, // 線分の縮小比率
float theta, // 扇形の中心角の角度
int number, // 扇形の番号(奇数のとき、黒と白が入れ替わる)
int inversion // 奇数のとき、黒と白が入れ替わる
){
float x; // 収束点からの長さ
float segment_len; // 線分の長さ
x = line_len;
segment_len = segment_len_initial;
int i = 0;
while(i<100){
noStroke();
// 図形の番号やinversionフラグに合わせて色を指定する
if ((i+number+inversion) % 2 == 0){
fill(0,0,0);
} else {
fill(255,255,255);
}
// バームクーヘンのような形状を描いていく
pushMatrix();
beginShape();
for(int j=0; j<=100; j++){
vertex(x * cos(-theta/2.0+j*theta/100.0), x * sin(-theta/2.0+j*theta/100.0));
}
for(int j=0; j<=100; j++){
vertex((x - segment_len) * cos(theta/2.0-j*theta/100.0), (x - segment_len) * sin(theta/2.0-j*theta/100.0));
}
endShape(CLOSE);
popMatrix();
x = x - segment_len; // 収束点からの長さを更新
segment_len = segment_len * ratio; // 線分の長さを更新
i++;
}
}
// 菱形の列を描いていく関数
void drawRhombus(
float line_len, // 円盤の半径
float segment_len_initial, // 線分の長さの初期値
float ratio, // 線分の縮小比率
float theta, // 扇形の角度
int number // 図形の番号(奇数のとき、菱形の色が入れ替わる)
){
float x; // 収束点からの長さ
float segment_len; // 線分の長さ
x = line_len;
segment_len = segment_len_initial;
float x1, y1, x2, y2, x3, y3, x4, y4;
int i = 0;
while(i<100){
x1 = x;
y1 = 0.0;
x2 = x - segment_len/2.0;
y2 = x2 * tan(theta/3.0);
x3 = x - segment_len;
y3 = 0.0;
x4 = x2;
y4 = -y2;
noStroke();
// 菱形の色を指定
if ((i+number) % 2 == 0){
fill(0,104,183);
} else {
fill(200,217,33);
}
// 菱形を描く
pushMatrix();
beginShape();
vertex(x1, y1);
vertex(x2, y2);
vertex(x3, y3);
vertex(x4, y4);
endShape(CLOSE);
popMatrix();
x = x - segment_len; // 収束点からの長さを更新
segment_len = segment_len * ratio; // 線分の長さを更新
i++;
}
}
// 蛇の舌を描く関数
void drawSnakeTongue(
float line_len // 円盤の半径
){
noFill();
float x1, y1, x2, y2, cx, cy;
x1 = 0.0;
y1 = 0.0;
x2 = line_len/5.0;
y2 = x2/4.0;
cx = x2*4.0/5.0;
cy = 0.0;
strokeWeight(3);
stroke(255,0,0);
beginShape();
vertex(x1, y1);
quadraticVertex(cx, cy, x2, y2);
endShape();
beginShape();
vertex(x1, y1);
quadraticVertex(cx, cy, x2, -y2);
endShape();
}