Skip to content

Commit 4a44e0e

Browse files
authored
Merge pull request #8504 from AnalyticalGraphicsInc/fix-camera-underground
Prevent camera from going underground during mouse navigation
2 parents 4d9a866 + d51c318 commit 4a44e0e

File tree

5 files changed

+143
-98
lines changed

5 files changed

+143
-98
lines changed

CHANGES.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Change Log
22
==========
33

4+
### 1.66.0 - 2020-02-03
5+
6+
##### Fixes :wrench:
7+
* Fixed a bug where the camera could go underground during mouse navigation. [#8504](https://github.com/AnalyticalGraphicsInc/cesium/pull/8504)
8+
49
### 1.65.0 - 2020-01-06
510

611
##### Breaking Changes :mega:

Source/Scene/Camera.js

-83
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ import SceneMode from './SceneMode.js';
215215
this._projection = projection;
216216
this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO));
217217
this._max2Dfrustum = undefined;
218-
this._suspendTerrainAdjustment = false;
219218

220219
// set default view
221220
rectangleCameraPosition3D(this, Camera.DEFAULT_VIEW_RECTANGLE, this.position, true);
@@ -380,76 +379,6 @@ import SceneMode from './SceneMode.js';
380379
}
381380
};
382381

383-
var scratchAdjustHeightTransform = new Matrix4();
384-
var scratchAdjustHeightCartographic = new Cartographic();
385-
386-
Camera.prototype._adjustHeightForTerrain = function() {
387-
var scene = this._scene;
388-
389-
var screenSpaceCameraController = scene.screenSpaceCameraController;
390-
var enableCollisionDetection = screenSpaceCameraController.enableCollisionDetection;
391-
var minimumCollisionTerrainHeight = screenSpaceCameraController.minimumCollisionTerrainHeight;
392-
var minimumZoomDistance = screenSpaceCameraController.minimumZoomDistance;
393-
394-
if (this._suspendTerrainAdjustment || !enableCollisionDetection) {
395-
return;
396-
}
397-
398-
var mode = this._mode;
399-
var globe = scene.globe;
400-
401-
if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
402-
return;
403-
}
404-
405-
var ellipsoid = globe.ellipsoid;
406-
var projection = scene.mapProjection;
407-
408-
var transform;
409-
var mag;
410-
if (!Matrix4.equals(this.transform, Matrix4.IDENTITY)) {
411-
transform = Matrix4.clone(this.transform, scratchAdjustHeightTransform);
412-
mag = Cartesian3.magnitude(this.position);
413-
this._setTransform(Matrix4.IDENTITY);
414-
}
415-
416-
var cartographic = scratchAdjustHeightCartographic;
417-
if (mode === SceneMode.SCENE3D) {
418-
ellipsoid.cartesianToCartographic(this.position, cartographic);
419-
} else {
420-
projection.unproject(this.position, cartographic);
421-
}
422-
423-
var heightUpdated = false;
424-
if (cartographic.height < minimumCollisionTerrainHeight) {
425-
var height = globe.getHeight(cartographic);
426-
if (defined(height)) {
427-
height += minimumZoomDistance;
428-
if (cartographic.height < height) {
429-
cartographic.height = height;
430-
if (mode === SceneMode.SCENE3D) {
431-
ellipsoid.cartographicToCartesian(cartographic, this.position);
432-
} else {
433-
projection.project(cartographic, this.position);
434-
}
435-
heightUpdated = true;
436-
}
437-
}
438-
}
439-
440-
if (defined(transform)) {
441-
this._setTransform(transform);
442-
if (heightUpdated) {
443-
Cartesian3.normalize(this.position, this.position);
444-
Cartesian3.negate(this.position, this.direction);
445-
Cartesian3.multiplyByScalar(this.position, Math.max(mag, minimumZoomDistance), this.position);
446-
Cartesian3.normalize(this.direction, this.direction);
447-
Cartesian3.cross(this.direction, this.up, this.right);
448-
Cartesian3.cross(this.right, this.direction, this.up);
449-
}
450-
}
451-
};
452-
453382
function convertTransformForColumbusView(camera) {
454383
Transforms.basisTo2D(camera._projection, camera._transform, camera._actualTransform);
455384
}
@@ -976,16 +905,6 @@ import SceneMode from './SceneMode.js';
976905
if (this._mode === SceneMode.SCENE2D) {
977906
clampMove2D(this, this.position);
978907
}
979-
980-
var globe = this._scene.globe;
981-
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);
982-
if (this._suspendTerrainAdjustment) {
983-
this._suspendTerrainAdjustment = !globeFinishedUpdating;
984-
}
985-
986-
if (globeFinishedUpdating) {
987-
this._adjustHeightForTerrain();
988-
}
989908
};
990909

