サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

three.js入門 05: アニメーションを加える


「three.js入門」04「マテリアルを使う」は、立体表面の素材となるマテリアルに、いくつかの設定を加えたりマップも与えてみました。本稿は他のライブラリも使って、立体やカメラをアニメーションさせます。

01 立方体の頂点をランダムに歪めて回す

「three.js入門」03のコード002「3次元空間で頂点の歪んだ立方体が回る」に手を加えるかたちで進めます(サンプル001)。立方体の8頂点座標をランダムに歪ませたうえで、xy軸で回しています(図001)。

02 正二十面体の頂点をランダムに歪めて回す

立方体のジオメトリーは、正二十面体に改めます。BoxGeometry()コンストラクタの引数が幅と高さおよび奥行きだったのに対して、IcosahedronGeometry()コンストラクタに渡すのは中心から頂点までの距離(半径)です(「three.js入門 03: ジオメトリーを使う」04「正二十面体をつくる」参照)。それにともなって、立体をつくる関数(createMesh())の引数と呼び出しが変わります。


function init() {

	// mesh = createMesh(side, side, side);
	mesh = createMesh(side);

}

// function createMesh(width, height, depth) {
function createMesh(radius) {
	// var geometry = new THREE.BoxGeometry(width, height, depth);
	var geometry = new THREE.IcosahedronGeometry(radius);

}

正二十面体の頂点座標は、次項からアニメーションで歪めるようにします。そのとき、歪んだ二十面体の座標をさらにランダムに動かすと、立体がどんどん崩れてゆくでしょう。ですから、もとの正二十面体の頂点座標はとっておき、ランダムな座標はつねにもとの座標から求めるようにします。ジオメトリーの頂点座標を配列で参照するのがGeometry.verticesプロパティです。そこで、つぎのようにあらかじめ関数(getVertices())で頂点座標を別の配列に取り出して、変数(originalVertices)に納めておくことにしました。


var originalVertices;
function init() {

	var vertices = mesh.geometry.vertices;
	originalVertices = getVertices(vertices);

}

function getVertices(sourceVertices) {
	var vertices = [];
	var length = sourceVertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = sourceVertices[i];
		vertices[i] = {x: vertex.x, y: vertex.y, z: vertex.z};
	}
	return vertices;
}

正二十面体の頂点座標をランダムに歪ませる処理も、ふたつの段階に分けます。まず、変数(originalVertices)にとっておいたもとの正二十面体の頂点座標から、歪ませた座標の配列を受け取ります(getRandomVertices())。この機会に、歪みの大きさを関数の引数(range)に加えました。つぎに、歪ませた座標の配列(randomVertices)にもとづいて、正二十面体の頂点座標(vertices)を変える(moveVertices())という流れです。


function init() {

	var vertices = mesh.geometry.vertices;

	// randomizeVertices(mesh.geometry.vertices);
	var randomVertices = getRandomVertices(originalVertices, side / 2);
	moveVertices(vertices, randomVertices);

}

// function randomizeVertices(vertices) {
function getRandomVertices(vertices, range) {
	// var range = side / 2;
	var randomVertices = [];
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var randomVertex = {x: vertex.x, y: vertex.y, z: vertex.z};
		/* vertex.x += -range / 2 + Math.random() * range;
		vertex.y += -range / 2 + Math.random() * range;
		vertex.z += -range / 2 + Math.random() * range; */
		randomVertex.x += -range / 2 + Math.random() * range;
		randomVertex.y += -range / 2 + Math.random() * range;
		randomVertex.z += -range / 2 + Math.random() * range;
		randomVertices[i] = randomVertex;
	}
	return randomVertices;
}
function moveVertices(vertices, newVertices) {
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var newVertex = newVertices[i];
		vertex.x = newVertex.x;
		vertex.y = newVertex.y;
		vertex.z = newVertex.z;
	}
}

これで、正二十面体の頂点をランダムに歪めた立体が回ります(図002)。頂点の座標はランダムに決めていますので、実行し直すたびに二十面体のかたちが変わるでしょう(サンプル002)。スクリプトは以下のコード002にまとめました。

コード001■正二十面体の頂点座標をランダムに歪めて回す


