Return to Snippet

Revision: 59825
at October 3, 2012 22:45 by adrianparr


Initial Code
package nl.funkymonkey.drawing
{
	import flash.display.*;

	/**
	 * 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
	 *
	 * Usage:
	 * var s : Shape = new Shape( ); // Or Sprite of MovieClip or any other Class that makes use of the Graphics class
	 *
	 * // Draw an ARC
	 * s.graphics.lineStyle( 4, 0xE16606 );
	 * DrawingShapes.drawArc( s.graphics, 50, 50, 10, 150, 60 );
	 *
	 * // Draw an BURST
	 * s.graphics.lineStyle( 3, 0x000000 );
	 * DrawingShapes.drawBurst( s.graphics, 80, 60, 3, 15, 6, 27 );
	 *
	 * // Draw an DASHED-LINE like so - - - -
	 * s.graphics.lineStyle( 1, 0x3C3C39 );
	 * DrawingShapes.drawDash( s.graphics, 120, 60, 150, 80, 2, 2 );
	 *
	 * // Draw an GEAR
	 * s.graphics.lineStyle( 3, 0xE16606 );
	 * DrawingShapes.drawGear( s.graphics, 200, 60, 13, 31, 26, 0, 7, 13 );
	 *
	 * // Draw a POLYGON
	 * s.graphics.lineStyle( 3, 0x0074B9 );
	 * DrawingShapes.drawPolygon( s.graphics, 270, 60, 7, 30, 45 );
	 *
	 * // Draw a STAR
	 * s.graphics.lineStyle( 2, 0x000000 );
	 * DrawingShapes.drawStar( s.graphics, 340, 60, 18, 24, 19, 27 );
	 *
	 * // Draw an WEDGE - good for pie charts or pacmans
	 * s.graphics.lineStyle( 2, 0xFFCC00 );
	 * DrawingShapes.drawWedge( s.graphics, 400, 60, 30, 309, 209 );
	 *
	 * // Draw a LINE
	 * s.graphics.lineStyle( 2, 0x0074B9 );
	 * DrawingShapes.drawLine( s.graphics, 440, 85, 30, DrawingShapes.VERTICAL_LINE );
	 *
	 * addChild( s );
	 */
	public 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.");
		}

		/**
		 * drawDash
		 * Draws a dashed line from the point x1,y1 to the point x2,y2
		 *
		 * @param target Graphics the Graphics Class on which the dashed line will be drawn.
		 * @param x1 Number starting position on x axis - <strong>required</strong>
		 * @param y1 Number starting position on y axis - <strong>required</strong>
		 * @param x2 Number finishing position on x axis - <strong>required</strong>
		 * @param y2 Number finishing position on y axis - <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(target:Graphics, x1:Number, y1:Number, x2:Number, y2:Number, dashLength:Number=5, spaceLength:Number=5):void
		{

			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 target the Graphics Class that the Arc is drawn on.
		 * @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(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number=0, yRadius:Number=0):void
		{

			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 target the Graphics on which the wedge is to be drawn.
		 * @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 at formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawWedge(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number=0, yRadius:Number=0):void
		{

			// 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);
			}
		}

		/**
		 * 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 target the Graphics that the star is drawn on
		 * @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 at formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawStar(target:Graphics, x:Number, y:Number, points:uint, innerRadius:Number, outerRadius:Number, angle:Number=0):void
		{

			// 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 target the Graphics that the polygon is to be drawn on
		 * @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 at formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawPolygon(target:Graphics, x:Number, y:Number, sides:uint, radius:Number, angle:Number=0):void
		{

			// 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 target Graphics where the Burst is to be drawn.
		 * @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 at formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawBurst(target:Graphics, x:Number, y:Number, sides:uint, innerRadius:Number, outerRadius:Number, angle:Number=0):void
		{

			// 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 target Graphics on which the gear is to be drawn.
		 * @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 at formequalsfunction.com) - version 1.4 - 4.7.2002
		 */
		public static function drawGear(target:Graphics, x:Number, y:Number, sides:uint, innerRadius:Number=80, outerRadius:Number=4, angle:Number=0, holeSides:Number=2, holeRadius:Number=0):void
		{

			// 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 target Graphics on which the gear is to be drawn.
		 * @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)
		 *
		 *
		 */
		public static function drawLine(target:Graphics, x:Number, y:Number, length:Number, direction:String=DrawingShapes.HORIZONTAL_LINE):void
		{
			target.moveTo(x, y);
			switch (direction)
			{
				case DrawingShapes.HORIZONTAL_LINE:
					target.lineTo(length, y);
					break;
				case DrawingShapes.VERTICAL_LINE:
					target.moveTo(x, y);
					target.lineTo(x, length);
					break;
			}
		}

		/*
		* 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;
		}
	}
}

Initial URL


Initial Description
Credit for this goes to Adobe, Ric Ewing, Kevin Williams, Aden Forshaw and Sidney de Koning.

Initial Title
AS3 Drawing Shapes (ARC, BURST, DASHED-LINE, GEAR, POLYGON, STAR, WEDGE, LINE)

Initial Tags
line

Initial Language
ActionScript 3