diff --git a/CHANGES.md b/CHANGES.md
index 8b190f0d9e36..d6d23fc89831 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,6 +25,7 @@
- Fixed sky atmosphere artifacts below the horizon. [#8866](https://github.com/CesiumGS/cesium/pull/8866)
- Fixed ground primitives in orthographic mode. [#5110](https://github.com/CesiumGS/cesium/issues/5110)
- Fixed the depth plane in orthographic mode. This improves the quality of polylines and other primitives that are rendered near the horizon. [8858](https://github.com/CesiumGS/cesium/pull/8858)
+- Fixed camera controls when the camera is underground. [#8811](https://github.com/CesiumGS/cesium/pull/8811)
### 1.69.0 - 2020-05-01
diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js
index cbfedd8b73c8..668d41ec29dd 100644
--- a/Source/Scene/Camera.js
+++ b/Source/Scene/Camera.js
@@ -1145,6 +1145,7 @@ Camera.prototype._adjustOrthographicFrustum = function (zooming) {
rayIntersection = globe.pickWorldCoordinates(
ray,
scene,
+ true,
scratchRayIntersection
);
diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js
index 9a42146beaca..117d6ed3cc67 100644
--- a/Source/Scene/Globe.js
+++ b/Source/Scene/Globe.js
@@ -612,12 +612,18 @@ var scratchSphereIntersectionResult = {
*
* @param {Ray} ray The ray to test for intersection.
* @param {Scene} scene The scene.
+ * @param {Boolean} [cullBackFaces=true] Set to true to not pick back faces.
* @param {Cartesian3} [result] The object onto which to store the result.
* @returns {Cartesian3|undefined} The intersection or undefined
if none was found. The returned position is in projected coordinates for 2D and Columbus View.
*
* @private
*/
-Globe.prototype.pickWorldCoordinates = function (ray, scene, result) {
+Globe.prototype.pickWorldCoordinates = function (
+ ray,
+ scene,
+ cullBackFaces,
+ result
+) {
//>>includeStart('debug', pragmas.debug);
if (!defined(ray)) {
throw new DeveloperError("ray is required");
@@ -627,6 +633,8 @@ Globe.prototype.pickWorldCoordinates = function (ray, scene, result) {
}
//>>includeEnd('debug');
+ cullBackFaces = defaultValue(cullBackFaces, true);
+
var mode = scene.mode;
var projection = scene.mapProjection;
@@ -691,7 +699,7 @@ Globe.prototype.pickWorldCoordinates = function (ray, scene, result) {
ray,
scene.mode,
scene.mapProjection,
- true,
+ cullBackFaces,
result
);
if (defined(intersection)) {
@@ -717,7 +725,7 @@ var cartoScratch = new Cartographic();
* var intersection = globe.pick(ray, scene);
*/
Globe.prototype.pick = function (ray, scene, result) {
- result = this.pickWorldCoordinates(ray, scene, result);
+ result = this.pickWorldCoordinates(ray, scene, true, result);
if (defined(result) && scene.mode !== SceneMode.SCENE3D) {
result = Cartesian3.fromElements(result.y, result.z, result.x, result);
var carto = scene.mapProjection.unproject(result, cartoScratch);
diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js
index 61ece96a6826..ff85ad6a9c71 100644
--- a/Source/Scene/Scene.js
+++ b/Source/Scene/Scene.js
@@ -211,6 +211,9 @@ function Scene(options) {
this._primitives = new PrimitiveCollection();
this._groundPrimitives = new PrimitiveCollection();
+ this._globeHeight = undefined;
+ this._cameraUnderground = false;
+
this._logDepthBuffer = context.fragmentDepth;
this._logDepthBufferDirty = true;
@@ -1649,6 +1652,15 @@ Object.defineProperties(Scene.prototype, {
return 0.9999;
},
},
+
+ /**
+ * @private
+ */
+ globeHeight: {
+ get: function () {
+ return this._globeHeight;
+ },
+ },
});
/**
@@ -3309,7 +3321,10 @@ Scene.prototype.updateEnvironment = function () {
environmentState.isReadyForAtmosphere ||
globe._surface._tilesToRender.length > 0;
}
- environmentState.skyAtmosphereCommand = skyAtmosphere.update(frameState);
+ environmentState.skyAtmosphereCommand = skyAtmosphere.update(
+ frameState,
+ globe
+ );
if (defined(environmentState.skyAtmosphereCommand)) {
this.updateDerivedCommands(environmentState.skyAtmosphereCommand);
}
@@ -3693,6 +3708,16 @@ function callAfterRenderFunctions(scene) {
functions.length = 0;
}
+function getGlobeHeight(scene) {
+ var globe = scene._globe;
+ var camera = scene.camera;
+ var cartographic = camera.positionCartographic;
+ if (defined(globe) && globe.show && defined(cartographic)) {
+ return globe.getHeight(cartographic);
+ }
+ return undefined;
+}
+
function isCameraUnderground(scene) {
var camera = scene.camera;
var mode = scene._mode;
@@ -3700,6 +3725,10 @@ function isCameraUnderground(scene) {
var cameraController = scene._screenSpaceCameraController;
var cartographic = camera.positionCartographic;
+ if (!defined(cartographic)) {
+ return false;
+ }
+
if (!cameraController.onMap() && cartographic.height < 0.0) {
// The camera can go off the map while in Columbus View.
// Make a best guess as to whether it's underground by checking if its height is less than zero.
@@ -3715,17 +3744,8 @@ function isCameraUnderground(scene) {
return false;
}
- if (cameraController.adjustedHeightForTerrain()) {
- // The camera controller already adjusted the camera, no need to call globe.getHeight again
- return false;
- }
-
- var globeHeight = globe.getHeight(cartographic);
- if (defined(globeHeight) && cartographic.height < globeHeight) {
- return true;
- }
-
- return false;
+ var globeHeight = scene._globeHeight;
+ return defined(globeHeight) && cartographic.height < globeHeight;
}
/**
@@ -3741,6 +3761,10 @@ Scene.prototype.initializeFrame = function () {
this._tweens.update();
+ this._globeHeight = getGlobeHeight(this);
+ this._cameraUnderground = isCameraUnderground(this);
+ this._globeTranslucencyState.update(this);
+
this._screenSpaceCameraController.update();
if (defined(this._deviceOrientationCameraController)) {
this._deviceOrientationCameraController.update();
@@ -3748,10 +3772,6 @@ Scene.prototype.initializeFrame = function () {
this.camera.update(this._mode);
this.camera._updateCameraChanged();
-
- this._cameraUnderground = isCameraUnderground(this);
-
- this._globeTranslucencyState.update(this);
};
function updateDebugShowFramesPerSecond(scene, renderedThisFrame) {
diff --git a/Source/Scene/SceneTransitioner.js b/Source/Scene/SceneTransitioner.js
index fbb892b6a683..d2789fd815c7 100644
--- a/Source/Scene/SceneTransitioner.js
+++ b/Source/Scene/SceneTransitioner.js
@@ -644,6 +644,7 @@ function morphFromColumbusViewTo2D(transitioner, duration) {
var pickPos = globe.pickWorldCoordinates(
ray,
scene,
+ true,
scratchCVTo2DPickPos
);
if (defined(pickPos)) {
@@ -773,6 +774,7 @@ function morphFrom3DTo2D(transitioner, duration, ellipsoid) {
var pickedPos = globe.pickWorldCoordinates(
ray,
scene,
+ true,
scratch3DTo2DPickPosition
);
if (defined(pickedPos)) {
diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js
index 92e50e9eac8a..9866dde783a2 100644
--- a/Source/Scene/ScreenSpaceCameraController.js
+++ b/Source/Scene/ScreenSpaceCameraController.js
@@ -231,7 +231,7 @@ function ScreenSpaceCameraController(scene) {
this._minimumCollisionTerrainHeight = this.minimumCollisionTerrainHeight;
/**
* The minimum height the camera must be before switching from rotating a track ball to
- * free look when clicks originate on the sky on in space.
+ * free look when clicks originate on the sky or in space.
* @type {Number}
* @default 7500000.0
*/
@@ -255,6 +255,20 @@ function ScreenSpaceCameraController(scene) {
this._lastInertiaTranslateMovement = undefined;
this._lastInertiaTiltMovement = undefined;
+ // Zoom disables tilt, spin, and translate inertia
+ // Tilt disables spin and translate inertia
+ this._inertiaDisablers = {
+ _lastInertiaZoomMovement: [
+ "_lastInertiaSpinMovement",
+ "_lastInertiaTranslateMovement",
+ "_lastInertiaTiltMovement",
+ ],
+ _lastInertiaTiltMovement: [
+ "_lastInertiaSpinMovement",
+ "_lastInertiaTranslateMovement",
+ ],
+ };
+
this._tweens = new TweenCollection();
this._tween = undefined;
@@ -265,6 +279,8 @@ function ScreenSpaceCameraController(scene) {
this._rotateMousePosition = new Cartesian2(-1.0, -1.0);
this._rotateStartPosition = new Cartesian3();
this._strafeStartPosition = new Cartesian3();
+ this._strafeMousePosition = new Cartesian2();
+ this._strafeEndMousePosition = new Cartesian2();
this._zoomMouseStart = new Cartesian2(-1.0, -1.0);
this._zoomWorldPosition = new Cartesian3();
this._useZoomWorldPosition = false;
@@ -273,8 +289,10 @@ function ScreenSpaceCameraController(scene) {
this._rotating = false;
this._strafing = false;
this._zoomingOnVector = false;
+ this._zoomingUnderground = false;
this._rotatingZoom = false;
this._adjustedHeightForTerrain = false;
+ this._cameraUnderground = false;
var projection = scene.mapProjection;
this._maxCoord = projection.project(
@@ -289,6 +307,8 @@ function ScreenSpaceCameraController(scene) {
this._minimumRotateRate = 1.0 / 5000.0;
this._minimumZoomRate = 20.0;
this._maximumZoomRate = 5906376272000.0; // distance from the Sun to Pluto in meters.
+ this._minimumUndergroundPickDistance = 2000.0;
+ this._maximumUndergroundPickDistance = 10000.0;
}
function decay(time, coefficient) {
@@ -329,7 +349,7 @@ function maintainInertia(
startPosition: new Cartesian2(),
endPosition: new Cartesian2(),
motion: new Cartesian2(),
- active: false,
+ inertiaEnabled: true,
};
}
@@ -343,56 +363,35 @@ function maintainInertia(
if (ts && tr && threshold < inertiaMaxClickTimeThreshold) {
var d = decay(fromNow, decayCoef);
- if (!movementState.active) {
- var lastMovement = aggregator.getLastMovement(type, modifier);
- if (!defined(lastMovement) || sameMousePosition(lastMovement)) {
- return;
- }
-
- movementState.motion.x =
- (lastMovement.endPosition.x - lastMovement.startPosition.x) * 0.5;
- movementState.motion.y =
- (lastMovement.endPosition.y - lastMovement.startPosition.y) * 0.5;
-
- movementState.startPosition = Cartesian2.clone(
- lastMovement.startPosition,
- movementState.startPosition
- );
-
- movementState.endPosition = Cartesian2.multiplyByScalar(
- movementState.motion,
- d,
- movementState.endPosition
- );
- movementState.endPosition = Cartesian2.add(
- movementState.startPosition,
- movementState.endPosition,
- movementState.endPosition
- );
+ var lastMovement = aggregator.getLastMovement(type, modifier);
+ if (
+ !defined(lastMovement) ||
+ sameMousePosition(lastMovement) ||
+ !movementState.inertiaEnabled
+ ) {
+ return;
+ }
- movementState.active = true;
- } else {
- movementState.startPosition = Cartesian2.clone(
- movementState.endPosition,
- movementState.startPosition
- );
+ movementState.motion.x =
+ (lastMovement.endPosition.x - lastMovement.startPosition.x) * 0.5;
+ movementState.motion.y =
+ (lastMovement.endPosition.y - lastMovement.startPosition.y) * 0.5;
- movementState.endPosition = Cartesian2.multiplyByScalar(
- movementState.motion,
- d,
- movementState.endPosition
- );
- movementState.endPosition = Cartesian2.add(
- movementState.startPosition,
- movementState.endPosition,
- movementState.endPosition
- );
+ movementState.startPosition = Cartesian2.clone(
+ lastMovement.startPosition,
+ movementState.startPosition
+ );
- movementState.motion = Cartesian2.clone(
- Cartesian2.ZERO,
- movementState.motion
- );
- }
+ movementState.endPosition = Cartesian2.multiplyByScalar(
+ movementState.motion,
+ d,
+ movementState.endPosition
+ );
+ movementState.endPosition = Cartesian2.add(
+ movementState.startPosition,
+ movementState.endPosition,
+ movementState.endPosition
+ );
// If value from the decreasing exponential function is close to zero,
// the end coordinates may be NaN.
@@ -404,7 +403,6 @@ function maintainInertia(
movementState.endPosition
) < 0.5
) {
- movementState.active = false;
return;
}
@@ -412,8 +410,27 @@ function maintainInertia(
var startPosition = aggregator.getStartMousePosition(type, modifier);
action(object, startPosition, movementState);
}
- } else {
- movementState.active = false;
+ }
+}
+
+function activateInertia(controller, inertiaStateName) {
+ if (defined(inertiaStateName)) {
+ // Re-enable inertia if it was disabled
+ var movementState = controller[inertiaStateName];
+ if (defined(movementState)) {
+ movementState.inertiaEnabled = true;
+ }
+ // Disable inertia on other movements
+ var inertiasToDisable = controller._inertiaDisablers[inertiaStateName];
+ if (defined(inertiasToDisable)) {
+ var length = inertiasToDisable.length;
+ for (var i = 0; i < length; ++i) {
+ movementState = controller[inertiasToDisable[i]];
+ if (defined(movementState)) {
+ movementState.inertiaEnabled = false;
+ }
+ }
+ }
}
}
@@ -452,6 +469,7 @@ function reactToInput(
if (controller.enableInputs && enabled) {
if (movement) {
action(controller, startPosition, movement);
+ activateInertia(controller, inertiaStateName);
} else if (inertiaConstant < 1.0) {
maintainInertia(
aggregator,
@@ -531,18 +549,24 @@ function handleZoom(
rangeWindowRatio = Math.min(rangeWindowRatio, object.maximumMovementRatio);
var distance = zoomRate * rangeWindowRatio;
- if (distance > 0.0 && Math.abs(distanceMeasure - minHeight) < 1.0) {
- return;
- }
+ if (
+ object.enableCollisionDetection ||
+ object.minimumZoomDistance === 0.0 ||
+ !defined(object._globe) // look-at mode
+ ) {
+ if (distance > 0.0 && Math.abs(distanceMeasure - minHeight) < 1.0) {
+ return;
+ }
- if (distance < 0.0 && Math.abs(distanceMeasure - maxHeight) < 1.0) {
- return;
- }
+ if (distance < 0.0 && Math.abs(distanceMeasure - maxHeight) < 1.0) {
+ return;
+ }
- if (distanceMeasure - distance < minHeight) {
- distance = distanceMeasure - minHeight - 1.0;
- } else if (distanceMeasure - distance > maxHeight) {
- distance = distanceMeasure - maxHeight;
+ if (distanceMeasure - distance < minHeight) {
+ distance = distanceMeasure - minHeight - 1.0;
+ } else if (distanceMeasure - distance > maxHeight) {
+ distance = distanceMeasure - maxHeight;
+ }
}
var scene = object._scene;
@@ -601,6 +625,7 @@ function handleZoom(
zoomingOnVector = object._zoomingOnVector = false;
rotatingZoom = object._rotatingZoom = false;
+ object._zoomingUnderground = object._cameraUnderground;
}
if (!object._useZoomWorldPosition) {
@@ -660,8 +685,11 @@ function handleZoom(
scratchCameraPositionNormal
);
if (
- camera.positionCartographic.height < 3000.0 &&
- Math.abs(Cartesian3.dot(camera.direction, cameraPositionNormal)) < 0.6
+ object._cameraUnderground ||
+ object._zoomingUnderground ||
+ (camera.positionCartographic.height < 3000.0 &&
+ Math.abs(Cartesian3.dot(camera.direction, cameraPositionNormal)) <
+ 0.6)
) {
zoomOnVector = true;
} else {
@@ -900,7 +928,9 @@ function handleZoom(
camera.zoomIn(distance);
}
- camera.setView(scratchZoomViewOptions);
+ if (!object._cameraUnderground) {
+ camera.setView(scratchZoomViewOptions);
+ }
}
var translate2DStart = new Ray();
@@ -1070,6 +1100,8 @@ function pickGlobe(controller, mousePosition, result) {
return undefined;
}
+ var cullBackFaces = !controller._cameraUnderground;
+
var depthIntersection;
if (scene.pickPositionSupported) {
depthIntersection = scene.pickPositionWorldCoordinates(
@@ -1082,6 +1114,7 @@ function pickGlobe(controller, mousePosition, result) {
var rayIntersection = globe.pickWorldCoordinates(
ray,
scene,
+ cullBackFaces,
scratchRayIntersection
);
@@ -1099,11 +1132,108 @@ function pickGlobe(controller, mousePosition, result) {
return Cartesian3.clone(rayIntersection, result);
}
+var scratchDistanceCartographic = new Cartographic();
+
+function getDistanceFromSurface(controller) {
+ var ellipsoid = controller._ellipsoid;
+ var scene = controller._scene;
+ var camera = scene.camera;
+ var mode = scene.mode;
+
+ var height = 0.0;
+ if (mode === SceneMode.SCENE3D) {
+ var cartographic = ellipsoid.cartesianToCartographic(
+ camera.position,
+ scratchDistanceCartographic
+ );
+ if (defined(cartographic)) {
+ height = cartographic.height;
+ }
+ } else {
+ height = camera.position.z;
+ }
+ var globeHeight = defaultValue(controller._scene.globeHeight, 0.0);
+ var distanceFromSurface = Math.abs(globeHeight - height);
+ return distanceFromSurface;
+}
+
+var scratchSurfaceNormal = new Cartesian3();
+
+function getZoomDistanceUnderground(controller, ray) {
+ var origin = ray.origin;
+ var direction = ray.direction;
+ var distanceFromSurface = getDistanceFromSurface(controller);
+
+ // Weight zoom distance based on how strongly the pick ray is pointing inward.
+ // Geocentric normal is accurate enough for these purposes
+ var surfaceNormal = Cartesian3.normalize(origin, scratchSurfaceNormal);
+ var strength = Math.abs(Cartesian3.dot(surfaceNormal, direction));
+ strength = Math.max(strength, 0.5) * 2.0;
+ return distanceFromSurface * strength;
+}
+
+function getTiltCenterUnderground(controller, ray, pickedPosition, result) {
+ var distance = Cartesian3.distance(ray.origin, pickedPosition);
+ var distanceFromSurface = getDistanceFromSurface(controller);
+
+ var maximumDistance = CesiumMath.clamp(
+ distanceFromSurface * 5.0,
+ controller._minimumUndergroundPickDistance,
+ controller._maximumUndergroundPickDistance
+ );
+
+ if (distance > maximumDistance) {
+ // Simulate look-at behavior by tilting around a small invisible sphere
+ distance = Math.min(distance, distanceFromSurface / 5.0);
+ distance = Math.max(distance, 100.0);
+ }
+
+ return Ray.getPoint(ray, distance, result);
+}
+
+function getStrafeStartPositionUnderground(
+ controller,
+ ray,
+ pickedPosition,
+ result
+) {
+ var distance;
+ if (!defined(pickedPosition)) {
+ distance = getDistanceFromSurface(controller);
+ } else {
+ distance = Cartesian3.distance(ray.origin, pickedPosition);
+ if (distance > controller._maximumUndergroundPickDistance) {
+ // If the picked position is too far away set the strafe speed based on the
+ // camera's height from the globe surface
+ distance = getDistanceFromSurface(controller);
+ }
+ }
+
+ return Ray.getPoint(ray, distance, result);
+}
+
+var scratchInertialDelta = new Cartesian2();
+
+function continueStrafing(controller, movement) {
+ // Update the end position continually based on the inertial delta
+ var originalEndPosition = movement.endPosition;
+ var inertialDelta = Cartesian2.subtract(
+ movement.endPosition,
+ movement.startPosition,
+ scratchInertialDelta
+ );
+ var endPosition = controller._strafeEndMousePosition;
+ Cartesian2.add(endPosition, inertialDelta, endPosition);
+ movement.endPosition = endPosition;
+ strafe(controller, movement, controller._strafeStartPosition);
+ movement.endPosition = originalEndPosition;
+}
+
var translateCVStartRay = new Ray();
var translateCVEndRay = new Ray();
var translateCVStartPos = new Cartesian3();
var translateCVEndPos = new Cartesian3();
-var translatCVDifference = new Cartesian3();
+var translateCVDifference = new Cartesian3();
var translateCVOrigin = new Cartesian3();
var translateCVPlane = new Plane(Cartesian3.UNIT_X, 0.0);
var translateCVStartMouse = new Cartesian2();
@@ -1124,12 +1254,13 @@ function translateCV(controller, startPosition, movement) {
}
if (controller._strafing) {
- strafe(controller, startPosition, movement);
+ continueStrafing(controller, movement);
return;
}
var scene = controller._scene;
var camera = scene.camera;
+ var cameraUnderground = controller._cameraUnderground;
var startMouse = Cartesian2.clone(
movement.startPosition,
translateCVStartMouse
@@ -1148,14 +1279,24 @@ function translateCV(controller, startPosition, movement) {
}
}
- if (origin.x > camera.position.z && defined(globePos)) {
- Cartesian3.clone(globePos, controller._strafeStartPosition);
+ if (
+ cameraUnderground ||
+ (origin.x > camera.position.z && defined(globePos))
+ ) {
+ var pickPosition = globePos;
+ if (cameraUnderground) {
+ pickPosition = getStrafeStartPositionUnderground(
+ controller,
+ startRay,
+ globePos,
+ translateCVStartPos
+ );
+ }
+ Cartesian2.clone(startPosition, controller._strafeMousePosition);
+ Cartesian2.clone(startPosition, controller._strafeEndMousePosition);
+ Cartesian3.clone(pickPosition, controller._strafeStartPosition);
controller._strafing = true;
- strafe(controller, startPosition, movement);
- controller._strafeMousePosition = Cartesian2.clone(
- startPosition,
- controller._strafeMousePosition
- );
+ strafe(controller, movement, controller._strafeStartPosition);
return;
}
@@ -1185,7 +1326,7 @@ function translateCV(controller, startPosition, movement) {
var diff = Cartesian3.subtract(
startPlanePos,
endPlanePos,
- translatCVDifference
+ translateCVDifference
);
var temp = diff.x;
diff.x = diff.y;
@@ -1234,7 +1375,7 @@ function rotateCV(controller, startPosition, movement) {
if (
controller._tiltCVOffMap ||
!controller.onMap() ||
- camera.position.z > controller._minimumPickingTerrainHeight
+ Math.abs(camera.position.z) > controller._minimumPickingTerrainHeight
) {
controller._tiltCVOffMap = true;
rotateCVOnPlane(controller, startPosition, movement);
@@ -1309,6 +1450,7 @@ function rotateCVOnPlane(controller, startPosition, movement) {
function rotateCVOnTerrain(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
+ var cameraUnderground = controller._cameraUnderground;
var center;
var ray;
@@ -1343,6 +1485,13 @@ function rotateCVOnTerrain(controller, startPosition, movement) {
Cartesian3.add(position, center, center);
}
+ if (cameraUnderground) {
+ if (!defined(ray)) {
+ ray = camera.getPickRay(startPosition, rotateCVWindowRay);
+ }
+ getTiltCenterUnderground(controller, ray, center, center);
+ }
+
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
Cartesian3.clone(center, controller._tiltCenter);
}
@@ -1420,7 +1569,12 @@ function rotateCVOnTerrain(controller, startPosition, movement) {
camera._setTransform(verticalTransform);
if (dot < 0.0) {
- if (movement.startPosition.y > movement.endPosition.y) {
+ var movementDelta = movement.startPosition.y - movement.endPosition.y;
+ if (
+ (cameraUnderground && movementDelta < 0.0) ||
+ (!cameraUnderground && movementDelta > 0.0)
+ ) {
+ // Prevent camera from flipping past the up axis
constrainedAxis = undefined;
}
@@ -1518,23 +1672,48 @@ function zoomCV(controller, startPosition, movement) {
var camera = scene.camera;
var canvas = scene.canvas;
- var windowPosition = zoomCVWindowPos;
- windowPosition.x = canvas.clientWidth / 2;
- windowPosition.y = canvas.clientHeight / 2;
+ var cameraUnderground = controller._cameraUnderground;
+
+ var windowPosition;
+
+ if (cameraUnderground) {
+ windowPosition = startPosition;
+ } else {
+ windowPosition = zoomCVWindowPos;
+ windowPosition.x = canvas.clientWidth / 2;
+ windowPosition.y = canvas.clientHeight / 2;
+ }
+
var ray = camera.getPickRay(windowPosition, zoomCVWindowRay);
+ var position = ray.origin;
+ var direction = ray.direction;
+ var height = camera.position.z;
var intersection;
- if (camera.position.z < controller._minimumPickingTerrainHeight) {
+ if (height < controller._minimumPickingTerrainHeight) {
intersection = pickGlobe(controller, windowPosition, zoomCVIntersection);
}
var distance;
if (defined(intersection)) {
- distance = Cartesian3.distance(ray.origin, intersection);
- } else {
+ distance = Cartesian3.distance(position, intersection);
+ }
+
+ if (cameraUnderground) {
+ var distanceUnderground = getZoomDistanceUnderground(
+ controller,
+ ray,
+ height
+ );
+ if (defined(distance)) {
+ distance = Math.min(distance, distanceUnderground);
+ } else {
+ distance = distanceUnderground;
+ }
+ }
+
+ if (!defined(distance)) {
var normal = Cartesian3.UNIT_X;
- var position = ray.origin;
- var direction = ray.direction;
distance =
-Cartesian3.dot(normal, position) / Cartesian3.dot(normal, direction);
}
@@ -1609,10 +1788,6 @@ function updateCV(controller) {
if (
!controller._aggregator.anyButtonDown &&
- (!defined(controller._lastInertiaZoomMovement) ||
- !controller._lastInertiaZoomMovement.active) &&
- (!defined(controller._lastInertiaTranslateMovement) ||
- !controller._lastInertiaTranslateMovement.active) &&
!tweens.contains(controller._tween)
) {
var tween = camera.createCorrectPositionTween(
@@ -1633,21 +1808,11 @@ var scratchStrafeIntersection = new Cartesian3();
var scratchStrafeDirection = new Cartesian3();
var scratchMousePos = new Cartesian3();
-function strafe(controller, startPosition, movement) {
+function strafe(controller, movement, strafeStartPosition) {
var scene = controller._scene;
var camera = scene.camera;
- var mouseStartPosition = pickGlobe(
- controller,
- movement.startPosition,
- scratchMousePos
- );
- if (!defined(mouseStartPosition)) {
- return;
- }
-
- var mousePosition = movement.endPosition;
- var ray = camera.getPickRay(mousePosition, scratchStrafeRay);
+ var ray = camera.getPickRay(movement.endPosition, scratchStrafeRay);
var direction = Cartesian3.clone(camera.direction, scratchStrafeDirection);
if (scene.mode === SceneMode.COLUMBUS_VIEW) {
@@ -1655,7 +1820,7 @@ function strafe(controller, startPosition, movement) {
}
var plane = Plane.fromPointNormal(
- mouseStartPosition,
+ strafeStartPosition,
direction,
scratchStrafePlane
);
@@ -1668,7 +1833,7 @@ function strafe(controller, startPosition, movement) {
return;
}
- direction = Cartesian3.subtract(mouseStartPosition, intersection, direction);
+ direction = Cartesian3.subtract(strafeStartPosition, intersection, direction);
if (scene.mode === SceneMode.COLUMBUS_VIEW) {
Cartesian3.fromElements(direction.y, direction.z, direction.x, direction);
}
@@ -1681,10 +1846,13 @@ var scratchCartographic = new Cartographic();
var scratchRadii = new Cartesian3();
var scratchEllipsoid = new Ellipsoid();
var scratchLookUp = new Cartesian3();
+var scratchNormal = new Cartesian3();
function spin3D(controller, startPosition, movement) {
var scene = controller._scene;
var camera = scene.camera;
+ var cameraUnderground = controller._cameraUnderground;
+ var ellipsoid = controller._ellipsoid;
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
rotate3D(controller, startPosition, movement);
@@ -1693,34 +1861,8 @@ function spin3D(controller, startPosition, movement) {
var magnitude;
var radii;
- var ellipsoid;
- var up = controller._ellipsoid.geodeticSurfaceNormal(
- camera.position,
- scratchLookUp
- );
-
- var height = controller._ellipsoid.cartesianToCartographic(
- camera.positionWC,
- scratchCartographic
- ).height;
- var globe = controller._globe;
-
- var mousePos;
- var tangentPick = false;
- if (defined(globe) && height < controller._minimumPickingTerrainHeight) {
- mousePos = pickGlobe(controller, movement.startPosition, scratchMousePos);
- if (defined(mousePos)) {
- var ray = camera.getPickRay(movement.startPosition, pickGlobeScratchRay);
- var normal = controller._ellipsoid.geodeticSurfaceNormal(mousePos);
- tangentPick = Math.abs(Cartesian3.dot(ray.direction, normal)) < 0.05;
-
- if (tangentPick && !controller._looking) {
- controller._rotating = false;
- controller._strafing = true;
- }
- }
- }
+ var up = ellipsoid.geodeticSurfaceNormal(camera.position, scratchLookUp);
if (Cartesian2.equals(startPosition, controller._rotateMousePosition)) {
if (controller._looking) {
@@ -1728,9 +1870,15 @@ function spin3D(controller, startPosition, movement) {
} else if (controller._rotating) {
rotate3D(controller, startPosition, movement);
} else if (controller._strafing) {
- Cartesian3.clone(mousePos, controller._strafeStartPosition);
- strafe(controller, startPosition, movement);
+ continueStrafing(controller, movement);
} else {
+ if (
+ Cartesian3.magnitude(camera.position) <
+ Cartesian3.magnitude(controller._rotateStartPosition)
+ ) {
+ // Pan action is no longer valid if camera moves below the pan ellipsoid
+ return;
+ }
magnitude = Cartesian3.magnitude(controller._rotateStartPosition);
radii = scratchRadii;
radii.x = radii.y = radii.z = magnitude;
@@ -1743,15 +1891,44 @@ function spin3D(controller, startPosition, movement) {
controller._rotating = false;
controller._strafing = false;
+ var height = ellipsoid.cartesianToCartographic(
+ camera.positionWC,
+ scratchCartographic
+ ).height;
+ var globe = controller._globe;
+
if (defined(globe) && height < controller._minimumPickingTerrainHeight) {
+ var mousePos = pickGlobe(
+ controller,
+ movement.startPosition,
+ scratchMousePos
+ );
if (defined(mousePos)) {
- if (
- Cartesian3.magnitude(camera.position) < Cartesian3.magnitude(mousePos)
- ) {
- Cartesian3.clone(mousePos, controller._strafeStartPosition);
+ var strafing = false;
+ var ray = camera.getPickRay(movement.startPosition, pickGlobeScratchRay);
+
+ if (cameraUnderground) {
+ strafing = true;
+ getStrafeStartPositionUnderground(controller, ray, mousePos, mousePos);
+ } else {
+ var normal = ellipsoid.geodeticSurfaceNormal(mousePos, scratchNormal);
+ var tangentPick =
+ Math.abs(Cartesian3.dot(ray.direction, normal)) < 0.05;
+
+ if (tangentPick) {
+ strafing = true;
+ } else {
+ strafing =
+ Cartesian3.magnitude(camera.position) <
+ Cartesian3.magnitude(mousePos);
+ }
+ }
+ if (strafing) {
+ Cartesian2.clone(startPosition, controller._strafeEndMousePosition);
+ Cartesian3.clone(mousePos, controller._strafeStartPosition);
controller._strafing = true;
- strafe(controller, startPosition, movement);
+ strafe(controller, movement, controller._strafeStartPosition);
} else {
magnitude = Cartesian3.magnitude(mousePos);
radii = scratchRadii;
@@ -1974,9 +2151,18 @@ function zoom3D(controller, startPosition, movement) {
var camera = scene.camera;
var canvas = scene.canvas;
- var windowPosition = zoomCVWindowPos;
- windowPosition.x = canvas.clientWidth / 2;
- windowPosition.y = canvas.clientHeight / 2;
+ var cameraUnderground = controller._cameraUnderground;
+
+ var windowPosition;
+
+ if (cameraUnderground) {
+ windowPosition = startPosition;
+ } else {
+ windowPosition = zoomCVWindowPos;
+ windowPosition.x = canvas.clientWidth / 2;
+ windowPosition.y = canvas.clientHeight / 2;
+ }
+
var ray = camera.getPickRay(windowPosition, zoomCVWindowRay);
var intersection;
@@ -1991,7 +2177,22 @@ function zoom3D(controller, startPosition, movement) {
var distance;
if (defined(intersection)) {
distance = Cartesian3.distance(ray.origin, intersection);
- } else {
+ }
+
+ if (cameraUnderground) {
+ var distanceUnderground = getZoomDistanceUnderground(
+ controller,
+ ray,
+ height
+ );
+ if (defined(distance)) {
+ distance = Math.min(distance, distanceUnderground);
+ } else {
+ distance = distanceUnderground;
+ }
+ }
+
+ if (!defined(distance)) {
distance = height;
}
@@ -2149,6 +2350,7 @@ function tilt3DOnTerrain(controller, startPosition, movement) {
var ellipsoid = controller._ellipsoid;
var scene = controller._scene;
var camera = scene.camera;
+ var cameraUnderground = controller._cameraUnderground;
var center;
var ray;
@@ -2181,6 +2383,13 @@ function tilt3DOnTerrain(controller, startPosition, movement) {
center = Ray.getPoint(ray, intersection.start, tilt3DCenter);
}
+ if (cameraUnderground) {
+ if (!defined(ray)) {
+ ray = camera.getPickRay(startPosition, tilt3DRay);
+ }
+ getTiltCenterUnderground(controller, ray, center, center);
+ }
+
Cartesian2.clone(startPosition, controller._tiltCenterMousePosition);
Cartesian3.clone(center, controller._tiltCenter);
}
@@ -2242,7 +2451,12 @@ function tilt3DOnTerrain(controller, startPosition, movement) {
camera._setTransform(verticalTransform);
if (dot < 0.0) {
- if (movement.startPosition.y > movement.endPosition.y) {
+ var movementDelta = movement.startPosition.y - movement.endPosition.y;
+ if (
+ (cameraUnderground && movementDelta < 0.0) ||
+ (!cameraUnderground && movementDelta > 0.0)
+ ) {
+ // Prevent camera from flipping past the up axis
constrainedAxis = undefined;
}
@@ -2527,9 +2741,9 @@ function adjustHeightForTerrain(controller) {
var heightUpdated = false;
if (cartographic.height < controller._minimumCollisionTerrainHeight) {
- var height = globe.getHeight(cartographic);
- if (defined(height)) {
- height += controller.minimumZoomDistance;
+ var globeHeight = controller._scene.globeHeight;
+ if (defined(globeHeight)) {
+ var height = globeHeight + controller.minimumZoomDistance;
if (cartographic.height < height) {
cartographic.height = height;
if (mode === SceneMode.SCENE3D) {
@@ -2584,23 +2798,29 @@ var scratchPreviousDirection = new Cartesian3();
* @private
*/
ScreenSpaceCameraController.prototype.update = function () {
- var camera = this._scene.camera;
+ var scene = this._scene;
+ var camera = scene.camera;
+ var globe = scene.globe;
+ var mode = scene.mode;
+
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
this._globe = undefined;
this._ellipsoid = Ellipsoid.UNIT_SPHERE;
} else {
- this._globe = this._scene.globe;
+ this._globe = globe;
this._ellipsoid = defined(this._globe)
? this._globe.ellipsoid
- : this._scene.mapProjection.ellipsoid;
+ : scene.mapProjection.ellipsoid;
}
+ this._cameraUnderground = scene.cameraUnderground && defined(this._globe);
+
this._minimumCollisionTerrainHeight =
- this.minimumCollisionTerrainHeight * this._scene.terrainExaggeration;
+ this.minimumCollisionTerrainHeight * scene.terrainExaggeration;
this._minimumPickingTerrainHeight =
- this.minimumPickingTerrainHeight * this._scene.terrainExaggeration;
+ this.minimumPickingTerrainHeight * scene.terrainExaggeration;
this._minimumTrackBallHeight =
- this.minimumTrackBallHeight * this._scene.terrainExaggeration;
+ this.minimumTrackBallHeight * scene.terrainExaggeration;
var radius = this._ellipsoid.maximumRadius;
this._rotateFactor = 1.0 / radius;
@@ -2616,8 +2836,6 @@ ScreenSpaceCameraController.prototype.update = function () {
scratchPreviousDirection
);
- var scene = this._scene;
- var mode = scene.mode;
if (mode === SceneMode.SCENE2D) {
update2D(this);
} else if (mode === SceneMode.COLUMBUS_VIEW) {
@@ -2629,7 +2847,7 @@ ScreenSpaceCameraController.prototype.update = function () {
}
if (this.enableCollisionDetection && !this._adjustedHeightForTerrain) {
- // Adjust the camera height if the camera moved at all (user input or intertia) and an action didn't already adjust the camera height
+ // Adjust the camera height if the camera moved at all (user input or inertia) and an action didn't already adjust the camera height
var cameraChanged =
!Cartesian3.equals(previousPosition, camera.positionWC) ||
!Cartesian3.equals(previousDirection, camera.directionWC);
@@ -2641,13 +2859,6 @@ ScreenSpaceCameraController.prototype.update = function () {
this._aggregator.reset();
};
-/**
- * @private
- */
-ScreenSpaceCameraController.prototype.adjustedHeightForTerrain = function () {
- return this._adjustedHeightForTerrain;
-};
-
/**
* Returns true if this object was destroyed; otherwise, false.
*
diff --git a/Source/Scene/SkyAtmosphere.js b/Source/Scene/SkyAtmosphere.js
index 3cf73277e25c..33c9904c4cd6 100644
--- a/Source/Scene/SkyAtmosphere.js
+++ b/Source/Scene/SkyAtmosphere.js
@@ -166,7 +166,7 @@ var scratchModelMatrix = new Matrix4();
/**
* @private
*/
-SkyAtmosphere.prototype.update = function (frameState) {
+SkyAtmosphere.prototype.update = function (frameState, globe) {
if (!this.show) {
return undefined;
}
@@ -204,7 +204,8 @@ SkyAtmosphere.prototype.update = function (frameState) {
var colorCorrect = hasColorCorrection(this);
var translucent = frameState.globeTranslucencyState.translucent;
- var perFragmentAtmosphere = this.perFragmentAtmosphere || translucent;
+ var perFragmentAtmosphere =
+ this.perFragmentAtmosphere || translucent || !defined(globe) || !globe.show;
var command = this._command;
diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js
index ab1a2a989115..65ba5488ea1d 100644
--- a/Specs/Scene/SceneSpec.js
+++ b/Specs/Scene/SceneSpec.js
@@ -2064,22 +2064,6 @@ describe(
});
});
- it("detects that camera is above ground if screen space camera controller adjusted height for terrain", function () {
- var scene = createScene();
- var globe = new Globe();
- scene.globe = globe;
-
- spyOn(
- ScreenSpaceCameraController.prototype,
- "adjustedHeightForTerrain"
- ).and.returnValue(true);
-
- return updateGlobeUntilDone(scene).then(function () {
- expect(scene.cameraUnderground).toBe(false);
- scene.destroyForSpecs();
- });
- });
-
it("detects that camera is above ground if globe is undefined", function () {
var scene = createScene();
scene.renderForSpecs();
diff --git a/Specs/Scene/ScreenSpaceCameraControllerSpec.js b/Specs/Scene/ScreenSpaceCameraControllerSpec.js
index cc0ea14890db..4881111f4d22 100644
--- a/Specs/Scene/ScreenSpaceCameraControllerSpec.js
+++ b/Specs/Scene/ScreenSpaceCameraControllerSpec.js
@@ -34,6 +34,8 @@ describe("Scene/ScreenSpaceCameraController", function () {
this.mapProjection = new GeographicProjection(ellipsoid);
this.terrainExaggeration = 1.0;
this.screenSpaceCameraController = undefined;
+ this.cameraUnderground = false;
+ this.globeHeight = 0.0;
}
function MockGlobe(ellipsoid) {
@@ -55,6 +57,7 @@ describe("Scene/ScreenSpaceCameraController", function () {
tilesWaitingForChildren: 0,
},
};
+ this.show = true;
}
beforeAll(function () {
usePointerEvents = FeatureDetection.supportsPointerEvents();
@@ -183,6 +186,8 @@ describe("Scene/ScreenSpaceCameraController", function () {
mapProjection: scene.mapProjection,
};
+ scene.cameraUnderground = false;
+
var maxRadii = ellipsoid.maximumRadius;
var frustum = new OrthographicOffCenterFrustum();
frustum.right = maxRadii * Math.PI;
@@ -210,6 +215,9 @@ describe("Scene/ScreenSpaceCameraController", function () {
mapProjection: scene.mapProjection,
};
+ scene.cameraUnderground = false;
+ controller.enableCollisionDetection = true;
+
var maxRadii = ellipsoid.maximumRadius;
camera.position = new Cartesian3(0.0, 0.0, maxRadii);
camera.direction = Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3());
@@ -217,6 +225,31 @@ describe("Scene/ScreenSpaceCameraController", function () {
camera.right = Cartesian3.clone(Cartesian3.UNIT_X);
}
+ function setUpCVUnderground() {
+ scene.mode = SceneMode.COLUMBUS_VIEW;
+
+ var ellipsoid = Ellipsoid.WGS84;
+ scene.globe = new MockGlobe(ellipsoid);
+ scene.mapProjection = new GeographicProjection(ellipsoid);
+
+ scene.frameState = {
+ mode: scene.mode,
+ mapProjection: scene.mapProjection,
+ };
+
+ scene.cameraUnderground = true;
+ controller.enableCollisionDetection = false;
+
+ camera.position = new Cartesian3(0.0, 0.0, -100.0);
+ camera.direction = Cartesian3.clone(Cartesian3.UNIT_Z);
+ camera.up = Cartesian3.clone(Cartesian3.UNIT_Y);
+ camera.right = Cartesian3.cross(
+ camera.direction,
+ camera.up,
+ new Cartesian3()
+ );
+ }
+
function setUp3D() {
scene.mode = SceneMode.SCENE3D;
@@ -227,6 +260,21 @@ describe("Scene/ScreenSpaceCameraController", function () {
mode: scene.mode,
mapProjection: scene.mapProjection,
};
+
+ scene.cameraUnderground = false;
+ controller.enableCollisionDetection = true;
+ }
+
+ function setUp3DUnderground() {
+ setUp3D();
+ scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);
+ scene.cameraUnderground = true;
+ controller.enableCollisionDetection = false;
+
+ camera.setView({ destination: Camera.DEFAULT_VIEW_RECTANGLE });
+ var positionCart = Ellipsoid.WGS84.cartesianToCartographic(camera.position);
+ positionCart.height = -100.0;
+ camera.position = Ellipsoid.WGS84.cartographicToCartesian(positionCart);
}
it("constructor throws without a scene", function () {
@@ -718,6 +766,26 @@ describe("Scene/ScreenSpaceCameraController", function () {
expect(position.z).toEqual(camera.position.z);
});
+ it("translates in Columbus view when camera is underground", function () {
+ setUpCVUnderground();
+
+ var position = Cartesian3.clone(camera.position);
+ var startPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 4
+ );
+ var endPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 2
+ );
+
+ moveMouse(MouseButtons.LEFT, startPosition, endPosition);
+ updateController();
+ expect(position.x).toEqual(camera.position.x);
+ expect(position.y).not.toEqual(camera.position.y);
+ expect(position.z).toEqual(camera.position.z);
+ });
+
it("looks in Columbus view", function () {
setUpCV();
var position = Cartesian3.clone(camera.position);
@@ -804,6 +872,24 @@ describe("Scene/ScreenSpaceCameraController", function () {
expect(position.z).toBeLessThan(camera.position.z);
});
+ it("zoom in Columbus view when camera is underground", function () {
+ setUpCVUnderground();
+
+ var position = Cartesian3.clone(camera.position);
+ var startPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 4
+ );
+ var endPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 2
+ );
+
+ moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
+ updateController();
+ expect(position.z).toBeLessThan(camera.position.z);
+ });
+
it("rotates in Columbus view", function () {
setUpCV();
var startPosition = new Cartesian2(
@@ -869,6 +955,25 @@ describe("Scene/ScreenSpaceCameraController", function () {
).toEqualEpsilon(camera.up, CesiumMath.EPSILON14);
});
+ it("rotates in Columbus view when camera is underground", function () {
+ setUpCVUnderground();
+ camera.position.y = -100.0;
+
+ var position = Cartesian3.clone(camera.position);
+ var startPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 2
+ );
+ var endPosition = new Cartesian2(
+ (3 * canvas.clientWidth) / 8,
+ (3 * canvas.clientHeight) / 8
+ );
+
+ moveMouse(MouseButtons.MIDDLE, startPosition, endPosition);
+ updateController();
+ expect(camera.position).not.toEqual(position);
+ });
+
it("zooms in Columus view with camera transform set", function () {
setUpCV();
@@ -988,6 +1093,28 @@ describe("Scene/ScreenSpaceCameraController", function () {
).toEqualEpsilon(camera.up, CesiumMath.EPSILON14);
});
+ it("strafes in 3D when camera is underground", function () {
+ setUp3DUnderground();
+
+ var position = Cartesian3.clone(camera.position);
+ var direction = Cartesian3.clone(camera.direction);
+
+ var startPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 2
+ );
+ var endPosition = new Cartesian2(
+ (3 * canvas.clientWidth) / 8,
+ (3 * canvas.clientHeight) / 8
+ );
+
+ moveMouse(MouseButtons.LEFT, startPosition, endPosition);
+ updateController();
+
+ expect(camera.position).not.toEqual(position);
+ expect(camera.direction).toEqual(direction);
+ });
+
it("rotates in 3D", function () {
setUp3D();
var position = Cartesian3.clone(camera.position);
@@ -1225,6 +1352,34 @@ describe("Scene/ScreenSpaceCameraController", function () {
expect(frustumWidth).toBeLessThan(camera.frustum.width);
});
+ it("zoom in 3D when camera is underground", function () {
+ setUp3DUnderground();
+
+ var position = Cartesian3.clone(camera.position);
+ var direction = Cartesian3.clone(camera.direction);
+
+ var startPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 4
+ );
+ var endPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 2
+ );
+
+ moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
+ updateController();
+ var vector = Cartesian3.subtract(
+ camera.position,
+ position,
+ new Cartesian3()
+ );
+ var normalizedVector = Cartesian3.normalize(vector, vector);
+
+ expect(normalizedVector).toEqualEpsilon(direction, CesiumMath.EPSILON2);
+ expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON6);
+ });
+
it("tilts in 3D", function () {
setUp3D();
var position = Cartesian3.clone(camera.position);
@@ -1319,6 +1474,27 @@ describe("Scene/ScreenSpaceCameraController", function () {
).toEqualEpsilon(camera.up, CesiumMath.EPSILON14);
});
+ it("tilts in 3D when camera is underground", function () {
+ setUp3DUnderground();
+
+ var position = Cartesian3.clone(camera.position);
+ var direction = Cartesian3.clone(camera.direction);
+
+ var startPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 2
+ );
+ var endPosition = new Cartesian2(
+ canvas.clientWidth / 2,
+ canvas.clientHeight / 4
+ );
+
+ moveMouse(MouseButtons.MIDDLE, startPosition, endPosition);
+ updateController();
+ expect(camera.position).not.toEqual(position);
+ expect(camera.direction).not.toEqual(direction);
+ });
+
it("looks in 3D", function () {
setUp3D();
var position = Cartesian3.clone(camera.position);