@preference-sl/pref-viewer 2.12.0-beta.4 → 2.12.0-beta.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.12.0-beta.4",
3
+ "version": "2.12.0-beta.5",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- import { Color3, HighlightLayer, Mesh, PickingInfo, Scene } from "@babylonjs/core";
1
+ import { AssetContainer, Color3, HighlightLayer, InstancedMesh, Mesh, PickingInfo, Quaternion, Scene, UtilityLayerRenderer, Vector3 } from "@babylonjs/core";
2
2
  import { PrefViewerColors } from "./styles.js";
3
3
  import OpeningAnimation from "./babylonjs-animation-opening.js";
4
4
 
@@ -18,27 +18,36 @@ import OpeningAnimation from "./babylonjs-animation-opening.js";
18
18
  *
19
19
  * Public Methods:
20
20
  * - dispose(): Disposes all resources managed by the animation controller.
21
- * - hightlightMeshes(pickingInfo): Highlights meshes that are children of an animated node when hovered.
21
+ * - highlightMeshes(pickingInfo): Highlights meshes that are children of an animated node when hovered.
22
22
  * - hideMenu(): Hides and disposes the animation control menu if it exists.
23
23
  * - showMenu(pickingInfo): Displays the animation control menu for the animated node under the pointer.
24
24
  *
25
25
  * @class
26
26
  */
