Processing中毒者の嘔吐物

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

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

ということで、ProcessingでPCやスマホに使える壁紙制作していきます。
今回作るのはこんな感じの壁紙↓
f:id:P5Aholic:20150630222315j:plain
f:id:P5Aholic:20150630222709j:plain
f:id:P5Aholic:20150630222805j:plain
f:id:P5Aholic:20150630222914j:plain
美しい

1000個の点を球体上にランダムに配置して、全ての点に対して他の全ての点との距離を計算し一定距離内にある点同士で多角形を作るという処理をしています。球体の作成法については
Processingで三角関数を使って球体を作る - Processing中毒者の嘔吐物を参照。
画像一枚目のコードがこちら↓

int numPoints = 1000; // 点の数
int lengthLimit = 80; // 距離制限
int hueColor;         // hueの値
color bgColor;        // 背景色
Point[] points;

boolean save = false;

void setup(){
  size(displayWidth, displayHeight, P3D);
  // アンチエイリアスの品質を設定
  smooth(32);
  // HSBカラーモード
  colorMode(HSB, 360, 100, 100, 100);
  // 加算合成モード
  blendMode(ADD);
  noLoop();
  reset();
}

void reset(){
  // hueの値をランダムに設定
  hueColor = int(random(360));
  // 背景色を設定
  bgColor = color(hueColor, 80, 15);
  // 点を生成
  points = new Point[numPoints];
  for(int i = 0; i < numPoints; i++){
    points[i] = new Point();
  }
}

void draw(){
  background(bgColor);
  translate(width/2, height/2, 0);
  
  // 全ての点に対して、他の全ての点との距離を計算し
  // lengthLimit以下である点同士で多角形を作る
  for(int i = 0; i < points.length; i++){
    Point fromP = points[i];
    // 近くの点を格納するArrayList
    ArrayList<Point> nearPs = new ArrayList<Point>();
    for(int j = 0; j < points.length; j++){
      Point toP = points[j];
      // fromPとtoPの距離を計算
      float dist = dist(fromP.x, fromP.y, fromP.z, toP.x, toP.y, toP.z);
      // lengthLimit以下ならnearPsに追加
      if(dist < lengthLimit){
        nearPs.add(toP);
      }
    }
    // 多角形を描画
    strokeWeight(0.5);
    stroke(200, 50);
    fill(fromP.col);
    beginShape();
    for(Point p : nearPs){
      vertex(p.x, p.y, p.z);
    }
    endShape(CLOSE);
  }
  
  if(save){
    saveFrame("img/img-"+(int)random(10000)+".jpg");
    save = false;
  }
}

// 左クリックでresetして再描画
// 右クリックで画像保存
void mousePressed(){
  if(mouseButton == LEFT){
    reset();
    redraw();
  }
  else if(mouseButton == RIGHT){
    save = true;
    redraw();
  }
}

// Pointクラス
class Point{
  float x, y, z; // 座標
  float radius;  // 半径
  color col;     // 色
  
  Point(){
    // 球体上の座標を計算
    // 球体のつむじ側に点を少なく
    // 球体のお腹側に点を多く配置するようにする
    float s = 0;
    int r = (int)random(7);
    // 下側のつむじ
    if(r == 0)           s = random(0, QUARTER_PI);
    // お腹周り
    if(r >= 1 && r <= 5) s = random(QUARTER_PI, 3*QUARTER_PI);
    // 上側のつむじ
    if(r == 6)           s = random(3*QUARTER_PI, PI);
    float t = random(TWO_PI);
    radius = random(200, 500);
    x = radius * sin(s) * cos(t);
    y = radius * cos(s);
    z = radius * sin(s) * sin(t);
    // hueColorをベースにランダムな色を設定
    col = color(hueColor+random(60), random(100), random(40));
  }
}

カラーモードはHSB、色は加算合成にしています。加算合成モードについては
Processingの加算合成モードを使う - Processing中毒者の嘔吐物を参照。
またP2D、P3Dモードではsmooth()に2の乗数を指定してアンチエイリアスの品質を上げることができます。指定できる最大値はPC環境によって異なります。僕の環境では32が限界でした。
点はPointクラスのオブジェクトとして扱っています。Pointコンストラクタの中の処理をすこし解説します。球体作成記事を読まれた方はお分かりだと思うのですが、sはy座標と半径の計算に対応しています。コメントにもあるように球体のつむじ側に点を少なく配置するようにしています。なぜかというと、s = random(PI)だけでは点同士の距離を計算して多角形を繋ぐとき、つむじ側の点は半径が小さくなっているので多角形が自然とたくさんできます。一方お腹側は、半径が大きいので点同士の距離も大きく多角形はあまりできません。そうすると見た目上多角形がつむじ側に過密、お腹周りに過疎になるので、調整をしているわけです。

