@mapvx/web-js 3.6.0-dev.1 → 4.0.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 +6 -1
- package/dist/cjs/index.js +2 -95
- package/dist/cjs/index.js.LICENSE.txt +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/controllers/routeController.d.ts.map +1 -1
- package/dist/es/controllers/routeController.js +25 -8
- package/dist/es/controllers/routeController.js.map +1 -1
- package/dist/es/logger/sentry.js +1 -1
- package/dist/es/map/liveMapFitTransform.d.ts +11 -5
- package/dist/es/map/liveMapFitTransform.d.ts.map +1 -1
- package/dist/es/map/liveMapFitTransform.js +28 -19
- package/dist/es/map/liveMapFitTransform.js.map +1 -1
- package/dist/es/map/map.d.ts.map +1 -1
- package/dist/es/map/map.js +3 -6
- package/dist/es/map/map.js.map +1 -1
- package/dist/umd/index.js +932 -126
- package/dist/umd/index.js.map +1 -1
- package/dist/umd/styles.css +1 -1
- package/dist/umd/styles.css.map +1 -1
- package/package.json +7 -4
- package/dist/cjs/assets/icons.js +0 -85
- package/dist/cjs/assets/icons.js.map +0 -1
- package/dist/cjs/config/draco.js +0 -15
- package/dist/cjs/config/draco.js.map +0 -1
- package/dist/cjs/config/sdkConfig.js +0 -3
- package/dist/cjs/config/sdkConfig.js.map +0 -1
- package/dist/cjs/controllers/markerLayoutController.js +0 -359
- package/dist/cjs/controllers/markerLayoutController.js.map +0 -1
- package/dist/cjs/controllers/routeController.js +0 -1549
- package/dist/cjs/controllers/routeController.js.map +0 -1
- package/dist/cjs/domain/models/_internal.js +0 -14
- package/dist/cjs/domain/models/_internal.js.map +0 -1
- package/dist/cjs/domain/models/_rtl.js +0 -10
- package/dist/cjs/domain/models/_rtl.js.map +0 -1
- package/dist/cjs/domain/models/animation.js +0 -64
- package/dist/cjs/domain/models/animation.js.map +0 -1
- package/dist/cjs/domain/models/banner.js +0 -24
- package/dist/cjs/domain/models/banner.js.map +0 -1
- package/dist/cjs/domain/models/campaign.js +0 -73
- package/dist/cjs/domain/models/campaign.js.map +0 -1
- package/dist/cjs/domain/models/categories.js +0 -37
- package/dist/cjs/domain/models/categories.js.map +0 -1
- package/dist/cjs/domain/models/circle.js +0 -253
- package/dist/cjs/domain/models/circle.js.map +0 -1
- package/dist/cjs/domain/models/cityFilterOption.js +0 -3
- package/dist/cjs/domain/models/cityFilterOption.js.map +0 -1
- package/dist/cjs/domain/models/configuration.js +0 -3
- package/dist/cjs/domain/models/configuration.js.map +0 -1
- package/dist/cjs/domain/models/cssCustomization.js +0 -3
- package/dist/cjs/domain/models/cssCustomization.js.map +0 -1
- package/dist/cjs/domain/models/fitOptions.js +0 -3
- package/dist/cjs/domain/models/fitOptions.js.map +0 -1
- package/dist/cjs/domain/models/innerFloors.js +0 -3
- package/dist/cjs/domain/models/innerFloors.js.map +0 -1
- package/dist/cjs/domain/models/institution.js +0 -45
- package/dist/cjs/domain/models/institution.js.map +0 -1
- package/dist/cjs/domain/models/latLng.js +0 -3
- package/dist/cjs/domain/models/latLng.js.map +0 -1
- package/dist/cjs/domain/models/location.js +0 -3
- package/dist/cjs/domain/models/location.js.map +0 -1
- package/dist/cjs/domain/models/loggeable.js +0 -52
- package/dist/cjs/domain/models/loggeable.js.map +0 -1
- package/dist/cjs/domain/models/mapCamera.js +0 -3
- package/dist/cjs/domain/models/mapCamera.js.map +0 -1
- package/dist/cjs/domain/models/mapConfig.js +0 -37
- package/dist/cjs/domain/models/mapConfig.js.map +0 -1
- package/dist/cjs/domain/models/marker.js +0 -502
- package/dist/cjs/domain/models/marker.js.map +0 -1
- package/dist/cjs/domain/models/multilanguage.js +0 -17
- package/dist/cjs/domain/models/multilanguage.js.map +0 -1
- package/dist/cjs/domain/models/openingHours.js +0 -3
- package/dist/cjs/domain/models/openingHours.js.map +0 -1
- package/dist/cjs/domain/models/place.js +0 -127
- package/dist/cjs/domain/models/place.js.map +0 -1
- package/dist/cjs/domain/models/platform.js +0 -3
- package/dist/cjs/domain/models/platform.js.map +0 -1
- package/dist/cjs/domain/models/popover.js +0 -3
- package/dist/cjs/domain/models/popover.js.map +0 -1
- package/dist/cjs/domain/models/product.js +0 -3
- package/dist/cjs/domain/models/product.js.map +0 -1
- package/dist/cjs/domain/models/property.js +0 -104
- package/dist/cjs/domain/models/property.js.map +0 -1
- package/dist/cjs/domain/models/route.js +0 -166
- package/dist/cjs/domain/models/route.js.map +0 -1
- package/dist/cjs/domain/models/routeConfiguration.js +0 -82
- package/dist/cjs/domain/models/routeConfiguration.js.map +0 -1
- package/dist/cjs/domain/models/routeInstance.js +0 -30
- package/dist/cjs/domain/models/routeInstance.js.map +0 -1
- package/dist/cjs/domain/models/routeStyle.js +0 -3
- package/dist/cjs/domain/models/routeStyle.js.map +0 -1
- package/dist/cjs/domain/models/scene3D.js +0 -3
- package/dist/cjs/domain/models/scene3D.js.map +0 -1
- package/dist/cjs/domain/models/transport.js +0 -22
- package/dist/cjs/domain/models/transport.js.map +0 -1
- package/dist/cjs/domain/ports/ICache.js +0 -3
- package/dist/cjs/domain/ports/ICache.js.map +0 -1
- package/dist/cjs/domain/ports/IHttpClient.js +0 -3
- package/dist/cjs/domain/ports/IHttpClient.js.map +0 -1
- package/dist/cjs/domain/ports/ILogger.js +0 -23
- package/dist/cjs/domain/ports/ILogger.js.map +0 -1
- package/dist/cjs/domain/ports/index.js +0 -10
- package/dist/cjs/domain/ports/index.js.map +0 -1
- package/dist/cjs/infrastructure/cache/CacheManager.js +0 -258
- package/dist/cjs/infrastructure/cache/CacheManager.js.map +0 -1
- package/dist/cjs/infrastructure/cache/LRUCache.js +0 -153
- package/dist/cjs/infrastructure/cache/LRUCache.js.map +0 -1
- package/dist/cjs/infrastructure/cache/PersistentCache.js +0 -168
- package/dist/cjs/infrastructure/cache/PersistentCache.js.map +0 -1
- package/dist/cjs/infrastructure/cache/cacheConfig.js +0 -70
- package/dist/cjs/infrastructure/cache/cacheConfig.js.map +0 -1
- package/dist/cjs/infrastructure/http/FetchHttpClient.js +0 -75
- package/dist/cjs/infrastructure/http/FetchHttpClient.js.map +0 -1
- package/dist/cjs/infrastructure/logging/MVXLogger.js +0 -80
- package/dist/cjs/infrastructure/logging/MVXLogger.js.map +0 -1
- package/dist/cjs/interfaces/cache.js +0 -3
- package/dist/cjs/interfaces/cache.js.map +0 -1
- package/dist/cjs/interfaces/healthResponse.js +0 -3
- package/dist/cjs/interfaces/healthResponse.js.map +0 -1
- package/dist/cjs/interfaces/institutionResponse.js +0 -3
- package/dist/cjs/interfaces/institutionResponse.js.map +0 -1
- package/dist/cjs/interfaces/metadata.js +0 -3
- package/dist/cjs/interfaces/metadata.js.map +0 -1
- package/dist/cjs/interfaces/placeResponse.js +0 -3
- package/dist/cjs/interfaces/placeResponse.js.map +0 -1
- package/dist/cjs/interfaces/routeCacheResponse.js +0 -3
- package/dist/cjs/interfaces/routeCacheResponse.js.map +0 -1
- package/dist/cjs/interfaces/routeResponse.js +0 -3
- package/dist/cjs/interfaces/routeResponse.js.map +0 -1
- package/dist/cjs/interfaces/styleResponse.js +0 -3
- package/dist/cjs/interfaces/styleResponse.js.map +0 -1
- package/dist/cjs/logger/logger.js +0 -78
- package/dist/cjs/logger/logger.js.map +0 -1
- package/dist/cjs/logger/sentry.js +0 -81
- package/dist/cjs/logger/sentry.js.map +0 -1
- package/dist/cjs/map/fitCamera.js +0 -124
- package/dist/cjs/map/fitCamera.js.map +0 -1
- package/dist/cjs/map/liveMapFitTransform.js +0 -180
- package/dist/cjs/map/liveMapFitTransform.js.map +0 -1
- package/dist/cjs/map/map.js +0 -2766
- package/dist/cjs/map/map.js.map +0 -1
- package/dist/cjs/map/mapInteractionOptions.js +0 -73
- package/dist/cjs/map/mapInteractionOptions.js.map +0 -1
- package/dist/cjs/map/scene3DController.js +0 -849
- package/dist/cjs/map/scene3DController.js.map +0 -1
- package/dist/cjs/repository/repository.js +0 -694
- package/dist/cjs/repository/repository.js.map +0 -1
- package/dist/cjs/repository/requester.js +0 -771
- package/dist/cjs/repository/requester.js.map +0 -1
- package/dist/cjs/sdk.js +0 -438
- package/dist/cjs/sdk.js.map +0 -1
- package/dist/cjs/utils/3d.js +0 -245
- package/dist/cjs/utils/3d.js.map +0 -1
- package/dist/cjs/utils/deckLayers.js +0 -153
- package/dist/cjs/utils/deckLayers.js.map +0 -1
- package/dist/cjs/utils/error.js +0 -19
- package/dist/cjs/utils/error.js.map +0 -1
- package/dist/cjs/utils/images.js +0 -25
- package/dist/cjs/utils/images.js.map +0 -1
- package/dist/cjs/utils/loadGlb.js +0 -190
- package/dist/cjs/utils/loadGlb.js.map +0 -1
- package/dist/cjs/utils/mapvxHostname.js +0 -21
- package/dist/cjs/utils/mapvxHostname.js.map +0 -1
- package/dist/cjs/utils/nav-functions.js +0 -150
- package/dist/cjs/utils/nav-functions.js.map +0 -1
- package/dist/cjs/utils/opening-hours-helper.js +0 -914
- package/dist/cjs/utils/opening-hours-helper.js.map +0 -1
- package/dist/cjs/utils/preconnect.js +0 -131
- package/dist/cjs/utils/preconnect.js.map +0 -1
- package/dist/cjs/utils/route-utils.js +0 -57
- package/dist/cjs/utils/route-utils.js.map +0 -1
- package/dist/cjs/utils/semaphore.js +0 -143
- package/dist/cjs/utils/semaphore.js.map +0 -1
- package/dist/cjs/utils/three.js +0 -113
- package/dist/cjs/utils/three.js.map +0 -1
- package/dist/cjs/utils/threeScene.js +0 -126
- package/dist/cjs/utils/threeScene.js.map +0 -1
- package/dist/cjs/utils/update-css.js +0 -76
- package/dist/cjs/utils/update-css.js.map +0 -1
- package/dist/cjs/utils/utils.js +0 -104
- package/dist/cjs/utils/utils.js.map +0 -1
|
@@ -1,849 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Scene3DController = void 0;
|
|
4
|
-
const maplibre_gl_1 = require("maplibre-gl");
|
|
5
|
-
const _3d_1 = require("../utils/3d");
|
|
6
|
-
const deckLayers_1 = require("../utils/deckLayers");
|
|
7
|
-
const loadGlb_1 = require("../utils/loadGlb");
|
|
8
|
-
const semaphore_1 = require("../utils/semaphore");
|
|
9
|
-
const three_1 = require("../utils/three");
|
|
10
|
-
const threeScene_1 = require("../utils/threeScene");
|
|
11
|
-
/**
|
|
12
|
-
* Owns the experimental 3D scene for a MapLibre map: the deck.gl overlay layers (elevated room
|
|
13
|
-
* outlines, indoor labels, POI icons, store logos) and the declarative GLB custom layer rendered
|
|
14
|
-
* with Three.js.
|
|
15
|
-
*
|
|
16
|
-
* This is the WebGL / deck.gl "glue": creating the `WebGLRenderer`, instantiating deck layers and
|
|
17
|
-
* driving the per-frame render loop all require a real GPU context and a live map, so this module is
|
|
18
|
-
* excluded from unit-test coverage (see `sonar.coverage.exclusions`). The unit-testable logic it
|
|
19
|
-
* relies on lives in `utils/deckLayers.ts`, `utils/threeScene.ts` and `utils/3d.ts`.
|
|
20
|
-
*
|
|
21
|
-
* The map creates one instance only when {@link MapConfig.mode} is `"3D"` and
|
|
22
|
-
* {@link Scene3DConfig.enabled} is not `false`, and forwards its lifecycle to the `on*` methods.
|
|
23
|
-
*
|
|
24
|
-
* @group Map
|
|
25
|
-
*/
|
|
26
|
-
class Scene3DController {
|
|
27
|
-
constructor(map, scene3DConfig, initialCenter, onModelsProgress) {
|
|
28
|
-
var _a;
|
|
29
|
-
/** Latest known floor key; updated by the floor-change hooks and read when (re)building layers. */
|
|
30
|
-
this.currentFloor = "";
|
|
31
|
-
// Lazily-loaded Three.js / deck.gl runtime handles (see loadThree()/loadDeck()).
|
|
32
|
-
this.threeMod = undefined;
|
|
33
|
-
this.deckMod = undefined;
|
|
34
|
-
this.deckOverlay = undefined;
|
|
35
|
-
this.scene3DGlbScene = undefined;
|
|
36
|
-
/** Loaded GLB roots keyed by URL (deduplicated). */
|
|
37
|
-
this.scene3DGlbRootByUrl = new globalThis.Map();
|
|
38
|
-
/** Per-asset template root and bottom anchor for cloning. */
|
|
39
|
-
this.scene3DGlbTemplateById = new globalThis.Map();
|
|
40
|
-
/** Number of light objects in `scene3DGlbScene` (kept when clearing meshes). */
|
|
41
|
-
this.scene3DGlbLightCount = 0;
|
|
42
|
-
/** Container group holding all placed GLB pivots; rebuilt on each floor change. */
|
|
43
|
-
this.scene3DModelsGroup = undefined;
|
|
44
|
-
/** The GLB custom layer's renderer, stored so {@link dispose} can release it; see `createGlbCustomLayer`. */
|
|
45
|
-
this.glbRenderer = undefined;
|
|
46
|
-
/**
|
|
47
|
-
* Route points currently used for GLB route-transparency collision, tagged with the floor on
|
|
48
|
-
* which each point's route segment is visible. Stored as coordinates (not pivot references) so
|
|
49
|
-
* they survive `placeScene3DModels` rebuilds on floor change. Only points matching the current
|
|
50
|
-
* floor participate, so a model only fades when a route is visibly crossing it on that floor.
|
|
51
|
-
*/
|
|
52
|
-
this.scene3DRouteCollisionPoints = [];
|
|
53
|
-
this.spriteIconMapping = {};
|
|
54
|
-
this.spriteAtlasImage = new Image();
|
|
55
|
-
/** True while the GLB models are unloaded by LOD (camera zoomed out past the threshold). */
|
|
56
|
-
this.modelsUnloadedByLod = false;
|
|
57
|
-
/**
|
|
58
|
-
* True while {@link loadScene3DGlbTemplates} is populating `scene3DGlbRootByUrl` /
|
|
59
|
-
* `scene3DGlbTemplateById` (initial load or an LOD reload). Guards against overlapping reloads
|
|
60
|
-
* and, in {@link updateLodForZoom}, against an LOD unload running concurrently and clearing
|
|
61
|
-
* those same maps out from under the in-flight load.
|
|
62
|
-
*/
|
|
63
|
-
this.glbTemplatesLoadInFlight = false;
|
|
64
|
-
this.map = map;
|
|
65
|
-
this.scene3DConfig = scene3DConfig;
|
|
66
|
-
this.initialCenter = initialCenter;
|
|
67
|
-
this.onModelsProgress = onModelsProgress;
|
|
68
|
-
this.deckTileCache = (0, _3d_1.resolveDeckTileCache)((_a = scene3DConfig.customLayers) === null || _a === void 0 ? void 0 : _a.tileCache);
|
|
69
|
-
this.lodThresholds = (0, _3d_1.resolveModels3DLodThresholds)(scene3DConfig.models);
|
|
70
|
-
}
|
|
71
|
-
// Lifecycle entry points forwarded by the map ---------------------------------------------
|
|
72
|
-
/**
|
|
73
|
-
* Sets up the deck.gl overlay and GLB custom layer once the map style has loaded. Awaits the lazy
|
|
74
|
-
* library load first so the base 2D flow is never blocked by it.
|
|
75
|
-
*/
|
|
76
|
-
async onMapLoad() {
|
|
77
|
-
var _a, _b;
|
|
78
|
-
try {
|
|
79
|
-
await this.ensure3DLibs();
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
console.error("Failed to load 3D libraries (three/deck.gl)", err);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (!this.deckMod)
|
|
86
|
-
return;
|
|
87
|
-
// Load the sprite atlas (icons/logos) before building deck layers so the first
|
|
88
|
-
// updateDeckOverlay has the atlas available.
|
|
89
|
-
const spriteUrl = (_b = (_a = this.scene3DConfig.customLayers) === null || _a === void 0 ? void 0 : _a.iconLayers) === null || _b === void 0 ? void 0 : _b.spriteUrl;
|
|
90
|
-
if (spriteUrl) {
|
|
91
|
-
await this.loadSpriteAtlas(spriteUrl).catch((err) => console.error("Failed to load sprite atlas", err));
|
|
92
|
-
}
|
|
93
|
-
this.deckOverlay = new this.deckMod.MapboxOverlay({ layers: [] });
|
|
94
|
-
this.map.addControl(this.deckOverlay);
|
|
95
|
-
// Hide MapLibre icon layers — replaced by deck.gl IconLayer
|
|
96
|
-
this.hideLayers(this.scene3DConfig.hideLayers);
|
|
97
|
-
this.installDebugStatsHook();
|
|
98
|
-
const models = this.scene3DConfig.models;
|
|
99
|
-
const glbModelsActive = models !== undefined && models.enabled !== false && models.assets.length > 0;
|
|
100
|
-
if (glbModelsActive) {
|
|
101
|
-
this.map.addLayer(this.createGlbCustomLayer());
|
|
102
|
-
}
|
|
103
|
-
// Populate the overlay for the floor that may already be set: the map's floor hooks
|
|
104
|
-
// (onFloorChange/refreshOverlay) run during init and early-return while `deckOverlay` is still
|
|
105
|
-
// undefined during the async library load, so without this the overlay stays empty until the
|
|
106
|
-
// next floor change (e.g. broken on totem/route views that never change floor after load).
|
|
107
|
-
this.updateDeckOverlay();
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Re-attaches the style-owned half of the 3D scene after the map style has been fully
|
|
111
|
-
* replaced (`map.setStyle(..., { diff: false })`).
|
|
112
|
-
*
|
|
113
|
-
* A full swap deletes every layer, so MapLibre removes the GLB custom layer and calls
|
|
114
|
-
* its `onRemove` — which runs {@link dispose} and frees the renderer, the GLB roots and
|
|
115
|
-
* the templates. `onMapLoad` is the only other place that adds the layer and it never
|
|
116
|
-
* runs twice, so without this call the models are gone permanently and the MapLibre
|
|
117
|
-
* icon layers that deck.gl replaced come back on top of the deck icons.
|
|
118
|
-
*
|
|
119
|
-
* Only style-owned state is rebuilt. The deck.gl overlay is a map CONTROL, not a style
|
|
120
|
-
* layer, so it survives the swap untouched — re-adding it here would stack a second
|
|
121
|
-
* overlay on the map. Its layers are repopulated separately by the map's
|
|
122
|
-
* `refreshOverlay` call in `whenStyleUpdates`.
|
|
123
|
-
*
|
|
124
|
-
* No-op before `onMapLoad` has run (2D mode, or the lazy 3D libraries still loading):
|
|
125
|
-
* `deckMod` is the same readiness signal `onMapLoad` itself checks, and the initial
|
|
126
|
-
* `onMapLoad` will add the layer once it completes.
|
|
127
|
-
*/
|
|
128
|
-
reattachToStyle() {
|
|
129
|
-
if (!this.deckMod)
|
|
130
|
-
return;
|
|
131
|
-
// Hide MapLibre icon layers again — replaced by deck.gl IconLayer. These are layout
|
|
132
|
-
// properties on the STYLE's layers, so the swap reset them to visible.
|
|
133
|
-
this.hideLayers(this.scene3DConfig.hideLayers);
|
|
134
|
-
// `dispose()` (via the removed layer's `onRemove`) uninstalled this.
|
|
135
|
-
this.installDebugStatsHook();
|
|
136
|
-
const models = this.scene3DConfig.models;
|
|
137
|
-
const glbModelsActive = models !== undefined && models.enabled !== false && models.assets.length > 0;
|
|
138
|
-
// `createGlbCustomLayer` rebuilds the scene, its lights and the renderer (in `onAdd`)
|
|
139
|
-
// and kicks off a fresh template load, so it fully recovers from the dispose.
|
|
140
|
-
if (glbModelsActive) {
|
|
141
|
-
this.map.addLayer(this.createGlbCustomLayer());
|
|
142
|
-
}
|
|
143
|
-
this.updateDeckOverlay();
|
|
144
|
-
}
|
|
145
|
-
/** Re-applies zoom-based model transparency and evaluates LOD; call on every map `zoom`. */
|
|
146
|
-
onZoom() {
|
|
147
|
-
this.applyModelOpacity();
|
|
148
|
-
this.updateLodForZoom();
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Evaluates the zoom-based LOD (see {@link Models3DConfig.lod}): unloads the GLB models from
|
|
152
|
-
* memory when the camera drops below the unload boundary and reloads them when it returns to the
|
|
153
|
-
* reload boundary. No-op when LOD is disabled. Cheap enough to call on every `zoom` event — it
|
|
154
|
-
* only acts on a boundary crossing.
|
|
155
|
-
*/
|
|
156
|
-
updateLodForZoom() {
|
|
157
|
-
if (!this.lodThresholds) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const action = (0, _3d_1.lodActionForZoom)(this.map.getZoom(), this.lodThresholds, this.modelsUnloadedByLod);
|
|
161
|
-
if (action === "unload") {
|
|
162
|
-
if (this.glbTemplatesLoadInFlight) {
|
|
163
|
-
// A load is already populating the model maps; unloading now would race with it.
|
|
164
|
-
// reloadScene3DModelsForLod()/the initial load re-evaluate the zoom once they settle.
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
this.unloadScene3DModelsForLod();
|
|
168
|
-
}
|
|
169
|
-
else if (action === "reload") {
|
|
170
|
-
void this.reloadScene3DModelsForLod();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Frees the GLB models' GPU resources (placed group's material clones + every cached template's
|
|
175
|
-
* geometry/textures) while keeping the custom layer, its `WebGLRenderer`, scene and lights alive,
|
|
176
|
-
* so a later {@link reloadScene3DModelsForLod} can repopulate without recreating the map layer.
|
|
177
|
-
* Distinct from {@link dispose}, which tears the whole controller down.
|
|
178
|
-
*/
|
|
179
|
-
unloadScene3DModelsForLod() {
|
|
180
|
-
var _a, _b;
|
|
181
|
-
const three = (_a = this.threeMod) === null || _a === void 0 ? void 0 : _a.THREE;
|
|
182
|
-
if (!three) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (this.scene3DModelsGroup) {
|
|
186
|
-
(0, threeScene_1.disposeMaterialsOnly)(three, this.scene3DModelsGroup);
|
|
187
|
-
(_b = this.scene3DGlbScene) === null || _b === void 0 ? void 0 : _b.remove(this.scene3DModelsGroup);
|
|
188
|
-
this.scene3DModelsGroup = undefined;
|
|
189
|
-
}
|
|
190
|
-
// Iterate the deduped-by-URL roots so a shared root is never disposed twice (mirrors dispose()).
|
|
191
|
-
for (const root of this.scene3DGlbRootByUrl.values()) {
|
|
192
|
-
(0, threeScene_1.disposeObject3D)(three, root);
|
|
193
|
-
}
|
|
194
|
-
this.scene3DGlbRootByUrl.clear();
|
|
195
|
-
this.scene3DGlbTemplateById.clear();
|
|
196
|
-
this.modelsUnloadedByLod = true;
|
|
197
|
-
this.map.triggerRepaint();
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Reloads the GLB templates from disk/network and re-places them for the current floor after an
|
|
201
|
-
* LOD unload. Guards against overlapping reloads, and re-evaluates the zoom on completion so a
|
|
202
|
-
* camera that zoomed back out mid-download ends up unloaded again rather than stuck loaded.
|
|
203
|
-
*/
|
|
204
|
-
async reloadScene3DModelsForLod() {
|
|
205
|
-
if (this.glbTemplatesLoadInFlight) {
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
this.glbTemplatesLoadInFlight = true;
|
|
209
|
-
try {
|
|
210
|
-
await this.loadScene3DGlbTemplates();
|
|
211
|
-
this.placeScene3DModels();
|
|
212
|
-
// Only mark as loaded once placement actually succeeded, so a failed reload leaves
|
|
213
|
-
// modelsUnloadedByLod = true and the trailing updateLodForZoom() below retries it.
|
|
214
|
-
this.modelsUnloadedByLod = false;
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
console.error("Failed to reload declarative GLB models after LOD unload", err);
|
|
218
|
-
}
|
|
219
|
-
finally {
|
|
220
|
-
this.glbTemplatesLoadInFlight = false;
|
|
221
|
-
}
|
|
222
|
-
// The camera may have moved during the async load; re-evaluate so a zoom back out unloads again.
|
|
223
|
-
this.updateLodForZoom();
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Sets the route path used for GLB route-transparency collision and re-evaluates which models a
|
|
227
|
-
* route currently crosses. Points are tagged with the floor on which their route segment is
|
|
228
|
-
* visible; only points on the current floor participate, so a model never fades unless a route is
|
|
229
|
-
* visibly crossing it on the displayed floor. Pass an empty array to clear.
|
|
230
|
-
*/
|
|
231
|
-
setRouteCollisionPath(points) {
|
|
232
|
-
this.scene3DRouteCollisionPoints = points;
|
|
233
|
-
this.recomputeRouteAffectedModels();
|
|
234
|
-
this.applyModelOpacity();
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Rebuilds the deck overlay and GLB placements for the given floor. Host apps call this on every
|
|
238
|
-
* floor/place-changing navigation, including many that leave the floor unchanged (e.g. re-entering
|
|
239
|
-
* a screen, tapping the already-active floor selector); a no-op floor is skipped so those don't
|
|
240
|
-
* trigger a full, wasteful GLB rebuild.
|
|
241
|
-
*/
|
|
242
|
-
onFloorChange(currentFloor) {
|
|
243
|
-
const nextFloor = currentFloor !== null && currentFloor !== void 0 ? currentFloor : "";
|
|
244
|
-
if (nextFloor === this.currentFloor) {
|
|
245
|
-
return;
|
|
246
|
-
}
|
|
247
|
-
this.currentFloor = nextFloor;
|
|
248
|
-
this.updateDeckOverlay();
|
|
249
|
-
this.placeScene3DModels();
|
|
250
|
-
}
|
|
251
|
-
/** Rebuilds only the deck overlay (no GLB replacement) for the given floor. */
|
|
252
|
-
refreshOverlay(currentFloor) {
|
|
253
|
-
this.currentFloor = currentFloor !== null && currentFloor !== void 0 ? currentFloor : "";
|
|
254
|
-
this.updateDeckOverlay();
|
|
255
|
-
}
|
|
256
|
-
/**
|
|
257
|
-
* A point-in-time snapshot of the 3D scene's memory drivers, for profiling. The Three.js
|
|
258
|
-
* `renderer.info` counters are the best in-page proxy for GLB VRAM (decoded geometries/textures
|
|
259
|
-
* live on the GPU and are not visible to `performance.memory`, which only reports the JS heap).
|
|
260
|
-
* The deck tile counts approximate the overlay's parsed-tile working set — the thing the tile-cache
|
|
261
|
-
* bounds (see {@link DeckTileCacheConfig}) keep from growing without limit as the map pans/zooms.
|
|
262
|
-
*
|
|
263
|
-
* @group Map
|
|
264
|
-
*/
|
|
265
|
-
getDebugStats() {
|
|
266
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
267
|
-
const info = (_a = this.glbRenderer) === null || _a === void 0 ? void 0 : _a.info;
|
|
268
|
-
const three = info
|
|
269
|
-
? {
|
|
270
|
-
geometries: info.memory.geometries,
|
|
271
|
-
textures: info.memory.textures,
|
|
272
|
-
programs: (_c = (_b = info.programs) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0,
|
|
273
|
-
renderCalls: info.render.calls,
|
|
274
|
-
triangles: info.render.triangles,
|
|
275
|
-
}
|
|
276
|
-
: null;
|
|
277
|
-
let deckTiles = null;
|
|
278
|
-
try {
|
|
279
|
-
// Best-effort read of each overlay layer's parsed-tile count. deck.gl internals are private,
|
|
280
|
-
// so this is guarded — it degrades to `null` rather than throwing if the shape changes.
|
|
281
|
-
const overlay = this.deckOverlay;
|
|
282
|
-
const layers = (_e = (_d = overlay === null || overlay === void 0 ? void 0 : overlay._deck) === null || _d === void 0 ? void 0 : _d.layerManager) === null || _e === void 0 ? void 0 : _e.layers;
|
|
283
|
-
if (Array.isArray(layers)) {
|
|
284
|
-
const counts = {};
|
|
285
|
-
for (const layer of layers) {
|
|
286
|
-
const size = (_h = (_g = (_f = layer === null || layer === void 0 ? void 0 : layer.state) === null || _f === void 0 ? void 0 : _f.tileset) === null || _g === void 0 ? void 0 : _g._cache) === null || _h === void 0 ? void 0 : _h.size;
|
|
287
|
-
if (typeof size === "number" && layer.id) {
|
|
288
|
-
// Strip deck's per-sublayer suffixes so the four MVT roots aggregate by base id.
|
|
289
|
-
const baseId = layer.id.split("-sublayer")[0];
|
|
290
|
-
counts[baseId] = Math.max((_j = counts[baseId]) !== null && _j !== void 0 ? _j : 0, size);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
deckTiles = counts;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
catch (_m) {
|
|
297
|
-
deckTiles = null;
|
|
298
|
-
}
|
|
299
|
-
return {
|
|
300
|
-
zoom: this.map.getZoom(),
|
|
301
|
-
currentFloor: this.currentFloor,
|
|
302
|
-
modelsUnloadedByLod: this.modelsUnloadedByLod,
|
|
303
|
-
glbRoots: this.scene3DGlbRootByUrl.size,
|
|
304
|
-
glbTemplates: this.scene3DGlbTemplateById.size,
|
|
305
|
-
placedModels: (_l = (_k = this.scene3DModelsGroup) === null || _k === void 0 ? void 0 : _k.children.length) !== null && _l !== void 0 ? _l : 0,
|
|
306
|
-
three,
|
|
307
|
-
deckTiles,
|
|
308
|
-
deckTileCache: this.deckTileCache,
|
|
309
|
-
lodThresholds: this.lodThresholds,
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Exposes {@link getDebugStats} as `globalThis.__mvxScene3DStats()` when the opt-in flag
|
|
314
|
-
* `globalThis.__MVX_SCENE3D_DEBUG__` is truthy, so a profiling session can read the scene's memory
|
|
315
|
-
* drivers from the console/tests without external tooling. No-op (and no global written) otherwise.
|
|
316
|
-
*/
|
|
317
|
-
installDebugStatsHook() {
|
|
318
|
-
const g = globalThis;
|
|
319
|
-
if (g.__MVX_SCENE3D_DEBUG__) {
|
|
320
|
-
g.__mvxScene3DStats = () => this.getDebugStats();
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
/** Removes the debug hook if it points at this controller. Called from {@link dispose}. */
|
|
324
|
-
uninstallDebugStatsHook() {
|
|
325
|
-
const g = globalThis;
|
|
326
|
-
if (g.__mvxScene3DStats) {
|
|
327
|
-
delete g.__mvxScene3DStats;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Releases every GPU resource this controller owns: the placed models group's material clones,
|
|
332
|
-
* the cached GLB templates' geometries/materials/textures, and the `WebGLRenderer` created for the
|
|
333
|
-
* declarative GLB layer. Idempotent — safe to call more than once (e.g. once via the custom
|
|
334
|
-
* layer's own `onRemove` and again explicitly).
|
|
335
|
-
*
|
|
336
|
-
* Callers must invoke this themselves before tearing down the map: MapLibre's `map.remove()` does
|
|
337
|
-
* not call a custom layer's `onRemove`, so without an explicit call here nothing ever frees these
|
|
338
|
-
* resources, and every context-loss recovery cycle (destroy + recreate the map) leaks a full scene.
|
|
339
|
-
*/
|
|
340
|
-
dispose() {
|
|
341
|
-
var _a, _b;
|
|
342
|
-
const three = (_a = this.threeMod) === null || _a === void 0 ? void 0 : _a.THREE;
|
|
343
|
-
if (three) {
|
|
344
|
-
if (this.scene3DModelsGroup) {
|
|
345
|
-
(0, threeScene_1.disposeMaterialsOnly)(three, this.scene3DModelsGroup);
|
|
346
|
-
this.scene3DModelsGroup = undefined;
|
|
347
|
-
}
|
|
348
|
-
// Iterate the deduplicated-by-URL roots (not `scene3DGlbTemplateById`, which may map several
|
|
349
|
-
// asset ids to the same root) so a shared root is never disposed more than once.
|
|
350
|
-
for (const root of this.scene3DGlbRootByUrl.values()) {
|
|
351
|
-
(0, threeScene_1.disposeObject3D)(three, root);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
this.scene3DGlbRootByUrl.clear();
|
|
355
|
-
this.scene3DGlbTemplateById.clear();
|
|
356
|
-
this.scene3DGlbScene = undefined;
|
|
357
|
-
(_b = this.glbRenderer) === null || _b === void 0 ? void 0 : _b.dispose();
|
|
358
|
-
this.glbRenderer = undefined;
|
|
359
|
-
this.uninstallDebugStatsHook();
|
|
360
|
-
}
|
|
361
|
-
// Internal implementation -----------------------------------------------------------------
|
|
362
|
-
/**
|
|
363
|
-
* Lazily loads the Three.js / deck.gl runtime handles (dynamic import) and caches them so the
|
|
364
|
-
* heavy libraries stay out of the base 2D bundle.
|
|
365
|
-
*/
|
|
366
|
-
async ensure3DLibs() {
|
|
367
|
-
var _a, _b;
|
|
368
|
-
(_a = this.threeMod) !== null && _a !== void 0 ? _a : (this.threeMod = await (0, three_1.loadThree)());
|
|
369
|
-
(_b = this.deckMod) !== null && _b !== void 0 ? _b : (this.deckMod = await (0, three_1.loadDeck)());
|
|
370
|
-
}
|
|
371
|
-
createGlbCustomLayer() {
|
|
372
|
-
const three = this.threeMod.THREE;
|
|
373
|
-
const refMercator = maplibre_gl_1.MercatorCoordinate.fromLngLat(this.initialCenter, 0);
|
|
374
|
-
const meterScale = refMercator.meterInMercatorCoordinateUnits();
|
|
375
|
-
const modelTransform = new three.Matrix4()
|
|
376
|
-
.makeTranslation(refMercator.x, refMercator.y, refMercator.z)
|
|
377
|
-
.scale(new three.Vector3(meterScale, -meterScale, meterScale))
|
|
378
|
-
.multiply(new three.Matrix4().makeRotationX(Math.PI / 2));
|
|
379
|
-
this.scene3DGlbScene = new three.Scene();
|
|
380
|
-
const models = this.scene3DConfig.models;
|
|
381
|
-
if (models && models.enabled !== false && models.assets.length > 0) {
|
|
382
|
-
const lights = (0, _3d_1.resolveModels3DLights)(models);
|
|
383
|
-
this.scene3DGlbLightCount = (0, threeScene_1.addScene3DLights)(three, this.scene3DGlbScene, lights);
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
this.scene3DGlbLightCount = 0;
|
|
387
|
-
}
|
|
388
|
-
this.glbTemplatesLoadInFlight = true;
|
|
389
|
-
void this.loadScene3DGlbTemplates()
|
|
390
|
-
.then(() => {
|
|
391
|
-
this.placeScene3DModels();
|
|
392
|
-
})
|
|
393
|
-
.catch((err) => {
|
|
394
|
-
console.error("Failed to initialize declarative GLB models", err);
|
|
395
|
-
})
|
|
396
|
-
.finally(() => {
|
|
397
|
-
this.glbTemplatesLoadInFlight = false;
|
|
398
|
-
// Honor LOD from the first frame: if the map opened below the unload threshold, free the
|
|
399
|
-
// models just loaded (they reload when the user zooms in). No-op when LOD is disabled.
|
|
400
|
-
this.updateLodForZoom();
|
|
401
|
-
});
|
|
402
|
-
let renderer = undefined;
|
|
403
|
-
let camera = undefined;
|
|
404
|
-
const tmpMatrix = new three.Matrix4();
|
|
405
|
-
return {
|
|
406
|
-
id: "3d-glb-models",
|
|
407
|
-
type: "custom",
|
|
408
|
-
renderingMode: "3d",
|
|
409
|
-
onAdd: (_map, gl) => {
|
|
410
|
-
camera = new three.Camera();
|
|
411
|
-
renderer = new three.WebGLRenderer({
|
|
412
|
-
canvas: _map.getCanvas(),
|
|
413
|
-
context: gl,
|
|
414
|
-
antialias: true,
|
|
415
|
-
});
|
|
416
|
-
renderer.autoClear = false;
|
|
417
|
-
// MapLibre owns canvas dimensions; only sync the GL viewport on resize.
|
|
418
|
-
renderer.setSize = function (width, height) {
|
|
419
|
-
this.setViewport(0, 0, width, height);
|
|
420
|
-
};
|
|
421
|
-
// Stored on the controller (not just this closure) so `dispose()` can release it — MapLibre
|
|
422
|
-
// does not call a custom layer's `onRemove` when the whole map/style is torn down via
|
|
423
|
-
// `map.remove()`, so callers must dispose this controller explicitly before that happens.
|
|
424
|
-
this.glbRenderer = renderer;
|
|
425
|
-
},
|
|
426
|
-
onRemove: () => {
|
|
427
|
-
this.dispose();
|
|
428
|
-
},
|
|
429
|
-
render: (_gl, args) => {
|
|
430
|
-
var _a;
|
|
431
|
-
if (!camera ||
|
|
432
|
-
!renderer ||
|
|
433
|
-
!this.scene3DGlbScene ||
|
|
434
|
-
!this.scene3DModelsGroup ||
|
|
435
|
-
this.scene3DModelsGroup.children.length === 0) {
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const canvas = this.map.getCanvas();
|
|
439
|
-
const width = canvas.width;
|
|
440
|
-
const height = canvas.height;
|
|
441
|
-
// Skip while the framebuffer has no drawable area (e.g. devtools opening).
|
|
442
|
-
if (width === 0 || height === 0) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const mainMatrix = (_a = args.defaultProjectionData) === null || _a === void 0 ? void 0 : _a.mainMatrix;
|
|
446
|
-
if (!mainMatrix) {
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
renderer.setSize(width, height);
|
|
450
|
-
tmpMatrix.fromArray(mainMatrix).multiply(modelTransform);
|
|
451
|
-
camera.projectionMatrix.copy(tmpMatrix);
|
|
452
|
-
renderer.resetState();
|
|
453
|
-
renderer.render(this.scene3DGlbScene, camera);
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
async loadScene3DGlbTemplates() {
|
|
458
|
-
var _a;
|
|
459
|
-
const three = (_a = this.threeMod) === null || _a === void 0 ? void 0 : _a.THREE;
|
|
460
|
-
const models = this.scene3DConfig.models;
|
|
461
|
-
if (!three || !models || models.enabled === false || models.assets.length === 0) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
this.scene3DGlbRootByUrl.clear();
|
|
465
|
-
this.scene3DGlbTemplateById.clear();
|
|
466
|
-
// Load every distinct URL with bounded concurrency (see GLB_LOAD_CONCURRENCY) instead of one
|
|
467
|
-
// at a time, and let each load time out (see loadGlbRoot) — a single hung or malformed/truncated
|
|
468
|
-
// asset is skipped and logged instead of blocking every asset queued behind it. Deduplicating by
|
|
469
|
-
// URL up front — not inside the concurrent task — means assets sharing a URL never trigger
|
|
470
|
-
// duplicate loads and never tie up more than one concurrency slot waiting on the same result.
|
|
471
|
-
const uniqueUrls = [...new Set(models.assets.map((asset) => asset.url))];
|
|
472
|
-
const errorByUrl = new globalThis.Map();
|
|
473
|
-
const semaphore = new semaphore_1.Semaphore(Scene3DController.GLB_LOAD_CONCURRENCY);
|
|
474
|
-
// Progress bookkeeping for `onModelsProgress` (e.g. a totem's loading bar). Kept even when no
|
|
475
|
-
// callback is registered — the cost is a handful of Map writes over at most a few dozen assets.
|
|
476
|
-
const loadedBytesByUrl = new globalThis.Map();
|
|
477
|
-
const totalBytesByUrl = new globalThis.Map();
|
|
478
|
-
let settledAssets = 0;
|
|
479
|
-
const totalAssets = uniqueUrls.length;
|
|
480
|
-
const reportProgress = (done) => {
|
|
481
|
-
var _a;
|
|
482
|
-
if (!this.onModelsProgress)
|
|
483
|
-
return;
|
|
484
|
-
let loadedBytes = 0;
|
|
485
|
-
let totalBytes = 0;
|
|
486
|
-
for (const url of uniqueUrls) {
|
|
487
|
-
loadedBytes += (_a = loadedBytesByUrl.get(url)) !== null && _a !== void 0 ? _a : 0;
|
|
488
|
-
const total = totalBytesByUrl.get(url);
|
|
489
|
-
totalBytes = total == null || totalBytes === null ? null : totalBytes + total;
|
|
490
|
-
}
|
|
491
|
-
try {
|
|
492
|
-
this.onModelsProgress({
|
|
493
|
-
loadedAssets: settledAssets,
|
|
494
|
-
totalAssets,
|
|
495
|
-
loadedBytes,
|
|
496
|
-
totalBytes,
|
|
497
|
-
done,
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
catch (err) {
|
|
501
|
-
// Isolate the SDK's own load bookkeeping from a throwing consumer callback — letting this
|
|
502
|
-
// escape would reject the `Promise.all` below (or, from the `onProgress` path, break
|
|
503
|
-
// three.js's internal stream-reading chain) and lose every already-loaded asset.
|
|
504
|
-
console.error("onScene3DModelsProgress callback threw", err);
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
await Promise.all(uniqueUrls.map(async (url) => {
|
|
508
|
-
var _a, _b;
|
|
509
|
-
await semaphore.acquire();
|
|
510
|
-
try {
|
|
511
|
-
this.scene3DGlbRootByUrl.set(url, await (0, loadGlb_1.loadGlbRoot)(url, {
|
|
512
|
-
onProgress: (event) => {
|
|
513
|
-
loadedBytesByUrl.set(url, event.loaded);
|
|
514
|
-
totalBytesByUrl.set(url, event.lengthComputable ? event.total : null);
|
|
515
|
-
reportProgress(false);
|
|
516
|
-
},
|
|
517
|
-
}));
|
|
518
|
-
}
|
|
519
|
-
catch (err) {
|
|
520
|
-
errorByUrl.set(url, err);
|
|
521
|
-
}
|
|
522
|
-
finally {
|
|
523
|
-
settledAssets++;
|
|
524
|
-
// Once a URL has settled, its total is knowable even if no progress event ever supplied
|
|
525
|
-
// one (failed before the first byte, or the server omitted Content-Length): fall back to
|
|
526
|
-
// however many bytes actually arrived. Otherwise a single such asset would force the
|
|
527
|
-
// whole batch's `totalBytes` to stay `null` forever, even after `done: true`.
|
|
528
|
-
totalBytesByUrl.set(url, (_b = (_a = totalBytesByUrl.get(url)) !== null && _a !== void 0 ? _a : loadedBytesByUrl.get(url)) !== null && _b !== void 0 ? _b : 0);
|
|
529
|
-
semaphore.release();
|
|
530
|
-
reportProgress(false);
|
|
531
|
-
}
|
|
532
|
-
}));
|
|
533
|
-
reportProgress(true);
|
|
534
|
-
for (const asset of models.assets) {
|
|
535
|
-
const root = this.scene3DGlbRootByUrl.get(asset.url);
|
|
536
|
-
if (!root) {
|
|
537
|
-
console.error(`Failed to load GLB asset "${asset.id}" from ${asset.url}`, errorByUrl.get(asset.url));
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
const center = (0, threeScene_1.computeGlbBottomAnchor)(three, root);
|
|
541
|
-
this.scene3DGlbTemplateById.set(asset.id, { root, center });
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
placeScene3DModels() {
|
|
545
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
546
|
-
const three = (_a = this.threeMod) === null || _a === void 0 ? void 0 : _a.THREE;
|
|
547
|
-
if (!three || !this.scene3DGlbScene) {
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
const models = this.scene3DConfig.models;
|
|
551
|
-
if (!models || models.enabled === false) {
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (this.scene3DModelsGroup) {
|
|
555
|
-
// Only the per-placement material clones (made below so each placement's zoom-based opacity
|
|
556
|
-
// is independent) are exclusively owned by this group — geometries/textures come from the
|
|
557
|
-
// cached templates and stay alive for the next rebuild, so they must not be disposed here.
|
|
558
|
-
(0, threeScene_1.disposeMaterialsOnly)(three, this.scene3DModelsGroup);
|
|
559
|
-
this.scene3DGlbScene.remove(this.scene3DModelsGroup);
|
|
560
|
-
this.scene3DModelsGroup = undefined;
|
|
561
|
-
}
|
|
562
|
-
if (this.scene3DGlbTemplateById.size === 0) {
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
const refMercator = maplibre_gl_1.MercatorCoordinate.fromLngLat(this.initialCenter, 0);
|
|
566
|
-
const meterScale = refMercator.meterInMercatorCoordinateUnits();
|
|
567
|
-
const floorKey = this.currentFloor;
|
|
568
|
-
const modelsGroup = new three.Group();
|
|
569
|
-
modelsGroup.position.set((_c = (_b = models.globalOffset) === null || _b === void 0 ? void 0 : _b.x) !== null && _c !== void 0 ? _c : 0, (_e = (_d = models.globalOffset) === null || _d === void 0 ? void 0 : _d.y) !== null && _e !== void 0 ? _e : 0, (_g = (_f = models.globalOffset) === null || _f === void 0 ? void 0 : _f.z) !== null && _g !== void 0 ? _g : 0);
|
|
570
|
-
for (const placement of models.placements) {
|
|
571
|
-
if (!(0, _3d_1.isPlacementVisibleOnFloor)(placement, floorKey)) {
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
const template = this.scene3DGlbTemplateById.get(placement.assetId);
|
|
575
|
-
if (!template) {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
const asset = models.assets.find((a) => a.id === placement.assetId);
|
|
579
|
-
if (!asset) {
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
|
-
const target = maplibre_gl_1.MercatorCoordinate.fromLngLat([placement.position.lng, placement.position.lat], (_h = placement.position.altitude) !== null && _h !== void 0 ? _h : 0);
|
|
583
|
-
const [posX, posY, posZ] = (0, _3d_1.computeScenePosition)(target, refMercator, meterScale, asset.defaultModelOffset, placement.offset);
|
|
584
|
-
const pivot = new three.Group();
|
|
585
|
-
pivot.position.set(posX, posY, posZ);
|
|
586
|
-
const mergedRot = (0, _3d_1.mergeRotation3D)(asset.defaultRotation, placement.rotation);
|
|
587
|
-
pivot.rotation.set(three.MathUtils.degToRad(mergedRot.pitch), three.MathUtils.degToRad(-mergedRot.yaw), three.MathUtils.degToRad(mergedRot.roll), "YXZ");
|
|
588
|
-
const [sx3, sy3, sz3] = (0, _3d_1.mergeGlbScale)(asset.defaultScale, placement.scale);
|
|
589
|
-
pivot.scale.set(sx3, sy3, sz3);
|
|
590
|
-
const model = template.root.clone(true);
|
|
591
|
-
// `clone(true)` shares materials between instances; clone them per placement so each
|
|
592
|
-
// placement's zoom-based opacity is independent and does not bleed into sibling models.
|
|
593
|
-
model.traverse((obj) => {
|
|
594
|
-
const mesh = obj;
|
|
595
|
-
if (!mesh.isMesh) {
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
mesh.material = Array.isArray(mesh.material)
|
|
599
|
-
? mesh.material.map((m) => m.clone())
|
|
600
|
-
: mesh.material.clone();
|
|
601
|
-
});
|
|
602
|
-
model.position.set(-template.center.x, -template.center.y, -template.center.z);
|
|
603
|
-
// Stash the resolved zoom-transparency config so it can be re-applied on every zoom change.
|
|
604
|
-
pivot.userData.zoomTransparency = (0, _3d_1.resolveZoomTransparency)(models, asset, placement);
|
|
605
|
-
// Stash route-transparency config (may be undefined => feature off for this model) so a
|
|
606
|
-
// route crossing this model can fade it independently of zoom.
|
|
607
|
-
pivot.userData.routeTransparency = (0, _3d_1.resolveRouteTransparency)(models, asset, placement);
|
|
608
|
-
// Precompute the model footprint in the pivot's local x/z frame while `model` has no parent
|
|
609
|
-
// (its world matrix equals its local matrix), for cheap point-in-building collision tests.
|
|
610
|
-
// After `pivot.add(model)` the world matrix would fold in the pivot transform, giving a loose
|
|
611
|
-
// world-axis box instead; `pivot.worldToLocal` at test time undoes the pivot transform.
|
|
612
|
-
const localBox = new three.Box3().setFromObject(model);
|
|
613
|
-
pivot.userData.footprint = {
|
|
614
|
-
minX: localBox.min.x,
|
|
615
|
-
maxX: localBox.max.x,
|
|
616
|
-
minZ: localBox.min.z,
|
|
617
|
-
maxZ: localBox.max.z,
|
|
618
|
-
};
|
|
619
|
-
pivot.add(model);
|
|
620
|
-
modelsGroup.add(pivot);
|
|
621
|
-
}
|
|
622
|
-
this.scene3DModelsGroup = modelsGroup;
|
|
623
|
-
this.scene3DGlbScene.add(modelsGroup);
|
|
624
|
-
// Recompute route collision against the freshly rebuilt pivots (footprints/floor changed),
|
|
625
|
-
// then apply the combined opacity.
|
|
626
|
-
this.recomputeRouteAffectedModels();
|
|
627
|
-
this.applyModelOpacity();
|
|
628
|
-
this.map.triggerRepaint();
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* Recomputes `pivot.userData.routeAffected` for every placed model by testing the current-floor
|
|
632
|
-
* route collision points against each model's precomputed footprint. Cheap point-in-box tests in
|
|
633
|
-
* the pivot's local x/z frame; runs only when the route path or the placed models change, never
|
|
634
|
-
* per zoom frame.
|
|
635
|
-
*/
|
|
636
|
-
recomputeRouteAffectedModels() {
|
|
637
|
-
var _a;
|
|
638
|
-
const three = (_a = this.threeMod) === null || _a === void 0 ? void 0 : _a.THREE;
|
|
639
|
-
const modelsGroup = this.scene3DModelsGroup;
|
|
640
|
-
if (!three || !modelsGroup) {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
const points = this.scene3DRouteCollisionPoints.filter((p) => p.floorKey === this.currentFloor);
|
|
644
|
-
// Convert each route point into the models group's local frame (metres relative to
|
|
645
|
-
// initialCenter), exactly as placements are positioned in `placeScene3DModels`. The group's
|
|
646
|
-
// own transform (globalOffset) is applied via `localToWorld` at test time, so route points and
|
|
647
|
-
// model pivots share one frame regardless of the collective offset.
|
|
648
|
-
const refMercator = maplibre_gl_1.MercatorCoordinate.fromLngLat(this.initialCenter, 0);
|
|
649
|
-
const meterScale = refMercator.meterInMercatorCoordinateUnits();
|
|
650
|
-
const groupLocalPoints = points.map((p) => {
|
|
651
|
-
const target = maplibre_gl_1.MercatorCoordinate.fromLngLat([p.lng, p.lat], 0);
|
|
652
|
-
const x = (target.x - refMercator.x) / meterScale;
|
|
653
|
-
const z = (target.y - refMercator.y) / meterScale;
|
|
654
|
-
return new three.Vector3(x, 0, z);
|
|
655
|
-
});
|
|
656
|
-
modelsGroup.updateMatrixWorld(true);
|
|
657
|
-
const local = new three.Vector3();
|
|
658
|
-
for (const pivot of modelsGroup.children) {
|
|
659
|
-
const routeCfg = pivot.userData.routeTransparency;
|
|
660
|
-
const footprint = pivot.userData.footprint;
|
|
661
|
-
// Only models with a route-transparency config participate (feature activation gate).
|
|
662
|
-
if (!routeCfg || !footprint || groupLocalPoints.length === 0) {
|
|
663
|
-
pivot.userData.routeAffected = false;
|
|
664
|
-
continue;
|
|
665
|
-
}
|
|
666
|
-
pivot.userData.routeAffected = groupLocalPoints.some((gp) => {
|
|
667
|
-
// group-local -> world (applies globalOffset) -> pivot-local (undoes position/rotation/
|
|
668
|
-
// scale), then test its x/z against the footprint. y is ignored (floor-level footprint).
|
|
669
|
-
local.copy(gp);
|
|
670
|
-
modelsGroup.localToWorld(local);
|
|
671
|
-
pivot.worldToLocal(local);
|
|
672
|
-
return (0, _3d_1.pointInFootprint)(footprint, local.x, local.z);
|
|
673
|
-
});
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
/**
|
|
677
|
-
* Applies transparency to placed GLB models, combining zoom-based transparency
|
|
678
|
-
* ({@link ZoomTransparencyConfig}, a hard threshold: fully opaque below the configured zoom,
|
|
679
|
-
* configured opacity at/above it) with route-based transparency
|
|
680
|
-
* ({@link RouteTransparencyConfig}) for models a route currently crosses
|
|
681
|
-
* (`pivot.userData.routeAffected`). The more transparent of the two wins. Only triggers a
|
|
682
|
-
* repaint when at least one material actually changed.
|
|
683
|
-
*/
|
|
684
|
-
applyModelOpacity() {
|
|
685
|
-
if (!this.scene3DModelsGroup) {
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const zoom = this.map.getZoom();
|
|
689
|
-
let changed = false;
|
|
690
|
-
for (const pivot of this.scene3DModelsGroup.children) {
|
|
691
|
-
const zoomCfg = pivot.userData.zoomTransparency;
|
|
692
|
-
const routeCfg = pivot.userData.routeTransparency;
|
|
693
|
-
const routeAffected = pivot.userData.routeAffected === true;
|
|
694
|
-
const opacity = (0, _3d_1.effectiveModelOpacity)(zoomCfg, routeCfg, zoom, routeAffected);
|
|
695
|
-
pivot.traverse((obj) => {
|
|
696
|
-
const mesh = obj;
|
|
697
|
-
if (!mesh.isMesh) {
|
|
698
|
-
return;
|
|
699
|
-
}
|
|
700
|
-
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
701
|
-
for (const material of materials) {
|
|
702
|
-
if (material.opacity !== opacity) {
|
|
703
|
-
material.transparent = opacity < 1;
|
|
704
|
-
material.opacity = opacity;
|
|
705
|
-
// Disable depth writes while transparent to avoid draw-order artifacts.
|
|
706
|
-
material.depthWrite = opacity >= 1;
|
|
707
|
-
changed = true;
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
if (changed) {
|
|
713
|
-
this.map.triggerRepaint();
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
async loadSpriteAtlas(spriteUrl) {
|
|
717
|
-
const [json, img] = await Promise.all([
|
|
718
|
-
fetch(`${spriteUrl}.json`).then((r) => r.json()),
|
|
719
|
-
new Promise((resolve, reject) => {
|
|
720
|
-
const image = new Image();
|
|
721
|
-
image.crossOrigin = "anonymous";
|
|
722
|
-
image.onload = () => resolve(image);
|
|
723
|
-
image.onerror = reject;
|
|
724
|
-
image.src = `${spriteUrl}.png`;
|
|
725
|
-
}),
|
|
726
|
-
]);
|
|
727
|
-
this.spriteIconMapping = json;
|
|
728
|
-
this.spriteAtlasImage = img;
|
|
729
|
-
}
|
|
730
|
-
/**
|
|
731
|
-
* Cache-bounding props spread onto every deck.gl `MVTLayer` so the overlays never keep an
|
|
732
|
-
* unbounded set of parsed tiles in memory. `maxCacheByteSize` is only included when configured
|
|
733
|
-
* (deck.gl enforces it only when a tile's byte size is known).
|
|
734
|
-
*/
|
|
735
|
-
get deckTileCacheProps() {
|
|
736
|
-
const { maxCacheSize, maxRequests, refinementStrategy, maxCacheByteSize } = this.deckTileCache;
|
|
737
|
-
const props = { maxCacheSize, maxRequests, refinementStrategy };
|
|
738
|
-
if (maxCacheByteSize !== undefined) {
|
|
739
|
-
props.maxCacheByteSize = maxCacheByteSize;
|
|
740
|
-
}
|
|
741
|
-
return props;
|
|
742
|
-
}
|
|
743
|
-
makeElevatedLinesLayer(floorKey, layerConfig) {
|
|
744
|
-
var _a;
|
|
745
|
-
const deck = this.deckMod;
|
|
746
|
-
if (!deck)
|
|
747
|
-
return null;
|
|
748
|
-
return new deck.MVTLayer(Object.assign(Object.assign({}, this.deckTileCacheProps), { id: "indoor-lines-elevated", data: layerConfig.dataUrl, binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false, renderSubLayers: (props) => {
|
|
749
|
-
var _a, _b, _c, _d;
|
|
750
|
-
const paths = (0, deckLayers_1.buildElevatedPaths)((props.data || []), floorKey, (_a = layerConfig.defaultHeight) !== null && _a !== void 0 ? _a : 5);
|
|
751
|
-
if (!paths.length)
|
|
752
|
-
return null;
|
|
753
|
-
// Default color hardcoded to "#cbcbcb" (from the original style) to avoid a type mismatch.
|
|
754
|
-
return new deck.PathLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-paths`, data: paths, getPath: (d) => d.path, getColor: (0, _3d_1.hexToRgb)((_b = layerConfig.lineColor) !== null && _b !== void 0 ? _b : "#cbcbcb"), getWidth: (_c = layerConfig.lineWidth) !== null && _c !== void 0 ? _c : 1.5, widthUnits: (_d = layerConfig.widthUnits) !== null && _d !== void 0 ? _d : "pixels", pickable: false }));
|
|
755
|
-
}, updateTriggers: {
|
|
756
|
-
renderSubLayers: [floorKey],
|
|
757
|
-
} }));
|
|
758
|
-
}
|
|
759
|
-
makeLabelLayer(floorKey, layerConfig, useSdf = true) {
|
|
760
|
-
var _a, _b, _c;
|
|
761
|
-
const deck = this.deckMod;
|
|
762
|
-
if (!deck)
|
|
763
|
-
return null;
|
|
764
|
-
return new deck.MVTLayer(Object.assign(Object.assign({}, this.deckTileCacheProps), { id: "indoor-labels", data: layerConfig.dataUrl, binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false, minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 19.5, maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24, renderSubLayers: (props) => {
|
|
765
|
-
var _a, _b, _c, _d, _e, _f;
|
|
766
|
-
// Use area_name and poi Point features — unique per tile, no duplicates.
|
|
767
|
-
// If a logo-{name} image exists in the sprite, the icon takes priority over the text label.
|
|
768
|
-
const textData = (0, deckLayers_1.buildLabelData)((props.data || []), floorKey, (imageId) => this.map.hasImage(imageId), (_a = layerConfig.defaultHeight) !== null && _a !== void 0 ? _a : 5);
|
|
769
|
-
if (!textData.length)
|
|
770
|
-
return null;
|
|
771
|
-
return new deck.TextLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-labels`, data: textData, getPosition: (d) => d.position, getText: (d) => d.text, getSize: (_b = layerConfig.size) !== null && _b !== void 0 ? _b : 16, getColor: (0, _3d_1.hexToRgb)((_c = layerConfig.color) !== null && _c !== void 0 ? _c : "#91969b"), outlineColor: useSdf ? [255, 255, 255, 255] : [0, 0, 0, 0], outlineWidth: useSdf ? 4 : 0, fontSettings: { sdf: useSdf }, fontFamily: (_d = layerConfig.fontFamily) !== null && _d !== void 0 ? _d : "Open Sans, sans-serif", fontWeight: (_e = layerConfig.fontWeight) !== null && _e !== void 0 ? _e : 600, billboard: true, pickable: false, characterSet: "auto", wordBreak: "break-word", maxWidth: (_f = layerConfig.maxWidth) !== null && _f !== void 0 ? _f : 150 }));
|
|
772
|
-
}, updateTriggers: {
|
|
773
|
-
renderSubLayers: [floorKey],
|
|
774
|
-
} }));
|
|
775
|
-
}
|
|
776
|
-
makePoiIconLayer(floorKey, layerConfig) {
|
|
777
|
-
var _a, _b, _c;
|
|
778
|
-
const deck = this.deckMod;
|
|
779
|
-
if (!deck || !this.spriteIconMapping || !this.spriteAtlasImage)
|
|
780
|
-
return null;
|
|
781
|
-
return new deck.MVTLayer(Object.assign(Object.assign({}, this.deckTileCacheProps), { id: "indoor-poi-icons", data: layerConfig.dataUrl, binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false, minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 17, maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24, renderSubLayers: (props) => {
|
|
782
|
-
var _a, _b, _c, _d, _e;
|
|
783
|
-
const iconData = (0, deckLayers_1.buildPoiIconData)((props.data || []), floorKey, (_a = layerConfig.defaultHeight) !== null && _a !== void 0 ? _a : 5);
|
|
784
|
-
if (!iconData.length)
|
|
785
|
-
return null;
|
|
786
|
-
return new deck.IconLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-poi-icons`, data: iconData, iconAtlas: this.spriteAtlasImage, iconMapping: this.spriteIconMapping, getIcon: (d) => d.icon, getPosition: (d) => d.position, getSize: (_b = layerConfig.size) !== null && _b !== void 0 ? _b : 2, sizeUnits: (_c = layerConfig.sizeUnits) !== null && _c !== void 0 ? _c : "meters", sizeScale: (_d = layerConfig.sizeScale) !== null && _d !== void 0 ? _d : 1, billboard: (_e = layerConfig.billboard) !== null && _e !== void 0 ? _e : true, pickable: false }));
|
|
787
|
-
}, updateTriggers: {
|
|
788
|
-
renderSubLayers: [floorKey],
|
|
789
|
-
} }));
|
|
790
|
-
}
|
|
791
|
-
makeStoreLogoLayer(floorKey, layerConfig) {
|
|
792
|
-
var _a, _b, _c;
|
|
793
|
-
const deck = this.deckMod;
|
|
794
|
-
if (!deck || !this.spriteIconMapping || !this.spriteAtlasImage)
|
|
795
|
-
return null;
|
|
796
|
-
return new deck.MVTLayer(Object.assign(Object.assign({}, this.deckTileCacheProps), { id: "indoor-store-logos", data: layerConfig.dataUrl, binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false, minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 13, maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24, renderSubLayers: (props) => {
|
|
797
|
-
var _a, _b, _c, _d;
|
|
798
|
-
const logoData = (0, deckLayers_1.buildStoreLogoData)((props.data || []), floorKey, this.spriteIconMapping, (_a = layerConfig.defaultHeight) !== null && _a !== void 0 ? _a : 5);
|
|
799
|
-
if (!logoData.length)
|
|
800
|
-
return null;
|
|
801
|
-
return new deck.IconLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-store-logos`, data: logoData, iconAtlas: this.spriteAtlasImage, iconMapping: this.spriteIconMapping, getIcon: (d) => d.icon, getPosition: (d) => d.position, getAngle: (d) => -d.rotation, getSize: (d) => { var _a; return (0, deckLayers_1.computeStoreLogoSize)(this.spriteIconMapping, d.icon, (_a = layerConfig.logoScale) !== null && _a !== void 0 ? _a : {}); }, sizeUnits: (_b = layerConfig.sizeUnits) !== null && _b !== void 0 ? _b : "meters", sizeScale: (_c = layerConfig.sizeScale) !== null && _c !== void 0 ? _c : 1, billboard: (_d = layerConfig.billboard) !== null && _d !== void 0 ? _d : false, pickable: false }));
|
|
802
|
-
}, updateTriggers: {
|
|
803
|
-
renderSubLayers: [floorKey],
|
|
804
|
-
} }));
|
|
805
|
-
}
|
|
806
|
-
updateDeckOverlay() {
|
|
807
|
-
if (!this.deckOverlay || !this.currentFloor)
|
|
808
|
-
return;
|
|
809
|
-
if (!this.scene3DConfig.customLayers)
|
|
810
|
-
return;
|
|
811
|
-
const { customLayers } = this.scene3DConfig;
|
|
812
|
-
const layers = [];
|
|
813
|
-
if (customLayers.elevatedLinesLayer) {
|
|
814
|
-
layers.push(this.makeElevatedLinesLayer(this.currentFloor, customLayers.elevatedLinesLayer));
|
|
815
|
-
}
|
|
816
|
-
if (customLayers.labelsLayer) {
|
|
817
|
-
layers.push(this.makeLabelLayer(this.currentFloor, customLayers.labelsLayer, false));
|
|
818
|
-
}
|
|
819
|
-
if (customLayers.iconLayers) {
|
|
820
|
-
const { storeLogosLayer, poiIconsLayer } = customLayers.iconLayers;
|
|
821
|
-
if (storeLogosLayer) {
|
|
822
|
-
layers.push(this.makeStoreLogoLayer(this.currentFloor, storeLogosLayer));
|
|
823
|
-
}
|
|
824
|
-
if (poiIconsLayer) {
|
|
825
|
-
layers.push(this.makePoiIconLayer(this.currentFloor, poiIconsLayer));
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
this.deckOverlay.setProps({ layers });
|
|
829
|
-
}
|
|
830
|
-
hideLayers(layersIds) {
|
|
831
|
-
if (!layersIds)
|
|
832
|
-
return;
|
|
833
|
-
for (const layerId of layersIds) {
|
|
834
|
-
const layer = this.map.getLayer(layerId);
|
|
835
|
-
if (!layer)
|
|
836
|
-
continue;
|
|
837
|
-
this.map.setLayoutProperty(layer.id, "visibility", "none");
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
exports.Scene3DController = Scene3DController;
|
|
842
|
-
/**
|
|
843
|
-
* Bounded concurrency for loading declarative GLB assets in {@link loadScene3DGlbTemplates}.
|
|
844
|
-
* High enough that one slow/broken asset (see {@link loadGlbRoot}'s timeout) does not stall the
|
|
845
|
-
* rest of the batch, low enough to avoid saturating the browser with concurrent Draco/KTX2
|
|
846
|
-
* decodes when a scene declares many assets.
|
|
847
|
-
*/
|
|
848
|
-
Scene3DController.GLB_LOAD_CONCURRENCY = 4;
|
|
849
|
-
//# sourceMappingURL=scene3DController.js.map
|