p5.jsのorbitControlでいろいろ回してみた


2017年12月31日
With
p5.jsのorbitControlでいろいろ回してみた はコメントを受け付けていません

(2018/01/25追記:こちらのエントリで紹介しているコードは、p5.js 0.5.16向けのものになります。0.6.0以降では正しく動作しません)

今年も終わりですね。この年末はいろいろやらなきゃならないことをほったらかして、p5.jsのWebGLモードをリファレンス見ながら試してみるというのをやっているのですが、たまたまこんなAPIを見つけまして。

・orbitControl() – p5.js | reference

リファレンスに、機能についての記述がまったくないので、とりあえず動かしてみたのですが、どうやら「draw()内に書いておけば、カメラ位置をマウスドラッグでグリグリ回せるようになる」という楽しいコマンドのようです。

camera()とorbitControl()を同時に使うときは、camera()をdraw()以外の部分に置くようにするのがポイント。せっかくなので、グリグリ回すと楽しそうなスケッチをいくつか作ってみました。

●なんかパワーがありそうなオーブっぽいの(クリックすると別タブが開きます)

3Dで何か作れるようになると、こういうクルクル回るのをやりたくなるのはなぜなのでしょうね(笑)。赤い球を中心として、周囲を四角いパネルみたいなのが回っていると思います。ブラウザ上でマウスドラッグすると、カメラをグリグリできますので、試してみてください。コードはこちら。

const RAD = 200;
const planeArr = [];
const planeCount = 70;

function setup() {
	createCanvas(600, 600, WEBGL);
	ambientLight(150, 150, 200);
	pointLight(255, 255, 255, 255, -800, -800, 800);
	perspective(radians(80), width / height, 0, 700);
	camera(0, 0, 360, 0, 0, 0, 0, 1, 0);
	noStroke();
	setPlane(planeCount);
}

function draw() {
	orbitControl();
	background(0);
	specularMaterial(255, 80, 80, 255);
	sphere(20);
	planeArr.forEach(drawobj => { drawobj.drawMe(); });
}

function setPlane(count) {
	for (let i = 0; i < count; i++) {
		planeArr[i] = new PlaneObj();
	}
}

function setBool() {
	const flag = random();
	if (flag < 0.5) { return true; }
	else { return false; }
}
class PlaneObj {
	constructor() {
		this.rotx = random(0, 359);
		this.roty = random(0, 359);
		this.rotz = random(0, 359);
		this.transrad = random(60, RAD);
		this.pWidth = random(2, 20);
		this.pHeight = random(10, 100);
		this.rotDirX = setBool();
		this.rotDirY = setBool();
		this.rotDirZ = setBool();
		this.rotDirRev = setBool();
		this.rotSpeed = random(50, 200);
		if (this.rotDirX == false && this.rotDirY == false && this.rotDirZ == false) { this.rotDirX = true; }
	}

	drawMe() {
		let rev = this.rotDirRev ? -1 : 1;
		push();
		if (this.rotDirY) { rotateY(rev * (frameCount / this.rotSpeed)) };
		if (this.rotDirX) { rotateX(rev * (frameCount / this.rotSpeed)) };
		if (this.rotDirZ) { rotateZ(rev * (frameCount / this.rotSpeed)) };
		rotateX(radians(this.rotx));
		rotateY(radians(this.roty));
		rotateZ(radians(this.rotz));
		translate(0, this.transrad, 0);
		ambientMaterial(150, 150, 255, 255);
		plane(this.pWidth, this.pHeight);
		pop();
	}
}

では次。

●エセ・プラネタリウムを外側から見る(クリックすると別タブが開きます)

このあいだ「エセ・プラネタリウム」というスケッチを作りました。あれは「中心にカメラを置いた、1600×1600×1600の3次元空間上にランダムに5500個のsphereをばらまき、カメラ位置は中央のままで、注視点を自動でゆっくりと動かす」というものだったのですが、今回のスケッチでは、sphereがある空間の外側にカメラを置いています。これにもorbitControl()を入れていますので、マウスをドラッグすると、