行数は100行に満たないのにこんなにも美しいスケッチが描けてしまうProcessingはやはり最高ですね。
残りの3つの画像も処理はほぼ同じです。ソースコードを順番に載せておきます。

int numPoints = 1000;
int lengthLimit = 80;
int hueColor;
color bgColor;
Point[] points;

boolean save = false;

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

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

void draw(){
  background(bgColor);
  translate(width/2, height/2, 0);
  
  for(int i = 0; i < points.length; i++){
    Point fromP = points[i];
    ArrayList<Point> nearPs = new ArrayList<Point>();
    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){
        nearPs.add(toP);
      }
    }
    strokeWeight(0.5);
    stroke(200, 50);
    fill(fromP.col);
    beginShape();
    for(Point p : nearPs){
      vertex(p.x, p.y, p.z);
    }
    endShape(CLOSE);
  }
  
  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 Point{
  float x, y, z;
  float radius;
  color col;
  
  Point(){
    float s = 0;
    int r = (int)random(7);
    if(r == 0)           s = random(0, QUARTER_PI);
    if(r >= 1 && r <= 5) s = random(QUARTER_PI, 3*QUARTER_PI);
    if(r == 6)           s = random(3*QUARTER_PI, PI);
    float t = random(TWO_PI);
    radius = random(200, 500);
    x = radius * sin(s) * cos(t);
    y = radius * cos(s);
    z = radius * sin(s) * sin(t);
    col = color(hueColor+random(60), random(80), random(20, 100), random(100));
  }
}
int numPoints = 1200;
int lengthLimit = 80;
int hueColor;
color bgColor;
Point[] points;

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);
  points = new Point[numPoints];
  for(int i = 0; i < numPoints; i++){
    points[i] = new Point();
  }
}
void draw(){
  background(bgColor);
  translate(width/2, height/2, 0);
  
  for(int i = 0; i < points.length; i++){
    Point fromP = points[i];
    ArrayList<Point> nearPs = new ArrayList<Point>();
    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){
        nearPs.add(toP);
      }
    }
    strokeWeight(0.5);
    stroke(200, 50);
    fill(fromP.col);
    beginShape();
    for(Point p : nearPs){
      vertex(p.x, p.y, p.z);
    }
    endShape(CLOSE);
  }
  
  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 Point{
  float x, y, z;
  float radius;
  color col;
  
  Point(){
    float s = 0;
    int r = (int)random(7);
    if(r == 0)           s = random(0, QUARTER_PI);
    if(r >= 1 && r <= 5) s = random(QUARTER_PI, 3*QUARTER_PI);
    if(r == 6)           s = random(3*QUARTER_PI, PI);
    float t = random(TWO_PI);
    // 二つの半径に分ける
    if(random(2) < 1){
      radius = 450;
    }
    else radius = 250;
    x = radius * sin(s) * cos(t);
    y = radius * cos(s);
    z = radius * sin(s) * sin(t);
    col = color(hueColor+random(60), random(100), random(40));
  }
}
int numPoints = 1200;
int lengthLimit = 80;
int hueColor;
color bgColor;
Point[] points;

boolean save = false;

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

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

void draw(){
  background(bgColor);
  translate(width/2, height/2, 0);
  
  for(int i = 0; i < points.length; i++){
    Point fromP = points[i];
    ArrayList<Point> nearPs = new ArrayList<Point>();
    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){
        nearPs.add(toP);
      }
    }
    strokeWeight(0.5);
    stroke(200, 50);
    fill(fromP.col);
    beginShape();
    for(Point p : nearPs){
      vertex(p.x, p.y, p.z);
    }
    endShape(CLOSE);
  }
  
  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 Point{
  float x, y, z;
  float radius;
  color col;
  
  Point(){
    float s = 0;
    int r = (int)random(7);
    if(r == 0)           s = random(0, QUARTER_PI);
    if(r >= 1 && r <= 5) s = random(QUARTER_PI, 3*QUARTER_PI);
    if(r == 6)           s = random(3*QUARTER_PI, PI);
    float t = random(TWO_PI);
    if(random(2) < 1){
      radius = 450;
    }
    else radius = 250;
    x = radius * sin(s) * cos(t);
    y = radius * cos(s);
    z = radius * sin(s) * sin(t);
    col = color(hueColor+random(60), random(80), random(20, 100), random(100));
  }
}