Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent camera from going underground during mouse navigation #8504

Merged
merged 6 commits into from
Jan 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
83 changes: 0 additions & 83 deletions Source/Scene/Camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
9 changes: 8 additions & 1 deletion Source/Scene/Globe.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
92 changes: 89 additions & 3 deletions Source/Scene/ScreenSpaceCameraController.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -1137,7 +1138,10 @@ import TweenCollection from './TweenCollection.js';
controller._rotateRateRangeAdjustment = radius;

var originalPosition = Cartesian3.clone(camera.positionWC, rotateCVCartesian3);
camera._adjustHeightForTerrain();

if (controller.enableCollisionDetection) {
adjustHeightForTerrain(controller);
}

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

var originalPosition = Cartesian3.clone(camera.positionWC, tilt3DCartesian3);
camera._adjustHeightForTerrain();

if (controller.enableCollisionDetection) {
adjustHeightForTerrain(controller);
}

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

var scratchAdjustHeightTransform = new Matrix4();
var scratchAdjustHeightCartographic = new Cartographic();

function adjustHeightForTerrain(controller) {
controller._adjustedHeightForTerrain = true;

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);
}
}
}

var scratchPreviousPosition = new Cartesian3();
var scratchPreviousDirection = new Cartesian3();

/**
* @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 {
Expand All @@ -1938,6 +2012,10 @@ import TweenCollection from './TweenCollection.js';
this._rotateFactor = 1.0 / radius;
this._rotateRateRangeAdjustment = radius;

this._adjustedHeightForTerrain = false;
var previousPosition = Cartesian3.clone(camera.positionWC, scratchPreviousPosition);
var previousDirection = Cartesian3.clone(camera.directionWC, scratchPreviousDirection);

var scene = this._scene;
var mode = scene.mode;
if (mode === SceneMode.SCENE2D) {
Expand All @@ -1950,6 +2028,14 @@ import TweenCollection from './TweenCollection.js';
update3D(this);
}

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) {
adjustHeightForTerrain(this);
}
}

this._aggregator.reset();
};

Expand Down
52 changes: 41 additions & 11 deletions Specs/Scene/ScreenSpaceCameraControllerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down