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

A Star algorithm - AS3 - Path finding

/!\ NOTE - To see the performance of this algorithm in action - uncheck the bottom left combo box


Inspired by 
http://lab.polygonal.de/wp-content/assets/110722/flash.html
this version doesn't include polygonal de library.

The solve algorithm inspired by:
// http://web.mit.edu/eranki/www/tutorials/search/
/**
 * Copyright WLAD ( http://wonderfl.net/user/WLAD )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/z1Ri
 */

package
{
    import com.bit101.components.*;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.utils.getTimer;
    
    /**
     * ...
     * @author vladik.voina@gmail.com
     */
    public class Main extends Sprite 
    {
        static public const MAX_CIRCLES:int = 20;
        private var output:Output;
        
        private var traceOutput:TextArea;
        
        private var circles:Vector.<Circle>;
        
        private var cb:CheckBox;
        
        public function Main():void {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private var state:String;
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
            
            state = "creatingNodes";
            
            cb = new CheckBox(this, 10, stage.stageHeight - 20, "Output (slow)");
            cb.selected = true;
            traceOutput = new TextArea(this, stage.stageWidth - 100, 0);
            traceOutput.setSize( 100, stage.stageHeight - 20);
            var fsButton:PushButton = new PushButton( stage, traceOutput.x, traceOutput.height, "FULLSCREEN TEXT", function(e:*= null):void
            {
                var t:String = traceOutput.textField.text;
                
                if ( traceOutput.scaleX == 1)
                {
                    traceOutput.parent.addChild( traceOutput );
                    
                    fsButton.label = "EXIT FULLSCREEN TEXT";
                    
                    traceOutput.x = traceOutput.y = 0;
                    traceOutput.setSize( stage.stageWidth / 2, stage.stageHeight / 2);
                    traceOutput.scaleX = traceOutput.scaleY = 2;
                } else {
                    
                    traceOutput.scaleX = traceOutput.scaleY = 1;
                    traceOutput.move( stage.stageWidth - 100, 0 );
                    traceOutput.setSize( 100, stage.stageHeight - 20);
                    fsButton.label = "FULLSCREEN TEXT";
                }
                
                traceOutput.draw();
                traceOutput.text = t;
                traceOutput.draw();
                
            }); fsButton.width = traceOutput.width;
            
            traceOutput.text = "Add 20 Circles to stage\n\n" + 
                "Use the bottom left check box to toogle the output. It can slow down the function up to Infinite times slower\n\n";
            
            output = new Output( );
            addChild( output );
            
            output.text = "Click on the stage to add nodes";
            
            circles = new Vector.<Circle>();
            
            stage.addEventListener(MouseEvent.MOUSE_DOWN, addCircle);
        }
        
        private function addCircle(e:MouseEvent):void 
        {
            // dist1 + dist2 < 50
            
            if ( mouseX > traceOutput.x - 10 || mouseY > stage.stageHeight - 25 ) return;
            
            var c:Circle = new Circle( circles.length + 1 );
            
            c.x = mouseX;
            c.y = mouseY;
            
            var closest1:Circle; var dist1:Number = Infinity;
            var closest2:Circle; var dist2:Number = Infinity;
            
            var dist:Number = 0;
            
            for ( var i:int = 0; i < circles.length; ++i )
            {
                dist = c.dist( circles[i] );
                
                if ( dist < 50 )
                {
                    output.text = "Don't add nodes too close to each other\nJust don't...";
                    
                    c.dispose();
                    
                    return;
                    
                } else {
                    
                    if ( dist < dist1 )
                    {
                        dist1 = dist;
                        closest1 = circles[i];
                    }
                }
            }
            
            for ( i = 0; i < circles.length; ++i )
            {
                if ( circles[i] == closest1 ) continue;
                
                dist = c.dist( circles[i] );
                
                if ( dist < dist2 )
                {
                    dist2 = dist;
                    closest2 = circles[i];
                }
            }
            
            if ( closest1 ) c.node( closest1 );
            if ( closest2 ) c.node( closest2 );
            
            c.addEventListener(MouseEvent.CLICK, onCircleClick);
            circles.push( c );
            addChild( c );
            
            graphics.clear();
            
            drawArrows();
            
            if ( circles.length == MAX_CIRCLES )
            {
                state = "selectStart";
                
                stage.removeEventListener(MouseEvent.MOUSE_DOWN, addCircle);
                
                output.text = "Select node-start";
            }
        }
        
        private function drawArrows():void 
        {
            // Draw arrows
            
            var c1:Circle;
            var c2:Circle;
            
            for (var i:int = 0; i < circles.length; ++i )
            {
                c1 = circles[i];
                c1.color = 0x0;
                c1.draw();
                
                
                for ( var j:int = 0; j < c1.nodes.length; ++j )
                {
                    c2 = c1.nodes[j];
                    
                    var p1:Point = c1.getConnectPoint( c2 );
                    var p2:Point = c2.getConnectPoint( c1 );
                    
                    DrawingShapes.target = graphics;
                    graphics.beginFill(0xAAAAAA);
                    graphics.lineStyle( 2, 0xAAAAAA );
                    DrawingShapes.drawArrow( p1.x, p1.y, p2.x, p2.y, 6, true);
                    graphics.endFill();
                }
            }
            
        }
        
        private var nodeStart:Circle = null;
        private var nodeFinish:Circle = null;
        
        
        private function onCircleClick(e:MouseEvent):void 
        {
            var c:Circle = e.target as Circle;
            
            if ( state == "selectStart")
            {
                if ( nodeFinish != null ) 
                {
                    //c.color = 0x0;
                    //c.draw();
                    
                    graphics.clear();
                    drawArrows();
                }
                
                nodeStart = c;
                
                c.color = 0xFF0000;
                c.draw();
                
                state = "selectEnd";
                
                output.text = "Select node-finish";
            }
            
            else if (state == "selectEnd")
            {
                if ( c == nodeStart ) 
                {
                    output.text = "Select node-finish other then node-start";
                } 
                else 
                {
                    nodeFinish = c;
                    
                    c.color = 0xFF0000;
                    c.draw();
                    
                    traceOutput.textField.scrollV = traceOutput.textField.maxScrollV;
                    
                    state = "selectStart";
                    
                    output.text = "Computing...";
                    
                    var t:int = getTimer();
                    
                    // A Star algorithm
                    c = solve( nodeStart, nodeFinish , cb.selected);
                    
                    t = getTimer() - t;
                    
                    if ( c == null ) output.text += "\nFailed. no path was found, try again";
                    
                    else {
                        
                        output.text += "\nPath found in " + t.toString() + "ms";
                        
                        
                        traceOut("\n\n\n");
                        traceOut(output.text);
                        
                        output.text += "\nSelect another starting node...";
                        
                        // reverse path -----
                        
                        var path:Vector.<int> = new Vector.<int>();
                        
                        path.push( c.n );
                        
                        var cpath:String = c.n.toString() + " -> ";
                        
                        while ( c.parentNode )
                        {
                            c = c.parentNode;
                            path.push( c.n );
                            
                            cpath += c.n.toString();
                            
                            if( c.parentNode ) 
                                cpath += " -> ";
                        }
                        
                        // reverse path end -----
                        
                        traceOut("Original path: " + cpath );
                        
                        graphics.clear();
                        drawArrows();
                        
                        nodeStart.color = 0xFF0000;
                        nodeStart.draw();
                        nodeFinish.color = 0xFF0000;
                        nodeFinish.draw();
                        
                        // draw path
                        DrawingShapes.target = graphics;
                        
                        graphics.lineStyle(3, 0xFF0000);
                        
                        var p1:Point, p2:Point;
                        
                        c = null;
                        
                        var prevC:Circle = circles[path.shift() - 1];
                        
                        while ( path.length )
                        {
                            if ( c ) prevC = c;
                            
                            c = circles[path.shift() - 1];
                            
                            p1 = prevC.getConnectPoint( c );
                            p2 = c.getConnectPoint( prevC );
                            
                            graphics.beginFill( 0xFF0000 );
                            DrawingShapes.drawArrow(p2.x, p2.y, p1.x, p1.y, 10);
                            graphics.endFill();
                        }
                    }
                }
            }
        }
        
        private function solve( startingNode:Circle, finishNode:Circle , debugTrace:Boolean = true) :Circle
        {
            // http://web.mit.edu/eranki/www/tutorials/search/
            
            //initialize the open list
            var open:Vector.<Circle> = new Vector.<Circle>();
            //initialize the closed list
            var closed:Vector.<Circle> = new Vector.<Circle>();
            //put the starting node on the open list (you can leave its f at zero)
            open.push( startingNode );
            
            startingNode.parentNode = null;
            startingNode.g = 0;
            startingNode.f = 0;
            startingNode.h = finishNode.dist( startingNode );
            
            if( debugTrace ) traceOut("Solve begin", "tStartingNode:", 1, startingNode);
            
            // while the open list is not empty
            while ( open.length > 0 )
            {
                if( debugTrace ) traceOut("While", "open list is not empty", 2, open);
                
                // find the node with the least f on the open list, call it "q"
                var q:Circle = null; var index:int = 0;
                for (var i:int = 0; i < open.length; ++i) 
                {
                    if ( q == null ) q = open[i];
                    else { if ( q.f > open[i].f ) 
                    { 
                        q = open[i]; 
                        index = i; 
                    } }
                }
                
                if( debugTrace ) traceOut("Node search", "found node 'q["+q.n.toString()+"]' with the least f on the open list", 2, q);
                
                if( debugTrace ) traceOut("Pop 'q["+q.n.toString()+"]' off the open list nad add to the closed list");
                
                // Pop q from the open list and add to the closed list
                closed.push( open.splice(index, 1)[0] );
                
                if( debugTrace ) traceOut("Trace", "Lists", 2, open, closed);
                
                var successor:Circle;
                
                if( debugTrace ) traceOut("loop begin - generate q["+q.n.toString()+"]'s successors and set their parents to q["+q.n.toString()+"]");
                
                //generate q's successors and set their parents to q
                for ( i = 0; i < q.nodes.length; ++i)
                {
                    successor = q.nodes[ i ];
                    
                    if( debugTrace ) traceOut("Read", "successor", 3, successor);
                    
                    var successorClosedIndex:int = closed.indexOf( successor );
                    var successorOpenIndex:int = open.indexOf( successor );
                    
                    // Edit : Check if it's in the closed list, if so skip this successor
                    if ( successorClosedIndex >= 0 || successorOpenIndex >= 0) 
                    {
                        if( debugTrace ) traceOut("successor was found in the closed list or in the open list, continue loop");
                        continue;
                    }
                    
                    
                    if( debugTrace ) traceOut("successor wasn't found in the closed list");
                    
                    successor.parentNode = q;
                    
                    if( debugTrace ) traceOut("successor[" + successor.n.toString() + "] parent was set to q[" + q.n.toString() + "]");
                    
                    // if successor is the goal, stop the search
                    if ( successor == finishNode )
                    {
                        if( debugTrace ) traceOut("successor is the finish node, stop search, return successor");
                        
                        return successor;
                    }
                    
                    if( debugTrace ) traceOut("Trace successor", "before change", 3, successor);
                    
                    // G - distance of the current node to the starting node
                    
                    // successor.g = q.g + distance between successor and q
                    successor.g = q.g + q.dist( successor );
                    
                    // H - linear distance to the goal ( approximation )
                    
                    //  successor.h = distance from goal to successor
                    successor.h = nodeFinish.dist( successor );
                    
                    // F - force ( sum of h and g )
                    
                    successor.f = successor.g + successor.h;
                    
                    if( debugTrace ) traceOut("Trace successor", "after change", 3, successor);
                    
                    // if a node with the same position as successor is in the OPEN
                    // list which has a lower f than successor, skip this successor
                    
                    // ...
                    
                    // if a node with the same position as successor is in the CLOSED
                    // list which has a lower f than successor, skip this successor
                    
                    // ...
                    
                    // otherwise, add the node to the open list
                    open.push( successor );
                }
            }
                
            if( debugTrace ) traceOut("Solve end", "[ no path was found]", 1, open, closed);
                
            return null;
        }
        
        private function traceOut( name:String, msg:String = "", depth:int = 0, ...arguments):void
        {
            if ( msg == "" ) 
            {
                traceText( ">", name );
                traceText( "----------------" );
                
                return;
            }
            
            var stars:String = "";while ( depth > 0 ) { depth--; stars += "*"; }
            
            traceText( stars + " " + name + " - " + msg );
            
            var wasOpened:Boolean = false;
            
            for ( var i:int = 0; i < arguments.length; i++)
            {
                if ( arguments[i] is Vector.<Circle> )
                {
                    traceText( stars + " " + ( !wasOpened ? "Open" : "Closed" ) + " list:");
                    
                    wasOpened = true;
                    
                    traceText( stars + "\t" + arguments[i].join("\n" + stars + "\t") );
                }
                else {
                    traceText( stars + "\t" + String(arguments[i]) );
                }
            }
            
            traceText( stars + " " + name );
            traceText( "----------------" );
        }
        
        private function traceText(...arguments):void
        {
            traceOutput.textField.appendText( arguments.join(" ") + "\n" );
        }
    }
}
import flash.geom.Point;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.display.*;
import flash.geom.Matrix;


