Creative coding on Turing pattern

Turing pattern made with the 'Processing'

It's an article that describes the creative coding with the 'p5.js' and the 'Processing' to create the unique and mysterious Turing pattern.

It contains the points to create interesting patterns, the simple example code, and the application example code. You can surely make your original work with these.

👉 この記事は日本語でも読めます。

 

What is the Turing pattern?

Simply speaking, the Turing pattern is the pattern that looks like the surface of tropical fish. And the Turing pattern is generated with the reaction-diffusion equations.

The pattern that looks like the surface of tropical fish.

English mathematician Alan Turing researched the reaction-diffusion theory.

Wikipedia : Turing pattern

It needs to do many calculations to draw the Turing pattern. And that is where the computer is at its strongest.

Creative coding on Turing pattern.

 

Basic example code of 'p5.js'.

Here is the basic code of 'p5.js' to draw the Turing pattern.


/*
 * Reaction-Diffusion system by the Gray-Scott Model.
 * basic example.
 * 
 * @author @deconbatch
 * @version 0.1
 * p5.js 1.1.3
 * license CC0
 * created 2022.03.26
 */

const w = 480;
const h = w;
const cSiz = 3;   // cell size
const pCnt = 500; // calculation count

function setup() {
  createCanvas(w, h);
  noLoop();

  const lab = new Labo(cSiz);
  lab.init();
  for (let i = 0; i < pCnt; i++) {
    lab.proceed();
  }
  lab.observe();
}

/*
 * Labo : reaction-diffusion system.
 */
class Labo {

  cellSize;
  matrixW;
  matrixH;
  diffU;
  diffV;
  cells;

  constructor(_cSiz) {
    this.cellSize = _cSiz;
    this.matrixW = floor(width / this.cellSize);
    this.matrixH = floor(height / this.cellSize);
    this.diffU = 0.9;
    this.diffV = 0.1;
    this.cells = new Array();
  }

  /*
   * init : initialize reaction-diffusion system.
   */
  init() {
    for (let x = 0; x < this.matrixW; x++) {
      this.cells[x] = [];
      for (let y = 0; y < this.matrixH; y++) {
        this.cells[x][y] = new Cell(
          map(x, 0.0, this.matrixW, 0.03, 0.12),   // feed
          map(y, 0.0, this.matrixH, 0.045, 0.055), // kill
          1,                         // u
          (random(1) < 0.1) ? 1 : 0  // v
        );
      }
    }
  }

  /*
   * proceed : proceed reaction-diffusion calculation.
   */
  proceed() {

    // calculate Laplacian
    const nD = Array(); // neighbors on diagonal
    const nH = Array(); // neighbors on vertical and horizontal
    for (let x = 0; x < this.matrixW; x++) {
      for (let y = 0; y < this.matrixH; y++) {

        // set neighbors
        nD[0] = this.cells[max(x-1,0)][max(y-1,0)];
        nD[1] = this.cells[max(x-1,0)][min(y+1,this.matrixH-1)];
        nD[2] = this.cells[min(x+1,this.matrixW-1)][max(y-1,0)];
        nD[3] = this.cells[min(x+1,this.matrixW-1)][min(y+1,this.matrixH-1)];
        nH[0] = this.cells[max(x-1,0)][y];
        nH[1] = this.cells[x][max(y-1,0)];
        nH[2] = this.cells[x][min(y+1,this.matrixH-1)];
        nH[3] = this.cells[min(x+1,this.matrixW-1)][y];

        // Laplacian
        let c = this.cells[x][y];
        let sum = 0.0;
        for (let i = 0; i < 4; i++) {
          sum += nD[i].valU * 0.05 + nH[i].valU * 0.2;
        }
        sum -= c.valU;
        c.lapU = sum;

        sum = 0.0;
        for (let i = 0; i < 4; i++) {
          sum += nD[i].valV * 0.05 + nH[i].valV * 0.2;
        }
        sum -= c.valV;
        c.lapV = sum;

      }
    }

    // reaction-diffusion
    for (let x = 0; x < this.matrixW; x++) {
      for (let y = 0; y < this.matrixH; y++) {
        let c = this.cells[x][y];
        let reaction = c.valU * c.valV * c.valV;
        let inflow   = c.feed * (1.0 - c.valU);
        let outflow  = (c.feed + c.kill) * c.valV;
        c.valU = c.valU + this.diffU * c.lapU - reaction + inflow;
        c.valV = c.valV + this.diffV * c.lapV + reaction - outflow;
        c.standardization();
      }
    }
  }

