サイトトップ

Director Flash 書籍 業務内容 プロフィール

ActionScript 3.0 for 3D

□04 3次元座標空間でDisplayObjectインスタンスを扱う − Matrix3Dクラス

3次元空間でDisplayObjectインスタンスを扱う考え方は2次元平面と基本は同じで、次元がひとつ増えます。3次元空間の座標変換は、Matrix3Dクラスが行います。もっとも、3次元空間の変換行列を表すMatrix3Dクラスでは、行列の各要素(成分)を個別に操作する必要はほとんどありません。けれども、行列計算の基本的な性質は、知っておいた方がよいでしょう。

また、3次元空間でインスタンスを扱うときは、その表示の重ね順を考えなければなりません。インスタンスのz座標値は、表示リスト内の位置(インデックス)とは連動していないからです。なお、DisplayObjectクラスにも、基本的な3次元座標空間に関わるプロパティが備わっています。比較的単純な操作であれば、これらのプロパティで扱うこともできます。


04-01 DisplayObjectクラスのプロパティを使った3次元座標空間の操作
3次元座標空間には、水平のx軸と垂直のy軸に、奥行きのz軸が加わります。3つの座標軸は、互いに直角に交わります(これを「直交」といいます)。このように直交する座標軸で定められる座標空間を、「直交座標系」と呼びます。そして、Flash Player 10の3次元座標空間は、z軸が奥に向かって正になります(図04-000)。

図04-000■Flash Player 10の3次元直交座標系
各軸の正方向は、x軸が右、y軸は下、z軸は奥向きとなる。

[*筆者用参考] 「Vector3Dクラス」。

まず、DisplayObjectインスタンスを3次元座標空間で扱うためのプロパティからご紹介しましょう。位置座標を表すDisplayObject.x/DisplayObject.y/DisplayObject.zプロパティのほかに、各直交座標(xyz)軸に対する回転角を示すDisplayObject.rotationX/DisplayObject.rotationY/DisplayObject.rotationZや伸縮(拡大・縮小)比率のDisplayObject.scaleX/DisplayObject.scaleY/DisplayObject.scaleZといったプロパティが挙げられます(シンタックス04-001)。

シンタックス04-001■DisplayObjectインスタンスの回転角と伸縮のプロパティ
DisplayObject.rotationX/rotationY/rotationZプロパティ
文法 rotationX:Number
rotationY:Number
rotationZ:Number
プロパティ値 DisplayObjectインスタンスのデフォルト状態からのxyz各軸に対する回転角を、親(DisplayObjectContainer)インスタンスの座標空間において度数で示す。回転の向きは、各軸の正方向に対して時計回りに定められる。角度の値は±180の範囲を基準とする。
DisplayObject.scaleX/scaleY/scaleZプロパティ
文法 scaleX:Number
scaleY:Number
scaleZ:Number
プロパティ値 DisplayObjectインスタンスの基準点からx(水平)y(垂直)z(奥行き)各軸に対する伸縮率を小数値の比率で示す。実寸は1.0。マイナスの値が設定されると、インスタンスは反転する。

Tips 04-001■回転角のプロパティで±180の範囲を超える値の扱い
[ヘルプ]によれば、回転角を示すDisplayObject.rotationX/DisplayObject.rotationY/DisplayObject.rotationZプロパティで±180の「範囲を超える値は、360を加算または減算して、範囲内に収まる値になるように調整され」ると説明されています。

しかし実際には、DisplayObject.rotationプロパティとは異なり,この範囲を超えた値も設定できてしまいます。そのため、たとえば回転角を単純に増やしていくような処理では、正しく扱えない数値にまで達してしまうおそれがあります。ですから、プロパティに設定する値は、後述のように360の剰余をとるなど、予め適切な範囲に収めておいた方がよいでしょう。

DisplayObject.rotationYプロパティを使って、タイムラインに置いたインスタンスmy_mcを、マウスポインタの水平位置に応じて水平に回転させてみましょう。以下のスクリプト04-001は、マウスポインタとインスタンスとの水平座標の差に応じて回転角を計算し、その値をDisplayObject.rotationYプロパティに設定することにより、インスタンスを水平に回転させます(図04-001)。なお、インスタンスの中心を基準点に設定しています。

スクリプト04-001■インスタンスをマウスポインタの水平位置に応じて水平に回転させる
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nDeceleration:Number = 0.3;
  3. addEventListener(Event.ENTER_FRAME, xRotate);
  4. function xRotate(eventObject:Event):void {
  5.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  6.   my_mc.rotationY += nRotationY;
  7.   // trace(my_mc.rotationY);   // 確認用
  8. }

図04-001■インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転する
マウスポインタの水平位置がインスタンスの中心から離れるほど、インスタンスは速く水平に回る。

上記スクリプト04-001第7行目でコメントアウトしているtrace()関数のステートメントを有効にすると、設定されたy軸回りの角度が[出力]パネルに表示されます(図04-002)。その値は±180どころか、その範囲を大きく超える値でも設定されます(前掲Tips 04-001「回転角のプロパティで±180の範囲を超える値の扱い」参照)。しかし、内部的に扱える限度はあるはずですので、それを超えた値が設定されると、インスタンスが正しく表示されません。

図04-002■設定されたDisplayObject.rotationYプロパティの値がtrace()関数で[出力]される


プロパティには、±180の範囲を大きく超える値でも設定される。

そこで、以下のスクリプト04-002には、引数の角度を±180の範囲に変換する関数xGetDegrees()の定義が加わっています(スクリプト第11〜18行目)。この関数を使えば、さまざまな正負の数値が、つぎのように換算されます。

trace(xGetDegrees(360));   // 出力: 0
trace(xGetDegrees(270));   // 出力: -90
trace(xGetDegrees(135));   // 出力: 135
trace(xGetDegrees(-90));   // 出力: -90
trace(xGetDegrees(-270));   // 出力: 90
スクリプト04-002■引数の角度を±180の範囲に変換する関数xGetDegrees()が定義された
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nDeceleration:Number = 0.3;
  3. var nRotationY:Number = my_mc.rotationY;
  4. addEventListener(Event.ENTER_FRAME, xRotate);
  5. function xRotate(eventObject:Event):void {
  6.   nRotationY += (mouseX - nX) * nDeceleration;
  7.   nRotationY = xGetDegrees(nRotationY);
  8.   my_mc.rotationY = nRotationY;
  9.   // trace(my_mc.rotationY);   // 確認用
  10. }
  11. function xGetDegrees(nDegrees:Number):Number {
  12.   nDegrees += 180;
  13.   nDegrees %= 360;
  14.   nDegrees += 360;
  15.   nDegrees %= 360;
  16.   nDegrees -= 180;
  17.   return nDegrees;
  18. }

スクリプト04-002は、関数xGetDegrees()を定義したことに伴い,角度の処理についてもスクリプト04-001から少し修正が加わっています。まず、スクリプト第3行目で変数(nRotationY)を宣言し、±180の範囲に換算した角度の値を保持することとしました(スクリプト04-001第5行目では、同名のローカル変数を用いていました)。つぎに、スクリプト第6行目で、その変数値にマウスポインタの水平位置座標にもとづく回転角の値を加算しています。そして、スクリプト第7〜8行目は、その変数値を関数xGetDegrees()で±180の範囲に変換したうえで,DisplayObject.rotationYプロパティに設定しました。

関数xGetDegrees()(スクリプト第11〜18行目)の処理で鍵になるのは、剰余演算子%(剰余後代入演算子%=)です。剰余とは、割った余りを求めます。もし渡される角度が0以上の数値で、それを0以上360未満の範囲に換算したいときは、「角度 % 360」で導けます。扱う値の範囲を少しずつ広げていくと、下表04-001のように考えられます。

表04-001■角度の変数値を0以上360未満の値に換算する
値の範囲 演算処理(変数はnDegrees) 変換の例
0以上の値 nDegrees %= 360; 450 → 90
-360以上の値 nDegrees += 360;
nDegrees %= 360;
-270 → 90
任意の実数値 nDegrees %= 360;
nDegrees += 360;
nDegrees %= 360;
-450 → 270

表04-001の考え方で、任意の実数値を0以上360未満の角度に換算することができました。これを-180以上180未満の範囲の値にするには、初めの値に180足しておき、それを0以上360未満の数値に変換してから、180を差引けばよいでしょう。この処理を行ったのが、前掲スクリプト04-002の関数xGetDegrees()なのです。

前掲スクリプト04-002第9行目のtrace()関数を有効にすると、DisplayObject.rotationYプロパティに設定された値が[出力]されます。そして、その値が±180の範囲に変換されていることを確かめられるでしょう(図04-003)。

図04-003■DisplayObject.rotationYプロパティの値が±180の範囲に変換されている


[出力]されたDisplayObject.rotationYプロパティの値は、±180の範囲に変換されていることが確かめられる。

Tips 04-002■引数の角度を±180の範囲に変換する関数のもうひとつの定義
上記スクリプト04-002に定義した関数xGetDegrees()(スクリプト第11〜18行目)は、別の書き方も考えられます。最初の剰余をとったあと、値がプラスかマイナスかで処理を分ければ、改めて剰余を求める必要はなくなります。

function xGetDegrees(nDegrees:Number):Number {
  nDegrees += 180;
  nDegrees % =360;
  if (nDegrees < 0) {
    nDegrees += 180;
  } else {
    nDegrees -= 180;
  }
  return nDegrees;
}

つぎは、複数のビットマップの画像を、やはり水平に回転してみます(図04-004)。まとめて回すために、ビットマップはひとつのSpriteインスタンスの中に配置しましょう。Spriteインスタンスをその座標軸で回しますので、各ビットマップを置く座標はそれぞれ計算する必要があります。

図04-004■複数のビットマップ画像を水平回転する
Spriteインスタンス内に複数のビットマップ画像を配置して、Spriteインスタンスごと回す。

まずはビットマップ画像ひとつで試し、動くことを確かめてから、さらにビットマップを加えていきます。ビットマップのインスタンスは、[ライブラリ]から動的に生成することにします。そのためには、ビットマップにクラス名を設定しなければなりません。

[ライブラリ]パネルでビットマップを選び、[ビットマッププロパティ]ダイアログボックスを開きます(ビットマップを右クリックして[プロパティ]を選択するか、[ライブラリ]パネル下部の[プロパティ]ボタンをクリックします)。そして、[リンケージ]セクションの[ActionScript用に書き出し]をチェックしたうえで、[クラス]にクラス名(識別子)を入力します(図04-005上図)。最初のビットマップはクラス名を"Image0"とし、後から加えるビットマップ画像にも同じように連番の名前を設定することにします。

設定したクラスは、とくに定義する必要はありません。クラスが定義されていなければ、FlashがSWF書出し時に自動的にその名前で空のクラスを生成します。その警告のダイアログが出たら、[OK]ボタンをクリックします(図04-005下図)。

図04-005■[ビットマッププロパティ]ダイアログボックスで[リンケージ]セクションに[クラス]を設定する

[リンケージ]セクションの[ActionScript用に書き出し]をチェックして、[クラス]にクラス名を入力。クラスが定義されていなければ、自動生成される。

ビットマップは、BitmapDataクラスを継承します。そこで、[ビットマッププロパティ]ダイアログボックスの[リンケージ]セクションで、[基本クラス]のフィールドにはBitmapDataクラス(flash.display.BitmapData)がデフォルトで設定されます(図04-005上図)。

ただし、注意しなければならないのは、BitmapDataクラスがDisplayObjectクラスを継承しないことです(図04-006)。つまり、DisplayObjectContainer.addChild()メソッドの引数として渡せず(シンタックス04-002)、そのままではタイムラインに表示できないことを意味します。

図04-006■[ヘルプ]の[BitmapData]クラスの説明冒頭
BitmapDataクラスは、DisplayObjectクラスを継承しない。

シンタックス04-002■DisplayObjectContainer.addChild()メソッド
DisplayObjectContainer.addChild()メソッド
文法 addChild(child:DisplayObject):DisplayObject
概要 DisplayObjectContainerインスタンスの表示リストに子としてDisplayObjectインスタンスを加える。子インスタンスは表示リストの最後に納められ、リスト内でインデックスは最大、重ね順は最前面となる。
引数 child:DisplayObject ― DisplayObjectContainerインスタンスの表示リストに子として加えるDisplayObjectインスタンス。
戻り値 引数で渡され、子として加えられたDisplayObjectインスタンス。

Tips 04-003■DisplayObjectインスタンスと表示リスト
ひとつのDisplayObjectインスタンスは、同時に複数の表示リストに納めることはできません。したがって、すでに他のDisplayObjectContainerインスタンスの子になっているDisplayObjectインスタンスを別のDisplayObjectContainerインスタンスの表示リストに子として加えると、その子インスタンスは前のDisplayObjectContainerインスタンスの表示リストからは削除されます。

BitmapDataインスタンスは一旦Bitmapクラスのコンストラクタに引数として渡し、Bitmapインスタンスを生成します(シンタックス04-003)。BitmapクラスはDisplayObjectクラスを継承します。したがって、BitmapDataインスタンスのビットマップイメージが納められたBitmapインスタンスを、DisplayObjectContainer.addChild()メソッドの引数に渡してタイムラインに表示すればよいのです。

シンタックス04-003■Bitmapクラスのコンストラクタメソッド
Bitmap()コンストラクタ
文法 Bitmap(bitmapData:BitmapData = null, pixelSnapping:String = "auto", smoothing:Boolean = false)
概要

指定したBitmapDataオブジェクトが参照されたBitmapインスタンスを生成する。

引数

bitmapData:BitmapData ― データとして参照するBitmapDataインスタンス。デフォルト値はnull

pixelSnapping:String ― Bitmapインスタンスをピクセルに吸着させるかどうかの指定。PixelSnappingクラスの定数を用いて、文字列で渡す。デフォルト値は、PixelSnapping.AUTO(文字列"auto")。

smoothing:Boolean ― Bitmapインスタンスを拡大・縮小するときにスムージングするならtrue、その必要がなければfalseを指定する。デフォルト値はfalse

ビットマップ画像は最終的に、Spriteインスタンスのy軸を中心とした四方に、全部で4面配置するつもりです。Bitmapインスタンスの基準点は、画像イメージの左上隅になります。正方形のビットマップの1辺の半分を変数nUnitの値とすると、Spriteインスタンスの3次元座標空間に、4つのBitmapインスタンスの座標(基準点)を下図04-007のように設定することになります。

図04-007■Spriteインスタンスの3次元座標空間におけるBitmapインスタンスの配置

正方形のビットマップの1辺の半分を変数nUnitの値とすると、4つのBitmapインスタンスの座標(基準点)が図のように定められる。

1枚目のビットマップは、前面つまりSpriteインスタンス内の座標(-nUnit, -nUnit, -nUnit)に配置することにします。なお、正方形のビットマップの1辺は100ピクセルとします。前掲スクリプト04-002と同じく、Spriteインスタンスの基準点から見たマウスポインタの水平座標に応じて、Spriteインスタンスをその基準点で回しましょう。すると、フレームアクションは、つぎのスクリプト04-003のようになります。

