draw3DLine : 3D drawing on 2D canvas (Processing.js)

最近、3Dになったユニバ社のロゴ
3Dの描画機能だけを切り出してみたので、皆さん使ってね。

ユニバの新ロゴはcanvas+Processing.jsで作られていますが、Processingの3D系の命令は使っていません(WebGLに対応しているブラウザがまだ少ないので…)。3Dのオブジェクトをスクリーン(canvas)に投影する計算を行って、2D系の命令だけで描画しています。
その描画系を切り出したのが以下の関数です。

使い方

draw3DLine(x1, y1, z1, x2, y2, z2, xRotate, yRotate, zRotate)

6つの引数(XYZ座標を2つと、XYZ軸の回転角度)を渡すと、canvasに2座標を結ぶ線を描画します。原点は画面の中央です。
カメラはZ軸上に固定です。カメラの視野角(radS)、カメラの位置(camZ)、スクリーン位置(scZ)だけ、変更することができます。線の太さ、色はweight、strokeH、strokeS、strokeBで持っていますが、2Dで線を描画するパラメータなので、太い線ではパース感の矛盾がよりはっきり見えてきます。細い線でも厳密に言えば奥行き感ゼロ。そこは見せ方でがんばってね。

サンプルでは、線を12本引き、XYZ軸それぞれを1度ずつ回転させて、直方体が回転するアニメーションを作っています。
http://hascanvas.com/draw3DLine

//透視変換用の変数
//視野角θ
float radS = TWO_PI/360*35;
//カメラの原点からの距離
float camZ = -4500;
//スクリーンの原点からの距離
float scZ = -1000;
//回転
float xRotate = 0;
float yRotate = 0;
float zRotate = 0;

//描画用の変数
//線の太さ
float weight = 0.5;
//線の色
int strokeH = 0;
int strokeS = 0;
int strokeB = 0;

//頂点座標を格納する配列 : x1, y1, z1, x2, y2, z2
int[][] va = {
  {-200,-200,-20, 200,-200,-20},
  { 200,-200,-20, 200, 200,-20},
  { 200, 200,-20,-200, 200,-20},
  {-200, 200,-20,-200,-200,-20},
  {-200,-200, 20, 200,-200, 20},
  { 200,-200, 20, 200, 200, 20},
  { 200, 200, 20,-200, 200, 20},
  {-200, 200, 20,-200,-200, 20},
  {-200,-200,-20,-200,-200, 20},
  { 200,-200,-20, 200,-200, 20},
  { 200, 200,-20, 200, 200, 20},
  {-200, 200,-20,-200, 200, 20}
};

//初期化
void setup()
{
  size(300, 300);
  frameRate(30);
  colorMode(HSB);
  noSmooth();
  noFill();
  background(255);
}

//メインループ
void draw()
{
  background(255);
  //描画ループ
  for(int i=0; i<va.length; i++) {
    //描画
    //x1, y1, z1, x2, y2, z2, rotateX, rotateY, rotateZ
    draw3DLine(va[i][0],va[i][1],va[i][2],va[i][3],va[i][4],va[i][5], xRotate, yRotate, zRotate);
  }
  //回転
  xRotate += TWO_PI/360;
  yRotate += TWO_PI/360;
  zRotate += TWO_PI/360;
}

//透視変換 x1, y1, z1, x2, y2, z2, rotateX, rotateY, rotateZ
void draw3DLine(float x1,float y1, float z1, float x2, float y2, float z2, float rx, float ry, float rz)
{
  //Y軸の回転量を反映
  float z1cash = z1;
  float z2cash = z2;
  z1 = x1 * sin(ry) - z1cash * cos(ry);
  z2 = x2 * sin(ry) - z2cash * cos(ry);
  x1 = x1 * cos(ry) + z1cash * sin(ry);
  x2 = x2 * cos(ry) + z2cash * sin(ry);
  //X軸の回転量を反映
  z1cash = z1;
  z2cash = z2;
  z1 = y1 * sin(rx) - z1cash * cos(rx);
  z2 = y2 * sin(rx) - z2cash * cos(rx);
  y1 = y1 * cos(rx) + z1cash * sin(rx);
  y2 = y2 * cos(rx) + z2cash * sin(rx);
  //Z軸の回転量を反映
  float x1cash = x1;
  float x2cash = x2;
  float y1cash = y1;
  float y2cash = y2;
  x1 = x1 * sin(rz) + y1cash * cos(rz);
  x2 = x2 * sin(rz) + y2cash * cos(rz);
  y1 = y1 * sin(rz) - x1cash * cos(rz);
  y2 = y2 * sin(rz) - x2cash * cos(rz);
  //透視変換
  float x1b = width/2  + x1 * (camZ + z1) * tan(radS/2) / (scZ + z1) * tan(radS/2);
  float y1b = height/2 + y1 * (camZ + z1) * tan(radS/2) / (scZ + z1) * tan(radS/2);
  float x2b = width/2  + x2 * (camZ + z2) * tan(radS/2) / (scZ + z2) * tan(radS/2);
  float y2b = height/2 + y2 * (camZ + z2) * tan(radS/2) / (scZ + z2) * tan(radS/2);
  //描画開始位置を初期化
  translate(0,0);
  //線の色・太さ
  stroke(strokeH, strokeS, strokeB);
  strokeWeight(weight);
  //描画
  line(x1b,y1b,x2b,y2b);
}

