/*global define*/ define([ '../Core/Cartesian3', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', '../Core/Ellipsoid', '../Core/JulianDate', '../Core/Math', '../Core/Matrix3', '../Core/Transforms', '../Scene/SceneMode' ], function( Cartesian3, defaultValue, defined, defineProperties, DeveloperError, Ellipsoid, JulianDate, CesiumMath, Matrix3, Transforms, SceneMode) { "use strict"; var updateTransformMatrix3Scratch1 = new Matrix3(); var updateTransformMatrix3Scratch2 = new Matrix3(); var updateTransformMatrix3Scratch3 = new Matrix3(); var updateTransformCartesian3Scratch1 = new Cartesian3(); var updateTransformCartesian3Scratch2 = new Cartesian3(); var updateTransformCartesian3Scratch3 = new Cartesian3(); var updateTransformCartesian3Scratch4 = new Cartesian3(); var updateTransformCartesian3Scratch5 = new Cartesian3(); var updateTransformCartesian3Scratch6 = new Cartesian3(); var deltaTime = new JulianDate(); var northUpAxisFactor = 1.25; // times ellipsoid's maximum radius function updateTransform(that, camera, updateLookAt, positionProperty, time, ellipsoid) { var cartesian = positionProperty.getValue(time, that._lastCartesian); if (defined(cartesian)) { var hasBasis = false; var xBasis; var yBasis; var zBasis; // The time delta was determined based on how fast satellites move compared to vehicles near the surface. // Slower moving vehicles will most likely default to east-north-up, while faster ones will be VVLH. deltaTime = JulianDate.addSeconds(time, 0.001, deltaTime); var deltaCartesian = positionProperty.getValue(deltaTime, updateTransformCartesian3Scratch1); if (defined(deltaCartesian)) { var toInertial = Transforms.computeFixedToIcrfMatrix(time, updateTransformMatrix3Scratch1); var toInertialDelta = Transforms.computeFixedToIcrfMatrix(deltaTime, updateTransformMatrix3Scratch2); var toFixed; if (!defined(toInertial) || !defined(toInertialDelta)) { toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, updateTransformMatrix3Scratch3); toInertial = Matrix3.transpose(toFixed, updateTransformMatrix3Scratch1); toInertialDelta = Transforms.computeTemeToPseudoFixedMatrix(deltaTime, updateTransformMatrix3Scratch2); Matrix3.transpose(toInertialDelta, toInertialDelta); } else { toFixed = Matrix3.transpose(toInertial, updateTransformMatrix3Scratch3); } var inertialCartesian = Matrix3.multiplyByVector(toInertial, cartesian, updateTransformCartesian3Scratch5); var inertialDeltaCartesian = Matrix3.multiplyByVector(toInertialDelta, deltaCartesian, updateTransformCartesian3Scratch6); Cartesian3.subtract(inertialCartesian, inertialDeltaCartesian, updateTransformCartesian3Scratch4); var inertialVelocity = Cartesian3.magnitude(updateTransformCartesian3Scratch4) * 1000.0; // meters/sec // http://en.wikipedia.org/wiki/Standard_gravitational_parameter // Consider adding this to Cesium.Ellipsoid? var mu = 3.986004418e14; // m^3 / sec^2 var semiMajorAxis = -mu / (inertialVelocity * inertialVelocity - (2 * mu / Cartesian3.magnitude(inertialCartesian))); if (semiMajorAxis < 0 || semiMajorAxis > northUpAxisFactor * ellipsoid.maximumRadius) { // North-up viewing from deep space. // X along the nadir xBasis = updateTransformCartesian3Scratch2; Cartesian3.normalize(cartesian, xBasis); Cartesian3.negate(xBasis, xBasis); // Z is North zBasis = Cartesian3.clone(Cartesian3.UNIT_Z, updateTransformCartesian3Scratch3); // Y is along the cross of z and x (right handed basis / in the direction of motion) yBasis = Cartesian3.cross(zBasis, xBasis, updateTransformCartesian3Scratch1); if (Cartesian3.magnitude(yBasis) > CesiumMath.EPSILON7) { Cartesian3.normalize(xBasis, xBasis); Cartesian3.normalize(yBasis, yBasis); zBasis = Cartesian3.cross(xBasis, yBasis, updateTransformCartesian3Scratch3); Cartesian3.normalize(zBasis, zBasis); hasBasis = true; } } else if (!Cartesian3.equalsEpsilon(cartesian, deltaCartesian, CesiumMath.EPSILON7)) { // Approximation of VVLH (Vehicle Velocity Local Horizontal) with the Z-axis flipped. // Z along the position zBasis = updateTransformCartesian3Scratch2; Cartesian3.normalize(inertialCartesian, zBasis); Cartesian3.normalize(inertialDeltaCartesian, inertialDeltaCartesian); // Y is along the angular momentum vector (e.g. "orbit normal") yBasis = Cartesian3.cross(zBasis, inertialDeltaCartesian, updateTransformCartesian3Scratch3); if (!Cartesian3.equalsEpsilon(yBasis, Cartesian3.ZERO, CesiumMath.EPSILON7)) { // X is along the cross of y and z (right handed basis / in the direction of motion) xBasis = Cartesian3.cross(yBasis, zBasis, updateTransformCartesian3Scratch1); Matrix3.multiplyByVector(toFixed, xBasis, xBasis); Matrix3.multiplyByVector(toFixed, yBasis, yBasis); Matrix3.multiplyByVector(toFixed, zBasis, zBasis); Cartesian3.normalize(xBasis, xBasis); Cartesian3.normalize(yBasis, yBasis); Cartesian3.normalize(zBasis, zBasis); hasBasis = true; } } } if (hasBasis) { var transform = camera.transform; transform[0] = xBasis.x; transform[1] = xBasis.y; transform[2] = xBasis.z; transform[3] = 0.0; transform[4] = yBasis.x; transform[5] = yBasis.y; transform[6] = yBasis.z; transform[7] = 0.0; transform[8] = zBasis.x; transform[9] = zBasis.y; transform[10] = zBasis.z; transform[11] = 0.0; transform[12] = cartesian.x; transform[13] = cartesian.y; transform[14] = cartesian.z; transform[15] = 0.0; } else { // Stationary or slow-moving, low-altitude objects use East-North-Up. Transforms.eastNorthUpToFixedFrame(cartesian, ellipsoid, camera.transform); } } if (updateLookAt) { if (that.scene.mode === SceneMode.SCENE2D) { camera.lookAt(that._offset2D, Cartesian3.ZERO, that._up2D); } else { camera.lookAt(that._offset3D, Cartesian3.ZERO, that._up3D); } } } var offset3DCrossScratch = new Cartesian3(); /** * A utility object for tracking an entity with the camera. * @alias EntityView * @constructor * * @param {Entity} entity The entity to track with the camera. * @param {Scene} scene The scene to use. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to use for orienting the camera. */ var EntityView = function(entity, scene, ellipsoid) { /** * The entity to track with the camera. * @type {Entity} */ this.entity = entity; /** * The scene in which to track the object. * @type {Scene} */ this.scene = scene; /** * The ellipsoid to use for orienting the camera. * @type {Ellipsoid} */ this.ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); //Shadow copies of the objects so we can detect changes. this._lastEntity = undefined; this._mode = undefined; //Re-usable objects to be used for retrieving position. this._lastCartesian = new Cartesian3(); this._offset3D = new Cartesian3(); this._up3D = new Cartesian3(); this._offset2D = new Cartesian3(); this._up2D = new Cartesian3(); }; // STATIC properties defined here, not per-instance. defineProperties(EntityView, { /** * Gets or sets a camera offset that will be used to * initialize subsequent EntityViews. * @memberof EntityView * @type {Cartesian3} */ defaultOffset3D : { get : function() { return this._defaultOffset3D; }, set : function(vector) { this._defaultOffset3D = Cartesian3.clone(vector, new Cartesian3()); this._defaultUp3D = Cartesian3.cross(this._defaultOffset3D, Cartesian3.cross(Cartesian3.UNIT_Z, this._defaultOffset3D, offset3DCrossScratch), new Cartesian3()); Cartesian3.normalize(this._defaultUp3D, this._defaultUp3D); this._defaultOffset2D = new Cartesian3(0.0, 0.0, Cartesian3.magnitude(this._defaultOffset3D)); this._defaultUp2D = Cartesian3.clone(Cartesian3.UNIT_Y); } } }); // Initialize the static property. EntityView.defaultOffset3D = new Cartesian3(-14000, 3500, 3500); /** * Should be called each animation frame to update the camera * to the latest settings. * @param {JulianDate} time The current animation time. * */ EntityView.prototype.update = function(time) { var scene = this.scene; var entity = this.entity; var ellipsoid = this.ellipsoid; //>>includeStart('debug', pragmas.debug); if (!defined(time)) { throw new DeveloperError('time is required.'); } if (!defined(scene)) { throw new DeveloperError('EntityView.scene is required.'); } if (!defined(entity)) { throw new DeveloperError('EntityView.entity is required.'); } if (!defined(ellipsoid)) { throw new DeveloperError('EntityView.ellipsoid is required.'); } if (!defined(entity.position)) { throw new DeveloperError('entity.position is required.'); } //>>includeEnd('debug'); var positionProperty = entity.position; var objectChanged = entity !== this._lastEntity; var sceneModeChanged = scene.mode !== this._mode && scene.mode !== SceneMode.MORPHING; var offset3D = this._offset3D; var up3D = this._up3D; var offset2D = this._offset2D; var up2D = this._up2D; var camera = scene.camera; if (objectChanged) { var viewFromProperty = entity.viewFrom; if (!defined(viewFromProperty) || !defined(viewFromProperty.getValue(time, offset3D))) { Cartesian3.clone(EntityView._defaultOffset2D, offset2D); Cartesian3.clone(EntityView._defaultUp2D, up2D); Cartesian3.clone(EntityView._defaultOffset3D, offset3D); Cartesian3.clone(EntityView._defaultUp3D, up3D); } else { Cartesian3.cross(Cartesian3.UNIT_Z, offset3D, up3D); Cartesian3.cross(offset3D, up3D, up3D); Cartesian3.normalize(up3D, up3D); var mag = Cartesian3.magnitude(offset3D); Cartesian3.fromElements(0.0, 0.0, mag, offset2D); Cartesian3.clone(this._defaultUp2D, up2D); } } else if (!sceneModeChanged && scene.mode !== SceneMode.MORPHING) { if (this._mode === SceneMode.SCENE2D) { var distance = Math.max(camera.frustum.right - camera.frustum.left, camera.frustum.top - camera.frustum.bottom); Cartesian3.fromElements(0.0, 0.0, distance, offset2D); Cartesian3.clone(camera.up, up2D); } else if (this._mode === SceneMode.SCENE3D || this._mode === SceneMode.COLUMBUS_VIEW) { Cartesian3.clone(camera.position, offset3D); Cartesian3.clone(camera.up, up3D); } } var updateLookAt = objectChanged || sceneModeChanged; this._lastEntity = entity; this._mode = scene.mode !== SceneMode.MORPHING ? scene.mode : this._mode; if (scene.mode !== SceneMode.MORPHING) { updateTransform(this, camera, updateLookAt, positionProperty, time, ellipsoid); } }; return EntityView; });