//////////////////////////////////////////////////////////////////////////////// // // ADOBE SYSTEMS INCORPORATED // Copyright 2008 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file // in accordance with the terms of the license agreement accompanying it. // //////////////////////////////////////////////////////////////////////////////// package spark.primitives { import flash.display.Graphics; import flash.display.GraphicsPath; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import mx.core.mx_internal; import mx.events.PropertyChangeEvent; import mx.graphics.IStroke; import mx.utils.MatrixUtil; import spark.primitives.supportClasses.FilledElement; use namespace mx_internal; /** * The Path class is a filled graphic element that draws a series of path segments. * In vector graphics, a path is a series of points connected by straight or curved line segments. * Together the lines form an image. In Flex, you use the Path class to define a complex vector shape * constructed from a set of line segments. * *
Typically, the first element of a path definition is a Move segment to specify the starting pen * position of the graphic. You then use the Line, CubicBezier and QuadraticBezier segments to * draw the lines of the graphic. When using these classes, you only specify the x and y coordinates * of the end point of the line; the x and y coordinate of the starting point is defined by the current * pen position.
* *After drawing a line segment, the current pen position becomes the x and y coordinates of the end * point of the line. You can use multiple Move segments in the path definition to * reposition the pen.
* *The syntax used by the Path class to define the shape is the same as the SVG path syntax, * which makes it easy to convert SVG paths to Flex paths.
* * @includeExample examples/ArrowExample.mxml * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public class Path extends FilledElement { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function Path() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * Dirty flag to indicate when path data has changed. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ private var graphicsPathChanged:Boolean = true; /** * Private data structure to hold the parsed * path segment information * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ private var segments:PathSegmentsCollection; /** * A GraphicsPath object that contains the drawing * commands to draw this Path. * * The data commands expressed in a Path'sdata
* property are translated into drawing commands and
* coordinate parameters for those commands, and then
* drawn to screen.
*/
mx_internal var graphicsPath:GraphicsPath = new GraphicsPath(new Vector.The value is a space-delimited string describing each path segment. Each * segment entry has a single character which denotes the segment type and * two or more segment parameters.
* *If the segment command is upper-case, the parameters are absolute values. * If the segment command is lower-case, the parameters are relative values.
* *The following table shows the syntax for the segments: * * *
| Segment Type | *Command | *Parameters | *Example | *
|---|---|---|---|
| Move | *M/m | *x y | *M 10 20 - Move line to 10, 20. |
*
| Line | *L/l | *x y | *L 50 30 - Line to 50, 30. |
*
| Horizontal line | *H/h | *x | *H 40 = Horizontal line to 40. |
*
| Vertical line | *V/v | *y | *V 100 - Vertical line to 100. |
*
| QuadraticBezier | *Q/q | *controlX controlY x y | *Q 110 45 90 30 - Curve to 90, 30 with the control point at 110, 45. |
*
| CubicBezier | *C/c | *control1X control1Y control2X control2Y x y | *C 45 50 20 30 10 20 - Curve to 10, 20 with the first control point at 45, 50 and the second control point at 20, 30. |
*
| Close path | *Z/z | *n/a | *Closes off the path. | *
GraphicsPathWinding.EVEN_ODD or GraphicsPathWinding.NON_ZERO.
*
* @default evenOdd
*
* @see flash.display.GraphicsPathWinding
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function set winding(value:String):void
{
if (_winding != value)
{
_winding = value;
graphicsPathChanged = true;
invalidateDisplayList();
}
}
/**
* @private
*/
public function get winding():String
{
return _winding;
}
//----------------------------------
// bounds
//----------------------------------
private function getBounds():Rectangle
{
return segments ? segments.getBounds() : new Rectangle();
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override protected function measure():void
{
var bounds:Rectangle = getBounds();
measuredWidth = bounds.width;
measuredHeight = bounds.height;
measuredX = bounds.left;
measuredY = bounds.top;
}
/**
* @private
* Storage for the cached bounding box for particular transformation and size.
* We are also caching the bounding box original x & y, so that we can reuse it
* when bounds are requested with same size and transform that only differs by offsets.
*/
private var _boundingBoxCached:Rectangle;
private var _boundingBoxMatrixCached:Matrix;
private var _boundingBoxWidthParamCached:Number;
private var _boundingBoxHeightParamCached:Number;
private var _boundingBoxX:Number;
private var _boundingBoxY:Number;
/**
* @private
* Returns the bounding box for the path including stroke, if the path is resized
* to the specified size and transformed with "m".
* Pass null for "m" to specify identity matrix.
*
* Calling this method multiple times with the same parameters, or differences
* only in the matrix offset is fast, as it returns the cached bounding box.
*
* Don't modify directly the return value!
*/
private function getBoundingBoxWithStroke(width:Number, height:Number, m:Matrix):Rectangle
{
if (_boundingBoxCached &&
_boundingBoxWidthParamCached == width &&
_boundingBoxHeightParamCached == height)
{
// Compare matrices:
if (!m && !_boundingBoxMatrixCached)
{
_boundingBoxCached.x = _boundingBoxX;
_boundingBoxCached.y = _boundingBoxY;
return _boundingBoxCached;
}
else if (m && _boundingBoxMatrixCached &&
m.a == _boundingBoxMatrixCached.a &&
m.b == _boundingBoxMatrixCached.b &&
m.c == _boundingBoxMatrixCached.c &&
m.d == _boundingBoxMatrixCached.d)
{
_boundingBoxCached.x = _boundingBoxX + m.tx;
_boundingBoxCached.y = _boundingBoxY + m.ty;
return _boundingBoxCached;
}
}
// Setup the matrix, ignore tx & ty, we'll account for it later
if (m)
{
_boundingBoxMatrixCached = m.clone();
_boundingBoxMatrixCached.tx = 0;
_boundingBoxMatrixCached.ty = 0;
}
else
_boundingBoxMatrixCached = null;
// Remember width and height
_boundingBoxWidthParamCached = width;
_boundingBoxHeightParamCached = height;
_boundingBoxCached = computeBoundsWithStroke(_boundingBoxWidthParamCached,
_boundingBoxHeightParamCached,
m);
// Remember the original x & y:
_boundingBoxX = _boundingBoxCached.x - (m ? m.tx : 0);
_boundingBoxY = _boundingBoxCached.y - (m ? m.ty : 0);
return _boundingBoxCached; // No need to return clone, as this is for internal use only
}
/**
* @private
* Static storage for intermediate calculations while calculating miter-limit bounds.
*/
static private var tangent:Point = new Point();
/**
* @private
* Returns true when we have a valid tangent for curSegment. Pass prevSegment
* to know what the starting point of curSegment is.
*/
private function tangentIsValid(prevSegment:PathSegment, curSegment:PathSegment,
sx:Number, sy:Number, m:Matrix):Boolean
{
// TODO (egeorgie): optimize, we don't need to compute the tangent,
// but just make sure the segment is not collapsed into a single point?
// Check the start tangent only. If it's valid,
// then there is a valid end tangent as well.
curSegment.getTangent(prevSegment, true, sx, sy, m, tangent);
return (tangent.x != 0 || tangent.y != 0);
}
/**
* @private
* @return Returns the axis aligned bounding box of the path when
* resized to (width, height) and then transformed by matrix m.
*/
mx_internal function computeBoundsWithStroke(width:Number,
height:Number,
m:Matrix):Rectangle
{
var naturalBounds:Rectangle = getBounds();
var sx:Number = naturalBounds.width == 0 ? 1 : width / naturalBounds.width;
var sy:Number = naturalBounds.height == 0 ? 1 : height / naturalBounds.height;
// First, figure out the bounding box without stroke
var pathBBox:Rectangle;
// Special case, if there's no transformation or only offset,
// then the non-stroked path bounds for the give size can be
// scaled from the pre-transform natural bounds:
if (!m || MatrixUtil.isDeltaIdentity(m) || !this.segments)
{
pathBBox = new Rectangle(naturalBounds.x * sx,
naturalBounds.y * sy,
naturalBounds.width * sx,
naturalBounds.height * sy);
if (m)
pathBBox.offset(m.tx, m.ty);
}
else
{
pathBBox = this.segments.getBoundingBox(width, height, m);
}
// Do we have stroke?
var strokeSettings:IStroke = this.stroke;
if (!strokeSettings || !this.segments)
return pathBBox;
// Always add half the stroke weight, even for miter limit paths,
// as a point on a curve and not necessarily a joint tip could be
// an extreme that pushes the bounds.
var strokeExtents:Rectangle = getStrokeExtents();
pathBBox.inflate(strokeExtents.right, strokeExtents.bottom);
var seg:Vector.segments
* array and draws each path egment based on its control points.
*
* Segments are drawn from the x and y position of the path.
* Additionally, segments are drawn by taking into account the scale
* applied to the path.
*
* @param tx A Number representing the x position of where this
* path segment should be drawn
*
* @param ty A Number representing the y position of where this
* path segment should be drawn
*
* @param sx A Number representing the scaleX at which to draw
* this path segment
*
* @param sy A Number representing the scaleY at which to draw this
* path segment
*/
public function generateGraphicsPath(graphicsPath:GraphicsPath,
tx:Number,
ty:Number,
sx:Number,
sy:Number):void
{
graphicsPath.commands = null;
graphicsPath.data = null;
// Always start by moving to drawX, drawY. Otherwise
// the path will begin at the previous pen location
// if it does not start with a MoveSegment.
graphicsPath.moveTo(tx, ty);
var curSegment:PathSegment;
var prevSegment:PathSegment;
var count:int = _segments.length;
for (var i:int = 0; i < count; i++)
{
prevSegment = curSegment;
curSegment = _segments[i];
curSegment.draw(graphicsPath, tx, ty, sx, sy, prevSegment);
}
}
//--------------------------------------------------------------------------
//
// Private methods
//
//--------------------------------------------------------------------------
private var _charPos:int = 0;
private var _dataLength:int = 0;
private function skipWhiteSpace(data:String):void
{
while (_charPos < _dataLength)
{
var c:Number = data.charCodeAt(_charPos);
if (c != 0x20 && // Space
c != 0x2C && // Comma
c != 0xD && // Carriage return
c != 0x9 && // Tab
c != 0xA) // New line
{
break;
}
_charPos++;
}
}
private function getNumber(useRelative:Boolean, offset:Number, value:String):Number
{
// Parse the string and find the first occurrance of the following RexExp
// numberRegExp:RegExp = /[+-]?\d*\.?\d+([Ee][+-]?\d+)?/g;
skipWhiteSpace(value); // updates _charPos
if (_charPos >= _dataLength)
return NaN;
// Remember the start of the number
var numberStart:int = _charPos;
var hasSignCharacter:Boolean = false;
var hasDigits:Boolean = false;
// The number could start with '+' or '-' (the "[+-]?" part of the RegExp)
var c:Number = value.charCodeAt(_charPos);
if (c == 0x2B || c == 0x2D) // '+' or '-'
{
hasSignCharacter = true;
_charPos++;
}
// The index of the '.' if any
var dotIndex:int = -1;
// First sequence of digits and optional dot in between (the "\d*\.?\d+" part of the RegExp)
while (_charPos < _dataLength)
{
c = value.charCodeAt(_charPos);
if (c >= 0x30 && c < 0x3A) // A digit
{
hasDigits = true;
}
else if (c == 0x2E && dotIndex == -1) // '.'
{
dotIndex = _charPos;
}
else
break;
_charPos++;
}
// Now check whether we had at least one digit.
if (!hasDigits)
{
// Go to the end of the data
_charPos = _dataLength;
return NaN;
}
// 1. Was the last character a '.'? If so, rewind one character back.
if (c == 0x2E)
_charPos--;
// So far we have a valid number, remember its end character index
var numberEnd:int = _charPos;
// Check to see if we have scientific notation (the "([Ee][+-]?\d+)?" part of the RegExp)
if (c == 0x45 || c == 0x65)
{
_charPos++;
// Check for '+' or '-'
if (_charPos < _dataLength)
{
c = value.charCodeAt(_charPos);
if (c == 0x2B || c == 0x2D)
_charPos++;
}
// Find all the digits
var digitStart:int = _charPos;
while (_charPos < _dataLength)
{
c = value.charCodeAt(_charPos);
// Not a digit?
if (!(c >= 0x30 && c < 0x3A))
{
break;
}
_charPos++;
}
// Do we have at least one digit?
if (digitStart < _charPos)
numberEnd = _charPos; // Scientific notation, update the end index of the number.
else
_charPos = numberEnd; // No scientific notation, rewind back to the end index of the number.
}
// Use parseFloat to get the actual number.
// TODO (egeorgie): we could build the number while matching the RegExp which will save the substr and parseFloat
var subString:String = value.substr(numberStart, numberEnd - numberStart);
var result:Number = parseFloat(subString);
if (isNaN(result))
{
// Go to the end of the data
_charPos = _dataLength;
return NaN;
}
_charPos = numberEnd;
return useRelative ? result + offset : result;
}
}
//--------------------------------------------------------------------------
//
// Internal Helper Class - PathSegment
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import mx.events.PropertyChangeEvent;
/**
* The PathSegment class is the base class for a segment of a path.
* This class is not created directly. It is the base class for
* MoveSegment, LineSegment, CubicBezierSegment and QuadraticBezierSegment.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
class PathSegment extends Object
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param _x The x position of the pen in the current coordinate system.
*
* @param _y The y position of the pen in the current coordinate system.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function PathSegment(_x:Number = 0, _y:Number = 0)
{
super();
x = _x;
y = _y;
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// x
//----------------------------------
/**
* The ending x position for this segment.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var x:Number = 0;
//----------------------------------
// y
//----------------------------------
/**
* The ending y position for this segment.
*
* @default 0
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public var y:Number = 0;
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Draws this path segment. You can determine the current pen position by
* reading the x and y values of the previous segment.
*
* @param g The graphics context to draw into.
* @param prev The previous segment drawn, or null if this is the first segment.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
// Override to draw your segment
}
/**
* @param prev The previous segment drawn, or null if this is the first segment.
* @param sx Pre-transform scale factor for x coordinates.
* @param sy Pre-transform scale factor for y coordinates.
* @param m Transformation matrix.
* @param rect If non-null, rect is expanded to include the bounding box of the segment.
* @return Returns the union of rect and the axis aligned bounding box of the post-transformed
* path segment.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number, m:Matrix, rect:Rectangle):Rectangle
{
// Override to calculate your segment's bounding box.
return rect;
}
/**
* Returns the tangent for the segment.
* @param prev The previous segment drawn, or null if this is the first segment.
* @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
* @param sx Pre-transform scale factor for x coordinates.
* @param sy Pre-transform scale factor for y coordinates.
* @param m Transformation matrix.
* @param result The tangent is returned as vector (x, y) in result.
*/
public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
{
result.x = 0;
result.y = 0;
}
}
//--------------------------------------------------------------------------
//
// Internal Helper Class - LineSegment
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.utils.MatrixUtil;
/**
* The LineSegment draws a line from the current pen position to the coordinate located at x, y.
*
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
class LineSegment extends PathSegment
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param x The current location of the pen along the x axis. The draw() method uses
* this value to determine where to draw to.
*
* @param y The current location of the pen along the y axis. The draw() method uses
* this value to determine where to draw to.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function LineSegment(x:Number = 0, y:Number = 0)
{
super(x, y);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
graphicsPath.lineTo(dx + x*sx, dy + y*sy);
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number, m:Matrix, rect:Rectangle):Rectangle
{
pt = MatrixUtil.transformPoint(x * sx, y * sy, m);
var x1:Number = pt.x;
var y1:Number = pt.y;
// If the previous segment actually draws, then only add the end point to the rectangle,
// as the start point would have been added by the previous segment:
if (prev != null && !(prev is MoveSegment))
return MatrixUtil.rectUnion(x1, y1, x1, y1, rect);
var pt:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m);
var x2:Number = pt.x;
var y2:Number = pt.y;
return MatrixUtil.rectUnion(Math.min(x1, x2), Math.min(y1, y2),
Math.max(x1, x2), Math.max(y1, y2), rect);
}
/**
* Returns the tangent for the segment.
* @param prev The previous segment drawn, or null if this is the first segment.
* @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
* @param sx Pre-transform scale factor for x coordinates.
* @param sy Pre-transform scale factor for y coordinates.
* @param m Transformation matrix.
* @param result The tangent is returned as vector (x, y) in result.
*/
override public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
{
var pt0:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m).clone();
var pt1:Point = MatrixUtil.transformPoint(x * sx, y * sy, m);
result.x = pt1.x - pt0.x;
result.y = pt1.y - pt0.y;
}
}
//--------------------------------------------------------------------------
//
// Internal Helper Class - MoveSegment
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;
/**
* The MoveSegment moves the pen to the x,y position. This class calls the Graphics.moveTo() method
* from the draw() method.
*
*
* @see flash.display.Graphics
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
class MoveSegment extends PathSegment
{
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param x The target x-axis location in 2-d coordinate space.
*
* @param y The target y-axis location in 2-d coordinate space.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
public function MoveSegment(x:Number = 0, y:Number = 0)
{
super(x, y);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @inheritDoc
*
* The MoveSegment class moves the pen to the position specified by the
* x and y properties.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
graphicsPath.moveTo(dx+x*sx, dy+y*sy);
}
}
//--------------------------------------------------------------------------
//
// Internal Helper Class - CubicBezierSegment
//
//--------------------------------------------------------------------------
import flash.display.GraphicsPath;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import mx.utils.MatrixUtil;
/**
* The CubicBezierSegment draws a cubic bezier curve from the current pen position
* to x, y. The control1X and control1Y properties specify the first control point;
* the control2X and control2Y properties specify the second control point.
*
* Cubic bezier curves are not natively supported in Flash Player. This class does * an approximation based on the fixed midpoint algorithm and uses 4 quadratic curves * to simulate a cubic curve.
* *For details on the fixed midpoint algorithm, see:
* http://timotheegroleau.com/Flash/articles/cubic_bezier_in_flash.htm
For a CubicBezierSegment, there are two control points, each with x and y coordinates. Control points * are points that define the direction and amount of curves of a Bezier curve. * The curved line never reaches the control points; however, the line curves as though being drawn * toward the control point.
* * @param _control1X The x-axis location in 2-d coordinate space of the first control point. * * @param _control1Y The y-axis location of the first control point. * * @param _control2X The x-axis location of the second control point. * * @param _control2Y The y-axis location of the second control point. * * @param x The x-axis location of the starting point of the curve. * * @param y The y-axis location of the starting point of the curve. * * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function CubicBezierSegment( _control1X:Number = 0, _control1Y:Number = 0, _control2X:Number = 0, _control2Y:Number = 0, x:Number = 0, y:Number = 0) { super(x, y); control1X = _control1X; control1Y = _control1Y; control2X = _control2X; control2Y = _control2Y; } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- private var _qPts:QuadraticPoints; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // control1X //---------------------------------- /** * The first control point x position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var control1X:Number = 0; //---------------------------------- // control1Y //---------------------------------- /** * The first control point y position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var control1Y:Number = 0; //---------------------------------- // control2X //---------------------------------- /** * The second control point x position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var control2X:Number = 0; //---------------------------------- // control2Y //---------------------------------- /** * The second control point y position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var control2Y:Number = 0; //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Draws the segment. * * @param g The graphics context where the segment is drawn. * * @param prev The previous location of the pen. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ override public function draw(graphicsPath:GraphicsPath, dx:Number, dy:Number, sx:Number, sy:Number, prev:PathSegment):void { var qPts:QuadraticPoints = getQuadraticPoints(prev); graphicsPath.curveTo(dx + qPts.control1.x*sx, dy+qPts.control1.y*sy, dx+qPts.anchor1.x*sx, dy+qPts.anchor1.y*sy); graphicsPath.curveTo(dx + qPts.control2.x*sx, dy+qPts.control2.y*sy, dx+qPts.anchor2.x*sx, dy+qPts.anchor2.y*sy); graphicsPath.curveTo(dx + qPts.control3.x*sx, dy+qPts.control3.y*sy, dx+qPts.anchor3.x*sx, dy+qPts.anchor3.y*sy); graphicsPath.curveTo(dx + qPts.control4.x*sx, dy+qPts.control4.y*sy, dx+qPts.anchor4.x*sx, dy+qPts.anchor4.y*sy); } /** * @inheritDoc * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ override public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number, m:Matrix, rect:Rectangle):Rectangle { var qPts:QuadraticPoints = getQuadraticPoints(prev); rect = MatrixUtil.getQBezierSegmentBBox(prev ? prev.x : 0, prev ? prev.y : 0, qPts.control1.x, qPts.control1.y, qPts.anchor1.x, qPts.anchor1.y, sx, sy, m, rect); rect = MatrixUtil.getQBezierSegmentBBox(qPts.anchor1.x, qPts.anchor1.y, qPts.control2.x, qPts.control2.y, qPts.anchor2.x, qPts.anchor2.y, sx, sy, m, rect); rect = MatrixUtil.getQBezierSegmentBBox(qPts.anchor2.x, qPts.anchor2.y, qPts.control3.x, qPts.control3.y, qPts.anchor3.x, qPts.anchor3.y, sx, sy, m, rect); rect = MatrixUtil.getQBezierSegmentBBox(qPts.anchor3.x, qPts.anchor3.y, qPts.control4.x, qPts.control4.y, qPts.anchor4.x, qPts.anchor4.y, sx, sy, m, rect); return rect; } /** * Returns the tangent for the segment. * @param prev The previous segment drawn, or null if this is the first segment. * @param start If true, returns the tangent to the start point, otherwise the tangend to the end point. * @param sx Pre-transform scale factor for x coordinates. * @param sy Pre-transform scale factor for y coordinates. * @param m Transformation matrix. * @param result The tangent is returned as vector (x, y) in result. */ override public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void { // Get the approximation (we want the tangents to be the same as the ones we use to draw var qPts:QuadraticPoints = getQuadraticPoints(prev); var pt0:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m).clone(); var pt1:Point = MatrixUtil.transformPoint(qPts.control1.x * sx, qPts.control1.y * sy, m).clone(); var pt2:Point = MatrixUtil.transformPoint(qPts.anchor1.x * sx, qPts.anchor1.y * sy, m).clone(); var pt3:Point = MatrixUtil.transformPoint(qPts.control2.x * sx, qPts.control2.y * sy, m).clone(); var pt4:Point = MatrixUtil.transformPoint(qPts.anchor2.x * sx, qPts.anchor2.y * sy, m).clone(); var pt5:Point = MatrixUtil.transformPoint(qPts.control3.x * sx, qPts.control3.y * sy, m).clone(); var pt6:Point = MatrixUtil.transformPoint(qPts.anchor3.x * sx, qPts.anchor3.y * sy, m).clone(); var pt7:Point = MatrixUtil.transformPoint(qPts.control4.x * sx, qPts.control4.y * sy, m).clone(); var pt8:Point = MatrixUtil.transformPoint(qPts.anchor4.x * sx, qPts.anchor4.y * sy, m).clone(); if (start) { QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt1.x, pt1.y, pt2.x, pt2.y, start, result); // If there is no tangent if (result.x == 0 && result.y == 0) { // Try 3 & 4 QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt3.x, pt3.y, pt4.x, pt4.y, start, result); // If there is no tangent if (result.x == 0 && result.y == 0) { // Try 5 & 6 QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt5.x, pt5.y, pt6.x, pt6.y, start, result); // If there is no tangent if (result.x == 0 && result.y == 0) // Try 7 & 8 QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt7.x, pt7.y, pt8.x, pt8.y, start, result); } } } else { QuadraticBezierSegment.getQTangent(pt6.x, pt6.y, pt7.x, pt7.y, pt8.x, pt8.y, start, result); // If there is no tangent if (result.x == 0 && result.y == 0) { // Try 4 & 5 QuadraticBezierSegment.getQTangent(pt4.x, pt4.y, pt5.x, pt5.y, pt8.x, pt8.y, start, result); // If there is no tangent if (result.x == 0 && result.y == 0) { // Try 2 & 3 QuadraticBezierSegment.getQTangent(pt2.x, pt2.y, pt3.x, pt3.y, pt8.x, pt8.y, start, result); // If there is no tangent if (result.x == 0 && result.y == 0) // Try 0 & 1 QuadraticBezierSegment.getQTangent(pt0.x, pt0.y, pt1.x, pt1.y, pt8.x, pt8.y, start, result); } } } } /** * @private * Tim Groleau's method to approximate a cubic bezier with 4 quadratic beziers, * with endpoint and control point of each saved. */ private function getQuadraticPoints(prev:PathSegment):QuadraticPoints { if (_qPts) return _qPts; var p1:Point = new Point(prev ? prev.x : 0, prev ? prev.y : 0); var p2:Point = new Point(x, y); var c1:Point = new Point(control1X, control1Y); var c2:Point = new Point(control2X, control2Y); // calculates the useful base points var PA:Point = Point.interpolate(c1, p1, 3/4); var PB:Point = Point.interpolate(c2, p2, 3/4); // get 1/16 of the [p2, p1] segment var dx:Number = (p2.x - p1.x) / 16; var dy:Number = (p2.y - p1.y) / 16; _qPts = new QuadraticPoints; // calculates control point 1 _qPts.control1 = Point.interpolate(c1, p1, 3/8); // calculates control point 2 _qPts.control2 = Point.interpolate(PB, PA, 3/8); _qPts.control2.x -= dx; _qPts.control2.y -= dy; // calculates control point 3 _qPts.control3 = Point.interpolate(PA, PB, 3/8); _qPts.control3.x += dx; _qPts.control3.y += dy; // calculates control point 4 _qPts.control4 = Point.interpolate(c2, p2, 3/8); // calculates the 3 anchor points _qPts.anchor1 = Point.interpolate(_qPts.control1, _qPts.control2, 0.5); _qPts.anchor2 = Point.interpolate(PA, PB, 0.5); _qPts.anchor3 = Point.interpolate(_qPts.control3, _qPts.control4, 0.5); // the 4th anchor point is p2 _qPts.anchor4 = p2; return _qPts; } } //-------------------------------------------------------------------------- // // Internal Helper Class - QuadraticPoints // //-------------------------------------------------------------------------- import flash.geom.Point; /** * Utility class to store the computed quadratic points. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ class QuadraticPoints { public var control1:Point; public var anchor1:Point; public var control2:Point; public var anchor2:Point; public var control3:Point; public var anchor3:Point; public var control4:Point; public var anchor4:Point; /** * Constructor. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function QuadraticPoints() { super(); } } //-------------------------------------------------------------------------- // // Internal Helper Class - QuadraticBezierSegment // //-------------------------------------------------------------------------- import flash.display.GraphicsPath; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import mx.utils.MatrixUtil; /** * The QuadraticBezierSegment draws a quadratic curve from the current pen position * to x, y. * * Quadratic bezier is the native curve type * in Flash Player. * * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ class QuadraticBezierSegment extends PathSegment { //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * *For a QuadraticBezierSegment, there is one control point. A control point * is a point that defines the direction and amount of a Bezier curve. * The curved line never reaches the control point; however, the line curves as though being drawn * toward the control point.
* * @param _control1X The x-axis location in 2-d coordinate space of the control point. * * @param _control1Y The y-axis location in 2-d coordinate space of the control point. * * @param x The x-axis location of the starting point of the curve. * * @param y The y-axis location of the starting point of the curve. * * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function QuadraticBezierSegment( _control1X:Number = 0, _control1Y:Number = 0, x:Number = 0, y:Number = 0) { super(x, y); control1X = _control1X; control1Y = _control1Y; } //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // control1X //---------------------------------- /** * The control point's x position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var control1X:Number = 0; //---------------------------------- // control1Y //---------------------------------- /** * The control point's y position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public var control1Y:Number = 0; //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Draws the segment using the control point location and the x and y coordinates. * This method calls theGraphics.curveTo() method.
*
* @see flash.display.Graphics
*
* @param g The graphics context where the segment is drawn.
*
* @param prev The previous location of the pen.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function draw(graphicsPath:GraphicsPath, dx:Number,dy:Number,sx:Number,sy:Number,prev:PathSegment):void
{
graphicsPath.curveTo(dx+control1X*sx, dy+control1Y*sy, dx+x*sx, dy+y*sy);
}
static public function getQTangent(x0:Number, y0:Number,
x1:Number, y1:Number,
x2:Number, y2:Number,
start:Boolean,
result:Point):void
{
if (start)
{
if (x0 == x1 && y0 == y1)
{
result.x = x2 - x0;
result.y = y2 - y0;
}
else
{
result.x = x1 - x0;
result.y = y1 - y0;
}
}
else
{
if (x2 == x1 && y2 == y1)
{
result.x = x2 - x0;
result.y = y2 - y0;
}
else
{
result.x = x2 - x1;
result.y = y2 - y1;
}
}
}
/**
* Returns the tangent for the segment.
* @param prev The previous segment drawn, or null if this is the first segment.
* @param start If true, returns the tangent to the start point, otherwise the tangend to the end point.
* @param sx Pre-transform scale factor for x coordinates.
* @param sy Pre-transform scale factor for y coordinates.
* @param m Transformation matrix.
* @param result The tangent is returned as vector (x, y) in result.
*/
override public function getTangent(prev:PathSegment, start:Boolean, sx:Number, sy:Number, m:Matrix, result:Point):void
{
var pt0:Point = MatrixUtil.transformPoint(prev ? prev.x * sx : 0, prev ? prev.y * sy : 0, m).clone();
var pt1:Point = MatrixUtil.transformPoint(control1X * sx, control1Y * sy, m).clone();;
var pt2:Point = MatrixUtil.transformPoint(x * sx, y * sy, m).clone();
getQTangent(pt0.x, pt0.y, pt1.x, pt1.y, pt2.x, pt2.y, start, result);
}
/**
* @inheritDoc
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
override public function getBoundingBox(prev:PathSegment, sx:Number, sy:Number,
m:Matrix, rect:Rectangle):Rectangle
{
return MatrixUtil.getQBezierSegmentBBox(prev ? prev.x : 0, prev ? prev.y : 0,
control1X, control1Y, x, y, sx, sy, m, rect);
}
}