スクリプト04-003■ビットマップをインスタンスの前面に置いてマウスポインタの水平座標に応じて回転
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImage0を設定
  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  3. var nUnit:Number = 100 / 2;
  4. var nDeceleration:Number = 0.2;
  5. var mySprite:Sprite = new Sprite();
  6. var nRotationY:Number = mySprite.rotationY;
  7. var myBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  8. addChild(mySprite);
  9. mySprite.x = nX;
  10. mySprite.y = nY;
  11. mySprite.addChild(myBitmap);
  12. myBitmap.x = -nUnit;
  13. myBitmap.y = -nUnit;
  14. myBitmap.z = -nUnit;
  15. addEventListener(Event.ENTER_FRAME, xRotate);
  16. function xRotate(eventObject:Event):void {
  17.   nRotationY += (mouseX - nX) * nDeceleration;
  18.   nRotationY = xGetDegrees(nRotationY);
  19.   mySprite.rotationY = nRotationY;
  20. }
  21. function xGetDegrees(nDegrees:Number):Number {
  22.   nDegrees += 180;
  23.   nDegrees %= 360;
  24.   nDegrees += 360;
  25.   nDegrees %= 360;
  26.   nDegrees -= 180;
  27.   return nDegrees;
  28. }

関数xGetDegrees()(スクリプト第21〜28行目)は、前掲スクリプト04-002とまったく変わっていません。リスナー関数xRotate()(スクリプト第16〜20行目)も、回す対象がSpriteインスタンスになったことを除けば、スクリプト04-002と同じ処理です。

したがって、本スクリプト04-003で新たなのは、Spriteインスタンスをタイムラインに動的に置き、その中に同じく動的に[ライブラリ]から生成したビットマップのインスタンスを配置したことです。まず、Spriteインスタンスの生成とタイムラインへの配置の処理は、つぎのとおりです。

  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  1. var mySprite:Sprite = new Sprite();
  2. var nRotationY:Number = mySprite.rotationY;
  1. addChild(mySprite);
  2. mySprite.x = nX;
  3. mySprite.y = nY;

スクリプト第5〜8行目は、Spriteインスタンスを生成して、スクリプトを記述したタイムラインに配置しています。スクリプト第1〜2行目でステージ中央の座標を変数に代入し、第9〜10行目がSpiteの位置をそのステージ中央に設定します。

つぎに、ビットマップ画像とそれを含んだBitmapインスタンスの生成と、それをSpriteインスタンス内に配置する処理です。スクリプト第7行目が、[ライブラリ]内にあるビットマップ画像Image0からインスタンスを動的につくり、前述のとおりそのインスタンスを引数に渡してBitmapインスタンスを生成しています。

注意しなければならないのは、ビットマップ画像のクラスImage0のコンストラクタを呼出すとき、引数をふたつ渡さなければならないことです。引数がないと、[コンパイルエラー]が表示されます(図04-008)。これは、ビットマップに設定したクラスがBitmapDataクラスを継承し、そのコンストラクタが幅と高さのふたつの引数を必要とすることにもとづくものと考えられます。もっとも、渡す数値はふたつとも0でよく、BitmapDataが生成されれば、その幅と高さは正しい値に設定されます。

あとは、スクリプト第11〜14行目で、BitmapインスタンスをSpriteインスタンスの子として加えたうえで、そのxyz座標を前述(図04-007)のとおり前面の位置に設定しています。

  1. var nUnit:Number = 100 / 2;
    // ビットマップのクラスのコンストラクタImage0()にふたつの引数を渡す
  1. var myBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  1. mySprite.addChild(myBitmap);
  2. myBitmap.x = -nUnit;
  3. myBitmap.y = -nUnit;
  4. myBitmap.z = -nUnit;

図04-008■ビットマップのコンストラクタに引数を渡さないと[コンパイルエラー]が表示される


ビットマップ画像のクラスのコンストラクタImage0()を呼出すとき、ふたつの引数が求められる。

これで、Spriteインスタンス内の前面にビットマップ画像のBitmapインスタンスが配置され、Spriteインスタンスはマウスポインタの水平座標に応じて、インスタンスのy軸で水平に回転します(図04-009)。

図04-009■マウスポインタの水平座標に応じて画像が水平に回転する
ビットマップ画像が前面に配置されたSpriteインスタンスは、マウスポインタの水平座標に応じてy軸で水平に回る。

さてつぎに、後面にビットマップ画像を加えてみます。ふたつめのビットマップには、クラスとしてImage1を設定しておきます。すると、Bitmapインスタンスの生成とその設定は、以下のスクリプトのようにすればよいでしょう。Bitmapインスタンスの変数名は、わかりやすいように前面をfrontBitmap、後面はbackBitmapとしました(各xyz座標値は前掲図04-007参照)。なお、後面は裏返す必要がありますので、BitmapインスタンスのDisplayObject.rotationYプロパティを180度回転しています。

var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));   // 前面
var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));   // 後面
mySprite.addChild(frontBitmap);
mySprite.addChild(backBitmap);
frontBitmap.x = -nUnit;
frontBitmap.y = -nUnit;
frontBitmap.z = -nUnit;
backBitmap.x = nUnit;
backBitmap.y = -nUnit;
backBitmap.z = nUnit;
backBitmap.rotationY = 180;   // 裏返す

もっとも、これだけでは正しい3次元の表現にはなりません。インスタンスのz座標が動いても、表示の重ね順は変わらないからです。そのため、後面の画像がつねに前面より前に表示されてしまいます(図04-010)。DisplayObjectインスタンスの重ね順の表示を変えるには、親DisplayObjectContainerインスタンスの表示リスト内における位置(インデックス)を動かさなければなりません。

図04-010■インスタンスのz座標が動いても表示の重ね順は変わらない
DisplayObjectインスタンスのz座標値に関わらず、親DisplayObjectContainerインスタンスの表示リスト内におけるDisplayObjectインスタンスの位置によって重ね順が決まる。

[イラスト] 奥行きの(z)座標と重ね順は別。

重ね順の問題は、Flashの3次元のアニメーションでたびたび出くわします。解決方法はさまざまで、ムービーや処理の内容によっても異なります。今回のスクリプトで注意しなければならないのは、ビットマップ画像が含まれたSpriteインスタンスを回していることです。そのため、Spriteインスタンス内に配置された画像のBitmapインスタンスは動きません。よって、Spriteインスタンス内の座標空間におけるBitmapインスタンスのz座標も変わらないということです。

もっとも、Spriteインスタンスは単純に水平に回っているだけです。だとすれば、インスタンス内のふたつの面のどちらが手前に来るかは、y軸回りの角度つまりDisplayObject.rotationYプロパティの値を調べれば明らかでしょう。

図04-011は、Spriteインスタンス内のふたつの面の位置を、y軸の真上から見たものです。SpriteインスタンスのDisplayObject.rotationYプロパティの値が0度のとき、前面の画像は正面を向き、後面はその真後ろに位置します(上図)。ふたつの面の重ね順が変わるのは、SpriteインスタンスのDisplayObject.rotationYプロパティの値が-90度のとき(中図)と90度のとき(下図)です。すると、SpriteインスタンスのDisplayObject.rotationYプロパティの値に対して、手前に表示される面は下表04-002のようになります。

図04-011■SpriteインスタンスのDisplayObject.rotationYプロパティ値と前面・後面の位置
rotationY = 0°

rotationY = -90°

rotationY = 90°
SpriteインスタンスのDisplayObject.rotationYプロパティの値が-90°と90°のとき、前面と後面の重ね順が入替わる。

表04-002■SpriteインスタンスのDisplayObject.rotationYプロパティ値と手前に表示される面
SpriteインスタンスのDisplayObject.rotationYプロパティ値 手前に表示される面
-180°〜-90° 後面
-90°〜90° 前面
90°〜180° 後面

インスタンスの重ね順を変えるには、親DisplayObjectContainerインスタンスの表示リスト内における位置(インデックス)を動かします。そのためのメソッドが、DisplayObjectContainer.setChildIndex()です。引数にはインデックスを変えたいDisplayObjectインスタンスと移動先インデックス番号を渡します(シンタックス04-004)。また、DisplayObjectContainer.numChildrenプロパティで、DisplayObjectContainerインスタンスの表示リストに納められた子DisplayObjectインスタンスの数が調べられます。

シンタックス04-004■DisplayObjectContainer.setChildIndex()メソッドとDisplayObjectContainer.numChildrenプロパティ
DisplayObjectContainer.setChildIndex()メソッド
文法 setChildIndex(child:DisplayObject, index:int):void
概要 DisplayObjectContainerインスタンスの表示リスト内における子のDisplayObjectインスタンスの位置を指定したインデックスに変える。他の子インスタンスのインデックスは、移動した子インスタンスの後の番号が1ずつ繰上がり、移動先の番号以降は1ずつ繰下がる。
引数

child:DisplayObject ― DisplayObjectContainerインスタンスの表示リスト内におけるインデックスを変えるDisplayObjectインスタンス。

index:int ― DisplayObjectContainerインスタンスの表示リスト内で子DisplayObjectインスタンスを移動したい位置のインデックスの整数。

戻り値 なし。
DisplayObjectContainer.numChildrenプロパティ
文法 numChildren:int
プロパティ値 [読取り専用] DisplayObjectContainerインスタンスの表示リストに納められた子DisplayObjectインスタンスの数を示す整数。

つぎのスクリプト04-004は、前面と後面のふたつのビットマップ画像をSpriteインスタンスに加え、Spriteインスタンスのy軸回りの回転角DisplayObject.rotationYプロパティの値により、ふたつの面のBitmapインスタンスの重ね順を正しく変えています。

スクリプト04-004■前面と後面が配置されたインスタンスの回転角により画像の重ね順を変える
    // フレームアクション
    // [ライブラリ]のビットマップふたつにそれぞれ[クラス]としてImage0とImage1を設定
  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  3. var nUnit:Number = 100 / 2;
  4. var nDeceleration:Number = 0.2;
  5. var mySprite:Sprite = new Sprite();
  6. var nRotationY:Number = mySprite.rotationY;
  7. var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  8. var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));
  9. addChild(mySprite);
  10. mySprite.x = nX;
  11. mySprite.y = nY;
  12. mySprite.addChild(frontBitmap);
  13. mySprite.addChild(backBitmap);
  14. frontBitmap.x = -nUnit;
  15. frontBitmap.y = -nUnit;
  16. frontBitmap.z = -nUnit;
  17. backBitmap.x = nUnit;
  18. backBitmap.y = -nUnit;
  19. backBitmap.z = nUnit;
  20. backBitmap.rotationY = 180;
  21. addEventListener(Event.ENTER_FRAME, xRotate);
  22. xSetOrder(nRotationY);
  23. function xRotate(eventObject:Event):void {
  24.   nRotationY += (mouseX - nX) * nDeceleration;
  25.   nRotationY = xGetDegrees(nRotationY);
  26.   mySprite.rotationY = nRotationY;
  27.   xSetOrder(nRotationY);
  28. }
  29. function xSetOrder(nRotationY:Number):void {
  30.   var nTop:uint = mySprite.numChildren-1;
  31.   if (nRotationY > 90 || -90 > nRotationY) {
  32.     mySprite.setChildIndex(backBitmap, nTop);
  33.   } else {
  34.     mySprite.setChildIndex(frontBitmap, nTop);
  35.   }
  36. }
  37. function xGetDegrees(nDegrees:Number):Number {
  38.   nDegrees += 180;
  39.   nDegrees %= 360;
  40.   nDegrees += 360;
  41.   nDegrees %= 360;
  42.   nDegrees -= 180;
  43.   return nDegrees;
  44. }

ふたつの面のBitmapインスタンスの生成(スクリプト第7〜8行目)と配置(スクリプト第12〜20行目)については、すでにご説明しました。Spriteインスタンスのy軸回りの回転角であるDisplayObject.rotationYプロパティの値により重ね順を変える処理は、関数xSetOrder()として定義しました(スクリプト第29〜36行目)。引数にはSpriteインスタンスのy軸回りの回転角を渡します。

まず、スクリプト第30行目で、表示リスト内の一番手前(最後)のインデックスを、DisplayObjectContainer.numChildrenプロパティから求め,変数(nTop)に取得します。つぎに、スクリプト第31行目は、引数として受取った回転角を調べ、90度より大きいか、または-90度未満となって、後面が手前になるかを判定します。

Bitmapインスタンスの重ね順は、DisplayObjectContainer.setChildIndex()メソッドで定めます。引数のインデックスはもっとも手前の値ですので、if条件がtrueと評価されれば後面(スクリプト第32行目)、falseなら前面を指定して、重ね順を一番前にもってきます。

  1. function xSetOrder(nRotationY:Number):void {
  2.   var nTop:uint = mySprite.numChildren-1;
  3.   if (nRotationY > 90 || -90 > nRotationY) {   // 後面が手前になる角度のとき
  4.     mySprite.setChildIndex(backBitmap, nTop);   // 後面が手前
  5.   } else {
  6.     mySprite.setChildIndex(frontBitmap, nTop);   // 前面が手前
  7.   }
  8. }

この関数xSetOrder()は、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)のリスナー関数xRotate()から呼出されます(スクリプト第27行目)。それだけでなく、イベントリスナーを登録した直後(スクリプト第22行目)にも、関数xSetOrder()を呼出しています。これは、最初のDisplayObject.enterFrameイベントが発生するまでの一瞬、前面のビットマップ画像の手前に後面の画像が表示されるのを避けるための処理です。

  1. addEventListener(Event.ENTER_FRAME, xRotate);
  2. xSetOrder(nRotationY);
  3. function xRotate(eventObject:Event):void {
  1.   xSetOrder(nRotationY);
  2. }

これで、ふたつのBitmapインスタンスを含んだSpriteインスタンスがマウスポインタの水平座標に応じてy軸で水平に回り、Bitmapインスタンスの重ね順もSpriteインスタンスのDisplayObject.rotationYプロパティの値に応じて正しく表示されます(図04-012)。

図04-012■Bitmapインスタンスの重ね順が正しく入替わる
Spriteインスタンスのy軸回りの回転角によって、Bitmapインスタンスの重ね順が入替わる。

Tips 04-004■[自動フォーマット]できない
上記スクリプト第31行目のif条件で、ふたつめの不等式の両辺をつぎのように入替えると、[自動フォーマット]できず、エラーが表示されます(図04-013)。けれども、[シンタックスチェック]は通り、コンパイル(SWF書出し)も正しく行えます。したがって、Flash CS4 Professionalのバグだと考えられます。

if (nRotationY >90 || nRotationY < -90) {
図04-013■不等式の両辺を入替えると[自動フォーマット]できない
右に開いた不等号(<)に続くマイナス記号(-)が問題らしい。

右に開いた不等号(<)に続くマイナス記号(-)は、[自動フォーマット]で問題を生じることがあります。今回の例では、複合代入演算子(加算後代入演算子+=)を使ったステートメントが加わると、エラーが生じるようです。たとえば、つぎのフレームアクションでも、同じエラーが表示されます。

x += 0;
if (-2 < -1) {}

それではさらに、左右2面を加えて、四方の都合4面を配置しましょう。右面と左面のビットマップには、クラスとしてそれぞれImage2とImage3を設定しておきます。Bitmapインスタンス名は、それぞれrightBitmapとleftBitmapとします。

図04-007■Spriteインスタンスの3次元座標空間におけるBitmapインスタンスの配置(再掲)

正方形のビットマップの1辺の半分を変数nUnitの値とすると、4つのBitmapインスタンスの座標(基準点)が図のように定められる。

面のBitmapインスタンスを加える処理は、これまでと変わりません(各面の位置座標は、再掲図04-007参照)。なお、なお、左右の面はy軸回りに90度、ただし互いに逆方向に回します。設定するDisplayObject.rotationYプロパティの値は、右面が-90、左面は90です。Biemapインスタンスの位置座標とy軸回りの角度の設定には、別に関数xSetPosition()を定めることにします。

var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));
var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));
var rightBitmap:Bitmap = new Bitmap(new Image2(0, 0));
var leftBitmap:Bitmap = new Bitmap(new Image3(0, 0));
mySprite.addChild(frontBitmap);
mySprite.addChild(backBitmap);
mySprite.addChild(rightBitmap);
mySprite.addChild(leftBitmap);
xSetPosition(frontBitmap, -nUnit, -nUnit, -nUnit);
xSetPosition(backBitmap, nUnit, -nUnit, nUnit, 180);
xSetPosition(rightBitmap, nUnit, -nUnit, -nUnit, -90);
xSetPosition(leftBitmap, -nUnit, -nUnit, nUnit, 90);

