サイトトップ

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

Now on Sale!!
ActionScript 3.0プロフェッショナルガイド

■Mailing List: ActionScript 3.0


F-siteセミナー

Flash Player 10で新たに加わった3Dの行列とベクトルを使ってみる

Date: 2008年11月15日 Product: Flash Platform: All Version: CS4/ActionScript 3.0

    index
  1. 配列を最適化したVectorクラス
  2. 変換行列を数学的に捉える
  3. 変換行列とMatrixクラス
  4. Matrix3Dクラスにおける変換行列の扱い
  5. サンプルファイル (Flash CS4形式/約72KB)


1. 配列を最適化したVectorクラス
Vectorクラスは、インスタンスに複数の値を納めて操作することができる[*1]。配列(Arrayクラス)によく似たクラスだ。

    【配列と似ている点】
  • 複数のエレメントを整数インデックスで管理し、配列アクセス演算子[]を使って操作できる。
  • 配列と同じlengthプロパティやpush()pop()slice()sort()などのメソッドを備える。

しかし、配列と異なる点もある。とくに、エレメントについて、データ型の指定を始めとする制約がある。

    【配列と異なる点】
  • すべてのエレメントをひとつのデータ型で指定し、他の型のデータはエレメントに加えることができない。
  • エレメントのインデックスは連番でなければならず、lengthプロパティの値より大きいインデックスにはエレメントが加えられない。

Vectorクラスのインスタンスを作成するコンストラクタメソッドは、つぎのようなシンタックス(文法)だ[*2]。他のクラスと異なるのは、コンストラクタの後にドット(.)とタグのような不等号の組(<>)を記述し、その中にエレメントのデータ型を指定することである。

Vector.<データ型>()

Vectorクラスを使うと、エレメントにデータ型が指定されるため、エレメントに値を設定するときだけでなく、取出した値についても型がチェックされる。また、エレメントへのアクセスも、配列と比べて高速だ。

    【Vectorクラスの利点】
  • エレメントにデータ型が指定でき、取出した値もそのデータ型をもつ。
  • エレメントへのアクセスが高速。

Vectorインスタンスの生成とエレメントの値の追加、変更、取出しは、つぎのように行う(スクリプト001)。エレメントのデータ型は、整数intで指定している。

スクリプト001■Vectorクラスでエレメントの追加・変更・取出しを行う

var myVector:Vector.<int> = new Vector.<int>();   // エレメントがint型のインスタンスを生成
myVector.push(0);   // エレメントに整数0を追加(インデックス0)
myVector.push(2);   // エレメントに整数2を追加(インデックス1)
myVector[1] = 1;   // インデックス1のエレメントを整数1に変更
myVector[2] = 2;   // インデックス2に整数2をエレメントとして追加(連番なのでOK)
// myVector[5] = 5;   // インデックスが連番にならないためエラー
// myVector[2] = "fumio";   // データ型がintではないので型不一致のエラー
var n:int = myVector[0];   // インデックス0のエレメントをint型の変数に代入
// var my_str:String = myVector[1];   // データ型が一致しないためエラー

Vectorインスタンスはエレメントのデータ型まで指定できるので、メソッドの引数にも適している(配列では、エレメントにどんなデータが入っているかわからない)。

たとえば、Flash Player 10から実装されたGraphics.drawTriangles()メソッドは3つの引数に、エレメントが数値型のVectorインスタンスを取り、第2引数はエレメントが整数(int)型で指定されている[*3]

drawTriangles(頂点座標:Vector.<Number>, 頂点番号:Vector.<int> = null, uvtデータ:Vector.<Number> = null):void

[*1] 「Vecotrクラス」のほか、つぎのようなサイトの情報を参照。Francis Cheng「Vectors in ECMAScript 4」、「Type Parameters in ECMAScript 4th Edition」、BeInteractive!「FlashPlayer10のVectorについて分かっていることまとめ」、「FlashPlayer10のVectorについて更に色々」、閃光的網站・弛緩複合体「[Astro] Vector クラス(1)」〜。

[*2] Vectorクラスのコンストラクタメソッドには、オプションとしてふたつの引数が指定できる。

Vector.<データ型>(長さ:uint=0, 長さの固定:Boolean=false)

第1引数の長さには、エレメント数を指定する。デフォルトは0だ。第2引数にtrueを指定すると、長さが固定される。デフォルトはfalseで、長さが変えられる。

[*3] Graphics.drawTriangles()メソッドにより長方形を任意の四角形に変形するスクリプトのサンプルは、11月18日発売の「Web Designing」2008年12月号で紹介した(図001)。

図001■Graphics.drawTriangles()メソッドで長方形を任意の四角形に変形


2. 変換行列を数学的に捉える[*4]
「行列」とは、数を行と列の長方形に並べて、その演算規則を定めたものだ。たとえば、2行×2列の行列は、下図002のように書き表す。

【行列とは】
数を行と列の長方形に並べて、その演算規則を定めたもの。

図002■2行×2列の行列
図001

