Skip to content

Commit d94a4e0

Browse files
avpeerySnailBoneskarimnaaji
authored
Remove pitching and clipping while zooming or dragging with terrain (#12354)
* remove code that pitches camera above terrain * update integration tests - since pitch is not being adjusted * add new render test to show camera stops and doesn't pitch when colliding with terrain * fix clipping terrain with drag gestures Co-authored-by: Aidan H <[email protected]> Co-authored-by: Karim Naaji <[email protected]>
1 parent 3415445 commit d94a4e0

File tree

12 files changed

+150
-54
lines changed

12 files changed

+150
-54
lines changed

src/geo/transform.js

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ class Transform {
149149
_nearZ: number;
150150
_farZ: number;
151151
_mercatorScaleRatio: number;
152+
_isCameraConstrained: boolean;
152153

153154
constructor(minZoom: ?number, maxZoom: ?number, minPitch: ?number, maxPitch: ?number, renderWorldCopies: boolean | void, projection?: ?ProjectionSpecification, bounds: ?LngLatBounds) {
154155
this.tileSize = 512; // constant
@@ -225,13 +226,14 @@ class Transform {
225226
this._updateCameraOnTerrain();
226227
this._calcMatrices();
227228
}
228-
updateElevation(constrainCameraOverTerrain: boolean) { // On render, no need for higher granularity on update reasons.
229+
230+
updateElevation(constrainCameraOverTerrain: boolean, adaptCameraAltitude: boolean = false) {
229231
const centerAltitudeChanged = this._elevation && this._elevation.exaggeration() !== this._centerAltitudeValidForExaggeration;
230232
if (this._seaLevelZoom == null || centerAltitudeChanged) {
231233
this._updateCameraOnTerrain();
232234
}
233235
if (constrainCameraOverTerrain || centerAltitudeChanged) {
234-
this._constrainCameraAltitude();
236+
this._constrainCamera(adaptCameraAltitude);
235237
}
236238
this._calcMatrices();
237239
}
@@ -1620,7 +1622,6 @@ class Transform {
16201622
}
16211623

16221624
recenterOnTerrain() {
1623-
16241625
if (!this._elevation || this.projection.name === 'globe')
16251626
return;
16261627

@@ -1659,41 +1660,41 @@ class Transform {
16591660
}
16601661
}
16611662

1662-
_constrainCameraAltitude() {
1663+
_constrainCamera(adaptCameraAltitude: boolean = false) {
16631664
if (!this._elevation)
16641665
return;
16651666

16661667
const elevation: Elevation = this._elevation;
1667-
this._updateCameraState();
16681668

16691669
// Find uncompensated camera position for elevation sampling.
16701670
// The default camera position might have been compensated by the active projection model.
16711671
const mercPixelsPerMeter = mercatorZfromAltitude(1, this._center.lat) * this.worldSize;
16721672
const pos = this._computeCameraPosition(mercPixelsPerMeter);
1673-
16741673
const elevationAtCamera = elevation.getAtPointOrZero(new MercatorCoordinate(...pos));
1675-
const minHeight = this._minimumHeightOverTerrain() * Math.cos(degToRad(this._maxPitch));
16761674
const terrainElevation = this.pixelsPerMeter / this.worldSize * elevationAtCamera;
1677-
const cameraHeight = this._camera.position[2] - terrainElevation;
1678-
1679-
if (cameraHeight < minHeight) {
1680-
const center = this.locationCoordinate(this._center, this._centerAltitude);
1681-
const cameraToCenter = [center.x - pos[0], center.y - pos[1], center.z - pos[2]];
1682-
const prevDistToCamera = vec3.length(cameraToCenter);
1683-
1684-
// Adjust the camera vector so that the camera is placed above the terrain.
1685-
// Distance between the camera and the center point is kept constant.
1686-
cameraToCenter[2] -= (minHeight - cameraHeight) / this._pixelsPerMercatorPixel;
1687-
1688-
const newDistToCamera = vec3.length(cameraToCenter);
1689-
if (newDistToCamera === 0)
1690-
return;
1691-
1692-
vec3.scale(cameraToCenter, cameraToCenter, prevDistToCamera / newDistToCamera * this._pixelsPerMercatorPixel);
1693-
this._camera.position = [center.x - cameraToCenter[0], center.y - cameraToCenter[1], center.z * this._pixelsPerMercatorPixel - cameraToCenter[2]];
1694-
1695-
this._camera.orientation = orientationFromFrame(cameraToCenter, this._camera.up());
1696-
this._updateStateFromCamera();
1675+
const minHeight = this._minimumHeightOverTerrain();
1676+
const cameraHeight = pos[2] - terrainElevation;
1677+
1678+
if (cameraHeight <= minHeight) {
1679+
if (cameraHeight < 0 || adaptCameraAltitude) {
1680+
const center = this.locationCoordinate(this._center, this._centerAltitude);
1681+
const cameraToCenter = [pos[0], pos[1], center.z - pos[2]];
1682+
1683+
const prevDistToCamera = vec3.length(cameraToCenter);
1684+
// Adjust the camera vector so that the camera is placed above the terrain.
1685+
// Distance between the camera and the center point is kept constant.
1686+
cameraToCenter[2] -= (minHeight - cameraHeight) / this._pixelsPerMercatorPixel;
1687+
const newDistToCamera = vec3.length(cameraToCenter);
1688+
1689+
if (newDistToCamera === 0)
1690+
return;
1691+
1692+
vec3.scale(cameraToCenter, cameraToCenter, prevDistToCamera / newDistToCamera * this._pixelsPerMercatorPixel);
1693+
this._camera.position = [pos[0], pos[1], center.z * this._pixelsPerMercatorPixel - cameraToCenter[2]];
1694+
this._updateStateFromCamera();
1695+
} else {
1696+
this._isCameraConstrained = true;
1697+
}
16971698
}
16981699
}
16991700

@@ -1754,7 +1755,7 @@ class Transform {
17541755
this.zoom += this.scaleZoom(s);
17551756
}
17561757

1757-
this._constrainCameraAltitude();
1758+
this._constrainCamera();
17581759
this._unmodified = unmodified;
17591760
this._constraining = false;
17601761
}
@@ -2041,10 +2042,10 @@ class Transform {
20412042

20422043
_minimumHeightOverTerrain(): number {
20432044
// Determine minimum height for the camera over the terrain related to current zoom.
2044-
// Values above than 2 allow max-pitch camera closer to e.g. top of the hill, exposing
2045+
// Values above 4 allow camera closer to e.g. top of the hill, exposing
20452046
// drape raster overscale artifacts or cut terrain (see under it) as it gets clipped on
20462047
// near plane. Returned value is in mercator coordinates.
2047-
const MAX_DRAPE_OVERZOOM = 2;
2048+
const MAX_DRAPE_OVERZOOM = 4;
20482049
const zoom = Math.min((this._seaLevelZoom != null ? this._seaLevelZoom : this._zoom) + MAX_DRAPE_OVERZOOM, this._maxZoom);
20492050
return this._mercatorZfromZoom(zoom);
20502051
}

src/render/painter.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,15 @@ class Painter {
185185
this._backgroundTiles = {};
186186
}
187187

188-
updateTerrain(style: Style, cameraChanging: boolean) {
188+
updateTerrain(style: Style, adaptCameraAltitude: boolean) {
189189
const enabled = !!style && !!style.terrain && this.transform.projection.supportsTerrain;
190190
if (!enabled && (!this._terrain || !this._terrain.enabled)) return;
191191
if (!this._terrain) {
192192
this._terrain = new Terrain(this, style);
193193
}
194194
const terrain: Terrain = this._terrain;
195195
this.transform.elevation = enabled ? terrain : null;
196-
terrain.update(style, this.transform, cameraChanging);
196+
terrain.update(style, this.transform, adaptCameraAltitude);
197197
}
198198

199199
_updateFog(style: Style) {

src/terrain/terrain.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,8 @@ export class Terrain extends Elevation {
285285
* Validate terrain and update source cache used for elevation.
286286
* Explicitly pass transform to update elevation (Transform.updateElevation)
287287
* before using transform for source cache update.
288-
* cameraChanging is true when camera is zooming, panning or orbiting.
289288
*/
290-
update(style: Style, transform: Transform, cameraChanging: boolean) {
289+
update(style: Style, transform: Transform, adaptCameraAltitude: boolean) {
291290
if (style && style.terrain) {
292291
if (this._style !== style) {
293292
this.style = style;
@@ -324,9 +323,9 @@ export class Terrain extends Elevation {
324323
}
325324

326325
updateSourceCache();
327-
// Camera, when changing, gets constrained over terrain. Issue constrainCameraOverTerrain = true
328-
// here to cover potential under terrain situation on data or style change.
329-
transform.updateElevation(!cameraChanging);
326+
// Camera gets constrained over terrain. Issue constrainCameraOverTerrain = true
327+
// here to cover potential under terrain situation on data, style, or other camera changes.
328+
transform.updateElevation(true, adaptCameraAltitude);
330329

331330
// Reset tile lookup cache and update draped tiles coordinates.
332331
this.resetTileLookupCache(this.proxySourceCache.id);

src/ui/handler/keyboard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,11 @@ class KeyboardHandler {
125125
return {
126126
cameraAnimation: (map: Map) => {
127127
const zoom = map.getZoom();
128+
128129
map.easeTo({
129130
duration: 300,
130131
easeId: 'keyboardHandler',
131132
easing: easeOut,
132-
133133
zoom: zoomDir ? Math.round(zoom) + zoomDir * (e.shiftKey ? 2 : 1) : zoom,
134134
bearing: map.getBearing() + bearingDir * this._bearingStep,
135135
pitch: map.getPitch() + pitchDir * this._pitchStep,

src/ui/handler_manager.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,10 @@ class HandlerManager {
322322
return !!isMoving(this._eventsInProgress) || this.isZooming();
323323
}
324324

325+
_isDragging(): boolean {
326+
return !!this._eventsInProgress.drag;
327+
}
328+
325329
_blockedByActive(activeHandlers: { [string]: Handler }, allowed: Array<string>, myName: string): boolean {
326330
for (const name in activeHandlers) {
327331
if (name === myName) continue;
@@ -491,12 +495,22 @@ class HandlerManager {
491495
if (preZoom !== tr.zoom) this._map._update(true);
492496
}
493497

498+
// Catches double click and double tap zooms when camera is constrained over terrain
499+
if (tr._isCameraConstrained) map._stop(true);
500+
494501
if (!hasChange(combinedResult)) {
495502
this._fireEvents(combinedEventsInProgress, deactivatedHandlers, true);
496503
return;
497504
}
505+
498506
let {panDelta, zoomDelta, bearingDelta, pitchDelta, around, aroundCoord, pinchAround} = combinedResult;
499507

508+
if (tr._isCameraConstrained) {
509+
// Catches wheel zoom events when camera is constrained over terrain
510+
if (zoomDelta > 0) zoomDelta = 0;
511+
tr._isCameraConstrained = false;
512+
}
513+
500514
if (pinchAround !== undefined) {
501515
around = pinchAround;
502516
}
@@ -582,7 +596,6 @@ class HandlerManager {
582596
this._map._update();
583597
if (!combinedResult.noInertia) this._inertia.record(combinedResult);
584598
this._fireEvents(combinedEventsInProgress, deactivatedHandlers, true);
585-
586599
}
587600

588601
_fireEvents(newEventsInProgress: { [string]: Object }, deactivatedHandlers: Object, allowEndAnimation: boolean) {

src/ui/map.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,10 @@ class Map extends Camera {
13481348
return this._rotating || (this.handlers && this.handlers.isRotating()) || false;
13491349
}
13501350

1351+
_isDragging(): boolean {
1352+
return (this.handlers && this.handlers._isDragging()) || false;
1353+
}
1354+
13511355
_createDelegatedListener(type: MapEvent, layers: Array<any>, listener: any): any {
13521356
if (type === 'mouseenter' || type === 'mouseover') {
13531357
let mousein = false;
@@ -3410,7 +3414,8 @@ class Map extends Camera {
34103414
_updateTerrain() {
34113415
// Recalculate if enabled/disabled and calculate elevation cover. As camera is using elevation tiles before
34123416
// render (and deferred update after zoom recalculation), this needs to be called when removing terrain source.
3413-
this.painter.updateTerrain(this.style, this.isMoving() || this.isRotating() || this.isZooming());
3417+
const adaptCameraAltitude = this._isDragging();
3418+
this.painter.updateTerrain(this.style, adaptCameraAltitude);
34143419
}
34153420

34163421
_calculateSpeedIndex(): number {
3.94 KB
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"version": 8,
3+
"metadata": {
4+
"test": {
5+
"height": 256,
6+
"width": 256
7+
}
8+
},
9+
"center": [
10+
-104.93611234965806,
11+
38.85724324489064
12+
],
13+
"zoom": 22,
14+
"pitch": 85,
15+
"bearing": -79,
16+
"terrain": {
17+
"source": "rgbterrain",
18+
"exaggeration": 1
19+
},
20+
"sources": {
21+
"rgbterrain": {
22+
"type": "raster-dem",
23+
"tiles": [
24+
"local://tiles/12-759-1609.terrain.png"
25+
],
26+
"maxzoom": 11,
27+
"tileSize": 256
28+
}
29+
},
30+
"glyphs": "local://glyphs/{fontstack}/{range}.pbf",
31+
"layers": [
32+
{
33+
"id": "background",
34+
"type": "background",
35+
"paint": {
36+
"background-color": "gray"
37+
}
38+
},
39+
{
40+
"id": "hillshade-translucent",
41+
"type": "hillshade",
42+
"source": "rgbterrain",
43+
"paint": {
44+
"hillshade-exaggeration": 1
45+
}
46+
}
47+
]
48+
}
-1.23 KB
Loading
774 Bytes
Loading

0 commit comments

Comments
 (0)