前回は特別編が加わったため,本編としては第53回「座標にもっとも近い線分上の点を内積で求める」の続きになる。ベクトルの内積のつぎは,外積を使ったサンプルの作成に移ろう。三角形で分割された領域があるとき,ある座標を与えて,それがどの三角形に含まれるのかを調べたい。また,座標などの多くの数値を扱うとき,スクリプトをどのように組立てたらよいのか,今回のお題で練習してみる。
平面上のふたつのベクトルの外積から互いの位置関係がわかる
三角形はもっとも単純な多角形だ。そのため,数学的に扱いやすい。たとえば,Graphics.drawTriangles()メソッドは,三角形に分けた領域をビットマップで塗った(第45回「領域をビットマップで塗る - テクスチャマッピング」以降参照)。すべての多角形は三角形に分けられ,自由な形状も三角形の集まりに近似できるからだ。
ベクトルの外積を使うと,座標が三角形の内側か外側かがわかる。今回のお題は,領域を三角形に分け,そのうちのどれがクリックされたのかを調べたい(図1)。
まず,外積の意味をおさらいしておこう(第52回「ベクトルの外積で回転の軸を定める」参照)。ベクトルAとBの外積A×Bは,つぎの第52回表1(再掲)のように定められる新たなベクトルだった。そして今回は,とくに「方向」に着目する。
第52回表1 外積A×Bで定められるベクトル(再掲)
| 外積の要素 | 求められた外積のベクトルとふたつのベクトルAとBとの関係 | ||
|---|---|---|---|
| 方向 | 角度 | ふたつのベクトルAとBのどちらにも垂直 | ![]() |
| 向き | ベクトルAからBに向かう回転で右ネジの進む向き | ||
| 大きさ | |A||B|sinθ | ||
お題は,xy平面の三角形について考える。すると,xy平面上のベクトルの外積は,角度が必ずxy平面と垂直つまりz軸と平行になる。つぎにその方向は,ふたつのベクトルが時計回りの位置にあれば,右ネジの向きであるz軸の正方向だ。逆に,xy平面上のふたつのベクトルAとBの外積A×Bがz軸正方向だったとすると,ベクトルAの右側にBがある。
そこで,三角形の3辺を時計回りの向きのベクトルと考えよう。調べる座標が三角形の内側にある場合,1辺のベクトルとその始点からその座標へのベクトルとの外積を求める(図2)。3辺のベクトルすべてについて,調べる座標のベクトルが右側にあれば,すなわちその座標は三角形の内側になる。
では早速,ふたつのベクトルを与えて,右ネジの位置にあるかどうか調べる関数(xIsRight())を定めよう。ベクトルはxy平面で考えるので,ふたつのPointオブジェクトにして引数に渡す。第1引数のベクトル(point0)の右に第2引数のベクトル(point1)があればtrue,そうでなければfalseを返す(スクリプト1)。
スクリプト1 ふたつのベクトルが右ネジの位置にあるかどうかを返す関数定義
function xIsRight(point0:Point, point1:Point):Boolean {
var vector3D_0:Vector3D = new Vector3D(point0.x, point0.y, 0);
var vector3D_1:Vector3D = new Vector3D(point1.x, point1.y, 0);
var crossProduct:Vector3D = vector3D_0.crossProduct(vector3D_1);
var bIsRight:Boolean = (crossProduct.z >= 0);
return bIsRight;
}
あいにく,Pointクラスには外積を求めるメソッドはない。したがって,引数のPointオブジェクトからxy座標を取出して,Vector3Dオブジェクトとしてつくり直す。そのうえで,Vector3D.crossProduct()メソッドにより,ふたつのVector3Dオブジェクトの外積を求める。
前述のとおり,外積のベクトルが奥向き,つまりz座標が正ならふたつの引数のベクトルは右ネジの位置にある。その結果をブール(論理)値にして関数から返す。たとえば,座標(0, -1)のベクトルに対して座標(1, 0)は右側にある。したがって,このふたつをPointオブジェクトとして関数に渡せば,trueが返される。
var bResult:Boolean = xIsRight(new Point(0, -1), new Point(1, 0))
trace(bResult); // 出力: true
この関数ができあがれば,今回のお題で使う数学の話はおしまいだ。これから先は,この関数をどう使うか,さらにはスクリプトをどう組立てるかが主題になる。そこで,今感じているかもしれないふたつの疑問に答えておく。
第1に,引数のPointをすぐにVector3Dオブジェクトに直すのなら,初めからVector3Dオブジェクトで渡すか,xy座標の数値を4つ引数にした方がよいのではないかということだ。まず,Vector3Dオブジェクトにすると,xy平面の座標にいちいちz座標として0を加えなければならない。それも手間だろう。つぎに,引数を単純な数値にするという手はありえる。しかし,数多くの座標を扱うときには,座標はひとつのオブジェクトの方が捉えやすい。
第2は,そもそもほしいのは,三角形の3頂点座標を定めて,ある座標がその内側かどうかを返す関数なのではないかということだ。確かに,そのような関数は必要だ。しかし,ひとつひとつの三角形やその頂点座標,さらには三角形の集まりをどう定めて,どう扱うのか,まだ決めていない。設計を先にきちんと考えなければならない。


