@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
@@ -1,9 +1,8 @@
1
1
  import { MVTLayer } from "@deck.gl/geo-layers";
2
2
  import { IconLayer, PathLayer, TextLayer } from "@deck.gl/layers";
3
3
  import maplibregl, { LngLatBounds, Map, MercatorCoordinate, NavigationControl, setRTLTextPlugin, } from "maplibre-gl";
4
- import * as THREE from "three";
5
- import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
6
4
  import { Semaphore } from "../utils/semaphore";
5
+ import { THREE } from "../utils/three";
7
6
  /** Shared GeoJSON source holding every circle drawn through the circle API. */
8
7
  const CIRCLE_SOURCE_ID = "mapvx-circles";
9
8
  /** Fill layer rendering the translucent interior of the circles. */
@@ -136,13 +135,15 @@ import { userLocationDataUrl } from "../assets/icons";
136
135
  import { RouteController } from "../controllers/routeController";
137
136
  import { rtlLanguages } from "../domain/models/_rtl";
138
137
  import { InternalAnimationConfig, InternalAnimationDrawingConfig, } from "../domain/models/animation";
139
- import { CIRCLE_RENDER_ORDERS, circleRing, cloneCircleRecord, MAPVX_BRAND_COLOR, resolveCircleConfig, } from "../domain/models/circle";
140
138
  import { Loggeable } from "../domain/models/loggeable";
141
139
  import { DEFAULT_TILE_CACHE_CONFIG, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP, } from "../domain/models/mapConfig";
140
+ import { CIRCLE_RENDER_ORDERS, circleRing, cloneCircleRecord, MAPVX_BRAND_COLOR, resolveCircleConfig, } from "../domain/models/circle";
142
141
  import { MarkerAttribute } from "../domain/models/marker";
143
142
  import { MVXRoute } from "../domain/models/route";
144
143
  import { InternalDrawRouteConfiguration, InternalGetRouteConfiguration, } from "../domain/models/routeConfiguration";
145
144
  import { Repository } from "../repository/repository";
145
+ import { hexToRgb, isPlacementVisibleOnFloor, mergeGlbScale, mergeRotation3D, opacityForZoom, resolveModels3DLights, resolveZoomTransparency, } from "../utils/3d";
146
+ import { addScene3DLights, computeGlbBottomAnchor, loadGlbRoot } from "../utils/loadGlb";
146
147
  import { extractStepCoordinates } from "../utils/route-utils";
147
148
  import { getBoundingBox } from "../utils/utils";
148
149
  import { buildInteractionOptions, shouldDisableTouchRotation } from "./mapInteractionOptions";