class Circle extends Sprite
{
    static public const RADIUS:int = 10;
    
    public var n:int;
    
    /// F - force ( the sum of g and h )
    public var f:Number;
    /// G - distance of the current node to the starting node
    public var g:Number;
    /// H - linear distance to the goal ( approximation )
    public var h:Number;
    
    public var color:uint = 0x0;
    
    public var parentNode:Circle = null;
    
    public function Circle( n:int )
    {
        this.n = n;
        buttonMode = useHandCursor = true;
        mouseChildren = false;
        
        
        color = 0x0;
        
        draw();
        
        nodes = new Vector.<Circle>();
    }
    
    public var nodes:Vector.<Circle>;
    
    public function getConnectPoint( c:Circle ):Point
    {
        var p:Point = new Point( c.x - x, c.y - y );
        var l:Number = Math.sqrt(p.x * p.x + p.y * p.y)
        p.x *= (RADIUS * 1.2) / l;
        p.y *= (RADIUS * 1.2) / l;
        return new Point( x + p.x, y + p.y );
    }
    
    public function node( c:Circle ):void
    {
        var double:Boolean = false;
        
        for ( var i:int = 0; i < nodes.length; ++i )
            if ( nodes[i] == c ) double = true
            
        if ( !double ) {
            nodes.push( c );
            c.node( this );
        }
    }
    
