Beautifl - Flash Gallery

Thumbnail : Bezier Curve Paint
Bezier Curve Paint
clockmaker 2011-07-29 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

package
{
    import com.bit101.components.CheckBox;
    import com.bit101.components.PushButton;
    import com.bit101.components.RadioButton;

    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;

    [SWF(width = "465", height = "465", frameRate = "60")]
    /**
     * ベジェ曲線で、ライン上にビットマップ塗りを適用するサンプルです。
     * @author yasu
     * @since 2010/5/10
     */
    public class BezierPaint extends Sprite
    {

        //----------------------------------------------------------
        //
        //   Constructor 
        //
        //----------------------------------------------------------

        /**
         * 新しいインスタンスを作成します。
         */
        public function BezierPaint()
        {
            bezierLineMaker = new BezierLineMaker();
            addChild(bezierLineMaker);

            btnToolAdd = new RadioButton(this, 10, 20, "Add Point ", true, toolType_clickHanekler);
            btnToolRemove = new RadioButton(this, 10, 40, "Remove Point", false, toolType_clickHanekler);
            btnToolChange = new RadioButton(this, 10, 60, "Change Point", false, toolType_clickHanekler);
            btnToolArrow = new RadioButton(this, 10, 80, "Select Tool", false, toolType_clickHanekler);

            new PushButton(this, 10, 110, "Reset", btnReset_clickHandler);

            new CheckBox(this, 10, 140, "Show UV", checkBox_changeHandler);

            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
            loader.load(new URLRequest("http://assets.wonderfl.net/images/related_images/1/1f/1f60/1f60f1efaba1276e534cbb1023fa4f62891215e5"), new LoaderContext(true));
        }

        //----------------------------------------------------------
        //
        //   Property 
        //
        //----------------------------------------------------------

        private var bezierLineMaker:BezierLineMaker;
        private var btnToolAdd:RadioButton;
        private var btnToolArrow:RadioButton;
        private var btnToolChange:RadioButton;
        private var btnToolRemove:RadioButton;

        //----------------------------------------------------------
        //
        //   Function 
        //
        //----------------------------------------------------------

        private function completeHandler(event:Event):void
        {
            bezierLineMaker.bitmapData = event.target.content.bitmapData;
        }

        private function toolType_clickHanekler(event:MouseEvent):void
        {
            switch (event.currentTarget)
            {
                case btnToolAdd:
                {
                    bezierLineMaker.toolType = ToolType.PLUS;
                    break;
                }
                case btnToolRemove:
                {
                    bezierLineMaker.toolType = ToolType.DELETE;
                    break;
                }
                case btnToolChange:
                {
                    bezierLineMaker.toolType = ToolType.CHANGE;
                    break;
                }
                case btnToolArrow:
                {
                    bezierLineMaker.toolType = ToolType.ARROW;
                    break;
                }
            }
        }

        private function btnReset_clickHandler(event:MouseEvent):void
        {
            btnToolAdd.selected = true;
            bezierLineMaker.toolType = ToolType.PLUS;

            bezierLineMaker.reset();
        }

        private function checkBox_changeHandler(event:Event):void
        {
            var selected:Boolean = event.currentTarget.selected;

            bezierLineMaker.showUV = selected;
        }
    }
}

import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.ContextMenuEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;

internal class AbstractBezierView extends Sprite
{

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function AbstractBezierView()
    {
        _controls = new Vector.<BezierPoint>();
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    //--------------------------------------
    // bitmapData 
    //--------------------------------------

    protected var _bitmapData:BitmapData = new BitmapData(1, 1);

    public function get bitmapData():BitmapData
    {
        return _bitmapData;
    }

    public function set bitmapData(value:BitmapData):void
    {
        _bitmapData = value;

        _updateAll();
    }

    public function set bitmapDataGoal(value:BitmapData):void
    {
        _bmdGoal = value;

        _updateAll();
    }
    
    //--------------------------------------
    // showUV 
    //--------------------------------------
    
    private var _showUV:Boolean = false;
    
    public function get showUV():Boolean
    {
        return _showUV;
    }
    
    public function set showUV(value:Boolean):void
    {
        _showUV = value;
        
        _updateAll();
    }

    //--------------------------------------
    // lineWidth 
    //--------------------------------------

    protected var _lineWidth:Number = 20;

    public function get lineWidth():Number
    {
        return _lineWidth;
    }

    public function set lineWidth(value:Number):void
    {
        _lineWidth = value;

        _updateAll();
    }

    //--------------------------------------
    // vec 
    //--------------------------------------

    protected var _vec:Vector.<Point>;

    public function get vec():Vector.<Point>
    {
        return _vec;
    }

    protected var _controls:Vector.<BezierPoint>;

    private var _bmdGoal:BitmapData;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    protected function _updateAll():void
    {
        _calcPoints();
        if (_vec.length > 1)
        {
            graphics.clear();
            LineUtil.drawBitmapLine(graphics, _vec, _bitmapData, _lineWidth, _showUV);
        }
        else
            graphics.clear();

    }

    protected function _calcPoints():void
    {
        var i:int;
        const segment:Number = 0.005;

        var vec:Vector.<Point> = new Vector.<Point>();

        // bezier loop
        for (i = 0; i < _controls.length; i++)
        {
            // ベジェを閉じる
            var next:int = i + 1;
            if (next >= _controls.length)
                next = 0;

            var bezier:BezierSegment = new BezierSegment(
                _controls[i].toPoint(),
                _controls[i].controlPointPost,
                _controls[next].controlPointPre,
                _controls[next].toPoint());

            var interNum:int = Point.distance(_controls[i].toPoint(), _controls[next].toPoint()) / 20;

            for (var t:Number = 0.0; t <= 1.0; t += 1 / interNum)
            {
                var pt:Point = bezier.getValue(t);
                vec.push(pt);
            }
        }

        _vec = vec;
    }
}

/**
 * BezierLineMakerクラスはベジェ曲線編集用のパネルです。
 * @author yasu
 */
internal class BezierLineMaker extends AbstractBezierView
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /**
     * グラフの矩形領域です。
     */
    public static const GRAPH_RECT:Rectangle = new Rectangle(0, 0, GRAPH_STEP_W * 24, GRAPH_STEP_H * 20);

