API Docs for: 0.7.0

File: 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
 */
(function () {

    "use strict";

    xeogl.CameraPathAnimation = xeogl.Component.extend({

        /**
         JavaScript class name for this Component.

         @property type
         @type String
         @final
         */
        type: "xeogl.CameraPathAnimation",

        _init: function (cfg) {

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

            this._t = 0;

            this.state = this.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: function () {

            var cameraPath = this._attached.cameraPath;

            if (!cameraPath) {
                return;
            }

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

            switch (this.state) {

                case this.SCRUBBING:
                    return;

                case this.PLAYING:

                    this._t += this._playingRate * f;

                    var 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 = this.SCRUBBING;
                        this._t = this.cameraPath.frames[numFrames-1].t;
                        return;
                    }

                    cameraPath.loadFrame(this._t);

                    break;

                case this.PLAYING_TO:

                    var 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 = this.SCRUBBING;
                    }

                    this._t = t;

                    cameraPath.loadFrame(this._t);

                    break;
            }
        },

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

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

        STOPPED: 0,
        SCRUBBING: 1,
        PLAYING: 2,
        PLAYING_TO: 3,

        _props: {

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

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

             @property cameraPath
             @type CameraPath
             */
            cameraPath: {

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

                get: function () {
                    return this._attached.cameraPath;
                }
            },

            /**
             The rate at which this CameraPathAnimation plays.

             @property rate
             @type Number
             */
            rate: {

                set: function (value) {
                    this._playingRate = value;
                },

                get: function () {
                    return this._playingRate;
                }
            }
        },

        /**
         * Begins playing this CameraPathAnimation from the current time.
         * @method play
         */
        play: function () {

            if (!this._attached.cameraPath) {
                return;
            }

            this.state = this.PLAYING;
        },

        /**
         * Begins playing this CameraPathAnimation from the current time to the given time.
         *
         * @method playToT
         * @param {Number} t Time instant.
         */
        playToT: function (t) {

            var 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 = this.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: function (frameIdx) {

            var cameraPath = this._attached.cameraPath;

            if (!cameraPath) {
                return;
            }

            var frame = cameraPath.frames[frameIdx];

            if (!frame) {
                this.error("playToFrame - frame index out of range: " + frameIdx);
                return;
            }

            var 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: function (frameIdx, ok) {

            var cameraPath = this._attached.cameraPath;

            if (!cameraPath) {
                return;
            }

            var frame = cameraPath.frames[frameIdx];

            if (!frame) {
                this.error("flyToFrame - frame index out of range: " + frameIdx);
                return;
            }

            this.state = this.SCRUBBING;
            this._cameraFlightAnimation.flyTo(frame, ok);
        },

        /**
         * Scrubs (sets) this CameraPathAnimation to the the given time.
         *
         * @method scrubToT
         * @param {Number} t Time instant.
         */
        scrubToT: function (t) {

            var cameraPath = this._attached.cameraPath;

            if (!cameraPath) {
                return;
            }

            var camera = this.scene.camera;

            if (!camera) {
                return;
            }

            this._t = t;

            cameraPath.loadFrame(this._t, camera);

            this.state = this.SCRUBBING;
        },

        /**
         * Scrubs this CameraPathAnimation to the given frame.
         *
         * @method scrubToFrame
         * @param {Number} frameIdx Index of the frame to scrub to.
         */
        scrubToFrame: function (frameIdx) {

            var cameraPath = this._attached.cameraPath;

            if (!cameraPath) {
                return;
            }

            var camera = this.scene.camera;

            if (!camera) {
                return;
            }

            var 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 = this.SCRUBBING;
        },

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

        _destroy: function () {
            this.scene.off(this._tick);
        }
    });

})();