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

Closest Point Bezier Curve - Dragable Points

<<< DIRTY GEOMETRY >>>

Computes the closest point to a bezier curve

@author Andre Michelle
// forked from AndreMichelle's Closest Point Bezier Curve
package  
{
    import flash.events.MouseEvent;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Point;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;

    /**
     * <<< DIRTY GEOMETRY >>>
     * 
     * Computes the closest point to a bezier curve
     * 
     * @author Andre Michelle
     */
    [SWF(width='465',height='490',frameRate='50')]
    public class BezierClosestPoint extends Sprite 
    {
        private const point: Point = new Point();
        
        private const pointA: AnchorPoint = new AnchorPoint();
        private const pointB: AnchorPoint = new AnchorPoint();
        private const pointC: AnchorPoint = new AnchorPoint(0x00FF00);
        private const pointD: AnchorPoint = new AnchorPoint(0x0000FF);
        private const pointE: AnchorPoint = new AnchorPoint(0x000000);

        private const LINE:Number = 4;
        private const EPSILON: Number = 1e-8;
        private const R: Vector.<Number> = new Vector.<Number>( 3, true );
        
        private const textField: TextField = new TextField();
        
        private var dragAnchor: AnchorPoint;

        public function BezierClosestPoint()
        {
            pointE.x = -100;
            pointE.y = -100;
            addChild( pointE );   
            
            pointA.x = 140.0;
            pointA.y = 140.0;
            addChild( pointA );

            pointB.x = 300;
            pointB.y = 300;
            addChild( pointB );

            pointC.x = 200;
            pointC.y = 320;
            addChild( pointC );

            pointD.x = 120;
            pointD.y = 300;
            addChild( pointD );
            
            textField.mouseEnabled = false;
            textField.selectable = false;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.defaultTextFormat = new TextFormat( 'Arial', 14, 0x000000 );
            addChild( textField );

            addEventListener( Event.ENTER_FRAME, enterFrame );
            addEventListener( MouseEvent.MOUSE_DOWN, mouseDown );
            addEventListener( MouseEvent.MOUSE_UP, mouseUp );
        }
        
        private function mouseDown( event: MouseEvent ): void
        {
            dragAnchor = event.target as AnchorPoint;
        }

        private function mouseUp( event: MouseEvent ): void
        {
            dragAnchor = null;
        }

        private function enterFrame( event: Event ): void
        {
            if( dragAnchor == pointA )
            {
                pointA.x = mouseX;
                pointA.y = mouseY;
            }
            else
            if( dragAnchor == pointB )
            {
                pointB.x = mouseX;
                pointB.y = mouseY;
            }
            else
            if( dragAnchor == pointC )
            {
                pointC.x = mouseX;
                pointC.y = mouseY;
            }
            else
            if( dragAnchor == pointD )
            {
                pointD.x = mouseX;
                pointD.y = mouseY;
            }
            
            var x0: Number = pointA.x;
            var y0: Number = pointA.y;
            var x1: Number = pointC.x;
            var y1: Number = pointC.y;
            var x2: Number = pointB.x;
            var y2: Number = pointB.y;

            graphics.clear();
            graphics.lineStyle( LINE, 0xFF9900 );
            graphics.moveTo( x0, y0 );
            graphics.curveTo( x1, y1, x2, y2 );
            
            point.x = pointD.x;
            point.y = pointD.y;
            
            var distance: Number = getClosestPoint( x0, y0, x1, y1, x2, y2, point );
            
            textField.text = 'Distance to bezier: ' + distance.toFixed( 2 );

            graphics.lineStyle( LINE, 0x777777 );
            graphics.moveTo( pointD.x, pointD.y );
            graphics.lineTo( point.x, point.y );
            
            pointE.x = point.x;
            pointE.y = point.y;
        }

        private function getClosestPoint( x0: Number, y0: Number, x1: Number, y1: Number, x2: Number, y2: Number, point: Point ): Number
        {
            var ax: Number;
            var ay: Number;
            var dx: Number;
            var dy: Number;
            var px: Number = point.x;
            var py: Number = point.y;
            var dd: Number = Number.MAX_VALUE;

            var t: Number;

            var i: int = 0;
            var n: int = getClosestT( x0, y0, x1, y1, x2, y2, px, py );

            for( ; i < n ; ++i )
            {
                t = R[i];

                if( t <= 0.0 )
                {
                    ax = x0;
                    ay = y0;
                }
                else
                if( t >= 1.0 )
                {
                    ax = x2;
                    ay = y2;
                }
                else
                {
                    ax = eval( x0, x1, x2, t );
                    ay = eval( y0, y1, y2, t );
                }

                dx = px - ax;
                dy = py - ay;

                if( dx * dx + dy * dy < dd )
                {
                    point.x = ax;
                    point.y = ay;

                    dd = dx * dx + dy * dy;
                }
            }

            return Math.sqrt( dd );
        }
        
        private function eval( v0: Number, v1: Number, v2: Number, t: Number ): Number
        {
            var t1: Number = 1.0 - t;

            return v0 * t1 * t1 + v1 * 2.0 * t1 * t + v2 * t * t;
        }
        
        private function getClosestT( x0: Number, y0: Number, x1: Number, y1: Number, x2: Number, y2: Number, px: Number, py: Number ): int 
        {
            var dx: Number = x0 - px;
            var dy: Number = y0 - py;

            var ex: Number = x0 - 2.0 * x1 + x2;
            var ey: Number = y0 - 2.0 * y1 + y2;
            var fx: Number = 2.0 * x1 - 2.0 * x0;
            var fy: Number = 2.0 * y1 - 2.0 * y0;

            var d: Number = 2.0 * ( ex * ex + ey * ey );

            var a: Number;
            var b: Number;
            
            var u: Number;
            var v: Number;
            var w: Number;
            
            var p: Number;
            var q: Number;
            var r: Number;
            var x: Number;

            if( 0.0 == d )
            {
                b = ( fx * fx + fy * fy ) + 2.0 * ( dx * ex + dy * ey );

                if( b < 0.0 )
                {
                    if( -b > EPSILON )
                        return 0;
                }
                else
                if( b < EPSILON )
                    return 0;

                R[ 0 ] = -( fx * dx + fy * dy ) / b;
                return 1;
            }
            else
            {
                var dInv: Number = 1.0 / d;

                a = 3.0 * ( fx * ex + fy * ey ) * dInv;
                b = ( ( fx * fx + fy * fy ) + 2.0 * ( dx * ex + dy * ey ) ) * dInv;
                p = -a * a * 0.33333333333 + b;
                q = a * a * a * 0.074074074074074 - a * b * 0.33333333333 + ( fx * dx + fy * dy ) * dInv;
                r = q * q * 0.25 + p * p * p * 0.037037037037037;

                if( r >= 0.0 ) 
                {
                    r = Math.sqrt( r );
                    x = sqrt3( -q * 0.5 + r ) + sqrt3( -q * 0.5 - r ) - a * 0.33333333333;
                }
                else
                {
                    x = 2.0 * Math.sqrt( -p * 0.33333333333 ) * Math.cos( Math.atan2( Math.sqrt( -r ), -q * 0.5 ) * 0.33333333333 ) - a * 0.33333333333;
                }

                u = x + a;
                v = x * x + a * x + b;
                w = u * u - 4.0 * v;

                if( w < 0.0 )
                {
                    R[ 0 ] = x;
                    return 1;
                }

                if( w > 0.0 )
                {
                    w = Math.sqrt( w );
                    
                    R[ 0 ] = x;
                    R[ 1 ] = -( u + w ) * 0.5;
                    R[ 2 ] =  ( w - u ) * 0.5;
                    return 3;
                }

                R[ 0 ] = x;
                R[ 1 ] = -u * 0.5;
                return 2;
            }
        }

        private function sqrt3( x: Number ): Number 
        {
            if( x > 0.0 ) 
                return Math.pow( x, 0.33333333333 );
            else
            if( x < 0.0 ) 
                return -Math.pow( -x, 0.33333333333 );

            return 0.0;
        }
    }
}
import flash.display.Graphics;

import flash.display.Sprite;

class AnchorPoint extends Sprite
{
    public function AnchorPoint(color:uint = 0xFF0000)
    {
        var g:Graphics = this.graphics;
        
        g.clear();
        g.beginFill(0x000000,.3);
        g.drawCircle(0,0,10);
        g.endFill();        
        
        g.beginFill(color);
        g.lineStyle(2,0x000000);
        g.drawCircle(0,0,5);
        g.endFill();
    }
}