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
JavaScript
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