API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/curves/curve.js

/**

 **Curve** is the abstract base class for {{#crossLink "SplineCurve"}}{{/crossLink}},
 {{#crossLink "CubicBezierCurve"}}{{/crossLink}}, {{#crossLink "QuadraticBezierCurve"}}{{/crossLink}} and {{#crossLink "Path"}}{{/crossLink}}.

 ## Examples

 <ul>
 <li>[CubicBezierCurve example](../../examples/#animation_curves_cubicBezier)</li>
 <li>[Tweening position along a QuadraticBezierCurve](../../examples/#animation_curves_quadraticBezier)</li>
 <li>[Tweening color along a QuadraticBezierCurve](../../examples/#animation_curves_quadraticBezier_color)</li>
 <li>[Simple SplineCurve example](../../examples/#animation_curves_spline)</li>
 <li>[Moving a PointLight along a SplineCurve](../../examples/#animation_lights_point_world)</li>
 <li>[Path example](../../examples/#curves_Path)</li>
 </ul>

 @class Curve
 @module xeogl
 @submodule curves
 @constructor
 @param [scene] {Scene} Parent {{#crossLink "Scene"}}Scene{{/crossLink}}.
 @param [cfg] {*} Configuration
 @param [cfg.id] {String} Optional ID, unique among all components in the parent {{#crossLink "Scene"}}Scene{{/crossLink}}, generated automatically when omitted.
 @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this Curve.
 @param [cfg.t=0] Current position on this Curve, in range between 0..1.
 @extends Component
 */
xeogl.Curve = class xeoglCurve extends xeogl.Component {

    init(cfg) {
        super.init(cfg);
        this.t = cfg.t;
    }

    /**
     Progress along this Curve.

     Automatically clamps to range [0..1].

     Fires a {{#crossLink "Curve/t:event"}}{{/crossLink}} event on change.

     @property t
     @default 0
     @type Number
     */
    set t(value) {
        value = value || 0;
        this._t = value < 0.0 ? 0.0 : (value > 1.0 ? 1.0 : value);
        /**
         * Fired whenever this Curve's
         * {{#crossLink "Curve/t:property"}}{{/crossLink}} property changes.
         * @event t
         * @param value The property's new value
         */
        this.fire("t", this._t);
    }

    get t() {
        return this._t;
    }

    /**
     Tangent on this Curve at position {{#crossLink "Curve/t:property"}}{{/crossLink}}.

     @property tangent
     @type {{Array of Number}}
     */
    get tangent() {
        return this.getTangent(this._t);
    }

    /**
     Length of this Curve.
     @property length
     @type {Number}
     */
    get length() {
        var lengths = this._getLengths();
        return lengths[lengths.length - 1];
    }

    /**
     * Returns a normalized tangent vector on this Curve at the given position.
     * @method getTangent
     * @param {Number} t Position to get tangent at.
     * @returns {{Array of Number}} Normalized tangent vector
     */
    getTangent(t) {
        var delta = 0.0001;
        if (t === undefined) {
            t = this._t;
        }
        var t1 = t - delta;
        var t2 = t + delta;
        if (t1 < 0) {
            t1 = 0;
        }
        if (t2 > 1) {
            t2 = 1;
        }
        var pt1 = this.getPoint(t1);
        var pt2 = this.getPoint(t2);
        var vec = xeogl.math.subVec3(pt2, pt1, []);
        return xeogl.math.normalizeVec3(vec, []);
    }

    getPointAt(u) {
        var t = this.getUToTMapping(u);
        return this.getPoint(t);
    }

    /**
     * Samples points on this Curve, at the given number of equally-spaced divisions.
     * @method getPoints
     * @param {Number} divisions The number of divisions.
     * @returns {Array of Array} Array of sampled 3D points.
     */
    getPoints(divisions) {
        if (!divisions) {
            divisions = 5;
        }
        var d, pts = [];
        for (d = 0; d <= divisions; d++) {
            pts.push(this.getPoint(d / divisions));
        }
        return pts;
    }

    getSpacedPoints(divisions) {
        if (!divisions) {
            divisions = 5;
        }
        var d, pts = [];
        for (d = 0; d <= divisions; d++) {
            pts.push(this.getPointAt(d / divisions));
        }
        return pts;
    }

    _getLengths(divisions) {
        if (!divisions) {
            divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions) : 200;
        }
        if (this.cacheArcLengths && ( this.cacheArcLengths.length === divisions + 1 ) && !this.needsUpdate) {
            return this.cacheArcLengths;

        }
        this.needsUpdate = false;
        var cache = [];
        var current;
        var last = this.getPoint(0);
        var p;
        var sum = 0;
        cache.push(0);
        for (p = 1; p <= divisions; p++) {
            current = this.getPoint(p / divisions);
            sum += xeogl.math.lenVec3(xeogl.math.subVec3(current, last, []));
            cache.push(sum);
            last = current;
        }
        this.cacheArcLengths = cache;
        return cache; // { sums: cache, sum:sum }, Sum is in the last element.
    }

    _updateArcLengths() {
        this.needsUpdate = true;
        this._getLengths();
    }

    // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance

    getUToTMapping(u, distance) {
        var arcLengths = this._getLengths();
        var i = 0;
        var il = arcLengths.length;
        var t;
        var targetArcLength; // The targeted u distance value to get
        if (distance) {
            targetArcLength = distance;
        } else {
            targetArcLength = u * arcLengths[il - 1];
        }
        //var time = Date.now();
        var low = 0, high = il - 1, comparison;
        while (low <= high) {
            i = Math.floor(low + ( high - low ) / 2); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
            comparison = arcLengths[i] - targetArcLength;
            if (comparison < 0) {
                low = i + 1;
            } else if (comparison > 0) {
                high = i - 1;
            } else {
                high = i;
                break;
                // DONE
            }
        }
        i = high;
        if (arcLengths[i] === targetArcLength) {
            t = i / ( il - 1 );
            return t;
        }
        var lengthBefore = arcLengths[i];
        var lengthAfter = arcLengths[i + 1];
        var segmentLength = lengthAfter - lengthBefore;
        var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
        t = ( i + segmentFraction ) / ( il - 1 );
        return t;
    }
};