Music Boxel Prototype (Processing+PeasyCam)

こんばんは〜

Processingでスケッチするのは、楽しいですね!的な話として今日は、Music Boxelのプロトタイプのご紹介です。

本物のMusic Boxelのリアルタイム・ヴィジュアルオーディオはaircord森氏によるopenFrameworks+SuperColliderのプログラムですが、ヴィジュアルの試作品はProcessingで書かれました。

開発中のMusic Boxelのヴィジュアル×オーディオを制作チーム内でイメージするために、「キューブ」が同心円状に4列になってぐるぐると周り、円周上の一定の角度(0度〜10度)にさしかかったときに「発光(発音)」しつつ「摩耗」する、というアイディアをProcessingでスケッチしました。

JavaApplet版はここに置いてあります。
Music Boxel Prototype (JavaApplet build with Processing)

Javaプラグインをブラウザに入れていない皆さまはこちらをどうぞ。 ↓ こんな感じです。
(動作中のJavaAppletをiPhoneでビデオ撮影したもの)

Processingのコードはこんな感じ。PeasyCamさえ入っていれば、手元のProcessingで動くと思います。
(PeasyCam、ありがとう!わたしのようなものぐさでも3Dでスケッチする気が起こります)

// 3Dカメラのコントロール用にpeasyを読み込んでいます。描画はOpenGL
import peasy.*;
import processing.opengl.*;

PeasyCam cam;

// キューブのサイズ初期値
float boxSize = 5;

// 4つの円周上に配置するキューブの数の初期値
int num = 4;
int num2 = 8;
int num3 = 4;
int num4 = 8;

// 4つの円の半径
float R = 5;
float R2 = 10;
float R3 = 15;
float R4 = 20;

// 4つの円の初期角度、回転スピード
float Rdiff = 0;
float Rspeed = 1;
float Rdiff2 = 0;
float Rspeed2 = 2;
float Rdiff3 = 0;
float Rspeed3 = 3;
float Rdiff4 = 0;
float Rspeed4 = 4;

void setup() {
  size(500,500,OPENGL);
  // 3Dカメラを作っています
  cam = new PeasyCam(this, 100);
  cam.setMinimumDistance(50);
  cam.setMaximumDistance(500);
}

void draw() {
  background(0);
  // 角度を加算。4つの円を回転させています
  Rdiff = Rdiff + Rspeed;
  Rdiff2 = Rdiff2 + Rspeed2;
  Rdiff3 = Rdiff3 + Rspeed3;
  Rdiff4 = Rdiff4 + Rspeed4;
  // 一番内側の円周上のキューブを描画
  for(int i=0; i<num; i=i+1) {
    // 現在の角度からキューブを配置する位置を決定しています
    pushMatrix();
    rotateZ(radians(360/num*i+Rdiff));
    translate(R,0,0);
    // "摩耗エリア(0〜10度)" にあるときに、キューブを "摩耗" させています。
    // また白い輪郭を表示しています
    if ((360/num*i+Rdiff)%360 < 10) {
      stroke(255);
      fill(255);
      boxSize = boxSize*0.9;
      box(boxSize*2);
      noFill();
      box(boxSize*3);
    } else {
      // "摩耗エリア" でないときは、通常の色で描画します
      stroke(255,0,0);
      fill(255,0,0);
    }
    // キューブを描画
    box(boxSize);
    popMatrix();
  }
  // 内側から二番目の円周上のキューブを描画
  for(int i=0; i<num2; i=i+1) {
    pushMatrix();
    rotateZ(radians(360/num2*i+Rdiff2));
    translate(R2,0,boxSize);
    if ((360/num2*i+Rdiff2)%360 < 10) {
      stroke(255);
      fill(255);
      boxSize = boxSize*0.9;
      box(boxSize*2);
      noFill();
      box(boxSize*3);
    } else {
      stroke(0,255,0);
      fill(0,255,0);
    }
    stroke(0,255,0);
    box(boxSize);
    popMatrix();
  }
  // 内側から三番目の円周上のキューブを描画
  for(int i=0; i<num3; i=i+1) {
    pushMatrix();
    rotateZ(radians(360/num3*i+Rdiff3));
    translate(R3,0,boxSize*2);
    if ((360/num3*i+Rdiff3)%360 < 10) {
      stroke(255);
      fill(255);
      boxSize = boxSize*0.9;
      box(boxSize*2);
      noFill();
      box(boxSize*3);
    } else {
      stroke(0,0,255);
      fill(0,0,255);
    }
    box(boxSize);
    popMatrix();
  }
  // 一番外側の円周上のキューブを描画
  for(int i=0; i<num4; i=i+1) {
    pushMatrix();
    rotateZ(radians(360/num4*i+Rdiff4));
    translate(R4,0,boxSize*3);
    if ((360/num4*i+Rdiff4)%360 < 10) {
      stroke(255);
      fill(255);
      boxSize = boxSize*0.9;
      box(boxSize*2);
      noFill();
      box(boxSize*3);
    } else {
      stroke(0,255,255);
      fill(0,255,255);
    }
    box(boxSize);
    popMatrix();
  }
  // キューブがある程度以上 "摩耗" したら、キューブの数、サイズをリセット
  if (boxSize < 0.1) {
    boxSize = 5;
    num = round(random(8));
    num2 = round(random(8));
    num3 = round(random(8));
    num4 = round(random(8));
  }
}

