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

第46回分割した三角形にビットマップを変形して塗る

前回は、Graphics.drawTriangles()メソッドにより、三角形に分けた領域をビットマップで塗った。今回は、このいわゆる「テクスチャマッピング」について、さらに解説を加える。このメソッドの使い方は奥深い。その練習も兼ねよう。

矩形のビットマップを任意の四角形に変形する

まずは、前回のおさらいから入る。Graphics.drawTriangles()メソッドを使えば、正方形のビットマップも自由な四角形に変形できる。たとえば、100×100ピクセルのビットマップを、以下のような四角形に塗るとしよう。なお、位置合わせがしやすいように、Shapeインスタンスはステージの真ん中におく。したがって、基準点(0, 0)は中央になる。各頂点には、わかりやすいように整数の頂点番号(0~3)を添えた。

図1 正方形のビットマップを自由な四角形に変形する
図1 正方形のビットマップを自由な四角形に変形する

Graphics.drawTriangles()メソッドは、第1引数のVectorオブジェクト(Numberベース型)で三角形の頂点座標を順に納め、第3引数のVectorオブジェクト(Numberベース型)にはビットマップの対応するuv座標を定めて渡した。フレームアクションは、つぎのスクリプト1のようになるだろう。

Graphicsオブジェクト.drawTriangles(頂点座標, null, uv座標) 
スクリプト1 三角形分割した領域にビットマップを変形して塗る
// フレームアクション
var nCenterX:Number = stage.stageWidth / 2;
var nCenterY:Number = stage.stageHeight / 2;
var myShape:Shape = new Shape();
var myGraphics:Graphics = myShape.graphics;
var myTexture:BitmapData = new Image();
var vertices:Vector.<Number> = new Vector.<Number>();
var uvData:Vector.<Number> = new Vector.<Number>();
myShape.x = nCenterX;
myShape.y = nCenterY;
vertices.push(-50, -50);   // 頂点0
vertices.push(50, -70);   // 頂点1
vertices.push(-70, 70);   // 頂点2
vertices.push(50, -70);   // 頂点1 (重複)
vertices.push(70, 50);   // 頂点3
vertices.push(-70, 70);   // 頂点2 (重複)
uvData.push(0, 0);
uvData.push(1, 0);
uvData.push(0, 1);
uvData.push(1, 0);   // 重複
uvData.push(1, 1);
uvData.push(0, 1);   // 重複
myGraphics.beginBitmapFill(myTexture);
myGraphics.drawTriangles(vertices, null, uvData);
myGraphics.endFill();
addChild(myShape);

