Technology Blog

技術ブログ

2014.09.26

【3D】three.jsで400個のボックスを伸縮させるチュートリアル


iOS8にWebGLが搭載されたこともあり、three.jsにあらためて注目しています。

順を追ってコードのメモを書いていきます。

ボックス生成

for(i=0;i<20;i++){
	for(j=0;j<20;j++){
		var geometry = new THREE.BoxGeometry(10,10,10);
		var material = new THREE.MeshNormalMaterial();
		var cube = new THREE.Mesh(geometry , material);
		cube.castShadow = true;
		cube.position.set(i*10,0,j*10);
		scene.add(cube);
	}
}

iが縦軸、jが横軸になるように、2回ループを使って配置していきます。

座標を10ずつずらしているのは、ボックスのサイズが10だからです。

図示するとこんな感じです。

メインループの呼び方

(function mainLoop() {
requestAnimationFrame(mainLoop);

/*
ここが毎フレーム呼ばれる
*/

renderer.render(scene, camera);
})();

そうです「即時関数」です。即時関数で再帰的にメインループを呼びますので、ぐるぐるループして呼ばれます。

その際「requestAnimationFrame()」でフレームごとに呼ぶのがポイントですね。

処理が重たいなら、ここでフレーム調整するといいと思います。

メインループ内(1) → 全てのボックスに対して、forループで処理

var ob = scene['children'];

for(i=0;i<ob.length;i++){
	if(ob[i].scale.y>max){
		incFlg[i] = 1;
	}else if(ob[i].scale.y<min){
		incFlg[i] = 0;
	}
	if(incFlg[i] == 1){
		ob[i].scale.y -= yInc[i];
		ob[i].position.y -= yInc[i]*5;
	}else{
		ob[i].scale.y += yInc[i];
		ob[i].position.y += yInc[i]*5;
	}
}

先に「var ob = scene[‘children’];」でobにsceneの子を代入したうえで、ループをまわしていきます。

フラグ(incFlg)がある程度までいったら、今度はサイズを減らしていきます。

カメラの軌道は・・・

rad = angle * (Math.PI / 180)/4;
camerax =centerx + radian * Math.cos(rad);
cameray =centery + radian * Math.sin(rad);
camera.position.set(cameray,200,camerax);
angle+=3;

カメラの軌道に関して、ここで説明するのは割愛させていただきます。

こちらのコードを公式的に理解するか、根本から理解したい場合は「ジェネラティブ・アート -Processingによる実践ガイド」をおすすめします。この本は、軌道の描き方や三角関数などを原理から分かりやすく説明してくれています。Processingの本なのですが、実はどんなものにも応用できます。JavaScriptの類書でも、この本より分かりやすく刺激的な本はなさそうです。

完成コードはこちら

var main = function () {
var scene = new THREE.Scene();
//---------------------------------------------
//properties
var radius = 100;
var angle = 0;
var angleArr = [];
var angleInc = [];
var roteArr = [];
var radArr = [];
var rad = 0;
var radiuschange = -0.1;
var count = 1500;
var saikoro = 0;

var width = 800,
	height = 600;

//---------------------------------------------
//mesh

for(i=0;i<20;i++){
	for(j=0;j<20;j++){
		var geometry = new THREE.BoxGeometry(10,10,10);
		var material = new THREE.MeshNormalMaterial();
		var cube = new THREE.Mesh(geometry , material);
		cube.castShadow = true;
		cube.position.set(i*10,0,j*10);
		scene.add(cube);
	}
}

//---------------------------------------------
//light

//---------------------------------------------
//camera
var camera = new THREE.PerspectiveCamera(65,width/height+0.1,1,2000);
camera.position.set(200,220,200);
camera.lookAt(cube.position);

//---------------------------------------------
//renderer
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width,height);
renderer.setClearColor(0xeeeeee,1);
renderer.shadowMapEnabled = true;
document.getElementById('game').appendChild(renderer.domElement);
renderer.render(scene,camera);

/*---------------------------------------------
//helper
var axis = new THREE.AxisHelper(1000);
axis.position.set(0,0,0);
scene.add(axis);
*/

var controls = new THREE.OrbitControls(camera,renderer.domElement);

var ob = scene['children'];
var inc = 0;
console.log(ob[1]);
var y = 1;
var noise = 0;
var max = 20;
var min = 1;
var incFlg = [];
var ypos = 0;
yInc = [];
var camerax = 0
var cameray = 0;
var centerx = 150;
var centery = 150;
var rad = 0;
var angle = 0;
var radian = 100;
var cameraAngFlg = 0;

for(i=0;i<ob.length;i++){
	yInc[i] = Math.random()/10;
	incFlg[i] = 0;
}
//---------------------------------------------
//---------------------------------------------
//mainloop
(function mainLoop() {
requestAnimationFrame(mainLoop);

for(i=0;i<ob.length;i++){
	if(ob[i].scale.y>max){
		incFlg[i] = 1;
	}else if(ob[i].scale.y<min){
		incFlg[i] = 0;
	}
	if(incFlg[i] == 1){
		ob[i].scale.y -= yInc[i];
		ob[i].position.y -= yInc[i]*5;
	}else{
		ob[i].scale.y += yInc[i];
		ob[i].position.y += yInc[i]*5;
	}
}

//くわしくは「ジェネラティブ・アート -Processingによる実践ガイド」を参照
rad = angle * (Math.PI / 180)/4;
camerax =centerx + radian * Math.cos(rad);
cameray =centery + radian * Math.sin(rad);
camera.position.set(cameray,200,camerax);
angle+=3;

renderer.render(scene, camera);
controls.update();
})();
//end mainloop
//---------------------------------------------
//---------------------------------------------

};

function r(n){
	Math.floor(Math.random() * (n + 1) );
}

window.addEventListener( 'DOMContentLoaded', main, false );

関連ブログ