HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第19回 3次元空間で弾むオブジェクトとz座標による重ね順の並べ替え

この記事を読むのに必要な時間:およそ 7 分

前回の第18回クラスの継承と透視投影では,Shapeのサブクラスとして3次元座標のボールのクラスを定め,100個のオブジェクトをランダムな向きに落としたうえで,2次元平面に透視投影した。今回は仕上げとして,3つ手を加える。まず,ボールを床で弾ませる。つぎに,ステージから見えなくなったオブジェクトは,メモリを無駄に使わないように片づける。そして,3次元の表現で忘れてならないのは,オブジェクトの重ね順を整えることだ。

ボールを床で弾ませる

第18回コード2100個つくったオブジェクトの3次元座標をを透視投影してランダムな向きに落とすを書き替えていく。まず,ボールを弾ませるには,以下のようなコードを加える。弾ませる床の垂直位置は変数(floor)に与えた。そして,ボールを落とすアニメーションの関数(move())で,床より落ちたボール(ball)の垂直速度(velocityY)を逆転し,床にめり込んでしまった垂直位置(realY)はその分持ち上げる。

var floor = 50;
var bounce = -0.6;

function move(ball) {
  ball.move(gravity);
  var realY = ball.realY;
  if (realY > floor) {
    ball.velocityY *= bounce;
    ball.realY = floor - realY % floor;
  }

}

これで,ボールは床で弾むようになる。このアニメーションの処理は,第13回「モーションブラーと弾むアニメーション」オブジェクトをステージ下端で弾ませるから抜き出した次のコードと基本的に同じだ。理解に不安のある読者は,こちらの解説をお読みいただきたい。

function animate(eventObject) {

  if (newY > stageHeight) {
    child.velocityY *= -0.8;
    newY = stageHeight - newY % stageHeight;
  }

}

つぎに,要らなくなったオブジェクトをメモリから除こう。やはりボールを落とすアニメーションの関数(move())で,以下のコードのようにふたつの軸で条件を与えた。ひとつは,透視投影したボールのx座標だ。この値がステージの端(0またはstageWidth)から外に出れば,見えなくなってもう戻ってくることはない。

もうひとつ,z座標(realZ)も考える。透視投影では,スクリーンの位置をz座標0として,視点は焦点距離下がった(マイナスの)位置に置く(再掲第16回図6)。すると,焦点距離の分手前(-focalLength)まで迫ったオブジェクトは眼の前にあり,さらに下がれば視点の後方に消える。したがって,そのオブジェクトも除いて構わない。

第16回図6 z軸における焦点距離と視野角(再掲)

第16回図6 z軸における焦点距離と視野角(再掲)

ステージから消すオブジェクトは,Stageオブジェクトの表示リストからContainer.removeChild()メソッドで除く。さらに,第18回コード2では,インスタンスを配列(balls)に入れてまとめた。この配列エレメントも削除したい。だが,配列エレメントはインデックスで指定しなければならない。

EaselJS 0.7.0で新たに備わったindexOf()関数に,配列と取出したいエレメントを引数に渡すと,そのエレメントのインデックスが返される。インデックスがわかれば,Array.splice()メソッドで配列エレメントは除ける。indexOf()はクラスに属さないグローバルな関数なので,名前空間「createjs」に続けて直接参照して呼び出す。

indexOf(配列, エレメント)
function move(ball) {
  ball.move(gravity);
  var x = ball.x;

  if (ball.realZ < -focalLength || x < 0 || stageWidth < x) {
    var index = createjs.indexOf(balls, ball);
    stage.removeChild(ball);
    balls.splice(index, 1);
  } else {

  }
}

これで,3次元空間にランダムに落ちたボールは床で弾む。そして,ステージから外れたオブジェクトは,参照が除かれ,やがてメモリからも消える(第14回「オブジェクトの使い回しとアニメーション素材の変更」ガベージコレクションを減らす参照)。書直したscript要素のJavaScript全体は,つぎのコード1のとおりだ。

コード1 ランダムに落ちた100個のボールが床に落ちて弾みながらステージ外に消える

function Ball3D(radius, color) {
  this.initialize();
  this.radius = radius;
  this.color = color;
  this.realX = 0;
  this.realY = 0;
  this.realZ = 0;
  this.velocityX = 0;
  this.velocityY = 0;
  this.velocityZ = 0;
  this.drawBall(radius, color);
}
Ball3D.prototype = new createjs.Shape();
Ball3D.prototype.move = function(gravity) {
  this.realX += this.velocityX;
  this.realY += this.velocityY;
  this.realZ += this.velocityZ;
  this.velocityY += gravity;
};
Ball3D.prototype.getProjectedData = function(focalLength) {
    var scale = focalLength / (focalLength + this.realZ);
    var x = this.realX * scale;
    var y = this.realY * scale;
    return {x:x, y:y, scale:scale};
};
Ball3D.prototype.drawBall = function(radius, color) {
  this.graphics.beginFill(color)
  .drawCircle(0, 0, radius);
};
var stage;
var balls = [];
var numBalls = 100;
var stageWidth;
var centerX;
var centerY;
var gravity = 0.2;
var focalLength = 200;
var floor = 50;
var bounce = -0.6;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageWidth = canvasElement.width;
  centerX = stageWidth / 2;
  centerY = canvasElement.height / 2;
  for (var i = 0; i < numBalls; i++) {
    var color = createjs.Graphics.getRGB(getRandom(0, 0xFFFFFF));
    var ball = new Ball3D(3, color);
    balls.push(ball);
    ball.realY = -50;
    ball.velocityX = getRandom(-3, 3);
    ball.velocityY = getRandom(-6, 0);
    ball.velocityZ = getRandom(-3, 3);
    stage.addChild(ball);
  }
  createjs.Ticker.addEventListener("tick", animate);
}
function animate(eventObject) {
  for (var i = balls.length - 1; i > -1; i--) {
    var ball = balls[i];
    move(ball);
  }
  stage.update();
}
function move(ball) {
  ball.move(gravity);
  var x = ball.x;
  var realY = ball.realY;
  if (realY > floor) {
    ball.velocityY *= bounce;
    ball.realY = floor - realY % floor;
  }
  if (ball.realZ < -focalLength || x < 0 || stageWidth < x) {
    var index = createjs.indexOf(balls, ball);
    stage.removeChild(ball);
    balls.splice(index, 1);

  } else {
    var data = ball.getProjectedData(focalLength);
    ball.scaleX = ball.scaleY = data.scale;
    ball.x = centerX + data.x;
    ball.y = centerY + data.y;
  }
}
function getRandom(min, max) {
  var randomNumber = Math.random() * (max - min) + min;
  return randomNumber;
}

残るは,オブジェクトの重ね順だ。このお題では,ボールの大きさはすべて同じにした。そのため,透視投影にしたがえば,手前のボールほど大きく映り,小さいボールがその前にあってはいけない図1)。この重ね順を正しく整えたい。

図1 透視投影で手前に見えるべきボールの前に小さいボールが重なる

図1 透視投影で手前に見えるべきボールの前に小さいボールが重なる

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入