サイトトップ

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

Adobe Flash非公式テクニカルノート

3次元空間における面の向きを調べる

ID: FN1008001 Product: Flash CS4 and above Platform: All Version: 10 and above/ActionScript 3.0

3次元座標空間で面を扱うとき、その向きを知りたいことがあります。面の向きは、面に対する垂線のベクトルで定められます。これを「法線ベクトル」と呼びます。なお、本稿では面をDisplayObjectインスタンス、または3頂点の3次元座標で表される3角形で考えることにします。


01 ベクトルの外積から法線ベクトルを導く
3角形の3頂点座標が与えられたとき、ベクトルの外積を使って法線ベクトルが求められます。3頂点のうちのひとつと他の2頂点をそれぞれ結んで、ベクトルAおよびBとします。そのとき、ベクトルAとBの外積A×Bは、ふたつのベクトルAおよびBのどちらにも垂直なベクトルを導きます。

外積A×Bのベクトルの向きは、ベクトルAからBに右ネジを回して進む先になります(図001左図)[*1]。また、その大きさは、ふたつのベクトルAとBを2辺とする平行四辺形の面積に等しいです(図001右図)。なお、3次元空間におけるベクトルの外積について詳しくは、「Vector3D.crossProduct()メソッド」をお読みください。

図001■ベクトルの外積
cross product cross product

3頂点の座標をVector3Dクラスのインスタンスとして生成すると、外積はVector3D.crossProduct()メソッドを使って求めることができます。以下のスクリプト001は、そのやり方で法線ベクトルを導く簡単な計算例です。3頂点のxyz座標は、頂点0(0, 0, 0)と頂点1(100, 0, 0)および頂点2(100, 100, 0)です(スクリプト第1〜3行目)。また、座標はMatrix3Dオブジェクトの変換行列により、z軸で90度、y軸で45度、さらにx軸で45度回転します(スクリプト第4〜7行目)。

スクリプト001■3角形の3頂点から外積により法線ベクトルを求める
    // フレームアクション
    // 3頂点座標のVector3Dインスタンス生成
  1. var vector3D_0:Vector3D = new Vector3D(0, 0, 0);   // 頂点0
  2. var vector3D_1:Vector3D = new Vector3D(100, 0, 0);   // 頂点1
  3. var vector3D_2:Vector3D = new Vector3D(100, 100, 0);   // 頂点1
  4. // Matrix3Dオブジェクトの変換行列を作成
  5. var myMatrix3D:Matrix3D = new Matrix3D();
  6. myMatrix3D.appendRotation(90, Vector3D.Z_AXIS);   // z軸で90度回転
  7. myMatrix3D.appendRotation(45, Vector3D.Y_AXIS);   // y軸で45度回転
  8. myMatrix3D.appendRotation(45, Vector3D.X_AXIS);   // x軸で45度回転
  9. // 3頂点の座標を行列変換
  10. vector3D_0 = myMatrix3D.transformVector(vector3D_0);
  11. vector3D_1 = myMatrix3D.transformVector(vector3D_1);
  12. vector3D_2 = myMatrix3D.transformVector(vector3D_2);
  13. // ふたつのベクトルの外積から法線ベクトルを導く
  14. var vector3D_0_1:Vector3D = vector3D_1.subtract(vector3D_0);
  15. var vector3D_0_2:Vector3D = vector3D_2.subtract(vector3D_0);
  16. var vector3D_z0:Vector3D = vector3D_2.crossProduct(vector3D_1);
  17. vector3D_z0.normalize();   // ベクトルの正規化
  18. trace(vector3D_z0);  
  19. // 出力: Vector3D(-0.7071067677898997, 0.5000000094728604, -0.5000000094728603)

Vector3Dインスタンスの座標にMatrix3Dオブジェクトの行列変換を加えるのが、Matrix3D.transformVector()メソッドです。変換された座標をもつ新たなVector3Dオブジェクトが返されます(スクリプト第8〜10行目)。これらの新たな座標が納められた3頂点のVector3Dオブジェクトから外積により、行列で変換された3角形の法線ベクトルを求めます。

まず、頂点0から1と頂点0から2へのふたつのベクトルを求めます。2点を結ぶベクトルは、終点から始点を差引いて導きます。引き算のメソッドは、Vector3D.subtract()です(スクリプト第11〜12行目)。つぎに、得られたふたつのベクトルから、外積により法線ベクトルを計算します。3角形の3頂点は、0-1-2の順に時計回りです。面は初め、視点に対する手前向きと想定します(図002)。すると、右ねじの向きは視点から見て反時計回りですから、終点が頂点2のベクトル(vector3D_2)に対して、終点が頂点1のベクトル(vector3D_1)の外積を求めます(スクリプト第13行目)。

