@mapvx/web-js 1.3.0-dev.2 → 1.4.0-dev.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.
Files changed (51) hide show
  1. package/dist/cjs/config/draco.js +15 -0
  2. package/dist/cjs/config/draco.js.map +1 -0
  3. package/dist/cjs/domain/models/mapConfig.js.map +1 -1
  4. package/dist/cjs/domain/models/scene3D.js +3 -0
  5. package/dist/cjs/domain/models/scene3D.js.map +1 -0
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/logger/logger.js +1 -1
  8. package/dist/cjs/logger/rollbar.js +1 -1
  9. package/dist/cjs/map/map.js +298 -245
  10. package/dist/cjs/map/map.js.map +1 -1
  11. package/dist/cjs/utils/3d.js +104 -3
  12. package/dist/cjs/utils/3d.js.map +1 -1
  13. package/dist/cjs/utils/loadGlb.js +63 -0
  14. package/dist/cjs/utils/loadGlb.js.map +1 -0
  15. package/dist/cjs/utils/three.js +55 -0
  16. package/dist/cjs/utils/three.js.map +1 -0
  17. package/dist/es/config/draco.d.ts +12 -0
  18. package/dist/es/config/draco.d.ts.map +1 -0
  19. package/dist/es/config/draco.js +12 -0
  20. package/dist/es/config/draco.js.map +1 -0
  21. package/dist/es/domain/models/mapConfig.d.ts +13 -0
  22. package/dist/es/domain/models/mapConfig.d.ts.map +1 -1
  23. package/dist/es/domain/models/mapConfig.js.map +1 -1
  24. package/dist/es/domain/models/scene3D.d.ts +308 -0
  25. package/dist/es/domain/models/scene3D.d.ts.map +1 -0
  26. package/dist/es/domain/models/scene3D.js +2 -0
  27. package/dist/es/domain/models/scene3D.js.map +1 -0
  28. package/dist/es/index.d.ts +1 -0
  29. package/dist/es/index.d.ts.map +1 -1
  30. package/dist/es/index.js.map +1 -1
  31. package/dist/es/logger/logger.js +1 -1
  32. package/dist/es/logger/rollbar.js +1 -1
  33. package/dist/es/map/map.d.ts +30 -18
  34. package/dist/es/map/map.d.ts.map +1 -1
  35. package/dist/es/map/map.js +291 -238
  36. package/dist/es/map/map.js.map +1 -1
  37. package/dist/es/utils/3d.d.ts +39 -1
  38. package/dist/es/utils/3d.d.ts.map +1 -1
  39. package/dist/es/utils/3d.js +95 -2
  40. package/dist/es/utils/3d.js.map +1 -1
  41. package/dist/es/utils/loadGlb.d.ts +22 -0
  42. package/dist/es/utils/loadGlb.d.ts.map +1 -0
  43. package/dist/es/utils/loadGlb.js +57 -0
  44. package/dist/es/utils/loadGlb.js.map +1 -0
  45. package/dist/es/utils/three.d.ts +26 -0
  46. package/dist/es/utils/three.d.ts.map +1 -0
  47. package/dist/es/utils/three.js +26 -0
  48. package/dist/es/utils/three.js.map +1 -0
  49. package/dist/umd/index.js +23965 -21381
  50. package/dist/umd/index.js.map +1 -1
  51. package/package.json +3 -2
@@ -27,9 +27,8 @@ exports.InternalMapVXMap = void 0;
27
27
  const geo_layers_1 = require("@deck.gl/geo-layers");
28
28
  const layers_1 = require("@deck.gl/layers");
29
29
  const maplibre_gl_1 = __importStar(require("maplibre-gl"));
30
- const THREE = __importStar(require("three"));
31
- const GLTFLoader_js_1 = require("three/examples/jsm/loaders/GLTFLoader.js");
32
30
  const semaphore_1 = require("../utils/semaphore");
31
+ const three_1 = require("../utils/three");
33
32
  /** Shared GeoJSON source holding every circle drawn through the circle API. */
34
33
  const CIRCLE_SOURCE_ID = "mapvx-circles";
35
34
  /** Fill layer rendering the translucent interior of the circles. */
@@ -162,13 +161,15 @@ const icons_1 = require("../assets/icons");
162
161
  const routeController_1 = require("../controllers/routeController");
163
162
  const _rtl_1 = require("../domain/models/_rtl");
164
163
  const animation_1 = require("../domain/models/animation");
165
- const circle_1 = require("../domain/models/circle");
166
164
  const loggeable_1 = require("../domain/models/loggeable");
167
165
  const mapConfig_1 = require("../domain/models/mapConfig");
166
+ const circle_1 = require("../domain/models/circle");
168
167
  const marker_1 = require("../domain/models/marker");
169
168
  const route_1 = require("../domain/models/route");
170
169
  const routeConfiguration_1 = require("../domain/models/routeConfiguration");
171
170
  const repository_1 = require("../repository/repository");
171
+ const _3d_1 = require("../utils/3d");
172
+ const loadGlb_1 = require("../utils/loadGlb");
172
173
  const route_utils_1 = require("../utils/route-utils");
173
174
  const utils_1 = require("../utils/utils");
174
175
  const mapInteractionOptions_1 = require("./mapInteractionOptions");
@@ -187,7 +188,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
187
188
  * @returns A new instance of MapVXMap.
188
189
  */
