@preference-sl/pref-viewer 2.11.0-beta.3 → 2.11.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.
@@ -0,0 +1,496 @@
1
+ import { AdvancedDynamicTexture } from "@babylonjs/gui";
2
+ import { OpeningAnimationMenu } from "./babylonjs-animation-opening-menu.js";
3
+
4
+ /**
5
+ * OpeningAnimation - Manages open/close animations for a model part (e.g., a door) in a Babylon.js scene.
6
+ *
7
+ * Responsibilities:
8
+ * - Controls playback of opening and closing AnimationGroups.
9
+ * - Tracks animation state (paused, closed, opened, opening, closing).
10
+ * - Synchronizes animation progress and UI controls.
11
+ * - Handles loop mode and progress threshold logic.
12
+ * - Provides methods for play, pause, go to opened/closed, and progress control.
13
+ * - Manages the animation control menu (OpeningAnimationMenu) and its callbacks.
14
+ *
15
+ * Public Methods:
16
+ * - isAnimationForNode(node): Checks if the animation affects the given node.
17
+ * - playOpen(): Starts the opening animation.
18
+ * - playClose(): Starts the closing animation.
19
+ * - pause(): Pauses the current animation.
20
+ * - goToOpened(): Moves animation to the fully opened state.
21
+ * - goToClosed(): Moves animation to the fully closed state.
22
+ * - showControls(advancedDynamicTexture): Displays the animation control menu.
23
+ * - hideControls(): Hides the animation control menu.
24
+ * - isControlsVisible(): Returns true if the control menu is visible for this animation.
25
+ *
26
+ * Public Properties:
27
+ * - state: Returns the current animation state.
28
+ *
29
+ * Private Methods:
30
+ * - #getNodesFromAnimationGroups(): Collects node IDs affected by the animation groups.
31
+ * - #onOpened(): Handles end of opening animation.
32
+ * - #onClosed(): Handles end of closing animation.
33
+ * - #goToOpened(useLoop): Sets state to opened and optionally loops to close.
34
+ * - #goToClosed(useLoop): Sets state to closed and optionally loops to open.
35
+ * - #goToFrameAndPause(frame): Moves to a specific frame and pauses.
36
+ * - #getCurrentFrame(): Gets the current frame based on state.
37
+ * - #getFrameFromProgress(progress): Calculates frame from progress value.
38
+ * - #getProgress(): Calculates progress (0-1) from current frame.
39
+ * - #checkProgress(progress): Applies threshold logic to progress.
40
+ * - #updateControlsSlider(): Updates the slider in the control menu.
41
+ * - #updateControls(): Updates all controls in the menu.
42
+ *
43
+ * Usage Example:
44
+ * const anim = new OpeningAnimation("door", openGroup, closeGroup);
45
+ * anim.playOpen();
46
+ * anim.pause();
47
+ * anim.goToClosed();
48
+ * anim.showControls(adt);
49
+ * anim.hideControls();
50
+ */
51
+ export class OpeningAnimation {
52
+ static states = {
53
+ paused: 0,
54
+ closed: 1,
55
+ opened: 2,
56
+ opening: 3,
57
+ closing: 4,
58
+ };
59
+
60
+ #openAnimation = null;
61
+ #closeAnimation = null;
62
+
63
+ #nodes = [];
64
+ #state = OpeningAnimation.states.closed;
65
+ #lastPausedFrame = 0;
66
+ #startFrame = 0;
67
+ #endFrame = 0;
68
+ #speedRatio = 1.0;
69
+ #loop = false;
70
+
71
+ #advancedDynamicTexture = null;
72
+ #menu = null;
73
+ #progressThreshold = 0.025;
74
+
75
+ /**
76
+ * Creates a new OpeningAnimation instance for managing open/close animations of a model part.
77
+ * @param {string} name - The identifier for this animation (e.g., door name).
78
+ * @param {AnimationGroup} openAnimationGroup - Babylon.js AnimationGroup for the opening animation.
79
+ * @param {AnimationGroup} closeAnimationGroup - Babylon.js AnimationGroup for the closing animation.
80
+ * @description
81
+ * Initializes internal state, sets up frame ranges, collects affected nodes, and attaches end-of-animation observers.
82
+ */
83
+ constructor(name, openAnimationGroup, closeAnimationGroup) {
84
+ this.name = name;
85
+ this.#openAnimation = openAnimationGroup;
86
+ this.#closeAnimation = closeAnimationGroup;
87
+
88
+ this.#openAnimation.stop();
89
+ this.#openAnimation._loopAnimation = false;
90
+ this.#closeAnimation.stop();
91
+ this.#closeAnimation._loopAnimation = false;
92
+
93
+ this.#startFrame = this.#openAnimation.from;
94
+ this.#endFrame = this.#openAnimation.to;
95
+ this.#speedRatio = this.#openAnimation.speedRatio || 1.0;
96
+
97
+ this.#getNodesFromAnimationGroups();
98
+ this.#openAnimation.onAnimationGroupEndObservable.add(this.#onOpened.bind(this));
99
+ this.#closeAnimation.onAnimationGroupEndObservable.add(this.#onClosed.bind(this));
100
+ }
101
+
102
+ /**
103
+ * Collects node IDs affected by the opening and closing animation groups.
104
+ * Populates the #nodes array with unique node identifiers.
105
+ * @private
106
+ */
107
+ #getNodesFromAnimationGroups() {
108
+ [this.#openAnimation, this.#closeAnimation].forEach((animationGroup) => {
109
+ animationGroup._targetedAnimations.forEach((targetedAnimation) => {
110
+ if (!this.#nodes.includes(targetedAnimation.target.id)) {
111
+ this.#nodes.push(targetedAnimation.target.id);
112
+ }
113
+ });
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Handles the end of the opening animation group.
119
+ * @private
120
+ */
121
+ #onOpened() {
122
+ this.#goToOpened(true);
123
+ }
124
+
125
+ /**
126
+ * Handles the end of the closing animation group.
127
+ * @private
128
+ */
129
+ #onClosed() {
130
+ this.#goToClosed(true);
131
+ }
132
+
133
+ /**
134
+ * Moves the animation to the fully opened state, optionally looping to close.
135
+ * @private
136
+ * @param {boolean} useLoop - If true, starts closing animation after opening.
137
+ */
138
+ #goToOpened(useLoop = false) {
139
+ this.#lastPausedFrame = this.#endFrame;
140
+
141
+ if (this.#openAnimation._isStarted && !this.#openAnimation._isPaused) {
142
+ this.#openAnimation.pause();
143
+ }
144
+ this.#openAnimation.goToFrame(this.#endFrame);
145
+
146
+ this.#closeAnimation.pause();
147
+ this.#closeAnimation.goToFrame(this.#startFrame);
148
+
149
+ this.#state = OpeningAnimation.states.opened;
150
+ this.#updateControls();
151
+
152
+ if (this.#loop && useLoop) {
153
+ this.playClose();
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Moves the animation to the fully closed state, optionally looping to open.
159
+ * @private
160
+ * @param {boolean} useLoop - If true, starts opening animation after closing.
161
+ */
162
+ #goToClosed(useLoop = false) {
163
+ this.#lastPausedFrame = this.#startFrame;
164
+
165
+ if (this.#closeAnimation._isStarted && !this.#closeAnimation._isPaused) {
166
+ this.#closeAnimation.pause();
167
+ }
168
+ this.#closeAnimation.goToFrame(this.#endFrame - this.#startFrame);
169
+
170
+ this.#openAnimation.pause();
171
+ this.#openAnimation.goToFrame(this.#startFrame);
172
+
173
+ this.#state = OpeningAnimation.states.closed;
174
+ this.#updateControls();
175
+
176
+ if (this.#loop && useLoop) {
177
+ this.playOpen();
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Moves the animation to a specific frame and pauses.
183
+ * @private
184
+ * @param {number} frame - The frame to go to and pause.
185
+ */
186
+ #goToFrameAndPause(frame) {
187
+ if (!frame) {
188
+ return;
189
+ }
190
+
191
+ if (this.#state === OpeningAnimation.states.opening) {
192
+ this.#openAnimation.pause();
193
+ }
194
+ if (this.#state === OpeningAnimation.states.closing) {
195
+ this.#closeAnimation.pause();
196
+ }
197
+
198
+ if (this.#openAnimation._isStarted && this.#openAnimation._isPaused) {
199
+ this.#openAnimation.goToFrame(frame);
200
+ } else {
201
+ this.#openAnimation.start();
202
+ this.#openAnimation.pause();
203
+ this.#openAnimation.goToFrame(frame);
204
+ }
205
+
206
+ this.#lastPausedFrame = frame;
207
+ this.#state = OpeningAnimation.states.paused;
208
+ this.#updateControls();
209
+ }
210
+
211
+ /**
212
+ * Gets the current frame of the animation based on its state.
213
+ * @private
214
+ * @returns {number} The current frame.
215
+ */
216
+ #getCurrentFrame() {
217
+ let currentFrame;
218
+ if (this.#state === OpeningAnimation.states.opening) {
219
+ currentFrame = this.#openAnimation.getCurrentFrame();
220
+ } else if (this.#state === OpeningAnimation.states.closing) {
221
+ currentFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
222
+ } else {
223
+ currentFrame = this.#lastPausedFrame;
224
+ }
225
+
226
+ // Ensure currentFrame is within startFrame and endFrame
227
+ if (currentFrame < this.#startFrame) {
228
+ currentFrame = this.#startFrame;
229
+ } else if (currentFrame > this.#endFrame) {
230
+ currentFrame = this.#endFrame;
231
+ }
232
+
233
+ return currentFrame;
234
+ }
235
+
236
+ /**
237
+ * Calculates the frame number from a normalized progress value (0-1).
238
+ * @private
239
+ * @param {number} progress - Progress value between 0 and 1.
240
+ * @returns {number} The corresponding frame.
241
+ */
242
+ #getFrameFromProgress(progress) {
243
+ const frame = this.#startFrame + (this.#endFrame - this.#startFrame) * progress;
244
+ return frame;
245
+ }
246
+
247
+ /**
248
+ * Calculates the normalized progress (0-1) from the current frame.
249
+ * @private
250
+ * @returns {number} Progress value.
251
+ */
252
+ #getProgress() {
253
+ const currentFrame = this.#getCurrentFrame();
254
+ const progress = (currentFrame - this.#startFrame) / (this.#endFrame - this.#startFrame);
255
+ return progress;
256
+ }
257
+
258
+ /**
259
+ * Applies threshold logic to the progress value to snap to 0 or 1 if near the ends.
260
+ * Prevents floating point errors from leaving the animation in an in-between state.
261
+ * @private
262
+ * @param {number} progress - Progress value.
263
+ * @returns {number} Thresholded progress.
264
+ */
265
+ #checkProgress(progress) {
266
+ if (progress <= this.#progressThreshold) {
267
+ progress = 0;
268
+ } else if (progress >= 1 - this.#progressThreshold) {
269
+ progress = 1;
270
+ }
271
+ return progress;
272
+ }
273
+
274
+ /**
275
+ * Updates the slider value in the animation control menu to match the current progress.
276
+ * @private
277
+ */
278
+ #updateControlsSlider() {
279
+ if (!this.isControlsVisible()) {
280
+ return;
281
+ }
282
+ this.#menu.animationProgress = this.#getProgress();
283
+ }
284
+
285
+ /**
286
+ * Updates all controls in the animation menu (buttons, slider) to reflect the current state and progress.
287
+ * @private
288
+ */
289
+ #updateControls() {
290
+ if (!this.isControlsVisible()) {
291
+ return;
292
+ }
293
+ if (!this.#menu) {
294
+ return;
295
+ }
296
+ this.#menu.animationState = this.#state;
297
+ this.#menu.animationProgress = this.#getProgress();
298
+ }
299
+
300
+ /**
301
+ * ---------------------------
302
+ * Public methods
303
+ * ---------------------------
304
+ */
305
+
306
+ /**
307
+ * Checks if the animation affects the given node.
308
+ * @param {string} node - Node identifier.
309
+ * @returns {boolean}
310
+ */
311
+ isAnimationForNode(node) {
312
+ return this.#nodes.includes(node);
313
+ }
314
+
315
+ /**
316
+ * Starts the opening animation.
317
+ * @public
318
+ */
319
+ playOpen() {
320
+ if (this.#state === OpeningAnimation.states.opening || this.#state === OpeningAnimation.states.opened) {
321
+ return;
322
+ }
323
+ if (this.#state === OpeningAnimation.states.closing) {
324
+ this.#lastPausedFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
325
+ this.#closeAnimation.pause();
326
+ }
327
+
328
+ if (this.#openAnimation._isStarted && this.#openAnimation._isPaused) {
329
+ this.#openAnimation.goToFrame(this.#lastPausedFrame);
330
+ this.#openAnimation.restart();
331
+ } else {
332
+ this.#openAnimation.start(false, this.#speedRatio, this.#lastPausedFrame, this.#endFrame, undefined);
333
+ }
334
+
335
+ this.#state = OpeningAnimation.states.opening;
336
+ this.#updateControls();
337
+ }
338
+
339
+ /**
340
+ * Starts the closing animation.
341
+ * @public
342
+ */
343
+ playClose() {
344
+ if (this.#state === OpeningAnimation.states.closing || this.#state === OpeningAnimation.states.closed) {
345
+ return;
346
+ }
347
+ if (this.#state === OpeningAnimation.states.opening) {
348
+ this.#lastPausedFrame = this.#openAnimation.getCurrentFrame();
349
+ this.#openAnimation.pause();
350
+ }
351
+
352
+ if (this.#closeAnimation._isStarted && this.#closeAnimation._isPaused) {
353
+ this.#closeAnimation.goToFrame(this.#endFrame - this.#lastPausedFrame);
354
+ this.#closeAnimation.restart();
355
+ } else {
356
+ this.#closeAnimation.start(false, this.#speedRatio, this.#endFrame - this.#lastPausedFrame, this.#endFrame, undefined);
357
+ }
358
+
359
+ this.#state = OpeningAnimation.states.closing;
360
+ this.#updateControls();
361
+ }
362
+
363
+ /**
364
+ * Pauses the current animation.
365
+ * @public
366
+ */
367
+ pause() {
368
+ if (this.#state === OpeningAnimation.states.opening) {
369
+ this.#lastPausedFrame = this.#openAnimation.getCurrentFrame();
370
+ this.#openAnimation.pause();
371
+ }
372
+ if (this.#state === OpeningAnimation.states.closing) {
373
+ this.#lastPausedFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
374
+ this.#closeAnimation.pause();
375
+ }
376
+ this.#state = OpeningAnimation.states.paused;
377
+ this.#updateControls();
378
+ }
379
+
380
+ /**
381
+ * Moves animation to the fully opened state.
382
+ * @public
383
+ */
384
+ goToOpened() {
385
+ this.#goToOpened(false);
386
+ }
387
+
388
+ /**
389
+ * Moves animation to the fully closed state.
390
+ * @public
391
+ */
392
+ goToClosed() {
393
+ this.#goToClosed(false);
394
+ }
395
+
396
+ /**
397
+ * Displays the animation control menu and sets up callbacks.
398
+ * Synchronizes slider and button states with animation.
399
+ * @public
400
+ * @param {AdvancedDynamicTexture} advancedDynamicTexture - Babylon.js GUI texture.
401
+ */
402
+ showControls(advancedDynamicTexture) {
403
+ this.#advancedDynamicTexture = advancedDynamicTexture;
404
+ this.#advancedDynamicTexture.metadata = { animationName: this.name };
405
+ const controlCallbacks = {
406
+ onGoToOpened: () => {
407
+ if (this.#state === OpeningAnimation.states.opened) {
408
+ return;
409
+ }
410
+ this.goToOpened();
411
+ },
412
+ onOpen: () => {
413
+ if (this.#state === OpeningAnimation.states.opened || this.#state === OpeningAnimation.states.opening) {
414
+ return;
415
+ }
416
+ this.playOpen();
417
+ },
418
+ onPause: () => {
419
+ if (this.#state === OpeningAnimation.states.paused || this.#state === OpeningAnimation.states.closed || this.#state === OpeningAnimation.states.opened) {
420
+ return;
421
+ }
422
+ this.pause();
423
+ },
424
+ onClose: () => {
425
+ if (this.#state === OpeningAnimation.states.closed || this.#state === OpeningAnimation.states.closing) {
426
+ return;
427
+ }
428
+ this.playClose();
429
+ },
430
+ onGoToClosed: () => {
431
+ if (this.#state === OpeningAnimation.states.closed) {
432
+ return;
433
+ }
434
+ this.goToClosed();
435
+ },
436
+ onSetAnimationProgress: (progress) => {
437
+ progress = this.#checkProgress(progress);
438
+ if (progress === 0) {
439
+ this.goToClosed();
440
+ } else if (progress === 1) {
441
+ this.goToOpened();
442
+ } else {
443
+ const frame = this.#getFrameFromProgress(progress);
444
+ this.#goToFrameAndPause(frame);
445
+ }
446
+ },
447
+ onToggleLoop: () => {
448
+ this.#loop = !this.#loop;
449
+ this.#menu.animationLoop = this.#loop;
450
+ },
451
+ };
452
+ this.#menu = new OpeningAnimationMenu(this.#advancedDynamicTexture, this.#state, this.#getProgress(), this.#loop, controlCallbacks);
453
+
454
+ // Attach to Babylon.js scene render loop for real-time updates
455
+ this.#openAnimation._scene.onBeforeRenderObservable.add(this.#updateControlsSlider.bind(this));
456
+ }
457
+
458
+ /**
459
+ * Hides the animation control menu and removes observers.
460
+ * @public
461
+ */
462
+ hideControls() {
463
+ if (!this.isControlsVisible()) {
464
+ return;
465
+ }
466
+ // Remove the observer when controls are hidden
467
+ this.#openAnimation._scene.onBeforeRenderObservable.removeCallback(this.#updateControlsSlider.bind(this));
468
+ this.#advancedDynamicTexture.dispose();
469
+ this.#advancedDynamicTexture = null;
470
+ }
471
+
472
+ /**
473
+ * Checks if the animation controls menu is currently visible for this animation instance.
474
+ * @public
475
+ * @returns {boolean} True if controls are visible for this animation; otherwise, false.
476
+ */
477
+ isControlsVisible() {
478
+ return !!(this.#advancedDynamicTexture && this.#advancedDynamicTexture.metadata?.animationName === this.name && this.#menu);
479
+ }
480
+
481
+ /**
482
+ * ---------------------------
483
+ * Public properties
484
+ * ---------------------------
485
+ */
486
+
487
+ /**
488
+ * Returns the current animation state.
489
+ * @public
490
+ * @returns {number}
491
+ */
492
+
493
+ get state() {
494
+ return this.#state;
495
+ }
496
+ }
@@ -1,10 +1,11 @@
1
- import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName, HDRCubeTexture, IblShadowsRenderPipeline } from "@babylonjs/core";
1
+ import { ArcRotateCamera, AssetContainer, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager } from "@babylonjs/core";
2
2
  import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