    /**
     * 0〜100%の領域を示すグラフの矩形領域です。
     */
    public static const PERCENT_RECT:Rectangle = new Rectangle(0, 0, GRAPH_STEP_W * 24, GRAPH_STEP_H * 12);

    private static const GRAPH_STEP_W:Number = 24;
    private static const GRAPH_STEP_H:Number = 17;

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    /**
     * 新しい BezierLineMaker インスタンスを作成します。
     */
    public function BezierLineMaker()
    {

        super();

        _bitmapData = new BitmapData(1, 1);

        _container = new Sprite();
        _canvas = new Sprite();

        _curveCanvas = new Sprite();
        _curveCanvas.x = GRAPH_RECT.left + PERCENT_RECT.left;
        _curveCanvas.y = GRAPH_RECT.top + PERCENT_RECT.top;
        _curveCanvas.addEventListener(MouseEvent.MOUSE_DOWN, canvas_mouseDownHandler);

        _clickCanvas = new Sprite();
        _clickCanvas.x = GRAPH_RECT.left + PERCENT_RECT.left;
        _clickCanvas.y = GRAPH_RECT.top + PERCENT_RECT.top;
        _clickCanvas.addEventListener(MouseEvent.MOUSE_DOWN, canvas_mouseDownHandler);

        _clickCanvas.graphics.beginFill(0xFF, 0)
        _clickCanvas.graphics.drawRect(0, 0, 2880, 2880)

        _controlCanvasSub = new Shape();
        _controlCanvasSub.x = GRAPH_RECT.left + PERCENT_RECT.left;
        _controlCanvasSub.y = GRAPH_RECT.top + PERCENT_RECT.top;

        _controlCanvas = new Sprite();
        _controlCanvas.x = GRAPH_RECT.left + PERCENT_RECT.left;
        _controlCanvas.y = GRAPH_RECT.top + PERCENT_RECT.top;

        addChild(_container);
        _container.addChild(_canvas);
        addChild(_curveCanvas);
        addChild(_clickCanvas);
        addChild(_controlCanvasSub);
        addChild(_controlCanvas);
        //_update(null);
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    public function get isCrossed():Boolean
    {
        if (!_vec)
            return false;

        var flag:Boolean = false;
        var lines:Vector.<Line> = new Vector.<Line>();
        var i:int;
        var j:int;

        for (i = 0; i < _vec.length - 1; i++)
            lines[i] = new Line(_vec[i], _vec[i + 1]);
        lines.fixed = true;
        for (i = 0; i < lines.length; i++)
        {
            for (j = 0; j < lines.length; j++)
            {
                if (j < i)
                    continue;

                flag ||= Line.isCross(lines[i], lines[j]);
                if (flag)
                    break;
            }
        }

        return flag;
    }

    /**
     * @inheritDoc
     */
    override public function set scaleX(value:Number):void
    {
        super.scaleX = value;

        for (var i:int = 0; i < _controls.length; i++)
            _controls[i].scalePoint = value;
    }

    

    //--------------------------------------
    // toolType 
    //--------------------------------------

    private var _toolType:String = ToolType.PLUS;

    public function get toolType():String
    {
        return _toolType;
    }

    public function set toolType(value:String):void
    {
        _toolType = value;
    }

    /**
     * 作成した距離の長さ(単位は px )
     */
    public function get totalDistance():Number
    {
        if (!_vec)
            return 0;
        var dist:Number = 0;
        for (var i:int = 0; i < _vec.length - 1; i++)
            dist += Point.distance(_vec[i], _vec[i + 1]);
        return dist;
    }

    private var _canvas:Sprite;
    private var _clickCanvas:Sprite;
    private var _container:Sprite;
    private var _controlCanvas:Sprite;
    private var _controlCanvasSub:Shape;
    private var _currentKnob:BezierPoint;
    private var _curveCanvas:Sprite;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * ポイントを削除します。
     * @param target
     */
    public function deletePoint(target:BezierPoint):void
    {
        // 一個以下なら終了
        if (_controls.length <= 1)
            return;

        var index:int = _controls.indexOf(target);

        // 消去処理
        _controls.splice(index, 1);
        _controlCanvas.removeChild(target);

        // メモリ対策
        target.removeEventListener(Event.CHANGE, knob_dragChangeHandler);
        target.dispose();

        _updateAll();
    }

    public function reset():void
    {
        for (var i:int = 0; i < _controls.length; i++)
        {
            var target:BezierPoint = _controls[i];
            _controlCanvas.removeChild(target);

            // メモリ対策
            target.removeEventListener(Event.CHANGE, knob_dragChangeHandler);
            target.dispose();
        }

        _controls = new Vector.<BezierPoint>();
        _updateAll();
    }

    /**
     * @inheritDoc
     */
    override protected function _updateAll():void
    {
        super._updateAll();
        _updateControlLines();

        // コース変更イベント送出
        dispatchEvent(new CourseEvent(CourseEvent.COURSE_CHANGE, true, false));
    }

    private function _updateControlLines():void
    {
        super._calcPoints();
        var vecLo:Vector.<Point> = _vec;
        var i:int;

        _controlCanvas.graphics.clear();
        for (i = 0; i < vecLo.length - 1; i++)
        {
            _controlCanvas.graphics.lineStyle(1, 0x5083fc, 1);
            _controlCanvas.graphics.moveTo(vecLo[i].x, vecLo[i].y);
            _controlCanvas.graphics.lineTo(vecLo[i + 1].x, vecLo[i + 1].y);
        }
    }

    private function _cloneControlLines():void
    {
        var vec:Vector.<Point> = _vec;
        var i:int;

        _controlCanvasSub.graphics.clear();
        for (i = 0; i < vec.length - 1; i++)
        {
            _controlCanvasSub.graphics.lineStyle(1, 0x5083fc, 0.5);
            _controlCanvasSub.graphics.moveTo(vec[i].x, vec[i].y);
            _controlCanvasSub.graphics.lineTo(vec[i + 1].x, vec[i + 1].y);
        }
    }

    private function _digit(value:Number, ratio:Number = 100):Number
    {
        return Math.round(value * ratio) / ratio;
    }

    private function canvas_mouseDownHandler(event:MouseEvent):void
    {
        var p:Point;
        if (_toolType == ToolType.PLUS)
        {
            p = new Point(
                _controlCanvas.mouseX / PERCENT_RECT.width,
                1 - _controlCanvas.mouseY / PERCENT_RECT.height);

            _currentKnob = new BezierPoint(this, new Point(p.x, p.y), false, false, new Point(p.x, p.y), new Point(p.x, p.y));
            _currentKnob.addEventListener(KnobEvent.DRAG_START, knob_dragStartHandler);
            _currentKnob.addEventListener(KnobEvent.DRAG_CHANGE, knob_dragChangeHandler);
            _currentKnob.addEventListener(KnobEvent.DRAG_END, knob_dragEndHandler);

            _controlCanvas.addChild(_currentKnob);
            _controls.push(_currentKnob);

            stage.addEventListener(MouseEvent.MOUSE_MOVE, canvas_mouseMoveHandler);
            stage.addEventListener(MouseEvent.MOUSE_UP, canvas_mouseUpHandler);
        }

        if (_toolType == ToolType.PLUS)
        {
            p = new Point(
                _controlCanvas.mouseX / PERCENT_RECT.width,
                1 - _controlCanvas.mouseY / PERCENT_RECT.height);

            _currentKnob = new BezierPoint(this, new Point(p.x, p.y), false, false, new Point(p.x, p.y), new Point(p.x, p.y));
            _currentKnob.addEventListener(KnobEvent.DRAG_START, knob_dragStartHandler);
            _currentKnob.addEventListener(KnobEvent.DRAG_CHANGE, knob_dragChangeHandler);
            _currentKnob.addEventListener(KnobEvent.DRAG_END, knob_dragEndHandler);

            _controlCanvas.addChild(_currentKnob);
            _controls.push(_currentKnob);

            stage.addEventListener(MouseEvent.MOUSE_MOVE, canvas_mouseMoveHandler);
            stage.addEventListener(MouseEvent.MOUSE_UP, canvas_mouseUpHandler);
        }
    }

    private function canvas_mouseMoveHandler(event:MouseEvent):void
    {
        _currentKnob.useControlPoint = true;
        _currentKnob.updateControlPoint(_controlCanvas.mouseX, _controlCanvas.mouseY);
        _updateAll();
        //event.updateAfterEvent();
    }

    private function canvas_mouseUpHandler(event:MouseEvent):void
    {
        _currentKnob = null;
        stage.removeEventListener(MouseEvent.MOUSE_MOVE, canvas_mouseMoveHandler);
        stage.removeEventListener(MouseEvent.MOUSE_UP, canvas_mouseUpHandler);

        _updateAll();
    }

    private function knob_dragStartHandler(event:Event):void
    {
        _cloneControlLines();
        _controlCanvasSub.visible = true;
    }

    private function knob_dragChangeHandler(event:Event):void
    {
        var t:BezierPoint = (event.currentTarget as BezierPoint);
        t.tx = t.x / GRAPH_RECT.width;
        t.ty = (GRAPH_RECT.height - t.y) / GRAPH_RECT.height;

        _updateControlLines();
    }

    private function knob_dragEndHandler(event:Event):void
    {
        var t:BezierPoint = (event.currentTarget as BezierPoint);
        t.tx = t.x / GRAPH_RECT.width;
        t.ty = (GRAPH_RECT.height - t.y) / GRAPH_RECT.height;

        _updateAll();
        _controlCanvasSub.visible = false;
    }
}

/**
 * BezierPointクラスは、ベジェの制御点です。
 * @author yasu
 */
internal class BezierPoint extends Sprite
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    private static const FIT_HEIGHT:uint = 4;

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    /**
     * 新しい BezierPoint インスタンスを作成します。
     *
     * @param parentObj
     * @param main
     * @param lock
     * @param useControlPoint
     * @param pointPre
     * @param pointPost
     */
    public function BezierPoint(parentObj:BezierLineMaker, main:Point, lock:Boolean, useControlPoint:Boolean, pointPre:Point, pointPost:Point):void
    {
        this._parentObj = parentObj;
        this.lock = lock;
        this.pointPre = pointPre;
        this.pointPost = pointPost;

        _knobCenter = new KnobCenter();
        addChild(_knobCenter);

        if (!lock)
            _knobCenter.buttonMode = true;

        _controlPre = new ControlKnob();

        _controlPre.buttonMode = true;
        if (pointPre)
            addChild(_controlPre);

        _controlPost = new ControlKnob();
        if (pointPost)
            addChild(_controlPost);

        this.x = main.x * (BezierLineMaker.PERCENT_RECT.width);
        this.y = (1 - main.y) * (BezierLineMaker.PERCENT_RECT.height);

        if (pointPre)
        {
            _controlPre.x = pointPre.x * (BezierLineMaker.PERCENT_RECT.width) - this.x;
            _controlPre.y = (1 - pointPre.y) * (BezierLineMaker.PERCENT_RECT.height) - this.y;
        }
        if (pointPost)
        {
            _controlPost.x = pointPost.x * (BezierLineMaker.PERCENT_RECT.width) - this.x;
            _controlPost.y = (1 - pointPost.y) * (BezierLineMaker.PERCENT_RECT.height) - this.y;
        }

        _knobCenter.addEventListener(MouseEvent.MOUSE_DOWN, _mainPoint_mouseDownHandler);
        _controlPre.addEventListener(MouseEvent.MOUSE_DOWN, _controlPre_mouseDownHandler);
        _controlPost.addEventListener(MouseEvent.MOUSE_DOWN, _controlPost_mouseDownHandler);

        graphics.clear();

        _drawLine(_knobCenter, _controlPre);
        _drawLine(_knobCenter, _controlPost);

        this.useControlPoint = useControlPoint;
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    public function get controlPointPost():Point
    {
        return new Point(this.x + _controlPost.x, this.y + _controlPost.y);
    }

    public function get controlPointPostNormaled():Point
    {
        return new Point(
            (this.x + _controlPost.x) / BezierLineMaker.PERCENT_RECT.width,
            (BezierLineMaker.PERCENT_RECT.height - this.y - _controlPost.y) / BezierLineMaker.PERCENT_RECT.height);
    }

    public function get controlPointPre():Point
    {
        return new Point(this.x + _controlPre.x, this.y + _controlPre.y);
    }

    public function get controlPointPreNormaled():Point
    {
        return new Point(
            (this.x + _controlPre.x) / BezierLineMaker.PERCENT_RECT.width,
            (BezierLineMaker.PERCENT_RECT.height - this.y - _controlPre.y) / BezierLineMaker.PERCENT_RECT.height);
    }

    /** 編集可能かどうかを取得または設定します。 */
    public var lock:Boolean;
    /** コントロールポイント */
    public var pointPost:Point;
    /** コントロールポイント */
    public var pointPre:Point;

    public function set scalePoint(value:Number):void
    {
        _knobCenter.scaleX = _knobCenter.scaleY = 1 / value;
        _controlPost.scaleX = _controlPost.scaleY = 1 / value;
        _controlPre.scaleX = _controlPre.scaleY = 1 / value;
    }

    public var tx:Number;
    public var ty:Number;

    //--------------------------------------
    // useControlPoint 
    //--------------------------------------

    private var _useControlPoint:Boolean;

    /**
     *
     */
    public function get useControlPoint():Boolean
    {
        return _useControlPoint;
    }

    /**
     * @private
     */
    public function set useControlPoint(value:Boolean):void
    {
        _useControlPoint = value;

        _controlPost.visible = _useControlPoint;
        _controlPre.visible = _useControlPoint;
    }

    private var _controlPost:ControlKnob;
    private var _controlPre:ControlKnob;
    private var _currentDrag:Sprite;
    private var _knobCenter:KnobCenter;
    private var _oldPoint:Point;
    private var _parentObj:BezierLineMaker;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * @inheritDoc
     */
    public function dispose():void
    {
        _knobCenter.removeEventListener(MouseEvent.MOUSE_DOWN, _mainPoint_mouseDownHandler);
        _controlPre.removeEventListener(MouseEvent.MOUSE_DOWN, _controlPre_mouseDownHandler);
        _controlPost.removeEventListener(MouseEvent.MOUSE_DOWN, _controlPost_mouseDownHandler);

        pointPost = null;
        pointPre = null;

        _knobCenter = null;
        _controlPre = null;
        _controlPost = null;
        _currentDrag = null;
        _oldPoint = null;
        _parentObj = null;
    }

    /**
     *
     * @return
     *
     */
    public function toPoint():Point
    {
        return new Point(this.x, this.y);
    }

    /**
     *
     * @return
     *
     */
    public function toNormalPoint():Point
    {
        var xx:Number = (this.x) / BezierLineMaker.PERCENT_RECT.width;
        var yy:Number = (BezierLineMaker.PERCENT_RECT.height - this.y) / BezierLineMaker.PERCENT_RECT.height;

        return new Point(xx, yy);
    }

    public function updateControlPoint(mx:Number, my:Number):void
    {
        var rot:Number = Math.atan2(my - this.y, mx - this.x);
        var t:Point = new Point(mx, my);
        var me:Point = new Point(this.x, this.y);

        var post:Point = Point.polar(Point.distance(t, me), rot);
        _controlPost.x = post.x;
        _controlPost.y = post.y;

        var pre:Point = Point.polar(Point.distance(t, me), rot - Math.PI);
        _controlPre.x = pre.x;
        _controlPre.y = pre.y;

        graphics.clear();

        _drawLine(_knobCenter, _controlPre);
        _drawLine(_knobCenter, _controlPost);
    }

    public function toBezierPointData():BezierPointData
    {
        var o:BezierPointData = new BezierPointData();
        o.main = toNormalPoint();
        o.post = this.controlPointPostNormaled;
        o.pre = this.controlPointPreNormaled;
        return o;
    }

    private function _controlPost_mouseDownHandler(event:MouseEvent):void
    {
        switch (_parentObj.toolType)
        {
            case ToolType.ARROW:
            {
                _currentDrag = _controlPost;
                _currentDrag.startDrag(true);

                stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
                dispatchEvent(new KnobEvent(KnobEvent.DRAG_START));
                break;
            }

            case ToolType.CHANGE:
            {
                stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
                dispatchEvent(new KnobEvent(KnobEvent.DRAG_START));
                break;
            }
        }
        event.stopPropagation();
    }

    private function _controlPre_mouseDownHandler(event:MouseEvent):void
    {
        switch (_parentObj.toolType)
        {
            case ToolType.ARROW:
            {
                _currentDrag = _controlPre;
                _currentDrag.startDrag(true);

                stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);

                dispatchEvent(new KnobEvent(KnobEvent.DRAG_START));
                break;
            }
            case ToolType.CHANGE:
            {
                stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
                dispatchEvent(new KnobEvent(KnobEvent.DRAG_START));
                break;
            }
        }
        event.stopPropagation();
    }