↑こんな感じで回せます。sphereのある範囲は分かりやすいように紫のボックスで囲んでみました。同時に、このスケッチではマウスホイールの動きを取る「mouseWheel()」を使ってみました。ホイールを回すと、カメラをz軸方向に動かしてズームイン/アウトができます。sphere帯の中でドラッグすると、オリジナルに近い視界が回せますので試してみてください。

コードはこちら。

const RANGE = 800;
const stars = [];
const limStars = 5500;
let rotCamPos;
let cameraPosZ = 4000;

function setup() {
	createCanvas(600, 600, WEBGL);
	ambientLight(120, 120, 170);
	pointLight(200, 200, 250, -400, -400, 400);
	perspective(radians(42), width / height, 0, 10000);
	camera(0, 0, cameraPosZ, 0, 0, 0, 0, 1, 0);
	noStroke();
	initStars();
}

function draw() {
	orbitControl();
	background(0, 0, 25);
	ambientMaterial(255, 100, 100, 10);
	box(RANGE * 2);
	ambientMaterial(255, 255, 240, 255);
	drawSphere();
}

function initStars() {
	for (let i = 0; i < limStars; i++) {
		let x, y, z;
		x = random(-RANGE, RANGE);
		y = random(-RANGE, RANGE);
		z = random(-RANGE, RANGE);
		stars[i] = new PointObj(x, y, z);
	}
}

function drawSphere() {
	for (let i = 0; i < limStars; i++) {
		push();
		translate(stars[i].x, stars[i].y, stars[i].z);
		sphere(5);
		pop();
	}
}

function mouseWheel(event) {
	if (cameraPosZ + event.delta > 0) {
		cameraPosZ += event.delta;
	}
	camera(0, 0, cameraPosZ, 0, 0, 0, 0, 1, 0);
}

class PointObj {
	constructor(ex, why, zi) {
		this.x = ex;
		this.y = why;
		this.z = zi;
	}
}

次も以前作ったスケッチのアレンジです。

●聖夜に降るコッホ雪片3Dバージョン(クリックすると別タブが開きます)

前に作った「聖夜に降るコッホ雪片」は2Dで書いたスケッチだったのですが、今回は3Dにして回転する方向を増やしてみました。

WebGLモードでは、イメージファイルをそのまま表示できないので、板状の3Dプリミティブである「plane()」にテクスチャとして画像を張り付けて回しています。これもドラッグで回せますので思いのままにどうぞ。

var dots = [];
var count = 40;
var noiseval = 0.01;
var img = [];

function setup() {
	createCanvas(600, 600, WEBGL);
	ambientLight(255, 255);
	img[0] = loadImage('./images/kochcrystal.png');
	img[1] = loadImage('./images/kochcrystal2.png');
	img[2] = loadImage('./images/kochcrystal3.png');

	pointinit();
	for (var i = 0; i < count; i++) {
		dots[i].initMe();
	}
}

function draw() {
	orbitControl();
	background(0);
	for (var i = 0; i < count; i++) {
		dots[i].drawMe();
		dots[i].updateMe();
	}
}


function pointinit() {
	for (var i = 0; i < count; i++) {
		dots[i] = new SnowObj();
	}
}

function SnowObj() {
	var x, y;
	var sizeScale;
	var rotAngle;
	var rotSpeed;
	var speed, xnoise;
	var imgCol;
}

SnowObj.prototype.initMe = function () {
	this.x = random(-width / 2, width / 2);
	this.y = random(-height / 2, height / 2);
	this.imgCol = floor(random(0, 3));
	this.sizeScale = random(0.05, 0.25);
	this.rotAngle = 0;
	this.rotSpeed = random(-3, 3);
	this.speed = random(0.5, 2);
	this.xnoise = random(100);
}

