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

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 +254 -337
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.26",
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";
@@ -61,7 +61,7 @@ class PrefViewer extends HTMLElement {
61
61
  visible: false,
62
62
  size: null,
63
63
  timeStamp: null,
64
- changed: { pending: false, success: false },
64
+ changed: false,
65
65
  },
66
66
  environment: {
67
67
  name: "environment",
@@ -71,7 +71,7 @@ class PrefViewer extends HTMLElement {
71
71
  visible: false,
72
72
  size: null,
73
73
  timeStamp: null,
74
- changed: { pending: false, success: false },
74
+ changed: false,
75
75
  },
76
76
  materials: {
77
77
  name: "materials",
@@ -81,35 +81,35 @@ class PrefViewer extends HTMLElement {
81
81
  visible: false,
82
82
  size: null,
83
83
  timeStamp: null,
84
- changed: { pending: false, success: false },
84
+ changed: false,
85
85
  },
86
86
  },
87
87
  options: {
88
88
  camera: {
89
89
  value: null,
90
90
  locked: true,
91
- changed: { pending: false, success: false },
91
+ changed: false,
92
92
  },
93
93
  materials: {
94
94
  innerWall: {
95
95
  value: null,
96
96
  prefix: "innerWall",
97
- changed: { pending: false, success: false },
97
+ changed: false,
98
98
  },
99
99
  outerWall: {
100
100
  value: null,
101
101
  prefix: "outerWall",
102
- changed: { pending: false, success: false },
102
+ changed: false,
103
103
  },
104
104
  innerFloor: {
105
105
  value: null,
106
106
  prefix: "innerFloor",
107
- changed: { pending: false, success: false },
107
+ changed: false,
108
108
  },
109
109
  outerFloor: {
110
110
  value: null,
111
111
  prefix: "outerFloor",
112
- changed: { pending: false, success: false },
112
+ changed: false,
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,29 +245,28 @@ 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
 
263
251
  const toLoadDetail = {
264
- container_model: !!this.#data.containers.model.changed.pending,
265
- container_environment: !!this.#data.containers.environment.changed.pending,
266
- container_materials: !!this.#data.containers.materials.changed.pending,
267
- options_camera: !!this.#data.options.camera.changed.pending,
268
- options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.pending,
269
- options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.pending,
270
- options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.pending,
271
- options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.pending,
252
+ container_model: !!this.#data.containers.model.changed,
253
+ container_environment: !!this.#data.containers.environment.changed,
254
+ container_materials: !!this.#data.containers.materials.changed,
255
+ options_camera: !!this.#data.options.camera.changed,
256
+ options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed,
257
+ options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
258
+ options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
259
+ options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
272
260
  };
273
261
  const loadedDetail = {
274
- container_model: !!this.#data.containers.model.changed.success,
275
- container_environment: !!this.#data.containers.environment.changed.success,
276
- container_materials: !!this.#data.containers.materials.changed.success,
277
- options_camera: !!this.#data.options.camera.changed.success,
278
- options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.success,
279
- options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.success,
280
- options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.success,
281
- options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.success,
262
+ container_model: !!this.#data.containers.model.changed?.success,
263
+ container_environment: !!this.#data.containers.environment.changed?.success,
264
+ container_materials: !!this.#data.containers.materials.changed?.success,
265
+ options_camera: !!this.#data.options.camera.changed?.success,
266
+ options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
267
+ options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
268
+ options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
269
+ options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
282
270
  };
283
271
 
284
272
  const detail = {
@@ -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,18 +300,19 @@ class PrefViewer extends HTMLElement {
313
300
  }
314
301
 
315
302
  #setStatusOptionsLoaded() {
316
- console.log("PrefViewer: #setStatusOptionsLoaded()");
317
303
  const toLoadDetail = {
318
- innerWallMaterial: !!this.#data.options.materials.innerWall.changed.pending,
319
- outerWallMaterial: !!this.#data.options.materials.outerWall.changed.pending,
320
- innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.pending,
321
- outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.pending,
304
+ camera: !!this.#data.options.camera.changed,
305
+ innerWallMaterial: !!this.#data.options.materials.innerWall.changed,
306
+ outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
307
+ innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
308
+ outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
322
309
  };
323
310
  const loadedDetail = {
324
- innerWallMaterial: !!this.#data.options.materials.innerWall.changed.success,
325
- outerWallMaterial: !!this.#data.options.materials.outerWall.changed.success,
326
- innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.success,
327
- outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.success,
311
+ camera: !!this.#data.options.camera.changed?.success,
312
+ innerWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
313
+ outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
314
+ innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
315
+ outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
328
316
  };
329
317
 
330
318
  const detail = {
@@ -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,18 +336,10 @@ 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
-
362
- return changed;
339
+ const cameraChanged = options.camera && options.camera !== this.#data.options.camera.value ? true : false;
340
+ this.#data.options.camera.changed = cameraChanged ? { oldValue: this.#data.options.camera.value, success: false } : false;
341
+ this.#data.options.camera.value = cameraChanged ? options.camera : this.#data.options.camera.value;
342
+ return cameraChanged;
363
343
  }
364
344
 
365
345
  #checkMaterialsChanged(options) {
@@ -369,68 +349,53 @@ class PrefViewer extends HTMLElement {
369
349
  let someChanged = false;
370
350
  Object.keys(this.#data.options.materials).forEach((material) => {
371
351
  const key = `${material}Material`;
372
- const materialState = this.#data.options.materials[material];
373
- const prev = materialState.value;
374
- const incoming = options[key];
375
- 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;
384
- someChanged = someChanged || materialChanged;
352
+ const materialChanged = options[key] && options[key] !== this.#data.options.materials[material].value ? true : false;
353
+ this.#data.options.materials[material].changed = materialChanged ? { oldValue: this.#data.options.materials[material].value, success: false } : false;
354
+ this.#data.options.materials[material].value = materialChanged ? options[key] : this.#data.options.materials[material].value;
355
+ someChanged = someChanged || this.#data.options.materials[material].changed;
385
356
  });
386
357
  return someChanged;
387
358
  }
388
359
 
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;
360
+ #storeChangedFlagsForContainer(container, success) {
361
+ if (success) {
362
+ container.timeStamp = container.changed.timeStamp;
363
+ container.size = container.changed.size;
364
+ container.changed.success = success;
365
+ } else if (container.changed) {
366
+ container.source = container.changed.source;
367
+ container.changed = false;
368
+ } else {
369
+ container.changed = false;
370
+ }
395
371
  }
396
372
 
397
373
  #resetChangedFlags() {
398
- console.log("PrefViewer: #resetChangedFlags()");
399
- const reset = (node) => {
400
- node.changed.success = false;
401
- node.changed.pending = false;
402
- };
403
- Object.values(this.#data.containers).forEach(reset);
404
- Object.values(this.#data.options.materials).forEach(reset);
405
- reset(this.#data.options.camera);
374
+ Object.values(this.#data.containers).forEach((container) => (container.changed = false));
375
+ Object.values(this.#data.options.materials).forEach((material) => (material.changed = false));
376
+ this.#data.options.camera.changed = false;
406
377
  }
407
378
 
408
379
  // Babylon.js
409
380
  async #initializeBabylon() {
410
- console.log("PrefViewer: #initializeBabylon() START");
411
381
  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
-
382
+ this.#engine.disableUniformBuffers = true;
415
383
  this.#scene = new Scene(this.#engine);
416
384
  this.#scene.clearColor = new Color4(1, 1, 1, 1);
417
- console.log("PrefViewer: Scene created, clearColor set to white");
418
-
419
385
  this.#createCamera();
420
386
  this.#createLights();
421
387
  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
388
  await this.#createXRExperience();
429
- console.log("PrefViewer: #initializeBabylon() END");
389
+ this.#engine.runRenderLoop(this.#renderLoop);
390
+ this.#canvasResizeObserver.observe(this.#canvas);
430
391
  }
431
392
 
432
- addStylesToARButton() {
433
- console.log("PrefViewer: addStylesToARButton()");
393
+ // If this function is defined as '#renderLoop() {}' it is not executed in 'this.#engine.runRenderLoop(this.#renderLoop)'
394
+ #renderLoop = () => {
395
+ this.#scene && this.#scene.render();
396
+ };
397
+
398
+ #addStylesToARButton() {
434
399
  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
400
  const style = document.createElement("style");
436
401
  style.appendChild(document.createTextNode(css));
@@ -438,15 +403,12 @@ class PrefViewer extends HTMLElement {
438
403
  }
439
404
 
440
405
  async #createXRExperience() {
441
- console.log("PrefViewer: #createXRExperience() START");
442
406
  if (this.#XRExperience) {
443
- console.log("PrefViewer: XR already exists, skipping.");
444
407
  return true;
445
408
  }
446
409
 
447
410
  const sessionMode = "immersive-ar";
448
411
  const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
449
- console.log("PrefViewer: WebXR session supported =", sessionSupported);
450
412
  if (!sessionSupported) {
451
413
  console.info("PrefViewer: WebXR in mode AR is not supported");
452
414
  return false;
@@ -467,7 +429,6 @@ class PrefViewer extends HTMLElement {
467
429
  };
468
430
 
469
431
  this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
470
- console.log("PrefViewer: XR experience created");
471
432
 
472
433
  const featuresManager = this.#XRExperience.baseExperience.featuresManager;
473
434
  featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
@@ -477,43 +438,39 @@ class PrefViewer extends HTMLElement {
477
438
  });
478
439
 
479
440
  this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
480
- console.log("PrefViewer: onXRReady - syncing camera pose");
481
441
  // Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
482
442
  this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
483
443
  this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
484
444
  this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
485
445
  });
486
446
 
487
- this.addStylesToARButton();
447
+ this.#addStylesToARButton();
488
448
  } catch (error) {
489
449
  console.warn("PrefViewer: failed to create WebXR experience", error);
490
450
  this.#XRExperience = null;
491
451
  }
492
- console.log("PrefViewer: #createXRExperience() END");
493
452
  }
494
453
 
495
454
  #canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
496
455
 
497
456
  #createCamera() {
498
- console.log("PrefViewer: #createCamera()");
499
457
  this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
500
458
  this.#camera.upperBetaLimit = Math.PI * 0.48;
501
459
  this.#camera.lowerBetaLimit = Math.PI * 0.25;
502
460
  this.#camera.lowerRadiusLimit = 5;
503
461
  this.#camera.upperRadiusLimit = 20;
504
- this.#camera.metadata = { locked: false }
462
+ this.#camera.metadata = { locked: false };
505
463
  this.#camera.attachControl(this.#canvas, true);
506
464
  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
465
  }
514
466
 
515
467
  #createLights() {
516
- console.log("PrefViewer: #createLights()");
468
+ this.#initEnvironmentTexture();
469
+
470
+ if (this.#scene.environmentTexture) {
471
+ return true;
472
+ }
473
+
517
474
  // 1) Stronger ambient fill
518
475
  this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
519
476
  this.#hemiLight.intensity = 0.6;
@@ -523,7 +480,7 @@ class PrefViewer extends HTMLElement {
523
480
  this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
524
481
  this.#dirLight.intensity = 0.6;
525
482
 
526
- // 3) Soft shadows
483
+ // // 3) Soft shadows
527
484
  this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
528
485
  this.#shadowGen.useBlurExponentialShadowMap = true;
529
486
  this.#shadowGen.blurKernel = 16;
@@ -533,21 +490,103 @@ class PrefViewer extends HTMLElement {
533
490
  this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
534
491
  this.#cameraLight.parent = this.#camera;
535
492
  this.#cameraLight.intensity = 0.3;
493
+ }
494
+
495
+ #initEnvironmentTexture() {
496
+ return false;
497
+ if (this.#scene.environmentTexture) {
498
+ return true;
499
+ }
500
+ const hdrTextureURI = "../src/environments/noon_grass.hdr";
501
+ const hdrTexture = new HDRCubeTexture(hdrTextureURI, this.#scene, 128);
502
+ hdrTexture.gammaSpace = true;
503
+ hdrTexture._noMipmap = false;
504
+ hdrTexture.level = 2.0;
505
+ this.#scene.environmentTexture = hdrTexture;
506
+ }
507
+
508
+ #initIBLShadows() {
509
+ if (!this.#scene.environmentTexture) {
510
+ return false;
511
+ }
512
+
513
+ let createIBLShadowPipeline = function (scene) {
514
+ const pipeline = new IblShadowsRenderPipeline(
515
+ "iblShadowsPipeline",
516
+ scene,
517
+ {
518
+ resolutionExp: 7,
519
+ sampleDirections: 2,
520
+ ssShadowsEnabled: true,
521
+ shadowRemanence: 0.8,
522
+ triPlanarVoxelization: true,
523
+ shadowOpacity: 0.8,
524
+ },
525
+ [scene.activeCamera]
526
+ );
527
+ pipeline.allowDebugPasses = false;
528
+ pipeline.gbufferDebugEnabled = true;
529
+ pipeline.importanceSamplingDebugEnabled = false;
530
+ pipeline.voxelDebugEnabled = false;
531
+ pipeline.voxelDebugDisplayMip = 1;
532
+ pipeline.voxelDebugAxis = 2;
533
+ pipeline.voxelTracingDebugEnabled = false;
534
+ pipeline.spatialBlurPassDebugEnabled = false;
535
+ pipeline.accumulationPassDebugEnabled = false;
536
+ return pipeline;
537
+ };
538
+
539
+ let iblShadowsPipeline = createIBLShadowPipeline(this.#scene);
540
+
541
+ this.#scene.meshes.forEach((mesh) => {
542
+ if (mesh.id.startsWith("__root__") || mesh.name === "hdri") {
543
+ return false;
544
+ }
545
+ iblShadowsPipeline.addShadowCastingMesh(mesh);
546
+ iblShadowsPipeline.updateSceneBounds();
547
+ });
548
+
549
+ this.#scene.materials.forEach((material) => {
550
+ iblShadowsPipeline.addShadowReceivingMaterial(material);
551
+ });
552
+ }
553
+
554
+ #initShadows() {
555
+ if (!this.#scene.environmentTexture) {
556
+ this.#initIBLShadows();
557
+ return true;
558
+ }
559
+
560
+ this.#scene.meshes.forEach((mesh) => {
561
+ if (mesh.id.startsWith("__root__")) {
562
+ return false;
563
+ }
564
+ mesh.receiveShadows = true;
565
+ if (!mesh.name === "hdri") {
566
+ this.#shadowGen.addShadowCaster(mesh, true);
567
+ }
568
+ });
569
+ }
536
570
 
