@preference-sl/pref-viewer 2.10.0-beta.1 → 2.10.0-beta.11

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 +1 -1
  2. package/src/index.js +377 -85
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@preference-sl/pref-viewer",
3
- "version": "2.10.0-beta.1",
3
+ "version": "2.10.0-beta.11",
4
4
  "description": "Web Component to preview GLTF models with Babylon.js",
5
5
  "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
6
  "scripts": {
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 } from "@babylonjs/core";
42
+ import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName } 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";
@@ -49,20 +49,72 @@ import { initDb, loadModel } from "./gltf-storage.js";
49
49
  class PrefViewer extends HTMLElement {
50
50
  #initialized = false;
51
51
 
52
- #model = {
53
- container: null,
54
- show: true, // Show model by default
55
- storage: null,
56
- visible: false,
57
- };
58
-
59
- #environment = {
60
- container: null,
61
- show: true, // Show environment by default
62
- storage: null,
63
- visible: false,
52
+ #data = {
53
+ containers: {
54
+ model: {
55
+ name: "model",
56
+ container: null,
57
+ show: true,
58
+ storage: null,
59
+ visible: false,
60
+ size: null,
61
+ timestamp: null,
62
+ changed: false,
63
+ },
64
+ environment: {
65
+ name: "environment",
66
+ container: null,
67
+ show: true,
68
+ storage: null,
69
+ visible: false,
70
+ size: null,
71
+ timestamp: null,
72
+ changed: false,
73
+ },
74
+ materials: {
75
+ name: "materials",
76
+ container: null,
77
+ storage: null,
78
+ show: true,
79
+ visible: false,
80
+ size: null,
81
+ timestamp: null,
82
+ changed: false,
83
+ },
84
+ },
85
+ options: {
86
+ camera: {
87
+ value: null,
88
+ locked: true,
89
+ changed: false,
90
+ },
91
+ materials: {
92
+ innerWall: {
93
+ value: null,
94
+ prefix: "innerWall",
95
+ changed: false,
96
+ },
97
+ outerWall: {
98
+ value: null,
99
+ prefix: "outerWall",
100
+ changed: false,
101
+ },
102
+ innerFloor: {
103
+ value: null,
104
+ prefix: "innerFloor",
105
+ changed: false,
106
+ },
107
+ outerFloor: {
108
+ value: null,
109
+ prefix: "outerFloor",
110
+ changed: false,
111
+ },
112
+ },
113
+ },
64
114
  };
65
115
 
116
+ // DOM elements
117
+ #wrapper = null;
66
118
  #canvas = null;
67
119
 
68
120
  // Babylon.js core objects
