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

第16回 3次元空間で座標を回す

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

前回の第15回「Matrix2Dクラスで座標を回す」では,星形の2次元平面座標をPointオブジェクトで定めて直線で描き,平面上で回るアニメーションまでつくった第15回コード3「マウスポインタの水平位置に応じてアニメーションが回る向きと速さを変える」)。今回は座標を3次元に拡げて,y軸で水平方向に回転させる。そのとき,遠近法の計算も加えていく。

z座標を加えてy軸で回す

前回述べた通り,EaselJSライブラリそのものには3次元空間を扱うクラスがない。Pointクラスもxy座標しかもたない。そこで,3次元空間の座標はObjectインスタンスでつくることにする。もちろん,プロパティはxyz座標だ。

{x:x座標値, y:y座標値, z:z座標値}

第15回コード3に新たな関数(newPoint3D())を加え,戻り値は3次元座標のオブジェクトとする。すでに定めてあった星形の座標をつくる関数(createStarPoints())は,Point()コンストラクタの呼出しを,この新たな関数に差し替える。z座標値はすべて0でよい。

function createStarPoints(numVertices, longRadius, shortRadius) {
  var starPoints = [];
  var angle = Math.PI;
  var theta = angle / numVertices;
  angle /= -2;
  for (var i = 0; i < numVertices; i++) {
    // starPoints.push(new createjs.Point(longRadius * Math.cos(angle), longRadius * Math.sin(angle)));
    starPoints.push(newPoint3D(longRadius * Math.cos(angle), longRadius * Math.sin(angle), 0));
    angle += theta;
    // starPoints.push(new createjs.Point(shortRadius * Math.cos(angle), shortRadius * Math.sin(angle)));
    starPoints.push(newPoint3D(shortRadius * Math.cos(angle), shortRadius * Math.sin(angle), 0));
    angle += theta;
  }
  return starPoints;
}
function newPoint3D(x, y, z) {
  var point3D = {x:x, y:y, z:z};
  return point3D;
}

この書替えをしても,コードはそのまま動く。もちろん,z座標値など見てもいないし,回転は2次元平面,つまり第15回コード3と変わらないアニメーションになる図1)。コードを書くときにはこのように小さな段階で,正しく動いているかどうか確かめられるように進めるとよい。

図1 星形が2次元平面で回る

図1 星形が2次元平面で回る 図1 星形が2次元平面で回る

では,座標をy軸で水平に回そう。だが,どうやって。前回使ったMatrix2D.transformPoint()メソッドは,2次元平面の座標変換しか扱えない。ここで,文字どおり視点を変えよう。3次元空間を真上から見下ろす図2)。今回のお題は座標をy軸で回すのだから,y座標はずっとそのままだ。つまり,xz平面で座標を変換すれば済むことになる※1)。

図1 xz平面で座標を回す

図1 xz平面で座標を回す

だったら,(x, z)座標を(x, y)に見立てて,Matrix2D.transformPoint()メソッドで回した座標を得る。そのy座標をz座標に入れてやればよい。回転するアニメーションの関数(rotate())につぎのような手を加える。Matrix2D.transformPoint()メソッドには3次元座標のxとzを渡す。結果をひとまず入れるPointオブジェクトは予め変数(_point)に与えた。そのうえで,結果のPointオブジェクトのxy座標値を,3次元座標オブジェクトのxとzにそれぞれ定める。

var _point = new createjs.Point();

function rotate(eventObject) {
  var count = points.length;
  matrix.identity().rotate(angle);
  for (var i = 0; i < count; i++) {
    var point = points[i];
    // matrix.transformPoint(point.x, point.y, point);
    matrix.transformPoint(point.x, point.z, _point);
    point.x = _point.x;
    point.z = _point.y;
  }
  draw(points);
}

これで,3次元座標はy軸で回転する。星形が水平に回るアニメーションになった。第13回コード3を書直した全体がつぎのコード1だ。マウスポインタの水平位置に応じて回転の向きと速さも変わる。だが,アニメーションをじっと見続けていると違和感を覚えるだろう。見方によっては,回っているというより,単に水平に伸び縮みしているともいえる図3)。この表現でよいなら,初めからShapeインスタンスを水平方向に伸縮した方が早い。こうなってしまうのは,座標の計算に遠近法が加えられていないからだ。

図3 遠近法が加わらない回る星のアニメーション

図3 遠近法が加わらない回る星のアニメーション 図3 遠近法が加わらない回る星のアニメーション

コード1 星形の3次元座標をy軸で回すアニメーション

var stage;
var drawGraphics;
var points;
var angle = Math.PI / 36;
var matrix = new createjs.Matrix2D();
var stageCenterX;
var _point = new createjs.Point();
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageCenterX = canvasElement.width / 2;
  drawGraphics = createGraphics(stageCenterX, canvasElement.height / 2);
  points = createStarPoints(5, 65, 25);
  draw(points);
  createjs.Ticker.addEventListener("tick", rotate);
  stage.addEventListener("stagemousemove", setAngle);
}
function setAngle(eventObject) {
  var mouseX = eventObject.stageX;
  angle = (mouseX - stageCenterX) * 1 / 300;
}
function rotate(eventObject) {
  var count = points.length;
  matrix.identity().rotate(angle);
  for (var i = 0; i < count; i++) {
    var point = points[i];
    matrix.transformPoint(point.x, point.z, _point);
    point.x = _point.x;
    point.z = _point.y;
  }
  draw(points);
}
function createGraphics(x, y) {
  var drawShape = new createjs.Shape();
  drawShape.x = x;
  drawShape.y = y;
  stage.addChild(drawShape);
  return drawShape.graphics;
}
function draw(points) {
  var count = points.length;
  var point = points[count - 1];
  drawGraphics.clear()
  .beginStroke("mediumblue")
  .setStrokeStyle(3)
  .moveTo(point.x, point.y);
  for (var i = 0; i < count; i++) {
    point = points[i];
    drawGraphics.lineTo(point.x, point.y);
  }
  stage.update();
}
function createStarPoints(numVertices, longRadius, shortRadius) {
  var starPoints = [];
  var angle = Math.PI;
  var theta = angle / numVertices;
  angle /= -2;
  for (var i = 0; i < numVertices; i++) {
    starPoints.push(newPoint3D(longRadius * Math.cos(angle), longRadius * Math.sin(angle), 0));
    angle += theta;
    starPoints.push(newPoint3D(shortRadius * Math.cos(angle), shortRadius * Math.sin(angle), 0));
    angle += theta;
  }
  return starPoints;
}
function newPoint3D(x, y, z) {
  var point3D = {x:x, y:y, z:z};
  return point3D;
}
※1
さらにzy平面の座標変換も加えれば,x軸で垂直に回すこともできる。ただ,今回のお題のようなひとつの平面図形では,xyの2軸で回すとむしろどう動いているのか捉えにくくなってしまう。拙著WebクリエイターのためのCreateJSスタイルブックのサンプルとしてjsdo.itに掲げたコードは,矩形のビットマップ6枚で立方体をつくり,水平および垂直に回している。ただし,Bitmapインスタンスには次項で説明する遠近法は加えられない。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入