537
- console.log("PrefViewer: lights created", {
538
- hemiIntensity: this.#hemiLight.intensity,
539
- dirIntensity: this.#dirLight.intensity,
540
- shadowMapSize: 1024,
541
- pointIntensity: this.#cameraLight.intensity
571
+ #setMaxSimultaneousLights() {
572
+ let lightsNumber = 1; // Como mínimo una luz correspondiente a la textura de environmentTexture
573
+ this.#scene.lights.forEach((light) => {
574
+ if (light.isEnabled()) {
575
+ ++lightsNumber;
576
+ }
542
577
  });
578
+ if (this.#scene.materials) {
579
+ this.#scene.materials.forEach((material) => (material.maxSimultaneousLights = lightsNumber));
580
+ }
543
581
  }
544
582
 
545
583
  #setupInteraction() {
546
- console.log("PrefViewer: #setupInteraction()");
547
584
  this.#canvas.addEventListener("wheel", (event) => {
548
585
  if (!this.#scene || !this.#camera) {
549
586
  return false;
550
587
  }
588
+ //const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
589
+ //this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
551
590
  if (!this.#scene.activeCamera.metadata?.locked) {
552
591
  this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
553
592
  }
@@ -556,10 +595,7 @@ class PrefViewer extends HTMLElement {
556
595
  }
