サイトトップ

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

ActionScript 3.0 for 3D

□07 三角形分割によるテクスチャマッピング − Graphics.drawTriangles()メソッド

前章06「3次元空間の座標を扱う − Vector3Dクラス」では、3次元座標のデータの扱い方を学び、おもにワイヤーフレームでアニメーションを描きました。この章では、3次元座標で描かれる図形にビットマップを貼ってみます。3次元グラフィックスでは、この素材となるビットマップを「テクスチャ」といい、透視投影を加えたうえでビットマップを貼ることは「マッピング」と呼ばれます。本編の最後となるこの章のお題は、「テクスチャマッピング」です。


Word 07-001■テクスチャマッピング
テクスチャマッピングとは、テクスチャとなる素材のビットマップを、オブジェクトの面に合わせて貼る(塗る)ことです。オブジェクトの表面に質感や現実感を与えます。


07-01 Graphicsオブジェクトをビットマップで塗る
図形をビットマップで塗ることそのものは、何も3次元空間にかぎった操作ではありません。Graphicsクラスの塗りの指定として、Flash Player 9から実装されています。メソッドはGraphics.beginBitmapFill()で、役割は前章で用いたGraphics.beginFill()メソッド(シンタックス06-006)と同じです。描画する前に塗りの設定をします。もちろん、塗りの内容が変わりますので、引数は違ってきます(シンタックス07-001)。

シンタックス07-001■Graphicsクラスでビットマップの塗りを指定する
Graphics.beginBitmapFill()メソッド
文法 beginBitmapFill(bitmap:BitmapData, matrix:Matrix = null, repeat:Boolean = true, smooth:Boolean = false):void
概要 以降の描画に対して適用されるビットマップの塗りを指定する。
引数

bitmap:BitmapData ― 塗りとして用いるBitmapDataインスタンス。

matrix:Matrix ― ビットマップに変形として適用する変換行列のMatrixオブジェクト。デフォルト値はnull(指定なし)。

repeat:Boolean ― 描画する範囲がビットマップのサイズより大きいとき、タイル状に塗りのパターンを繰返すかどうか指定するブール(論理)値。trueなら繰返され(タイル表示)、falseではビットマップの端のピクセルで引き伸ばしたように塗りつぶす。デフォルト値はtrue

smooth:Boolean ― ビットマップに変形を加えた場合、レンダリングのアルゴリズムとして双線形補間法を使うかどうか指定するブール(論理)値。trueは双線形補間法を用いる。falseでは最近傍法が使われ、処理は速くなるものの、歪み(ジャギー)の生じることがある。デフォルト値はtrue

戻り値 なし。
Graphics.drawCircle()メソッド
文法 drawCircle(x:Number, y:Number, radius:Number):void
概要 中心の座標と半径を指定して円を描く。
引数

x:Number ― Graphicsオブジェクトが属するDisplayObjectインスタンスの基準点から見た円の中心の水平ピクセル座標値。

y:Number ― Graphicsオブジェクトが属するDisplayObjectインスタンスの基準点から見た円の中心の垂直ピクセル座標値。

radius:Number ― 円の半径を指定するピクセル数。

戻り値 なし。
Graphics.drawRect()メソッド
文法 drawRect(x:Number, y:Number, width:Number, height:Number):void
概要 左上隅の座標と幅および高さを指定して矩形を描く。
引数

x:Number ― Graphicsオブジェクトが属するDisplayObjectインスタンスの基準点から見た左隅ピクセル座標値。

y:Number ― Graphicsオブジェクトが属するDisplayObjectインスタンスの基準点から見た上隅ピクセル座標値。

width:Number ― 矩形の幅のピクセル値。

height:Number ― 矩形の高さのピクセル値。

戻り値 なし。

描くのは、前章と同じくGraphics.moveTo()やGraphics.lineTo()メソッドを用いた線形状でも、もちろん構いません。けれど、ここでは円や矩形を簡単に描けるメソッドを使いましょう。Graphics.drawRect()メソッドは、左上角の座標および幅と高さの定められた矩形を描きます(シンタックス07-001)。

たとえば、[ライブラリ]のビットマップに[クラス]Imageを指定してあるとき、以下のフレームアクションは左上隅座標(0, 0)で幅と高さが120×120ピクセルの矩形に、そのビットマップを塗ります。なお、ビットマップの大きさは100×100ピクセルです。Graphics.beginBitmapFill()メソッドのデフォルト(第3引数)では、描画する領域がビットマップより大きいと、タイル状に塗りのパターンを繰返します(図07-001)。

var mySprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
var myTexture:BitmapData = new Image(0, 0);
addChild(mySprite);
myGraphics.beginBitmapFill(myTexture);
myGraphics.drawRect(0, 0, 120, 120);
myGraphics.endFill();

図07-001■[ライブラリ]のビットマップで矩形を塗る
描画領域がビットマップより大きいと、デフォルトではタイル状に塗られる。

つぎのフレームアクションは、上記と同じビットマップで中心座標(30, 30)、半径30ピクセルの円を塗ります。そして、矩形は左上角座標(60, 30)に加えました。ビットマップの塗りは、Graphicsオブジェクトが属するDisplayObject(Sprite)インスタンス(mySprite)の基準点を起点とします(図07-002)。

var mySprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
var myTexture:BitmapData = new Image(0, 0);
addChild(mySprite);
myGraphics.beginBitmapFill(myTexture);
myGraphics.drawCircle(30, 30, 30);
myGraphics.drawRect(60, 30, 120, 120);
myGraphics.endFill();

図07-002■[ライブラリ]のビットマップで円と矩形を塗る
ビットマップの塗りは、DisplayObjectインスタンスの基準点を起点とする。

Graphics.beginBitmapFill()メソッドの第2引数にMatrixオブジェクトを渡すと、塗りのビットマップに変換が加えられます。つぎのフレームアクション(スクリプト07-001)は、矩形の位置と大きさに合わせてビットマップの塗りに変換を加えています(図07-003)。

スクリプト07-001■Graphics.beginBitmapFill()メソッドの第2引数でビットマップの塗りを変換する
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImageを設定
  1. var mySprite:Sprite = new Sprite();
  2. var myGraphics:Graphics = mySprite.graphics;
  3. var myTexture:BitmapData = new Image(0, 0);
  4. var myMatrix:Matrix = new Matrix();
  5. myMatrix.scale(1.2, 1.2);
  6. myMatrix.translate(60, 30);
  7. addChild(mySprite);
  8. myGraphics.beginBitmapFill(myTexture, myMatrix);   // 第2引数で変換を加える
  9. myGraphics.drawCircle(30, 30, 30);
  10. myGraphics.drawRect(60, 30, 120, 120);
  11. myGraphics.endFill();

図07-003■ビットマップの塗りに変換を加えて矩形に合わせる
Graphics.beginBitmapFill()メソッドの第2引数にMatrixオブジェクトを渡すと、でビットマップの塗りが変換できる。

ビットマップの大きさが100×10ピクセルなのに対して、矩形は120×120ピクセルで左上角座標が(60, 30)です。そこで、スクリプト第4〜6行目で、新たなMatrixインスタンスを、幅と高さともに1.2倍し、水平方向に60ピクセル、垂直方向は30ピクセルだけ平行移動しました。そして第8行目は、そのMatrixオブジェクトをGraphics.beginBitmapFill()メソッドの第2引数に渡しています。


07-02 Graphics.drawTriangles()メソッド
Graphics.drawTriangles()メソッドを使うと、いくつもの三角形が描けます。その塗りにビットマップを指定すれば、それぞれの三角形ごとに変形も加えられます。さらに、遠近法(透視)投影することで、3次元空間の表現ができます。


Graphics.drawTriangles()メソッドの第1引数 − 頂点座標
Graphics.drawTriangles()メソッドを使うと、三角形をいくつでも描くことができます(シンタックス07-002)。たとえば、下図07-004のようなふたつの三角形を描いてみましょう。このとき、ふたつの三角形の頂点座標を3つずつ計6つ、Graphics.drawTriangles()メソッドの第1引数に渡します。

図07-004■ふたつの三角形を描く

ふたつの三角形の頂点座標を3つずつ、計6つ指定する。

シンタックス07-002■Spriteインスタンスに対する線描のためのプロパティとメソッド
Graphics.drawTriangles()メソッド
文法 drawTriangles(vertices:Vector.<Number>, indices:Vector.<int> = null, uvtData:Vector.<Number> = null, culling:String = "none"):void
概要 頂点座標の指定により、複数の三角形を描く。塗りに指定したビットマップには変形が加えられる。
引数

vertices:Vector.<Number> ― 描画する三角形の頂点のxy座標を列挙して納めた数値のVectorインスタンス。ふたつの数値ごとにひと組のxy座標を示す。

indices:Vector.<int> ― 三角形の3頂点の組を頂点番号(インデックス)で列挙した整数のVectorインスタンス。インデックスは0から始まり、第1引数verticesのxy座標値の組に対して順に設定される。つまり、この引数のインデックスi(indices[i])の頂点番号は、第1引数のインデックス2iと2i + 1のxy座標(vertices[2i], vertices[2i + 1])に対応する。引数のインデックス3つごとにひとつの三角形を表す。引数がnull(デフォルト)のときは、第1引数の頂点座標の組3つ(xy座標の値は6つ)ごとに三角形が描画される。

uvtData:Vector.<Number> ― 描画する三角形に塗りとして設定されるビットマップの座標を列挙して納めた数値のVectorインスタンス。これらはuv座標と呼ばれ、(0, 0)がビットマップの左上隅、(1, 1)はその右下隅となる。第1引数verticesのxy座標に対応する。ただし、この引数のエレメント数が引数verticesのエレメント数の1.5倍となる場合はt座標が加わり、3つの数値でひと組のuvt座標となる。t座標値は、視点からテクスチャまでの3次元の距離として扱われ、塗りに遠近法が適用される。デフォルト値はnull(指定なし)で、分割・変形が加えられることなく、通常の塗りとしてビットマップが設定される。

