@tomtom-org/maps-sdk-plugin-landmarks-3d 0.0.13 → 0.1.0
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/README.md +9 -7
- package/dist/bundledTranscoder-IHtuhlDT.js +1 -0
- package/dist/index.d.ts +134 -57
- package/dist/index.es.js +1 -1
- package/package.json +4 -3
- package/dist/bundledTranscoder-Dn27cuYm.js +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,73 @@
|
|
|
1
|
-
import { TomTomMap } from "@tomtom-org/maps-sdk/map";
|
|
1
|
+
import { KnobConfigOf, KnobEntry, KnobRange, KnobValueOf, TomTomMap } from "@tomtom-org/maps-sdk/map";
|
|
2
2
|
import { CustomLayerInterface, CustomRenderMethodInput, Map } from "maplibre-gl";
|
|
3
3
|
import { AmbientLight, Camera, DirectionalLight, Group, Mesh, Scene, ShaderMaterial, Texture, WebGLRenderer } from "three";
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/knobCatalogue.d.ts
|
|
5
|
+
declare const landmarks3DKnobDefinitions: {
|
|
6
|
+
readonly visible: {
|
|
7
|
+
readonly kind: "toggle";
|
|
8
|
+
readonly description: "The landmark models.";
|
|
9
|
+
readonly default: true;
|
|
10
|
+
};
|
|
11
|
+
readonly color: {
|
|
12
|
+
readonly kind: "color";
|
|
13
|
+
readonly description: "Colour the landmarks are shaded in. Unset: the base map's 3D buildings' colour, following the map colours that restyle them.";
|
|
14
|
+
};
|
|
15
|
+
readonly opacity: {
|
|
16
|
+
readonly kind: "number";
|
|
17
|
+
readonly description: "Opacity of the landmarks, 0 to 1. Unset: the base map's 3D buildings' opacity.";
|
|
18
|
+
readonly range: KnobRange;
|
|
19
|
+
};
|
|
20
|
+
readonly verticalGradient: {
|
|
21
|
+
readonly kind: "toggle";
|
|
22
|
+
readonly description: "Whether the landmarks' walls darken towards the ground. Unset: as the base map's 3D buildings do.";
|
|
23
|
+
};
|
|
24
|
+
readonly minZoom: {
|
|
25
|
+
readonly kind: "number";
|
|
26
|
+
readonly description: "Lowest zoom the landmarks are drawn at. Unset: the base map's 3D buildings' one.";
|
|
27
|
+
readonly range: KnobRange;
|
|
28
|
+
};
|
|
29
|
+
readonly maxZoom: {
|
|
30
|
+
readonly kind: "number";
|
|
31
|
+
readonly description: "Highest whole zoom level the landmarks are drawn at, up to the next one. Unset: the base map's 3D buildings' one.";
|
|
32
|
+
readonly range: KnobRange;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
5
35
|
/**
|
|
6
|
-
*
|
|
7
|
-
* landmarks as maplibre-style fill-extrusion buildings:
|
|
8
|
-
* - `inherited`: mirrors the colour, opacity and vertical gradient of the basemap 3D building layer (default).
|
|
9
|
-
* - `dark`: uses the 3D building look of the SDK's `streetDark` style.
|
|
10
|
-
* - `light`: uses the 3D building look of the SDK's `streetLight` style.
|
|
36
|
+
* The id of a landmarks knob, as accepted by the SDK's `setKnob` and as a key of {@link Landmarks3DConfig}.
|
|
11
37
|
*
|
|
12
38
|
* @group Landmarks 3D
|
|
13
39
|
*/
|
|
14
|
-
type
|
|
40
|
+
type Landmarks3DKnobId = keyof typeof landmarks3DKnobDefinitions;
|
|
41
|
+
/**
|
|
42
|
+
* The value type a landmarks knob takes.
|
|
43
|
+
*
|
|
44
|
+
* @group Landmarks 3D
|
|
45
|
+
*/
|
|
46
|
+
type Landmarks3DKnobValueOf<ID extends Landmarks3DKnobId> = KnobValueOf<(typeof landmarks3DKnobDefinitions)[ID]['kind']>;
|
|
47
|
+
/**
|
|
48
|
+
* One entry of {@link landmarks3DKnobCatalogue}, with what a UI or a coding agent needs to offer the knob
|
|
49
|
+
* and validate a value for it. The SDK's `getKnob` reads the value in force.
|
|
50
|
+
*
|
|
51
|
+
* @group Landmarks 3D
|
|
52
|
+
*/
|
|
53
|
+
type Landmarks3DKnob = KnobEntry<Landmarks3DKnobId>;
|
|
54
|
+
/**
|
|
55
|
+
* Every landmarks knob with its kind, description and, for `visible`, its default. It's static data,
|
|
56
|
+
* readable without a map.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* A knob is a plain value in {@link Landmarks3DConfig}, under its id. Every knob but `visible`
|
|
60
|
+
* follows the base map's 3D buildings while unset, which the SDK's `getKnob` reports.
|
|
61
|
+
*
|
|
62
|
+
* @group Landmarks 3D
|
|
63
|
+
*/
|
|
64
|
+
export declare const landmarks3DKnobCatalogue: readonly Landmarks3DKnob[];
|
|
65
|
+
/**
|
|
66
|
+
* Every landmarks knob id, in catalogue order.
|
|
67
|
+
*
|
|
68
|
+
* @group Landmarks 3D
|
|
69
|
+
*/
|
|
70
|
+
export declare const landmarks3DKnobIds: readonly Landmarks3DKnobId[];
|
|
15
71
|
//#endregion
|
|
16
72
|
//#region src/FillExtrusionMaterial.d.ts
|
|
17
73
|
/**
|
|
@@ -234,35 +290,23 @@ export declare class ModelsLayer implements CustomLayerInterface {
|
|
|
234
290
|
//#endregion
|
|
235
291
|
//#region src/Landmarks3D.d.ts
|
|
236
292
|
/**
|
|
237
|
-
*
|
|
293
|
+
* The landmark knobs an app sets, as {@link Landmarks3D.getConfig} returns them. Each knob id is its
|
|
294
|
+
* key in here. Every knob but `visible` follows the base map's 3D buildings while unset. It's plain JSON.
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* const config: Landmarks3DConfig = { color: '#c8b28a', opacity: 0.9 };
|
|
299
|
+
* ```
|
|
238
300
|
*
|
|
239
301
|
* @group Landmarks 3D
|
|
240
302
|
*/
|
|
241
|
-
type
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Whether the landmarks are initially visible. To change it at runtime, call
|
|
250
|
-
* {@link Landmarks3D.setVisible}.
|
|
251
|
-
* @defaultValue `true`
|
|
252
|
-
*/
|
|
253
|
-
visible?: boolean;
|
|
254
|
-
/**
|
|
255
|
-
* Minimum zoom at which landmarks are rendered.
|
|
256
|
-
* @defaultValue The basemap 3D building layer's minimum zoom, so landmarks appear together
|
|
257
|
-
* with it (falls back to `10` when that layer is absent).
|
|
258
|
-
*/
|
|
259
|
-
minZoom?: number;
|
|
260
|
-
/**
|
|
261
|
-
* Maximum zoom at which landmarks are rendered.
|
|
262
|
-
* @defaultValue The basemap 3D building layer's maximum zoom, so landmarks disappear together
|
|
263
|
-
* with it (falls back to `22` when that layer is absent).
|
|
264
|
-
*/
|
|
265
|
-
maxZoom?: number;
|
|
303
|
+
type Landmarks3DConfig = KnobConfigOf<{ [ID in Landmarks3DKnobId]: Landmarks3DKnobValueOf<ID>; }>;
|
|
304
|
+
/**
|
|
305
|
+
* What the {@link Landmarks3D} plugin starts with: its knobs, and how it requests tiles.
|
|
306
|
+
*
|
|
307
|
+
* @group Landmarks 3D
|
|
308
|
+
*/
|
|
309
|
+
type Landmarks3DOptions = Landmarks3DConfig & {
|
|
266
310
|
/**
|
|
267
311
|
* Whether each tile request sends `credentials: 'include'`, so a proxy session
|
|
268
312
|
* cookie travels with it. By default, this tracks the SDK's proxy config,
|
|
@@ -282,9 +326,11 @@ type Landmarks3DOptions = {
|
|
|
282
326
|
* across map style changes.
|
|
283
327
|
*
|
|
284
328
|
* @remarks
|
|
285
|
-
* It requires an API key with Orbis 3D Landmarks entitlements.
|
|
286
|
-
*
|
|
287
|
-
*
|
|
329
|
+
* It requires an API key with Orbis 3D Landmarks entitlements. Its look is a set of knobs, listed in
|
|
330
|
+
* {@link landmarks3DKnobCatalogue}. Each one you leave unset follows the base map's 3D buildings,
|
|
331
|
+
* through style changes and anything that restyles them, falling back to the `streetLight` style's look.
|
|
332
|
+
* Whether those buildings show is the `buildings3D` group of the `BaseMapModule`, hidden in most
|
|
333
|
+
* standard styles; the plugin leaves it as it is.
|
|
288
334
|
*
|
|
289
335
|
* @example
|
|
290
336
|
* ```typescript
|
|
@@ -293,7 +339,7 @@ type Landmarks3DOptions = {
|
|
|
293
339
|
* // assume `map` is your initialized TomTomMap instance
|
|
294
340
|
* const landmarks = new Landmarks3D(map);
|
|
295
341
|
*
|
|
296
|
-
*
|
|
342
|
+
* landmarks.updateConfig({ color: '#c8b28a' });
|
|
297
343
|
* ```
|
|
298
344
|
*
|
|
299
345
|
* @group Landmarks 3D
|
|
@@ -303,50 +349,81 @@ export declare class Landmarks3D {
|
|
|
303
349
|
private readonly layerID;
|
|
304
350
|
private readonly basemapBuildingLayerID;
|
|
305
351
|
private readonly modelsLayer;
|
|
306
|
-
private
|
|
307
|
-
private
|
|
308
|
-
private displayMode;
|
|
352
|
+
private config;
|
|
353
|
+
private renderedLook;
|
|
309
354
|
/**
|
|
310
355
|
* Creates the plugin bound to a TomTom map and starts rendering landmarks
|
|
311
356
|
* as soon as the map is ready.
|
|
312
357
|
*
|
|
313
358
|
* @param map - The TomTom map instance to render landmarks on.
|
|
359
|
+
* @param options - The knobs to start with, and how to request tiles.
|
|
314
360
|
*/
|
|
315
|
-
constructor(map: TomTomMap,
|
|
361
|
+
constructor(map: TomTomMap, { withCredentials, ...config }?: Landmarks3DOptions);
|
|
316
362
|
private installWhenReady;
|
|
317
363
|
/**
|
|
318
364
|
* The underlying MapLibre custom layer, for advanced use beyond this facade.
|
|
319
365
|
*/
|
|
320
366
|
get layer(): ModelsLayer;
|
|
321
367
|
/**
|
|
322
|
-
* The
|
|
368
|
+
* Returns the knobs set, as a plain serializable copy. The SDK's `getKnob` reads a knob's value
|
|
369
|
+
* in force, the base map's one when unset.
|
|
323
370
|
*/
|
|
324
|
-
|
|
371
|
+
getConfig(): Landmarks3DConfig;
|
|
325
372
|
/**
|
|
326
|
-
*
|
|
373
|
+
* Replaces the whole configuration. An empty object has every knob follow the base map's 3D buildings.
|
|
374
|
+
*
|
|
375
|
+
* @remarks
|
|
376
|
+
* Use it, and {@link updateConfig}, for a look your code writes: as on a map module, the type is
|
|
377
|
+
* the check, and the values are not checked again. For values held as data, the SDK's `setKnobs`
|
|
378
|
+
* checks each against {@link landmarks3DKnobCatalogue} and sets them by id.
|
|
327
379
|
*
|
|
328
|
-
* @
|
|
329
|
-
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* landmarks.applyConfig({ color: '#c8b28a', opacity: 0.9 });
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
applyConfig(config: Landmarks3DConfig): void;
|
|
386
|
+
/**
|
|
387
|
+
* Changes the given knobs and keeps the others.
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```typescript
|
|
391
|
+
* landmarks.updateConfig({ opacity: 1 }); // the colour still follows the base map
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
updateConfig(partial: Landmarks3DConfig): void;
|
|
395
|
+
/**
|
|
396
|
+
* Unsets every knob, so the landmarks are shown and look like the base map's 3D buildings.
|
|
330
397
|
*/
|
|
331
|
-
|
|
398
|
+
resetConfig(): void;
|
|
332
399
|
/**
|
|
333
|
-
* Whether the landmarks are
|
|
400
|
+
* Whether the landmarks are set to be shown: the `visible` knob, `true` unless set.
|
|
334
401
|
*/
|
|
335
402
|
isVisible(): boolean;
|
|
336
403
|
/**
|
|
337
|
-
* Shows or hides the landmarks
|
|
404
|
+
* Shows or hides the landmarks. Stored as `visible` in the configuration.
|
|
338
405
|
*
|
|
339
406
|
* @param visible - `true` to show landmarks, `false` to hide them.
|
|
340
|
-
* @returns A promise that resolves once the visibility has been applied.
|
|
341
407
|
*/
|
|
342
|
-
setVisible(visible: boolean):
|
|
408
|
+
setVisible(visible: boolean): void;
|
|
409
|
+
/**
|
|
410
|
+
* @ignore
|
|
411
|
+
*/
|
|
412
|
+
_knobValue(id: string): unknown;
|
|
413
|
+
/**
|
|
414
|
+
* @ignore
|
|
415
|
+
*/
|
|
416
|
+
_setKnobValues(values: Readonly<Record<string, unknown>>): void;
|
|
417
|
+
/**
|
|
418
|
+
* @ignore
|
|
419
|
+
*/
|
|
420
|
+
_resetKnobValue(id: string): void;
|
|
343
421
|
private install;
|
|
344
422
|
private landmarkInsertBeforeID;
|
|
345
|
-
private alignZoomRangeWithBasemapBuildings;
|
|
346
423
|
private excludeLandmarkBuildingsFromBasemap;
|
|
347
|
-
private
|
|
348
|
-
private
|
|
349
|
-
private
|
|
424
|
+
private readBasemapBuildingsLook;
|
|
425
|
+
private look;
|
|
426
|
+
private render;
|
|
350
427
|
}
|
|
351
428
|
//#endregion
|
|
352
429
|
//#region src/landmarksTileURL.d.ts
|
|
@@ -361,4 +438,4 @@ export declare class Landmarks3D {
|
|
|
361
438
|
*/
|
|
362
439
|
export declare const buildLandmarksTileURL: () => string;
|
|
363
440
|
//#endregion
|
|
364
|
-
export type {
|
|
441
|
+
export type { Landmarks3DConfig, Landmarks3DKnob, Landmarks3DKnobId, Landmarks3DKnobValueOf, Landmarks3DOptions, ModelsLayerSpecification, ModelsSourceSpecification };
|
package/dist/index.es.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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 w}from"three";import{MeshoptDecoder as b}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{GLTFLoader as k}from"three/examples/jsm/loaders/GLTFLoader.js";import{KTX2Loader as C}from"three/examples/jsm/loaders/KTX2Loader.js";import{toCreasedNormals as S}from"three/examples/jsm/utils/BufferGeometryUtils.js";import{MercatorCoordinate as P}from"maplibre-gl";var T={diffuseColor:"hsl(38, 6%, 90%)",opacity:.7,verticalGradient:!0},D={diffuseColor:"hsl(210, 9%, 17%)",opacity:.7,verticalGradient:!0},z=()=>{const{commonBaseURL:i,apiKey:t}=e.instance.get();return`${i}/maps/orbis/display/3d/landmarks/tile/{z}/{x}/{y}?${t?`key=${t}&`:""}apiVersion=2`},B="fillExtrusionGradient",O=`\n attribute float ${B};\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 = ${B};\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})}},j=e=>!0===e.isMesh,Z=1/(2*Math.PI*6378137),K=Math.PI/6,V=class{type="models";id;minzoom;maxzoom;map;tiles;scene;loadedTiles=/* @__PURE__ */new Set;sceneOrigin=null;onTileLoaded;ktx2Loader;transcoderReady;gltfLoader;specification;withCredentials;coveringTileKeys=/* @__PURE__ */new Set;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 C(new p).setCrossOrigin("anonymous").setTranscoderPath(i.transcoderPath??""),this.gltfLoader=(new k).setKTX2Loader(this.ktx2Loader).setMeshoptDecoder(b)}onAdd(e,i){this.map=e,this.ktx2Loader.detectSupport(i)}updateTiles(){if(0===this.tiles.length)return;const e=this.coveringTiles();this.coveringTileKeys=new Set(e.map(e=>e.key));for(const i of e)this.loadTile(i);this.showCoveringTilesOnly()}showCoveringTilesOnly(){for(const e of this.scene.children)e.visible=this.coveringTileKeys.has(e.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},i.visible=this.coveringTileKeys.has(e.key),this.scene.add(i),this.map.triggerRepaint(),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=>{j(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=>{j(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(403===i.status&&"InvalidReferer"!==await(async e=>(await e.json().catch(()=>{}))?.detailedError?.code)(i))throw new Error("tile request failed with 403: Orbis 3D Landmarks is in Private Preview (https://docs.tomtom.com/legal/private-preview), and the API key needs access to it: request it from TomTom Sales at https://www.tomtom.com/contact-sales/?source_app=docs-portal&source_product=map-display-api-3d");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(!j(e))return;const i=e.material;i.alphaTest=.1,i.side=m,e.userData.originalMaterial=i,e.geometry=S(e.geometry,K),$(e)})},$=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(B,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;tilesMainMatrix=/* @__PURE__ */new Float64Array(16);tilesStale=!0;markTilesStale=()=>{this.tilesStale=!0};constructor(e,i){this.id=e.id,this.source=new V(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 w({canvas:e.getCanvas(),context:i,antialias:!0}),this.renderer.autoClear=!1,this.source.onAdd(e,this.renderer),this.tilesStale=!0,e.off("data",this.markTilesStale),e.on("data",this.markTilesStale)}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.updateTilesWhenStale(i.defaultProjectionData.mainMatrix),this.groundLandmarksOnTerrain(),this.alignCameraToMap(i),this.renderer.resetState(),this.needsTranslucentPrepass()&&this.renderDepthPrepass(),this.applyMaterials(),this.renderer.render(this.scene,this.camera))}updateTilesWhenStale(e){(this.tilesMainMatrix.some((i,t)=>i!==e[t])||this.tilesStale)&&(this.tilesMainMatrix.set(e),this.tilesStale=!1,this.source.updateTiles())}groundLandmarksOnTerrain(){for(const e of this.tiles.children)e.visible&&e.traverse(e=>{j(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 P(.5+(a+e)*Z,.5-(s+i)*Z).toLngLat().toArray()),e.userData.terrainSamplePoints=i}return i}alignCameraToMap(e){const i=Z/Math.cos(H(this.map.getCenter().lat)),t=.5+Z*this.sceneOrigin.x,a=.5-Z*this.sceneOrigin.y,s=new f;s.set(Z,0,0,t,0,-Z,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=>{j(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=>{j(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:[z()],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 D;case"light":return T;default:return{...T,...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,V as ModelsSource,z as buildLandmarksTileURL};
|
|
1
|
+
import{TomTomConfig,isProxyCredentialsMode}from"@tomtom-org/maps-sdk/core";import{LayerFilterComposer,configWithKnobs,configWithoutKnob,mapStyleLayerIDs}from"@tomtom-org/maps-sdk/map";import{AmbientLight,BufferAttribute,Camera,Color,CustomBlending,DirectionalLight,DoubleSide,FrontSide,Group,LoaderUtils,LoadingManager,Matrix4,OneFactor,OneMinusSrcAlphaFactor,SRGBColorSpace,Scene,ShaderMaterial,Vector3,WebGLRenderer}from"three";import{MeshoptDecoder}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{GLTFLoader}from"three/examples/jsm/loaders/GLTFLoader.js";import{KTX2Loader}from"three/examples/jsm/loaders/KTX2Loader.js";import{toCreasedNormals}from"three/examples/jsm/utils/BufferGeometryUtils.js";import{MercatorCoordinate}from"maplibre-gl";var ZOOM_RANGE={min:0,max:22,step:1,bounds:"hard"},landmarks3DKnobDefinitions={visible:{kind:"toggle",description:"The landmark models.",default:!0},color:{kind:"color",description:"Colour the landmarks are shaded in. Unset: the base map's 3D buildings' colour, following the map colours that restyle them."},opacity:{kind:"number",description:"Opacity of the landmarks, 0 to 1. Unset: the base map's 3D buildings' opacity.",range:{min:0,max:1,step:.05,bounds:"hard"}},verticalGradient:{kind:"toggle",description:"Whether the landmarks' walls darken towards the ground. Unset: as the base map's 3D buildings do."},minZoom:{kind:"number",description:"Lowest zoom the landmarks are drawn at. Unset: the base map's 3D buildings' one.",range:ZOOM_RANGE},maxZoom:{kind:"number",description:"Highest whole zoom level the landmarks are drawn at, up to the next one. Unset: the base map's 3D buildings' one.",range:ZOOM_RANGE}},landmarks3DKnobCatalogue=/* @__PURE__ */(()=>Object.keys(landmarks3DKnobDefinitions).map(e=>({id:e,...landmarks3DKnobDefinitions[e]})))(),landmarks3DKnobIds=/* @__PURE__ */landmarks3DKnobCatalogue.map(({id:e})=>e),buildLandmarksTileURL=()=>{const{commonBaseURL:e,apiKey:i}=TomTomConfig.instance.get();return`${e}/maps/orbis/display/3d/landmarks/tile/{z}/{x}/{y}?${i?`key=${i}&`:""}apiVersion=2`},STREET_LIGHT_LOOK={visible:!0,color:"hsl(38, 6%, 90%)",opacity:.7,verticalGradient:!0,minZoom:10,maxZoom:22},VERTEX_SHADER="\n attribute float fillExtrusionGradient;\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 = fillExtrusionGradient;\n #ifdef USE_ALPHA_MASK\n vUv = uv;\n #endif\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n }\n",ALPHA_MASK_FRAGMENT_CHUNK="\n #ifdef USE_ALPHA_MASK\n uniform sampler2D alphaMask;\n uniform float alphaMaskCutoff;\n varying vec2 vUv;\n #endif\n",ALPHA_MASK_MAIN_CHUNK="\n #ifdef USE_ALPHA_MASK\n if (texture2D(alphaMask, vUv).a < alphaMaskCutoff) {\n discard;\n }\n #endif\n",FRAGMENT_SHADER=`\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`,DEFAULT_LIGHT_POSITION=/* @__PURE__ */new Vector3(.2875,-.498,.996),parsedColor=/* @__PURE__ */new Color,srgbColor=/* @__PURE__ */new Color,FillExtrusionMaterial=class extends ShaderMaterial{constructor(e){super({uniforms:{diffuseColor:{value:new Vector3(1,1,1)},lightPosition:{value:DEFAULT_LIGHT_POSITION.clone()},lightIntensity:{value:.5},lightColor:{value:new Vector3(1,1,1)},layerOpacity:{value:1},verticalGradient:{value:1},...e&&{alphaMask:{value:e.map},alphaMaskCutoff:{value:e.cutoff}}},...e&&{defines:{USE_ALPHA_MASK:""}},vertexShader:VERTEX_SHADER,fragmentShader:FRAGMENT_SHADER,side:e?DoubleSide:FrontSide,transparent:!0,blending:CustomBlending,blendSrc:OneFactor,blendDst:OneMinusSrcAlphaFactor})}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){parsedColor.setStyle(e),parsedColor.getRGB(srgbColor,SRGBColorSpace),this.uniforms.diffuseColor.value.set(srgbColor.r,srgbColor.g,srgbColor.b)}setLayerOpacity(e){this.uniforms.layerOpacity.value=e}setVerticalGradient(e){this.uniforms.verticalGradient.value=e?1:0}setLight(e,i,t,a){const r=Math.cos(i),s=Math.sin(i);this.uniforms.lightPosition.value.set(e.x*r-e.y*s,e.x*s+e.y*r,e.z),this.uniforms.lightIntensity.value=t,this.uniforms.lightColor.value.set(a.r,a.g,a.b)}},FillExtrusionDepthMaterial=class extends ShaderMaterial{constructor(e){super({...e&&{uniforms:{alphaMask:{value:e.map},alphaMaskCutoff:{value:e.cutoff}},defines:{USE_ALPHA_MASK:""}},vertexShader:VERTEX_SHADER,fragmentShader:`\n ${ALPHA_MASK_FRAGMENT_CHUNK}\n void main() {\n ${ALPHA_MASK_MAIN_CHUNK}\n gl_FragColor = vec4(0.0);\n }\n `,side:e?DoubleSide:FrontSide,colorWrite:!1})}},isMesh=e=>!0===e.isMesh,MERCATOR_UNITS_PER_METRE=1/(2*Math.PI*6378137),CREASE_ANGLE_RADIANS=Math.PI/6,ModelsSource=class{type="models";id;minzoom;maxzoom;map;tiles;scene;loadedTiles=/* @__PURE__ */new Set;sceneOrigin=null;onTileLoaded;ktx2Loader;transcoderReady;gltfLoader;specification;withCredentials;coveringTileKeys=/* @__PURE__ */new Set;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 Group,this.ktx2Loader=new KTX2Loader(new LoadingManager).setCrossOrigin("anonymous").setTranscoderPath(i.transcoderPath??""),this.gltfLoader=(new GLTFLoader).setKTX2Loader(this.ktx2Loader).setMeshoptDecoder(MeshoptDecoder)}onAdd(e,i){this.map=e,this.ktx2Loader.detectSupport(i)}updateTiles(){if(0===this.tiles.length)return;const e=this.coveringTiles();this.coveringTileKeys=new Set(e.map(e=>e.key));for(const i of e)this.loadTile(i);this.showCoveringTilesOnly()}showCoveringTilesOnly(){for(const e of this.scene.children)e.visible=this.coveringTileKeys.has(e.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&&(prepareTileMeshes(i),this.rebaseToSceneOrigin(i),i.userData={isTile:!0,key:e.key},i.visible=this.coveringTileKeys.has(e.key),this.scene.add(i),this.map.triggerRepaint(),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=>{isMesh(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=>{isMesh(t)&&(t.position.x-=e,t.position.y-=i)}),this.sceneOrigin=a}}else this.sceneOrigin=a;for(const r of i)r.position.x-=this.sceneOrigin.x,r.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(403===i.status&&"InvalidReferer"!==await(async e=>(await e.json().catch(()=>{}))?.detailedError?.code)(i))throw new Error("tile request failed with 403: Orbis 3D Landmarks is in Private Preview (https://docs.tomtom.com/legal/private-preview), and the API key needs access to it: request it from TomTom Sales at https://www.tomtom.com/contact-sales/?source_app=docs-portal&source_product=map-display-api-3d");if(!i.ok)throw new Error(`tile request failed with ${i.status}`);const t=await i.arrayBuffer();this.transcoderReady??=(async e=>{if(void 0!==this.specification.transcoderPath)return;const{resolveBundledTranscoderUrl:i}=await(import("./bundledTranscoder-IHtuhlDT.js"));e.setURLModifier(i)})(this.ktx2Loader.manager),await this.transcoderReady;const{scene:a}=await this.gltfLoader.parseAsync(t,LoaderUtils.extractUrlBase(e));return a}},prepareTileMeshes=e=>{e.traverse(e=>{if(!isMesh(e))return;const i=e.material;i.alphaTest=.1,i.side=DoubleSide,e.userData.originalMaterial=i,e.geometry=toCreasedNormals(e.geometry,CREASE_ANGLE_RADIANS),bakeGradientAttribute(e)})},bakeGradientAttribute=e=>{const i=e.geometry,t=i.getAttribute("position");i.computeBoundingBox();const a=i.boundingBox?.max.z??0,r=a>0?Math.sqrt(a/150)/a:0,s=new Float32Array(t.count);for(let o=0;o<t.count;o++)s[o]=t.getZ(o)*r;i.setAttribute("fillExtrusionGradient",new BufferAttribute(s,1))},toRadians=e=>e*Math.PI/180,ModelsLayer=class{id;type="custom";renderingMode="3d";renderer;scene;camera;tiles;directionalLight;ambientLight;map;source;minzoom=0;maxzoom=22;visible=!0;diffuseColor="#ffffff";fillExtrusionMaterial=new FillExtrusionMaterial;depthPrepassMaterial=new FillExtrusionDepthMaterial;maskedFillExtrusionMaterials=/* @__PURE__ */new Map;tilesMainMatrix=/* @__PURE__ */new Float64Array(16);tilesStale=!0;markTilesStale=()=>{this.tilesStale=!0};constructor(e,i){this.id=e.id,this.source=new ModelsSource(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,toRadians(i),toRadians(t))}onAdd(e,i){this.map=e,this.scene=new Scene,this.tiles=this.source.scene,this.camera=new Camera,this.directionalLight=new DirectionalLight(16777215,.5),this.setDirectionalLight(1.15,90,0),this.scene.add(this.directionalLight),this.ambientLight=new AmbientLight(16777215,.1),this.scene.add(this.ambientLight),this.scene.add(this.tiles),this.renderer=new WebGLRenderer({canvas:e.getCanvas(),context:i,antialias:!0}),this.renderer.autoClear=!1,this.source.onAdd(e,this.renderer),this.tilesStale=!0,e.off("data",this.markTilesStale),e.on("data",this.markTilesStale)}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.updateTilesWhenStale(i.defaultProjectionData.mainMatrix),this.groundLandmarksOnTerrain(),this.alignCameraToMap(i),this.renderer.resetState(),this.needsTranslucentPrepass()&&this.renderDepthPrepass(),this.applyMaterials(),this.renderer.render(this.scene,this.camera))}updateTilesWhenStale(e){(this.tilesMainMatrix.some((i,t)=>i!==e[t])||this.tilesStale)&&(this.tilesMainMatrix.set(e),this.tilesStale=!1,this.source.updateTiles())}groundLandmarksOnTerrain(){for(const e of this.tiles.children)e.visible&&e.traverse(e=>{isMesh(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,r=e.position.y+this.sceneOrigin.y,s=(t.min.x+t.max.x)/2,o=(t.min.y+t.max.y)/2;i=[[s,o],[t.min.x,t.min.y],[t.min.x,t.max.y],[t.max.x,t.min.y],[t.max.x,t.max.y],[s,t.min.y],[s,t.max.y],[t.min.x,o],[t.max.x,o]].map(([e,i])=>new MercatorCoordinate(.5+(a+e)*MERCATOR_UNITS_PER_METRE,.5-(r+i)*MERCATOR_UNITS_PER_METRE).toLngLat().toArray()),e.userData.terrainSamplePoints=i}return i}alignCameraToMap(e){const i=MERCATOR_UNITS_PER_METRE/Math.cos(toRadians(this.map.getCenter().lat)),t=.5+MERCATOR_UNITS_PER_METRE*this.sceneOrigin.x,a=.5-MERCATOR_UNITS_PER_METRE*this.sceneOrigin.y,r=new Matrix4;r.set(MERCATOR_UNITS_PER_METRE,0,0,t,0,-MERCATOR_UNITS_PER_METRE,0,a,0,0,i,0,0,0,0,1);const s=(new Matrix4).fromArray(e.defaultProjectionData.mainMatrix);this.camera.projectionMatrix.copy(s).multiply(r)}get sceneOrigin(){return this.source.sceneOrigin??{x:0,y:0}}needsTranslucentPrepass(){return this.fillExtrusionMaterial.uniforms.layerOpacity.value<1}renderDepthPrepass(){this.tiles.traverse(e=>{isMesh(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 FillExtrusionMaterial(e),depth:new FillExtrusionDepthMaterial(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 r={x:-a[0],y:a[1],z:a[2]};this.directionalLight.position.set(r.x,r.y,r.z);const s="viewport"===t.get("anchor")?-(e._camera.transform.bearingInRadians??0):0,o=t.get("color");this.forEachFillExtrusionMaterial(e=>e.setLight(r,s,i,o))}catch{}}applyMaterials(){this.forEachFillExtrusionMaterial(e=>e.setDiffuseColor(this.diffuseColor)),this.tiles.traverse(e=>{isMesh(e)&&(e.material=this.getMaterial(e))})}},NO_LANDMARK_FILTER_CLAUSE=["!",["coalesce",["get","has_landmark"],!1]],Landmarks3D=class{map;layerID="orbis-3d-landmarks";basemapBuildingLayerID="3D - Building";modelsLayer;config;renderedLook;constructor(e,{withCredentials:i,...t}={}){this.map=e,this.config=t;const a={id:this.layerID,type:"models",source:this.layerID},r={type:"models",tiles:[buildLandmarksTileURL()],minzoom:15,maxzoom:15,withCredentials:i??isProxyCredentialsMode()};this.modelsLayer=new ModelsLayer(a,r),this.render(),this.map.addStyleChangeHandler({onStyleChanged:()=>this.install()}),this.map.mapLibreMap.on("styledata",()=>{var e,i;this.map.mapReady&&(e=this.look(),void 0===(i=this.renderedLook)||!Object.keys(e).every(t=>e[t]===i[t]))&&this.render()}),this.installWhenReady()}installWhenReady(){this.map.mapReady?this.install():this.map.mapLibreMap.once("styledata",()=>this.installWhenReady())}get layer(){return this.modelsLayer}getConfig(){return structuredClone(this.config)}applyConfig(e){this.config=e,this.render()}updateConfig(e){this.applyConfig({...this.config,...e})}resetConfig(){this.applyConfig({})}isVisible(){return!1!==this.config.visible}setVisible(e){this.updateConfig({visible:e})}_knobValue(e){return this.look()[e]}_setKnobValues(e){this.applyConfig(configWithKnobs(this.config,e))}_resetKnobValue(e){this.applyConfig(configWithoutKnob(this.config,e))}install(){const e=this.map.mapLibreMap;e.getLayer(this.layerID)||e.addLayer(this.modelsLayer,this.landmarkInsertBeforeID()),this.excludeLandmarkBuildingsFromBasemap(),this.modelsLayer.source.onTileLoaded=()=>{this.look().opacity<1&&this.render()},this.render()}landmarkInsertBeforeID(){const e=mapStyleLayerIDs.poi;return this.map.mapLibreMap.getLayer(e)?e:void 0}excludeLandmarkBuildingsFromBasemap(){LayerFilterComposer.for(this.map).setClause(this.basemapBuildingLayerID,"landmarks3d.buildings",NO_LANDMARK_FILTER_CLAUSE)}readBasemapBuildingsLook(){const e=this.map.mapLibreMap,i=e.getLayer(this.basemapBuildingLayerID);if(!i)return{};const t=i=>e.getPaintProperty(this.basemapBuildingLayerID,i),a=(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})(t("fill-extrusion-color")),r=t("fill-extrusion-opacity"),s=t("fill-extrusion-vertical-gradient");return{...a&&{color:a},..."number"==typeof r&&{opacity:r},..."boolean"==typeof s&&{verticalGradient:s},...void 0!==i.minzoom&&{minZoom:i.minzoom},...void 0!==i.maxzoom&&{maxZoom:i.maxzoom}}}look(){const e=this.map.mapReady?this.readBasemapBuildingsLook():{};return((e,i)=>{const t={...STREET_LIGHT_LOOK,...i},a=i=>e[i]??t[i];return{visible:a("visible"),color:a("color"),opacity:a("opacity"),verticalGradient:a("verticalGradient"),minZoom:a("minZoom"),maxZoom:a("maxZoom")}})(this.config,e)}render(){const e=this.look();this.renderedLook=e,this.modelsLayer.visible=e.visible,this.modelsLayer.minzoom=e.minZoom,this.modelsLayer.maxzoom=e.maxZoom,this.modelsLayer.diffuseColor=e.color,this.modelsLayer.setOpacity(e.opacity),this.modelsLayer.setVerticalGradient(e.verticalGradient),this.map.mapReady&&this.map.mapLibreMap.triggerRepaint()}};export{Landmarks3D,ModelsLayer,ModelsSource,buildLandmarksTileURL,landmarks3DKnobCatalogue,landmarks3DKnobIds};
|
package/package.json
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
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.1.0",
|
|
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
|
+
"sideEffects": false,
|
|
9
10
|
"peerDependencies": {
|
|
10
|
-
"@tomtom-org/maps-sdk": ">=
|
|
11
|
+
"@tomtom-org/maps-sdk": ">=1.0.0-rc.2 <1.0.0",
|
|
11
12
|
"maplibre-gl": "^6.13.0",
|
|
12
13
|
"three": "^0.170.0"
|
|
13
14
|
},
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"vite": "8.3.3",
|
|
23
24
|
"vitest": "^5.0.3",
|
|
24
25
|
"@testing/core-utils": "0.0.0",
|
|
25
|
-
"@tomtom-org/maps-sdk": "1.0.0-rc.
|
|
26
|
+
"@tomtom-org/maps-sdk": "1.0.0-rc.3"
|
|
26
27
|
},
|
|
27
28
|
"files": [
|
|
28
29
|
"dist",
|