Processing中毒者の嘔吐物

ProcessingやらopenFrameworksやら色んなプログラミングについて吐きます

Processingユーザーなら壁紙自作しようぜ? #4

Processing Advent Calendar 2015 : ATNDの参加記事です。

この記事ではクラスの作り方やnoise()の使い方、球体の座標の計算方法などは解説していません。
クラスの使い方とnoise()の使い方はP5 Code Schoolで、球体の座標の計算方法はこのブログの記事で先に読んでおいてください。
P5 Code School:Chapter15「クラス基礎 Part1」
P5 Code School:Chapter12「乱数」
Processingで三角関数を使って球体を作る - Processing中毒者の嘔吐物

Processingで壁紙自作記事第4弾になります。
今回作るのはコチラ↓
f:id:P5Aholic:20151129212707j:plain
f:id:P5Aholic:20151129212733j:plain
f:id:P5Aholic:20151129212808j:plain
f:id:P5Aholic:20151129212829j:plain
最初の2つの画像と後の2つの画像は違うスケッチで作られています。
まずは最初のスケッチの解説から。
ソースはコチラ↓

int numLines = 8; // Lineオブジェクトの数
Line[] lines = new Line[numLines]; // Lineオブジェクト配列

void setup() {
  fullScreen(P3D);
  // RetinaとかのHigh-Resディスプレイ用の処理
  pixelDensity(displayDensity());
  // アンチエイリアスの質を上げる
  smooth(8);
  // HSBカラーモード
  colorMode(HSB, 360, 100, 100, 100);
  // 加算合成で発光してるっぽくみせる
  blendMode(ADD);
  initWindow();
}

void initWindow() {
  background(0, 0, 0);
  for (int i = 0; i < numLines; i++) {
    lines[i] = new Line();
  }
}

void draw() {
  translate(width/2, height/2, 0);
  
  // 線の描画と更新
  for (int i = 0; i < numLines; i++) {
    for (int j = 0; j < 2; j++) {
      lines[i].update();
      lines[i].display();
    }
  }
}

void mousePressed() {
  if (mouseButton == RIGHT) {
    saveFrame("images/img-####.jpg");
  } else {
    initWindow();
  }
}

class Line {
  float x1, y1, z1;                // 座標1
  float x2, y2, z2;                // 座標2
  float sx, sy, sz;                // 線の始点
  float sxNoise, syNoise, szNoise; // sx, sy, szのノイズ
  float radius, radiusNoise;       // 半径とノイズ
  float radianS, radianT;          // 球体の計算用の角度
  float sNoise, tNoise;            // radiasSとradianTのノイズ
  int hue;                         // 色相

  Line() {
    // 初期化
    sxNoise = random(10);
    syNoise = random(10);
    szNoise = random(10);
    sNoise = random(10);
    tNoise = random(10);
    hue = int(random(360));
    radius = height / 2.0 - 50.0;
  }

  // 更新を行うメソッド
  void update() {
    // noise()で始点を変動させる
    sxNoise += 0.01;
    syNoise += 0.01;
    szNoise += 0.01;
    sx = noise(sxNoise) * 100 - 50;
    sy = noise(syNoise) * 100 - 50;
    sz = noise(szNoise) * 100 - 50;
    
    // noise()で角度を変動させる
    sNoise += 0.003;
    tNoise += 0.003;
    radianS = noise(sNoise) * 2 * TWO_PI - TWO_PI;
    radianT = noise(tNoise) * 2 * TWO_PI - TWO_PI;
    
    // 球体の面上にある座標の計算
    x1 = radius * sin(radianS) * cos(radianT);
    y1 = radius * sin(radianS) * sin(radianT);
    z1 = radius * cos(radianS);
    x2 = - x1;
    y2 = - y1;
    z2 = - z1;
  }

  // 描画を行うメソッド
  void display() {
    stroke(hue, 80, 8, 50);
    strokeWeight(1);
    line(sx, sy, sz, x1, y1, z1);
    line(sx, sy, sz, x2, y2, z2);

    stroke(0, 0, 100, 25);
    strokeWeight(1);
    point(x1, y1, z1);
    point(x2, y2, z2);
  }
}

