lecture:design_with_prototyping:数理シミュレーション

数理シミュレーション

ここまでの内容を経て,プログラム(スケッチ)を通じて視覚的な形状を作り出すことを, 再構成をキーワードに行ってきました.結果として,このような手法は何かをデザインする 手前における,シミュレーションやプロトタイピングとしての機能があることがわかります. 例えばモンドリアンのスケッチでは,グリッドやカラーリングの様々なバリエーションを自動で生成し,自分で気に入る結果を簡単に探索することができます.手を使って描画するよりも非常に高速になります.

観察をベースにアルゴリズムを作り出すことをここまで繰り返し練習してきました。すでにある形状やデザインを見つけた場合は非常に有用な手法である一方、これら観察から、経験的に「このような手続きを踏んだらどうだろうか」といった思考が生まれてくることがあります。このページでは、これまで出力が先にあって、それを観察から描き出すところから、もう少し深く掘り下げて、出力がなくても(観察対象がなくとも)、「こんなやり方をしたらどのような絵を描くことができるのか」といった概念的(アルゴリズミック)なアプローチで視覚効果を生成してみたいと思います。

糸掛け数楽アートとは,シンプルなルールに則って円形に配置されたピンに糸を掛けることで,様々な形状を作り出す作品の 名称です.

糸掛けのシンプルな例として,糸を掛けるピンの番号(円の各頂点)を $ P_{2n-1}, P_{2n}, ただし n \in \mathcal{N} $ とした場合, $$ P_{2n-1} = n \\ P_{2n} = 2n \\ $$ を $P_n$ が円を一周するまで続け,通った頂点を線分で結ぶ.

これがアルゴリズムになります.実際に糸掛けをする場合は,円を60分割した各頂点を使いますが, 今回はプログラム上わかりやすく,360/5 = 72 頂点に分割して考えます.以上のアルゴリズムを プログラムに記述すると下記の通りです.

sample01.pde
void setup() {
  size(500, 500);
}
void draw() {
  int step = 2;
  background(255);
 
  float r = 0.9*width/2;
 
  noFill();
  stroke(24, 48, 151, 150);
  beginShape(LINES);
  int i, j;
  for ( i = 0, j = 0; i < 360; i=i+5, j=j+5*step ) {
    vertex(width/2 + r*cos(2*3.14*(i/360.0)), height/2 + r*sin(2*3.14*(i/360.0)));
    vertex(width/2 + r*cos(2*3.14*(j/360.0)), height/2 + r*sin(2*3.14*(j/360.0)));
  }
  endShape(CLOSE);
}
 
void keyPressed()
{
  save("output.png");
}

beginShape(LINES)は糸掛けの意味からすると、厳密にはbeginShape()としたほうがよいのですが、 こちらで記述すると透明度が再現されない問題がありました。原因はまだわかっていないのですが、みなさんも お気をつけください。