関数xSetPosition()の定義は以下のとおりで、5つの引数を受取ります。最初の引数から順に、DisplayObject(実際にはBitmap)インスタンス、x、y、z座標値、y軸回りの角度です。最後のy軸回りの角度には、デフォルト値として0が指定されています。関数は引数のDisplayObjectインスタンスに、指定された位置座標とy軸回りの角度を定めます。

function xSetPosition(instance:DisplayObject, nX:Number, nY:Number, nZ:Number, nRotationY:Number = 0):void {
  instance.x = nX;
  instance.y = nY;
  instance.z = nZ;
  instance.rotationY = nRotationY;
}

問題は、面の重ね順です。前後2面のときは180度で切り分けましたので、4面であれば90度刻みで判定して切替えるとよさそうに思われます。その場合、関数xSetOrder()は、つぎのような処理になるでしょう。

function xSetOrder(nRotationY:Number):void {
  var nTop:uint=mySprite.numChildren - 1;
  if (nRotationY > 90) {
    mySprite.setChildIndex(backBitmap, nTop);
    mySprite.setChildIndex(rightBitmap, nTop);
  } else if (nRotationY > 0) {
    mySprite.setChildIndex(rightBitmap, nTop);
    mySprite.setChildIndex(frontBitmap, nTop);
  } else if (nRotationY > -90) {
    mySprite.setChildIndex(frontBitmap, nTop);
    mySprite.setChildIndex(leftBitmap, nTop);
  } else {
    mySprite.setChildIndex(leftBitmap, nTop);
    mySprite.setChildIndex(backBitmap, nTop);
  }
}

しかし、実際に試してみると、隣り合ったふたつの面の重ね順を固定したまま90度回転した間場合、その回り始めまたは終わりのふたつ面の前後の表示に問題が生じます。それは、3次元の表示に遠近法が適用されるため、重ね順が手前の面を正面から回してそのまま横向きにしたとき、替わって正面になるつぎの面の前に表示されてしまうからです(図04-014)。

図04-014■横を向いた手前の面が正面向きのつぎの面の前に表示される

手前の面をそのまま回転して横向きにすると、正面になったつぎの面の前に表示されてしまう。

この重ね順を正しく表示するには、90度をさらに半分の45度ずつに分けて、隣り合うふたつの面の前後を切替えなければならないのです。ここで、「隣り合うふたつの面」といいました。でも、4つある面すべての重ね順を考えなくてよいのでしょうか。y軸を囲んで互いに垂直に接するように並べられた4面が水平に回転するかぎり、見えるのは手前の2面だけです。よって、その2面を手前にもってきさえすれば、あとの2面の順序は気にしなくて構いません。

表04-003■SpriteインスタンスのDisplayObject.rotationYプロパティ値に対して手前とつぎに表示される面
SpriteインスタンスのDisplayObject.rotationYプロパティ値 手前に表示される面 つぎに表示される面
-180°〜-135° 後面 左面
-135°〜-90° 左面 後面
-90°〜-45° 左面 前面
-45°〜0° 前面 左面
0°〜45° 前面 右面
45°〜90° 右面 前面
90°〜135° 右面 後面
135°〜180° 後面 右面

ビットマップをSpriteインスタンス内の四方に配置し、マウスポインタの水平位置に応じてy軸で水平に回すフレームアクションが、つぎのスクリプト04-005です。関数xSetOrder()が45度刻みで面の重ね順を替えている(スクリプト第30〜57行目)以外は、すでにご説明しました。

スクリプト04-005■4面が配置されたインスタンスをマウスポインタの水平位置に応じて回転する
    // フレームアクション
    // [ライブラリ]の4つのビットマップにそれぞれ[クラス]としてImage0〜Image3を設定
  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  3. var nUnit:Number = 100 / 2;
  4. var nDeceleration:Number = 0.2;
  5. var mySprite:Sprite = new Sprite();
  6. var nRotationY:Number = mySprite.rotationY;
  7. var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  8. var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));
  9. var rightBitmap:Bitmap = new Bitmap(new Image2(0, 0));
  10. var leftBitmap:Bitmap = new Bitmap(new Image3(0, 0));
  11. addChild(mySprite);
  12. mySprite.x = nX;
  13. mySprite.y = nY;
  14. mySprite.addChild(frontBitmap);
  15. mySprite.addChild(backBitmap);
  16. mySprite.addChild(rightBitmap);
  17. mySprite.addChild(leftBitmap);
  18. xSetPosition(frontBitmap, -nUnit, -nUnit, -nUnit);
  19. xSetPosition(backBitmap, nUnit, -nUnit, nUnit, 180);
  20. xSetPosition(rightBitmap, nUnit, -nUnit, -nUnit, -90);
  21. xSetPosition(leftBitmap, -nUnit, -nUnit, nUnit, 90);
  22. addEventListener(Event.ENTER_FRAME, xRotate);
  23. xSetOrder(nRotationY);
  24. function xRotate(eventObject:Event):void {
  25.   nRotationY += (mouseX - nX) * nDeceleration;
  26.   nRotationY = xGetDegrees(nRotationY);
  27.   mySprite.rotationY = nRotationY;
  28.   xSetOrder(nRotationY);
  29. }
  30. function xSetOrder(nRotationY:Number):void {
  31.   var nTop:uint = mySprite.numChildren - 1;
  32.   if (nRotationY > 135) {
  33.     mySprite.setChildIndex(rightBitmap, nTop);
  34.     mySprite.setChildIndex(backBitmap, nTop);
  35.   } else if (nRotationY > 90) {
  36.     mySprite.setChildIndex(backBitmap, nTop);
  37.     mySprite.setChildIndex(rightBitmap, nTop);
  38.   } else if (nRotationY > 45) {
  39.     mySprite.setChildIndex(frontBitmap, nTop);
  40.     mySprite.setChildIndex(rightBitmap, nTop);
  41.   } else if (nRotationY > 0) {
  42.     mySprite.setChildIndex(rightBitmap, nTop);
  43.     mySprite.setChildIndex(frontBitmap, nTop);
  44.   } else if (nRotationY > -45) {
  45.     mySprite.setChildIndex(leftBitmap, nTop);
  46.     mySprite.setChildIndex(frontBitmap, nTop);
  47.   } else if (nRotationY > -90) {
  48.     mySprite.setChildIndex(frontBitmap, nTop);
  49.     mySprite.setChildIndex(leftBitmap, nTop);
  50.   } else if (nRotationY > -135) {
  51.     mySprite.setChildIndex(backBitmap, nTop);
  52.     mySprite.setChildIndex(leftBitmap, nTop);
  53.   } else {
  54.     mySprite.setChildIndex(leftBitmap, nTop);
  55.     mySprite.setChildIndex(backBitmap, nTop);
  56.   }
  57. }
  58. function xGetDegrees(nDegrees:Number):Number {
  59.   nDegrees += 180;
  60.   nDegrees %= 360;
  61.   nDegrees += 360;
  62.   nDegrees %= 360;
  63.   nDegrees -= 180;
  64.   return nDegrees;
  65. }
  66. function xSetPosition(instance:DisplayObject, nX:Number, nY:Number, nZ:Number, nRotationY:Number = 0):void {
  67.   instance.x = nX;
  68.   instance.y = nY;
  69.   instance.z = nZ;
  70.   instance.rotationY = nRotationY;
  71. }

関数xSetOrder()は、前にまとめた表04-003にしたがって、45度刻みでふたつの面を手前に配置します。DisplayObjectContainer.setChildIndex()メソッドの第2引数には一番前のインデックスを指定していますので、第1引数の面のインスタンスはもっとも手前に表示されます。第1引数のインスタンスは変えて同じメソッドを2度呼出せば、2度目の呼出しで指定した面が、1度目の呼出しの面の手前に置かれます。

[ムービープレビュー]を確かめると、4面が配置されたSpriteインスタンスは、マウスポインタの水平位置に応じてSpriteインスタンスのy軸で水平に回ります(図04-015)。手前にくるふたつの面の重ね順も、正しく設定されて表示されます。

図04-015■四方にビットマップを配置したSpriteインスタンスがマウスポインタの水平位置に応じて回転する
手前にくるふたつの面は、重ね順が正しく表示される。

04-02 Matrix3Dクラスの先に加える座標変換
3次元空間の変換行列は、Matrix3Dクラスが扱います。3次元空間における回転や伸縮、あるいは平行移動はすべてMatrix3Dオブジェクトによる座標変換として処理できます。複雑な変換もMatrix3Dオブジェクトの統一した(行列)演算により扱えるので応用性に優れます。また、2次元平面の変換行列を表すMatrixクラスと基本的な考え方は変わりません。

Matrix3Dクラスの座標変換にも、やはり回転と伸縮、および平行移動があります。それらのメソッドは、シンタックス04-005のとおりです。ただ、Matrix3Dクラスの具体的なメソッドの構成は、Matrixクラスとは少し異なるところがあります。気づくこととして、メソッド名の頭がみな"prepend"で始まる長い綴りになっていることでしょう。実は、それぞれについて"prepend"を"append"に置換えたメソッドが対になっているのです。それらの違いと意味については、後に詳しく説明します。

シンタックス04-005■Matrix3Dクラスの先に加える回転・伸縮・平行移動のプロパティとメソッド
Transform.matrix3Dプロパティ
文法 matrix3D:Matrix3D
プロパティ値 インスタンスの3次元空間における回転や伸縮、平行移動などの座標変換を表すMatrix3Dオブジェクトの参照。
Matrix3D.prependRotation()メソッド
文法 prependRotation(degrees:Number, axis:Vector3D, pivotPoint:Vector3D = null):void
概要 増やす回転角をMatrix3Dオブジェクトの最初の変換として加える。
引数

degrees:Number ― 回転を加える角度の度数値。

axis:Vector3D ― 回転の軸または方向を示すVector3Dインスタンス。xyz軸は、基本的にVector3Dクラスの定数で指定する(表04-004)。

表04-004■Vector3Dクラスの座標軸を指定する定数
座標軸 Vector3Dクラス定数 値となるVector3Dオブジェクトの座標
x軸 Vector3D.X_AXIS (1, 0, 0)
y軸 Vector3D.Y_AXIS (0, 1, 0)
z軸 Vector3D.Z_AXIS (0, 0, 1)

pivotPoint:Vector3D ― 回転後に加える座標の移動を示すVector3Dインスタンス。回転の中心を、その座標分ずらすことができる。デフォルト値は指定なし(null)で、Matrix3Dオブジェクト(変換行列)が適用されるDisplayObjectインスタンスの基準点を中心とする。

戻り値 なし。
Matrix3D.prependScale()メソッド
文法 prependScale(xScale:Number, yScale:Number, zScale:Number):void
概要 水平・垂直・奥行き方向の拡大・縮小をMatrix3Dオブジェクトの最初の変換として加える。
引数

xScale:Number ― 水平方向の伸縮を示す比率。実寸は1.0。

yScale:Number ― 垂直方向の伸縮を示す比率。実寸は1.0。

zScale:Number ― 奥行き方向の伸縮を示す比率。実寸は1.0。

戻り値 なし。
Matrix3D.prependTranslation()メソッド
文法 prependTranslation(x:Number, y:Number, z:Number):void
概要 xyz座標の平行移動をMatrix3Dオブジェクトの最初の変換として加える
引数

x:Number ― x座標の移動ピクセル数。

y:Number ― y座標の移動ピクセル数。

z:Number ― z座標の移動ピクセル数。

戻り値 なし。

Tips 04-005■"prepend"と"append"
"prepend"という英語は、「前に加える」ことを意味します。もっとも、英和辞典にはあまり載っていません。「後に加える」という意味の"append"に対して使われるようになった技術用語のようです。

Matrix3Dクラスのメソッドでは、変換行列の演算(乗算)対象を前に行うか後に行うかを表します。その意味と処理(演算)結果の違いについては、後で解説します。

[*筆者用参考] 「辞書で引けない技術英語 200703」。

以下のスクリプト04-006は、矢印キーの操作によりタイムラインに配置したインスタンスmy_mcを回転または拡大・縮小します(図04-016)。左右の矢印キーがy軸を中心にした水平回転、上下の矢印キーは拡大・縮小です。

スクリプト04-006■矢印キーの操作によりインスタンスを回転または拡大・縮小する
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nRotation:Number = 10;
  2. var nScaleUp:Number = 1.05;
  3. var nScaleDown:Number = 0.95;
  4. my_mc.z = 0;   // DisplayObject.zプロパティを設定
  5. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  6. stage.addEventListener(KeyboardEvent.KEY_DOWN, xTransform);
  7. function xTransform(eventObject:KeyboardEvent):void {
  8.   switch (eventObject.keyCode) {
  9.     case Keyboard.LEFT :
  10.       myMatrix3D.prependRotation(nRotation, Vector3D.Y_AXIS);
  11.       break;
  12.     case Keyboard.RIGHT :
  13.       myMatrix3D.prependRotation(-nRotation, Vector3D.Y_AXIS);
  14.       break;
  15.     case Keyboard.UP :
  16.       myMatrix3D.prependScale(nScaleUp, nScaleUp, 1);
  17.       break;
  18.     case Keyboard.DOWN :
  19.       myMatrix3D.prependScale(nScaleDown, nScaleDown, 1);
  20.       break;
  21.   }
  22. }

図04-016■矢印キーの操作でインスタンスが回転または拡大・縮小される


インスタンスは、左右の矢印キーでy軸を中心に水平回転、上下の矢印キーで拡大・縮小する。

スクリプト04-006について、処理内容を簡単に確かめていきましょう。まず、スクリプト第6行目でInteractiveObject.keyDownイベント(定数KeyboardEvent.KEY_DOWN)にイベントリスナーを登録し、キーボードのキーを押したときにリスナー関数xTransform()を呼出します。押したキーのキーコードは、リスナー関数が受取るイベントオブジェクトのKeyboardEvent.keyCodeプロパティで調べます(シンタックス04-006)。また、上下左右の矢印キーのキーコードは、下表04-005のとおりKeyboardクラスの定数で表せます。

シンタックス04-006■KeyboardEvent.keyCodeプロパティ
KeyboardEvent.keyCodeプロパティ
文法 keyCode:uint
プロパティ値 キーイベントが発生したキーのキーコードを示す正の整数値。各キーのキーコードは、Keyboardクラスの定数で示すとわかりやすい(下表04-005参照)。

表04-005■Keyboardクラスの矢印キーのキーコードを指定する定数
キー Keyboardクラス定数 値の整数値
上矢印キー Keyboard.UP 38
下矢印キー Keyboard.DOWN 40
左矢印キー Keyboard.LEFT 37
右矢印キー Keyboard.RIGHT 39

つぎに、押されたキーが上下左右の矢印キーのいずれかであることの判定には、switchステートメントを用いています(スクリプト第9〜21行目)。switchステートメントは、つぎのように式0が式1から式Nのいずれかと等しいかどうかを評価します。そして、等しければそのcaseステートメントの処理が行われることになります。なお、いずれのcaseステートメントとも等しいと評価されなかったときは、defaultステートメントが処理されます。

【switchステートメントによる条件判定の処理】
switch (式0) {
  case 式1 :
    ステートメント1;
  case 式2 :
    ステートメント2;
  ……
  case 式N :
    ステートメントN;
  default :
  ステートメントDefault;
}

