//////////////////////////////////////////////////////////////////////////////// // // ADOBE SYSTEMS INCORPORATED // Copyright 2003-2006 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.pathSegments { 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 //---------------------------------- private var _control1X:Number = 0; [Bindable("propertyChange")] [Inspectable(category="General")] /** * The first control point x position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get control1X():Number { return _control1X; } /** * @private */ public function set control1X(value:Number):void { var oldValue:Number = _control1X; if (value != oldValue) { _control1X = value; dispatchSegmentChangedEvent("control1X", oldValue, value); } } //---------------------------------- // control1Y //---------------------------------- private var _control1Y:Number = 0; [Bindable("propertyChange")] [Inspectable(category="General")] /** * The first control point y position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get control1Y():Number { return _control1Y; } /** * @private */ public function set control1Y(value:Number):void { var oldValue:Number = _control1Y; if (value != oldValue) { _control1Y = value; dispatchSegmentChangedEvent("control1Y", oldValue, value); } } //---------------------------------- // control2X //---------------------------------- private var _control2X:Number = 0; [Bindable("propertyChange")] [Inspectable(category="General")] /** * The second control point x position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get control2X():Number { return _control2X; } /** * @private */ public function set control2X(value:Number):void { var oldValue:Number = _control2X; if (value != oldValue) { _control2X = value; dispatchSegmentChangedEvent("control2X", oldValue, value); } } //---------------------------------- // control2Y //---------------------------------- private var _control2Y:Number = 0; [Bindable("propertyChange")] [Inspectable(category="General")] /** * The second control point y position. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get control2Y():Number { return _control2Y; } /** * @private */ public function set control2Y(value:Number):void { var oldValue:Number = _control2Y; if (value != oldValue) { _control2Y = value; dispatchSegmentChangedEvent("control2Y", oldValue, value); } } //-------------------------------------------------------------------------- // // 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; } /** * @private */ override protected function notifySegmentChanged():void { super.notifySegmentChanged(); // Need to recalculate our quadratic points _qPts = null; } /** * @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; } } } 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(); } }