@@ -73,6 +125,7 @@ class PrefViewer extends HTMLElement {
73
125
  #dirLight = null;
74
126
  #cameraLight = null;
75
127
  #shadowGen = null;
128
+ #XRExperience = null;
76
129
 
77
130
  constructor() {
78
131
  super();
@@ -112,7 +165,7 @@ class PrefViewer extends HTMLElement {
112
165
  if (this.#initialized) {
113
166
  data ? this.showModel() : this.hideModel();
114
167
  } else {
115
- this.#model.show = data;
168
+ this.#data.containers.model.show = data;
116
169
  }
117
170
  break;
118
171
  case "show-scene":
@@ -120,7 +173,7 @@ class PrefViewer extends HTMLElement {
120
173
  if (this.#initialized) {
121
174
  data ? this.showScene() : this.hideScene();
122
175
  } else {
123
- this.#environment.show = data;
176
+ this.#data.containers.environment.show = data;
124
177
  }
125
178
  break;
126
179
  }
@@ -141,7 +194,7 @@ class PrefViewer extends HTMLElement {
141
194
  }
142
195
 
143
196
  this.#initializeBabylon();
144
- this.#loadContainers(true, true);
197
+ this.#loadContainers(true, true, true);
145
198
  this.#initialized = true;
146
199
  }
147
200
 
@@ -162,38 +215,132 @@ class PrefViewer extends HTMLElement {
162
215
  }
163
216
 
164
217
  #wrapCanvas() {
165
- const wrapper = document.createElement("div");
166
- Object.assign(wrapper.style, {
218
+ this.#wrapper = document.createElement("div");
219
+ Object.assign(this.#wrapper.style, {
167
220
  width: "100%",
168
221
  height: "100%",
169
222
  position: "relative",
170
223
  });
171
- wrapper.appendChild(this.#canvas);
172
- this.shadowRoot.append(wrapper);
224
+ this.#wrapper.appendChild(this.#canvas);
225
+ this.shadowRoot.append(this.#wrapper);
173
226
  }
174
227
 
175
-
176
- // Bbylon.js
177
- #initializeBabylon() {
228
+ // Data
229
+ #checkCameraChanged(options) {
230
+ if (!options || !options.camera) {
231
+ return false;
232
+ }
233
+ this.#data.options.camera.changed = options.camera && options.camera !== this.#data.options.camera.value ? true : false;
234
+ this.#data.options.camera.value = this.#data.options.camera.changed ? options.camera : this.#data.options.camera.value;
235
+ return this.#data.options.camera.changed;
236
+ }
237
+
238
+ #checkMaterialsChanged(options) {
239
+ if (!options) {
240
+ return false;
241
+ }
242
+ let someChanged = false;
243
+ Object.keys(this.#data.options.materials).forEach((material) => {
244
+ const key = `${material}Material`;
245
+ this.#data.options.materials[material].changed = options[key] && options[key] !== this.#data.options.materials[material].value ? true : false;
246
+ this.#data.options.materials[material].value = this.#data.options.materials[material].changed ? options[key] : this.#data.options.materials[material].value;
247
+ someChanged = someChanged || this.#data.options.materials[material].changed;
248
+ });
249
+ return someChanged;
250
+ }
251
+
252
+ #storeChangedFlagsForContainer(container) {
253
+ container.timestamp = container.changed.timestamp;
254
+ container.size = container.changed.size;
255
+ }
256
+
257
+ #resetChangedFlags() {
258
+ Object.values(this.#data.containers).forEach((container) => (container.changed = false));
259
+ Object.values(this.#data.options.materials).forEach((material) => (material.changed = false));
260
+ this.#data.options.camera.changed = false;
261
+ }
262
+
263
+ // Babylon.js
264
+ async #initializeBabylon() {
178
265
  this.#engine = new Engine(this.#canvas, true, { alpha: true });
179
266
  this.#scene = new Scene(this.#engine);
180
267
  this.#scene.clearColor = new Color4(1, 1, 1, 1);
181
268
  this.#createCamera();
182
269
  this.#createLights();
183
270
  this.#setupInteraction();
184
-
271
+
185
272
  this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
186
273
  this.#canvasResizeObserver.observe(this.#canvas);
274
+
275
+ await this.#createXRExperience();
276
+ }
277
+
278
+ addStylesToARButton() {
279
+ 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"}';
280
+ const style = document.createElement("style");
281
+ style.appendChild(document.createTextNode(css));
282
+ this.#wrapper.appendChild(style);
283
+ }
284
+
285
+ async #createXRExperience() {
286
+ if (this.#XRExperience) {
287
+ return true;
288
+ }
289
+
290
+ const sessionMode = "immersive-ar";
291
+ const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
292
+ if (!sessionSupported) {
293
+ console.info("PrefViewer: WebXR in mode AR is not supported");
294
+ return false;
295
+ }
296
+
297
+ try {
298
+ const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
299
+ ground.isVisible = false;
300
+
301
+ const options = {
302
+ floorMeshes: [ground],
303
+ uiOptions: {
304
+ sessionMode: sessionMode,
305
+ renderTarget: "xrLayer",
306
+ referenceSpaceType: "local",
307
+ },
308
+ optionalFeatures: true,
309
+ };
310
+
311
+ this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
312
+
313
+ const featuresManager = this.#XRExperience.baseExperience.featuresManager;
314
+ featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
315
+ xrInput: this.#XRExperience.input,
316
+ floorMeshes: [ground],
317
+ timeToTeleport: 1500,
318
+ });
319
+
320
+ this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
321
+ // Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
322
+ this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
323
+ this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
324
+ this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
325
+ });
326
+
327
+ this.addStylesToARButton();
328
+ } catch (error) {
329
+ console.warn("PrefViewer: failed to create WebXR experience", error);
330
+ this.#XRExperience = null;
331
+ }
187
332
  }
