ジュリア集合を勘違い

2023年5月6日土曜日

p5.js Processing 作例

t f B! P L
タイトル絵

ジュリア集合を勘違いして、全然違うものを作っていました。何と呼んだらよいのかわからない物ですが、せっかく出来たので、p5.js のコードと共にご紹介します。

後で、ちゃんとした(たぶん)ジュリア集合のコードもご提示いたします。


👉 Read this article in English.

 

ジュリア集合の計算式

ジュリア集合とは、「複素平面上のある近傍で反復関数が非正規族となる点の集合」とのこと。

Wikipedia:ジュリア集合

その意味するところをさっぱり理解できないまま、クリエイティブ・コーディングで図として描いてみようと調べてみたところ、下記の計算式が見つかりました。

Xn+1 = Xn * Xn - Yn * Yn + a
Yn+1 = 2 * Xn * Yn + b
参考サイト:ジュリア集合(Julia set)

なるほど、なるほど。漸化式で (x, y) を計算してそれをプロットするんですね?

 

なんか雰囲気ちがわない?

ということで、a = -0.3, b = -0.63 のパラメータで計算した点をプロットしてできたのがこれです。

大量のプロットされた点が不思議な形を描いている

/** 
 * ジュリア集合を勘違い
 * 漸化式の (x, y) をそのままプロット
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.6.0
 * created 2023.05.06
 */

  const w = 640;
  const h = w;
  const a = -0.3;
  const b = -0.623;

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

    translate(w * 0.8, h * 0.8);
    background(240);
    noFill();

    let xn = 0;
    let yn = 0;
    for (let i = 0; i < 10000; i++) {
      let x = pow(xn, 2) - pow(yn, 2) + a;
      let y = 2 * xn * yn + b;
      point(x * w, y * h);
      xn = x;
      yn = y;
    }

  }


「あれ?よく見るジュリア集合の画像となんか雰囲気違うな?」とこの時点では思っていました。

 

脱線したまま進んでみる

描画の仕方を変えてもそれっぽくならないし、他のよく知られた a, b のパラメータ値を入れてみても形になりません。

「ジュリア集合とは複素平面上のある近傍で反復関数が非正規族となる点の集合である」という説明を全く理解しないまま作るからこういうことになるんです。

しかし、私のクリエイティブ・コーディングでは、ジュリア集合を作ることより面白いものを描くコードを書いて楽しむことの方が優先なので、このまま進めてみます。

試しに計算結果の (x, y) を線で結んでみたら、思いがけず面白い(気色悪い)ものができました。

気味の悪いモアレが描かれている

/** 
 * ジュリア集合を勘違い
 * 漸化式の (x, y) を曲線で結ぶ
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.6.0
 * created 2023.05.06
 */

  const w = 640;
  const h = w;
  const a = -0.3;
  const b = -0.6225;
 
  function setup() {
    createCanvas(w, h);
    noLoop();
 
    translate(w * 0.8, h * 0.8);
    background(240);
    noFill();
 
    let xn = 0;
    let yn = 0;
    beginShape();
    for (let i = 0; i < 5000; i++) {
      let x = pow(xn, 2) - pow(yn, 2) + a;
      let y = 2 * xn * yn + b;
      if (i % 3 == 0) {
        curveVertex(x * w, y * h);
      }
      xn = x;
      yn = y;
    }
    endShape();
 
  }


コードのこの箇所で描画に使う (x, y) を間引いているのがポイントです。

if (i % 3 == 0) {


 

まともなジュリア集合の描画

まともなジュリア集合の計算と描画は、漸化式なのはその通りなんだけど、その結果の (x, y) をプロットするのではなく、(x, y) がある条件となるまでの計算回数を描画に使うというものでした。


ちゃんとしたジュリア集合のコード例を紹介しておきます。描画に noise() を用いて変化を出しています。彩度や明度は固定にしています。ここを工夫するともっと面白い描画になると思いますよ。

p5.js版


/** 
 * ちゃんとしたジュリア集合
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * p5.js 1.6.0
 * created 2023.05.06
 */

  const w = 640;
  const h = w;
  const calcMax = 100;
  const paramA = -0.7015;
  const paramB = -0.3845;
  const zoom = 1.5;

  function setup() {
    createCanvas(w, h);
    colorMode(HSB, 360, 100, 100, 100);
    noLoop();

    background(0.0, 0.0, 90.0, 100.0);
    noStroke();

    for (let x = 0; x < w; x++) {
      for (let y = 0; y < h; y++) {
        // calc
        let x0 = map(x, 0, w, -zoom, zoom);
        let y0 = map(y, 0, h, -zoom, zoom);
        let diverCnt = 0;
        for (let calcCnt = 0; calcCnt < calcMax; calcCnt++) {
          let x1 = pow(x0, 2) - pow(y0, 2) + paramA;
          let y1 = 2 * x0 * y0 + paramB;
          if (pow(x1, 2) + pow(y1, 2) > 4.0) {
            break;
          }
          x0 = x1;
          y0 = y1;
          diverCnt++;
        }

        // draw
        let nParam = map(diverCnt, 0, calcMax, 0.0, 1.0);
        let hueVal = noise(100.0, nParam * 50.0) * 240.0;
        let satVal = 90.0;
        let briVal = 30.0;
        let alpVal = noise(200.0, nParam * 20.0) * 100.0;
        fill(hueVal % 360.0, satVal, briVal, alpVal);
        rect(x, y, 1.0, 1.0);
      }
    }
  }

Processing版


/** 
 * ちゃんとしたジュリア集合
 * 
 * @author @deconbatch
 * @version 0.1
 * @license CC0
 * Processing 3.5.3
 * created 2023.05.06
 */

public void setup() {
  size(640, 640);
  colorMode(HSB, 360.0, 100.0, 100.0, 100.0);
  smooth();
  noLoop();

  int calcMax = 100;
  float paramA = -0.7015;
  float paramB = -0.3845;
  float zoom = 1.5;

  background(0.0, 0.0, 90.0, 100.0);
  noStroke();

  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      // calc
      float x0 = map(x, 0, width, -zoom, zoom);
      float y0 = map(y, 0, height, -zoom, zoom);
      int diverCnt = 0;
      for (int calcCnt = 0; calcCnt < calcMax; calcCnt++) {
        float x1 = pow(x0, 2) - pow(y0, 2) + paramA;
        float y1 = 2 * x0 * y0 + paramB;
        if (pow(x1, 2) + pow(y1, 2) > 4.0) {
          break;
        }
        x0 = x1;
        y0 = y1;
        diverCnt++;
      }

      // draw
      float nParam = map(diverCnt, 0, calcMax, 0.0, 1.0);
      float hueVal = noise(100.0, nParam * 50.0) * 240.0;
      float satVal = 90.0;
      float briVal = 30.0;
      float alpVal = noise(200.0, nParam * 20.0) * 100.0;
      fill(hueVal % 360.0, satVal, briVal, alpVal);
      rect(x, y, 1.0, 1.0);
    }
  }

}

 

あとがき

間違ったコードでも何かを作れてしまうのはクリエイティブ・コーディングの醍醐味かもしれません。

ジュリア集合を作ってて、ジュリア集合と違うのが出来ても良い、脱線して面白そうな方へふらふら歩いていっても良い、そんなクリエイティブ・コーディングの楽しさを味わえた今回でした。

 

QooQ