@tomtom-org/maps-sdk-plugin-landmarks-3d 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY.txt +1 -1
- package/dist/THIRD_PARTY.txt +1 -1
- package/dist/bundledTranscoder-Dn27cuYm.js +2 -0
- package/dist/bundledTranscoder-Dn27cuYm.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/third-party.json +1 -1
- package/package.json +7 -7
package/dist/index.d.ts
CHANGED
|
@@ -114,9 +114,9 @@ interface ModelsSourceSpecification {
|
|
|
114
114
|
maxzoom?: number;
|
|
115
115
|
attribution?: string;
|
|
116
116
|
/**
|
|
117
|
-
* Base path of the Basis Universal transcoder
|
|
118
|
-
*
|
|
119
|
-
*
|
|
117
|
+
* Base path of the Basis Universal transcoder (`basis_transcoder.js` and
|
|
118
|
+
* `basis_transcoder.wasm`) used to decode KTX2 textures. Defaults to the copy
|
|
119
|
+
* bundled with the plugin; set it to serve your own copy instead.
|
|
120
120
|
*/
|
|
121
121
|
transcoderPath?: string;
|
|
122
122
|
/**
|
|
@@ -158,6 +158,7 @@ export declare class ModelsSource {
|
|
|
158
158
|
/** Optional consumer callback fired once per successful tile load. */
|
|
159
159
|
onTileLoaded?: (scene: Group, key: string) => void;
|
|
160
160
|
private readonly ktx2Loader;
|
|
161
|
+
private transcoderReady?;
|
|
161
162
|
private readonly gltfLoader;
|
|
162
163
|
private readonly specification;
|
|
163
164
|
private readonly withCredentials;
|
package/dist/index.es.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{TomTomConfig as e,isProxyCredentialsMode as i}from"@tomtom-org/maps-sdk/core";import{LayerFilterComposer as t,mapStyleLayerIDs as a}from"@tomtom-org/maps-sdk/map";import{AmbientLight as s,BufferAttribute as r,Camera as n,Color as o,CustomBlending as l,DirectionalLight as h,DoubleSide as m,FrontSide as d,Group as c,LoaderUtils as u,Matrix4 as p,OneFactor as f,OneMinusSrcAlphaFactor as y,REVISION as g,SRGBColorSpace as v,Scene as x,ShaderMaterial as M,Vector3 as L,WebGLRenderer as b}from"three";import{MeshoptDecoder as w}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{GLTFLoader as C}from"three/examples/jsm/loaders/GLTFLoader.js";import{KTX2Loader as k}from"three/examples/jsm/loaders/KTX2Loader.js";import{toCreasedNormals as P}from"three/examples/jsm/utils/BufferGeometryUtils.js";import{MercatorCoordinate as D}from"maplibre-gl";var z={diffuseColor:"hsl(38, 6%, 90%)",opacity:.7,verticalGradient:!0},S={diffuseColor:"hsl(210, 9%, 17%)",opacity:.7,verticalGradient:!0},B=()=>{const{commonBaseURL:i,apiKey:t}=e.instance.get();return`${i}/maps/orbis/display/3d/landmarks/tile/{z}/{x}/{y}?${t?`key=${t}&`:""}apiVersion=2`},O="fillExtrusionGradient",T=`\n attribute float ${O};\n varying vec3 vWorldNormal;\n varying float vGradient;\n #ifdef USE_ALPHA_MASK\n varying vec2 vUv;\n #endif\n\n void main() {\n vWorldNormal = normalize(mat3(modelMatrix) * normal);\n vGradient = ${O};\n #ifdef USE_ALPHA_MASK\n vUv = uv;\n #endif\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n`,E="\n #ifdef USE_ALPHA_MASK\n uniform sampler2D alphaMask;\n uniform float alphaMaskCutoff;\n varying vec2 vUv;\n #endif\n",I="\n #ifdef USE_ALPHA_MASK\n if (texture2D(alphaMask, vUv).a < alphaMaskCutoff) {\n discard;\n }\n #endif\n",A=`\n uniform vec3 diffuseColor;\n uniform vec3 lightPosition;\n uniform float lightIntensity;\n uniform vec3 lightColor;\n uniform float layerOpacity;\n uniform float verticalGradient;\n varying vec3 vWorldNormal;\n varying float vGradient;\n ${E}\n\n void main() {\n ${I}\n vec3 normal = normalize(vWorldNormal);\n // Double-sided (alpha-masked) surfaces show their interior through carved openings;\n // flip backface normals so those interiors shade like the facade instead of going dark.\n if (!gl_FrontFacing) {\n normal = -normal;\n }\n // Extrusions have only vertical walls and flat roofs, so a sloped landmark roof would shade\n // darker than every basemap roof around it: up-facing normals light like a flat roof, the\n // rest like a wall, blended for curved surfaces.\n vec3 flattened = vec3(normal.xy, 0.0);\n vec3 wallNormal = flattened / max(length(flattened), 1e-5);\n float upness = smoothstep(0.4, 0.8, normal.z);\n normal = normalize(mix(wallNormal, vec3(0.0, 0.0, 1.0), upness));\n float colorValue = dot(diffuseColor, vec3(0.2126, 0.7152, 0.0722));\n vec3 color = diffuseColor + vec3(0.03);\n float directional = clamp(dot(normal, lightPosition), 0.0, 1.0);\n directional = mix(1.0 - lightIntensity, max(1.0 - colorValue + lightIntensity, 1.0), directional);\n if (abs(normal.y) > 0.001) {\n directional *=\n (1.0 - verticalGradient) +\n verticalGradient * clamp(vGradient, mix(0.7, 0.98, 1.0 - lightIntensity), 1.0);\n }\n vec3 shaded = clamp(\n color * directional * lightColor,\n mix(vec3(0.0), vec3(0.3), vec3(1.0) - lightColor),\n vec3(1.0)\n );\n gl_FragColor = vec4(shaded, 1.0) * layerOpacity;\n }\n`,F=new L(.2875,-.498,.996),G=new o,_=new o,R=class extends M{constructor(e){super({uniforms:{diffuseColor:{value:new L(1,1,1)},lightPosition:{value:F.clone()},lightIntensity:{value:.5},lightColor:{value:new L(1,1,1)},layerOpacity:{value:1},verticalGradient:{value:1},...e&&{alphaMask:{value:e.map},alphaMaskCutoff:{value:e.cutoff}}},...e&&{defines:{USE_ALPHA_MASK:""}},vertexShader:T,fragmentShader:A,side:e?m:d,transparent:!0,blending:l,blendSrc:f,blendDst:y})}copyShadingFrom(e){this.uniforms.diffuseColor.value.copy(e.uniforms.diffuseColor.value),this.uniforms.lightPosition.value.copy(e.uniforms.lightPosition.value),this.uniforms.lightIntensity.value=e.uniforms.lightIntensity.value,this.uniforms.lightColor.value.copy(e.uniforms.lightColor.value),this.uniforms.layerOpacity.value=e.uniforms.layerOpacity.value,this.uniforms.verticalGradient.value=e.uniforms.verticalGradient.value}setDiffuseColor(e){G.setStyle(e),G.getRGB(_,v),this.uniforms.diffuseColor.value.set(_.r,_.g,_.b)}setLayerOpacity(e){this.uniforms.layerOpacity.value=e}setVerticalGradient(e){this.uniforms.verticalGradient.value=e?1:0}setLight(e,i,t,a){const s=Math.cos(i),r=Math.sin(i);this.uniforms.lightPosition.value.set(e.x*s-e.y*r,e.x*r+e.y*s,e.z),this.uniforms.lightIntensity.value=t,this.uniforms.lightColor.value.set(a.r,a.g,a.b)}},Z=class extends M{constructor(e){super({...e&&{uniforms:{alphaMask:{value:e.map},alphaMaskCutoff:{value:e.cutoff}},defines:{USE_ALPHA_MASK:""}},vertexShader:T,fragmentShader:`\n ${E}\n void main() {\n ${I}\n gl_FragColor = vec4(0.0);\n }\n `,side:e?m:d,colorWrite:!1})}},U=e=>!0===e.isMesh,V=1/(2*Math.PI*6378137),$=Math.PI/6,j=`https://unpkg.com/three@0.${g}.x/examples/jsm/libs/basis/`,W=class{type="models";id;minzoom;maxzoom;map;tiles;scene;loadedTiles=/* @__PURE__ */new Set;sceneOrigin=null;onTileLoaded;ktx2Loader;gltfLoader;specification;withCredentials;constructor(e,i){this.id=e,this.minzoom=i.minzoom??0,this.maxzoom=i.maxzoom??22,this.tiles=i.tiles,this.specification=i,this.withCredentials=i.withCredentials??!1,this.scene=new c,this.ktx2Loader=(new k).setCrossOrigin("anonymous").setTranscoderPath(i.transcoderPath??j),this.gltfLoader=(new C).setKTX2Loader(this.ktx2Loader).setMeshoptDecoder(w)}onAdd(e,i){this.map=e,this.ktx2Loader.detectSupport(i)}updateTiles(){if(0===this.tiles.length)return;const e=/* @__PURE__ */new Set;for(const i of this.coveringTiles())e.add(i.key),this.loadTile(i);this.showCoveringTilesOnly(e)}showCoveringTilesOnly(e){for(const i of this.scene.children)i.visible=e.has(i.userData.key)}coveringTiles(){const e={tileSize:this.map._camera.transform.tileSize,minzoom:this.minzoom,maxzoom:this.maxzoom,terrain:this.map.terrain};return this.map.coveringTiles(e)}loadTile(e){if(this.loadedTiles.has(e.key))return;this.loadedTiles.add(e.key);const i=e.canonical.url(this.tiles,this.map.getPixelRatio(),this.specification.scheme);this.fetchTile(i).then(i=>{i&&(K(i),this.rebaseToSceneOrigin(i),i.userData={isTile:!0,key:e.key},this.scene.add(i),this.onTileLoaded?.(i,e.key))}).catch(i=>{console.warn(`Problem with loading ${e.toString()}: ${i?.message??"unknown"}`)})}rebaseToSceneOrigin(e){const i=[];if(e.traverse(e=>{U(e)&&i.push(e)}),0===i.length)return;const t=i[0].position,a={x:Math.round(t.x),y:Math.round(t.y)};if(this.sceneOrigin){if(Math.hypot(t.x-this.sceneOrigin.x,t.y-this.sceneOrigin.y)>1e5){const e=a.x-this.sceneOrigin.x,i=a.y-this.sceneOrigin.y;this.scene.traverse(t=>{U(t)&&(t.position.x-=e,t.position.y-=i)}),this.sceneOrigin=a}}else this.sceneOrigin=a;for(const s of i)s.position.x-=this.sceneOrigin.x,s.position.y-=this.sceneOrigin.y}async fetchTile(e){const i=await fetch(e,this.withCredentials?{credentials:"include"}:void 0);if(204===i.status||404===i.status)return null;if(!i.ok)throw new Error(`tile request failed with ${i.status}`);const t=await i.arrayBuffer(),{scene:a}=await this.gltfLoader.parseAsync(t,u.extractUrlBase(e));return a}},K=e=>{e.traverse(e=>{if(!U(e))return;const i=e.material;i.alphaTest=.1,i.side=m,e.userData.originalMaterial=i,e.geometry=P(e.geometry,$),H(e)})},H=e=>{const i=e.geometry,t=i.getAttribute("position");i.computeBoundingBox();const a=i.boundingBox?.max.z??0,s=a>0?Math.sqrt(a/150)/a:0,n=new Float32Array(t.count);for(let r=0;r<t.count;r++)n[r]=t.getZ(r)*s;i.setAttribute(O,new r(n,1))},N=e=>e*Math.PI/180,q=class{id;type="custom";renderingMode="3d";renderer;scene;camera;tiles;directionalLight;ambientLight;map;source;minzoom=0;maxzoom=22;visible=!0;diffuseColor="#ffffff";fillExtrusionMaterial=new R;depthPrepassMaterial=new Z;maskedFillExtrusionMaterials=/* @__PURE__ */new Map;constructor(e,i){this.id=e.id,this.source=new W(this.id,i),this.tiles=this.source.scene,this.updateProperties(e)}updateProperties(e){this.visible="none"!==e.layout?.visibility,this.diffuseColor=e.paint?.["models-base-color"]??"#ffffff",this.minzoom=e.minzoom??0,this.maxzoom=e.maxzoom??22}setDirectionalLight(e,i,t){this.directionalLight.position.setFromSphericalCoords(e,N(i),N(t))}onAdd(e,i){this.map=e,this.scene=new x,this.tiles=this.source.scene,this.camera=new n,this.directionalLight=new h(16777215,.5),this.setDirectionalLight(1.15,90,0),this.scene.add(this.directionalLight),this.ambientLight=new s(16777215,.1),this.scene.add(this.ambientLight),this.scene.add(this.tiles),this.renderer=new b({canvas:e.getCanvas(),context:i,antialias:!0}),this.renderer.autoClear=!1,this.source.onAdd(e,this.renderer)}render(e,i){const t=this.map._camera.transform.tileZoom,a=t>=this.minzoom&&t<=this.maxzoom;this.tiles.visible=this.visible&&a,this.visible&&a&&(this.syncLighting(),this.source.updateTiles(),this.groundLandmarksOnTerrain(),this.alignCameraToMap(i),this.renderer.resetState(),this.needsTranslucentPrepass()&&this.renderDepthPrepass(),this.applyMaterials(),this.renderer.render(this.scene,this.camera),this.map.triggerRepaint())}groundLandmarksOnTerrain(){for(const e of this.tiles.children)e.visible&&e.traverse(e=>{U(e)&&this.groundMeshOnTerrain(e)})}groundMeshOnTerrain(e){const i=this.terrainSamplePoints(e);if(!i)return;let t=null;for(const a of i){const i=this.map.queryTerrainElevation(a);if(null===i)return void(e.position.z=0);(null===t||i<t)&&(t=i)}null!==t&&(e.position.z=t-.5)}terrainSamplePoints(e){let i=e.userData.terrainSamplePoints;if(!i){e.geometry.boundingBox||e.geometry.computeBoundingBox();const t=e.geometry.boundingBox;if(!t||t.isEmpty())return null;const a=e.position.x+this.sceneOrigin.x,s=e.position.y+this.sceneOrigin.y,r=(t.min.x+t.max.x)/2,n=(t.min.y+t.max.y)/2;i=[[r,n],[t.min.x,t.min.y],[t.min.x,t.max.y],[t.max.x,t.min.y],[t.max.x,t.max.y],[r,t.min.y],[r,t.max.y],[t.min.x,n],[t.max.x,n]].map(([e,i])=>new D(.5+(a+e)*V,.5-(s+i)*V).toLngLat().toArray()),e.userData.terrainSamplePoints=i}return i}alignCameraToMap(e){const i=V/Math.cos(N(this.map.getCenter().lat)),t=.5+V*this.sceneOrigin.x,a=.5-V*this.sceneOrigin.y,s=new p;s.set(V,0,0,t,0,-V,0,a,0,0,i,0,0,0,0,1);const r=(new p).fromArray(e.defaultProjectionData.mainMatrix);this.camera.projectionMatrix.copy(r).multiply(s)}get sceneOrigin(){return this.source.sceneOrigin??{x:0,y:0}}needsTranslucentPrepass(){return this.fillExtrusionMaterial.uniforms.layerOpacity.value<1}renderDepthPrepass(){this.tiles.traverse(e=>{U(e)&&(e.material=this.maskedFillExtrusionVariant(e)?.depth??this.depthPrepassMaterial)}),this.renderer.render(this.scene,this.camera)}setOpacity(e){this.forEachFillExtrusionMaterial(i=>i.setLayerOpacity(e))}setVerticalGradient(e){this.forEachFillExtrusionMaterial(i=>i.setVerticalGradient(e))}getMaterial(e){return this.maskedFillExtrusionVariant(e)?.color??this.fillExtrusionMaterial}maskedFillExtrusionVariant(e){const i=e.userData.originalMaterial,t=i?.map;if(!t)return null;let a=this.maskedFillExtrusionMaterials.get(t);if(!a){const e={map:t,cutoff:i.alphaTest||.1};a={color:new R(e),depth:new Z(e)},a.color.copyShadingFrom(this.fillExtrusionMaterial),this.maskedFillExtrusionMaterials.set(t,a)}return a}forEachFillExtrusionMaterial(e){e(this.fillExtrusionMaterial),this.maskedFillExtrusionMaterials.forEach(i=>e(i.color))}syncLighting(){const e=this.map,i=e.style?.light,t=i?.properties,a=i?.getCartesianPosition?.();if(t&&a)try{const i=t.get("intensity");this.directionalLight.intensity=i*Math.PI,this.ambientLight.intensity=(1-i)*Math.PI;const s={x:-a[0],y:a[1],z:a[2]};this.directionalLight.position.set(s.x,s.y,s.z);const r="viewport"===t.get("anchor")?-(e._camera.transform.bearingInRadians??0):0,n=t.get("color");this.forEachFillExtrusionMaterial(e=>e.setLight(s,r,i,n))}catch{}}applyMaterials(){this.forEachFillExtrusionMaterial(e=>e.setDiffuseColor(this.diffuseColor)),this.tiles.traverse(e=>{U(e)&&(e.material=this.getMaterial(e))})}},X=["!",["coalesce",["get","has_landmark"],!1]],J=class{map;layerID="orbis-3d-landmarks";basemapBuildingLayerID="3D - Building";modelsLayer;layerMinZoom;layerMaxZoom;displayMode;constructor(e,t={}){this.map=e,this.layerMinZoom=t.minZoom,this.layerMaxZoom=t.maxZoom,this.displayMode=t.displayMode??"inherited";const a={id:this.layerID,type:"models",source:this.layerID,minzoom:t.minZoom??10,maxzoom:t.maxZoom??22},s={type:"models",tiles:[B()],minzoom:15,maxzoom:15,withCredentials:t.withCredentials??i()};this.modelsLayer=new q(a,s),this.modelsLayer.visible=t.visible??!0,this.map.addStyleChangeHandler({onStyleChanged:()=>this.install()}),this.installWhenReady()}installWhenReady(){this.map.mapReady?this.install():this.map.mapLibreMap.once("styledata",()=>this.installWhenReady())}get layer(){return this.modelsLayer}getDisplayMode(){return this.displayMode}async setDisplayMode(e){this.displayMode=e,this.map.mapReady&&this.applyDisplayMode()}isVisible(){return this.modelsLayer.visible}async setVisible(e){this.modelsLayer.visible=e,this.map.mapReady&&this.map.mapLibreMap.triggerRepaint()}install(){const e=this.map.mapLibreMap;e.getLayer(this.layerID)||e.addLayer(this.modelsLayer,this.landmarkInsertBeforeID()),e.getLayer(this.basemapBuildingLayerID)&&e.setLayoutProperty(this.basemapBuildingLayerID,"visibility","visible"),this.alignZoomRangeWithBasemapBuildings(),this.excludeLandmarkBuildingsFromBasemap(),this.modelsLayer.source.onTileLoaded=()=>{this.resolveCurrentMaterialState().opacity<1&&this.applyDisplayMode()},this.applyDisplayMode()}landmarkInsertBeforeID(){const e=a.poi;return this.map.mapLibreMap.getLayer(e)?e:void 0}alignZoomRangeWithBasemapBuildings(){const e=this.map.mapLibreMap.getLayer(this.basemapBuildingLayerID);e&&(this.modelsLayer.minzoom=this.layerMinZoom??e.minzoom??10,this.modelsLayer.maxzoom=this.layerMaxZoom??e.maxzoom??22)}excludeLandmarkBuildingsFromBasemap(){t.for(this.map).setClause(this.basemapBuildingLayerID,"landmarks3d.buildings",X)}readBasemapBuildingMaterial(){const e=this.map.mapLibreMap;if(!e.getLayer(this.basemapBuildingLayerID))return null;const i=(e=>{if("string"==typeof e)return e;if(Array.isArray(e)&&"match"===e[0]){const i=e.at(-1);if("string"==typeof i)return i}return null})(e.getPaintProperty(this.basemapBuildingLayerID,"fill-extrusion-color"));if(!i)return null;const t=e.getPaintProperty(this.basemapBuildingLayerID,"fill-extrusion-opacity"),a=e.getPaintProperty(this.basemapBuildingLayerID,"fill-extrusion-vertical-gradient");return{diffuseColor:i,..."number"==typeof t&&{opacity:t},..."boolean"==typeof a&&{verticalGradient:a}}}resolveCurrentMaterialState(){const e="inherited"===this.displayMode?this.readBasemapBuildingMaterial():null;return((e,i)=>{switch(e){case"dark":return S;case"light":return z;default:return{...z,...i}}})(this.displayMode,e)}applyDisplayMode(){const e=this.resolveCurrentMaterialState();this.modelsLayer.diffuseColor=e.diffuseColor,this.modelsLayer.setOpacity(e.opacity),this.modelsLayer.setVerticalGradient(e.verticalGradient),this.map.mapLibreMap.triggerRepaint()}};export{J as Landmarks3D,q as ModelsLayer,W as ModelsSource,B as buildLandmarksTileURL};
|
|
1
|
+
import{TomTomConfig as e,isProxyCredentialsMode as i}from"@tomtom-org/maps-sdk/core";import{LayerFilterComposer as t,mapStyleLayerIDs as a}from"@tomtom-org/maps-sdk/map";import{AmbientLight as s,BufferAttribute as r,Camera as n,Color as o,CustomBlending as l,DirectionalLight as h,DoubleSide as m,FrontSide as d,Group as c,LoaderUtils as u,LoadingManager as p,Matrix4 as f,OneFactor as y,OneMinusSrcAlphaFactor as g,SRGBColorSpace as v,Scene as x,ShaderMaterial as M,Vector3 as L,WebGLRenderer as b}from"three";import{MeshoptDecoder as w}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{GLTFLoader as C}from"three/examples/jsm/loaders/GLTFLoader.js";import{KTX2Loader as k}from"three/examples/jsm/loaders/KTX2Loader.js";import{toCreasedNormals as P}from"three/examples/jsm/utils/BufferGeometryUtils.js";import{MercatorCoordinate as D}from"maplibre-gl";var z={diffuseColor:"hsl(38, 6%, 90%)",opacity:.7,verticalGradient:!0},S={diffuseColor:"hsl(210, 9%, 17%)",opacity:.7,verticalGradient:!0},B=()=>{const{commonBaseURL:i,apiKey:t}=e.instance.get();return`${i}/maps/orbis/display/3d/landmarks/tile/{z}/{x}/{y}?${t?`key=${t}&`:""}apiVersion=2`},T="fillExtrusionGradient",O=`\n attribute float ${T};\n varying vec3 vWorldNormal;\n varying float vGradient;\n #ifdef USE_ALPHA_MASK\n varying vec2 vUv;\n #endif\n\n void main() {\n vWorldNormal = normalize(mat3(modelMatrix) * normal);\n vGradient = ${T};\n #ifdef USE_ALPHA_MASK\n vUv = uv;\n #endif\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n`,E="\n #ifdef USE_ALPHA_MASK\n uniform sampler2D alphaMask;\n uniform float alphaMaskCutoff;\n varying vec2 vUv;\n #endif\n",I="\n #ifdef USE_ALPHA_MASK\n if (texture2D(alphaMask, vUv).a < alphaMaskCutoff) {\n discard;\n }\n #endif\n",A=`\n uniform vec3 diffuseColor;\n uniform vec3 lightPosition;\n uniform float lightIntensity;\n uniform vec3 lightColor;\n uniform float layerOpacity;\n uniform float verticalGradient;\n varying vec3 vWorldNormal;\n varying float vGradient;\n ${E}\n\n void main() {\n ${I}\n vec3 normal = normalize(vWorldNormal);\n // Double-sided (alpha-masked) surfaces show their interior through carved openings;\n // flip backface normals so those interiors shade like the facade instead of going dark.\n if (!gl_FrontFacing) {\n normal = -normal;\n }\n // Extrusions have only vertical walls and flat roofs, so a sloped landmark roof would shade\n // darker than every basemap roof around it: up-facing normals light like a flat roof, the\n // rest like a wall, blended for curved surfaces.\n vec3 flattened = vec3(normal.xy, 0.0);\n vec3 wallNormal = flattened / max(length(flattened), 1e-5);\n float upness = smoothstep(0.4, 0.8, normal.z);\n normal = normalize(mix(wallNormal, vec3(0.0, 0.0, 1.0), upness));\n float colorValue = dot(diffuseColor, vec3(0.2126, 0.7152, 0.0722));\n vec3 color = diffuseColor + vec3(0.03);\n float directional = clamp(dot(normal, lightPosition), 0.0, 1.0);\n directional = mix(1.0 - lightIntensity, max(1.0 - colorValue + lightIntensity, 1.0), directional);\n if (abs(normal.y) > 0.001) {\n directional *=\n (1.0 - verticalGradient) +\n verticalGradient * clamp(vGradient, mix(0.7, 0.98, 1.0 - lightIntensity), 1.0);\n }\n vec3 shaded = clamp(\n color * directional * lightColor,\n mix(vec3(0.0), vec3(0.3), vec3(1.0) - lightColor),\n vec3(1.0)\n );\n gl_FragColor = vec4(shaded, 1.0) * layerOpacity;\n }\n`,F=new L(.2875,-.498,.996),G=new o,_=new o,R=class extends M{constructor(e){super({uniforms:{diffuseColor:{value:new L(1,1,1)},lightPosition:{value:F.clone()},lightIntensity:{value:.5},lightColor:{value:new L(1,1,1)},layerOpacity:{value:1},verticalGradient:{value:1},...e&&{alphaMask:{value:e.map},alphaMaskCutoff:{value:e.cutoff}}},...e&&{defines:{USE_ALPHA_MASK:""}},vertexShader:O,fragmentShader:A,side:e?m:d,transparent:!0,blending:l,blendSrc:y,blendDst:g})}copyShadingFrom(e){this.uniforms.diffuseColor.value.copy(e.uniforms.diffuseColor.value),this.uniforms.lightPosition.value.copy(e.uniforms.lightPosition.value),this.uniforms.lightIntensity.value=e.uniforms.lightIntensity.value,this.uniforms.lightColor.value.copy(e.uniforms.lightColor.value),this.uniforms.layerOpacity.value=e.uniforms.layerOpacity.value,this.uniforms.verticalGradient.value=e.uniforms.verticalGradient.value}setDiffuseColor(e){G.setStyle(e),G.getRGB(_,v),this.uniforms.diffuseColor.value.set(_.r,_.g,_.b)}setLayerOpacity(e){this.uniforms.layerOpacity.value=e}setVerticalGradient(e){this.uniforms.verticalGradient.value=e?1:0}setLight(e,i,t,a){const s=Math.cos(i),r=Math.sin(i);this.uniforms.lightPosition.value.set(e.x*s-e.y*r,e.x*r+e.y*s,e.z),this.uniforms.lightIntensity.value=t,this.uniforms.lightColor.value.set(a.r,a.g,a.b)}},U=class extends M{constructor(e){super({...e&&{uniforms:{alphaMask:{value:e.map},alphaMaskCutoff:{value:e.cutoff}},defines:{USE_ALPHA_MASK:""}},vertexShader:O,fragmentShader:`\n ${E}\n void main() {\n ${I}\n gl_FragColor = vec4(0.0);\n }\n `,side:e?m:d,colorWrite:!1})}},Z=e=>!0===e.isMesh,V=1/(2*Math.PI*6378137),j=Math.PI/6,$=class{type="models";id;minzoom;maxzoom;map;tiles;scene;loadedTiles=/* @__PURE__ */new Set;sceneOrigin=null;onTileLoaded;ktx2Loader;transcoderReady;gltfLoader;specification;withCredentials;constructor(e,i){this.id=e,this.minzoom=i.minzoom??0,this.maxzoom=i.maxzoom??22,this.tiles=i.tiles,this.specification=i,this.withCredentials=i.withCredentials??!1,this.scene=new c,this.ktx2Loader=new k(new p).setCrossOrigin("anonymous").setTranscoderPath(i.transcoderPath??""),this.gltfLoader=(new C).setKTX2Loader(this.ktx2Loader).setMeshoptDecoder(w)}onAdd(e,i){this.map=e,this.ktx2Loader.detectSupport(i)}updateTiles(){if(0===this.tiles.length)return;const e=/* @__PURE__ */new Set;for(const i of this.coveringTiles())e.add(i.key),this.loadTile(i);this.showCoveringTilesOnly(e)}showCoveringTilesOnly(e){for(const i of this.scene.children)i.visible=e.has(i.userData.key)}coveringTiles(){const e={tileSize:this.map._camera.transform.tileSize,minzoom:this.minzoom,maxzoom:this.maxzoom,terrain:this.map.terrain};return this.map.coveringTiles(e)}loadTile(e){if(this.loadedTiles.has(e.key))return;this.loadedTiles.add(e.key);const i=e.canonical.url(this.tiles,this.map.getPixelRatio(),this.specification.scheme);this.fetchTile(i).then(i=>{i&&(W(i),this.rebaseToSceneOrigin(i),i.userData={isTile:!0,key:e.key},this.scene.add(i),this.onTileLoaded?.(i,e.key))}).catch(i=>{console.warn(`Problem with loading ${e.toString()}: ${i?.message??"unknown"}`)})}rebaseToSceneOrigin(e){const i=[];if(e.traverse(e=>{Z(e)&&i.push(e)}),0===i.length)return;const t=i[0].position,a={x:Math.round(t.x),y:Math.round(t.y)};if(this.sceneOrigin){if(Math.hypot(t.x-this.sceneOrigin.x,t.y-this.sceneOrigin.y)>1e5){const e=a.x-this.sceneOrigin.x,i=a.y-this.sceneOrigin.y;this.scene.traverse(t=>{Z(t)&&(t.position.x-=e,t.position.y-=i)}),this.sceneOrigin=a}}else this.sceneOrigin=a;for(const s of i)s.position.x-=this.sceneOrigin.x,s.position.y-=this.sceneOrigin.y}async fetchTile(e){const i=await fetch(e,this.withCredentials?{credentials:"include"}:void 0);if(204===i.status||404===i.status)return null;if(!i.ok)throw new Error(`tile request failed with ${i.status}`);const t=await i.arrayBuffer();this.transcoderReady??=(async(e,i)=>{if(void 0!==i)return;const{resolveBundledTranscoderUrl:t}=await import("./bundledTranscoder-Dn27cuYm.js");e.setURLModifier(t)})(this.ktx2Loader.manager,this.specification.transcoderPath),await this.transcoderReady;const{scene:a}=await this.gltfLoader.parseAsync(t,u.extractUrlBase(e));return a}},W=e=>{e.traverse(e=>{if(!Z(e))return;const i=e.material;i.alphaTest=.1,i.side=m,e.userData.originalMaterial=i,e.geometry=P(e.geometry,j),K(e)})},K=e=>{const i=e.geometry,t=i.getAttribute("position");i.computeBoundingBox();const a=i.boundingBox?.max.z??0,s=a>0?Math.sqrt(a/150)/a:0,n=new Float32Array(t.count);for(let r=0;r<t.count;r++)n[r]=t.getZ(r)*s;i.setAttribute(T,new r(n,1))},H=e=>e*Math.PI/180,N=class{id;type="custom";renderingMode="3d";renderer;scene;camera;tiles;directionalLight;ambientLight;map;source;minzoom=0;maxzoom=22;visible=!0;diffuseColor="#ffffff";fillExtrusionMaterial=new R;depthPrepassMaterial=new U;maskedFillExtrusionMaterials=/* @__PURE__ */new Map;constructor(e,i){this.id=e.id,this.source=new $(this.id,i),this.tiles=this.source.scene,this.updateProperties(e)}updateProperties(e){this.visible="none"!==e.layout?.visibility,this.diffuseColor=e.paint?.["models-base-color"]??"#ffffff",this.minzoom=e.minzoom??0,this.maxzoom=e.maxzoom??22}setDirectionalLight(e,i,t){this.directionalLight.position.setFromSphericalCoords(e,H(i),H(t))}onAdd(e,i){this.map=e,this.scene=new x,this.tiles=this.source.scene,this.camera=new n,this.directionalLight=new h(16777215,.5),this.setDirectionalLight(1.15,90,0),this.scene.add(this.directionalLight),this.ambientLight=new s(16777215,.1),this.scene.add(this.ambientLight),this.scene.add(this.tiles),this.renderer=new b({canvas:e.getCanvas(),context:i,antialias:!0}),this.renderer.autoClear=!1,this.source.onAdd(e,this.renderer)}render(e,i){const t=this.map._camera.transform.tileZoom,a=t>=this.minzoom&&t<=this.maxzoom;this.tiles.visible=this.visible&&a,this.visible&&a&&(this.syncLighting(),this.source.updateTiles(),this.groundLandmarksOnTerrain(),this.alignCameraToMap(i),this.renderer.resetState(),this.needsTranslucentPrepass()&&this.renderDepthPrepass(),this.applyMaterials(),this.renderer.render(this.scene,this.camera),this.map.triggerRepaint())}groundLandmarksOnTerrain(){for(const e of this.tiles.children)e.visible&&e.traverse(e=>{Z(e)&&this.groundMeshOnTerrain(e)})}groundMeshOnTerrain(e){const i=this.terrainSamplePoints(e);if(!i)return;let t=null;for(const a of i){const i=this.map.queryTerrainElevation(a);if(null===i)return void(e.position.z=0);(null===t||i<t)&&(t=i)}null!==t&&(e.position.z=t-.5)}terrainSamplePoints(e){let i=e.userData.terrainSamplePoints;if(!i){e.geometry.boundingBox||e.geometry.computeBoundingBox();const t=e.geometry.boundingBox;if(!t||t.isEmpty())return null;const a=e.position.x+this.sceneOrigin.x,s=e.position.y+this.sceneOrigin.y,r=(t.min.x+t.max.x)/2,n=(t.min.y+t.max.y)/2;i=[[r,n],[t.min.x,t.min.y],[t.min.x,t.max.y],[t.max.x,t.min.y],[t.max.x,t.max.y],[r,t.min.y],[r,t.max.y],[t.min.x,n],[t.max.x,n]].map(([e,i])=>new D(.5+(a+e)*V,.5-(s+i)*V).toLngLat().toArray()),e.userData.terrainSamplePoints=i}return i}alignCameraToMap(e){const i=V/Math.cos(H(this.map.getCenter().lat)),t=.5+V*this.sceneOrigin.x,a=.5-V*this.sceneOrigin.y,s=new f;s.set(V,0,0,t,0,-V,0,a,0,0,i,0,0,0,0,1);const r=(new f).fromArray(e.defaultProjectionData.mainMatrix);this.camera.projectionMatrix.copy(r).multiply(s)}get sceneOrigin(){return this.source.sceneOrigin??{x:0,y:0}}needsTranslucentPrepass(){return this.fillExtrusionMaterial.uniforms.layerOpacity.value<1}renderDepthPrepass(){this.tiles.traverse(e=>{Z(e)&&(e.material=this.maskedFillExtrusionVariant(e)?.depth??this.depthPrepassMaterial)}),this.renderer.render(this.scene,this.camera)}setOpacity(e){this.forEachFillExtrusionMaterial(i=>i.setLayerOpacity(e))}setVerticalGradient(e){this.forEachFillExtrusionMaterial(i=>i.setVerticalGradient(e))}getMaterial(e){return this.maskedFillExtrusionVariant(e)?.color??this.fillExtrusionMaterial}maskedFillExtrusionVariant(e){const i=e.userData.originalMaterial,t=i?.map;if(!t)return null;let a=this.maskedFillExtrusionMaterials.get(t);if(!a){const e={map:t,cutoff:i.alphaTest||.1};a={color:new R(e),depth:new U(e)},a.color.copyShadingFrom(this.fillExtrusionMaterial),this.maskedFillExtrusionMaterials.set(t,a)}return a}forEachFillExtrusionMaterial(e){e(this.fillExtrusionMaterial),this.maskedFillExtrusionMaterials.forEach(i=>e(i.color))}syncLighting(){const e=this.map,i=e.style?.light,t=i?.properties,a=i?.getCartesianPosition?.();if(t&&a)try{const i=t.get("intensity");this.directionalLight.intensity=i*Math.PI,this.ambientLight.intensity=(1-i)*Math.PI;const s={x:-a[0],y:a[1],z:a[2]};this.directionalLight.position.set(s.x,s.y,s.z);const r="viewport"===t.get("anchor")?-(e._camera.transform.bearingInRadians??0):0,n=t.get("color");this.forEachFillExtrusionMaterial(e=>e.setLight(s,r,i,n))}catch{}}applyMaterials(){this.forEachFillExtrusionMaterial(e=>e.setDiffuseColor(this.diffuseColor)),this.tiles.traverse(e=>{Z(e)&&(e.material=this.getMaterial(e))})}},q=["!",["coalesce",["get","has_landmark"],!1]],X=class{map;layerID="orbis-3d-landmarks";basemapBuildingLayerID="3D - Building";modelsLayer;layerMinZoom;layerMaxZoom;displayMode;constructor(e,t={}){this.map=e,this.layerMinZoom=t.minZoom,this.layerMaxZoom=t.maxZoom,this.displayMode=t.displayMode??"inherited";const a={id:this.layerID,type:"models",source:this.layerID,minzoom:t.minZoom??10,maxzoom:t.maxZoom??22},s={type:"models",tiles:[B()],minzoom:15,maxzoom:15,withCredentials:t.withCredentials??i()};this.modelsLayer=new N(a,s),this.modelsLayer.visible=t.visible??!0,this.map.addStyleChangeHandler({onStyleChanged:()=>this.install()}),this.installWhenReady()}installWhenReady(){this.map.mapReady?this.install():this.map.mapLibreMap.once("styledata",()=>this.installWhenReady())}get layer(){return this.modelsLayer}getDisplayMode(){return this.displayMode}async setDisplayMode(e){this.displayMode=e,this.map.mapReady&&this.applyDisplayMode()}isVisible(){return this.modelsLayer.visible}async setVisible(e){this.modelsLayer.visible=e,this.map.mapReady&&this.map.mapLibreMap.triggerRepaint()}install(){const e=this.map.mapLibreMap;e.getLayer(this.layerID)||e.addLayer(this.modelsLayer,this.landmarkInsertBeforeID()),e.getLayer(this.basemapBuildingLayerID)&&e.setLayoutProperty(this.basemapBuildingLayerID,"visibility","visible"),this.alignZoomRangeWithBasemapBuildings(),this.excludeLandmarkBuildingsFromBasemap(),this.modelsLayer.source.onTileLoaded=()=>{this.resolveCurrentMaterialState().opacity<1&&this.applyDisplayMode()},this.applyDisplayMode()}landmarkInsertBeforeID(){const e=a.poi;return this.map.mapLibreMap.getLayer(e)?e:void 0}alignZoomRangeWithBasemapBuildings(){const e=this.map.mapLibreMap.getLayer(this.basemapBuildingLayerID);e&&(this.modelsLayer.minzoom=this.layerMinZoom??e.minzoom??10,this.modelsLayer.maxzoom=this.layerMaxZoom??e.maxzoom??22)}excludeLandmarkBuildingsFromBasemap(){t.for(this.map).setClause(this.basemapBuildingLayerID,"landmarks3d.buildings",q)}readBasemapBuildingMaterial(){const e=this.map.mapLibreMap;if(!e.getLayer(this.basemapBuildingLayerID))return null;const i=(e=>{if("string"==typeof e)return e;if(Array.isArray(e)&&"match"===e[0]){const i=e.at(-1);if("string"==typeof i)return i}return null})(e.getPaintProperty(this.basemapBuildingLayerID,"fill-extrusion-color"));if(!i)return null;const t=e.getPaintProperty(this.basemapBuildingLayerID,"fill-extrusion-opacity"),a=e.getPaintProperty(this.basemapBuildingLayerID,"fill-extrusion-vertical-gradient");return{diffuseColor:i,..."number"==typeof t&&{opacity:t},..."boolean"==typeof a&&{verticalGradient:a}}}resolveCurrentMaterialState(){const e="inherited"===this.displayMode?this.readBasemapBuildingMaterial():null;return((e,i)=>{switch(e){case"dark":return S;case"light":return z;default:return{...z,...i}}})(this.displayMode,e)}applyDisplayMode(){const e=this.resolveCurrentMaterialState();this.modelsLayer.diffuseColor=e.diffuseColor,this.modelsLayer.setOpacity(e.opacity),this.modelsLayer.setVerticalGradient(e.verticalGradient),this.map.mapLibreMap.triggerRepaint()}};export{X as Landmarks3D,N as ModelsLayer,$ as ModelsSource,B as buildLandmarksTileURL};
|
|
2
2
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","names":[],"sources":["../src/displayMode.ts","../src/landmarksTileURL.ts","../src/FillExtrusionMaterial.ts","../src/utils.ts","../src/ModelsSource.ts","../src/ModelsLayer.ts","../src/Landmarks3D.ts"],"sourcesContent":["/**\n * How 3D landmarks are shaded relative to the base map. Every mode renders the\n * landmarks as maplibre-style fill-extrusion buildings:\n * - `inherited`: mirrors the colour, opacity and vertical gradient of the basemap 3D building layer (default).\n * - `dark`: uses the 3D building look of the SDK's standard dark style.\n * - `light`: uses the 3D building look of the SDK's standard light style.\n *\n * @group Landmarks 3D\n */\nexport type Landmarks3DDisplayMode = 'inherited' | 'dark' | 'light';\n\n/**\n * The fill-extrusion look landmarks are shaded with: a 3D building layer's colour,\n * `fill-extrusion-opacity` and `fill-extrusion-vertical-gradient`.\n *\n * @group Landmarks 3D\n */\nexport type BuildingMaterial = {\n diffuseColor: string;\n opacity: number;\n verticalGradient: boolean;\n};\n\n// The `3D - Building` look of the SDK's standard light/dark styles.\nconst STANDARD_LIGHT_BUILDINGS: BuildingMaterial = {\n diffuseColor: 'hsl(38, 6%, 90%)',\n opacity: 0.7,\n verticalGradient: true,\n};\nconst STANDARD_DARK_BUILDINGS: BuildingMaterial = {\n diffuseColor: 'hsl(210, 9%, 17%)',\n opacity: 0.7,\n verticalGradient: true,\n};\n\n/**\n * Resolves a display mode to the material to apply on the models layer.\n *\n * @param mode - The display mode to resolve.\n * @param basemapMaterial - What the `inherited` mode could read off the basemap building layer.\n * The standard light look fills in whatever it lacks, and all of it when `null` (the layer is\n * absent, or its colour does not resolve to one value).\n *\n * @group Landmarks 3D\n */\nexport const resolveDisplayMode = (\n mode: Landmarks3DDisplayMode,\n basemapMaterial: Partial<BuildingMaterial> | null,\n): BuildingMaterial => {\n switch (mode) {\n case 'dark':\n return STANDARD_DARK_BUILDINGS;\n case 'light':\n return STANDARD_LIGHT_BUILDINGS;\n default:\n return { ...STANDARD_LIGHT_BUILDINGS, ...basemapMaterial };\n }\n};\n","import { TomTomConfig } from '@tomtom-org/maps-sdk/core';\n\nconst LANDMARKS_API_VERSION = 2;\n\n/**\n * Builds the TomTom Orbis 3D Landmarks tile URL template.\n *\n * @remarks\n * The API serves meshopt-compressed GLB tiles with KTX2/Basis textures and requires\n * an API key with Orbis 3D Landmarks entitlements (still in Private Preview).\n *\n * @group Landmarks 3D\n */\nexport const buildLandmarksTileURL = (): string => {\n const { commonBaseURL, apiKey } = TomTomConfig.instance.get();\n // Proxy mode passes apiKey='' and lets the proxy inject the real key server-side —\n // skip the param so the proxy key never reaches the browser.\n const keyParam = apiKey ? `key=${apiKey}&` : '';\n return `${commonBaseURL}/maps/orbis/display/3d/landmarks/tile/{z}/{x}/{y}?${keyParam}apiVersion=${LANDMARKS_API_VERSION}`;\n};\n","import {\n Color,\n CustomBlending,\n DoubleSide,\n FrontSide,\n OneFactor,\n OneMinusSrcAlphaFactor,\n ShaderMaterial,\n SRGBColorSpace,\n type Texture,\n Vector3,\n} from 'three';\n\n/**\n * Name of the per-vertex gradient attribute baked into landmark geometry: maplibre's\n * `(t + base) * pow(height / 150.0, 0.5)` vertical-gradient term, precomputed per vertex.\n *\n * @group Landmarks 3D\n */\nexport const GRADIENT_ATTRIBUTE = 'fillExtrusionGradient';\n\nconst VERTEX_SHADER = /* glsl */ `\n attribute float ${GRADIENT_ATTRIBUTE};\n varying vec3 vWorldNormal;\n varying float vGradient;\n #ifdef USE_ALPHA_MASK\n varying vec2 vUv;\n #endif\n\n void main() {\n vWorldNormal = normalize(mat3(modelMatrix) * normal);\n vGradient = ${GRADIENT_ATTRIBUTE};\n #ifdef USE_ALPHA_MASK\n vUv = uv;\n #endif\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n`;\n\n// Samples the as-authored texture alpha so untextured shading keeps the mesh's carved silhouette.\nconst ALPHA_MASK_FRAGMENT_CHUNK = /* glsl */ `\n #ifdef USE_ALPHA_MASK\n uniform sampler2D alphaMask;\n uniform float alphaMaskCutoff;\n varying vec2 vUv;\n #endif\n`;\n\nconst ALPHA_MASK_MAIN_CHUNK = /* glsl */ `\n #ifdef USE_ALPHA_MASK\n if (texture2D(alphaMask, vUv).a < alphaMaskCutoff) {\n discard;\n }\n #endif\n`;\n\n// Term-for-term replica of maplibre's fill_extrusion shading, in sRGB on the raw paint colour, so\n// facades match the basemap buildings from every angle. Like maplibre, the vertical gradient only\n// darkens walls with a y normal component.\nconst FRAGMENT_SHADER = /* glsl */ `\n uniform vec3 diffuseColor;\n uniform vec3 lightPosition;\n uniform float lightIntensity;\n uniform vec3 lightColor;\n uniform float layerOpacity;\n uniform float verticalGradient;\n varying vec3 vWorldNormal;\n varying float vGradient;\n ${ALPHA_MASK_FRAGMENT_CHUNK}\n\n void main() {\n ${ALPHA_MASK_MAIN_CHUNK}\n vec3 normal = normalize(vWorldNormal);\n // Double-sided (alpha-masked) surfaces show their interior through carved openings;\n // flip backface normals so those interiors shade like the facade instead of going dark.\n if (!gl_FrontFacing) {\n normal = -normal;\n }\n // Extrusions have only vertical walls and flat roofs, so a sloped landmark roof would shade\n // darker than every basemap roof around it: up-facing normals light like a flat roof, the\n // rest like a wall, blended for curved surfaces.\n vec3 flattened = vec3(normal.xy, 0.0);\n vec3 wallNormal = flattened / max(length(flattened), 1e-5);\n float upness = smoothstep(0.4, 0.8, normal.z);\n normal = normalize(mix(wallNormal, vec3(0.0, 0.0, 1.0), upness));\n float colorValue = dot(diffuseColor, vec3(0.2126, 0.7152, 0.0722));\n vec3 color = diffuseColor + vec3(0.03);\n float directional = clamp(dot(normal, lightPosition), 0.0, 1.0);\n directional = mix(1.0 - lightIntensity, max(1.0 - colorValue + lightIntensity, 1.0), directional);\n if (abs(normal.y) > 0.001) {\n directional *=\n (1.0 - verticalGradient) +\n verticalGradient * clamp(vGradient, mix(0.7, 0.98, 1.0 - lightIntensity), 1.0);\n }\n vec3 shaded = clamp(\n color * directional * lightColor,\n mix(vec3(0.0), vec3(0.3), vec3(1.0) - lightColor),\n vec3(1.0)\n );\n gl_FragColor = vec4(shaded, 1.0) * layerOpacity;\n }\n`;\n\n// Maplibre's default style light position [1.15, 210, 30] as a cartesian vector.\nconst DEFAULT_LIGHT_POSITION = new Vector3(0.2875, -0.498, 0.996);\n\nconst parsedColor = new Color();\nconst srgbColor = new Color();\n\n/**\n * Alpha mask carried over from a landmark mesh's as-authored texture.\n *\n * Landmark meshes are coarse hulls (e.g. a wall is one rectangle) whose realistic\n * silhouette is carved out by the texture's alpha channel. Untextured shading must keep\n * sampling that alpha and discard the carved-away fragments, otherwise the raw hull\n * shape gets coloured in.\n *\n * @group Landmarks 3D\n */\nexport type FillExtrusionAlphaMask = {\n /** Texture whose alpha channel carves the mesh silhouette (the mesh's base colour map). */\n map: Texture;\n /** Fragments with alpha below this are discarded. */\n cutoff: number;\n};\n\n/**\n * Material that shades untextured landmark meshes exactly like maplibre-gl shades\n * `fill-extrusion` buildings, so they blend in with the basemap building layer.\n *\n * Colour and opacity premultiply like maplibre's `v_color *= u_opacity`, paired with\n * (ONE, ONE_MINUS_SRC_ALPHA) blending. Backfaces are culled like maplibre's extrusion\n * rendering, except for alpha-masked meshes whose cutout surfaces must stay visible from\n * both sides; pair with {@link FillExtrusionDepthMaterial} as a depth prepass so that\n * translucent landmarks blend with the basemap exactly once per pixel.\n *\n * @group Landmarks 3D\n */\nexport class FillExtrusionMaterial extends ShaderMaterial {\n constructor(alphaMask?: FillExtrusionAlphaMask) {\n super({\n uniforms: {\n diffuseColor: { value: new Vector3(1, 1, 1) },\n lightPosition: { value: DEFAULT_LIGHT_POSITION.clone() },\n lightIntensity: { value: 0.5 },\n lightColor: { value: new Vector3(1, 1, 1) },\n layerOpacity: { value: 1 },\n verticalGradient: { value: 1 },\n ...(alphaMask && {\n alphaMask: { value: alphaMask.map },\n alphaMaskCutoff: { value: alphaMask.cutoff },\n }),\n },\n ...(alphaMask && { defines: { USE_ALPHA_MASK: '' } }),\n vertexShader: VERTEX_SHADER,\n fragmentShader: FRAGMENT_SHADER,\n side: alphaMask ? DoubleSide : FrontSide,\n transparent: true,\n blending: CustomBlending,\n blendSrc: OneFactor,\n blendDst: OneMinusSrcAlphaFactor,\n });\n }\n\n /**\n * Copies the shading uniforms (colour, light, layer opacity, vertical gradient) from another\n * instance, bringing a freshly created alpha-masked variant up to date with the shared material.\n */\n copyShadingFrom(source: FillExtrusionMaterial): void {\n (this.uniforms.diffuseColor.value as Vector3).copy(source.uniforms.diffuseColor.value as Vector3);\n (this.uniforms.lightPosition.value as Vector3).copy(source.uniforms.lightPosition.value as Vector3);\n this.uniforms.lightIntensity.value = source.uniforms.lightIntensity.value;\n (this.uniforms.lightColor.value as Vector3).copy(source.uniforms.lightColor.value as Vector3);\n this.uniforms.layerOpacity.value = source.uniforms.layerOpacity.value;\n this.uniforms.verticalGradient.value = source.uniforms.verticalGradient.value;\n }\n\n /**\n * Sets the building colour from a CSS colour string, stored as raw sRGB\n * components — maplibre shades paint colours in sRGB space, not linear.\n */\n setDiffuseColor(cssColor: string): void {\n parsedColor.setStyle(cssColor);\n parsedColor.getRGB(srgbColor, SRGBColorSpace);\n (this.uniforms.diffuseColor.value as Vector3).set(srgbColor.r, srgbColor.g, srgbColor.b);\n }\n\n setLayerOpacity(opacity: number): void {\n this.uniforms.layerOpacity.value = opacity;\n }\n\n /** Enables or disables maplibre's `fill-extrusion-vertical-gradient` wall darkening. */\n setVerticalGradient(enabled: boolean): void {\n this.uniforms.verticalGradient.value = enabled ? 1 : 0;\n }\n\n /**\n * Mirrors maplibre's fill-extrusion light uniforms: the style light position is\n * rotated by the map bearing for viewport-anchored lights and the light vector is\n * deliberately left unnormalised, exactly like maplibre's `u_lightpos`.\n *\n * @param position - The style light position, in maplibre's y-south axes.\n * @param bearingInRadians - Map bearing to rotate by; pass `0` for map-anchored lights.\n * @param intensity - The style light intensity.\n * @param color - The style light colour.\n */\n setLight(\n position: { x: number; y: number; z: number },\n bearingInRadians: number,\n intensity: number,\n color: { r: number; g: number; b: number },\n ): void {\n const cosine = Math.cos(bearingInRadians);\n const sine = Math.sin(bearingInRadians);\n (this.uniforms.lightPosition.value as Vector3).set(\n position.x * cosine - position.y * sine,\n position.x * sine + position.y * cosine,\n position.z,\n );\n this.uniforms.lightIntensity.value = intensity;\n (this.uniforms.lightColor.value as Vector3).set(color.r, color.g, color.b);\n }\n}\n\n/**\n * Depth-only prepass companion to {@link FillExtrusionMaterial}. Rendering the scene\n * with this material first fills the depth buffer with the nearest landmark surfaces,\n * so the colour pass shades each pixel exactly once — overlapping translucent walls\n * don't blend on top of each other, mirroring how maplibre's depth-writing\n * fill-extrusion pass keeps buildings from showing through one another.\n *\n * Shares {@link FillExtrusionMaterial}'s vertex shader and alpha mask so both passes\n * produce identical depth values and cut out the same fragments.\n *\n * @group Landmarks 3D\n */\nexport class FillExtrusionDepthMaterial extends ShaderMaterial {\n constructor(alphaMask?: FillExtrusionAlphaMask) {\n super({\n ...(alphaMask && {\n uniforms: {\n alphaMask: { value: alphaMask.map },\n alphaMaskCutoff: { value: alphaMask.cutoff },\n },\n defines: { USE_ALPHA_MASK: '' },\n }),\n vertexShader: VERTEX_SHADER,\n fragmentShader: /* glsl */ `\n ${ALPHA_MASK_FRAGMENT_CHUNK}\n void main() {\n ${ALPHA_MASK_MAIN_CHUNK}\n gl_FragColor = vec4(0.0);\n }\n `,\n side: alphaMask ? DoubleSide : FrontSide,\n colorWrite: false,\n });\n }\n}\n","import { MercatorCoordinate } from 'maplibre-gl';\nimport type { Mesh, Object3D } from 'three';\n\n/**\n * @ignore\n */\nexport const isMesh = (object: Object3D): object is Mesh => (object as Mesh).isMesh === true;\n\nconst EARTH_RADIUS_METRES = 6378137;\n\n/**\n * Scale from EPSG:3857 (Web Mercator) metres, centred on the equator and the prime meridian, to\n * MapLibre's mercator units, where the world spans [0..1].\n * @ignore\n */\nexport const MERCATOR_UNITS_PER_METRE = 1 / (2 * Math.PI * EARTH_RADIUS_METRES);\n\n/**\n * Converts EPSG:3857 (Web Mercator) metres to `[longitude, latitude]` degrees.\n * @ignore\n */\nexport const mercatorMetresToLngLat = (x: number, y: number): [number, number] =>\n new MercatorCoordinate(0.5 + x * MERCATOR_UNITS_PER_METRE, 0.5 - y * MERCATOR_UNITS_PER_METRE).toLngLat().toArray();\n","import type { Map as MapLibreMap } from 'maplibre-gl';\nimport {\n BufferAttribute,\n DoubleSide,\n Group,\n LoaderUtils,\n type Mesh,\n type MeshStandardMaterial,\n REVISION,\n type WebGLRenderer,\n} from 'three';\nimport { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';\nimport { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';\nimport { toCreasedNormals } from 'three/examples/jsm/utils/BufferGeometryUtils.js';\nimport { GRADIENT_ATTRIBUTE } from './FillExtrusionMaterial';\nimport type { ModelsSourceSpecification } from './types/modelsSpecifications';\nimport { isMesh } from './utils';\n\n// Edges sharper than 30° keep their own normals (crisp corners); smoother ones share one (no grain).\nconst CREASE_ANGLE_RADIANS = Math.PI / 6;\n\n// When a new tile lands this far from the scene origin, rebase the origin so relative\n// coordinates stay within float32 precision.\nconst REBASE_DISTANCE_METRES = 100_000;\n\nconst DEFAULT_TRANSCODER_PATH = `https://unpkg.com/three@0.${REVISION}.x/examples/jsm/libs/basis/`;\n\n// Private maplibre-gl internals this source needs, reached via an `unknown` cast.\n// The transform hangs off the composed `_camera`, not the map itself.\ntype MapLibreTransformInternals = {\n _camera: { transform: { tileSize: number } };\n};\n\n// Element of maplibre's coveringTiles() result; exposes `.key`, `.canonical.{z,url}` and `.toString()`.\ntype CoveringTile = ReturnType<MapLibreMap['coveringTiles']>[number];\n\n/**\n * Source of tiled glTF/GLB models for a {@link ModelsLayer}.\n *\n * Tracks the tiles covering the current viewport, fetches each tile's models once\n * and keeps them in a Three.js {@link Group} that the owning layer renders.\n *\n * @group Landmarks 3D\n */\nexport class ModelsSource {\n type = 'models' as const;\n id: string;\n minzoom: number;\n maxzoom: number;\n map!: MapLibreMap;\n tiles: Array<string>;\n scene: Group;\n loadedTiles = new Set<string>();\n /**\n * EPSG:3857 origin subtracted from all mesh positions: absolute coordinates (~6·10⁶ m) exceed\n * float32 precision (~0.5 m), which makes landmark edges tremble while zooming. `null` until the\n * first tile with meshes loads.\n */\n sceneOrigin: { x: number; y: number } | null = null;\n /** Optional consumer callback fired once per successful tile load. */\n onTileLoaded?: (scene: Group, key: string) => void;\n\n private readonly ktx2Loader: KTX2Loader;\n private readonly gltfLoader: GLTFLoader;\n private readonly specification: ModelsSourceSpecification;\n private readonly withCredentials: boolean;\n\n constructor(id: string, options: ModelsSourceSpecification) {\n this.id = id;\n this.minzoom = options.minzoom ?? 0;\n this.maxzoom = options.maxzoom ?? 22;\n this.tiles = options.tiles;\n this.specification = options;\n this.withCredentials = options.withCredentials ?? false;\n this.scene = new Group();\n this.ktx2Loader = new KTX2Loader()\n .setCrossOrigin('anonymous')\n .setTranscoderPath(options.transcoderPath ?? DEFAULT_TRANSCODER_PATH);\n this.gltfLoader = new GLTFLoader().setKTX2Loader(this.ktx2Loader).setMeshoptDecoder(MeshoptDecoder);\n }\n\n onAdd(map: MapLibreMap, renderer: WebGLRenderer) {\n this.map = map;\n this.ktx2Loader.detectSupport(renderer);\n }\n\n updateTiles() {\n if (this.tiles.length === 0) {\n return;\n }\n\n const coveringTileKeys = new Set<string>();\n for (const tile of this.coveringTiles()) {\n coveringTileKeys.add(tile.key);\n this.loadTile(tile);\n }\n this.showCoveringTilesOnly(coveringTileKeys);\n }\n\n // Renders only the tiles that cover the viewport, so landmarks leave the screen together with\n // the basemap buildings of the same area. Tiles that fall outside stay in memory, ready to\n // render again without a refetch.\n private showCoveringTilesOnly(coveringTileKeys: Set<string>): void {\n for (const tile of this.scene.children) {\n tile.visible = coveringTileKeys.has(tile.userData.key as string);\n }\n }\n\n // With 3D terrain, a tile on a hill is in view before its sea-level footprint is: MapLibre's own\n // sources pass the terrain so tiles are bounded by their elevation range. The public options type\n // leaves `terrain` out, but `coveringTiles` forwards it, so the options go in as a variable.\n private coveringTiles(): CoveringTile[] {\n const transform = (this.map as unknown as MapLibreTransformInternals)._camera.transform;\n const options = {\n tileSize: transform.tileSize,\n minzoom: this.minzoom,\n maxzoom: this.maxzoom,\n terrain: this.map.terrain,\n };\n return this.map.coveringTiles(options);\n }\n\n // The source loads a single zoom level, so tiles never overlap; each is fetched once.\n private loadTile(tile: CoveringTile): void {\n if (this.loadedTiles.has(tile.key)) {\n return;\n }\n this.loadedTiles.add(tile.key);\n\n const url = tile.canonical.url(this.tiles, this.map.getPixelRatio(), this.specification.scheme);\n this.fetchTile(url)\n .then((scene) => {\n if (!scene) {\n return; // expected empty tile (no landmark / not produced)\n }\n prepareTileMeshes(scene);\n this.rebaseToSceneOrigin(scene);\n scene.userData = { isTile: true, key: tile.key };\n this.scene.add(scene);\n this.onTileLoaded?.(scene, tile.key);\n })\n .catch((error: { message?: string }) => {\n console.warn(`Problem with loading ${tile.toString()}: ${error?.message ?? 'unknown'}`);\n });\n }\n\n // Rewrites the tile's meshes from absolute EPSG:3857 positions to positions relative to\n // `sceneOrigin`, which ModelsLayer.alignCameraToMap folds back in.\n private rebaseToSceneOrigin(tileScene: Group): void {\n const meshes: Mesh[] = [];\n tileScene.traverse((child) => {\n if (isMesh(child)) {\n meshes.push(child);\n }\n });\n if (meshes.length === 0) {\n return;\n }\n\n const anchor = meshes[0].position;\n const anchorOrigin = { x: Math.round(anchor.x), y: Math.round(anchor.y) };\n if (!this.sceneOrigin) {\n this.sceneOrigin = anchorOrigin;\n } else if (Math.hypot(anchor.x - this.sceneOrigin.x, anchor.y - this.sceneOrigin.y) > REBASE_DISTANCE_METRES) {\n // The viewport moved far from the origin (e.g. a fly-to across cities): move the\n // origin and shift the already-loaded meshes accordingly.\n const deltaX = anchorOrigin.x - this.sceneOrigin.x;\n const deltaY = anchorOrigin.y - this.sceneOrigin.y;\n this.scene.traverse((child) => {\n if (isMesh(child)) {\n child.position.x -= deltaX;\n child.position.y -= deltaY;\n }\n });\n this.sceneOrigin = anchorOrigin;\n }\n for (const mesh of meshes) {\n mesh.position.x -= this.sceneOrigin.x;\n mesh.position.y -= this.sceneOrigin.y;\n }\n }\n\n // Fetch via the global `fetch` (not three.js' XHR) so it rides any installed\n // session wrapper — e.g. the demos-proxy cookie gate — then parse the GLB.\n private async fetchTile(url: string): Promise<Group | null> {\n const response = await fetch(url, this.withCredentials ? { credentials: 'include' } : undefined);\n // 204 = no landmark in this tile, 404 = tile not produced — both expected.\n if (response.status === 204 || response.status === 404) {\n return null;\n }\n if (!response.ok) {\n throw new Error(`tile request failed with ${response.status}`);\n }\n const buffer = await response.arrayBuffer();\n // Resolve relative resources against the tile URL, as GLTFLoader.load would.\n const { scene } = await this.gltfLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));\n return scene;\n }\n}\n\n// Stores each mesh's authored material and re-creases its normals so untextured shading keeps crisp edges.\nconst prepareTileMeshes = (scene: Group): void => {\n scene.traverse((child) => {\n if (!isMesh(child)) {\n return;\n }\n\n const material = child.material as MeshStandardMaterial;\n material.alphaTest = 0.1;\n material.side = DoubleSide;\n child.userData.originalMaterial = material;\n child.geometry = toCreasedNormals(child.geometry, CREASE_ANGLE_RADIANS);\n bakeGradientAttribute(child);\n });\n};\n\n// Bakes the GRADIENT_ATTRIBUTE term per vertex, with base = 0 and t = z / height.\nconst bakeGradientAttribute = (mesh: Mesh): void => {\n const geometry = mesh.geometry;\n const positions = geometry.getAttribute('position');\n geometry.computeBoundingBox();\n const height = geometry.boundingBox?.max.z ?? 0;\n const gradientScale = height > 0 ? Math.sqrt(height / 150) / height : 0;\n const gradient = new Float32Array(positions.count);\n for (let index = 0; index < positions.count; index++) {\n gradient[index] = positions.getZ(index) * gradientScale;\n }\n geometry.setAttribute(GRADIENT_ATTRIBUTE, new BufferAttribute(gradient, 1));\n};\n","import type { CustomLayerInterface, CustomRenderMethodInput, Map as MapLibreMap } from 'maplibre-gl';\nimport {\n AmbientLight,\n Camera,\n DirectionalLight,\n Group,\n Matrix4,\n type Mesh,\n type MeshStandardMaterial,\n Scene,\n type Texture,\n WebGLRenderer,\n} from 'three';\nimport { FillExtrusionDepthMaterial, FillExtrusionMaterial } from './FillExtrusionMaterial';\nimport { ModelsSource } from './ModelsSource';\nimport type { ModelsLayerSpecification, ModelsSourceSpecification } from './types/modelsSpecifications';\nimport { isMesh, MERCATOR_UNITS_PER_METRE, mercatorMetresToLngLat } from './utils';\n\nconst TRANSLATE_X = 0.5;\nconst TRANSLATE_Y = 0.5;\n\nconst toRadians = (degrees: number): number => (degrees * Math.PI) / 180;\n\n// Extra sinking below the sampled terrain: keeps walls grounded across unsampled dips and\n// the flat bottom cap below the surface, where it cannot z-fight with the ground.\nconst TERRAIN_BURIAL_MARGIN_METRES = 0.5;\n\n// Subset of the private maplibre-gl internals this layer needs, reached via an `unknown` cast.\n// The transform hangs off the composed `_camera`, not the map itself.\ntype MapLibreRenderInternals = {\n _camera: { transform: { tileZoom: number; bearingInRadians?: number } };\n style: {\n light?: {\n // The evaluated 'position' property is the raw spherical [radial, azimuthal, polar]\n // array; this getter converts it to cartesian [x, y, z].\n getCartesianPosition?(): [number, number, number];\n properties?: {\n get(key: 'intensity'): number;\n get(key: 'anchor'): 'map' | 'viewport';\n get(key: 'color'): { r: number; g: number; b: number };\n };\n };\n };\n};\n\n/**\n * MapLibre custom layer that renders tiled glTF/GLB models with Three.js.\n *\n * The layer owns a {@link ModelsSource} that streams model tiles for the current\n * viewport, renders them as maplibre-style fill-extrusion buildings, and keeps its\n * lighting in sync with the map style light.\n *\n * @group Landmarks 3D\n */\nexport class ModelsLayer implements CustomLayerInterface {\n id: string;\n type = 'custom' as const;\n renderingMode = '3d' as const;\n\n renderer!: WebGLRenderer;\n scene!: Scene;\n camera!: Camera;\n tiles: Group;\n directionalLight!: DirectionalLight;\n ambientLight!: AmbientLight;\n map!: MapLibreMap;\n source: ModelsSource;\n minzoom = 0;\n maxzoom = 22;\n visible = true;\n\n diffuseColor = '#ffffff';\n\n private readonly fillExtrusionMaterial = new FillExtrusionMaterial();\n private readonly depthPrepassMaterial = new FillExtrusionDepthMaterial();\n // Per-texture alpha-masked variants so untextured shading keeps each mesh's carved silhouette.\n private readonly maskedFillExtrusionMaterials = new Map<\n Texture,\n { color: FillExtrusionMaterial; depth: FillExtrusionDepthMaterial }\n >();\n\n constructor(options: ModelsLayerSpecification, source: ModelsSourceSpecification) {\n this.id = options.id;\n this.source = new ModelsSource(this.id, source);\n this.tiles = this.source.scene;\n this.updateProperties(options);\n }\n\n updateProperties(options: ModelsLayerSpecification) {\n this.visible = options.layout?.visibility !== 'none';\n this.diffuseColor = options.paint?.['models-base-color'] ?? '#ffffff';\n this.minzoom = options.minzoom ?? 0;\n this.maxzoom = options.maxzoom ?? 22;\n }\n\n setDirectionalLight(radius: number, phi: number, theta: number) {\n this.directionalLight.position.setFromSphericalCoords(radius, toRadians(phi), toRadians(theta));\n }\n\n onAdd(map: MapLibreMap, gl: WebGLRenderingContext | WebGL2RenderingContext) {\n this.map = map;\n\n this.scene = new Scene();\n this.tiles = this.source.scene;\n this.camera = new Camera();\n\n this.directionalLight = new DirectionalLight(0xffffff, 0.5);\n this.setDirectionalLight(1.15, 90, 0);\n this.scene.add(this.directionalLight);\n\n this.ambientLight = new AmbientLight(0xffffff, 0.1);\n this.scene.add(this.ambientLight);\n\n this.scene.add(this.tiles);\n\n this.renderer = new WebGLRenderer({\n canvas: map.getCanvas(),\n context: gl,\n antialias: true,\n });\n this.renderer.autoClear = false;\n\n this.source.onAdd(map, this.renderer);\n }\n\n render(_gl: WebGLRenderingContext | WebGL2RenderingContext, options: CustomRenderMethodInput) {\n const internals = this.map as unknown as MapLibreRenderInternals;\n const tileZoom = internals._camera.transform.tileZoom;\n const inZoomRange = tileZoom >= this.minzoom && tileZoom <= this.maxzoom;\n this.tiles.visible = this.visible && inZoomRange;\n\n if (!(this.visible && inZoomRange)) {\n return;\n }\n\n this.syncLighting();\n this.source.updateTiles();\n this.groundLandmarksOnTerrain();\n this.alignCameraToMap(options);\n\n this.renderer.resetState();\n if (this.needsTranslucentPrepass()) {\n this.renderDepthPrepass();\n }\n\n this.applyMaterials();\n this.renderer.render(this.scene, this.camera);\n this.map.triggerRepaint();\n }\n\n // Landmarks are authored with their base at sea level; with 3D terrain the ground rises to\n // the (exaggeration-scaled) elevation. Grounds every landmark each frame, since terrain tiles\n // load progressively and exaggeration can change at runtime.\n private groundLandmarksOnTerrain() {\n for (const tile of this.tiles.children) {\n if (!tile.visible) {\n continue; // off-viewport tiles stay loaded; grounding them each frame costs terrain queries\n }\n tile.traverse((child) => {\n if (isMesh(child)) {\n this.groundMeshOnTerrain(child);\n }\n });\n }\n }\n\n // Lifts one landmark to the LOWEST terrain elevation across its footprint, minus a small\n // burial margin: no wall ever floats, and the bottom cap stays below the surface. Grounding is\n // per landmark because terrain can vary by tens of metres across a tile.\n private groundMeshOnTerrain(mesh: Mesh) {\n const samplePoints = this.terrainSamplePoints(mesh);\n if (!samplePoints) {\n return;\n }\n\n let lowestElevation: number | null = null;\n for (const samplePoint of samplePoints) {\n const elevation = this.map.queryTerrainElevation(samplePoint);\n if (elevation === null) {\n // Terrain is disabled — restore the authored sea-level base.\n mesh.position.z = 0;\n return;\n }\n if (lowestElevation === null || elevation < lowestElevation) {\n lowestElevation = elevation;\n }\n }\n if (lowestElevation !== null) {\n mesh.position.z = lowestElevation - TERRAIN_BURIAL_MARGIN_METRES;\n }\n }\n\n // `[longitude, latitude]` points spread over the mesh footprint (center, corners, edge\n // midpoints — long walls can dip between corners), derived once from its bounding box.\n private terrainSamplePoints(mesh: Mesh): [number, number][] | null {\n let samplePoints = mesh.userData.terrainSamplePoints as [number, number][] | undefined;\n if (!samplePoints) {\n if (!mesh.geometry.boundingBox) {\n mesh.geometry.computeBoundingBox();\n }\n const bounds = mesh.geometry.boundingBox;\n if (!bounds || bounds.isEmpty()) {\n return null;\n }\n\n // Mesh positions are relative to the scene origin; the samples need absolute coordinates.\n const anchorX = mesh.position.x + this.sceneOrigin.x;\n const anchorY = mesh.position.y + this.sceneOrigin.y;\n const centerX = (bounds.min.x + bounds.max.x) / 2;\n const centerY = (bounds.min.y + bounds.max.y) / 2;\n samplePoints = [\n [centerX, centerY],\n [bounds.min.x, bounds.min.y],\n [bounds.min.x, bounds.max.y],\n [bounds.max.x, bounds.min.y],\n [bounds.max.x, bounds.max.y],\n [centerX, bounds.min.y],\n [centerX, bounds.max.y],\n [bounds.min.x, centerY],\n [bounds.max.x, centerY],\n ].map(([offsetX, offsetY]) => mercatorMetresToLngLat(anchorX + offsetX, anchorY + offsetY));\n mesh.userData.terrainSamplePoints = samplePoints;\n }\n return samplePoints;\n }\n\n // maplibre hands custom layers `defaultProjectionData.mainMatrix`, which takes mercator 0..1 for x/y\n // and — in `3d` rendering mode — a conformal z in those same mercator units, not metres. Metre\n // altitudes therefore need the mercator scale factor at the current latitude, hence the extra\n // 1/cos(lat) that z carries and x/y don't.\n private alignCameraToMap(options: CustomRenderMethodInput) {\n const zScale = MERCATOR_UNITS_PER_METRE / Math.cos(toRadians(this.map.getCenter().lat));\n // The scene origin (see ModelsSource.sceneOrigin) is folded in here, in float64.\n const translateX = TRANSLATE_X + MERCATOR_UNITS_PER_METRE * this.sceneOrigin.x;\n const translateY = TRANSLATE_Y - MERCATOR_UNITS_PER_METRE * this.sceneOrigin.y;\n const mapLibreScale = new Matrix4();\n mapLibreScale.set(\n MERCATOR_UNITS_PER_METRE,\n 0,\n 0,\n translateX,\n 0,\n -MERCATOR_UNITS_PER_METRE,\n 0,\n translateY,\n 0,\n 0,\n zScale,\n 0,\n 0,\n 0,\n 0,\n 1,\n );\n const projection = new Matrix4().fromArray(options.defaultProjectionData.mainMatrix as ArrayLike<number>);\n this.camera.projectionMatrix.copy(projection).multiply(mapLibreScale);\n }\n\n // Zero until the first tile with meshes sets the source's origin.\n private get sceneOrigin(): NonNullable<ModelsSource['sceneOrigin']> {\n return this.source.sceneOrigin ?? { x: 0, y: 0 };\n }\n\n // Translucent shading needs a depth pass so overlapping walls blend with the basemap once per pixel.\n private needsTranslucentPrepass(): boolean {\n const layerOpacity = this.fillExtrusionMaterial.uniforms.layerOpacity.value as number;\n return layerOpacity < 1;\n }\n\n // Per-mesh (not overrideMaterial) so each mesh's alpha mask carves the prepass depth too.\n private renderDepthPrepass() {\n this.tiles.traverse((child) => {\n if (isMesh(child)) {\n child.material = this.maskedFillExtrusionVariant(child)?.depth ?? this.depthPrepassMaterial;\n }\n });\n this.renderer.render(this.scene, this.camera);\n }\n\n setOpacity(value: number) {\n this.forEachFillExtrusionMaterial((material) => material.setLayerOpacity(value));\n }\n\n /** Mirrors the basemap's fill-extrusion-vertical-gradient on the landmark shading. */\n setVerticalGradient(enabled: boolean) {\n this.forEachFillExtrusionMaterial((material) => material.setVerticalGradient(enabled));\n }\n\n getMaterial(mesh: Mesh): FillExtrusionMaterial {\n return this.maskedFillExtrusionVariant(mesh)?.color ?? this.fillExtrusionMaterial;\n }\n\n // Fill-extrusion variant masked by the mesh's own texture alpha; null for untextured meshes.\n private maskedFillExtrusionVariant(mesh: Mesh) {\n const original = mesh.userData.originalMaterial as MeshStandardMaterial | undefined;\n const map = original?.map;\n if (!map) {\n return null;\n }\n\n let variant = this.maskedFillExtrusionMaterials.get(map);\n if (!variant) {\n const alphaMask = { map, cutoff: original.alphaTest || 0.1 };\n variant = {\n color: new FillExtrusionMaterial(alphaMask),\n depth: new FillExtrusionDepthMaterial(alphaMask),\n };\n variant.color.copyShadingFrom(this.fillExtrusionMaterial);\n this.maskedFillExtrusionMaterials.set(map, variant);\n }\n return variant;\n }\n\n private forEachFillExtrusionMaterial(apply: (material: FillExtrusionMaterial) => void) {\n apply(this.fillExtrusionMaterial);\n this.maskedFillExtrusionMaterials.forEach((variant) => apply(variant.color));\n }\n\n private syncLighting() {\n // The maplibre style light is private API; fall back to the onAdd defaults when absent.\n const internals = this.map as unknown as MapLibreRenderInternals;\n const light = internals.style?.light;\n const properties = light?.properties;\n const cartesianPosition = light?.getCartesianPosition?.();\n if (!properties || !cartesianPosition) {\n return;\n }\n\n try {\n const intensity = properties.get('intensity');\n // `* π` cancels Three.js's internal 1/π Lambert factor, matching maplibre's shading range.\n this.directionalLight.intensity = intensity * Math.PI;\n this.ambientLight.intensity = (1 - intensity) * Math.PI;\n\n // The cartesian style light maps into the scene's x-east/y-north frame with x negated.\n const position = { x: -cartesianPosition[0], y: cartesianPosition[1], z: cartesianPosition[2] };\n this.directionalLight.position.set(position.x, position.y, position.z);\n\n // Viewport-anchored lights counter-rotate with the bearing so camera-facing walls\n // stay lit at every bearing, matching maplibre's rendered output.\n const anchor = properties.get('anchor');\n const bearingInRadians = anchor === 'viewport' ? -(internals._camera.transform.bearingInRadians ?? 0) : 0;\n const color = properties.get('color');\n this.forEachFillExtrusionMaterial((material) =>\n material.setLight(position, bearingInRadians, intensity, color),\n );\n } catch {\n // Internal API surface changed — keep the configured defaults.\n }\n }\n\n private applyMaterials() {\n this.forEachFillExtrusionMaterial((material) => material.setDiffuseColor(this.diffuseColor));\n\n this.tiles.traverse((child) => {\n if (isMesh(child)) {\n child.material = this.getMaterial(child);\n }\n });\n }\n}\n","import { isProxyCredentialsMode } from '@tomtom-org/maps-sdk/core';\nimport { LayerFilterComposer, mapStyleLayerIDs, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { ExpressionFilterSpecification } from 'maplibre-gl';\nimport { type BuildingMaterial, type Landmarks3DDisplayMode, resolveDisplayMode } from './displayMode';\nimport { buildLandmarksTileURL } from './landmarksTileURL';\nimport { ModelsLayer } from './ModelsLayer';\nimport type { ModelsLayerSpecification, ModelsSourceSpecification } from './types/modelsSpecifications';\n\n// Load landmark tiles at a single zoom level: the meshes are full-detail at this zoom and upscale\nconst SOURCE_ZOOM = 15;\n\nconst DEFAULT_LAYER_ID = 'orbis-3d-landmarks';\nconst DEFAULT_LAYER_MIN_ZOOM = 10;\nconst DEFAULT_LAYER_MAX_ZOOM = 22;\n\n// Filters out basemap extruded buildings that overlap a landmark, so they don't clip through its mesh.\nconst DEFAULT_BASEMAP_BUILDING_LAYER_ID = '3D - Building';\nconst NO_LANDMARK_FILTER_CLAUSE: ExpressionFilterSpecification = ['!', ['coalesce', ['get', 'has_landmark'], false]];\n// This plugin's key with the shared filter composer.\nconst LANDMARKS_CONTRIBUTOR = 'landmarks3d.buildings';\n\n// Resolves `fill-extrusion-color` to one CSS colour, for `match` expressions, the last operand is the default.\nconst extractColorFromPaint = (paintColor: unknown): string | null => {\n if (typeof paintColor === 'string') {\n return paintColor;\n }\n\n if (Array.isArray(paintColor) && paintColor[0] === 'match') {\n const fallback = paintColor.at(-1);\n if (typeof fallback === 'string') {\n return fallback;\n }\n }\n\n return null;\n};\n\n/**\n * Configuration for the {@link Landmarks3D} plugin.\n *\n * @group Landmarks 3D\n */\nexport type Landmarks3DOptions = {\n /**\n * Initial display mode for the landmarks. Can also be changed at runtime with\n * {@link Landmarks3D.setDisplayMode}.\n * @defaultValue `'inherited'`\n */\n displayMode?: Landmarks3DDisplayMode;\n /**\n * Whether the landmarks are initially visible. Can also be changed at runtime with\n * {@link Landmarks3D.setVisible}.\n * @defaultValue `true`\n */\n visible?: boolean;\n /**\n * Minimum zoom at which landmarks are rendered.\n * @defaultValue The basemap 3D building layer's minimum zoom, so landmarks appear together\n * with it (falls back to `10` when that layer is absent).\n */\n minZoom?: number;\n /**\n * Maximum zoom at which landmarks are rendered.\n * @defaultValue The basemap 3D building layer's maximum zoom, so landmarks disappear together\n * with it (falls back to `22` when that layer is absent).\n */\n maxZoom?: number;\n /**\n * Send `credentials: 'include'` with each tile request so a proxy session\n * cookie travels with it. By default this tracks the SDK's proxy config, so\n * tiles behave like every other SDK request; set it explicitly only to\n * override that.\n * @defaultValue Auto-detected — `true` when the SDK is configured for a\n * credentialed proxy (no `apiKey` + non-default `commonBaseURL`).\n */\n withCredentials?: boolean;\n};\n\n/**\n * Renders TomTom Orbis 3D Landmarks (high-detail building meshes) on a {@link TomTomMap}.\n *\n * The plugin streams GLB tiles from the Orbis 3D Landmarks API (Private Preview),\n * renders them with Three.js via a MapLibre custom layer, filters overlapping\n * extruded basemap buildings out of the style, and keeps everything restored\n * across map style changes.\n *\n * @remarks\n * Requires an API key with Orbis 3D Landmarks entitlements and a map style that\n * shows extruded 3D buildings for the exclusion filter and `inherited` mode to\n * have an effect.\n *\n * @example\n * ```typescript\n * import { Landmarks3D } from '@tomtom-org/maps-sdk-plugin-landmarks-3d';\n *\n * // assume `map` is your initialized TomTomMap instance\n * const landmarks = new Landmarks3D(map);\n *\n * await landmarks.setDisplayMode('inherited');\n * ```\n *\n * @group Landmarks 3D\n */\nexport class Landmarks3D {\n private readonly map: TomTomMap;\n private readonly layerID = DEFAULT_LAYER_ID;\n private readonly basemapBuildingLayerID = DEFAULT_BASEMAP_BUILDING_LAYER_ID;\n private readonly modelsLayer: ModelsLayer;\n // Explicit zoom-range overrides; when unset, the range tracks the basemap 3D building layer.\n private readonly layerMinZoom?: number;\n private readonly layerMaxZoom?: number;\n private displayMode: Landmarks3DDisplayMode;\n\n /**\n * Creates the plugin bound to a TomTom map and starts rendering landmarks\n * as soon as the map is ready.\n *\n * @param map - The TomTom map instance to render landmarks on.\n * @param options - Optional configuration.\n */\n constructor(map: TomTomMap, options: Landmarks3DOptions = {}) {\n this.map = map;\n this.layerMinZoom = options.minZoom;\n this.layerMaxZoom = options.maxZoom;\n this.displayMode = options.displayMode ?? 'inherited';\n\n const layerSpecification: ModelsLayerSpecification = {\n id: this.layerID,\n type: 'models',\n source: this.layerID,\n minzoom: options.minZoom ?? DEFAULT_LAYER_MIN_ZOOM,\n maxzoom: options.maxZoom ?? DEFAULT_LAYER_MAX_ZOOM,\n };\n const sourceSpecification: ModelsSourceSpecification = {\n type: 'models',\n tiles: [buildLandmarksTileURL()],\n minzoom: SOURCE_ZOOM,\n maxzoom: SOURCE_ZOOM,\n withCredentials: options.withCredentials ?? isProxyCredentialsMode(),\n };\n this.modelsLayer = new ModelsLayer(layerSpecification, sourceSpecification);\n this.modelsLayer.visible = options.visible ?? true;\n\n this.map.addStyleChangeHandler({\n onStyleChanged: () => this.install(),\n });\n this.installWhenReady();\n }\n\n // Installs the layer as soon as the map is ready\n private installWhenReady(): void {\n if (this.map.mapReady) {\n this.install();\n } else {\n this.map.mapLibreMap.once('styledata', () => this.installWhenReady());\n }\n }\n\n /**\n * The underlying MapLibre custom layer, for advanced use beyond this facade.\n */\n get layer(): ModelsLayer {\n return this.modelsLayer;\n }\n\n /**\n * The currently applied display mode.\n */\n getDisplayMode(): Landmarks3DDisplayMode {\n return this.displayMode;\n }\n\n /**\n * Changes how landmarks are rendered relative to the base map.\n *\n * @param mode - The display mode to apply.\n * @returns A promise that resolves once the mode has been applied.\n */\n async setDisplayMode(mode: Landmarks3DDisplayMode): Promise<void> {\n this.displayMode = mode;\n // Before the map is ready the layer isn't installed; install() applies the stored\n // mode once it runs, so simply recording it above is enough in that case.\n if (this.map.mapReady) {\n this.applyDisplayMode();\n }\n }\n\n /**\n * Whether the landmarks are currently visible.\n */\n isVisible(): boolean {\n return this.modelsLayer.visible;\n }\n\n /**\n * Shows or hides the landmarks layer.\n *\n * @param visible - `true` to show landmarks, `false` to hide them.\n * @returns A promise that resolves once the visibility has been applied.\n */\n async setVisible(visible: boolean): Promise<void> {\n this.modelsLayer.visible = visible;\n // When the map isn't ready yet the layer is added (with this visibility) by\n // install(); only an already-installed layer needs an explicit repaint here.\n if (this.map.mapReady) {\n this.map.mapLibreMap.triggerRepaint();\n }\n }\n\n // (Re-)adds the layer and re-applies filter + display mode on first readiness and after style changes.\n private install(): void {\n const mapLibreMap = this.map.mapLibreMap;\n if (!mapLibreMap.getLayer(this.layerID)) {\n mapLibreMap.addLayer(this.modelsLayer, this.landmarkInsertBeforeID());\n }\n\n // The standard styles ship the 3D building layer hidden; show it so landmarks get a 3D city context.\n if (mapLibreMap.getLayer(this.basemapBuildingLayerID)) {\n mapLibreMap.setLayoutProperty(this.basemapBuildingLayerID, 'visibility', 'visible');\n }\n\n this.alignZoomRangeWithBasemapBuildings();\n this.excludeLandmarkBuildingsFromBasemap();\n\n // Late-arriving tiles load with opaque materials; re-apply non-opaque looks to them.\n this.modelsLayer.source.onTileLoaded = () => {\n if (this.resolveCurrentMaterialState().opacity < 1) {\n this.applyDisplayMode();\n }\n };\n\n this.applyDisplayMode();\n }\n\n // Insert before the POI anchor so POIs draw over the meshes and roads stay below; undefined = on top.\n private landmarkInsertBeforeID(): string | undefined {\n const poiAnchor = mapStyleLayerIDs.poi;\n return this.map.mapLibreMap.getLayer(poiAnchor) ? poiAnchor : undefined;\n }\n\n // Tracks the basemap 3D building layer's zoom range so landmarks appear and disappear together with\n // it, unless the caller pinned an explicit min/max zoom. No-op when the building layer is absent.\n private alignZoomRangeWithBasemapBuildings(): void {\n const buildingLayer = this.map.mapLibreMap.getLayer(this.basemapBuildingLayerID);\n if (!buildingLayer) {\n return;\n }\n\n this.modelsLayer.minzoom = this.layerMinZoom ?? buildingLayer.minzoom ?? DEFAULT_LAYER_MIN_ZOOM;\n this.modelsLayer.maxzoom = this.layerMaxZoom ?? buildingLayer.maxzoom ?? DEFAULT_LAYER_MAX_ZOOM;\n }\n\n private excludeLandmarkBuildingsFromBasemap(): void {\n // The composer owns the layer's filter: registering under this plugin's key adds the clause\n // once however often `install` runs, and keeps it beside what the style and any other\n // contributor filter the layer by.\n LayerFilterComposer.for(this.map).setClause(\n this.basemapBuildingLayerID,\n LANDMARKS_CONTRIBUTOR,\n NO_LANDMARK_FILTER_CLAUSE,\n );\n }\n\n // Reads the basemap building look for `inherited` mode, leaving out what does not resolve to one\n // value; null when the layer is absent or its colour does not resolve.\n private readBasemapBuildingMaterial(): Partial<BuildingMaterial> | null {\n const mapLibreMap = this.map.mapLibreMap;\n if (!mapLibreMap.getLayer(this.basemapBuildingLayerID)) {\n return null;\n }\n\n const paintColor = mapLibreMap.getPaintProperty(this.basemapBuildingLayerID, 'fill-extrusion-color');\n const diffuseColor = extractColorFromPaint(paintColor);\n if (!diffuseColor) {\n return null;\n }\n\n const opacity = mapLibreMap.getPaintProperty(this.basemapBuildingLayerID, 'fill-extrusion-opacity');\n const verticalGradient = mapLibreMap.getPaintProperty(\n this.basemapBuildingLayerID,\n 'fill-extrusion-vertical-gradient',\n );\n return {\n diffuseColor,\n ...(typeof opacity === 'number' && { opacity }),\n ...(typeof verticalGradient === 'boolean' && { verticalGradient }),\n };\n }\n\n private resolveCurrentMaterialState(): BuildingMaterial {\n const basemapMaterial = this.displayMode === 'inherited' ? this.readBasemapBuildingMaterial() : null;\n return resolveDisplayMode(this.displayMode, basemapMaterial);\n }\n\n private applyDisplayMode(): void {\n const state = this.resolveCurrentMaterialState();\n this.modelsLayer.diffuseColor = state.diffuseColor;\n this.modelsLayer.setOpacity(state.opacity);\n this.modelsLayer.setVerticalGradient(state.verticalGradient);\n this.map.mapLibreMap.triggerRepaint();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.es.js","names":["TomTomConfig","isProxyCredentialsMode","LayerFilterComposer","mapStyleLayerIDs","AmbientLight","BufferAttribute","Camera","Color","CustomBlending","DirectionalLight","DoubleSide","FrontSide","Group","LoaderUtils","LoadingManager","Matrix4","OneFactor","OneMinusSrcAlphaFactor","SRGBColorSpace","Scene","ShaderMaterial","Vector3","WebGLRenderer","MeshoptDecoder","GLTFLoader","KTX2Loader","toCreasedNormals","MercatorCoordinate","STANDARD_LIGHT_BUILDINGS","diffuseColor","opacity","verticalGradient","STANDARD_DARK_BUILDINGS","buildLandmarksTileURL","commonBaseURL","apiKey","instance","get","GRADIENT_ATTRIBUTE","VERTEX_SHADER","ALPHA_MASK_FRAGMENT_CHUNK","ALPHA_MASK_MAIN_CHUNK","FRAGMENT_SHADER","DEFAULT_LIGHT_POSITION","parsedColor","srgbColor","FillExtrusionMaterial","constructor","alphaMask","super","uniforms","value","lightPosition","clone","lightIntensity","lightColor","layerOpacity","map","alphaMaskCutoff","cutoff","defines","USE_ALPHA_MASK","vertexShader","fragmentShader","side","transparent","blending","blendSrc","blendDst","copyShadingFrom","source","this","copy","setDiffuseColor","cssColor","setStyle","getRGB","set","r","g","b","setLayerOpacity","setVerticalGradient","enabled","setLight","position","bearingInRadians","intensity","color","cosine","Math","cos","sine","sin","x","y","z","FillExtrusionDepthMaterial","colorWrite","isMesh","object","MERCATOR_UNITS_PER_METRE","PI","CREASE_ANGLE_RADIANS","ModelsSource","type","id","minzoom","maxzoom","tiles","scene","loadedTiles","Set","sceneOrigin","onTileLoaded","ktx2Loader","transcoderReady","gltfLoader","specification","withCredentials","options","setCrossOrigin","setTranscoderPath","transcoderPath","setKTX2Loader","setMeshoptDecoder","onAdd","renderer","detectSupport","updateTiles","length","coveringTileKeys","tile","coveringTiles","add","key","loadTile","showCoveringTilesOnly","children","visible","has","userData","tileSize","_camera","transform","terrain","url","canonical","getPixelRatio","scheme","fetchTile","then","prepareTileMeshes","rebaseToSceneOrigin","isTile","catch","error","console","warn","toString","message","tileScene","meshes","traverse","child","push","anchor","anchorOrigin","round","hypot","deltaX","deltaY","mesh","response","fetch","credentials","status","ok","Error","buffer","arrayBuffer","async","manager","resolveBundledTranscoderUrl","import","setURLModifier","prepareTranscoder","parseAsync","extractUrlBase","material","alphaTest","originalMaterial","geometry","bakeGradientAttribute","positions","getAttribute","computeBoundingBox","height","boundingBox","max","gradientScale","sqrt","gradient","Float32Array","count","index","getZ","setAttribute","toRadians","degrees","ModelsLayer","renderingMode","camera","directionalLight","ambientLight","fillExtrusionMaterial","depthPrepassMaterial","maskedFillExtrusionMaterials","Map","updateProperties","layout","visibility","paint","setDirectionalLight","radius","phi","theta","setFromSphericalCoords","gl","canvas","getCanvas","context","antialias","autoClear","render","_gl","tileZoom","inZoomRange","syncLighting","groundLandmarksOnTerrain","alignCameraToMap","resetState","needsTranslucentPrepass","renderDepthPrepass","applyMaterials","triggerRepaint","groundMeshOnTerrain","samplePoints","terrainSamplePoints","lowestElevation","samplePoint","elevation","queryTerrainElevation","bounds","isEmpty","anchorX","anchorY","centerX","min","centerY","offsetX","offsetY","toLngLat","toArray","zScale","getCenter","lat","translateX","translateY","mapLibreScale","projection","fromArray","defaultProjectionData","mainMatrix","projectionMatrix","multiply","maskedFillExtrusionVariant","depth","setOpacity","forEachFillExtrusionMaterial","getMaterial","original","variant","apply","forEach","internals","light","style","properties","cartesianPosition","getCartesianPosition","NO_LANDMARK_FILTER_CLAUSE","Landmarks3D","layerID","basemapBuildingLayerID","modelsLayer","layerMinZoom","layerMaxZoom","displayMode","minZoom","maxZoom","layerSpecification","sourceSpecification","addStyleChangeHandler","onStyleChanged","install","installWhenReady","mapReady","mapLibreMap","once","layer","getDisplayMode","setDisplayMode","mode","applyDisplayMode","isVisible","setVisible","getLayer","addLayer","landmarkInsertBeforeID","setLayoutProperty","alignZoomRangeWithBasemapBuildings","excludeLandmarkBuildingsFromBasemap","resolveCurrentMaterialState","poiAnchor","poi","buildingLayer","for","setClause","readBasemapBuildingMaterial","paintColor","Array","isArray","fallback","at","extractColorFromPaint","getPaintProperty","basemapMaterial","resolveDisplayMode","state"],"sources":["../src/displayMode.ts","../src/landmarksTileURL.ts","../src/FillExtrusionMaterial.ts","../src/utils.ts","../src/ModelsSource.ts","../src/ModelsLayer.ts","../src/Landmarks3D.ts"],"sourcesContent":["/**\n * How 3D landmarks are shaded relative to the base map. Every mode renders the\n * landmarks as maplibre-style fill-extrusion buildings:\n * - `inherited`: mirrors the colour, opacity and vertical gradient of the basemap 3D building layer (default).\n * - `dark`: uses the 3D building look of the SDK's standard dark style.\n * - `light`: uses the 3D building look of the SDK's standard light style.\n *\n * @group Landmarks 3D\n */\nexport type Landmarks3DDisplayMode = 'inherited' | 'dark' | 'light';\n\n/**\n * The fill-extrusion look landmarks are shaded with: a 3D building layer's colour,\n * `fill-extrusion-opacity` and `fill-extrusion-vertical-gradient`.\n *\n * @group Landmarks 3D\n */\nexport type BuildingMaterial = {\n diffuseColor: string;\n opacity: number;\n verticalGradient: boolean;\n};\n\n// The `3D - Building` look of the SDK's standard light/dark styles.\nconst STANDARD_LIGHT_BUILDINGS: BuildingMaterial = {\n diffuseColor: 'hsl(38, 6%, 90%)',\n opacity: 0.7,\n verticalGradient: true,\n};\nconst STANDARD_DARK_BUILDINGS: BuildingMaterial = {\n diffuseColor: 'hsl(210, 9%, 17%)',\n opacity: 0.7,\n verticalGradient: true,\n};\n\n/**\n * Resolves a display mode to the material to apply on the models layer.\n *\n * @param mode - The display mode to resolve.\n * @param basemapMaterial - What the `inherited` mode could read off the basemap building layer.\n * The standard light look fills in whatever it lacks, and all of it when `null` (the layer is\n * absent, or its colour does not resolve to one value).\n *\n * @group Landmarks 3D\n */\nexport const resolveDisplayMode = (\n mode: Landmarks3DDisplayMode,\n basemapMaterial: Partial<BuildingMaterial> | null,\n): BuildingMaterial => {\n switch (mode) {\n case 'dark':\n return STANDARD_DARK_BUILDINGS;\n case 'light':\n return STANDARD_LIGHT_BUILDINGS;\n default:\n return { ...STANDARD_LIGHT_BUILDINGS, ...basemapMaterial };\n }\n};\n","import { TomTomConfig } from '@tomtom-org/maps-sdk/core';\n\nconst LANDMARKS_API_VERSION = 2;\n\n/**\n * Builds the TomTom Orbis 3D Landmarks tile URL template.\n *\n * @remarks\n * The API serves meshopt-compressed GLB tiles with KTX2/Basis textures and requires\n * an API key with Orbis 3D Landmarks entitlements (still in Private Preview).\n *\n * @group Landmarks 3D\n */\nexport const buildLandmarksTileURL = (): string => {\n const { commonBaseURL, apiKey } = TomTomConfig.instance.get();\n // Proxy mode passes apiKey='' and lets the proxy inject the real key server-side —\n // skip the param so the proxy key never reaches the browser.\n const keyParam = apiKey ? `key=${apiKey}&` : '';\n return `${commonBaseURL}/maps/orbis/display/3d/landmarks/tile/{z}/{x}/{y}?${keyParam}apiVersion=${LANDMARKS_API_VERSION}`;\n};\n","import {\n Color,\n CustomBlending,\n DoubleSide,\n FrontSide,\n OneFactor,\n OneMinusSrcAlphaFactor,\n ShaderMaterial,\n SRGBColorSpace,\n type Texture,\n Vector3,\n} from 'three';\n\n/**\n * Name of the per-vertex gradient attribute baked into landmark geometry: maplibre's\n * `(t + base) * pow(height / 150.0, 0.5)` vertical-gradient term, precomputed per vertex.\n *\n * @group Landmarks 3D\n */\nexport const GRADIENT_ATTRIBUTE = 'fillExtrusionGradient';\n\nconst VERTEX_SHADER = /* glsl */ `\n attribute float ${GRADIENT_ATTRIBUTE};\n varying vec3 vWorldNormal;\n varying float vGradient;\n #ifdef USE_ALPHA_MASK\n varying vec2 vUv;\n #endif\n\n void main() {\n vWorldNormal = normalize(mat3(modelMatrix) * normal);\n vGradient = ${GRADIENT_ATTRIBUTE};\n #ifdef USE_ALPHA_MASK\n vUv = uv;\n #endif\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n`;\n\n// Samples the as-authored texture alpha so untextured shading keeps the mesh's carved silhouette.\nconst ALPHA_MASK_FRAGMENT_CHUNK = /* glsl */ `\n #ifdef USE_ALPHA_MASK\n uniform sampler2D alphaMask;\n uniform float alphaMaskCutoff;\n varying vec2 vUv;\n #endif\n`;\n\nconst ALPHA_MASK_MAIN_CHUNK = /* glsl */ `\n #ifdef USE_ALPHA_MASK\n if (texture2D(alphaMask, vUv).a < alphaMaskCutoff) {\n discard;\n }\n #endif\n`;\n\n// Term-for-term replica of maplibre's fill_extrusion shading, in sRGB on the raw paint colour, so\n// facades match the basemap buildings from every angle. Like maplibre, the vertical gradient only\n// darkens walls with a y normal component.\nconst FRAGMENT_SHADER = /* glsl */ `\n uniform vec3 diffuseColor;\n uniform vec3 lightPosition;\n uniform float lightIntensity;\n uniform vec3 lightColor;\n uniform float layerOpacity;\n uniform float verticalGradient;\n varying vec3 vWorldNormal;\n varying float vGradient;\n ${ALPHA_MASK_FRAGMENT_CHUNK}\n\n void main() {\n ${ALPHA_MASK_MAIN_CHUNK}\n vec3 normal = normalize(vWorldNormal);\n // Double-sided (alpha-masked) surfaces show their interior through carved openings;\n // flip backface normals so those interiors shade like the facade instead of going dark.\n if (!gl_FrontFacing) {\n normal = -normal;\n }\n // Extrusions have only vertical walls and flat roofs, so a sloped landmark roof would shade\n // darker than every basemap roof around it: up-facing normals light like a flat roof, the\n // rest like a wall, blended for curved surfaces.\n vec3 flattened = vec3(normal.xy, 0.0);\n vec3 wallNormal = flattened / max(length(flattened), 1e-5);\n float upness = smoothstep(0.4, 0.8, normal.z);\n normal = normalize(mix(wallNormal, vec3(0.0, 0.0, 1.0), upness));\n float colorValue = dot(diffuseColor, vec3(0.2126, 0.7152, 0.0722));\n vec3 color = diffuseColor + vec3(0.03);\n float directional = clamp(dot(normal, lightPosition), 0.0, 1.0);\n directional = mix(1.0 - lightIntensity, max(1.0 - colorValue + lightIntensity, 1.0), directional);\n if (abs(normal.y) > 0.001) {\n directional *=\n (1.0 - verticalGradient) +\n verticalGradient * clamp(vGradient, mix(0.7, 0.98, 1.0 - lightIntensity), 1.0);\n }\n vec3 shaded = clamp(\n color * directional * lightColor,\n mix(vec3(0.0), vec3(0.3), vec3(1.0) - lightColor),\n vec3(1.0)\n );\n gl_FragColor = vec4(shaded, 1.0) * layerOpacity;\n }\n`;\n\n// Maplibre's default style light position [1.15, 210, 30] as a cartesian vector.\nconst DEFAULT_LIGHT_POSITION = new Vector3(0.2875, -0.498, 0.996);\n\nconst parsedColor = new Color();\nconst srgbColor = new Color();\n\n/**\n * Alpha mask carried over from a landmark mesh's as-authored texture.\n *\n * Landmark meshes are coarse hulls (e.g. a wall is one rectangle) whose realistic\n * silhouette is carved out by the texture's alpha channel. Untextured shading must keep\n * sampling that alpha and discard the carved-away fragments, otherwise the raw hull\n * shape gets coloured in.\n *\n * @group Landmarks 3D\n */\nexport type FillExtrusionAlphaMask = {\n /** Texture whose alpha channel carves the mesh silhouette (the mesh's base colour map). */\n map: Texture;\n /** Fragments with alpha below this are discarded. */\n cutoff: number;\n};\n\n/**\n * Material that shades untextured landmark meshes exactly like maplibre-gl shades\n * `fill-extrusion` buildings, so they blend in with the basemap building layer.\n *\n * Colour and opacity premultiply like maplibre's `v_color *= u_opacity`, paired with\n * (ONE, ONE_MINUS_SRC_ALPHA) blending. Backfaces are culled like maplibre's extrusion\n * rendering, except for alpha-masked meshes whose cutout surfaces must stay visible from\n * both sides; pair with {@link FillExtrusionDepthMaterial} as a depth prepass so that\n * translucent landmarks blend with the basemap exactly once per pixel.\n *\n * @group Landmarks 3D\n */\nexport class FillExtrusionMaterial extends ShaderMaterial {\n constructor(alphaMask?: FillExtrusionAlphaMask) {\n super({\n uniforms: {\n diffuseColor: { value: new Vector3(1, 1, 1) },\n lightPosition: { value: DEFAULT_LIGHT_POSITION.clone() },\n lightIntensity: { value: 0.5 },\n lightColor: { value: new Vector3(1, 1, 1) },\n layerOpacity: { value: 1 },\n verticalGradient: { value: 1 },\n ...(alphaMask && {\n alphaMask: { value: alphaMask.map },\n alphaMaskCutoff: { value: alphaMask.cutoff },\n }),\n },\n ...(alphaMask && { defines: { USE_ALPHA_MASK: '' } }),\n vertexShader: VERTEX_SHADER,\n fragmentShader: FRAGMENT_SHADER,\n side: alphaMask ? DoubleSide : FrontSide,\n transparent: true,\n blending: CustomBlending,\n blendSrc: OneFactor,\n blendDst: OneMinusSrcAlphaFactor,\n });\n }\n\n /**\n * Copies the shading uniforms (colour, light, layer opacity, vertical gradient) from another\n * instance, bringing a freshly created alpha-masked variant up to date with the shared material.\n */\n copyShadingFrom(source: FillExtrusionMaterial): void {\n (this.uniforms.diffuseColor.value as Vector3).copy(source.uniforms.diffuseColor.value as Vector3);\n (this.uniforms.lightPosition.value as Vector3).copy(source.uniforms.lightPosition.value as Vector3);\n this.uniforms.lightIntensity.value = source.uniforms.lightIntensity.value;\n (this.uniforms.lightColor.value as Vector3).copy(source.uniforms.lightColor.value as Vector3);\n this.uniforms.layerOpacity.value = source.uniforms.layerOpacity.value;\n this.uniforms.verticalGradient.value = source.uniforms.verticalGradient.value;\n }\n\n /**\n * Sets the building colour from a CSS colour string, stored as raw sRGB\n * components — maplibre shades paint colours in sRGB space, not linear.\n */\n setDiffuseColor(cssColor: string): void {\n parsedColor.setStyle(cssColor);\n parsedColor.getRGB(srgbColor, SRGBColorSpace);\n (this.uniforms.diffuseColor.value as Vector3).set(srgbColor.r, srgbColor.g, srgbColor.b);\n }\n\n setLayerOpacity(opacity: number): void {\n this.uniforms.layerOpacity.value = opacity;\n }\n\n /** Enables or disables maplibre's `fill-extrusion-vertical-gradient` wall darkening. */\n setVerticalGradient(enabled: boolean): void {\n this.uniforms.verticalGradient.value = enabled ? 1 : 0;\n }\n\n /**\n * Mirrors maplibre's fill-extrusion light uniforms: the style light position is\n * rotated by the map bearing for viewport-anchored lights and the light vector is\n * deliberately left unnormalised, exactly like maplibre's `u_lightpos`.\n *\n * @param position - The style light position, in maplibre's y-south axes.\n * @param bearingInRadians - Map bearing to rotate by; pass `0` for map-anchored lights.\n * @param intensity - The style light intensity.\n * @param color - The style light colour.\n */\n setLight(\n position: { x: number; y: number; z: number },\n bearingInRadians: number,\n intensity: number,\n color: { r: number; g: number; b: number },\n ): void {\n const cosine = Math.cos(bearingInRadians);\n const sine = Math.sin(bearingInRadians);\n (this.uniforms.lightPosition.value as Vector3).set(\n position.x * cosine - position.y * sine,\n position.x * sine + position.y * cosine,\n position.z,\n );\n this.uniforms.lightIntensity.value = intensity;\n (this.uniforms.lightColor.value as Vector3).set(color.r, color.g, color.b);\n }\n}\n\n/**\n * Depth-only prepass companion to {@link FillExtrusionMaterial}. Rendering the scene\n * with this material first fills the depth buffer with the nearest landmark surfaces,\n * so the colour pass shades each pixel exactly once — overlapping translucent walls\n * don't blend on top of each other, mirroring how maplibre's depth-writing\n * fill-extrusion pass keeps buildings from showing through one another.\n *\n * Shares {@link FillExtrusionMaterial}'s vertex shader and alpha mask so both passes\n * produce identical depth values and cut out the same fragments.\n *\n * @group Landmarks 3D\n */\nexport class FillExtrusionDepthMaterial extends ShaderMaterial {\n constructor(alphaMask?: FillExtrusionAlphaMask) {\n super({\n ...(alphaMask && {\n uniforms: {\n alphaMask: { value: alphaMask.map },\n alphaMaskCutoff: { value: alphaMask.cutoff },\n },\n defines: { USE_ALPHA_MASK: '' },\n }),\n vertexShader: VERTEX_SHADER,\n fragmentShader: /* glsl */ `\n ${ALPHA_MASK_FRAGMENT_CHUNK}\n void main() {\n ${ALPHA_MASK_MAIN_CHUNK}\n gl_FragColor = vec4(0.0);\n }\n `,\n side: alphaMask ? DoubleSide : FrontSide,\n colorWrite: false,\n });\n }\n}\n","import { MercatorCoordinate } from 'maplibre-gl';\nimport type { Mesh, Object3D } from 'three';\n\n/**\n * @ignore\n */\nexport const isMesh = (object: Object3D): object is Mesh => (object as Mesh).isMesh === true;\n\nconst EARTH_RADIUS_METRES = 6378137;\n\n/**\n * Scale from EPSG:3857 (Web Mercator) metres, centred on the equator and the prime meridian, to\n * MapLibre's mercator units, where the world spans [0..1].\n * @ignore\n */\nexport const MERCATOR_UNITS_PER_METRE = 1 / (2 * Math.PI * EARTH_RADIUS_METRES);\n\n/**\n * Converts EPSG:3857 (Web Mercator) metres to `[longitude, latitude]` degrees.\n * @ignore\n */\nexport const mercatorMetresToLngLat = (x: number, y: number): [number, number] =>\n new MercatorCoordinate(0.5 + x * MERCATOR_UNITS_PER_METRE, 0.5 - y * MERCATOR_UNITS_PER_METRE).toLngLat().toArray();\n","import type { Map as MapLibreMap } from 'maplibre-gl';\nimport {\n BufferAttribute,\n DoubleSide,\n Group,\n LoaderUtils,\n LoadingManager,\n type Mesh,\n type MeshStandardMaterial,\n type WebGLRenderer,\n} from 'three';\nimport { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js';\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';\nimport { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';\nimport { toCreasedNormals } from 'three/examples/jsm/utils/BufferGeometryUtils.js';\nimport { GRADIENT_ATTRIBUTE } from './FillExtrusionMaterial';\nimport type { ModelsSourceSpecification } from './types/modelsSpecifications';\nimport { isMesh } from './utils';\n\n// Edges sharper than 30° keep their own normals (crisp corners); smoother ones share one (no grain).\nconst CREASE_ANGLE_RADIANS = Math.PI / 6;\n\n// When a new tile lands this far from the scene origin, rebase the origin so relative\n// coordinates stay within float32 precision.\nconst REBASE_DISTANCE_METRES = 100_000;\n\n// Without a transcoderPath, KTX2 textures decode with the transcoder bundled in a lazy chunk.\nconst prepareTranscoder = async (manager: LoadingManager, transcoderPath?: string): Promise<void> => {\n if (transcoderPath !== undefined) return;\n\n const { resolveBundledTranscoderUrl } = await import('./bundledTranscoder');\n manager.setURLModifier(resolveBundledTranscoderUrl);\n};\n\n// Private maplibre-gl internals this source needs, reached via an `unknown` cast.\n// The transform hangs off the composed `_camera`, not the map itself.\ntype MapLibreTransformInternals = {\n _camera: { transform: { tileSize: number } };\n};\n\n// Element of maplibre's coveringTiles() result; exposes `.key`, `.canonical.{z,url}` and `.toString()`.\ntype CoveringTile = ReturnType<MapLibreMap['coveringTiles']>[number];\n\n/**\n * Source of tiled glTF/GLB models for a {@link ModelsLayer}.\n *\n * Tracks the tiles covering the current viewport, fetches each tile's models once\n * and keeps them in a Three.js {@link Group} that the owning layer renders.\n *\n * @group Landmarks 3D\n */\nexport class ModelsSource {\n type = 'models' as const;\n id: string;\n minzoom: number;\n maxzoom: number;\n map!: MapLibreMap;\n tiles: Array<string>;\n scene: Group;\n loadedTiles = new Set<string>();\n /**\n * EPSG:3857 origin subtracted from all mesh positions: absolute coordinates (~6·10⁶ m) exceed\n * float32 precision (~0.5 m), which makes landmark edges tremble while zooming. `null` until the\n * first tile with meshes loads.\n */\n sceneOrigin: { x: number; y: number } | null = null;\n /** Optional consumer callback fired once per successful tile load. */\n onTileLoaded?: (scene: Group, key: string) => void;\n\n private readonly ktx2Loader: KTX2Loader;\n // Started by the first tile, so a source that never fetches one never loads the transcoder chunk.\n private transcoderReady?: Promise<void>;\n private readonly gltfLoader: GLTFLoader;\n private readonly specification: ModelsSourceSpecification;\n private readonly withCredentials: boolean;\n\n constructor(id: string, options: ModelsSourceSpecification) {\n this.id = id;\n this.minzoom = options.minzoom ?? 0;\n this.maxzoom = options.maxzoom ?? 22;\n this.tiles = options.tiles;\n this.specification = options;\n this.withCredentials = options.withCredentials ?? false;\n this.scene = new Group();\n // An own manager keeps the transcoder URL modifier off three's DefaultLoadingManager.\n this.ktx2Loader = new KTX2Loader(new LoadingManager())\n .setCrossOrigin('anonymous')\n .setTranscoderPath(options.transcoderPath ?? '');\n this.gltfLoader = new GLTFLoader().setKTX2Loader(this.ktx2Loader).setMeshoptDecoder(MeshoptDecoder);\n }\n\n onAdd(map: MapLibreMap, renderer: WebGLRenderer) {\n this.map = map;\n this.ktx2Loader.detectSupport(renderer);\n }\n\n updateTiles() {\n if (this.tiles.length === 0) {\n return;\n }\n\n const coveringTileKeys = new Set<string>();\n for (const tile of this.coveringTiles()) {\n coveringTileKeys.add(tile.key);\n this.loadTile(tile);\n }\n this.showCoveringTilesOnly(coveringTileKeys);\n }\n\n // Renders only the tiles that cover the viewport, so landmarks leave the screen together with\n // the basemap buildings of the same area. Tiles that fall outside stay in memory, ready to\n // render again without a refetch.\n private showCoveringTilesOnly(coveringTileKeys: Set<string>): void {\n for (const tile of this.scene.children) {\n tile.visible = coveringTileKeys.has(tile.userData.key as string);\n }\n }\n\n // With 3D terrain, a tile on a hill is in view before its sea-level footprint is: MapLibre's own\n // sources pass the terrain so tiles are bounded by their elevation range. The public options type\n // leaves `terrain` out, but `coveringTiles` forwards it, so the options go in as a variable.\n private coveringTiles(): CoveringTile[] {\n const transform = (this.map as unknown as MapLibreTransformInternals)._camera.transform;\n const options = {\n tileSize: transform.tileSize,\n minzoom: this.minzoom,\n maxzoom: this.maxzoom,\n terrain: this.map.terrain,\n };\n return this.map.coveringTiles(options);\n }\n\n // The source loads a single zoom level, so tiles never overlap; each is fetched once.\n private loadTile(tile: CoveringTile): void {\n if (this.loadedTiles.has(tile.key)) {\n return;\n }\n this.loadedTiles.add(tile.key);\n\n const url = tile.canonical.url(this.tiles, this.map.getPixelRatio(), this.specification.scheme);\n this.fetchTile(url)\n .then((scene) => {\n if (!scene) {\n return; // expected empty tile (no landmark / not produced)\n }\n prepareTileMeshes(scene);\n this.rebaseToSceneOrigin(scene);\n scene.userData = { isTile: true, key: tile.key };\n this.scene.add(scene);\n this.onTileLoaded?.(scene, tile.key);\n })\n .catch((error: { message?: string }) => {\n console.warn(`Problem with loading ${tile.toString()}: ${error?.message ?? 'unknown'}`);\n });\n }\n\n // Rewrites the tile's meshes from absolute EPSG:3857 positions to positions relative to\n // `sceneOrigin`, which ModelsLayer.alignCameraToMap folds back in.\n private rebaseToSceneOrigin(tileScene: Group): void {\n const meshes: Mesh[] = [];\n tileScene.traverse((child) => {\n if (isMesh(child)) {\n meshes.push(child);\n }\n });\n if (meshes.length === 0) {\n return;\n }\n\n const anchor = meshes[0].position;\n const anchorOrigin = { x: Math.round(anchor.x), y: Math.round(anchor.y) };\n if (!this.sceneOrigin) {\n this.sceneOrigin = anchorOrigin;\n } else if (Math.hypot(anchor.x - this.sceneOrigin.x, anchor.y - this.sceneOrigin.y) > REBASE_DISTANCE_METRES) {\n // The viewport moved far from the origin (e.g. a fly-to across cities): move the\n // origin and shift the already-loaded meshes accordingly.\n const deltaX = anchorOrigin.x - this.sceneOrigin.x;\n const deltaY = anchorOrigin.y - this.sceneOrigin.y;\n this.scene.traverse((child) => {\n if (isMesh(child)) {\n child.position.x -= deltaX;\n child.position.y -= deltaY;\n }\n });\n this.sceneOrigin = anchorOrigin;\n }\n for (const mesh of meshes) {\n mesh.position.x -= this.sceneOrigin.x;\n mesh.position.y -= this.sceneOrigin.y;\n }\n }\n\n // Fetch via the global `fetch` (not three.js' XHR) so it rides any installed\n // session wrapper — e.g. the demos-proxy cookie gate — then parse the GLB.\n private async fetchTile(url: string): Promise<Group | null> {\n const response = await fetch(url, this.withCredentials ? { credentials: 'include' } : undefined);\n // 204 = no landmark in this tile, 404 = tile not produced — both expected.\n if (response.status === 204 || response.status === 404) {\n return null;\n }\n if (!response.ok) {\n throw new Error(`tile request failed with ${response.status}`);\n }\n const buffer = await response.arrayBuffer();\n this.transcoderReady ??= prepareTranscoder(this.ktx2Loader.manager, this.specification.transcoderPath);\n await this.transcoderReady;\n // Resolve relative resources against the tile URL, as GLTFLoader.load would.\n const { scene } = await this.gltfLoader.parseAsync(buffer, LoaderUtils.extractUrlBase(url));\n return scene;\n }\n}\n\n// Stores each mesh's authored material and re-creases its normals so untextured shading keeps crisp edges.\nconst prepareTileMeshes = (scene: Group): void => {\n scene.traverse((child) => {\n if (!isMesh(child)) {\n return;\n }\n\n const material = child.material as MeshStandardMaterial;\n material.alphaTest = 0.1;\n material.side = DoubleSide;\n child.userData.originalMaterial = material;\n child.geometry = toCreasedNormals(child.geometry, CREASE_ANGLE_RADIANS);\n bakeGradientAttribute(child);\n });\n};\n\n// Bakes the GRADIENT_ATTRIBUTE term per vertex, with base = 0 and t = z / height.\nconst bakeGradientAttribute = (mesh: Mesh): void => {\n const geometry = mesh.geometry;\n const positions = geometry.getAttribute('position');\n geometry.computeBoundingBox();\n const height = geometry.boundingBox?.max.z ?? 0;\n const gradientScale = height > 0 ? Math.sqrt(height / 150) / height : 0;\n const gradient = new Float32Array(positions.count);\n for (let index = 0; index < positions.count; index++) {\n gradient[index] = positions.getZ(index) * gradientScale;\n }\n geometry.setAttribute(GRADIENT_ATTRIBUTE, new BufferAttribute(gradient, 1));\n};\n","import type { CustomLayerInterface, CustomRenderMethodInput, Map as MapLibreMap } from 'maplibre-gl';\nimport {\n AmbientLight,\n Camera,\n DirectionalLight,\n Group,\n Matrix4,\n type Mesh,\n type MeshStandardMaterial,\n Scene,\n type Texture,\n WebGLRenderer,\n} from 'three';\nimport { FillExtrusionDepthMaterial, FillExtrusionMaterial } from './FillExtrusionMaterial';\nimport { ModelsSource } from './ModelsSource';\nimport type { ModelsLayerSpecification, ModelsSourceSpecification } from './types/modelsSpecifications';\nimport { isMesh, MERCATOR_UNITS_PER_METRE, mercatorMetresToLngLat } from './utils';\n\nconst TRANSLATE_X = 0.5;\nconst TRANSLATE_Y = 0.5;\n\nconst toRadians = (degrees: number): number => (degrees * Math.PI) / 180;\n\n// Extra sinking below the sampled terrain: keeps walls grounded across unsampled dips and\n// the flat bottom cap below the surface, where it cannot z-fight with the ground.\nconst TERRAIN_BURIAL_MARGIN_METRES = 0.5;\n\n// Subset of the private maplibre-gl internals this layer needs, reached via an `unknown` cast.\n// The transform hangs off the composed `_camera`, not the map itself.\ntype MapLibreRenderInternals = {\n _camera: { transform: { tileZoom: number; bearingInRadians?: number } };\n style: {\n light?: {\n // The evaluated 'position' property is the raw spherical [radial, azimuthal, polar]\n // array; this getter converts it to cartesian [x, y, z].\n getCartesianPosition?(): [number, number, number];\n properties?: {\n get(key: 'intensity'): number;\n get(key: 'anchor'): 'map' | 'viewport';\n get(key: 'color'): { r: number; g: number; b: number };\n };\n };\n };\n};\n\n/**\n * MapLibre custom layer that renders tiled glTF/GLB models with Three.js.\n *\n * The layer owns a {@link ModelsSource} that streams model tiles for the current\n * viewport, renders them as maplibre-style fill-extrusion buildings, and keeps its\n * lighting in sync with the map style light.\n *\n * @group Landmarks 3D\n */\nexport class ModelsLayer implements CustomLayerInterface {\n id: string;\n type = 'custom' as const;\n renderingMode = '3d' as const;\n\n renderer!: WebGLRenderer;\n scene!: Scene;\n camera!: Camera;\n tiles: Group;\n directionalLight!: DirectionalLight;\n ambientLight!: AmbientLight;\n map!: MapLibreMap;\n source: ModelsSource;\n minzoom = 0;\n maxzoom = 22;\n visible = true;\n\n diffuseColor = '#ffffff';\n\n private readonly fillExtrusionMaterial = new FillExtrusionMaterial();\n private readonly depthPrepassMaterial = new FillExtrusionDepthMaterial();\n // Per-texture alpha-masked variants so untextured shading keeps each mesh's carved silhouette.\n private readonly maskedFillExtrusionMaterials = new Map<\n Texture,\n { color: FillExtrusionMaterial; depth: FillExtrusionDepthMaterial }\n >();\n\n constructor(options: ModelsLayerSpecification, source: ModelsSourceSpecification) {\n this.id = options.id;\n this.source = new ModelsSource(this.id, source);\n this.tiles = this.source.scene;\n this.updateProperties(options);\n }\n\n updateProperties(options: ModelsLayerSpecification) {\n this.visible = options.layout?.visibility !== 'none';\n this.diffuseColor = options.paint?.['models-base-color'] ?? '#ffffff';\n this.minzoom = options.minzoom ?? 0;\n this.maxzoom = options.maxzoom ?? 22;\n }\n\n setDirectionalLight(radius: number, phi: number, theta: number) {\n this.directionalLight.position.setFromSphericalCoords(radius, toRadians(phi), toRadians(theta));\n }\n\n onAdd(map: MapLibreMap, gl: WebGLRenderingContext | WebGL2RenderingContext) {\n this.map = map;\n\n this.scene = new Scene();\n this.tiles = this.source.scene;\n this.camera = new Camera();\n\n this.directionalLight = new DirectionalLight(0xffffff, 0.5);\n this.setDirectionalLight(1.15, 90, 0);\n this.scene.add(this.directionalLight);\n\n this.ambientLight = new AmbientLight(0xffffff, 0.1);\n this.scene.add(this.ambientLight);\n\n this.scene.add(this.tiles);\n\n this.renderer = new WebGLRenderer({\n canvas: map.getCanvas(),\n context: gl,\n antialias: true,\n });\n this.renderer.autoClear = false;\n\n this.source.onAdd(map, this.renderer);\n }\n\n render(_gl: WebGLRenderingContext | WebGL2RenderingContext, options: CustomRenderMethodInput) {\n const internals = this.map as unknown as MapLibreRenderInternals;\n const tileZoom = internals._camera.transform.tileZoom;\n const inZoomRange = tileZoom >= this.minzoom && tileZoom <= this.maxzoom;\n this.tiles.visible = this.visible && inZoomRange;\n\n if (!(this.visible && inZoomRange)) {\n return;\n }\n\n this.syncLighting();\n this.source.updateTiles();\n this.groundLandmarksOnTerrain();\n this.alignCameraToMap(options);\n\n this.renderer.resetState();\n if (this.needsTranslucentPrepass()) {\n this.renderDepthPrepass();\n }\n\n this.applyMaterials();\n this.renderer.render(this.scene, this.camera);\n this.map.triggerRepaint();\n }\n\n // Landmarks are authored with their base at sea level; with 3D terrain the ground rises to\n // the (exaggeration-scaled) elevation. Grounds every landmark each frame, since terrain tiles\n // load progressively and exaggeration can change at runtime.\n private groundLandmarksOnTerrain() {\n for (const tile of this.tiles.children) {\n if (!tile.visible) {\n continue; // off-viewport tiles stay loaded; grounding them each frame costs terrain queries\n }\n tile.traverse((child) => {\n if (isMesh(child)) {\n this.groundMeshOnTerrain(child);\n }\n });\n }\n }\n\n // Lifts one landmark to the LOWEST terrain elevation across its footprint, minus a small\n // burial margin: no wall ever floats, and the bottom cap stays below the surface. Grounding is\n // per landmark because terrain can vary by tens of metres across a tile.\n private groundMeshOnTerrain(mesh: Mesh) {\n const samplePoints = this.terrainSamplePoints(mesh);\n if (!samplePoints) {\n return;\n }\n\n let lowestElevation: number | null = null;\n for (const samplePoint of samplePoints) {\n const elevation = this.map.queryTerrainElevation(samplePoint);\n if (elevation === null) {\n // Terrain is disabled — restore the authored sea-level base.\n mesh.position.z = 0;\n return;\n }\n if (lowestElevation === null || elevation < lowestElevation) {\n lowestElevation = elevation;\n }\n }\n if (lowestElevation !== null) {\n mesh.position.z = lowestElevation - TERRAIN_BURIAL_MARGIN_METRES;\n }\n }\n\n // `[longitude, latitude]` points spread over the mesh footprint (center, corners, edge\n // midpoints — long walls can dip between corners), derived once from its bounding box.\n private terrainSamplePoints(mesh: Mesh): [number, number][] | null {\n let samplePoints = mesh.userData.terrainSamplePoints as [number, number][] | undefined;\n if (!samplePoints) {\n if (!mesh.geometry.boundingBox) {\n mesh.geometry.computeBoundingBox();\n }\n const bounds = mesh.geometry.boundingBox;\n if (!bounds || bounds.isEmpty()) {\n return null;\n }\n\n // Mesh positions are relative to the scene origin; the samples need absolute coordinates.\n const anchorX = mesh.position.x + this.sceneOrigin.x;\n const anchorY = mesh.position.y + this.sceneOrigin.y;\n const centerX = (bounds.min.x + bounds.max.x) / 2;\n const centerY = (bounds.min.y + bounds.max.y) / 2;\n samplePoints = [\n [centerX, centerY],\n [bounds.min.x, bounds.min.y],\n [bounds.min.x, bounds.max.y],\n [bounds.max.x, bounds.min.y],\n [bounds.max.x, bounds.max.y],\n [centerX, bounds.min.y],\n [centerX, bounds.max.y],\n [bounds.min.x, centerY],\n [bounds.max.x, centerY],\n ].map(([offsetX, offsetY]) => mercatorMetresToLngLat(anchorX + offsetX, anchorY + offsetY));\n mesh.userData.terrainSamplePoints = samplePoints;\n }\n return samplePoints;\n }\n\n // maplibre hands custom layers `defaultProjectionData.mainMatrix`, which takes mercator 0..1 for x/y\n // and — in `3d` rendering mode — a conformal z in those same mercator units, not metres. Metre\n // altitudes therefore need the mercator scale factor at the current latitude, hence the extra\n // 1/cos(lat) that z carries and x/y don't.\n private alignCameraToMap(options: CustomRenderMethodInput) {\n const zScale = MERCATOR_UNITS_PER_METRE / Math.cos(toRadians(this.map.getCenter().lat));\n // The scene origin (see ModelsSource.sceneOrigin) is folded in here, in float64.\n const translateX = TRANSLATE_X + MERCATOR_UNITS_PER_METRE * this.sceneOrigin.x;\n const translateY = TRANSLATE_Y - MERCATOR_UNITS_PER_METRE * this.sceneOrigin.y;\n const mapLibreScale = new Matrix4();\n mapLibreScale.set(\n MERCATOR_UNITS_PER_METRE,\n 0,\n 0,\n translateX,\n 0,\n -MERCATOR_UNITS_PER_METRE,\n 0,\n translateY,\n 0,\n 0,\n zScale,\n 0,\n 0,\n 0,\n 0,\n 1,\n );\n const projection = new Matrix4().fromArray(options.defaultProjectionData.mainMatrix as ArrayLike<number>);\n this.camera.projectionMatrix.copy(projection).multiply(mapLibreScale);\n }\n\n // Zero until the first tile with meshes sets the source's origin.\n private get sceneOrigin(): NonNullable<ModelsSource['sceneOrigin']> {\n return this.source.sceneOrigin ?? { x: 0, y: 0 };\n }\n\n // Translucent shading needs a depth pass so overlapping walls blend with the basemap once per pixel.\n private needsTranslucentPrepass(): boolean {\n const layerOpacity = this.fillExtrusionMaterial.uniforms.layerOpacity.value as number;\n return layerOpacity < 1;\n }\n\n // Per-mesh (not overrideMaterial) so each mesh's alpha mask carves the prepass depth too.\n private renderDepthPrepass() {\n this.tiles.traverse((child) => {\n if (isMesh(child)) {\n child.material = this.maskedFillExtrusionVariant(child)?.depth ?? this.depthPrepassMaterial;\n }\n });\n this.renderer.render(this.scene, this.camera);\n }\n\n setOpacity(value: number) {\n this.forEachFillExtrusionMaterial((material) => material.setLayerOpacity(value));\n }\n\n /** Mirrors the basemap's fill-extrusion-vertical-gradient on the landmark shading. */\n setVerticalGradient(enabled: boolean) {\n this.forEachFillExtrusionMaterial((material) => material.setVerticalGradient(enabled));\n }\n\n getMaterial(mesh: Mesh): FillExtrusionMaterial {\n return this.maskedFillExtrusionVariant(mesh)?.color ?? this.fillExtrusionMaterial;\n }\n\n // Fill-extrusion variant masked by the mesh's own texture alpha; null for untextured meshes.\n private maskedFillExtrusionVariant(mesh: Mesh) {\n const original = mesh.userData.originalMaterial as MeshStandardMaterial | undefined;\n const map = original?.map;\n if (!map) {\n return null;\n }\n\n let variant = this.maskedFillExtrusionMaterials.get(map);\n if (!variant) {\n const alphaMask = { map, cutoff: original.alphaTest || 0.1 };\n variant = {\n color: new FillExtrusionMaterial(alphaMask),\n depth: new FillExtrusionDepthMaterial(alphaMask),\n };\n variant.color.copyShadingFrom(this.fillExtrusionMaterial);\n this.maskedFillExtrusionMaterials.set(map, variant);\n }\n return variant;\n }\n\n private forEachFillExtrusionMaterial(apply: (material: FillExtrusionMaterial) => void) {\n apply(this.fillExtrusionMaterial);\n this.maskedFillExtrusionMaterials.forEach((variant) => apply(variant.color));\n }\n\n private syncLighting() {\n // The maplibre style light is private API; fall back to the onAdd defaults when absent.\n const internals = this.map as unknown as MapLibreRenderInternals;\n const light = internals.style?.light;\n const properties = light?.properties;\n const cartesianPosition = light?.getCartesianPosition?.();\n if (!properties || !cartesianPosition) {\n return;\n }\n\n try {\n const intensity = properties.get('intensity');\n // `* π` cancels Three.js's internal 1/π Lambert factor, matching maplibre's shading range.\n this.directionalLight.intensity = intensity * Math.PI;\n this.ambientLight.intensity = (1 - intensity) * Math.PI;\n\n // The cartesian style light maps into the scene's x-east/y-north frame with x negated.\n const position = { x: -cartesianPosition[0], y: cartesianPosition[1], z: cartesianPosition[2] };\n this.directionalLight.position.set(position.x, position.y, position.z);\n\n // Viewport-anchored lights counter-rotate with the bearing so camera-facing walls\n // stay lit at every bearing, matching maplibre's rendered output.\n const anchor = properties.get('anchor');\n const bearingInRadians = anchor === 'viewport' ? -(internals._camera.transform.bearingInRadians ?? 0) : 0;\n const color = properties.get('color');\n this.forEachFillExtrusionMaterial((material) =>\n material.setLight(position, bearingInRadians, intensity, color),\n );\n } catch {\n // Internal API surface changed — keep the configured defaults.\n }\n }\n\n private applyMaterials() {\n this.forEachFillExtrusionMaterial((material) => material.setDiffuseColor(this.diffuseColor));\n\n this.tiles.traverse((child) => {\n if (isMesh(child)) {\n child.material = this.getMaterial(child);\n }\n });\n }\n}\n","import { isProxyCredentialsMode } from '@tomtom-org/maps-sdk/core';\nimport { LayerFilterComposer, mapStyleLayerIDs, type TomTomMap } from '@tomtom-org/maps-sdk/map';\nimport type { ExpressionFilterSpecification } from 'maplibre-gl';\nimport { type BuildingMaterial, type Landmarks3DDisplayMode, resolveDisplayMode } from './displayMode';\nimport { buildLandmarksTileURL } from './landmarksTileURL';\nimport { ModelsLayer } from './ModelsLayer';\nimport type { ModelsLayerSpecification, ModelsSourceSpecification } from './types/modelsSpecifications';\n\n// Load landmark tiles at a single zoom level: the meshes are full-detail at this zoom and upscale\nconst SOURCE_ZOOM = 15;\n\nconst DEFAULT_LAYER_ID = 'orbis-3d-landmarks';\nconst DEFAULT_LAYER_MIN_ZOOM = 10;\nconst DEFAULT_LAYER_MAX_ZOOM = 22;\n\n// Filters out basemap extruded buildings that overlap a landmark, so they don't clip through its mesh.\nconst DEFAULT_BASEMAP_BUILDING_LAYER_ID = '3D - Building';\nconst NO_LANDMARK_FILTER_CLAUSE: ExpressionFilterSpecification = ['!', ['coalesce', ['get', 'has_landmark'], false]];\n// This plugin's key with the shared filter composer.\nconst LANDMARKS_CONTRIBUTOR = 'landmarks3d.buildings';\n\n// Resolves `fill-extrusion-color` to one CSS colour, for `match` expressions, the last operand is the default.\nconst extractColorFromPaint = (paintColor: unknown): string | null => {\n if (typeof paintColor === 'string') {\n return paintColor;\n }\n\n if (Array.isArray(paintColor) && paintColor[0] === 'match') {\n const fallback = paintColor.at(-1);\n if (typeof fallback === 'string') {\n return fallback;\n }\n }\n\n return null;\n};\n\n/**\n * Configuration for the {@link Landmarks3D} plugin.\n *\n * @group Landmarks 3D\n */\nexport type Landmarks3DOptions = {\n /**\n * Initial display mode for the landmarks. Can also be changed at runtime with\n * {@link Landmarks3D.setDisplayMode}.\n * @defaultValue `'inherited'`\n */\n displayMode?: Landmarks3DDisplayMode;\n /**\n * Whether the landmarks are initially visible. Can also be changed at runtime with\n * {@link Landmarks3D.setVisible}.\n * @defaultValue `true`\n */\n visible?: boolean;\n /**\n * Minimum zoom at which landmarks are rendered.\n * @defaultValue The basemap 3D building layer's minimum zoom, so landmarks appear together\n * with it (falls back to `10` when that layer is absent).\n */\n minZoom?: number;\n /**\n * Maximum zoom at which landmarks are rendered.\n * @defaultValue The basemap 3D building layer's maximum zoom, so landmarks disappear together\n * with it (falls back to `22` when that layer is absent).\n */\n maxZoom?: number;\n /**\n * Send `credentials: 'include'` with each tile request so a proxy session\n * cookie travels with it. By default this tracks the SDK's proxy config, so\n * tiles behave like every other SDK request; set it explicitly only to\n * override that.\n * @defaultValue Auto-detected — `true` when the SDK is configured for a\n * credentialed proxy (no `apiKey` + non-default `commonBaseURL`).\n */\n withCredentials?: boolean;\n};\n\n/**\n * Renders TomTom Orbis 3D Landmarks (high-detail building meshes) on a {@link TomTomMap}.\n *\n * The plugin streams GLB tiles from the Orbis 3D Landmarks API (Private Preview),\n * renders them with Three.js via a MapLibre custom layer, filters overlapping\n * extruded basemap buildings out of the style, and keeps everything restored\n * across map style changes.\n *\n * @remarks\n * Requires an API key with Orbis 3D Landmarks entitlements and a map style that\n * shows extruded 3D buildings for the exclusion filter and `inherited` mode to\n * have an effect.\n *\n * @example\n * ```typescript\n * import { Landmarks3D } from '@tomtom-org/maps-sdk-plugin-landmarks-3d';\n *\n * // assume `map` is your initialized TomTomMap instance\n * const landmarks = new Landmarks3D(map);\n *\n * await landmarks.setDisplayMode('inherited');\n * ```\n *\n * @group Landmarks 3D\n */\nexport class Landmarks3D {\n private readonly map: TomTomMap;\n private readonly layerID = DEFAULT_LAYER_ID;\n private readonly basemapBuildingLayerID = DEFAULT_BASEMAP_BUILDING_LAYER_ID;\n private readonly modelsLayer: ModelsLayer;\n // Explicit zoom-range overrides; when unset, the range tracks the basemap 3D building layer.\n private readonly layerMinZoom?: number;\n private readonly layerMaxZoom?: number;\n private displayMode: Landmarks3DDisplayMode;\n\n /**\n * Creates the plugin bound to a TomTom map and starts rendering landmarks\n * as soon as the map is ready.\n *\n * @param map - The TomTom map instance to render landmarks on.\n * @param options - Optional configuration.\n */\n constructor(map: TomTomMap, options: Landmarks3DOptions = {}) {\n this.map = map;\n this.layerMinZoom = options.minZoom;\n this.layerMaxZoom = options.maxZoom;\n this.displayMode = options.displayMode ?? 'inherited';\n\n const layerSpecification: ModelsLayerSpecification = {\n id: this.layerID,\n type: 'models',\n source: this.layerID,\n minzoom: options.minZoom ?? DEFAULT_LAYER_MIN_ZOOM,\n maxzoom: options.maxZoom ?? DEFAULT_LAYER_MAX_ZOOM,\n };\n const sourceSpecification: ModelsSourceSpecification = {\n type: 'models',\n tiles: [buildLandmarksTileURL()],\n minzoom: SOURCE_ZOOM,\n maxzoom: SOURCE_ZOOM,\n withCredentials: options.withCredentials ?? isProxyCredentialsMode(),\n };\n this.modelsLayer = new ModelsLayer(layerSpecification, sourceSpecification);\n this.modelsLayer.visible = options.visible ?? true;\n\n this.map.addStyleChangeHandler({\n onStyleChanged: () => this.install(),\n });\n this.installWhenReady();\n }\n\n // Installs the layer as soon as the map is ready\n private installWhenReady(): void {\n if (this.map.mapReady) {\n this.install();\n } else {\n this.map.mapLibreMap.once('styledata', () => this.installWhenReady());\n }\n }\n\n /**\n * The underlying MapLibre custom layer, for advanced use beyond this facade.\n */\n get layer(): ModelsLayer {\n return this.modelsLayer;\n }\n\n /**\n * The currently applied display mode.\n */\n getDisplayMode(): Landmarks3DDisplayMode {\n return this.displayMode;\n }\n\n /**\n * Changes how landmarks are rendered relative to the base map.\n *\n * @param mode - The display mode to apply.\n * @returns A promise that resolves once the mode has been applied.\n */\n async setDisplayMode(mode: Landmarks3DDisplayMode): Promise<void> {\n this.displayMode = mode;\n // Before the map is ready the layer isn't installed; install() applies the stored\n // mode once it runs, so simply recording it above is enough in that case.\n if (this.map.mapReady) {\n this.applyDisplayMode();\n }\n }\n\n /**\n * Whether the landmarks are currently visible.\n */\n isVisible(): boolean {\n return this.modelsLayer.visible;\n }\n\n /**\n * Shows or hides the landmarks layer.\n *\n * @param visible - `true` to show landmarks, `false` to hide them.\n * @returns A promise that resolves once the visibility has been applied.\n */\n async setVisible(visible: boolean): Promise<void> {\n this.modelsLayer.visible = visible;\n // When the map isn't ready yet the layer is added (with this visibility) by\n // install(); only an already-installed layer needs an explicit repaint here.\n if (this.map.mapReady) {\n this.map.mapLibreMap.triggerRepaint();\n }\n }\n\n // (Re-)adds the layer and re-applies filter + display mode on first readiness and after style changes.\n private install(): void {\n const mapLibreMap = this.map.mapLibreMap;\n if (!mapLibreMap.getLayer(this.layerID)) {\n mapLibreMap.addLayer(this.modelsLayer, this.landmarkInsertBeforeID());\n }\n\n // The standard styles ship the 3D building layer hidden; show it so landmarks get a 3D city context.\n if (mapLibreMap.getLayer(this.basemapBuildingLayerID)) {\n mapLibreMap.setLayoutProperty(this.basemapBuildingLayerID, 'visibility', 'visible');\n }\n\n this.alignZoomRangeWithBasemapBuildings();\n this.excludeLandmarkBuildingsFromBasemap();\n\n // Late-arriving tiles load with opaque materials; re-apply non-opaque looks to them.\n this.modelsLayer.source.onTileLoaded = () => {\n if (this.resolveCurrentMaterialState().opacity < 1) {\n this.applyDisplayMode();\n }\n };\n\n this.applyDisplayMode();\n }\n\n // Insert before the POI anchor so POIs draw over the meshes and roads stay below; undefined = on top.\n private landmarkInsertBeforeID(): string | undefined {\n const poiAnchor = mapStyleLayerIDs.poi;\n return this.map.mapLibreMap.getLayer(poiAnchor) ? poiAnchor : undefined;\n }\n\n // Tracks the basemap 3D building layer's zoom range so landmarks appear and disappear together with\n // it, unless the caller pinned an explicit min/max zoom. No-op when the building layer is absent.\n private alignZoomRangeWithBasemapBuildings(): void {\n const buildingLayer = this.map.mapLibreMap.getLayer(this.basemapBuildingLayerID);\n if (!buildingLayer) {\n return;\n }\n\n this.modelsLayer.minzoom = this.layerMinZoom ?? buildingLayer.minzoom ?? DEFAULT_LAYER_MIN_ZOOM;\n this.modelsLayer.maxzoom = this.layerMaxZoom ?? buildingLayer.maxzoom ?? DEFAULT_LAYER_MAX_ZOOM;\n }\n\n private excludeLandmarkBuildingsFromBasemap(): void {\n // The composer owns the layer's filter: registering under this plugin's key adds the clause\n // once however often `install` runs, and keeps it beside what the style and any other\n // contributor filter the layer by.\n LayerFilterComposer.for(this.map).setClause(\n this.basemapBuildingLayerID,\n LANDMARKS_CONTRIBUTOR,\n NO_LANDMARK_FILTER_CLAUSE,\n );\n }\n\n // Reads the basemap building look for `inherited` mode, leaving out what does not resolve to one\n // value; null when the layer is absent or its colour does not resolve.\n private readBasemapBuildingMaterial(): Partial<BuildingMaterial> | null {\n const mapLibreMap = this.map.mapLibreMap;\n if (!mapLibreMap.getLayer(this.basemapBuildingLayerID)) {\n return null;\n }\n\n const paintColor = mapLibreMap.getPaintProperty(this.basemapBuildingLayerID, 'fill-extrusion-color');\n const diffuseColor = extractColorFromPaint(paintColor);\n if (!diffuseColor) {\n return null;\n }\n\n const opacity = mapLibreMap.getPaintProperty(this.basemapBuildingLayerID, 'fill-extrusion-opacity');\n const verticalGradient = mapLibreMap.getPaintProperty(\n this.basemapBuildingLayerID,\n 'fill-extrusion-vertical-gradient',\n );\n return {\n diffuseColor,\n ...(typeof opacity === 'number' && { opacity }),\n ...(typeof verticalGradient === 'boolean' && { verticalGradient }),\n };\n }\n\n private resolveCurrentMaterialState(): BuildingMaterial {\n const basemapMaterial = this.displayMode === 'inherited' ? this.readBasemapBuildingMaterial() : null;\n return resolveDisplayMode(this.displayMode, basemapMaterial);\n }\n\n private applyDisplayMode(): void {\n const state = this.resolveCurrentMaterialState();\n this.modelsLayer.diffuseColor = state.diffuseColor;\n this.modelsLayer.setOpacity(state.opacity);\n this.modelsLayer.setVerticalGradient(state.verticalGradient);\n this.map.mapLibreMap.triggerRepaint();\n }\n}\n"],"mappings":"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"}
|
package/dist/third-party.json
CHANGED
package/package.json
CHANGED
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
"name": "@tomtom-org/maps-sdk-plugin-landmarks-3d",
|
|
3
3
|
"author": "TomTom International B.V.",
|
|
4
4
|
"license": "See in LICENSE.txt",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.8",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Plugin to render TomTom Orbis 3D Landmarks (GLB tiles) on a TomTomMap with Three.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"peerDependencies": {
|
|
10
|
-
"@tomtom-org/maps-sdk": ">=0.
|
|
11
|
-
"maplibre-gl": "^6.
|
|
10
|
+
"@tomtom-org/maps-sdk": ">=0.62.0 <1.0.0",
|
|
11
|
+
"maplibre-gl": "^6.11.2",
|
|
12
12
|
"three": "^0.170.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@tsconfig/recommended": "^1.0.13",
|
|
16
|
-
"@types/node": "^26.
|
|
16
|
+
"@types/node": "^26.6.2",
|
|
17
17
|
"@types/three": "^0.170.0",
|
|
18
18
|
"@vitest/coverage-v8": "^4.1.11",
|
|
19
|
-
"rolldown": "^1.2.
|
|
19
|
+
"rolldown": "^1.2.11",
|
|
20
20
|
"ts-node": "^10.9.2",
|
|
21
21
|
"typescript": "^6.0.3",
|
|
22
|
-
"vite": "^8.
|
|
22
|
+
"vite": "^8.3.1",
|
|
23
23
|
"vitest": "^4.1.11",
|
|
24
|
-
"@tomtom-org/maps-sdk": "0.
|
|
24
|
+
"@tomtom-org/maps-sdk": "0.62.0"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|