188
333
 
189
334
  #canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
190
335
 
191
336
  #createCamera() {
192
- this.#camera = new ArcRotateCamera("camera", 3 * Math.PI / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
337
+ this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
193
338
  this.#camera.upperBetaLimit = Math.PI * 0.48;
194
339
  this.#camera.lowerBetaLimit = Math.PI * 0.25;
195
340
  this.#camera.lowerRadiusLimit = 5;
196
341
  this.#camera.upperRadiusLimit = 20;
342
+ this.#camera.metadata = { locked: false }
343
+ this.#camera = this.#camera;
197
344
  this.#camera.attachControl(this.#canvas, true);
198
345
  }
199
346
 
@@ -221,10 +368,14 @@ class PrefViewer extends HTMLElement {
221
368
 
222
369
  #setupInteraction() {
223
370
  this.#canvas.addEventListener("wheel", (event) => {
224
- if (!this.#scene || !this.#camera) return;
225
- const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
371
+ if (!this.#scene || !this.#camera) {
372
+ return false;
373
+ }
374
+ //const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
226
375
  //this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
227
- this.#camera.inertialRadiusOffset -= event.deltaY * this.#camera.wheelPrecision * 0.001;
376
+ if (!this.#scene.activeCamera.metadata?.locked) {
377
+ this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
378
+ }
228
379
  event.preventDefault();
229
380
  });
230
381
  }
@@ -238,6 +389,27 @@ class PrefViewer extends HTMLElement {
238
389
  }
239
390
 
240
391
  // Utility methods for loading gltf/glb
392
+ async #getServerFileDataHeader(uri) {
393
+ return new Promise((resolve) => {
394
+ const xhr = new XMLHttpRequest();
395
+ xhr.open("HEAD", uri, true);
396
+ xhr.responseType = "blob";
397
+ xhr.onload = () => {
398
+ if (xhr.status === 200) {
399
+ const size = parseInt(xhr.getResponseHeader("Content-Length"));
400
+ const timestamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
401
+ resolve(size, timestamp);
402
+ } else {
403
+ resolve(0, null);
404
+ }
405
+ };
406
+ xhr.onerror = () => {
407
+ resolve(0, null);
408
+ };
409
+ xhr.send();
410
+ });
411
+ }
412
+
241
413
  #transformUrl(url) {
