ActionScript 3.0で始めるオブジェクト指向スクリプティング

第35回Matrix3Dクラスによる座標変換

3次元空間における(平行)移動や回転および伸縮は、Matrix3Dクラスを使って行うこともできる。Matrixというのは、数学の行列を意味する(あの映画のことではない⁠⁠。3次元座標を変換するために、内部的に行列による演算が行われるためにこの呼び名がつけられた。もっとも、基本的な処理であれば、私たちがその計算を行う必要はない。座標変換の性質と特徴を知っておけば足りる[1]⁠。

DisplayObjectインスタンスのもつMatrix3Dオブジェクトを操作する

DisplayObjectインスタンスの3次元空間における座標変換の情報をもつMatrix3Dオブジェクトは、PerspectiveProjectionオブジェクトと同じくTransformクラスのプロパティで、Transform.matrix3Dから得る。そして、Transformオブジェクトの参照は、DisplayObject.transformプロパティがもっていた第33回図6再掲⁠。

第33回図6 プロパティDisplayObject.transformからTransform.perspectiveProjectionの参照を得る(再掲)
第33回図6 プロパティDisplayObject.transformからTransform.perspectiveProjectionの参照を得る(再掲)

DisplayObjectインスタンスに平行移動や回転あるいは伸縮の変換を加えるには、Matrix3Dオブジェクトに対してそれぞれMatrix3D.prependTranslation(), Matrix3D.prependRotation(), Matrix3D.prependScale()といったメソッドが用いられる。なお、各メソッドの頭についているprependという語の意味は後で説明する。

Matrix3Dオブジェクト.prependTranslation(移動x座標値, 移動y座標値, 移動z座標値)
Matrix3Dオブジェクト.prependRotation(回転度数値, 回転軸)
Matrix3Dオブジェクト.prependScale(水平伸縮率, 垂直伸縮率, 奥行き伸縮率)

それでは、タイムラインに置いたMovieClipインスタンスmy_mcを、y軸で水平に45度回転してみよう。Matrix3D.prependRotation()メソッドの第1引数には、回転する角度を度数で渡す。そして、第2引数の回転軸は、Vector3Dクラスの3つの定数Vector3D.X_AXIS, Vector3D.Y_AXIS, Vector3D.Z_AXISから指定する。

// タイムライン: メイン
// タイムラインにMovieClipインスタンスmy_mcを配置
var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
myMatrix3D.prependRotation(45, Vector3D.Y_AXIS);

ところが、このフレームアクションを[ムービープレビュー]で試すと、以下のようなランタイムエラー#1009が示される。これは「オブジェクトを参照してプロパティにアクセスしようとしているステートメントで、実際にはオブジェクトが存在しない場合に起こる」エラーだった(第22回「MovieClipシンボルにクラスを定義する」フレームアクションをクラスに移行する⁠。

TypeError: Error #1009: nullのオブジェクト参照のプロパティまたはメソッドにアクセスすることはできません。

実際、MovieClipインスタンスのDisplayObject.transformプロパティから取出したTransform.matrix3Dプロパティの値をtrace()関数で[出力]すると、nullが示される図1⁠。なぜなら、タイムラインのインスタンスすべてに3次元座標の操作を加える必要はない。そして、インスタンスに3次元空間の情報をもたせれば、少なからず負荷が増える。そのため、DisplayObjectインスタンスは、デフォルトではMatrix3Dオブジェクトをもたないのである。

図1 MovieClipインスタンスのDisplayObject.transform.matrix3Dプロパティはnull
図1 MovieClipインスタンスのDisplayObject.transform.matrix3Dプロパティはnull

DisplayObjectインスタンスにMatrix3Dオブジェクトを与えるには、何かしら3次元座標空間の操作を加えればよい。スクリプトであれば、簡単なのはDisplayObject.zプロパティに0を設定する。z座標値はデフォルトが0なので、見た目は変わりない。しかし、3次元空間の操作が行われたことにより、インスタンスのDisplayObject.transformプロパティのもつTransform.matrix3DプロパティにMatrix3Dオブジェクトが生成される。

// タイムライン: メイン
// タイムラインにMovieClipインスタンスmy_mcを配置
my_mc.z = 0;   // z座標を操作
var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
trace(myMatrix3D);   // 出力: [object Matrix3D]
myMatrix3D.prependRotation(45, Vector3D.Y_AXIS);

これで、インスタンスのMatrix3Dオブジェクトをメソッドで操作して、座標が変換できる。Matrix3D.prependRotation()メソッドにより、MovieClipインスタンスはy軸で水平に45度回転する図2⁠。なお、回転は軸の正の方向に対して、右ネジを回す向きが正の角度になる。

図2 z座標を設定するとMatrix3Dオブジェクトが生成されて3次元空間の操作は可能になる
図2 z座標を設定するとMatrix3Dオブジェクトが生成されて3次元空間の操作は可能になる

動的に配置したビットマップをマウスポインタに応じて水平回転させる

それでは、前回のスクリプト2と同じくビットマップのインスタンスをタイムラインに動的に置いて、マウスポインタの水平位置に応じて水平に回してみようスクリプト1⁠。プロパティDisplayObject.rotationX/DisplayObject.rotationY/DisplayObject.rotationZがインスタンスの角度そのものを設定したのに対して、Matrix3D.prependRotation()メソッドは第1引数の角度を現在の角度に加えるということに注意してほしい。なお、[ライブラリ]のビットマップには[クラス]としてImage0を設定してある。

スクリプト1 インスタンスの基準点からのマウスポインタの水平位置に応じてインスタンスを水平回転
// タイムライン: メイン
// [ライブラリ]のビットマップに[クラス]としてImage0を設定
var mySprite:Sprite = new Sprite();
var myBitmap:Bitmap = new Bitmap(new Image0(0, 0));
var nX:Number = stage.stageWidth / 2;
var nDeceleration:Number = 0.3;
mySprite.z = 0;
var myMatrix3D:Matrix3D = mySprite.transform.matrix3D;
trace(myMatrix3D);
mySprite.x = nX;
mySprite.y = stage.stageHeight / 2;
myBitmap.x = -myBitmap.width / 2;
myBitmap.y = -myBitmap.height / 2;
addChild(mySprite);
mySprite.addChild(myBitmap);
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = (mouseX - nX) * nDeceleration;
  myMatrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
}