switchステートメントを使うとき注意しなければならないのは、ifステートメントとは違って、「勝抜け」にならないことです(ifステートメントについては01-01「イベントリスナーと条件判定」参照)。たとえば、式0が式1と等しいとされれば、ステートメント1だけでなく、ステートメント2以降も無条件で処理されてしまいます。等価とされたcaseステートメントだけを処理して「勝抜け」させるには、スクリプト04-006のように、それぞれにbreakステートメントを加えて、明示的に処理を抜けなければなりません。

インスタンスの回転や拡大・縮小の処理には,前掲シンタックス04-005のMatrix3D.prependRotation()およびMatrix3D.prependScale()メソッドを用いています(スクリプト第10、13、16、および19行目)。メソッドを呼出すときに参照するMatrix3Dオブジェクトは、スクリプト第5行目でMovieClipインスタンスmy_mcのDisplayObject.transformプロパティ(シンタックス02-001)から変数に取得しています。

Tips 04-006■Matrix3D.prependScale()メソッドは拡大・縮小の比率を乗じる
Matrix3D.prependRotation()メソッドは、現在の角度に引数の値を加えます。それに対して、Matrix3D.prependScale()メソッドは、実寸を1.0とする現在の伸縮率に引数の比率を乗じます。

前掲スクリプト04-006第2〜3行目は、拡大と縮小の比率をそれぞれ1.05(変数nScaleUp)と0.95(変数nScaleDown)と定めました。すると、拡大後に縮小しても、もとに戻らない(1.05×0.95 = 0.9975)ことにご注意ください。


Tips 04-007■Transform.matrix3Dプロパティは参照渡し
DisplayObjectインスタンスのDisplayObject.transformプロパティがもつTransform.matrix3Dプロパティは、前述Column 03「Transformクラスのオブジェクトへのアクセス」のとおり、Matrix3Dオブジェクトの参照を受渡しします。ですから、取出したMatrix3Dオブジェクトを操作すれば、その対象となるDisplayObjectインスタンスに変換が適用されます。Matrixオブジェクトのように、改めてTransformオブジェクトのプロパティに設定し直す必要はありません。

最後に、ひとつ大事な注意があります。それは、スクリプト第4行目のDisplayObject.zプロパティの設定です。このプロパティのデフォルト値は0です。したがって、このステートメントで表示は何も変わりません。けれど、このステートメントをコメントアウトして[ムービープレビュー]で矢印キーを操作すると、たちまちランタイムエラーが起こります(図04-017)。

エラーは、プロパティまたはメソッドにアクセスするために参照したオブジェクトが、実際にはnullつまり存在しないことを示します。原因となるステートメントが特定されないため、解決がやっかいなエラーです。

図04-017■DisplayObject.zプロパティの設定をコメントアウトするとランタイムエラーが起こる
オブジェクトとして参照した値が、実際にはnullつまり存在しないことを示すエラー。

Tips 04-008■ランタイムエラー#1009
ランタイムエラー#1009の意味は、本文に述べたとおり、「プロパティまたはメソッドにアクセスするために参照したオブジェクトが、実際にはnullつまり存在しない」ということです。ただ、[ヘルプ]におけるこのエラーの説明は日本語訳が少しわかりにくいようです(図04-018)。参考までに、筆者の訳を掲げます。

図04-018■[ヘルプ]の[ランタイムエラー]#1009の説明
「説明」の日本語の意味がわかりにくい。

オブジェクトがnullと評価されると、プロパティをもてません。このエラーは、予期できない(けれども一応有効ではある)状況で起こります。たとえば,以下のコードは、Spriteオブジェクトを生成します。このSpriteオブジェクトは、(DisplayObjectContainerオブジェクトのaddChild()メソッドで)表示リストに加えられてはいません。そのため、そのstageプロパティはnullになります。すると、stageプロパティはプロパティをもてません。ですから、このコードはエラーを生じます。

import flash.display.Sprite;
var sprite1:Sprite = new Sprite();
var q:String = sprite1.stage.quality;

なお、[ヘルプ]の[ランタイムエラー]の英語原文は、[Runtime Errors]<http://help.adobe.com/en_US/AS3LCR/Flash_10.0/runtimeErrors.html>をご覧ください。

実は、DisplayObjectインスタンスのDisplayObject.transformプロパティがもつTransform.matrix3Dプロパティの値は、デフォルトではnullになります。オーサリング(ムービー作成)時あるいはスクリプトでインスタンスに3次元空間の操作を加えると、Transform.matrix3DプロパティにMatrix3Dオブジェクトが生成されて設定されるのです。すると、2次元平面の座標を担うTransform.matrixプロパティは、逆にnullに変わります。スクリプトによる3次元空間の操作として簡単なのは、DisplayObject.zプロパティを設定することです。

trace(my_mc.transform.matrix3D, my_mc.transform.matrix);
// 出力: null (a=1, b=0, c=0, d=1, tx=120, ty=90)
my_mc.z = 0;   // DisplayObject.zプロパティを設定
trace(my_mc.transform.matrix3D, my_mc.transform.matrix);
// 出力: [object Matrix3D] null

Tips 04-008■DisplayObject.zプロパティは0に設定してもMatrix3Dオブジェクトが生成される
[ヘルプ]の「DisplayObject.zプロパティ」の項は、以下のように説明します。しかし、すでに確かめたように、DisplayObject.zプロパティをデフォルト値の0に設定しても、DisplayObjectインスタンスのもつTransform.matrix3DプロパティにMatrix3Dオブジェクトは生成されます。

表示オブジェクトのzプロパティをデフォルト値の0以外に設定すると、対応するMatrix3Dオブジェクトが自動的に作成されます。

[イラスト] Matrix3Dオブジェクトを使うには、3次元で操作するよと教えてあげる必要がある。そうすると、Matrixオブジェクトは消えてしまう。

さて、回転のメソッドMatrix3D.prependRotation()を、もう少し試してみましょう。前掲スクリプト04-001は、DisplayObject.rotationYプロパティでインスタンスを回転しました。これを、Matrix3D.prependRotation()で書替えると、つぎのようなフレームアクションになります(スクリプト04-007)。

スクリプト04-007■インスタンスをマウスポインタの水平位置に応じてMatrix3D.prependRotation()メソッドで水平に回す
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nDeceleration:Number = 0.3;
  3. my_mc.z = 0;
  4. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  5. addEventListener(Event.ENTER_FRAME, xRotate);
  6. function xRotate(eventObject:Event):void {
  7.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  8.   myMatrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
  9. }

もちろん、スクリプトの動作結果は、前掲スクリプト04-001と同じです。[ムービープレビュー]を確かめると、タイムラインに置いたインスタンスmy_mcの基準点から見たマウスポインタの水平位置に応じて、インスタンスmy_mcはy軸で水平に回ります(再掲図04-001)。

図04-001■インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転する(再掲)
マウスポインタの水平位置がインスタンスの中心から離れるほど、インスタンスは速く水平に回る。

インスタンスは、基準点を中央に置いてあります。前記スクリプト04-007では、インスタンスは基準点を中心として水平に回ります。この回転の垂直軸をインスタンスの左端に移したのが、つぎのスクリプト04-008です。

スクリプト04-008■インスタンスの左端を軸にしてマウスポインタの水平位置に応じて水平に回す
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nDeceleration:Number = 0.3;
  3. var nHalfWidth:Number = my_mc.width / 2;
  4. var nHalfHeight:Number = my_mc.height / 2;
  5. my_mc.z = 0;
  6. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  7. addEventListener(Event.ENTER_FRAME, xRotate);
  8. function xRotate(eventObject:Event):void {
  9.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  10.   myMatrix3D.prependTranslation(-nHalfWidth, -nHalfHeight, 0);
  11.   myMatrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
  12.   myMatrix3D.prependTranslation(nHalfWidth, nHalfHeight, 0);
  13. }

2次元平面でMatrixクラスによりインスタンスの回転の中心をずらしたのと同じように(03-01「Matrixクラスのメソッドを使った座標変換」)、インスタンスの左端が回転の中心になるようにMatrix3D.prependTranslation()メソッドで位置を整えた後、Matrix3D.prependRotation()メソッドでインスタンスを回転し、改めて位置をもとに戻しています(図4-018)。

図04-019■インスタンスは左端を軸にしてマウスポインタの水平位置に応じて水平に回る
インスタンスの位置を動かして、回転の中心を左端にずらしてから回して、もとの位置に戻している。

もっとも、インスタンスを右に動かして回転の中心を基準点から左端にずらす処理(スクリプト第12行目)と、位置をもとに戻すステートメント(スクリプト第10行目)とは順序が逆ではないでしょうか。そもそも、Matrixクラスで2次元平面について学んだときには、座標変換の原点はインスタンスの配置された親タイムライン(DisplayObjectContainerインスタンス)でした。

これらの疑問は、メソッド名の頭についた"prepend"の意味を理解すると解決します。そこでもうひとつ、前掲スクリプト04-007に、垂直方向の回転を加えてみましょう。スクリプトとしては、単に水平方向と同じ処理を垂直方向にも行っただけです。

  1. var nX:Number = my_mc.x;
  2. var nY:Number = my_mc.y;   // インスタンスの垂直座標
  3. var nDeceleration:Number = 0.3;
  4. my_mc.z = 0;
  5. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  6. addEventListener(Event.ENTER_FRAME, xRotate);
  7. function xRotate(eventObject:Event):void {
  8.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  9.   var nRotationX:Number = (mouseY - nY) * nDeceleration;   // マウスポインタの垂直位置
  10.   myMatrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
  11.   myMatrix3D.prependRotation(nRotationX, Vector3D.X_AXIS);   // 垂直方向の回転
  12. }

[ムービープレビュー]で試すと、確かにインスタンスは垂直方向にも回るようになりました。しかし、マウスポインタの移動方向と回転の方向が、必ずしも一致しません。たとえば、マウスポインタを上に動かしたのに、インスタンスは横に回ってしまうことがあります。

図04-019■マウスポインタの移動方向と回転の方向が必ずしも一致しない
マウスポインタを上に動かしたのに、インスタンスは横に回ることもある。

ここで、前掲シンタックス04-005でご説明した各メソッドの頭についた"prepend"の意味を明らかにしなければなりません。フレームに置かれたインスタンスに、オーサリング時あるいはスクリプトで回転や伸縮、あるいは移動が加えられた場合、Flash Playerはまずこれらの変換が行われていないデフォルト状態のインスタンスを認識します。つまり、位置は親インスタンスの基準点、拡大・縮小率は実寸(100%)、回転角0度のインスタンスです(図04-020左図)。つぎに、インスタンスが(DisplayObject.transformプロパティに)もつ変換行列を調べて、インスタンスに対して適用します(図04-020)。

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

デフォルト状態

変換を適用
まずデフォルト状態のインスタンスを認識し、つぎにインスタンスのもつ変換行列を適用する。

そして、"prepend"が頭につくMatrix3Dクラスの変換のメソッドは、インスタンスのもつ変換行列よりも前に適用されるのです。したがって、スクリプト04-007に垂直方向の動きを加えた前掲フレームアクションでは、直前の状態ではなく、つねにデフォルトつまり真正面向きのインスタンスを垂直に回すことになります。

たとえば、インスタンスが前のフレームでy軸回りに横を向いていたとします(図04-021上図)。そのインスタンスをMatrix3D.prependRotation()メソッドで垂直に回すと、まずデフォルト状態で正面向きのインスタンスが認識され、つぎにその状態から垂直に回転し、その後直前の横向きの変換が加えられます(図04-021下図)。ですから、直前の横向きの状態をもとに垂直に回す結果にはならないのです。

図04-021■Matrix3D.prependRotation()メソッドは正面向きのインスタンスを回すことになる

直前の状態

デフォルト状態

Matrix3D.prependRotation()
メソッドを適用

直前の変換を適用
デフォルト状態からの回転になるので、直前の状態のインスタンスを回すことにはならない。

また、前掲スクリプト04-008のように、"prepend"のメソッドを複数続けて呼出した場合(スクリプト第10〜12行目)、メソッドによる変換はつねに先頭の順序に加えられていきます。したがって、最後(第12行目)に呼出した変換のメソッドが最初に適用され、そこから遡って順に変換が加えられることになります。つまり、スクリプト04-008第10〜12行目の処理は、まずインスタンスは右下に移動し(第12行目)、つぎに水平の回転が加えられたうえで(第11行目)、もとの左上の位置に戻るのです(第10行目)。


04-03 Matrix3Dクラスの後から加える座標変換
さて、直前の状態にもとづいて回転などの座標変換を加えるには、デフォルト状態から始めてはいけません。その場合には、後から加える変換のメソッドを用います(シンタックス04-007)。メソッドの頭につく単語は、"prepend"ではなく"append"になります。

シンタックス04-007■Matrix3Dクラスの後から加える回転・伸縮・平行移動のメソッド
Matrix3D.appendRotation()メソッド
文法 appendRotation(degrees:Number, axis:Vector3D, pivotPoint:Vector3D = null):void
概要 増やす回転角をMatrix3Dオブジェクトの変換として後から加える。
引数

degrees:Number ― 回転を加える角度の度数値。

axis:Vector3D ― 回転の軸または方向を示すVector3Dインスタンス。xyz軸は、基本的にVector3Dクラスの定数で指定する(再掲表04-004)。

表04-004■Vector3Dクラスの座標軸を指定する定数(再掲)
座標軸 Vector3Dクラス定数 値となるVector3Dオブジェクトの座標
x軸 Vector3D.X_AXIS (1, 0, 0)
y軸 Vector3D.Y_AXIS (0, 1, 0)
z軸 Vector3D.Z_AXIS (0, 0, 1)

pivotPoint:Vector3D ― 回転後に加える座標の移動を示すVector3Dインスタンス。回転の中心を、その座標分ずらすことができる。デフォルト値は指定なし(null)で、Matrix3Dオブジェクト(変換行列)が適用されるDisplayObjectインスタンスの基準点を中心とする。

戻り値 なし。
Matrix3D.appendScale()メソッド
文法 appendScale(xScale:Number, yScale:Number, zScale:Number):void
概要 水平・垂直・奥行き方向の拡大・縮小をMatrix3Dオブジェクトの変換として後から加える。
引数

xScale:Number ― 水平方向の伸縮を示す比率。実寸は1.0。

yScale:Number ― 垂直方向の伸縮を示す比率。実寸は1.0。

zScale:Number ― 奥行き方向の伸縮を示す比率。実寸は1.0。

戻り値 なし。
Matrix3D.appendTranslation()メソッド
文法 appendTranslation(x:Number, y:Number, z:Number):void
概要 xyz座標の平行移動をMatrix3Dオブジェクトの変換として後から加える
引数

x:Number ― x座標の移動ピクセル数。

y:Number ― y座標の移動ピクセル数。

z:Number ― z座標の移動ピクセル数。

戻り値 なし。

ただし、忘れてならないのは、座標変換の原点が、親(DisplayObjectContainer)インスタンスの基準点だということです。もっとも、対処の仕方は、すでに2次元平面のMatrixクラスで学びました(前述03-01「Matrixクラスのメソッドを使った座標変換」参照)。

たとえば、インスタンスを自身の基準点で回したいときには、(1)位置座標だけを親の基準点に動かし、(2)そのうえでインスタンスを回し、(3)改めて位置をもとに戻せばよかったのです。後から変換するメソッドなら、すべてをデフォルトに戻すのではなく、必要な変換だけを選んで加えることができます。