    private function _mainPoint_mouseDownHandler(event:MouseEvent):void
    {
        if (lock)
            return;

        switch (_parentObj.toolType)
        {
            case ToolType.ARROW:
            {
                _currentDrag = this;
                _currentDrag.startDrag(true);

                stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
                event.stopPropagation();
                dispatchEvent(new KnobEvent(KnobEvent.DRAG_START));
                break;
            }

            case ToolType.PLUS:
                break;

            case ToolType.PLUS:
            {
                event.stopPropagation();
                break;
            }

            case ToolType.DELETE:
            {
                _parentObj.deletePoint(this);
                event.stopPropagation();
                break;
            }

            case ToolType.CHANGE:
            {

                if (event.altKey)
                {
                    this.useControlPoint = false;
                    _controlPre.x = 0;
                    _controlPre.y = 0;
                    _controlPost.x = 0;
                    _controlPost.y = 0;
                    graphics.clear();
                    event.stopPropagation();
                    dispatchEvent(new KnobEvent(KnobEvent.DRAG_END));
                }
                else
                {
                    stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
                    stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
                    event.stopPropagation();
                    dispatchEvent(new KnobEvent(KnobEvent.DRAG_START));
                }
                break;
            }
        }
    }

    private function menuItem_selectHandler(event:ContextMenuEvent):void
    {
        _parentObj.deletePoint(this);
    }

