Just like you said, a naive solution would be to get the height of the terrain at the screen's center and the height of your model and subtract them. This could be achieved like so:
// edited version of https://maplibre.org/maplibre-gl-js/docs/examples/add-3d-model/
render(gl, matrix) {
const modelOrigin = new LngLat(11.53, 47.67); // just an example
const modelElevation = map.terrain ? map.terrain.getElevationForLngLatZoom(modelOrigin, map.getZoom()) : 0;
const modelOriginMercator = mlg.MercatorCoordinate.fromLngLat(modelOrigin, 0);
// calculating the difference between center-elevation and model-elevation
const terrainCenterElevation = map.transform.elevation;
const deltaMetersZ = modelElevation - terrainCenterElevation;
const mercatorPerMeter = modelOriginMercator.meterInMercatorCoordinateUnits();
const deltaMercatorZ = deltaMetersZ * mercatorPerMeter;
const modelTransform = {
translateX: modelOriginMercator.x,
translateY: modelOriginMercator.y,
translateZ: modelOriginMercator.z + deltaMercatorZ,
rotateX: Math.PI / 2,
rotateY: 0,
rotateZ: 0,
scale: modelOriginMercator.meterInMercatorCoordinateUnits()
};
const rotationX = new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), modelTransform.rotateX);
const rotationY = new Matrix4().makeRotationAxis(new Vector3(1, 0, 0), modelTransform.rotateY);
const rotationZ = new Matrix4().makeRotationAxis(new Vector3(0, 0, 1), modelTransform.rotateZ);
const m = new Matrix4().fromArray(matrix);
const l = new Matrix4()
.makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
.scale(new Vector3(modelTransform.scale, modelTransform.scale, modelTransform.scale))
.multiply(rotationX).multiply(rotationY).multiply(rotationZ);
const cameraProjectionMatrix = m.multiply(l);
// @ts-ignore
this.camera.projectionMatrix = cameraProjectionMatrix;
// @ts-ignore
this.renderer.resetState();
// @ts-ignore
this.renderer.render(this.scene, this.camera);
map.triggerRepaint();
}
- maplibre seems to always position the threejs-scene exactly at the height of the screen's center ... so when getting the model's height with
mlg.MercatorCoordinate.fromLngLat(modelOrigin, 0)
, we don't need to provide the actual model elevation and just pass 0
deltaMercatorZ
is being added to the model's z-translation
Just a few notes on maplibre's nomenclature in transform.ts:
elevation
[meters]: height of terrain under center
center
[lng, lat]: position of center of screen in world
centerPoint
[pixel]: position of center of screen on screen
altitude
[m]: height of camera relative to sea-level
I think there might be a more elegant solution, but this might do the job for now!