@preference-sl/pref-viewer 2.10.0-beta.25 → 2.10.0-beta.27

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 (2) hide show
  1. package/package.json +5 -5
  2. package/src/index.js +261 -294
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.25",
3
+ "version": "2.10.0-beta.27",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
@@ -34,10 +34,10 @@
34
34
  "index.d.ts"
35
35
  ],
36
36
  "dependencies": {
37
- "@babylonjs/core": "^8.28.2",
38
- "@babylonjs/loaders": "^8.28.2",
39
- "@babylonjs/serializers": "^8.28.2",
40
- "babylonjs-gltf2interface": "^8.28.2"
37
+ "@babylonjs/core": "^8.31.3",
38
+ "@babylonjs/loaders": "^8.31.3",
39
+ "@babylonjs/serializers": "^8.31.3",
40
+ "babylonjs-gltf2interface": "^8.31.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "esbuild": "^0.25.10",
package/src/index.js CHANGED
@@ -39,7 +39,7 @@
39
39
  * </pref-viewer>
40
40
  * ```
41
41
  */
42
- import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName } from "@babylonjs/core";
42
+ import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName, HDRCubeTexture, IblShadowsRenderPipeline } from "@babylonjs/core";
43
43
  import "@babylonjs/loaders";
44
44
  import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
45
45
  import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
@@ -88,28 +88,28 @@ class PrefViewer extends HTMLElement {
88
88
  camera: {
89
89
  value: null,
90
90
  locked: true,
91
- changed: { pending: false, success: false },
91
+ changed: { pending: false, success: false, oldValue: null, oldLocked: true },
92
92
  },
93
93
  materials: {
94
94
  innerWall: {
95
95
  value: null,
96
96
  prefix: "innerWall",
97
- changed: { pending: false, success: false },
97
+ changed: { pending: false, success: false, oldValue: null },
98
98
  },
99
99
  outerWall: {
100
100
  value: null,
101
101
  prefix: "outerWall",
102
- changed: { pending: false, success: false },
102
+ changed: { pending: false, success: false, oldValue: null },
103
103
  },
104
104
  innerFloor: {
105
105
  value: null,
106
106
  prefix: "innerFloor",
107
- changed: { pending: false, success: false },
107
+ changed: { pending: false, success: false, oldValue: null },
108
108
  },
109
109
  outerFloor: {
110
110
  value: null,
111
111
  prefix: "outerFloor",
112
- changed: { pending: false, success: false },
112
+ changed: { pending: false, success: false, oldValue: null },
113
113
  },
114
114
  },
115
115
  },
@@ -131,13 +131,11 @@ class PrefViewer extends HTMLElement {
131
131
 
132
132
  constructor() {
133
133
  super();
134
- console.log("PrefViewer: constructor()");
135
134
  this.attachShadow({ mode: "open" });
136
135
  this.#createCanvas();
137
136
  this.#wrapCanvas();
138
137
  // Point to whichever version you packaged or want to use:
139
138
  const DRACO_BASE = "https://www.gstatic.com/draco/versioned/decoders/1.5.7";
140
- console.log("PrefViewer: DRACO config base =", DRACO_BASE);
141
139
  DracoCompression.Configuration.decoder = {
142
140
  // loader for the “wrapper” that pulls in the real WASM
143
141
  wasmUrl: `${DRACO_BASE}/draco_wasm_wrapper_gltf.js`,
@@ -153,7 +151,6 @@ class PrefViewer extends HTMLElement {
153
151
  }
154
152
 
155
153
  attributeChangedCallback(name, _old, value) {
156
- console.log("PrefViewer: attributeChangedCallback()", { name, old: _old, value });
157
154
  let data = null;
158
155
  switch (name) {
159
156
  case "config":
@@ -168,7 +165,6 @@ class PrefViewer extends HTMLElement {
168
165
  case "show-model":
169
166
  data = value.toLowerCase?.() === "true";
170
167
  if (this.initialized) {
171
- console.log("PrefViewer: toggling model visibility (attr)", data);
172
168
  data ? this.showModel() : this.hideModel();
173
169
  } else {
174
170
  this.#data.containers.model.show = data;
@@ -177,7 +173,6 @@ class PrefViewer extends HTMLElement {
177
173
  case "show-scene":
178
174
  data = value.toLowerCase?.() === "true";
179
175
  if (this.initialized) {
180
- console.log("PrefViewer: toggling scene visibility (attr)", data);
181
176
  data ? this.showScene() : this.hideScene();
182
177
  } else {
183
178
  this.#data.containers.environment.show = data;
@@ -187,7 +182,6 @@ class PrefViewer extends HTMLElement {
187
182
  }
188
183
 
189
184
  connectedCallback() {
190
- console.log("PrefViewer: connectedCallback()");
191
185
  if (!this.hasAttribute("config")) {
192
186
  const error = 'PrefViewer: provide "models" as array of model and environment';
193
187
  console.error(error);
@@ -204,19 +198,16 @@ class PrefViewer extends HTMLElement {
204
198
 
205
199
  this.#initializeBabylon();
206
200
  this.initialized = true;
207
- console.log("PrefViewer: initialized = true, loading containers…");
208
201
  this.#loadContainers(true, true, true);
209
202
  }
210
203
 
211
204
  disconnectedCallback() {
212
- console.log("PrefViewer: disconnectedCallback()");
213
205
  this.#disposeEngine();
214
206
  this.#canvasResizeObserver.disconnect();
215
207
  }
216
208
 
217
209
  // Web Component
218
210
  #createCanvas() {
219
- console.log("PrefViewer: #createCanvas()");
220
211
  this.#canvas = document.createElement("canvas");
221
212
  Object.assign(this.#canvas.style, {
222
213
  width: "100%",
@@ -227,7 +218,6 @@ class PrefViewer extends HTMLElement {
227
218
  }
228
219
 
229
220
  #wrapCanvas() {
230
- console.log("PrefViewer: #wrapCanvas()");
231
221
  this.#wrapper = document.createElement("div");
232
222
  Object.assign(this.#wrapper.style, {
233
223
  width: "100%",
@@ -239,7 +229,6 @@ class PrefViewer extends HTMLElement {
239
229
  }
240
230
 
241
231
  #setStatusSceneLoading() {
242
- console.log("PrefViewer: #setStatusSceneLoading()");
243
232
  this.loaded = false;
244
233
  this.loading = true;
245
234
  if (this.hasAttribute("loaded")) {
@@ -256,7 +245,6 @@ class PrefViewer extends HTMLElement {
256
245
  }
257
246
 
258
247
  #setStatusSceneLoaded() {
259
- console.log("PrefViewer: #setStatusSceneLoaded()");
260
248
  this.loaded = true;
261
249
  this.loading = false;
262
250
 
@@ -298,11 +286,10 @@ class PrefViewer extends HTMLElement {
298
286
  detail: detail,
299
287
  })
300
288
  );
301
- console.log("PrefViewer: scene-loaded detail =", detail);
289
+ this.#resetChangedFlags();
302
290
  }
303
291
 
304
292
  #setStatusOptionsLoading() {
305
- console.log("PrefViewer: #setStatusOptionsLoading()");
306
293
  this.dispatchEvent(
307
294
  new CustomEvent("options-loading", {
308
295
  bubbles: true,
@@ -313,14 +300,15 @@ class PrefViewer extends HTMLElement {
313
300
  }
314
301
 
315
302
  #setStatusOptionsLoaded() {
316
- console.log("PrefViewer: #setStatusOptionsLoaded()");
317
303
  const toLoadDetail = {
304
+ camera: !!this.#data.options.camera.changed.pending,
318
305
  innerWallMaterial: !!this.#data.options.materials.innerWall.changed.pending,
319
306
  outerWallMaterial: !!this.#data.options.materials.outerWall.changed.pending,
320
307
  innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.pending,
321
308
  outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.pending,
322
309
  };
323
310
  const loadedDetail = {
311
+ camera: !!this.#data.options.camera.changed.success,
324
312
  innerWallMaterial: !!this.#data.options.materials.innerWall.changed.success,
325
313
  outerWallMaterial: !!this.#data.options.materials.outerWall.changed.success,
326
314
  innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.success,
@@ -340,7 +328,7 @@ class PrefViewer extends HTMLElement {
340
328
  detail: detail,
341
329
  })
342
330
  );
343
- console.log("PrefViewer: options-loaded detail =", detail);
331
+ this.#resetChangedFlags();
344
332
  }
345
333
 
346
334
  // Data
@@ -348,17 +336,16 @@ class PrefViewer extends HTMLElement {
348
336
  if (!options || !options.camera) {
349
337
  return false;
350
338
  }
351
- const changed = options.camera !== this.#data.options.camera.value;
352
- console.log("PrefViewer: #checkCameraChanged()", { incoming: options.camera, previous: this.#data.options.camera.value, changed });
353
- this.#data.options.camera.changed = {
354
- ...this.#data.options.camera.changed,
355
- pending: changed,
356
- success: false,
357
- oldValue: changed ? this.#data.options.camera.value : this.#data.options.camera.changed.oldValue,
358
- oldLocked: changed ? this.#data.options.camera.locked : this.#data.options.camera.changed.oldLocked,
359
- };
360
- if (changed) this.#data.options.camera.value = options.camera;
361
-
339
+ const prev = this.#data.options.camera.value;
340
+ const changed = options.camera !== prev;
341
+
342
+ this.#data.options.camera.changed.pending = changed;
343
+ this.#data.options.camera.changed.success = false;
344
+ if (changed) {
345
+ this.#data.options.camera.changed.oldValue = prev;
346
+ this.#data.options.camera.changed.oldLocked = this.#data.options.camera.locked;
347
+ this.#data.options.camera.value = options.camera;
348
+ }
362
349
  return changed;
363
350
  }
364
351
 
@@ -369,36 +356,39 @@ class PrefViewer extends HTMLElement {
369
356
  let someChanged = false;
370
357
  Object.keys(this.#data.options.materials).forEach((material) => {
371
358
  const key = `${material}Material`;
372
- const materialState = this.#data.options.materials[material];
373
- const prev = materialState.value;
359
+ const state = this.#data.options.materials[material];
360
+ const prev = state.value;
374
361
  const incoming = options[key];
375
362
  const materialChanged = !!incoming && incoming !== prev;
376
- console.log("PrefViewer: #checkMaterialsChanged()", { key, incoming, previous: prev, materialChanged });
377
- materialState.changed = {
378
- ...materialState.changed,
379
- pending: materialChanged,
380
- success: false,
381
- oldValue: materialChanged ? prev : materialState.changed.oldValue,
382
- };
383
- materialState.value = materialChanged ? incoming : prev;
363
+
364
+ state.changed.pending = materialChanged;
365
+ state.changed.success = false;
366
+ if (materialChanged) {
367
+ state.changed.oldValue = prev;
368
+ state.value = incoming;
369
+ }
384
370
  someChanged = someChanged || materialChanged;
385
371
  });
386
372
  return someChanged;
387
373
  }
388
374
 
389
- #storeChangedFlagsForContainer(container) {
390
- console.log("PrefViewer: #storeChangedFlagsForContainer()", { name: container.name, changed: container.changed });
391
- container.timeStamp = container.changed.timeStamp;
392
- container.size = container.changed.size;
393
- container.changed.success = true;
394
- container.changed.pending = false;
375
+ #storeChangedFlagsForContainer(container, success) {
376
+ if (success) {
377
+ container.timeStamp = container.changed.timeStamp;
378
+ container.size = container.changed.size;
379
+ container.changed.success = true;
380
+ container.changed.pending = false;
381
+ } else {
382
+ container.changed.success = false;
383
+ container.changed.pending = false;
384
+ }
395
385
  }
396
386
 
397
387
  #resetChangedFlags() {
398
- console.log("PrefViewer: #resetChangedFlags()");
399
388
  const reset = (node) => {
400
- node.changed.success = false;
389
+ if (!node.changed) node.changed = {};
401
390
  node.changed.pending = false;
391
+ node.changed.success = false;
402
392
  };
403
393
  Object.values(this.#data.containers).forEach(reset);
404
394
  Object.values(this.#data.options.materials).forEach(reset);
@@ -407,30 +397,24 @@ class PrefViewer extends HTMLElement {
407
397
 
408
398
  // Babylon.js
409
399
  async #initializeBabylon() {
410
- console.log("PrefViewer: #initializeBabylon() START");
411
400
  this.#engine = new Engine(this.#canvas, true, { alpha: true });
412
- this.#engine.disableUniformBuffers = true; // <- evita el límite de GL_MAX_*_UNIFORM_BUFFERS // PROVISIONAL, YA QUE ESTO ES UN POCO OVERKILL
413
- console.log("PrefViewer: Engine created", { disableUBO: this.#engine.disableUniformBuffers });
414
-
401
+ this.#engine.disableUniformBuffers = true;
415
402
  this.#scene = new Scene(this.#engine);
416
403
  this.#scene.clearColor = new Color4(1, 1, 1, 1);
417
- console.log("PrefViewer: Scene created, clearColor set to white");
418
-
419
404
  this.#createCamera();
420
405
  this.#createLights();
421
406
  this.#setupInteraction();
422
-
423
- this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
424
- console.log("PrefViewer: runRenderLoop started");
425
- this.#canvasResizeObserver.observe(this.#canvas);
426
- console.log("PrefViewer: ResizeObserver attached");
427
-
428
407
  await this.#createXRExperience();
429
- console.log("PrefViewer: #initializeBabylon() END");
408
+ this.#engine.runRenderLoop(this.#renderLoop);
409
+ this.#canvasResizeObserver.observe(this.#canvas);
430
410
  }
431
411
 
432
- addStylesToARButton() {
433
- console.log("PrefViewer: addStylesToARButton()");
412
+ // If this function is defined as '#renderLoop() {}' it is not executed in 'this.#engine.runRenderLoop(this.#renderLoop)'
413
+ #renderLoop = () => {
414
+ this.#scene && this.#scene.render();
415
+ };
416
+
417
+ #addStylesToARButton() {
434
418
  const css = '.babylonVRicon { color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%222048%22%20height%3D%221152%22%20viewBox%3D%220%200%202048%201152%22%20version%3D%221.1%22%3E%3Cpath%20transform%3D%22rotate%28180%201024%2C576.0000000000001%29%22%20d%3D%22m1109%2C896q17%2C0%2030%2C-12t13%2C-30t-12.5%2C-30.5t-30.5%2C-12.5l-170%2C0q-18%2C0%20-30.5%2C12.5t-12.5%2C30.5t13%2C30t30%2C12l170%2C0zm-85%2C256q59%2C0%20132.5%2C-1.5t154.5%2C-5.5t164.5%2C-11.5t163%2C-20t150%2C-30t124.5%2C-41.5q23%2C-11%2042%2C-24t38%2C-30q27%2C-25%2041%2C-61.5t14%2C-72.5l0%2C-257q0%2C-123%20-47%2C-232t-128%2C-190t-190%2C-128t-232%2C-47l-81%2C0q-37%2C0%20-68.5%2C14t-60.5%2C34.5t-55.5%2C45t-53%2C45t-53%2C34.5t-55.5%2C14t-55.5%2C-14t-53%2C-34.5t-53%2C-45t-55.5%2C-45t-60.5%2C-34.5t-68.5%2C-14l-81%2C0q-123%2C0%20-232%2C47t-190%2C128t-128%2C190t-47%2C232l0%2C257q0%2C68%2038%2C115t97%2C73q54%2C24%20124.5%2C41.5t150%2C30t163%2C20t164.5%2C11.5t154.5%2C5.5t132.5%2C1.5zm939%2C-298q0%2C39%20-24.5%2C67t-58.5%2C42q-54%2C23%20-122%2C39.5t-143.5%2C28t-155.5%2C19t-157%2C11t-148.5%2C5t-129.5%2C1.5q-59%2C0%20-130%2C-1.5t-148%2C-5t-157%2C-11t-155.5%2C-19t-143.5%2C-28t-122%2C-39.5q-34%2C-14%20-58.5%2C-42t-24.5%2C-67l0%2C-257q0%2C-106%2040.5%2C-199t110%2C-162.5t162.5%2C-109.5t199%2C-40l81%2C0q27%2C0%2052%2C14t50%2C34.5t51%2C44.5t55.5%2C44.5t63.5%2C34.5t74%2C14t74%2C-14t63.5%2C-34.5t55.5%2C-44.5t51%2C-44.5t50%2C-34.5t52%2C-14l14%2C0q37%2C0%2070%2C0.5t64.5%2C4.5t63.5%2C12t68%2C23q71%2C30%20128.5%2C78.5t98.5%2C110t63.5%2C133.5t22.5%2C149l0%2C257z%22%20fill%3D%22white%22%20/%3E%3C/svg%3E%0A); background-size: 80%; background-repeat:no-repeat; background-position: center; border: none; outline: none; transition: transform 0.125s ease-out } .babylonVRicon:hover { transform: scale(1.05) } .babylonVRicon:active {background-color: rgba(51,51,51,1) } .babylonVRicon:focus {background-color: rgba(51,51,51,1) }.babylonVRicon.vrdisplaypresenting { background-image: none;} .vrdisplaypresenting::after { content: "EXIT"} .xr-error::after { content: "ERROR"}';
435
419
  const style = document.createElement("style");
436
420
  style.appendChild(document.createTextNode(css));
@@ -438,15 +422,12 @@ class PrefViewer extends HTMLElement {
438
422
  }
439
423
 
440
424
  async #createXRExperience() {
441
- console.log("PrefViewer: #createXRExperience() START");
442
425
  if (this.#XRExperience) {
443
- console.log("PrefViewer: XR already exists, skipping.");
444
426
  return true;
445
427
  }
446
428
 
447
429
  const sessionMode = "immersive-ar";
448
430
  const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
449
- console.log("PrefViewer: WebXR session supported =", sessionSupported);
450
431
  if (!sessionSupported) {
451
432
  console.info("PrefViewer: WebXR in mode AR is not supported");
452
433
  return false;
@@ -467,7 +448,6 @@ class PrefViewer extends HTMLElement {
467
448
  };
468
449
 
469
450
  this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
470
- console.log("PrefViewer: XR experience created");
471
451
 
472
452
  const featuresManager = this.#XRExperience.baseExperience.featuresManager;
473
453
  featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
@@ -477,43 +457,39 @@ class PrefViewer extends HTMLElement {
477
457
  });
478
458
 
479
459
  this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
480
- console.log("PrefViewer: onXRReady - syncing camera pose");
481
460
  // Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
482
461
  this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
483
462
  this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
484
463
  this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
485
464
  });
486
465
 
487
- this.addStylesToARButton();
466
+ this.#addStylesToARButton();
488
467
  } catch (error) {
489
468
  console.warn("PrefViewer: failed to create WebXR experience", error);
490
469
  this.#XRExperience = null;
491
470
  }
492
- console.log("PrefViewer: #createXRExperience() END");
493
471
  }
494
472
 
495
473
  #canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
496
474
 
497
475
  #createCamera() {
498
- console.log("PrefViewer: #createCamera()");
499
476
  this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
500
477
  this.#camera.upperBetaLimit = Math.PI * 0.48;
501
478
  this.#camera.lowerBetaLimit = Math.PI * 0.25;
502
479
  this.#camera.lowerRadiusLimit = 5;
503
480
  this.#camera.upperRadiusLimit = 20;
504
- this.#camera.metadata = { locked: false }
481
+ this.#camera.metadata = { locked: false };
505
482
  this.#camera.attachControl(this.#canvas, true);
506
483
  this.#scene.activeCamera = this.#camera;
507
- console.log("PrefViewer: camera configured", {
508
- upperBetaLimit: this.#camera.upperBetaLimit,
509
- lowerBetaLimit: this.#camera.lowerBetaLimit,
510
- lowerRadiusLimit: this.#camera.lowerRadiusLimit,
511
- upperRadiusLimit: this.#camera.upperRadiusLimit
512
- });
513
484
  }
514
485
 
515
486
  #createLights() {
516
- console.log("PrefViewer: #createLights()");
487
+ this.#initEnvironmentTexture();
488
+
489
+ if (this.#scene.environmentTexture) {
490
+ return true;
491
+ }
492
+
517
493
  // 1) Stronger ambient fill
518
494
  this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
519
495
  this.#hemiLight.intensity = 0.6;
@@ -523,7 +499,7 @@ class PrefViewer extends HTMLElement {
523
499
  this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
524
500
  this.#dirLight.intensity = 0.6;
525
501
 
526
- // 3) Soft shadows
502
+ // // 3) Soft shadows
527
503
  this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
528
504
  this.#shadowGen.useBlurExponentialShadowMap = true;
529
505
  this.#shadowGen.blurKernel = 16;
@@ -533,21 +509,103 @@ class PrefViewer extends HTMLElement {
533
509
  this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
534
510
  this.#cameraLight.parent = this.#camera;
535
511
  this.#cameraLight.intensity = 0.3;
512
+ }
513
+
514
+ #initEnvironmentTexture() {
515
+ return false;
516
+ if (this.#scene.environmentTexture) {
517
+ return true;
518
+ }
519
+ const hdrTextureURI = "../src/environments/noon_grass.hdr";
520
+ const hdrTexture = new HDRCubeTexture(hdrTextureURI, this.#scene, 128);
521
+ hdrTexture.gammaSpace = true;
522
+ hdrTexture._noMipmap = false;
523
+ hdrTexture.level = 2.0;
524
+ this.#scene.environmentTexture = hdrTexture;
525
+ }
526
+
527
+ #initIBLShadows() {
528
+ if (!this.#scene.environmentTexture) {
529
+ return false;
530
+ }
531
+
532
+ let createIBLShadowPipeline = function (scene) {
533
+ const pipeline = new IblShadowsRenderPipeline(
534
+ "iblShadowsPipeline",
535
+ scene,
536
+ {
537
+ resolutionExp: 7,
538
+ sampleDirections: 2,
539
+ ssShadowsEnabled: true,
540
+ shadowRemanence: 0.8,
541
+ triPlanarVoxelization: true,
542
+ shadowOpacity: 0.8,
543
+ },
544
+ [scene.activeCamera]
545
+ );
546
+ pipeline.allowDebugPasses = false;
547
+ pipeline.gbufferDebugEnabled = true;
548
+ pipeline.importanceSamplingDebugEnabled = false;
549
+ pipeline.voxelDebugEnabled = false;
550
+ pipeline.voxelDebugDisplayMip = 1;
551
+ pipeline.voxelDebugAxis = 2;
552
+ pipeline.voxelTracingDebugEnabled = false;
553
+ pipeline.spatialBlurPassDebugEnabled = false;
554
+ pipeline.accumulationPassDebugEnabled = false;
555
+ return pipeline;
556
+ };
557
+
558
+ let iblShadowsPipeline = createIBLShadowPipeline(this.#scene);
559
+
560
+ this.#scene.meshes.forEach((mesh) => {
561
+ if (mesh.id.startsWith("__root__") || mesh.name === "hdri") {
562
+ return false;
563
+ }
564
+ iblShadowsPipeline.addShadowCastingMesh(mesh);
565
+ iblShadowsPipeline.updateSceneBounds();
566
+ });
567
+
568
+ this.#scene.materials.forEach((material) => {
569
+ iblShadowsPipeline.addShadowReceivingMaterial(material);
570
+ });
571
+ }
572
+
573
+ #initShadows() {
574
+ if (!this.#scene.environmentTexture) {
575
+ this.#initIBLShadows();
576
+ return true;
577
+ }
578
+
579
+ this.#scene.meshes.forEach((mesh) => {
580
+ if (mesh.id.startsWith("__root__")) {
581
+ return false;
582
+ }
583
+ mesh.receiveShadows = true;
584
+ if (!mesh.name === "hdri") {
585
+ this.#shadowGen.addShadowCaster(mesh, true);
586
+ }
587
+ });
588
+ }
536
589
 
537
- console.log("PrefViewer: lights created", {
538
- hemiIntensity: this.#hemiLight.intensity,
539
- dirIntensity: this.#dirLight.intensity,
540
- shadowMapSize: 1024,
541
- pointIntensity: this.#cameraLight.intensity
590
+ #setMaxSimultaneousLights() {
591
+ let lightsNumber = 1; // Como mínimo una luz correspondiente a la textura de environmentTexture
592
+ this.#scene.lights.forEach((light) => {
593
+ if (light.isEnabled()) {
594
+ ++lightsNumber;
595
+ }
542
596
  });
597
+ if (this.#scene.materials) {
598
+ this.#scene.materials.forEach((material) => (material.maxSimultaneousLights = lightsNumber));
599
+ }
543
600
  }
544
601
 
545
602
  #setupInteraction() {
546
- console.log("PrefViewer: #setupInteraction()");
547
603
  this.#canvas.addEventListener("wheel", (event) => {
548
604
  if (!this.#scene || !this.#camera) {
549
605
  return false;
550
606
  }
607
+ //const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
608
+ //this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
551
609
  if (!this.#scene.activeCamera.metadata?.locked) {
552
610
  this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
553
611
  }
@@ -556,10 +614,7 @@ class PrefViewer extends HTMLElement {
556
614
  }
557
615
 
558
616
  #disposeEngine() {
559
- console.log("PrefViewer: #disposeEngine()");
560
617
  if (!this.#engine) return;
561
- this.#shadowGen?.dispose();
562
- this.#scene?.lights?.slice().forEach(l => l.dispose());
563
618
  this.#engine.dispose();
564
619
  this.#engine = this.#scene = this.#camera = null;
565
620
  this.#hemiLight = this.#dirLight = this.#cameraLight = null;
@@ -568,7 +623,6 @@ class PrefViewer extends HTMLElement {
568
623
 
569
624
  // Utility methods for loading gltf/glb
570
625
  async #getServerFileDataHeader(uri) {
571
- console.log("PrefViewer: #getServerFileDataHeader()", uri);
572
626
  return new Promise((resolve) => {
573
627
  const xhr = new XMLHttpRequest();
574
628
  xhr.open("HEAD", uri, true);
@@ -577,15 +631,12 @@ class PrefViewer extends HTMLElement {
577
631
  if (xhr.status === 200) {
578
632
  const size = parseInt(xhr.getResponseHeader("Content-Length"));
579
633
  const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
580
- console.log("PrefViewer: HEAD ok", { size, timeStamp });
581
634
  resolve([size, timeStamp]);
582
635
  } else {
583
- console.warn("PrefViewer: HEAD failed", xhr.status);
584
636
  resolve([0, null]);
585
637
  }
586
638
  };
587
639
  xhr.onerror = () => {
588
- console.warn("PrefViewer: HEAD network error");
589
640
  resolve([0, null]);
590
641
  };
591
642
  xhr.send();
@@ -594,14 +645,11 @@ class PrefViewer extends HTMLElement {
594
645
 
595
646
  #transformUrl(url) {
596
647
  return new Promise((resolve) => {
597
- const transformed = url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/");
598
- console.log("PrefViewer: #transformUrl()", { in: url, out: transformed });
599
- resolve(transformed);
648
+ resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
600
649
  });
601
650
  }
602
651
 
603
652
  #decodeBase64(base64) {
604
- console.log("PrefViewer: #decodeBase64() START");
605
653
  const [, payload] = base64.split(",");
606
654
  const raw = payload || base64;
607
655
  let decoded = "";
@@ -611,7 +659,6 @@ class PrefViewer extends HTMLElement {
611
659
  try {
612
660
  decoded = atob(raw);
613
661
  } catch {
614
- console.warn("PrefViewer: base64 decode failed (not base64?)");
615
662
  return { blob, extension, size };
616
663
  }
617
664
  let isJson = false;
@@ -623,61 +670,44 @@ class PrefViewer extends HTMLElement {
623
670
  const type = isJson ? "model/gltf+json" : "model/gltf-binary";
624
671
  const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
625
672
  blob = new Blob([array], { type });
626
- console.log("PrefViewer: #decodeBase64() END", { extension, size, type });
627
673
  return { blob, extension, size };
628
674
  }
629
675
 
630
676
  async #initStorage(db, table) {
631
- console.log("PrefViewer: #initStorage()", { db, table });
632
677
  if (window.gltfDB && window.gltfDB.name === db && window.gltfDB.objectStoreNames.contains(table)) {
633
- console.log("PrefViewer: existing DB connection reused");
634
678
  return true;
635
679
  }
636
680
  await initDb(db, table);
637
- console.log("PrefViewer: DB initialized");
638
681
  }
639
682
 
640
683
  // Methods for managing Asset Containers
641
684
  #setVisibilityOfWallAndFloorInModel(show) {
642
- console.log("PrefViewer: #setVisibilityOfWallAndFloorInModel()", { incomingShow: show });
643
685
  if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
644
- console.log("PrefViewer: no model assetContainer or not visible, skip visibility set");
645
686
  return false;
646
687
  }
647
688
  show = show !== undefined ? show : this.#data.containers.environment.visible;
648
689
  const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
649
- console.log("PrefViewer: toggling meshes with prefixes", prefixes, "->", show);
650
690
  this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
651
691
  }
652
692
 
653
693
  #setOptionsMaterial(optionMaterial) {
654
- console.log("PrefViewer: #setOptionsMaterial()", { optionMaterial });
655
694
  if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
656
- console.log("PrefViewer: optionMaterial incomplete, skipping");
657
695
  return false;
658
696
  }
659
697
 
660
- const material = this.#data.containers.materials.assetContainer?.materials
661
- .find((mat) => mat.name === optionMaterial.value) || null;
662
-
698
+ const material = this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) || null;
663
699
  if (!material) {
664
- console.warn("PrefViewer: material not found", optionMaterial.value);
665
700
  return false;
666
701
  }
667
702
 
668
- const hadExplicitChange = !!optionMaterial.changed.pending;
669
-
670
703
  const containers = [];
671
- if (this.#data.containers.model.assetContainer &&
672
- (this.#data.containers.model.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
704
+ if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
673
705
  containers.push(this.#data.containers.model.assetContainer);
674
706
  }
675
- if (this.#data.containers.environment.assetContainer &&
676
- (this.#data.containers.environment.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
707
+ if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
677
708
  containers.push(this.#data.containers.environment.assetContainer);
678
709
  }
679
710
  if (containers.length === 0) {
680
- console.log("PrefViewer: no containers require material update");
681
711
  return false;
682
712
  }
683
713
 
@@ -691,156 +721,97 @@ class PrefViewer extends HTMLElement {
691
721
  })
692
722
  );
693
723
 
694
- console.log("PrefViewer: material assignment result", {
695
- prefix: optionMaterial.prefix, material: optionMaterial.value, someSetted
696
- });
697
-
698
724
  if (someSetted) {
699
725
  optionMaterial.changed.success = true;
700
726
  optionMaterial.changed.pending = false;
701
- } else if (hadExplicitChange) {
702
- // Solo revertimos si el usuario pidió el cambio y falló
727
+ } else if (optionMaterial.changed.pending) {
703
728
  optionMaterial.value = optionMaterial.changed.oldValue;
729
+ optionMaterial.changed.success = false;
730
+ optionMaterial.changed.pending = false;
704
731
  }
705
732
 
706
733
  return someSetted;
707
734
  }
708
735
 
709
736
  #setOptionsMaterials() {
710
- console.log("PrefViewer: #setOptionsMaterials() START");
711
737
  let someSetted = false;
712
738
  Object.values(this.#data.options.materials).forEach((material) => {
713
739
  let settedMaterial = this.#setOptionsMaterial(material);
714
740
  someSetted = someSetted || settedMaterial;
715
741
  });
716
- console.log("PrefViewer: #setOptionsMaterials() END", { someSetted });
717
742
  return someSetted;
718
743
  }
719
744
 
720
745
  #setOptionsCamera() {
721
- console.log("PrefViewer: #setOptionsCamera()", {
722
- requested: this.#data.options.camera.value,
723
- changed: this.#data.options.camera.changed,
724
- modelChanged: this.#data.containers.model.changed,
725
- envChanged: this.#data.containers.environment.changed
726
- });
727
-
728
- const camState = this.#data.options.camera;
729
-
730
- if (!camState.value && !camState.changed.pending && !this.#data.containers.model.changed.pending && !this.#data.containers.environment.changed.pending) {
746
+ if (
747
+ !this.#data.options.camera.value &&
748
+ !this.#data.options.camera.changed.pending &&
749
+ !this.#data.containers.model.changed.pending &&
750
+ !this.#data.containers.environment.changed.pending
751
+ ) {
731
752
  return false;
732
753
  }
733
754
 
734
- const hadExplicitChange = !!camState.changed.pending;
735
-
736
- let camera =
737
- this.#data.containers.model.assetContainer?.cameras.find(c => c.name === camState.value) ||
738
- this.#data.containers.environment.assetContainer?.cameras.find(c => c.name === camState.value) ||
739
- null;
740
-
755
+ let camera = this.#data.containers.model.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.value) || this.#data.containers.environment.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.value) || null;
741
756
  if (!camera) {
742
- // Si falló la cámara solicitada y hubo cambio explícito, intentamos volver a la anterior
743
- if (hadExplicitChange && camState.changed.oldValue && camState.changed.oldValue !== camState.value) {
744
- camera =
745
- this.#data.containers.model.assetContainer?.cameras.find(c => c.name === camState.changed.oldValue) ||
746
- this.#data.containers.environment.assetContainer?.cameras.find(c => c.name === camState.changed.oldValue) ||
747
- null;
748
-
749
- if (camera) {
750
- camera.metadata = { locked: camState.changed.oldLocked };
751
- camState.value = camState.changed.oldValue;
752
- camState.locked = camState.changed.oldLocked;
753
- camState.changed.success = false;
754
- camState.changed.pending = false;
755
- } else {
756
- // Fallback a la cámara por defecto del componente
757
- camera = this.#camera;
758
- camState.value = null;
759
- camState.locked = this.#camera.metadata.locked;
760
- camState.changed.success = false;
761
- camState.changed.pending = false;
762
- }
757
+ if (this.#data.options.camera.changed?.oldValue && this.#data.options.camera.changed?.oldValue !== this.#data.options.camera.value) {
758
+ camera = this.#data.containers.model.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.changed.oldValue) || this.#data.containers.environment.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.changed.oldValue) || null;
759
+ }
760
+ if (camera) {
761
+ camera.metadata = { locked: this.#data.options.camera.changed.oldLocked };
762
+ this.#data.options.camera.value = this.#data.options.camera.changed.oldValue;
763
+ this.#data.options.camera.locked = this.#data.options.camera.changed.oldLocked;
764
+ this.#data.options.camera.changed.success = false;
765
+ this.#data.options.camera.changed.pending = false;
763
766
  } else {
764
- // No hubo cambio explícito: usa fallback sin tocar changed
765
767
  camera = this.#camera;
766
- camState.value = null;
767
- camState.locked = this.#camera.metadata.locked;
768
+ this.#data.options.camera.value = null;
769
+ this.#data.options.camera.locked = this.#camera.metadata.locked;
770
+ this.#data.options.camera.changed.success = false;
771
+ this.#data.options.camera.changed.pending = false;
768
772
  }
769
773
  } else {
770
- camera.metadata = { locked: camState.locked };
771
- // Marca success si hubo cambio explícito
772
- if (hadExplicitChange) {
773
- camState.changed.success = true;
774
- camState.changed.pending = false;
774
+ camera.metadata = { locked: this.#data.options.camera.locked };
775
+ if (this.#data.options.camera.changed.pending) {
776
+ this.#data.options.camera.changed.success = true;
777
+ this.#data.options.camera.changed.pending = false;
775
778
  }
776
779
  }
777
-
778
- if (!camState.locked && camState.value !== null) {
780
+ if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
779
781
  camera.attachControl(this.#canvas, true);
780
782
  }
781
783
  this.#scene.activeCamera = camera;
782
- console.log("PrefViewer: active camera set", { name: camera.name, locked: camera.metadata?.locked });
783
784
  return true;
784
785
  }
785
786
 
786
787
  #addContainer(container) {
787
- console.log("PrefViewer: #addContainer()", { name: container.name, hasContainer: !!container.assetContainer, visible: container.visible, show: container.show });
788
788
  if (container.assetContainer && !container.visible && container.show) {
789
789
  container.assetContainer.addAllToScene();
790
790
  container.visible = true;
791
- console.log("PrefViewer: container added to scene", container.name);
792
791
  }
793
792
  }
794
793
 
795
794
  #removeContainer(container) {
796
- console.log("PrefViewer: #removeContainer()", { name: container.name, hasContainer: !!container.assetContainer, visible: container.visible });
797
795
  if (container.assetContainer && container.visible) {
798
796
  container.assetContainer.removeAllFromScene();
799
797
  container.visible = false;
800
- console.log("PrefViewer: container removed from scene", container.name);
801
798
  }
802
799
  }
803
800
 
804
801
  #replaceContainer(container, newAssetContainer) {
805
- console.log("PrefViewer: #replaceContainer()", { name: container.name, hadContainer: !!container.assetContainer });
806
- // 1) quita y destruye el anterior si existía
807
- const old = container.assetContainer;
808
- if (old) {
809
- if (container.visible) { old.removeAllFromScene(); }
810
- old.dispose(); // <- importante
811
- console.log("PrefViewer: old container disposed", container.name);
802
+ if (container.assetContainer) {
803
+ container.assetContainer.dispose();
804
+ container.assetContainer = null;
812
805
  }
813
-
814
- // 2) asigna el nuevo y prepara
806
+ this.#scene.getEngine().releaseEffects();
815
807
  container.assetContainer = newAssetContainer;
816
-
817
- // Opcional: limitar luces por material para ganar margen
818
- container.assetContainer.materials?.forEach(m => {
819
- if ("maxSimultaneousLights" in m) {
820
- m.maxSimultaneousLights = 2; // 2–3 suele ir bien
821
- }
822
- });
823
-
824
- // 3) sombras solo para los meshes que te interesen (mejor que todos)
825
- container.assetContainer.meshes.forEach(mesh => {
826
- mesh.receiveShadows = true;
827
- this.#shadowGen.addShadowCaster(mesh, true);
828
- });
829
-
830
- // 4) añade a escena
831
808
  this.#addContainer(container);
832
-
833
- // 5) fuerza recompilación con defines correctos del nuevo estado
834
- this.#scene.getEngine().releaseEffects();
835
- console.log("PrefViewer: container replaced and effects released", container.name);
836
809
  }
837
810
 
838
811
  async #loadAssetContainer(container) {
839
- console.log("PrefViewer: #loadAssetContainer() START", { name: container?.name, storage: container?.storage });
840
812
  let storage = container?.storage;
841
813
 
842
814
  if (!storage) {
843
- console.log("PrefViewer: no storage, skipping", container?.name);
844
815
  return false;
845
816
  }
846
817
 
@@ -851,22 +822,15 @@ class PrefViewer extends HTMLElement {
851
822
  const object = await loadModel(storage.id, storage.table);
852
823
  source = object.data;
853
824
  if (object.timeStamp === container.timeStamp) {
854
- console.log("PrefViewer: DB entry unchanged, skipping", container.name);
825
+ container.changed.pending = false;
826
+ container.changed.success = false;
855
827
  return false;
856
828
  } else {
857
- container.changed = {
858
- ...container.changed,
859
- pending: true,
860
- success: false,
861
- timeStamp: object.timeStamp,
862
- size: object.size,
863
- };
864
- console.log("PrefViewer: DB entry changed", container.changed);
829
+ Object.assign(container.changed, { timeStamp: object.timeStamp, size: object.size, success: false, pending: true });
865
830
  }
866
831
  }
867
832
 
868
833
  if (!source) {
869
- console.log("PrefViewer: no source after storage resolution", container.name);
870
834
  return false;
871
835
  }
872
836
 
@@ -879,38 +843,27 @@ class PrefViewer extends HTMLElement {
879
843
  });
880
844
  if (!container.changed.pending) {
881
845
  if (container.timeStamp === null && container.size === size) {
882
- console.log("PrefViewer: same base64 size and null timestamp, skipping", container.name);
846
+ container.changed.pending = false;
847
+ container.changed.success = false;
883
848
  return false;
884
849
  } else {
885
- container.changed = {
886
- ...container.changed,
887
- pending: true,
888
- success: false,
889
- timeStamp: null,
890
- size: size,
891
- };
850
+ Object.assign(container.changed, { timeStamp: null, size: size, success: false, pending: true });
892
851
  }
893
852
  }
894
- console.log("PrefViewer: prepared File from base64", { name: file.name, size: file.size, type: file.type });
895
853
  } else {
896
854
  const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
897
855
  extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
898
856
  const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
899
857
  if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
900
- console.log("PrefViewer: remote file unchanged, skipping", container.name);
858
+ container.changed.pending = false;
859
+ container.changed.success = false;
901
860
  return false;
902
861
  } else {
903
- container.changed = {
904
- ...container.changed,
905
- pending: true,
906
- success: false,
907
- timeStamp: fileTimeStamp,
908
- size: fileSize,
909
- };
910
- console.log("PrefViewer: remote file changed", container.changed);
862
+ Object.assign(container.changed, { timeStamp: fileTimeStamp, size: fileSize, success: false, pending: true });
911
863
  }
912
864
  }
913
865
 
866
+ // https://doc.babylonjs.com/typedoc/interfaces/BABYLON.LoadAssetContainerOptions
914
867
  let options = {
915
868
  pluginExtension: extension,
916
869
  pluginOptions: {
@@ -921,58 +874,56 @@ class PrefViewer extends HTMLElement {
921
874
  },
922
875
  };
923
876
 
924
- console.log("PrefViewer: calling LoadAssetContainerAsync()", { extension });
925
877
  return LoadAssetContainerAsync(file || source, this.#scene, options);
926
878
  }
927
879
 
928
880
  async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
929
- console.log("PrefViewer: #loadContainers()", { loadModel, loadEnvironment, loadMaterials });
881
+ this.#setStatusSceneLoading();
882
+ this.#engine.stopRenderLoop(this.#renderLoop);
883
+
930
884
  const promiseArray = [];
931
885
  promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
932
886
  promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
933
887
  promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
934
888
 
935
- this.#setStatusSceneLoading();
936
-
937
889
  Promise.allSettled(promiseArray)
938
890
  .then(async (values) => {
939
- console.log("PrefViewer: Promise.allSettled results", values);
940
891
  const modelContainer = values[0];
941
892
  const environmentContainer = values[1];
942
893
  const materialsContainer = values[2];
943
894
 
895
+ this.#removeContainer(this.#data.containers.model);
896
+ this.#removeContainer(this.#data.containers.environment);
897
+ this.#removeContainer(this.#data.containers.materials);
898
+
944
899
  if (modelContainer.status === "fulfilled" && modelContainer.value) {
945
- console.log("PrefViewer: model container fulfilled, applying");
946
- this.#stripImportedLights(modelContainer.value);
900
+ modelContainer.value.lights = [];
947
901
  this.#replaceContainer(this.#data.containers.model, modelContainer.value);
948
- this.#storeChangedFlagsForContainer(this.#data.containers.model);
902
+ this.#storeChangedFlagsForContainer(this.#data.containers.model, true);
949
903
  } else {
950
- console.log("PrefViewer: model container not fulfilled or unchanged; ensuring visibility state");
951
- this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
904
+ this.#addContainer(this.#data.containers.model);
905
+ this.#storeChangedFlagsForContainer(this.#data.containers.model, false);
952
906
  }
953
907
 
954
908
  if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
955
- console.log("PrefViewer: environment container fulfilled, applying");
956
- this.#stripImportedLights(environmentContainer.value);
957
909
  this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
958
- this.#storeChangedFlagsForContainer(this.#data.containers.environment);
910
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment, true);
959
911
  } else {
960
- console.log("PrefViewer: environment container not fulfilled or unchanged; ensuring visibility state");
961
- this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
912
+ this.#addContainer(this.#data.containers.environment);
913
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment, false);
962
914
  }
963
915
 
964
916
  if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
965
- console.log("PrefViewer: materials container fulfilled, applying");
966
- this.#stripImportedLights(materialsContainer.value);
967
917
  this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
968
- this.#storeChangedFlagsForContainer(this.#data.containers.materials);
918
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials, true);
919
+ } else {
920
+ this.#addContainer(this.#data.containers.materials);
921
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials, false);
969
922
  }
970
923
 
971
924
  this.#setOptionsMaterials();
972
925
  this.#setOptionsCamera();
973
926
  this.#setVisibilityOfWallAndFloorInModel();
974
- this.#setStatusSceneLoaded();
975
- this.#resetChangedFlags();
976
927
  })
977
928
  .catch((error) => {
978
929
  this.loaded = true;
@@ -985,34 +936,43 @@ class PrefViewer extends HTMLElement {
985
936
  detail: { error: error },
986
937
  })
987
938
  );
939
+ })
940
+ .finally(() => {
941
+ this.#setMaxSimultaneousLights();
942
+ this.#initShadows();
943
+ this.#setStatusSceneLoaded();
944
+ this.#engine.runRenderLoop(this.#renderLoop);
988
945
  });
989
946
  }
990
947
 
991
- #stripImportedLights(container) {
992
- console.log("PrefViewer: #stripImportedLights()", { lights: container?.lights?.length || 0 });
993
- // El glTF puede traer KHR_lights_punctual: bórralas antes de añadir a la escena
994
- if (container?.lights?.length) {
995
- // Clonar para no mutar mientras iteras
996
- container.lights.slice().forEach(l => l.dispose());
997
- console.log("PrefViewer: stripped punctual lights from imported asset");
998
- }
999
- }
1000
-
1001
948
  // Public Methods
1002
949
  loadConfig(config) {
1003
- console.log("PrefViewer: loadConfig()", config);
1004
950
  config = typeof config === "string" ? JSON.parse(config) : config;
1005
951
  if (!config) {
1006
- console.warn("PrefViewer: loadConfig() no config provided");
1007
952
  return false;
1008
953
  }
1009
954
 
1010
955
  // Containers
1011
- this.#data.containers.model.storage = config.model?.storage || null;
956
+ const loadModel = !!config.model?.storage;
957
+ // CHANGED: marcar pending, no boolean
958
+ this.#data.containers.model.changed.pending = loadModel;
959
+ this.#data.containers.model.changed.success = false;
960
+ this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
961
+ this.#data.containers.model.storage = loadModel ? config.model.storage : this.#data.containers.model.storage;
1012
962
  this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
1013
- this.#data.containers.environment.storage = config.scene?.storage || null;
963
+
964
+ const loadEnvironment = !!config.scene?.storage;
965
+ this.#data.containers.environment.changed.pending = loadEnvironment;
966
+ this.#data.containers.environment.changed.success = false;
967
+ this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
968
+ this.#data.containers.environment.storage = loadEnvironment ? config.scene.storage : this.#data.containers.environment.storage;
1014
969
  this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
1015
- this.#data.containers.materials.storage = config.materials?.storage || null;
970
+
971
+ const loadMaterials = !!config.materials?.storage;
972
+ this.#data.containers.materials.changed.pending = loadMaterials;
973
+ this.#data.containers.materials.changed.success = false;
974
+ this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
975
+ this.#data.containers.materials.storage = loadMaterials ? config.materials.storage : this.#data.containers.materials.storage;
1016
976
 
1017
977
  // Options
1018
978
  if (config.options) {
@@ -1020,11 +980,10 @@ class PrefViewer extends HTMLElement {
1020
980
  this.#checkMaterialsChanged(config.options);
1021
981
  }
1022
982
 
1023
- this.initialized && this.#loadContainers(true, true, true);
983
+ this.initialized && this.#loadContainers(loadModel, loadEnvironment, loadMaterials);
1024
984
  }
1025
985
 
1026
986
  setOptions(options) {
1027
- console.log("PrefViewer: setOptions()", options);
1028
987
  if (!options) {
1029
988
  return false;
1030
989
  }
@@ -1040,69 +999,79 @@ class PrefViewer extends HTMLElement {
1040
999
  }
1041
1000
 
1042
1001
  this.#setStatusOptionsLoaded();
1043
- this.#resetChangedFlags();
1044
1002
 
1045
1003
  return someSetted;
1046
1004
  }
1047
1005
 
1048
1006
  loadModel(model) {
1049
- console.log("PrefViewer: loadModel()", model);
1050
1007
  model = typeof model === "string" ? JSON.parse(model) : model;
1051
1008
  if (!model) {
1052
- console.warn("PrefViewer: loadModel() no model provided");
1053
1009
  return false;
1054
1010
  }
1055
- this.#data.containers.model.storage = model.storage || null;
1011
+ const loadModel = !!model.storage;
1012
+ this.#data.containers.model.changed.pending = loadModel;
1013
+ this.#data.containers.model.changed.success = false;
1014
+ this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
1015
+ this.#data.containers.model.storage = loadModel ? model.storage : this.#data.containers.model.storage;
1056
1016
  this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
1057
- this.initialized && this.#loadContainers(true, false, false);
1017
+ this.initialized && this.#loadContainers(loadModel, false, false);
1058
1018
  }
1059
1019
 
1060
1020
  loadScene(scene) {
1061
- console.log("PrefViewer: loadScene()", scene);
1062
1021
  scene = typeof scene === "string" ? JSON.parse(scene) : scene;
1063
1022
  if (!scene) {
1064
- console.warn("PrefViewer: loadScene() no scene provided");
1065
1023
  return false;
1066
1024
  }
1067
- this.#data.containers.environment.storage = scene.storage || null;
1025
+ const loadEnvironment = !!scene.storage;
1026
+ this.#data.containers.environment.changed.pending = loadEnvironment;
1027
+ this.#data.containers.environment.changed.success = false;
1028
+ this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
1029
+ this.#data.containers.environment.storage = loadEnvironment ? scene.storage : this.#data.containers.environment.storage;
1068
1030
  this.#data.containers.environment.show = scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
1069
- this.initialized && this.#loadContainers(false, true, false);
1031
+ this.initialized && this.#loadContainers(false, loadEnvironment, false);
1032
+ }
1033
+
1034
+ loadMaterials(materials) {
1035
+ materials = typeof materials === "string" ? JSON.parse(materials) : materials;
1036
+ if (!materials) {
1037
+ return false;
1038
+ }
1039
+ const loadMaterials = !!materials.storage;
1040
+ this.#data.containers.materials.changed.pending = loadMaterials;
1041
+ this.#data.containers.materials.changed.success = false;
1042
+ this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
1043
+ this.#data.containers.materials.storage = loadMaterials ? materials.storage : this.#data.containers.materials.storage;
1044
+ this.initialized && this.#loadContainers(false, false, loadMaterials);
1070
1045
  }
1071
1046
 
1072
1047
  showModel() {
1073
- console.log("PrefViewer: showModel()");
1074
1048
  this.#data.containers.model.show = true;
1075
1049
  this.#addContainer(this.#data.containers.model);
1076
1050
  }
1077
1051
 
1078
1052
  hideModel() {
1079
- console.log("PrefViewer: hideModel()");
1080
1053
  this.#data.containers.model.show = false;
1081
1054
  this.#removeContainer(this.#data.containers.model);
1082
1055
  }
1083
1056
 
1084
1057
  showScene() {
1085
- console.log("PrefViewer: showScene()");
1086
1058
  this.#data.containers.environment.show = true;
1087
1059
  this.#addContainer(this.#data.containers.environment);
1088
1060
  this.#setVisibilityOfWallAndFloorInModel();
1089
1061
  }
1090
1062
 
1091
1063
  hideScene() {
1092
- console.log("PrefViewer: hideScene()");
1093
1064
  this.#data.containers.environment.show = false;
1094
1065
  this.#removeContainer(this.#data.containers.environment);
1095
1066
  this.#setVisibilityOfWallAndFloorInModel();
1096
1067
  }
1097
1068
 
1098
1069
  downloadModelGLB() {
1099
- console.log("PrefViewer: downloadModelGLB()");
1100
1070
  const fileName = "model";
1101
1071
  GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
1102
1072
  }
1103
1073
 
1104
1074
  downloadModelUSDZ() {
1105
- console.log("PrefViewer: downloadModelUSDZ()");
1106
1075
  const fileName = "model";
1107
1076
  USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
1108
1077
  if (response) {
@@ -1112,7 +1081,6 @@ class PrefViewer extends HTMLElement {
1112
1081
  }
1113
1082
 
1114
1083
  downloadModelAndSceneUSDZ() {
1115
- console.log("PrefViewer: downloadModelAndSceneUSDZ()");
1116
1084
  const fileName = "scene";
1117
1085
  USDZExportAsync(this.#scene).then((response) => {
1118
1086
  if (response) {
@@ -1122,7 +1090,6 @@ class PrefViewer extends HTMLElement {
1122
1090
  }
1123
1091
 
1124
1092
  downloadModelAndSceneGLB() {
1125
- console.log("PrefViewer: downloadModelAndSceneGLB()");
1126
1093
  const fileName = "scene";
1127
1094
  GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
1128
1095
  }