行列は、加減算のほか、乗算ができる。乗算は、掛けられる左の行列の行と、掛合わせる右の行列の列とを、それぞれつぎのように計算する(図003)。

図003■2行×2列の行列と2行×1列の列ベクトルとの乗算[*5]
図003上図

図003下図

乗算では、掛けられる左の行列の列数と、掛合わせる右の行列の行数は等しくなければならない(図004上図)。したがって、行列では乗算の交換法則は成立たない(図004下図)。

図004■乗算では左の行列の列数と右の行列の行数は等しくなければならない
図004上図

図004下図

行と列の数が等しい行列を「正方行列」と呼ぶ。行数×列数の同じ正方行列同士を乗算すると、その結果も同じ行数×列数の正方行列になる(図005)[*6]。したがって、同じ行数×列数の正方行列は、いくつでも掛合わせられる。

図005■正方行列同士の乗算結果は同じ行数×列数の正方行列になる
図005

まとめると、行列の乗算における重要な特徴は以下のとおり。その性質は、フィルタに似ている(図006)。

    【行列の乗算の特徴】
  • 順序を変えると結果が変わる
  • 正方行列同士ならいくつでも掛けられる

図006■フィルタはいくつでも掛けられて順序により効果が変わる
図006

[*4] 詳しくは、「変換行列を数学的に捉える」参照。

[*5] 座標は1列(もしくは1行)の行列で表される。1列または1行の行列は、それぞれ「列ベクトル」または「行ベクトル」と呼ばれることがある。

[*6] したがって、正方行列は掛ける順序を交換しても、乗算はできる。しかし、計算結果の行列のそれぞれの値は、変わってくることに注意。正方行列同士を乗算する具体的な計算方法については、拙著『ActionScript 3.0プロフェッショナルガイド』M.6「行列」(p.629〜)あるいは「行列」などを参照。


3. 変換行列とMatrixクラス
変換行列は正方行列で、座標と乗じて値を変換する。平面のxy座標を2行×2列の正方行列に掛合わせると、拡大・縮小と回転が可能になる。ただし、これらの変換について、原点は固定され、動かすことができない。

Flashの座標空間では、インスタンスの配置された親タイムラインの基準点が原点になる。そのため、親の基準点(0, 0)をつねに原点として、インスタンスの拡大・縮小や回転が行われる。[自由変形ツール]で、中心点を親タイムラインの基準点に設定したのと同じ状態だ(図007)

図007■配置されたタイムラインの基準点を原点に変形
図008

この原点が固定される問題を解決する方法として、Matrixクラスの変換行列はひとつ列を増やした[*7]。そして、正方行列にするため、行数も列数に等しくする。また、掛合わせる列ベクトルも同じ行数にしなければならない。

各行列の3行目は、下図008上図のように値を固定する。変換行列を3行×3列にしたことにより、座標を平行移動することが可能になった。変換行列の3列目のtxとtyに、それぞれxy座標の移動ピクセル数を指定する(図008下図)。

図008■Matrixクラスの変換行列と列ベクトル
図008上図
図008下図

すると、親タイムラインの原点でなく、たとえばインスタンスの基準点を中心に変換することができる。そのときは、インスタンスの基準点を原点と一致するように移動し、拡大・縮小や回転などの変換を行ったうえで、初めとは反対方向に移動してもとの位置座標に戻せばよい。

[*7] Matrixクラスを使ったスクリプトの例は、Adobeデベロッパーセンターの「Matrixクラス − 変換行列」で紹介した。


4. Matrix3Dクラスにおける変換行列の扱い
Matrix3Dクラスは、3次元座標空間の変換行列を扱う。この変換行列は、Matrixクラスより1次元増えて、4行×4列の正方行列になる。しかし、Matrixクラスとは異なり、行列の中身は意識しなくてよい。

たとえば、3次元座標空間でDisplayObjectインスタンスを回転するには、Matrix3D.prependRotation()メソッドが使える。メソッドのシンタックスは、つぎのとおりだ。

prependRotation(度数:Number, 軸:Vector3D):void

第1引数には、加える回転角を度数で指定する。第2引数は、回転の軸を、定数Vector3D.X_AXISVector3D.Y_AXISVector3D.Z_AXISからひとつ選ぶ。

Matrix3Dクラスにより座標の変換を行うには、対象となるDisplayObjectインスタンスのDisplayObject.transformプロパティに保持される、Transform.matrix3DプロパティのMatrix3Dインスタンスを操作する。ただし、注意しなければならないのは、DisplayObjectインスタンスに対して3次元座標の操作をしないと、DisplayObject.transform.matrix3DプロパティにはMatrix3Dインスタンスが設定されず、nullが返されることだ。

それでは、タイムラインに配置したMovieClipインスタンスmy_mcを、マウスポインタの水平座標に応じて、y軸で水平に回転させてみよう[*8]。回転のアニメーションは、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)にリスナー関数xRotate()として登録する[*9]。すると、フレームアクションは、つぎのスクリプト001のようになる。