図002■3角形の面の向きは視点に対する手前向きとする

得られた外積のベクトル(vector3D_z0)は、面の向きを調べるうえではそのままでも問題はありません。しかし、大きさが1の単位ベクトルにしておくと、使いやすくなります。そこで、Vector3D.normalize()メソッドにより、単位法線ベクトルに直しました(スクリプト第14行目)。この法線単位ベクトルのVector3Dオブジェクトを[出力]すると、位置ベクトルとしてのxyz座標値は少し誤差を含むものの、(√2/2, 0.5, -0.5)になります(スクリプト第15行目)。

[*1] したがって、ふたつのベクトルの順序を入換えた外積は、ベクトルの方向が逆になります。つまり、外積の計算には交換法則が成立ちません。なお、外積から導かれるベクトルが右ねじの向きに定められるのは、Flashの3次元座標空間である「右手座標系」、つまりz軸が奥に向かって正となる場合です(図003)。「右手座標系」については、「Vector3Dクラス」をご参照ください。

図003■Flash Player 10の3次元直交座標系

* [ヘルプ] > [Adobe Flash Professional CS5用ActionScript 3.0リファレンスガイド] > [Vector3D]より引用


02 回転行列からz軸の方向を取出す
前掲スクリプト001では、3角形の頂点座標に回転を加えました。この変換を与えるMatrix3Dオブジェクトは、「回転行列」と呼ばれます。そして、回転行列はその成分から面の向きが調べられます。前述のように「面は初め、視点に対する手前向きと想定」するなら、その方向はz軸と同じです。すると、回転行列によりそのz軸の方向がどう変わるかを確かめれば、面の向きはわかるのです。

それでは、回転行列で各座標軸のベクトルがどう変換されるかを、まず2次元平面で確かめてみましょう。2次元平面で原点(0, 0)を中心に角度θ回す回転行列は、つぎのような2次(2行×2列)の正方行列です。この回転行列に2次元の位置ベクトルを掛合わせると、座標が回転されます。変換行列と位置ベクトルの乗算について詳しくは、「変換行列を数学的に捉える」をお読みください。

cosθ   -sinθ
sinθ   cosθ

x軸およびy軸がこの回転行列でどのように変換されるかは、それぞれの単位ベクトル(1, 0)と(0, 1)を以下のように掛合わせてみればわかります。回転行列にx軸の単位行列(1, 0)を乗じると、行列の第1列の成分である(cosθ, sinθ)が積つまり変換された位置ベクトルになります。y軸の単位ベクトル(0, 1)も同じように、回転行列の第2列の成分(-sinθ, cosθ)に変換されます。

cosθ   -sinθ 1 = cosθ     cosθ   -sinθ 0 = -sinθ
sinθ   cosθ 0 sinθ sinθ   cosθ 1 cosθ

これは何も行列計算をしなくても、x軸の点(1, 0)とy軸の点(0, 1)が角度θ回転したら単位円上のどの位置に移るかを三角関数で考えても同じ結果に至ります(図004)[*2]

図004■単位円上のx軸の点(1, 0)とy軸の点(0 1)を角度θ回す

3次元座標空間の変換行列は、基本が3次正方行列になります。回転行列によるxyz軸の変換については、2次元平面のときと変わりません[*3]。回転行列の第1列の成分がx軸、第2列の成分がy軸の変換された単位ベクトルを示します。z軸の単位ベクトル(0, 0, 1)も、つぎのように回転行列の第3列成分に変換されます。

a11   a12   a13 0 = a13
a21   a22   a23 0 a23
a31   a32   a33 1 a33

これで回転行列からz軸の単位行列がどの向きに変換されたかを知ることができます。つまり、面の向きがわかるのです。ただし、Matrix3Dオブジェクトを扱う場合には、注意がふたつあります。

第1に、Matrix3Dオブジェクトが表す変換行列は4次正方行列です。これは平行移動の変換が加えられるように、正方行列の次数をひとつ増やしたことによります[*4]。けれど、回転が3行×3列の9成分(a11〜a33)で定まることには変わりありません。

a11   a12   a13   tx x
a21   a22   a23   ty y
a31   a32   a33   tz z
0   0   0   1 1

第2に、「面は初め、視点に対する手前向きと想定」しました。z軸は奥向きが正ですので、「手前向き」のz軸の単位行列は(0, 0, -1)としなければなりません。つまり、回転行列の第3列の成分から取出したz軸の向きを逆にする必要があるのです。具体的には、z軸の方向を示す位置ベクトルに-1を乗じます。

