HaxePunk/HaxePunk

View on GitHub
haxepunk/tweens/motion/QuadPath.hx

Summary

Maintainability
Test Coverage
package haxepunk.tweens.motion;

import haxepunk.utils.Ease.EaseFunction;
import haxepunk.math.Vector2;

/**
 * A series of points which will determine a path from the
 * beginning point to the end poing using quadratic curves.
 */
class QuadPath extends Motion
{
    /**
     * Starts moving along the path.
     * @param    duration    Duration of the movement.
     * @param    ease        Optional easer function.
     */
    public function setMotion(duration:Float, ?ease:EaseFunction)
    {
        updatePath();
        _target = duration;
        _speed = _distance / duration;
        _ease = ease;
        start();
    }

    /**
     * Starts moving along the path at the speed.
     * @param    speed        Speed of the movement.
     * @param    ease        Optional easer function.
     */
    public function setMotionSpeed(speed:Float, ?ease:EaseFunction)
    {
        updatePath();
        _target = _distance / speed;
        _speed = speed;
        _ease = ease;
        start();
    }

    /**
     * Adds the point to the path.
     * @param    x        X position.
     * @param    y        Y position.
     */
    public function addPoint(x:Float = 0, y:Float = 0)
    {
        updateInternalCurve = true;
        if (_points.length == 0) _curve[0] = new Vector2(x, y);
        _points[_points.length] = new Vector2(x, y);
    }

    /**
     * Gets the point on the path.
     * @param    index        Index of the point.
     * @return    The Point object.
     */
    public function getPoint(index:Int = 0):Vector2
    {
        if (_points.length == 0)
            throw "No points have been added to the path yet.";

        return _points[index % _points.length];
    }

    /** @private Starts the Tween. */
    @:dox(hide)
    override public function start()
    {
        _index = 0;
        super.start();
    }

    /** @private Updates the Tween. */
    @:dox(hide)
    override function updateInternal()
    {
        if (_index < _curve.length - 1)
        {
            while (_t > _curveT[_index + 1]) _index++;
        }
        var td:Float = _curveT[_index],
            tt:Float = _curveT[_index + 1] - td;
        td = (_t - td) / tt;
        _a = _curve[_index];
        _b = _points[_index + 1];
        _c = _curve[_index + 1];
        x = _a.x * (1 - td) * (1 - td) + _b.x * 2 * (1 - td) * td + _c.x * td * td;
        y = _a.y * (1 - td) * (1 - td) + _b.y * 2 * (1 - td) * td + _c.y * td * td;
    }

    /** @private Updates the path, preparing the curve. */
    function updatePath()
    {
        if (_points.length < 3)
            throw "A QuadPath must have at least 3 points to operate.";

        if (!updateInternalCurve) return;
        updateInternalCurve = false;

        // produce the curve points
        var p:Vector2,
            c:Vector2,
            l:Vector2 = _points[1],
            i:Int = 2;
        while (i < _points.length)
        {
            p = _points[i];
            if (_curve.length > i - 1) c = _curve[i - 1];
            else c = _curve[i - 1] = new Vector2();
            if (i < _points.length - 1)
            {
                c.x = l.x + (p.x - l.x) / 2;
                c.y = l.y + (p.y - l.y) / 2;
            }
            else
            {
                c.x = p.x;
                c.y = p.y;
            }
            l = p;
            i++;
        }

        // find the total distance of the path
        i = 0;
        _distance = 0;
        while (i < _curve.length - 1)
        {
            _curveD[i] = curveLength(_curve[i], _points[i + 1], _curve[i + 1]);
            _distance += _curveD[i++];
        }

        // find t for each point on the curve
        i = 1;
        var d:Float = 0;
        while (i < _curve.length - 1)
        {
            d += _curveD[i];
            _curveT[i++] = d / _distance;
        }
        _curveT[_curve.length - 1] = 1;
    }

    /**
     * Amount of points on the path.
     */
    public var pointCount(get, null):Float;
    function get_pointCount():Float return _points.length;

    /** @private Calculates the lenght of the curve. */
    function curveLength(start:Vector2, control:Vector2, finish:Vector2):Float
    {
        var a:Vector2 = HXP.point,
            b:Vector2 = HXP.point2;
        a.x = start.x - 2 * control.x + finish.x;
        a.y = start.y - 2 * control.y + finish.y;
        b.x = 2 * control.x - 2 * start.x;
        b.y = 2 * control.y - 2 * start.y;
        var a1:Float = 4 * (a.x * a.x + a.y * a.y),
            b1:Float = 4 * (a.x * b.x + a.y * b.y),
            c1:Float = b.x * b.x + b.y * b.y,
            abc:Float = 2 * Math.sqrt(a1 + b1 + c1),
            a2:Float = Math.sqrt(a1),
            a32:Float = 2 * a1 * a2,
            c2:Float = 2 * Math.sqrt(c1),
            ba:Float = b1 / a2;
        return (a32 * abc + a2 * b1 * (abc - c2) + (4 * c1 * a1 - b1 * b1) * Math.log((2 * a2 + ba + abc) / (ba + c2))) / (4 * a32);
    }

    // Path information.
    var _points:Array<Vector2> = [];
    var _distance:Float = 0;
    var _speed:Float = 0;
    var _index:Int = 0;

    // Curve information.
    var updateInternalCurve:Bool = true;
    var _curve:Array<Vector2> = [];
    var _curveT:Array<Float> = [0];
    var _curveD:Array<Float> = [];

    // Curve points.
    var _a:Vector2;
    var _b:Vector2;
    var _c:Vector2;
}