    public function get pos():Point
    {
        return new Point( x , y );
    }
    
    public function dist( c:Circle ):Number
    {
        return Point.distance( c.pos, pos ); 
    }
    
    public function draw():void
    {
        graphics.clear();
        DrawingShapes.target = graphics;
        
        graphics.beginFill(0xFFFFFF);
        graphics.lineStyle(2 , color);
        graphics.drawCircle(0, 0, RADIUS);
        graphics.endFill();
        
        DrawingShapes.drawText( n.toString(), 0, 0, "center" );
    }
    
    public function dispose():void 
    {
        nodes.length = 0;
    }
    
    override public function toString():String
    {
        return "N[" + n.toString() + "]::" +
            "( h:" + h.toFixed(1) + 
            " ,g: " + g.toFixed(1) +
            " ,f: " + f.toFixed(1) + 
                " )-P[" + ( parentNode == null ? 
                "null" : parentNode.n.toString() ) + 
                "]-Nodes[" + nodesToString() + "].";
    }
    
    private function nodesToString():String
    {
        if ( nodes.length == 0 ) return "empty";
        var s:String = "";
        for (var i:int = 0; i < nodes.length; i++)
            s += nodes[i].n.toString() + (i < nodes.length - 1 ? "," : "");
        return s;
    }
}


class Output extends TextField
{
    public function Output()
    {
        super();
        
        x = y = 5;
        
        mouseEnabled = mouseWheelEnabled = 
        multiline = wordWrap = 
        selectable = false;
            
        autoSize = "left";
        
        var defTextFormat:TextFormat = 
            new TextFormat("Arial", 16, 0x0, true);
            
        defaultTextFormat = defTextFormat;
        
        setTextFormat( defTextFormat );
    }
}

