UNPKG

cesium

Version:

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

1,748 lines (1,544 loc) 120 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|Cesium Sandcastle Camera Tutorial Example} * @demo {@link 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 {PerspectiveFrustum|PerspectiveOffCenterFrustum|OrthographicFrustum} * @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 scratchAdjustOrthographicFrustumMousePosition = new Cartesian2(); var scratchPickRay = new Ray(); var scratchRayIntersection = new Cartesian3(); var scratchDepthIntersection = new Cartesian3(); function calculateOrthographicFrustumWidth(camera) { // Camera is fixed to an object, so keep frustum width constant. if (!Matrix4.equals(Matrix4.IDENTITY, camera.transform)) { return Cartesian3.magnitude(camera.position); } var scene = camera._scene; var globe = scene.globe; var mousePosition = scratchAdjustOrthographicFrustumMousePosition; mousePosition.x = scene.drawingBufferWidth / 2.0; mousePosition.y = scene.drawingBufferHeight / 2.0; var rayIntersection; if (defined(globe)) { var ray = camera.getPickRay(mousePosition, scratchPickRay); rayIntersection = globe.pickWorldCoordinates( ray, scene, true, scratchRayIntersection ); } var depthIntersection; if (scene.pickPositionSupported) { depthIntersection = scene.pickPositionWorldCoordinates( mousePosition, scratchDepthIntersection ); } var distance; if (defined(rayIntersection) || defined(depthIntersection)) { var depthDistance = defined(depthIntersection) ? Cartesian3.distance(depthIntersection, camera.positionWC) : Number.POSITIVE_INFINITY; var rayDistance = defined(rayIntersection) ? Cartesian3.distance(rayIntersection, camera.positionWC) : Number.POSITIVE_INFINITY; distance = Math.min(depthDistance, rayDistance); } else { distance = Math.max(camera.positionCartographic.height, 0.0); } return distance; } Camera.prototype._adjustOrthographicFrustum = function (zooming) { if (!(this.frustum instanceof OrthographicFrustum)) { return; } if (!zooming && this._positionCartographic.height < 150000.0) { return; } this.frustum.width = calculateOrthographicFrustumWidth(this); }; 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 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 rotatable2D = camera._scene.mapMode2D === MapMode2D.ROTATE; var maxProjectedX = camera._maxCoord.x; var maxProjectedY = camera._maxCoord.y; var minX; var maxX; if (rotatable2D) { maxX = maxProjectedX; minX = -maxX; } else { maxX = position.x - maxProjectedX * 2.0; minX = position.x + maxProjectedX * 2.0; } if (position.x > maxProjectedX) { position.x = maxX; } if (position.x < -maxProjectedX) { position.x = minX; } if (position.y > maxProjectedY) { position.y = maxProjectedY; } if (position.y < -maxProjectedY) { position.y = -maxProjectedY; } } 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); } this._adjustOrthographicFrustum(true); }; /** * Translates the camera's position by <code>amount</code> along the camera's view vector. * When in 2D mode, this will zoom in the camera instead of translating the camera's position. * * @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); if (this._mode === SceneMode.SCENE2D) { // 2D mode zoom2D(this, amount); } else { // 3D or Columbus view mode this.move(this.direction, amount); } }; /** * Translates the camera's position by <code>amount</code> along the opposite direction * of the camera's view vector. * When in 2D mode, this will zoom out the camera instead of translating the camera's position. * * @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); if (this._mode === SceneMode.SCENE2D) { // 2D mode zoom2D(this, -amount); } else { // 3D or Columbus view mode 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.defaultMoveAmo