package com.ulfben.utils { import flash.display.*; import flash.geom.*; public class FastShapes { public static const HORIZONTAL_LINE:String = "FastShapes.horizontal"; public static const VERTICAL_LINE:String = "FastShapes.vertical"; public static const TO_RAD:Number = (Math.PI / 180); public static const TO_DEG:Number = (180 / Math.PI); public static const PI2:Number = Math.PI * 2; public static const HALF_PI:Number = Math.PI * 0.5; public static const QUARTER_PI:Number = Math.PI * 0.25; public static const EIGHTH_PI:Number = Math.PI * 0.125; /** * Creates look up tables for sin and cos, to reduce function calls. * index = int(angle * (2048/2PI)) */ public static var sCosLUT:Vector. = new Vector.(0x800, true); public static var sSinLUT:Vector. = new Vector.(0x800, true); public static function initLUTs():void { for (var i:int = 0; i < 0x800; ++i) { sCosLUT[i & 0x7FF] = Math.cos(i * 0.00306796157577128245943617517898); // 0.003067... is 2PI/2048 sSinLUT[i & 0x7FF] = Math.sin(i * 0.00306796157577128245943617517898); } } public static function sinLUT(x:Number):Number{ return sSinLUT[(x * 325.94932345220164765467394738691) & 2047]; // 325.949... is 2048/2PI } public static function cosLUT(x:Number):Number{ return sCosLUT[((x * 325.94932345220164765467394738691) & 2047)]; //325.949... is 2048/2PI } public static function sinCoarse(x:Number):Number { if (x < -3.14159265) { //always wrap input angle to -PI..PI x += 6.28318531; } else if (x > 3.14159265) { x -= 6.28318531; } return (x < 0) ? (1.27323954 * x + .405284735 * x * x) : (1.27323954 * x - 0.405284735 * x * x); } public static function cosCoarse(x:Number):Number { if (x < -3.14159265) { //always wrap input angle to -PI..PI x += 6.28318531; } else if (x > 3.14159265) { x -= 6.28318531; } x += 1.57079632; // //compute cosine: sin(x + PI/2) = cos(x) if (x > 3.14159265) { x -= 6.28318531; } return (x < 0) ? (1.27323954 * x + 0.405284735 * x * x) : (1.27323954 * x - 0.405284735 * x * x); } public static function sinFine(x:Number):Number { if(x < -3.14159265) { x += 6.28318531; } else if(x > 3.14159265) { x -= 6.28318531; } x = (x < 0.0) ? (1.27323954 * x + .405284735 * x * x) : (1.27323954 * x - 0.405284735 * x * x) return (x < 0.0) ? (0.225 * (x *-x - x) + x) : (0.225 * (x * x - x) + x) } public static function cosFine(x:Number): Number { if(x < -3.14159265) { x += 6.28318531; } else if(x > 3.14159265) { x -= 6.28318531; } x += 1.57079632; if(x > 3.14159265) { x -= 6.28318531; } x = (x < 0.0) ? (1.27323954 * x + .405284735 * x * x) : (1.27323954 * x - 0.405284735 * x * x) return (x < 0.0) ? (0.225 * (x *-x - x) + x) : (0.225 * (x * x - x) + x) } public static var testCos:Function = cosFine; // cosLUT, cosCoarse, cosFine public static var testSin:Function = sinFine; // sinLUT, sinCoarse, sinFine public function FastShapes() { throw new ArgumentError("The FastShapes Class cannot be instanicated."); } /*** * drawCircle * Since Flash Player 8 there is an internal drawCircle-method. Before that I * used this. At some point they should be benchmarked against eachother. */ public static function drawCircle(target:Graphics, x:Number, y:Number, r:Number):void { var tan:Number = Math.tan(EIGHTH_PI) * r; var sin:Number = testSin(QUARTER_PI) * r; target.moveTo(x + r, y); target.curveTo(r + x, tan + y, sin + x, sin + y); target.curveTo(tan + x, r + y, x, r + y); target.curveTo((-tan) + x, r + y, (-sin) + x, sin + y); target.curveTo(-r + x, tan + y, -r + x, y); target.curveTo(-r + x, (-tan) + y, (-sin) + x, (-sin) + y); target.curveTo((-tan) + x, -r + y, x, -r + y); target.curveTo(tan + x, -r + y, sin + x, (-sin) + y); target.curveTo(r + x, (-tan) + y, r + x, y); target.moveTo(x, y); } // wobbleFactor in percent. 0.04-0.08 is a good default range. public static function handDrawLine(target:Graphics, startPoint:Point, endPoint:Point, wobbleFactor:Number = 0.05):void { var wobble:Number = Point.distance(startPoint, endPoint) * wobbleFactor; var r1:Number = Math.random(); var r2:Number = Math.random(); var xfactor:Number = Math.random() > 0.5 ? wobble : -wobble; var yfactor:Number = Math.random() > 0.5 ? wobble : -wobble; var control1:Point = new Point((endPoint.x - startPoint.x) * r1 + startPoint.x + xfactor, (endPoint.y - startPoint.y) * r1 + startPoint.y + yfactor); var control2:Point = new Point((endPoint.x - startPoint.x) * r2 + startPoint.x - xfactor, (endPoint.y - startPoint.y) * r2 + startPoint.y - yfactor); target.moveTo(startPoint.x, startPoint.y); target.cubicCurveTo(control1.x, control1.y, control2.x, control2.y, endPoint.x, endPoint.y); } // Draws a dashed line from the point x1,y1 to the point x2,y2 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 invertedUnits:Number = 1 / units; var dashSpaceRatio:Number = dashLength / (dashLength + spaceLength); var dashX:Number = (x * invertedUnits) * dashSpaceRatio; var spaceX:Number = (x * invertedUnits) - dashX; var dashY:Number = (y * invertedUnits) * dashSpaceRatio; var spaceY:Number = (y * invertedUnits) - 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. public static function drawArc(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number = 0, yRadius:Number = 0):void { if (yRadius == 0) { yRadius = radius; } var segAngle:Number, theta:Number, halfTheta:Number, angle:Number, angleMid:Number, segs:Number, ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number; if (FastShapes.abs(arc) > 360) { arc = 360; // no sense in drawing more than is needed :) } // Flash uses 8 segments per circle, to match that, we draw in a maximum of 45 degree segments. segs = FastShapes.ceil(FastShapes.abs(arc) / 45); segAngle = arc / segs; theta = -(segAngle) * FastShapes.TO_RAD; angle = -(startAngle) * FastShapes.TO_RAD; ax = x - testCos(angle) * radius; ay = y - testSin(angle) * yRadius; if (segs > 0) { target.moveTo(x, y); halfTheta = theta * 0.5; var radiusCosHalfTheta:Number = radius / testCos(halfTheta); var yRadiusCosHalfTheta:Number = yRadius / testCos(halfTheta); for (var i:int = 0; i < segs; ++i) { angle += theta; angleMid = angle - halfTheta; bx = ax + testCos(angle) * radius; by = ay + testSin(angle) * yRadius; cx = ax + testCos(angleMid) * radiusCosHalfTheta; cy = ay + testSin(angleMid) * yRadiusCosHalfTheta; target.curveTo(cx, cy, bx, by); } } } // draws pie shaped wedges. Could be employed to draw pie charts. public static function drawWedge(target:Graphics, x:Number, y:Number, radius:Number, arc:Number, startAngle:Number = 0, yRadius:Number = 0):void { if (yRadius == 0) { yRadius = radius; } if (FastShapes.abs(arc) > 360) { arc = 360; } target.moveTo(x, y); var segAngle:Number, theta:Number, halfTheta:Number, angle:Number, angleMid:Number, segs:Number, ax:Number, ay:Number, bx:Number, by:Number, cx:Number, cy:Number; // Flash uses 8 segments per circle, to match that, we draw in a maximum of 45 degree segments. segs = FastShapes.ceil(FastShapes.abs(arc) / 45); segAngle = arc / segs; theta = -(segAngle) * FastShapes.TO_RAD; angle = -(startAngle) * FastShapes.TO_RAD; // draw the curve in segments no larger than 45 degrees. if (segs > 0) { ay = y + testSin(angle) * yRadius; //NOTE: angle is negative! ax = x + testCos(-angle) * radius; //NOTE: -angle == positive value here! target.lineTo(ax, ay); halfTheta = theta * 0.5; var radiusCosHalfTheta:Number = radius / testCos(halfTheta); var yRadiusCosHalfTheta:Number = yRadius / testCos(halfTheta); for (var i:int = 0; i < segs; ++i) { angle += theta; angleMid = angle - halfTheta; bx = x + testCos(angle) * radius; by = y + testSin(angle) * yRadius; cx = x + testCos(angleMid) * radiusCosHalfTheta; cy = y + testSin(angleMid) * yRadiusCosHalfTheta; target.curveTo(cx, cy, bx, by); } target.lineTo(x, y); } } // draws a star shaped polygon. public static function drawStar(target:Graphics, x:Number, y:Number, points:uint, innerRadius:Number, outerRadius:Number, angle:Number = 0):void { if (points <= 2) { throw ArgumentError("FastShapes.drawStar() - parameter 'points' needs to be atleast 3"); return; } var step:Number, startnstep:Number, halfStep:Number, start:Number, n:Number, dx:Number, dy:Number; step = FastShapes.PI2 / points; halfStep = step * 0.5; start = angle * FastShapes.TO_RAD; target.moveTo(x + (testCos(start) * outerRadius), y - (testSin(start) * outerRadius)); for (n = 1; n <= points; ++n) { startnstep = start + (step * n); dx = x + testCos(startnstep - halfStep) * innerRadius; dy = y - testSin(startnstep - halfStep) * innerRadius; target.lineTo(dx, dy); dx = x + testCos(startnstep) * outerRadius; dy = y - testSin(startnstep) * 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. */ public static function drawPolygon(target:Graphics, x:Number, y:Number, sides:uint, radius:Number, angle:Number = 0):void { if (sides <= 2) { throw ArgumentError("FastShapes.drawPolygon() - parameter 'sides' needs to be atleast 3"); return; } var step:Number, startnstep:Number, start:Number, n:Number, dx:Number, dy:Number; step = FastShapes.PI2 / sides; start = angle * FastShapes.TO_RAD; target.moveTo(x + (testCos(start) * radius), y - (testSin(start) * radius)); for (n = 1; n <= sides; ++n) { startnstep = start + (step * n); dx = x + testCos(startnstep) * radius; dy = y - testSin(startnstep) * radius; target.lineTo(dx, dy); } } /** * a method for creating hand drawn polygon shapes. Negative values will draw * the polygon in reverse direction. Negative drawing may be useful * for creating knock-outs in masks. */ public static function handDrawPolygon(target:Graphics, x:Number, y:Number, sides:uint, radius:Number, wobbleFactor:Number = 0.05, angle:Number = 0):void { if (sides <= 2) { throw ArgumentError("FastShapes.handDrawPolygon() - parameter 'sides' needs to be atleast 3"); return; } var step:Number, start:Number, startnstep:Number, n:Number, dx:Number, dy:Number; step = FastShapes.PI2 / sides; start = angle * FastShapes.TO_RAD; var p1:Point = new Point(x + (testCos(start) * radius), y - (testSin(start) * radius)) var p2:Point = new Point(); target.moveTo(p1.x, p1.y); for (n = 1; n <= sides; ++n) { startnstep = start + (step * n); p2.x = x + testCos(startnstep) * radius; //dx p2.y = y - testSin(startnstep) * radius; //dy handDrawLine(target, p1, p2, wobbleFactor); p1.x = p2.x; p1.y = p2.y; } } /** * 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... */ public static function drawBurst(target:Graphics, x:Number, y:Number, sides:uint, innerRadius:Number, outerRadius:Number, angle:Number = 0):void { if (sides <= 2) { throw ArgumentError("FastShapes.drawBurst - parameter 'sides' needs to be atleast 3"); return; } var step:Number, halfStep:Number, qtrStep:Number, qtrStep3:Number, start:Number, n:Number, dx:Number, dy:Number, cx:Number, cy:Number; var startnstep:Number, innercos:Number; step = FastShapes.PI2 / sides; qtrStep = step * 0.25; halfStep = step * 0.5; qtrStep3 = step * 0.75; start = angle * FastShapes.TO_RAD; target.moveTo(x + (testCos(start) * outerRadius), y - (testSin(start) * outerRadius)); for (n = 1; n <= sides; ++n) { startnstep = start + (step * n); innercos = (innerRadius / testCos(qtrStep)); cx = x + testCos(startnstep - qtrStep3) * innercos; cy = y - testSin(startnstep - qtrStep3) * innercos; dx = x + testCos(startnstep - halfStep) * innerRadius; dy = y - testSin(startnstep - halfStep) * innerRadius; target.curveTo(cx, cy, dx, dy); cx = x + testCos(startnstep - qtrStep) * innercos; cy = y - testSin(startnstep - qtrStep) * innercos; dx = x + testCos(startnstep) * outerRadius; dy = y - testSin(startnstep) * outerRadius; target.curveTo(cx, cy, dx, dy); } } // draws a gear shape on the Graphics target 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 { if (sides <= 2) { throw ArgumentError("FastShapes.drawGear() - parameter 'sides' needs to be atleast 3"); return; } var step:Number, startnstep:Number, qtrStep:Number, qtrStep2:Number, qtrStep3:Number, start:Number, n:Number, dx:Number, dy:Number; step = FastShapes.PI2 / sides; qtrStep = step * 0.25; qtrStep2 = (qtrStep * 2); qtrStep3 = (qtrStep * 3); start = angle * FastShapes.TO_RAD; target.moveTo(x + (testCos(start) * outerRadius), y - (testSin(start) * outerRadius)); for (n = 1; n <= sides; ++n) { startnstep = start + (step * n); dx = x + testCos(startnstep - qtrStep3) * innerRadius; dy = y - testSin(startnstep - qtrStep3) * innerRadius; target.lineTo(dx, dy); dx = x + testCos(startnstep - qtrStep2) * innerRadius; dy = y - testSin(startnstep - qtrStep2) * innerRadius; target.lineTo(dx, dy); dx = x + testCos(startnstep - qtrStep) * outerRadius; dy = y - testSin(startnstep - qtrStep) * outerRadius; target.lineTo(dx, dy); dx = x + testCos(startnstep) * outerRadius; dy = y - testSin(startnstep) * outerRadius; target.lineTo(dx, dy); } // This is complete overkill... but I had it done already. :) if (holeSides > 2) { step = FastShapes.PI2 / holeSides; target.moveTo(x + (testCos(start) * holeRadius), y - (testSin(start) * holeRadius)); for (n = 1; n <= holeSides; ++n) { startnstep = start + (step * n); dx = x + testCos(startnstep) * holeRadius; dy = y - testSin(startnstep) * holeRadius; target.lineTo(dx, dy); } } } // draws a line between two points. Make it horizontal or vertical public static function drawLine(target:Graphics, x:Number, y:Number, length:Number, direction:String = FastShapes.HORIZONTAL_LINE):void { switch (direction) { case FastShapes.HORIZONTAL_LINE: target.moveTo(x, y); target.lineTo(length, y); break; case FastShapes.VERTICAL_LINE: target.moveTo(x, y); target.lineTo(x, length); break; } } } }