/**
 * http://www.funky-monkey.nl/blog/2010/04/20/drawing-shapes-in-as3-a-class-to-draw-an-arc-star-gear-cog-wedge-and-a-burst/
 * 
 * based on source code found at:
 * http://www.macromedia.com/devnet/mx/flash/articles/adv_draw_methods.html
 * 
 * @author Ric Ewing - version 1.4 - 4.7.2002
 * @author Kevin Williams - version 2.0 - 4.7.2005
 * @author Aden Forshaw - Version AS3 - 19.4.2010
 * @author Sidney de Koning - Version AS3 - 20.4.2010 - errors/correct datatypes/optimized math operations
 *         Arie de Bonth - Version AS3 - 09.04.2011 - update/rewrite of drawLine() function
 * @author Vladik V - Version AS3 - 20.3.2014 
                * Removed target:Graphics from each function, now use - DrawingShapes.target = mySprite.graphics;
                * Added function drawWedge2()
                * added draw text function
 * 
 * Usage:
 * var s : Shape = new Shape( ); // Or Sprite of MovieClip or any other Class that makes use of the Graphics class
 * 
 * // Important ! - link the target 
 * DrawingShapes.target = s.graphics;
 * 
 * // Draw an ARC
 * s.graphics.lineStyle( 4, 0xE16606 );
 * DrawingShapes.drawArc( 50, 50, 10, 150, 60 );
 * 
 * // Draw an BURST    
 * s.graphics.lineStyle( 3, 0x000000 );
 * DrawingShapes.drawBurst( 80, 60, 3, 15, 6, 27 );
 * 
 * // Draw an DASHED-LINE like so - - - -         
 * s.graphics.lineStyle( 1, 0x3C3C39 );
 * DrawingShapes.drawDash( 120, 60, 150, 80, 2, 2 );
 * 
 * // Draw an GEAR        
 * s.graphics.lineStyle( 3, 0xE16606 );
 * DrawingShapes.drawGear( 200, 60, 13, 31, 26, 0, 7, 13 );
 * 
 * // Draw a POLYGON        
 * s.graphics.lineStyle( 3, 0x0074B9 );
 * DrawingShapes.drawPolygon( 270, 60, 7, 30, 45 );
 * 
 * // Draw a STAR        
 * s.graphics.lineStyle( 2, 0x000000 );
 * DrawingShapes.drawStar( 340, 60, 18, 24, 19, 27 );
 * 
 * // Draw an WEDGE - good for pie charts or pacmans        
 * s.graphics.lineStyle( 2, 0xFFCC00 );
 * DrawingShapes.drawWedge( 400, 60, 30, 309, 209 );
 * 
 * // Draw a LINE    
 * s.graphics.lineStyle( 2, 0x0074B9 );
 * DrawingShapes.drawLine( 440, 85, 30, DrawingShapes.VERTICAL_LINE );
 *         
 * addChild( s );
 * 
 * 
 */
class DrawingShapes {

    public static const HORIZONTAL_LINE : String = "DrawingShapes.horizontal";
    public static const VERTICAL_LINE : String = "DrawingShapes.vertical";
    
    public function DrawingShapes() {
        throw new ArgumentError( "The DrawingShapes Class " +
                                 "cannot be instanicated." );
    }
    
    public static var target:Graphics;
    
    static private function checkTarget():void 
    {
        if ( target == null ) throw new Error("The target variable" + 
            " in DrawingShapes class was not linked.\n" + 
            " Use 'DrawingShapes.target = myShape.graphics;' " + 
            "to connect this class to it's drawing [Graphics] target");
    }
    
    /**
     * drawDash
     * Draws a dashed line from the point x1,y1 to the point x2,y2
     * 
     * @param x1 Number starting position on x axis - <strong></strong>required</strong>
     * @param y1 Number starting position on y axis - <strong></strong>required</strong>
     * @param x2 Number finishing position on x axis - <strong></strong>required</strong>
     * @param y2 Number finishing position on y axis - <strong></strong>required</strong>
     * @param dashLength [optional] Number the number of pixels long each dash 
     * will be.  Default = 5
     * @param spaceLength [optional] Number the number of pixels between each 
     * dash.  Default = 5
     */
    public static function drawDash(x1 : Number, y1 : Number, x2 : Number, y2 : Number, dashLength : Number = 5, spaceLength : Number = 5 ) : void { checkTarget();
        
        var x : Number = x2 - x1;
        var y : Number = y2 - y1;
        var hyp : Number = Math.sqrt( (x) * (x) + (y) * (y) );
        var units : Number = hyp / (dashLength + spaceLength);
        var dashSpaceRatio : Number = dashLength / (dashLength + spaceLength);
        var dashX : Number = (x / units) * dashSpaceRatio;
        var spaceX : Number = (x / units) - dashX;
        var dashY : Number = (y / units) * dashSpaceRatio;
        var spaceY : Number = (y / units) - dashY;
        
        target.moveTo( x1, y1 );
        while (hyp > 0) {
            x1 += dashX;
            y1 += dashY;
            hyp -= dashLength;
            if (hyp < 0) {
                x1 = x2;
                y1 = y2;
            }
            target.lineTo( x1, y1 );
            x1 += spaceX;
            y1 += spaceY;
            target.moveTo( x1, y1 );
            hyp -= spaceLength;
        }
        target.moveTo( x2, y2 );
    }
    

