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

Camera-Terrain Collision #2470

Merged
merged 11 commits into from
Feb 17, 2015
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Change Log
* Fixed a bug in imagery loading that could cause some or all of the globe to be missing when using an imagery layer that does not cover the entire globe.
* Added support for rendering a water effect on Quantized-Mesh terrain tiles.
* Added `pack` and `unpack` functions to `Matrix2` and `Matrix3`.
* Added camera-terrain collision detection/response when the camera reference frame is set.
* Added `ScreenSpaceCameraController.enableCollisionDetection` to enable/disable camera collision detection with terrain.

### 1.6 - 2015-02-02

Expand Down
60 changes: 41 additions & 19 deletions Source/Scene/ScreenSpaceCameraController.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ define([
* @default 7500000.0
*/
this.minimumTrackBallHeight = 7500000.0;
/**
* Enables or disables camera collision detection with terrain.
* @type {Boolean}
* @default true
*/
this.enableCollisionDetection = true;

this._scene = scene;
this._globe = undefined;
Expand Down Expand Up @@ -1559,44 +1565,60 @@ define([
var scratchAdjustHeightCartographic = new Cartographic();

function adjustHeightForTerrain(controller) {
if (!controller.enableCollisionDetection) {
return;
}

var scene = controller._scene;
var mode = scene.mode;
var globe = controller._globe;
var globe = scene.globe;

if (!defined(globe) || mode === SceneMode.SCENE2D || mode === SceneMode.MORPHING) {
return;
}

var camera = scene.camera;
var ellipsoid = controller._ellipsoid;
var ellipsoid = globe.ellipsoid;
var projection = scene.mapProjection;

var transform;
var mag;
if (!Matrix4.equals(camera.transform, Matrix4.IDENTITY)) {
transform = Matrix4.clone(camera.transform);
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);
}

if (cartographic.height > controller.minimumCollisionTerrainHeight) {
return;
}

var height = globe.getHeight(cartographic);
if (!defined(height)) {
return;
}

height += controller.minimumZoomDistance;
if (cartographic.height >= height) {
return;
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);
}
}
}
}

cartographic.height = height;
if (mode === SceneMode.SCENE3D) {
ellipsoid.cartographicToCartesian(cartographic, camera.position);
} else {
projection.project(cartographic, camera.position);
if (defined(transform)) {
camera._setTransform(transform);
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);
}
}

Expand Down
95 changes: 95 additions & 0 deletions Specs/Scene/ScreenSpaceCameraControllerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ defineSuite([
this.mapProjection = new GeographicProjection(ellipsoid);
};

var MockGlobe = function(ellipsoid) {
this.ellipsoid = ellipsoid;
this.getHeight = function(cartographic) {
return 0.0;
};
};

beforeAll(function() {
canvas = createCanvas(1024, 768);
});
Expand Down Expand Up @@ -989,6 +996,94 @@ defineSuite([
expect(Cartesian3.magnitude(camera.position)).toBeGreaterThan(Cartesian3.magnitude(position));
});

it('camera does not go below the terrain in 3D', function() {
setUp3D();
scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);

updateController();

camera.setView({
position : Cartesian3.fromDegrees(-72.0, 40.0, 1.0)
});

updateController();

expect(camera.positionCartographic.height).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON7);
});

it('camera does not go below the terrain in CV', function() {
setUpCV();
scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);

updateController();

camera.setView({
position : Cartesian3.fromDegrees(-72.0, 40.0, 1.0)
});

updateController();

expect(camera.position.z).toEqualEpsilon(controller.minimumZoomDistance, CesiumMath.EPSILON7);
});

it('camera does go below the terrain in 3D when collision detection is disabled', function() {
setUp3D();
scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);
controller.enableCollisionDetection = false;

updateController();

camera.setView({
position : Cartesian3.fromDegrees(-72.0, 40.0, 1.0)
});

updateController();

expect(camera.positionCartographic.height).toBeLessThan(controller.minimumZoomDistance);
});

it('camera does go below the terrain in CV when collision detection is disabled', function() {
setUpCV();
scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);
controller.enableCollisionDetection = false;

updateController();

camera.setView({
position : Cartesian3.fromDegrees(-72.0, 40.0, 1.0)
});

updateController();

expect(camera.position.z).toBeLessThan(controller.minimumZoomDistance);
});

it('camera does not go below the terrain in 3D with the transform set', function() {
setUp3D();
scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);

updateController();

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

updateController();

expect(camera.positionCartographic.height).toBeGreaterThanOrEqualTo(controller.minimumZoomDistance);
});

it('camera does not go below the terrain in CV with the transform set', function() {
setUpCV();
scene.globe = new MockGlobe(scene.mapProjection.ellipsoid);

updateController();

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

updateController();

expect(camera.positionWC.x).toBeGreaterThanOrEqualTo(controller.minimumZoomDistance);
});

it('is destroyed', function() {
expect(controller.isDestroyed()).toEqual(false);
controller.destroy();
Expand Down