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

第22回 立方体の6面をx軸とy軸で回す

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

立方体をx軸でも回す

いよいよ,立方体の回転にx軸も加える。つまり,マウスポインタの位置に応じて,立方体が上下左右に回ることになる。さて,どうしたら3次元座標をx軸で回転できるか。実は,y軸で回すのと変わらない。2次元平面で座標を変換するMatrix2D.transformPoint()メソッドが使える。y軸で回転するときは,⁠視点」を変えて真上から見たxz平面で座標変換した(第16回「3次元空間で座標を回す」z座標を加えてy軸で回すの項参照⁠⁠。今度は,真横から見てzy平面で回せばよい図2⁠。

図2 zy平面で座標を回す

図2 zy平面で座標を回す

3次元空間座標をxy両軸で回すなら,クラス(Point3D)のメソッドとして備えた方が使いやすいだろう。引数には,つぎのようにMatrix2Dオブジェクトと回転軸を渡す。回転軸は"x"か"y"かの文字列で示す。

Point3DオブジェクトrotatePoint(Matrix2Dオブジェクト, 回転軸)

例によって,メソッドはできたものとして,それを呼び出すコードから以下のように書き替えよう。回転するMatrix2Dオブジェクトは,x軸とy軸のふたつを分けて変数(matrixXとmatrixY)に定める。回転角を決めるStage.stagemousemoveイベントのリスナー関数setAngle()は,マウスポインタのxy座標からy軸とx軸それぞれの回転角を変数(angle)のPointオブジェクトに与える。

立方体を回すTicker.tickイベントのリスナー関数(rotate())は,y軸とx軸のMatrix2Dオブジェクト(matrixXとmatrixY)をそれぞれ定められた角度回転する。そして8頂点座標は,できたことにしたクラス(Point3D)のメソッド(rotatePoint())にMatrix2Dオブジェクトと回転軸の引数を渡して回す。なお,クラスのメソッドで座標を変換するようにしたため,Matrix2D.transformPoint()メソッドで使い回すPointオブジェクトはクラスに定めることとし,変数(_point)からは除く。

// var matrix = new createjs.Matrix2D();
var matrixX = new createjs.Matrix2D();
var matrixY = new createjs.Matrix2D();

// var _point = new createjs.Point();

function setAngle(eventObject) {
  var mouseX = eventObject.stageX;
  var mouseY = eventObject.stageY;
  angle.y = (mouseX - stageCenter.x) / 300;
  angle.x = (mouseY - stageCenter.y) / -300;
}
function rotate(eventObject) {
  var count = points.length;
  points2D.length = 0;
  // matrix.identity().rotate(angle.y);
  matrixY.identity().rotate(angle.y);
  matrixX.identity().rotate(angle.x);
  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;
    */
    point.rotatePoint(matrixY, "y");
    point.rotatePoint(matrixX, "x");
    points2D[i] = point.getProjetedPoint(focalLength);
  }
  drawFaces(points2D, facesVertices);
}

改めて,クラス(Point3D)に座標回転のメソッド(rotatePoint())を加えよう。ふたつの引数はもう決めた。また,⁠視点」を変えることでMatrix2D.transformPoint()メソッドにより,y軸とx軸それぞれの回転ができるという考え方も確かめた。そこで,自身のプロパティである3次元座標を回転するメソッドはつぎのように定めた。

Point3D._point = new createjs.Point();
Point3D.prototype.rotatePoint = function(matrix, axis) {
  if (axis == "y") {
    matrix.transformPoint(this.x, this.z, Point3D._point);
    this.x = Point3D._point.x;
    this.z = Point3D._point.y;
  } else if (axis == "x") {
    matrix.transformPoint(this.z, this.y, Point3D._point);
    this.z = Point3D._point.x;
    this.y = Point3D._point.y;
  }

};

これらの手を加えれば,晴れて立方体がマウスポインタの位置に応じて,y軸およびx軸で上下左右に回るようになる図3⁠。書上げたコードを以下にまとめよう。コード2は3つのクラスの定めだ。そして,これらのクラスを使ったインタラクティブな立方体のアニメーションがコード3のスクリプトである。

図3 立方体がマウスポインタの位置に応じて上下左右に回る

図3左 立方体がマウスポインタの位置に応じて上下左右に回る 図3右 立方体がマウスポインタの位置に応じて上下左右に回る

コード2 3次元座標と面および数学計算のクラス定義

