In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Triangulate

Triangulator는 http://www.flipcode.com/archives/triangulate.cpp 이 코드를 AS3으로 포팅했습니다.

꼭짓점을 잡고 드래그해보세요.
Please drag the vertices
/**
 * 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;
    }
    
}