    private function mouseMoveHandler(event:MouseEvent):void
    {
        switch (_parentObj.toolType)
        {
            case ToolType.ARROW:
            {
                if (_currentDrag == this)
                {
                    if (Math.abs(this.y) < FIT_HEIGHT)
                        this.y = 0;
                    if (Math.abs(this.y - BezierLineMaker.PERCENT_RECT.height) < FIT_HEIGHT)
                        this.y = BezierLineMaker.PERCENT_RECT.height;
                }
                graphics.clear();

                _drawLine(_knobCenter, _controlPre);
                _drawLine(_knobCenter, _controlPost);

                dispatchEvent(new KnobEvent(KnobEvent.DRAG_CHANGE));
                break;
            }

            case ToolType.CHANGE:
            {
                if (!this.useControlPoint)
                    this.useControlPoint = true;
                updateControlPoint(parent.mouseX, parent.mouseY);
                dispatchEvent(new KnobEvent(KnobEvent.DRAG_CHANGE));
                break;
            }
        }

        //            event.updateAfterEvent();
    }

    private function mouseUpHandler(event:MouseEvent):void
    {
        switch (_parentObj.toolType)
        {
            case ToolType.ARROW:
            {
                _currentDrag.stopDrag();

                if (_currentDrag == this)
                {
                    if (Math.abs(this.y) < FIT_HEIGHT)
                        this.y = 0;
                    if (Math.abs(this.y - BezierLineMaker.PERCENT_RECT.height) < FIT_HEIGHT)
                        this.y = BezierLineMaker.PERCENT_RECT.height;
                }

                graphics.clear();
                _drawLine(_knobCenter, _controlPre);
                _drawLine(_knobCenter, _controlPost);

                dispatchEvent(new KnobEvent(KnobEvent.DRAG_END));
                break;
            }

            case ToolType.CHANGE:
            {
                dispatchEvent(new KnobEvent(KnobEvent.DRAG_END));
                break;
            }
        }

        stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
        stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
    }

