UNPKG

cesium

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

1,300 lines (1,115 loc) 133 kB
import BoundingSphere from '../Core/BoundingSphere.js'; import Cartesian2 from '../Core/Cartesian2.js'; import Cartesian3 from '../Core/Cartesian3.js'; import Cartesian4 from '../Core/Cartesian4.js'; import Cartographic from '../Core/Cartographic.js'; import defaultValue from '../Core/defaultValue.js'; import defined from '../Core/defined.js'; import DeveloperError from '../Core/DeveloperError.js'; import EasingFunction from '../Core/EasingFunction.js'; import Ellipsoid from '../Core/Ellipsoid.js'; import EllipsoidGeodesic from '../Core/EllipsoidGeodesic.js'; import Event from '../Core/Event.js'; import getTimestamp from '../Core/getTimestamp.js'; import HeadingPitchRange from '../Core/HeadingPitchRange.js'; import HeadingPitchRoll from '../Core/HeadingPitchRoll.js'; import Intersect from '../Core/Intersect.js'; import IntersectionTests from '../Core/IntersectionTests.js'; import CesiumMath from '../Core/Math.js'; import Matrix3 from '../Core/Matrix3.js'; import Matrix4 from '../Core/Matrix4.js'; import OrthographicFrustum from '../Core/OrthographicFrustum.js'; import OrthographicOffCenterFrustum from '../Core/OrthographicOffCenterFrustum.js'; import PerspectiveFrustum from '../Core/PerspectiveFrustum.js'; import Quaternion from '../Core/Quaternion.js'; import Ray from '../Core/Ray.js'; import Rectangle from '../Core/Rectangle.js'; import Transforms from '../Core/Transforms.js'; import CameraFlightPath from './CameraFlightPath.js'; import MapMode2D from './MapMode2D.js'; import SceneMode from './SceneMode.js'; /** * 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 * * @param {Scene} scene The scene. * * @demo {@link https://sandcastle.cesium.com/index.html?src=Camera.html|Cesium Sandcastle Camera Demo} * @demo {@link https://sandcastle.cesium.com/index.html?src=Camera%20Tutorial.html">Sandcastle Example</a> from the <a href="https://cesium.com/docs/tutorials/camera/|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; */ function Camera(scene) { //>>includeStart('debug', pragmas.debug); if (!defined(scene)) { throw new DeveloperError('scene is required.'); } //>>includeEnd('debug'); this._scene = scene; this._transform = Matrix4.clone(Matrix4.IDENTITY); this._invTransform = Matrix4.clone(Matrix4.IDENTITY); this._actualTransform = Matrix4.clone(Matrix4.IDENTITY); this._actualInvTransform = Matrix4.clone(Matrix4.IDENTITY); this._transformChanged = false; /** * The position of the camera. * * @type {Cartesian3} */ this.position = new Cartesian3(); this._position = new Cartesian3(); this._positionWC = new Cartesian3(); this._positionCartographic = new Cartographic(); this._oldPositionWC = undefined; /** * The position delta magnitude. * * @private */ this.positionWCDeltaMagnitude = 0.0; /** * The position delta magnitude last frame. * * @private */ this.positionWCDeltaMagnitudeLastFrame = 0.0; /** * How long in seconds since the camera has stopped moving * * @private */ this.timeSinceMoved = 0.0; this._lastMovedTimestamp = 0.0; /** * 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 zooming out from the surface. The default is 1.5. Only valid for 2D and the map is rotatable. * @type {Number} * @default 1.5 */ this.maximumZoomFactor = 1.5; this._moveStart = new Event(); this._moveEnd = new Event(); this._changed = new Event(); this._changedPosition = undefined; this._changedDirection = undefined; this._changedFrustum = undefined; /** * The amount the camera has to change before the <code>changed</code> event is raised. The value is a percentage in the [0, 1] range. * @type {number} * @default 0.5 */ this.percentageChanged = 0.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 rectangleCameraPosition3D(this, Camera.DEFAULT_VIEW_RECTANGLE, this.position, true); 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 rectangle 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; /** * The default heading/pitch/range that is used when the camera flies to a location that contains a bounding sphere. * @type HeadingPitchRange */ Camera.DEFAULT_OFFSET = new HeadingPitchRange(0.0, -CesiumMath.PI_OVER_FOUR, 0.0); function updateViewMatrix(camera) { Matrix4.computeView(camera._position, camera._direction, camera._up, camera._right, camera._viewMatrix); Matrix4.multiply(camera._viewMatrix, camera._actualInvTransform, camera._viewMatrix); Matrix4.inverseTransformation(camera._viewMatrix, camera._invViewMatrix); } function updateCameraDeltas(camera) { if (!defined(camera._oldPositionWC)) { camera._oldPositionWC = Cartesian3.clone(camera.positionWC, camera._oldPositionWC); } else { camera.positionWCDeltaMagnitudeLastFrame = camera.positionWCDeltaMagnitude; var delta = Cartesian3.subtract(camera.positionWC, camera._oldPositionWC, camera._oldPositionWC); camera.positionWCDeltaMagnitude = Cartesian3.magnitude(delta); camera._oldPositionWC = Cartesian3.clone(camera.positionWC, camera._oldPositionWC); // Update move timers if (camera.positionWCDeltaMagnitude > 0.0) { camera.timeSinceMoved = 0.0; camera._lastMovedTimestamp = getTimestamp(); } else { camera.timeSinceMoved = Math.max(getTimestamp() - camera._lastMovedTimestamp, 0.0) / 1000.0; } } } /** * Checks if there's a camera flight with preload for this camera. * * @returns {Boolean} Whether or not this camera has a current flight with a valid preloadFlightCamera in scene. * * @private * */ Camera.prototype.canPreloadFlight = function() { return defined(this._currentFlight) && this._mode !== SceneMode.SCENE2D; }; Camera.prototype._updateCameraChanged = function() { var camera = this; updateCameraDeltas(camera); if (camera._changed.numberOfListeners === 0) { return; } var percentageChanged = camera.percentageChanged; if (camera._mode === SceneMode.SCENE2D) { if (!defined(camera._changedFrustum)) { camera._changedPosition = Cartesian3.clone(camera.position, camera._changedPosition); camera._changedFrustum = camera.frustum.clone(); return; } var position = camera.position; var lastPosition = camera._changedPosition; var frustum = camera.frustum; var lastFrustum = camera._changedFrustum; var x0 = position.x + frustum.left; var x1 = position.x + frustum.right; var x2 = lastPosition.x + lastFrustum.left; var x3 = lastPosition.x + lastFrustum.right; var y0 = position.y + frustum.bottom; var y1 = position.y + frustum.top; var y2 = lastPosition.y + lastFrustum.bottom; var y3 = lastPosition.y + lastFrustum.top; var leftX = Math.max(x0, x2); var rightX = Math.min(x1, x3); var bottomY = Math.max(y0, y2); var topY = Math.min(y1, y3); var areaPercentage; if (leftX >= rightX || bottomY >= y1) { areaPercentage = 1.0; } else { var areaRef = lastFrustum; if (x0 < x2 && x1 > x3 && y0 < y2 && y1 > y3) { areaRef = frustum; } areaPercentage = 1.0 - ((rightX - leftX) * (topY - bottomY)) / ((areaRef.right - areaRef.left) * (areaRef.top - areaRef.bottom)); } if (areaPercentage > percentageChanged) { camera._changed.raiseEvent(areaPercentage); camera._changedPosition = Cartesian3.clone(camera.position, camera._changedPosition); camera._changedFrustum = camera.frustum.clone(camera._changedFrustum); } return; } if (!defined(camera._changedDirection)) { camera._changedPosition = Cartesian3.clone(camera.positionWC, camera._changedPosition); camera._changedDirection = Cartesian3.clone(camera.directionWC, camera._changedDirection); return; } var dirAngle = CesiumMath.acosClamped(Cartesian3.dot(camera.directionWC, camera._changedDirection)); var dirPercentage; if (defined(camera.frustum.fovy)) { dirPercentage = dirAngle / (camera.frustum.fovy * 0.5); } else { dirPercentage = dirAngle; } var distance = Cartesian3.distance(camera.positionWC, camera._changedPosition); var heightPercentage = distance / camera.positionCartographic.height; if (dirPercentage > percentageChanged || heightPercentage > percentageChanged) { camera._changed.raiseEvent(Math.max(dirPercentage, heightPercentage)); camera._changedPosition = Cartesian3.clone(camera.positionWC, camera._changedPosition); camera._changedDirection = Cartesian3.clone(camera.directionWC, camera._changedDirection); } }; function convertTransformForColumbusView(camera) { Transforms.basisTo2D(camera._projection, camera._transform, camera._actualTransform); } 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 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 mode = camera._mode; var heightChanged = false; var height = 0.0; if (mode === SceneMode.SCENE2D) { height = camera.frustum.right - camera.frustum.left; heightChanged = height !== camera._positionCartographic.height; } var position = camera._position; var positionChanged = !Cartesian3.equals(position, camera.position) || heightChanged; if (positionChanged) { position = Cartesian3.clone(camera.position, camera._position); } var direction = camera._direction; var directionChanged = !Cartesian3.equals(direction, camera.direction); if (directionChanged) { Cartesian3.normalize(camera.direction, camera.direction); direction = Cartesian3.clone(camera.direction, camera._direction); } var up = camera._up; var upChanged = !Cartesian3.equals(up, camera.up); if (upChanged) { Cartesian3.normalize(camera.up, camera.up); up = Cartesian3.clone(camera.up, camera._up); } var right = camera._right; var rightChanged = !Cartesian3.equals(right, camera.right); if (rightChanged) { Cartesian3.normalize(camera.right, camera.right); right = Cartesian3.clone(camera.right, camera._right); } var transformChanged = camera._transformChanged || camera._modeChanged; camera._transformChanged = false; if (transformChanged) { 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. 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 = height; } 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 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); Cartesian3.normalize(camera._directionWC, camera._directionWC); } if (upChanged || transformChanged) { camera._upWC = Matrix4.multiplyByPointAsVector(transform, up, camera._upWC); Cartesian3.normalize(camera._upWC, camera._upWC); } if (rightChanged || transformChanged) { camera._rightWC = Matrix4.multiplyByPointAsVector(transform, right, camera._rightWC); Cartesian3.normalize(camera._rightWC, camera._rightWC); } if (positionChanged || directionChanged || upChanged || rightChanged || transformChanged) { updateViewMatrix(camera); } } function getHeading(direction, up) { var heading; if (!CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)) { heading = Math.atan2(direction.y, direction.x) - CesiumMath.PI_OVER_TWO; } else { heading = Math.atan2(up.y, up.x) - CesiumMath.PI_OVER_TWO; } return CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(heading); } function getPitch(direction) { return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(direction.z); } function getRoll(direction, up, right) { var roll = 0.0; if (!CesiumMath.equalsEpsilon(Math.abs(direction.z), 1.0, CesiumMath.EPSILON3)) { roll = Math.atan2(-right.z, up.z); roll = CesiumMath.zeroToTwoPi(roll + CesiumMath.TWO_PI); } return roll; } var scratchHPRMatrix1 = new Matrix4(); var scratchHPRMatrix2 = new Matrix4(); Object.defineProperties(Camera.prototype, { /** * Gets the camera's reference frame. The inverse of this transformation is appended to the view matrix. * @memberof Camera.prototype * * @type {Matrix4} * @readonly * * @default {@link Matrix4.IDENTITY} */ transform : { get : function() { return this._transform; } }, /** * 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} * @readonly */ 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 the camera heading in radians. * @memberof Camera.prototype * * @type {Number} * @readonly */ heading : { get : function() { if (this._mode !== SceneMode.MORPHING) { var ellipsoid = this._projection.ellipsoid; var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1); var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); this._setTransform(transform); var heading = getHeading(this.direction, this.up); this._setTransform(oldTransform); return heading; } return undefined; } }, /** * Gets the camera pitch in radians. * @memberof Camera.prototype * * @type {Number} * @readonly */ pitch : { get : function() { if (this._mode !== SceneMode.MORPHING) { var ellipsoid = this._projection.ellipsoid; var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1); var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); this._setTransform(transform); var pitch = getPitch(this.direction); this._setTransform(oldTransform); return pitch; } return undefined; } }, /** * Gets the camera roll in radians. * @memberof Camera.prototype * * @type {Number} * @readonly */ roll : { get : function() { if (this._mode !== SceneMode.MORPHING) { var ellipsoid = this._projection.ellipsoid; var oldTransform = Matrix4.clone(this._transform, scratchHPRMatrix1); var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); this._setTransform(transform); var roll = getRoll(this.direction, this.up, this.right); this._setTransform(oldTransform); return roll; } return undefined; } }, /** * Gets the event that will be raised at when the camera starts to move. * @memberof Camera.prototype * @type {Event} * @readonly */ moveStart : { get : function() { return this._moveStart; } }, /** * Gets the event that will be raised when the camera has stopped moving. * @memberof Camera.prototype * @type {Event} * @readonly */ moveEnd : { get : function() { return this._moveEnd; } }, /** * Gets the event that will be raised when the camera has changed by <code>percentageChanged</code>. * @memberof Camera.prototype * @type {Event} * @readonly */ changed : { get : function() { return this._changed; } } }); /** * @private */ Camera.prototype.update = function(mode) { //>>includeStart('debug', pragmas.debug); if (!defined(mode)) { throw new DeveloperError('mode is required.'); } if (mode === SceneMode.SCENE2D && !(this.frustum instanceof OrthographicOffCenterFrustum)) { throw new DeveloperError('An OrthographicOffCenterFrustum is required in 2D.'); } if ((mode === SceneMode.SCENE3D || mode === SceneMode.COLUMBUS_VIEW) && (!(this.frustum instanceof PerspectiveFrustum) && !(this.frustum instanceof OrthographicFrustum))) { throw new DeveloperError('A PerspectiveFrustum or OrthographicFrustum is required in 3D and Columbus view'); } //>>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 (!(frustum instanceof OrthographicOffCenterFrustum)) { 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; } if (this._mode === SceneMode.SCENE2D) { clampMove2D(this, this.position); } }; var setTransformPosition = new Cartesian3(); var setTransformUp = new Cartesian3(); var setTransformDirection = new Cartesian3(); 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); this._transformChanged = true; 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); updateMembers(this); }; var scratchAdjustOrtghographicFrustumMousePosition = new Cartesian2(); var pickGlobeScratchRay = new Ray(); var scratchRayIntersection = new Cartesian3(); var scratchDepthIntersection = new Cartesian3(); Camera.prototype._adjustOrthographicFrustum = function(zooming) { if (!(this.frustum instanceof OrthographicFrustum)) { return; } if (!zooming && this._positionCartographic.height < 150000.0) { return; } if (!Matrix4.equals(Matrix4.IDENTITY, this.transform)) { this.frustum.width = Cartesian3.magnitude(this.position); return; } var scene = this._scene; var globe = scene.globe; var rayIntersection; var depthIntersection; if (defined(globe)) { var mousePosition = scratchAdjustOrtghographicFrustumMousePosition; mousePosition.x = scene.drawingBufferWidth / 2.0; mousePosition.y = scene.drawingBufferHeight / 2.0; var ray = this.getPickRay(mousePosition, pickGlobeScratchRay); rayIntersection = globe.pickWorldCoordinates(ray, scene, scratchRayIntersection); if (scene.pickPositionSupported) { depthIntersection = scene.pickPositionWorldCoordinates(mousePosition, scratchDepthIntersection); } if (defined(rayIntersection) && defined(depthIntersection)) { var depthDistance = defined(depthIntersection) ? Cartesian3.distance(depthIntersection, this.positionWC) : Number.POSITIVE_INFINITY; var rayDistance = defined(rayIntersection) ? Cartesian3.distance(rayIntersection, this.positionWC) : Number.POSITIVE_INFINITY; this.frustum.width = Math.min(depthDistance, rayDistance); } else if (defined(depthIntersection)) { this.frustum.width = Cartesian3.distance(depthIntersection, this.positionWC); } else if (defined(rayIntersection)) { this.frustum.width = Cartesian3.distance(rayIntersection, this.positionWC); } } if (!defined(globe) || (!defined(rayIntersection) && !defined(depthIntersection))) { var distance = Math.max(this.positionCartographic.height, 0.0); this.frustum.width = distance; } }; var scratchSetViewCartesian = new Cartesian3(); var scratchSetViewTransform1 = new Matrix4(); var scratchSetViewTransform2 = new Matrix4(); var scratchSetViewQuaternion = new Quaternion(); var scratchSetViewMatrix3 = new Matrix3(); var scratchSetViewCartographic = new Cartographic(); function setView3D(camera, position, hpr) { var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); var localTransform = Transforms.eastNorthUpToFixedFrame(position, camera._projection.ellipsoid, scratchSetViewTransform2); camera._setTransform(localTransform); Cartesian3.clone(Cartesian3.ZERO, camera.position); hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO; var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion); var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); Matrix3.getColumn(rotMat, 0, camera.direction); Matrix3.getColumn(rotMat, 2, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); camera._setTransform(currentTransform); camera._adjustOrthographicFrustum(true); } function setViewCV(camera, position,hpr, convert) { var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); camera._setTransform(Matrix4.IDENTITY); if (!Cartesian3.equals(position, camera.positionWC)) { if (convert) { var projection = camera._projection; var cartographic = projection.ellipsoid.cartesianToCartographic(position, scratchSetViewCartographic); position = projection.project(cartographic, scratchSetViewCartesian); } Cartesian3.clone(position, camera.position); } hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO; var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion); var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); Matrix3.getColumn(rotMat, 0, camera.direction); Matrix3.getColumn(rotMat, 2, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); camera._setTransform(currentTransform); camera._adjustOrthographicFrustum(true); } function setView2D(camera, position, hpr, convert) { var currentTransform = Matrix4.clone(camera.transform, scratchSetViewTransform1); camera._setTransform(Matrix4.IDENTITY); if (!Cartesian3.equals(position, camera.positionWC)) { if (convert) { var projection = camera._projection; var cartographic = projection.ellipsoid.cartesianToCartographic(position, scratchSetViewCartographic); position = projection.project(cartographic, scratchSetViewCartesian); } Cartesian2.clone(position, camera.position); var newLeft = -position.z * 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; } } if (camera._scene.mapMode2D === MapMode2D.ROTATE) { hpr.heading = hpr.heading - CesiumMath.PI_OVER_TWO; hpr.pitch = -CesiumMath.PI_OVER_TWO; hpr.roll = 0.0; var rotQuat = Quaternion.fromHeadingPitchRoll(hpr, scratchSetViewQuaternion); var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); Matrix3.getColumn(rotMat, 2, camera.up); Cartesian3.cross(camera.direction, camera.up, camera.right); } camera._setTransform(currentTransform); } var scratchToHPRDirection = new Cartesian3(); var scratchToHPRUp = new Cartesian3(); var scratchToHPRRight = new Cartesian3(); function directionUpToHeadingPitchRoll(camera, position, orientation, result) { var direction = Cartesian3.clone(orientation.direction, scratchToHPRDirection); var up = Cartesian3.clone(orientation.up, scratchToHPRUp); if (camera._scene.mode === SceneMode.SCENE3D) { var ellipsoid = camera._projection.ellipsoid; var transform = Transforms.eastNorthUpToFixedFrame(position, ellipsoid, scratchHPRMatrix1); var invTransform = Matrix4.inverseTransformation(transform, scratchHPRMatrix2); Matrix4.multiplyByPointAsVector(invTransform, direction, direction); Matrix4.multiplyByPointAsVector(invTransform, up, up); } var right = Cartesian3.cross(direction, up, scratchToHPRRight); result.heading = getHeading(direction, up); result.pitch = getPitch(direction); result.roll = getRoll(direction, up, right); return result; } var scratchSetViewOptions = { destination : undefined, orientation : { direction : undefined, up : undefined, heading : undefined, pitch : undefined, roll : undefined }, convert : undefined, endTransform : undefined }; var scratchHpr = new HeadingPitchRoll(); /** * Sets the camera position, orientation and transform. * * @param {Object} options Object with the following properties: * @param {Cartesian3|Rectangle} [options.destination] The final position of the camera in WGS84 (world) coordinates or a rectangle that would be visible from a top-down view. * @param {Object} [options.orientation] An object that contains either direction and up properties or heading, pitch and roll properties. By default, the direction will point * towards the center of the frame in 3D and in the negative z direction in Columbus view. The up direction will point towards local north in 3D and in the positive * y direction in Columbus view. Orientation is not used in 2D when in infinite scrolling mode. * @param {Matrix4} [options.endTransform] Transform matrix representing the reference frame of the camera. * @param {Boolean} [options.convert] Whether to convert the destination from world coordinates to scene coordinates (only relevant when not using 3D). Defaults to <code>true</code>. * * @example * // 1. Set position with a top-down view * viewer.camera.setView({ * destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0) * }); * * // 2 Set view with heading, pitch and roll * viewer.camera.setView({ * destination : cartesianPosition, * orientation: { * heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north) * pitch : Cesium.Math.toRadians(-90), // default value (looking down) * roll : 0.0 // default value * } * }); * * // 3. Change heading, pitch and roll with the camera position remaining the same. * viewer.camera.setView({ * orientation: { * heading : Cesium.Math.toRadians(90.0), // east, default value is 0.0 (north) * pitch : Cesium.Math.toRadians(-90), // default value (looking down) * roll : 0.0 // default value * } * }); * * * // 4. View rectangle with a top-down view * viewer.camera.setView({ * destination : Cesium.Rectangle.fromDegrees(west, south, east, north) * }); * * // 5. Set position with an orientation using unit vectors. * viewer.camera.setView({ * destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0), * orientation : { * direction : new Cesium.Cartesian3(-0.04231243104240401, -0.20123236049443421, -0.97862924300734), * up : new Cesium.Cartesian3(-0.47934589305293746, -0.8553216253114552, 0.1966022179118339) * } * }); */ Camera.prototype.setView = function(options) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); var orientation = defaultValue(options.orientation, defaultValue.EMPTY_OBJECT); var mode = this._mode; if (mode === SceneMode.MORPHING) { return; } if (defined(options.endTransform)) { this._setTransform(options.endTransform); } var convert = defaultValue(options.convert, true); var destination = defaultValue(options.destination, Cartesian3.clone(this.positionWC, scratchSetViewCartesian)); if (defined(destination) && defined(destination.west)) { destination = this.getRectangleCameraCoordinates(destination, scratchSetViewCartesian); convert = false; } if (defined(orientation.direction)) { orientation = directionUpToHeadingPitchRoll(this, destination, orientation, scratchSetViewOptions.orientation); } scratchHpr.heading = defaultValue(orientation.heading, 0.0); scratchHpr.pitch = defaultValue(orientation.pitch, -CesiumMath.PI_OVER_TWO); scratchHpr.roll = defaultValue(orientation.roll, 0.0); if (mode === SceneMode.SCENE3D) { setView3D(this, destination, scratchHpr); } else if (mode === SceneMode.SCENE2D) { setView2D(this, destination, scratchHpr, convert); } else { setViewCV(this, destination, scratchHpr, convert); } }; var pitchScratch = new Cartesian3(); /** * Fly the camera to the home view. Use {@link Camera#.DEFAULT_VIEW_RECTANGLE} to set * the default view for the 3D scene. The home view for 2D and columbus view shows the * entire map. * * @param {Number} [duration] The duration of the flight in seconds. If omitted, Cesium attempts to calculate an ideal duration based on the distance to be traveled by the flight. See {@link Camera#flyTo} */ Camera.prototype.flyHome = function(duration) { var mode = this._mode; if (mode === SceneMode.MORPHING) { this._scene.completeMorph(); } if (mode === SceneMode.SCENE2D) { this.flyTo({ destination : Camera.DEFAULT_VIEW_RECTANGLE, duration : duration, endTransform : Matrix4.IDENTITY }); } else if (mode === SceneMode.SCENE3D) { var destination = this.getRectangleCameraCoordinates(Camera.DEFAULT_VIEW_RECTANGLE); var mag = Cartesian3.magnitude(destination); mag += mag * Camera.DEFAULT_VIEW_FACTOR; Cartesian3.normalize(destination, destination); Cartesian3.multiplyByScalar(destination, mag, destination); this.flyTo({ destination : destination, duration : duration, endTransform : Matrix4.IDENTITY }); } else if (mode === SceneMode.COLUMBUS_VIEW) { var maxRadii = this._projection.ellipsoid.maximumRadius; var position = new Cartesian3(0.0, -1.0, 1.0); position = Cartesian3.multiplyByScalar(Cartesian3.normalize(position, position), 5.0 * maxRadii, position); this.flyTo({ destination : position, duration : duration, orientation : { heading : 0.0, pitch : -Math.acos(Cartesian3.normalize(position, pitchScratch).z), roll : 0.0 }, endTransform : Matrix4.IDENTITY, convert : false }); } }; /** * 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 came