この記事では書籍「世界一美しい錯視アート」に掲載されている「火事場の蛇の回転」という作品をProcessingを使って再現してみます。
火事場の蛇の回転
今回作成した錯視アート「火事場の蛇の回転」です。
書籍では「ディスプレーでは赤→黒(物理的には暗い赤紫)→青(物理的には青紫)→明るいピンク(物理的には明るい赤)→赤の方向に円盤は回転して見える。印刷物を暗いところで見ると、その逆方向に動いて見える。」と解説されています。どうでしょうか。
描き方のポイント
円盤の描き方
この作品は基本的には1つの円盤を描くことができれば、あとはそれを並べていくことで作成することができます。この円盤の描き方については、ほとんど別記事「冷凍蛇の回転」と同じですので、そちらをご覧ください。ただし、「冷凍蛇の回転」ではチェック柄を描いた後、菱形の列を描きましたが、今回の「火事場の蛇の回転」では、楕円の列となります。楕円は短軸がチェック柄の1つの形の横幅より少し小さめに取ります。
蛇の舌を描く
あと「冷凍蛇の回転」と同様、蛇の舌を描いています。赤色の背景に赤色の蛇の舌を描いているので、分かりにくいかもしれませんが、よく見てみると分かります。
プログラムコード
今回作成した錯視アート「火事場の蛇の回転」のプログラムコードを載せておきます。
void setup() {
size(1000, 1000);
background(255,0,0);
noStroke();
translate(width/2, height/2);
float line_len = width/8.0; // 円盤の半径
int lines_num = 40; // 扇形の個数
// 4×3に並んだ円盤を描画
for(int i=0; i<3; i++){
for(int j=0; j<4; j++){
resetMatrix();
translate(width/4.0+i*width/4.0, width/8.0+j*width/4.0);
drawDisk(line_len, lines_num, i+j);
if( j==0 || j==2 ){ // 1行目と3行目の円盤に蛇の舌を付与する
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);
}
if( j==1 ){ // 2行目の円盤に蛇の舌を付与する
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==3 ){ // 4行目の円盤に蛇の舌を付与する
translate(line_len * cos(radians(90)+radians(45)*(1-2*(i%2))+(2*(i%2)-1)*radians(360/lines_num)/2.0),
line_len * sin(radians(90)+radians(45)*(1-2*(i%2))+(2*(i%2)-1)*radians(360/lines_num)/2.0) );
rotate(radians(90)+radians(45)*(1-2*(i%2))+(2*(i%2)-1)*radians(360/lines_num)/2.0);
drawSnakeTongue(line_len);
}
}
}
// 3×2に並んだ円盤を4×3に並んだ円盤に重ねて描画
for(int i=0; i<2; i++){
for(int j=0; j<3; j++){
resetMatrix();
translate(3.0*width/8.0+i*width/4.0, width/4.0+j*width/4.0);
drawDisk(line_len, lines_num, i+j+1);
}
}
}
// 円盤を描く関数
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++){
drawEllipses(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(242,156,159); // ローズピンク
} else {
fill(175,0,175); // 紫
}
// バームクーヘンのような形状を描いていく
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);
x = x - segment_len; // 収束点からの長さを更新
segment_len = segment_len * ratio; // 線分の長さを更新
i++;
}
}
// 楕円の列を描いていく関数
void drawEllipses(
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 center_x, center_y; // 楕円の中心位置
float ellipse_width, ellipse_height; // 楕円の幅と高さ
int i = 0;
while(i<100){
center_x = x - segment_len/2.0;
center_y = 0.0;
ellipse_width = segment_len;
ellipse_height = center_x * tan(theta/3.0) * 2.0;
noStroke();
// 楕円の色を指定
if ((i+number) % 2 == 0){
fill(112,108,170);
} else {
fill(255,0,255);
}
// 楕円を描く
ellipse(center_x, center_y, ellipse_width, ellipse_height);
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(240,0,0);
beginShape();
vertex(x1, y1);
quadraticVertex(cx, cy, x2, y2);
endShape();
beginShape();
vertex(x1, y1);
quadraticVertex(cx, cy, x2, -y2);
endShape();
}