189
190
  constructor(mapConfig, container, token) {
190
- var _a, _b, _c;
191
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
191
192
  super();
192
193
  this.potentialParentPlaces = [];
193
194
  this.innerFloors = [];
@@ -202,18 +203,17 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
202
203
  this.geoLocation = navigator.geolocation;
203
204
  // 3d related variables
204
205
  this.mode = "2D";
206
+ this.scene3DConfig = undefined;
205
207
  this.deckOverlay = undefined;
206
- this.escalatorScene = undefined;
207
- this.escalatorModelTemplate = undefined;
208
- this.escalatorCenter = undefined;
209
- this.escalatorCache = {};
210
- this.ESCALATOR_MODEL_URL = "https://mapvx-glb-assets.s3.us-east-1.amazonaws.com/shared/escalator_up.glb";
211
- /**
212
- * Number of lights to use for the escalators
213
- *
214
- */
215
- this.ESCALATOR_LIGHTS = 3;
216
- this.SPRITE_URL = "https://lazarillo.app/internal/maps/vector-style/cenco-cl-pe-co-ar-3D/sprites_cencosud";
208
+ this.scene3DGlbScene = undefined;
209
+ /** Loaded GLB roots keyed by URL (deduplicated). */
210
+ this.scene3DGlbRootByUrl = new globalThis.Map();
211
+ /** Per-asset template root and bottom anchor for cloning. */
212
+ this.scene3DGlbTemplateById = new globalThis.Map();
213
+ /** Number of light objects in `scene3DGlbScene` (kept when clearing meshes). */
214
+ this.scene3DGlbLightCount = 0;
215
+ /** Container group holding all placed GLB pivots; rebuilt on each floor change. */
216
+ this.scene3DModelsGroup = undefined;
217
217
  this.spriteIconMapping = {};
218
218
  this.spriteAtlasImage = new Image();
219
219
  if (_rtl_1.rtlLanguages.includes((_a = mapConfig.lang) !== null && _a !== void 0 ? _a : "")) {
@@ -227,15 +227,16 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
227
227
  this.watchPositionID = undefined;
228
228
  this.onFloorChange = mapConfig.onFloorChange;
229
229
  this.onParentPlaceChange = mapConfig.onParentPlaceChange;
230
- this.mode = (_c = mapConfig.mode) !== null && _c !== void 0 ? _c : "2D";
231
- this.initialCenter = mapConfig.center;
232
230
  this.tileCacheConfig = (() => {
233
231
  const merged = Object.assign(Object.assign({}, mapConfig_1.DEFAULT_TILE_CACHE_CONFIG), mapConfig.tileCache);
234
232
  merged.maxTiles = Math.min(merged.maxTiles, mapConfig_1.MAPLIBRE_MAX_TILE_CACHE_HARD_CAP);
235
233
  return merged;
236
234
  })();
237
- if (this.mode === "3D") {
238
- this.loadSpriteAtlas().catch(console.error);
235
+ this.mode = (_c = mapConfig.mode) !== null && _c !== void 0 ? _c : "2D";
236
+ this.scene3DConfig = mapConfig.scene3D;
237
+ this.initialCenter = mapConfig.center;
238
+ if (this.is3DSceneActive() && ((_f = (_e = (_d = this.scene3DConfig) === null || _d === void 0 ? void 0 : _d.customLayers) === null || _e === void 0 ? void 0 : _e.iconLayers) === null || _f === void 0 ? void 0 : _f.spriteUrl)) {
239
+ this.loadSpriteAtlas((_j = (_h = (_g = this.scene3DConfig) === null || _g === void 0 ? void 0 : _g.customLayers) === null || _h === void 0 ? void 0 : _h.iconLayers) === null || _j === void 0 ? void 0 : _j.spriteUrl).catch(console.error);
239
240
  }
240
241
  if (mapConfig.parentPlaceId != null) {
241
242
  this.initialPlaceDetailSetUp(mapConfig.parentPlaceId, mapConfig.authToken);
@@ -363,35 +364,22 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
363
364
  showZoom: mapConfig.showZoom !== undefined ? mapConfig.showZoom : true,
364
365
  }), (_e = mapConfig.navigationPosition) !== null && _e !== void 0 ? _e : "top-right");
365
366
  this.map.on("load", () => {
366
- var _a;
367
- if (this.mode === "3D") {
367
+ var _a, _b, _c;
368
+ if (this.is3DSceneActive()) {
368
369
  this.deckOverlay = new mapbox_1.MapboxOverlay({ layers: [] });
369
370
  this.map.addControl(this.deckOverlay);
370
- this.hideIndoorSymbolLayers();
371
371
  // Hide MapLibre icon layers — replaced by deck.gl IconLayer
372
- this.map.setLayoutProperty("indoor-poi-logo", "visibility", "none");
373
- const style = this.map.getStyle();
374
- for (const layer of style.layers) {
375
- if (layer.id.startsWith("indoor-logo-tiendas-ancla")) {
376
- this.map.setLayoutProperty(layer.id, "visibility", "none");
377
- }
372
+ this.hideLayers((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.hideLayers);
373
+ const models = (_b = this.scene3DConfig) === null || _b === void 0 ? void 0 : _b.models;
374
+ const glbModelsActive = models !== undefined && models.enabled !== false && models.assets.length > 0;
375
+ if (glbModelsActive) {
376
+ this.map.addLayer(this.createGlbCustomLayer());
378
377
  }
379
- // Add 3D escalator layer and hide 2D transportation line
380
- this.map.addLayer(this.createEscalatorLayer());
381
- this.map.setPaintProperty("indoor-transportation", "line-opacity", 0);
382
- // Capture escalator positions when new tiles arrive (stops once cached)
383
- this.map.on("sourcedata", (e) => {
384
- if (e.sourceId === "indoorequal" &&
385
- this.currentFloor &&
386
- !this.escalatorCache[this.currentFloor]) {
387
- this.placeEscalators();
388
- }
389
- });
390
378
  }
391
379
  this.whenStyleUpdates(style);
392
380
  if (mapConfig.lang)
393
381
  this.setLayersForLanguage(mapConfig.lang);
394
- (_a = mapConfig.onMapReady) === null || _a === void 0 ? void 0 : _a.call(mapConfig);
382
+ (_c = mapConfig.onMapReady) === null || _c === void 0 ? void 0 : _c.call(mapConfig);
395
383
  this.onHover();
396
384
  this.subscribeToFailedTiles();
397
385
  });
@@ -401,6 +389,11 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
401
389
  if (this.circles.length > 0)
402
390
  this.ensureCircleLayers();
403
391
  });
392
+ this.map.on("zoom", () => {
393
+ if (this.is3DSceneActive()) {
394
+ this.applyModelZoomOpacity();
395
+ }
396
+ });
404
397
  this.map.on("zoomend", () => {
405
398
  var _a;
406
399
  (_a = mapConfig.onZoomEnd) === null || _a === void 0 ? void 0 : _a.call(mapConfig, this.getZoomLevel());
@@ -538,214 +531,253 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
538
531
  // Tile cache clear may fail if cache doesn't exist
539
532
  }
540
533
  }
541
- // Start 3D related methods ------------------------------------------------------------
542
- hideIndoorSymbolLayers() {
543
- var _a, _b;
544
- const style = this.map.getStyle();
545
- for (const layer of style.layers) {
546
- if (layer.type === "symbol" && layer.id.startsWith("indoor-")) {
547
- const hasText = !!((_a = layer.layout) === null || _a === void 0 ? void 0 : _a["text-field"]);
548
- const hasIcon = !!((_b = layer.layout) === null || _b === void 0 ? void 0 : _b["icon-image"]);
549
- // Hide text-only layers (replaced by deck.gl)
550
- if (hasText && !hasIcon) {
551
- this.map.setLayoutProperty(layer.id, "visibility", "none");
552
- }
553
- }
554
- }
534
+ /**
535
+ * Whether 3D scene features (deck.gl overlay, declarative GLB custom layer, etc.) are active.
536
+ * Requires {@link MapConfig.mode} `"3D"` and {@link Scene3DConfig.enabled} not `false`.
537
+ * When `scene3D` is omitted, 3D features remain available for backward compatibility.
538
+ */
539
+ is3DSceneActive() {
540
+ var _a;
541
+ return this.mode === "3D" && ((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.enabled) !== false;
555
542
  }
556
- createEscalatorLayer() {
543
+ // Start 3D related methods ------------------------------------------------------------
544
+ createGlbCustomLayer() {
545
+ var _a;
557
546
  const refMercator = maplibre_gl_1.MercatorCoordinate.fromLngLat(this.initialCenter, 0);
558
547
  const meterScale = refMercator.meterInMercatorCoordinateUnits();
559
- const modelTransform = new THREE.Matrix4()
548
+ const modelTransform = new three_1.THREE.Matrix4()
560
549
  .makeTranslation(refMercator.x, refMercator.y, refMercator.z)
561
- .scale(new THREE.Vector3(meterScale, -meterScale, meterScale))
562
- .multiply(new THREE.Matrix4().makeRotationX(Math.PI / 2));
563
- this.escalatorScene = new THREE.Scene();
564
- const dirLight = new THREE.DirectionalLight(0xffffff, 2.0);
565
- dirLight.position.set(50, 100, 80);
566
- this.escalatorScene.add(dirLight);
567
- const dirLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
568
- dirLight2.position.set(-50, 80, -60);
569
- this.escalatorScene.add(dirLight2);
570
- this.escalatorScene.add(new THREE.AmbientLight(0xffffff, 1.5));
571
- new GLTFLoader_js_1.GLTFLoader().load(this.ESCALATOR_MODEL_URL, (gltf) => {
572
- this.escalatorModelTemplate = gltf.scene;
573
- const box = new THREE.Box3().setFromObject(this.escalatorModelTemplate);
574
- this.escalatorCenter = box.getCenter(new THREE.Vector3());
575
- this.escalatorCenter.y = box.min.y; // align bottom to ground
576
- this.placeEscalators();
550
+ .scale(new three_1.THREE.Vector3(meterScale, -meterScale, meterScale))
551
+ .multiply(new three_1.THREE.Matrix4().makeRotationX(Math.PI / 2));
552
+ this.scene3DGlbScene = new three_1.THREE.Scene();
553
+ const models = (_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.models;
554
+ if (models && models.enabled !== false && models.assets.length > 0) {
555
+ const lights = (0, _3d_1.resolveModels3DLights)(models);
556
+ this.scene3DGlbLightCount = (0, loadGlb_1.addScene3DLights)(this.scene3DGlbScene, lights);
557
+ }
558
+ else {
559
+ this.scene3DGlbLightCount = 0;
560
+ }
561
+ void this.loadScene3DGlbTemplates()
562
+ .then(() => this.placeScene3DModels())
563
+ .catch((err) => {
564
+ console.error("Failed to initialize declarative GLB models", err);
577
565
  });
578
566
  let renderer = undefined;
579
567
  let camera = undefined;
580
- const tmpMatrix = new THREE.Matrix4();
568
+ const tmpMatrix = new three_1.THREE.Matrix4();
581
569
  return {
582
- id: "3d-escalators",
570
+ id: "3d-glb-models",
583
571
  type: "custom",
584
572
  renderingMode: "3d",
585
573
  onAdd(_map, gl) {
586
- camera = new THREE.Camera();
587
- renderer = new THREE.WebGLRenderer({
574
+ camera = new three_1.THREE.Camera();
575
+ renderer = new three_1.THREE.WebGLRenderer({
588
576
  canvas: _map.getCanvas(),
589
577
  context: gl,
590
578
  antialias: true,
591
579
  });
592
580
  renderer.autoClear = false;
581
+ // MapLibre owns canvas dimensions; only sync the GL viewport on resize.
582
+ renderer.setSize = function (width, height) {
583
+ this.setViewport(0, 0, width, height);
584
+ };
593
585
  },
594
- render: (gl, args) => {
595
- var _a, _b;
586
+ render: (_gl, args) => {
587
+ var _a;
596
588
  if (!camera ||
597
589
  !renderer ||
598
- !this.escalatorModelTemplate ||
599
- !this.escalatorScene ||
600
- this.escalatorScene.children.length <= this.ESCALATOR_LIGHTS)
590
+ !this.scene3DGlbScene ||
591
+ !this.scene3DModelsGroup ||
592
+ this.scene3DModelsGroup.children.length === 0) {
593
+ return;
594
+ }
595
+ const canvas = this.map.getCanvas();
596
+ const width = canvas.width;
597
+ const height = canvas.height;
598
+ // Skip while the framebuffer has no drawable area (e.g. devtools opening).
599
+ if (width === 0 || height === 0) {
600
+ return;
601
+ }
602
+ const mainMatrix = (_a = args.defaultProjectionData) === null || _a === void 0 ? void 0 : _a.mainMatrix;
603
+ if (!mainMatrix) {
601
604
  return;
602
- const matrixData = (_b = (_a = args === null || args === void 0 ? void 0 : args.defaultProjectionData) === null || _a === void 0 ? void 0 : _a.mainMatrix) !== null && _b !== void 0 ? _b : args;
603
- tmpMatrix.fromArray(matrixData).multiply(modelTransform);
605
+ }
606
+ renderer.setSize(width, height);
607
+ tmpMatrix.fromArray(mainMatrix).multiply(modelTransform);
604
608
  camera.projectionMatrix.copy(tmpMatrix);
605
609
  renderer.resetState();
606
- renderer.render(this.escalatorScene, camera);
610
+ renderer.render(this.scene3DGlbScene, camera);
607
611
  },
608
612
  };
609
613
  }
610
- placeEscalators() {
611
- if (!this.escalatorModelTemplate ||
612
- !this.escalatorScene ||
613
- !this.currentFloor ||
614
- !this.escalatorCenter)
615
- return;
616
- // Try to capture positions if not cached yet
617
- this.captureEscalatorPositions();
618
- const positions = this.escalatorCache[this.currentFloor];
619
- if (!positions)
614
+ async loadScene3DGlbTemplates() {
615
+ var _a;
616
+ const models = (_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.models;
617
+ if (!models || models.enabled === false || models.assets.length === 0) {
620
618
  return;
621
- // Clear scene models (keep lights)
622
- while (this.escalatorScene.children.length > this.ESCALATOR_LIGHTS) {
623
- const lastChild = this.escalatorScene.children.at(-1);
624
- if (lastChild) {
625
- this.escalatorScene.remove(lastChild);
619
+ }
620
+ this.scene3DGlbRootByUrl.clear();
621
+ this.scene3DGlbTemplateById.clear();
622
+ for (const asset of models.assets) {
623
+ try {
624
+ let root = this.scene3DGlbRootByUrl.get(asset.url);
625
+ if (!root) {
626
+ root = await (0, loadGlb_1.loadGlbRoot)(asset.url);
627
+ this.scene3DGlbRootByUrl.set(asset.url, root);
628
+ }
629
+ const center = (0, loadGlb_1.computeGlbBottomAnchor)(root);
630
+ this.scene3DGlbTemplateById.set(asset.id, { root, center });
631
+ }
632
+ catch (err) {
633
+ console.error(`Failed to load GLB asset "${asset.id}" from ${asset.url}`, err);
626
634
  }
627
635
  }
636
+ }
637
+ placeScene3DModels() {
638
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
639
+ if (!this.scene3DGlbScene) {
640
+ return;
641
+ }
642
+ const models = (_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.models;
643
+ if (!models || models.enabled === false) {
644
+ return;
645
+ }
646
+ if (this.scene3DModelsGroup) {
647
+ this.scene3DGlbScene.remove(this.scene3DModelsGroup);
648
+ this.scene3DModelsGroup = undefined;
649
+ }
650
+ if (this.scene3DGlbTemplateById.size === 0) {
651
+ return;
652
+ }
628
653
  // Guard against an invalid initial center before it reaches fromLngLat.
629
654
  if (!this.initialCenter ||
630
655
  !Number.isFinite(this.initialCenter.lng) ||
631
- !Number.isFinite(this.initialCenter.lat))
656
+ !Number.isFinite(this.initialCenter.lat)) {
632
657
  return;
658
+ }
633
659
  const refMercator = maplibre_gl_1.MercatorCoordinate.fromLngLat(this.initialCenter, 0);
634
660
  const meterScale = refMercator.meterInMercatorCoordinateUnits();
635
- for (const pos of positions) {
636
- // Defense in depth: never feed a non-finite coordinate to fromLngLat.
637
- if (!Number.isFinite(pos.lng) || !Number.isFinite(pos.lat))
661
+ const floorKey = (_b = this.currentFloor) !== null && _b !== void 0 ? _b : "";
662
+ const modelsGroup = new three_1.THREE.Group();
663
+ modelsGroup.position.set((_d = (_c = models.globalOffset) === null || _c === void 0 ? void 0 : _c.x) !== null && _d !== void 0 ? _d : 0, (_f = (_e = models.globalOffset) === null || _e === void 0 ? void 0 : _e.y) !== null && _f !== void 0 ? _f : 0, (_h = (_g = models.globalOffset) === null || _g === void 0 ? void 0 : _g.z) !== null && _h !== void 0 ? _h : 0);
664
+ for (const placement of models.placements) {
665
+ if (!(0, _3d_1.isPlacementVisibleOnFloor)(placement, floorKey)) {
638
666
  continue;
639
- const target = maplibre_gl_1.default.MercatorCoordinate.fromLngLat([pos.lng, pos.lat], 0);
667
+ }
668
+ const template = this.scene3DGlbTemplateById.get(placement.assetId);
669
+ if (!template) {
670
+ continue;
671
+ }
672
+ const asset = models.assets.find((a) => a.id === placement.assetId);
673
+ if (!asset) {
674
+ continue;
675
+ }
676
+ // Placement positions come from external consumer config: skip any
677
+ // non-finite coordinate so a malformed config can never crash
678
+ // fromLngLat and break the whole floor render.
679
+ if (!placement.position ||
680
+ !Number.isFinite(placement.position.lng) ||
681
+ !Number.isFinite(placement.position.lat)) {
682
+ this.logWarning(new Error(`Invalid scene3D placement position (assetId=${placement.assetId}, floorKey=${floorKey})`));
683
+ continue;
684
+ }
685
+ const target = maplibre_gl_1.default.MercatorCoordinate.fromLngLat([placement.position.lng, placement.position.lat], (_j = placement.position.altitude) !== null && _j !== void 0 ? _j : 0);
640
686
  const sx = (target.x - refMercator.x) / meterScale;
641
687
  const sz = (target.y - refMercator.y) / meterScale;
642
- const pivot = new THREE.Group();
643
- pivot.position.set(sx, 0, sz);
644
- pivot.rotation.y = -pos.bearing;
645
- pivot.scale.set(1, 1, 1);
646
- const model = this.escalatorModelTemplate.clone();
647
- model.position.set(-this.escalatorCenter.x, -this.escalatorCenter.y + 0.7, -this.escalatorCenter.z);
688
+ const ax = (_l = (_k = asset.defaultModelOffset) === null || _k === void 0 ? void 0 : _k.x) !== null && _l !== void 0 ? _l : 0;
689
+ const ay = (_o = (_m = asset.defaultModelOffset) === null || _m === void 0 ? void 0 : _m.y) !== null && _o !== void 0 ? _o : 0;
690
+ const az = (_q = (_p = asset.defaultModelOffset) === null || _p === void 0 ? void 0 : _p.z) !== null && _q !== void 0 ? _q : 0;
691
+ const px = (_s = (_r = placement.offset) === null || _r === void 0 ? void 0 : _r.x) !== null && _s !== void 0 ? _s : 0;
692
+ const py = (_u = (_t = placement.offset) === null || _t === void 0 ? void 0 : _t.y) !== null && _u !== void 0 ? _u : 0;
693
+ const pz = (_w = (_v = placement.offset) === null || _v === void 0 ? void 0 : _v.z) !== null && _w !== void 0 ? _w : 0;
694
+ const pivot = new three_1.THREE.Group();
695
+ pivot.position.set(sx + ax + px, ay + py, sz + az + pz);
696
+ const mergedRot = (0, _3d_1.mergeRotation3D)(asset.defaultRotation, placement.rotation);
697
+ pivot.rotation.set(three_1.THREE.MathUtils.degToRad(mergedRot.pitch), three_1.THREE.MathUtils.degToRad(-mergedRot.yaw), three_1.THREE.MathUtils.degToRad(mergedRot.roll), "YXZ");
698
+ const [sx3, sy3, sz3] = (0, _3d_1.mergeGlbScale)(asset.defaultScale, placement.scale);
699
+ pivot.scale.set(sx3, sy3, sz3);
700
+ const model = template.root.clone(true);
701
+ // `clone(true)` shares materials between instances; clone them per placement so each
702
+ // placement's zoom-based opacity is independent and does not bleed into sibling models.
703
+ model.traverse((obj) => {
704
+ const mesh = obj;
705
+ if (!mesh.isMesh) {
706
+ return;
707
+ }
708
+ mesh.material = Array.isArray(mesh.material)
709
+ ? mesh.material.map((m) => m.clone())
710
+ : mesh.material.clone();
711
+ });
712
+ model.position.set(-template.center.x, -template.center.y, -template.center.z);
713
+ // Stash the resolved zoom-transparency config so it can be re-applied on every zoom change.
714
+ pivot.userData.zoomTransparency = (0, _3d_1.resolveZoomTransparency)(models, asset, placement);
648
715
  pivot.add(model);
649
- this.escalatorScene.add(pivot);
716
+ modelsGroup.add(pivot);
650
717
  }
718
+ this.scene3DModelsGroup = modelsGroup;
719
+ this.scene3DGlbScene.add(modelsGroup);
720
+ this.applyModelZoomOpacity();
651
721
  this.map.triggerRepaint();
652
722
  }
653
- captureEscalatorPositions() {
654
- if (this.currentFloor && this.escalatorCache[this.currentFloor])
655
- return;
656
- // queryRenderedFeatures uses exact rendered coordinates (not tile-approximated)
657
- const features = this.map
658
- .queryRenderedFeatures(undefined, {
659
- layers: ["indoor-transportation"],
660
- })
661
- .filter((f) => { var _a; return ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.floor_key) === this.currentFloor; });
662
- if (!features.length)
723
+ /**
724
+ * Applies zoom-based transparency to placed GLB models, using each placement's resolved
725
+ * {@link ZoomTransparencyConfig} stored in `pivot.userData.zoomTransparency`. Hard threshold:
726
+ * fully opaque below the configured zoom, configured opacity at/above it. Only triggers a
727
+ * repaint when at least one material actually changed.
728
+ */
729
+ applyModelZoomOpacity() {
730
+ if (!this.scene3DModelsGroup) {
663
731
  return;
664
- // Collect all midpoints first
665
- const allMids = [];
666
- for (const f of features) {
667
- const geom = f.geometry;
668
- // queryRenderedFeatures may return a LineString or, when a line is split
669
- // across tile boundaries, a MultiLineString. The previous code assumed
670
- // LineString and read coords[0]/coords[n-1] as [lng,lat], producing
671
- // NaN for MultiLineString (coordinates is number[][][]). Normalize both
672
- // shapes to a list of line parts (Position[][]) and flatten them into a
673
- // single continuous list of points so we still derive ONE midpoint per
674
- // feature (a MultiLineString is just one escalator line split across
675
- // tiles, not several escalators).
676
- let lineParts;
677
- if (geom.type === "LineString") {
678
- lineParts = [geom.coordinates];
679
- }
680
- else if (geom.type === "MultiLineString") {
681
- lineParts = geom.coordinates;
682
- }
683
- else {
684
- continue;
685
- }
686
- const coords = lineParts.flat();
687
- if (coords.length < 2)
688
- continue;
689
- const start = coords[0];
690
- const end = coords[coords.length - 1];
691
- const midLng = (start[0] + end[0]) / 2;
692
- const midLat = (start[1] + end[1]) / 2;
693
- const bearing = Math.atan2(end[0] - start[0], end[1] - start[1]);
694
- // Skip any non-finite result so a bad feature can never poison the
695
- // cache (and later crash MercatorCoordinate.fromLngLat).
696
- if (!Number.isFinite(midLng) || !Number.isFinite(midLat) || !Number.isFinite(bearing))
697
- continue;
698
- allMids.push({ lng: midLng, lat: midLat, bearing });
699
- }
700
- // Merge points within ~15m of each other into one
701
- const MERGE_DEG = 0.00015; // ~15m
702
- const positions = [];
703
- const used = new Set();
704
- for (let i = 0; i < allMids.length; i++) {
705
- if (used.has(i))
706
- continue;
707
- used.add(i);
708
- const group = [allMids[i]];
709
- for (let j = i + 1; j < allMids.length; j++) {
710
- if (used.has(j))
711
- continue;
712
- const dLng = allMids[i].lng - allMids[j].lng;
713
- const dLat = allMids[i].lat - allMids[j].lat;
714
- if (Math.abs(dLng) < MERGE_DEG && Math.abs(dLat) < MERGE_DEG) {
715
- used.add(j);
716
- group.push(allMids[j]);
732
+ }
733
+ const zoom = this.getZoomLevel();
734
+ let changed = false;
735
+ for (const pivot of this.scene3DModelsGroup.children) {
736
+ const cfg = pivot.userData.zoomTransparency;
737
+ const opacity = (0, _3d_1.opacityForZoom)(cfg, zoom);
738
+ pivot.traverse((obj) => {
739
+ const mesh = obj;
740
+ if (!mesh.isMesh) {
741
+ return;
717
742
  }
718
- }
719
- // Use average position and first bearing
720
- const avgLng = group.reduce((s, p) => s + p.lng, 0) / group.length;
721
- const avgLat = group.reduce((s, p) => s + p.lat, 0) / group.length;
722
- positions.push({ lng: avgLng, lat: avgLat, bearing: group[0].bearing });
743
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
744
+ for (const material of materials) {
745
+ if (material.opacity !== opacity) {
746
+ material.transparent = opacity < 1;
747
+ material.opacity = opacity;
748
+ // Disable depth writes while transparent to avoid draw-order artifacts.
749
+ material.depthWrite = opacity >= 1;
750
+ changed = true;
751
+ }
752
+ }
753
+ });
723
754
  }
724
- if (positions.length && this.currentFloor) {
725
- this.escalatorCache[this.currentFloor] = positions;
755
+ if (changed) {
756
+ this.map.triggerRepaint();
726
757
  }
727
758
  }
728
- async loadSpriteAtlas() {
759
+ async loadSpriteAtlas(spriteUrl) {
729
760
  const [json, img] = await Promise.all([
730
- fetch(`${this.SPRITE_URL}.json`).then((r) => r.json()),
761
+ fetch(`${spriteUrl}.json`).then((r) => r.json()),
731
762
  new Promise((resolve, reject) => {
732
763
  const image = new Image();
733
764
  image.crossOrigin = "anonymous";
734
765
  image.onload = () => resolve(image);
735
766
  image.onerror = reject;
736
- image.src = `${this.SPRITE_URL}.png`;
767
+ image.src = `${spriteUrl}.png`;
737
768
  }),
738
769
  ]);
739
770
  this.spriteIconMapping = json;
740
771
  this.spriteAtlasImage = img;
741
772
  }
742
- makeMVTLayer(floorKey) {
773
+ makeElevatedLinesLayer(floorKey, layerConfig) {
774
+ var _a;
743
775
  return new geo_layers_1.MVTLayer({
744
776
  id: "indoor-lines-elevated",
745
- data: "https://tiles.mapvx.com/",
746
- binary: false,
777
+ data: layerConfig.dataUrl,
778
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
747
779
  renderSubLayers: (props) => {
748
- var _a;
780
+ var _a, _b, _c, _d, _e;
749
781
  const roomData = (props.data || []);
750
782
  const roomFeatures = roomData.filter((f) => {
751
783
  var _a, _b, _c;
@@ -757,7 +789,9 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
757
789
  return null;
758
790
  const paths = [];
759
791
  for (const f of roomFeatures) {
760
- const z = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5;
792
+ const z = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height)
793
+ ? Number(f.properties.height)
794
+ : ((_b = layerConfig.defaultHeight) !== null && _b !== void 0 ? _b : 5);
761
795
  const geom = f.geometry;
762
796
  const polygons = geom.type === "Polygon"
763
797
  ? [geom.coordinates]
@@ -770,21 +804,23 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
770
804
  }
771
805
  }
772
806
  }
773
- return new layers_1.PathLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-paths`, data: paths, getPath: (d) => d.path, getColor: [203, 203, 203], getWidth: 1.5, widthUnits: "pixels", pickable: false }));
807
+ return new layers_1.PathLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-paths`, data: paths, getPath: (d) => d.path, getColor: (0, _3d_1.hexToRgb)((_c = layerConfig.lineColor) !== null && _c !== void 0 ? _c : "#cbcbcb"), getWidth: (_d = layerConfig.lineWidth) !== null && _d !== void 0 ? _d : 1.5, widthUnits: (_e = layerConfig.widthUnits) !== null && _e !== void 0 ? _e : "pixels", pickable: false }));
774
808
  },
775
809
  updateTriggers: {
776
810
  renderSubLayers: [floorKey],
777
811
  },
778
812
  });
779
813
  }
780
- makeLabelLayer(floorKey, useSdf = true) {
814
+ makeLabelLayer(floorKey, layerConfig, useSdf = true) {
815
+ var _a, _b, _c;
781
816
  return new geo_layers_1.MVTLayer({
782
817
  id: "indoor-labels",
783
- data: "https://tiles.mapvx.com/",
784
- binary: false,
785
- minZoom: 19.5,
786
- maxZoom: 24,
818
+ data: layerConfig.dataUrl,
819
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
820
+ minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 19.5,
821
+ maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24,
787
822
  renderSubLayers: (props) => {
823
+ var _a, _b, _c, _d, _e;
788
824
  // Use area_name and poi Point features — unique per tile, no duplicates
789
825
  // If logo-{name} image exists in sprite, icon has priority over text label
790
826
  const labelData = (props.data || []);
@@ -806,32 +842,34 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
806
842
  if (!labelFeatures.length)
807
843
  return null;
808
844
  const textData = labelFeatures.map((f) => {
809
- var _a, _b, _c;
845
+ var _a, _b, _c, _d;
810
846
  return ({
811
847
  position: [
812
848
  ...f.geometry.coordinates.slice(0, 2),
813
- ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5,
849
+ ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : ((_b = layerConfig.defaultHeight) !== null && _b !== void 0 ? _b : 5),
814
850
  ],
815
- text: ((_b = f.properties) === null || _b === void 0 ? void 0 : _b["name:latin"]) || ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.name) || "",
851
+ text: ((_c = f.properties) === null || _c === void 0 ? void 0 : _c["name:latin"]) || ((_d = f.properties) === null || _d === void 0 ? void 0 : _d.name) || "",
816
852
  });
817
853
  });
818
- return new layers_1.TextLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-labels`, data: textData, getPosition: (d) => d.position, getText: (d) => d.text, getSize: 16, getColor: [145, 150, 155, 255], outlineColor: useSdf ? [255, 255, 255, 255] : [0, 0, 0, 0], outlineWidth: useSdf ? 4 : 0, fontSettings: { sdf: useSdf }, fontFamily: "Open Sans, sans-serif", fontWeight: 600, billboard: true, pickable: false, characterSet: "auto", wordBreak: "break-word", maxWidth: 150 }));
854
+ return new layers_1.TextLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-labels`, data: textData, getPosition: (d) => d.position, getText: (d) => d.text, getSize: (_a = layerConfig.size) !== null && _a !== void 0 ? _a : 16, getColor: (0, _3d_1.hexToRgb)((_b = layerConfig.color) !== null && _b !== void 0 ? _b : "#91969b"), outlineColor: useSdf ? [255, 255, 255, 255] : [0, 0, 0, 0], outlineWidth: useSdf ? 4 : 0, fontSettings: { sdf: useSdf }, fontFamily: (_c = layerConfig.fontFamily) !== null && _c !== void 0 ? _c : "Open Sans, sans-serif", fontWeight: (_d = layerConfig.fontWeight) !== null && _d !== void 0 ? _d : 600, billboard: true, pickable: false, characterSet: "auto", wordBreak: "break-word", maxWidth: (_e = layerConfig.maxWidth) !== null && _e !== void 0 ? _e : 150 }));
819
855
  },
820
856
  updateTriggers: {
821
857
  renderSubLayers: [floorKey],
822
858
  },
823
859
  });
824
860
  }
825
- makePoiIconLayer(floorKey) {
861
+ makePoiIconLayer(floorKey, layerConfig) {
862
+ var _a, _b, _c;
826
863
  if (!this.spriteIconMapping || !this.spriteAtlasImage)
827
864
  return null;
828
865
  return new geo_layers_1.MVTLayer({
829
866
  id: "indoor-poi-icons",
830
- data: "https://tiles.mapvx.com/",
831
- binary: false,
832
- minZoom: 17,
833
- maxZoom: 24,
867
+ data: layerConfig.dataUrl,
868
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
869
+ minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 17,
870
+ maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24,
834
871
  renderSubLayers: (props) => {
872
+ var _a, _b, _c, _d;
835
873
  const poiData = (props.data || []);
836
874
  const poiFeatures = poiData.filter((f) => {
837
875
  var _a, _b, _c;
@@ -845,7 +883,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
845
883
  if (!poiFeatures.length)
846
884
  return null;
847
885
  const iconData = poiFeatures.map((f) => {
848
- var _a, _b, _c;
886
+ var _a, _b, _c, _d;
849
887
  const tags = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.tags) || "";
850
888
  let icon;
851
889
  if (tags.includes("wheelchair"))
@@ -857,28 +895,30 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
857
895
  return {
858
896
  position: [
859
897
  ...f.geometry.coordinates.slice(0, 2),
860
- ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
898
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : ((_d = layerConfig.defaultHeight) !== null && _d !== void 0 ? _d : 5),
861
899
  ],
862
900
  icon,
863
901
  };
864
902
  });
865
- return new layers_1.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: 2, sizeUnits: "meters", sizeScale: 1, billboard: true, pickable: false }));
903
+ return new layers_1.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: (_a = layerConfig.size) !== null && _a !== void 0 ? _a : 2, 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 : true, pickable: false }));
866
904
  },
867
905
  updateTriggers: {
868
906
  renderSubLayers: [floorKey],
869
907
  },
870
908
  });
871
909
  }
872
- makeStoreLogoLayer(floorKey) {
910
+ makeStoreLogoLayer(floorKey, layerConfig) {
911
+ var _a, _b, _c;
873
912
  if (!this.spriteIconMapping || !this.spriteAtlasImage)
874
913
  return null;
875
914
  return new geo_layers_1.MVTLayer({
876
915
  id: "indoor-store-logos",
877
- data: "https://tiles.mapvx.com/",
878
- binary: false,
879
- minZoom: 13,
880
- maxZoom: 24,
916
+ data: layerConfig.dataUrl,
917
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
918
+ minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 13,
919
+ maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24,
881
920
  renderSubLayers: (props) => {
921
+ var _a, _b, _c;
882
922
  const propsData = (props.data || []);
883
923
  const logoFeatures = propsData.filter((f) => {
884
924
  var _a, _b, _c;
@@ -894,30 +934,22 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
894
934
  if (!logoFeatures.length)
895
935
  return null;
896
936
  const logoData = logoFeatures.map((f) => {
897
- var _a, _b, _c;
937
+ var _a, _b, _c, _d;
898
938
  const name = (_a = f.properties) === null || _a === void 0 ? void 0 : _a.name;
899
939
  const icon = "logo-" + name;
900
940
  const rotation = ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.rotation_icon) ? Number(f.properties.rotation_icon) : 0;
901
941
  return {
902
942
  position: [
903
943
  ...f.geometry.coordinates.slice(0, 2),
904
- ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
944
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : ((_d = layerConfig.defaultHeight) !== null && _d !== void 0 ? _d : 5),
905
945
  ],
906
946
  icon,
907
947
  rotation,
908
948
  };
909
949
  });
910
950
  return new layers_1.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) => {
911
- const LOGO_SCALE = {
912
- "logo-Maxi-K": 0.4,
913
- "logo-Ripley": 2,
914
- "logo-Falabella": 2,
915
- "logo-CasaIdeas": 2,
916
- "logo-Sky Costanera": 0.6,
917
- "logo-Easy": 1.4,
918
- "logo-Jumbo": 2,
919
- "logo-Decathlon": 1.4,
920
- };
951
+ var _a;
952
+ const LOGO_SCALE = (_a = layerConfig.logoScale) !== null && _a !== void 0 ? _a : {};
921
953
  const m = this.spriteIconMapping[d.icon];
922
954
  if (!m)
923
955
  return 10;
@@ -926,7 +958,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
926
958
  const h = m.height / pr;
927
959
  const base = 10 * (h / Math.max(w, h));
928
960
  return base * (LOGO_SCALE[d.icon] || 1);
929
- }, sizeUnits: "meters", sizeScale: 1, billboard: false, pickable: false }));
961
+ }, sizeUnits: (_a = layerConfig.sizeUnits) !== null && _a !== void 0 ? _a : "meters", sizeScale: (_b = layerConfig.sizeScale) !== null && _b !== void 0 ? _b : 1, billboard: (_c = layerConfig.billboard) !== null && _c !== void 0 ? _c : false, pickable: false }));
930
962
  },
931
963
  updateTriggers: {
932
964
  renderSubLayers: [floorKey],
@@ -934,20 +966,40 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
934
966
  });
935
967
  }
936
968
  updateDeckOverlay() {
969
+ var _a;
937
970
  if (!this.deckOverlay || !this.currentFloor)
938
971
  return;
939
- const layers = [
940
- this.makeMVTLayer(this.currentFloor),
941
- this.makeLabelLayer(this.currentFloor, false),
942
- ];
943
- const poiLayer = this.makePoiIconLayer(this.currentFloor);
944
- if (poiLayer)
945
- layers.push(poiLayer);
946
- const storeLayer = this.makeStoreLogoLayer(this.currentFloor);
947
- if (storeLayer)
948
- layers.push(storeLayer);
972
+ if (!((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.customLayers))
973
+ return;
974
+ const { customLayers } = this.scene3DConfig;
975
+ const layers = [];
976
+ if (customLayers.elevatedLinesLayer) {
977
+ layers.push(this.makeElevatedLinesLayer(this.currentFloor, customLayers.elevatedLinesLayer));
978
+ }
979
+ if (customLayers.labelsLayer) {
980
+ layers.push(this.makeLabelLayer(this.currentFloor, customLayers.labelsLayer, false));
981
+ }
982
+ if (customLayers.iconLayers) {
983
+ const { storeLogosLayer, poiIconsLayer } = customLayers.iconLayers;
984
+ if (storeLogosLayer) {
985
+ layers.push(this.makeStoreLogoLayer(this.currentFloor, storeLogosLayer));
986
+ }
987
+ if (poiIconsLayer) {
988
+ layers.push(this.makePoiIconLayer(this.currentFloor, poiIconsLayer));
989
+ }
990
+ }
949
991
  this.deckOverlay.setProps({ layers });
950
992
  }
993
+ hideLayers(layersIds) {
994
+ if (!layersIds)
995
+ return;
996
+ for (const layerId of layersIds) {
997
+ const layer = this.map.getLayer(layerId);
998
+ if (!layer)
999
+ continue;
1000
+ this.map.setLayoutProperty(layer.id, "visibility", "none");
1001
+ }
1002
+ }
951
1003
  // End 3D related methods ------------------------------------------------------------
952
1004
  getCurrentFloor() {
953
1005
  var _a;
@@ -968,9 +1020,9 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
968
1020
  if (place) {
969
1021
  this.subPlacesLoad(place.mapvxId);
970
1022
  }
971
- if (this.mode === "3D") {
1023
+ if (this.is3DSceneActive()) {
972
1024
  this.updateDeckOverlay();
973
- this.placeEscalators();
1025
+ this.placeScene3DModels();
974
1026
  }
975
1027
  if (updateStyle) {
976
1028
  this.repository
@@ -1000,9 +1052,9 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
1000
1052
  this.innerFloors = (_a = place === null || place === void 0 ? void 0 : place.innerFloors.sort((a, b) => a.index - b.index)) !== null && _a !== void 0 ? _a : [];
1001
1053
  this.currentFloor =
1002
1054
  (_e = (_c = floorId !== null && floorId !== void 0 ? floorId : (_b = this.innerFloors.find((floor) => floor.defaultFloor)) === null || _b === void 0 ? void 0 : _b.key) !== null && _c !== void 0 ? _c : (_d = this.innerFloors[0]) === null || _d === void 0 ? void 0 : _d.key) !== null && _e !== void 0 ? _e : "";
1003
- if (this.mode === "3D") {
1055
+ if (this.is3DSceneActive()) {
1004
1056
  this.updateDeckOverlay();
1005
- this.placeEscalators();
1057
+ this.placeScene3DModels();
1006
1058
  }
1007
1059
  }
1008
1060
  updateParentPlaceAndFloor(parentPlaceId, floorId, options) {
@@ -1056,7 +1108,7 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
1056
1108
  this.routeController.addSourcesAndLayers();
1057
1109
  this.refreshCircles();
1058
1110
  this.filterByFloorKey(this.currentFloor);
1059
- if (this.mode === "3D") {
1111
+ if (this.is3DSceneActive()) {
1060
1112
  this.updateDeckOverlay();
1061
1113
  }
1062
1114
  }
@@ -1488,9 +1540,10 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
1488
1540
  this.logEvent("invalidFloorKey", { floorKey: floorKeyString });
1489
1541
  }
1490
1542
  // Critical base-map updates run first and must never be blocked by an
1491
- // optional 3D decoration failing. A throw in placeEscalators used to abort
1492
- // this method before updateFiltersTo, leaving the base polygons unfiltered
1493
- // (showing the wrong floor) while the caller's empty catch hid the error.
1543
+ // optional 3D decoration failing. A throw in placeScene3DModels used to
1544
+ // abort this method before updateFiltersTo, leaving the base polygons
1545
+ // unfiltered (showing the wrong floor) while the caller's empty catch hid
1546
+ // the error.
1494
1547
  this.updateFiltersTo(floorKeyString);
1495
1548
  this.updateMarkersTo(floorKeyString);
1496
1549
  this.refreshCircles();
@@ -1498,10 +1551,10 @@ class InternalMapVXMap extends loggeable_1.Loggeable {
1498
1551
  this.routeController.updateRouteMarkerVisibility(floorKeyString);
1499
1552
  // 3D decorations are best-effort: isolate so any failure here can never
1500
1553
  // break base-map rendering.
1501
- if (this.mode === "3D") {
1554
+ if (this.is3DSceneActive()) {
1502
1555
  try {
1503
1556
  this.updateDeckOverlay();
1504
- this.placeEscalators();
1557
+ this.placeScene3DModels();
1505
1558
  }
1506
1559
  catch (error) {
1507
1560
  this.logError(error, "filterByFloorKey: 3D layer update failed");