557
596
 
558
597
  #disposeEngine() {
559
- console.log("PrefViewer: #disposeEngine()");
560
598
  if (!this.#engine) return;
561
- this.#shadowGen?.dispose();
562
- this.#scene?.lights?.slice().forEach(l => l.dispose());
563
599
  this.#engine.dispose();
564
600
  this.#engine = this.#scene = this.#camera = null;
565
601
  this.#hemiLight = this.#dirLight = this.#cameraLight = null;
@@ -568,7 +604,6 @@ class PrefViewer extends HTMLElement {
568
604
 
569
605
  // Utility methods for loading gltf/glb
570
606
  async #getServerFileDataHeader(uri) {
571
- console.log("PrefViewer: #getServerFileDataHeader()", uri);
572
607
  return new Promise((resolve) => {
573
608
  const xhr = new XMLHttpRequest();
574
609
  xhr.open("HEAD", uri, true);
@@ -577,15 +612,12 @@ class PrefViewer extends HTMLElement {
577
612
  if (xhr.status === 200) {
578
613
  const size = parseInt(xhr.getResponseHeader("Content-Length"));
579
614
  const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
580
- console.log("PrefViewer: HEAD ok", { size, timeStamp });
581
615
  resolve([size, timeStamp]);
582
616
  } else {
583
- console.warn("PrefViewer: HEAD failed", xhr.status);
584
617
  resolve([0, null]);
585
618
  }
586
619
  };
587
620
  xhr.onerror = () => {
588
- console.warn("PrefViewer: HEAD network error");
589
621
  resolve([0, null]);
590
622
  };
591
623
  xhr.send();
@@ -594,14 +626,11 @@ class PrefViewer extends HTMLElement {
594
626
 
595
627
  #transformUrl(url) {
596
628
  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);
629
+ resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
600
630
  });
601
631
  }
602
632
 
603
633
  #decodeBase64(base64) {
604
- console.log("PrefViewer: #decodeBase64() START");
605
634
  const [, payload] = base64.split(",");
606
635
  const raw = payload || base64;
607
636
  let decoded = "";
@@ -611,73 +640,55 @@ class PrefViewer extends HTMLElement {
611
640
  try {
612
641
  decoded = atob(raw);
613
642
  } catch {
614
- console.warn("PrefViewer: base64 decode failed (not base64?)");
615
643
  return { blob, extension, size };
616
644
  }
617
645
  let isJson = false;
618
646
  try {
619
647
  JSON.parse(decoded);
620
648
  isJson = true;
621
- } catch { }
649
+ } catch {}
622
650
  extension = isJson ? ".gltf" : ".glb";
623
651
  const type = isJson ? "model/gltf+json" : "model/gltf-binary";
624
652
  const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
625
653
  blob = new Blob([array], { type });
626
- console.log("PrefViewer: #decodeBase64() END", { extension, size, type });
627
654
  return { blob, extension, size };
628
655
  }