  /*
   * observe : display the result.
   */
  observe() {
    background(0);
    fill(255);
    noStroke();
    for (let x = 0; x < this.matrixW; x++) {
      for (let y = 0; y < this.matrixH; y++) {
        let cx = x * this.cellSize;
        let cy = y * this.cellSize;
        let cs = this.cells[x][y].valU * this.cellSize;
        rect(cx, cy, cs, cs);
      }
    }
  }
}

/*
 * Cell : holds cell informations.
 */
class Cell {

  feed; 
  kill;
  valU;
  valV;
  lapU;
  lapV;

  constructor(_f, _k, _u, _v) {
    this.feed = _f;
    this.kill = _k;
    this.valU = _u;
    this.valV = _v;
    this.lapU = 0;
    this.lapV = 0;
  }

  standardization() {
    this.valU = constrain(this.valU, 0, 1);
    this.valV = constrain(this.valV, 0, 1);
  }

}


Notice : When you try this on OpenProcessing, care about 'loop protection'.

 

This code contains the reaction-diffusion system called the 'Gray-Scott' model. And the pattern changes with the parameters on the reaction-diffusion equation on the XY axis.

Calculation result of reaction-diffusion equation.

 

Brief description of the code.

It calculates the reaction-diffusion equations in 'class Labo'. The calculation scope is 'matrixW x matrixH' area. 'calss Cell' symbolizes the square on the matrix. And 'cell[matrixW][matrixH]' stands for the whole squares in the calculation scope.

'class Labo init()' initializes the whole squares in the calculation scope. 'class Labo proceed()' calculates the reaction-diffusion equations once. The caller controls how many times to call the calculation.

The calculation amount of this code is quite much. So it takes a long time to get a result. The calculation time is proportional to 'pCnt', and it will be four times if 'cSize' was halved.

The parameters below took about one minute in my environment.


const cSiz = 3;   // cell size
const pCnt = 500; // calculation count


The two-dimensional array 'this.cells[x][y]' hold the calculation results. And 'class Labo observe()' draws with it.

Though example code draws monochrome image, you can develop any way you like.

Calculation result of reaction-diffusion equation in color.

 

Creative coding points to create interesting patterns.

Tweak the parameter values.

The looks of the pattern depend on the parameter values of the reaction-diffusion equations.