culling:String ― 視線となるz軸に対する三角形の面の向きにより描画するかどうかを指定する文字列。TriangleCullingクラス定数により設定し、TriangleCulling.NONE(デフォルト)はすべてを描画する。三角形の頂点番号を時計回りに設定した場合、TriangleCulling.NEGATIVEはz軸で正となる奥向きの面、TriangleCulling.POSITIVEではz軸負の手前に向く面が描画されない。

戻り値 なし。

Maniac! 07-001■[ヘルプ]のVector3D.crossProduct()メソッドの第3引数uvtDataについての説明
[ヘルプ]で[Graphics]クラスの「drawTriangles()メソッド」の項を読むと、第3引数uvtDataについて本稿執筆時点ではつぎのように説明されています。なお、「ベクター」というのは、Vectorインスタンスを意味します。

このベクターの長さがverticesベクターの2倍である場合、外見上の修正を行うことなく、正規化座標が使用されます。

このベクターの長さがverticesベクターの3倍である場合、3番目の座標は、「t」(視点空間における視点からテクスチャまでの距離)として解釈されます。

しかし、前の文は「2倍」ではなく「同じ」、後の文は「3倍」でなく「1.5倍」が正しいです。第3引数uvtDataのVectorエレメントについて、前者は(u, v)、後者は(u, v, t)の座標の組が、第1引数verticesの(x, y)座標値のVectorエレメントに対応するからです。

ちなみに、「正規化座標」というのは、それぞれが0から1までの値をとる(u, v)座標を指すと考えられます。

Graphics.drawTriangles()メソッドの第1引数には、数値(Number型)のVectorオブジェクトを指定します。Number型ということは、数値エレメントの羅列です。すると、その順番が問題です。まず、数値エレメントはふたつずつ組にして、(x, y)座標として扱われます。つぎに、(x, y)座標が3つで、ひとつの三角形を表します。三角形のどの頂点から始めるかは自由です。ただ、その順序は時計回りか反時計回りかに決めておく方が、後あと便利です。本稿では、時計回りにします。そして、三角形の順番は、ここでは気にしなくて構いません。

つぎのフレームアクションは、上図07-004で示したふたつの三角形を描き、[ライブラリ]のビットマップ([クラス]はImage)で塗ります(図07-005)。

var mySprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
var myTexture:BitmapData = new Image(0,0);
// 頂点座標を納めるVectorインスタンス生成
var vertices:Vector.<Number> = new Vector.<Number>();
addChild(mySprite);
// ふたつの三角形の6頂点座標をVectorインスタンスに納める
vertices.push(0, 0);
vertices.push(100, 0);
vertices.push(0, 100);
vertices.push(120, 20);
vertices.push(120, 120);
vertices.push(20, 120);
myGraphics.beginBitmapFill(myTexture);
myGraphics.drawTriangles(vertices);   // 頂点座標のVectorインスタンスを引数に渡す
myGraphics.endFill();

図07-005■ふたつの三角形をビットマップで塗る

デフォルトでは、ビットマップがタイル状に塗られる。

上記スクリプトは、頂点座標を納める数値のVectorインスタンスを生成して、ふたつの三角形の6頂点座標つまり12の数値エレメントをVector.push()メソッドで納めています。しかし、12の数値を1度に格納すると、見にくいうえに、数や組合わせを間違えるおそれがあります。そこで、頂点ごとにxy座標を組にして、6回に分けて数値を納めました。

Tips 07-001■三角形のアウトラインを描く
Graphics.lineStyle()メソッドで線の設定をしておけば、三角形のアウトラインも描けます。


Graphics.drawTriangles()メソッドの第3引数 − uv座標
Graphics.drawTriangles()メソッドに第3引数を渡すと、塗りに使うビットマップの領域を三角形ごとに別々に指定できます。たとえば、前の例と同じふたつの三角形に対して、ビットマップを右上から左下の対角線でふたつに分け、それぞれを左上と右下の三角形に塗ることができます(図07-006)。

図07-006■ビットマップをふたつの三角形に分けて塗る

ビットマップを右上から左下の対角線で分け、ふたつの三角形にそれぞれ塗りとして設定できる。

Graphics.drawTriangles()メソッドの第3引数も、第1引数と同じく、数値(Number型)のVectorオブジェクトを渡します。エレメントの数値はビットマップ上の平面座標値です。第1引数に対応するように、座標の水平と垂直の値を順にエレメントとして納めます。

ただし、座標は左上隅を(0, 0)、右下隅を(1, 1)とする比率で示します(図07-007)。水平軸をu、垂直軸をvで表すので、uv座標と呼ばれます。

図07-007■ビットマップのuv座標
座標は左上隅を(0, 0)、右下隅を(1, 1)とする比率で示す。

すると、第1引数の頂点座標(前掲図07-006参照)に対応するビットマップのuv座標は、下表07-001のようになります。

表07-001■第1引数の頂点座標とそれに対応する第3引数のuv座標
第1引数/頂点座標 第3引数/uv座標
(0, 0) (0, 0)
(100, 0) (1, 0)
(0, 100) (0, 1)
(120, 20) (1, 0)
(120, 120) (1, 1)
(20, 100) (0, 1)

つぎのフレームアクションは、Graphics.drawTriangles()メソッドの第1引数に渡したふたつの三角形に対し、第3引数でビットマップのuv座標を指定して、テクスチャマッピングします(スクリプト07-002)。

スクリプト07-002■Graphics.drawTriangles()メソッドの第1・第3引数を指定してテクスチャマッピング
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImageを設定
  1. var mySprite:Sprite = new Sprite();
  2. var myGraphics:Graphics = mySprite.graphics;
  3. var myTexture:BitmapData = new Image(0, 0);
  4. // 頂点座標を納めるVectorインスタンス生成
  5. var vertices:Vector.<Number> = new Vector.<Number>();
  6. // uv座標を納めるVectorインスタンス生成
  7. var uvtData:Vector.<Number> = new Vector.<Number>();
  8. addChild(mySprite);
  9. // 三角形の頂点座標(第1引数)
  10. vertices.push(0, 0);
  11. vertices.push(100, 0);
  12. vertices.push(0, 100);
  13. vertices.push(120, 20);
  14. vertices.push(120, 120);
  15. vertices.push(20, 120);
  16. // テクスチャのuv座標(第3引数)
  17. uvtData.push(0, 0);
  18. uvtData.push(1, 0);
  19. uvtData.push(0, 1);
  20. uvtData.push(1, 0);
  21. uvtData.push(1, 1);
  22. uvtData.push(0, 1);
  23. myGraphics.beginBitmapFill(myTexture);
  24. // 頂点座標とuv座標を引数に渡す
  25. myGraphics.drawTriangles(vertices, null, uvtData);
  26. myGraphics.endFill();

スクリプト第5行目は、uv座標が納められる数値のVectorインスタンスを生成します。そして、第13〜18行目で、頂点座標に対応するuv座標の値(前掲表07-001参照)を加えています。第20行目は、Graphics.drawTriangles()メソッドに第1引数の頂点座標と第3引数のuv座標を渡します。なお、第2引数は指定しないので、nullを渡しています。

[ムービープレビュー]を確かめると、ビットマップがuv座標にしたがってふたつに分けられ、ふたつの三角形にそれぞれ塗られます(図07-008)。

図07-008■ふたつに分かれたビットマップでふたつの三角形が塗られる
ビットマップは、uv座標の指定にしたがってふたつに分けられた。

第3引数のuv座標はそのままで、第1引数の頂点座標を変えると、ビットマップを変形することもできます。たとえば、上記スクリプト07-002の第11行目の座標値をつぎのように書替えてみます。

    // 三角形の頂点座標(第1引数)
  1. vertices.push(0, 0);
  2. vertices.push(100, 0);
  3. vertices.push(0, 100);
  4. vertices.push(120, 20);
  5. // vertices.push(120, 120);
  6. vertices.push(140, 130);   // 座標値を変更
  7. vertices.push(20, 120);

右下の三角形の右下隅の座標を、(120, 120)から(140, 130)に変えました。すると、右下の三角形の右下隅を右下に引き伸ばしたように、ビットマップが塗られます(図07-009)。

図07-009■頂点座標を変えるとビットマップが変形される
右下の三角形の右下隅が外に引き伸ばされたように、ビットマップが塗られる。

Graphics.drawTriangles()メソッドの第2引数 − 頂点番号
Graphics.drawTriangles()メソッドの第2引数には、三角形の頂点番号を整数(int型)のVectorオブジェクトで渡します。この引数は、三角形同士が頂点を共有する場合に用います。たとえば、下図07-010のようにテクスチャマッピングしてみます。図には、頂点番号0から3をZ状につけました。ふたつの三角形0-1-2と1-3-2は、頂点1と2を共有しています。

図07-010■三角形がふたつの頂点を共有している
ふたつの三角形0-1-2と1-3-2は、頂点1と2を共有する。

もちろん、ふたつの三角形の6つの頂点について、Graphics.drawTriangles()メソッドに第1引数の頂点座標と第3引数のuv座標を渡せば、第2引数を指定しなくてもテクスチャマッピングはできます。前掲スクリプト07-002の第10〜12行目を、つぎのように書替えればよいでしょう。けれど、同じ座標値を重複して記述するのは無駄です。また、内部処理もその分増えることになります。

    // 三角形の頂点座標(第1引数)
  1. vertices.push(0, 0);
  2. vertices.push(100, 0);
  3. vertices.push(0, 100);
  4. // vertices.push(120, 20);
  5. vertices.push(100, 0);   // 第8行目と同じ
  6. // vertices.push(120, 120);
  7. vertices.push(120, 110);
  8. // vertices.push(20, 120);
  9. vertices.push(0, 100);   // 第9行目と同じ