991910
var setTransformPosition = new Cartesian3();
@@ -1275,8 +1194,6 @@ import SceneMode from './SceneMode.js';
12751194
scratchHpr.pitch = defaultValue(orientation.pitch, -CesiumMath.PI_OVER_TWO);
12761195
scratchHpr.roll = defaultValue(orientation.roll, 0.0);
12771196

1278-
this._suspendTerrainAdjustment = true;
1279-
12801197
if (mode === SceneMode.SCENE3D) {
12811198
setView3D(this, destination, scratchHpr);
12821199
} else if (mode === SceneMode.SCENE2D) {

Source/Scene/Globe.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -618,19 +618,26 @@ import TileSelectionResult from './TileSelectionResult.js';
618618
return undefined;
619619
}
620620

621+
var tileWithMesh = tile;
622+
621623
while (tile._lastSelectionResult === TileSelectionResult.REFINED) {
622624
tile = tileIfContainsCartographic(tile.southwestChild, cartographic) ||
623625
tileIfContainsCartographic(tile.southeastChild, cartographic) ||
624626
tileIfContainsCartographic(tile.northwestChild, cartographic) ||
625627
tile.northeastChild;
628+
if (defined(tile.data) && defined(tile.data.renderedMesh)) {
629+
tileWithMesh = tile;
630+
}
626631
}
627632

