From a3e291a2ba55cda7059fe7c6bbb11410162b9901 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 1 Jan 2020 19:53:36 -0500 Subject: [PATCH 1/6] Only adjust for terrain if screen space camera controller moved camera --- Source/Scene/Camera.js | 83 --------------------- Source/Scene/ScreenSpaceCameraController.js | 80 +++++++++++++++++++- 2 files changed, 77 insertions(+), 86 deletions(-) diff --git a/Source/Scene/Camera.js b/Source/Scene/Camera.js index 4ce27eb78ef0..071d3c509aef 100644 --- a/Source/Scene/Camera.js +++ b/Source/Scene/Camera.js @@ -215,7 +215,6 @@ import SceneMode from './SceneMode.js'; this._projection = projection; this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)); this._max2Dfrustum = undefined; - this._suspendTerrainAdjustment = false; // set default view rectangleCameraPosition3D(this, Camera.DEFAULT_VIEW_RECTANGLE, this.position, true); @@ -380,76 +379,6 @@ import SceneMode from './SceneMode.js'; } }; - var scratchAdjustHeightTransform = new Matrix4(); - var scratchAdjustHeightCartographic = new Cartographic(); - - Camera.prototype._adjustHeightForTerrain = function() { - var scene = this._scene; - - var screenSpaceCameraController = scene.screenSpaceCameraController; - var enableCollisionDetection = screenSpaceCameraController.enableCollisionDetection; - var minimumCollisionTerrainHeight = screenSpaceCameraController.minimumCollisionTerrainHeight; - var minimumZoomDistance = screenSpaceCameraController.minimumZoomDistance; - - if (this._suspendTerrainAdjustment || !enableCollisionDetection) { - return; - } - - var mode = this._mode; - var globe = scene.globe; - - if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) { - return; - } - - var ellipsoid = globe.ellipsoid; - var projection = scene.mapProjection; - - var transform; - var mag; - if (!Matrix4.equals(this.transform, Matrix4.IDENTITY)) { - transform = Matrix4.clone(this.transform, scratchAdjustHeightTransform); - mag = Cartesian3.magnitude(this.position); - this._setTransform(Matrix4.IDENTITY); - } - - var cartographic = scratchAdjustHeightCartographic; - if (mode === SceneMode.SCENE3D) { - ellipsoid.cartesianToCartographic(this.position, cartographic); - } else { - projection.unproject(this.position, cartographic); - } - - var heightUpdated = false; - if (cartographic.height < minimumCollisionTerrainHeight) { - var height = globe.getHeight(cartographic); - if (defined(height)) { - height += minimumZoomDistance; - if (cartographic.height < height) { - cartographic.height = height; - if (mode === SceneMode.SCENE3D) { - ellipsoid.cartographicToCartesian(cartographic, this.position); - } else { - projection.project(cartographic, this.position); - } - heightUpdated = true; - } - } - } - - if (defined(transform)) { - this._setTransform(transform); - if (heightUpdated) { - Cartesian3.normalize(this.position, this.position); - Cartesian3.negate(this.position, this.direction); - Cartesian3.multiplyByScalar(this.position, Math.max(mag, minimumZoomDistance), this.position); - Cartesian3.normalize(this.direction, this.direction); - Cartesian3.cross(this.direction, this.up, this.right); - Cartesian3.cross(this.right, this.direction, this.up); - } - } - }; - function convertTransformForColumbusView(camera) { Transforms.basisTo2D(camera._projection, camera._transform, camera._actualTransform); } @@ -976,16 +905,6 @@ import SceneMode from './SceneMode.js'; if (this._mode === SceneMode.SCENE2D) { clampMove2D(this, this.position); } - - var globe = this._scene.globe; - var globeFinishedUpdating = !defined(globe) || (globe._surface.tileProvider.ready && globe._surface._tileLoadQueueHigh.length === 0 && globe._surface._tileLoadQueueMedium.length === 0 && globe._surface._tileLoadQueueLow.length === 0 && globe._surface._debug.tilesWaitingForChildren === 0); - if (this._suspendTerrainAdjustment) { - this._suspendTerrainAdjustment = !globeFinishedUpdating; - } - - if (globeFinishedUpdating) { - this._adjustHeightForTerrain(); - } }; var setTransformPosition = new Cartesian3(); @@ -1275,8 +1194,6 @@ import SceneMode from './SceneMode.js'; scratchHpr.pitch = defaultValue(orientation.pitch, -CesiumMath.PI_OVER_TWO); scratchHpr.roll = defaultValue(orientation.roll, 0.0); - this._suspendTerrainAdjustment = true; - if (mode === SceneMode.SCENE3D) { setView3D(this, destination, scratchHpr); } else if (mode === SceneMode.SCENE2D) { diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index 6a9278ac11d3..3870331511af 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -1137,7 +1137,7 @@ import TweenCollection from './TweenCollection.js'; controller._rotateRateRangeAdjustment = radius; var originalPosition = Cartesian3.clone(camera.positionWC, rotateCVCartesian3); - camera._adjustHeightForTerrain(); + adjustHeightForTerrain(controller); if (!Cartesian3.equals(camera.positionWC, originalPosition)) { camera._setTransform(verticalTransform); @@ -1766,7 +1766,7 @@ import TweenCollection from './TweenCollection.js'; controller._rotateRateRangeAdjustment = radius; var originalPosition = Cartesian3.clone(camera.positionWC, tilt3DCartesian3); - camera._adjustHeightForTerrain(); + adjustHeightForTerrain(controller); if (!Cartesian3.equals(camera.positionWC, originalPosition)) { camera._setTransform(verticalTransform); @@ -1918,11 +1918,77 @@ import TweenCollection from './TweenCollection.js'; reactToInput(controller, controller.enableLook, controller.lookEventTypes, look3D); } + var scratchAdjustHeightTransform = new Matrix4(); + var scratchAdjustHeightCartographic = new Cartographic(); + + function adjustHeightForTerrain(controller) { + if (!controller.enableCollisionDetection) { + return; + } + + var scene = controller._scene; + var mode = scene.mode; + var globe = scene.globe; + + if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) { + return; + } + + var camera = scene.camera; + var ellipsoid = globe.ellipsoid; + var projection = scene.mapProjection; + + var transform; + var mag; + if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) { + transform = Matrix4.clone(camera.transform, scratchAdjustHeightTransform); + mag = Cartesian3.magnitude(camera.position); + camera._setTransform(Matrix4.IDENTITY); + } + + var cartographic = scratchAdjustHeightCartographic; + if (mode === SceneMode.SCENE3D) { + ellipsoid.cartesianToCartographic(camera.position, cartographic); + } else { + projection.unproject(camera.position, cartographic); + } + + var heightUpdated = false; + if (cartographic.height < controller._minimumCollisionTerrainHeight) { + var height = globe.getHeight(cartographic); + if (defined(height)) { + height += controller.minimumZoomDistance; + if (cartographic.height < height) { + cartographic.height = height; + if (mode === SceneMode.SCENE3D) { + ellipsoid.cartographicToCartesian(cartographic, camera.position); + } else { + projection.project(cartographic, camera.position); + } + heightUpdated = true; + } + } + } + + if (defined(transform)) { + camera._setTransform(transform); + if (heightUpdated) { + Cartesian3.normalize(camera.position, camera.position); + Cartesian3.negate(camera.position, camera.direction); + Cartesian3.multiplyByScalar(camera.position, Math.max(mag, controller.minimumZoomDistance), camera.position); + Cartesian3.normalize(camera.direction, camera.direction); + Cartesian3.cross(camera.direction, camera.up, camera.right); + Cartesian3.cross(camera.right, camera.direction, camera.up); + } + } + } + /** * @private */ ScreenSpaceCameraController.prototype.update = function() { - if (!Matrix4.equals(this._scene.camera.transform, Matrix4.IDENTITY)) { + var camera = this._scene.camera; + if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) { this._globe = undefined; this._ellipsoid = Ellipsoid.UNIT_SPHERE; } else { @@ -1938,6 +2004,9 @@ import TweenCollection from './TweenCollection.js'; this._rotateFactor = 1.0 / radius; this._rotateRateRangeAdjustment = radius; + var previousPosition = Cartesian3.clone(camera.positionWC); + var previousDirection = Cartesian3.clone(camera.directionWC); + var scene = this._scene; var mode = scene.mode; if (mode === SceneMode.SCENE2D) { @@ -1950,6 +2019,11 @@ import TweenCollection from './TweenCollection.js'; update3D(this); } + var cameraChanged = !Cartesian3.equals(previousPosition, camera.positionWC) || !Cartesian3.equals(previousDirection, camera.directionWC); + if (cameraChanged) { + adjustHeightForTerrain(this); + } + this._aggregator.reset(); }; From 79ca994eabbcb5e495f38a4b546cef92b6ea6fc0 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 1 Jan 2020 20:14:25 -0500 Subject: [PATCH 2/6] Prevent multiple adjust height calls in the same update --- Source/Scene/ScreenSpaceCameraController.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index 3870331511af..6c9e880db5f1 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -266,6 +266,7 @@ import TweenCollection from './TweenCollection.js'; this._strafing = false; this._zoomingOnVector = false; this._rotatingZoom = false; + this._adjustedHeightForTerrain = false; var projection = scene.mapProjection; this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO)); @@ -1926,6 +1927,8 @@ import TweenCollection from './TweenCollection.js'; return; } + controller._adjustedHeightForTerrain = true; + var scene = controller._scene; var mode = scene.mode; var globe = scene.globe; @@ -2004,6 +2007,7 @@ import TweenCollection from './TweenCollection.js'; this._rotateFactor = 1.0 / radius; this._rotateRateRangeAdjustment = radius; + this._adjustedHeightForTerrain = false; var previousPosition = Cartesian3.clone(camera.positionWC); var previousDirection = Cartesian3.clone(camera.directionWC); @@ -2019,9 +2023,12 @@ import TweenCollection from './TweenCollection.js'; update3D(this); } - var cameraChanged = !Cartesian3.equals(previousPosition, camera.positionWC) || !Cartesian3.equals(previousDirection, camera.directionWC); - if (cameraChanged) { - adjustHeightForTerrain(this); + if (!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 + var cameraChanged = !Cartesian3.equals(previousPosition, camera.positionWC) || !Cartesian3.equals(previousDirection, camera.directionWC); + if (cameraChanged) { + adjustHeightForTerrain(this); + } } this._aggregator.reset(); From b1862104a865544c0caf0378ccc6cd20f5ca37f8 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 1 Jan 2020 21:11:38 -0500 Subject: [PATCH 3/6] Fix tests --- Source/Scene/ScreenSpaceCameraController.js | 7 ++- .../Scene/ScreenSpaceCameraControllerSpec.js | 52 +++++++++++++++---- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index 6c9e880db5f1..2a6c39efd4dd 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -1986,6 +1986,9 @@ import TweenCollection from './TweenCollection.js'; } } + var scratchPreviousPosition = new Cartesian3(); + var scratchPreviousDirection = new Cartesian3(); + /** * @private */ @@ -2008,8 +2011,8 @@ import TweenCollection from './TweenCollection.js'; this._rotateRateRangeAdjustment = radius; this._adjustedHeightForTerrain = false; - var previousPosition = Cartesian3.clone(camera.positionWC); - var previousDirection = Cartesian3.clone(camera.directionWC); + var previousPosition = Cartesian3.clone(camera.positionWC, scratchPreviousPosition); + var previousDirection = Cartesian3.clone(camera.directionWC, scratchPreviousDirection); var scene = this._scene; var mode = scene.mode; diff --git a/Specs/Scene/ScreenSpaceCameraControllerSpec.js b/Specs/Scene/ScreenSpaceCameraControllerSpec.js index 8f92738f9bf4..de412812ac00 100644 --- a/Specs/Scene/ScreenSpaceCameraControllerSpec.js +++ b/Specs/Scene/ScreenSpaceCameraControllerSpec.js @@ -1024,18 +1024,18 @@ describe('Scene/ScreenSpaceCameraController', function() { moveMouse(MouseButtons.LEFT, startPosition, endPosition); updateController(); - expect(camera.position).toEqual(position); - expect(camera.direction).toEqual(direction); - expect(camera.up).toEqual(up); - expect(camera.right).toEqual(right); + expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON7); + expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON7); + expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON7); + expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON7); controller.enableRotate = true; updateController(); - expect(camera.position).toEqual(position); - expect(camera.direction).toEqual(direction); - expect(camera.up).toEqual(up); - expect(camera.right).toEqual(right); + expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON7); + expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON7); + expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON7); + expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON7); }); it('can set input type to undefined', function() { @@ -1101,12 +1101,17 @@ describe('Scene/ScreenSpaceCameraController', function() { updateController(); camera.setView({ - destination : Cartesian3.fromDegrees(-72.0, 40.0, 1.0) + destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0) }); + // Trigger terrain adjustment with a small mouse movement + 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(camera.positionCartographic.height).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON7); + expect(camera.positionCartographic.height).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON5); }); it('camera does not go below the terrain in CV', function() { @@ -1116,9 +1121,14 @@ describe('Scene/ScreenSpaceCameraController', function() { updateController(); camera.setView({ - destination : Cartesian3.fromDegrees(-72.0, 40.0, 1.0) + destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0) }); + // Trigger terrain adjustment with a small mouse movement + 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(camera.position.z).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON7); @@ -1135,6 +1145,11 @@ describe('Scene/ScreenSpaceCameraController', function() { destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0) }); + // Trigger terrain adjustment with a small mouse movement + 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(camera.positionCartographic.height).toBeLessThan(controller.minimumZoomDistance); @@ -1151,6 +1166,11 @@ describe('Scene/ScreenSpaceCameraController', function() { destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0) }); + // Trigger terrain adjustment with a small mouse movement + 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(camera.position.z).toBeLessThan(controller.minimumZoomDistance); @@ -1164,6 +1184,11 @@ describe('Scene/ScreenSpaceCameraController', function() { camera.lookAt(Cartesian3.fromDegrees(-72.0, 40.0, 1.0), new Cartesian3(1.0, 1.0, -10.0)); + // Trigger terrain adjustment with a small mouse movement + 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(camera.positionCartographic.height).toBeGreaterThanOrEqualTo(controller.minimumZoomDistance); @@ -1177,6 +1202,11 @@ describe('Scene/ScreenSpaceCameraController', function() { camera.lookAt(Cartesian3.fromDegrees(-72.0, 40.0, 1.0), new Cartesian3(1.0, 1.0, -10.0)); + // Trigger terrain adjustment with a small mouse movement + 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(camera.positionWC.x).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON8); From 587ce9533e6f2d422586d23f6c99b98a69bed2a1 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Wed, 1 Jan 2020 21:13:51 -0500 Subject: [PATCH 4/6] Update CHANGES.md --- CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a3d5969a5c03..d26ff808c711 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Change Log ========== +### 1.66.0 - 2020-02-03 + +##### Fixes :wrench: +* Fixed a bug where the camera could go underground during mouse navigation. [#8504](https://github.com/AnalyticalGraphicsInc/cesium/pull/8504) + ### 1.65.0 - 2020-01-06 ##### Fixes :wrench: From 648b10a79124b4c81634a64ef2da47c93b2347b8 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 2 Jan 2020 11:56:09 -0500 Subject: [PATCH 5/6] globe.getHeight traverses to deepest tile with a rendered mesh --- Source/Scene/Globe.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index eb17e1ac979c..30cff1b60dd3 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -618,19 +618,26 @@ import TileSelectionResult from './TileSelectionResult.js'; return undefined; } + var tileWithMesh = tile; + while (tile._lastSelectionResult === TileSelectionResult.REFINED) { tile = tileIfContainsCartographic(tile.southwestChild, cartographic) || tileIfContainsCartographic(tile.southeastChild, cartographic) || tileIfContainsCartographic(tile.northwestChild, cartographic) || tile.northeastChild; + if (defined(tile.data) && defined(tile.data.renderedMesh)) { + tileWithMesh = tile; + } } + tile = tileWithMesh; + // This tile was either rendered or culled. // It is sometimes useful to get a height from a culled tile, // e.g. when we're getting a height in order to place a billboard // on terrain, and the camera is looking at that same billboard. // The culled tile must have a valid mesh, though. - if (!defined(tile.data) || !defined(tile.data.renderedMesh)) { + if (!defined(tile)) { // Tile was not rendered (culled). return undefined; } From d51c31843931a9d1eecc0c52f2e63fbee1dec81a Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Thu, 2 Jan 2020 18:25:27 -0500 Subject: [PATCH 6/6] Slight reorganization for clarity --- Source/Scene/ScreenSpaceCameraController.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Source/Scene/ScreenSpaceCameraController.js b/Source/Scene/ScreenSpaceCameraController.js index 2a6c39efd4dd..8f37739d2356 100644 --- a/Source/Scene/ScreenSpaceCameraController.js +++ b/Source/Scene/ScreenSpaceCameraController.js @@ -1138,7 +1138,10 @@ import TweenCollection from './TweenCollection.js'; controller._rotateRateRangeAdjustment = radius; var originalPosition = Cartesian3.clone(camera.positionWC, rotateCVCartesian3); - adjustHeightForTerrain(controller); + + if (controller.enableCollisionDetection) { + adjustHeightForTerrain(controller); + } if (!Cartesian3.equals(camera.positionWC, originalPosition)) { camera._setTransform(verticalTransform); @@ -1767,7 +1770,10 @@ import TweenCollection from './TweenCollection.js'; controller._rotateRateRangeAdjustment = radius; var originalPosition = Cartesian3.clone(camera.positionWC, tilt3DCartesian3); - adjustHeightForTerrain(controller); + + if (controller.enableCollisionDetection) { + adjustHeightForTerrain(controller); + } if (!Cartesian3.equals(camera.positionWC, originalPosition)) { camera._setTransform(verticalTransform); @@ -1923,10 +1929,6 @@ import TweenCollection from './TweenCollection.js'; var scratchAdjustHeightCartographic = new Cartographic(); function adjustHeightForTerrain(controller) { - if (!controller.enableCollisionDetection) { - return; - } - controller._adjustedHeightForTerrain = true; var scene = controller._scene; @@ -2026,7 +2028,7 @@ import TweenCollection from './TweenCollection.js'; update3D(this); } - if (!this._adjustedHeightForTerrain) { + 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 var cameraChanged = !Cartesian3.equals(previousPosition, camera.positionWC) || !Cartesian3.equals(previousDirection, camera.directionWC); if (cameraChanged) {