四角形の頂点は4つですので、第1引数の頂点座標も第3引数のuv座標も、Graphics.drawTriangles()メソッドに4頂点分だけ渡すことにします(表07-002)。その代わり、第2引数にそれぞれの三角形がどの3つの頂点で構成されるのかを指定します。

頂点番号は第1引数に渡した頂点座標の順に、0から整数インデックスが決まります。Graphics.drawTriangles()メソッドの第2引数は、エレメントの頂点番号3つごとにひとつの三角形を定めます。三角形のどの頂点から指定するかは自由です。ただ、頂点番号の順序は時計回りか反時計回りかに決めておく方が、後あと便利です。本稿では、時計回りにします。

表07-002■四角形の4頂点に対する第1引数の頂点座標と第3引数のuv座標
頂点番号 第1引数/頂点座標 第3引数/uv座標
0 (0, 0) (0, 0)
1 (100, 0) (1, 0)
2 (0, 100) (0, 1)
3 (120, 110) (1, 1)

つぎのフレームアクション(スクリプト07-003)は、Graphics.drawTriangles()メソッドに第1引数として四角形の4頂点の座標、第3引数にuv座標、そして第2引数には三角形を構成する頂点番号を渡してテクスチャマッピングします(図07-011)。

スクリプト07-003■4頂点の座標とuv座標に頂点番号を指定してテクスチャマッピング
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImageを設定
  1. var mySprite:Sprite = new Sprite();
  2. var myGraphics:Graphics = mySprite.graphics;
  3. var myTexture:BitmapData = new Image(0, 0);
  4. // 頂点座標を納めるVectorインスタンス生成
  5. var vertices:Vector.<Number> = new Vector.<Number>();
  6. // 頂点番号を納めるVectorインスタンス生成
  7. var indices:Vector.<int> = new Vector.<int>();
  8. // uv座標を納めるVectorインスタンス生成
  9. var uvtData:Vector.<Number> = new Vector.<Number>();
  10. addChild(mySprite);
  11. // 三角形の頂点座標(第1引数)
  12. vertices.push(0, 0);   // 頂点0
  13. vertices.push(100, 0);   // 頂点1
  14. vertices.push(0, 100);   // 頂点2
  15. vertices.push(120, 110);   // 頂点3
  16. // 三角形の頂点番号(第2引数)
  17. indices.push(0, 1, 2);   // 左上三角形0-1-2
  18. indices.push(1, 3, 2);   // 右下三角形1-3-2
  19. // テクスチャのuv座標(第3引数)
  20. uvtData.push(0, 0);   // 頂点0
  21. uvtData.push(1, 0);   // 頂点1
  22. uvtData.push(0, 1);   // 頂点2
  23. uvtData.push(1, 1);   // 頂点3
  24. myGraphics.beginBitmapFill(myTexture);
  25. // 頂点座標とuv座標を引数に渡す
  26. myGraphics.drawTriangles(vertices, indices, uvtData);
  27. myGraphics.endFill();

図07-011■4頂点の座標とuv座標に頂点番号の組を指定してテクスチャマッピング
Graphics.drawTriangles()メソッドに第1引数として四角形の4頂点の座標、第3引数にuv座標、そして第2引数には三角形を構成する頂点番号を渡した。

スクリプト第5行目は、各三角形の頂点番号が納められる整数(int型)のVectorインスタンスを生成します。そして、第12〜13行目で、ふたつの三角形(0-1-2と1-3-2)の3頂点番号の組を加えています(前掲図07-010参照)。第19行目は、Graphics.drawTriangles()メソッドに第1引数の頂点座標と第3引数のuv座標に加えて、第2引数として頂点番号のVectorインスタンスを渡しています。

[イラスト] どの頂点3つで三角形を描くのか、頂点番号の組合わせを教えてあげる。

矩形をドラッグして任意の四角形に変形する
Graphics.drawTriangles()メソッドの基本的な使い方がわかりましたので、ここはやはり動かしてみることにしましょう。前述03「2次元平面の座標を変換する行列 − Matrixクラス」03-03「傾斜を加える」では、Matrixクラスにより矩形を自由な平行四辺形に変形しました(図03-017)。Graphics.drawTriangles()メソッドを用いれば、平行四辺形にかぎることなく、任意の四角形に変えられます。

図03-017■ポイントをドラッグするとその座標に合わせて矩形のインスタンスが平行四辺形に変換される(再掲)
矩形のインスタンスの3つの角にあるポイントをドラッグすると、その座標に合わせてインスタンスが任意の平行四辺形に変換される。

まず、タイムラインにドラッグするポイントのMovieClipインスタンスを4つ置いて、下図07-012のようにZ状にpoint0_mcからpoint3_mcまでの名前をつけます。スクリプトでSpriteインスタンスを動的に配置してテクスチャマッピングしますので、タイムラインには他にビジュアルエレメントは要りません。テクスチャとして使う[ライブラリ]のビットマップには、これまでのスクリプトと同じように、[クラス]Imageを設定しておきます。

図07-012■タイムラインに置いたドラッグする4つのMovieClipインスタンスに名前をつける
4つのMovieClipインスタンスには、Z状にpoint0_mcからpoint3_mcまでの名前を設定。

ドラッグするポイントのMovieClipシンボルには、前掲スクリプト03-004と同じフレームアクションを記述します。したがって、ポイントのインスタンスをドラッグしている間、親タイムラインの関数xTransform()が呼出され続けます。よって、この関数xTransform()に、任意の四角形を三角形に分けて、テクスチャマッピングすればよいことになります。

スクリプト03-004■インスタンスのドラッグ操作と親タイムラインの関数呼出し(再掲)
    // フレームアクション
    // MovieClip: ドラッグするポイントのシンボル
  1. buttonMode = true;
  2. addEventListener(MouseEvent.MOUSE_DOWN, xPress);
  3. function xPress(eventObject:MouseEvent):void {
  4.   stage.addEventListener(MouseEvent.MOUSE_UP, xRelease);
  5.   addEventListener(Event.ENTER_FRAME, xUpdate);
  6.   startDrag();
  7. }
  8. function xRelease(eventObject:MouseEvent):void {
  9.   stage.removeEventListener(MouseEvent.MOUSE_UP, xRelease);
  10.   removeEventListener(Event.ENTER_FRAME, xUpdate);
  11.   stopDrag();
  12.   xUpdate();
  13. }
  14. function xUpdate(eventObject:Event=null):void {
  15.   MovieClip(parent).xTransform();
  16. }

関数xTransform()を定義したフレームアクションは、以下のスクリプト07-004のとおりです。基本的な処理の組立ては、前掲スクリプト07-003と大きく変わりません。ただ、ひとつ考えなければならないのは、ドラッグのたびに変わる値とそうでない値とを分けることです。

Graphics.drawTriangles()メソッドに渡す引数のうち、三角形の頂点番号(第2引数)とテクスチャのuv座標(第3引数)は、四角形を変形してもとくに変える必要がありません。したがって、スクリプト第4〜5行目でタイムラインの変数としてVectorインスタンスを宣言し、第7〜12行目で値を設定しています。

他方、頂点座標(第1引数)は、ドラッグでそれらの位置が変われば値を更新しなければなりません。したがって、関数xTransform()が呼出されるたびに、その値を新たに設定することになります。

スクリプト07-004■4頂点のポイントのインスタンスに合わせて任意の四角形にテクスチャマッピングする
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImageを設定
  1. var mySprite:Sprite = new Sprite();
  2. var myGraphics:Graphics = mySprite.graphics;
  3. var myTexture:BitmapData = new Image(0, 0);
  4. var indices:Vector.<int> = new Vector.<int>();   // 頂点番号のVectorインスタンス生成
  5. var uvtData:Vector.<Number> = new Vector.<Number>();   // uv座標のVectorインスタンス生成
  6. addChildAt(mySprite, 0);   // Spriteインスタンスを最背面に置く
  7. // 三角形の頂点番号
  8. indices.push(0, 1, 2);
  9. indices.push(1, 3, 2);
  10. // テクスチャのuv座標
  11. uvtData.push(0, 0);
  12. uvtData.push(1, 0);
  13. uvtData.push(0, 1);
  14. uvtData.push(1, 1);
  15. xTransform();
  16. function xTransform():void {
  17.   var vertices:Vector.<Number> = new Vector.<Number>();   // 頂点座標のVectorインスタンス生成
  18.   // 三角形の頂点座標
  19.   vertices.push(point0_mc.x,point0_mc.y);
  20.   vertices.push(point1_mc.x,point1_mc.y);
  21.   vertices.push(point2_mc.x,point2_mc.y);
  22.   vertices.push(point3_mc.x,point3_mc.y);
  23.   myGraphics.clear();
  24.   myGraphics.beginBitmapFill(myTexture);
  25.   myGraphics.drawTriangles(vertices, indices, uvtData);
  26.   myGraphics.endFill();
  27. }

ドラッグしたポイントのインスタンスから呼出される関数xTransform()(スクリプト第14〜24行目)は、4頂点のインスタンスのxy座標を数値のVectorインスタンスに加えます。そして、そのVectorオブジェクトを第1引数、予めタイムライン変数に設定しておいたVectorオブジェクトを第2および第3引数として、Graphics.drawTriangles()メソッドを呼出して、4頂点座標に対してテクスチャマッピングします。なお、描画は毎回塗り直す必要がありますので、第20行目でGraphics.clear()メソッド(シンタックス06-002)を呼出しています。

