読者です 読者をやめる 読者になる 読者になる

Processing中毒者の嘔吐物

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

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

Processing

壁紙自作記事第2弾です。
今回作るのはこちら↓
f:id:P5Aholic:20150710203525j:plain
f:id:P5Aholic:20150710203533j:plain
f:id:P5Aholic:20150710203538j:plain
f:id:P5Aholic:20150710203544j:plain

ふつくしぃ・・・

ということで、解説します。以下の記事を読んでおいてください。
Processingで三角関数を使って球体を作る - Processing中毒者の嘔吐物
Processingユーザーなら壁紙自作しようぜ? #1 - Processing中毒者の嘔吐物

前回の記事で作った壁紙とほぼ同じで、違うのは点の配置の方法だけです。ArcクラスとPointクラスを作りました。Arcクラスのインスタンスの集合で球体を表します。ArcクラスがPointクラス配列を内包します。コードがこちら↓

Arc[] arcs;
int numArcs = 100;     // 弧の数
int lengthLimit = 160; // 距離制限
int hueColor;
color bgColor;
boolean save = false;

void setup(){
  size(displayWidth, displayHeight, P3D);
  smooth(32);
  colorMode(HSB, 360, 100, 100, 100);
  blendMode(ADD);
  noLoop();
  reset();
}

void reset(){
  hueColor = int(random(360));
  bgColor = color(hueColor, 80, 15);
  arcs = new Arc[numArcs];
  for(int i = 0; i < numArcs; i++){
    arcs[i] = new Arc();
  }
}

void draw(){
  background(bgColor);
  translate(width/2, height/2, 0);
  // このアングルがカッコイイ
  rotateX(radians(-30));
  rotateZ(radians(30));
  
  // 弧を表示
  for(int i = 0; i < numArcs; i++){
    arcs[i].display();
  }
  
  if(save){
    saveFrame("img/img-"+(int)random(10000)+".jpg");
    save = false;
  }
}

void mousePressed(){
  if(mouseButton == LEFT){
    reset();
    redraw();
  }
  else if(mouseButton == RIGHT){
    save = true;
    redraw();
  }
}

// 一個の弧を表すクラス
class Arc{
  Point[] points; // 点の集合
  float maxRadius = height/2-100; // 最大半径
  
  Arc(){
    // 弧の長さ
    float arcLength = random(HALF_PI, 3*HALF_PI);
    // 球体の座標計算用の値
    float radianS = random(PI);
    float radianT = random(TWO_PI);
    // 半径を100~maxRadiusまでランダムに設定
    float radius = 100 + random( (maxRadius-100)*sin(radianS) );
    // 半径の大きさに応じて点の数を決める
    int n = (int)map(radius, 100, maxRadius, 5, 100);
    points = new Point[n];
    for(int i = 0; i < n; i++){
      points[i] = new Point();
    }
    // 点を弧上に配置する
    for(int i = 0; i < n; i++){
      float randRadianT = random(radianT, radianT+arcLength);
      // 各方向のランダムな拡散値
      float spreadXZ = random(-10, 10);
      float spreadY = random(-10, 10);
      points[i].x = (radius+spreadXZ) * cos(randRadianT);
      points[i].y = maxRadius * cos(radianS) + spreadY;
      points[i].z = (radius+spreadXZ) * sin(randRadianT);
    }
    // 全ての点が他の全ての点に対してlengthLimit以内にある点を保存
    for(int i = 0; i < points.length; i++){
      Point fromP = points[i];
      for(int j = 0; j < points.length; j++){
        if(i == j) continue; // 自分自身は含まない
        Point toP = points[j];
        // 距離を計算
        float dist = dist(fromP.x, fromP.y, fromP.z, toP.x, toP.y, toP.z);
        if(dist < lengthLimit){
          // fromPのnearPsにtoPを追加
          fromP.addNearPoint(toP);
        }
      }
    }
  }
  