242
414
  return new Promise((resolve) => {
243
415
  resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
@@ -250,10 +422,11 @@ class PrefViewer extends HTMLElement {
250
422
  let decoded = "";
251
423
  let blob = null;
252
424
  let extension = null;
425
+ let size = raw.length;
253
426
  try {
254
427
  decoded = atob(raw);
255
428
  } catch {
256
- return { blob, extension };
429
+ return { blob, extension, size };
257
430
  }
258
431
  let isJson = false;
259
432
  try {
@@ -264,7 +437,7 @@ class PrefViewer extends HTMLElement {
264
437
  const type = isJson ? "model/gltf+json" : "model/gltf-binary";
265
438
  const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
266
439
  blob = new Blob([array], { type });
267
- return { blob, extension };
440
+ return { blob, extension, size };
268
441
  }
269
442
 
270
443
  async #initStorage(db, table) {
@@ -276,41 +449,103 @@ class PrefViewer extends HTMLElement {
276
449
 
277
450
  // Methods for managing Asset Containers
278
451
  #setVisibilityOfWallAndFloorInModel(show) {
279
- if (this.#model.container && this.#model.visible) {
280
- const names = ["outer_0", "inner_1", "outerFloor", "innerFloor"];
281
- const nodes = this.#model.container.getNodes();
282
- this.#model.container
283
- .getNodes()
284
- .filter((filter) => names.includes(filter.name))
285
- .forEach((node) => node.setEnabled(show !== undefined ? show : this.#environment.show));
452
+ if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
453
+ return false;
454
+ }
455
+ show = show !== undefined ? show : this.#data.containers.environment.visible;
456
+ const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
457
+ this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
458
+ }
459
+
460
+ #setOptionsMaterial(optionMaterial) {
461
+ if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
462
+ return false;
463
+ }
464
+
465
+ const material = this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) || null;
466
+ if (!material) {
467
+ return false;
468
+ }
469
+
470
+ const containers = [];
471
+ if (this.#data.containers.model.assetContainer && (this.#data.containers.model.assetContainer.changed || optionMaterial.changed)) {
472
+ containers.push(this.#data.containers.model.assetContainer);
473
+ }
474
+ if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.assetContainer.changed || optionMaterial.changed)) {
475
+ containers.push(this.#data.containers.environment.assetContainer);
476
+ }
477
+ if (containers.length === 0) {
478
+ return false;
479
+ }
480
+
481
+ let someSetted = false;
482
+ containers.forEach((container) =>
483
+ container.meshes
484
+ .filter((meshToFilter) => meshToFilter.name.startsWith(optionMaterial.prefix))
485
+ .forEach((mesh) => {
486
+ mesh.material = material;
487
+ someSetted = true;
488
+ })
489
+ );
490
+
491
+ return someSetted;
492
+ }
493
+
494
+ #setOptionsMaterials() {
495
+ let someSetted = false;
496
+ Object.values(this.#data.options.materials).forEach((material) => {
497
+ let settedMaterial = this.#setOptionsMaterial(material);
498
+ someSetted = someSetted || settedMaterial;
499
+ });
500
+ return someSetted;
501
+ }
502
+
503
+ #setOptionsCamera() {
504
+ if (!this.#data.options.camera.value || (!this.#data.options.camera.changed && !this.#data.containers.model.assetContainer.changed)) {
505
+ return false;
506
+ }
507
+
508
+ let camera = this.#data.containers.model.assetContainer?.cameras.find((cam) => cam.name === this.#data.options.camera.value) || null;
509
+ if (!camera) {
510
+ return false;
286
511
  }
512
+
513
+ camera.metadata = { locked: this.#data.options.camera.locked };
514
+ if (!this.#data.options.camera.locked) {
515
+ camera.attachControl(this.#canvas, true);
516
+ }
517
+ this.#scene.activeCamera = camera;
518
+
519
+ return true;
287
520
  }
288
521
 
289
- #addContainer(group) {
290
- if (group.container && !group.visible && group.show) {
291
- group.container.addAllToScene();
292
- group.visible = true;
522
+ #addContainer(container) {
523
+ if (container.assetContainer && !container.visible && container.show) {
524
+ container.assetContainer.addAllToScene();
525
+ container.visible = true;
293
526
  }
294
527
  }
295
528
 
296
- #removeContainer(group) {
297
- if (group.container && group.visible) {
298
- group.container.removeAllFromScene();
299
- group.visible = false;
529
+ #removeContainer(container) {
530
+ if (container.assetContainer && container.visible) {
531
+ container.assetContainer.removeAllFromScene();
532
+ container.visible = false;
300
533
  }
301
534
  }
302
535
 
303
- #replaceContainer(group, newContainer) {
304
- this.#removeContainer(group);
305
- group.container = newContainer;
306
- group.container.meshes.forEach((mesh) => {
536
+ #replaceContainer(container, newAssetContainer) {
537
+ this.#removeContainer(container);
538
+ container.assetContainer = newAssetContainer;
539
+ container.assetContainer.meshes.forEach((mesh) => {
307
540
  mesh.receiveShadows = true;
308
541
  this.#shadowGen.addShadowCaster(mesh, true);
309
542
  });
310
- this.#addContainer(group);
543
+ this.#addContainer(container);
311
544
  }
312
545
 
313
- async #loadAssetContainer(storage) {
546
+ async #loadAssetContainer(container) {
547
+ let storage = container?.storage;
548
+
314
549
  if (!storage) {
315
550
  return false;
316
551
  }
@@ -321,6 +556,11 @@ class PrefViewer extends HTMLElement {
321
556
  await this.#initStorage(storage.db, storage.table);
322
557
  const object = await loadModel(storage.id, storage.table);
323
558
  source = object.data;
559
+ if (object.timestamp === container.timestamp) {
560
+ return false;
561
+ } else {
562
+ container.changed = { timestamp: object.timestamp, size: object.size };
563
+ }
324
564
  }
325
565
 
326
566
  if (!source) {
@@ -329,20 +569,34 @@ class PrefViewer extends HTMLElement {
329
569
 
330
570
  let file = null;
331
571
 
332
- let { blob, extension } = this.#decodeBase64(source);
572
+ let { blob, extension, size } = this.#decodeBase64(source);
333
573
  if (blob && extension) {
334
- file = new File([blob], `model${extension}`, {
574
+ file = new File([blob], `${container.name}${extension}`, {
335
575
  type: blob.type,
336
576
  });
577
+ if (!container.changed) {
578
+ if (container.timestamp === null && container.size === size) {
579
+ return false;
580
+ } else {
581
+ container.changed = { timestamp: null, size: size };
582
+ }
583
+ }
337
584
  } else {
338
585
  const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
339
586
  extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
587
+ const { fileSize, fileTimestamp } = await this.#getServerFileDataHeader(source);
588
+ if (container.timestamp === fileTimestamp && container.size === fileSize) {
589
+ return false;
590
+ } else {
591
+ container.changed = { timestamp: fileTimestamp, size: fileSize };
592
+ }
340
593
  }
341
594
 
342
595
  let options = {
343
596
  pluginExtension: extension,
344
597
  pluginOptions: {
345
598
  gltf: {
599
+ loadAllMaterials: true,
346
600
  preprocessUrlAsync: this.#transformUrl,
347
601
  },
348
602
  },
@@ -351,27 +605,43 @@ class PrefViewer extends HTMLElement {
351
605
  return LoadAssetContainerAsync(file || source, this.#scene, options);
352
606
  }
353
607
 
354
- async #loadContainers(loadModel = true, loadEnvironment = true) {
608
+ async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
355
609
  const promiseArray = [];
356
-
357
- promiseArray.push(loadModel ? this.#loadAssetContainer(this.#model.storage) : false);
358
- promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#environment.storage) : false);
610
+ promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
611
+ promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
612
+ promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
359
613
 
360
614
  Promise.allSettled(promiseArray)
361
615
  .then(async (values) => {
362
616
  const modelContainer = values[0];
363
617
  const environmentContainer = values[1];
618
+ const materialsContainer = values[2];
364
619
 
365
620
  if (modelContainer.status === "fulfilled" && modelContainer.value) {
366
- this.#replaceContainer(this.#model, modelContainer.value);
621
+ this.#replaceContainer(this.#data.containers.model, modelContainer.value);
622
+ this.#storeChangedFlagsForContainer(this.#data.containers.model);
623
+ } else {
624
+ this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
367
625
  }
368
626
 
369
627
  if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
370
- this.#replaceContainer(this.#environment, environmentContainer.value);
628
+ this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
629
+ this.#storeChangedFlagsForContainer(this.#data.containers.environment);
630
+ } else {
631
+ this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
632
+ }
633
+
634
+ if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
635
+ this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
636
+ this.#storeChangedFlagsForContainer(this.#data.containers.materials);
371
637
  }
372
638
 
639
+ this.#setOptionsMaterials();
640
+ this.#setOptionsCamera();
373
641
  this.#setVisibilityOfWallAndFloorInModel();
374
642
 
643
+ this.#resetChangedFlags();
644
+
375
645
  this.dispatchEvent(
376
646
  new CustomEvent("model-loaded", {
377
647
  detail: { success: "" },
@@ -398,11 +668,37 @@ class PrefViewer extends HTMLElement {
398
668
  if (!config) {
399
669
  return false;
400
670
  }
401
- this.#model.storage = config.model?.storage || null;
402
- this.#model.show = config.model?.visible !== undefined ? config.model.visible : this.#model.show;
403
- this.#environment.storage = config.scene?.storage || null;
404
- this.#environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#environment.show;
405
- this.#initialized && this.#loadContainers(true, true);
671
+
672
+ // Containers
673
+ this.#data.containers.model.storage = config.model?.storage || null;
674
+ this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
675
+ this.#data.containers.environment.storage = config.scene?.storage || null;
676
+ this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
677
+ this.#data.containers.materials.storage = config.materials?.storage || null;
678
+
679
+ // Options
680
+ if (config.options) {
681
+ this.#checkCameraChanged(config.options);
682
+ this.#checkMaterialsChanged(config.options);
683
+ }
684
+
685
+ this.#initialized && this.#loadContainers(true, true, true);
686
+ }
687
+
688
+ setOptions(options) {
689
+ if (!options) {
690
+ return false;
691
+ }
692
+ let someSetted = false;
693
+ if (this.#checkCameraChanged(options)) {
694
+ someSetted = someSetted || this.#setOptionsCamera();
695
+ }
696
+ if (this.#checkMaterialsChanged(options)) {
697
+ someSetted = someSetted || this.#setOptionsMaterials();
698
+ }
699
+ this.#resetChangedFlags();
700
+ debugger;
701
+ return someSetted;
406
702
  }
407
703
 
408
704
  loadModel(model) {
@@ -410,9 +706,9 @@ class PrefViewer extends HTMLElement {
410
706
  if (!model) {
411
707
  return false;
412
708
  }
413
- this.#model.storage = model.storage || null;
414
- this.#model.show = model.visible !== undefined ? model.visible : this.#model.show;
415
- this.#initialized && this.#loadContainers(true, false);
709
+ this.#data.containers.model.storage = model.storage || null;
710
+ this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
711
+ this.#initialized && this.#loadContainers(true, false, false);
416
712
  }
417
713
 
418
714
  loadScene(scene) {
@@ -420,43 +716,41 @@ class PrefViewer extends HTMLElement {
420
716
  if (!scene) {
421
717
  return false;
422
718
  }
423
- this.#environment.storage = scene.storage || null;
424
- this.#environment.show = scene.visible !== undefined ? scene.visible : this.#environment.show;
425
- this.#initialized && this.#loadContainers(false, true);
719
+ this.#data.containers.environment.storage = scene.storage || null;
720
+ this.#data.containers.environment.show = scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
721
+ this.#initialized && this.#loadContainers(false, true, false);
426
722
  }
427
723
 
428
724
  showModel() {
429
- this.#model.show = true;
430
- this.#addContainer(this.#model);
725
+ this.#data.containers.model.show = true;
726
+ this.#addContainer(this.#data.containers.model);
431
727
  }
432
728
 
433
729
  hideModel() {
434
- this.#model.show = false;
435
- this.#removeContainer(this.#model);
730
+ this.#data.containers.model.show = false;
731
+ this.#removeContainer(this.#data.containers.model);
436
732
  }
437
733
 
438
734
  showScene() {
439
- this.#environment.show = true;
440
- this.#addContainer(this.#environment);
735
+ this.#data.containers.environment.show = true;
736
+ this.#addContainer(this.#data.containers.environment);
441
737
  this.#setVisibilityOfWallAndFloorInModel();
442
738
  }
443
739
 
444
740
  hideScene() {
445
- this.#environment.show = false;
446
- this.#removeContainer(this.#environment);
741
+ this.#data.containers.environment.show = false;
742
+ this.#removeContainer(this.#data.containers.environment);
447
743
  this.#setVisibilityOfWallAndFloorInModel();
448
744
  }
449
745
 
450
746
  downloadModelGLB() {
451
747
  const fileName = "model";
452
- GLTF2Export.GLBAsync(this.#model.container, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
453
- glb.downloadFiles();
454
- });
748
+ GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
455
749
  }
456
750
 
457
751
  downloadModelUSDZ() {
458
752
  const fileName = "model";
459
- USDZExportAsync(this.#model.container).then((response) => {
753
+ USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
460
754
  if (response) {
461
755
  Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
462
756
  }
@@ -474,9 +768,7 @@ class PrefViewer extends HTMLElement {
474
768
 
475
769
  downloadModelAndSceneGLB() {
476
770
  const fileName = "scene";
477
- GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
478
- glb.downloadFiles();
479
- });
771
+ GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
480
772
  }
481
773
  }
482
774