var width = window.innerWidth;
var height = window.innerHeight;
var side = Math.min(width, height) / 50;
var scene;
var camera;
var renderer;
var mesh;
var originalVertices;
function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(30, width / height, 1, 1000);
	renderer = createRenderer(width, height);
	mesh = createMesh(side);
	var vertices = mesh.geometry.vertices;
	originalVertices = getVertices(vertices);
	var randomVertices = getRandomVertices(originalVertices, side / 2);
	moveVertices(vertices, randomVertices);
	camera.position.z = 100;
	scene.add(mesh);
	update();
}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);
	document.body.appendChild(renderer.domElement);
	return renderer;
}
function createMesh(radius) {
	var geometry = new THREE.IcosahedronGeometry(radius);
	var material = new THREE.MeshNormalMaterial();
	var mesh = new THREE.Mesh(geometry, material);
	return mesh;
}
function getVertices(sourceVertices) {
	var vertices = [];
	var length = sourceVertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = sourceVertices[i];
		vertices[i] = {x: vertex.x, y: vertex.y, z: vertex.z};
	}
	return vertices;
}
function getRandomVertices(vertices, range) {
	var randomVertices = [];
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var randomVertex = {x: vertex.x, y: vertex.y, z: vertex.z};
		randomVertex.x += -range / 2 + Math.random() * range;
		randomVertex.y += -range / 2 + Math.random() * range;
		randomVertex.z += -range / 2 + Math.random() * range;
		randomVertices[i] = randomVertex;
	}
	return randomVertices;
}
function moveVertices(vertices, newVertices) {
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var newVertex = newVertices[i];
		vertex.x = newVertex.x;
		vertex.y = newVertex.y;
		vertex.z = newVertex.z;
	}
}
function update() {
	mesh.rotation.x += 0.01;
	mesh.rotation.y += 0.01;
	requestAnimationFrame(update);
	renderer.render(scene, camera);
}
window.addEventListener('DOMContentLoaded', init);

03 頂点座標をトゥイーンアニメーションで変える

正二十面体のもとのかたちから頂点が歪む動きを、トゥイーンアニメーションにしてみましょう。three.jsにトゥイーンのモジュールは含まれていません。そこで、ライブラリモジュールのスイートであるCreateJSからTweenJSを使います。つぎの<script>要素を加えると、CDNからTweenJSが読み込めます。


<script src="https://code.createjs.com/tweenjs-0.6.2.min.js"></script>

立体の頂点座標を指定した座標に動かす関数(moveVertices())は、座標は変えずにトゥイーンを定める関数(setTween())に渡します。関数の引数は立体の頂点のVector3オブジェクトと、トゥイーン先のランダムな座標をもったオブジェクトです。

Tween.get()メソッドの第1引数にトゥイーンしたいオブジェクトを渡すと、Tweenオブジェクトが返されます。オプションの第2引数のオブジェクトにはloopプロパティを加えました。trueを与えればアニメーションが繰り返されます(デフォルト値false)。トゥイーンを定めるのは、Tween.to()メソッドです。第1引数のオブジェクトにトゥイーンするプロパティと値を納めます。第2引数はアニメーションする時間(ミリ秒)です。第3引数は値の変わり方を決めるイージング関数で、Ease.bounceOutは弾むように変化します。


var tweens = [];

function moveVertices(vertices, newVertices) {

	for (var i = 0; i < length; i++) {

		/* vertex.x = newVertex.x;
		vertex.y = newVertex.y;
		vertex.z = newVertex.z; */
		setTween(vertex, newVertex);
	}
}
function setTween(vertex, newVertex) {
	var tween = createjs.Tween.get(vertex, {loop: true})
	.to({x: newVertex.x, y: newVertex.y, z: newVertex.z}, 1000, createjs.Ease.bounceOut);
	tweens.push(tween);
}

イージング関数にどのようなものがあり、どういう値の変わり方をするのかは、TweenJSサイトのデモご覧になるとよいでしょう。画面右側の一覧から選ぶと、左にグラフが描かれます(図003)。

図003■TweenJSサイトのイージング関数をグラフ表示するデモ

図003

ところが、これだけではトゥイーンアニメーションされません。Geometry.verticesプロパティの頂点座標を変えても、Geometry.verticesNeedUpdateプロパティtrueにしないと結果が反映されないのです。しかも、座標を変えて描画が必要になるたびに、プロパティはtrueに定め直さなければなりません。描画更新の関数(update())に、この記述を加えましょう。また、立体を回転するコードは除きます。スクリプトは以下のコード002にまとめ、サンプル003をjsdo.itに掲げました。


var geometry;