    private function _drawLine(p0:DisplayObject, p1:DisplayObject, color:uint = 0x5083fc):void
    {
        graphics.lineStyle(1, color);
        graphics.moveTo(p0.x, p0.y);
        graphics.lineTo(p1.x, p1.y);
        graphics.lineStyle();
    }
}

internal class BezierPointData
{

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function BezierPointData()
    {
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    public var main:Point;
    public var post:Point;
    public var pre:Point;
}

internal class ControlKnob extends Sprite
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    private static const COLOR:int = 0x0;
    private static const RADIUS:int = 4;
    private static const RADIUS_OVER:int = 8;
    private static const SNAP_HEIGHT:uint = 14;

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function ControlKnob()
    {
        hit = new Shape();
        hit.graphics.beginFill(COLOR, 0);
        hit.graphics.drawRect(-SNAP_HEIGHT / 2, -SNAP_HEIGHT / 2, SNAP_HEIGHT, SNAP_HEIGHT);

        def = new Shape();
        def.graphics.lineStyle(1, 0x5083fc);
        def.graphics.beginFill(0xFFFFFF);
        def.graphics.drawRect(-RADIUS / 2 >> 0, -RADIUS / 2 >> 0, RADIUS, RADIUS);
        def.graphics.endFill();

        over = new Shape();
        over.graphics.lineStyle(1, 0x5083fc);
        over.graphics.beginFill(0xFFFFFF);
        over.graphics.drawRect(-RADIUS_OVER / 2, -RADIUS_OVER / 2, RADIUS_OVER, RADIUS_OVER);
        over.graphics.endFill();

        addChild(hit);
        addChild(def);
        addChild(over);

        over.visible = false;
        buttonMode = true;

        addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
        addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    private var def:Shape;

    private var hit:Shape;

    private var over:Shape;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * @inheritDoc
     */
    public function dispose():void
    {
        removeEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
        removeEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
    }

    protected function rollOverHandler(event:MouseEvent):void
    {
        over.visible = true;
    }

    protected function rollOutHandler(event:MouseEvent):void
    {
        over.visible = false;
    }
}

internal class CourseEvent extends Event
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    public static const COURSE_CHANGE:String = "courseChange";

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function CourseEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
    {
        super(type, bubbles, cancelable);
    }
}