    /**
     * Draws an arc from the starting position of x,y.
     * 
     * @param x x coordinate of the starting pen position
     * @param y y coordinate of the starting pen position 
     * @param radius radius of Arc.
     * @param arc = sweep of the arc. Negative values draw clockwise.
     * @param startAngle = [optional] starting offset angle in degrees.
     * @param yRadius = [optional] y radius of arc. if different than 
     * radius, then the arc will draw as the arc of an oval.  
     * default = radius.
     *
     * Based on mc.drawArc by Ric Ewing.
     * the version by Ric assumes that the pen is at x:y before this
     * method is called.  I explictily move the pen to x:y to be 
     * consistent with the behaviour of the other methods.
     */
    public static function drawArc(x : Number, y : Number, radius : Number, arc : Number, startAngle : Number = 0, yRadius : Number = 0) : void  { checkTarget();
        
        if (arguments.length < 5) {
            throw new ArgumentError( "DrawingShapes.drawArc() - too few parameters, need atleast 5." );
            return;
        }
        
        // if startAngle is undefined, startAngle = 0
        if( startAngle == 0 ) {
            startAngle = 0;
        }
        // if yRadius is undefined, yRadius = radius
        if (yRadius == 0) {
            yRadius = radius;
        }
        
        // Init vars
        var segAngle : Number, theta : Number, angle : Number, angleMid : Number, segs : Number, ax : Number, ay : Number, bx : Number, by : Number, cx : Number, cy : Number;
        // no sense in drawing more than is needed :)
        if (DrawingShapes.abs( arc ) > 360) {
            arc = 360;
        }
        // Flash uses 8 segments per circle, to match that, we draw in a maximum
        // of 45 degree segments. First we calculate how many segments are needed
        // for our arc.
        segs = DrawingShapes.ceil( DrawingShapes.abs( arc ) / 45 );
        // Now calculate the sweep of each segment
        segAngle = arc / segs;
        // The math requires radians rather than degrees. To convert from degrees
        // use the formula (degrees/180)*Math.PI to get radians. 
        theta = -(segAngle / 180) * Math.PI;
        // convert angle startAngle to radians
        angle = -(startAngle / 180) * Math.PI;
        // find our starting points (ax,ay) relative to the secified x,y
        ax = x - Math.cos( angle ) * radius;
        ay = y - Math.sin( angle ) * yRadius;
        // if our arc is larger than 45 degrees, draw as 45 degree segments
        // so that we match Flash's native circle routines.
        if (segs > 0) {
            target.moveTo( x, y );
            // Loop for drawing arc segments
            for (var i : int = 0; i < segs; ++i) {
                // increment our angle
                angle += theta;
                // find the angle halfway between the last angle and the new
                angleMid = angle - (theta / 2);
                // calculate our end point
                bx = ax + Math.cos( angle ) * radius;
                by = ay + Math.sin( angle ) * yRadius;
                // calculate our control point
                cx = ax + Math.cos( angleMid ) * (radius / Math.cos( theta / 2 ));
                cy = ay + Math.sin( angleMid ) * (yRadius / Math.cos( theta / 2 ));
                // draw the arc segment
                target.curveTo( cx, cy, bx, by );
            }
        }
    }