MusicBoxelのデモ・ムービーは、近日、もうそろそろ公開予定…

Processingで学ぶプログラミングその2

お久しぶりです!
フロントエンドエンジニアの清水です。

Processingで学ぶプログラミングその1 から大分時間がたってしまいましたがProcessing連載の2回目になります。

まずはこの動画から見ていただきましょう!

前回はマウスカーソルの位置にオブジェクトが出ているだけでしたが、今回はクリックの際にレンダリングされるように変更しました。
また外部コントローラーとしてiPadを使用しました!
MacとiPadの通信はWi-Fi経由で接続しています。(通信速度の観点からアドホックで接続するとレイテンシーが少ないです。)
TouchOSCというアプリを使用し、Processing側でiPadから送られてきたOSCを受け取りオブジェクトをレンダリングするように変更しています。

時間が空いてしまいましたので面白いものを見せられるようにと頑張りました。
外部入力を使用するという少し背伸びした形になりましたが、非常にProcessing熱が上がっております!
今後の連載(?)にご期待下さい。

ソースコード

import oscP5.*;
import netP5.*;

OscP5 oscP5;

void setup(){
  smooth();
  size(1680, 1050);
  colorMode(RGB);
  noStroke();
  background(#000000);
  oscP5 = new OscP5(this,8000);
  frameRate(60);
  noCursor();
}

void draw(){
}

void erase()
{
  background(0);
}

void mouseMoved(float x , float y ) {
  float r = random(1, 50);

  float R = random(0, 255);
  float G = random(0, 255);
  float B = random(0, 255);
  float a = random(0, 255);

  float xs = random(-15, 15);
  float ys = random(-15, 15);

  float z = random(-50, 50);

  ellipse(x+xs, y+ys, r, r);
  fill(R, G, B, a);
}

void oscEvent(OscMessage theOscMessage) {
    if(theOscMessage.checkAddrPattern("/reset") == true )
     {
       erase();
       return;
     }

  println("touch osc"+theOscMessage);
  float xValue = theOscMessage.get(0).floatValue();
  float yValue = theOscMessage.get(1).floatValue();

  println(xValue+", "+yValue);
  mouseMoved(xValue*width, yValue*height);
}

Processingで学ぶプログラミングその1

お久しぶりです。
フロントエンドエンジニアの清水です。

以前からプログラミングを学びたい!
とは思いつつもなかなか続けることが難しく三日坊主になっていました。
そんなわけで実行結果が見た目でわかりやすく確認できるProcessingで少しずつ学んでいこうと考えています。
プログラミングがデキる人にとってみたらそんなことかと思われるかもしれませんが、続けていけたらいいなと思っています。

というわけで簡単に書いてみました。

void setup(){
  smooth();
  size(400, 400);
  colorMode(RGB);
  noStroke();
  background(#ffffff);
}

void draw(){
  float x = mouseX;
  float y = mouseY;

  float r = random(1, 15);

  float R = random(0, 255);
  float G = random(0, 255);
  float B = random(0, 255);
  float a = random(0, 255);

  ellipse(x, y, r, r);
  fill(R, G, B, a);
}

実行結果

メインループで回りっぱなしなのでレンダリングされ続けます。
マウスの位置にオブジェクトが置かれるぐらいしか書いていませんが、クリックで散らばったり出来ると見た目にも面白いですね。

とは言いつつも構築方法のリテラシーが低いので、少しずつ色々な方法を学んでいけたらと思っています。
積み重ねて習慣にして行きたいです!

それではまた!

preload preload preload