改めて前掲スクリプト04-007に垂直方向の回転を加えたのが、以下のスクリプト04-009です。スクリプト第10行目は、インスタンス位置だけをMatrix3D.appendTranslation()メソッドで親インスタンスの基準点に移しています。そのうえでスクリプト第11〜12行目が、Matrix3D.appendRotation()メソッドにより水平と垂直の両方向にインスタンスを回します。そして、スクリプト第13行目で、インスタンスをもとの位置に戻しています。

スクリプト04-009■インスタンスをマウスポインタの位置に応じて水平および垂直方向に回す
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nY:Number = my_mc.y;
  3. var nDeceleration:Number = 0.3;
  4. my_mc.z = 0;
  5. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  6. addEventListener(Event.ENTER_FRAME, xRotate);
  7. function xRotate(eventObject:Event):void {
  8.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  9.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  10.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  11.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  12.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  13.   myMatrix3D.appendTranslation(nX, nY, 0);
  14. }

これでインスタンスの直前の状態にから、マウスポインタの位置に応じて回すことができます。[ムービープレビュー]を確かめると、マウスポインタを動かした方向にインスタンスが回転します(図04-022)。

図04-022■マウスポインタの動きとインスタンスの回転する方向が一致する
インスタンスの直前の状態から、マウスポインタの位置に応じて回転する。

つぎは、ビットマップ画像を使って、面の数を増やします。初めは前掲スクリプト04-005と同じく、4面を水平方向に回します。その後,さらに2面増やして立方体とし、垂直方向の回転も加えます。そこで大切なのは、面の重ね順の扱いです。水平・垂直両方向に回りますので、スクリプト04-005のように単純にSpriteインスタンスの回転角で決めるのは難しいでしょう。したがって、各面のz座標を調べることにします。もっとも、Spriteインスタンスごと回してしまうために、その中の各面の位置座標は動かないという問題がありました。

このようなとき、Transform.getRelativeMatrix3D()メソッドを用いると、DisplayObjectインスタンスのもつMatrix3Dオブジェクトの座標空間が変換できます。メソッドの引数には、変換の基準となるDisplayObjectインスタンスを渡します。たとえば、面のインスタンスのDisplayObject.transformプロパティを参照して、引数にメインタイムラインを渡せば、メインタイムラインから見た面のインスタンスのMatrix3Dオブジェクトが得られるのです。

そして、そのMatrix3D.positionプロパティでVector3Dオブジェクトを取出すことにより、xyz座標が調べられます(シンタックス04-008)。Vector3Dは、xyz座標値をプロパティとしてもつオブジェクトです。Pointクラスを3次元に拡張し、さらにベクトルとしての扱いも充実させたクラスです。Vector3Dクラスについては、後述06「3次元空間の座標を扱う − Vector3Dクラス」で詳しく解説します。

シンタックス04-008■Transform.getRelativeMatrix3D()メソッドとMatrix3D.positionプロパティ
Transform.getRelativeMatrix3D()メソッド
文法 getRelativeMatrix3D(relativeTo:DisplayObject):Matrix3D
概要 DisplayObjectインスタンスのもつMatrix3Dオブジェクトを、指定したDisplayObjectインスタンスが基準となる座標空間に変換する。
引数 relativeTo:DisplayObject ― 座標を変換する基準となるDisplayObjectインスタンス。
戻り値 指定したDisplayObjectインスタンスの座標空間を基準として変換されたMatrix3Dオブジェクト。
Matrix3D.positionプロパティ
文法 position:Vector3D
プロパティ値 3次元空間における位置座標をもつVector3Dオブジェクト。Vector3D.xVector3D.yVector3D.zの各プロパティにより、それぞれのxyz座標値が示される。

もうひとつ、各面の座標についても考える必要があります。Bitmapインスタンスの基準点は左上隅です。しかし、各面はその中心をSpriteインスタンスのxまたはy軸がとおるように配置しています(図04-023)。すると、重ね順の前後を決めるとき、面の左上隅ではなく中心のz座標を調べなければなりません。

図04-023■各面の中心をSpriteインスタンスのxまたはz軸がとおる
面の重ね順の前後は、中心のz座標で決めなければならない。

面の中心座標は、3次元のベクトル演算で求めることもできます。けれど、もっと簡単なのは面のBitmapインスタンスを新たなSpriteインスタンスに入れ子にして、その基準点を面の中央に置くことでしょう(図04-024)。この考え方にもとづき、前掲スクリプト04-005と同じようにSpriteインスタンスの四方に(Spriteに入れ子にした)面を置き、マウスポインタの水平位置に応じてy軸で水平に回すフレームアクションが下記スクリプト04-010です。

図04-024■Bitmapが納められたSpriteインスタンスの基準点を中央に置く
面のBitmapインスタンスを新たなSpriteインスタンスに入れ子にして、その基準点を面の中央に置く。

まず、Spriteインスタンスをマウスポインタの水平位置に応じてy軸で水平に回す処理は、前掲スクリプト04-009と基本的に同じです(ただし、垂直方向には回転させていません)。

  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  1. var nDeceleration:Number = 0.2;
  2. var mySprite:Sprite = new Sprite();
  1. mySprite.z = 0;
  2. var myMatrix3D:Matrix3D = mySprite.transform.matrix3D;
  3. addChild(mySprite);
  4. mySprite.x = nX;
  5. mySprite.y = nY;
  1. addEventListener(Event.ENTER_FRAME, xRotate);
  1. function xRotate(eventObject:Event):void {
  2.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  3.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  4.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  5.   myMatrix3D.appendTranslation(nX, nY, 0);
  1. }

つぎに、Spriteインスタンス内の四方に面を配置する処理です。スクリプト第6〜9行目は、前掲スクリプト04-005と同じく、[ライブラリ]のビットマップが納められたBitmapインスタンスを4面分生成します。第15〜18行目は、それらのBitmapインスタンスを関数xCreateFace()の引数に渡しています。この関数はBitmapが入れ子になった面のSpriteインスタンスを返すので、それらを親のSpriteインスタンス(mySprite)に子として加えます。

スクリプト第45〜55行目の関数xCreateFace()は、前述のとおり、新たなSpriteインスタンスをつくり(第46行目)、親インスタンスに配置する位置座標と面の角度を整えます(第48〜51行目)。引数に受取ったBitmapインスタンスはSpriteインスタンスに入れ子として加え(第47行目)、基準点が面の中心になるように配置します(第52〜53行目)。そのうえで、そのSpriteインスタンスを返します(第54行目)。

  1. var nUnit:Number = 100 / 2;
  1. var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  2. var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));
  3. var rightBitmap:Bitmap = new Bitmap(new Image2(0, 0));
  4. var leftBitmap:Bitmap = new Bitmap(new Image3(0, 0));
  1. mySprite.addChild(xCreateFace(frontBitmap, 0, 0, -nUnit));
  2. mySprite.addChild(xCreateFace(backBitmap, 0, 0, nUnit, 180));
  3. mySprite.addChild(xCreateFace(rightBitmap, nUnit, 0, 0, -90));
  4. mySprite.addChild(xCreateFace(leftBitmap, -nUnit, 0, 0, 90));
  1. function xCreateFace(faceBitmap:Bitmap, nX:Number, nY:Number, nZ:Number, nRotationY:Number = 0):Sprite {
  2.   var faceSprite:Sprite = new Sprite();
  3.   faceSprite.addChild(faceBitmap);
  4.   faceSprite.x = nX;
  5.   faceSprite.y = nY;
  6.   faceSprite.z = nZ;
  7.   faceSprite.rotationY = nRotationY;
  8.   faceBitmap.x = -nUnit;
  9.   faceBitmap.y = -nUnit;
  10.   return faceSprite;
  11. }

スクリプト04-010■4面が配置されたインスタンスをマウスポインタの水平位置に応じて回転する2
    // フレームアクション
    // [ライブラリ]の4つのビットマップにそれぞれ[クラス]としてImage0〜Image3を設定
  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  3. var nUnit:Number = 100 / 2;
  4. var nDeceleration:Number = 0.2;
  5. var mySprite:Sprite = new Sprite();
  6. var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  7. var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));
  8. var rightBitmap:Bitmap = new Bitmap(new Image2(0, 0));
  9. var leftBitmap:Bitmap = new Bitmap(new Image3(0, 0));
  10. mySprite.z = 0;
  11. var myMatrix3D:Matrix3D = mySprite.transform.matrix3D;
  12. addChild(mySprite);
  13. mySprite.x = nX;
  14. mySprite.y = nY;
  15. mySprite.addChild(xCreateFace(frontBitmap, 0, 0, -nUnit));
  16. mySprite.addChild(xCreateFace(backBitmap, 0, 0, nUnit, 180));
  17. mySprite.addChild(xCreateFace(rightBitmap, nUnit, 0, 0, -90));
  18. mySprite.addChild(xCreateFace(leftBitmap, -nUnit, 0, 0, 90));
  19. addEventListener(Event.ENTER_FRAME, xRotate);
  20. xSetOrder();
  21. function xRotate(eventObject:Event):void {
  22.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  23.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  24.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  25.   myMatrix3D.appendTranslation(nX, nY, 0);
  26.   xSetOrder();
  27. }
  28. function xSetOrder():void {
  29.   var faces_array:Array = new Array();
  30.   var nChildren:uint = mySprite.numChildren;
  31.   var i:uint;
  32.   var faceSprite:Sprite;
  33.   for (i = 0; i < nChildren; i++) {
  34.     faceSprite = mySprite.getChildAt(i) as Sprite;
  35.     var myVector3D:Vector3D = faceSprite.transform.getRelativeMatrix3D(this).position;
  36.     var nZ:Number = myVector3D.z;
  37.     faces_array.push({face:faceSprite, z:nZ});
  38.   }
  39.   faces_array.sortOn("z", Array.NUMERIC | Array.DESCENDING);
  40.   for (i = 0; i < nChildren; i++) {
  41.     faceSprite = faces_array[i].face;
  42.     mySprite.setChildIndex(faceSprite, i);
  43.   }
  44. }
  45. function xCreateFace(faceBitmap:Bitmap, nX:Number, nY:Number, nZ:Number, nRotationY:Number = 0):Sprite {
  46.   var faceSprite:Sprite = new Sprite();
  47.   faceSprite.addChild(faceBitmap);
  48.   faceSprite.x = nX;
  49.   faceSprite.y = nY;
  50.   faceSprite.z = nZ;
  51.   faceSprite.rotationY = nRotationY;
  52.   faceBitmap.x = -nUnit;
  53.   faceBitmap.y = -nUnit;
  54.   return faceSprite;
  55. }

各面のSpriteインスタンスの重ね順をそれぞれの奥行きによって並べ替えるのは、関数xSetOrder()です(スクリプト第28〜44行目)。親Spriteインスタンスに納められた4面の子Spriteインスタンスすべてについて、タイムラインから見たz座標を調べます(第33〜38行目)。子インスタンスの数はDisplayObjectContainer.numChildrenプロパティ(前掲シンタックス04-004)で調べ(第30行目)、DisplayObjectContainer.getChildAt()メソッド(シンタックス04-009)により指定インデックスのインスタンスを取出します(第34行目)。

  1. function xSetOrder():void {
  2.   var faces_array:Array = new Array();
  3.   var nChildren:uint = mySprite.numChildren;
  4.   var i:uint;
  5.   var faceSprite:Sprite;
  6.   for (i = 0; i < nChildren; i++) {
  7.     faceSprite = mySprite.getChildAt(i) as Sprite;
  8.     var myVector3D:Vector3D = faceSprite.transform.getRelativeMatrix3D(this).position;
  9.     var nZ:Number = myVector3D.z;
  10.     faces_array.push({face:faceSprite, z:nZ});
  11.   }
  12.   faces_array.sortOn("z", Array.NUMERIC | Array.DESCENDING);
  13.   for (i = 0; i < nChildren; i++) {
  14.     faceSprite = faces_array[i].face;
  15.     mySprite.setChildIndex(faceSprite, i);
  16.   }
  17. }
シンタックス04-009■DisplayObjectContainer.getChildAt()メソッド
DisplayObjectContainer.getChildAt()メソッド
文法 getChildAt(index:int):DisplayObject
概要 表示リスト内の指定インデックスに納められた子DisplayObjectインスタンスを返す。
引数 index:int ― 子DisplayObjectインスタンスを指定する表示リスト内のインデックスの整数。
戻り値 表示リスト内の指定インデックスに納められた子DisplayObjectインスタンス。

Tips 04-009■データ型の変換
DisplayObjectContainer.getChildAt()メソッドの戻り値は、DisplayObjectクラスで型指定されています。その値をスクリプト04-010第34行目で、そのままSpriteクラスで型指定された変数(faceSprite)に代入しようとすると、実際にSpriteインスタンスであっても[コンパイルエラー]が起こります(図04-025)。

  1. faceSprite = mySprite.getChildAt(i);   // as Sprite;
図04-025■DisplayObjectクラスの戻り値をSpriteクラスで型指定された変数に入れると[コンパイルエラー]
実際にはSpriteインスタンスの参照だとしても、データ型がDisplayObjectクラスで指定されているために[コンパイルエラー]が起こる。

このようなとき、as演算子は指定したデータ型で式の値を評価し直します。

式 as データ型
  1.     faceSprite = mySprite.getChildAt(i) as Sprite;

式の値が指定のデータ型で扱えるのは、値がそのデータ型のインスタンスであるか、そのデータ型のサブクラスのインスタンスである場合、あるいはそのインスタンスの属するクラスがインターフェイスを実装する場合です。式の値が指定のデータ型で扱えるときは、値をそのデータ型に変換します。扱えない場合には、nullを返します。

なお、スクリプト04-010第41行目では、配列から取出したインスタンスをSpriteクラスで型指定された第34行目と同じ変数(faceSprite)に代入するとき、データ型は変換していません。これは、配列エレメントが型指定されないためです。

[*筆者用参考] .NET TIPS「as演算子とキャストの違いは?」。

スクリプト第33〜38行目は、親Spriteインスタンス内のすべての面の子Spriteインスタンスに対して、同じ内容の処理を行います。forステートメントは、このような繰返し(ループ)処理を記述するために用いられます。

【forステートメントによるループ処理】
for (初期化; 継続条件; 更新) {
  ステートメント;
}

forステートメントに指定する第1の初期化では,処理に必要な初期設定を行います。多くの場合、カウンタの変数に初期値を代入します。第2の継続条件は、それが満たされている(trueと評価される)間処理が続けられる条件の指定です。カウンタ変数に対する条件を指定することが多いでしょう。第3の更新は、ループが繰返されるたびに最初に行われる処理です。カウンタ変数の加算がよく行われます。

スクリプト第33行目のforステートメントは、変数(i)に親Spriteインスタンスの表示リスト内における子インスタンスのインデックスを指定して、0から最終インデックス(DisplayObjectContainer.numChildren - 1)まですべての子インスタンスを処理しています。

Tips 04-010■forステートメントの処理の最適化
まず、カウンタ変数が0以上の整数のときは、データ型の指定はuintにすると、最適化が期待できます(ただし、Flash Playerのバージョンによって効果は異なります)。

つぎに、継続条件にオブジェクトのプロパティを指定するときは、予め変数に納めておく方がよいでしょう。前掲スクリプト04-010第30行目は、DisplayObjectContainer.numChildrenプロパティの値を変数(nChildren)に取っています。継続条件はループのたびに、つまりループ回数分繰返し評価されます。そのとき、オブジェクトからプロパティ値を取出すよりも、変数にアクセスする方が速いからです。