    /**
     * draws pie shaped wedges.  Could be employeed to draw pie charts.
     * 
     * @param x x coordinate of the center point of the wedge
     * @param y y coordinate of the center point of the wedge
     * @param radius the radius of the wedge 
     * @param arc the sweep of the wedge. negative values draw clockwise
     * @param startAngle the starting angle in degrees
     * @param yRadius [optional] the y axis radius of the wedge. 
     * If not defined, then yRadius = radius.
     * 
     * based on mc.drawWedge() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawWedge(x : Number, y : Number, radius : Number, arc : Number, startAngle : Number = 0,  yRadius : Number = 0) : void { checkTarget();
        
        // if yRadius is undefined, yRadius = radius
        if (yRadius == 0) {
            yRadius = radius;
        }
        
        // move to x,y position
        target.moveTo( x, y );
        // if yRadius is undefined, yRadius = radius
        if (yRadius == 0) {
            yRadius = radius;
        }
        // Init vars
        var segAngle : Number, theta : Number, angle : Number, angleMid : Number, segs : Number, ax : Number, ay : Number, bx : Number, by : Number, cx : Number, cy : Number;
        // limit sweep to reasonable numbers
        if (DrawingShapes.abs( arc ) > 360) {
            arc = 360;
        }
        // Flash uses 8 segments per circle, to match that, we draw in a maximum
        // of 45 degree segments. First we calculate how many segments are needed
        // for our arc.
        segs = DrawingShapes.ceil( DrawingShapes.abs( arc ) / 45 );
        // Now calculate the sweep of each segment.
        segAngle = arc / segs;
        // The math requires radians rather than degrees. To convert from degrees
        // use the formula (degrees/180)*Math.PI to get radians.
        theta = -(segAngle / 180) * Math.PI;
        // convert angle startAngle to radians
        angle = -(startAngle / 180) * Math.PI;
        // draw the curve in segments no larger than 45 degrees.
        if (segs > 0) {
            // draw a line from the center to the start of the curve
            ax = x + Math.cos( startAngle / 180 * Math.PI ) * radius;
            ay = y + Math.sin( -startAngle / 180 * Math.PI ) * yRadius;
            target.lineTo( ax, ay );
            // Loop for drawing curve segments
            for (var i : int = 0; i < segs; ++i) {
                angle += theta;
                angleMid = angle - (theta / 2);
                bx = x + Math.cos( angle ) * radius;
                by = y + Math.sin( angle ) * yRadius;
                cx = x + Math.cos( angleMid ) * (radius / Math.cos( theta / 2 ));
                cy = y + Math.sin( angleMid ) * (yRadius / Math.cos( theta / 2 ));
                target.curveTo( cx, cy, bx, by );
            }
            // close the wedge by drawing a line to the center
            target.lineTo( x, y );
        }
    }
    
    public static function drawWedge2(x : Number, y : Number, radius : Number, arc : Number, startAngle : Number = 0, innerRadius : Number = 0) : void { checkTarget();
        
        var segAngle : Number, theta : Number, angle : Number, angleMid : Number, segs : Number, ax : Number, ay : Number, bx : Number, by : Number, cx : Number, cy : Number;
        
        if (DrawingShapes.abs( arc ) > 360) {
            arc = 360;
        }
        
        segs = DrawingShapes.ceil( DrawingShapes.abs( arc ) / 45 );
        
        segAngle = arc / segs;
        
        theta = -(segAngle / 180) * Math.PI;
        
        angle = -(startAngle / 180) * Math.PI;
        
        if (segs > 0) {
            
            ax = x + Math.cos( startAngle / 180 * Math.PI ) * innerRadius;
            ay = y + Math.sin( -startAngle / 180 * Math.PI ) * innerRadius;
            
            target.moveTo( ax, ay );
            
            ax = x + Math.cos( startAngle / 180 * Math.PI ) * radius;
            ay = y + Math.sin( -startAngle / 180 * Math.PI ) * radius;
            
            target.lineTo( ax, ay );
            
            for (var i : int = 0; i < segs; ++i) {
                angle += theta;
                angleMid = angle - (theta / 2);
                bx = x + Math.cos( angle ) * radius;
                by = y + Math.sin( angle ) * radius;
                cx = x + Math.cos( angleMid ) * (radius / Math.cos( theta / 2 ));
                cy = y + Math.sin( angleMid ) * (radius / Math.cos( theta / 2 ));
                target.curveTo( cx, cy, bx, by );
            }
            
            ax = x + Math.cos( angle ) * innerRadius;
            ay = y + Math.sin( angle) * innerRadius;
            
            target.lineTo( ax, ay );
            
            for (i = segs - 1; i >= 0; --i) {
                angle -= theta;
                angleMid = angle + (theta / 2);
                bx = x + Math.cos( angle ) * innerRadius;
                by = y + Math.sin( angle ) * innerRadius;
                cx = x + Math.cos( angleMid ) * (innerRadius / Math.cos( theta / 2 ));
                cy = y + Math.sin( angleMid ) * (innerRadius / Math.cos( theta / 2 ));
                target.curveTo( cx, cy, bx, by );
            }
            
            ax = x + Math.cos( startAngle / 180 * Math.PI ) * innerRadius;
            ay = y + Math.sin( -startAngle / 180 * Math.PI ) * innerRadius;
            
            target.lineTo( ax, ay );
        }
    }

    /**
     * start draws a star shaped polygon.
     * 
     * <blockquote>Note that the stars by default 'point' to
     * the right. This is because the method starts drawing
     * at 0 degrees by default, putting the first point to
     * the right of center. Negative values for points
     * draws the star in reverse direction, allowing for
     * knock-outs when used as part of a mask.</blockquote>
     *  
     * @param x x coordinate of the center of the star
     * @param y y coordinate of the center of the star
     * @param points the number of points on the star
     * @param innerRadius the radius of the inside angles of the star
     * @param outerRadius the radius of the outside angles of the star
     * @param angle [optional] the offet angle that the start is rotated
     * 
     * based on mc.drawStar() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawStar(x : Number, y : Number, points : uint, innerRadius : Number, outerRadius : Number, angle : Number = 0) : void { checkTarget();
        
        // check that points is sufficient to build polygon
        if(points <= 2) {
            throw ArgumentError( "DrawingShapes.drawStar() - parameter 'points' needs to be atleast 3" ); 
            return;
        }
        if (points > 2) {
            // init vars
            var step : Number, halfStep : Number, start : Number, n : Number, dx : Number, dy : Number;
            // calculate distance between points
            step = (Math.PI * 2) / points;
            halfStep = step / 2;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo( x + (Math.cos( start ) * outerRadius), y - (Math.sin( start ) * outerRadius) );
            // draw lines
            for (n = 1; n <= points; ++n) {
                dx = x + Math.cos( start + (step * n) - halfStep ) * innerRadius;
                dy = y - Math.sin( start + (step * n) - halfStep ) * innerRadius;
                target.lineTo( dx, dy );
                dx = x + Math.cos( start + (step * n) ) * outerRadius;
                dy = y - Math.sin( start + (step * n) ) * outerRadius;
                target.lineTo( dx, dy );
            }
        }
    }

    /**
     * a method for creating polygon shapes.  Negative values will draw 
     * the polygon in reverse direction.  Negative drawing may be useful 
     * for creating knock-outs in masks.
     * 
     * @param x x coordinate of the center of the polygon
     * @param y y coordinate of the center of the polygon
     * @param sides the number of sides (must be > 2)
     * @param radius the radius from the center point to the points
     * on the polygon
     * @param angle [optional] the starting offset angle (degrees) from
     * 0. Default = 0
     * 
     * based on mc.drawPoly() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawPolygon(x : Number, y : Number, sides : uint, radius : Number, angle : Number = 0) : void { checkTarget();
        
        // check that sides is sufficient to build
        if(sides <= 2) {
            throw ArgumentError( "DrawingShapes.drawPolygon() - parameter 'sides' needs to be atleast 3" ); 
            return;
        }
        if (sides > 2) {
            // init vars
            var step : Number, start : Number, n : Number, dx : Number, dy : Number;
            // calculate span of sides
            step = (Math.PI * 2) / sides;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo( x + (Math.cos( start ) * radius), y - (Math.sin( start ) * radius) );
            // draw the polygon
            for (n = 1; n <= sides; ++n) {
                dx = x + Math.cos( start + (step * n) ) * radius;
                dy = y - Math.sin( start + (step * n) ) * radius;
                target.lineTo( dx, dy );
            }
        }
    }

    /**
     * Burst is a method for drawing star bursts.  If you've ever worked
     * with an advertising department, you know what they are ;-)
     * Clients tend to want them, Developers tend to hate them...
     *
     * @param x x coordinate of the center of the burst
     * @param y y coordinate of the center of the burst
     * @param sides number of sides or points
     * @param innerRadius radius of the indent of the curves
     * @param outerRadius radius of the outermost points
     * @param angle [optional] starting angle in degrees. (defaults to 0)
     * 
     * based on mc.drawBurst() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawBurst(x : Number, y : Number, sides : uint, innerRadius : Number, outerRadius : Number, angle : Number = 0 ) : void { checkTarget();
        
        // check that sides is sufficient to build
        if(sides <= 2) {
            throw ArgumentError( "DrawingShapes.drawBurst() - parameter 'sides' needs to be atleast 3" ); 
            return;
        }
        if (sides > 2) {
            // init vars
            var step : Number, halfStep : Number, qtrStep : Number, start : Number, n : Number, dx : Number, dy : Number, cx : Number, cy : Number;
            // calculate length of sides
            step = (Math.PI * 2) / sides;
            halfStep = step / 2;
            qtrStep = step / 4;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo( x + (Math.cos( start ) * outerRadius), y - (Math.sin( start ) * outerRadius) );
            // draw curves
            for (n = 1; n <= sides; ++n) {
                cx = x + Math.cos( start + (step * n) - (qtrStep * 3) ) * (innerRadius / Math.cos( qtrStep ));
                cy = y - Math.sin( start + (step * n) - (qtrStep * 3) ) * (innerRadius / Math.cos( qtrStep ));
                dx = x + Math.cos( start + (step * n) - halfStep ) * innerRadius;
                dy = y - Math.sin( start + (step * n) - halfStep ) * innerRadius;
                target.curveTo( cx, cy, dx, dy );
                cx = x + Math.cos( start + (step * n) - qtrStep ) * (innerRadius / Math.cos( qtrStep ));
                cy = y - Math.sin( start + (step * n) - qtrStep ) * (innerRadius / Math.cos( qtrStep ));
                dx = x + Math.cos( start + (step * n) ) * outerRadius;
                dy = y - Math.sin( start + (step * n) ) * outerRadius;
                target.curveTo( cx, cy, dx, dy );
            }
        }
    }

    /**
     * draws a gear shape on the Graphics target.  The gear position 
     * is indicated by the x and y arguments.
     * 
     * @param x x coordinate of the center of the gear
     * @param y y coordinate of the center of the gear
     * @param sides number of teeth on gear. (must be > 2)
     * @param innerRadius radius of the indent of the teeth.
     * @param outerRadius outer radius of the teeth.
     * @param angle = [optional] starting angle in degrees. Defaults to 0.
     * @param holeSides [optional] draw a polygonal hole with this many sides (must be > 2)
     * @param holeRadius [optional] size of hole. Default = innerRadius/3.
     * 
     * based on mc.drawGear() - by Ric Ewing (ric@formequalsfunction.com) - version 1.4 - 4.7.2002
     */
    public static function drawGear(x : Number, y : Number, sides : uint, innerRadius : Number = 80, outerRadius : Number = 4, angle : Number = 0, holeSides : Number = 2, holeRadius : Number = 0 ) : void { checkTarget();
        
        // check that sides is sufficient to build polygon
        if(sides <= 2) {
            throw ArgumentError( "DrawingShapes.drawGear() - parameter 'sides' needs to be atleast 3" ); 
            return;
        }
        if (sides > 2) {
            // init vars
            var step : Number, qtrStep : Number, start : Number, n : Number, dx : Number, dy : Number;
            // calculate length of sides
            step = (Math.PI * 2) / sides;
            qtrStep = step / 4;
            // calculate starting angle in radians
            start = (angle / 180) * Math.PI;
            target.moveTo( x + (Math.cos( start ) * outerRadius), y - (Math.sin( start ) * outerRadius) );
            // draw lines
            for (n = 1; n <= sides; ++n) {
                dx = x + Math.cos( start + (step * n) - (qtrStep * 3) ) * innerRadius;
                dy = y - Math.sin( start + (step * n) - (qtrStep * 3) ) * innerRadius;
                target.lineTo( dx, dy );
                dx = x + Math.cos( start + (step * n) - (qtrStep * 2) ) * innerRadius;
                dy = y - Math.sin( start + (step * n) - (qtrStep * 2) ) * innerRadius;
                target.lineTo( dx, dy );
                dx = x + Math.cos( start + (step * n) - qtrStep ) * outerRadius;
                dy = y - Math.sin( start + (step * n) - qtrStep ) * outerRadius;
                target.lineTo( dx, dy );
                dx = x + Math.cos( start + (step * n) ) * outerRadius;
                dy = y - Math.sin( start + (step * n) ) * outerRadius;
                target.lineTo( dx, dy );
            }
            // This is complete overkill... but I had it done already. :)
            if (holeSides > 2) {
                step = (Math.PI * 2) / holeSides;
                target.moveTo( x + (Math.cos( start ) * holeRadius), y - (Math.sin( start ) * holeRadius) );
                for (n = 1; n <= holeSides; ++n) {
                    dx = x + Math.cos( start + (step * n) ) * holeRadius;
                    dy = y - Math.sin( start + (step * n) ) * holeRadius;
                    target.lineTo( dx, dy );
                }
            }
        }
    }

