API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/animation/cameraPathAnimation.js

/**
 A **CameraPathAnimation** animates the {{#crossLink "Scene"}}{{/crossLink}}'s {{#crossLink "Camera"}}{{/crossLink}} along a {{#crossLink "CameraPath"}}{{/crossLink}}.

 ## Examples

 * [Interpolating the Camera along a path](../../examples/#camera_path_interpolation)
 * [Flying directly to each frame on a path](../../examples/#camera_path_flyToFrame)
 * [Jumping directly to each frame on a path](../../examples/#camera_path_scrubToFrame)
 * [A menu of Camera waypoints to fly to](../../examples/#camera_path_frameMenu)

 ## Usage

 ### Interpolating the Camera along a path

 In this example we'll use the CameraPathAnimation's
 {{#crossLink "CameraPathAnimation/play"}}{{/crossLink}} method to smoothly <b>interpolate</b>
 the {{#crossLink "Scene"}}Scene{{/crossLink}}'s {{#crossLink "Camera"}}{{/crossLink}} along a {{#crossLink "CameraPath"}}{{/crossLink}}:

 <a href="../../examples/#camera_path_interpolation"><img src="http://i.giphy.com/l0MYDGMYzdFf6TqRW.gif"></img></a>

 ````Javascript
 // Load a model from glTF

 var gearbox = new xeogl.GLTFModel({
         src: "models/gltf/GearboxAssy/glTF-MaterialsCommon/GearboxAssy.gltf"
 });

 // Define a CameraPath

 var cameraPath = new xeogl.CameraPath({
     frames: [
         {t: 0, eye: [184.21, 10.54, -7.03], look: [159.2, 17.02, 3.21], up: [-0.15, 0.97, 0.13]},
         {t: 1, eye: [184.91, 10.10, -11.26], look: [171.03, 13.69, -5.57], up: [-0.15, 0.97, 0.12]},
         {t: 2, eye: [181.37, 12.35, -16.93], look: [171.03, 13.69, -5.57], up: [-0.17, 0.93, 0.28]},
         {t: 2, eye: [174.01, 13.55, -20.70], look: [171.03, 13.69, -5.57], up: [-0.01, 0.90, 0.40]},
         {t: 4, eye: [166.48, 14.36, -20.30], look: [171.03, 13.69, -5.57], up: [0.19, 0.88, 0.40]},
         {t: 5, eye: [160.32, 14.69, -16.63], look: [171.03, 13.69, -5.57], up: [0.36, 0.87, 0.29]},
         {t: 6, eye: [156.67, 17.97, -4.77], look: [162.53, 17.42, 1.28], up: [0.36, 0.87, 0.29]},
         {t: 7, eye: [151.14, 16.68, -10.04], look: [158.94, 15.95, -1.99], up: [0.36, 0.87, 0.29]},
         {t: 8, eye: [146.26, 17.56, -4.77], look: [152.13, 17.05, 1.28], up: [0.36, 0.87, 0.28]},
         {t: 9, eye: [137.26, 18.36, -9.65], look: [149.76, 17.27, 3.24], up: [0.36, 0.87, 0.28]},
         {t: 10, eye: [139.04, 18.29, -11.17], look: [149.76, 17.27, 3.24], up: [0.32, 0.87, 0.33]},
         {t: 11, eye: [140.66, 18.13, -12.26], look: [149.76, 17.27, 3.24], up: [0.28, 0.87, 0.35]},
         {t: 12, eye: [147.18, 17.66, -14.56], look: [149.76, 17.27, 3.24], up: [0.12, 0.89, 0.41]},
         {t: 13, eye: [158.05, 16.33, -12.69], look: [149.76, 17.27, 3.24], up: [-0.11, 0.91, 0.34]},
         {t: 14, eye: [150.11, 13.26, -6.69], look: [147.95, 13.50, -2.52], up: [-0.11, 0.91, 0.34]},
         {t: 15, eye: [149.27, 13.00, -3.34], look: [148.72, 13.05, -2.29], up: [-0.11, 0.91, 0.35]},
         {t: 16, eye: [152.62, 11.65, -4.87], look: [148.47, 12.08, 3.08], up: [-0.11, 0.91, 0.35]},
         {t: 17, eye: [153.35, 12.24, -1.84], look: [148.69, 12.72, 7.01], up: [-0.11, 0.91, 0.35]},
         {t: 18, eye: [156.49, 12.11, 0.74], look: [148.69, 12.72, 7.012], up: [-0.23, 0.92, 0.26]},
         {t: 19, eye: [158.52, 11.98, 5.21], look: [148.69, 12.72, 7.01], up: [-0.32, 0.92, 0.12]},
         {t: 20, eye: [158.60, 11.50, 7.91], look: [148.69, 12.72, 7.01], up: [-0.30, 0.94, 0.035]},
         {t: 21, eye: [157.60, 11.76, 11.51], look: [148.69, 12.72, 7.01], up: [-0.31, 0.93, -0.089]},
         {t: 22, eye: [152.67, 18.35, 14.29], look: [148.69, 12.72, 7.01], up: [-0.46, 0.51, -0.70]},
         {t: 23, eye: [148.79, 21.67, 11.52], look: [148.69, 12.72, 7.01], up: [-0.15, 0.036, -0.97]},
         {t: 24, eye: [147.11, 22.40, 9.07], look: [148.69, 12.72, 7.01], up: [0.38, -0.16, -0.89]},
         {t: 25, eye: [144.80, 21.92, 6.23], look: [148.69, 12.72, 7.01], up: [0.98, -0.02, 0.03]},
         {t: 26, eye: [144.11, 20.18, 2.13], look: [148.69, 12.72, 7.01], up: [0.71, 0.29, 0.62]},
         {t: 27, eye: [145.87, 17.37, -1.40], look: [148.69, 12.72, 7.01], up: [0.31, 0.60, 0.71]},
         {t: 28, eye: [144.37, 19.17, -7.33], look: [146.13, 16.27, -2.08], up: [0.31, 0.60, 0.71]},
         {t: 29, eye: [142.54, 21.91, -17.26], look: [146.89, 14.81, -4.28], up: [0.31, 0.60, 0.71]}
     ]
 });

 // Once the model has loaded, animate the
 // (default Scene's default Camera) along the CameraPath

 var cameraPathAnimation = new xeogl.CameraPathAnimation({
     cameraPath: cameraPath
 });

 gearbox.on("loaded", function () {
     cameraPathAnimation.play();
 });
 ````

 <br>
 ### Flying directly to each frame on a path

 In this example, we'll use the CameraPathAnimation's {{#crossLink "CameraPathAnimation/flyToFrame"}}{{/crossLink}} method
 to <b>fly</b> the {{#crossLink "Camera"}}{{/crossLink}} directly to each frame on the {{#crossLink "CameraPath"}}{{/crossLink}}:

 <a href="../../examples/#camera_path_flyToFrame"><img src="http://i.giphy.com/l3vQYNjsnAQwPBeYU.gif"></img></a>

 ````javascript
 var i = 0;
 var dir = 1;

 gearbox.on("loaded", function () {
     function nextFrame() {
         cameraPathAnimation.flyToFrame(i += dir, function() { setTimeout(nextFrame, 1000); });

         if (i <= 0 || i >= 29) {
             dir = -dir;
         }
     }
     nextFrame();
 });
 ````
 <br>
 ### Jumping directly to each frame on a path

 In this example, we'll use the CameraPathAnimation's {{#crossLink "CameraPathAnimation/scrubToFrame"}}{{/crossLink}} method
 to <b>jump</b> the {{#crossLink "Camera"}}{{/crossLink}} directly to each frame on the {{#crossLink "CameraPath"}}{{/crossLink}}:

 <a href="../../examples/#camera_path_scrubToFrame"><img src="http://i.giphy.com/l0Hlyqk7kewTjSBZ6.gif"></img></a>

 ````javascript
 var i = 0;
 var dir = 1;

 gearbox.on("loaded", function () {
     function nextFrame() {
         cameraPathAnimation.scrubToFrame(i += dir);

         if (i <= 0 || i >= 29) {
             dir = -dir;
         }
         setTimeout(nextFrame, 1000);
     }
     nextFrame();
 });
 ````

 @class CameraPathAnimation
 @module xeogl
 @submodule animation
 @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 CameraPathAnimation.
 @param [cfg.cameraPath] {Number|String|CameraPath} ID or instance of a {{#crossLink "CameraPath"}}{{/crossLink}} to animate the {{#crossLink "Camera"}}{{/crossLink}} along.
 Must be within the same {{#crossLink "Scene"}}{{/crossLink}} as CameraPathAnimation. .
 @extends Component
 */