ふたつ補足があります。ひとつ目は、スクリプト第6行目で、タイムラインにSpriteインスタンスを加えるとき、DisplayObjectContainer.addChildAt()メソッドを使っていることです(シンタックス07-003)。もし、ここでDisplayObjectContainer.addChild()メソッドを用いると、四角形のSpriteは最前面に置かれます。すると、4隅のポイントのインスタンスの手前に表示されてしまいます。そこで、DisplayObjectContainer.addChild()メソッドにより、Spriteインスタンスを最背面(インデックス0)に加えました。

シンタックス07-003■DisplayObjectContainer.addChildAt()メソッド
DisplayObjectContainer.addChildAt()メソッド
文法 addChildAt(child:DisplayObject, index:int):DisplayObject
概要 DisplayObjectContainerインスタンスの表示リストの指定インデックスに子としてDisplayObjectインスタンスを加える。子インスタンスは表示リストのインデックス位置に納められる。インデックスは0から始まり、数値が大きいほどリスト内のインスタンスの重ね順は手前になる。
引数

child:DisplayObject ― DisplayObjectContainerインスタンスの表示リストに子として加えるDisplayObjectインスタンス。

index:int ― 子のDisplayObjectインスタンスが加えられる表示リスト内のインデックスを示す整数。0からDisplayObjectContainer.numChildrenプロパティ(シンタックス04-004)の値までの範囲で指定する。表示リスト内の指定されたインデックス以降に納められていたインスタンスは、インデックスがひとつずつ繰り上がる。

戻り値 引数で渡され、子として加えられたDisplayObjectインスタンス。

補足のふたつ目は、スクリプト第13行目で関数xTransform()を呼出していることです。テクスチャマッピングは、ポイントのインスタンスをドラッグするたびに繰返し行われます。しかし、ドラッグし始めるまでは、ビットマップはまったく塗られていない状態です。そこで、関数xTransform()を呼出して、予め置かれたポイントのインスタンスに合わせてテクスチャをマッピングしたのです。

[ムービープレビュー]て確かめると、矩形のビットマップの4隅に置かれたポイントをドラッグすれば、その位置に合わせて任意の四角形にかたちが変わります(図07-013)。

図07-013■ドラッグした4隅のポイントに合わせて任意の四角形に変形する
ポイントのインスタンスをドラッグするたびに、4つのインスタンスの位置に合わせてテクスチャがマッピングされる。

三角形の塗りの重ね順
Graphics.drawTriangles()メソッドで描かれる三角形は、引数に渡された順に塗られます。つまり、第2引数の頂点番号が渡されていればその順番、渡されていなければ第1引数の頂点座標により指定される三角形の順に、上に重ねて描かれます。

前掲スクリプト07-004第7〜8行目は、左上の三角形を先に、右下の三角形を後にしていしました。したがって、つねに右下の三角形が手前に描かれます。そのため、左上頂点のポイントを右下三角形の上にドラッグすると、左上三角形はその裏に回ってしまいます(図07-014)。

図07-014■左上の三角形より右下の三角形がつにね手前に描かれる
左上頂点のポイントを右下三角形の上にドラッグすると、左上三角形は裏に回ってしまう。

この三角形の重ね順を変えるには、第2引数の頂点番号の順序を必要に応じて変えなければなりません。今回三角形はふたつだけですので、第2引数用のVectorインスタンスを、右下が手前のものと左上が手前のもののふたつ用意して、それを切替えることにしましょう。

2種類のVectorオブジェクトは、Dictionaryインスタンスにエレメントとして納めます。Dictionaryクラスには、ArrayやObjectクラスのように、いくつもの値をエレメントとして加えることができます。しかも、そのキーに、整数や文字列はもちろん、オブジェクトの参照が使えます(シンタックス07-004)。

シンタックス07-004■Dictionaryクラスのコンストラクタメソッド
Dictionary()コンストラクタ
文法 Dictionary(weakKeys:Boolean = false)
概要 Dictionaryインスタンスを生成する。インスタンスにはエレメントが納められ、キーには整数や文字列はもちろん、オブジェクトの参照を用いることができる。キーとともにエレメントを削除するには、delete演算子を使う。
引数 weakKeys:Boolean ― キーに弱い参照(Column 01「ガベージコレクションと弱い参照」参照)を指定するときはtruefalseを渡す。デフォルト値は弱い参照を使わないfalse
戻り値 なし。

まず、ドラッグするポイントのMovieClipシンボルに書いたフレームアクション(前掲スクリプト03-004)を、以下のスクリプト07-005のように1箇所だけ修正します。第15行目で親タイムラインの関数xTransform()を呼出すとき、引数として自身の参照を渡します。これで、関数xTransform()から、ドラッグしたのがどのインスタンスなのかを調べるられるようになります。

スクリプト07-005■自身の参照を引数にして親タイムラインの関数呼出し
    // フレームアクション
    // MovieClip: ドラッグするポイントのシンボル
  1. buttonMode = true;
  2. addEventListener(MouseEvent.MOUSE_DOWN, xPress);
  3. function xPress(eventObject:MouseEvent):void {
  4.   stage.addEventListener(MouseEvent.MOUSE_UP, xRelease);
  5.   addEventListener(Event.ENTER_FRAME, xUpdate);
  6.   startDrag();
  7. }
  8. function xRelease(eventObject:MouseEvent):void {
  9.   stage.removeEventListener(MouseEvent.MOUSE_UP, xRelease);
  10.   removeEventListener(Event.ENTER_FRAME, xUpdate);
  11.   stopDrag();
  12.   xUpdate();
  13. }
  14. function xUpdate(eventObject:Event=null):void {
  15.   MovieClip(parent).xTransform(this);   // 自身の参照を引数に渡す
  16. }

前述のとおり、Dictionaryインスタンスには、オブジェクトをキーとしてエレメントが納められます。また、関数xTransform()には、引数としてドラッグしたインスタンスの参照が渡されるようになりました。

そこで、Dictionaryオブジェクトに、ポイントのインスタンスをキーとして、Graphics.drawTriangles()メソッドの第2引数に渡すべきVectorオブジェクトをエレメントとして加えます。そうすれば、関数xTransform()が受取ったインスタンスの参照から、適切な頂点番号の納められたVectorオブジェクトが取出せます。前掲スクリプト07-004にその修正を加えたのが、つぎのフレームアクションです(スクリプト07-006)。

スクリプト07-006■引数で受取ったインスタンスにより三角形の頂点番号の順序を変える
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImageを設定
  1. var mySprite:Sprite = new Sprite();
  2. var myGraphics:Graphics = mySprite.graphics;
  3. var myTexture:BitmapData = new Image(0, 0);
  4. var indices:Dictionary = new Dictionary();
  5. var indices0:Vector.<int> = new Vector.<int>();
  6. var indices3:Vector.<int> = new Vector.<int>();
  7. var uvtData:Vector.<Number> = new Vector.<Number>();
  8. addChildAt(mySprite, 0);
  9. indices[point0_mc] = indices0;
  10. indices[point1_mc] = indices0;
  11. indices[point2_mc] = indices3;
  12. indices[point3_mc] = indices3;
  13. indices0.push(1, 3, 2);
  14. indices0.push(0, 1, 2);
  15. indices3.push(0, 1, 2);
  16. indices3.push(1, 3, 2);
  17. uvtData.push(0, 0);
  18. uvtData.push(1, 0);
  19. uvtData.push(0, 1);
  20. uvtData.push(1, 1);
  21. xTransform(point0_mc);
  22. function xTransform(point_mc:MovieClip):void {
  23.   var vertices:Vector.<Number> = new Vector.<Number>();
  24.   vertices.push(point0_mc.x,point0_mc.y);
  25.   vertices.push(point1_mc.x,point1_mc.y);
  26.   vertices.push(point2_mc.x,point2_mc.y);
  27.   vertices.push(point3_mc.x,point3_mc.y);
  28.   myGraphics.clear();
  29.   myGraphics.beginBitmapFill(myTexture);
  30.   myGraphics.drawTriangles(vertices, indices[point_mc], uvtData);
  31.   myGraphics.endFill();
  32. }

まず、スクリプト第4行目は、Dictionaryインスタンスを生成します。つぎに、第5〜6行目で、Graphics.drawTriangles()メソッドの第2引数として渡す頂点番号のVectorインスタンスは、左上の(頂点番号0を含む)三角形が手前にくるものと、右下の(頂点番号3を含む)三角形が手前にくるもののふたつを用意します。

そして、スクリプト第9〜12行目は、Dictionaryオブジェクトに4つのポイントのインスタンスをキーとして、それぞれに対応する頂点番号のVectorインスタンスを納めています。そのうえで、第13〜16行目で、各Vectorインスタンスに頂点番号を加えました。

関数xTransform()(第22〜32行目)は引数にポイントのインスタンスを受取り、第30行目でGraphics.drawTriangles()メソッドを呼出すとき、DictionaryオブジェクトからそのインスタンスをキーにVectorオブジェクトが取出され、第2引数に指定されます。

これで左上の頂点(番号0)のインスタンスをドラッグすると左上の三角形が、右下の頂点(番号3)のインスタンスをドラッグすれば右下の三角形が手前に描かれるようになります(図07-015)。なお、右上の頂点は左上三角形、左下の頂点は右下三角形の側に設定してあります。

図07-015■ドラッグする頂点のインスタンスによって三角形の重ね順が変わる
左上の頂点をドラッグすると左上の三角形が、右下の頂点をドラッグすれば右下の三角形が手前に描かれる。

07-03 遠近法(透視)投影 − Utils3D.projectVectors()メソッド
それでは、テクスチャマッピングで3次元空間を表現します。例によって、回してみましょう。まずは、一枚の矩形画像から始めます。04「3次元座標空間でDisplayObjectインスタンスを扱う − Matrix3Dクラス」で最初につくったサンプルと見た目は同じで、マウスポインタの水平位置に応じて画像を横に回転させます(図04-001)。

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