@@ -161,7 +162,7 @@ export class InternalMapVXMap extends Loggeable {
161
162
  * @returns A new instance of MapVXMap.
162
163
  */
163
164
  constructor(mapConfig, container, token) {
164
- var _a, _b, _c;
165
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
165
166
  super();
166
167
  this.potentialParentPlaces = [];
167
168
  this.innerFloors = [];
@@ -176,18 +177,17 @@ export class InternalMapVXMap extends Loggeable {
176
177
  this.geoLocation = navigator.geolocation;
177
178
  // 3d related variables
178
179
  this.mode = "2D";
180
+ this.scene3DConfig = undefined;
179
181
  this.deckOverlay = undefined;
180
- this.escalatorScene = undefined;
181
- this.escalatorModelTemplate = undefined;
182
- this.escalatorCenter = undefined;
183
- this.escalatorCache = {};
184
- this.ESCALATOR_MODEL_URL = "https://mapvx-glb-assets.s3.us-east-1.amazonaws.com/shared/escalator_up.glb";
185
- /**
186
- * Number of lights to use for the escalators
187
- *
188
- */
189
- this.ESCALATOR_LIGHTS = 3;
190
- this.SPRITE_URL = "https://lazarillo.app/internal/maps/vector-style/cenco-cl-pe-co-ar-3D/sprites_cencosud";
182
+ this.scene3DGlbScene = undefined;
183
+ /** Loaded GLB roots keyed by URL (deduplicated). */
184
+ this.scene3DGlbRootByUrl = new globalThis.Map();
185
+ /** Per-asset template root and bottom anchor for cloning. */
186
+ this.scene3DGlbTemplateById = new globalThis.Map();
187
+ /** Number of light objects in `scene3DGlbScene` (kept when clearing meshes). */
188
+ this.scene3DGlbLightCount = 0;
189
+ /** Container group holding all placed GLB pivots; rebuilt on each floor change. */
190
+ this.scene3DModelsGroup = undefined;
191
191
  this.spriteIconMapping = {};
192
192
  this.spriteAtlasImage = new Image();
193
193
  if (rtlLanguages.includes((_a = mapConfig.lang) !== null && _a !== void 0 ? _a : "")) {
@@ -201,15 +201,16 @@ export class InternalMapVXMap extends Loggeable {
201
201
  this.watchPositionID = undefined;
202
202
  this.onFloorChange = mapConfig.onFloorChange;
203
203
  this.onParentPlaceChange = mapConfig.onParentPlaceChange;
204
- this.mode = (_c = mapConfig.mode) !== null && _c !== void 0 ? _c : "2D";
205
- this.initialCenter = mapConfig.center;
206
204
  this.tileCacheConfig = (() => {
207
205
  const merged = Object.assign(Object.assign({}, DEFAULT_TILE_CACHE_CONFIG), mapConfig.tileCache);
208
206
  merged.maxTiles = Math.min(merged.maxTiles, MAPLIBRE_MAX_TILE_CACHE_HARD_CAP);
209
207
  return merged;
210
208
  })();
211
- if (this.mode === "3D") {
212
- this.loadSpriteAtlas().catch(console.error);
209
+ this.mode = (_c = mapConfig.mode) !== null && _c !== void 0 ? _c : "2D";
210
+ this.scene3DConfig = mapConfig.scene3D;
211
+ this.initialCenter = mapConfig.center;
212
+ 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)) {
213
+ 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);
213
214
  }
214
215
  if (mapConfig.parentPlaceId != null) {
215
216
  this.initialPlaceDetailSetUp(mapConfig.parentPlaceId, mapConfig.authToken);
@@ -337,35 +338,22 @@ export class InternalMapVXMap extends Loggeable {
337
338
  showZoom: mapConfig.showZoom !== undefined ? mapConfig.showZoom : true,
338
339
  }), (_e = mapConfig.navigationPosition) !== null && _e !== void 0 ? _e : "top-right");
339
340
  this.map.on("load", () => {
340
- var _a;
341
- if (this.mode === "3D") {
341
+ var _a, _b, _c;
342
+ if (this.is3DSceneActive()) {
342
343
  this.deckOverlay = new MapboxOverlay({ layers: [] });
343
344
  this.map.addControl(this.deckOverlay);
344
- this.hideIndoorSymbolLayers();
345
345
  // Hide MapLibre icon layers — replaced by deck.gl IconLayer
346
- this.map.setLayoutProperty("indoor-poi-logo", "visibility", "none");
347
- const style = this.map.getStyle();
348
- for (const layer of style.layers) {
349
- if (layer.id.startsWith("indoor-logo-tiendas-ancla")) {
350
- this.map.setLayoutProperty(layer.id, "visibility", "none");
351
- }
346
+ this.hideLayers((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.hideLayers);
347
+ const models = (_b = this.scene3DConfig) === null || _b === void 0 ? void 0 : _b.models;
348
+ const glbModelsActive = models !== undefined && models.enabled !== false && models.assets.length > 0;
349
+ if (glbModelsActive) {
350
+ this.map.addLayer(this.createGlbCustomLayer());
352
351
  }
353
- // Add 3D escalator layer and hide 2D transportation line
354
- this.map.addLayer(this.createEscalatorLayer());
355
- this.map.setPaintProperty("indoor-transportation", "line-opacity", 0);
356
- // Capture escalator positions when new tiles arrive (stops once cached)
357
- this.map.on("sourcedata", (e) => {
358
- if (e.sourceId === "indoorequal" &&
359
- this.currentFloor &&
360
- !this.escalatorCache[this.currentFloor]) {
361
- this.placeEscalators();
362
- }
363
- });
364
352
  }
365
353
  this.whenStyleUpdates(style);
366
354
  if (mapConfig.lang)
367
355
  this.setLayersForLanguage(mapConfig.lang);
368
- (_a = mapConfig.onMapReady) === null || _a === void 0 ? void 0 : _a.call(mapConfig);
356
+ (_c = mapConfig.onMapReady) === null || _c === void 0 ? void 0 : _c.call(mapConfig);
369
357
  this.onHover();
370
358
  this.subscribeToFailedTiles();
371
359
  });
@@ -375,6 +363,11 @@ export class InternalMapVXMap extends Loggeable {
375
363
  if (this.circles.length > 0)
376
364
  this.ensureCircleLayers();
377
365
  });
366
+ this.map.on("zoom", () => {
367
+ if (this.is3DSceneActive()) {
368
+ this.applyModelZoomOpacity();
369
+ }
370
+ });
378
371
  this.map.on("zoomend", () => {
379
372
  var _a;
380
373
  (_a = mapConfig.onZoomEnd) === null || _a === void 0 ? void 0 : _a.call(mapConfig, this.getZoomLevel());
@@ -512,48 +505,43 @@ export class InternalMapVXMap extends Loggeable {
512
505
  // Tile cache clear may fail if cache doesn't exist
513
506
  }
514
507
  }
515
- // Start 3D related methods ------------------------------------------------------------
516
- hideIndoorSymbolLayers() {
517
- var _a, _b;
518
- const style = this.map.getStyle();
519
- for (const layer of style.layers) {
520
- if (layer.type === "symbol" && layer.id.startsWith("indoor-")) {
521
- const hasText = !!((_a = layer.layout) === null || _a === void 0 ? void 0 : _a["text-field"]);
522
- const hasIcon = !!((_b = layer.layout) === null || _b === void 0 ? void 0 : _b["icon-image"]);
523
- // Hide text-only layers (replaced by deck.gl)
524
- if (hasText && !hasIcon) {
525
- this.map.setLayoutProperty(layer.id, "visibility", "none");
526
- }
527
- }
528
- }
508
+ /**
509
+ * Whether 3D scene features (deck.gl overlay, declarative GLB custom layer, etc.) are active.
510
+ * Requires {@link MapConfig.mode} `"3D"` and {@link Scene3DConfig.enabled} not `false`.
511
+ * When `scene3D` is omitted, 3D features remain available for backward compatibility.
512
+ */
513
+ is3DSceneActive() {
514
+ var _a;
515
+ return this.mode === "3D" && ((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.enabled) !== false;
529
516
  }
530
- createEscalatorLayer() {
517
+ // Start 3D related methods ------------------------------------------------------------
518
+ createGlbCustomLayer() {
519
+ var _a;
531
520
  const refMercator = MercatorCoordinate.fromLngLat(this.initialCenter, 0);
532
521
  const meterScale = refMercator.meterInMercatorCoordinateUnits();
533
522
  const modelTransform = new THREE.Matrix4()
534
523
  .makeTranslation(refMercator.x, refMercator.y, refMercator.z)
535
524
  .scale(new THREE.Vector3(meterScale, -meterScale, meterScale))
536
525
  .multiply(new THREE.Matrix4().makeRotationX(Math.PI / 2));
537
- this.escalatorScene = new THREE.Scene();
538
- const dirLight = new THREE.DirectionalLight(0xffffff, 2.0);
539
- dirLight.position.set(50, 100, 80);
540
- this.escalatorScene.add(dirLight);
541
- const dirLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
542
- dirLight2.position.set(-50, 80, -60);
543
- this.escalatorScene.add(dirLight2);
544
- this.escalatorScene.add(new THREE.AmbientLight(0xffffff, 1.5));
545
- new GLTFLoader().load(this.ESCALATOR_MODEL_URL, (gltf) => {
546
- this.escalatorModelTemplate = gltf.scene;
547
- const box = new THREE.Box3().setFromObject(this.escalatorModelTemplate);
548
- this.escalatorCenter = box.getCenter(new THREE.Vector3());
549
- this.escalatorCenter.y = box.min.y; // align bottom to ground
550
- this.placeEscalators();
526
+ this.scene3DGlbScene = new THREE.Scene();
527
+ const models = (_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.models;
528
+ if (models && models.enabled !== false && models.assets.length > 0) {
529
+ const lights = resolveModels3DLights(models);
530
+ this.scene3DGlbLightCount = addScene3DLights(this.scene3DGlbScene, lights);
531
+ }
532
+ else {
533
+ this.scene3DGlbLightCount = 0;
534
+ }
535
+ void this.loadScene3DGlbTemplates()
536
+ .then(() => this.placeScene3DModels())
537
+ .catch((err) => {
538
+ console.error("Failed to initialize declarative GLB models", err);
551
539
  });
552
540
  let renderer = undefined;
553
541
  let camera = undefined;
554
542
  const tmpMatrix = new THREE.Matrix4();
555
543
  return {
556
- id: "3d-escalators",
544
+ id: "3d-glb-models",
557
545
  type: "custom",
558
546
  renderingMode: "3d",
559
547
  onAdd(_map, gl) {
@@ -564,162 +552,206 @@ export class InternalMapVXMap extends Loggeable {
564
552
  antialias: true,
565
553
  });
566
554
  renderer.autoClear = false;
555
+ // MapLibre owns canvas dimensions; only sync the GL viewport on resize.
556
+ renderer.setSize = function (width, height) {
557
+ this.setViewport(0, 0, width, height);
558
+ };
567
559
  },
568
- render: (gl, args) => {
569
- var _a, _b;
560
+ render: (_gl, args) => {
561
+ var _a;
570
562
  if (!camera ||
571
563
  !renderer ||
572
- !this.escalatorModelTemplate ||
573
- !this.escalatorScene ||
574
- this.escalatorScene.children.length <= this.ESCALATOR_LIGHTS)
564
+ !this.scene3DGlbScene ||
565
+ !this.scene3DModelsGroup ||
566
+ this.scene3DModelsGroup.children.length === 0) {
567
+ return;
568
+ }
569
+ const canvas = this.map.getCanvas();
570
+ const width = canvas.width;
571
+ const height = canvas.height;
572
+ // Skip while the framebuffer has no drawable area (e.g. devtools opening).
573
+ if (width === 0 || height === 0) {
574
+ return;
575
+ }
576
+ const mainMatrix = (_a = args.defaultProjectionData) === null || _a === void 0 ? void 0 : _a.mainMatrix;
577
+ if (!mainMatrix) {
575
578
  return;
576
- 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;
577
- tmpMatrix.fromArray(matrixData).multiply(modelTransform);
579
+ }
580
+ renderer.setSize(width, height);
581
+ tmpMatrix.fromArray(mainMatrix).multiply(modelTransform);
578
582
  camera.projectionMatrix.copy(tmpMatrix);
579
583
  renderer.resetState();
580
- renderer.render(this.escalatorScene, camera);
584
+ renderer.render(this.scene3DGlbScene, camera);
581
585
  },
582
586
  };
583
587
  }
584
- placeEscalators() {
585
- if (!this.escalatorModelTemplate ||
586
- !this.escalatorScene ||
587
- !this.currentFloor ||
588
- !this.escalatorCenter)
589
- return;
590
- // Try to capture positions if not cached yet
591
- this.captureEscalatorPositions();
592
- const positions = this.escalatorCache[this.currentFloor];
593
- if (!positions)
588
+ async loadScene3DGlbTemplates() {
589
+ var _a;
590
+ const models = (_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.models;
591
+ if (!models || models.enabled === false || models.assets.length === 0) {
594
592
  return;
595
- // Clear scene models (keep lights)
596
- while (this.escalatorScene.children.length > this.ESCALATOR_LIGHTS) {
597
- const lastChild = this.escalatorScene.children.at(-1);
598
- if (lastChild) {
599
- this.escalatorScene.remove(lastChild);
593
+ }
594
+ this.scene3DGlbRootByUrl.clear();
595
+ this.scene3DGlbTemplateById.clear();
596
+ for (const asset of models.assets) {
597
+ try {
598
+ let root = this.scene3DGlbRootByUrl.get(asset.url);
599
+ if (!root) {
600
+ root = await loadGlbRoot(asset.url);
601
+ this.scene3DGlbRootByUrl.set(asset.url, root);
602
+ }
603
+ const center = computeGlbBottomAnchor(root);
604
+ this.scene3DGlbTemplateById.set(asset.id, { root, center });
605
+ }
606
+ catch (err) {
607
+ console.error(`Failed to load GLB asset "${asset.id}" from ${asset.url}`, err);
600
608
  }
601
609
  }
610
+ }
611
+ placeScene3DModels() {
612
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
613
+ if (!this.scene3DGlbScene) {
614
+ return;
615
+ }
616
+ const models = (_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.models;
617
+ if (!models || models.enabled === false) {
618
+ return;
619
+ }
620
+ if (this.scene3DModelsGroup) {
621
+ this.scene3DGlbScene.remove(this.scene3DModelsGroup);
622
+ this.scene3DModelsGroup = undefined;
623
+ }
624
+ if (this.scene3DGlbTemplateById.size === 0) {
625
+ return;
626
+ }
602
627
  // Guard against an invalid initial center before it reaches fromLngLat.
603
628
  if (!this.initialCenter ||
604
629
  !Number.isFinite(this.initialCenter.lng) ||
605
- !Number.isFinite(this.initialCenter.lat))
630
+ !Number.isFinite(this.initialCenter.lat)) {
606
631
  return;
632
+ }
607
633
  const refMercator = MercatorCoordinate.fromLngLat(this.initialCenter, 0);
608
634
  const meterScale = refMercator.meterInMercatorCoordinateUnits();
609
- for (const pos of positions) {
610
- // Defense in depth: never feed a non-finite coordinate to fromLngLat.
611
- if (!Number.isFinite(pos.lng) || !Number.isFinite(pos.lat))
635
+ const floorKey = (_b = this.currentFloor) !== null && _b !== void 0 ? _b : "";
636
+ const modelsGroup = new THREE.Group();
637
+ 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);
638
+ for (const placement of models.placements) {
639
+ if (!isPlacementVisibleOnFloor(placement, floorKey)) {
612
640
  continue;
613
- const target = maplibregl.MercatorCoordinate.fromLngLat([pos.lng, pos.lat], 0);
641
+ }
642
+ const template = this.scene3DGlbTemplateById.get(placement.assetId);
643
+ if (!template) {
644
+ continue;
645
+ }
646
+ const asset = models.assets.find((a) => a.id === placement.assetId);
647
+ if (!asset) {
648
+ continue;
649
+ }
650
+ // Placement positions come from external consumer config: skip any
651
+ // non-finite coordinate so a malformed config can never crash
652
+ // fromLngLat and break the whole floor render.
653
+ if (!placement.position ||
654
+ !Number.isFinite(placement.position.lng) ||
655
+ !Number.isFinite(placement.position.lat)) {
656
+ this.logWarning(new Error(`Invalid scene3D placement position (assetId=${placement.assetId}, floorKey=${floorKey})`));
657
+ continue;
658
+ }
659
+ const target = maplibregl.MercatorCoordinate.fromLngLat([placement.position.lng, placement.position.lat], (_j = placement.position.altitude) !== null && _j !== void 0 ? _j : 0);
614
660
  const sx = (target.x - refMercator.x) / meterScale;
615
661
  const sz = (target.y - refMercator.y) / meterScale;
662
+ const ax = (_l = (_k = asset.defaultModelOffset) === null || _k === void 0 ? void 0 : _k.x) !== null && _l !== void 0 ? _l : 0;
663
+ const ay = (_o = (_m = asset.defaultModelOffset) === null || _m === void 0 ? void 0 : _m.y) !== null && _o !== void 0 ? _o : 0;
664
+ const az = (_q = (_p = asset.defaultModelOffset) === null || _p === void 0 ? void 0 : _p.z) !== null && _q !== void 0 ? _q : 0;
665
+ const px = (_s = (_r = placement.offset) === null || _r === void 0 ? void 0 : _r.x) !== null && _s !== void 0 ? _s : 0;
666
+ const py = (_u = (_t = placement.offset) === null || _t === void 0 ? void 0 : _t.y) !== null && _u !== void 0 ? _u : 0;
667
+ const pz = (_w = (_v = placement.offset) === null || _v === void 0 ? void 0 : _v.z) !== null && _w !== void 0 ? _w : 0;
616
668
  const pivot = new THREE.Group();
617
- pivot.position.set(sx, 0, sz);
618
- pivot.rotation.y = -pos.bearing;
619
- pivot.scale.set(1, 1, 1);
620
- const model = this.escalatorModelTemplate.clone();
621
- model.position.set(-this.escalatorCenter.x, -this.escalatorCenter.y + 0.7, -this.escalatorCenter.z);
669
+ pivot.position.set(sx + ax + px, ay + py, sz + az + pz);
670
+ const mergedRot = mergeRotation3D(asset.defaultRotation, placement.rotation);
671
+ pivot.rotation.set(THREE.MathUtils.degToRad(mergedRot.pitch), THREE.MathUtils.degToRad(-mergedRot.yaw), THREE.MathUtils.degToRad(mergedRot.roll), "YXZ");
672
+ const [sx3, sy3, sz3] = mergeGlbScale(asset.defaultScale, placement.scale);
673
+ pivot.scale.set(sx3, sy3, sz3);
674
+ const model = template.root.clone(true);
675
+ // `clone(true)` shares materials between instances; clone them per placement so each
676
+ // placement's zoom-based opacity is independent and does not bleed into sibling models.
677
+ model.traverse((obj) => {
678
+ const mesh = obj;
679
+ if (!mesh.isMesh) {
680
+ return;
681
+ }
682
+ mesh.material = Array.isArray(mesh.material)
683
+ ? mesh.material.map((m) => m.clone())
684
+ : mesh.material.clone();
685
+ });
686
+ model.position.set(-template.center.x, -template.center.y, -template.center.z);
687
+ // Stash the resolved zoom-transparency config so it can be re-applied on every zoom change.
688
+ pivot.userData.zoomTransparency = resolveZoomTransparency(models, asset, placement);
622
689
  pivot.add(model);
623
- this.escalatorScene.add(pivot);
690
+ modelsGroup.add(pivot);
624
691
  }
692
+ this.scene3DModelsGroup = modelsGroup;
693
+ this.scene3DGlbScene.add(modelsGroup);
694
+ this.applyModelZoomOpacity();
625
695
  this.map.triggerRepaint();
626
696
  }
627
- captureEscalatorPositions() {
628
- if (this.currentFloor && this.escalatorCache[this.currentFloor])
629
- return;
630
- // queryRenderedFeatures uses exact rendered coordinates (not tile-approximated)
631
- const features = this.map
632
- .queryRenderedFeatures(undefined, {
633
- layers: ["indoor-transportation"],
634
- })
635
- .filter((f) => { var _a; return ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.floor_key) === this.currentFloor; });
636
- if (!features.length)
697
+ /**
698
+ * Applies zoom-based transparency to placed GLB models, using each placement's resolved
699
+ * {@link ZoomTransparencyConfig} stored in `pivot.userData.zoomTransparency`. Hard threshold:
700
+ * fully opaque below the configured zoom, configured opacity at/above it. Only triggers a
701
+ * repaint when at least one material actually changed.
702
+ */
703
+ applyModelZoomOpacity() {
704
+ if (!this.scene3DModelsGroup) {
637
705
  return;
638
- // Collect all midpoints first
639
- const allMids = [];
640
- for (const f of features) {
641
- const geom = f.geometry;
642
- // queryRenderedFeatures may return a LineString or, when a line is split
643
- // across tile boundaries, a MultiLineString. The previous code assumed
644
- // LineString and read coords[0]/coords[n-1] as [lng,lat], producing
645
- // NaN for MultiLineString (coordinates is number[][][]). Normalize both
646
- // shapes to a list of line parts (Position[][]) and flatten them into a
647
- // single continuous list of points so we still derive ONE midpoint per
648
- // feature (a MultiLineString is just one escalator line split across
649
- // tiles, not several escalators).
650
- let lineParts;
651
- if (geom.type === "LineString") {
652
- lineParts = [geom.coordinates];
653
- }
654
- else if (geom.type === "MultiLineString") {
655
- lineParts = geom.coordinates;
656
- }
657
- else {
658
- continue;
659
- }
660
- const coords = lineParts.flat();
661
- if (coords.length < 2)
662
- continue;
663
- const start = coords[0];
664
- const end = coords[coords.length - 1];
665
- const midLng = (start[0] + end[0]) / 2;
666
- const midLat = (start[1] + end[1]) / 2;
667
- const bearing = Math.atan2(end[0] - start[0], end[1] - start[1]);
668
- // Skip any non-finite result so a bad feature can never poison the
669
- // cache (and later crash MercatorCoordinate.fromLngLat).
670
- if (!Number.isFinite(midLng) || !Number.isFinite(midLat) || !Number.isFinite(bearing))
671
- continue;
672
- allMids.push({ lng: midLng, lat: midLat, bearing });
673
- }
674
- // Merge points within ~15m of each other into one
675
- const MERGE_DEG = 0.00015; // ~15m
676
- const positions = [];
677
- const used = new Set();
678
- for (let i = 0; i < allMids.length; i++) {
679
- if (used.has(i))
680
- continue;
681
- used.add(i);
682
- const group = [allMids[i]];
683
- for (let j = i + 1; j < allMids.length; j++) {
684
- if (used.has(j))
685
- continue;
686
- const dLng = allMids[i].lng - allMids[j].lng;
687
- const dLat = allMids[i].lat - allMids[j].lat;
688
- if (Math.abs(dLng) < MERGE_DEG && Math.abs(dLat) < MERGE_DEG) {
689
- used.add(j);
690
- group.push(allMids[j]);
706
+ }
707
+ const zoom = this.getZoomLevel();
708
+ let changed = false;
709
+ for (const pivot of this.scene3DModelsGroup.children) {
710
+ const cfg = pivot.userData.zoomTransparency;
711
+ const opacity = opacityForZoom(cfg, zoom);
712
+ pivot.traverse((obj) => {
713
+ const mesh = obj;
714
+ if (!mesh.isMesh) {
715
+ return;
691
716
  }
692
- }
693
- // Use average position and first bearing
694
- const avgLng = group.reduce((s, p) => s + p.lng, 0) / group.length;
695
- const avgLat = group.reduce((s, p) => s + p.lat, 0) / group.length;
696
- positions.push({ lng: avgLng, lat: avgLat, bearing: group[0].bearing });
717
+ const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
718
+ for (const material of materials) {
719
+ if (material.opacity !== opacity) {
720
+ material.transparent = opacity < 1;
721
+ material.opacity = opacity;
722
+ // Disable depth writes while transparent to avoid draw-order artifacts.
723
+ material.depthWrite = opacity >= 1;
724
+ changed = true;
725
+ }
726
+ }
727
+ });
697
728
  }
698
- if (positions.length && this.currentFloor) {
699
- this.escalatorCache[this.currentFloor] = positions;
729
+ if (changed) {
730
+ this.map.triggerRepaint();
700
731
  }
701
732
  }
702
- async loadSpriteAtlas() {
733
+ async loadSpriteAtlas(spriteUrl) {
703
734
  const [json, img] = await Promise.all([
704
- fetch(`${this.SPRITE_URL}.json`).then((r) => r.json()),
735
+ fetch(`${spriteUrl}.json`).then((r) => r.json()),
705
736
  new Promise((resolve, reject) => {
706
737
  const image = new Image();
707
738
  image.crossOrigin = "anonymous";
708
739
  image.onload = () => resolve(image);
709
740
  image.onerror = reject;
710
- image.src = `${this.SPRITE_URL}.png`;
741
+ image.src = `${spriteUrl}.png`;
711
742
  }),
712
743
  ]);
713
744
  this.spriteIconMapping = json;
714
745
  this.spriteAtlasImage = img;
715
746
  }
716
- makeMVTLayer(floorKey) {
747
+ makeElevatedLinesLayer(floorKey, layerConfig) {
748
+ var _a;
717
749
  return new MVTLayer({
718
750
  id: "indoor-lines-elevated",
719
- data: "https://tiles.mapvx.com/",
720
- binary: false,
751
+ data: layerConfig.dataUrl,
752
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
721
753
  renderSubLayers: (props) => {
722
- var _a;
754
+ var _a, _b, _c, _d, _e;
723
755
  const roomData = (props.data || []);
724
756
  const roomFeatures = roomData.filter((f) => {
725
757
  var _a, _b, _c;
@@ -731,7 +763,9 @@ export class InternalMapVXMap extends Loggeable {
731
763
  return null;
732
764
  const paths = [];
733
765
  for (const f of roomFeatures) {
734
- const z = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5;
766
+ const z = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height)
767
+ ? Number(f.properties.height)
768
+ : ((_b = layerConfig.defaultHeight) !== null && _b !== void 0 ? _b : 5);
735
769
  const geom = f.geometry;
736
770
  const polygons = geom.type === "Polygon"
737
771
  ? [geom.coordinates]
@@ -744,21 +778,23 @@ export class InternalMapVXMap extends Loggeable {
744
778
  }
745
779
  }
746
780
  }
747
- return new 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 }));
781
+ return new PathLayer(Object.assign(Object.assign({}, props), { id: `${props.id}-paths`, data: paths, getPath: (d) => d.path, getColor: 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 }));
748
782
  },
749
783
  updateTriggers: {
750
784
  renderSubLayers: [floorKey],
751
785
  },
752
786
  });
753
787
  }
754
- makeLabelLayer(floorKey, useSdf = true) {
788
+ makeLabelLayer(floorKey, layerConfig, useSdf = true) {
789
+ var _a, _b, _c;
755
790
  return new MVTLayer({
756
791
  id: "indoor-labels",
757
- data: "https://tiles.mapvx.com/",
758
- binary: false,
759
- minZoom: 19.5,
760
- maxZoom: 24,
792
+ data: layerConfig.dataUrl,
793
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
794
+ minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 19.5,
795
+ maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24,
761
796
  renderSubLayers: (props) => {
797
+ var _a, _b, _c, _d, _e;
762
798
  // Use area_name and poi Point features — unique per tile, no duplicates
763
799
  // If logo-{name} image exists in sprite, icon has priority over text label
764
800
  const labelData = (props.data || []);
@@ -780,32 +816,34 @@ export class InternalMapVXMap extends Loggeable {
780
816
  if (!labelFeatures.length)
781
817
  return null;
782
818
  const textData = labelFeatures.map((f) => {
783
- var _a, _b, _c;
819
+ var _a, _b, _c, _d;
784
820
  return ({
785
821
  position: [
786
822
  ...f.geometry.coordinates.slice(0, 2),
787
- ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : 5,
823
+ ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.height) ? Number(f.properties.height) : ((_b = layerConfig.defaultHeight) !== null && _b !== void 0 ? _b : 5),
788
824
  ],
789
- text: ((_b = f.properties) === null || _b === void 0 ? void 0 : _b["name:latin"]) || ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.name) || "",
825
+ text: ((_c = f.properties) === null || _c === void 0 ? void 0 : _c["name:latin"]) || ((_d = f.properties) === null || _d === void 0 ? void 0 : _d.name) || "",
790
826
  });
791
827
  });
792
- return new 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 }));
828
+ return new 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: 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 }));
793
829
  },
794
830
  updateTriggers: {
795
831
  renderSubLayers: [floorKey],
796
832
  },
797
833
  });
798
834
  }
799
- makePoiIconLayer(floorKey) {
835
+ makePoiIconLayer(floorKey, layerConfig) {
836
+ var _a, _b, _c;
800
837
  if (!this.spriteIconMapping || !this.spriteAtlasImage)
801
838
  return null;
802
839
  return new MVTLayer({
803
840
  id: "indoor-poi-icons",
804
- data: "https://tiles.mapvx.com/",
805
- binary: false,
806
- minZoom: 17,
807
- maxZoom: 24,
841
+ data: layerConfig.dataUrl,
842
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
843
+ minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 17,
844
+ maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24,
808
845
  renderSubLayers: (props) => {
846
+ var _a, _b, _c, _d;
809
847
  const poiData = (props.data || []);
810
848
  const poiFeatures = poiData.filter((f) => {
811
849
  var _a, _b, _c;
@@ -819,7 +857,7 @@ export class InternalMapVXMap extends Loggeable {
819
857
  if (!poiFeatures.length)
820
858
  return null;
821
859
  const iconData = poiFeatures.map((f) => {
822
- var _a, _b, _c;
860
+ var _a, _b, _c, _d;
823
861
  const tags = ((_a = f.properties) === null || _a === void 0 ? void 0 : _a.tags) || "";
824
862
  let icon;
825
863
  if (tags.includes("wheelchair"))
@@ -831,28 +869,30 @@ export class InternalMapVXMap extends Loggeable {
831
869
  return {
832
870
  position: [
833
871
  ...f.geometry.coordinates.slice(0, 2),
834
- ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
872
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : ((_d = layerConfig.defaultHeight) !== null && _d !== void 0 ? _d : 5),
835
873
  ],
836
874
  icon,
837
875
  };
838
876
  });
839
- return new 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 }));
877
+ return new 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 }));
840
878
  },
841
879
  updateTriggers: {
842
880
  renderSubLayers: [floorKey],
843
881
  },
844
882
  });
845
883
  }
846
- makeStoreLogoLayer(floorKey) {
884
+ makeStoreLogoLayer(floorKey, layerConfig) {
885
+ var _a, _b, _c;
847
886
  if (!this.spriteIconMapping || !this.spriteAtlasImage)
848
887
  return null;
849
888
  return new MVTLayer({
850
889
  id: "indoor-store-logos",
851
- data: "https://tiles.mapvx.com/",
852
- binary: false,
853
- minZoom: 13,
854
- maxZoom: 24,
890
+ data: layerConfig.dataUrl,
891
+ binary: (_a = layerConfig.binaryData) !== null && _a !== void 0 ? _a : false,
892
+ minZoom: (_b = layerConfig.minZoom) !== null && _b !== void 0 ? _b : 13,
893
+ maxZoom: (_c = layerConfig.maxZoom) !== null && _c !== void 0 ? _c : 24,
855
894
  renderSubLayers: (props) => {
895
+ var _a, _b, _c;
856
896
  const propsData = (props.data || []);
857
897
  const logoFeatures = propsData.filter((f) => {
858
898
  var _a, _b, _c;
@@ -868,30 +908,22 @@ export class InternalMapVXMap extends Loggeable {
868
908
  if (!logoFeatures.length)
869
909
  return null;
870
910
  const logoData = logoFeatures.map((f) => {
871
- var _a, _b, _c;
911
+ var _a, _b, _c, _d;
872
912
  const name = (_a = f.properties) === null || _a === void 0 ? void 0 : _a.name;
873
913
  const icon = "logo-" + name;
874
914
  const rotation = ((_b = f.properties) === null || _b === void 0 ? void 0 : _b.rotation_icon) ? Number(f.properties.rotation_icon) : 0;
875
915
  return {
876
916
  position: [
877
917
  ...f.geometry.coordinates.slice(0, 2),
878
- ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : 5,
918
+ ((_c = f.properties) === null || _c === void 0 ? void 0 : _c.height) ? Number(f.properties.height) : ((_d = layerConfig.defaultHeight) !== null && _d !== void 0 ? _d : 5),
879
919
  ],
880
920
  icon,
881
921
  rotation,
882
922
  };
883
923
  });
884
924
  return new 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) => {
885
- const LOGO_SCALE = {
886
- "logo-Maxi-K": 0.4,
887
- "logo-Ripley": 2,
888
- "logo-Falabella": 2,
889
- "logo-CasaIdeas": 2,
890
- "logo-Sky Costanera": 0.6,
891
- "logo-Easy": 1.4,
892
- "logo-Jumbo": 2,
893
- "logo-Decathlon": 1.4,
894
- };
925
+ var _a;
926
+ const LOGO_SCALE = (_a = layerConfig.logoScale) !== null && _a !== void 0 ? _a : {};
895
927
  const m = this.spriteIconMapping[d.icon];
896
928
  if (!m)
897
929
  return 10;
@@ -900,7 +932,7 @@ export class InternalMapVXMap extends Loggeable {
900
932
  const h = m.height / pr;
901
933
  const base = 10 * (h / Math.max(w, h));
902
934
  return base * (LOGO_SCALE[d.icon] || 1);
903
- }, sizeUnits: "meters", sizeScale: 1, billboard: false, pickable: false }));
935
+ }, 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 }));
904
936
  },
905
937
  updateTriggers: {
906
938
  renderSubLayers: [floorKey],
@@ -908,20 +940,40 @@ export class InternalMapVXMap extends Loggeable {
908
940
  });
909
941
  }
910
942
  updateDeckOverlay() {
943
+ var _a;
911
944
  if (!this.deckOverlay || !this.currentFloor)
912
945
  return;
913
- const layers = [
914
- this.makeMVTLayer(this.currentFloor),
915
- this.makeLabelLayer(this.currentFloor, false),
916
- ];
917
- const poiLayer = this.makePoiIconLayer(this.currentFloor);
918
- if (poiLayer)
919
- layers.push(poiLayer);
920
- const storeLayer = this.makeStoreLogoLayer(this.currentFloor);
921
- if (storeLayer)
922
- layers.push(storeLayer);
946
+ if (!((_a = this.scene3DConfig) === null || _a === void 0 ? void 0 : _a.customLayers))
947
+ return;
948
+ const { customLayers } = this.scene3DConfig;
949
+ const layers = [];
950
+ if (customLayers.elevatedLinesLayer) {
951
+ layers.push(this.makeElevatedLinesLayer(this.currentFloor, customLayers.elevatedLinesLayer));
952
+ }
953
+ if (customLayers.labelsLayer) {
954
+ layers.push(this.makeLabelLayer(this.currentFloor, customLayers.labelsLayer, false));
955
+ }
956
+ if (customLayers.iconLayers) {
957
+ const { storeLogosLayer, poiIconsLayer } = customLayers.iconLayers;
958
+ if (storeLogosLayer) {
959
+ layers.push(this.makeStoreLogoLayer(this.currentFloor, storeLogosLayer));
960
+ }
961
+ if (poiIconsLayer) {
962
+ layers.push(this.makePoiIconLayer(this.currentFloor, poiIconsLayer));
963
+ }
964
+ }
923
965
  this.deckOverlay.setProps({ layers });
924
966
  }
967
+ hideLayers(layersIds) {
968
+ if (!layersIds)
969
+ return;
970
+ for (const layerId of layersIds) {
971
+ const layer = this.map.getLayer(layerId);
972
+ if (!layer)
973
+ continue;
974
+ this.map.setLayoutProperty(layer.id, "visibility", "none");
975
+ }
976
+ }
925
977
  // End 3D related methods ------------------------------------------------------------
926
978
  getCurrentFloor() {
927
979
  var _a;
@@ -942,9 +994,9 @@ export class InternalMapVXMap extends Loggeable {
942
994
  if (place) {
943
995
  this.subPlacesLoad(place.mapvxId);
944
996
  }
945
- if (this.mode === "3D") {
997
+ if (this.is3DSceneActive()) {
946
998
  this.updateDeckOverlay();
947
- this.placeEscalators();
999
+ this.placeScene3DModels();
948
1000
  }
949
1001
  if (updateStyle) {
950
1002
  this.repository
@@ -974,9 +1026,9 @@ export class InternalMapVXMap extends Loggeable {
974
1026
  this.innerFloors = (_a = place === null || place === void 0 ? void 0 : place.innerFloors.sort((a, b) => a.index - b.index)) !== null && _a !== void 0 ? _a : [];
975
1027
  this.currentFloor =
976
1028
  (_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 : "";
977
- if (this.mode === "3D") {
1029
+ if (this.is3DSceneActive()) {
978
1030
  this.updateDeckOverlay();
979
- this.placeEscalators();
1031
+ this.placeScene3DModels();
980
1032
  }
981
1033
  }
982
1034
  updateParentPlaceAndFloor(parentPlaceId, floorId, options) {
@@ -1030,7 +1082,7 @@ export class InternalMapVXMap extends Loggeable {
1030
1082
  this.routeController.addSourcesAndLayers();
1031
1083
  this.refreshCircles();
1032
1084
  this.filterByFloorKey(this.currentFloor);
1033
- if (this.mode === "3D") {
1085
+ if (this.is3DSceneActive()) {
1034
1086
  this.updateDeckOverlay();
1035
1087
  }
1036
1088
  }
@@ -1462,9 +1514,10 @@ export class InternalMapVXMap extends Loggeable {
1462
1514
  this.logEvent("invalidFloorKey", { floorKey: floorKeyString });
1463
1515
  }
1464
1516
  // Critical base-map updates run first and must never be blocked by an
1465
- // optional 3D decoration failing. A throw in placeEscalators used to abort
1466
- // this method before updateFiltersTo, leaving the base polygons unfiltered
1467
- // (showing the wrong floor) while the caller's empty catch hid the error.
1517
+ // optional 3D decoration failing. A throw in placeScene3DModels used to
1518
+ // abort this method before updateFiltersTo, leaving the base polygons
1519
+ // unfiltered (showing the wrong floor) while the caller's empty catch hid
1520
+ // the error.
1468
1521
  this.updateFiltersTo(floorKeyString);
1469
1522
  this.updateMarkersTo(floorKeyString);
1470
1523
  this.refreshCircles();
@@ -1472,10 +1525,10 @@ export class InternalMapVXMap extends Loggeable {
1472
1525
  this.routeController.updateRouteMarkerVisibility(floorKeyString);
1473
1526
  // 3D decorations are best-effort: isolate so any failure here can never
1474
1527
  // break base-map rendering.
1475
- if (this.mode === "3D") {
1528
+ if (this.is3DSceneActive()) {
1476
1529
  try {
1477
1530
  this.updateDeckOverlay();
1478
- this.placeEscalators();
1531
+ this.placeScene3DModels();
1479
1532
  }
1480
1533
  catch (error) {
1481
1534
  this.logError(error, "filterByFloorKey: 3D layer update failed");