function createMesh(radius) {
	// var
	geometry = new THREE.IcosahedronGeometry(radius);

}

function update() {
	// mesh.rotation.x += 0.01;
	// mesh.rotation.y += 0.01;

	geometry.verticesNeedUpdate = true;

}

サンプル003■three.js r85: Icosahedron of random vertices with simple tween

コード002■正二十面体の頂点座標をトゥイーンアニメーションで歪ませる


var width = window.innerWidth;
var height = window.innerHeight;
var side = Math.min(width, height) / 50;
var scene;
var camera;
var renderer;
var mesh;
var originalVertices;
var tweens = [];
var geometry;
function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(30, width / height, 1, 1000);
	renderer = createRenderer(width, height);
	mesh = createMesh(side);
	var vertices = mesh.geometry.vertices;
	originalVertices = getVertices(vertices);
	var randomVertices = getRandomVertices(originalVertices, side / 2);
	moveVertices(vertices, randomVertices);
	camera.position.z = 100;
	scene.add(mesh);
	update();
}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);
	document.body.appendChild(renderer.domElement);
	return renderer;
}
function createMesh(radius) {
	geometry = new THREE.IcosahedronGeometry(radius);
	var material = new THREE.MeshNormalMaterial();
	var mesh = new THREE.Mesh(geometry, material);
	return mesh;
}
function getVertices(sourceVertices) {
	var vertices = [];
	var length = sourceVertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = sourceVertices[i];
		vertices[i] = {x: vertex.x, y: vertex.y, z: vertex.z};
	}
	return vertices;
}
function getRandomVertices(vertices, range) {
	var randomVertices = [];
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var randomVertex = {x: vertex.x, y: vertex.y, z: vertex.z};
		randomVertex.x += -range / 2 + Math.random() * range;
		randomVertex.y += -range / 2 + Math.random() * range;
		randomVertex.z += -range / 2 + Math.random() * range;
		randomVertices[i] = randomVertex;
	}
	return randomVertices;
}
function moveVertices(vertices, newVertices) {
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var newVertex = newVertices[i];
		setTween(vertex, newVertex);
	}
}
function setTween(vertex, newVertex) {
	var tween = createjs.Tween.get(vertex, {loop: true})
	.to({x: newVertex.x, y: newVertex.y, z: newVertex.z}, 1000, createjs.Ease.bounceOut);
	tweens.push(tween);
}
function update() {
	requestAnimationFrame(update);
	geometry.verticesNeedUpdate = true;
	renderer.render(scene, camera);
}
window.addEventListener('DOMContentLoaded', init);

04 トゥイーンアニメーションごとに新たな頂点座標を定める

前掲コード002のトゥイーンはループで繰り返されるものの、歪めた頂点座標ははじめに決まったまま変わりませんので、まったく同じアニメーションが繰り返されます。そうではなく、毎回ランダムな頂点座標が変わるようにしましょう。そのためには、ランダムな座標決め(getRandomVertices())と頂点座標のトゥイーンの定め(moveVertices())とを繰り返さなければなりません。新たな関数(setMotion())に切り分けることにしましょう。トゥイーンを初期化するには、Tweenオブジェクトの配列(tweens)は空にしたうえで、Tween.removeAllTweens()メソッドを呼び出します。


function init() {

	// var randomVertices = getRandomVertices(vertices, side / 2);
	// moveVertices(vertices, randomVertices);
	setMotion();

}
function setMotion() {
	var vertices = mesh.geometry.vertices;
	var randomVertices = getRandomVertices(originalVertices, side / 2);
	tweens.length = 0;
	createjs.Tween.removeAllTweens();
	moveVertices(vertices, randomVertices);
}

Tween.to()メソッドは、プロパティのトゥイーンを定めるだけです。関数を呼び出すにはTween.call()メソッドに、関数の参照を渡します。Tweenクラスのメソッドは、基本的にTweenオブジェクトの参照を返すので、メソッドはドット(.)をつなげて呼び出せるのです。また、前のトゥイーンが済むのを待って、順番に実行されます。イージング関数は、Ease.elasticOutを試しましょう。これで二十面体は、トゥイーンアニメーションでつぎつぎにかたちが変わります(サンプル004)。


function setTween(vertex, newVertex) {
	var tween = createjs.Tween.get(vertex)  // , {loop: true})
	.to({x: newVertex.x, y: newVertex.y, z: newVertex.z}, 1000, createjs.Ease.elasticOut)  // .bounceOut)
	.call(setMotion);
	tweens.push(tween);
}