The example code uses four parameters in the two parts below. It sets 'diffU' and 'diffV' to constant values, and sets 'feed' and 'kill' the values related to the XY axis location.


  // diffU, diffV
  this.diffU = 0.9;
  this.diffV = 0.1;

  // feed, kill
  this.cells[x][y] = new Cell(
    map(x, 0.0, this.matrixW, 0.03, 0.12),   // feed
    map(y, 0.0, this.matrixH, 0.045, 0.055), // kill


For example, setting 'feed' and 'kill' to the 'A' and 'B' related values show characteristic patterns like this.

Some characteristic pattern examples.
Some characteristic pattern examples.
Some characteristic pattern examples.

The example code will create the patterns when the values of 'feed' and 'kill' are in a range like these.

feed : 0.03 - 0.12
kill : 0.045 - 0.055

You can change the values of 'diffU' and 'diffV'. But it will be hard to get some patterns.

 

Plot the seeds.

The example code set 'V' values in the calculation scope to '1' randomly.


  this.cells[x][y] = new Cell(
    (random(1) < 0.1) ? 1 : 0    // v


You can change the calculation result with the way to set the 'V' values.



You can set the 'V' values in 'class Labo init()'. At first, set the whole 'V' values to '0' and set the part of 'V' values to '1' you want to set.


// Example to set circular.
for (let t = 0; t < TWO_PI; t += PI * 0.2) {
  let x = floor(this.matrixW * (0.5 + 0.25 * cos(t)));
  let y = floor(this.matrixW * (0.5 + 0.25 * sin(t)));
  this.cells[x][y].valV = 1;
}

 

Calculation count and the square size.

The calculation count of reaction-diffusion equations and the square size in the calculation scope affect the look of the pattern.


const cSiz = 3;    // cell size
const pCnt = 1000; // calculation count


 

Creative coding application example of the Turing pattern.

These are the creative coding application example based on the points to create the pattern shown above. I'll share these with the CC0 license.

 

The 'p5.js' application example code.

Creative coding application example of the Turing pattern.

Change 'class Labo init()' below.


  /*
   * init : initialize reaction-diffusion system.
   */
  init() {
    const hW = floor(this.matrixW * 0.5);
    const hH = floor(this.matrixH * 0.5);
    for (let x = 0; x < this.matrixW; x++) {
      this.cells[x] = [];
      for (let y = 0; y < this.matrixH; y++) {
        let d = dist(x, y, hW, hH);
        let f = map(sin(TWO_PI * d * 3 / hW), -1, 1, 0.12, 0.03);
        this.cells[x][y] = new Cell(
          f,     // feed
          0.045, // kill
          1,     // u
          0      // v
        );
      }
    }

    for (let t = 0; t < TWO_PI; t += PI * 0.2) {
      for (let r = 0.1; r < 0.4; r += 0.1) {
        let x = floor(this.matrixW * (0.5 + r * cos(t)));
        let y = floor(this.matrixW * (0.5 + r * sin(t)));
        this.cells[x][y].valV = 1;
      }
    }
  }

 

The 'Processing' application example code.

It's the same application example written in the 'Processing'.

<

/**
 * Reaction-Diffusion system by the Gray-Scott Model.
 * application example.
 *
 * @author @deconbatch
 * @version 0.1
 * Processing 3.5.3
 * license CC0
 * created 2022.03.26
 */

void setup() {
  size(480, 480);
  noLoop();

  int cSiz = 2;    // cell size
  int pCnt = 1000; // calculation count
  Labo lab = new Labo(cSiz);

  lab.init();
  for (int i = 0; i < pCnt; i++) {
    lab.proceed();
  }
  lab.observe();
}

/*
 * Labo : reaction-diffusion system.
 */
public class Labo {
  int cellSize;
  int matrixW;
  int matrixH;
  float diffU;
  float diffV;
  Cell[][] cells;

  Labo(int _cSiz) {
    cellSize = _cSiz;
    matrixW =  floor(width / cellSize);
    matrixH = floor(height / cellSize);
    diffU = 0.9;
    diffV = 0.1;
    cells = new Cell[matrixW][matrixH];
  }

  /*
   * init : initialize reaction-diffusion system.
   */
  void init() {
    float hW = matrixW * 0.5;
    float hH = matrixH * 0.5;
    for (int x = 0; x < matrixW; x++) {
      for (int y = 0; y < matrixH; y++) {
        float d = dist(x, y, hW, hH);
        float f = map(sin(TWO_PI * d * 3.0 / hW), -1.0, 1.0, 0.03, 0.12);
        cells[x][y] = new Cell(
                               f,     // feed
                               0.045, // kill
                               1.0,   // u
                               0.0    // v
                               );
      }
    }

    for (float t = 0.0; t < TWO_PI; t += PI * 0.2) {
      for (float r = 0.1; r < 0.4; r += 0.1) {
        int x = floor(matrixW * (0.5 + r * cos(t)));
        int y = floor(matrixW * (0.5 + r * sin(t)));
        cells[x][y].setV(1.0);
      }
    }
  }

  /*
   * proceed : proceed reaction-diffusion calculation.
   */
  void proceed() {
    for (int x = 0; x < matrixW; x++) {
      for (int y = 0; y < matrixH; y++) {

        // neighbors on diagonal
        Cell[] nD = new Cell[4];
        nD[0] = cells[max(x-1,0)][max(y-1,0)];
        nD[1] = cells[max(x-1,0)][min(y+1,matrixH-1)];
        nD[2] = cells[min(x+1,matrixW-1)][max(y-1,0)];
        nD[3] = cells[min(x+1,matrixW-1)][min(y+1,matrixH-1)];

        // neighbors on vertical and horizontal
        Cell[] nH = new Cell[4];
        nH[0] = cells[max(x-1,0)][y];
        nH[1] = cells[x][max(y-1,0)];
        nH[2] = cells[x][min(y+1,matrixH-1)];
        nH[3] = cells[min(x+1,matrixW-1)][y];

        // lapU
        Cell c = cells[x][y];
        float sum = 0.0;
        for (int i = 0; i < 4; i++) {
          sum += nD[i].getU() * 0.05 + nH[i].getU() * 0.2;
        }
        sum -= c.getU();
        c.setLapU(sum);

        // lapV
        sum = 0.0;
        for (int i = 0; i < 4; i++) {
          sum += nD[i].getV() * 0.05 + nH[i].getV() * 0.2;;
        }
        sum -= c.getV();
        c.setLapV(sum);

      }
    }

    // reaction-diffusion
    for (int x = 0; x < matrixW; x++) {
      for (int y = 0; y < matrixH; y++) {
        Cell c = cells[x][y];
        float reaction = c.getU() * c.getV() * c.getV();
        float inflow   = c.getFeed() * (1.0 - c.getU());
        float outflow  = (c.getFeed() + c.getKill()) * c.getV();
        c.setU(c.getU() + diffU * c.getLapU() - reaction + inflow);
        c.setV(c.getV() + diffV * c.getLapV() + reaction - outflow);
        c.standardization();
      }
    }
  }

  /*
   * observe : display the result.
   */
  void observe() {
    background(0);
    fill(255);
    noStroke();
    for (int x = 0; x < matrixW; x++) {
      for (int y = 0; y < matrixH; y++) {
        int cx = x * cellSize;
        int cy = y * cellSize;
        float cs = cells[x][y].getU() * cellSize;
        rect(cx, cy, cs, cs);
      }
    }
  }


}


/**
 * Cell : hold the informations of the cell.
 */
public class Cell {

  private float feed;
  private float kill;
  private float valU;
  private float valV;
  private float lapU;
  private float lapV;

  Cell(float _f, float _k, float _u, float _v) {
    feed = _f;
    kill = _k;
    valU = _u;
    valV = _v;
    lapU = 0.0;
    lapV = 0.0;
  }

  public void setLapU(float _l) {
    lapU = _l;
  }

  public void setLapV(float _l) {
    lapV = _l;
  }

  public void setU(float _u) {
    valU = _u;
  }

  public void setV(float _v) {
    valV = _v;
  }

  public float getFeed() {
    return feed;
  }

  public float getKill() {
    return kill;
  }

  public float getU() {
    return valU;
  }

  public float getV() {
    return valV;
  }

  public float getLapU() {
    return lapU;
  }

  public float getLapV() {
    return lapV;
  }

  public void standardization() {
    valU = constrain(valU, 0.0, 1.0);
    valV = constrain(valV, 0.0, 1.0);
  }

}

 

Next Post Previous Post
No Comment
Add Comment
comment url