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

第55回 平面上における三角形の内側か外側かを外積で調べる

この記事を読むのに必要な時間:およそ 10.5 分

クリックした三角形にアウトラインを描く

いよいよお題の本丸となる関数(xInsideTriangle())を定める。三角形の3頂点座標のVectorオブジェクトと調べる座標のPointオブジェクトを引数に渡して,その座標が三角形の内側にあるかどうかブール値で返す。第1引数(triangle)のVectorオブジェクトには,前掲スクリプト3で加えた関数(xGetVertices())の戻り値と同じく,3頂点の座標をPointエレメントとして納める。

function xInsideTriangle(triangle:Vector.<Point>, myPoint:Point):Boolean {
  var nLength:uint = triangle.length;
  var pointStart:Point = triangle[nLength - 1];
  for (var i:uint = 0; i < nLength; i++) {
    var pointEnd:Point = triangle[i];
    var point0:Point = pointEnd.subtract(pointStart);
    var point1:Point = myPoint.subtract(pointStart);
    var bIsRight:Boolean = xIsRight(point0, point1);
    if (!bIsRight) {
      return false;
    }
    pointStart = pointEnd;
  }
  return true;
}

三角形の辺のベクトルは,ふたつの頂点座標の引き算で求める。Pointクラスは引き算をPoint.subtract()メソッドで定めている。ベクトルには方向があり,終点の座標から始点の座標を差引く※1⁠。

  • 引かれるPointオブジェクト.subtract(引くPointオブジェクト)

forループの中で,三角形の3辺のベクトルをひとつずつとり上げ(point0⁠⁠,同じ始点(pointStart)から調べる座標(myPoint)までのベクトル(point1)が右ネジの位置にあるかを前掲スクリプト1の関数(xIsRight())で調べている。

調べる座標が右ネジの位置になければ,三角形の外だということなので,ループ処理を直ちに止めreturnステートメント)falseを返す。逆にループが最後まで中断されなければ,調べる座標が3辺に対して右ネジの位置,つまり三角形の内側になるので,戻り値をtrueとする。

つぎは,マウスクリックのリスナー関数(xDrawTriangle())だ。引数に受取ったMouseEventオブジェクトのMouseEvent.localXMouseEvent.localYプロパティから,クリックした座標が得られる。その座標をPointオブジェクト(pointMouse)にし,forループで取出した三角形の3頂点座標のVectorオブジェクト(triangleVertices)とともに引数として,先ほど定めた関数(xInsideTriangle)を呼出す。

function xDrawTriangle(eventObject:MouseEvent):void {
  var nX:Number = eventObject.localX;
  var nY:Number = eventObject.localY;
  var pointMouse:Point = new Point(nX, nY);
  var nLength:uint = xGetNumTriangles();
  for (var i:uint = 0; i < nLength; i++) {
    var triangleIndices:Vector.<int> = xGetTriangleAt(i);
    var triangleVertices:Vector.<Point> = xGetVertices(triangleIndices);
    var bInsideTriangle:Boolean = xInsideTriangle(triangleVertices, pointMouse);
    if (bInsideTriangle) {
      xDrawOutline(triangleVertices);   // 三角形のアウトラインを描く
      return;
    }
  }
}

各三角形は互いに重なり合うことはない前提なので,クリックした座標を含む三角形が見つかれば,returnステートメントで関数の処理を終える。その三角形にはアウトラインを描くことにし,そのための関数(xDrawOutline())をつぎに定める。

var outlineSprite:Sprite = new Sprite();
var outlineGraphics:Graphics = outlineSprite.graphics;
mySprite.addChild(outlineSprite);
function xDrawOutline(triangleVertices:Vector.<Point>):void {
  var nLength:uint = triangleVertices.length;
  var pointLast:Point = triangleVertices[nLength - 1];
  outlineGraphics.clear();
  outlineGraphics.lineStyle(2, 0x0000FF);
  outlineGraphics.moveTo(pointLast.x, pointLast.y);
  for (var i:uint = 0; i < nLength; i++) {
    var myPoint:Point = triangleVertices[i];
    outlineGraphics.lineTo(myPoint.x, myPoint.y);
  }
}

アウトラインは別のSpriteインスタンス(outlineSprite)をつくって,そこに描くことにした。あとは,Graphicsクラスの描画メソッドを使って,三角形の3頂点座標を結んでいる※2⁠。