サンプル004■three.js r85: Animation of cosahedron with vertices randomized

05 OrbitControlsでカメラをインタラクティブに動かす

仕上げに、カメラもアニメーションさせましょう。ダウンロードしたthree.jsライブラリの「examples/js/controls/」に含まれているOrbitControls.jsを使うと、マウスでカメラが操作できます。JavaScriptファイルをライブラリ用のフォルダ(ここではlib)に納めたら、<script>要素に読み込んで、つぎのようにOrbitControls()コンストラクタにカメラを渡して呼び出します(詳しくは「three.jsのOrbitControls.jsで簡単にカメラをマウスコントロールする」をお読みください)。


<script src="lib/OrbitControls.js"></script>


function init() {

    controls = new THREE.OrbitControls(camera);
    controls.autoRotate = true;

}

function update() {

    controls.update();

}

カメラを動かすためのマウス操作は、つぎのとおりです。

OrbitControls.autoRotateプロパティにtrueを与えれば、カメラは立体を中心にゆっくりと周回します。この場合、アニメーションの再描画メソッド(update())の中からOrbitControls.update()メソッドを呼び出さなければなりません(サンプル005)。書き上がったスクリプトは、以下のコード003にまとめました。


var controls;
function init() {

	controls = new THREE.OrbitControls(camera);
	controls.autoRotate = true;

}

function update() {

	controls.update();

}

サンプル005■three.js r85: Animation of cosahedron with vertices randomized and the camera

コード003■二十面体をトゥイーンアニメーションでランダムに歪める


var width = window.innerWidth;
var height = window.innerHeight;
var side = Math.min(width, height) / 50;
var scene;
var camera;
var renderer;
var mesh;
var originalVertices;
var tweens = [];
var geometry;
var controls;
function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(30, width / height, 1, 1000);
	renderer = createRenderer(width, height);
	mesh = createMesh(side);
	var vertices = mesh.geometry.vertices;
	originalVertices = getVertices(vertices);
	setMotion();
	camera.position.z = 100;
	scene.add(mesh);
	controls = new THREE.OrbitControls(camera);
	controls.autoRotate = true;
	update();
}
function setMotion() {
	var vertices = mesh.geometry.vertices;
	var randomVertices = getRandomVertices(originalVertices, side / 2);
	tweens.length = 0;
	createjs.Tween.removeAllTweens();
	moveVertices(vertices, randomVertices);
}
function createRenderer(width, height) {
	var renderer = new THREE.WebGLRenderer();
	renderer.setSize(width, height);
	document.body.appendChild(renderer.domElement);
	return renderer;
}
function createMesh(radius) {
	geometry = new THREE.IcosahedronGeometry(radius);
	var material = new THREE.MeshNormalMaterial();
	var mesh = new THREE.Mesh(geometry, material);
	return mesh;
}
function getVertices(sourceVertices) {
	var vertices = [];
	var length = sourceVertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = sourceVertices[i];
		vertices[i] = {x: vertex.x, y: vertex.y, z: vertex.z};
	}
	return vertices;
}
function getRandomVertices(vertices, range) {
	var randomVertices = [];
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var randomVertex = {x: vertex.x, y: vertex.y, z: vertex.z};
		randomVertex.x += -range / 2 + Math.random() * range;
		randomVertex.y += -range / 2 + Math.random() * range;
		randomVertex.z += -range / 2 + Math.random() * range;
		randomVertices[i] = randomVertex;
	}
	return randomVertices;
}
function moveVertices(vertices, newVertices) {
	var length = vertices.length;
	for (var i = 0; i < length; i++) {
		var vertex = vertices[i];
		var newVertex = newVertices[i];
		setTween(vertex, newVertex);
	}
}
function setTween(vertex, newVertex) {
	var tween = createjs.Tween.get(vertex)
	.to({x: newVertex.x, y: newVertex.y, z: newVertex.z}, 1000, createjs.Ease.elasticOut)
	.call(setMotion);
	tweens.push(tween);
}
function update() {
	requestAnimationFrame(update);
	geometry.verticesNeedUpdate = true;
	controls.update();
	renderer.render(scene, camera);
}
window.addEventListener('DOMContentLoaded', init);


作成者: 野中文雄
作成日: 2017年5月23日


Copyright © 2001-2016 Fumio Nonaka.  All rights reserved.