629
656
 
630
657
  async #initStorage(db, table) {
631
- console.log("PrefViewer: #initStorage()", { db, table });
632
658
  if (window.gltfDB && window.gltfDB.name === db && window.gltfDB.objectStoreNames.contains(table)) {
633
- console.log("PrefViewer: existing DB connection reused");
634
659
  return true;
635
660
  }
636
661
  await initDb(db, table);
637
- console.log("PrefViewer: DB initialized");
638
662
  }
639
663
 
640
664
  // Methods for managing Asset Containers
641
665
  #setVisibilityOfWallAndFloorInModel(show) {
642
- console.log("PrefViewer: #setVisibilityOfWallAndFloorInModel()", { incomingShow: show });
643
666
  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
667
  return false;
646
668
  }
647
669
  show = show !== undefined ? show : this.#data.containers.environment.visible;
648
670
  const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
649
- console.log("PrefViewer: toggling meshes with prefixes", prefixes, "->", show);
650
671
  this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
651
672
  }
652
673
 
653
674
  #setOptionsMaterial(optionMaterial) {
654
- console.log("PrefViewer: #setOptionsMaterial()", { optionMaterial });
655
675
  if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
656
- console.log("PrefViewer: optionMaterial incomplete, skipping");
657
676
  return false;
658
677
  }