このプログラムはジェネラティブ・アートに載っているwave clockというプログラムをアレンジしたものです。

[普及版]ジェネラティブ・アート―Processingによる実践ガイド

[普及版]ジェネラティブ・アート―Processingによる実践ガイド

  • 作者: マット・ピアソン,Matt Pearson,久保田晃弘,沖啓介
  • 出版社/メーカー: ビー・エヌ・エヌ新社
  • 発売日: 2014/11/21
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログ (1件) を見る
まず8本の線を用意します。1つ1つの線は球体の面上にある点からその反対にある点まで線を描きます。線の座標をパーリンノイズで動かし、その軌跡を毎フレームbackground()で消さずに残します。加算合成で描画しているので、線が重なりやすい球体の中心ほど明るくなり、全体として発光しているような球体が表現できます。

線の1本1本はLineクラスのオブジェクトになります。update()メソッドで座標を動かし、display()メソッドで線を描画しています。noise()で変動させる値としてsx, sy, szがありますが、これは線の始点(start point)のことです。sx, sy, szを使わずにline(x1, y1, z1, x2, y2, z2)と書くと、線は常に画面中心を通る線になります。line(sx, sy, sz, x1, y1, z1)、line(sx, sy, sz, x2, y2, z2)と書くと画面中心から(sx, sy, sz)ずれた点を通るような線になります。

描画速度を調整するために、draw関数内でupdate()メソッドとdisplay()メソッドを1本の線につき2回ずつ呼び出しています。

もう一つのスケッチのソースがコチラ↓

int numLines = 6;
Line[] lines = new Line[numLines];

void setup() {
  fullScreen(P3D);
  pixelDensity(displayDensity());
  smooth(8);
  colorMode(HSB, 360, 100, 100, 100);
  blendMode(ADD);
  initWindow();
}

void initWindow() {
  background(0, 0, 0);
  for (int i = 0; i < numLines; i++) {
    lines[i] = new Line();
  }
}

void draw() {
  translate(width/2, height/2, 0);
  
  for (int i = 0; i < numLines; i++) {
    for (int j = 0; j < 2; j++) {
      lines[i].update();
      lines[i].display();
    }
  }
}

void mousePressed() {
  if (mouseButton == RIGHT) {
    saveFrame("images/img-####.jpg");
  } else {
    initWindow();
  }
}

class Line {
  float x1, y1, z1;                // 座標1
  float x2, y2, z2;                // 座標2
  float targetX, targetY, targetZ; // 座標1,2の到達目標点
  float sx, sy, sz;
  float radius;
  float radianS, radianT;
  int hue;

  Line() {
    hue = int(random(360));
    radius = height / 2.0;
    setTarget();
  }
  
  // targetX, Y, Zの設定
  void setTarget() {
    // targetX, Y, Zを球体の面上のランダムな座標に設定
    sx = random(-50, 50);
    sy = random(-50, 50);
    sz = random(-50, 50);

    radianS = random(-TWO_PI, TWO_PI);
    radianT = random(-TWO_PI, TWO_PI);

    targetX = radius * sin(radianS) * cos(radianT);
    targetY = radius * sin(radianS) * sin(radianT);
    targetZ = radius * cos(radianS);
  }

  void update() {
    // 一定確率でtargetを設定
    if (random(100) < 5) {
      setTarget();
    }
    
    // targetに向かって座標をイージングで動かす
    x1 += (targetX - x1) * 0.04;
    y1 += (targetY - y1) * 0.04;
    z1 += (targetZ - z1) * 0.04;
    x2 = - x1;
    y2 = - y1;
    z2 = - z1;
  }

  void display() {
    stroke(hue, 80, 8, 50);
    strokeWeight(1);
    line(sx, sy, sz, x1, y1, z1);
    line(sx, sy, sz, x2, y2, z2);

    stroke(0, 0, 100, 25);
    strokeWeight(1);
    point(x1, y1, z1);
    point(x2, y2, z2);
  }
}

基本的な処理は一つ目のスケッチと同じですが、線の座標の動かし方が違います。毎フレーム5%の確率でsetTarget()メソッドを呼び出して新しい球体の面上の座標を設定し、そこに向かってイージングで座標を動かしています。noise()で動かすよりかは処理が簡単です。