maplibre-gl-js icon indicating copy to clipboard operation
maplibre-gl-js copied to clipboard

Camera can move inside 3D terrain

Open mattgrid opened this issue 3 years ago • 29 comments

This is a follow-on ticket from #1241. It's possible to move the camera to within terrain when it's extruded from the 2D map surface.

@zdila said:

May be a different issue, but in my case not only closest tiles but all. Open https://labs.maptiler.com/samples/maplibre/terrain/#style=satellite&lat=48.57344071&lng=20.46078220&zoom=17.16&bearing=-127.95&pitch=76.00&3d=true then zoom a little bit and tiles starts to disappear. If you refresh the page, nothing is shown until you zoom out. MapLibre 2.3.0.

@prozessor13 replied:

Walking with the camera into terrain is still an open issue :/

Expected behaviour

When the camera "bumps up" against 3D terrain, it should not be possible to move the camera to within the 3D surface.

Actual behaviour

When you move the camera towards 3D terrain, nothing stops you when you "hit" the surface. You can keep moving through the surface.

mattgrid avatar Aug 23 '22 17:08 mattgrid

I think i will not have time to look onto this in near future. Also I think before start implementing this, all possible scenarios should be played through. What should happen if, for example:

  • map pitch changed with mouse-interaction, and camera collide with terrain.
    • may changing pitch should stop?
    • or should camera go up?
  • map bearing changed with mouse-interaction, and camera collide with terrain.
    • I think camera should go up?
    • should it also rotate downwards do look onto the same center?
  • flyTo/easeTo collide with terrain
  • ...

prozessor13 avatar Aug 24 '22 08:08 prozessor13

My company would be happy to hire someone to fix this issue

767160 avatar Sep 17 '22 00:09 767160

@767160 thanks for sharing. You can ping me on slack and I can maybe put you in touch with people who are interested... https://osmus-slack.herokuapp.com/

wipfli avatar Sep 17 '22 10:09 wipfli

@wipfli Sorry, I do not use slack. Anyone interested, please send me an email at [email protected]

767160 avatar Sep 17 '22 14:09 767160

Assigned L bounty. Link to parent Bounty: https://github.com/maplibre/maplibre/issues/189 If you would like to work on this and you think the bounty is not enough please ping me or contact @767160

HarelM avatar Mar 04 '23 18:03 HarelM

I already have a basic working prototype for this, may in near future i find the time to create a PR.

prozessor13 avatar Mar 04 '23 18:03 prozessor13

I'd be interested in working on this if you're open to passing the baton @prozessor13.

SnailBones avatar Jun 13 '23 21:06 SnailBones

I've increased the bounty as I think L bounty isn't enough.

HarelM avatar Jun 14 '23 05:06 HarelM

This is the basic working code, based on old versions of transform.ts and painter.ts, but i think the patch should work in the current classes as well.

Regards. Max.

diff --git a/src/geo/transform.ts b/src/geo/transform.ts
index 466fafaf7..bedc18210 100644
--- a/src/geo/transform.ts
+++ b/src/geo/transform.ts
@@ -520,6 +520,14 @@ class Transform {
         return {lngLat, altitude: altitude + this.elevation};
     }
 