659
-
660
- const material = this.#data.containers.materials.assetContainer?.materials
661
- .find((mat) => mat.name === optionMaterial.value) || null;
662
-
678
+
679
+ const material = this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) || null;
663
680
  if (!material) {
664
- console.warn("PrefViewer: material not found", optionMaterial.value);
665
681
  return false;
666
682
  }
667
-
668
- const hadExplicitChange = !!optionMaterial.changed.pending;
669
-
683
+
670
684
  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)) {
685
+ if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed || this.#data.containers.materials.changed || optionMaterial.changed)) {
673
686
  containers.push(this.#data.containers.model.assetContainer);
674
687
  }
675
- if (this.#data.containers.environment.assetContainer &&
676
- (this.#data.containers.environment.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
688
+ if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed || this.#data.containers.materials.changed || optionMaterial.changed)) {
677
689
  containers.push(this.#data.containers.environment.assetContainer);
678
690
  }
679
691
  if (containers.length === 0) {
680
- console.log("PrefViewer: no containers require material update");
681
692
  return false;
682
693
  }
683
694
 
@@ -691,15 +702,9 @@ class PrefViewer extends HTMLElement {
691
702
  })
692
703
  );
693
704
 
694
- console.log("PrefViewer: material assignment result", {
695
- prefix: optionMaterial.prefix, material: optionMaterial.value, someSetted
696
- });
697
-
698
705
  if (someSetted) {
699
706
  optionMaterial.changed.success = true;
700
- optionMaterial.changed.pending = false;
701
- } else if (hadExplicitChange) {
702
- // Solo revertimos si el usuario pidió el cambio y falló
707
+ } else {
703
708
  optionMaterial.value = optionMaterial.changed.oldValue;
704
709
  }
705
710
 
@@ -707,140 +712,72 @@ class PrefViewer extends HTMLElement {
707
712
  }
708
713
 
709
714
  #setOptionsMaterials() {
710
- console.log("PrefViewer: #setOptionsMaterials() START");
711
715
  let someSetted = false;
712
716
  Object.values(this.#data.options.materials).forEach((material) => {
713
717
  let settedMaterial = this.#setOptionsMaterial(material);
714
718
  someSetted = someSetted || settedMaterial;
715
719
  });
716
- console.log("PrefViewer: #setOptionsMaterials() END", { someSetted });
717
720
  return someSetted;
718
721
  }
719
722
 
720
723
  #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) {
724
+ if (!this.#data.options.camera.value || (!this.#data.options.camera.changed && !this.#data.containers.model.changed && !this.#data.containers.environment.changed)) {
731
725
  return false;
732
726
  }
733
727
 
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
-
728
+ 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
729
  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
- }
730
+ if (this.#data.options.camera.changed?.oldValue && this.#data.options.camera.changed?.oldValue !== this.#data.options.camera.value) {
731
+ 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 === his.#data.options.camera.changed.oldValue) || null;
732
+ }
733
+ if (camera) {
734
+ camera.metadata = { locked: this.#data.options.camera.changed.oldLocked };
735
+ this.#data.options.camera.value = this.#data.options.camera.changed.oldValue;
736
+ this.#data.options.camera.locked = this.#data.options.camera.changed.oldLocked;
763
737
  } else {
764
- // No hubo cambio explícito: usa fallback sin tocar changed
765
738
  camera = this.#camera;
766
- camState.value = null;
767
- camState.locked = this.#camera.metadata.locked;
739
+ this.#data.options.camera.value = null;
740
+ this.#data.options.camera.locked = this.#camera.metadata.locked;
768
741
  }
742
+ this.#data.options.camera.changed.success = false;
769
743
  } 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;
775
- }
744
+ camera.metadata = { locked: this.#data.options.camera.locked };
776
745
  }
777
-
778
- if (!camState.locked && camState.value !== null) {
746
+ if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
779
747
  camera.attachControl(this.#canvas, true);
780
748
  }
781
749
  this.#scene.activeCamera = camera;
782
- console.log("PrefViewer: active camera set", { name: camera.name, locked: camera.metadata?.locked });
783
750
  return true;
784
751
  }