// Point3D
function Point3D(x, y, z) {
  this.x = x;
  this.y = y;
  this.z = z;
}
Point3D._point = new createjs.Point();
Point3D.prototype.getProjetedPoint = function(focalLength) {
  var point2D = new createjs.Point();
  var w = focalLength / (focalLength + this.z);
  point2D.x = this.x * w;
  point2D.y = this.y * w;
  return point2D;
};
Point3D.prototype.rotatePoint = function(matrix, axis) {
  if (axis == "y") {
    matrix.transformPoint(this.x, this.z, Point3D._point);
    this.x = Point3D._point.x;
    this.z = Point3D._point.y;
  } else if (axis == "x") {
    matrix.transformPoint(this.z, this.y, Point3D._point);
    this.z = Point3D._point.x;
    this.y = Point3D._point.y;
  }
};
// Face
function Face(pos0, pos1, pos2, pos3, color) {
  this.length = 4;
  this.color = color;
  this[0] = pos0;
  this[1] = pos1;
  this[2] = pos2;
  this[3] = pos3;
}
Face.prototype.getFacePoints = function (points) {
  var faces = this.length;
  var facePoints = [];
  for (var i = 0; i < faces; i++) {
    facePoints[i] = points[this[i]];
  }
  return facePoints;
};
// MathUtils
var MathUtils = {};
MathUtils.getRandomInt = function(min, max) {
  if (min > max) {
    var temp = min;
    min = max;
    max = temp;
  }
  var randomNumber = Math.random() * (max - min) + min;
  return Math.floor(randomNumber);
};
MathUtils.subtractVectors = function(vector0, vector1) {
  var vectorX = vector1.x - vector0.x;
  var vectorY = vector1.y - vector0.y;
  return new createjs.Point(vectorX, vectorY);
};
MathUtils.crossProduct2D = function(vector0, vector1) {
  return vector0.x * vector1.y - vector0.y * vector1.x;
}

コード2 マウスポインタの位置に応じて立方体をy軸およびx軸で回す

var stage;
var drawGraphics;
var points;
var angle = new createjs.Point();
var matrixX = new createjs.Matrix2D();
var matrixY = new createjs.Matrix2D();
var stageCenter = new createjs.Point();
var points2D = [];
var facesVertices;
var focalLength = 300;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageCenter.x = canvasElement.width / 2;
  stageCenter.y = canvasElement.height / 2;
  drawGraphics = createGraphics(stageCenter.x, stageCenter.y);
  points = createCubePoints(50);
  facesVertices = getFacesVertices();
  drawFaces(points, facesVertices);
  createjs.Ticker.addEventListener("tick", rotate);
  stage.addEventListener("stagemousemove", setAngle);
}
function setAngle(eventObject) {
  var mouseX = eventObject.stageX;
  var mouseY = eventObject.stageY;
  angle.y = (mouseX - stageCenter.x) / 300;
  angle.x = (mouseY - stageCenter.y) / -300;
}
function rotate(eventObject) {
  var count = points.length;
  points2D.length = 0;
  matrixY.identity().rotate(angle.y);
  matrixX.identity().rotate(angle.x);
  for (var i = 0; i < count; i++) {
    var point = points[i];
    point.rotatePoint(matrixY, "y");
    point.rotatePoint(matrixX, "x");  //
    points2D[i] = point.getProjetedPoint(focalLength);
  }
  drawFaces(points2D, facesVertices);
}
function drawFaces(points, faces) {
  var numFaces = faces.length;
  drawGraphics.clear();
  for (var i = 0; i < numFaces; i++) {
    var face = faces[i];
    var facePoints = face.getFacePoints(points);
    if (isFront(facePoints)) {
      draw(facePoints, face.color);
    }
  }
  stage.update();
}
function isFront(facePoints) {
  var origin = facePoints[0];
  var point0 = MathUtils.subtractVectors(origin, facePoints[1]);
  var point1 = MathUtils.subtractVectors(origin, facePoints[2]);
  return (0 <= MathUtils.crossProduct2D(point0, point1));
}
function draw(points, color) {
  var count = points.length;
  var point = points[count - 1];
  drawGraphics
  .beginFill(color)
  .moveTo(point.x, point.y);
  for (var i = 0; i < count; i++) {
    point = points[i];
    drawGraphics.lineTo(point.x, point.y);
  }
}
function createGraphics(x, y) {
  var drawShape = new createjs.Shape();
  drawShape.x = x;
  drawShape.y = y;
  stage.addChild(drawShape);
  return drawShape.graphics;
}
function createCubePoints(halfEdge) {
  var cubePoints = [
    new Point3D(-halfEdge, -halfEdge, -halfEdge),
    new Point3D(halfEdge, -halfEdge, -halfEdge),
    new Point3D(halfEdge, halfEdge, -halfEdge),
    new Point3D(-halfEdge, halfEdge, -halfEdge),
    new Point3D(-halfEdge, -halfEdge, halfEdge),
    new Point3D(halfEdge, -halfEdge, halfEdge),
    new Point3D(halfEdge, halfEdge, halfEdge),
    new Point3D(-halfEdge, halfEdge, halfEdge)
  ];
  return cubePoints;
}
function getFacesVertices() {
  var vertices = [
    new Face(0, 1, 2, 3, getRandomColor()),
    new Face(1, 5, 6, 2, getRandomColor()),
    new Face(4, 0, 3, 7, getRandomColor()),
    new Face(4, 5, 1, 0, getRandomColor()),
    new Face(6, 7, 3, 2, getRandomColor()),
    new Face(5, 4, 7, 6, getRandomColor())
  ];
  return vertices;
}
function getRandomColor() {
  return createjs.Graphics.getRGB(Math.floor(MathUtils.getRandomInt(0, 0xFFFFFF)));
}

第20回から3回にわたって取り組んできたお題は,これで仕上がった。jsdo.itにコードを掲げておこう。3つのクラスは[HTML]の欄に分けて書いた。次回は2次元平面に戻ろう。マウスポインタの動きに合わせて滑らかな線が描かれては消えてゆくサンプルを考えている。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書