以上のつくり込みをしたフレームアクション全体が,つぎのスクリプト4だ。関数の数は増え,ボリュームもそれなりに膨らんだ。しかし,必要な機能をひとつひとつ関数にして,部品を組上げるようにスクリプティングしたので,戸惑うことは少なかったのではないか。

スクリプト4 クリックした三角形にアウトラインを描く

// フレームアクション
var nCenterX:Number = stage.stageWidth / 2;
var nCenterY:Number = stage.stageHeight / 2;
var mySprite:Sprite = new Sprite();
var outlineSprite:Sprite = new Sprite();
var myGraphics:Graphics = mySprite.graphics;
var outlineGraphics:Graphics = outlineSprite.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);
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);
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.addChild(outlineSprite);
mySprite.addEventListener(MouseEvent.MOUSE_DOWN, xDrawTriangle);
function xDrawTriangle(eventObject:MouseEvent):void {
  var nX:Number = eventObject.localX;
  var nY:Number = eventObject.localY;
  var pointMouse:Point = new Point(nX, nY);
  var nLength:uint = xGetNumTriangles();
  for (var i:uint = 0; i < nLength; i++) {
    var triangleIndices:Vector.<int> = xGetTriangleAt(i);
    var triangleVertices:Vector.<Point> = xGetVertices(triangleIndices);
    var bInsideTriangle:Boolean = xInsideTriangle(triangleVertices, pointMouse);
    if (bInsideTriangle) {
      xDrawOutline(triangleVertices);
      return;
    }
  }
}
function xDrawOutline(triangleVertices:Vector.<Point>):void {
  var nLength:uint = triangleVertices.length;
  var pointLast:Point = triangleVertices[nLength - 1];
  outlineGraphics.clear();
  outlineGraphics.lineStyle(2, 0x0000FF);
  outlineGraphics.moveTo(pointLast.x, pointLast.y);
  for (var i:uint = 0; i < nLength; i++) {
    var myPoint:Point = triangleVertices[i];
    outlineGraphics.lineTo(myPoint.x, myPoint.y);
  }
}
function xGetNumTriangles():uint {
  var numTriangles:uint = uint(indices.length / 3);
  return numTriangles;
}
function xGetTriangleAt(nIndex:int):Vector.<int>  {
  var nIndices:int = 3;
  var n:int = nIndex * nIndices;
  var triangleIndices:Vector.<int> = new Vector.<int>(nIndices);
  for (var i:uint = 0; i < nIndices; i++) {
    triangleIndices[i] = indices[uint(n + i)];
  }
  return triangleIndices;
}
function xGetVertices(triangle:Vector.<int>):Vector.<Point> {
  var nLength:uint = triangle.length;
  var triangleVertices:Vector.<Point> = new Vector.<Point>(nLength);
  for (var i:uint = 0; i < nLength; i++) {
    var nIndex:int = triangle[i];
    var n:int = nIndex * 2;
    var myPoint:Point = new Point(vertices[n], vertices[int(n + 1)]);
    triangleVertices[i] = myPoint;
  }
  return triangleVertices;
}
function xInsideTriangle(triangle:Vector.<Point>, myPoint:Point):Boolean {
  var nLength:uint = triangle.length;
  var pointStart:Point = triangle[nLength - 1];
  for (var i:uint = 0; i < nLength; i++) {
    var pointEnd:Point = triangle[i];
    var point0:Point = pointEnd.subtract(pointStart);
    var point1:Point = myPoint.subtract(pointStart);
    var bIsRight:Boolean = xIsRight(point0, point1);
    if (!bIsRight) {
      return false;
    }
    pointStart = pointEnd;
  }
  return true;
}
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;
}

[ムービープレビュー]で動きを確かめてみよう。テクスチャマッピングされたインスタンスをクリックすると,その座標を含む三角形にアウトラインが描かれる図5⁠。

図5 クリックした座標を含む三角形にアウトラインが描かれる

図5 クリックした座標を含む三角形にアウトラインが描かれる

次回は,やはり外積を使って,もう少し見栄えのするサンプルをつくるつもりだ。

※1)
Graphics.drawTriangles()メソッドで使う頂点番号は,時計回りでVectorオブジェクト(indices)に納めた前掲第46回図3参照⁠⁠。したがって,後の頂点の座標が終点になる。
※2)
閉じた図形の描き方について詳しくは,F-site直線で閉じた図形を描くを参照してほしい。

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

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書