785
752
 
786
753
  #addContainer(container) {
787
- console.log("PrefViewer: #addContainer()", { name: container.name, hasContainer: !!container.assetContainer, visible: container.visible, show: container.show });
788
754
  if (container.assetContainer && !container.visible && container.show) {
789
755
  container.assetContainer.addAllToScene();
790
756
  container.visible = true;
791
- console.log("PrefViewer: container added to scene", container.name);
792
757
  }
793
758
  }
794
759
 
795
760
  #removeContainer(container) {
796
- console.log("PrefViewer: #removeContainer()", { name: container.name, hasContainer: !!container.assetContainer, visible: container.visible });
797
761
  if (container.assetContainer && container.visible) {
798
762
  container.assetContainer.removeAllFromScene();
799
763
  container.visible = false;
800
- console.log("PrefViewer: container removed from scene", container.name);
801
764
  }
802
765
  }
803
766
 
804
767
  #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);
768
+ if (container.assetContainer) {
769
+ container.assetContainer.dispose();
770
+ container.assetContainer = null;
812
771
  }
813
-
814
- // 2) asigna el nuevo y prepara
772
+ this.#scene.getEngine().releaseEffects();
815
773
  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
774
  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
775
  }
837
776
 
838
777
  async #loadAssetContainer(container) {
839
- console.log("PrefViewer: #loadAssetContainer() START", { name: container?.name, storage: container?.storage });
840
778
  let storage = container?.storage;
841
779
 
842
780
  if (!storage) {
843
- console.log("PrefViewer: no storage, skipping", container?.name);
844
781
  return false;
845
782
  }
846
783
 
@@ -851,22 +788,14 @@ class PrefViewer extends HTMLElement {
851
788
  const object = await loadModel(storage.id, storage.table);
852
789
  source = object.data;
853
790
  if (object.timeStamp === container.timeStamp) {
854
- console.log("PrefViewer: DB entry unchanged, skipping", container.name);
791
+ container.changed = false;
855
792
  return false;
856
793
  } 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);
794
+ Object.assign(container.changed, { timeStamp: object.timeStamp, size: object.size, success: false });
865
795
  }
866
796
  }
867
797
 
868
798
  if (!source) {
869
- console.log("PrefViewer: no source after storage resolution", container.name);
870
799
  return false;
871
800
  }
872
801
 
@@ -877,40 +806,27 @@ class PrefViewer extends HTMLElement {
877
806
  file = new File([blob], `${container.name}${extension}`, {
878
807
  type: blob.type,
879
808
  });
880
- if (!container.changed.pending) {
809
+ if (!container.changed) {
881
810
  if (container.timeStamp === null && container.size === size) {
882
- console.log("PrefViewer: same base64 size and null timestamp, skipping", container.name);
811
+ container.changed = false;
883
812
  return false;
884
813
  } else {
885
- container.changed = {
886
- ...container.changed,
887
- pending: true,
888
- success: false,
889
- timeStamp: null,
890
- size: size,
891
- };
814
+ Object.assign(container.changed, { timeStamp: null, size: size, success: false });
892
815
  }
893
816
  }
894
- console.log("PrefViewer: prepared File from base64", { name: file.name, size: file.size, type: file.type });
895
817
  } else {
896
818
  const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
897
819
  extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
898
820
  const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
899
821
  if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
900
- console.log("PrefViewer: remote file unchanged, skipping", container.name);
822
+ container.changed = false;
901
823
  return false;
902
824
  } 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);
825
+ Object.assign(container.changed, { timeStamp: fileTimeStamp, size: fileSize, success: false });
911
826
  }
912
827
  }
913
828
 