もっとも、DisplayObject(あるいはそのサブクラス)のインスタンスをタイムラインの上で回すのではなく、3次元空間座標のVector3Dインスタンスに変換を加えて2次元平面のディスプレイに投影します。したがって、そのつくりは前章06「3次元空間の座標を扱う − Vector3Dクラス」でワイヤーフレームのアニメーションをつくったのと同じ考え方になります。

実際、後に掲げるできあがりのスクリプト07-007は初期設定の基本部分が、つぎのように前掲スクリプト06-002「Vector3Dオブジェクトで座標が定められた正方形のワイヤーフレームを透視投影して回す」とほぼ同じ処理です。

  1. var nUnit:Number = 100 / 2;
  2. var nDeceleration:Number = 0.3;
  3. var mySprite:Sprite = new Sprite();
  4. var myGraphics:Graphics = mySprite.graphics;
  5. var myTexture:BitmapData = new Image(0, 0);
  6. var vertices3D:Vector.<Number> = new Vector.<Number>();
  1. addChild(mySprite);
  2. mySprite.x = stage.stageWidth / 2;
  3. mySprite.y = stage.stageHeight / 2;
  4. vertices3D.push(-nUnit, -nUnit, 0);
  5. vertices3D.push(nUnit, -nUnit, 0);
  6. vertices3D.push(-nUnit, nUnit, 0);
  7. vertices3D.push(nUnit, nUnit, 0);
  1. addEventListener(Event.ENTER_FRAME, xRotate);

もっとも、スクリプト06-002では、Vector3D.project()メソッドで3次元空間の座標をひとつひとつ2次元平面に投影したうえで、ワイヤーフレームを描きました。けれど、Graphics.drawTriangles()メソッドでテクスチャマッピングするときには、Utils3D.projectVectors()という便利な静的メソッドが使えます(シンタックス07-005)。このメソッドに、変換のためのMatrix3DオブジェクトとVectorオブジェクトに納めた3次元空間座標ならびにuv座標をそれぞれ引数として渡すと、2次元平面に投影した座標を別のVectorインスタンスに設定してくれます。

シンタックス07-005■Utils3D.projectVectors()メソッド
Utils3D.projectVectors()メソッド
文法 projectVectors(projectionMatrix3D:Matrix3D, vertices:Vector.<Number>, projectedVertices:Vector.<Number>, uvtData:Vector.<Number>):void
概要 [静的] 投影用のMatrix3Dオブジェクトにより、3次元空間座標の数値エレメントが納められたVectorを2次元座標の数値ベース型Vectorに変換する。さらに、uvt座標のt値も設定する。
引数

projectionMatrix3D:Matrix3D ― 投影のための変換行列となるMatrix3Dオブジェクト。

vertices:Vector.<Number> ― 変換する3次元空間の座標を納めた数値ベース型のVectorオブジェクト。エレメント3つごとにxyzの各座標値を表す。

projectedVertices:Vector.<Number> ― 第2引数の3次元空間座標を2次元平面に投影した座標値が納められる数値ベース型のVectorオブジェクト。エレメントは書替えられるので、空のVectorオブジェクトを指定すればよい。設定された値はエレメント2つごとにxy座標を表す。

uvtData:Vector.<Number> ― 投影されるテクスチャのuvt座標値を表す数値ベース型のVectorオブジェクト。uv座標は、テクスチャの左上角が(0, 0)、右下隅を(1, 1)とする。t値は奥行きを示し、このメソッドにより設定される(したがって、引数に渡すt値は0でよい)。

戻り値 なし。

Utils3D.projectVectors()メソッドの使い方は、少し変わっています。引数が4つあり、値は返しません。

第1引数は、平行移動・回転・伸縮の変換を行うMatrix3Dオブジェクトです。第2引数には、投影する三次元空間の座標値を、それぞれxyzの順で数値ベース型のVectorオブジェクトに納めて指定します。第3引数には、空の数値ベース型のVectorインスタンスを渡します。第2引数の3次元座標を2次元平面に投影したxy座標値が、このオブジェクトに設定されます。処理結果の値をメソッドから戻り値で受取るのでなく、引数として渡すのです。第4引数は、uv座標に奥行きを示すオプションのt値を加え、3次元座標としてこれもVectorオブジェクトで指定します。ただし、t値はメソッドが計算して設定しますので、仮に0としておいて構いません。

Utils3D.projectVectors()メソッドを用いると、前掲スクリプト06-002とは異なり、3次元空間座標をまとめて2次元平面に投影できます。それだけでなく、さらに手間は減らせます。3次元空間で変換した座標値を、いちいち求めなくて済むからです。テクスチャマッピングに必要なのは、2次元平面に投影した座標値です。そして、3次元空間における変換は、その行列を表すMatrix3Dオブジェクトがあれば足り、変換した後の3次元座標値そのものは要りません。

[イラスト] 3次元の変換はMatrix3Dに任せておけば、座標をいちいち計算しなくても大丈夫。

矩形の3次元空間座標をマウスポインタの水平座標に応じて回し、Utils3D.projectVectors()メソッドにより2次元平面に投影したうえで、Graphics.drawTriangles()メソッドでテクスチャマッピングしたのが以下のフレームアクションです。

初期設定ではこれまでのフレームアクション(たとえばスクリプト07-004)と同じように、Graphics.drawTriangles()メソッドの第2および第3引数に渡すVectorインスタンスを生成し(スクリプト第7〜8行目)、それぞれに初期値を与えます(第21〜26行目)。

ただし、前述のとおりUtils3D.projectVectors()メソッドにはuvt座標を渡しますので、t値として0を加えた3次元座標が設定してあります(第23〜26行目)。Graphics.drawTriangles()メソッドは、第3引数のVectorエレメント数が第1引数の1.5倍(3/2)のときは、uvt座標として扱います(前述シンタックス07-002)。なお、3次元空間における座標変換を保持するMatrix3Dオブジェクトも、ここで作成しておきます(第11行目)。

  1. var nUnit:Number = 100 / 2;
  2. var nDeceleration:Number = 0.3;
  3. var mySprite:Sprite = new Sprite();
  4. var myGraphics:Graphics = mySprite.graphics;
  5. var myTexture:BitmapData = new Image(0, 0);
  6. var vertices3D:Vector.<Number> = new Vector.<Number>();
  7. var indices:Vector.<int> = new Vector.<int>();
  8. var uvtData:Vector.<Number> = new Vector.<Number>();
  1. var worldMatrix3D:Matrix3D = new Matrix3D();
  1. addChild(mySprite);
  2. mySprite.x = stage.stageWidth / 2;
  3. mySprite.y = stage.stageHeight / 2;
  4. vertices3D.push(-nUnit, -nUnit, 0);
  5. vertices3D.push(nUnit, -nUnit, 0);
  6. vertices3D.push(-nUnit, nUnit, 0);
  7. vertices3D.push(nUnit, nUnit, 0);
  8. indices.push(0, 1, 2);
  9. indices.push(1, 3, 2);
  10. // uvt座標: t値は0を設定
  11. uvtData.push(0, 0, 0);
  12. uvtData.push(1, 0, 0);
  13. uvtData.push(0, 1, 0);
  14. uvtData.push(1, 1, 0);
  1. addEventListener(Event.ENTER_FRAME, xRotate);
  2. function xRotate(eventObject:Event):void {
  3.   var nRotationY:Number = mySprite.mouseX * nDeceleration;
  4.   var vertices2D:Vector.<Number> = new Vector.<Number>();
  5.   worldMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  6.   var myMatrix3D:Matrix3D = worldMatrix3D.clone();
  1.   Utils3D.projectVectors(myMatrix3D, vertices3D, vertices2D, uvtData);
  2.   myGraphics.clear();
  3.   myGraphics.beginBitmapFill(myTexture);
  4.   myGraphics.drawTriangles(vertices2D, indices, uvtData);
  5.   myGraphics.endFill();
  6. }

さて、座標の変換と描画は、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)のリスナー関数xRotate()が担います(スクリプト第29〜41行目)。

スクリプト第33行目は、第11行目で新たに生成したMatrix3Dインスタンスに、マウスポインタの水平座標に応じた(第31行目)回転の変換を加えます。座標の変換はこのMatrix3Dインスタンスが一手に引受け、矩形の3次元空間の座標値(変数vertices3D)そのものは初期値のまま変えません。

スクリプト第36行目でUtils3D.projectVectors()を呼出し、このMatrix3Dオブジェクト(変数vertices3D)を第1引数、初期設定で作成した頂点座標とuvt座標の数値ベース型Vectorオブジェクト(変数vertices3DおよびuvtData)をそれぞれ第2および第4引数として渡します。また、第2引数に渡す2次元平面投影用の空のVectorインスタンスは、スクリプト第32行目で生成しています(変数vertices2D)。

そして、スクリプト第39行目は、2次元平面に投影された座標値のVectorインスタンス(変数vertices2D)を第1引数、初期設定の頂点番号とuvt座標のVectorオブジェクト(変数indicesおよびuvtData)をそれぞれ第2および第3引数として、Graphics.drawTriangles()メソッドを呼出しています。

これで[ムービープレビュー]を確かめると、マウスポインタの水平位置に応じて矩形の3次元座標が回転し、テクスチャマッピングされます。ただし、遠近法が投影されておらず、パースペクティブはかかりません(図07-016)。

図07-016■回転の変換に遠近法が投影されていない


適用した変換行列に、透視投影が加えられていない。