  // 近くの点を結んで多角形を描画
  void display(){
    for(int i = 0; i < points.length; i++){
      ArrayList<Point> ps = points[i].nearPs;
      fill(points[i].col);
      beginShape();
      for(Point p : ps){
        vertex(p.x, p.y, p.z);
      }
      endShape(CLOSE);
    }
  }
}

// 一個の点を表すクラス
class Point{
  float x, y, z; // 座標
  // 近くの点を保存するArrayList
  ArrayList<Point> nearPs = new ArrayList<Point>();
  color col;
  
  Point(){
    col = color(hueColor+random(-60, 60), random(100), random(40));
  }
  
  void addNearPoint(Point p){
    nearPs.add(p);
  }
}

ちょっとややこしいかもしれませんが、値を色々変えてみながら動かしてみてください。

この壁紙の動画版を作りました。そのままではフレームレートが20くらいになってしまうので、弧の中の点の数を減らしてフレームレートを確保しています。www.youtube.com
ソースはこちら

Arc[] arcs;
int numArcs = 100;
int lengthLimit = 180;
int hueColor;
color bgColor;

void setup(){
  size(displayWidth, displayHeight, P3D);
  smooth(32);
  colorMode(HSB, 360, 100, 100, 100);
  blendMode(ADD);
  noStroke();
  frameRate(30);
  reset();
}

void reset(){
  hueColor = int(random(360));
  bgColor = color(hueColor, 80, 15);
  arcs = new Arc[numArcs];
  for(int i = 0; i < numArcs; i++){
    arcs[i] = new Arc();
  }
}

void draw(){
  background(bgColor);
  translate(width/2, height/2, 0);
  rotateX(radians(-30));
  rotateZ(radians(30));
  
  for(int i = 0; i < numArcs; i++){
    arcs[i].display();
  }
}

void mousePressed(){
  reset();
}

class Arc{
  Point[] points;
  float maxRadius = height/2-100;
  float rotSpeed; // 回転速度
  
  Arc(){
    float arcLength = random(HALF_PI, 3*HALF_PI);
    float radianS = random(PI);
    float radianT = random(TWO_PI);
    float radius = 100 + random( (maxRadius-100)*sin(radianS) );
    int n = (int)map(radius, 100, maxRadius, 5, 30);
    points = new Point[n];
    for(int i = 0; i < n; i++){
      points[i] = new Point();
    }
    for(int i = 0; i < n; i++){
      float randRadianT = random(radianT, radianT+arcLength);
      float spreadXZ = random(-10, 10);
      float spreadY = random(-10, 10);
      points[i].x = (radius+spreadXZ) * cos(randRadianT);
      points[i].y = maxRadius * cos(radianS) + spreadY;
      points[i].z = (radius+spreadXZ) * sin(randRadianT);
    }
    
    for(int i = 0; i < points.length; i++){
      Point fromP = points[i];
      for(int j = 0; j < points.length; j++){
        Point toP = points[j];
        float dist = dist(fromP.x, fromP.y, fromP.z, toP.x, toP.y, toP.z);
        if(dist < lengthLimit){
          fromP.addNearPoint(toP);
        }
      }
    }
    // 回転速度をランダムに設定
    rotSpeed = random(-0.015, 0.015);
  }
  
  void display(){
    pushMatrix();
    // 弧の中心を軸に回転させる
    translate(0, points[0].y, 0);
    rotateY(frameCount*rotSpeed);
    for(int i = 0; i < points.length; i++){
      ArrayList<Point> ps = points[i].nearPs;
      fill(points[i].col);
      beginShape();
      for(Point p : ps){
        vertex(p.x, p.y-points[0].y, p.z);
      }
      endShape(CLOSE);
    }
    popMatrix();
  }
}

class Point{
  float x, y, z;
  ArrayList<Point> nearPs = new ArrayList<Point>();
  color col;
  
  Point(){
    col = color(hueColor+random(-60, 60), random(100), random(40));
  }
  
  void addNearPoint(Point p){
    nearPs.add(p);
  }
}