日常の進捗

主に自分のための,行為とその習慣化の記録

Mod:Coding Challenge #13: Reaction Diffusion Algorithm in p5.js

https://d26dzxoao6i3hh.cloudfront.net/items/2f200j2j0g1E133Z2E36/animation.gif

www.openprocessing.org

反応拡散系のアルゴリズム。グレイ・スコット方程式を使う。使う、とか言ってるけど分かってるわけではない。数式化されてるアルゴリズムをプログラムに組み込む訓練だと思ってやってる。以前やった迷路ジェネレータもそう。

動画見ていくと、液体Aと液体Bが混ざって反応しながらカンバス上を拡散してくようなイメージっぽい。loadPixelsとupdatePixelsはメタボールなんかでも使ってるけど、ウィンドウのピクセル操作をするのに使う。拡散なのでピクセルごとに周囲の8ピクセルへ液体の濃度に影響を与えながら反応してく。for(int x = -1; x < width -1; x++)みたいな書き方はこういう場合に条件分けを避ける賢い書き方だと思った。液体AとBの割合をCellというクラスに持たせて、それをもとに白から黒の色をピクセルごとに描画している。

書き換えた部分はテキストを元に反応するセルを作る、一定間隔でsaveFrameするところ。GIFアニ書き出しのライブラリを使っても良いかもしれない。カンバスサイズが大きいので重たい。実行してしばらくほっとくと文字通り、じわじわくる。

コード

Cell[][] grid_current;
Cell[][] grid_prev;

int num = 1000;

int offset = 10;
float dA = 1.0;
float dB = 0.2;
float feed = 0.055;
float k = 0.062;

PGraphics pg;
Boolean isSave = false;

// setup関数 : 初回1度だけ実行される
void setup() {
  size(960, 540); // ウィンドウサイズを960px,540pxに
  colorMode(HSB, 360, 100, 100); // HSBでの色指定にする
  pgInit();
  init();
}

void pgInit() {
  pg = createGraphics(width, height);
  pg.beginDraw();
  pg.colorMode(HSB, 360, 100, 100);
  pg.background(0, 0, 100);

  pg.stroke(0, 0, 0);
  pg.strokeWeight(20);
  pg.fill(0, 0, 0);
  pg.textSize(150);
  pg.textLeading(120);
  pg.textAlign(CENTER, CENTER);
  pg.text("COMPUTER\nCLUB", width/2, height/2);
  //pg.rectMode(CENTER);
  //pg.ellipse(width/2, height/2, 300, 300);
  pg.endDraw();
}

void init() {

  grid_current = new Cell[width][height];
  grid_prev = new Cell[width][height];

  for (int j = 0; j < height; j++) {
    for (int i = 0; i < width; i++) {
      float a = 1;
      float b = 0;
      grid_current[i][j] = new Cell(a, b);
      grid_prev[i][j] = new Cell(a, b);
    }
  }
  int n = 0;
  while (n < num) {
    int startX = int(random(offset, width-offset));
    int startY = int(random(offset, height-offset));
    color c = pg.get(startX, startY);
    if (brightness(c) < 50) {
      for (int j = startY; j < startY+offset/2; j++) {
        for (int i = startX; i < startX+offset/2; i++) {
          float a = 1;
          float b = 1;
          grid_current[i][j] = new Cell(a, b);
          grid_prev[i][j] = new Cell(a, b);
        }
      }
      n++;
    }
  }
  for (int j = 1; j < height -1; j+= 10) {
    for (int i = 1; i < width -1; i+= 10) {
      if (random(1) > 0.995) {
        float a = 1;
        float b = 1;
        grid_current[i][j] = new Cell(a, b);
        grid_prev[i][j] = new Cell(a, b);
      }
    }
  }
}

// draw関数 : setup関数実行後繰り返し実行される
void draw() {
  for (int i= 0; i < 1; i++) {
    update();
    swap();
  }
  loadPixels();
  for (int j = 1; j < height -1; j++) {
    for (int i = 1; i < width -1; i++) {
      Cell c = grid_current[i][j];
      float a = c.a;
      float b = c.b;
      int pos = i + j * width;
      pixels[pos] = color(0, 0, (a-b)*100);
    }
  }
  updatePixels();
  if (isSave && frameCount%50 == 0) {
    save();
  }
}

void update() {
  for (int j = 1; j < height -1; j++) {
    for (int i = 1; i < width -1; i++) {
      Cell c_prev = grid_prev[i][j];
      Cell c_current = grid_current[i][j];

      float a = c_prev.a;
      float b = c_prev.b;

      float rate1 = 0.12;
      float rate2 = (1 - rate1*4)/4;

      float laplaceA = 0;
      laplaceA += a * -1;
      laplaceA += grid_prev[i-1][j].a * rate1;
      laplaceA += grid_prev[i+1][j].a * rate1;
      laplaceA += grid_prev[i][j-1].a * rate1;
      laplaceA += grid_prev[i][j+1].a * rate1;
      laplaceA += grid_prev[i-1][j-1].a * rate2;
      laplaceA += grid_prev[i-1][j+1].a * rate2;
      laplaceA += grid_prev[i+1][j-1].a * rate2;
      laplaceA += grid_prev[i+1][j+1].a * rate2;

      float laplaceB = 0;
      laplaceB += b * -1;
      laplaceB += grid_prev[i-1][j].b * rate1;
      laplaceB += grid_prev[i+1][j].b * rate1;
      laplaceB += grid_prev[i][j-1].b * rate1;
      laplaceB += grid_prev[i][j+1].b * rate1;
      laplaceB += grid_prev[i-1][j-1].b * rate2;
      laplaceB += grid_prev[i-1][j+1].b * rate2;
      laplaceB += grid_prev[i+1][j-1].b * rate2;
      laplaceB += grid_prev[i+1][j+1].b * rate2;

      c_current.a = a +(dA * laplaceA - a * b * b + feed * (1-a)) * 1;
      c_current.b = b +(dB * laplaceB + a * b * b - (k+feed) * b) * 1;

      c_current.a = constrain(c_current.a, 0, 1);
      c_current.b = constrain(c_current.b, 0, 1);
    }
  }
}

void swap() {
  Cell[][] grid_temp = grid_prev;
  grid_prev = grid_current;
  grid_current = grid_temp;
}

void save() {
  String filename = nf(year(), 2) + nf(month(), 2) + nf(day(), 2) +"-"+ nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2) + "-" + frameCount;
  saveFrame(filename+".png");
}

void mousePressed() {
  save();
  init();
}

class Cell {
  float a;
  float b;
  Cell(float _a, float _b) {
    a = _a;
    b = _b;
  }
}

リファレンス