タイムラインに加えられたDisplayObjectインスタンスでしたら、親もしくは自身のDisplayObject.transformプロパティがTransform.perspectiveProjectionプロパティにPerspectiveProjectionオブジェクトをもち、遠近法が適用されます(シンタックス02-001)。しかし、3次元空間座標を純粋に数値として扱う場合には、透視投影の演算をしなければなりません。つまり、この変換もMatrix3Dオブジェクトに加える必要があるのです。もっとも、PerspectiveProjectionオブジェクトは変換行列ではありません。けれども、その設定を変換行列として取出すPerspectiveProjection.toMatrix3D()メソッドがあります(シンタックス07-006)。

シンタックス07-006■PerspectiveProjection.toMatrix3D()メソッド
PerspectiveProjection.toMatrix3D()メソッド
文法 toMatrix3D():Matrix3D
概要 PerspectiveProjectionオブジェクトから、その透視投影のための変換をMatrix3Dオブジェクトにして取出す。
引数 なし。
戻り値 PerspectiveProjectionオブジェクトの透視投影を変換行列として表すMatrix3Dオブジェクト。

透視投影の変換を加えてでき上がったのが、以下のスクリプト07-007です。透視投影は3次元空間の座標を2次元平面に引き移す演算です。したがって、3次元空間自体における座標の状態(変換行列。変数worldMatrix3D)とは分けておく必要があります。そこで、透視投影用のMatrix3Dインスタンスは、スクリプト第12行目で新たに生成しています(変数viewMatrix3D)。

スクリプト07-007■マウスポインタの水平位置により矩形の頂点座標を回してテクスチャマッピング
    // フレームアクション
    // [ライブラリ]のビットマップに[クラス]としてImageを設定
  1. var nUnit:Number = 100 / 2;
  2. var nDeceleration:Number = 0.3;
  3. var mySprite:Sprite = new Sprite();
  4. var myGraphics:Graphics = mySprite.graphics;
  5. var myTexture:BitmapData = new Image(0, 0);
  6. var vertices3D:Vector.<Number> = new Vector.<Number>();
  7. var indices:Vector.<int> = new Vector.<int>();
  8. var uvtData:Vector.<Number> = new Vector.<Number>();
  9. var nDistance:Number = 300;
  10. var myPerspective:PerspectiveProjection = new PerspectiveProjection();
  11. var worldMatrix3D:Matrix3D = new Matrix3D();
  12. var viewMatrix3D:Matrix3D = new Matrix3D();
  13. myPerspective.focalLength = nDistance;
  14. addChild(mySprite);
  15. mySprite.x = stage.stageWidth / 2;
  16. mySprite.y = stage.stageHeight / 2;
  17. vertices3D.push(-nUnit, -nUnit, 0);
  18. vertices3D.push(nUnit, -nUnit, 0);
  19. vertices3D.push(-nUnit, nUnit, 0);
  20. vertices3D.push(nUnit, nUnit, 0);
  21. indices.push(0, 1, 2);
  22. indices.push(1, 3, 2);
  23. uvtData.push(0, 0, 0);
  24. uvtData.push(1, 0, 0);
  25. uvtData.push(0, 1, 0);
  26. uvtData.push(1, 1, 0);
  27. viewMatrix3D.appendTranslation(0, 0, nDistance);
  28. viewMatrix3D.append(myPerspective.toMatrix3D());
  29. addEventListener(Event.ENTER_FRAME, xRotate);
  30. function xRotate(eventObject:Event):void {
  31.   var nRotationY:Number = mySprite.mouseX * nDeceleration;
  32.   var vertices2D:Vector.<Number> = new Vector.<Number>();
  33.   worldMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  34.   var myMatrix3D:Matrix3D = worldMatrix3D.clone();
  35.   myMatrix3D.append(viewMatrix3D);
  36.   Utils3D.projectVectors(myMatrix3D, vertices3D, vertices2D, uvtData);
  37.   myGraphics.clear();
  38.   myGraphics.beginBitmapFill(myTexture);
  39.   myGraphics.drawTriangles(vertices2D, indices, uvtData);
  40.   myGraphics.endFill();
  41. }

PerspectiveProjectionインスタンスはスクリプト第10行目で生成し、第13行目は焦点距離(PerspectiveProjection.focalLengthプロパティ)を設定しています。また、第27〜28行目は、透視投影用のMatrix3Dインスタンス(変数viewMatrix3D)を設定します。第27行目は、オブジェクトをz軸の奥に移動することで、いわば視点との距離をとります。そして第28行目で、PerspectiveProjectionオブジェクトの変換を与えて、焦点距離(あるいは視野角)を適用します。なお、このスクリプトでは、第13行目の焦点距離と第27行目の視点からオブジェクトまでの距離には、等しい値(変数nDistance)を設定しました。

DisplayObject.enterFrameイベントのリスナー関数xRotate()では、スクリプト第35行目に透視投影のMatrix3Dオブジェクト(変数viewMatrix3D)を変換に加えています。ここで参照しているMatrix3Dオブジェクト(変数myMatrix3D)は、3次元空間における座標変換を保持するMatrix3Dインスタンス(変数worldMatrix3D)から複製しています(第34行目)。これは、前述のとおり、3次元空間座標の変換そのものと2次元平面に透視投影する変換行列とを分けておくためです。

これで、スクリプト第36行目の静的メソッドUtils3D.projectVectors()の呼出しには、第1引数として回転に加えて透視投影が適用されたMatrix3Dインスタンスを渡しました。[ムービープレビュー]を確かめれば、遠近法によるパースペクティブがかかっています(図07-017)。

図07-017■回転の変換に透視(遠近法)投影を加える


適用した変換行列に、透視投影を加えた。

07-04 Graphics.drawTriangles()メソッドの第4引数 − カリング
1枚の矩形が水平に回せましたので、面の数を四方の4つに増やします。3次元空間における計8頂点の座標は、前章の立方体の例と同じに定めましょう(図06-007)。よって、原点(0, 0, 0)をその中心に置きます。

図06-007■3次元空間に立方体の座標を定める(再掲)
原点(0, 0, 0)が中心になるように、立方体の8頂点の座標を決める。

Graphics.drawTriangles()メソッドでテクスチャとして貼りつけるビットマップは、1枚絵でなければなりません。したがって、4面の画像をひとつのビットマップとして用意しておきます(図07-018)。各頂点番号に対する3次元区間の頂点座標とuv座標は、下表07-003のとおりです。

図07-018■4つの面をひとつのビットマップにする

各頂点に対応するuv座標は、下表07-003に示された10個。

表07-003■頂点番号と3次元空間座標およびuv座標
頂点番号 頂点座標 uv座標
0 (-nUnit, -nUnit, -nUnit) (0, 0)
1 (nUnit, -nUnit, -nUnit) (1/4, 0)
2 (nUnit, nUnit, -nUnit) (1/4, 1)
3 (-nUnit, nUnit, -nUnit) (0, 1)
4 (-nUnit, -nUnit, nUnit) (3/4, 0)
5 (nUnit, nUnit, nUnit) (2/4, 0)
6 (nUnit, nUnit, nUnit) (2/4, 1)
7 (-nUnit, nUnit, nUnit) (3/4, 1)
8(0) (-nUnit, -nUnit, -nUnit) (1, 0)
9(3) (-nUnit, nUnit, -nUnit) (1, 1)

表07-003には、頂点数が0から9までの10個示されています。立方体の頂点数は、全部で8つしかないはずです。けれども、uv座標は10個必要だからです(図07-018)。uv座標(1, 0)と(1, 1)は、それぞれ頂点番号0と3の座標(上図06-007参照)に当たります。ところが、ふたつの頂点番号には、すでにそれぞれuv座標(0, 0)と(0, 1)が割当てられています。そのため、ふたつの頂点番号8と9を新たに加え、uv座標は(1, 0)と(1, 1)を与えたうえで、それぞれ頂点番号0と3と同じ頂点座標を定めたのです。

そこで、後に掲げるスクリプト07-008では、前掲フレームアクション(スクリプト07-007)を修正して、これら10個の頂点座標およびuvt座標の設定を加えます(t値は0)。

  1. vertices3D.push(-nUnit, -nUnit, -nUnit);   // 頂点0
  2. vertices3D.push(nUnit, -nUnit, -nUnit);   // 頂点1
  3. vertices3D.push(nUnit, nUnit, -nUnit);   // 頂点2
  4. vertices3D.push(-nUnit, nUnit, -nUnit);   // 頂点3
  5. vertices3D.push(-nUnit, -nUnit, nUnit);   // 頂点4
  6. vertices3D.push(nUnit, -nUnit, nUnit);   // 頂点5
  7. vertices3D.push(nUnit, nUnit, nUnit);   // 頂点6
  8. vertices3D.push(-nUnit, nUnit, nUnit);   // 頂点7
  9. vertices3D.push(-nUnit, -nUnit, -nUnit);   // 頂点8(0と同じ)
  10. vertices3D.push(-nUnit, nUnit, -nUnit);   // 頂点9(3と同じ)
  1. uvtData.push(0, 0, 0);   // 頂点0
  2. uvtData.push(1/4, 0, 0);   // 頂点1
  3. uvtData.push(1/4, 1, 0);   // 頂点2
  4. uvtData.push(0, 1, 0);   // 頂点3
  5. uvtData.push(3/4, 0, 0);   // 頂点4
  6. uvtData.push(2/4, 0, 0);   // 頂点5
  7. uvtData.push(2/4, 1, 0);   // 頂点6
  8. uvtData.push(3/4, 1, 0);   // 頂点7
  9. uvtData.push(1, 0, 0);   // 頂点8
  10. uvtData.push(1, 1, 0);   // 頂点9

