/**
* Copyright 0xABCDEF ( http://wonderfl.net/user/0xABCDEF )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/rMeH
*/
package
{
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.geom.Point;
public class Triangulate extends Sprite
{
private var sw:int;
private var sh:int;
private var count:int;
private var contour:Vector.<Point>;
private var controls:Vector.<PointControl>;
private var triangulator:Triangulator;
public function Triangulate()
{
sw = stage.stageWidth;
sh = stage.stageHeight;
count = 10;
contour = new Vector.<Point>;
controls = new Vector.<PointControl>;
triangulator = new Triangulator;
for( var i:int=0; i<count; ++i )
{
controls[ i ] = addChild( new PointControl( Point.polar( sh/2-20, ( 360/count )*i*Math.PI/180 ) ) ) as PointControl;
controls[ i ].x += sw/2;
controls[ i ].y += sh/2;
contour[ i ] = controls[ i ].point;
}
addEventListener( Event.ENTER_FRAME, ENTER_FRAME );
}
private function drawContour():void
{
graphics.moveTo( contour[ 0 ].x, contour[ 0 ].y );
for( var i:int=1; i<contour.length; ++i )
{
graphics.lineTo( contour[ i ].x, contour[ i ].y );
}
graphics.lineTo( contour[ 0 ].x, contour[ 0 ].y );
}
private function drawTriangles():void
{
triangulator.contour = contour;
for each( var triangle:Triangle in triangulator.triangles )
{
graphics.moveTo( triangle.a.x, triangle.a.y );
graphics.lineTo( triangle.b.x, triangle.b.y );
graphics.lineTo( triangle.c.x, triangle.c.y );
graphics.lineTo( triangle.a.x, triangle.a.y );
}
}
private function ENTER_FRAME( e:Event ):void
{
graphics.clear();
for each( var control:PointControl in controls ) control.updatePoint();
graphics.lineStyle( 4, 0xFF0000 );
drawContour();
graphics.lineStyle( 1 );
drawTriangles();
}
}
}
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
internal class PointControl extends Sprite
{
/**
* 드래그 가능한 PointControl이 포인터 위치의 가운데에 잠기는지(true) 아니면 사용자가 PointControl을 처음으로 클릭한 위치에 잠기는지(false)를 지정합니다.
* @default
*/
public var lockCenter:Boolean;
/**
* PointControl의 제한 영역을 지정하는 PointControl의 부모의 좌표와 관련된 값입니다.
* @default
*/
public var bounds:Rectangle;
/**
* 연결시킬 Point 객체입니다. 실제로 연결( Bind )되는 것은 아니고 updatePoint()와 updateControl() 메서드로 서로 연결되어있는 것처럼 보이게 할 수 있습니다.
* @default
*/
public var point:Point;
/**
* PointControl 객체를 생성합니다.
* @param point 연결시킬 Point 객체입니다. 실제로 연결( Bind )되는 것은 아니고 updatePoint()와 updateControl() 메서드로 서로 연결되어있는 것처럼 보이게 할 수 있습니다.
* @param drawMethod 이 컨트롤을 그릴, Graphics인자 하나를 받는 메서드입니다.
* @param lockCenter 드래그 가능한 PointControl이 포인터 위치의 가운데에 잠기는지(true) 아니면 사용자가 PointControl을 처음으로 클릭한 위치에 잠기는지(false)를 지정합니다.
* @param bounds PointControl의 제한 영역을 지정하는 PointControl의 부모의 좌표와 관련된 값입니다.
*/
public function PointControl( point:Point = null, drawMethod:Function = null, lockCenter:Boolean = false, bounds:Rectangle = null )
{
this.lockCenter = lockCenter;
this.bounds = bounds;
this.point = point?point:new Point;
updateControl();
drawMethod = Boolean( drawMethod )?drawMethod:defaultDrawMethod;
drawMethod( graphics );
addEventListener( MouseEvent.MOUSE_DOWN, MOUSE_DOWN );
addEventListener( MouseEvent.MOUSE_UP, MOUSE_UP );
super();
}
private function defaultDrawMethod( graphics:Graphics ):void
{
graphics.clear();
graphics.beginFill( 0xDDDDDD, 0.7 );
graphics.lineStyle( 1, 0x555555 );
graphics.drawCircle( 0, 0, 4 );
graphics.endFill();
}
private function MOUSE_DOWN( e:MouseEvent ):void
{
startDrag( lockCenter, bounds );
}
private function MOUSE_UP( e:MouseEvent ):void
{
stopDrag();
}
/**
* 연결된 Point 객체를 갱신시킵니다. PointControl의 좌표를 변경했을 때 사용합니다.
*/
public function updatePoint():void
{
point.x = x;
point.y = y;
}
/**
* PointControl의 좌표를 갱신합니다. 연결된 Point 객체의 속성이 수정되었을 때 사용합니다.
*/
public function updateControl():void
{
x = point.x;
y = point.y;
}
/**
* 이 PointControl 객체를 제거하기 위한 작업을 합니다. 이 메서드를 호출하면 이 객체의 부모로부터 removeChild 되며, 기본등록된 이벤트리스너가 제거됩니다.
*/
public function destroy():void
{
graphics.clear();
parent.removeChild( this );
removeEventListener( MouseEvent.MOUSE_DOWN, MOUSE_DOWN );
removeEventListener( MouseEvent.MOUSE_UP, MOUSE_UP );
}
}
internal class Triangle
{
/**
* 삼각형을 이루는 정점입니다.
* @default
*/
public var a:Point;
/**
* 삼각형을 이루는 정점입니다.
* @default
*/
public var b:Point;
/**
* 삼각형을 이루는 정점입니다.
* @default
*/
public var c:Point;
/**
* 삼각형을 생성합니다.
* @param ax 정점의 x값입니다.
* @param ay 정점의 y값입니다.
* @param bx 정점의 x값입니다.
* @param by 정점의 y값입니다.
* @param cx 정점의 x값입니다.
* @param cy 정점의 y값입니다.
*/
public function Triangle( ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number )
{
a = new Point( ax, ay );
b = new Point( bx, by );
c = new Point( cx, cy );
}
/**
* 삼각형이 해당 점을 포함하는지 여부를 반환합니다.
* @param point 알잖아요.
* @return 삼각형 안쪽에 점이 있으면 true를 반환합니다. 삼각형이 플래시의 화면좌표계 기준으로 CCW( 반시계 방향 )로 구성되어있다면 무조건 false가 반환됩니다.
*/
public function haveCW( point:Point ):Boolean
{
var Px:Number = point.x, Py:Number = point.y;
var Ax:Number = a.x, Ay:Number = a.y;
var Bx:Number = b.x, By:Number = b.y;
var Cx:Number = c.x, Cy:Number = c.y;
var ax:Number = Cx-Bx, ay:Number = Cy-By;
var bx:Number = Ax-Cx, by:Number = Ay-Cy;
var cx:Number = Bx-Ax, cy:Number = By-Ay;
var apx:Number = Px-Ax, apy:Number = Py-Ay;
var bpx:Number = Px-Bx, bpy:Number = Py-By;
var cpx:Number = Px-Cx, cpy:Number = Py-Cy;
var axb:Number = ax*bpy - ay*bpx;
var bxc:Number = bx*cpy - by*cpx;
var cxa:Number = cx*apy - cy*apx;
return ( ( axb>=0 )&&( bxc>=0 )&&( cxa>=0 ) );
}
/**
* 삼각형이 해당 점을 포함하는지 여부를 반환합니다.
* @param point 알잖아요.
* @return 삼각형 안쪽에 점이 있으면 true를 반환합니다. 삼각형이 플래시의 화면좌표계 기준으로 CW( 시계 방향 )로 구성되어있다면 무조건 false가 반환됩니다.
*/
public function haveCCW( point:Point ):Boolean
{
var Px:Number = point.x, Py:Number = point.y;
var Ax:Number = c.x, Ay:Number = c.y;
var Bx:Number = b.x, By:Number = b.y;
var Cx:Number = a.x, Cy:Number = a.y;
var ax:Number = Cx-Bx, ay:Number = Cy-By;
var bx:Number = Ax-Cx, by:Number = Ay-Cy;
var cx:Number = Bx-Ax, cy:Number = By-Ay;
var apx:Number = Px-Ax, apy:Number = Py-Ay;
var bpx:Number = Px-Bx, bpy:Number = Py-By;
var cpx:Number = Px-Cx, cpy:Number = Py-Cy;
var axb:Number = ax*bpy - ay*bpx;
var bxc:Number = bx*cpy - by*cpx;
var cxa:Number = cx*apy - cy*apx;
return ( ( axb>=0 )&&( bxc>=0 )&&( cxa>=0 ) );
}
/**
* 삼각형이 해당 점을 포함하는지 여부를 반환합니다.
* @param point 알잖아요.
* @return 삼각형 안쪽에 점이 있으면 true를 반환합니다.
*/
public function have( point:Point ):Boolean
{
return haveCW( point )||haveCCW( point );
}
}
internal class Triangulator
{
private static const EPSILON:Number = 0.0000000001;
private var _contour:Vector.<Point>;
private var _triangles:Vector.<Triangle>;
/**
* 삼각화 객체를 생성합니다.
* @param contour 삼각화 하고자 하는 도형( 윤곽선 )입니다.
*/
public function Triangulator( contour:Vector.<Point> = null )
{
this.contour = contour;
}
/**
* @return 삼각화를 처리하고자 하는 도형입니다.
*/
public function get contour():Vector.<Point>
{
return _contour;
}
/**
* @param value 처음에 들어오는 점과 끝에 들어오는 점이 같을 필요는 없습니다.
*/
public function set contour( value:Vector.<Point> ):void
{
_contour = value;
if( value ) process();
}
/**
* 삼각화가 처리된 결과입니다.
* @return 삼각형들이 들어있습니다. 입력값이 적절하지 않을 경우 null을 반환합니다.
*/
public function get triangles():Vector.<Triangle>
{
return _triangles;
}
private function area():Number
{
var n:int = _contour.length;
var A:Number = 0;
for( var p:int=n-1, q:int=0; q<n; p=q++ )
{
A += _contour[ p ].x*_contour[ q ].y - _contour[ q ].x*_contour[ p ].y;
}
return A*0.5;
}
private function snip( u:int, v:int, w:int, n:int, V:Vector.<int> ):Boolean
{
var Ax:Number = _contour[ V[ u ] ].x;
var Ay:Number = _contour[ V[ u ] ].y;
var Bx:Number = _contour[ V[ v ] ].x;
var By:Number = _contour[ V[ v ] ].y;
var Cx:Number = _contour[ V[ w ] ].x;
var Cy:Number = _contour[ V[ w ] ].y;
if( EPSILON > (((Bx-Ax)*(Cy-Ay))-((By-Ay)*(Cx-Ax))) ) return false;
var P:Point;
var T:Triangle;
for( var p:int=0; p<n; ++p )
{
if( (p==u)||(p==v)||(p==w) ) continue;
P = new Point( _contour[ V[ p ] ].x, _contour[ V[ p ] ].y );
T = new Triangle( Ax, Ay, Bx, By, Cx, Cy );
if( T.haveCW( P ) ) return false;
}
return true;
}
private function process():void
{
_triangles = new Vector.<Triangle>;
var n:int = _contour.length;
if( n<3 )
{
_triangles = null;
return;
}
var V:Vector.<int> = new Vector.<int>( n, true );
var v:int;
if( 0<area() ) for( v=0; v<n; ++v ) V[ v ] = v;
else for( v=0; v<n; ++v ) V[ v ] = ( n-1 )-v;
var nv:int = n;
var count:int = 2*nv;
v = nv-1;
var u:int, w:int;
var a:Point, b:Point, c:Point;
var s:int, t:int;
while( nv>2 )
{
if( count-- <= 0 )
{
_triangles = null;
return;
}
u = v; if( nv<=u ) u=0;
v = u+1; if( nv<=v ) v=0;
w = v+1; if( nv<=w ) w=0;
if( snip( u, v, w, nv, V ) )
{
a = _contour[ V[ u ] ];
b = _contour[ V[ v ] ];
c = _contour[ V[ w ] ];
_triangles.push( new Triangle( a.x, a.y, b.x, b.y, c.x, c.y ) );
for( s=v, t=v+1; t<nv; V[ s++ ] = V[ t++ ] ) continue;
--nv;
count = 2*nv;
}
}
V = null;
}
}