/*global define*/ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', '../Core/Cartographic', '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', '../Core/DeveloperError', '../Core/EasingFunction', '../Core/Ellipsoid', '../Core/IntersectionTests', '../Core/Math', '../Core/Matrix3', '../Core/Matrix4', '../Core/Quaternion', '../Core/Ray', '../Core/Rectangle', '../Core/Transforms', './CameraFlightPath', './PerspectiveFrustum', './SceneMode' ], function( Cartesian2, Cartesian3, Cartesian4, Cartographic, defaultValue, defined, defineProperties, DeveloperError, EasingFunction, Ellipsoid, IntersectionTests, CesiumMath, Matrix3, Matrix4, Quaternion, Ray, Rectangle, Transforms, CameraFlightPath, PerspectiveFrustum, SceneMode) { "use strict"; /** * The camera is defined by a position, orientation, and view frustum. * <br /><br /> * The orientation forms an orthonormal basis with a view, up and right = view x up unit vectors. * <br /><br /> * The viewing frustum is defined by 6 planes. * Each plane is represented by a {@link Cartesian4} object, where the x, y, and z components * define the unit vector normal to the plane, and the w component is the distance of the * plane from the origin/camera position. * * @alias Camera * * @constructor * * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Camera.html|Cesium Sandcastle Camera Demo} * @demo {@link http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=Camera%20Tutorial.html">Sandcastle Example</a> from the <a href="http://cesiumjs.org/2013/02/13/Cesium-Camera-Tutorial/|Camera Tutorial} * * @example * // Create a camera looking down the negative z-axis, positioned at the origin, * // with a field of view of 60 degrees, and 1:1 aspect ratio. * var camera = new Cesium.Camera(scene); * camera.position = new Cesium.Cartesian3(); * camera.direction = Cesium.Cartesian3.negate(Cesium.Cartesian3.UNIT_Z, new Cesium.Cartesian3()); * camera.up = Cesium.Cartesian3.clone(Cesium.Cartesian3.UNIT_Y); * camera.frustum.fov = Cesium.Math.PI_OVER_THREE; * camera.frustum.near = 1.0; * camera.frustum.far = 2.0; */ var Camera = function(scene) { //>>includeStart('debug', pragmas.debug); if (!defined(scene)) { throw new DeveloperError('scene is required.'); } //>>includeEnd('debug'); this._scene = scene; /** * Modifies the camera's reference frame. The inverse of this transformation is appended to the view matrix. * * @type {Matrix4} * @default {@link Matrix4.IDENTITY} * * @see Transforms * @see Camera#inverseTransform */ this.transform = Matrix4.clone(Matrix4.IDENTITY); this._transform = Matrix4.clone(Matrix4.IDENTITY); this._invTransform = Matrix4.clone(Matrix4.IDENTITY); this._actualTransform = Matrix4.clone(Matrix4.IDENTITY); this._actualInvTransform = Matrix4.clone(Matrix4.IDENTITY); /** * The position of the camera. * * @type {Cartesian3} */ this.position = new Cartesian3(); this._position = new Cartesian3(); this._positionWC = new Cartesian3(); this._positionCartographic = new Cartographic(); /** * The view direction of the camera. * * @type {Cartesian3} */ this.direction = new Cartesian3(); this._direction = new Cartesian3(); this._directionWC = new Cartesian3(); /** * The up direction of the camera. * * @type {Cartesian3} */ this.up = new Cartesian3(); this._up = new Cartesian3(); this._upWC = new Cartesian3(); /** * The right direction of the camera. * * @type {Cartesian3} */ this.right = new Cartesian3(); this._right = new Cartesian3(); this._rightWC = new Cartesian3(); /** * The region of space in view. * * @type {Frustum} * @default PerspectiveFrustum() * * @see PerspectiveFrustum * @see PerspectiveOffCenterFrustum * @see OrthographicFrustum */ this.frustum = new PerspectiveFrustum(); this.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; this.frustum.fov = CesiumMath.toRadians(60.0); /** * The default amount to move the camera when an argument is not * provided to the move methods. * @type {Number} * @default 100000.0; */ this.defaultMoveAmount = 100000.0; /** * The default amount to rotate the camera when an argument is not * provided to the look methods. * @type {Number} * @default Math.PI / 60.0 */ this.defaultLookAmount = Math.PI / 60.0; /** * The default amount to rotate the camera when an argument is not * provided to the rotate methods. * @type {Number} * @default Math.PI / 3600.0 */ this.defaultRotateAmount = Math.PI / 3600.0; /** * The default amount to move the camera when an argument is not * provided to the zoom methods. * @type {Number} * @default 100000.0; */ this.defaultZoomAmount = 100000.0; /** * If set, the camera will not be able to rotate past this axis in either direction. * @type {Cartesian3} * @default undefined */ this.constrainedAxis = undefined; /** * The factor multiplied by the the map size used to determine where to clamp the camera position * when translating across the surface. The default is 1.5. Only valid for 2D and Columbus view. * @type {Number} * @default 1.5 */ this.maximumTranslateFactor = 1.5; /** * The factor multiplied by the the map size used to determine where to clamp the camera position * when zooming out from the surface. The default is 2.5. Only valid for 2D. * @type {Number} * @default 2.5 */ this.maximumZoomFactor = 2.5; this._viewMatrix = new Matrix4(); this._invViewMatrix = new Matrix4(); updateViewMatrix(this); this._mode = SceneMode.SCENE3D; this._modeChanged = true; var projection = scene.mapProjection; this._projection = projection; this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)); this._max2Dfrustum = undefined; // set default view this.viewRectangle(Camera.DEFAULT_VIEW_RECTANGLE); var mag = Cartesian3.magnitude(this.position); mag += mag * Camera.DEFAULT_VIEW_FACTOR; Cartesian3.normalize(this.position, this.position); Cartesian3.multiplyByScalar(this.position, mag, this.position); }; /** * @private */ Camera.TRANSFORM_2D = new Matrix4( 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); /** * @private */ Camera.TRANSFORM_2D_INVERSE = Matrix4.inverseTransformation(Camera.TRANSFORM_2D, new Matrix4()); /** * The default extent the camera will view on creation. * @type Rectangle */ Camera.DEFAULT_VIEW_RECTANGLE = Rectangle.fromDegrees(-95.0, -20.0, -70.0, 90.0); /** * A scalar to multiply to the camera position and add it back after setting the camera to view the rectangle. * A value of zero means the camera will view the entire {@link Camera#DEFAULT_VIEW_RECTANGLE}, a value greater than zero * will move it further away from the extent, and a value less than zero will move it close to the extent. * @type Number */ Camera.DEFAULT_VIEW_FACTOR = 0.5; function updateViewMatrix(camera) { var r = camera._right; var u = camera._up; var d = camera._direction; var e = camera._position; var viewMatrix = camera._viewMatrix; viewMatrix[0] = r.x; viewMatrix[1] = u.x; viewMatrix[2] = -d.x; viewMatrix[3] = 0.0; viewMatrix[4] = r.y; viewMatrix[5] = u.y; viewMatrix[6] = -d.y; viewMatrix[7] = 0.0; viewMatrix[8] = r.z; viewMatrix[9] = u.z; viewMatrix[10] = -d.z; viewMatrix[11] = 0.0; viewMatrix[12] = -Cartesian3.dot(r, e); viewMatrix[13] = -Cartesian3.dot(u, e); viewMatrix[14] = Cartesian3.dot(d, e); viewMatrix[15] = 1.0; Matrix4.multiply(viewMatrix, camera._actualInvTransform, camera._viewMatrix); Matrix4.inverseTransformation(camera._viewMatrix, camera._invViewMatrix); } var scratchCartographic = new Cartographic(); var scratchCartesian3Projection = new Cartesian3(); var scratchCartesian3 = new Cartesian3(); var scratchCartesian4Origin = new Cartesian4(); var scratchCartesian4NewOrigin = new Cartesian4(); var scratchCartesian4NewXAxis = new Cartesian4(); var scratchCartesian4NewYAxis = new Cartesian4(); var scratchCartesian4NewZAxis = new Cartesian4(); function convertTransformForColumbusView(camera) { var projection = camera._projection; var ellipsoid = projection.ellipsoid; var origin = Matrix4.getColumn(camera._transform, 3, scratchCartesian4Origin); var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic); var projectedPosition = projection.project(cartographic, scratchCartesian3Projection); var newOrigin = scratchCartesian4NewOrigin; newOrigin.x = projectedPosition.z; newOrigin.y = projectedPosition.x; newOrigin.z = projectedPosition.y; newOrigin.w = 1.0; var xAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 0, scratchCartesian3), origin, scratchCartesian3); ellipsoid.cartesianToCartographic(xAxis, cartographic); projection.project(cartographic, projectedPosition); var newXAxis = scratchCartesian4NewXAxis; newXAxis.x = projectedPosition.z; newXAxis.y = projectedPosition.x; newXAxis.z = projectedPosition.y; newXAxis.w = 0.0; Cartesian3.subtract(newXAxis, newOrigin, newXAxis); var yAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 1, scratchCartesian3), origin, scratchCartesian3); ellipsoid.cartesianToCartographic(yAxis, cartographic); projection.project(cartographic, projectedPosition); var newYAxis = scratchCartesian4NewYAxis; newYAxis.x = projectedPosition.z; newYAxis.y = projectedPosition.x; newYAxis.z = projectedPosition.y; newYAxis.w = 0.0; Cartesian3.subtract(newYAxis, newOrigin, newYAxis); var newZAxis = scratchCartesian4NewZAxis; Cartesian3.cross(newXAxis, newYAxis, newZAxis); Cartesian3.normalize(newZAxis, newZAxis); Cartesian3.cross(newYAxis, newZAxis, newXAxis); Cartesian3.normalize(newXAxis, newXAxis); Cartesian3.cross(newZAxis, newXAxis, newYAxis); Cartesian3.normalize(newYAxis, newYAxis); Matrix4.setColumn(camera._actualTransform, 0, newXAxis, camera._actualTransform); Matrix4.setColumn(camera._actualTransform, 1, newYAxis, camera._actualTransform); Matrix4.setColumn(camera._actualTransform, 2, newZAxis, camera._actualTransform); Matrix4.setColumn(camera._actualTransform, 3, newOrigin, camera._actualTransform); } function convertTransformFor2D(camera) { var projection = camera._projection; var ellipsoid = projection.ellipsoid; var origin = Matrix4.getColumn(camera._transform, 3, scratchCartesian4Origin); var cartographic = ellipsoid.cartesianToCartographic(origin, scratchCartographic); var projectedPosition = projection.project(cartographic, scratchCartesian3Projection); var newOrigin = scratchCartesian4NewOrigin; newOrigin.x = projectedPosition.z; newOrigin.y = projectedPosition.x; newOrigin.z = projectedPosition.y; newOrigin.w = 1.0; var newZAxis = Cartesian4.clone(Cartesian4.UNIT_X, scratchCartesian4NewZAxis); var xAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 0, scratchCartesian3), origin, scratchCartesian3); ellipsoid.cartesianToCartographic(xAxis, cartographic); projection.project(cartographic, projectedPosition); var newXAxis = scratchCartesian4NewXAxis; newXAxis.x = projectedPosition.z; newXAxis.y = projectedPosition.x; newXAxis.z = projectedPosition.y; newXAxis.w = 0.0; Cartesian3.subtract(newXAxis, newOrigin, newXAxis); newXAxis.x = 0.0; var newYAxis = scratchCartesian4NewYAxis; if (Cartesian3.magnitudeSquared(newXAxis) > CesiumMath.EPSILON10) { Cartesian3.cross(newZAxis, newXAxis, newYAxis); } else { var yAxis = Cartesian4.add(Matrix4.getColumn(camera._transform, 1, scratchCartesian3), origin, scratchCartesian3); ellipsoid.cartesianToCartographic(yAxis, cartographic); projection.project(cartographic, projectedPosition); newYAxis.x = projectedPosition.z; newYAxis.y = projectedPosition.x; newYAxis.z = projectedPosition.y; newYAxis.w = 0.0; Cartesian3.subtract(newYAxis, newOrigin, newYAxis); newYAxis.x = 0.0; if (Cartesian3.magnitudeSquared(newYAxis) < CesiumMath.EPSILON10) { Cartesian4.clone(Cartesian4.UNIT_Y, newXAxis); Cartesian4.clone(Cartesian4.UNIT_Z, newYAxis); } } Cartesian3.cross(newYAxis, newZAxis, newXAxis); Cartesian3.normalize(newXAxis, newXAxis); Cartesian3.cross(newZAxis, newXAxis, newYAxis); Cartesian3.normalize(newYAxis, newYAxis); Matrix4.setColumn(camera._actualTransform, 0, newXAxis, camera._actualTransform); Matrix4.setColumn(camera._actualTransform, 1, newYAxis, camera._actualTransform); Matrix4.setColumn(camera._actualTransform, 2, newZAxis, camera._actualTransform); Matrix4.setColumn(camera._actualTransform, 3, newOrigin, camera._actualTransform); } var scratchCartesian = new Cartesian3(); function updateMembers(camera) { var position = camera._position; var positionChanged = !Cartesian3.equals(position, camera.position); if (positionChanged) { position = Cartesian3.clone(camera.position, camera._position); } var direction = camera._direction; var directionChanged = !Cartesian3.equals(direction, camera.direction); if (directionChanged) { direction = Cartesian3.clone(camera.direction, camera._direction); } var up = camera._up; var upChanged = !Cartesian3.equals(up, camera.up); if (upChanged) { up = Cartesian3.clone(camera.up, camera._up); } var right = camera._right; var rightChanged = !Cartesian3.equals(right, camera.right); if (rightChanged) { right = Cartesian3.clone(camera.right, camera._right); } var transformChanged = !Matrix4.equals(camera._transform, camera.transform) || camera._modeChanged; if (transformChanged) { Matrix4.clone(camera.transform, camera._transform); Matrix4.inverseTransformation(camera._transform, camera._invTransform); if (camera._mode === SceneMode.COLUMBUS_VIEW || camera._mode === SceneMode.SCENE2D) { if (Matrix4.equals(Matrix4.IDENTITY, camera._transform)) { Matrix4.clone(Camera.TRANSFORM_2D, camera._actualTransform); } else if (camera._mode === SceneMode.COLUMBUS_VIEW) { convertTransformForColumbusView(camera); } else { convertTransformFor2D(camera); } } else { Matrix4.clone(camera._transform, camera._actualTransform); } Matrix4.inverseTransformation(camera._actualTransform, camera._actualInvTransform); camera._modeChanged = false; } var transform = camera._actualTransform; if (positionChanged || transformChanged) { camera._positionWC = Matrix4.multiplyByPoint(transform, position, camera._positionWC); // Compute the Cartographic position of the camera. var mode = camera._mode; if (mode === SceneMode.SCENE3D || mode === SceneMode.MORPHING) { camera._positionCartographic = camera._projection.ellipsoid.cartesianToCartographic(camera._positionWC, camera._positionCartographic); } else { // The camera position is expressed in the 2D coordinate system where the Y axis is to the East, // the Z axis is to the North, and the X axis is out of the map. Express them instead in the ENU axes where // X is to the East, Y is to the North, and Z is out of the local horizontal plane. var positionENU = scratchCartesian; positionENU.x = camera._positionWC.y; positionENU.y = camera._positionWC.z; positionENU.z = camera._positionWC.x; // In 2D, the camera height is always 12.7 million meters. // The apparent height is equal to half the frustum width. if (mode === SceneMode.SCENE2D) { positionENU.z = (camera.frustum.right - camera.frustum.left) * 0.5; } camera._projection.unproject(positionENU, camera._positionCartographic); } } if (directionChanged || upChanged || rightChanged) { var det = Cartesian3.dot(direction, Cartesian3.cross(up, right, scratchCartesian)); if (Math.abs(1.0 - det) > CesiumMath.EPSILON2) { //orthonormalize axes direction = Cartesian3.normalize(direction, camera._direction); Cartesian3.clone(direction, camera.direction); var invUpMag = 1.0 / Cartesian3.magnitudeSquared(up); var scalar = Cartesian3.dot(up, direction) * invUpMag; var w0 = Cartesian3.multiplyByScalar(direction, scalar, scratchCartesian); up = Cartesian3.normalize(Cartesian3.subtract(up, w0, camera._up), camera._up); Cartesian3.clone(up, camera.up); right = Cartesian3.cross(direction, up, camera._right); Cartesian3.clone(right, camera.right); } } if (directionChanged || transformChanged) { camera._directionWC = Matrix4.multiplyByPointAsVector(transform, direction, camera._directionWC); } if (upChanged || transformChanged) { camera._upWC = Matrix4.multiplyByPointAsVector(transform, up, camera._upWC); } if (rightChanged || transformChanged) { camera._rightWC = Matrix4.multiplyByPointAsVector(transform, right, camera._rightWC); } if (positionChanged || directionChanged || upChanged || rightChanged || transformChanged) { updateViewMatrix(camera); } } function getHeading2D(camera) { return Math.atan2(camera.right.y, camera.right.x); } var scratchHeadingMatrix4 = new Matrix4(); var scratchHeadingMatrix3 = new Matrix3(); var scratchHeadingCartesian3 = new Cartesian3(); function getHeading3D(camera) { var ellipsoid = camera._projection.ellipsoid; var toFixedFrame = Transforms.eastNorthUpToFixedFrame(camera.position, ellipsoid, scratchHeadingMatrix4); var transform = Matrix4.getRotation(toFixedFrame, scratchHeadingMatrix3); Matrix3.transpose(transform, transform); var right = Matrix3.multiplyByVector(transform, camera.right, scratchHeadingCartesian3); return Math.atan2(right.y, right.x); } function setHeading2D(camera, angle) { var rightAngle = getHeading2D(camera); angle = rightAngle - angle; camera.look(Cartesian3.UNIT_Z, angle); } var scratchHeadingAxis = new Cartesian3(); function setHeading3D(camera, angle) { var axis = Cartesian3.normalize(camera.position, scratchHeadingAxis); var upAngle = getHeading3D(camera); angle = upAngle - angle; camera.look(axis, angle); } function getTiltCV(camera) { // CesiumMath.acosClamped(dot(camera.direction, Cartesian3.negate(Cartesian3.UNIT_Z)) return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(-camera.direction.z); } var scratchTiltCartesian3 = new Cartesian3(); function getTilt3D(camera) { var direction = Cartesian3.normalize(camera.position, scratchTiltCartesian3); Cartesian3.negate(direction, direction); return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(Cartesian3.dot(camera.direction, direction)); } defineProperties(Camera.prototype, { /** * Gets the inverse camera transform. * @memberof Camera.prototype * * @type {Matrix4} * @readonly * * @default {@link Matrix4.IDENTITY} */ inverseTransform : { get : function() { updateMembers(this); return this._invTransform; } }, /** * Gets the view matrix. * @memberof Camera.prototype * * @type {Matrix4} * @readonly * * @see Camera#inverseViewMatrix */ viewMatrix : { get : function() { updateMembers(this); return this._viewMatrix; } }, /** * Gets the inverse view matrix. * @memberof Camera.prototype * * @type {Matrix4} * @readonly * * @see Camera#viewMatrix */ inverseViewMatrix : { get : function() { updateMembers(this); return this._invViewMatrix; } }, /** * Gets the {@link Cartographic} position of the camera, with longitude and latitude * expressed in radians and height in meters. In 2D and Columbus View, it is possible * for the returned longitude and latitude to be outside the range of valid longitudes * and latitudes when the camera is outside the map. * @memberof Camera.prototype * * @type {Cartographic} */ positionCartographic : { get : function() { updateMembers(this); return this._positionCartographic; } }, /** * Gets the position of the camera in world coordinates. * @memberof Camera.prototype * * @type {Cartesian3} * @readonly */ positionWC : { get : function() { updateMembers(this); return this._positionWC; } }, /** * Gets the view direction of the camera in world coordinates. * @memberof Camera.prototype * * @type {Cartesian3} * @readonly */ directionWC : { get : function() { updateMembers(this); return this._directionWC; } }, /** * Gets the up direction of the camera in world coordinates. * @memberof Camera.prototype * * @type {Cartesian3} * @readonly */ upWC : { get : function() { updateMembers(this); return this._upWC; } }, /** * Gets the right direction of the camera in world coordinates. * @memberof Camera.prototype * * @type {Cartesian3} * @readonly */ rightWC : { get : function() { updateMembers(this); return this._rightWC; } }, /** * Gets or sets the camera heading in radians. * @memberof Camera.prototype * * @type {Number} */ heading : { get : function () { if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) { return getHeading2D(this); } else if (this._mode === SceneMode.SCENE3D) { return getHeading3D(this); } return undefined; }, //TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832 set : function (angle) { //>>includeStart('debug', pragmas.debug); if (!defined(angle)) { throw new DeveloperError('angle is required.'); } //>>includeEnd('debug'); if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) { setHeading2D(this, angle); } else if (this._mode === SceneMode.SCENE3D) { setHeading3D(this, angle); } } }, /** * Gets or sets the camera tilt in radians. * @memberof Camera.prototype * * @type {Number} */ tilt : { get : function() { if (this._mode === SceneMode.COLUMBUS_VIEW) { return getTiltCV(this); } else if (this._mode === SceneMode.SCENE3D) { return getTilt3D(this); } return undefined; }, //TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832 set : function(angle) { //>>includeStart('debug', pragmas.debug); if (!defined(angle)) { throw new DeveloperError('angle is required.'); } //>>includeEnd('debug'); if (this._mode === SceneMode.COLUMBUS_VIEW || this._mode === SceneMode.SCENE3D) { angle = CesiumMath.clamp(angle, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); angle = angle - this.tilt; this.look(this.right, angle); } } } }); /** * @private */ Camera.prototype.update = function(mode) { //>>includeStart('debug', pragmas.debug); if (!defined(mode)) { throw new DeveloperError('mode is required.'); } //>>includeEnd('debug'); var updateFrustum = false; if (mode !== this._mode) { this._mode = mode; this._modeChanged = mode !== SceneMode.MORPHING; updateFrustum = this._mode === SceneMode.SCENE2D; } if (updateFrustum) { var frustum = this._max2Dfrustum = this.frustum.clone(); //>>includeStart('debug', pragmas.debug); if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) { throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.'); } //>>includeEnd('debug'); var maxZoomOut = 2.0; var ratio = frustum.top / frustum.right; frustum.right = this._maxCoord.x * maxZoomOut; frustum.left = -frustum.right; frustum.top = ratio * frustum.right; frustum.bottom = -frustum.top; } }; var setTransformPosition = new Cartesian3(); var setTransformUp = new Cartesian3(); var setTransformDirection = new Cartesian3(); /** * Sets the camera's transform without changing the current view. * * @param {Matrix4} transform The camera transform. */ Camera.prototype.setTransform = function(transform) { var position = Cartesian3.clone(this.positionWC, setTransformPosition); var up = Cartesian3.clone(this.upWC, setTransformUp); var direction = Cartesian3.clone(this.directionWC, setTransformDirection); Matrix4.clone(transform, this.transform); updateMembers(this); var inverse = this._actualInvTransform; Matrix4.multiplyByPoint(inverse, position, this.position); Matrix4.multiplyByPointAsVector(inverse, direction, this.direction); Matrix4.multiplyByPointAsVector(inverse, up, this.up); Cartesian3.cross(this.direction, this.up, this.right); }; /** * Transform a vector or point from world coordinates to the camera's reference frame. * * @param {Cartesian4} cartesian The vector or point to transform. * @param {Cartesian4} [result] The object onto which to store the result. * @returns {Cartesian4} The transformed vector or point. */ Camera.prototype.worldToCameraCoordinates = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); if (!defined(cartesian)) { throw new DeveloperError('cartesian is required.'); } //>>includeEnd('debug'); if (!defined(result)){ result = new Cartesian4(); } updateMembers(this); return Matrix4.multiplyByVector(this._actualInvTransform, cartesian, result); }; /** * Transform a point from world coordinates to the camera's reference frame. * * @param {Cartesian3} cartesian The point to transform. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3} The transformed point. */ Camera.prototype.worldToCameraCoordinatesPoint = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); if (!defined(cartesian)) { throw new DeveloperError('cartesian is required.'); } //>>includeEnd('debug'); if (!defined(result)){ result = new Cartesian3(); } updateMembers(this); return Matrix4.multiplyByPoint(this._actualInvTransform, cartesian, result); }; /** * Transform a vector from world coordinates to the camera's reference frame. * * @param {Cartesian3} cartesian The vector to transform. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3} The transformed vector. */ Camera.prototype.worldToCameraCoordinatesVector = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); if (!defined(cartesian)) { throw new DeveloperError('cartesian is required.'); } //>>includeEnd('debug'); if (!defined(result)){ result = new Cartesian3(); } updateMembers(this); return Matrix4.multiplyByPointAsVector(this._actualInvTransform, cartesian, result); }; /** * Transform a vector or point from the camera's reference frame to world coordinates. * * @param {Cartesian4} cartesian The vector or point to transform. * @param {Cartesian4} [result] The object onto which to store the result. * @returns {Cartesian4} The transformed vector or point. */ Camera.prototype.cameraToWorldCoordinates = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); if (!defined(cartesian)) { throw new DeveloperError('cartesian is required.'); } //>>includeEnd('debug'); if (!defined(result)){ result = new Cartesian4(); } updateMembers(this); return Matrix4.multiplyByVector(this._actualTransform, cartesian, result); }; /** * Transform a point from the camera's reference frame to world coordinates. * * @param {Cartesian3} cartesian The point to transform. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3} The transformed point. */ Camera.prototype.cameraToWorldCoordinatesPoint = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); if (!defined(cartesian)) { throw new DeveloperError('cartesian is required.'); } //>>includeEnd('debug'); if (!defined(result)){ result = new Cartesian3(); } updateMembers(this); return Matrix4.multiplyByPoint(this._actualTransform, cartesian, result); }; /** * Transform a vector from the camera's reference frame to world coordinates. * * @param {Cartesian3} cartesian The vector to transform. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3} The transformed vector. */ Camera.prototype.cameraToWorldCoordinatesVector = function(cartesian, result) { //>>includeStart('debug', pragmas.debug); if (!defined(cartesian)) { throw new DeveloperError('cartesian is required.'); } //>>includeEnd('debug'); if (!defined(result)){ result = new Cartesian3(); } updateMembers(this); return Matrix4.multiplyByPointAsVector(this._actualTransform, cartesian, result); }; function clampMove2D(camera, position) { var maxX = camera._maxCoord.x * camera.maximumTranslateFactor; if (position.x > maxX) { position.x = maxX; } if (position.x < -maxX) { position.x = -maxX; } var maxY = camera._maxCoord.y * camera.maximumTranslateFactor; if (position.y > maxY) { position.y = maxY; } if (position.y < -maxY) { position.y = -maxY; } } var moveScratch = new Cartesian3(); /** * Translates the camera's position by <code>amount</code> along <code>direction</code>. * * @param {Cartesian3} direction The direction to move. * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveBackward * @see Camera#moveForward * @see Camera#moveLeft * @see Camera#moveRight * @see Camera#moveUp * @see Camera#moveDown */ Camera.prototype.move = function(direction, amount) { //>>includeStart('debug', pragmas.debug); if (!defined(direction)) { throw new DeveloperError('direction is required.'); } //>>includeEnd('debug'); var cameraPosition = this.position; Cartesian3.multiplyByScalar(direction, amount, moveScratch); Cartesian3.add(cameraPosition, moveScratch, cameraPosition); if (this._mode === SceneMode.SCENE2D) { clampMove2D(this, cameraPosition); } }; /** * Translates the camera's position by <code>amount</code> along the camera's view vector. * * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveBackward */ Camera.prototype.moveForward = function(amount) { amount = defaultValue(amount, this.defaultMoveAmount); this.move(this.direction, amount); }; /** * Translates the camera's position by <code>amount</code> along the opposite direction * of the camera's view vector. * * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveForward */ Camera.prototype.moveBackward = function(amount) { amount = defaultValue(amount, this.defaultMoveAmount); this.move(this.direction, -amount); }; /** * Translates the camera's position by <code>amount</code> along the camera's up vector. * * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveDown */ Camera.prototype.moveUp = function(amount) { amount = defaultValue(amount, this.defaultMoveAmount); this.move(this.up, amount); }; /** * Translates the camera's position by <code>amount</code> along the opposite direction * of the camera's up vector. * * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveUp */ Camera.prototype.moveDown = function(amount) { amount = defaultValue(amount, this.defaultMoveAmount); this.move(this.up, -amount); }; /** * Translates the camera's position by <code>amount</code> along the camera's right vector. * * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveLeft */ Camera.prototype.moveRight = function(amount) { amount = defaultValue(amount, this.defaultMoveAmount); this.move(this.right, amount); }; /** * Translates the camera's position by <code>amount</code> along the opposite direction * of the camera's right vector. * * @param {Number} [amount] The amount, in meters, to move. Defaults to <code>defaultMoveAmount</code>. * * @see Camera#moveRight */ Camera.prototype.moveLeft = function(amount) { amount = defaultValue(amount, this.defaultMoveAmount); this.move(this.right, -amount); }; /** * Rotates the camera around its up vector by amount, in radians, in the opposite direction * of its right vector. * * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#lookRight */ Camera.prototype.lookLeft = function(amount) { amount = defaultValue(amount, this.defaultLookAmount); this.look(this.up, -amount); }; /** * Rotates the camera around its up vector by amount, in radians, in the direction * of its right vector. * * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#lookLeft */ Camera.prototype.lookRight = function(amount) { amount = defaultValue(amount, this.defaultLookAmount); this.look(this.up, amount); }; /** * Rotates the camera around its right vector by amount, in radians, in the direction * of its up vector. * * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#lookDown */ Camera.prototype.lookUp = function(amount) { amount = defaultValue(amount, this.defaultLookAmount); this.look(this.right, -amount); }; /** * Rotates the camera around its right vector by amount, in radians, in the opposite direction * of its up vector. * * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#lookUp */ Camera.prototype.lookDown = function(amount) { amount = defaultValue(amount, this.defaultLookAmount); this.look(this.right, amount); }; var lookScratchQuaternion = new Quaternion(); var lookScratchMatrix = new Matrix3(); /** * Rotate each of the camera's orientation vectors around <code>axis</code> by <code>angle</code> * * @param {Cartesian3} axis The axis to rotate around. * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#lookUp * @see Camera#lookDown * @see Camera#lookLeft * @see Camera#lookRight */ Camera.prototype.look = function(axis, angle) { //>>includeStart('debug', pragmas.debug); if (!defined(axis)) { throw new DeveloperError('axis is required.'); } //>>includeEnd('debug'); var turnAngle = defaultValue(angle, this.defaultLookAmount); var quaternion = Quaternion.fromAxisAngle(axis, -turnAngle, lookScratchQuaternion); var rotation = Matrix3.fromQuaternion(quaternion, lookScratchMatrix); var direction = this.direction; var up = this.up; var right = this.right; Matrix3.multiplyByVector(rotation, direction, direction); Matrix3.multiplyByVector(rotation, up, up); Matrix3.multiplyByVector(rotation, right, right); }; /** * Rotate the camera counter-clockwise around its direction vector by amount, in radians. * * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#twistRight */ Camera.prototype.twistLeft = function(amount) { amount = defaultValue(amount, this.defaultLookAmount); this.look(this.direction, amount); }; /** * Rotate the camera clockwise around its direction vector by amount, in radians. * * @param {Number} [amount] The amount, in radians, to rotate by. Defaults to <code>defaultLookAmount</code>. * * @see Camera#twistLeft */ Camera.prototype.twistRight = function(amount) { amount = defaultValue(amount, this.defaultLookAmount); this.look(this.direction, -amount); }; var rotateScratchQuaternion = new Quaternion(); var rotateScratchMatrix = new Matrix3(); /** * Rotates the camera around <code>axis</code> by <code>angle</code>. The distance * of the camera's position to the center of the camera's reference frame remains the same. * * @param {Cartesian3} axis The axis to rotate around given in world coordinates. * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>. * * @see Camera#rotateUp * @see Camera#rotateDown * @see Camera#rotateLeft * @see Camera#rotateRight */ Camera.prototype.rotate = function(axis, angle) { //>>includeStart('debug', pragmas.debug); if (!defined(axis)) { throw new DeveloperError('axis is required.'); } //>>includeEnd('debug'); var turnAngle = defaultValue(angle, this.defaultRotateAmount); var quaternion = Quaternion.fromAxisAngle(axis, -turnAngle, rotateScratchQuaternion); var rotation = Matrix3.fromQuaternion(quaternion, rotateScratchMatrix); Matrix3.multiplyByVector(rotation, this.position, this.position); Matrix3.multiplyByVector(rotation, this.direction, this.direction); Matrix3.multiplyByVector(rotation, this.up, this.up); Cartesian3.cross(this.direction, this.up, this.right); Cartesian3.cross(this.right, this.direction, this.up); }; /** * Rotates the camera around the center of the camera's reference frame by angle downwards. * * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>. * * @see Camera#rotateUp * @see Camera#rotate */ Camera.prototype.rotateDown = function(angle) { angle = defaultValue(angle, this.defaultRotateAmount); rotateVertical(this, angle); }; /** * Rotates the camera around the center of the camera's reference frame by angle upwards. * * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>. * * @see Camera#rotateDown * @see Camera#rotate */ Camera.prototype.rotateUp = function(angle) { angle = defaultValue(angle, this.defaultRotateAmount); rotateVertical(this, -angle); }; var rotateVertScratchP = new Cartesian3(); var rotateVertScratchA = new Cartesian3(); var rotateVertScratchTan = new Cartesian3(); var rotateVertScratchNegate = new Cartesian3(); function rotateVertical(camera, angle) { var position = camera.position; var p = Cartesian3.normalize(position, rotateVertScratchP); if (defined(camera.constrainedAxis)) { var northParallel = Cartesian3.equalsEpsilon(p, camera.constrainedAxis, CesiumMath.EPSILON2); var southParallel = Cartesian3.equalsEpsilon(p, Cartesian3.negate(camera.constrainedAxis, rotateVertScratchNegate), CesiumMath.EPSILON2); if ((!northParallel && !southParallel)) { var constrainedAxis = Cartesian3.normalize(camera.constrainedAxis, rotateVertScratchA); var dot = Cartesian3.dot(p, constrainedAxis); var angleToAxis = CesiumMath.acosClamped(dot); if (angle > 0 && angle > angleToAxis) { angle = angleToAxis - CesiumMath.EPSILON4; } dot = Cartesian3.dot(p, Cartesian3.negate(constrainedAxis, rotateVertScratchNegate)); angleToAxis = CesiumMath.acosClamped(dot); if (angle < 0 && -angle > angleToAxis) { angle = -angleToAxis + CesiumMath.EPSILON4; } var tangent = Cartesian3.cross(constrainedAxis, p, rotateVertScratchTan); camera.rotate(tangent, angle); } else if ((northParallel && angle < 0) || (southParallel && angle > 0)) { camera.rotate(camera.right, angle); } } else { camera.rotate(camera.right, angle); } } /** * Rotates the camera around the center of the camera's reference frame by angle to the right. * * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>. * * @see Camera#rotateLeft * @see Camera#rotate */ Camera.prototype.rotateRight = function(angle) { angle = defaultValue(angle, this.defaultRotateAmount); rotateHorizontal(this, -angle); }; /** * Rotates the camera around the center of the camera's reference frame by angle to the left. * * @param {Number} [angle] The angle, in radians, to rotate by. Defaults to <code>defaultRotateAmount</code>. * * @see Camera#rotateRight * @see Camera#rotate */ Camera.prototype.rotateLeft = function(angle) { angle = defaultValue(angle, this.defaultRotateAmount); rotateHorizontal(this, angle); }; function rotateHorizontal(camera, angle) { if (defined(camera.constrainedAxis)) { camera.rotate(camera.constrainedAxis, angle); } else { camera.rotate(camera.up, angle); } } function zoom2D(camera, amount) { var frustum = camera.frustum; //>>includeStart('debug', pragmas.debug); if (!defined(frustum.left) || !defined(frustum.right) || !defined(frustum.top) || !defined(frustum.bottom)) { throw new DeveloperError('The camera frustum is expected to be orthographic for 2D camera control.'); } //>>includeEnd('debug'); amount = amount * 0.5; var newRight = frustum.right - amount; var newLeft = frustum.left + amount; var maxRight = camera._maxCoord.x * camera.maximumZoomFactor; if (newRight > maxRight) { newRight = maxRight; newLeft = -maxRight; } if (newRight <= newLeft) { newRight = 1.0; newLeft = -1.0; } var ratio = frustum.top / frustum.right; frustum.right = newRight; frustum.left = newLeft; frustum.top = frustum.right * ratio; frustum.bottom = -frustum.top; } function zoom3D(camera, amount) { camera.move(camera.direction, amount); } /** * Zooms <code>amount</code> along the camera's view vector. * * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>. * * @see Camera#zoomOut */ Camera.prototype.zoomIn = function(amount) { amount = defaultValue(amount, this.defaultZoomAmount); if (this._mode === SceneMode.SCENE2D) { zoom2D(this, amount); } else { zoom3D(this, amount); } }; /** * Zooms <code>amount</code> along the opposite direction of * the camera's view vector. * * @param {Number} [amount] The amount to move. Defaults to <code>defaultZoomAmount</code>. * * @see Camera#zoomIn */ Camera.prototype.zoomOut = function(amount) { amount = defaultValue(amount, this.defaultZoomAmount); if (this._mode === SceneMode.SCENE2D) { zoom2D(this, -amount); } else { zoom3D(this, -amount); } }; /** * Gets the magnitude of the camera position. In 3D, this is the vector magnitude. In 2D and * Columbus view, this is the distance to the map. * * @returns {Number} The magnitude of the position. */ Camera.prototype.getMagnitude = function() { if (this._mode === SceneMode.SCENE3D) { return Cartesian3.magnitude(this.position); } else if (this._mode === SceneMode.COLUMBUS_VIEW) { return Math.abs(this.position.z); } else if (this._mode === SceneMode.SCENE2D) { return Math.max(this.frustum.right - this.frustum.left, this.frustum.top - this.frustum.bottom); } }; function setPositionCartographic2D(camera, cartographic) { var newLeft = -cartographic.height * 0.5; var newRight = -newLeft; var frustum = camera.frustum; if (newRight > newLeft) { var ratio = frustum.top / frustum.right; frustum.right = newRight; frustum.left = newLeft; frustum.top = frustum.right * ratio; frustum.bottom = -frustum.top; } //We use Cartesian2 instead of 3 here because Z must be constant in 2D mode. Cartesian2.clone(camera._projection.project(cartographic), camera.position); Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction); Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); Cartesian3.clone(Cartesian3.UNIT_X, camera.right); } function setPositionCartographicCV(camera, cartographic) { var projection = camera._projection; camera.position = projection.project(cartographic); Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction); Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); Cartesian3.clone(Cartesian3.UNIT_X, camera.right); } function setPositionCartographic3D(camera, cartographic) { var ellipsoid = camera._projection.ellipsoid; ellipsoid.cartographicToCartesian(cartographic, camera.position); Cartesian3.negate(camera.position, camera.direction); Cartesian3.normalize(camera.direction, camera.direction); Cartesian3.cross(camera.direction, Cartesian3.UNIT_Z, camera.right); Cartesian3.cross(camera.right, camera.direction, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); } /** * Moves the camera to the provided cartographic position. * * @param {Cartographic} cartographic The new camera position. */ Camera.prototype.setPositionCartographic = function(cartographic) { //>>includeStart('debug', pragmas.debug); if (!defined(cartographic)) { throw new DeveloperError('cartographic is required.'); } //>>includeEnd('debug'); if (this._mode === SceneMode.SCENE2D) { setPositionCartographic2D(this, cartographic); } else if (this._mode === SceneMode.COLUMBUS_VIEW) { setPositionCartographicCV(this, cartographic); } else if (this._mode === SceneMode.SCENE3D) { setPositionCartographic3D(this, cartographic); } }; /** * Sets the camera position and orientation with an eye position, target, and up vector. * This method is not supported in 2D mode because there is only one direction to look. * * @param {Cartesian3} eye The position of the camera. * @param {Cartesian3} target The position to look at. * @param {Cartesian3} up The up vector. * * @exception {DeveloperError} lookAt is not supported while morphing. */ Camera.prototype.lookAt = function(eye, target, up) { //>>includeStart('debug', pragmas.debug); if (!defined(eye)) { throw new DeveloperError('eye is required'); } if (!defined(target)) { throw new DeveloperError('target is required'); } if (!defined(up)) { throw new DeveloperError('up is required'); } if (this._mode === SceneMode.MORPHING) { throw new DeveloperError('lookAt is not supported while morphing.'); } //>>includeEnd('debug'); if (this._mode === SceneMode.SCENE2D) { Cartesian2.clone(target, this.position); Cartesian3.negate(Cartesian3.UNIT_Z, this.direction); Cartesian3.clone(up, this.up); this.up.z = 0.0; if (Cartesian3.magnitudeSquared(this.up) < CesiumMath.EPSILON10) { Cartesian3.clone(Cartesian3.UNIT_Y, this.up); } Cartesian3.cross(this.direction, this.up, this.right); var frustum = this.frustum; var ratio = frustum.top / frustum.right; frustum.right = eye.z; frustum.left = -frustum.right; frustum.top = ratio * frustum.right; frustum.bottom = -frustum.top; return; } this.position = Cartesian3.clone(eye, this.position); this.direction = Cartesian3.normalize(Cartesian3.subtract(target, eye, this.direction), this.direction); this.right = Cartesian3.normalize(Cartesian3.cross(this.direction, up, this.right), this.right); this.up = Cartesian3.cross(this.right, this.direction, this.up); }; var viewRectangle3DCartographic = new Cartographic(); var viewRectangle3DNorthEast = new Cartesian3(); var viewRectangle3DSouthWest = new Cartesian3(); var viewRectangle3DNorthWest = new Cartesian3(); var viewRectangle3DSouthEast = new Cartesian3(); var viewRectangle3DCenter = new Cartesian3(); var defaultRF = {direction: new Cartesian3(), right: new Cartesian3(), up: new Cartesian3()}; function rectangleCameraPosition3D (camera, rectangle, ellipsoid, result, positionOnly) { if (!defined(result)) { result = new Cartesian3(); } var cameraRF = camera; if (positionOnly) { cameraRF = defaultRF; } var north = rectangle.north; var south = rectangle.south; var east = rectangle.east; var west = rectangle.west; // If we go across the International Date Line if (west > east) { east += CesiumMath.TWO_PI; } var cart = viewRectangle3DCartographic; cart.longitude = east; cart.latitude = north; var northEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthEast); cart.latitude = south; var southEast = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthEast); cart.longitude = west; var southWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DSouthWest); cart.latitude = north; var northWest = ellipsoid.cartographicToCartesian(cart, viewRectangle3DNorthWest); var center = Cartesian3.subtract(northEast, southWest, viewRectangle3DCenter); Cartesian3.multiplyByScalar(center, 0.5, center); Cartesian3.add(southWest, center, center); var mag = Cartesian3.magnitude(center); if (mag < CesiumMath.EPSILON6) { cart.longitude = (east + west) * 0.5; cart.latitude = (north + south) * 0.5; ellipsoid.cartographicToCartesian(cart, center); } Cartesian3.subtract(northWest, center, northWest); Cartesian3.subtract(southEast, center, southEast); Cartesian3.subtract(northEast, center, northEast); Cartesian3.subtract(southWest, center, southWest); var direction = Cartesian3.negate(center, cameraRF.direction); Cartesian3.normalize(direction, direction); var right = Cartesian3.cross(direction, Cartesian3.UNIT_Z, cameraRF.right); Cartesian3.normalize(right, right); var up = Cartesian3.cross(right, direction, cameraRF.up); var height = Math.max( Math.abs(Cartesian3.dot(up, northWest)), Math.abs(Cartesian3.dot(up, southEast)), Math.abs(Cartesian3.dot(up, northEast)), Math.abs(Cartesian3.dot(up, southWest)) ); var width = Math.max( Math.abs(Cartesian3.dot(right, northWest)), Math.abs(Cartesian3.dot(right, southEast)), Math.abs(Cartesian3.dot(right, northEast)), Math.abs(Cartesian3.dot(right, southWest)) ); var tanPhi = Math.tan(camera.frustum.fovy * 0.5); var tanTheta = camera.frustum.aspectRatio * tanPhi; var d = Math.max(width / tanTheta, height / tanPhi); var scalar = mag + d; Cartesian3.normalize(center, center); return Cartesian3.multiplyByScalar(center, scalar, result); } var viewRectangleCVCartographic = new Cartographic(); var viewRectangleCVNorthEast = new Cartesian3(); var viewRectangleCVSouthWest = new Cartesian3(); function rectangleCameraPositionColumbusView(camera, rectangle, projection, result, positionOnly) { var north = rectangle.north; var south = rectangle.south; var east = rectangle.east; var west = rectangle.west; var transform = camera._actualTransform; var invTransform = camera._actualInvTransform; var cart = viewRectangleCVCartographic; cart.longitude = east; cart.latitude = north; var northEast = projection.project(cart, viewRectangleCVNorthEast); Matrix4.multiplyByPoint(transform, northEast, northEast); Matrix4.multiplyByPoint(invTransform, northEast, northEast); cart.longitude = west; cart.latitude = south; var southWest = projection.project(cart, viewRectangleCVSouthWest); Matrix4.multiplyByPoint(transform, southWest, southWest); Matrix4.multiplyByPoint(invTransform, southWest, southWest); var tanPhi = Math.tan(camera.frustum.fovy * 0.5); var tanTheta = camera.frustum.aspectRatio * tanPhi; if (!defined(result)) { result = new Cartesian3(); } result.x = (northEast.x - southWest.x) * 0.5 + southWest.x; result.y = (northEast.y - southWest.y) * 0.5 + southWest.y; result.z = Math.max((northEast.x - southWest.x) / tanTheta, (northEast.y - southWest.y) / tanPhi) * 0.5; if (!positionOnly) { var direction = Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); Cartesian3.negate(direction, direction); Cartesian3.clone(Cartesian3.UNIT_X, camera.right); Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); } return result; } var viewRectangle2DCartographic = new Cartographic(); var viewRectangle2DNorthEast = new Cartesian3(); var viewRectangle2DSouthWest = new Cartesian3(); function rectangleCameraPosition2D (camera, rectangle, projection, result, positionOnly) { var north = rectangle.north; var south = rectangle.south; var east = rectangle.east; var west = rectangle.west; var cart = viewRectangle2DCartographic; cart.longitude = east; cart.latitude = north; var northEast = projection.project(cart, viewRectangle2DNorthEast); cart.longitude = west; cart.latitude = south; var southWest = projection.project(cart, viewRectangle2DSouthWest); var width = Math.abs(northEast.x - southWest.x) * 0.5; var height = Math.abs(northEast.y - southWest.y) * 0.5; var right, top; var ratio = camera.frustum.right / camera.frustum.top; var heightRatio = height * ratio; if (width > heightRatio) { right = width; top = right / ratio; } else { top = height; right = heightRatio; } height = Math.max(2.0 * right, 2.0 * top); if (!defined(result)) { result = new Cartesian3(); } result.x = (northEast.x - southWest.x) * 0.5 + southWest.x; result.y = (northEast.y - southWest.y) * 0.5 + southWest.y; if (positionOnly) { cart = projection.unproject(result, cart); cart.height = height; result = projection.project(cart, result); } else { var frustum = camera.frustum; frustum.right = right; frustum.left = -right; frustum.top = top; frustum.bottom = -top; var direction = Cartesian3.clone(Cartesian3.UNIT_Z, camera.direction); Cartesian3.negate(direction, direction); Cartesian3.clone(Cartesian3.UNIT_X, camera.right); Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); } return result; } /** * Get the camera position needed to view an rectangle on an ellipsoid or map * * @param {Rectangle} rectangle The rectangle to view. * @param {Cartesian3} [result] The camera position needed to view the rectangle * @returns {Cartesian3} The camera position needed to view the rectangle */ Camera.prototype.getRectangleCameraCoordinates = function(rectangle, result) { //>>includeStart('debug', pragmas.debug); if (!defined(rectangle)) { throw new DeveloperError('rectangle is required'); } //>>includeEnd('debug'); if (this._mode === SceneMode.SCENE3D) { return rectangleCameraPosition3D(this, rectangle, this._projection.ellipsoid, result, true); } else if (this._mode === SceneMode.COLUMBUS_VIEW) { return rectangleCameraPositionColumbusView(this, rectangle, this._projection, result, true); } else if (this._mode === SceneMode.SCENE2D) { return rectangleCameraPosition2D(this, rectangle, this._projection, result, true); } return undefined; }; /** * View an rectangle on an ellipsoid or map. * * @param {Rectangle} rectangle The rectangle to view. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to view. */ Camera.prototype.viewRectangle = function(rectangle, ellipsoid) { //>>includeStart('debug', pragmas.debug); if (!defined(rectangle)) { throw new DeveloperError('rectangle is required.'); } //>>includeEnd('debug'); ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); if (this._mode === SceneMode.SCENE3D) { rectangleCameraPosition3D(this, rectangle, ellipsoid, this.position); } else if (this._mode === SceneMode.COLUMBUS_VIEW) { rectangleCameraPositionColumbusView(this, rectangle, this._projection, this.position); } else if (this._mode === SceneMode.SCENE2D) { rectangleCameraPosition2D(this, rectangle, this._projection, this.position); } }; var pickEllipsoid3DRay = new Ray(); function pickEllipsoid3D(camera, windowPosition, ellipsoid, result) { ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); var ray = camera.getPickRay(windowPosition, pickEllipsoid3DRay); var intersection = IntersectionTests.rayEllipsoid(ray, ellipsoid); if (!intersection) { return undefined; } var t = intersection.start > 0.0 ? intersection.start : intersection.stop; return Ray.getPoint(ray, t, result); } var pickEllipsoid2DRay = new Ray(); function pickMap2D(camera, windowPosition, projection, result) { var ray = camera.getPickRay(windowPosition, pickEllipsoid2DRay); var position = ray.origin; position.z = 0.0; var cart = projection.unproject(position); if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO || cart.longitude < - Math.PI || cart.longitude > Math.PI) { return undefined; } return projection.ellipsoid.cartographicToCartesian(cart, result); } var pickEllipsoidCVRay = new Ray(); function pickMapColumbusView(camera, windowPosition, projection, result) { var ray = camera.getPickRay(windowPosition, pickEllipsoidCVRay); var scalar = -ray.origin.x / ray.direction.x; Ray.getPoint(ray, scalar, result); var cart = projection.unproject(new Cartesian3(result.y, result.z, 0.0)); if (cart.latitude < -CesiumMath.PI_OVER_TWO || cart.latitude > CesiumMath.PI_OVER_TWO || cart.longitude < - Math.PI || cart.longitude > Math.PI) { return undefined; } return projection.ellipsoid.cartographicToCartesian(cart, result); } /** * Pick an ellipsoid or map. * * @param {Cartesian2} windowPosition The x and y coordinates of a pixel. * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid to pick. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3} If the ellipsoid or map was picked, returns the point on the surface of the ellipsoid or map * in world coordinates. If the ellipsoid or map was not picked, returns undefined. */ Camera.prototype.pickEllipsoid = function(windowPosition, ellipsoid, result) { //>>includeStart('debug', pragmas.debug); if (!defined(windowPosition)) { throw new DeveloperError('windowPosition is required.'); } //>>includeEnd('debug'); if (!defined(result)) { result = new Cartesian3(); } ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84); if (this._mode === SceneMode.SCENE3D) { result = pickEllipsoid3D(this, windowPosition, ellipsoid, result); } else if (this._mode === SceneMode.SCENE2D) { result = pickMap2D(this, windowPosition, this._projection, result); } else if (this._mode === SceneMode.COLUMBUS_VIEW) { result = pickMapColumbusView(this, windowPosition, this._projection, result); } else { return undefined; } return result; }; var pickPerspCenter = new Cartesian3(); var pickPerspXDir = new Cartesian3(); var pickPerspYDir = new Cartesian3(); function getPickRayPerspective(camera, windowPosition, result) { var canvas = camera._scene.canvas; var width = canvas.clientWidth; var height = canvas.clientHeight; var tanPhi = Math.tan(camera.frustum.fovy * 0.5); var tanTheta = camera.frustum.aspectRatio * tanPhi; var near = camera.frustum.near; var x = (2.0 / width) * windowPosition.x - 1.0; var y = (2.0 / height) * (height - windowPosition.y) - 1.0; var position = camera.positionWC; Cartesian3.clone(position, result.origin); var nearCenter = Cartesian3.multiplyByScalar(camera.directionWC, near, pickPerspCenter); Cartesian3.add(position, nearCenter, nearCenter); var xDir = Cartesian3.multiplyByScalar(camera.rightWC, x * near * tanTheta, pickPerspXDir); var yDir = Cartesian3.multiplyByScalar(camera.upWC, y * near * tanPhi, pickPerspYDir); var direction = Cartesian3.add(nearCenter, xDir, result.direction); Cartesian3.add(direction, yDir, direction); Cartesian3.subtract(direction, position, direction); Cartesian3.normalize(direction, direction); return result; } var scratchDirection = new Cartesian3(); function getPickRayOrthographic(camera, windowPosition, result) { var canvas = camera._scene.canvas; var width = canvas.clientWidth; var height = canvas.clientHeight; var x = (2.0 / width) * windowPosition.x - 1.0; x *= (camera.frustum.right - camera.frustum.left) * 0.5; var y = (2.0 / height) * (height - windowPosition.y) - 1.0; y *= (camera.frustum.top - camera.frustum.bottom) * 0.5; var origin = result.origin; Cartesian3.clone(camera.position, origin); Cartesian3.multiplyByScalar(camera.right, x, scratchDirection); Cartesian3.add(scratchDirection, origin, origin); Cartesian3.multiplyByScalar(camera.up, y, scratchDirection); Cartesian3.add(scratchDirection, origin, origin); Cartesian3.clone(camera.directionWC, result.direction); return result; } /** * Create a ray from the camera position through the pixel at <code>windowPosition</code> * in world coordinates. * * @param {Cartesian2} windowPosition The x and y coordinates of a pixel. * @param {Ray} [result] The object onto which to store the result. * @returns {Object} Returns the {@link Cartesian3} position and direction of the ray. */ Camera.prototype.getPickRay = function(windowPosition, result) { //>>includeStart('debug', pragmas.debug); if (!defined(windowPosition)) { throw new DeveloperError('windowPosition is required.'); } //>>includeEnd('debug'); if (!defined(result)) { result = new Ray(); } var frustum = this.frustum; if (defined(frustum.aspectRatio) && defined(frustum.fov) && defined(frustum.near)) { return getPickRayPerspective(this, windowPosition, result); } return getPickRayOrthographic(this, windowPosition, result); }; function createAnimation2D(camera, duration) { var position = camera.position; var translateX = position.x < -camera._maxCoord.x || position.x > camera._maxCoord.x; var translateY = position.y < -camera._maxCoord.y || position.y > camera._maxCoord.y; var animatePosition = translateX || translateY; var frustum = camera.frustum; var top = frustum.top; var bottom = frustum.bottom; var right = frustum.right; var left = frustum.left; var startFrustum = camera._max2Dfrustum; var animateFrustum = right > camera._max2Dfrustum.right; if (animatePosition || animateFrustum) { var translatedPosition = Cartesian3.clone(position); if (translatedPosition.x > camera._maxCoord.x) { translatedPosition.x = camera._maxCoord.x; } else if (translatedPosition.x < -camera._maxCoord.x) { translatedPosition.x = -camera._maxCoord.x; } if (translatedPosition.y > camera._maxCoord.y) { translatedPosition.y = camera._maxCoord.y; } else if (translatedPosition.y < -camera._maxCoord.y) { translatedPosition.y = -camera._maxCoord.y; } var update2D = function(value) { if (animatePosition) { camera.position = Cartesian3.lerp(position, translatedPosition, value.time, camera.position); } if (animateFrustum) { camera.frustum.top = CesiumMath.lerp(top, startFrustum.top, value.time); camera.frustum.bottom = CesiumMath.lerp(bottom, startFrustum.bottom, value.time); camera.frustum.right = CesiumMath.lerp(right, startFrustum.right, value.time); camera.frustum.left = CesiumMath.lerp(left, startFrustum.left, value.time); } }; return { easingFunction : EasingFunction.EXPONENTIAL_OUT, startObject : { time : 0.0 }, stopObject : { time : 1.0 }, duration : duration, update : update2D }; } return undefined; } function createAnimationTemplateCV(camera, position, center, maxX, maxY, duration) { var newPosition = Cartesian3.clone(position); if (center.y > maxX) { newPosition.y -= center.y - maxX; } else if (center.y < -maxX) { newPosition.y += -maxX - center.y; } if (center.z > maxY) { newPosition.z -= center.z - maxY; } else if (center.z < -maxY) { newPosition.z += -maxY - center.z; } var updateCV = function(value) { var interp = Cartesian3.lerp(position, newPosition, value.time, new Cartesian3()); camera.worldToCameraCoordinatesPoint(interp, camera.position); }; return { easingFunction : EasingFunction.EXPONENTIAL_OUT, startObject : { time : 0.0 }, stopObject : { time : 1.0 }, duration : duration, update : updateCV }; } var normalScratch = new Cartesian3(); var centerScratch = new Cartesian3(); var posScratch = new Cartesian3(); var scratchCartesian3Subtract = new Cartesian3(); function createAnimationCV(camera, duration) { var position = camera.position; var direction = camera.direction; var normal = camera.worldToCameraCoordinatesVector(Cartesian3.UNIT_X, normalScratch); var scalar = -Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction); var center = Cartesian3.add(position, Cartesian3.multiplyByScalar(direction, scalar, centerScratch), centerScratch); camera.cameraToWorldCoordinatesPoint(center, center); position = camera.cameraToWorldCoordinatesPoint(camera.position, posScratch); var tanPhi = Math.tan(camera.frustum.fovy * 0.5); var tanTheta = camera.frustum.aspectRatio * tanPhi; var distToC = Cartesian3.magnitude(Cartesian3.subtract(position, center, scratchCartesian3Subtract)); var dWidth = tanTheta * distToC; var dHeight = tanPhi * distToC; var mapWidth = camera._maxCoord.x; var mapHeight = camera._maxCoord.y; var maxX = Math.max(dWidth - mapWidth, mapWidth); var maxY = Math.max(dHeight - mapHeight, mapHeight); if (position.z < -maxX || position.z > maxX || position.y < -maxY || position.y > maxY) { var translateX = center.y < -maxX || center.y > maxX; var translateY = center.z < -maxY || center.z > maxY; if (translateX || translateY) { return createAnimationTemplateCV(camera, position, center, maxX, maxY, duration); } } return undefined; } /** * Create an animation to move the map into view. This method is only valid for 2D and Columbus modes. * * @param {Number} duration The duration, in seconds, of the animation. * @returns {Object} The animation or undefined if the scene mode is 3D or the map is already ion view. * * @exception {DeveloperException} duration is required. * * @private */ Camera.prototype.createCorrectPositionTween = function(duration) { //>>includeStart('debug', pragmas.debug); if (!defined(duration)) { throw new DeveloperError('duration is required.'); } //>>includeEnd('debug'); if (this._mode === SceneMode.SCENE2D) { return createAnimation2D(this, duration); } else if (this._mode === SceneMode.COLUMBUS_VIEW) { return createAnimationCV(this, duration); } return undefined; }; /** * Flies the camera from its current position to a new position. * * @param {Object} options Object with the following properties: * @param {Cartesian3} options.destination The final position of the camera in WGS84 (world) coordinates. * @param {Cartesian3} [options.direction] The final direction of the camera in WGS84 (world) coordinates. By default, the direction will point towards the center of the frame in 3D and in the negative z direction in Columbus view or 2D. * @param {Cartesian3} [options.up] The final up direction in WGS84 (world) coordinates. By default, the up direction will point towards local north in 3D and in the positive y direction in Columbus view or 2D. * @param {Number} [options.duration=3.0] The duration of the flight in seconds. * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete. * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled. * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed. * @param {Boolean} [options.convert=true] When <code>true</code>, the destination is converted to the correct coordinate system for each scene mode. When <code>false</code>, the destination is expected * to be in the correct coordinate system. * * @exception {DeveloperError} If either direction or up is given, then both are required. */ Camera.prototype.flyTo = function(options) { var scene = this._scene; scene.tweens.add(CameraFlightPath.createTween(scene, options)); }; /** * Flies the camera from its current position to a position where the entire rectangle is visible. * * @param {Object} options Object with the following properties: * @param {Rectangle} options.destination The rectangle to view, in WGS84 (world) coordinates, which determines the final position of the camera. * @param {Number} [options.duration=3.0] The duration of the flight in seconds. * @param {Camera~FlightCompleteCallback} [options.complete] The function to execute when the flight is complete. * @param {Camera~FlightCancelledCallback} [options.cancel] The function to execute if the flight is cancelled. * @param {Matrix4} [endTransform] Transform matrix representing the reference frame the camera will be in when the flight is completed. */ Camera.prototype.flyToRectangle = function(options) { var scene = this._scene; scene.tweens.add(CameraFlightPath.createTweenRectangle(scene, options)); }; /** * Returns a duplicate of a Camera instance. * * @returns {Camera} A new copy of the Camera instance. */ Camera.prototype.clone = function() { var camera = new Camera(this._scene); camera.position = Cartesian3.clone(this.position); camera.direction = Cartesian3.clone(this.direction); camera.up = Cartesian3.clone(this.up); camera.right = Cartesian3.clone(this.right); camera.transform = Matrix4.clone(this.transform); camera.frustum = this.frustum.clone(); return camera; }; /** * A function that will execute when a flight completes. * @callback Camera~FlightCompleteCallback */ /** * A function that will execute when a flight is cancelled. * @callback Camera~FlightCancelledCallback */ return Camera; });