スクリプト04-010第35行目は、取出した面の子Spriteインスタンスについて、Transform.getRelativeMatrix3D()メソッドでタイムラインから見たMatrix3Dオブジェクトを取得して、そのMatrix3D.positionプロパティから位置座標のVector3Dオブジェクトを取出しています(シンタックス04-008)。そして、第36行目でそのz座標値を調べ、第37行目はSpriteインスタンスとz座標値が(faceとz)プロパティとして納められたObjectインスタンスを生成して、第29行目でつくられた配列(faces_array)に加えています。

このObjectインスタンスをエレメントにもった配列(faces_array)が、z座標値による面の子Spriteインスタンスの並べ替えに用いられます(スクリプト第39〜43行目)。この配列の並べ替えには、Array.sortOn()メソッドを使います(シンタックス04-010)。なお、本書ではArrayクラスつまり配列の基本的な知識を前提としています。Arrayクラスのプロパティやメソッドについておわかりにならない点がある場合には、[ヘルプ]や参考書をご覧ください。

シンタックス04-010■Array.sortOn()メソッド
Array.sortOn()メソッド
文法 sortOn(fieldName:Object, options:Object = null):Array
概要 オブジェクトの配列エレメントを、指定したプロパティ値で並べ替える。並べ替えは、デフォルトではUnicode値にもとづいて、文字列として行われる。配列エレメントに共通し、並べ替えの指定となりうるプロパティをフィールドと呼ぶ。
引数

fieldName:Object ― 指定するフィールドがひとつのときは、その名前を文字列。複数のフィールドはその優先順位にしたがって、フィールド名の文字列を配列に納めて指定する。

options:Object― 並べ替えの仕方を指定するオプションの整数。通常は、次表04-006のArray定数で指定する。複数のオプションは、ビット単位の論理和演算子|で組合わせられる。また、第1引数のフィールドを配列で指定したときは、それぞれに対応するオプションを同じ長さの配列で指定できる。デフォルト値は指定なし(null)。

表04-006■並べ替えに指定できるオプションとArray定数
オプション Vector3Dクラス定数 値の整数値
大文字と小文字を区別しない Array.CASEINSENSITIVE 1
降順の並べ替え Array.DESCENDING 2
数値として並べ替え Array.UNIQUESORT 4
並べ替え結果をインデックスの配列で返す Array.RETURNINDEXEDARRAY 8
重複のない並べ替え Array.NUMERIC 16
戻り値 つぎの第2引数の指定による例外を除いて、並べ替えたArrayインスタンスの参照が返される。第2引数に並べ替え結果をインデックスの配列で返す(定数Array.RETURNINDEXEDARRAY)指定がされると、参照したArrayインスタンスは並べ替えずに、並べ替えたインデックスの配列を返す。第2引数に重複のない並べ替え(定数Array.NUMERIC)を指定したとき、重複したエレメントがあると0を返す。

スクリプト第39行目は、面の子Spriteインスタンスとz座標値がプロパティとして納められた配列(faces_array)を、Array.sortOn()メソッドでz座標の数値の数値の大きい順に並べ替えています(z座標値は大きいほど奥)。そのうえで、スクリプト第40〜43行目のforステートメントにより、z座標値で並べ替えられた配列から面の子Spriteを順に取出し、親Spriteインスタンスに対してDisplayObjectContainer.setChildIndex()メソッド(シンタックス04-004)で表示リスト内のインデックスをそのz座標値の順に設定しています。

このようにして前掲スクリプト04-010は、四方に面の子Spriteインスタンスが納められた親Spriteインスタンスを、マウスポインタの水平位置に応じてy軸回りに水平に回します。4つの面の子Spriteインスタンスは、正しい重ね順で表示されます(図04-026)。

図04-026■四方に面のSpriteを配置したインスタンスがマウスポインタの水平位置に応じて回転する
4つの面の子Spriteインスタンスは、正しい重ね順で表示される。

スクリプト04-010は、前掲スクリプト04-005と異なり、単純に親Spriteインスタンスのy軸回りの角度で場合分けしているのではなく、面の子Spriteインスタンスのz座標値により毎フレーム重ね順を定めています。ですから、スクリプト04-010の関数xRotate()(第21〜27行目)につぎのように垂直方向の処理を加えるだけで、x軸でも問題なく回せます(図04-027)。

  1. function xRotate(eventObject:Event):void {
  2.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  3.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  4.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  5.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  6.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  7.   myMatrix3D.appendTranslation(nX, nY, 0);
  8.   xSetOrder();
  9. }

図04-027■四方に面を配置したインスタンスはマウスポインタの位置に応じて水平・垂直に回転する
4つの面の子Spriteインスタンスは、垂直の回転が加わっても重ね順は正しく表示される。

さてそうすると、あと上下の2面さえ加えれば、立方体のインスタンスをマウスポインタの位置に応じて回すことはできそうです。[ライブラリ]の6つのビットマップには、それぞれ[クラス]としてImage0〜Image5を設定しておきます。立方体のSpriteインスタンスをマウスポインタの位置に応じて水平および垂直に回すフレームアクションが下記のスクリプト04-011です。

上下の面は水平軸つまりDisplayObject.rotationXプロパティで回転して、立方体に組上げなければなりません。そのため、関数xCreateFace()(スクリプト04-011第57〜68行目)の第6引数に水平軸の回転角を加え(第57行目)、DisplayObject.rotationXプロパティで回す処理を入れてあります(第64行目)。

  1. function xCreateFace(faceBitmap:Bitmap, nX:Number, nY:Number, nZ:Number, nRotationY:Number = 0, nRotationX:Number = 0):Sprite {
  2.   var faceSprite:Sprite = new Sprite();
  3.   faceSprite.addChild(faceBitmap);
  4.   faceSprite.x = nX;
  5.   faceSprite.y = nY;
  6.   faceSprite.z = nZ;
  7.   faceSprite.rotationY = nRotationY;
  8.   faceSprite.rotationX = nRotationX;
  9.   faceBitmap.x = -nUnit;
  10.   faceBitmap.y = -nUnit;
  11.   return faceSprite;
  12. }

関数xCreateFace()を呼出すスクリプト第17〜22行目のステートメントも、それに合わせてx軸回りの角度を引数に渡しています。この修正により、立方体の6面をマウスポインタの位置に合わせて水平および垂直に回すことが、一応できるようになります。

スクリプト04-011■立方体のSpriteインスタンスをマウスポインタの位置に応じて水平・垂直に回す
    // フレームアクション
    // [ライブラリ]の6つのビットマップにそれぞれ[クラス]としてImage0〜Image5を設定
  1. var nX:Number = stage.stageWidth / 2;
  2. var nY:Number = stage.stageHeight / 2;
  3. var nUnit:Number = 100 / 2;
  4. var nDeceleration:Number = 0.2;
  5. var mySprite:Sprite = new Sprite();
  6. var frontBitmap:Bitmap = new Bitmap(new Image0(0, 0));
  7. var backBitmap:Bitmap = new Bitmap(new Image1(0, 0));
  8. var rightBitmap:Bitmap = new Bitmap(new Image2(0, 0));
  9. var leftBitmap:Bitmap = new Bitmap(new Image3(0, 0));
  10. var topBitmap:Bitmap = new Bitmap(new Image4(0, 0));
  11. var bottomBitmap:Bitmap = new Bitmap(new Image5(0, 0));
  12. mySprite.z = 0;
  13. var myMatrix3D:Matrix3D = mySprite.transform.matrix3D;
  14. addChild(mySprite);
  15. mySprite.x = nX;
  16. mySprite.y = nY;
  17. mySprite.addChild(xCreateFace(frontBitmap, 0, 0, -nUnit));
  18. mySprite.addChild(xCreateFace(backBitmap, 0, 0, nUnit, 180, 0));
  19. mySprite.addChild(xCreateFace(rightBitmap, nUnit, 0, 0, -90, 0));
  20. mySprite.addChild(xCreateFace(leftBitmap, -nUnit, 0, 0, 90, 0));
  21. mySprite.addChild(xCreateFace(topBitmap, 0, -nUnit, 0, 0, -90));
  22. mySprite.addChild(xCreateFace(bottomBitmap, 0, nUnit, 0, 0, 90));
  23. addEventListener(Event.ENTER_FRAME, xRotate);
  24. xSetOrder();
  25. function xRotate(eventObject:Event):void {
  26.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  27.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  28.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  29.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  30.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  31.   myMatrix3D.appendTranslation(nX, nY, 0);
  32.   xSetOrder();
  33. }
  34. function xSetOrder():void {
  35.   var faces_array:Array = new Array();
  36.   var nChildren:uint = mySprite.numChildren;
  37.   var i:uint;
  38.   var faceSprite:Sprite;
  39.   for (i = 0; i < nChildren; i++) {
  40.     faceSprite = mySprite.getChildAt(i) as Sprite;
  41.     var myVector3D:Vector3D = faceSprite.transform.getRelativeMatrix3D(this).position;
  42.     var nZ:Number = myVector3D.z;
  43.     if (nZ < 0) {
  44.       faceSprite.visible = true;
  45.       faces_array.push({face:faceSprite, z:nZ});
  46.     } else {
  47.       faceSprite.visible = false;
  48.     }
  49.   }
  50.   faces_array.sortOn("z", Array.NUMERIC | Array.DESCENDING);
  51.   var nStart:uint = nChildren - faces_array.length;
  52.   for (i = nStart; i < nChildren; i++) {
  53.     faceSprite = faces_array[i - nStart].face;
  54.     mySprite.setChildIndex(faceSprite, i);
  55.   }
  56. }
  57. function xCreateFace(faceBitmap:Bitmap, nX:Number, nY:Number, nZ:Number, nRotationY:Number = 0, nRotationX:Number = 0):Sprite {
  58.   var faceSprite:Sprite = new Sprite();
  59.   faceSprite.addChild(faceBitmap);
  60.   faceSprite.x = nX;
  61.   faceSprite.y = nY;
  62.   faceSprite.z = nZ;
  63.   faceSprite.rotationY = nRotationY;
  64.   faceSprite.rotationX = nRotationX;
  65.   faceBitmap.x = -nUnit;
  66.   faceBitmap.y = -nUnit;
  67.   return faceSprite;
  68. }

スクリプト04-011は、面のインスタンスの重ね順を定める関数xSetOrder()にも手を加えました(スクリプト第34〜56行目)。立方体が回転するときに見える面は3つまでで、残りの3面は裏に隠れてしまいます。ですから、見えない3面は表示しないことにして、見える3面だけの重ね順を決めるようにしたのです。

  1. function xSetOrder():void {
  2.   var faces_array:Array = new Array();
  3.   var nChildren:uint = mySprite.numChildren;
  4.   var i:uint;
  5.   var faceSprite:Sprite;
  6.   for (i = 0; i < nChildren; i++) {
  7.     faceSprite = mySprite.getChildAt(i) as Sprite;
  8.     var myVector3D:Vector3D = faceSprite.transform.getRelativeMatrix3D(this).position;
  9.     var nZ:Number = myVector3D.z;
  10.     if (nZ < 0) {
  11.       faceSprite.visible = true;
  12.       faces_array.push({face:faceSprite, z:nZ});
  13.     } else {
  14.       faceSprite.visible = false;
  15.     }
  16.   }
  17.   faces_array.sortOn("z", Array.NUMERIC | Array.DESCENDING);
  18.   var nStart:uint = nChildren - faces_array.length;
  19.   for (i = nStart; i < nChildren; i++) {
  20.     faceSprite = faces_array[i - nStart].face;
  21.     mySprite.setChildIndex(faceSprite, i);
  22.   }
  23. }

見える面のインスタンスは、z座標が原点より手前になります。したがって、スクリプト第43〜48行目のif条件でz座標値が負のインスタンスのみ、DisplayObject.visibleプロパティをtrueにして、重ね順を決める配列(faces_array)に納めます。それ以外(z座標値が0以上)のインスタンスは、DisplayObject.visibleプロパティをfalseにすることで、重ね順の処理を省いてしまいます。

重ね順を定めるスクリプト第51〜55行目の処理は、インスタンス数が変わることに伴い、設定値を一部修正しました。これで、立方体のSpriteインスタンスが、マウスポインタの位置に応じて水平および垂直に回ります(図04-028)。

図04-028■立方体のインスタンスがマウスポインタの位置に応じて水平・垂直に回転する
見える3つの面だけの重ね順が定められ、隠れる裏側の面は表示していない。

04-04 Matrix3Dクラスのその他のメソッド
Matrix3Dクラスのメソッドを、もういくつかご紹介しておきましょう。つぎのシンタックス04-011に、Matrix3Dクラスのコンストラクタを含めて7つのメソッドを掲げました。

シンタックス04-011■Matrix3Dクラスのその他のメソッド
Matrix3D()コンストラクタ
文法 Matrix3D(elements:Vector.<Number> = null)
概要

指定されたVectorオブジェクトのエレメント値が要素(成分)となる4行×4列の変換行列のMatrix3Dインスタンスを生成する(図04-029左図)。引数がないときは、デフォルト状態を示すMatrix3Dインスタンス(単位行列)になる(図04-029右図)。

図04-029■変換行列の構成と単位行列
4行×4列の変換行列の構成(左図)。単位行列は、対角要素が1で他の要素が0(右図)。

なお、3次元座標空間における変換行列の行数および列数が、次元の数に1を加えた4行×4列であることについては、前掲Maniac! 03-001「変換行列の行数と列数」参照。

引数 elements:Vector.<Number> ― 4行×4列の計16要素の数値を納めたVectorオブジェクト(Vectorクラスについては後述05「配列に型指定を加えた厳格なクラス − Vectorクラス」参照)。デフォルト値は指定なし(null)。
Matrix3D.append()メソッド
文法 append(appendMatrix3D:Matrix3D):void
概要 Matrix3DインスタンスにMatrix3Dオブジェクトの変換を後から加える。
引数

appendMatrix3D:Matrix3D ― 変換を後から加えるMatrix3Dオブジェクト。

戻り値 なし。
Matrix3D.prepend()メソッド
文法 prepend(prependMatrix3D:Matrix3D):void
概要 Matrix3DインスタンスにMatrix3Dオブジェクトを最初の変換として加える。
引数

prependMatrix3D:Matrix3D ― 最初の変換として加えるMatrix3Dオブジェクト。

戻り値 なし。
Matrix3D.clone()メソッド
文法 clone():Matrix3D
概要 Matrix3Dインスタンス複製して、新たなオブジェクトとして返す。
引数 なし。
戻り値 複製された新たなMatrix3Dインスタンス。
Matrix3D.invert()メソッド
文法 invert():Boolean
概要 参照したMatrix3Dインスタンスの表す変換行列を逆行列に変える。逆行列にしたMatrix3Dインスタンスは、もとのオブジェクトとは逆の変換を表す。
引数 なし。
戻り値 変換行列が逆行列にできると、trueを返す。逆行列がつくれない場合にはfalseが返される。
Matrix3D.decompose()メソッド
文法 decompose(orientationStyle:String = "eulerAngles"):Vector.<Vector3D>
概要 Matrix3Dオブジェクトから平行移動と回転、および伸縮の3つの変換の情報を取出し、それらをVector3Dオブジェクトの3エレメントとして納めたVectorインスタンスで返す(Vectorクラスについては後述)。
引数

orientationStyle:String ― 回転の情報を取出すときに用いる方向スタイルの文字列。通常は、下表04-007のOrientation3Dクラスの定数を用いる。 デフォルト値は、回転をxyz各軸回りのラジアン角で示すオイラー角となるOrientation3D.EULER_ANGLES("eulerAngles")。なお、軸角度については後述Column 04「3次元座標空間における回転について」の「軸角度による回転」、「四元数(クォータニオン)」は数学編の同名の解説を参照。