829
+ // https://doc.babylonjs.com/typedoc/interfaces/BABYLON.LoadAssetContainerOptions
914
830
  let options = {
915
831
  pluginExtension: extension,
916
832
  pluginOptions: {
@@ -921,58 +837,56 @@ class PrefViewer extends HTMLElement {
921
837
  },
922
838
  };
923
839
 
924
- console.log("PrefViewer: calling LoadAssetContainerAsync()", { extension });
925
840
  return LoadAssetContainerAsync(file || source, this.#scene, options);
926
841
  }
927
842
 
928
843
  async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
929
- console.log("PrefViewer: #loadContainers()", { loadModel, loadEnvironment, loadMaterials });
844
+ this.#setStatusSceneLoading();
845
+ this.#engine.stopRenderLoop(this.#renderLoop);
846
+
930
847
  const promiseArray = [];
931
848
  promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
932
849
  promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
933
850
  promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
934
851
 
935
- this.#setStatusSceneLoading();
936
-
937
852
  Promise.allSettled(promiseArray)
938
853
  .then(async (values) => {
939
- console.log("PrefViewer: Promise.allSettled results", values);
940
854
  const modelContainer = values[0];
941
855
  const environmentContainer = values[1];
942
856
  const materialsContainer = values[2];
943
857
 
858
+ this.#removeContainer(this.#data.containers.model);
859
+ this.#removeContainer(this.#data.containers.environment);
860
+ this.#removeContainer(this.#data.containers.materials);
861
+
944
862
  if (modelContainer.status === "fulfilled" && modelContainer.value) {
945
- console.log("PrefViewer: model container fulfilled, applying");
946
- this.#stripImportedLights(modelContainer.value);
863
+ modelContainer.value.lights = [];
947
864
  this.#replaceContainer(this.#data.containers.model, modelContainer.value);
948
- this.#storeChangedFlagsForContainer(this.#data.containers.model);
865
+ this.#storeChangedFlagsForContainer(this.#data.containers.model, true);
949
866
  } 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);
867
+ this.#addContainer(this.#data.containers.model);
868
+ this.#storeChangedFlagsForContainer(this.#data.containers.model, false);
952
869
  }
953
870
 
954
871
  if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
955
- console.log("PrefViewer: environment container fulfilled, applying");
956
- this.#stripImportedLights(environmentContainer.value);
957
872
  this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
958
- this.#storeChangedFlagsForContainer(this.#data.containers.environment);
873
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment, true);
959
874
  } 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);
875
+ this.#addContainer(this.#data.containers.environment);
876
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment, false);
962
877
  }
963
878
 
964
879
  if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
965
- console.log("PrefViewer: materials container fulfilled, applying");
966
- this.#stripImportedLights(materialsContainer.value);
967
880
  this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
968
- this.#storeChangedFlagsForContainer(this.#data.containers.materials);
881
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials, true);
882
+ } else {
883
+ this.#addContainer(this.#data.containers.materials);
884
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials, false);
969
885
  }
970
886
 
971
887
  this.#setOptionsMaterials();
972
888
  this.#setOptionsCamera();
973
889
  this.#setVisibilityOfWallAndFloorInModel();
974
- this.#setStatusSceneLoaded();
975
- this.#resetChangedFlags();
976
890
  })
977
891
  .catch((error) => {
978
892
  this.loaded = true;
@@ -985,34 +899,36 @@ class PrefViewer extends HTMLElement {
985
899
  detail: { error: error },
986
900
  })
987
901
  );
902
+ })
903
+ .finally(() => {
904
+ this.#setMaxSimultaneousLights();
905
+ this.#initShadows();
906
+ this.#setStatusSceneLoaded();
907
+ this.#engine.runRenderLoop(this.#renderLoop);
988
908
  });
989
909
  }
990
910
 
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
911
  // Public Methods