[ムービープレビュー]を確かめると、指定した四角形にビットマップが変形して塗られる図2⁠。結果はまったく問題ない。しかし、スクリプト1を見ると、四角形の頂点は4つなのに、Graphics.drawTriangles()メソッドに渡した第1引数と第3引数のVectorオブジェクト(頂点座標とuv座標)にはそれぞれ6組、つまり12の数値エレメントが加えられている。ふたつの三角形に頂点1と2が重複しているからだ(前掲図1参照⁠⁠。

図2 指定した四角形にビットマップが変形して塗られた
図2 指定した四角形にビットマップが変形して塗られた

テクスチャマッピングは、面を細かな三角形に分けてビットマップを貼っていく手法である。三角形の数が多くなると、頂点の重複もそれに伴って増えてしまう。Graphics.drawTriangles()メソッドの第2引数を使えば、座標の重複は避けることができる。

三角形の頂点の組を定める - Graphics.drawTriangles()メソッドの第2引数

前掲スクリプト1Graphics.drawTriangles()メソッドに渡す引数の指定を変えよう。第1引数(vertices)の頂点座標は、重複しない4組つまり8つの数値エレメントのみVectorオブジェクトに加える。対応する第3引数(uvData)のVectorオブジェクトも、同じく8つの数値エレメントを納めることになる。しかし、これではどの3頂点で三角形をつくるのかがわからない。その3頂点の組合わせを決めるのが第2引数だ。

Graphics.drawTriangles()メソッドの第2引数はintベース型のVectorオブジェクトで、三角形の3頂点番号の組を納める。頂点番号は0から始まる整数で、第1引数に納めた座標の順序にしたがう。3頂点番号がひと組になるので、スクリプト1の例なら三角形ふたつで、6つの整数を加えればよい。その手直しをしたのが、つぎのスクリプト2だ。

スクリプト2 Graphics.drawTriangles()メソッドに第2引数の頂点番号の組を渡す
// フレームアクション
var nCenterX:Number = stage.stageWidth / 2;
var nCenterY:Number = stage.stageHeight / 2;
var myShape:Shape = new Shape();
var myGraphics:Graphics = myShape.graphics;
var myTexture:BitmapData = new Image();
var vertices:Vector.<Number> = new Vector.<Number>();
var indices:Vector.<int> = new Vector.<int>();
var uvData:Vector.<Number> = new Vector.<Number>();
myShape.x = nCenterX;
myShape.y = nCenterY;
// 頂点座標
vertices.push(-50, -50);   // 頂点0
vertices.push(50, -70);   // 頂点1
vertices.push(-70, 70);   // 頂点2
vertices.push(70, 50);   // 頂点3
// 頂点番号
indices.push(0, 1, 2);   // 左上三角形
indices.push(1, 3, 2);   // 右下三角形
// uv座標
uvData.push(0, 0);
uvData.push(1, 0);
uvData.push(0, 1);
uvData.push(1, 1);
myGraphics.beginBitmapFill(myTexture);
myGraphics.drawTriangles(vertices, indices, uvData);
myGraphics.endFill();
addChild(myShape);

Graphics.drawTriangles()メソッドに渡す第1引数のVectorオブジェクト(Numberベース型)には、重複のない4頂点の座標を納めた。その順序は前掲図1の頂点番号に合わせている。第3引数のVectorオブジェクト(Numberベース型)にも、もちろん第1引数に対応するuv座標を4組加えた。

そして、第2引数のintベース型Vectorオブジェクト(indices)には、ふたつの三角形の3頂点番号の組を定めた。3頂点番号の順序は、今のところ気にしなくとも構わない。ただ、後のちのため、本連載では時計回りに決める[1]⁠。

[ムービープレビュー]を確かめると、描かれるビットマップは前掲スクリプト1と変わらない(前掲図2⁠。しかし、座標の数が減ると、内部的な負荷は軽くなる[2]⁠。また、座標と三角形の指定を分けることで、スクリプトも扱いやすくなる。

描画する三角形の座標をマウスクリックで動かす

今回の最後は、テクスチャマッピングに簡単なインタラクションを加える。下図3のように正方形を4つの三角形に分け、インスタンス上をマウスクリックしたとき中央の座標をその位置に動かしてみる。ちょっとしたモーフィング効果だ。

図3 4つに分けた三角形の中央座標をマウスクリックした位置に動かす
画像 画像

Shapeインスタンスはマウスクリックを受け取れない。そこで、Spriteインスタンスに替えてビットマップを塗ることにする。あとは初めの描画については、三角形が4つに増えたこと以外、前掲スクリプト2と考え方は同じだ[3]⁠。マウスクリックで動かす頂点座標は、頂点番号0に設定している。

スクリプト3 三角形の頂点座標のひとつをマウスクリックした位置に動かしてテクスチャマッピング
// フレームアクション
var nCenterX:Number = stage.stageWidth / 2;
var nCenterY:Number = stage.stageHeight / 2;
var mySprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
var myTexture:BitmapData = new Image();
var nHalfWidth:Number = myTexture.width / 2;
var nHalfHeight:Number = myTexture.height / 2;
var vertices:Vector.<Number> = new Vector.<Number>();
var indices:Vector.<int> = new Vector.<int>();
var uvData:Vector.<Number> = new Vector.<Number>();
mySprite.x = nCenterX;
mySprite.y = nCenterY;
// 頂点座標
vertices.push(0, 0);   // 頂点0: マウスクリックで動かす座標
vertices.push(-nHalfWidth, -nHalfHeight);
vertices.push(nHalfWidth, -nHalfHeight);
vertices.push(nHalfWidth, nHalfHeight);
vertices.push(-nHalfWidth, nHalfHeight);
// 頂点番号
indices.push(0, 1, 2);
indices.push(0, 2, 3);
indices.push(0, 3, 4);
indices.push(0, 4, 1);
// uv座標
uvData.push(0.5, 0.5);
uvData.push(0, 0);
uvData.push(1, 0);
uvData.push(1, 1);
uvData.push(0, 1);
myGraphics.beginBitmapFill(myTexture);
myGraphics.drawTriangles(vertices, indices, uvData);
myGraphics.endFill();
addChild(mySprite);
mySprite.addEventListener(MouseEvent.MOUSE_DOWN, xDraw);
function xDraw(eventObject:MouseEvent):void {
  // 頂点0の数値エレメント書替え
  vertices[0] = eventObject.localX;
  vertices[1] = eventObject.localY;
  myGraphics.clear();
  myGraphics.beginBitmapFill(myTexture);
  myGraphics.drawTriangles(vertices, indices, uvData);
  myGraphics.endFill();
}

SpriteインスタンスをマウスクリックInteractiveObject.mouseDownイベントしたときのリスナー関数(xDraw())は、クリック位置のxy座標でVectorオブジェクト(vertices)の頂点0の数値エレメントを書替えたうえで、Graphics.drawTriangles()メソッドに渡して再描画している。クリック時のSpriteインスタンスの基準点から見たマウスポインタのxy座標は、MouseEventオブジェクトのMouseEvent.localX/MouseEvent.localYプロパティで得られる。

[ムービープレビュー]でSpriteインスタンスをクリックすると、その位置がテクスチャの中心になるように変形して描かれる図4⁠。テクスチャの中心になっている頂点0の座標は、前掲図3のとおり4つの三角形すべてに共通している。もし、Graphics.drawTriangles()メソッドの第2引数で頂点番号の組を定めなければ、頂点0の座標が4つ重複し、マウスクリックのリスナー関数(xDraw())でも第1引数のVectorエレメントを4座標分書き替えなければならなかったはずだ。第2引数を用いたことにより、その書替えがひと組のxy座標で済んだのである。

図4 クリックした位置がテクスチャの中心になるように変形される
画像 画像

ようやく次回から、3次元空間座標を2次元平面に透視投影したうえで、その面にテクスチャマッピングする方法の解説に入る。

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

スクリプト1~3のサンプルファイル(CS5形式/約106KB)

おすすめ記事

記事・ニュース一覧