    /**
     * draws a line between two points. Make it horizontal or vertical 
     * 
     * @param x x coordinate of the center of the gear
     * @param y y coordinate of the center of the gear
     * @param length length of the line in pixels
     * @param direction Use DrawingShapes.HORIZONTAL_LINE or DrawingShapes.VERTICAL_LINE
     */
    public static function drawLine( x : Number, y : Number, length : Number, direction : String = DrawingShapes.HORIZONTAL_LINE ) : void { checkTarget();
        target.moveTo( x, y );
        switch (direction) {
            case DrawingShapes.HORIZONTAL_LINE :
                target.lineTo( x + length, y );
                break;
            case DrawingShapes.VERTICAL_LINE :
                target.lineTo( x, y + length );
                break;
        }
    }
    
    private static var text:TextField;
    
    /**
     * @param    label The string text value of the text field
     * @param    x The X coordinates of the text on the target 
     * @param    y The Y coordinates of the text on the target  
     * @param    position "<b>topLeft</b>" - unchanged, "<b>center</b>" - the center of the text will be placed on top of the given x & y 
     * @param    txtFormat
     */
    public static function drawText( label:String ,x:int = 0, y:int = 0, position:String = "topLeft",txtFormat:TextFormat = null ):void { checkTarget();
        
        // Check for empty text
        if ( label == "" || label == " ") return;
        
        // Set up the text
        if( text == null ) text = new TextField();
        
        text.mouseEnabled = text.mouseWheelEnabled = text.multiline = text.wordWrap = text.selectable = false;
        
        text.autoSize = "left";
        
        var defaultTextFormat:TextFormat = txtFormat == null ? 
        
            new TextFormat("Calibri", 16, 0x0)    :     txtFormat;
        
        text.defaultTextFormat = defaultTextFormat;
        
        text.setTextFormat( defaultTextFormat );
        
        text.text = label;
        
        // Use bitmap data to capture text as image
        var bitmapData:BitmapData = new BitmapData( text.width, text.height , true, 0x0);
        
        bitmapData.draw( text );
        
        // Translate the image
        var matrix:Matrix = new Matrix();
        
        switch( position )
        {
            case "center":    
                
                matrix.translate( x - text.width / 2, y - text.height / 2 );
            
            break;
            
            case "topLeft":
                
            default:
            
                matrix.translate( x, y );
            
            break;
        }
        
        // Draw bitmapdata to target [Graphics]
        target.lineStyle( );
        
        target.beginBitmapFill( bitmapData , matrix , false , true );
        
        target.drawRect( matrix.tx , matrix.ty , text.width , text.height );
        
        target.endFill( );
    }
    