internal class KnobCenter extends Sprite
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    private static const COLOR:int = 0x0;
    private static const RADIUS:int = 4;
    private static const RADIUS_OVER:int = 8;
    private static const SNAP_HEIGHT:uint = 14;

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function KnobCenter()
    {
        hit = new Shape();
        hit.graphics.beginFill(COLOR, 0);
        hit.graphics.drawRect(-SNAP_HEIGHT / 2, -SNAP_HEIGHT / 2, SNAP_HEIGHT, SNAP_HEIGHT);
        hit.graphics.endFill();

        def = new Shape();
        def.graphics.beginFill(0x5083fc);
        def.graphics.drawRect(-RADIUS / 2 >> 0, -RADIUS / 2 >> 0, RADIUS, RADIUS);
        def.graphics.endFill();

        over = new Shape();
        over.graphics.beginFill(0x5083fc);
        over.graphics.drawRect(-RADIUS_OVER / 2, -RADIUS_OVER / 2, RADIUS_OVER, RADIUS_OVER);
        over.graphics.endFill();

        addChild(hit);
        addChild(def);
        addChild(over);

        over.visible = false;
        buttonMode = true;

        addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
        addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    private var def:Shape;
    private var hit:Shape;
    private var over:Shape;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * @inheritDoc
     */
    public function dispose():void
    {
        removeEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
        removeEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
    }

    protected function rollOverHandler(event:MouseEvent):void
    {
        over.visible = true;
    }

    protected function rollOutHandler(event:MouseEvent):void
    {
        over.visible = false;
    }
}

/** つまみのイベントを管理するイベントクラスです。 */
internal class KnobEvent extends Event
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /** ドラッグの開始イベントを示す定数です。 */
    public static const DRAG_START:String = "DRAG_START";
    /** ドラッグ中に変更があったことを示す定数です。 */
    public static const DRAG_CHANGE:String = "DRAG_CHANGE";
    /** ドラッグの終了イベントを示す定数です。 */
    public static const DRAG_END:String = "DRAG_END";

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function KnobEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
    {
        super(type, bubbles, cancelable);
    }
}

/**
 * 直線が交差しているか計算することのできるクラス
 * @see http://yamasv.blog92.fc2.com/blog-entry-105.html
 */
internal class Line
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /**
     * 直線の交点(線分の交点ではない)
     */
    public static function crossPoint(lhs:Line, rhs:Line):Point
    {
        var a:Point = new Point(lhs.p2.x - lhs.p1.x, lhs.p2.y - lhs.p1.y);
        var b:Point = new Point(rhs.p2.x - rhs.p1.x, rhs.p2.y - rhs.p1.y);
        var c:Point = new Point(rhs.p1.x - lhs.p1.x, rhs.p1.y - lhs.p1.y);

        var result:Point = new Point();

        var cross_b_c:Number = b.x * c.y - b.y * c.x;
        var cross_b_a:Number = b.x * a.y - b.y * a.x;

        if (cross_b_a == 0)
            return null;

        result.x = lhs.p1.x + a.x * cross_b_c / cross_b_a;
        result.y = lhs.p1.y + a.y * cross_b_c / cross_b_a;

        return result;
    }

    public static function isCross(lhs:Line, rhs:Line):Boolean
    {
        var p:Point = crossPoint(lhs, rhs);

        return (p != null &&
            (p.x - rhs.p1.x) * (p.x - rhs.p2.x) + (p.y - rhs.p1.y) * (p.y - rhs.p2.y) < 0) &&
            ((p.x - lhs.p1.x) * (p.x - lhs.p2.x) + (p.y - lhs.p1.y) * (p.y - lhs.p2.y) < 0);
    }

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    public function Line(p1:Point, p2:Point)
    {
        this.p1 = p1;
        this.p2 = p2;
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    public var p1:Point;
    public var p2:Point;
}

/**
 * LineMaker はラインを描くためのクラスです。
 * @author yasu
 * @version 1.0.0
 * @since 2010/5/11
 */
internal class LineUtil
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /**
     * ビットマップ塗りのラインを描きます。
     * @param g    グラフィック
     * @param vec    パスの頂点を格納した配列
     * @param _bmd    ビットマップデータ
     * @param _lineWidth    ライン幅
     *
     */
    public static function drawBitmapLine(g:Graphics, vec:Vector.<Point>, _bmd:BitmapData, _lineWidth:Number, _showUV:Boolean):void
    {
        var topPoints:Vector.<Point> = new Vector.<Point>();
        var bottomPoints:Vector.<Point> = new Vector.<Point>();

        var len:int = vec.length;
        var i:int;
        for (i = 0; i < len; i++)
        {
            var angle:Number;

            if (i == 0)
                angle = Math.atan2(vec[i + 1].y - vec[len - 1].y, vec[i + 1].x - vec[len - 1].x);
            else if (i == vec.length - 1)
                angle = Math.atan2(vec[0].y - vec[i - 1].y, vec[0].x - vec[i - 1].x);
            else
                angle = Math.atan2(vec[i + 1].y - vec[i - 1].y, vec[i + 1].x - vec[i - 1].x);

            var tPoint:Point = Point.polar(_lineWidth, angle + Math.PI / 2);
            tPoint.x += vec[i].x;
            tPoint.y += vec[i].y;

            var bPoint:Point = Point.polar(_lineWidth, angle - Math.PI / 2);
            bPoint.x += vec[i].x;
            bPoint.y += vec[i].y;

            topPoints.push(tPoint);
            bottomPoints.push(bPoint);
        }

        var indices:Vector.<int> = new Vector.<int>();
        indices.push(0, 1, 2);
        indices.push(1, 2, 3);

        var uvtData:Vector.<Number> = new Vector.<Number>();
        uvtData.push(0, 0, 1, 0);
        uvtData.push(0, 1, 1, 1);

        for (i = 0; i < topPoints.length; i++)
        {
            var next:int = i + 1;
            if (next == topPoints.length)
                next = 0;

            if(_showUV) g.lineStyle(1, 0x0);
            g.beginBitmapFill(_bmd, null, true, true);

            var vertieces:Vector.<Number> = new Vector.<Number>();
            vertieces.push(topPoints[i].x, topPoints[i].y, topPoints[next].x, topPoints[next].y);
            vertieces.push(bottomPoints[i].x, bottomPoints[i].y, bottomPoints[next].x, bottomPoints[next].y);

            g.drawTriangles(vertieces, indices, uvtData);
            g.endFill();
        }
    }
}

