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

第17回 簡単なクラスを定義する

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

前回の第16回3次元空間で座標を回すは,星形の3次元座標をy軸で水平に回し,2次元平面に透視投影したうえでアニメーションにした第16回コード2星形の3次元座標に遠近法を加えてy軸で回すアニメーション」)。この表現はもうこのまま変えない。今回は,スクリプトの書き方に磨きをかける。具体的には,簡単なクラスを定義してみよう。

透視投影のメソッドをオブジェクトに定める

オブジェクトをクラスからつくると,プロパティとして備わったデータをそのオブジェクトのメソッドで操作できる(IT用語辞典クラス参照)。今回のお題で,3次元空間座標をObjectインスタンスでつくった。これをクラスとして定義し直し,透視投影座標はそのメソッドで求めることにする。3次元空間座標のクラス(Point3D)と透視投影のメソッド(getProjetedPoint())が定められた暁には,3次元座標のオブジェクトをつくって,2次元座標に透視投影するステートメントはつぎのようになる。

// 3次元座標のオブジェクトをつくる
var point3D = new Point3D(x座標, y座標, z座標);

// 3次元座標を2次元座標に透視投影
var point2D = point3D.getProjetedPoint(焦点距離);

もっとも,オブジェクトを参照してメソッドを呼ぶには,クラスが必ずしも定義されていなくてもよい。まず,こちらから先に進めよう。第16回コード2は,Ticker.tickイベントのリスナー関数(rotate())から,別の関数(getProjetedPoint())の引数に焦点距離(focalLength)と3次元座標のオブジェクト(point)を与えて,2次元座標に透視投影した。そのステートメントは,つぎのようにオブジェクトを参照した同じ名前のメソッド(getProjetedPoint())の呼出しに書き替える。

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] = getProjetedPoint(focalLength, point);
    points2D[i] = point.getProjetedPoint(focalLength);
  }
  draw(points2D);

つぎに手を加えるのは,3次元座標のオブジェクトをつくる関数(newPoint3D())だ。つくったオブジェクト(point3D)にプロパティのかたちでメソッド名(getProjetedPoint)を与え,定義した関数(getProjetedPoint())の参照を代入する。これで,このプロパティをオブジェクトのメソッドとして呼び出せる。

function newPoint3D(x, y, z) {
  var point3D = {x:x, y:y, z:z};
  point3D.getProjetedPoint = getProjetedPoint;   // 関数をメソッドとして定める
  return point3D;
}

そして,オブジェクトのメソッドに定めた関数(getProjetedPoint())をつぎのように書き直す。3次元座標のオブジェクトにメソッドとして定めたのだから,引数にオブジェクト(_point3D)は受け取らなくてよい。オブジェクトのプロパティ(getProjetedPoint)に関数が与えられ,メソッドとして呼び出すとき,その関数内ではオブジェクトをthisキーワードで参照できる。したがって,引数のオブジェクトを参照していた記述は,すべてthisに書き替える。

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

これで,透視投影の関数は3次元座標のオブジェクトにメソッド(getProjetedPoint())として備わった。書き上がったスクリプトは,コード1のとおりだ。ワイヤーフレームの星形がy軸で水平に回るアニメーションは,第16回コード2と変わらない図1)。マウスポインタの水平位置に応じて,星形の回る向きと速さが変わる。

図1 星形の3次元座標がy軸で水平に回って透視投影される

図1 星形の3次元座標がy軸で水平に回って透視投影される 図1 星形の3次元座標がy軸で水平に回って透視投影される

コード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();
var points2D = [];
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 = 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;
  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);
  }
  draw(points2D);
}
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};
  point3D.getProjetedPoint = getProjetedPoint;
  return point3D;
}
function getProjetedPoint(focalLength) {
  var point2D = new createjs.Point();
  var w = focalLength / (focalLength + this.z);
  point2D.x = this.x * w;
  point2D.y = this.y * w;
  return point2D;
}

コンストラクタ関数を定める

つぎは,コンストラクタ関数を定義して呼び出す。それ自体はとても簡単だ。ただ関数を定めて,new演算子で呼び出しさえすればよい。

// コンストラクタ関数の定義
function クラス名() {}

// new演算子で呼出す
var 変数 = new クラス名();

コンストラクタ関数には値を返すreturnステートメントは書かない。new演算子で呼び出せば,新たにつくられたインスタンスが返る決まりだ。今回は,引数に渡したxyz座標をプロパティとして定めたい。そのときは,コンストラクタ内のthis参照にプロパティを与えればよい。コンストラクタ内でつくられるインスタンスは,thisで参照されるからだ。

すると,前掲コード1の3次元座標オブジェクトをつくって返す関数(newPoint3D())は,つぎのようにコンストラクタ関数として書き替えられる。なお,クラス名(Point3D)はそれらしく変えてみた。

// function newPoint3D(x, y, z) {
function Point3D(x, y, z) {
  /*
  var point3D = {x:x, y:y, z:z};
  point3D.getProjetedPoint = getProjetedPoint;
  return point3D;
  */
  this.x = x;
  this.y = y;
  this.z = z;
  this.getProjetedPoint = getProjetedPoint;
}

3次元座標をつくって配列に納める関数(createStarPoints())からは,前掲のコンストラクタ関数(Point3D())new演算子で呼出すように書き替える。

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));
    starPoints.push(new Point3D(longRadius * Math.cos(angle), longRadius * Math.sin(angle), 0));
    angle += theta;
    // starPoints.push(newPoint3D(shortRadius * Math.cos(angle), shortRadius * Math.sin(angle), 0));
    starPoints.push(new Point3D(shortRadius * Math.cos(angle), shortRadius * Math.sin(angle), 0));
    angle += theta;
  }
  return starPoints;
}

これで取りあえずクラス(Point3D)は定義でき,クラスにメソッド(getProjetedPoint())も備わった。試してみれば,星形はマウスポインタの水平位置に合わせてy軸で回る。ただし,クラスへのメソッドの定め方が標準的ではない。

著者プロフィール

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

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

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入