上記のプログラムでは、数列的なアルゴリズムでした。一つ進む点、2つ進む点を線でつなげた結果、ユニークな模様が描けるものでした。次のアルゴリズムは、画素情報をベースとして、自身の画素とその周辺画素との関係から自身の画素情報を更新するアルゴリズムです。これは古くから存在するGAME OF LIFE(https://ja.wikipedia.org/wiki/ライフゲーム)と呼ばれるアルゴリズムになります。一般にセル・オートマトンなどとも呼ばれています。https://ja.wikipedia.org/wiki/セル・オートマトン

GAME OF LIFEとはセル・オートマトン(格子上セルそれぞれに同一のルールを与えるアルゴリズム)の一種になります。詳しくはwikipediaを参照してください。 ルールは下記の通りです。

  • 誕生
    • 死んでいるセルに隣接する生きたセルがちょうど3つあれば、次の世代が誕生する。
  • 生存
    • 生きているセルに隣接する生きたセルが2つか3つならば、次の世代でも生存する。
  • 過疎
    • 生きているセルに隣接する生きたセルが1つ以下ならば、過疎により死滅する。
  • 過密
    • 生きているセルに隣接する生きたセルが4つ以上ならば、過密により死滅する。

では実際にプログラムを記述してみます。下記コードChallengeのウェブサイトにてダニエルがテンションアゲアゲでライブコードしてくれるので 一緒にやってみましょう。

int columns = 25; int rows = 25;

int[][] current = new int[columns][rows]; int[][] next = new int[columns][rows];

void keyPressed() {

for (int y = 0; y < rows; y++) {
  for (int x = 0; x < columns; x++) {
    // Initialize each cell with a 0 or 1.
    current[y][x] = int(random(2));
  }
}

} void setup() {

size(500, 500);
frameRate(5);
for (int y = 0; y < rows; y++) {
  for (int x = 0; x < columns; x++) {
    // Initialize each cell with a 0 or 1.
    current[y][x] = int(random(2));
  }
}

} void draw() {

background(0);
println(frameRate);
int x, y;
for ( y = 1; y < rows-1; y++ ) {
  for ( x = 1; x < columns-1; x++ ) {
    int neighbors = 0;
    for (int i = -1; i <= 1; i++) {
      for (int j = -1; j <= 1; j++) {
        neighbors += current[y+i][x+j];
      }
    }
    neighbors -= current[y][x];
    if ( (current[y][x] == 1) && (neighbors < 2 )) {
      next[y][x] = 0;
    } else if ( (current[y][x] == 1)&&(neighbors > 3 )) {
      next[y][x] = 0;
    } else if ( (current[y][x] ==0 )&& (neighbors == 3 )) {
      next[y][x] = 1;
    } else {
      next[y][x] = current[y][x];
    }
  }
}
current = next;

beginShape(POINTS); noStroke(); for ( y = 0; y < rows; y+=1 ) { for ( x = 0; x < columns; x+=1 ) { fill(255*current[y][x]); rect(x*20,y*20,18,18); } } endShape(); }

先程のゲームオブライフのアルゴリズムを応用し、今度は周辺画素情報の和を取るアルゴリズムに変更してみます。 まず、ある画素の点を$P_{x,y} $(ただしx,yはそれぞれの座標値)とします。1 frame前の画素情報を $P'_{x,y}$ と した場合、$P_{x,y}$を次のように定義する。 \[ P_{x,y} = \frac{P'_{x+1,y} + P'_{x-1, y} + P'_{x, y+1} + P'_{x, y-1}}{2} - P_{x,y} \]

その後、$P, P'$の画素情報を入れ替える。波紋を広げる箇所には、$P'_{x,y}$の任意の座標に500等の適当に大きい数値を 代入すればよい。このアルゴリズムに関しては GAME OF LIFE同様にコーディングChallengeにて、学習教材が提供されているので、 その動画と一緒に記述することができます。授業では馬場と一緒に0からコードを書いてみましょう。

なお、上記画像を生成するサンプルプログラムは下記の通り。

ripple.pde
int cols;
int rows;
 
float[][] current;
float[][] previous;
 
float damping = 0.99;
 
void setup()
{
  size(600, 400);
  cols = width;
  rows = height;
  current = new float[cols][rows];
  previous = new float[cols][rows];
}
 
void mouseMoved()
{
  previous[mouseX][mouseY] = 2055;
}
 
void keyPressed()
{
  save("output.png");
}
 
void draw()
{
 
  background(0);
 
  int x = (int)random(0,(int)width);
  int y = (int)random(0,(int)height);
  previous[x][y] = random(2000);
 
  loadPixels();
  for ( int  i = 1; i < cols-1; i++ ) {
    for ( int j = 1; j < rows-1; j++ ) {
      current[i][j] = (
        previous[i-1][j]+
        previous[i+1][j]+
        previous[i][j-1]+
        previous[i][j+1])/2.0 - current[i][j];
 
      current[i][j] = current[i][j]*damping;
      int index = i + j * cols;
      pixels[index] = color(100-current[i][j]);
    }
  }
 
  updatePixels();
 
  float[][] temp = previous;
  previous = current;
  current = temp;
 
}

すべてのオブジェクトに対して共通のルールを適応することで、簡単な群衆シミュレーションを作成することができます。 下記アルゴリズムはBoidsアルゴリズムと呼ばれ、鳥や魚の群れなど様々なところで利用されています。詳細は下記リンクから。

  • lecture/design_with_prototyping/数理シミュレーション.txt
  • 最終更新: 2019/12/06 12:57
  • by baba