- /**
- A **CameraFlightAnimation** jumps or flies the {{#crossLink "Scene"}}Scene's{{/crossLink}} {{#crossLink "Camera"}}{{/crossLink}} to look at a given target.
-
- <a href="../../examples/#animation_camera_flight"><img src="http://i.giphy.com/3o7TKP0jN800EQ99EQ.gif"></img></a>
-
- * TODO: Document behaviour for ortho projection
- * TODO: Update docs for camera refactor, where ortho and perspective components will always be present on camera
-
- ## Overview
-
- * Can be made to either fly or jump to its target.
- * While busy flying to a target, it can be stopped, or redirected to fly to a different target.
-
- A CameraFlightAnimation's target can be:
-
- * specific ````eye````, ````look```` and ````up```` positions,
- * an axis-aligned World-space bounding box (AABB), or
- * an instance or ID of any {{#crossLink "Component"}}{{/crossLink}} subtype that provides a World-space AABB.
-
- You can configure its {{#crossLink "CameraFlightAnimation/fit:property"}}{{/crossLink}}
- and {{#crossLink "CameraFlightAnimation/fitFOV:property"}}{{/crossLink}} properties to make it stop at the point where the target
- occupies a certain amount of the field-of-view.
-
- ## Examples
-
- * [Flying to random meshes](../../examples/#animation_camera_flight)
-
- ## Flying to a Mesh
-
- Flying to a {{#crossLink "Mesh"}}{{/crossLink}}:
-
- ````Javascript
- // Create a CameraFlightAnimation that takes one second to fly
- // the default Scene's Camera to each specified target
- var cameraFlight = new xeogl.CameraFlightAnimation({
- fit: true, // Default
- fitFOV: 45, // Default, degrees
- duration: 1 // Default, seconds
- }, function() {
- // Arrived
- });
-
- // Create a Mesh, which gets all the default components
- var mesh = new Mesh();
-
- // Fly to the Mesh's World-space AABB
- cameraFlight.flyTo(mesh);
- ````
- ## Flying to a position
-
- Flying the CameraFlightAnimation from the previous example to specified eye, look and up positions:
-
- ````Javascript
- cameraFlight.flyTo({
- eye: [-5,-5,-5],
- look: [0,0,0]
- up: [0,1,0],
- duration: 1 // Default, seconds
- }, function() {
- // Arrived
- });
- ````
-
- ## Flying to an AABB
-
- Flying the CameraFlightAnimation from the previous two examples explicitly to the {{#crossLink "Boundary3D"}}Boundary3D's{{/crossLink}}
- axis-aligned bounding box:
-
- ````Javascript
- cameraFlight.flyTo(mesh.aabb);
- ````
-
- @class CameraFlightAnimation
- @module xeogl
- @submodule animation
- @constructor
- @param [owner] {Component} Owner component. When destroyed, the owner will destroy this component as well. Creates this component within the default {{#crossLink "Scene"}}{{/crossLink}} when omitted.
- @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 CameraFlightAnimation.
- @param [cfg.fit=true] {Boolean} When true, will ensure that when this CameraFlightAnimation has flown or jumped to a boundary
- it will adjust the distance between the {{#crossLink "Camera"}}{{/crossLink}}'s {{#crossLink "Lookat/eye:property"}}eye{{/crossLink}}
- and {{#crossLink "Lookat/look:property"}}{{/crossLink}} position so as to ensure that the target boundary is filling the view volume.
- @param [cfg.fitFOV=45] {Number} How much field-of-view, in degrees, that a target boundary should
- fill the canvas when fitting the {{#crossLink "Camera"}}Camera{{/crossLink}} to the target boundary. Only applies when the {{#crossLink "Camera"}}Camera{{/crossLink}}'s active projection is a{{#crossLink "Perspective"}}{{/crossLink}}.
- @param [cfg.trail] {Boolean} When true, will cause this CameraFlightAnimation to point the {{#crossLink "Camera"}}{{/crossLink}} in the direction that it is travelling.
- @param [cfg.duration=1] {Number} Flight duration, in seconds, when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}CameraFlightAnimation#flyTo(){{/crossLink}}.
- @extends Component
- */
-
- import {math} from '../math/math.js';
- import {utils} from '../utils.js';
- import {tasks} from '../tasks.js';
- import {Component} from '../component.js';
- import {Mesh} from '../objects/mesh.js';
- import {AABBGeometry} from '../geometry/aabbGeometry.js';
- import {PhongMaterial} from '../materials/phongMaterial.js';
- import {componentClasses} from "./../componentClasses.js";
-
- const type = "xeogl.CameraFlightAnimation";
-
- const tempVec3 = math.vec3();
- const newLook = math.vec3();
- const newEye = math.vec3();
- const newUp = math.vec3();
- const newLookEyeVec = math.vec3();
- const lookEyeVec = math.vec3();
-
- class CameraFlightAnimation extends Component {
-
- /**
- JavaScript class name for this Component.
-
- For example: "xeogl.AmbientLight", "xeogl.MetallicMaterial" etc.
-
- @property type
- @type String
- @final
- */
- get type() {
- return type;
- }
-
- init(cfg) {
-
- super.init(cfg);
-
- this._aabbHelper = new Mesh(this, { // Shows a wireframe box for target AABBs
- geometry: new AABBGeometry(this),
- material: new PhongMaterial(this, {
- diffuse: [0, 0, 0],
- ambient: [0, 0, 0],
- specular: [0, 0, 0],
- emissive: [0.5, 1.0, 0.5],
- lineWidth: 2
- }),
- visible: false,
- collidable: false
- });
-
- this._look1 = math.vec3();
- this._eye1 = math.vec3();
- this._up1 = math.vec3();
- this._look2 = math.vec3();
- this._eye2 = math.vec3();
- this._up2 = math.vec3();
- this._orthoScale1 = 1;
- this._orthoScale2 = 1;
- this._flying = false;
- this._flyEyeLookUp = false;
- this._flyingEye = false;
- this._flyingLook = false;
- this._callback = null;
- this._callbackScope = null;
- this._time1 = null;
- this._time2 = null;
- this.easing = cfg.easing !== false;
-
- this.duration = cfg.duration;
- this.fit = cfg.fit;
- this.fitFOV = cfg.fitFOV;
- this.trail = cfg.trail;
- }
-
- /**
- * Begins flying the {{#crossLink "Camera"}}{{/crossLink}}'s {{#crossLink "Camera"}}{{/crossLink}} to the given target.
- *
- * * When the target is a boundary, the {{#crossLink "Camera"}}{{/crossLink}} will fly towards the target
- * and stop when the target fills most of the canvas.
- * * When the target is an explicit {{#crossLink "Camera"}}{{/crossLink}} position, given as ````eye````, ````look```` and ````up````
- * vectors, then this CameraFlightAnimation will interpolate the {{#crossLink "Camera"}}{{/crossLink}} to that target and stop there.
- * @method flyTo
- * @param [params=scene] {*|Component} Either a parameters object or a {{#crossLink "Component"}}{{/crossLink}} subtype that has an AABB.
- * @param[params.arc=0] {Number} Factor in range [0..1] indicating how much the
- * {{#crossLink "Lookat/eye:property"}}Camera's eye{{/crossLink}} position will
- * swing away from its {{#crossLink "Lookat/eye:property"}}look{{/crossLink}} position as it flies to the target.
- * @param [params.component] {Number|String|Component} ID or instance of a component to fly to. Defaults to the entire {{#crossLink "Scene"}}{{/crossLink}}.
- * @param [params.aabb] {*} World-space axis-aligned bounding box (AABB) target to fly to.
- * @param [params.eye] {Float32Array} Position to fly the eye position to.
- * @param [params.look] {Float32Array} Position to fly the look position to.
- * @param [params.up] {Float32Array} Position to fly the up vector to.
- * @param [params.fit=true] {Boolean} Whether to fit the target to the view volume. Overrides {{#crossLink "CameraFlightAnimation/fit:property"}}{{/crossLink}}.
- * @param [params.fitFOV] {Number} How much of field-of-view, in degrees, that a target {{#crossLink "Object"}}{{/crossLink}} or its AABB should
- * fill the canvas on arrival. Overrides {{#crossLink "CameraFlightAnimation/fitFOV:property"}}{{/crossLink}}.
- * @param [params.duration] {Number} Flight duration in seconds. Overrides {{#crossLink "CameraFlightAnimation/duration:property"}}{{/crossLink}}.
- * @param [params.orthoScale] {Number} TODO: document this
- * @param [callback] {Function} Callback fired on arrival
- * @param [scope] {Object} Optional scope for callback
- */
- flyTo(params, callback, scope) {
-
- params = params || this.scene;
-
- if (this._flying) {
- this.stop();
- }
-
- this._flying = false;
-
- this._callback = callback;
- this._callbackScope = scope;
-
- const camera = this.scene.camera;
-
- this._eye1[0] = camera.eye[0];
- this._eye1[1] = camera.eye[1];
- this._eye1[2] = camera.eye[2];
-
- this._look1[0] = camera.look[0];
- this._look1[1] = camera.look[1];
- this._look1[2] = camera.look[2];
-
- this._up1[0] = camera.up[0];
- this._up1[1] = camera.up[1];
- this._up1[2] = camera.up[2];
-
- this._orthoScale1 = camera.ortho.scale;
- this._orthoScale2 = params.orthoScale || this._orthoScale1;
-
- let aabb;
- let eye;
- let look;
- let up;
- let componentId;
-
- if (params.aabb) {
- aabb = params.aabb;
-
- } else if (params.length === 6) {
- aabb = params;
-
- } else if ((params.eye && params.look) || params.up) {
- eye = params.eye;
- look = params.look;
- up = params.up;
-
- } else if (params.eye) {
- eye = params.eye;
-
- } else if (params.look) {
- look = params.look;
-
- } else { // Argument must be an instance or ID of a Component (subtype)
-
- let component = params;
- if (utils.isNumeric(component) || utils.isString(component)) {
- componentId = component;
- component = this.scene.components[componentId];
- if (!component) {
- this.error("Component not found: " + utils.inQuotes(componentId));
- if (callback) {
- if (scope) {
- callback.call(scope);
- } else {
- callback();
- }
- }
- return;
- }
- }
- aabb = component.aabb || this.scene.aabb;
- }
-
- const poi = params.poi;
-
- if (aabb) {
- if (aabb[3] < aabb[0] || aabb[4] < aabb[1] || aabb[5] < aabb[2]) { // Don't fly to an inverted boundary
- return;
- }
- if (aabb[3] === aabb[0] && aabb[4] === aabb[1] && aabb[5] === aabb[2]) { // Don't fly to an empty boundary
- return;
- }
- if (params.showAABB !== false) { // Show boundary
- this._aabbHelper.geometry.targetAABB = aabb;
- //this._aabbHelper.visible = true;
- }
-
- aabb = aabb.slice();
- const aabbCenter = math.getAABB3Center(aabb);
-
- this._look2 = poi || aabbCenter;
-
- const eyeLookVec = math.subVec3(this._eye1, this._look1, tempVec3);
- const eyeLookVecNorm = math.normalizeVec3(eyeLookVec);
- const diag = poi ? math.getAABB3DiagPoint(aabb, poi) : math.getAABB3Diag(aabb);
- const fitFOV = params.fitFOV || this._fitFOV;
- const sca = Math.abs(diag / Math.tan(fitFOV * math.DEGTORAD));
-
- this._orthoScale2 = diag * 1.1;
-
- this._eye2[0] = this._look2[0] + (eyeLookVecNorm[0] * sca);
- this._eye2[1] = this._look2[1] + (eyeLookVecNorm[1] * sca);
- this._eye2[2] = this._look2[2] + (eyeLookVecNorm[2] * sca);
-
- this._up2[0] = this._up1[0];
- this._up2[1] = this._up1[1];
- this._up2[2] = this._up1[2];
-
- this._flyEyeLookUp = false;
-
- } else if (eye || look || up) {
-
- this._flyEyeLookUp = !!eye && !!look && !!up;
- this._flyingEye = !!eye && !look;
- this._flyingLook = !!look && !eye;
-
- if (look) {
- this._look2[0] = look[0];
- this._look2[1] = look[1];
- this._look2[2] = look[2];
- }
-
- if (eye) {
- this._eye2[0] = eye[0];
- this._eye2[1] = eye[1];
- this._eye2[2] = eye[2];
- }
-
- if (up) {
- this._up2[0] = up[0];
- this._up2[1] = up[1];
- this._up2[2] = up[2];
- }
- }
-
- this.fire("started", params, true);
-
- this._time1 = Date.now();
- this._time2 = this._time1 + (params.duration ? params.duration * 1000 : this._duration);
-
- this._flying = true; // False as soon as we stop
-
- tasks.scheduleTask(this._update, this);
- }
-
- /**
- * Jumps the {{#crossLink "Camera"}}{{/crossLink}}'s {{#crossLink "Camera"}}{{/crossLink}} to the given target.
- *
- * * When the target is a boundary, this CameraFlightAnimation will position the {{#crossLink "Camera"}}{{/crossLink}}
- * at where the target fills most of the canvas.
- * * When the target is an explicit {{#crossLink "Camera"}}{{/crossLink}} position, given as ````eye````, ````look```` and ````up````
- * vectors, then this CameraFlightAnimation will jump the {{#crossLink "Camera"}}{{/crossLink}} to that target.
- *
- * @method flyTo
- * @param params {*|Component} Either a parameters object or a {{#crossLink "Component"}}{{/crossLink}} subtype that has a World-space AABB.
- * @param[params.arc=0] {Number} Factor in range [0..1] indicating how much the
- * {{#crossLink "Camera/eye:property"}}Camera's eye{{/crossLink}} position will
- * swing away from its {{#crossLink "Camera/eye:property"}}look{{/crossLink}} position as it flies to the target.
- * @param [params.component] {Number|String|Component} ID or instance of a component to fly to.
- * @param [params.aabb] {*} World-space axis-aligned bounding box (AABB) target to fly to.
- * @param [params.eye] {Float32Array} Position to fly the eye position to.
- * @param [params.look] {Float32Array} Position to fly the look position to.
- * @param [params.up] {Float32Array} Position to fly the up vector to.
- * @param [params.fitFOV] {Number} How much of field-of-view, in degrees, that a target {{#crossLink "Object"}}{{/crossLink}} or its AABB should
- * fill the canvas on arrival. Overrides {{#crossLink "CameraFlightAnimation/fitFOV:property"}}{{/crossLink}}.
- * @param [params.fit] {Boolean} Whether to fit the target to the view volume. Overrides {{#crossLink "CameraFlightAnimation/fit:property"}}{{/crossLink}}.
- */
- jumpTo(params) {
- this._jumpTo(params);
- }
-
- _jumpTo(params) {
-
- if (this._flying) {
- this.stop();
- }
-
- const camera = this.scene.camera;
-
- var aabb;
- var componentId;
- var newEye;
- var newLook;
- var newUp;
-
- if (params.aabb) { // Boundary3D
- aabb = params.aabb;
-
- } else if (params.length === 6) { // AABB
- aabb = params;
-
- } else if (params.eye || params.look || params.up) { // Camera pose
- newEye = params.eye;
- newLook = params.look;
- newUp = params.up;
-
- } else { // Argument must be an instance or ID of a Component (subtype)
-
- let component = params;
-
- if (utils.isNumeric(component) || utils.isString(component)) {
- componentId = component;
- component = this.scene.components[componentId];
- if (!component) {
- this.error("Component not found: " + utils.inQuotes(componentId));
- return;
- }
- }
- aabb = component.aabb || this.scene.aabb;
- }
-
- const poi = params.poi;
-
- if (aabb) {
-
- if (aabb[3] <= aabb[0] || aabb[4] <= aabb[1] || aabb[5] <= aabb[2]) { // Don't fly to an empty boundary
- return;
- }
-
- var diag = poi ? math.getAABB3DiagPoint(aabb, poi) : math.getAABB3Diag(aabb);
-
- newLook = poi || math.getAABB3Center(aabb, newLook);
-
- if (this._trail) {
- math.subVec3(camera.look, newLook, newLookEyeVec);
- } else {
- math.subVec3(camera.eye, camera.look, newLookEyeVec);
- }
-
- math.normalizeVec3(newLookEyeVec);
- let dist;
- const fit = (params.fit !== undefined) ? params.fit : this._fit;
-
- if (fit) {
- dist = Math.abs((diag) / Math.tan((params.fitFOV || this._fitFOV) * math.DEGTORAD));
-
- } else {
- dist = math.lenVec3(math.subVec3(camera.eye, camera.look, tempVec3));
- }
-
- math.mulVec3Scalar(newLookEyeVec, dist);
-
- camera.eye = math.addVec3(newLook, newLookEyeVec, tempVec3);
- camera.look = newLook;
-
- } else if (newEye || newLook || newUp) {
-
- if (newEye) {
- camera.eye = newEye;
- }
- if (newLook) {
- camera.look = newLook;
- }
- if (newUp) {
- camera.up = newUp;
- }
- }
- }
-
- _update() {
- if (!this._flying) {
- return;
- }
- const time = Date.now();
- let t = (time - this._time1) / (this._time2 - this._time1);
- const stopping = (t >= 1);
- if (t > 1) {
- t = 1;
- }
- t = this.easing ? this._ease(t, 0, 1, 1) : t;
- const camera = this.scene.camera;
- if (this._flyingEye || this._flyingLook) {
- if (this._flyingEye) {
- math.subVec3(camera.eye, camera.look, newLookEyeVec);
- camera.eye = math.lerpVec3(t, 0, 1, this._eye1, this._eye2, newEye);
- camera.look = math.subVec3(newEye, newLookEyeVec, newLook);
- } else if (this._flyingLook) {
- camera.look = math.lerpVec3(t, 0, 1, this._look1, this._look2, newLook);
- // camera.eye = math.addVec3(newLook, newLookEyeVec, newEye);
- camera.up = math.lerpVec3(t, 0, 1, this._up1, this._up2, newUp);
- }
- } else if (this._flyEyeLookUp) {
- camera.eye = math.lerpVec3(t, 0, 1, this._eye1, this._eye2, newEye);
- camera.look = math.lerpVec3(t, 0, 1, this._look1, this._look2, newLook);
- camera.up = math.lerpVec3(t, 0, 1, this._up1, this._up2, newUp);
- } else {
- math.lerpVec3(t, 0, 1, this._look1, this._look2, newLook);
- let dist;
- if (this._trail) {
- math.subVec3(newLook, camera.look, newLookEyeVec);
- } else {
- math.subVec3(camera.eye, camera.look, newLookEyeVec);
- }
- math.normalizeVec3(newLookEyeVec);
- math.lerpVec3(t, 0, 1, this._eye1, this._eye2, newEye);
- math.subVec3(newEye, newLook, lookEyeVec);
- dist = math.lenVec3(lookEyeVec);
- math.mulVec3Scalar(newLookEyeVec, dist);
- camera.eye = math.addVec3(newLook, newLookEyeVec, newEye);
- camera.look = newLook;
- }
- this.scene.camera.ortho.scale = this._orthoScale1 + (t * (this._orthoScale2 - this._orthoScale1));
- if (stopping) {
- this.stop();
- return;
- }
- tasks.scheduleTask(this._update, this); // Keep flying
- }
-
- _ease(t, b, c, d) { // Quadratic easing out - decelerating to zero velocity http://gizma.com/easing
- t /= d;
- return -c * t * (t - 2) + b;
- }
-
- /**
- * Stops an earlier flyTo, fires arrival callback.
- * @method stop
- */
- stop() {
- if (!this._flying) {
- return;
- }
- this._aabbHelper.visible = false;
- this._flying = false;
- this._time1 = null;
- this._time2 = null;
- const callback = this._callback;
- if (callback) {
- this._callback = null;
- if (this._callbackScope) {
- callback.call(this._callbackScope);
- } else {
- callback();
- }
- }
- this.fire("stopped", true, true);
- }
-
- /**
- * Cancels an earlier flyTo without calling the arrival callback.
- * @method cancel
- */
- cancel() {
- if (!this._flying) {
- return;
- }
- this._aabbHelper.visible = false;
- this._flying = false;
- this._time1 = null;
- this._time2 = null;
- if (this._callback) {
- this._callback = null;
- }
- this.fire("canceled", true, true);
- }
-
- /**
- * Flight duration, in seconds, when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}CameraFlightAnimation#flyTo(){{/crossLink}}.
- *
- * Stops any flight currently in progress.
- *
- * @property duration
- * @default 0.5
- * @type Number
- */
- set duration(value) {
- this._duration = value ? (value * 1000.0) : 500;
- this.stop();
- }
-
- get duration() {
- return this._duration / 1000.0;
- }
-
- /**
- * When true, will ensure that this CameraFlightAnimation is flying to a boundary it will always adjust the distance between the
- * {{#crossLink "CameraFlightAnimation/camera:property"}}camera{{/crossLink}}'s {{#crossLink "Lookat/eye:property"}}eye{{/crossLink}}
- * and {{#crossLink "Lookat/look:property"}}{{/crossLink}}
- * so as to ensure that the target boundary is always filling the view volume.
- *
- * When false, the eye will remain at its current distance from the look position.
- *
- * @property fit
- * @type Boolean
- * @default true
- */
- set fit(value) {
- this._fit = value !== false;
- }
-
- get fit() {
- return this._fit;
- }
-
-
- /**
- * How much of the perspective field-of-view, in degrees, that a target {{#crossLink "Object"}}{{/crossLink}} or its AABB should
- * fill the canvas when calling {{#crossLink "CameraFlightAnimation/flyTo:method"}}CameraFlightAnimation#jumpTo(){{/crossLink}} or {{#crossLink "CameraFlightAnimation/jumpTo:method"}}{{/crossLink}}.
- *
- * @property fitFOV
- * @default 45
- * @type Number
- */
- set fitFOV(value) {
- this._fitFOV = value || 45;
- }
-
- get fitFOV() {
- return this._fitFOV;
- }
-
- /**
- * When true, will cause this CameraFlightAnimation to point the {{#crossLink "CameraFlightAnimation/camera:property"}}{{/crossLink}}
- * in the direction that it is travelling.
- *
- * @property trail
- * @type Boolean
- * @default false
- */
- set trail(value) {
- this._trail = !!value;
- }
-
- get trail() {
- return this._trail;
- }
- }
-
- componentClasses[type] = CameraFlightAnimation;
-
- export {CameraFlightAnimation};
-
-