    // Referance: http://stackoverflow.com/questions/8787391/drawing-an-arrow-at-the-end-point-of-the-line-using-line-slope
    /// ax, ay - arrow starting position. bx, by - arrow tip
    public static function drawArrow(ax:int, ay:int, bx:int, by:int, size:Number = 8, doubleSided:Boolean = false):void
    {
        var abx:int, aby:int, ab:int, cx:Number, cy:Number, dx:Number, dy:Number, ex:Number, ey:Number, fx:Number, fy:Number;
        var ratio:Number = 2, fullness1:Number = 2, fullness2:Number = 3;
        
        abx = bx - ax;
        aby = by - ay;
        ab = Math.sqrt(abx * abx + aby * aby);

        cx = bx - size * abx / ab;
        cy = by - size * aby / ab;
        dx = cx + (by - cy) / ratio;
        dy = cy + (cx - bx) / ratio;
        ex = cx - (by - cy) / ratio;
        ey = cy - (cx - bx) / ratio;
        fx = (fullness1 * cx + bx) / fullness2;
        fy = (fullness1 * cy + by) / fullness2;

        // draw lines: a -> b -> d -> f -> e -> b
        target.moveTo(ax, ay);
        target.lineTo(bx, by);
        target.lineTo(dx, dy);
        target.lineTo(fx, fy);
        target.lineTo(ex, ey);
        target.lineTo(bx, by);
        
        if ( doubleSided ) drawArrow(bx, by, ax, ay, size);
    }
    
    
    //private static function textFormat(font:String = "Arial", size:int = 16,color:uint = 0x0, bold:Boolean = false  ):TextFormat
    //{
        //return new TextFormat(font, size, color, bold);
    //}

    /*
     * new abs function, about 25x faster than Math.abs
     */
    private static function abs( value : Number ) : Number {
        return value < 0 ? -value : value;
    }

    /*
     * new ceil function about 75% faster than Math.ceil. 
     */
    private static function ceil( value : Number) : Number {
        return (value % 1) ? int( value ) + 1 : value;
    }
}