1002
912
  loadConfig(config) {
1003
- console.log("PrefViewer: loadConfig()", config);
1004
913
  config = typeof config === "string" ? JSON.parse(config) : config;
1005
914
  if (!config) {
1006
- console.warn("PrefViewer: loadConfig() no config provided");
1007
915
  return false;
1008
916
  }
1009
917
 
1010
918
  // Containers
1011
- this.#data.containers.model.storage = config.model?.storage || null;
919
+ const loadModel = !!config.model?.storage;
920
+ this.#data.containers.model.changed = loadModel ? { storage: this.#data.containers.model.storage } : false;
921
+ this.#data.containers.model.storage = loadModel ? config.model.storage : this.#data.containers.model.storage;
1012
922
  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;
923
+
924
+ const loadEnvironment = !!config.scene?.storage;
925
+ this.#data.containers.environment.changed = loadEnvironment ? { storage: this.#data.containers.environment.storage } : false;
926
+ this.#data.containers.environment.storage = loadEnvironment ? config.scene.storage : this.#data.containers.environment.storage;
1014
927
  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;
928
+
929
+ const loadMaterials = !!config.materials?.storage;
930
+ this.#data.containers.materials.changed = loadMaterials ? { storage: this.#data.containers.materials.storage } : false;
931
+ this.#data.containers.materials.storage = loadMaterials ? config.materials.storage : this.#data.containers.materials.storage;
1016
932
 
1017
933
  // Options
1018
934
  if (config.options) {
@@ -1020,11 +936,10 @@ class PrefViewer extends HTMLElement {
1020
936
  this.#checkMaterialsChanged(config.options);
1021
937
  }
1022
938
 
1023
- this.initialized && this.#loadContainers(true, true, true);
939
+ this.initialized && this.#loadContainers(loadModel, loadEnvironment, loadMaterials);
1024
940
  }
1025
941
 
1026
942
  setOptions(options) {
1027
- console.log("PrefViewer: setOptions()", options);
1028
943
  if (!options) {
1029
944
  return false;
1030
945
  }
@@ -1040,69 +955,73 @@ class PrefViewer extends HTMLElement {
1040
955
  }
1041
956
 
1042
957
  this.#setStatusOptionsLoaded();
1043
- this.#resetChangedFlags();
1044
958
 
1045
959
  return someSetted;
1046
960
  }
1047
961
 
1048
962
  loadModel(model) {
1049
- console.log("PrefViewer: loadModel()", model);
1050
963
  model = typeof model === "string" ? JSON.parse(model) : model;
1051
964
  if (!model) {
1052
- console.warn("PrefViewer: loadModel() no model provided");
1053
965
  return false;
1054
966
  }
1055
- this.#data.containers.model.storage = model.storage || null;
967
+ const loadModel = !!model.storage;
968
+ this.#data.containers.model.changed = loadModel ? { storage: this.#data.containers.model.storage } : false;
969
+ this.#data.containers.model.storage = loadModel ? model.storage : this.#data.containers.model.storage;
1056
970
  this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
1057
- this.initialized && this.#loadContainers(true, false, false);
971
+ this.initialized && this.#loadContainers(loadModel, false, false);
1058
972
  }
1059
973
 
1060
974
  loadScene(scene) {
1061
- console.log("PrefViewer: loadScene()", scene);
1062
975
  scene = typeof scene === "string" ? JSON.parse(scene) : scene;
1063
976
  if (!scene) {
1064
- console.warn("PrefViewer: loadScene() no scene provided");
1065
977
  return false;
1066
978
  }
1067
- this.#data.containers.environment.storage = scene.storage || null;
979
+ const loadEnvironment = !!scene.storage;
980
+ this.#data.containers.environment.changed = loadEnvironment ? { storage: this.#data.containers.environment.storage } : false;
981
+ this.#data.containers.environment.storage = loadEnvironment ? scene.storage : this.#data.containers.environment.storage;
1068
982
  this.#data.containers.environment.show = scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
1069
- this.initialized && this.#loadContainers(false, true, false);
983
+ this.initialized && this.#loadContainers(false, loadEnvironment, false);
984
+ }
985
+
986
+ loadMaterials(materials) {
987
+ materials = typeof materials === "string" ? JSON.parse(materials) : materials;
988
+ if (!materials) {
989
+ return false;
990
+ }
991
+ const loadMaterials = !!materials.storage;
992
+ this.#data.containers.materials.changed = loadMaterials ? { storage: this.#data.containers.materials.storage } : false;
993
+ this.#data.containers.materials.storage = loadMaterials ? materials.storage : this.#data.containers.materials.storage;
994
+ this.initialized && this.#loadContainers(false, false, loadMaterials);
1070
995
  }
1071
996
 
1072
997
  showModel() {
1073
- console.log("PrefViewer: showModel()");
1074
998
  this.#data.containers.model.show = true;
1075
999
  this.#addContainer(this.#data.containers.model);
1076
1000
  }
1077
1001
 
1078
1002
  hideModel() {
1079
- console.log("PrefViewer: hideModel()");
1080
1003
  this.#data.containers.model.show = false;
1081
1004
  this.#removeContainer(this.#data.containers.model);
1082
1005
  }
1083
1006
 
1084
1007
  showScene() {
1085
- console.log("PrefViewer: showScene()");
1086
1008
  this.#data.containers.environment.show = true;
1087
1009
  this.#addContainer(this.#data.containers.environment);
1088
1010
  this.#setVisibilityOfWallAndFloorInModel();
1089
1011
  }
1090
1012
 
1091
1013
  hideScene() {
1092
- console.log("PrefViewer: hideScene()");
1093
1014
  this.#data.containers.environment.show = false;
1094
1015
  this.#removeContainer(this.#data.containers.environment);
1095
1016
  this.#setVisibilityOfWallAndFloorInModel();
1096
1017
  }
1097
1018
 
1098
1019
  downloadModelGLB() {
1099
- console.log("PrefViewer: downloadModelGLB()");
1100
1020
  const fileName = "model";
1101
1021
  GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
1102
1022
  }
1103
1023
 
1104
1024
  downloadModelUSDZ() {
1105
- console.log("PrefViewer: downloadModelUSDZ()");
1106
1025
  const fileName = "model";
1107
1026
  USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
1108
1027
  if (response) {
@@ -1112,7 +1031,6 @@ class PrefViewer extends HTMLElement {
1112
1031
  }
1113
1032
 
1114
1033
  downloadModelAndSceneUSDZ() {
1115
- console.log("PrefViewer: downloadModelAndSceneUSDZ()");
1116
1034
  const fileName = "scene";
1117
1035
  USDZExportAsync(this.#scene).then((response) => {
1118
1036
  if (response) {
@@ -1122,7 +1040,6 @@ class PrefViewer extends HTMLElement {
1122
1040
  }
1123
1041
 
1124
1042
  downloadModelAndSceneGLB() {
1125
- console.log("PrefViewer: downloadModelAndSceneGLB()");
1126
1043
  const fileName = "scene";
1127
1044
  GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
1128
1045
  }