src/viewer/scene/camera/CameraPathAnimation.js
import {Component} from "../Component.js"
import {CameraFlightAnimation} from "./CameraFlightAnimation.js"
/**
* @desc Animates the {@link Scene}'s's {@link Camera} along a {@link CameraPath}.
*
* ## Usage
*
* In the example below, we'll load a model using a {@link GLTFLoaderPlugin}, then animate a {@link Camera}
* through the frames in a {@link CameraPath}.
*
* * [[Run this example](/examples/index.html#camera_CameraPathAnimation)]
*
* ````Javascript
* import {Viewer, GLTFLoaderPlugin, CameraPath, CameraPathAnimation} from "xeokit-sdk.es.js";
*
* // Create a Viewer and arrange camera
*
* const viewer = new Viewer({
* canvasId: "myCanvas",
* transparent: true
* });
*
* viewer.camera.eye = [124.86756896972656, -93.50288391113281, 173.2632598876953];
* viewer.camera.look = [102.14186096191406, -90.24193572998047, 173.4224395751953];
* viewer.camera.up = [0.23516440391540527, 0.9719591736793518, -0.0016466031083837152];
*
* // Load model
*
* const gltfLoader = new GLTFLoaderPlugin(viewer);
*
* const model = gltfLoader.load({
* id: "myModel",
* src: "./models/gltf/modern_office/scene.gltf",
* edges: true,
* edgeThreshold: 20,
* xrayed: false
* });
*
* // Create a CameraPath
*
* var cameraPath = new CameraPath(viewer.scene, {
* frames: [
* {
* t: 0,
* eye: [124.86, -93.50, 173.26],
* look: [102.14, -90.24, 173.42],
* up: [0.23, 0.97, -0.00]
* },
* {
* t: 1,
* eye: [79.75, -85.98, 226.57],
* look: [99.24, -84.11, 238.56],
* up: [-0.14, 0.98, -0.09]
* },
* // Rest of the frames omitted for brevity
* ]
* });
*
* // Create a CameraPathAnimation to play our CameraPath
*
* var cameraPathAnimation = new CameraPathAnimation(viewer.scene, {
* cameraPath: cameraPath,
* playingRate: 0.2 // Playing 0.2 time units per second
* });
*
* // Once model loaded, start playing after a couple of seconds delay
*
* model.on("loaded", function () {
* setTimeout(function () {
* cameraPathAnimation.play(0); // Play from the beginning of the CameraPath
* }, 2000);
* });
* ````
*/
class CameraPathAnimation extends Component {
/**
* Returns "CameraPathAnimation".
*
* @private
* @returns {string} "CameraPathAnimation"
*/
get type() {
return "CameraPathAnimation"
}
/**
* @constructor
* @param {Component} [owner] Owner component. When destroyed, the owner will destroy this CameraPathAnimation as well.
* @param {*} [cfg] Configuration
* @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted.
* @param {CameraPath} [cfg.eyeCurve] A {@link CameraPath} that defines the path of a {@link Camera}.
*/
constructor(owner, cfg = {}) {
super(owner, cfg);
this._cameraFlightAnimation = new CameraFlightAnimation(this);
this._t = 0;
this.state = CameraPathAnimation.SCRUBBING;
this._playingFromT = 0;
this._playingToT = 0;
this._playingRate = cfg.playingRate || 1.0;
this._playingDir = 1.0;
this._lastTime = null;
this.cameraPath = cfg.cameraPath;
this._tick = this.scene.on("tick", this._updateT, this);
}
_updateT() {
const cameraPath = this._cameraPath;
if (!cameraPath) {
return;
}
const f = 0.002;
let numFrames;
let t;
const time = performance.now();
const elapsedSecs = (this._lastTime) ? (time - this._lastTime) * 0.001 : 0;
this._lastTime = time;
if (elapsedSecs === 0) {
return;
}
switch (this.state) {
case CameraPathAnimation.SCRUBBING:
return;
case CameraPathAnimation.PLAYING:
this._t += this._playingRate * elapsedSecs;
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 = CameraPathAnimation.SCRUBBING;
this._t = this._cameraPath.frames[numFrames - 1].t;
this.fire("stopped");
return;
}
cameraPath.loadFrame(this._t);
break;
case CameraPathAnimation.PLAYING_TO:
t = this._t + (this._playingRate * elapsedSecs * this._playingDir);
if ((this._playingDir < 0 && t <= this._playingToT) || (this._playingDir > 0 && t >= this._playingToT)) {
t = this._playingToT;
this.state = CameraPathAnimation.SCRUBBING;
this.fire("stopped");
}
this._t = t;
cameraPath.loadFrame(this._t);
break;
}
}
/*
* @private
*/
_ease(t, b, c, d) {
t /= d;
return -c * t * (t - 2) + b;
}
/**
* Sets the {@link CameraPath} animated by this CameraPathAnimation.
*
@param {CameraPath} value The new CameraPath.
*/
set cameraPath(value) {
this._cameraPath = value;
}
/**
* Gets the {@link CameraPath} animated by this CameraPathAnimation.
*
@returns {CameraPath} The CameraPath.
*/
get cameraPath() {
return this._cameraPath;
}
/**
* Sets the rate at which the CameraPathAnimation animates the {@link Camera} along the {@link CameraPath}.
*
* @param {Number} value The amount of progress per second.
*/
set rate(value) {
this._playingRate = value;
}
/**
* Gets the rate at which the CameraPathAnimation animates the {@link Camera} along the {@link CameraPath}.
*
* @returns {*|number} The current playing rate.
*/
get rate() {
return this._playingRate;
}
/**
* Begins animating the {@link Camera} along CameraPathAnimation's {@link CameraPath} from the beginning.
*/
play() {
if (!this._cameraPath) {
return;
}
this._lastTime = null;
this.state = CameraPathAnimation.PLAYING;
}
/**
* Begins animating the {@link Camera} along CameraPathAnimation's {@link CameraPath} from the given time.
*
* @param {Number} t Time instant.
*/
playToT(t) {
const cameraPath = this._cameraPath;
if (!cameraPath) {
return;
}
this._playingFromT = this._t;
this._playingToT = t;
this._playingDir = (this._playingToT - this._playingFromT) < 0 ? -1 : 1;
this._lastTime = null;
this.state = CameraPathAnimation.PLAYING_TO;
}
/**
* Animates the {@link Camera} along CameraPathAnimation's {@link CameraPath} to the given frame.
*
* @param {Number} frameIdx Index of the frame to play to.
*/
playToFrame(frameIdx) {
const cameraPath = this._cameraPath;
if (!cameraPath) {
return;
}
const frame = cameraPath.frames[frameIdx];
if (!frame) {
this.error("playToFrame - frame index out of range: " + frameIdx);
return;
}
this.playToT(frame.t);
}
/**
* Flies the {@link Camera} directly to the given frame on the CameraPathAnimation's {@link CameraPath}.
*
* @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._cameraPath;
if (!cameraPath) {
return;
}
const frame = cameraPath.frames[frameIdx];
if (!frame) {
this.error("flyToFrame - frame index out of range: " + frameIdx);
return;
}
this.state = CameraPathAnimation.SCRUBBING;
this._cameraFlightAnimation.flyTo(frame, ok);
}
/**
* Scrubs the {@link Camera} to the given time on the CameraPathAnimation's {@link CameraPath}.
*
* @param {Number} t Time instant.
*/
scrubToT(t) {
const cameraPath = this._cameraPath;
if (!cameraPath) {
return;
}
const camera = this.scene.camera;
if (!camera) {
return;
}
this._t = t;
cameraPath.loadFrame(this._t);
this.state = CameraPathAnimation.SCRUBBING;
}
/**
* Scrubs the {@link Camera} to the given frame on the CameraPathAnimation's {@link CameraPath}.
*
* @param {Number} frameIdx Index of the frame to scrub to.
*/
scrubToFrame(frameIdx) {
const cameraPath = this._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;
}
cameraPath.loadFrame(this._t);
this.state = CameraPathAnimation.SCRUBBING;
}
/**
* Stops playing this CameraPathAnimation.
*/
stop() {
this.state = CameraPathAnimation.SCRUBBING;
this.fire("stopped");
}
destroy() {
super.destroy();
this.scene.off(this._tick);
}
}
CameraPathAnimation.STOPPED = 0;
CameraPathAnimation.SCRUBBING = 1;
CameraPathAnimation.PLAYING = 2;
CameraPathAnimation.PLAYING_TO = 3;
export {CameraPathAnimation}