スクリプト001■3次元座標空間におけるインスタンスをy軸で回転させる

// タイムライン: メイン
// 第1フレームアクション
var nX:Number = my_mc.x;
var nDeceleration:Number = 0.3;   // 回転スピードの調整係数
my_mc.z = 0;   // 3次元座標のプロパティを操作
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = (mouseX - nX)*nDeceleration;
  // 3次元座標空間においてy軸でインスタンスを回転
  my_mc.transform.matrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
}

前述のとおり、MovieClipインスタンスmy_mcに3次元座標の操作をしないと、Matrix3Dインスタンスが設定されない。そこで、スクリプト001では、インスタンスmy_mcのDisplayObject.zプロパティにデフォルト値0を設定した。

Matrix3D.prependRotation()メソッドに設定する回転角は、インスタンスの基準点から見たマウスポインタの水平座標値に比例させている。したがって、インスタンスから水平方向にマウスポインタを離すほど、インスタンスが水平に回転するスピードは増すことになる(図009)。

図009■インスタンスがマウスポインタの位置に応じて水平に回転
図009

つぎに、前掲スクリプト001に、y軸の水平回転だけでなく、x軸による垂直の回転も加えよう。

var nX:Number = my_mc.x;
var nY:Number = my_mc.y;
var nDeceleration:Number = 0.3;
my_mc.z = 0;
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = (mouseX - nX)*nDeceleration;
  var nRotationX:Number = (mouseY - nY)*nDeceleration;
  my_mc.transform.matrix3D.prependRotation(nRotationY, Vector3D.Y_AXIS);
  my_mc.transform.matrix3D.prependRotation(nRotationX, Vector3D.X_AXIS);
}

試してみると、垂直方向の回転の軸に違和感がある。いわばインスタンスが正面を向いた状態で垂直に回り、配置されたタイムラインのx軸でなく、インスタンス自身のx軸で回転しているように見える(図010)。

図010■インスタンスがマウスポインタの位置に応じて水平に回転
図010

Matrix3D.prependRotation()メソッドは、回転の変換行列を、インスタンスがもつMatrixインスタンス(DisplayObject.transform.matrix3Dプロパティ)よりも前(prepend)に適用つまり乗算する。

すると、変換をまったく加えていないデフォルト状態のインスタンス(図001)[*10]に、Matrix3D.prependRotation()メソッドの回転が加えられ、その後にインスタンスのもつ変換行列が適用される。そのため、つねに正面向きの状態で回転することになる。

図011■変換を行う前のデフォルト状態
図011

これを避けてインスタンスの現在の状態で回すには、後(append)から回転するMatrix3D.appendRotation()メソッドを使えばよい。ただし、そのままでは回転の原点が親タイムラインの基準点になってしまう。

したがって、前項(「3. 変換行列とMatrixクラス」)で確認したように、一旦インスタンスを親タイムラインの基準点に平行移動し、変換を加えた後、位置をもとに戻す必要がある。

後から平行移動するメソッドは、Matrix3D.appendTranslation()だ。引数は3つで、xyzの各座標値を指定する。この考え方に沿ってスクリプト001を修正したのが、つぎのフレームアクションである(スクリプト002)。

スクリプト002■3次元座標空間におけるインスタンスをx軸とy軸で回転させる

// タイムライン: メイン
// 第1フレームアクション
var nX:Number = mySprite.x;
var nY:Number = mySprite.y;
var nDeceleration:Number = 0.3;
mySprite.z = 0;
addEventListener(Event.ENTER_FRAME, xRotate);
function xRotate(eventObject:Event):void {
  var nRotationY:Number = (mouseX - nX)*nDeceleration;
  var nRotationX:Number = (mouseY - nY)*nDeceleration;
  // 3次元座標空間でインスタンスを回転
  mySprite.transform.matrix3D.appendTranslation(-nX, -nY, 0);   // 親タイムラインの基準点に移動
  mySprite.transform.matrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  mySprite.transform.matrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  mySprite.transform.matrix3D.appendTranslation(nX, nY, 0);   // 位置を戻す
}

これで、ステージ上のインスタンスの状態をもとに、マウスポインタの位置に応じてインスタンスがx軸およびy軸で回転する(図012)。

図012■ステージ上のインスタンスの状態から水平・垂直に回転
図011

[*8] このスクリプトについての解説は、Adobeデベロッパーセンターに「Matrix3Dクラス − 変換行列2」として寄稿した。詳しくは、その記事をお読みいただきたい。

[*9] EventDispatcher.addEventListener()メソッドを使ったイベントリスナーの登録の仕方について、詳しくはGihyo.jp連載「ActionScript 3.0で始めるオブジェクト指向スクリプティング」の第5回「イベントリスナーを使う」を参照。

[*10] デフォルト状態のインスタンスは、位置が親タイムラインの基準点、拡大・縮小は比率1、回転角が0になる。


作成者: 野中文雄
作成日: 2008年11月17日


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