HTML5テクニカルノート
three.js入門 05: アニメーションを加える
- ID: FN1705009
- Technique: HTML5 / JavaScript
- Library: three.js r85
「three.js入門」04「マテリアルを使う」は、立体表面の素材となるマテリアルに、いくつかの設定を加えたりマップも与えてみました。本稿は他のライブラリも使って、立体やカメラをアニメーションさせます。
01 立方体の頂点をランダムに歪めて回す
「three.js入門」03のコード002「3次元空間で頂点の歪んだ立方体が回る」に手を加えるかたちで進めます(サンプル001)。立方体の8頂点座標をランダムに歪ませたうえで、xy軸で回しています(図001)。
-
図001■頂点座標のランダムな六面体が回る
-
サンプル001■three.js r85: Rotating a cube with vertices randomized
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にまとめました。
-
図002■頂点座標のランダムに歪んだ二十面体が回る
-
サンプル002■three.js r85: Rotating a icosahedron with vertices randomized
コード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サイトのイージング関数をグラフ表示するデモ
ところが、これだけではトゥイーンアニメーションされません。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);
- three.js入門 01: 3次元空間で立方体を回す
- three.js入門 02: フォンマテリアルとライトを使う
- three.js入門 03: ジオメトリーを使う
- three.js入門 04: マテリアルを使う
作成者: 野中文雄
作成日: 2017年5月23日
Copyright © 2001-2016 Fumio Nonaka. All rights reserved.