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

bad position of 3D model when 3D terrain is enabled

Open 767160 opened this issue 2 years ago • 6 comments

The position of the 3D model and the map are not synchronized. The problem appears both in Firefox and Chrome

It is illustrated in the videos below:

https://user-images.githubusercontent.com/113735922/190832056-dba49775-19fd-47cc-a456-b0e3592514c3.mp4

https://user-images.githubusercontent.com/113735922/190832081-a2a15c1e-1d32-447e-a62f-3493f516f18d.mp4

767160 avatar Sep 17 '22 00:09 767160

Here is the code I use. This is a merge of documentation's examples of 3d terrain and THREE objet


<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>3D Terrain</title>
		<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
		<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
		<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet">
		<style>
	body { margin: 0; padding: 0; }
	#map { position: absolute; top: 0; bottom: 0; width: 100%; }

		</style>
	</head>
	<body>
		<script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
		<script src="https://unpkg.com/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
		<div id="map"></div>
		<script>
    var map = (window.map = new maplibregl.Map({
        container: 'map',
        zoom: 18,
        center: [11.39085, 47.27574],
        pitch: 52,
        hash: true,
        style: {
            version: 8,
            sources: {
                osm: {
                    type: 'raster',
                    tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
                    tileSize: 256,
                    attribution: '&copy; OpenStreetMap Contributors',
                    maxzoom: 19
                },
                terrainSource: {
                    type: 'raster-dem',
                    url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
                    tileSize: 256
                },
                hillshadeSource: {
                    type: 'raster-dem',
                    url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
                    tileSize: 256
                }
            },
            layers: [
                {
                    id: 'osm',
                    type: 'raster',
                    source: 'osm'
                },
                {
                    id: 'hills',
                    type: 'hillshade',
                    source: 'hillshadeSource',
                    layout: { visibility: 'visible' },
                    paint: { 'hillshade-shadow-color': '#473B24' }
                }
            ],
            terrain: {
                source: 'terrainSource',
                exaggeration: 1
            }
        },
        maxZoom: 18,
        maxPitch: 85,
        antialias: true
    }));

    map.addControl(
        new maplibregl.NavigationControl({
            visualizePitch: true,
            showZoom: true,
            showCompass: true
        })
    );

    map.addControl(
        new maplibregl.TerrainControl({
            source: 'terrainSource',
            exaggeration: 1
        })
    );
    
    
    // parameters to ensure the model is georeferenced correctly on the map
var modelOrigin = [11.39085, 47.27574];
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, 0, 0];
 
var modelAsMercatorCoordinate = maplibregl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
 
// transformation parameters to position, rotate and scale the 3D model onto the map
var modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
/* Since our 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale:  modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
 
var THREE = window.THREE;
 
// configuration of the custom layer for a 3D model per the CustomLayerInterface
var customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
 
// create two three.js lights to illuminate the model
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
this.scene.add(directionalLight);
 
var directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
this.scene.add(directionalLight2);
 
// use the three.js GLTF loader to add the 3D model to the three.js scene
var loader = new THREE.GLTFLoader();
loader.load(
'https://maplibre.org/maplibre-gl-js-docs/assets/34M_17/34M_17.gltf',
function (gltf) {
this.scene.add(gltf.scene);
}.bind(this)
);
this.map = map;
 
// use the MapLibre GL JS map canvas for three.js
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
 
this.renderer.autoClear = false;
},
render: function (gl, matrix) {
var rotationX = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(1, 0, 0),
modelTransform.rotateX
);
var rotationY = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 1, 0),
modelTransform.rotateY
);
var rotationZ = new THREE.Matrix4().makeRotationAxis(
new THREE.Vector3(0, 0, 1),
modelTransform.rotateZ
);
 
var m = new THREE.Matrix4().fromArray(matrix);
var l = new THREE.Matrix4()
.makeTranslation(
modelTransform.translateX,
modelTransform.translateY,
modelTransform.translateZ
)
.scale(
new THREE.Vector3(
modelTransform.scale,
-modelTransform.scale,
modelTransform.scale
)
)
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
 
this.camera.projectionMatrix = m.multiply(l);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
 
map.on('style.load', function () {
map.addLayer(customLayer);
});

		</script>
	</body>
</html>

767160 avatar Sep 17 '22 00:09 767160

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

767160 avatar Sep 17 '22 00:09 767160

Maybe the problem is just the altitude

https://user-images.githubusercontent.com/113735922/190833004-906a08e8-c0d9-4440-b5a6-c5560fa84b5d.mp4

767160 avatar Sep 17 '22 00:09 767160

We've just merged a removal of the elevationOffset, it might help (although not high chances). Can you try this example with the latest code base? If you can find someone who can look into it that it can be great, 3D and buildings etc is not an easy problem :-)

HarelM avatar Sep 17 '22 03:09 HarelM

Probably related or duplicate of #1294

HarelM avatar Sep 17 '22 03:09 HarelM

It's still an issue after the latest changes.

birkskyum avatar Sep 18 '22 14:09 birkskyum

I think on every redraw call you have to grab the elevation with:

map.transform.getElevation(lngLat, map.terrain)

a refetch is necessary because in different zoomlevels with different terrain-tiles the elvation-values are not the same. Then there exists the

map.transform.elevation

variable with the center-altitdue. In maplibre the proj-matrix is translated by this value in negative z-direction. So this has to somehow merged into the altitude of the 3d-model. May

modelAltitude = map.transform.getElevation(modelOrigin, map.terrain) - map.transform.elevation;

do the trick, but did not tested this, just a guess.

Note: The jump on the moveend event is, because the center-altitude is only updated at moveend. This is to let the camera in the same height during dragging. Then, on moveend, the zoom-level is recalculated, to let the camera on its position.

prozessor13 avatar Oct 15 '22 16:10 prozessor13

I've assigned a M bounty. Link to parent Bounty: https://github.com/maplibre/maplibre/issues/189

HarelM avatar Mar 03 '23 20:03 HarelM

Hi @HarelM, I am Cuong (call me Stefan) at devs pool. Can I assign this issue?

manhcuongincusar1 avatar Mar 04 '23 03:03 manhcuongincusar1

Sure! please keep me posted on your progress.

HarelM avatar Mar 04 '23 07:03 HarelM

Sure, thank you

manhcuongincusar1 avatar Mar 04 '23 07:03 manhcuongincusar1

@HarelM I added queryTerrainElevation into camera.ts. This method allows user to get elevation of a point at specific transform's elevation. Can you check this pull request, thanks

Specical thank for your support @hami9x @prozessor13

manhcuongincusar1 avatar Mar 13 '23 03:03 manhcuongincusar1

This is demo code that shows 3d model working with queryTerrainElevation (included in Stefan's pull request) https://github.com/maplibre/maplibre-gl-js/blob/7bab10e8dfd1680471126b02b4849423a5f50365/test/debug-pages/terrain-3d-object.html

hami9x avatar Mar 13 '23 03:03 hami9x

@manhcuongincusar1 please submit an expense report in open collective in order to get the bounty. Also please link the report here so that we'll know it was you who filed it. THANKS!

HarelM avatar Mar 16 '23 08:03 HarelM

@HarelM Thank you very much, this is the expense link: https://opencollective.com/maplibre/expenses/128542

manhcuongincusar1 avatar Mar 16 '23 08:03 manhcuongincusar1