それでは、前掲スクリプト001に加えるかたちで、Matrix3Dオブジェクトから面の方向を示すベクトルを取出してみましょう(スクリプト002)。スクリプト001より少ない処理で、同じ結果が得られます[*5][*6]

スクリプト002■回転行列から面の向きのベクトルを取出す
    // フレームアクションに追加
  1. var myData:Vector. = myMatrix3D.rawData;   // 変換行列の全成分をVectorオブジェクトで得る
  2. // 変換行列の第3列成分を取出す
  3. var vector3D_z1:Vector3D = new Vector3D(myData[8], myData[9], myData[10]);
  4. vector3D_z1.scaleBy(-1);   // ベクトルに-1を乗じる
  5. trace(vector3D_z1);
  6. // 出力: Vector3D(-0.7071067690849304, 0.4999999701976776, -0.4999999701976776)
  7. trace(vector3D_z1.nearEquals(vector3D_z0, 0.000001));   // 出力: true

Matrix3Dオブジェクトが表す変換行列の全成分は、Matrix3D.rawDataプロパティで数値(Number)のVectorオブジェクトとして得られます(スクリプト第1行目)。成分は列の順序でVectorインスタンスのエレメントに納められますので、インデックス8〜10が変換行列の第3列の3成分になります(スクリプト第2行目)。Vector3Dオブジェクトの3次元ベクトルに数値(スカラー)を乗じるには、Vector3D.scaleBy()メソッドにその数値を渡します(スクリプト第3行目)。

得られたVector3Dオブジェクトを[出力]すると、スクリプト001とわずかな誤差はあるものの、ほぼ同じ値が表示されます(スクリプト第4行目)。なお、回転の変換は、座標の原点からの距離を変えません。したがって、単位行列の大きさも変わらないので、ベクトルの正規化は要りません。

スクリプト002の第5行目は、得られた面の方向のVector3Dオブジェクトをスクリプト001と比べています。メソッドVector3D.nearEquals()は、参照するVector3Dオブジェクトと第1引数のVector3Dオブジェクトとを、第2引数の許容値(誤差)の範囲で等しいかどうかを返します。

[*2] 三角関数については、「角度と座標の計算 − Flash の三角関数を使う」および「座標と三角関数と、時々、ベクトル」をお読みください。

[*3] xyz各軸による回転行列と座標の変換結果は、つぎのとおりです。

【z軸による回転】
cosθ   -sinθ   0 x = x cosθ - y sinθ
sinθ   cosθ   0 y x sinθ + y cosθ
0   0   1 z z
【x軸による回転】
1   0   0 x = x
0   cosθ   -sinθ y y cosθ - z sinθ
0   sinθ   cosθ z y sinθ + z cosθ
【y軸による回転】
cosθ   0   sinθ x = z sinθ + x cosθ
0   1   0 y y
-sinθ   0   cosθ z z cosθ - x sinθ

[*4] 平行移動は変換行列の第4列の成分により行われます。このように平行移動が加わった行列変換を「アフィン変換」と呼びます。変換する位置ベクトルを納めるVector3Dオブジェクトも、これに合わせて第4成分をもちます。もっとも、Matrix3Dオブジェクトが表す変換行列の第4行目とVector3Dオブジェクトの第4成分となるVector3D.wプロパティはオプションとして扱われます。

[*5] スクリプト001の第1〜7行目は初期設定ですので、スクリプト002と共通の処理になります。しかし、座標に対して変換を加える(スクリプト001第8〜10行目)必要はないという利点があります。

[*6] この手法を活用した例としては、ClockMaker Blog「PV3D演出サンプルNo.08:カスタムフラットシェーディング」が参考になります。


03 回転以外にも変換が加わった行列
前項02の「回転行列からz軸の方向を取出す」やり方は、回転以外の変換が加わった行列でも使えるでしょうか。まず、平行移動は面の向きを変えません。実際、平行移動は変換行列の第4列目の成分で定められます。第3列目のz軸の方向を決める成分は変わらないのです。

つぎに、伸縮は一般に面の向きも変えてしまいます。変換行列の第3列目の成分で単純に面の方向のベクトルは定まりません。ただ、xyzの3軸すべてに同じ伸縮率を与えた場合のみ、面の向きは変わらず、変換行列の第3列目の成分が方向のベクトルを示します(ただし、正規化はされていません)。



作成者: 野中文雄
作成日: 2010年8月13日


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