/** ツールの種類を定義したクラスです。 */
internal class ToolType
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /** 選択可能なツールタイプを示す定数です。 */
    public static const ARROW:String = "ARROW";
    /** 削除可能なツールタイプを示す定数です。 */
    public static const DELETE:String = "remove";
    /** 追加可能なツールタイプを示す定数です。 */
    public static const PLUS:String = "plus";
    /** 変更可能なツールタイプを示す定数です。 */
    public static const CHANGE:String = "change";
}

/**
 * A Bezier segment consists of four Point objects that define a single cubic Bezier curve.
 * The BezierSegment class also contains methods to find coordinate values along the curve.
 * @playerversion Flash 9.0.28.0
 * @langversion 3.0
 * @keyword BezierSegment, Copy Motion as ActionScript
 * @see ../../motionXSD.html Motion XML Elements
 */
internal class BezierSegment
{

    //----------------------------------------------------------
    //
    //   Static Property 
    //
    //----------------------------------------------------------

    /**
     * Calculates the value of a one-dimensional cubic Bezier equation at a specific time.
     * By contrast, a Bezier curve is usually two-dimensional
     * and uses two of these equations, one for the x coordinate and one for the y coordinate.
     *
     * @param t The <code>time</code> or degree of progress along the curve, as a decimal value between <code>0</code> and <code>1</code>.
     * <p><strong>Note:</strong> The <code>t</code> parameter does not necessarily move along the curve at a uniform speed. For example, a <code>t</code> value of <code>0.5</code> does not always produce a value halfway along the curve.</p>
     *
     * @param a The first value of the Bezier equation.
     *
     * @param b The second value of the Bezier equation.
     *
     * @param c The third value of the Bezier equation.
     *
     * @param d The fourth value of the Bezier equation.
     *
     * @return The value of the Bezier equation at the specified time.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public static function getSingleValue(t:Number, a:Number = 0, b:Number = 0, c:Number = 0, d:Number = 0):Number
    {
        return (t * t * (d - a) + 3 * (1 - t) * (t * (c - a) + (1 - t) * (b - a))) * t + a;
    }

    /**
     * Calculates the coefficients for a cubic polynomial equation,
     * given the values of the corresponding cubic Bezier equation.
     *
     * @param a The first value of the Bezier equation.
     *
     * @param b The second value of the Bezier equation.
     *
     * @param c The third value of the Bezier equation.
     *
     * @param d The fourth value of the Bezier equation.
     *
     * @return An array containing four number values,
     * which are the coefficients for a cubic polynomial.
     * The coefficients are ordered from the highest degree to the lowest,
     * so the first number in the array would be multiplied by t^3, the second by t^2, and so on.
     *
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     * @see #getCubicRoots()
     */
    public static function getCubicCoefficients(a:Number, b:Number, c:Number, d:Number):Array
    {
        return [-a + 3 * b - 3 * c + d,
            3 * a - 6 * b + 3 * c,
            -3 * a + 3 * b,
            a];
    }

    /**
     * Finds the real solutions, if they exist, to a cubic polynomial equation of the form: at^3 + bt^2 + ct + d.
     * This method is used to evaluate custom easing curves.
     *
     * @param a The first coefficient of the cubic equation, which is multiplied by the cubed variable (t^3).
     *
     * @param b The second coefficient of the cubic equation, which is multiplied by the squared variable (t^2).
     *
     * @param c The third coefficient of the cubic equation, which is multiplied by the linear variable (t).
     *
     * @param d The fourth coefficient of the cubic equation, which is the constant.
     *
     * @return An array of number values, indicating the real roots of the equation.
     * There may be no roots, or as many as three.
     * Imaginary or complex roots are ignored.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public static function getCubicRoots(a:Number = 0, b:Number = 0, c:Number = 0, d:Number = 0):Array
    {
        // make sure we really have a cubic
        if (!a)
            return BezierSegment.getQuadraticRoots(b, c, d);

        // normalize the coefficients so the cubed term is 1 and we can ignore it hereafter
        if (a != 1)
        {
            b /= a;
            c /= a;
            d /= a;
        }

        var q:Number = (b * b - 3 * c) / 9; // won't change over course of curve
        var qCubed:Number = q * q * q; // won't change over course of curve
        var r:Number = (2 * b * b * b - 9 * b * c + 27 * d) / 54; // will change because d changes
        // but parts with b and c won't change
        // determine if there are 1 or 3 real roots using r and q
        var diff:Number = qCubed - r * r; // will change
        if (diff >= 0)
        {
            // avoid division by zero
            if (!q)
                return [0];
            // three real roots
            var theta:Number = Math.acos(r / Math.sqrt(qCubed)); // will change because r changes
            var qSqrt:Number = Math.sqrt(q); // won't change

            var root1:Number = -2 * qSqrt * Math.cos(theta / 3) - b / 3;
            var root2:Number = -2 * qSqrt * Math.cos((theta + 2 * Math.PI) / 3) - b / 3;
            var root3:Number = -2 * qSqrt * Math.cos((theta + 4 * Math.PI) / 3) - b / 3;

            return [root1, root2, root3];
        }
        else
        {
            // one real root
            var tmp:Number = Math.pow(Math.sqrt(-diff) + Math.abs(r), 1 / 3);
            var rSign:int = (r > 0) ? 1 : r < 0 ? -1 : 0;
            var root:Number = -rSign * (tmp + q / tmp) - b / 3;
            return [root];
        }
        return [];
    }

    /**
     * Finds the real solutions, if they exist, to a quadratic equation of the form: at^2 + bt + c.
     *
     * @param a The first coefficient of the quadratic equation, which is multiplied by the squared variable (t^2).
     *
     * @param b The second coefficient of the quadratic equation, which is multiplied by the linear variable (t).
     *
     * @param c The third coefficient of the quadratic equation, which is the constant.
     *
     * @return An array of number values, indicating the real roots of the equation.
     * There may be no roots, or as many as two.
     * Imaginary or complex roots are ignored.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public static function getQuadraticRoots(a:Number, b:Number, c:Number):Array
    {
        var roots:Array = [];
        // make sure we have a quadratic
        if (!a)
        {
            if (!b)
                return [];
            roots[0] = -c / b;
            return roots;
        }

        var q:Number = b * b - 4 * a * c;
        var signQ:int =
            (q > 0) ? 1
            : q < 0 ? -1
            : 0;

        if (signQ < 0)
            return [];
        else if (!signQ)
            roots[0] = -b / (2 * a);
        else
        {
            roots[0] = roots[1] = -b / (2 * a);
            var tmp:Number = Math.sqrt(q) / (2 * a);
            roots[0] -= tmp;
            roots[1] += tmp;
        }

        return roots;
    }

    //----------------------------------------------------------
    //
    //   Constructor 
    //
    //----------------------------------------------------------

    /**
     * Constructor for BezierSegment instances.
     *
     * @param a The first point of the curve, a node.
     *
     * @param b The second point of the curve, a control point.
     *
     * @param c The third point of the curve, a control point.
     *
     * @param d The fourth point of the curve, a node.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     * @see #propertyDetail property details
     */
    function BezierSegment(a:Point, b:Point, c:Point, d:Point)
    {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
    }