3
3
  import "@babylonjs/loaders";
4
4
  import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
5
5
  import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
6
6
 
7
7
  import GLTFResolver from "./gltf-resolver.js";
8
+ import { ContainerData, MaterialData } from "./pref-viewer-3d-data.js";
8
9
  import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
9
10
 
10
11
  /**
@@ -293,29 +294,42 @@ export default class BabylonJSController {
293
294
  return false;
294
295
  }
295
296
 
296
- let createIBLShadowPipeline = function (scene) {
297
+ /**
298
+ * Creates and configures the IBL shadow render pipeline for the Babylon.js scene.
299
+ * Sets recommended options for resolution, sampling, opacity, and disables debug passes.
300
+ * Accepts an optional camera array for pipeline targeting.
301
+ * @private
302
+ * @param {Scene} scene - The Babylon.js scene instance.
303
+ * @param {Camera[]} [cameras] - Optional array of cameras to target with the pipeline.
304
+ * @returns {IblShadowsRenderPipeline} The configured IBL shadow pipeline.
305
+ */
306
+ let createIBLShadowPipeline = function (scene, cameras = [scene.activeCamera]) {
297
307
  const pipeline = new IblShadowsRenderPipeline(
298
308
  "iblShadowsPipeline",
299
309
  scene,
300
310
  {
301
- resolutionExp: 7,
302
- sampleDirections: 2,
311
+ resolutionExp: 8, // Higher resolution for better shadow quality
312
+ sampleDirections: 4, // More sample directions for smoother shadows
303
313
  ssShadowsEnabled: true,
304
- shadowRemanence: 0.8,
314
+ shadowRemanence: 0.85,
305
315
  triPlanarVoxelization: true,
306
- shadowOpacity: 0.8,
316
+ shadowOpacity: 0.85,
307
317
  },
308
- [scene.activeCamera]
318
+ cameras
309
319
  );
310
- pipeline.allowDebugPasses = false;
311
- pipeline.gbufferDebugEnabled = true;
312
- pipeline.importanceSamplingDebugEnabled = false;
313
- pipeline.voxelDebugEnabled = false;
314
- pipeline.voxelDebugDisplayMip = 1;
315
- pipeline.voxelDebugAxis = 2;
316
- pipeline.voxelTracingDebugEnabled = false;
317
- pipeline.spatialBlurPassDebugEnabled = false;
318
- pipeline.accumulationPassDebugEnabled = false;
320
+ // Disable all debug passes for performance
321
+ const pipelineProps = {
322
+ allowDebugPasses: false,
323
+ gbufferDebugEnabled: false,
324
+ importanceSamplingDebugEnabled: false,
325
+ voxelDebugEnabled: false,
326
+ voxelDebugDisplayMip: 0,
327
+ voxelDebugAxis: 0,
328
+ voxelTracingDebugEnabled: false,
329
+ spatialBlurPassDebugEnabled: false,
330
+ accumulationPassDebugEnabled: false,
331
+ };
332
+ Object.assign(pipeline, pipelineProps);
319
333
  return pipeline;
320
334
  };