頂点番号の組合わせは、三角形に分けるとわずらわしく間違えやすいので、四角形の頂点番号で指定できる関数addRectangleIndices()を定めることにします(スクリプト第56〜59行目)。三角形の頂点番号は前述のとおり時計回りに決めたいので(07-02「Graphics.drawTriangles()メソッド」「Graphics.drawTriangles()メソッドの第2引数 − 頂点番号」)、関数の引数である四角形の頂点番号も時計回りに渡すものとします。

  1. addRectangleIndices(0, 1, 2, 3);   // 前面
  2. addRectangleIndices(1, 5, 6, 2);   // 左面
  3. addRectangleIndices(5, 4, 7, 6);   // 背面
  4. addRectangleIndices(4, 8, 9, 7);   // 右面
  1. function addRectangleIndices(n0:uint, n1:uint, n2:uint, n3:uint):void {
  2.   indices.push(n0, n1, n3);
  3.   indices.push(n1, n2, n3);
  4. }

これで立方体の8頂点座標がマウスポインタの水平位置に応じて3次元空間で横に回り、2次元平面に透視投影された4方の面にテクスチャがマッピングされます。けれど、このままではひとつ問題があります。Graphics.drawTriangles()メソッドの第2引数に渡す頂点番号は、初めに設定してずっとそのままです。したがって、三角形の描画の順序つまり面の重ね順が変わりません。そのため、3次元空間の座標で手前の面が背後に回っても、相変わらず前面に描画されてしまいます(図07-019)。

図07-019■3次元空間座標の前後が変わっても面の重ね順はそのまま

Graphics.drawTriangles()メソッドの第2引数に渡す頂点番号が初めに設定したままなので、面の重ね順も変わらない。

面を正しく重ね合わせるための一般的な方法は、z座標の前後により描画の順序を並べ替えることです。けれども、今回のように各面の裏が表示されることはないアニメーションであれば、裏返った面を表示しなければ済みます。

Graphics.drawTriangles()メソッドの第4引数は、三角形の向きがz軸に対して正か負かによって描画する面を定めます(前掲シンタックス07-002)。面の向きは、頂点番号の順序にしたがって回す右ネジの進む先です。つまり、頂点番号が時計回りであれば、面は手前向きになります。

面の表裏のうち一方だけを描画することは、「カリング」と呼ばれます。Graphics.drawTriangles()メソッドの第4引数には、下表07-004のTriangleCullingクラスの定数によりカリングを指定します。

表07-004■TriangleCullingクラスのカリングを指定する定数
描画する面 TriangleCullingクラス定数 z軸に対する方向
両面 TriangleCulling.NONE(デフォルト) 正負両方向
手前向きの面 TriangleCulling.NEGATIVE 正の方向
奥向きの面 TriangleCulling.POSITIVE 負の方向

Word 07-002■カリング
カリングとは、面の表裏の一方だけを描画することです。片面の表示を省くことにより、処理の負荷が減らせます。

Graphics.drawTriangles()メソッドに渡す第2引数の頂点番号は、時計回りに定めました。そこで、つぎのスクリプト07-008は、第53行目でGraphics.drawTriangles()メソッドの第4引数に定数TriangleCulling.NEGATIVEを渡します。したがって、手前に向いた面のみが描かれ、奥向きの面は表示されません。

スクリプト07-008■マウスポインタの水平位置により立方体の頂点座標を回して4面にテクスチャマッピング
  1. var nUnit:Number = 100 / 2;
  2. var nDeceleration:Number = 0.3;
  3. var mySprite:Sprite = new Sprite();
  4. var myGraphics:Graphics = mySprite.graphics;
  5. var myTexture:BitmapData = new Image(0, 0);
  6. var vertices3D:Vector.<Number> = new Vector.<Number>();
  7. var indices:Vector.<int> = new Vector.<int>();
  8. var uvtData:Vector.<Number> = new Vector.<Number>();
  9. var nDistance:Number = 300;
  10. var myPerspective:PerspectiveProjection = new PerspectiveProjection();
  11. var worldMatrix3D:Matrix3D = new Matrix3D();
  12. var viewMatrix3D:Matrix3D = new Matrix3D();
  13. myPerspective.focalLength = nDistance;
  14. addChild(mySprite);
  15. mySprite.x = stage.stageWidth / 2;
  16. mySprite.y = stage.stageHeight / 2;
  17. vertices3D.push(-nUnit, -nUnit, -nUnit);
  18. vertices3D.push(nUnit, -nUnit, -nUnit);
  19. vertices3D.push(nUnit, nUnit, -nUnit);
  20. vertices3D.push(-nUnit, nUnit, -nUnit);
  21. vertices3D.push(-nUnit, -nUnit, nUnit);
  22. vertices3D.push(nUnit, -nUnit, nUnit);
  23. vertices3D.push(nUnit, nUnit, nUnit);
  24. vertices3D.push(-nUnit, nUnit, nUnit);
  25. vertices3D.push(-nUnit, -nUnit, -nUnit);
  26. vertices3D.push(-nUnit, nUnit, -nUnit);
  27. addRectangleIndices(0, 1, 2, 3);
  28. addRectangleIndices(1, 5, 6, 2);
  29. addRectangleIndices(5, 4, 7, 6);
  30. addRectangleIndices(4, 8, 9, 7);
  31. uvtData.push(0, 0, 0);
  32. uvtData.push(1/4, 0, 0);
  33. uvtData.push(1/4, 1, 0);
  34. uvtData.push(0, 1, 0);
  35. uvtData.push(3/4, 0, 0);
  36. uvtData.push(2/4, 0, 0);
  37. uvtData.push(2/4, 1, 0);
  38. uvtData.push(3/4, 1, 0);
  39. uvtData.push(1, 0, 0);
  40. uvtData.push(1, 1, 0);
  41. viewMatrix3D.appendTranslation(0, 0, nDistance);
  42. viewMatrix3D.append(myPerspective.toMatrix3D());
  43. addEventListener(Event.ENTER_FRAME, xRotate);
  44. function xRotate(eventObject:Event):void {
  45.   var nRotationY:Number = mySprite.mouseX * nDeceleration;
  46.   var vertices2D:Vector.<Number> = new Vector.<Number>();
  47.   worldMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  48.   var myMatrix3D:Matrix3D = worldMatrix3D.clone();
  49.   myMatrix3D.append(viewMatrix3D);
  50.   Utils3D.projectVectors(myMatrix3D, vertices3D, vertices2D, uvtData);
  51.   myGraphics.clear();
  52.   myGraphics.beginBitmapFill(myTexture);
  53.   myGraphics.drawTriangles(vertices2D, indices, uvtData, TriangleCulling.NEGATIVE);
  54.   myGraphics.endFill();
  55. }
  56. function addRectangleIndices(n0:uint, n1:uint, n2:uint, n3:uint):void {
  57.   indices.push(n0, n1, n3);
  58.   indices.push(n1, n2, n3);
  59. }

[ムービープレビュー]を確かめると、裏に回った奥向きの面はカリングにより表示されないため、手前に向いた前の面だけが描画されます(図07-020)。これで立方体の8頂点座標がマウスポインタの水平位置に応じて3次元空間で横に回り、2次元平面に透視投影された4方の面にはテクスチャが正しくマッピングされました。

図07-020■カリングによって奥向きの面は表示されない

立方体の8頂点座標が透視投影された2次元平面に、4つの面のテクスチャが正しくマッピングされた。

立方体の残る上面と底面を同じように加え、さらにマウスポインタの垂直位置に応じて縦にも回転するようにしましょう(スクリプト07-009)。新たに覚えるべきことは、とくにありません。これまでの復習として、修正を加えていきます。まず、6面分のビットマップを用意します。上面と底面が増えますので、前掲スクリプト07-008とはu座標値が変わってきます。

図07-021■立方体の6面を展開したビットマップ

4面の上下に、上面と底面を加える。すると、u座標値が変わる。

つぎに、立方体の座標や頂点の値を追加します。立方体の頂点座標については、頂点4〜7の4頂点の座標を加えます(スクリプト第27〜30行目)。これは、uv座標が上面の上辺と底面の下辺の4つ加わる(スクリプト第47〜50行目)ことに対応するものです。面の頂点番号の組は上面と底面の矩形ふたつ分、つまり三角形が4つ増えます(スクリプト第35〜36行目)。

あとは、マウスポインタの垂直方向の位置を調べ(スクリプト第56行目)、その値に応じてx軸で回すだけです(スクリプト第59行目)。これで立方体がマウスポインタの位置に応じて上下左右に回り、矩形の6面はカリングされたうえでテクスチャが正しくマッピングされます(図07-022)。