633+
tile = tileWithMesh;
634+
628635
// This tile was either rendered or culled.
629636
// It is sometimes useful to get a height from a culled tile,
630637
// e.g. when we're getting a height in order to place a billboard
631638
// on terrain, and the camera is looking at that same billboard.
632639
// The culled tile must have a valid mesh, though.
633-
if (!defined(tile.data) || !defined(tile.data.renderedMesh)) {
640+
if (!defined(tile)) {
634641
// Tile was not rendered (culled).
635642
return undefined;
636643
}

Source/Scene/ScreenSpaceCameraController.js

+89-3
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ import TweenCollection from './TweenCollection.js';
266266
this._strafing = false;
267267
this._zoomingOnVector = false;
268268
this._rotatingZoom = false;
269+
this._adjustedHeightForTerrain = false;
269270

270271
var projection = scene.mapProjection;
271272
this._maxCoord = projection.project(new Cartographic(Math.PI, CesiumMath.PI_OVER_TWO));
@@ -1137,7 +1138,10 @@ import TweenCollection from './TweenCollection.js';
11371138
controller._rotateRateRangeAdjustment = radius;
11381139

11391140
var originalPosition = Cartesian3.clone(camera.positionWC, rotateCVCartesian3);
1140-
camera._adjustHeightForTerrain();
1141+
1142+
if (controller.enableCollisionDetection) {
1143+
adjustHeightForTerrain(controller);
1144+
}
11411145

11421146
if (!Cartesian3.equals(camera.positionWC, originalPosition)) {
11431147
camera._setTransform(verticalTransform);
@@ -1766,7 +1770,10 @@ import TweenCollection from './TweenCollection.js';
17661770
controller._rotateRateRangeAdjustment = radius;
17671771

17681772
var originalPosition = Cartesian3.clone(camera.positionWC, tilt3DCartesian3);
1769-
camera._adjustHeightForTerrain();
1773+
1774+
if (controller.enableCollisionDetection) {
1775+
adjustHeightForTerrain(controller);
1776+
}
17701777

17711778
if (!Cartesian3.equals(camera.positionWC, originalPosition)) {
17721779
camera._setTransform(verticalTransform);
@@ -1918,11 +1925,78 @@ import TweenCollection from './TweenCollection.js';
19181925
reactToInput(controller, controller.enableLook, controller.lookEventTypes, look3D);
19191926
}
19201927

1928+
var scratchAdjustHeightTransform = new Matrix4();
1929+
var scratchAdjustHeightCartographic = new Cartographic();
1930+
1931+
function adjustHeightForTerrain(controller) {
1932+
controller._adjustedHeightForTerrain = true;
1933+
1934+
var scene = controller._scene;
1935+
var mode = scene.mode;
1936+
var globe = scene.globe;
1937+
1938+
if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
1939+
return;
1940+
}
1941+
1942+
var camera = scene.camera;
1943+
var ellipsoid = globe.ellipsoid;
1944+
var projection = scene.mapProjection;
1945+
1946+
var transform;
1947+
var mag;
1948+
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
1949+
transform = Matrix4.clone(camera.transform, scratchAdjustHeightTransform);
1950+
mag = Cartesian3.magnitude(camera.position);
1951+
camera._setTransform(Matrix4.IDENTITY);
1952+
}
1953+
1954+
var cartographic = scratchAdjustHeightCartographic;
1955+
if (mode === SceneMode.SCENE3D) {
1956+
ellipsoid.cartesianToCartographic(camera.position, cartographic);
1957+
} else {
1958+
projection.unproject(camera.position, cartographic);
1959+
}
1960+
1961+
var heightUpdated = false;
1962+
if (cartographic.height < controller._minimumCollisionTerrainHeight) {
1963+
var height = globe.getHeight(cartographic);
1964+
if (defined(height)) {
1965+
height += controller.minimumZoomDistance;
1966+
if (cartographic.height < height) {
1967+
cartographic.height = height;
1968+
if (mode === SceneMode.SCENE3D) {
1969+
ellipsoid.cartographicToCartesian(cartographic, camera.position);
1970+
} else {
1971+
projection.project(cartographic, camera.position);
1972+
}
1973+
heightUpdated = true;
1974+
}
1975+
}
1976+
}
1977+
1978+
if (defined(transform)) {
1979+
camera._setTransform(transform);
1980+
if (heightUpdated) {
1981+
Cartesian3.normalize(camera.position, camera.position);
1982+
Cartesian3.negate(camera.position, camera.direction);
1983+
Cartesian3.multiplyByScalar(camera.position, Math.max(mag, controller.minimumZoomDistance), camera.position);
1984+
Cartesian3.normalize(camera.direction, camera.direction);
1985+
Cartesian3.cross(camera.direction, camera.up, camera.right);
1986+
Cartesian3.cross(camera.right, camera.direction, camera.up);
1987+
}
1988+
}
1989+
}
1990+
1991+
var scratchPreviousPosition = new Cartesian3();
1992+
var scratchPreviousDirection = new Cartesian3();
1993+
19211994
/**
19221995
* @private
19231996
*/
19241997
ScreenSpaceCameraController.prototype.update = function() {
1925-
if (!Matrix4.equals(this._scene.camera.transform, Matrix4.IDENTITY)) {
1998+
var camera = this._scene.camera;
1999+
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
19262000
this._globe = undefined;
19272001
this._ellipsoid = Ellipsoid.UNIT_SPHERE;
19282002
} else {
@@ -1938,6 +2012,10 @@ import TweenCollection from './TweenCollection.js';
19382012
this._rotateFactor = 1.0 / radius;
19392013
this._rotateRateRangeAdjustment = radius;
19402014

2015+
this._adjustedHeightForTerrain = false;
2016+
var previousPosition = Cartesian3.clone(camera.positionWC, scratchPreviousPosition);
2017+
var previousDirection = Cartesian3.clone(camera.directionWC, scratchPreviousDirection);
2018+
19412019
var scene = this._scene;
19422020
var mode = scene.mode;
19432021
if (mode === SceneMode.SCENE2D) {
@@ -1950,6 +2028,14 @@ import TweenCollection from './TweenCollection.js';
19502028
update3D(this);
19512029
}
19522030

2031+
if (this.enableCollisionDetection && !this._adjustedHeightForTerrain) {
2032+
// Adjust the camera height if the camera moved at all (user input or intertia) and an action didn't already adjust the camera height
2033+
var cameraChanged = !Cartesian3.equals(previousPosition, camera.positionWC) || !Cartesian3.equals(previousDirection, camera.directionWC);
2034+
if (cameraChanged) {
2035+
adjustHeightForTerrain(this);
2036+
}
2037+
}
2038+
19532039
this._aggregator.reset();
19542040
};
19552041

Specs/Scene/ScreenSpaceCameraControllerSpec.js

+41-11
Original file line numberDiff line numberDiff line change
@@ -1024,18 +1024,18 @@ describe('Scene/ScreenSpaceCameraController', function() {
10241024
moveMouse(MouseButtons.LEFT, startPosition, endPosition);
10251025
updateController();
10261026

1027-
expect(camera.position).toEqual(position);
1028-
expect(camera.direction).toEqual(direction);
1029-
expect(camera.up).toEqual(up);
1030-
expect(camera.right).toEqual(right);
1027+
expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON7);
1028+
expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON7);
1029+
expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON7);
1030+
expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON7);
10311031

