EaselJSのMatrix2Dクラスを使った変換行列の操作

EaselJSには、Matrix2Dクラスが用意されており、移動・回転・拡大/縮小・傾斜といったオブジェクトの変形を変換行列を使って行うことができます。ActionScript3.0のMatrixクラスと同様に3×3の正方行列なので、基本的な操作方法はFlashと似ていますが、オブジェクトへの適用方法など若干の違いはあります。今回は、いくつかのサンプルを通してEaselJSのMatrix2Dクラスを解説していきたいと思います。

Matrix2Dインスタンスの生成とオブジェクトへの適用

まず、コンストラクタでMatrix2Dインスタンスを生成してみましょう。3×3の正方行列の内、指定することができる値はa, b, c, d, tx, tyです。これらの値をコンストラクタに引数で渡します。

変換行列をオブジェクトに適用するには、decompose()を使用します。引数で対象となるオブジェクトを指定することにより、オブジェクトに変換行列が適用されます。下記の例では、正方形を水平方向に傾いた平行四辺形に変形させ、canvasの中央に配置させています。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
/*
 * a c tx
 * b d ty
 * 0 0 1
 */
var a = 1;
var b = 0;
var c = -1;
var d = 1;
var tx = _canvas.width >> 1;
var ty = _canvas.height >> 1;
var matrix = new createjs.Matrix2D(a, b, c, d, tx, ty);
matrix.decompose(_shape);

座標を移動させる translate()

DisplayObjectに定義されたgetMatrix()を使うとオブジェクトの現在の変換行列を取得することができます。取得したMatrix2Dインスタンスの値を確認してみると、初期状態では単位行列になっていることがわかります。

座標を移動させるには、translate()の第1, 2引数でx, yの移動量を指定します。結果として、Matrix2Dインスタンスのtxとtyの値が変更されます。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var matrix = _shape.getMatrix();
console.log(matrix.toString()); // 出力:[Matrix2D (a=1 b=0 c=0 d=1 tx=0 ty=0)]
matrix.translate(_canvas.width >> 1, _canvas.height >> 1);
console.log(matrix.toString()); // 出力:[Matrix2D (a=1 b=0 c=0 d=1 tx=320 ty=180)]
matrix.decompose(_shape);

回転させる rotate()

オブジェクトを回転させるには、rotate()の引数で回転させる角度をラジアン値で指定します。注意したいのは、回転がオブジェクトの基準点を中心に行われる点です。下記の例では、オブジェクトを45°回転させてcanvasの中央に配置していますが、rotate()とtranslate()の順番を逆にすると違った結果になります。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var matrix = _shape.getMatrix();
matrix.rotate(45 * createjs.Matrix2D.DEG_TO_RAD);
matrix.translate(_canvas.width >> 1, _canvas.height >> 1);
matrix.decompose(_shape);

拡大/縮小させる scale()

オブジェクトを拡大/縮小させるには、scale()の第1引数でx方向の伸縮率、第2引数でy方向の伸縮率を指定します。rotate()と同じように拡大/縮小はオブジェクトの基準点を中心に行われます。

下記の例では、45°回転させたオブジェクトをx方向に2倍、y方向に0.5倍に伸縮させて、canvasの中央に配置しています。結果として期待するのは、横に細長い菱形の図形ですが、EaselJS v0.50では期待した図形が描画されません。これが仕様なのかバグなのか、私には判断できませんが、ActionScript3.0での実行結果とは違います。Matrix2D.jsのソースを見てみましたが、scale()内でa, d, tx, tyしか処理していないことが原因と考えています。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var matrix = _shape.getMatrix();
matrix.rotate(45 * createjs.Matrix2D.DEG_TO_RAD);
matrix.scale(2, 0.5);
matrix.translate(_canvas.width >> 1, _canvas.height >> 1);
matrix.decompose(_shape);

ActionScript3.0と同じ実行結果が欲しい場合は、下記のようにすると自前で処理することができます。a, c, txにx方向の伸縮率、b, d, tyにy方向の伸縮率を掛け合わせます。また、後述するprependMatrix()やappendMatrix()を使うこともできます。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var matrix = _shape.getMatrix();
matrix.rotate(45 * createjs.Matrix2D.DEG_TO_RAD);
var scaleX = 2;
var scaleY = 0.5;
matrix.a *= scaleX;
matrix.b *= scaleY;
matrix.c *= scaleX;
matrix.d *= scaleY;
matrix.tx *= scaleX;
matrix.ty *= scaleY;
matrix.translate(_canvas.width >> 1, _canvas.height >> 1);
matrix.decompose(_shape);