表04-007■方向スタイルとOrientation3D定数
方向スタイル Vector3Dクラス定数 値の整数値
オイラー角 Orientation3D.EULER_ANGLES "eulerAngles"
軸角度 Orientation3D.AXIS_ANGLE "axisAngle"
四元数(クォータニオン) Orientation3D.QUATERNION "quaternion"

[*筆者用参考] Wikipedia「70秒で分る、使える、四元数・4元数・クォータニオン・ Quaternionで回転」。

戻り値

下表04-008の3つの変換情報をもつVector3Dオブジェクトのエレメントが納められたVectorインスタンス。

表04-008■Vectorインスタンス内のVector3Dエレメントがもつ変換情報
Vectorオブジェクト内のインデックス Vector3Dインスタンスのもつ変換情報 Vector3D.x/y/zプロパティの値
0 平行移動 xyz各座標値
1 回転 オイラー角 xyz各軸回りのラジアン角
軸角度 軸方向を表す単位ベクトルのxyz座標値とVector3D.wプロパティに回転のラジアン角
四元数 四元数のベクトル部のxyz座標値とVector3D.wプロパティにスカラー部の値
2 拡大・縮小 xyz各座標軸方向の伸縮率
Matrix3D.recompose()メソッド
文法 recompose(components:Vector.<Vector3D>, orientationStyle:String = "eulerAngles"):Boolean
概要 平行移動と回転、および伸縮の3つの変換の情報がVector3Dオブジェクトの3エレメントとして納められたVectorインスタンスにより、Matrix3Dインスタンスの変換行列の内容を書替える(Vectorクラスについては後述)。前掲シンタックス04-00504-007で紹介したメソッドとは異なり、変換の値を加えるのではなく、変換行列の値を上書きする。
引数

components:Vector.<Vector3D> ― 平行移動と回転、および伸縮の3つの変換の情報がVector3Dオブジェクトの3エレメントとして納められたVectorインスタンス(前掲表04-008参照)。

orientationStyle:String ― 回転の情報を取出すときに用いる方向スタイルの文字列。通常は、前掲表04-007のOrientation3Dクラスの定数を用いる。 デフォルト値は、オイラー角を示すOrientation3D.EULER_ANGLES("eulerAngles")。

戻り値 第1引数のVectorインスタンスに納められたエレメントのいずれかがnullのときfalseを返す。それ以外の場合にはtrueが返される。

Tips 04-011■Matrix3D.recompose()メソッドの戻り値
[ヘルプ]の[ActionScript 3.0言語およびコンポーネントリファレンス]で「Matrix3D.recompose()メソッド」を調べると、つぎのように拡大・縮小のVector3Dエレメントに値0が含まれるとfalseを返すと説明しています。

拡大/縮小エレメントのいずれかが0の場合は、falseを返します。

しかし、実際にはすべての値が0のVector3Dオブジェクトを拡大・縮小のエレメントに指定しても、メソッドはtrueを返します。もっとも、拡大・縮小のメソッドMatrix3D.appendScale()Matrix3D.prependScale()には値として0は渡せず、エラーになります(図04-030)。したがって、Matrix3D.recompose()メソッドについても、戻り値に拘らず、拡大・縮小のVector3Dエレメントには0を含めないよう注意した方がよいでしょう。

図04-030■Matrix3Dクラスの拡大・縮小のメソッドには値として0は渡せない
Matrix3D.appendScale()メソッドの引数のひとつに0を渡すと、エラーになる。

Maniac! 04-001■[ヘルプ]に掲載されている変換行列の図
本稿執筆時、[ヘルプ]の[ActionScript 3.0言語およびコンポーネントリファレンス]で[Matrix3D]の冒頭説明には、Matrix3Dの変換行列は下図のように表記されています。しかし、変換行列の演算でxyz各軸に対する値を決めるのは、各行ごとの要素(成分)です。

実際、[ヘルプ]の本文も以下のように解説しています。したがって、前掲シンタックス04-011の図04-029は、xyz軸の記載位置を変え、拡大・縮小の語を加えました。

マトリックスの最初の3行は3Dの各軸(x、y、z)のデータを保持します。平行移動情報は最後の列に格納されます。方向と拡大/縮小のデータは、最初の3列に格納されます。

[*筆者用参考] 「Matrix3Dクラス」。


Matrix3D()コンストラクタとMatrix3D.prepend()/Matrix3D.append()メソッド
まず、Matrix3Dクラスのコンストラクタメソッドは、インスタンスを新たに生成します。引数なしにコンストラクタを呼出すと、デフォルト状態つまり位置は親(DisplayObjectContainer)インスタンスの基準点で回転はなし、伸縮が実寸の変換行列を表すインスタンスがつくられます。したがって、このMatrix3DオブジェクトをプロパティDisplayObject.transformTransform.matrix3Dとして設定すれば、DisplayObjectインスタンスはデフォルト状態になります。

つぎに、Matrix3D.prepend()およびMatrix3D.append()メソッドです。これらは、平行移動や回転、あるいは伸縮といった変換をひとつひとつ行うのでなく、それら複数の操作が施された変換行列のMatrix3Dインスタンスをそのまま最初のあるいは最後の変換として適用します。

04-02「Matrix3Dクラスの先に加える座標変換」でご説明したとおり、"prepend"のメソッドはデフォルト状態のインスタンスに対して先に適用され、インスタンスがDisplayObject.transformプロパティにもっていた(Transform.matrix3Dプロパティの)Matrix3Dオブジェクトの変換は後から加えられます。たとえば、Matrix3D.prependRotation()メソッドであれば、デフォルト状態に戻したインスタンスを回してから、そのインスタンスの直前の変換が適用されました(再掲図04-021参照)。

図04-021■Matrix3D.prependRotation()メソッドは正面向きのインスタンスを回すことになる(再掲)

直前の状態

デフォルト状態

Matrix3D.prependRotation()
メソッドを適用

直前の変換を適用
デフォルト状態からの回転になるので、直前の状態のインスタンスを回すことにはならない。

上記シンタックス04-011のメソッドを用いれば、"append"のメソッドで"prepend"の処理をシミュレートできます。試しに、前掲スクリプト04-007の「インスタンスをマウスポインタの水平位置に応じてMatrix3D.prependRotation()メソッドで水平に回す」フレームアクションを、"prepend"のメソッドは使わずに書替えてみましょう(スクリプト04-012)。

スクリプト04-012■インスタンスをマウスポインタの水平位置に応じて水平に回す
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nDeceleration:Number = 0.3;
  3. my_mc.z = 0;
  4. addEventListener(Event.ENTER_FRAME, xRotate);
  5. function xRotate(eventObject:Event):void {
  6.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  7.   var myMatrix3D:Matrix3D = new Matrix3D();
  8.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  9.   myMatrix3D.append(my_mc.transform.matrix3D);
  10.   my_mc.transform.matrix3D = myMatrix3D;
  11. }

スクリプト04-012の第4〜11行目に定義したDisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)のリスナー関数xRotate()は、まず第7行目で引数なしにMatrix3Dクラスのコンストラクタを呼出して、デフォルト状態のMatrix3Dインスタンスを生成します。

つぎに、スクリプト第8行目は、そのMatrix3Dインスタンスに対してMatrix3D.appendRotation()メソッドを呼出します。デフォルトのMatrix3Dインスタンスに回転を適用することになりますから、結果としてMatrix3D.prependRotation()メソッドと同じ処理になります。

そして、スクリプト第9行目で、DisplayObjectインスタンスがDisplayObject.transformプロパティにもっていた(Transform.matrix3Dプロパティの)Matrix3Dオブジェクトを、Matrix3D.append()メソッドで適用します。

こうして変換されたMatix3Dオブジェクトを、スクリプト第10行目で改めてDisplayObject.transformプロパティに(Transform.matrix3Dプロパティとして)設定すれば、前掲スクリプト04-002でMatrix3D.prependRotation()メソッドを用いたのと同じく、インスタンスはマウスポインタの水平位置に応じて水平に回ります(再掲図04-001)。

図04-001■インスタンスの基準点から見たマウスポインタの水平位置に応じてインスタンスが水平回転する(再掲)
マウスポインタの水平位置がインスタンスの中心から離れるほど、インスタンスは速く水平に回る。

Matrix3D.clone()とMatrix3D.invert()メソッド
Matrix3Dクラスのメソッドの多くは、参照する変換行列を直接操作します。Matrix3Dオブジェクトには手を加えず、新たなインスタンスとして複製するにはMatrix3D.clone()メソッドを用います。また、Matrix3D.invert()メソッドは、参照するMatrix3Dインスタンスの変換行列を逆行列に変えて、変換を逆にすることができます。

これらのメソッドを使って、今度は前掲スクリプト04-009の「インスタンスをマウスポインタの位置に応じて水平および垂直方向に回す」フレームアクションを書替えてみましょう。このスクリプトでは、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)のリスナー関数xRotate()は、つぎのように第10行目で(DisplayObject)インスタンスを親(DisplayObjectContainer)インスタンスの基準点に移してから、水平および垂直に回し(第11〜12行目)、改めて第13行目でもとの位置に戻しています。

  1. function xRotate(eventObject:Event):void {
  2.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  3.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  4.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  5.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  6.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  7.   myMatrix3D.appendTranslation(nX, nY, 0);
  8. }

水平・垂直の回転は、マウスポインタの座標に応じていつも計算し直す必要があります。けれど、親インスタンスへの基準点に移動して、もとの位置に戻す処理はつねに同じです。すると、後者の変換行列となるMatrix3Dオブジェクトは予めつくっておいて、それを適用してもよいでしょう。その考え方にしたがって書替えたのが、つぎのスクリプト04-013です。

スクリプト04-013■インスタンスをマウスポインタの位置に応じて水平および垂直方向に回す2
    // フレームアクション
    // タイムラインにMovieClipインスタンスmy_mcを配置
  1. var nX:Number = my_mc.x;
  2. var nY:Number = my_mc.y;
  3. var nDeceleration:Number = 0.3;
  4. my_mc.z = 0;
  5. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  6. var translateToParent:Matrix3D = new Matrix3D();
  7. translateToParent.appendTranslation(-nX, -nY, 0);
  8. var translateToSelf:Matrix3D = translateToParent.clone();
  9. translateToSelf.invert();
  10. addEventListener(Event.ENTER_FRAME, xRotate);
  11. function xRotate(eventObject:Event):void {
  12.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  13.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  14.   myMatrix3D.append(translateToParent);
  15.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  16.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  17.   myMatrix3D.append(translateToSelf);
  18. }

まず、スクリプト04-013第6行目で新たにデフォルトのMatrix3Dインスタンスをつくり、第7行目でMatrix3D.appendTranslation()メソッドにより親インスタンスの基準点に移動する変換を加えて、変数(translateToParent)に納めます。

つぎに、もとの位置に戻す変換は、このMatrix3DインスタンスにMatrix3D.invert()メソッドを適用すれば得られます。しかし、親インスタンスの基準点に移動するMatrix3Dインスタンスは、とっておかなければなりません。そこで、スクリプト第8行目はMatrix3D.clone()メソッドでMatrix3Dオブジェクトを複製し、第9行目でその複製したオブジェクトを逆行列にしています。

あとはリスナー関数xRotate()の中で、DisplayObjectインスタンスを親インスタンスの基準点に移動するとき(第14行目)ともとの位置に戻すときに(第17行目)、それぞれのMatrix3DオブジェクトをMatrix3D.append()メソッドで適用するだけです。これで、マウスポインタの動きにしたがって、インスタンスが回転します(図04-031)。

図04-031■マウスポインタの動きにしたがってインスタンスが水平・垂直に回る
インスタンスの基準点を中心に、マウスポインタの位置に応じて、水平および垂直に回る。

この例の場合、前掲スクリプト04-009のまま、リスナー関数の中でMatrix3D.appendTranslation()メソッドを用いても、とくに問題はありません。ただ、決まった変換がいくつかあってそれを切替えるようなときは、予めそれらの変換行列をMatrix3Dオブジェクトとして用意しておくと、処理がまとめやすくなります。

[*筆者用参考]「3次元変換行列に別のMatrix3Dオブジェクトを変換として加えたい」。


Matrix3D.decompose()とMatrix3D.recompose()メソッド
Matrix3D.decompose()Matrix3D.recompose()メソッドは、平行移動と回転、および伸縮の情報をVector3Dオブジェクトにして、それらの値の取出しや設定を行います。3つのVector3Dオブジェクトは、さらにVectorという配列に似たインスタンスにエレメントとして納められます。Vector3DおよびVectorクラスについては、後の章で詳しく解説します。

たとえば、以下のフレームアクションは、Matrix3Dインスタンスに対して、水平に1.5倍、垂直に2倍拡大し、xyz軸でそれぞれ30度、45度、60度回転して、水平に100ピクセル、垂直に50ピクセル移動する変換を加えます。そのうえで、Matrix3D.decompose()メソッドを呼出すと、平行移動と回転、伸縮の情報がVectorインスタンスとして得られます。その値を[出力]すれば、エレメントとして納められた3つのVector3Dオブジェクトの値がつぎのように表示されます(見やすいように3つの情報の間に改行を入れました)。

Vector3D(100, 50, 0),
Vector3D(0.5235987305641174, 0.7853982448577881, 1.0471975803375244),
Vector3D(1.4999998807907104, 2, 1)

var myMatrix3D:Matrix3D = new Matrix3D();
myMatrix3D.appendScale(1.5, 2, 1);
myMatrix3D.appendRotation(30, Vector3D.X_AXIS);
myMatrix3D.appendRotation(45, Vector3D.Y_AXIS);
myMatrix3D.appendRotation(60, Vector3D.Z_AXIS);
myMatrix3D.appendTranslation(100, 50, 0);
trace(myMatrix3D.decompose());

3つのVector3Dオブジェクトのうち、最初のエレメント(インデックス0)は平行移動のxyz座標値で、最後のエレメント(インデックス2)がxyz軸方向の伸縮比率を表すことは、すぐにおわかりになるでしょう(ただ、変換行列の演算は小さな値の誤差が生じます)。2番目のエレメント(インデックス1)は、回転の情報を意味します。デフォルトでは、xyz各軸回りのラジアン角が示されます。

Word 04-001■オイラー角
3次元空間における回転をxyz軸回りの角度で示す方向スタイルは「オイラー角」と呼ばれます。


Maniac! 04-002■オイラー角の3軸
オイラー角は、3次元空間の3つの軸回りの角度で定義されます。ただし、数学的にはどの3軸を選んでどの順序で回すかは、何とおりか考えられます(回転の順序については、後述Column 04「3次元座標空間における回転について」の「Matrix3D.prependRotation()メソッドとDisplayObject.rotationX/rotationY/rotationZプロパティ」参照)。

この定義を考えたレオンハルト・オイラー自身は、まずz軸、つぎにx軸、そしてもう1度z軸で回す場合の3つの角度で回転を表しました。これをz-x-z系のオイラー角と呼びます。Matrix3Dクラスでは、x-y-z系のオイラー角を用います。

なお、度数にπ/180を乗じれば、ラジアン角に換算できます(前述01-03「三角関数で距離と角度から座標を捉える − 極座標」参照)。

trace(30 * Math.PI / 180, 45 * Math.PI / 180, 60 * Math.PI / 180);

// 出力: 0.5235987755982988 0.7853981633974483 1.0471975511965976

Matrix3D.recompose()メソッドは、平行移動と回転、および伸縮の3つのVector3Dオブジェクトがエレメントに納められたVectorインスタンスを引数として、Matrix3Dインスタンスの変換行列を書替えます。

