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

第21回 水平に回す立方体の面を塗る

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

書上げたJavaScriptコードを以下にまとめよう。まずは,クラスの定めがコード1だ。面のクラス(Face)には塗り色のプロパティ(color)を加えた。また,数学的な計算のためのクラス(MathUtils)を新たに備えた。このクラスのメソッドはすべて静的に(クラスを直接参照して)呼び出す。

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

// Point3D
function Point3D(x, y, z) {
  this.x = x;
  this.y = y;
  this.z = z;
}
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;
};
// 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は,表向きの面だけを塗り重ねた。新たに定めた関数(isFront())は,2次元ベクトルの外積(MathUtils.crossProduct2D())から面が表か裏かを確かめて返している。

コード2 水平に回す立方体の表向きの面だけを描く

var stage;
var drawGraphics;
var points;
var angle = 0;
var matrix = new createjs.Matrix2D();
var stageCenterX;
var _point = new createjs.Point();
var points2D = [];
var facesVertices;
var focalLength = 300;
function initialize() {
  var canvasElement = document.getElementById("myCanvas");
  stage = new createjs.Stage(canvasElement);
  stageCenterX = canvasElement.width / 2;
  drawGraphics = createGraphics(stageCenterX, canvasElement.height / 2);
  points = createCubePoints(50);
  facesVertices = getFacesVertices();
  drawFaces(points, facesVertices);
  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;
  points2D.length = 0;
  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;
    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(5, 4, 7, 6, getRandomColor())
  ];
  return vertices;
}
function getRandomColor() {
  return createjs.Graphics.getRGB(Math.floor(MathUtils.getRandomInt(0, 0xFFFFFF)));
}

サンプルのコードをjsdo.itに掲げた。クラス定義のscript要素(前掲コード1は[HTML]の欄に書き分けてある。次回は,立方体の6面をすべて塗ったうえで,さらにx軸で垂直にも回るようにしたい。

ベクトルの外積とは

ベクトルの外積についてもう少し知りたい読者のために,数学的な説明を補っておく。3次元空間のベクトルAとBの外積はA×Bで表され,つぎのような新たなベクトルとして求められる表1⁠。そして,ふたつのベクトルAからBに回したとき右ネジが進む向きに定められる図7左⁠⁠。つまり,外積には交換法則が成り立たない。

表1 3次元ベクトルの外積A×Bで求められるベクトル

外積の要素外積のベクトルとふたつのベクトルの関係
角度ふたつのベクトルAとBを含む平面に垂直(図7左)
方向ベクトルAからBに向かう回転を考えたとき,その回し方で右ネジの進む方向(図7左)
大きさベクトルAとBを隣り合う2辺とした平行四辺形の面積(図7右)

図7 3次元ベクトルの外積

図7左 3次元ベクトルの外積 図7右 3次元ベクトルの外積

3次元空間のベクトルAとBの位置座標を,それぞれ(ax, ay, az)および(bx, by, bzとすると,外積はつぎの式で定められる。

A×B = (aybz - azby, azbx - axbz, axby - aybx)

2次元平面で考えると,z座標値(azとbz)はつねに0だ。したがって,A×B = (0, 0, axby - aybx)となる。そこで,2次元平面のベクトルA(ax, ay)とB(bx, by)の外積は,3次元の外積のz座標値で表す。ベクトルAに対してBが右ネジの位置にあるとき値は正,左ネジなら負となる。

A×B =axby - aybx

2次元ベクトルの外積は,ほかにも使い道がある。興味がある読者は,珍味ベクトル外積3種盛りをお読みいただきたい。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書