1. ホーム
  2. 錯視アート
  3. 火事場の蛇の回転

この記事では書籍「世界一美しい錯視アート」に掲載されている「火事場の蛇の回転」という作品を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();
}

コメントを残す