引数の変換行列に現在の変換行列を乗算する prependMatrix()

prependMatrix()は、引数で渡した変換行列に現在の変換行列を乗算します。行列の乗算においては、A×BとB×Aの結果が同じとは限りません。prependMatrix()では、[引数の変換行列]×[現在の変換行列]で処理を行います。

下記の例では、回転・拡大/縮小・移動の変換行列を別々に作成し、prependMatrix()で順番に乗じています。結果として、横に細長い菱形の図形がcanvasの中央に配置されます。また、EaselJSでは引数をa, b, c, d, tx, tyで渡すprepend()も用意されています。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var angle = 45 * createjs.Matrix2D.DEG_TO_RAD;
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var rotateMatrix = new createjs.Matrix2D(cos, sin, -sin, cos, 0, 0);
var scaleX = 2;
var scaleY = 0.5;
var scaleMatrix = new createjs.Matrix2D(scaleX, 0, 0, scaleY, 0, 0);
var centerX = _canvas.width >> 1;
var centerY = _canvas.height >> 1;
var translateMatrix = new createjs.Matrix2D(1, 0, 0, 1, centerX, centerY);
var matrix = _shape.getMatrix();
matrix.prependMatrix(rotateMatrix);
matrix.prependMatrix(scaleMatrix);
matrix.prependMatrix(translateMatrix);
matrix.decompose(_shape);

現在の変換行列に引数の変換行列を乗算する appendMatrix()

appendMatrix()は、現在の変換行列に引数の変換行列を乗算します。prependMatrix()とは逆に[現在の変換行列]×[引数の変換行列]で処理を行います。

下記の例では、前述のprependMatrix()と同じ変形をオブジェクトに対して適用していますが、prependMatrix()のソースと見比べると、処理の順番が逆であることがわかると思います。また、prepend()と同様に引数をa, b, c, d, tx, tyで渡すappend()も用意されています。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var angle = 45 * createjs.Matrix2D.DEG_TO_RAD;
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var rotateMatrix = new createjs.Matrix2D(cos, sin, -sin, cos, 0, 0);
var scaleX = 2;
var scaleY = 0.5;
var scaleMatrix = new createjs.Matrix2D(scaleX, 0, 0, scaleY, 0, 0);
var centerX = _canvas.width >> 1;
var centerY = _canvas.height >> 1;
var translateMatrix = new createjs.Matrix2D(1, 0, 0, 1, centerX, centerY);
var matrix = _shape.getMatrix();
matrix.appendMatrix(translateMatrix);
matrix.appendMatrix(scaleMatrix);
matrix.appendMatrix(rotateMatrix);
matrix.decompose(_shape);

逆行列に変換する invert()

invert()は、現在の変換行列を逆行列に変換します。逆行列とは、対象となる行列に乗じると単位行列になる行列のことで、変形を元に戻すための変換行列とも言えます。

下記の例では、x方向に2倍、y方向に0.5倍に伸縮する変換行列scaleMatrixの逆行列となるinvertScaleMatrixを作成しています。出力した値を確認するとx方向に0.5倍、y方向に2倍の変換行列になっていることがわかります。また、描画された図形を確認するとscaleMatrixの後にinvertScaleMatrixを乗じているため、拡大/縮小の変形は適用されていないことがわかります。

JavaScript

_shape = new createjs.Shape();
_shape.graphics.beginFill("#0066cc").drawRect(-50, -50, 100, 100).endFill();
_stage.addChild(_shape);
var angle = 45 * createjs.Matrix2D.DEG_TO_RAD;
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var rotateMatrix = new createjs.Matrix2D(cos, sin, -sin, cos, 0, 0);
var scaleX = 2;
var scaleY = 0.5;
var scaleMatrix = new createjs.Matrix2D(scaleX, 0, 0, scaleY, 0, 0);
var centerX = _canvas.width >> 1;
var centerY = _canvas.height >> 1;
var translateMatrix = new createjs.Matrix2D(1, 0, 0, 1, centerX, centerY);
var invertScaleMatrix = scaleMatrix.clone().invert();
console.log(scaleMatrix.toString()); // 出力:[Matrix2D (a=2 b=0 c=0 d=0.5 tx=0 ty=0)]
console.log(invertScaleMatrix.toString()); // 出力:[Matrix2D (a=0.5 b=0 c=0 d=2 tx=0 ty=0)]
var matrix = _shape.getMatrix();
matrix.prependMatrix(rotateMatrix);
matrix.prependMatrix(scaleMatrix);
matrix.prependMatrix(invertScaleMatrix);
matrix.prependMatrix(translateMatrix);
matrix.decompose(_shape);