/!\ 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;
}
}