321
335
 
@@ -417,7 +431,7 @@ export default class BabylonJSController {
417
431
  /**
418
432
  * Applies material options from the configuration to the relevant meshes.
419
433
  * @private
420
- * @param {object} optionMaterial - Material option containing value, nodePrefixes, nodeNames, and state.
434
+ * @param {MaterialData} optionMaterial - Material option containing value, nodePrefixes, nodeNames, and state.
421
435
  * @returns {boolean} True if any mesh material was set, false otherwise.
422
436
  */
423
437
  #setOptionsMaterial(optionMaterial) {
@@ -545,7 +559,7 @@ export default class BabylonJSController {
545
559
  * Finds and returns the asset container object by its name.
546
560
  * @private
547
561
  * @param {string} name - The name of the container to find.
548
- * @returns {object|null} The matching container object, or null if not found.
562
+ * @returns {ContainerData|null} The matching container object, or null if not found.
549
563
  */
550
564
  #findContainerByName(name) {
551
565
  return Object.values(this.#containers).find((container) => container.state.name === name) || null;
@@ -582,7 +596,7 @@ export default class BabylonJSController {
582
596
  * @param {boolean} isVisible - True to show the container, false to hide it.
583
597
  * @returns {void}
584
598
  */
585
- #updateVisibilityAttributeInComponentes(name, isVisible) {
599
+ #updateVisibilityAttributeInComponents(name, isVisible) {
586
600
  // Cache references to parent custom elements
587
601
  this.#getPrefViewer3DComponent();
588
602
  this.#getPrefViewerComponent();
@@ -598,14 +612,17 @@ export default class BabylonJSController {
598
612
  /**
599
613
  * Adds the asset container to the Babylon.js scene if it should be shown and is not already visible.
600
614
  * @private
601
- * @param {object} container - The container object containing asset state and metadata.
615
+ * @param {ContainerData} container - The container object containing asset state and metadata.
616
+ * @param {boolean} [updateVisibility=true] - If true, updates the visibility attribute in parent components.
602
617
  * @returns {boolean} True if the container was added, false otherwise.
603
618
  */
604
- #addContainer(container) {
619
+ #addContainer(container, updateVisibility = true) {
605
620
  if (!container.assetContainer || container.state.isVisible || !container.state.mustBeShown) {
606
621
  return false;
607
622
  }
608
- this.#updateVisibilityAttributeInComponentes(container.state.name, true);
623
+ if (updateVisibility) {
624
+ this.#updateVisibilityAttributeInComponents(container.state.name, true);
625
+ }
609
626
  container.assetContainer.addAllToScene();
610
627
  container.state.visible = true;
611
628
  return true;
@@ -614,14 +631,17 @@ export default class BabylonJSController {
614
631
  /**
615
632
  * Removes the asset container from the Babylon.js scene if it is currently visible.
616
633
  * @private
617
- * @param {object} container - The container object containing asset state and metadata.
634
+ * @param {ContainerData} container - The container object containing asset state and metadata.
635
+ * @param {boolean} [updateVisibility=true] - If true, updates the visibility attribute in parent components.
618
636
  * @returns {boolean} True if the container was removed, false otherwise.
619
637
  */
620
- #removeContainer(container) {
638
+ #removeContainer(container, updateVisibility = true) {
621
639
  if (!container.assetContainer || !container.state.isVisible) {
622
640
  return false;
623
641
  }
624
- this.#updateVisibilityAttributeInComponentes(container.state.name, false);
642
+ if (updateVisibility) {
643
+ this.#updateVisibilityAttributeInComponents(container.state.name, false);
644
+ }
625
645
  container.assetContainer.removeAllFromScene();
626
646
  container.state.visible = false;
627
647
  return true;
@@ -630,20 +650,21 @@ export default class BabylonJSController {
630
650
  /**
631
651
  * Replaces the asset container in the Babylon.js scene with a new one.
632
652
  * @private
633
- * @param {object} container - The container object containing asset state and metadata.
634
- * @param {object} newAssetContainer - The new asset container to add to the scene.
653
+ * @param {ContainerData} container - The container object containing asset state and metadata.
654
+ * @param {AssetContainer} newAssetContainer - The new asset container to add to the scene.
635
655
  * @returns {boolean} True if the container was replaced and added, false otherwise.
636
656
  */
637
657
  #replaceContainer(container, newAssetContainer) {
638
658
  if (container.assetContainer) {
639
- this.#removeContainer(container);
659
+ this.#removeContainer(container, false);
640
660
  container.assetContainer.dispose();
641
661
  container.assetContainer = null;
642
662
  }
643
663
  this.#scene.getEngine().releaseEffects();
644
664
  container.assetContainer = newAssetContainer;
645
- return this.#addContainer(container);
665
+ return this.#addContainer(container, false);
646
666
  }
667
+
647
668
  /**
648
669
  * Sets the visibility of wall and floor meshes in the model container based on the provided value or environment visibility.
649
670
  * @private
@@ -682,8 +703,8 @@ export default class BabylonJSController {
682
703
  /**
683
704
  * Loads an asset container (model, environment, materials, etc.) using the provided container state.
684
705
  * @private
685
- * @param {object} container - The container object containing asset state and metadata.
686
- * @returns {Promise<[object, AssetContainer|boolean]>} Resolves to an array with the container and the loaded asset container, or false if loading fails.
706
+ * @param {ContainerData} container - The container object containing asset state and metadata.
707
+ * @returns {Promise<[ContainerData, AssetContainer|boolean]>} Resolves to an array with the container and the loaded asset container, or false if loading fails.
687
708
  * @description
688
709
  * Resolves the asset source using GLTFResolver, prepares plugin options, and loads the asset into the Babylon.js scene.
689
710
  * Updates the container's cache data and returns the container along with the loaded asset container or false if loading fails.
@@ -754,9 +775,9 @@ export default class BabylonJSController {
754
775
  if (container.state.name === "model") {
755
776
  assetContainer.lights = [];
756
777
  }
757
- const replacedAndAdded = this.#replaceContainer(container, assetContainer);
758
- if (replacedAndAdded && container.state.name === "model") {
759
- this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#scene, assetContainer);
778
+ const replaced = this.#replaceContainer(container, assetContainer);
779
+ if (replaced && container.state.name === "model") {
780
+ this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#scene);
760
781
  }
761
782
  container.state.setSuccess(true);
762
783
  } else {
@@ -1,4 +1,4 @@
1
- import { ContainerData, MaterialData, CameraData } from "./pref-viewer-3d-data.js";
1
+ import { CameraData, ContainerData, MaterialData } from "./pref-viewer-3d-data.js";
2
2
  import BabylonJSController from "./babylonjs-controller.js";
3
3
 
4
4
  /**
@@ -1 +0,0 @@
1
- <svg id="pause" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ffffff"><path d="M14,19H18V5H14M6,19H10V5H6V19Z" /></svg>