+    // check that camera is always over terrain
+    checkTerrainCollision(terrain: Terrain) {
+        const camera = this.getCameraPosition();
+        const altitude = this.getElevation(camera.lngLat, terrain) + 100;
+        if (camera.altitude < altitude)
+            this.pitch = Math.acos((altitude - this._elevation) * this._pixelPerMeter / this.cameraToCenterDistance) / Math.PI * 180;
+    }
+
     /**
      * This method works in combination with freezeElevation activated.
      * freezeElevtion is enabled during map-panning because during this the camera should sit in constant height.
diff --git a/src/render/painter.ts b/src/render/painter.ts
index a707f3b21..61cd282b6 100644
--- a/src/render/painter.ts
+++ b/src/render/painter.ts
@@ -415,10 +415,16 @@ class Painter {
             // this is disabled, because render-to-texture is rendering all layers from bottom to top.
             this.opaquePassCutoff = 0;
 
+            // check if projMatrix has changed
+            if (!mat4.equals(this.terrainFacilitator.matrix, this.transform.projMatrix)) {
+               this.terrainFacilitator.dirty = true;
+               mat4.copy(this.terrainFacilitator.matrix, this.transform.projMatrix);
+               this.transform.checkTerrainCollision(this.style.map.terrain);
+            }
+
             // update coords/depth-framebuffer on camera movement, or tile reloading
             const newTiles = this.style.map.terrain.sourceCache.tilesAfterTime(this.terrainFacilitator.renderTime);
-            if (this.terrainFacilitator.dirty || !mat4.equals(this.terrainFacilitator.matrix, this.transform.projMatrix) || newTiles.length) {
-                mat4.copy(this.terrainFacilitator.matrix, this.transform.projMatrix);
+            if (this.terrainFacilitator.dirty || newTiles.length) {
                 this.terrainFacilitator.renderTime = Date.now();
                 this.terrainFacilitator.dirty = false;
                 drawDepth(this, this.style.map.terrain);

prozessor13 avatar Jun 14 '23 07:06 prozessor13

This approach works great when changing bearing!

But there are some issues:

  1. When I pan map toward a slope, map rendering stop and I got the following errors:
Uncaught Error: failed to invert matrix         transform.ts:937:22    
    _calcMatrices transform.ts:937
    set pitch transform.ts:186
    checkTerrainCollision transform.ts:527
    render painter.ts:404
    ...
  1. When camera is facing the bottom of a slope, I try to increase the pitch with Ctrl and mouse. Then the map shaking very quickly since pitch is changing by checkTerrainCollision.

typebrook avatar Jun 14 '23 07:06 typebrook

For 1) please try:

diff --git a/src/geo/transform.ts b/src/geo/transform.ts
index bedc18210..4132d5687 100644
--- a/src/geo/transform.ts
+++ b/src/geo/transform.ts
@@ -523,9 +523,11 @@ class Transform {
     // check that camera is always over terrain
     checkTerrainCollision(terrain: Terrain) {
         const camera = this.getCameraPosition();
-        const altitude = this.getElevation(camera.lngLat, terrain) + 100;
-        if (camera.altitude < altitude)
-            this.pitch = Math.acos((altitude - this._elevation) * this._pixelPerMeter / this.cameraToCenterDistance) / Math.PI * 180;
+        const altitude = this.getElevation(camera.lngLat, terrain) + 10;
+        if (camera.altitude < altitude) {
+            const pitch = Math.acos((altitude - this._elevation) * this._pixelPerMeter / this.cameraToCenterDistance) / Math.PI * 180;
+            if (!isNaN(pitch)) this.pitch = pitch;
+        }
     }
 
     /**

prozessor13 avatar Jun 14 '23 08:06 prozessor13

Yes, this make sense.

Would you like to change constant 100 by zoom level when assign altitude?

typebrook avatar Jun 14 '23 08:06 typebrook

Yes, may 100 is to much! Feel free to make a proposal/change. This is only a quick and not very well tested solution! Next, i will not create a PR and tests and all that stuff, may @SnailBones or you is willed?

prozessor13 avatar Jun 14 '23 09:06 prozessor13

may @SnailBones or you is willed?

Sure, I am willing to work on this. Since I am new to frontend. This issue seems the perfect one to help me realize map rendering workflow.

But @SnailBones has already leave the comment before,let @HarelM decide the assignee.

@prozessor13 BTW, is it possible to check terrain collision in Transform._constrain()?

I notice this naming may related to constrain camera when terrain collision happens. But it seems Terrain is necessary to decide camera altitude.

typebrook avatar Jun 14 '23 11:06 typebrook

may @SnailBones or you is willed?

Sure, I am willing to work on this. Since I am new to frontend. This issue seems the perfect one to help me realize map rendering workflow.

But @SnailBones has already leave the comment before,let @HarelM decide the assignee.

@prozessor13 BTW, is it possible to check terrain collision in Transform._constrain()?

I notice this naming may related to constrain camera when terrain collision happens. But it seems Terrain is necessary to decide camera altitude.

Yes, this is the dilemma, that the Transform instance does nothing know about terrain. To be honest, my implementation was really a quick one, i did not go into detail. So it is really only a proposal! Feel free to change the code!

prozessor13 avatar Jun 14 '23 11:06 prozessor13

Well @SnailBones was the first one to request, so it's up to him to decide if he would like to push this forward or leave it to @typebrook.

HarelM avatar Jun 14 '23 11:06 HarelM

Well @SnailBones was the first one to request, so it's up to him to decide if he would like to push this forward or leave it to @typebrook.

All yours @typebrook! Feel free to tag me for review.

SnailBones avatar Jun 14 '23 16:06 SnailBones

Thank you @SnailBones, I'll make a PR with tests in these days.

typebrook avatar Jun 15 '23 03:06 typebrook

@SnailBones @prozessor13 @HarelM

Sorry for the late reply. A new draft PR is made.

Since I am still not confident about realizing map rendering flow, if you guys find my approach is too naive, feel free to correct me or change the assignee.

typebrook avatar Jun 29 '23 08:06 typebrook

@HarelM could you please assign this to me?

chrneumann avatar May 29 '24 09:05 chrneumann

Sure! Have you registered with the developer pool?

HarelM avatar May 29 '24 09:05 HarelM

Yes, I've talked to Oliver. Thanks!

chrneumann avatar May 29 '24 09:05 chrneumann