27
27
  export default class BabylonJSAnimationController {
28
- #scene = null;
29
28
  #canvas = null;
29
+ #scene = null;
30
+ #assetContainer = null;
31
+
30
32
  #animatedNodes = [];
31
- #highlightLayer = null;
32
- #highlightColor = Color3.FromHexString(PrefViewerColors.primary);
33
33
  #openingAnimations = [];
34
34
 
35
+ #highlightColor = Color3.FromHexString(PrefViewerColors.primary);
36
+ #highlightLayer = null;
37
+ #overlayLayer = null;
38
+ #useHighlightLayer = false; // Set to true to use HighlightLayer (better performance) and false to use overlay meshes (UtilityLayerRenderer - always on top)
39
+ #lastHighlightedMeshId = null; // Cache to avoid redundant highlight updates
40
+
35
41
  /**
36
42
  * Creates a new BabylonJSAnimationController for a Babylon.js scene.
37
- * @param {Scene} scene - The Babylon.js scene instance.
43
+ * @param {AssetContainer|Scene} assetContainer - The Babylon.js asset container or scene instance.
38
44
  */
39
- constructor(scene) {
40
- this.#scene = scene;
41
- this.#canvas = this.#scene._engine._renderingCanvas;
45
+ constructor(assetContainer) {
46
+ if (assetContainer instanceof AssetContainer || assetContainer instanceof Scene) {
47
+ this.#scene = assetContainer.scene ? assetContainer.scene : assetContainer;
48
+ this.#assetContainer = assetContainer;
49
+ this.#canvas = this.#scene._engine._renderingCanvas;
50
+ }
42
51
  this.#initializeAnimations();
43
52
  }
44
53
 
@@ -48,7 +57,7 @@ export default class BabylonJSAnimationController {
48
57
  */
49
58
  #initializeAnimations() {
50
59
  this.hideMenu(); // Clean up any existing menus
51
- if (!this.#scene.animationGroups.length) {
60
+ if (!this.#assetContainer?.animationGroups?.length) {
52
61
  return;
53
62
  }
54
63
  this.#getAnimatedNodes();
@@ -60,7 +69,7 @@ export default class BabylonJSAnimationController {
60
69
  * @private
61
70
  */
62
71
  #getAnimatedNodes() {
63
- this.#scene.animationGroups.forEach((animationGroup) => {
72
+ this.#assetContainer.animationGroups.forEach((animationGroup) => {
64
73
  animationGroup.stop();
65
74
  if (!animationGroup._targetedAnimations.length) {
66
75
  return;
@@ -81,7 +90,7 @@ export default class BabylonJSAnimationController {
81
90
  */
82
91
  #getOpeningAnimations() {
83
92
  const openings = {};
84
- this.#scene.animationGroups.forEach((animationGroup) => {
93
+ this.#assetContainer.animationGroups.forEach((animationGroup) => {
85
94
  const match = animationGroup.name.match(/^animation_(open|close)_(.+)$/);
86
95
  if (!match) {
87
96
  return;
@@ -118,7 +127,7 @@ export default class BabylonJSAnimationController {
118
127
  * @param {Mesh} mesh - The mesh to check.
119
128
  * @returns {Array<string>} Array of animated node IDs associated with the mesh.
120
129
  */
121
- #getNodesAnimatedByMesh = function (mesh) {
130
+ #getNodesAnimatedByMesh(mesh) {
122
131
  let nodeId = [];
123
132
  let node = mesh;
124
133
  while (node.parent !== null) {
@@ -128,7 +137,176 @@ export default class BabylonJSAnimationController {
128
137
  }
129
138
  }
130
139
  return nodeId;
131
- };
140
+ }
141
+
142
+ /**
143
+ * Creates overlay meshes with highlight rendering for a group of meshes.
144
+ * Handles both regular meshes and instanced meshes by creating clones in the utility layer.
145
+ * Synchronizes world matrix transformations between original and overlay meshes only when transforms change.
146
+ * @private
147
+ * @param {Mesh[]} meshes - Array of meshes to highlight with overlay outlines.
148
+ * @returns {Object} Cleanup API object with a dispose() method to release all overlay resources.
149
+ * @description
150
+ * - Uses UtilityLayerRenderer for rendering
151
+ * - Only syncs transforms when original mesh's world matrix changes (performance optimized)
152
+ * - Handles InstancedMeshes by creating instances of a shared overlay base
153
+ * - Removes overlay when mouse moves away from highlighted meshes
154
+ */
155
+ #addGroupOutlineOverlayForInstances(meshes) {
156
+ // Configuration
157
+ const OUTLINE_ENABLED = false; // Draw outline on individual meshes
158
+ const OUTLINE_COLOR = this.#highlightColor;
159
+ const OUTLINE_WIDTH = 1.5;
160
+ const OVERLAY_ENABLED = true; // Draw semi-transparent fill
161
+ const OVERLAY_COLOR = this.#highlightColor;
162
+ const OVERLAY_ALPHA = 0.25;
163
+ const RENDERING_GROUP_ID = 0; // MUST be > 0 to render on top of main scene
164
+
165
+ // Use UtilityLayerRenderer for rendering
166
+ const uScene = UtilityLayerRenderer.DefaultKeepDepthUtilityLayer.utilityLayerScene;
167
+
168
+ // Cache map: baseMesh -> overlayBaseMesh to reuse for InstancedMeshes
169
+ const baseToOverlayBase = new Map();
170
+
171
+ // Pairs of { original mesh, overlay mesh } for tracking
172
+ const pairs = [];
173
+
174
+ /**
175
+ * Gets the base mesh from either a regular mesh or an InstancedMesh
176
+ * @param {Mesh} mesh - The mesh to check
177
+ * @returns {Mesh} The base mesh or the mesh itself
178
+ */
179
+ function getBaseMesh(mesh) {
180
+ return mesh instanceof InstancedMesh ? mesh.sourceMesh : mesh;
181
+ }
182
+
183
+ /**
184
+ * Creates or retrieves an overlay base mesh in the utility scene
185
+ * Reuses the same base for all instances of the same source mesh
186
+ * @param {Mesh} baseMesh - The base mesh to clone
187
+ * @returns {Mesh} The overlay base mesh
188
+ */
189
+ function ensureOverlayBase(baseMesh) {
190
+ let overlayBase = baseToOverlayBase.get(baseMesh);
191
+ if (overlayBase) {
192
+ return overlayBase;
193
+ }
194
+
195
+ // Clone the source mesh into the utility scene (deep clone materials/geometry)
196
+ overlayBase = baseMesh.clone(baseMesh.name + "_overlayBase", null, true, false);
197
+ overlayBase._scene = uScene;
198
+ overlayBase.isPickable = false;
199
+ overlayBase.setEnabled(true);
200
+
201
+ // Configure visual appearance for the overlay
202
+ overlayBase.renderOutline = OUTLINE_ENABLED; // Only draw outline if enabled
203
+ overlayBase.outlineColor = OUTLINE_COLOR;
204
+ overlayBase.outlineWidth = OUTLINE_WIDTH;
205
+
206
+ // Overlay color and transparency (semi-transparent colored fill)
207
+ overlayBase.renderOverlay = OVERLAY_ENABLED;
208
+ overlayBase.overlayColor = OVERLAY_COLOR;
209
+ overlayBase.overlayAlpha = OVERLAY_ALPHA;
210
+
211
+ overlayBase.visibility = 1;
212
+
213
+ // Ensure always-on-top rendering or not
214
+ overlayBase.renderingGroupId = RENDERING_GROUP_ID;
215
+
216
+ baseToOverlayBase.set(baseMesh, overlayBase);
217
+ return overlayBase;
218
+ }
219
+
220
+ // Create overlay meshes for each input mesh
221
+ meshes.forEach((mesh) => {
222
+ // Skip meshes without geometry
223
+ if (!mesh.geometry && !mesh.getChildMeshes) {
224
+ return;
225
+ }
226
+
227
+ const baseMesh = getBaseMesh(mesh);
228
+ const overlayBase = ensureOverlayBase(baseMesh);
229
+
230
+ let overlay;
231
+ if (mesh instanceof InstancedMesh) {
232
+ // For instances, create a new instance of the overlay base
233
+ overlay = overlayBase.createInstance(mesh.name + "_overlayInst");
234
+ overlay._scene = uScene;
235
+ overlay.isPickable = false;
236
+ } else {
237
+ // For regular meshes, use the overlay base directly (avoids double cloning)
238
+ overlay = overlayBase;
239
+ }
240
+
241
+ // Remove parent to allow independent world matrix control
242
+ overlay.parent = null;
243
+ overlay.renderingGroupId = RENDERING_GROUP_ID;
244
+
245
+ pairs.push({ original: mesh, overlay });
246
+ });
247
+
248
+ // Sync world matrix transformations
249
+ const pos = new Vector3();
250
+ const scl = new Vector3();
251
+ const rot = new Quaternion();
252
+
253
+ // Track all observers for cleanup
254
+ const observers = [];
255
+
256
+ // Subscribe to world matrix update events
257
+ // This is more efficient than updating every frame
258
+ pairs.forEach(({ original, overlay }) => {
259
+ // Skip if mesh has no geometry to sync
260
+ if (!original.getWorldMatrix) {
261
+ return;
262
+ }
263
+
264
+ // Initial sync: copy the current world matrix
265
+ original.computeWorldMatrix(true);
266
+ original.getWorldMatrix().decompose(scl, rot, pos);
267
+
268
+ overlay.position.copyFrom(pos);
269
+ overlay.scaling.copyFrom(scl);
270
+ overlay.rotationQuaternion = overlay.rotationQuaternion || new Quaternion();
271
+ overlay.rotationQuaternion.copyFrom(rot);
272
+ overlay.computeWorldMatrix(true);
273
+
274
+ // Subscribe to world matrix changes
275
+ // Only updates when the original mesh's transformation actually changes
276
+ const obs = original.onAfterWorldMatrixUpdateObservable?.add(() => {
277
+ original.getWorldMatrix().decompose(scl, rot, pos);
278
+
279
+ overlay.position.copyFrom(pos);
280
+ overlay.scaling.copyFrom(scl);
281
+ overlay.rotationQuaternion.copyFrom(rot);
282
+ overlay.computeWorldMatrix(true);
283
+ });
284
+
285
+ if (obs) {
286
+ observers.push({ mesh: original, observer: obs });
287
+ }
288
+ });
289
+
290
+ // Cleanup API
291
+ return {
292
+ dispose() {
293
+ // Remove world matrix update observers
294
+ observers.forEach(({ mesh, observer }) => {
295
+ mesh.onAfterWorldMatrixUpdateObservable?.remove(observer);
296
+ });
297
+
298
+ // Dispose overlay instances and meshes
299
+ pairs.forEach(({ original, overlay }) => {
300
+ overlay.dispose();
301
+ });
302
+
303
+ // Dispose overlay base meshes
304
+ baseToOverlayBase.forEach((overlayBase) => {
305
+ overlayBase.dispose();
306
+ });
307
+ },
308
+ };
309
+ }
132
310
 
133
311
  /**
134
312
  * ---------------------------
@@ -148,6 +326,10 @@ export default class BabylonJSAnimationController {
148
326
  this.#highlightLayer.dispose();
149
327
  this.#highlightLayer = null;
150
328
  }
329
+ if (this.#overlayLayer) {
330
+ this.#overlayLayer.dispose();
331
+ this.#overlayLayer = null;
332
+ }
151
333
  this.hideMenu();
152
334
  this.#animatedNodes = [];
153
335
  this.#openingAnimations.forEach((openingAnimation) => openingAnimation.dispose());
@@ -159,39 +341,74 @@ export default class BabylonJSAnimationController {
159
341
  * @public
160
342
  * @param {PickingInfo} pickingInfo - Raycast info from pointer position.
161
343
  */
162
- hightlightMeshes(pickingInfo) {
163
- if (!this.#highlightLayer) {
164
- this.#highlightLayer = new HighlightLayer("hl_animations", this.#scene);
344
+ highlightMeshes(pickingInfo) {
345
+ // Check if we're hovering the same mesh to avoid recreating overlays/highlights
346
+ const pickedMeshId = pickingInfo?.pickedMesh?.id;
347
+ if (this.#lastHighlightedMeshId === pickedMeshId) {
348
+ return; // No need to update if hovering the same mesh
165
349
  }
350
+ this.#lastHighlightedMeshId = pickedMeshId;
166
351
 
167
- this.#highlightLayer.removeAllMeshes();
168
- if (!pickingInfo?.hit && !pickingInfo?.pickedMesh) {
169
- return;
170
- }
352
+ if (this.#useHighlightLayer) {
353
+ if (!this.#highlightLayer) {
354
+ this.#highlightLayer = new HighlightLayer("hl_animations", this.#scene);
355
+ }
171
356
 
172
- const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
173
- if (!nodeIds.length) {
174
- return;
175
- }
357
+ this.#highlightLayer.removeAllMeshes();
176
358
 
177
- const transformNodes = [];
178
- nodeIds.forEach((nodeId) => {
179
- const transformNode = this.#scene.getTransformNodeByID(nodeId);
180
- if (transformNode) {
181
- transformNodes.push(transformNode);
359
+ if (!pickingInfo?.hit || !pickingInfo?.pickedMesh) {
360
+ return;
182
361
  }
183
- });
184
362
 
185
- transformNodes.forEach((transformNode) => {
186
- const nodeMeshes = transformNode.getChildMeshes();
187
- if (nodeMeshes.length) {
188
- nodeMeshes.forEach((mesh) => {
189
- if (!this.#highlightLayer.hasMesh(mesh)) {
190
- this.#highlightLayer.addMesh(mesh, this.#highlightColor);
191
- }
192
- });
363
+ const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
364
+ if (!nodeIds.length) {
365
+ return;
193
366
  }
194
- });
367
+
368
+ const transformNodes = [];
369
+ nodeIds.forEach((nodeId) => {
370
+ const transformNode = this.#scene.getTransformNodeByID(nodeId);
371
+ if (transformNode) {
372
+ transformNodes.push(transformNode);
373
+ }
374
+ });
375
+
376
+ transformNodes.forEach((transformNode) => {
377
+ const transformNodeMeshes = transformNode.getChildMeshes();
378
+ if (transformNodeMeshes.length) {
379
+ transformNodeMeshes.forEach((mesh) => {
380
+ if (!this.#highlightLayer.hasMesh(mesh)) {
381
+ this.#highlightLayer.addMesh(mesh, this.#highlightColor);
382
+ }
383
+ });
384
+ }
385
+ });
386
+ } else {
387
+ if (this.#overlayLayer) {
388
+ this.#overlayLayer.dispose();
389
+ this.#overlayLayer = null;
390
+ }
391
+
392
+ if (!pickingInfo?.hit || !pickingInfo?.pickedMesh) {
393
+ return;
394
+ }
395
+
396
+ const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
397
+ if (!nodeIds.length) {
398
+ return;
399
+ }
400
+
401
+ const meshes = [];
402
+ nodeIds.forEach((nodeId) => {
403
+ const transformNode = this.#scene.getTransformNodeByID(nodeId);
404
+ if (transformNode) {
405
+ const transformNodeMeshes = transformNode.getChildMeshes(false);
406
+ meshes.push(...transformNodeMeshes);
407
+ }
408
+ });
409
+
410
+ this.#overlayLayer = this.#addGroupOutlineOverlayForInstances(meshes);
411
+ }
195
412
  }
196
413
 
197
414
  /**
@@ -201,7 +418,7 @@ export default class BabylonJSAnimationController {
201
418
  */
202
419
  hideMenu() {
203
420
  this.#openingAnimations.forEach((openingAnimation) => openingAnimation.hideControls());
204
- this.#canvas.parentElement.querySelectorAll("div.pref-viewer-3d.animation-menu").forEach((menu) => menu.remove());
421
+ this.#canvas?.parentElement?.querySelectorAll("div.pref-viewer-3d.animation-menu").forEach((menu) => menu.remove());
205
422
  }
206
423
 
207
424
  /**
@@ -95,6 +95,8 @@ export default class OpeningAnimation {
95
95
  this.#bindHandlers();
96
96
  this.#openAnimation.onAnimationGroupEndObservable.add(this.#handlers.onOpened);
97
97
  this.#closeAnimation.onAnimationGroupEndObservable.add(this.#handlers.onClosed);
98
+
99
+ this.#goToClosed();
98
100
  }
99
101
 
100
102
  #bindHandlers() {
@@ -1,4 +1,4 @@
1
- import { ArcRotateCamera, AssetContainer, Camera, Color4, DefaultRenderingPipeline, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PBRMaterial, PointerEventTypes, PointLight, Scene, ShadowGenerator, SpotLight, SSAORenderingPipeline, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager, WebXRState } from "@babylonjs/core";
1
+ import { ArcRotateCamera, AssetContainer, Camera, Color4, DefaultRenderingPipeline, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PBRMaterial, PointerEventTypes, PointLight, RenderTargetTexture, Scene, ShadowGenerator, SpotLight, SSAORenderingPipeline, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager, WebXRState } from "@babylonjs/core";
2
2
  import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression.js";
3
3
  import "@babylonjs/loaders";
4
4
  import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression.js";
@@ -118,20 +118,25 @@ export default class BabylonJSController {
118
118
  #shadowGen = [];
119
119
  #XRExperience = null;
120
120
  #canvasResizeObserver = null;
121
-
121
+
122
122
  #containers = {};
123
123
  #options = {};
124
-
124
+
125
125
  #gltfResolver = null; // GLTFResolver instance
126
126
  #babylonJSAnimationController = null; // AnimationController instance
127
-
127
+
128
128
  #handlers = {
129
129
  onKeyUp: null,
130
130
  onPointerObservable: null,
131
131
  renderLoop: null,
132
132
  };
133
-
134
- #shadowsEnabled = false;
133
+
134
+ #settings = {
135
+ antiAliasingEnabled: true,
136
+ ambientOcclusionEnabled: true,
137
+ iblEnabled: true,
138
+ shadowsEnabled: false,
139
+ };
135
140
 
136
141
  /**
137
142
  * Constructs a new BabylonJSController instance.
@@ -298,7 +303,7 @@ export default class BabylonJSController {
298
303
  * @returns {void}
299
304
  */
300
305
  #createLights() {
301
- if (this.#options.ibl && this.#options.ibl.url) {
306
+ if (this.#settings.iblEnabled && this.#options.ibl && this.#options.ibl.url) {
302
307
  this.#hemiLight = this.#dirLight = this.#cameraLight = null;
303
308
  this.#initializeEnvironmentTexture();
304
309
  }
@@ -334,6 +339,10 @@ export default class BabylonJSController {
334
339
  return false;
335
340
  }
336
341
 
342
+ if (!this.#settings.ambientOcclusionEnabled) {
343
+ return false;
344
+ }
345
+
337
346
  const pipelineName = "PrefViewerSSAORenderingPipeline";
338
347
 
339
348
  const supportedPipelines = this.#scene.postProcessRenderPipelineManager.supportedPipelines;
@@ -402,6 +411,10 @@ export default class BabylonJSController {
402
411
  this.#scene.postProcessRenderPipelineManager.update();
403
412
  }
404
413
 
414
+ if (!this.#settings.antiAliasingEnabled) {
415
+ return false;
416
+ }
417
+
405
418
  const defaultPipeline = new DefaultRenderingPipeline(pipelineName, true, this.#scene, [this.#scene.activeCamera], true);
406
419
 
407
420
  if (!defaultPipeline){
@@ -553,10 +566,14 @@ export default class BabylonJSController {
553
566
  */
554
567
  #initializeDefaultLightShadows() {
555
568
  this.#shadowGen = [];
569
+ this.#dirLight.autoUpdateExtends = false;
556
570
  const shadowGenerator = new ShadowGenerator(1024, this.#dirLight);
557
571
  shadowGenerator.useBlurExponentialShadowMap = true;
558
572
  shadowGenerator.blurKernel = 16;
559
573
  shadowGenerator.darkness = 0.5;
574
+ shadowGenerator.bias = 0.0005;
575
+ shadowGenerator.normalBias = 0.02;
576
+ shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
560
577
  this.#scene.meshes.forEach((mesh) => {
561
578
  if (mesh.id.startsWith("__root__")) {
562
579
  return false;
@@ -589,10 +606,14 @@ export default class BabylonJSController {
589
606
  if (!(light instanceof DirectionalLight || light instanceof SpotLight)) {
590
607
  return;
591
608
  }
609
+ light.autoUpdateExtends = false;
592
610
  const shadowGenerator = new ShadowGenerator(1024, light);
593
611
  shadowGenerator.useBlurExponentialShadowMap = true;
594
612
  shadowGenerator.blurKernel = 16;
595
613
  shadowGenerator.darkness = 0.5;
614
+ shadowGenerator.bias = 0.0005;
615
+ shadowGenerator.normalBias = 0.02;
616
+ shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
596
617
  this.#scene.meshes.forEach((mesh) => {
597
618
  if (mesh.id.startsWith("__root__")) {
598
619
  return false;
@@ -614,7 +635,7 @@ export default class BabylonJSController {
614
635
  * Otherwise, sets up shadow casting and receiving for all relevant meshes using the shadow generator.
615
636
  */
616
637
  #initializeShadows() {
617
- if (!this.#shadowsEnabled) {
638
+ if (!this.#settings.shadowsEnabled) {
618
639
  return false;
619
640
  }
620
641
  if (this.#scene.environmentTexture) {
@@ -815,7 +836,7 @@ export default class BabylonJSController {
815
836
  */
816
837
  #onPointerMove(event, pickInfo) {
817
838
  if (this.#babylonJSAnimationController) {
818
- this.#babylonJSAnimationController.hightlightMeshes(pickInfo);
839
+ this.#babylonJSAnimationController.highlightMeshes(pickInfo);
819
840
  }
820
841
  }
821
842
 
@@ -954,11 +975,12 @@ export default class BabylonJSController {
954
975
  * @returns {boolean} True when lights were refreshed due to pending IBL changes, otherwise false.
955
976
  */
956
977
  #setOptions_IBL() {
957
- if (this.#options.ibl.isPending) {
978
+ if (this.#options.ibl.isPending && this.#settings.iblEnabled) {
958
979
  this.#options.ibl.setSuccess(true);
959
980
  this.#createLights();
960
981
  return true;
961
982
  }
983
+ this.#createLights();
962
984
  return false;
963
985
  }
964
986
 
@@ -1224,7 +1246,8 @@ export default class BabylonJSController {
1224
1246
  this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
1225
1247
  this.#setMaxSimultaneousLights();
1226
1248
  this.#loadCameraDepentEffects();
1227
- this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#scene);
1249
+ this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
1250
+ this.#forceReflectionsInModelGlasses();
1228
1251
  this.#startRender();
1229
1252
  });
1230
1253
  return detail;
@@ -1318,6 +1341,21 @@ export default class BabylonJSController {
1318
1341
  }
1319
1342
  }
1320
1343
 
1344
+ #forceReflectionsInModelGlasses(assetContainer) {
1345
+ if (assetContainer === undefined) {
1346
+ assetContainer = this.#containers.model.assetContainer;
1347
+ }
1348
+ if (!this.#scene || !assetContainer) {
1349
+ return;
1350
+ }
1351
+ assetContainer.materials?.forEach(material => {
1352
+ if (material && material.name && material.name.includes("Glass")) {
1353
+ material.metallic = 1.0;
1354
+ material.environmentIntensity = 1.0;
1355
+ }
1356
+ });
1357
+ }
1358
+
1321
1359
  /**
1322
1360
  * Translates a node along the scene's vertical (Y) axis by the provided value.
1323
1361
  * @private
@@ -1483,7 +1521,6 @@ export default class BabylonJSController {
1483
1521
 
1484
1522
  this.#scene.clearColor = new Color4(1, 1, 1, 1);
1485
1523
  this.#createCamera();
1486
- this.#createLights();
1487
1524
  this.#enableInteraction();
1488
1525
  await this.#createXRExperience();
1489
1526
  this.#startRender();
@@ -336,12 +336,14 @@ export default class PrefViewer3D extends HTMLElement {
336
336
  let intensity = undefined;
337
337
 
338
338
  if (options.ibl.url) {
339
- const fileStorage = new FileStorage("PrefViewer", "Files");
340
- const newURL = await fileStorage.getURL(options.ibl.url);
341
- if (newURL) {
342
- url = newURL;
343
- timeStamp = await fileStorage.getTimeStamp(options.ibl.url);
344
- }
339
+ url = options.ibl.url;
340
+ // TEMPORARY: Disable FileStorage usage due to efficiency problems
341
+ // const fileStorage = new FileStorage("PrefViewer", "Files");
342
+ // const newURL = await fileStorage.getURL(options.ibl.url);
343
+ // if (newURL) {
344
+ // url = newURL;
345
+ // timeStamp = await fileStorage.getTimeStamp(options.ibl.url);
346
+ // }
345
347
  }
346
348
  if (options.ibl.shadows !== undefined) {
347
349
  shadows = options.ibl.shadows;