10321032
controller.enableRotate = true;
10331033
updateController();
10341034

1035-
expect(camera.position).toEqual(position);
1036-
expect(camera.direction).toEqual(direction);
1037-
expect(camera.up).toEqual(up);
1038-
expect(camera.right).toEqual(right);
1035+
expect(camera.position).toEqualEpsilon(position, CesiumMath.EPSILON7);
1036+
expect(camera.direction).toEqualEpsilon(direction, CesiumMath.EPSILON7);
1037+
expect(camera.up).toEqualEpsilon(up, CesiumMath.EPSILON7);
1038+
expect(camera.right).toEqualEpsilon(right, CesiumMath.EPSILON7);
10391039
});
10401040

10411041
it('can set input type to undefined', function() {
@@ -1101,12 +1101,17 @@ describe('Scene/ScreenSpaceCameraController', function() {
11011101
updateController();
11021102

11031103
camera.setView({
1104-
destination : Cartesian3.fromDegrees(-72.0, 40.0, 1.0)
1104+
destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0)
11051105
});
11061106

1107+
// Trigger terrain adjustment with a small mouse movement
1108+
var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4);
1109+
var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
1110+
moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
1111+
11071112
updateController();
11081113

1109-
expect(camera.positionCartographic.height).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON7);
1114+
expect(camera.positionCartographic.height).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON5);
11101115
});
11111116

11121117
it('camera does not go below the terrain in CV', function() {
@@ -1116,9 +1121,14 @@ describe('Scene/ScreenSpaceCameraController', function() {
11161121
updateController();
11171122

11181123
camera.setView({
1119-
destination : Cartesian3.fromDegrees(-72.0, 40.0, 1.0)
1124+
destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0)
11201125
});
11211126

1127+
// Trigger terrain adjustment with a small mouse movement
1128+
var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4);
1129+
var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
1130+
moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
1131+
11221132
updateController();
11231133

11241134
expect(camera.position.z).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON7);
@@ -1135,6 +1145,11 @@ describe('Scene/ScreenSpaceCameraController', function() {
11351145
destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0)
11361146
});
11371147

1148+
// Trigger terrain adjustment with a small mouse movement
1149+
var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4);
1150+
var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
1151+
moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
1152+
11381153
updateController();
11391154

11401155
expect(camera.positionCartographic.height).toBeLessThan(controller.minimumZoomDistance);
@@ -1151,6 +1166,11 @@ describe('Scene/ScreenSpaceCameraController', function() {
11511166
destination : Cartesian3.fromDegrees(-72.0, 40.0, -10.0)
11521167
});
11531168

1169+
// Trigger terrain adjustment with a small mouse movement
1170+
var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4);
1171+
var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
1172+
moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
1173+
11541174
updateController();
11551175

11561176
expect(camera.position.z).toBeLessThan(controller.minimumZoomDistance);
@@ -1164,6 +1184,11 @@ describe('Scene/ScreenSpaceCameraController', function() {
11641184

11651185
camera.lookAt(Cartesian3.fromDegrees(-72.0, 40.0, 1.0), new Cartesian3(1.0, 1.0, -10.0));
11661186

1187+
// Trigger terrain adjustment with a small mouse movement
1188+
var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4);
1189+
var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
1190+
moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
1191+
11671192
updateController();
11681193

11691194
expect(camera.positionCartographic.height).toBeGreaterThanOrEqualTo(controller.minimumZoomDistance);
@@ -1177,6 +1202,11 @@ describe('Scene/ScreenSpaceCameraController', function() {
11771202

11781203
camera.lookAt(Cartesian3.fromDegrees(-72.0, 40.0, 1.0), new Cartesian3(1.0, 1.0, -10.0));
11791204

1205+
// Trigger terrain adjustment with a small mouse movement
1206+
var startPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 4);
1207+
var endPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2);
1208+
moveMouse(MouseButtons.RIGHT, startPosition, endPosition);
1209+
11801210
updateController();
11811211

11821212
expect(camera.positionWC.x).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON8);

0 commit comments

Comments
 (0)