[ムービープレビュー]を確かめると、[ライブラリ]のビットマップがSpriteインスタンスの入れ子となって配置され、マウスポインタの水平位置に応じて水平に回転する図3⁠。

図3 インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転
図3 インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転

インスタンスに垂直の回転を加えてみる

インスタンスが水平に回せたら、垂直にも回してみたくなるのが人情だ。おそらく、水平座標の扱いと同じ処理を垂直座標でも行えばよいと見当がつく。

// タイムライン: メイン
// [ライブラリ]のビットマップに[クラス]としてImage0を設定
var mySprite:Sprite = new Sprite();
var myBitmap:Bitmap = new Bitmap(new Image0(0, 0));
var nX:Number = stage.stageWidth / 2;
var nY:Number = stage.stageWidth / 2;
var nDeceleration:Number = 0.3;
mySprite.z = 0;
var myMatrix3D:Matrix3D = mySprite.transform.matrix3D;
trace(myMatrix3D);
mySprite.x = nX;
mySprite.y = stage.stageHeight / 2;
myBitmap.x = -myBitmap.width / 2;
myBitmap.y = -myBitmap.height / 2;
addChild(mySprite);
mySprite.addChild(myBitmap);
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = (mouseX - nX) * nDeceleration;
  var nRotationX:Number = (mouseY - nY) * nDeceleration;
  myMatrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
  myMatrix3D.prependRotation(nRotationX, Vector3D.X_AXIS);
}

[ムービープレビュー]を試すと、確かに水平にも垂直にも回る。しかし、おそらく納得のいく動きではないだろう。というのは、マウスポインタを動かした方向とインスタンスの回る向きが一致しないからだ図4⁠。

図4 マウスポインタの移動方向と回転の方向が必ずしも一致しない
図4 マウスポインタの移動方向と回転の方向が必ずしも一致しない

こういうときは、もっとスクリプトを単純にして確かめるとよい。ステージの真ん中にMovieClipインスタンスmy_mcを置いてみよう図5⁠。そのうえで、つぎのようなフレームアクションを試してみる。さて、インスタンスはどうなるか。

// タイムライン: メイン
// タイムラインにMovieClipインスタンスmy_mcを配置
my_mc.z = 0;
var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
myMatrix3D.prependRotation(90, Vector3D.Z_AXIS);   // z軸で90度回転
myMatrix3D.prependRotation(45, Vector3D.Y_AXIS);   // y軸で45度回転
図5 ステージの真ん中にMovieClipインスタンスを配置
図5 ステージの真ん中にMovieClipインスタンスを配置

結果は下図6の左の図のようになる。おそらく、右の図を予想しただろう。実はこれが、メソッド名の頭につけられたprependの意味につながる。"prepend"は「前に加える」という意味だ。もっとも、英和辞典には載っていないらしい[2]⁠。⁠後に加える」を意味する"append"(こちらは辞書に載っている)の反対語だ。つまり、前に紹介したMatrix3Dクラスのメソッドには、prependをappendに置換えたものが対で存在する。

図6 ふたつの軸でインスタンスを回転した結果
図6 ふたつの軸でインスタンスを回転した結果

問題はprependが何の前なのかということだ。それは、Matrix3Dクラスを使った座標変換の適用順序を指す。つまり、prependというのは、その変換を最初に加えるということになる。よって、前記フレームアクションでは、まずz軸による90度の回転を変換処理の先頭に置く。そして、つぎのステートメントでは、y軸による45度の回転をさらにその前につけ加えた。したがって、まずy軸で45度傾け、つぎにz軸で90度回すことになる。

では、インスタンスを上図6の右図のように回転したければ、前掲フレームアクションでふたつのMatrix3D.prependRotation()メソッドのステートメントの順序を入替えればよい。そうなると、マウスポインタの位置に応じて水平および垂直にインスタンスを回転するその前に試したスクリプトについても、水平と垂直の処理を逆の順序にすれば済むだろうか。

実は、インスタンスに加えられる座標変換は、スクリプトに書いたふたつの回転のメソッドの処理だけではない。そのため、このふたつのステートメントの順序だけ考えたのでは足りない。インスタンスに加わるすべての座標変換を知ったうえで、その順序はappendのメソッドで整えることになる。マウスポインタの位置に応じてインスタンスを自由に回転するお題は、次回が解決編となる。

今回解説した次のサンプルファイルがダウンロードできます。

おすすめ記事

記事・ニュース一覧