このメソッドを用いるとき、平行移動と回転と伸縮の3つのVector3Dオブジェクトをすべてつくって、Vectorインスタンスに納め、引数に指定することがもちろん考えられます。しかし、Matrix3DインスタンスからMatrix3D.decompose()メソッドで現行の値を取出してから、その一部のVector3Dオブジェクトの値を書替えることも少なくないでしょう。

Matrix3D.recompose()メソッドを使ったスクリプトの例については、つぎのColumn 04「3次元座標空間における回転について」をお読みください。


Column 04 3次元座標空間における回転について
3次元座標空間における回転を扱うメソッドやプロパティについて、少し細かなご説明を4つほど加えておきます。


Matrix3D.prependRotation()メソッドとDisplayObject.rotationX/rotationY/rotationZプロパティ
Matrix3D.prependRotation()メソッドとDisplayObject.rotationX/rotationY/rotationZプロパティとでは、各軸の回転を適用する順序に違いがあります。たとえば、つぎのフレームアクションは、タイムラインに置いたインスタンスmy_mcをまずx軸で30度回し、つぎにy軸で60度回します(図04-032上図)。

var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
myMatrix3D.prependRotation(60, Vector3D.Y_AXIS);
myMatrix3D.prependRotation(30, Vector3D.X_AXIS);

ふたつのMatrix3D.prependRotation()メソッドのステートメントを入換えれば、まずy軸で60度回してからつぎにx軸で30度回すことになりますので、インスタンスの状態は当然変わってきます(図04-032下図)。

図04-032■Matrix3D.prependRotation()メソッドによりインスタンスをx軸とy軸で回転する
myMatrix3D.prependRotation(60, Vector3D.Y_AXIS);
myMatrix3D.prependRotation(30, Vector3D.X_AXIS);
myMatrix3D.prependRotation(30, Vector3D.X_AXIS);
myMatrix3D.prependRotation(60, Vector3D.Y_AXIS);
x軸で30度回してからy軸で60度回すのと、y軸で60度回してからx軸で30度回すのとでは結果が変わる。

ところが、DisplayObject.rotationXDisplayObject.rotationYプロパティでインスタンスを回すと、どちらのステートメントを先に書いても、まずx軸で回転し、つぎにy軸の回転が適用されます(図04-033)。

my_mc.rotationX += 30;
my_mc.rotationY += 60;
図04-033■DisplayObject.rotationXとDisplayObject.rotationYプロパティでインスタンスを回転する

my_mc.rotationX += 30;
my_mc.rotationY += 60;

// 以下でも同じ結果
// my_mc.rotationY += 60;
// my_mc.rotationX += 30;

DisplayObject.rotationXDisplayObject.rotationYプロパティでは、前者の回転が先に適用される。

したがって、適用する回転の軸の順序が問題となるような細かな変換を扱う場合には、Matrix3Dクラスを使った方がよいでしょう。


軸角度による回転
前述のとおりMatrix3D.prependRotation()(前掲シンタックス04-005)やMatrix3D.appendRotation()(前掲シンタックス04-007)メソッドの第2引数には「回転の軸または方向を示すVector3Dインスタンス」を渡します。このとき、Vector3Dクラス定数(前掲表04-004)を用いれば、xyz各軸のいずれかが指定できます。

しかし、それ以外のxyz座標値を与えたVector3Dインスタンスも、引数として渡せます。すると、メソッドはこのVector3Dインスタンスが示すベクトルを回転の軸とします。回転の軸を指定して表す角度は「軸角度」と呼ばれます。つまり、Matrix3D.prependRotation()Matrix3D.appendRotation()メソッドは、軸角度で回転できるのです。

たとえば、xy軸の成す直角を2等分する45度の軸で回転したいとしましょう。回転の軸は45度の方向で長さ1のベクトルを、Vector3Dインスタンスとして指定します。鋭角は45度で斜辺が1の直角2等辺三角形の他の2辺は、三平方の定理より長さがともに1/√2になります(図04-034)。

図04-034■回転の軸を長さ1のベクトルで指定する
鋭角は45度で斜辺が1の直角2等辺三角形の他の2辺は、長さがともに1/√2。

Vector3Dインスタンスは、コンストラクタメソッドにxyz座標を引数に渡して生成します。今回の例では回転軸のz座標は0です。つぎのフレームアクションは、Matrix3D.appendRotation()にこの45度のベクトルを軸に指定して、180度回転する処理になります。なお、Math.SQRT1_2は、1/√2(=約0.707)を値とする定数です。

var myMatrix3D:Matrix3D = new Matrix3D();
var axis:Vector3D = new Vector3D(Math.SQRT1_2, Math.SQRT1_2, 0);
myMatrix3D.prependRotation(180, axis);
trace(myMatrix3D.decompose()[1]);
trace(myMatrix3D.decompose(Orientation3D.AXIS_ANGLE)[1]);

スクリプトの最後の2行は、ともにMatrix3D.decompose()メソッド(シンタックス04-011)で取出したVectorインスタンスのインデックス1のエレメント、つまり回転が表されているVector3Dオブジェクトを[出力]しています。[出力]パネルには、つぎのように表示されます。

Vector3D(3.1415927410125732, 8.659276053295836e-17, 1.5707963705062866)
Vector3D(0.7071067094802856, 0.7071068286895752, -2.339619844353034e-24)

[出力]の第1行目は、回転のxyz各軸回りの角度を、ラジアン角で表しています。小さな誤差を丸めれば、x軸回りに180度、輪軸回りには0度、z軸回りに90度を意味します。たとえば、y軸上の点(0, 1)をx軸で180度回し(点(0, -1)に移動)、さらにz軸で90度回せばx軸上の点(1, 0)に移動し、45度の軸で180度回したのと同じ結果になります。

[出力]の2行目は、Matrix3D.decompose()メソッドの引数に軸角度を指定する定数Orientation3D.AXIS_ANGLEが渡されたので、回転の軸を示すベクトルの座標値が示されています。つまり、回転軸のVector3Dインスタンスがもつ座標(1/√2, 1/√2, 0)に当たる数値です。

Word 04-002■指数表記
以下の式の左辺のような数の表し方を、「指数表記」といいます。そして、ふたつの項のうち、最初の実数の項を「仮数部」、あとの10の累乗の項を「指数部」と呼びます。

1.23×10-4 = 0.000123

ActionScript 3.0では、指数記号eを使って、つぎのように示します。

1.23e-4

大きな数や小さい数を表すときに、よく使われます。大きい数の場合、指数は正の整数になります。

1.23e+4 = 1.23×104 = 12300

また、数値の精度が明らかになることも利点です。上記の例でいえば、仮数部は1.225以上1.235未満であることを意味するからです。数値の精度を表すとき、仮数部を「有効数字」と呼び、その桁数で示すことがあります。上記の例は有効数字が3桁ということです。有効桁数が大きいほど、数値の精度は高いことになります。

Matrix3D.recompose()メソッド(シンタックス04-011)で軸角度により回転を指定することもできます。その場合、回転を示すVector3Dインスタンスには、やはり回転軸となる長さ1のベクトルのxyz座標値を設定します。さらに、回転角はVector3Dオブジェクトが表すベクトルの第4成分(要素)となるVector3D.wプロパティに、ラジアン角で入れます。

以下のフレームアクションは、前掲スクリプトと同じく、Matrix3Dインスタンスにxy軸の成す角が2等分される角度45度の軸を指定して、πラジアン(180度)の回転を設定します。

平行移動と回転、および伸縮の情報をVector3DエレメントでもつVectorインスタンスは、スクリプトの第2行目でMatrix3D.decompose()メソッドにより得ています。データ型の指定は、Vectorクラスです。このクラスのデータには、エレメントのデータ型をドット(.)に続けて山括弧<>で指定します。

var 変数:Vector.<エレメントのデータ型>;
var myMatrix3D:Matrix3D = new Matrix3D();
var myVector:Vector.<Vector3D> = myMatrix3D.decompose(Orientation3D.AXIS_ANGLE);
var axis:Vector3D = new Vector3D(Math.SQRT1_2, Math.SQRT1_2, 0);
axis.w = Math.PI;
myVector[1] = axis;
myMatrix3D.recompose(myVector, Orientation3D.AXIS_ANGLE);
trace(myMatrix3D.decompose()[1]);
trace(myMatrix3D.decompose(Orientation3D.AXIS_ANGLE)[1]);

スクリプトは、まずMatrix3D.decompose()メソッドでVectorインスタンスを得ています。つぎに、回転軸のxyz座標が設定されたVector3Dインスタンスを生成し、Vector3D.wプロパティに回転角のラジアン値を与えます。そして、Vectorインスタンス内で回転のエレメントが納められるインデックス1を、このVector3Dオブジェクトで差替えました。

このように回転のVector3Dオブジェクトを設定したVectorインスタンスは、Matrix3D.decompose()メソッドに軸角度の定数Orientation3D.AXIS_ANGLEとともに渡され、Matrix3Dインスタンスを書替えます。[出力]結果は、つぎのようになります。

Vector3D(-3.141592502593994, -6.181724643283815e-8, 1.5707963705062866)
Vector3D(0.7071067690849304, 0.7071067690849304, 0)

前掲のMatrix3D.appendRotation()メソッドを使ったフレームアクションと比べると、小さな誤差があるほか、[出力]の第1行目でx軸回りの回転がマイナスの値になっています。しかし、180度の回転はプラスでもマイナスでも、結果として同じ変換になります。内部的に演算の処理が少し異なることによるものでしょう。


インスタンスをy軸で回転した角度が正しく設定されない
タイムラインに置いたMovieClipインスタンスをDisplayObject.rotationYプロパティで80度回転させてみます(図04-035左図)。

my_mc.rotationY = 80;
trace(my_mc.rotationY);   // 出力: 80

そのインスタンスをMatrix3D.prependRotation()メソッドでさらに20度回せば、回転角は100度になるはずです。インスタンスの見かけは、確かに100度回ります(図04-035右図)。けれども、DisplayObject.rotationYプロパティの値は80度のままです(数値は若干の誤差を含みます)。

var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
myMatrix3D.prependRotation(20, Vector3D.Y_AXIS);
trace(my_mc.rotationY);   // 出力: 80.00003182368921
図04-035■インスタンスをy軸で80度回した後さらに20度回転

y軸で80度回転

y軸でさらに20度回転
インスタンスの見かけは、それぞれ80度と100度回転している。

この原因は、y軸だけでなく、xとz軸回りの角度も確かめるとわかります。y軸で100度回したとき80度しか回転していない代わりに、x軸とz軸で反転(180度回転)しています。結果として、インスタンスはy軸で100度回転したのと同じ状態になるのです。この現象は、90度を境にして起こります。

my_mc.rotationY = 80;
trace(my_mc.rotationX, my_mc.rotationY, my_mc.rotationZ);
// 出力: 0 80 0
var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
myMatrix3D.prependRotation(20, Vector3D.Y_AXIS);
trace(my_mc.rotationX, my_mc.rotationY, my_mc.rotationZ);
// 出力: 180.00000500895632 80.00003182368921 180.00000500895632

もっとも、y軸回りに与えた角度がx軸やz軸の角度を変えてしまうことは、問題があると考えられます。また、x軸やz軸回りの角度であればMatrix3D.prependRotation()メソッドにより、たとえば100度に設定することもでき、他の軸の角度には影響を与えません。y軸回りの処理だけが異なる結果になるのは、数学的な演算として適切とはいえないでしょう。筆者としては、この動作はバグと評価します。

Matrix3D.prependRotation()メソッドでなくDisplayObject.rotationYプロパティを用いれば、y軸回りの角度を100度に設定することができます。ただしこの場合も、インスタンスのDisplayObject.transformプロパティからTransform.matrix3Dプロパティを取出して調べると、角度はy軸回りが80度(約1.40ラジアン)で、xとz軸は反転しています。

my_mc.rotationY = 80;
trace(my_mc.rotationX, my_mc.rotationY, my_mc.rotationZ);
// 出力: 0 80 0
my_mc.rotationY += 20;
// 出力: 0 100 0
trace(my_mc.rotationX, my_mc.rotationY, my_mc.rotationZ);
trace(my_mc.transform.matrix3D.decompose()[1]);
// 出力: Vector3D(3.1415927410125732, 1.3962639570236206, 3.1415927410125732)

Matrix3D.appendRotation()およびMatrix3D.prependRotation()メソッドの第3引数
Matrix3D.appendRotation()(シンタックス04-007)およびMatrix3D.prependRotation()(シンタックス04-005)メソッドの第3引数(pivotPoint)には、Vector3Dオブジェクトが指定できます。その役割について、[ヘルプ]にはつぎのように説明されています。

回転の中心を決定するポイントです。

この表現からは、メソッドによる回転の中心を、引数のVector3Dオブジェクトで指定できるように読めます。確かに、この引数で回転の中心がずらせます。しかし、内部的に行われる処理は、回転の後Vector3Dオブジェクトの座標値分だけ移動を加えるに過ぎません。

前述04-03「Matrix3Dクラスの後から加える座標変換」のとおり、インスタンスをある座標で回転したいときには、(1)その座標が親の基準点と合うように動かし、(2)そのうえでインスタンスを回し、(3)改めて位置をもとに戻します。(2)の回転にMatrix3D.prependRotation()メソッドを使った場合、第3引数は(3)の移動を加えるだけです。(1)の移動は相変わらず必要となることに注意しましょう。

前掲スクリプト04-009は、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)に以下のリスナー関数xRotate()を登録して、インスタンスをマウスポインタの位置に応じて水平および垂直方向に回しました。たとえば、このスクリプト第12行目のMatrix3D.prependRotation()メソッドに第3引数を加えれば、第13行目の移動のステートメントは省くことができます。

  1. function xRotate(eventObject:Event):void {
  2.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  3.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  4.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  5.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  6.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  7.   myMatrix3D.appendTranslation(nX, nY, 0);
  8. }

前掲スクリプト04-009を書替えて、Matrix3D.prependRotation()メソッドに第3引数を加えたのが、つぎのフレームアクションです。メソッドの引数に渡すVector3Dインスタンスは、予め変数(pivotPoint)として宣言しました。もちろん、動きはスクリプト04-009と同じです(再掲図04-031)。

  1. var nX:Number = my_mc.x;
  2. var nY:Number = my_mc.y;
  3. var pivotPoint:Vector3D = new Vector3D(nX, nY, 0);   // 追加
  4. var nDeceleration:Number = 0.3;
  5. my_mc.z = 0;
  6. var myMatrix3D:Matrix3D = my_mc.transform.matrix3D;
  7. addEventListener(Event.ENTER_FRAME, xRotate);
  8. function xRotate(eventObject:Event):void {
  9.   var nRotationY:Number = (mouseX - nX) * nDeceleration;
  10.   var nRotationX:Number = (mouseY - nY) * nDeceleration;
  11.   myMatrix3D.appendTranslation(-nX, -nY, 0);
  12.   myMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  13.   // myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);   // 第3引数を追加
  14.   myMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS, pivotPoint);
  15.   // myMatrix3D.appendTranslation(nX, nY, 0);
  16. }

図04-031■マウスポインタの動きにしたがってインスタンスが水平・垂直に回る(再掲)
インスタンスの基準点を中心に、マウスポインタの位置に応じて、水平および垂直に回る。

第3引数そのものが少しわかりにくいですし、複数の変換の内容と順序を明らかにする意味では、移動もはっきりとメソッドで行った方が見やすいように思います。なお、Matrix3D.prependRotation()メソッドでも、第3引数の移動は回転した後に加えられます。

[Prev/Next]


作成者: 野中文雄
作成日: 2009年12月21日


Copyright © 2001-2009 Fumio Nonaka.  All rights reserved.