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

第18回 クラスの継承と透視投影

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

z軸方向の速さと透視投影のメソッドを加える

2次元平面でボールを落とせたら,つぎはz軸方向の動きを加えて,透視投影する。つまり,3次元で動くボールに,z軸の向きにも速さ(velocityZ)を加える。初期化の関数(initialize())でz軸方向の速さをランダムに定め,ボールのクラス(Ball3D)のオブジェクトを落とすメソッド(move())で,z軸の向きの動きも与える。

Ball3D.prototype.move = function(gravity) {
  this.realX += this.velocityX;
  this.realY += this.velocityY;
  this.realZ += this.velocityZ;
  this.velocityY += gravity;
};

function initialize() {

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

    var ball = new Ball3D(3, color);

    ball.velocityX = getRandom(-3, 3);
    ball.velocityY = getRandom(-6, 0);
    ball.velocityZ = getRandom(-3, 3);

  }

}

3次元で動くボールのクラス(Ball3D)には,さらに透視投影のメソッド(getProjectedData())をつぎのように定める。焦点距離(focalLength)を引数に受け取って,xy座標(xおよびy)とオブジェクトの伸縮率(scale)をObjectインスタンスのプロパティに納めて返す。

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};
};

参考にしたのは第16回「3次元空間で座標を回す」3次元空間座標を透視投影するから抜き出したつぎの関数だ。ただし,前掲コードは大きさをもつボールのクラス(Ball3D)のメソッド(getProjectedData())なので,透視投影の比率(焦点距離 / ⁠焦点距離 + z位置))を戻り値のオブジェクトに伸縮率のプロパティ(scale)として加えた。

function getProjetedPoint(focalLength, _point3D) {
  var point2D = new createjs.Point();
  var w = focalLength / (focalLength + _point3D.z);
  point2D.x = _point3D.x * w;
  point2D.y = _point3D.y * w;
  return point2D;
}

前掲透視投影のメソッド(getProjetedPoint())は,ボールの動きを2次元平面に定める関数(move())から呼び出す。メソッドから返されたObjectインスタンスのプロパティ(scaleおよびxとy)を,ボールの(スーパークラスShapeの)xy座標と水平・垂直伸縮率に与えて透視投影する。なお,透視投影のメソッドに渡す焦点距離は,あらかじめ変数(focalLength)に定めた。

var focalLength = 200;

function move(ball) {
  ball.move(gravity);
  var data = ball.getProjectedData(focalLength);
  ball.scaleX = ball.scaleY = data.scale;
  // ball.x = centerX + ball.realX;
  ball.x = centerX + data.x;
  // ball.y = centerY + ball.realY;
  ball.y = centerY + data.y;
}

ボール100個が落ちる動きを3次元に拡げたのがつぎのコード2だ。ボールのオブジェクトはz座標値に応じて透視投影され,遠ざかれば小さく,近づけば大きく表示される(図2⁠。もっとも,今の表現では3次元の動きだといわれなければ,ただオブジェクトが伸縮しながら落ちているだけに見えるかもしれない。

図2 100個のボールが3次元空間でランダムな向きに落ちる

図2 100個のボールが3次元空間でランダムな向きに落ちる

コード2 100個つくったオブジェクトの3次元座標をを透視投影してランダムな向きに落とす

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;
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 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;
}

できあがったコードはjsdo.itに掲げた。次回は,3つ手を加えてお題を仕上げるつもりだ。まず,ボールを床で弾ませる。つぎに,ステージから見えなくなったオブジェクトは,メモリから片づけたい。そして,3次元の表現ではもうひとつ忘れてならない処理がある。それは何か。年をまたいでしまうので,お正月の宿題としよう(つぎのjsdo.itのアニメーションをよく見ていただくとヒントがある⁠⁠。では,よいお年をお迎えいただきたい!

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書