SnowObj.prototype.updateMe = function () {
	this.x = this.x + noise(this.xnoise) * 2 - 1;
	this.xnoise = this.xnoise + noiseval;
	this.y = this.y + this.speed;
	if (this.y > height / 2 + 100) {
		this.initMe();
		this.y = -height / 2 - 100;
	}
	this.rotAngle += this.rotSpeed;
}

SnowObj.prototype.drawMe = function () {
	push();
	translate(this.x, this.y, 0);
	rotateX(radians(this.rotAngle) / 2);
	rotateY(radians(this.rotAngle) / 2);
	rotateZ(radians(this.rotAngle));
	scale(this.sizeScale, this.sizeScale);
	plane(303, 348)
	texture(img[this.imgCol]);
	pop();
}

まだあります。

●カードの周りをたくさんのオーブが回転していると何か特別な力が宿っていそうに見える(クリックすると別タブが開きます)

最初「3Dモードで表裏のあるカードを回すような表現ができるとカッコイイゲームみたいなものも作れるのでは?」と思って始めたら、思いのほか簡単にできてしまったので、なんとなく周囲にたくさんのグルグル回るsphereを並べてみたという文字どおりの「ラクガキ」です。オーブが回っているだけでカードに何か強い力が宿っていそうに見えます。

plane()のテクスチャとして画像を張ると、表にも裏にも同じ絵が出てしまうので、ほんのちょっとZ軸をずらした2枚のplaneを描いて、それぞれに表面と裏面の画像を張っています。これで簡単なトランプゲーム的なものであればすぐ作れそうです。来年、時間ができたらちょっとがんばってみます。

var img = [];

function preload() {
    img[0] = loadImage('./assets/IM021.png');
    img[1] = loadImage('./assets/IM028.png');
}

function setup() {
    createCanvas(600, 600, WEBGL);
    camera(0, 0, 600, 0, 0, 0, 0, 1, 0);
    perspective(radians(70), width / height, 0, 1000);
}

function draw() {
    ambientLight(255, 255, 255, 255);
    pointLight(255, 255, 600, -600, 600);
    background(0, 0, 50);
    orbitControl();
    for (var j = 0; j < 5; j++) {
        drawSphere(j);
    }
    drawCard();
}

function drawCard() {
    rotateY(frameCount / 30);
    rotateZ(radians(30));
    push();
    texture(img[1]);
    plane(200, 400);
    translate(0, 0, 0.01);
    texture(img[0]);
    plane(200, 400);
    pop();
}

function drawSphere(count) {
    push();
    var rotRevZ, rotRevY, rotRevZ, spd;
    if (count == 0) {
        rotRevZ = 1; rotRevY = 1, rotRevX = -1; spd = 2;
    } else if (count == 1) {
        rotRevZ = -1; rotRevY = 1, rotRevX = -1; spd = 3;
    } else if (count == 2) {
        rotRevZ = 1; rotRevY = -1, rotRevX = -1; spd = 1.5;
    } else if (count == 3) {
        rotRevZ = -1; rotRevY = 1, rotRevX = 1; spd = 1;
    } else {
        rotRevZ = 1; rotRevY = -1, rotRevX = 1; spd = 1.2;
    }
    rotateZ(rotRevZ * (frameCount / 100 * spd));
    rotateX(rotRevX * (frameCount / 100 * spd));
    rotateY(rotRevY * (frameCount / 100 * spd));
    push();
    for (var i = 0; i < 720; i += 8) {
        push();
        rotateZ(radians(i));
        translate(240 + count * 15, 0, 0);
        ambientMaterial(120, 120, 255, 255);
        sphere(8);
        pop();
    }
    pop();
    pop();
}

これもorbitControl()で回せるようにしたのですが、orbitControlは動くスケッチよりも、動かないモデルをマウス操作で任意の方向から見るような用途のほうがより効果的な気がしますね。ただ、使いどころは多そうです。

今年は、春ごろにp5.jsと出会ったおかげで、ブログのエントリがいっぱい作れた良い年でした(笑)。来年も、時間見つけていろいろスケッチしてみたいと思います。それではみなさん、良いお年を。m(_ _)m