maplibre-gl-js
maplibre-gl-js copied to clipboard
bad position of 3D model when 3D terrain is enabled
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
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: '© 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>
My company would be happy to hire someone to fix this issue
Maybe the problem is just the altitude
https://user-images.githubusercontent.com/113735922/190833004-906a08e8-c0d9-4440-b5a6-c5560fa84b5d.mp4
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 :-)
Probably related or duplicate of #1294
It's still an issue after the latest changes.
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.
I've assigned a M bounty. Link to parent Bounty: https://github.com/maplibre/maplibre/issues/189
Hi @HarelM, I am Cuong (call me Stefan) at devs pool. Can I assign this issue?
Sure! please keep me posted on your progress.
Sure, thank you
@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
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
@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 Thank you very much, this is the expense link: https://opencollective.com/maplibre/expenses/128542