スクリプト07-009■マウスポインタの位置により立方体の頂点座標を回してテクスチャマッピング
    // フレームアクション
  1. var nUnit:Number = 100 / 2;
  2. var nDeceleration:Number = 0.3;
  3. var mySprite:Sprite = new Sprite();
  4. var myGraphics:Graphics = mySprite.graphics;
  5. var myTexture:BitmapData = new Image(0, 0);
  6. var vertices3D:Vector.<Number> = new Vector.<Number>();
  7. var indices:Vector.<int> = new Vector.<int>();
  8. var uvtData:Vector.<Number> = new Vector.<Number>();
  9. var nDistance:Number = 300;
  10. var myPerspective:PerspectiveProjection = new PerspectiveProjection();
  11. var worldMatrix3D:Matrix3D = new Matrix3D();
  12. var viewMatrix3D:Matrix3D = new Matrix3D();
  13. myPerspective.focalLength = nDistance;
  14. addChild(mySprite);
  15. mySprite.x = stage.stageWidth / 2;
  16. mySprite.y = stage.stageHeight / 2;
  17. vertices3D.push(-nUnit, -nUnit, -nUnit);
  18. vertices3D.push(nUnit, -nUnit, -nUnit);
  19. vertices3D.push(nUnit, nUnit, -nUnit);
  20. vertices3D.push(-nUnit, nUnit, -nUnit);
  21. vertices3D.push(-nUnit, -nUnit, nUnit);
  22. vertices3D.push(nUnit, -nUnit, nUnit);
  23. vertices3D.push(nUnit, nUnit, nUnit);
  24. vertices3D.push(-nUnit, nUnit, nUnit);
  25. vertices3D.push(-nUnit, -nUnit, -nUnit);
  26. vertices3D.push(-nUnit, nUnit, -nUnit);
  27. vertices3D.push(-nUnit, -nUnit, nUnit);
  28. vertices3D.push(nUnit, -nUnit, nUnit);
  29. vertices3D.push(nUnit, nUnit, nUnit);
  30. vertices3D.push(-nUnit, nUnit, nUnit);
  31. addRectangleIndices(0, 1, 2, 3);
  32. addRectangleIndices(1, 5, 6, 2);
  33. addRectangleIndices(5, 4, 7, 6);
  34. addRectangleIndices(4, 8, 9, 7);
  35. addRectangleIndices(1, 0, 10, 11);
  36. addRectangleIndices(3, 2, 12, 13);
  37. uvtData.push(0, 1/3, 0);
  38. uvtData.push(1/4, 1/3, 0);
  39. uvtData.push(1/4, 2/3, 0);
  40. uvtData.push(0, 2/3, 0);
  41. uvtData.push(3/4, 1/3, 0);
  42. uvtData.push(2/4, 1/3, 0);
  43. uvtData.push(2/4, 2/3, 0);
  44. uvtData.push(3/4, 2/3, 0);
  45. uvtData.push(1, 1/3, 0);
  46. uvtData.push(1, 2/3, 0);
  47. uvtData.push(0, 0, 0);
  48. uvtData.push(1/4, 0, 0);
  49. uvtData.push(1/4, 1, 0);
  50. uvtData.push(0, 1, 0);
  51. viewMatrix3D.appendTranslation(0, 0, nDistance);
  52. viewMatrix3D.append(myPerspective.toMatrix3D());
  53. addEventListener(Event.ENTER_FRAME, xRotate);
  54. function xRotate(eventObject:Event):void {
  55.   var nRotationY:Number = mySprite.mouseX * nDeceleration;
  56.   var nRotationX:Number = mySprite.mouseY * nDeceleration;
  57.   var vertices2D:Vector.<Number> = new Vector.<Number>();
  58.   worldMatrix3D.appendRotation(nRotationY, Vector3D.Y_AXIS);
  59.   worldMatrix3D.appendRotation(nRotationX, Vector3D.X_AXIS);
  60.   var myMatrix3D:Matrix3D = worldMatrix3D.clone();
  61.   myMatrix3D.append(viewMatrix3D);
  62.   Utils3D.projectVectors(myMatrix3D, vertices3D, vertices2D, uvtData);
  63.   myGraphics.clear();
  64.   myGraphics.beginBitmapFill(myTexture);
  65.   myGraphics.drawTriangles(vertices2D, indices, uvtData, TriangleCulling.NEGATIVE);
  66.   myGraphics.endFill();
  67. }
  68. function addRectangleIndices(n0:uint, n1:uint, n2:uint, n3:uint):void {
  69.   indices.push(n0, n1, n3);
  70.   indices.push(n1, n2, n3);
  71. }

図07-022■立方体がマウスポインタの位置に応じて上下左右に回る

立方体の8頂点座標が透視投影された2次元平面に、矩形の6面がカリングされたうえでテクスチャマッピングされる。

でき上がったアニメーションは、04「3次元座標空間でDisplayObjectインスタンスを扱う − Matrix3Dクラス」の04-03「Matrix3Dクラスの後から加える座標変換」でつくったムービー(スクリプト04-011)と見た目はほとんど同じです。ただ、この04章のスクリプトの組立てでは、6面のひとつひとつにSpriteインスタンスを割当てています。そのため、透視投影は親タイムライン(のDisplayObject.transformプロパティがもつTransform.perspectiveProjectionオブジェクト)に任せてしまえました。

今回のスクリプト07-009では、3次元空間座標の透視投影をスクリプトで行い、PerspectiveProjectionオブジェクトもわざわざ設定しなければなりませんでした。反面、描画用のSpriteインスタンス以外にはDisplayObjectインスタンスをもたず、3次元空間の扱いはVector3DとMatrix3Dオブジェクトの数値演算だけで済ませています。画面に描くのは、2次元平面に投影したテクスチャのみです。よって、処理の最適化が期待できます。さらに、立方体のような単純なかたちでないときは、多くの場合DisplayObjectインスタンスでつくることが難しく、座標計算により形状を描くことになるでしょう。

3次元空間で表現したい内容に応じて、どのように処理するのが適切かを考え、判断していくことが求められます。本書は、その基礎を解説しています。

Maniac! 07-002■背面の処理
04章のDisplayObjectインスタンスを使ったスクリプト04-011でも、後ろを向いた面のSpriteインスタンスは非表示にしました。そして、なおかつ面のインスタンスの重ね順を並べ替えています。この並べ替えをしないと、裏返った面が一瞬見えてしまう場合もあるからです(図07-023左図)。

図07-023■裏返った面が場合によって一瞬表示される

真横の面もパースペクティブのかかった裏向きで描画される場合がある。

スクリプト04-011では、3次元空間におけるz軸に対する角度によって面の向きを調べています。ところが、2次元平面に透視投影すると、真横の面もパースペクティブのかかった裏向きで描画されることがあります(図07-023右図)。そのため、重ね順を並べ替える処理が必要になるのです。

それに対して、Graphics.drawTriangles()メソッドは、2次元平面に投影した後の頂点の位置によりカリングを行います。したがって、立方体のような閉じた形状であれば、重ね順を変えなくても正しく描画されるのです。立方体の上面・底面がないような閉じていない形状で、裏面も描画する場合には、面の重ね順を整える必要があります(図07-024)。

図07-024■上面・底面を取った立方体

裏面も描画するときは面の重ね順を整えなければならない。


Column 07 Utils3D.projectVectors()メソッドの内部計算とt値
Utils3D.projectVectors()メソッドは、3次元空間座標を2次元平面に透視投影するとともに、uvt座標のt値を与えます(シンタックス07-005)。そこで、これらの値の内部的な計算を調べてみます。まず、uvt座標でt値の果たす役割からご説明しましょう。


uvt座標におけるt値の役割
uv座標は、07-03「遠近法(透視)投影 − Utils3D.projectVectors()メソッド」で述べたとおり、テクスチャとなるビットマップの上の水平および垂直座標を示します。それに対して、t値は3次元空間における奥行きを表します。

前掲スクリプト07-008で用いた4つの正方形のビットマップ(図07-025上図)にt値を与えると、テクスチャをマッピングするときに透視投影の変換が加えられます。図07-025下図はt値として、左端の2頂点に1、右端2頂点には0.5を設定してテクスチャマッピングしています。

図07-025■左右両端のt値を異なった値に設定する


上図のテクスチャにt値として、左端の2頂点に1、右端2頂点には0.5を設定して投影したのが下図。

実際には、頂点間のt値に大きな差がなければ、明らかな違いは見られないかもしれません。けれども、テクスチャマッピングに、より現実に近い遠近感を与えます。


Utils3D.projectVectors()メソッドの透視投影座標とt値の計算
Utils3D.projectVectors()メソッドは、2次元平面に投影する座標やuvt座標のt値を求めるために、下図07-026のような焦点距離と原点(z = 0)までの距離、およびz座標値を用いているようです。

図07-026■Utils3D.projectVectors()メソッドの透視投影座標とt値の計算に使われるパラメータ

Utils3D.projectVectors()メソッドの透視投影座標とt値の計算には、焦点距離と原点(z = 0)までの距離、およびz座標値が用いられる。

まず、uvt座標のt値は、原点までの距離とz座標値により、つぎの式から導かれます。

t値 = 1 / (原点までの距離 + z座標値)

つぎに、2次元平面に投影されたxまたはy座標値は、3次元空間のxあるいはy座標値と焦点距離、およびt値を用いて、つぎの式により求められます。

2次元平面に投影されたxまたはy座標値 = 3次元空間のxまたはy座標値×焦点距離×t値

以上の計算結果を確かめる簡単なフレームアクションはつぎのとおりです(図07-027)。

var myPerspective:PerspectiveProjection = new PerspectiveProjection();
var myMatrix3D:Matrix3D = new Matrix3D();
var nDistance:Number = 1000;   // 原点までの距離
var nX:Number = 100;   // 3次元空間のxまたはy座標値
var nZ:Number = 1500;   // 3次元空間のz座標値
var vertices3D:Vector.<Number> = Vector.<Number>([nX, nX, nZ]);
var vertices2D:Vector.<Number> = new Vector.<Number>();
var uvtData:Vector.<Number> = Vector.<Number>([0, 0, 0]);
var nT:Number = 1/(nDistance + nZ);   // t値
myPerspective.focalLength = 500;   // 焦点距離
myMatrix3D.appendTranslation(0, 0, nDistance);
myMatrix3D.append(myPerspective.toMatrix3D());
Utils3D.projectVectors(myMatrix3D, vertices3D, vertices2D, uvtData);
trace(uvtData);   // uvt座標値
trace(nT);   // 計算されたt値
trace(vertices2D);   // 2次元平面に投影されたyx座標値
trace(nX * myPerspective.focalLength * nT);   // 計算された2次元平面への投影座標値

図07-027■Utils3D.projectVectors()メソッドと上記の式との計算結果を比べる

上記の式により計算されたt値と2次元平面への投影座標値が、Utils3D.projectVectors()メソッドの結果と一致。

[Prev/Next]


作成者: 野中文雄
作成日: 2010年1月31日


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