    //----------------------------------------------------------
    //
    //   Property 
    //
    //----------------------------------------------------------

    /**
     * The first point of the Bezier curve.
     * It is a node, which means it falls directly on the curve.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public var a:Point;

    /**
     * The second point of the Bezier curve.
     * It is a control point, which means the curve moves toward it,
     * but usually does not pass through it.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public var b:Point;

    /**
     * The third point of the Bezier curve.
     * It is a control point, which means the curve moves toward it,
     * but usually does not pass through it.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public var c:Point;

    /**
     * The fourth point of the Bezier curve.
     * It is a node, which means it falls directly on the curve.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public var d:Point;

    //----------------------------------------------------------
    //
    //   Function 
    //
    //----------------------------------------------------------

    /**
     * Calculates the location of a two-dimensional cubic Bezier curve at a specific time.
     *
     * @param t The <code>time</code> or degree of progress along the curve, as a decimal value between <code>0</code> and <code>1</code>.
     * <p><strong>Note:</strong> The <code>t</code> parameter does not necessarily move along the curve at a uniform speed. For example, a <code>t</code> value of <code>0.5</code> does not always produce a value halfway along the curve.</p>
     *
     * @return A point object containing the x and y coordinates of the Bezier curve at the specified time.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, node, Copy Motion as ActionScript
     */
    public function getValue(t:Number):Point
    {
        var ax:Number = this.a.x;
        var x:Number = (t * t * (this.d.x - ax) + 3 * (1 - t) * (t * (this.c.x - ax) + (1 - t) * (this.b.x - ax))) * t + ax;
        var ay:Number = this.a.y;
        var y:Number = (t * t * (this.d.y - ay) + 3 * (1 - t) * (t * (this.c.y - ay) + (1 - t) * (this.b.y - ay))) * t + ay;
        return new Point(x, y);
    }

    /**
     * Finds the <code>y</code> value of a cubic Bezier curve at a given x coordinate.
     * Some Bezier curves overlap themselves horizontally,
     * resulting in more than one <code>y</code> value for a given <code>y</code> value.
     * In that case, this method will return whichever value is most logical.
     *
     * Used by CustomEase and BezierEase interpolation.
     *
     * @param x An x coordinate that lies between the first and last point, inclusive.
     *
     * @param coefficients An optional array of number values that represent the polynomial
     * coefficients for the Bezier. This array can be used to optimize performance by precalculating
     * values that are the same everywhere on the curve and do not need to be recalculated for each iteration.
     *
     * @return The <code>y</code> value of the cubic Bezier curve at the given x coordinate.
     * @playerversion Flash 9.0.28.0
     * @langversion 3.0
     * @keyword Bezier curve, Copy Motion as ActionScript
     */
    public function getYForX(x:Number, coefficients:Array = null):Number
    {
        // Clamp to range between end points.
        // The padding with the small decimal value is necessary to avoid bugs
        // that result from reaching the limits of decimal precision in calculations.
        // We have tests that demonstrate this.
        if (this.a.x < this.d.x)
        {
            if (x <= this.a.x + 0.0000000000000001)
                return this.a.y;
            if (x >= this.d.x - 0.0000000000000001)
                return this.d.y;
        }
        else
        {
            if (x >= this.a.x + 0.0000000000000001)
                return this.a.y;
            if (x <= this.d.x - 0.0000000000000001)
                return this.d.y;
        }

        if (!coefficients)
            coefficients = getCubicCoefficients(this.a.x, this.b.x, this.c.x, this.d.x);

        // x(t) = a*t^3 + b*t^2 + c*t + d
        var roots:Array = getCubicRoots(coefficients[0], coefficients[1], coefficients[2], coefficients[3] - x);
        var time:Number = NaN;
        if (roots.length == 0)
            time = 0;
        else if (roots.length == 1)
            time = roots[0];
        else
        {
            for each (var root:Number in roots)
            {
                if (0 <= root && root <= 1)
                {
                    time = root;
                    break;
                }
            }
        }

        if (isNaN(time))
            return NaN;

        var y:Number = getSingleValue(time, this.a.y, this.b.y, this.c.y, this.d.y);
        return y;
    }
}