xeogl.CameraPathAnimation = class xeoglCameraPathAnimation extends xeogl.Component {

    init(cfg) {

        super.init(cfg);

        this._cameraFlightAnimation = this.create({
            type: "xeogl.CameraFlightAnimation"
        });

        this._t = 0;

        this.state = xeogl.CameraPathAnimation.SCRUBBING;

        this._playingFromT = 0;
        this._playingToT = 0;
        this._playingRate = cfg.playingRate || 1.0;
        this._playingDir = 1.0;

        this.cameraPath = cfg.cameraPath;

        this._tick = this.scene.on("tick", this._updateT, this);
    }

    _updateT() {

        const cameraPath = this._attached.cameraPath;

        if (!cameraPath) {
            return;
        }

        const f = 0.002;
        //var f = 1.0;

        switch (this.state) {

            case xeogl.CameraPathAnimation.SCRUBBING:
                return;

            case xeogl.CameraPathAnimation.PLAYING:

                this._t += this._playingRate * f;

                const numFrames = this.cameraPath.frames.length;
                if (numFrames === 0 || (this._playingDir < 0 && this._t <= 0) || (this._playingDir > 0 && this._t >= this.cameraPath.frames[numFrames - 1].t)) {
                    this.state = xeogl.CameraPathAnimation.SCRUBBING;
                    this._t = this.cameraPath.frames[numFrames - 1].t;
                    return;
                }

                cameraPath.loadFrame(this._t);

                break;

            case xeogl.CameraPathAnimation.PLAYING_TO:

                let t = this._t + (this._playingRate * f * this._playingDir);

                //t = this._ease(t, this._playingFromT, this._playingToT, this._playingToT - this._playingFromT);

                if ((this._playingDir < 0 && t <= this._playingToT) || (this._playingDir > 0 && t >= this._playingToT)) {
                    t = this._playingToT;
                    this.state = xeogl.CameraPathAnimation.SCRUBBING;
                }

                this._t = t;

                cameraPath.loadFrame(this._t);

                break;
        }
    }

    // Quadratic easing out - decelerating to zero velocity
    // http://gizma.com/easing

    _ease(t, b, c, d) {
        t /= d;
        return -c * t * (t - 2) + b;
    }

    /**
     The {{#crossLink "CameraPath"}}{{/crossLink}} for this CameraPathAnimation.

     Fires a {{#crossLink "CameraPathAnimation/cameraPath:event"}}{{/crossLink}} event on change.

     @property cameraPath
     @type CameraPath
     */
    set cameraPath(value) {
        this._attach({name: "cameraPath", type: "xeogl.CameraPath", component: value, sceneDefault: false});
    }

    get cameraPath() {
        return this._attached.cameraPath;
    }

    /**
     The rate at which this CameraPathAnimation plays.

     @property rate
     @type Number
     */
    set rate(value) {
        this._playingRate = value;
    }

    get rate() {
        return this._playingRate;
    }

    /**
     * Begins playing this CameraPathAnimation from the current time.
     * @method play
     */
    play() {
        if (!this._attached.cameraPath) {
            return;
        }
        this.state = xeogl.CameraPathAnimation.PLAYING;
    }

    /**
     * Begins playing this CameraPathAnimation from the current time to the given time.
     *
     * @method playToT
     * @param {Number} t Time instant.
     */
    playToT(t) {
        const cameraPath = this._attached.cameraPath;
        if (!cameraPath) {
            return;
        }
        this._playingFromT = this._t;
        this._playingToT = t;
        this._playingDir = (this._playingToT - this._playingFromT) < 0 ? -1 : 1;
        this.state = xeogl.CameraPathAnimation.PLAYING_TO;
    }

    /**
     * Begins playing this CameraPathAnimation from the current time to the time at the given frame.
     *
     * @method playToFrame
     * @param {Number} frameIdx Index of the frame to play to.
     */
    playToFrame(frameIdx) {
        const cameraPath = this._attached.cameraPath;
        if (!cameraPath) {
            return;
        }
        const frame = cameraPath.frames[frameIdx];
        if (!frame) {
            this.error("playToFrame - frame index out of range: " + frameIdx);
            return;
        }
        const t = (1.0 / cameraPath.frames.length ) * frameIdx;
        this.playToT(t);
    }

    /**
     * Flies this CameraPathAnimation's {{#crossLink "Camera"}}{{/crossLink}} to the time at the given frame.
     *
     * @method flyToFrame
     * @param {Number} frameIdx Index of the frame to play to.
     * @param {Function} [ok] Callback to fire when playing is complete.
     */
    flyToFrame(frameIdx, ok) {
        const cameraPath = this._attached.cameraPath;
        if (!cameraPath) {
            return;
        }
        const frame = cameraPath.frames[frameIdx];
        if (!frame) {
            this.error("flyToFrame - frame index out of range: " + frameIdx);
            return;
        }
        this.state = xeogl.CameraPathAnimation.SCRUBBING;
        this._cameraFlightAnimation.flyTo(frame, ok);
    }

    /**
     * Scrubs (sets) this CameraPathAnimation to the the given time.
     *
     * @method scrubToT
     * @param {Number} t Time instant.
     */
    scrubToT(t) {
        const cameraPath = this._attached.cameraPath;
        if (!cameraPath) {
            return;
        }
        const camera = this.scene.camera;
        if (!camera) {
            return;
        }
        this._t = t;
        cameraPath.loadFrame(this._t, camera);
        this.state = xeogl.CameraPathAnimation.SCRUBBING;
    }

    /**
     * Scrubs this CameraPathAnimation to the given frame.
     *
     * @method scrubToFrame
     * @param {Number} frameIdx Index of the frame to scrub to.
     */
    scrubToFrame(frameIdx) {
        const cameraPath = this._attached.cameraPath;
        if (!cameraPath) {
            return;
        }
        const camera = this.scene.camera;
        if (!camera) {
            return;
        }
        const frame = cameraPath.frames[frameIdx];
        if (!frame) {
            this.error("playToFrame - frame index out of range: " + frameIdx);
            return;
        }
        this._t = (1.0 / cameraPath.frames.length ) * frameIdx;
        cameraPath.loadFrame(this._t, camera);
        this.state = xeogl.CameraPathAnimation.SCRUBBING;
    }

    /**
     * Stops playing this CameraPathAnimation.
     *
     * @method stop
     */
    stop() {
        this.state = xeogl.CameraPathAnimation.SCRUBBING;
    }

    destroy() {
        super.destroy();
        this.scene.off(this._tick);
    }
};

xeogl.CameraPathAnimation.STOPPED = 0;
xeogl.CameraPathAnimation.SCRUBBING = 1;
xeogl.CameraPathAnimation.PLAYING = 2;
xeogl.CameraPathAnimation.PLAYING_TO = 3;