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

第20回 立方体のワイヤーフレームを水平に回す

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

立方体の面を頂点番号で定める

立方体のそれぞれの面を,分けて扱わなければならないことはわかった。とはいえ,面ごとに座標をもたせるのは無駄だ。四角形の4頂点座標に6面を掛けると,24座標になる。けれど,立方体はひとつの頂点を3つの四角形が共有しているので,座標そのものは8つで済む。

そこで,立方体の8頂点にインデックス(整数番号)を与える図3⁠。すると,立方体の各面はその4頂点の番号で定められる。そして,回転の変換と透視投影は8頂点座標に加え,面を描くときに頂点番号から座標を求めればよい。そのため,頂点に定めるインデックスは,8頂点座標を入れた配列内のインデックスと揃えておく。一般に,3次元座標の変換や透視投影より,インデックスから値を探して取出す方が負荷は低い。

図3 立方体の8頂点にインデックスを与える

図3 立方体の8頂点にインデックスを与える

3次元座標(Point3D)と同じく,面もつぎのようにクラス(Face)で定めよう。コンストラクタの引数は,4頂点番号だ。また,配列(Arrayオブジェクト)と同じように扱いたいので,頂点番号はインスタンスの整数インデックス(0~3)にエレメントとして納め,インスタンスのlengthプロパティも定めた※1⁠。

function Face(pos0, pos1, pos2, pos3) {
  this.length = 4;
  this[0] = pos0;
  this[1] = pos1;
  this[2] = pos2;
  this[3] = pos3;
}

この面のクラス(Face)のインスタンスを配列に納めて返す関数(getFacesVertices())は,つぎのとおりだ。とりあえず,立方体の前と後ろの2面だけで試す。立方体の8頂点座標(points)と面の頂点番号(facesVertices)から面を描く関数(drawFaces())は,この後定める。

var points;
var facesVertices;
function initialize() {

  points = createCubePoints(50);
  facesVertices = getFacesVertices();
  // draw(points);
  drawFaces(points, facesVertices);
}

function getFacesVertices() {
  var vertices = [
    new Face(0, 1, 2, 3),
    new Face(5, 4, 7, 6)
  ];
  return vertices;
}

面(Face)のオブジェクトの配列(facesVertices)から順にエレメントを取出して,面の頂点番号を座標に直したうえでひとつひとつ描く。ひとつの面の座標の組(配列)からひと筆書きで線描する仕事は,第17回コード2の関数(draw())がほぼそのまま使える。

新たにほしいのは,立方体の8頂点の座標から面の頂点番号に対応したものを拾って返す関数だ。これは,面のクラス(Face)のメソッド(getFacePoints())として定めよう。面の頂点番号はインスタンスがもっているのだから,引数には立方体の8頂点の座標の配列を渡せばよい。戻り値は,面の頂点の座標を納めた配列だ。

このメソッドはできたとしよう。論文をあらすじから考えるのと同じで,いきなり細かい実装に手をつけるより,処理の大きな流れを先に考えた方が見通しはよくなる。また,実装の中身もはっきりするだろう。

面(Face)のオブジェクトの配列(facesVertices)から順にエレメントを取出して,すべての面を描く関数(drawFaces())はつぎのように定めた。引数は,立方体の8頂点座標が納められた配列(points)と面のオブジェクトの配列(faces)だ。この関数は,前掲の初期化の関数(initialize())に加え,アニメーションのリスナー関数(rotate())からも呼び出される。

function rotate(eventObject) {

  // draw(points2D);
  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);
    draw(facePoints);
  }
  stage.update();
}

面のクラス(Face)のメソッド(getFacePoints())ができたことにしてしまえば,とくに難しいところはない。ひとつ補っておくと,1面を線描する関数(draw())につぎのような手直しが加わる。複数の面を描くことになったので,Graphics.clear()メソッドの呼出しは前掲のすべての面を描く関数(drawFaces())から行う。Stage.update()メソッドの呼出しも同じだ。

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

今回の課題で残るは,面のクラス(Face)に新たに加えるメソッド(getFacePoints())だ。透視投影した立方体の8頂点座標が納められた配列を引数(points)に受け取って,自らの4頂点番号に合った座標エレメントを取り出し,頂点番号の順に新たな配列(facePoints)に入れて返す。予め,8頂点座標の配列内のインデックスと頂点番号は揃えておいた。したがって,自らに定められた4つの頂点番号から,そのインデックスの座標エレメントを取り出せばよい。

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

これで,アニメーションの骨組みはできた。確かめてみると,立方体の前面と後面が,マウスポインタの位置に応じて水平に回る図4⁠。では,以下のように立方体の両側面も座標の配列を返す関数(getFacesVertices())に含めよう。また,面のワイヤーフレームを描く関数(draw())における線幅の指定は細くする。

図4 立方体の前面と後面がマウスポインタの位置に応じて水平に回る

図4 立方体の前面と後面がマウスポインタの位置に応じて水平に回る 図4 立方体の前面と後面がマウスポインタの位置に応じて水平に回る

function draw(points) {

  drawGraphics
  .beginStroke("mediumblue")
  .setStrokeStyle(1)
  .moveTo(point.x, point.y);

}

function getFacesVertices() {
  var vertices = [
    new Face(0, 1, 2, 3),
    new Face(1, 5, 6, 2),
    new Face(4, 0, 3, 7),
    new Face(5, 4, 7, 6)
  ];
  return vertices;
}

読者の中には,初めから立方体の4面を入れておけば早かったと思われた方もあろう。けれど実は,筆者が初めてコードを試したとき,立方体の1面しか描かれなかった。理由を探るために,ワイヤーフレームを描く関数(draw())が呼び出されているか,引数の座標は正しいかといったことを調べた。原因は関数に,前述したGraphics.clear()メソッドの呼出しが残っていたためだった。こうした場合,面の数が多いと,その分確かめる情報が増えて煩わしいことになるのだ。

※1
配列と同じように扱いたいのなら,Arrayクラスを継承したらどうかという考えもあり得る。実際そのような意図から,EaselJS 0.7.0のColorMatrixクラスは,Arrayクラスを継承していた。しかし,0.7.1のアップデートで継承は除かれている。JavaScriptではArrayクラスを完全に継承するのは難しく,それがEaselJS 0.7.0のColorMatrixクラスでバグを生む原因になったためだ(⁠⁠CreateJS今日この頃: EaselJS 0.7.0および0.7.1で何が変わったのか」04-02ColorMatrixクラスのバグ3種盛り参照⁠⁠。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書