スケーラブルアート論
|  (→開発環境) |  (→文字列を画面中央に表示) | ||
| (1人の利用者による、間の16版が非表示) | |||
| 25行: | 25行: | ||
| *「The Nature of Code: Simulating Natural Systems with Processing」 | *「The Nature of Code: Simulating Natural Systems with Processing」 | ||
| **英語PDF版は無料でダウンロードできます。https://wtf.tw/ref/shiffman.pdf | **英語PDF版は無料でダウンロードできます。https://wtf.tw/ref/shiffman.pdf | ||
| − | **日本語PDF版を右のサイトで購入できます。https:// | + | **日本語PDF版を右のサイトで購入できます。https://wgn-obs.shop-pro.jp/?pid=144269527 | 
| **ソースコードがGithubで公開されています。https://github.com/nature-of-code/ | **ソースコードがGithubで公開されています。https://github.com/nature-of-code/ | ||
| *Processingのチュートリアル [https://processing.org/tutorials/ Processing Tutorial] | *Processingのチュートリアル [https://processing.org/tutorials/ Processing Tutorial] | ||
| 174行: | 174行: | ||
| } | } | ||
| </pre> | </pre> | ||
| + | |||
| === カメラ入力 === | === カメラ入力 === | ||
| 219行: | 220行: | ||
| } | } | ||
| </pre> | </pre> | ||
| − | |||
| ;差分から動きを検出 | ;差分から動きを検出 | ||
| 286行: | 286行: | ||
| } | } | ||
| </pre> | </pre> | ||
| + | |||
| === 引力と加速度 === | === 引力と加速度 === | ||
| 365行: | 366行: | ||
| } | } | ||
| </pre> | </pre> | ||
| + | |||
| === 画像ファイルの結合 === | === 画像ファイルの結合 === | ||
| 428行: | 430行: | ||
| } | } | ||
| </pre> | </pre> | ||
| + | |||
| ===クラスオブジェクトのソート=== | ===クラスオブジェクトのソート=== | ||
| 450行: | 453行: | ||
|    int level; |    int level; | ||
| }</pre> | }</pre> | ||
| + | |||
| + | |||
| + | ===3D回転=== | ||
| + | ;X軸を中心にフェルマーらせんを回転 | ||
| + | * '''rotateX()'''を使う (https://processing.org/reference/rotateX_.html) | ||
| + | * '''size()'''に'''P3D'''を指定 | ||
| + | <pre> | ||
| + | |||
| + | int itr = 0;  //描画の繰り返し回数 | ||
| + | float scalar = 5; //拡大倍率 | ||
| + | float rotation; | ||
| + | float r =0; | ||
| + | void setup() { | ||
| + |   size(500, 500, P3D); // 3D座標を指定する | ||
| + |   background(255);  //背景を白くする | ||
| + |   rotation = (1 + sqrt(5)) / 2; | ||
| + | } | ||
| + | void draw() { | ||
| + |   translate(width / 2, height / 2);  //描画ウィンドウの中心に移動 | ||
| + |   rotateX(r); | ||
| + |   fill(0);  //点を黒く塗る | ||
| + | |||
| + |   float theta = 2 * PI * itr * rotation; //回転角 | ||
| + |   PVector v = PVector.fromAngle(theta); | ||
| + |   v.mult(scalar * sqrt(itr)); | ||
| + |   ellipse(v.x, v.y, scalar, scalar); //点を描画 | ||
| + | |||
| + |   itr++; | ||
| + |   r = r + 0.01; | ||
| + | } | ||
| + | </pre> | ||
| + | |||
| + | ;Y軸を中心にフェルマーらせんを回転 | ||
| + | * '''rotateY()'''を使う (https://processing.org/reference/rotateY_.html) | ||
| + | * '''size()'''に'''P3D'''を指定 | ||
| + | <pre> | ||
| + | |||
| + | int itr = 0;  //描画の繰り返し回数 | ||
| + | float scalar = 5; //拡大倍率 | ||
| + | float rotation; | ||
| + | float r =0; | ||
| + | void setup() { | ||
| + |   size(500, 500, P3D); // 3D座標を指定する | ||
| + |   background(255);  //背景を白くする | ||
| + |   rotation = (1 + sqrt(5)) / 2; | ||
| + | } | ||
| + | void draw() { | ||
| + |   translate(width / 2, height / 2);  //描画ウィンドウの中心に移動 | ||
| + |   rotateY(r); | ||
| + |   fill(0);  //点を黒く塗る | ||
| + | |||
| + |   float theta = 2 * PI * itr * rotation; //回転角 | ||
| + |   PVector v = PVector.fromAngle(theta); | ||
| + |   v.mult(scalar * sqrt(itr)); | ||
| + |   ellipse(v.x, v.y, scalar, scalar); //点を描画 | ||
| + | |||
| + |   itr++; | ||
| + |   r = r + 0.01; | ||
| + | } | ||
| + | </pre> | ||
| + | |||
| + | ===文字列を画面中央に表示=== | ||
| + | ;ウィンドウ画面の中央にテキストを配置する方法 | ||
| + | |||
| + | 文字の表示について、CENTERやwidth/2を使って、横方法は中央に配置することができます。 | ||
| + | しかし、縦方向については、英語のアルファベットの場合、文字によって、高さや縦の位置が異なるので、 | ||
| + | CENTERやheight/2では、うまくいきません。 | ||
| + | |||
| + | フォントはベースラインを基準に作成されています。ベースラインから上の長さはAscent、ベースラインから下の長さはDescentと言います。 | ||
| + | 例えば、小文字のj,p などはベースラインから下にはみ出ていますし、小文字のk,lなどは縦に長く、iは短めです。 | ||
| + | テキストの高さは、Ascent+Descentになります。 | ||
| + | |||
| + | ref: https://github.com/alexheretic/ab-glyph/issues/6 | ||
| + | |||
| + | これを計算して、縦の位置を指定する必要があります。 | ||
| + | 例えば、以下のようにすると、ウィンドウの中央に配置できます。 | ||
| + | <pre> | ||
| + | void setup() { | ||
| + |   size(400, 400); | ||
| + |   textSize(30); | ||
| + |   textAlign(CENTER,TOP); | ||
| + |   background(0); | ||
| + | } | ||
| + | void draw(){ | ||
| + |   float ascent = textAscent(); | ||
| + |   float descent = textDescent(); | ||
| + |   float textHeight = ascent + descent; | ||
| + |   int textPosY = int(height-textHeight)/2; | ||
| + |   text("pijlk,hello world", width/2, textPosY); | ||
| + | } | ||
| + | </pre> | ||
| + | |||
| + | ===プログラム出力を動画にエクスポート=== | ||
| + | Processingで実行したプログラムの出力を動画にするには、 | ||
| + | 一旦、saveFrame()関数で、コマ送りの静止画を保存して | ||
| + | その後、MovieMakerで静止画をつなげて動画にします。 | ||
| + | |||
| + | 基本的には、draw()関数の最後にsaveFrame()関数を記述してプログラムを実行した後に、 | ||
| + | Processingのツールメニューから、ムービーメーカーを使って、書き出せばよいです。 | ||
| + | その際、コマ送りの静止画の数が多くなりすぎないように、frameRateを適度に設定するとよいでしょう。 | ||
| + | なお、frameRateのデフォルトは60です。 | ||
| + | |||
| + | <pre> | ||
| + | void setup(){ | ||
| + |   size(500,500); | ||
| + |   frameRate(30); | ||
| + | } | ||
| + | int x = 0; | ||
| + | void draw(){ | ||
| + |   if (x < 100) { | ||
| + |     line(x, 0, x, 100); | ||
| + |     x = x + 1; | ||
| + |   } else { | ||
| + |     noLoop(); | ||
| + |   } | ||
| + |   // Saves each frame as line000001.png, line000002.png, etc. | ||
| + |   saveFrame("line######.png"); | ||
| + | } | ||
| + | </pre> | ||
| == リンク == | == リンク == | ||
2024年4月27日 (土) 07:10時点における最新版
| 目次 | 
[編集] 概要
- 前提スキル
- 一年生の時にメディアプログラミング演習Iを履修し、基本的なProcessingのプログラミングができること。
- 不安な人は、以下の野口先生のメディアプログラミング演習Iのサイトで復習してください。https://r-dimension.xsrv.jp/classes_j/category/processing/
 
- 個人のPC(Windows、MacOSのどちらでもよい)に自分でProcessingの環境を構築できることが望ましい。
- 授業概要
- インタラクティブアートは芸術を基盤として科学や工学を統合する新しい領域で、プログラミングなどのIT技術によって実現されます。プログラミングによって制作プロセスをアルゴリズム化した作品は、拡張性が高く(スケーラブル)、多様性を持たせることが容易です。これが、スケーラブルアートです。
- この授業では、その中でも、生物に見られる生成的(ジェネラティブ)な特徴をアートに応用したジェネラティブアートに関連する分野を扱います。その中には、人工生命、フラクタル、オートマトン、遺伝的アルゴリズム、ニューラルネットワークなどといったものが含まれます。
- プログラミングを使用して、スケーラブルな特徴を持つ作品を作成します。
- 使用ソフト
- Processingを使用します。https://processing.org/
- 到達目標
- スケーラブルアートについて理解し、応用例を作成できる。
- 生物の特徴と生物的なシステムについて理解する。
- Processingを使ってジェネラティブアートのプログラミングができる。
- 成績評価
- 確認テスト、課題、まとめテストで、100点満点で評価します。
- 参考資料
- 「数学から創るジェネラティブアート ―Processingで学ぶかたちのデザイン」(Generative Art with Math)
- 「The Nature of Code: Simulating Natural Systems with Processing」
- 英語PDF版は無料でダウンロードできます。https://wtf.tw/ref/shiffman.pdf
- 日本語PDF版を右のサイトで購入できます。https://wgn-obs.shop-pro.jp/?pid=144269527
- ソースコードがGithubで公開されています。https://github.com/nature-of-code/
 
- Processingのチュートリアル Processing Tutorial
[編集] 開発環境
開発環境については、以下を参照してください。
- https://processing.org/
-  p5.js http://p5js.org
- ドットインストールp5.js入門 http://dotinstall.com/lessons/basic_p5js
- Generative Design with p5.js http://www.generative-gestaltung.de/
 
- Generative Art with Math https://www.openprocessing.org/user/57914
- Generative Design
- Reference https://processing.org/reference
[編集] 生物と情報とアート
-  生物とは? 生物の特徴とは?
- 例:小石と貝殻
- 「生物と無生物の違いは何か?」説明してみよう。
 
- ゲノムのDNAマップ NCBI Genome Map Viewer
- ヒト一人を再生するのに必要な情報量は?
- 自己相似性、フラクタル
- 雪の結晶(0:25)
- 樹木(1:17)
- オウム貝(1:30)
 
- らせん、渦巻き状パターン
- 黄金角とフィボナッチ数列(3:00)
- ジェネラティブアート
- 自律性
- 予測不可能性
- パラメータ変形
- 偶発性
- 自己相似性
- 再帰性
- 対称性
- 周期性
- 双対性
- 抽象化と具体化
[編集] 数学と力学の基礎
Nature of Code Chapter 1 Vector Git Processing
[編集] ベクトル
「Nature of Code」第1章の「1.1-1.6」pdf
- ベクトルを復習するための動画:高校数学Bベクトルの定義・成分
[編集] 位置・速度・加速度
「Nature of Code」第1章の「1.7-1.10」pdf
- 動くボールの位置、速度、加速度はベクトルとして表すことができます。
-  速度は、位置の変化の割合、すなわち「次の位置=現在位置+速度」
- 秒速10m/sのボールの1秒後の位置=現在位置+10
 
- 加速度は、速度の変化の割合、すなわち「次の速度=現在速度+加速度」
- 自然落下運動の加速度は、重力加速度といい、9.8m/s2(秒の2乗)
- したがって、自然落下するボールの1秒後の速度=現在速度+9.8
 
- 重力加速度 国土地理院 「重力を知る」https://www.gsi.go.jp/buturisokuchi/grageo_gravity.html
[編集] トポロジー
- パックマン型2次元世界は、3次元ではトーラス(ドーナツ型)
- https://wakara.co.jp/mathlog/20200204
[編集] 数学アート
[編集] 矩形分割
[編集] フィボナッチ数列
[編集] らせん
[編集] 整数の合同
[編集] コラッツ予想
[編集] 人工生命(ALife)
[編集] セルオートマトン
[編集] ラングトンのアリ
[編集] レイノルズのボイド
[編集] 反応拡散系
[編集] フラクタル
[編集] 人工知能(AI)
[編集] Genetic Algorithm
[編集] Neural Networks
[編集] Processing Samples
[編集] 音に反応する円
minimライブラリーをインストールする。
/**
* Circles responding Sound Level
*/
import ddf.minim.spi.*;
import ddf.minim.signals.*;
import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.ugens.*;
import ddf.minim.effects.*;
Minim minim;
AudioInput in;
void setup(){
  size(500, 500);
  minim = new Minim(this);
  in = minim.getLineIn(Minim.STEREO, 512); 
  background(0);
}
void draw(){
  colorMode(RGB, 255);
  fill(0, 150);
  rect(-1, -1, width, height);
  colorMode(HSB, 360, 100, 100);
  float brightness = 50 + map(in.mix.level(), 0, 0.5, 0, 50);
  float hue = map(in.mix.level(), 0, 0.7, 0, 360);
  fill(hue, 100, brightness);
  float radious = 50 + map(in.mix.level(), 0, 0.5, 0, 100);
  int x = 250;
  int y = 250;
  ellipse( x, y, radious *2, radious * 2);
}
void stop(){
  in.close();
  minim.stop();
  super.stop();
}
[編集] カメラ入力
Video|GStreamer-based video library for Processingライブラリをインストール。
- 使用可能なカメラのリスト出力
import processing.video.*;
void setup(){
  size(320, 240);
  
  String[] cameras = Capture.list();
  
  for(int i=0; i<cameras.length; i++){
    println("[" + i + "] " + cameras[i]);
  }
}
- カメラ画像の出力
import processing.video.*;
Capture cam;
void setup(){
  size(640, 480);
  
  String[] cameras = Capture.list();
  for(int i=0; i<cameras.length; i++){
    println("[" + i + "] " + cameras[i]);
  }
  
  cam = new Capture(this, cameras[1]);
  cam.start();  
}
void draw(){
  if(cam.available() == true){
    cam.read();
  }
  
  image(cam, 0, 0);
}
- 差分から動きを検出
import processing.video.*;
 
int numPixels;
int[] previousFrame;
int noiseFilter = 50;
Capture video;
 
void setup() {
  size(640, 480);
 
  video = new Capture(this, width, height, 30);
  video.start();
 
  numPixels = video.width * video.height;
  previousFrame = new int[numPixels];
  loadPixels();
}
void draw() {
  if (video.available()) {
    video.read();
    video.loadPixels();
 
    int movementSum = 0;
    for (int i = 0; i < numPixels; i++) {
      color currColor = video.pixels[i];
      color prevColor = previousFrame[i];
 
      //R, G, B
      int currR = (currColor >> 16) & 0xFF;
      int currG = (currColor >> 8) & 0xFF;
      int currB = currColor & 0xFF;
 
      //
      int prevR = (prevColor >> 16) & 0xFF;
      int prevG = (prevColor >> 8) & 0xFF;
      int prevB = prevColor & 0xFF;
 
      //
      int diffR = abs(currR - prevR);
      int diffG = abs(currG - prevG);
      int diffB = abs(currB - prevB);
 
      //noiseFilter
      if (diffR + diffG + diffB > noiseFilter) {
        movementSum ++;
        pixels[i] = color(currR, currG, currB);
        //
        //pixels[i] = 0xFF000000 | (currR << 16) | (currG << 8) | currB;
      } else {
        pixels[i] = color(0);
      }
 
      //
      previousFrame[i] = currColor;
    }
 
    updatePixels();    //
    println(movementSum);    //
  }
}
[編集] 引力と加速度
「Nature of Code」第1章の「1.10 Interactivity with Acceleration」(p57)より。(一部改変)pdf
- 粒子はマウスポインタに引力で引き付けられれ、軌跡を残しながらマウスに近づく。
- 引力は、距離が近いほど、強くなる(反比例)。
- 速度には上限(topspeed)があり、引力と粒子の運動量(速度)が釣り合うと、マウスのまわりを回る惑星のような動きになる。
- Moverクラスの作成
- ベクトルPVectorを使い、location(位置)、velocity(速度)、acceleration(加速度)定義し、力学運動を記述している。
- 速度は、現在速度に加速度を加算 velocity.add(acceleration);
- 位置は、現在位置に速を加算 location.add(velocity);
 
Mover[] movers = new Mover[1000];//An array of objects
void setup() {
  size(1000, 1000);
  smooth();
  background(0);
  for (int i = 0; i < movers.length; i++) {
    movers[i] = new Mover();// Initialize each object in the array.
  }
}
void draw() {
//  background(0);
  fill(0,40);
  rect(0,0,width,height);
  for (int i = 0; i < movers.length; i++) {
    //Calling functions on all the objects in the array
    movers[i].update();
    movers[i].checkEdges();
    movers[i].display();
  }
}
class Mover {
  PVector location;
  PVector velocity;
  PVector acceleration;
  float topspeed;
  Mover() {
    location = new PVector(random(width), random(height));
    velocity = new PVector(0, 0);
    topspeed = 4.5;
  }
  void update() {
    //Our algorithm for calculating acceleration:
    //Find the vector pointing towards the mouse.
    PVector mouse = new PVector(mouseX, mouseY);
    PVector dir = PVector.sub(mouse, location);
    float magn = dir.mag();
    dir.normalize();// Normalize.
    dir.mult(13 / magn );
//    dir.mult(0.5);// Scale.
    acceleration = dir;// Set to acceleration.
    //Motion 101! Velocity changes by acceleration. Location changes by velocity.
    velocity.add(acceleration);
    velocity.limit(topspeed);
    location.add(velocity);
  }
  void display() {// Display the Mover
//    stroke(0);
    noStroke();
    fill(250,255,100);
    ellipse(location.x, location.y, 10, 10);
  }
  void checkEdges() {// What to do at the edges
    if (location.x > width) {
      location.x = 0;
    } else if (location.x < 0) {
      location.x = width;
    }
    if (location.y > height) {
      location.y = 0;
    } else if (location.y < 0) {
      location.y = height;
    }
  }
}
[編集] 画像ファイルの結合
imagesフォルダにある画像_i000.pngから_i007.png(640*480)を縦2列、横4行に並べて結合する。
| 000 | 004 | 
| 001 | 005 | 
| 002 | 006 | 
| 003 | 007 | 
String folderName = "images";
String filePrefix = "_i";
int imageWidth = 640;
int imageHeight = 480;
int margin = 0;
int imageNumX = 2;
int imageNumY = 4;
int imageNum = imageNumX * imageNumY;
// X*Y
int canvasWidth = imageNumX * imageWidth;
int canvasHeight = imageNumY * imageHeight;
PImage images[] = new PImage[imageNum];
void setup() {
  for (int i = 0; i < imageNum; i++) {
    images[i] = loadImage(folderName + "/" + filePrefix + nf(i, 3) + ".png");
  }
  surface.setSize(canvasWidth, canvasHeight);
  noLoop();
}
void draw() {
  background(255);
  for (int i = 0; i < imageNumX; i++) {
    for(int j = 0; j < imageNumY; j++){
      image(images[i*imageNumY + j], imageWidth * i, imageHeight * j, imageWidth, imageHeight);
    }
  }
  save(folderName + "_combine.png");
  exit();
}
[編集] 配列のシャッフル
int[] nN = {0,1,2,3,4,5,6}; 
for(int i=(nN.length - 1); i>0; --i) {
  int j = (int)random(i+1);
  int tmp = nN[i];
  nN[i] = nN[j];
  nN[j] = tmp;
}
[編集] クラスオブジェクトのソート
- バブルソート
rectsをメンバlevelの値の小さい順にソートする。
Kurasu[] rects = new kurasu[100];
for(int j=0; j < rects.length - 1; j++){
  for(int i=0; i < rects.length - 1; i++){
    if(rects[i].level > rects[i+1].level){
      Kurasu tmp = rects[i+1];
      rects[i+1] = rects[i];
      rects[i] = tmp;
    }
  }
}
class Kurasu{
  int index;
  int level;
}
[編集] 3D回転
- X軸を中心にフェルマーらせんを回転
- rotateX()を使う (https://processing.org/reference/rotateX_.html)
- size()にP3Dを指定
int itr = 0;  //描画の繰り返し回数
float scalar = 5; //拡大倍率
float rotation;
float r =0;
void setup() {
  size(500, 500, P3D); // 3D座標を指定する
  background(255);  //背景を白くする
  rotation = (1 + sqrt(5)) / 2;
}
void draw() {
  translate(width / 2, height / 2);  //描画ウィンドウの中心に移動
  rotateX(r);
  fill(0);  //点を黒く塗る
  float theta = 2 * PI * itr * rotation; //回転角
  PVector v = PVector.fromAngle(theta);
  v.mult(scalar * sqrt(itr));
  ellipse(v.x, v.y, scalar, scalar); //点を描画
   
  itr++;
  r = r + 0.01;
}
- Y軸を中心にフェルマーらせんを回転
- rotateY()を使う (https://processing.org/reference/rotateY_.html)
- size()にP3Dを指定
int itr = 0;  //描画の繰り返し回数
float scalar = 5; //拡大倍率
float rotation;
float r =0;
void setup() {
  size(500, 500, P3D); // 3D座標を指定する
  background(255);  //背景を白くする
  rotation = (1 + sqrt(5)) / 2;
}
void draw() {
  translate(width / 2, height / 2);  //描画ウィンドウの中心に移動
  rotateY(r);
  fill(0);  //点を黒く塗る
  float theta = 2 * PI * itr * rotation; //回転角
  PVector v = PVector.fromAngle(theta);
  v.mult(scalar * sqrt(itr));
  ellipse(v.x, v.y, scalar, scalar); //点を描画
   
  itr++;
  r = r + 0.01;
}
[編集] 文字列を画面中央に表示
- ウィンドウ画面の中央にテキストを配置する方法
文字の表示について、CENTERやwidth/2を使って、横方法は中央に配置することができます。 しかし、縦方向については、英語のアルファベットの場合、文字によって、高さや縦の位置が異なるので、 CENTERやheight/2では、うまくいきません。
フォントはベースラインを基準に作成されています。ベースラインから上の長さはAscent、ベースラインから下の長さはDescentと言います。 例えば、小文字のj,p などはベースラインから下にはみ出ていますし、小文字のk,lなどは縦に長く、iは短めです。 テキストの高さは、Ascent+Descentになります。
ref: https://github.com/alexheretic/ab-glyph/issues/6
これを計算して、縦の位置を指定する必要があります。 例えば、以下のようにすると、ウィンドウの中央に配置できます。
void setup() {
  size(400, 400);
  textSize(30);
  textAlign(CENTER,TOP);
  background(0);
}
void draw(){
  float ascent = textAscent();
  float descent = textDescent();
  float textHeight = ascent + descent;
  int textPosY = int(height-textHeight)/2;
  text("pijlk,hello world", width/2, textPosY);
}
[編集] プログラム出力を動画にエクスポート
Processingで実行したプログラムの出力を動画にするには、 一旦、saveFrame()関数で、コマ送りの静止画を保存して その後、MovieMakerで静止画をつなげて動画にします。
基本的には、draw()関数の最後にsaveFrame()関数を記述してプログラムを実行した後に、 Processingのツールメニューから、ムービーメーカーを使って、書き出せばよいです。 その際、コマ送りの静止画の数が多くなりすぎないように、frameRateを適度に設定するとよいでしょう。 なお、frameRateのデフォルトは60です。
void setup(){
  size(500,500);
  frameRate(30);
}
int x = 0;
void draw(){
  if (x < 100) {
    line(x, 0, x, 100);
    x = x + 1;
  } else {
    noLoop();
  }
  // Saves each frame as line000001.png, line000002.png, etc.
  saveFrame("line######.png");
}
[編集] リンク
素数のグラフィック http://www.datapointed.net/visualizations/math/factorization/animated-diagrams/?infinity
[編集] Contents
- Scalable art, Generative art, Mathematical art, Artificial Intelligence, Artificial Life, Complext sysytem
- openFrameworks C++ / Xcode MacOSX
- Logic circuit
- 完全情報ゲーム:チェッカー、オセロ、チェス、将棋、囲碁
- Cell auttomaton
- Conway's game of life
- Wire world
- Random walk
- Langton's ant
- Boid
- Box2D
- Fractal, Self-similar
- Recursive call
- Complex square
- Mandelbrot
- Neural network
- Genetic algorithm
- Code, Chyper, Encript
- Space X
- Robotics
- Expert system
- Fourier transform, spectrum
- Fibonacci number
- Belousov-Zhabotinsky reaction
- Gray-Scott model
- Turing pattern
