diff --git a/Apps/Sandcastle/gallery/3D Models.html b/Apps/Sandcastle/gallery/3D Models.html index d373a5c8d087..7db881a8bc1a 100644 --- a/Apps/Sandcastle/gallery/3D Models.html +++ b/Apps/Sandcastle/gallery/3D Models.html @@ -30,10 +30,14 @@ var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; -function createModel(url, height) { +function createModel(url, height, heading, pitch, roll) { height = Cesium.defaultValue(height, 0.0); + heading = Cesium.defaultValue(heading, 0.0); + pitch = Cesium.defaultValue(pitch, 0.0); + roll = Cesium.defaultValue(roll, 0.0); - var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height)); + var origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height); + var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll); scene.primitives.removeAll(); // Remove previous model var model = scene.primitives.add(Cesium.Model.fromGltf({ @@ -77,7 +81,11 @@ var options = [{ text : 'Aircraft', onselect : function() { - createModel('../../SampleData/models/CesiumAir/Cesium_Air.gltf', 5000.0); + var height = 5000.0; + var heading = 0.0; + var pitch = Cesium.Math.toRadians(10.0); + var roll = Cesium.Math.toRadians(-20.0); + createModel('../../SampleData/models/CesiumAir/Cesium_Air.gltf', height, heading, pitch, roll); } }, { text : 'Ground vehicle', diff --git a/Apps/Sandcastle/gallery/Camera.html b/Apps/Sandcastle/gallery/Camera.html index a28cdaafa4d7..df8cd1afda12 100644 --- a/Apps/Sandcastle/gallery/Camera.html +++ b/Apps/Sandcastle/gallery/Camera.html @@ -124,6 +124,18 @@ })); } +function setHeadingPitchRoll() { + Sandcastle.declare(setHeadingPitchRoll); + + var camera = viewer.camera; + camera.setView({ + position : Cesium.Cartesian3.fromDegrees(-75.5847, 40.0397, 1000.0), + heading : -Cesium.Math.PI_OVER_TWO, + pitch : -Cesium.Math.PI_OVER_FOUR, + roll : 0.0 + }); +} + function icrf(scene, time) { if (scene.mode !== Cesium.SceneMode.SCENE3D) { return; @@ -181,6 +193,12 @@ Sandcastle.highlight(setReferenceFrame); } }, { + text : 'Set camera with heading, pitch, and roll', + onselect : function() { + setHeadingPitchRoll(); + Sandcastle.highlight(setHeadingPitchRoll); + } +},{ text : 'View in ICRF', onselect : function() { viewInICRF(); diff --git a/CHANGES.md b/CHANGES.md index 14ac5a8a2bd5..b93f13bdae8b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,10 +9,17 @@ Change Log * The `sourceUri` parameter to `GeoJsonDatasource.load` was deprecated in Cesium 1.4 and has been removed. Use options.sourceUri instead. * `PolygonGraphics.positions` created by `GeoJSONDataSource` now evaluate to a `PolygonHierarchy` object instead of an array of positions. * Deprecated + * `Camera.tilt` was deprecated in Cesium 1.6. It will be removed in Cesium 1.7. Use `Camera.pitch`. + * `Camera.heading` and `Camera.tilt` were deprecated in Cesium 1.6. They will become read-only in Cesium 1.7. Use `Camera.setView`. + * `Camera.setPositionCartographic` was deprecated in Cesium 1.6. It will be removed in Cesium 1.7. Use `Camera.setView`. * `PolygonGraphics.positions` was deprecated and replaced with `PolygonGraphics.hierarchy`, whose value is a `PolygonHierarchy` instead of an array of positions. `PolygonGraphics.positions` will be removed in Cesium 1.8. * The `Model.readyToRender` event was deprecated and will be removed in Cesium 1.9. Use the new 'Model.readyPromise' instead. * Improved performance of asynchronous geometry creation (as much as 20% faster in some use cases). [#2342](https://github.com/AnalyticalGraphicsInc/cesium/issues/2342) * Added `PolylineVolumeGraphics` and `Entity.polylineVolume` +* Added `Camera.setView` (which use heading, pitch, and roll) and `Camera.roll`. +* Added `Quaternion.fromHeadingPitchRoll` to create a rotation from heading, pitch, and roll angles. +* Added `Transforms.headingPitchRollToFixedFrame` to create a local frame from a position and heading/pitch/roll angles. +* Added `Transforms.headingPitchRollQuaternion` which is the quaternion rotation from `Transforms.headingPitchRollToFixedFrame`. * Added `BillboardGraphics.imageSubRegion`, to enable custom texture atlas use for `Entity` instances. * Added `CheckerboardMaterialProperty` to enable use of the checkerboard material with the entity API. * Added `PolygonHierarchy` to make defining polygons with holes clearer. diff --git a/Source/Core/Quaternion.js b/Source/Core/Quaternion.js index f189bc96f8fd..b2d8a043564c 100644 --- a/Source/Core/Quaternion.js +++ b/Source/Core/Quaternion.js @@ -175,6 +175,40 @@ define([ return result; }; + var scratchHPRQuaternion = new Quaternion(); + + /** + * Computes a rotation from the given heading, pitch and roll angles. Heading is the rotation about the + * negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about + * the positive x axis. + * + * @param {Number} heading The heading angle in radians. + * @param {Number} pitch The pitch angle in radians. + * @param {Number} roll The roll angle in radians. + * @param {Quaternion} result The object onto which to store the result. + * @returns {Quaternion} The modified result parameter or a new Quaternion instance if none was provided. + */ + Quaternion.fromHeadingPitchRoll = function(heading, pitch, roll, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(heading)) { + throw new DeveloperError('heading is required.'); + } + if (!defined(pitch)) { + throw new DeveloperError('pitch is required.'); + } + if (!defined(roll)) { + throw new DeveloperError('roll is required.'); + } + //>>includeEnd('debug'); + + var headingQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, -heading, result); + var pitchQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_Y, -pitch, scratchHPRQuaternion); + result = Quaternion.multiply(headingQuaternion, pitchQuaternion, headingQuaternion); + + var rollQuaternion = Quaternion.fromAxisAngle(Cartesian3.UNIT_X, roll, scratchHPRQuaternion); + return Quaternion.multiply(rollQuaternion, result, result); + }; + var sampledQuaternionAxis = new Cartesian3(); var sampledQuaternionRotation = new Cartesian3(); var sampledQuaternionTempQuaternion = new Quaternion(); diff --git a/Source/Core/Transforms.js b/Source/Core/Transforms.js index 6a0f52f1e6e9..1050e7f29e76 100644 --- a/Source/Core/Transforms.js +++ b/Source/Core/Transforms.js @@ -16,6 +16,7 @@ define([ './Math', './Matrix3', './Matrix4', + './Quaternion', './TimeConstants' ], function( when, @@ -34,6 +35,7 @@ define([ CesiumMath, Matrix3, Matrix4, + Quaternion, TimeConstants) { "use strict"; @@ -344,6 +346,72 @@ define([ return result; }; + var scratchHPRQuaternion = new Quaternion(); + var scratchScale = new Cartesian3(1.0, 1.0, 1.0); + var scratchHPRMatrix4 = new Matrix4(); + + /** + * Computes a 4x4 transformation matrix from a reference frame with axes computed from the heading-pitch-roll angles + * centered at the provided origin to the provided ellipsoid's fixed reference frame. Heading is the rotation from the local north + * direction where a positive angle is increasing eastward. Pitch is the rotation from the local east-north plane. Positive pitch angles + * are above the plane. Negative pitch angles are below the plane. Roll is the first rotation applied about the local east axis. + * + * @param {Cartesian3} origin The center point of the local reference frame. + * @param {Number} heading The heading angle in radians. + * @param {Number} pitch The pitch angle in radians. + * @param {Number} roll The roll angle in radians. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation. + * @param {Matrix4} [result] The object onto which to store the result. + * @returns {Matrix4} The modified result parameter or a new Matrix4 instance if none was provided. + * + * @example + * // Get the transform from local heading-pitch-roll at cartographic (0.0, 0.0) to Earth's fixed frame. + * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0); + * var heading = -Cesium.Math.PI_OVER_TWO; + * var pitch = Cesium.Math.PI_OVER_FOUR; + * var roll = 0.0; + * var transform = Cesium.Transforms.headingPitchRollToFixedFrame(center, heading, pitch, roll); + */ + Transforms.headingPitchRollToFixedFrame = function(origin, heading, pitch, roll, ellipsoid, result) { + // checks for required parameters happen in the called functions + var hprQuaternion = Quaternion.fromHeadingPitchRoll(heading, pitch, roll, scratchHPRQuaternion); + var hprMatrix = Matrix4.fromTranslationQuaternionRotationScale(Cartesian3.ZERO, hprQuaternion, scratchScale, scratchHPRMatrix4); + result = Transforms.eastNorthUpToFixedFrame(origin, ellipsoid, result); + return Matrix4.multiply(result, hprMatrix, result); + }; + + var scratchENUMatrix4 = new Matrix4(); + var scratchHPRMatrix3 = new Matrix3(); + + /** + * Computes a quaternion from a reference frame with axes computed from the heading-pitch-roll angles + * centered at the provided origin. Heading is the rotation from the local north + * direction where a positive angle is increasing eastward. Pitch is the rotation from the local east-north plane. Positive pitch angles + * are above the plane. Negative pitch angles are below the plane. Roll is the first rotation applied about the local east axis. + * + * @param {Cartesian3} origin The center point of the local reference frame. + * @param {Number} heading The heading angle in radians. + * @param {Number} pitch The pitch angle in radians. + * @param {Number} roll The roll angle in radians. + * @param {Ellipsoid} [ellipsoid=Ellipsoid.WGS84] The ellipsoid whose fixed frame is used in the transformation. + * @param {Quaternion} [result] The object onto which to store the result. + * @returns {Quaternion} The modified result parameter or a new Quaternion instance if none was provided. + * + * @example + * // Get the quaternion from local heading-pitch-roll at cartographic (0.0, 0.0) to Earth's fixed frame. + * var center = Cesium.Cartesian3.fromDegrees(0.0, 0.0); + * var heading = -Cesium.Math.PI_OVER_TWO; + * var pitch = Cesium.Math.PI_OVER_FOUR; + * var roll = 0.0; + * var quaternion = Cesium.Transforms.headingPitchRollQuaternion(center, heading, pitch, roll); + */ + Transforms.headingPitchRollQuaternion = function(origin, heading, pitch, roll, ellipsoid, result) { + // checks for required parameters happen in the called functions + var transform = Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll, ellipsoid, scratchENUMatrix4); + var rotation = Matrix4.getRotation(transform, scratchHPRMatrix3); + return Quaternion.fromRotationMatrix(rotation, result); + }; + var gmstConstant0 = 6 * 3600 + 41 * 60 + 50.54841; var gmstConstant1 = 8640184.812866; diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js index 51cfa610a9cc..c5d13949160b 100644 --- a/Source/Scene/Camera.js +++ b/Source/Scene/Camera.js @@ -7,6 +7,7 @@ define([ '../Core/defaultValue', '../Core/defined', '../Core/defineProperties', + '../Core/deprecationWarning', '../Core/DeveloperError', '../Core/EasingFunction', '../Core/Ellipsoid', @@ -29,6 +30,7 @@ define([ defaultValue, defined, defineProperties, + deprecationWarning, DeveloperError, EasingFunction, Ellipsoid, @@ -506,52 +508,8 @@ define([ } } - function getHeading2D(camera) { - return Math.atan2(camera.right.y, camera.right.x); - } - - var scratchHeadingMatrix4 = new Matrix4(); - var scratchHeadingMatrix3 = new Matrix3(); - var scratchHeadingCartesian3 = new Cartesian3(); - - function getHeading3D(camera) { - var ellipsoid = camera._projection.ellipsoid; - var toFixedFrame = Transforms.eastNorthUpToFixedFrame(camera.position, ellipsoid, scratchHeadingMatrix4); - var transform = Matrix4.getRotation(toFixedFrame, scratchHeadingMatrix3); - Matrix3.transpose(transform, transform); - - var right = Matrix3.multiplyByVector(transform, camera.right, scratchHeadingCartesian3); - return Math.atan2(right.y, right.x); - } - - function setHeading2D(camera, angle) { - var rightAngle = getHeading2D(camera); - angle = rightAngle - angle; - camera.look(Cartesian3.UNIT_Z, angle); - } - - var scratchHeadingAxis = new Cartesian3(); - - function setHeading3D(camera, angle) { - var axis = Cartesian3.normalize(camera.position, scratchHeadingAxis); - var upAngle = getHeading3D(camera); - angle = upAngle - angle; - camera.look(axis, angle); - } - - function getTiltCV(camera) { - // CesiumMath.acosClamped(dot(camera.direction, Cartesian3.negate(Cartesian3.UNIT_Z)) - return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(-camera.direction.z); - } - - var scratchTiltCartesian3 = new Cartesian3(); - - function getTilt3D(camera) { - var direction = Cartesian3.normalize(camera.position, scratchTiltCartesian3); - Cartesian3.negate(direction, direction); - - return CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(Cartesian3.dot(camera.direction, direction)); - } + var scratchHPRMatrix1 = new Matrix4(); + var scratchHPRMatrix2 = new Matrix4(); defineProperties(Camera.prototype, { /** @@ -675,23 +633,40 @@ define([ }, /** - * Gets or sets the camera heading in radians. + * Gets the camera heading in radians. * @memberof Camera.prototype * * @type {Number} */ heading : { get : function () { - if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) { - return getHeading2D(this); - } else if (this._mode === SceneMode.SCENE3D) { - return getHeading3D(this); + if (this._mode !== SceneMode.MORPHING) { + var origin = this.positionWC; + var ellipsoid = this._projection.ellipsoid; + + var oldTransform = Matrix4.clone(this.transform, scratchHPRMatrix1); + var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); + this.setTransform(transform); + + var right = this.right; + var direction = this.direction; + + var heading; + if (Math.abs(direction.z) < Math.abs(right.z)) { + heading = Math.atan2(direction.y, direction.x) - CesiumMath.PI_OVER_TWO; + } else { + heading = Math.atan2(right.y, right.x); + } + + this.setTransform(oldTransform); + + return CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(heading); } return undefined; }, - //TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832 set : function (angle) { + deprecationWarning('Camera.heading', 'Camera.heading was deprecated in Cesium 1.6. It will be removed in Cesium 1.7. Use Camera.setView.'); //>>includeStart('debug', pragmas.debug); if (!defined(angle)) { @@ -699,32 +674,54 @@ define([ } //>>includeEnd('debug'); - if (this._mode === SceneMode.SCENE2D || this._mode === SceneMode.COLUMBUS_VIEW) { - setHeading2D(this, angle); - } else if (this._mode === SceneMode.SCENE3D) { - setHeading3D(this, angle); + if (this._mode !== SceneMode.MORPHING) { + this.setView({ heading : angle }); } } }, /** - * Gets or sets the camera tilt in radians. + * Gets the camera pitch in radians. * @memberof Camera.prototype * * @type {Number} */ - tilt : { + pitch : { get : function() { - if (this._mode === SceneMode.COLUMBUS_VIEW) { - return getTiltCV(this); - } else if (this._mode === SceneMode.SCENE3D) { - return getTilt3D(this); + if (this._mode === SceneMode.COLUMBUS_VIEW || this._mode === SceneMode.SCENE3D) { + var origin = this.positionWC; + 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 = CesiumMath.PI_OVER_TWO - CesiumMath.acosClamped(this.direction.z); + + this.setTransform(oldTransform); + + return pitch; } return undefined; + } + }, + + /** + * Gets or sets the camera tilt in radians. + * @memberof Camera.prototype + * + * @type {Number} + * + * @deprecated + */ + tilt : { + get : function() { + deprecationWarning('Camera.tilt', 'Camera.tilt was deprecated in Cesium 1.6. It will be removed in Cesium 1.7. Use Camera.pitch.'); + return this.pitch; }, - //TODO See https://github.com/AnalyticalGraphicsInc/cesium/issues/832 set : function(angle) { + deprecationWarning('Camera.tilt', 'Camera.tilt was deprecated in Cesium 1.6. It will be removed in Cesium 1.7. Use Camera.setView.'); //>>includeStart('debug', pragmas.debug); if (!defined(angle)) { @@ -733,10 +730,41 @@ define([ //>>includeEnd('debug'); if (this._mode === SceneMode.COLUMBUS_VIEW || this._mode === SceneMode.SCENE3D) { - angle = CesiumMath.clamp(angle, -CesiumMath.PI_OVER_TWO, CesiumMath.PI_OVER_TWO); - angle = angle - this.tilt; - this.look(this.right, angle); + this.setView({ pitch : angle }); + } + } + }, + + /** + * Gets the camera roll in radians. + * @memberof Camera.prototype + * + * @type {Number} + */ + roll : { + get : function() { + if (this._mode === SceneMode.COLUMBUS_VIEW || this._mode === SceneMode.SCENE3D) { + var origin = this.positionWC; + var ellipsoid = this._projection.ellipsoid; + + var oldTransform = Matrix4.clone(this.transform, scratchHPRMatrix1); + var transform = Transforms.eastNorthUpToFixedFrame(this.positionWC, ellipsoid, scratchHPRMatrix2); + this.setTransform(transform); + + var up = this.up; + var right = this.right; + + var roll = Math.acos(-right.z) - CesiumMath.PI_OVER_TWO; + if (up.z < 0.0) { + roll = CesiumMath.PI - roll; + } + + this.setTransform(oldTransform); + + return CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(roll); } + + return undefined; } } }); @@ -801,6 +829,112 @@ define([ Cartesian3.cross(this.direction, this.up, this.right); }; + var scratchSetViewCartesian = new Cartesian3(); + var scratchSetViewTransform1 = new Matrix4(); + var scratchSetViewTransform2 = new Matrix4(); + var scratchSetViewQuaternion = new Quaternion(); + var scratchSetViewMatrix3 = new Matrix3(); + var scratchSetViewCartographic = new Cartographic(); + + /** + * Sets the camera position and orientation with heading, pitch and roll angles. + * + * The position can be given as either a cartesian or a cartographic. If both are given, + * then the cartesian will be used. If neither is given, then the current camera position + * will be used. + * + * @param {Cartesian3} [options.position] The cartesian position of the camera. + * @param {Cartographic} [options.positionCartographic] The cartographic position of the camera. + * @param {Number} [options.heading] The heading in radians or the current heading will be used if undefined. + * @param {Number} [options.pitch] The pitch in radians or the current pitch will be used if undefined. + * @param {Number} [options.roll] The roll in radians or the current roll will be used if undefined. + * + * @example + * // 1. Set view with heading, pitch and roll + * camera.setView({ + * position : cartesianPosition, + * 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 + * }); + * + * // 2. Set default top-down view with a cartographic position + * camera.setView({ + * positionCartographic : cartographic + * }); + * + * // 3. Change heading, pitch and roll with the camera position remaining the same. + * camera.setView({ + * 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 + * }); + */ + Camera.prototype.setView = function(options) { + if (this._mode === SceneMode.MORPHING) { + return; + } + + options = defaultValue(options, defaultValue.EMPTY_OBJECT); + + var scene2D = this._mode === SceneMode.SCENE2D; + var sceneCV = this._mode === SceneMode.COLUMBUS_VIEW; + + var heading = defaultValue(options.heading, this.heading); + var pitch = scene2D ? -CesiumMath.PI_OVER_TWO : defaultValue(options.pitch, this.pitch); + var roll = scene2D ? 0.0 : defaultValue(options.roll, this.roll); + + if (sceneCV) { + roll = -roll; + } + + var cartesian = options.position; + var cartographic = options.positionCartographic; + + var projection = this._projection; + var ellipsoid = projection.ellipsoid; + + if (!defined(cartesian)) { + if (defined(cartographic)) { + cartesian = ellipsoid.cartographicToCartesian(cartographic, scratchSetViewCartesian); + } else { + cartesian = Cartesian3.clone(this.positionWC, scratchSetViewCartesian); + } + } + + var currentTransform = Matrix4.clone(this.transform, scratchSetViewTransform1); + var localTransform = Transforms.eastNorthUpToFixedFrame(cartesian, ellipsoid, scratchSetViewTransform2); + this.setTransform(localTransform); + + if (scene2D) { + Cartesian2.clone(Cartesian3.ZERO, this.position); + + var cartographic2D = ellipsoid.cartesianToCartographic(cartesian, scratchSetViewCartographic); + var newLeft = -cartographic2D.height * 0.5; + var newRight = -newLeft; + + var frustum = this.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; + } + } else { + Cartesian3.clone(Cartesian3.ZERO, this.position); + } + + var rotQuat = Quaternion.fromHeadingPitchRoll(heading - CesiumMath.PI_OVER_TWO, pitch, roll, scratchSetViewQuaternion); + var rotMat = Matrix3.fromQuaternion(rotQuat, scratchSetViewMatrix3); + + Matrix3.getColumn(rotMat, 0, this.direction); + Matrix3.getColumn(rotMat, 2, this.up); + Cartesian3.cross(this.direction, this.up, this.right); + + this.setTransform(currentTransform); + }; + /** * Transform a vector or point from world coordinates to the camera's reference frame. * @@ -1369,64 +1503,28 @@ define([ } }; - function setPositionCartographic2D(camera, cartographic) { - var newLeft = -cartographic.height * 0.5; - var newRight = -newLeft; - - var frustum = camera.frustum; - if (newRight > newLeft) { - var ratio = frustum.top / frustum.right; - frustum.right = newRight; - frustum.left = newLeft; - frustum.top = frustum.right * ratio; - frustum.bottom = -frustum.top; - } - - //We use Cartesian2 instead of 3 here because Z must be constant in 2D mode. - Cartesian2.clone(camera._projection.project(cartographic), camera.position); - Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction); - Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); - Cartesian3.clone(Cartesian3.UNIT_X, camera.right); - } - - function setPositionCartographicCV(camera, cartographic) { - var projection = camera._projection; - camera.position = projection.project(cartographic); - Cartesian3.negate(Cartesian3.UNIT_Z, camera.direction); - Cartesian3.clone(Cartesian3.UNIT_Y, camera.up); - Cartesian3.clone(Cartesian3.UNIT_X, camera.right); - } - - function setPositionCartographic3D(camera, cartographic) { - var ellipsoid = camera._projection.ellipsoid; - - ellipsoid.cartographicToCartesian(cartographic, camera.position); - Cartesian3.negate(camera.position, camera.direction); - Cartesian3.normalize(camera.direction, camera.direction); - Cartesian3.cross(camera.direction, Cartesian3.UNIT_Z, camera.right); - Cartesian3.cross(camera.right, camera.direction, camera.up); - Cartesian3.cross(camera.direction, camera.up, camera.right); - } - /** * Moves the camera to the provided cartographic position. * + * @deprecated + * * @param {Cartographic} cartographic The new camera position. */ Camera.prototype.setPositionCartographic = function(cartographic) { + deprecationWarning('Camera.setPositionCartographic', 'Camera.setPositionCartographic was deprecated in Cesium 1.6. It will be removed in Cesium 1.7. Use Camera.setView.'); + //>>includeStart('debug', pragmas.debug); if (!defined(cartographic)) { throw new DeveloperError('cartographic is required.'); } //>>includeEnd('debug'); - if (this._mode === SceneMode.SCENE2D) { - setPositionCartographic2D(this, cartographic); - } else if (this._mode === SceneMode.COLUMBUS_VIEW) { - setPositionCartographicCV(this, cartographic); - } else if (this._mode === SceneMode.SCENE3D) { - setPositionCartographic3D(this, cartographic); - } + this.setView({ + cartographic : cartographic, + heading : 0.0, + pitch : -CesiumMath.PI_OVER_TWO, + roll : 0.0 + }); }; /** diff --git a/Specs/Core/QuaternionSpec.js b/Specs/Core/QuaternionSpec.js index 17f91141e1ad..10ce2ddf5300 100644 --- a/Specs/Core/QuaternionSpec.js +++ b/Specs/Core/QuaternionSpec.js @@ -104,6 +104,42 @@ defineSuite([ expect(Matrix3.fromQuaternion(quaternion)).toEqualEpsilon(matrix, CesiumMath.EPSILON12); }); + it('fromHeadingPitchRoll with just heading', function() { + var angle = CesiumMath.toRadians(20.0); + var quaternion = Quaternion.fromHeadingPitchRoll(angle, 0.0, 0.0); + expect(Matrix3.fromQuaternion(quaternion)).toEqualEpsilon(Matrix3.fromRotationZ(-angle), CesiumMath.EPSILON11); + }); + + it('fromHeadingPitchRoll with just pitch', function() { + var angle = CesiumMath.toRadians(20.0); + var quaternion = Quaternion.fromHeadingPitchRoll(0.0, angle, 0.0); + expect(Matrix3.fromQuaternion(quaternion)).toEqualEpsilon(Matrix3.fromRotationY(-angle), CesiumMath.EPSILON11); + }); + + it('fromHeadingPitchRoll with just roll', function() { + var angle = CesiumMath.toRadians(20.0); + var quaternion = Quaternion.fromHeadingPitchRoll(0.0, 0.0, angle); + expect(Matrix3.fromQuaternion(quaternion)).toEqualEpsilon(Matrix3.fromRotationX(angle), CesiumMath.EPSILON11); + }); + + it('fromHeadingPitchRoll with all angles', function() { + var angle = CesiumMath.toRadians(20.0); + var quaternion = Quaternion.fromHeadingPitchRoll(angle, angle, angle); + var expected = Matrix3.fromRotationY(-angle); + Matrix3.multiply(Matrix3.fromRotationZ(-angle), expected, expected); + Matrix3.multiply(Matrix3.fromRotationX(angle), expected, expected); + expect(Matrix3.fromQuaternion(quaternion)).toEqualEpsilon(expected, CesiumMath.EPSILON11); + }); + + it('fromHeadingPitchRoll works with result parameter', function() { + var angle = CesiumMath.toRadians(20.0); + var result = new Quaternion(); + var quaternion = Quaternion.fromHeadingPitchRoll(0.0, 0.0, angle, result); + var expected = Quaternion.fromRotationMatrix(Matrix3.fromRotationX(angle)); + expect(quaternion).toBe(result); + expect(quaternion).toEqualEpsilon(expected, CesiumMath.EPSILON11); + }); + it('clone without a result parameter', function() { var quaternion = new Quaternion(1.0, 2.0, 3.0, 4.0); var result = quaternion.clone(); @@ -608,6 +644,24 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('fromHeadingPitchRoll throws with undefined heading', function() { + expect(function() { + Quaternion.fromHeadingPitchRoll(undefined, 0.0, 0.0); + }).toThrowDeveloperError(); + }); + + it('fromHeadingPitchRoll throws with undefined pitch', function() { + expect(function() { + Quaternion.fromHeadingPitchRoll(0.0, undefined, 0.0); + }).toThrowDeveloperError(); + }); + + it('fromHeadingPitchRoll throws with undefined roll', function() { + expect(function() { + Quaternion.fromHeadingPitchRoll(0.0, 0.0, undefined); + }).toThrowDeveloperError(); + }); + it('clone returns undefined with no parameter', function() { expect(Quaternion.clone()).toBeUndefined(); }); diff --git a/Specs/Core/TransformsSpec.js b/Specs/Core/TransformsSpec.js index 56bf147ade2d..86a21816d15b 100644 --- a/Specs/Core/TransformsSpec.js +++ b/Specs/Core/TransformsSpec.js @@ -186,6 +186,92 @@ defineSuite([ expect(Matrix4.getColumn(returnedResult, 3, new Cartesian4())).toEqual(expectedTranslation); // translation }); + it('headingPitchRollToFixedFrame works without a result parameter', function() { + var origin = new Cartesian3(1.0, 0.0, 0.0); + var heading = CesiumMath.toRadians(20.0); + var pitch = CesiumMath.toRadians(30.0); + var roll = CesiumMath.toRadians(40.0); + + var expectedRotation = Matrix3.fromQuaternion(Quaternion.fromHeadingPitchRoll(heading, pitch, roll)); + var expectedX = Matrix3.getColumn(expectedRotation, 0, new Cartesian3()); + var expectedY = Matrix3.getColumn(expectedRotation, 1, new Cartesian3()); + var expectedZ = Matrix3.getColumn(expectedRotation, 2, new Cartesian3()); + + Cartesian3.fromElements(expectedX.z, expectedX.x, expectedX.y, expectedX); + Cartesian3.fromElements(expectedY.z, expectedY.x, expectedY.y, expectedY); + Cartesian3.fromElements(expectedZ.z, expectedZ.x, expectedZ.y, expectedZ); + + var returnedResult = Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll, Ellipsoid.UNIT_SPHERE); + var actualX = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 0, new Cartesian4())); + var actualY = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 1, new Cartesian4())); + var actualZ = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 2, new Cartesian4())); + var actualTranslation = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 3, new Cartesian4())); + + expect(actualX).toEqual(expectedX); + expect(actualY).toEqual(expectedY); + expect(actualZ).toEqual(expectedZ); + expect(actualTranslation).toEqual(origin); + }); + + it('headingPitchRollToFixedFrame works with a result parameter', function() { + var origin = new Cartesian3(1.0, 0.0, 0.0); + var heading = CesiumMath.toRadians(20.0); + var pitch = CesiumMath.toRadians(30.0); + var roll = CesiumMath.toRadians(40.0); + + var expectedRotation = Matrix3.fromQuaternion(Quaternion.fromHeadingPitchRoll(heading, pitch, roll)); + var expectedX = Matrix3.getColumn(expectedRotation, 0, new Cartesian3()); + var expectedY = Matrix3.getColumn(expectedRotation, 1, new Cartesian3()); + var expectedZ = Matrix3.getColumn(expectedRotation, 2, new Cartesian3()); + + Cartesian3.fromElements(expectedX.z, expectedX.x, expectedX.y, expectedX); + Cartesian3.fromElements(expectedY.z, expectedY.x, expectedY.y, expectedY); + Cartesian3.fromElements(expectedZ.z, expectedZ.x, expectedZ.y, expectedZ); + + var result = new Matrix4(); + var returnedResult = Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll, Ellipsoid.UNIT_SPHERE, result); + var actualX = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 0, new Cartesian4())); + var actualY = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 1, new Cartesian4())); + var actualZ = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 2, new Cartesian4())); + var actualTranslation = Cartesian3.fromCartesian4(Matrix4.getColumn(returnedResult, 3, new Cartesian4())); + + expect(returnedResult).toBe(result); + expect(actualX).toEqual(expectedX); + expect(actualY).toEqual(expectedY); + expect(actualZ).toEqual(expectedZ); + expect(actualTranslation).toEqual(origin); + }); + + it('headingPitchRollQuaternion works without a result parameter', function() { + var origin = new Cartesian3(1.0, 0.0, 0.0); + var heading = CesiumMath.toRadians(20.0); + var pitch = CesiumMath.toRadians(30.0); + var roll = CesiumMath.toRadians(40.0); + + var transform = Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll, Ellipsoid.UNIT_SPHERE); + var expected = Matrix4.getRotation(transform, new Matrix3()); + + var quaternion = Transforms.headingPitchRollQuaternion(origin, heading, pitch, roll, Ellipsoid.UNIT_SPHERE); + var actual = Matrix3.fromQuaternion(quaternion); + expect(actual).toEqualEpsilon(expected, CesiumMath.EPSILON11); + }); + + it('headingPitchRollQuaternion works with a result parameter', function() { + var origin = new Cartesian3(1.0, 0.0, 0.0); + var heading = CesiumMath.toRadians(20.0); + var pitch = CesiumMath.toRadians(30.0); + var roll = CesiumMath.toRadians(40.0); + + var transform = Transforms.headingPitchRollToFixedFrame(origin, heading, pitch, roll, Ellipsoid.UNIT_SPHERE); + var expected = Matrix4.getRotation(transform, new Matrix3()); + + var result = new Quaternion(); + var quaternion = Transforms.headingPitchRollQuaternion(origin, heading, pitch, roll, Ellipsoid.UNIT_SPHERE, result); + var actual = Matrix3.fromQuaternion(quaternion); + expect(quaternion).toBe(result); + expect(actual).toEqualEpsilon(expected, CesiumMath.EPSILON11); + }); + it('computeTemeToPseudoFixedMatrix works before noon', function() { var time = JulianDate.now(); var secondsDiff = TimeConstants.SECONDS_PER_DAY - time.secondsOfDay; @@ -683,6 +769,30 @@ defineSuite([ }).toThrowDeveloperError(); }); + it('headingPitchRollToFixedFrame throws without an origin', function() { + expect(function() { + Transforms.headingPitchRollToFixedFrame(undefined, 0.0, 0.0, 0.0); + }).toThrowDeveloperError(); + }); + + it('headingPitchRollToFixedFrame throws without an heading', function() { + expect(function() { + Transforms.headingPitchRollToFixedFrame(Cartesian3.ZERO, undefined, 0.0, 0.0); + }).toThrowDeveloperError(); + }); + + it('headingPitchRollToFixedFrame throws without an pitch', function() { + expect(function() { + Transforms.headingPitchRollToFixedFrame(Cartesian3.ZERO, 0.0, undefined, 0.0); + }).toThrowDeveloperError(); + }); + + it('headingPitchRollToFixedFrame throws without an roll', function() { + expect(function() { + Transforms.headingPitchRollToFixedFrame(Cartesian3.ZERO, 0.0, 0.0, undefined); + }).toThrowDeveloperError(); + }); + it('computeTemeToPseudoFixedMatrix throws without a date', function() { expect(function() { Transforms.computeTemeToPseudoFixedMatrix(undefined); diff --git a/Specs/Scene/CameraSpec.js b/Specs/Scene/CameraSpec.js index 100dd19c4a8f..505b02ae846d 100644 --- a/Specs/Scene/CameraSpec.js +++ b/Specs/Scene/CameraSpec.js @@ -126,14 +126,14 @@ defineSuite([ it('get heading in 2D', function() { camera._mode = SceneMode.SCENE2D; - var heading = Math.atan2(camera.right.y, camera.right.x); + var heading = CesiumMath.TWO_PI - Math.atan2(camera.right.y, camera.right.x); expect(camera.heading).toEqual(heading); }); it('get heading in CV', function() { camera._mode = SceneMode.COLUMBUS_VIEW; - var heading = Math.atan2(camera.right.y, camera.right.x); + var heading = CesiumMath.TWO_PI - Math.atan2(camera.right.y, camera.right.x); expect(camera.heading).toEqual(heading); }); @@ -146,23 +146,19 @@ defineSuite([ Matrix3.transpose(transform, transform); var right = Matrix3.multiplyByVector(transform, camera.right, new Cartesian3()); - var heading = Math.atan2(right.y, right.x); + var heading = CesiumMath.TWO_PI - CesiumMath.zeroToTwoPi(Math.atan2(right.y, right.x)); expect(camera.heading).toEqual(heading); }); - it('set heading throws without angle', function() { - expect(function() { - camera.heading = undefined; - }).toThrowDeveloperError(); - }); - it('set heading in 2D', function() { camera._mode = SceneMode.SCENE2D; var heading = camera.heading; var newHeading = CesiumMath.toRadians(45.0); - camera.heading = newHeading; + camera.setView({ + heading : newHeading + }); expect(camera.heading).not.toEqual(heading); expect(camera.heading).toEqualEpsilon(newHeading, CesiumMath.EPSILON14); @@ -171,9 +167,16 @@ defineSuite([ it('set heading in CV', function() { camera._mode = SceneMode.COLUMBUS_VIEW; + camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0); + camera.direction = Cartesian3.negate(Cartesian3.normalize(camera.position, new Cartesian3()), new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); + var heading = camera.heading; var newHeading = CesiumMath.toRadians(45.0); - camera.heading = newHeading; + camera.setView({ + heading : newHeading + }); expect(camera.heading).not.toEqual(heading); expect(camera.heading).toEqualEpsilon(newHeading, CesiumMath.EPSILON14); @@ -189,65 +192,147 @@ defineSuite([ var heading = camera.heading; var newHeading = CesiumMath.toRadians(45.0); - camera.heading = newHeading; + camera.setView({ + heading : newHeading + }); expect(camera.heading).not.toEqual(heading); expect(camera.heading).toEqualEpsilon(newHeading, CesiumMath.EPSILON14); }); - it('tilt is undefined when mode is not 3D or Columbus view', function() { + it('pitch is undefined when mode is not 3D or Columbus view', function() { camera._mode = SceneMode.MORPHING; - expect(camera.tilt).not.toBeDefined(); + expect(camera.pitch).not.toBeDefined(); }); - it('get tilt in CV', function() { + it('get pitch in CV', function() { camera._mode = SceneMode.COLUMBUS_VIEW; - var tilt = CesiumMath.PI_OVER_TWO - Math.acos(-camera.direction.z); - expect(camera.tilt).toEqual(tilt); + camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0); + camera.direction = Cartesian3.negate(Cartesian3.normalize(camera.position, new Cartesian3()), new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); + + var pitch = CesiumMath.PI_OVER_TWO - Math.acos(-camera.direction.z); + expect(camera.pitch).toEqualEpsilon(pitch, CesiumMath.EPSILON6); }); - it('get tilt in 3D', function() { + it('get pitch in 3D', function() { camera._mode = SceneMode.SCENE3D; var direction = Cartesian3.normalize(camera.position, new Cartesian3()); Cartesian3.negate(direction, direction); - var tilt = CesiumMath.PI_OVER_TWO - Math.acos(Cartesian3.dot(camera.direction, direction)); + var pitch = CesiumMath.PI_OVER_TWO - Math.acos(-Cartesian3.dot(camera.direction, direction)); - expect(camera.tilt).toEqual(tilt); + expect(camera.pitch).toEqual(pitch); }); - it('set tilt throws without angle', function() { - expect(function() { - camera.tilt = undefined; - }).toThrowDeveloperError(); + it('set pitch in CV', function() { + camera._mode = SceneMode.COLUMBUS_VIEW; + + camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0); + camera.direction = Cartesian3.negate(Cartesian3.normalize(camera.position, new Cartesian3()), new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); + + var pitch = camera.pitch; + var newPitch = CesiumMath.toRadians(45.0); + camera.setView({ + pitch : newPitch + }); + + expect(camera.pitch).not.toEqual(pitch); + expect(camera.pitch).toEqualEpsilon(newPitch, CesiumMath.EPSILON6); + }); + + it('set pitch in 3D', function() { + camera._mode = SceneMode.SCENE3D; + + camera.position = Cartesian3.clone(Cartesian3.UNIT_X); + camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); + + var pitch = camera.pitch; + var newPitch = CesiumMath.toRadians(45.0); + camera.setView({ + pitch : newPitch + }); + + expect(camera.pitch).not.toEqual(pitch); + expect(camera.pitch).toEqualEpsilon(newPitch, CesiumMath.EPSILON14); }); - it('set tilt in CV', function() { + it('roll is undefined when not 3D or Columbus view', function() { + camera._mode = SceneMode.SCENE2D; + expect(camera.roll).not.toBeDefined(); + }); + + it('get roll in CV', function() { camera._mode = SceneMode.COLUMBUS_VIEW; - var tilt = camera.tilt; - var newTilt = CesiumMath.toRadians(45.0); - camera.tilt = newTilt; + camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0); + camera.direction = Cartesian3.negate(Cartesian3.normalize(camera.position, new Cartesian3()), new Cartesian3()); + camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); + camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); + + camera.look(camera.direction, CesiumMath.toRadians(45.0)); - expect(camera.tilt).not.toEqual(tilt); - expect(camera.tilt).toEqualEpsilon(newTilt, CesiumMath.EPSILON14); + var roll = CesiumMath.zeroToTwoPi(-CesiumMath.toRadians(45.0)); + expect(camera.roll).toEqualEpsilon(roll, CesiumMath.EPSILON6); }); - it('set tilt in 3D', function() { + it('get roll in 3D', function() { camera._mode = SceneMode.SCENE3D; + var ellipsoid = Ellipsoid.WGS84; camera.position = Cartesian3.clone(Cartesian3.UNIT_X); - camera.direction = Cartesian3.negate(Cartesian3.UNIT_X, new Cartesian3()); + Cartesian3.multiplyByScalar(camera.position, ellipsoid.maximumRadius + 100.0, camera.position); + camera.direction = new Cartesian3(-1.0, 0.0, 1.0); + Cartesian3.normalize(camera.direction, camera.direction); + camera.right = Cartesian3.cross(camera.direction, Cartesian3.UNIT_Z, new Cartesian3()); + Cartesian3.normalize(camera.right, camera.right); + camera.up = Cartesian3.cross(camera.right, camera.direction, new Cartesian3()); + + var toFixedFrame = Transforms.eastNorthUpToFixedFrame(camera.position, ellipsoid); + var transform = Matrix4.getRotation(toFixedFrame, new Matrix3()); + Matrix3.transpose(transform, transform); + + var right = Matrix3.multiplyByVector(transform, camera.right, new Cartesian3()); + var roll = CesiumMath.TWO_PI - Math.atan2(right.z, right.x); + + expect(camera.roll).toEqual(roll); + }); + + it('set roll in CV', function() { + camera._mode = SceneMode.COLUMBUS_VIEW; + + camera.position = Cartesian3.fromDegrees(0.0, 0.0, 100000.0); + camera.direction = Cartesian3.negate(Cartesian3.normalize(camera.position, new Cartesian3()), new Cartesian3()); camera.up = Cartesian3.clone(Cartesian3.UNIT_Z); camera.right = Cartesian3.cross(camera.direction, camera.up, new Cartesian3()); - var tilt = camera.tilt; - var newTilt = CesiumMath.toRadians(45.0); - camera.tilt = newTilt; + var roll = camera.roll; + var newRoll = CesiumMath.PI_OVER_FOUR; + camera.setView({ + roll : newRoll + }); + + expect(camera.roll).not.toEqual(roll); + expect(camera.roll).toEqualEpsilon(newRoll, CesiumMath.EPSILON6); + }); + + it('set roll in 3D', function() { + camera._mode = SceneMode.SCENE3D; + + var roll = camera.roll; + var newRoll = CesiumMath.PI_OVER_FOUR; + camera.setView({ + roll : newRoll + }); - expect(camera.tilt).not.toEqual(tilt); - expect(camera.tilt).toEqualEpsilon(newTilt, CesiumMath.EPSILON14); + expect(camera.roll).not.toEqual(roll); + expect(camera.roll).toEqualEpsilon(newRoll, CesiumMath.EPSILON6); }); it('update throws without mode', function() { @@ -278,6 +363,82 @@ defineSuite([ expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON9); }); + it('setView with cartographic in 2D', function() { + var ellipsoid = Ellipsoid.WGS84; + var projection = new GeographicProjection(ellipsoid); + var maxRadii = ellipsoid.maximumRadius; + + camera._mode = SceneMode.SCENE2D; + camera._projection = projection; + + var frustum = new OrthographicFrustum(); + frustum.right = maxRadii * Math.PI; + frustum.left = -frustum.right; + frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); + frustum.bottom = -frustum.top; + frustum.near = 0.01 * maxRadii; + frustum.far = 60.0 * maxRadii; + camera.frustum = frustum; + + var ratio = frustum.top / frustum.right; + var cart = Cartographic.fromDegrees(-75.0, 42.0, 100.0); + camera.setView({ + heading : 0.0, + pitch : -CesiumMath.PI_OVER_TWO, + roll : 0.0, + positionCartographic : cart + }); + + expect(Cartesian2.fromCartesian3(camera.position, new Cartesian2())).toEqualEpsilon(Cartesian2.fromCartesian3(projection.project(cart), new Cartesian2()), CesiumMath.EPSILON11); + expect(camera.direction).toEqualEpsilon(Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), CesiumMath.EPSILON6); + expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON6); + expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON6); + expect(frustum.right - frustum.left).toEqualEpsilon(cart.height, CesiumMath.EPSILON6); + expect(frustum.top / frustum.right).toEqual(ratio); + }); + + it('setView with cartographic in Columbus View', function() { + var ellipsoid = Ellipsoid.WGS84; + var projection = new GeographicProjection(ellipsoid); + + camera._mode = SceneMode.COLUMBUS_VIEW; + camera._projection = projection; + + var cart = Cartographic.fromDegrees(-75.0, 42.0, 100.0); + camera.setView({ + heading : 0.0, + pitch : -CesiumMath.PI_OVER_TWO, + roll : 0.0, + positionCartographic : cart + }); + + expect(camera.position).toEqualEpsilon(projection.project(cart), CesiumMath.EPSILON11); + expect(camera.direction).toEqualEpsilon(Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()), CesiumMath.EPSILON6); + expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Y, CesiumMath.EPSILON6); + expect(camera.right).toEqualEpsilon(Cartesian3.UNIT_X, CesiumMath.EPSILON6); + }); + + it('setView with cartographic in 3D', function() { + var ellipsoid = Ellipsoid.WGS84; + var projection = new GeographicProjection(ellipsoid); + + camera._mode = SceneMode.SCENE3D; + camera._projection = projection; + + var cart = new Cartographic(-75.0, 0.0, 100.0); + camera.setView({ + heading : 0.0, + pitch : -CesiumMath.PI_OVER_TWO, + roll : 0.0, + positionCartographic : cart + }); + + expect(camera.position).toEqualEpsilon(ellipsoid.cartographicToCartesian(cart), CesiumMath.EPSILON6); + expect(camera.direction).toEqualEpsilon(Cartesian3.normalize(Cartesian3.negate(camera.position, new Cartesian3()), new Cartesian3()), CesiumMath.EPSILON6); + expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON6); + expect(camera.right).toEqualEpsilon(Cartesian3.cross(camera.direction, camera.up, new Cartesian3()), CesiumMath.EPSILON6); + }); + it('worldToCameraCoordinates throws without cartesian', function() { expect(function() { camera.worldToCameraCoordinates(); @@ -1232,72 +1393,6 @@ defineSuite([ expect(p).toBeUndefined(); }); - it('set position cartographic throws without a cartographic', function() { - expect(function() { - camera.setPositionCartographic(); - }).toThrowDeveloperError(); - }); - - it('set position cartographic in 2D', function() { - var ellipsoid = Ellipsoid.WGS84; - var projection = new GeographicProjection(ellipsoid); - var maxRadii = ellipsoid.maximumRadius; - - camera._mode = SceneMode.SCENE2D; - camera._projection = projection; - - var frustum = new OrthographicFrustum(); - frustum.right = maxRadii * Math.PI; - frustum.left = -frustum.right; - frustum.top = frustum.right * (scene.drawingBufferHeight / scene.drawingBufferWidth); - frustum.bottom = -frustum.top; - frustum.near = 0.01 * maxRadii; - frustum.far = 60.0 * maxRadii; - camera.frustum = frustum; - - var ratio = frustum.top / frustum.right; - var cart = new Cartographic(-75.0, 42.0, 100.0); - camera.setPositionCartographic(cart); - - expect(Cartesian2.fromCartesian3(camera.position, new Cartesian2())).toEqual(Cartesian2.fromCartesian3(projection.project(cart), new Cartesian2())); - expect(camera.direction).toEqual(Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())); - expect(camera.up).toEqual(Cartesian3.UNIT_Y); - expect(camera.right).toEqual(Cartesian3.UNIT_X); - expect(frustum.right - frustum.left).toEqual(cart.height); - expect(frustum.top / frustum.right).toEqual(ratio); - }); - - it('set position cartographic in Columbus View', function() { - var ellipsoid = Ellipsoid.WGS84; - var projection = new GeographicProjection(ellipsoid); - - camera._mode = SceneMode.COLUMBUS_VIEW; - camera._projection = projection; - - var cart = new Cartographic(-75.0, 42.0, 100.0); - camera.setPositionCartographic(cart); - expect(camera.position).toEqual(projection.project(cart)); - expect(camera.direction).toEqual(Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())); - expect(camera.up).toEqual(Cartesian3.UNIT_Y); - expect(camera.right).toEqual(Cartesian3.UNIT_X); - }); - - it('set position cartographic in 3D', function() { - var ellipsoid = Ellipsoid.WGS84; - var projection = new GeographicProjection(ellipsoid); - - camera._mode = SceneMode.SCENE3D; - camera._projection = projection; - - var cart = new Cartographic(-75.0, 0.0, 100.0); - camera.setPositionCartographic(cart); - - expect(camera.position).toEqual(ellipsoid.cartographicToCartesian(cart)); - expect(camera.direction).toEqual(Cartesian3.normalize(Cartesian3.negate(camera.position, new Cartesian3()), new Cartesian3())); - expect(camera.up).toEqualEpsilon(Cartesian3.UNIT_Z, CesiumMath.EPSILON15); - expect(camera.right).toEqual(Cartesian3.cross(camera.direction, camera.up, new Cartesian3()), new Cartesian3()); - }); - it('get pick ray throws without a position', function() { expect(function () { camera.getPickRay();