@preference-sl/pref-viewer 2.10.0-beta.3 → 2.10.0-beta.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/gltf-storage.js +150 -71
- package/src/index.js +718 -112
package/src/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* style="width:800px; height:600px;">
|
|
23
23
|
* </pref-viewer>
|
|
24
24
|
* ```
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* Load scene a URL:
|
|
27
27
|
* ```html
|
|
28
28
|
* <pref-viewer
|
|
@@ -39,28 +39,110 @@
|
|
|
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";
|
|
46
46
|
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
47
47
|
import { initDb, loadModel } from "./gltf-storage.js";
|
|
48
48
|
|
|
49
|
-
class
|
|
50
|
-
|
|
49
|
+
class PrefViewerTask {
|
|
50
|
+
static Types = Object.freeze({
|
|
51
|
+
Config: "config",
|
|
52
|
+
Environment: "environment",
|
|
53
|
+
Materials: "materials",
|
|
54
|
+
Model: "model",
|
|
55
|
+
Options: "options",
|
|
56
|
+
});
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
/**
|
|
59
|
+
* value: any payload for the task
|
|
60
|
+
* type: must match one of PrefViewerTask.Types values (case-insensitive)
|
|
61
|
+
*/
|
|
62
|
+
constructor(value, type) {
|
|
63
|
+
this.value = value;
|
|
64
|
+
|
|
65
|
+
const t = typeof type === "string" ? type.toLowerCase() : String(type).toLowerCase();
|
|
66
|
+
const allowed = Object.values(PrefViewerTask.Types);
|
|
67
|
+
if (!allowed.includes(t)) {
|
|
68
|
+
throw new TypeError(
|
|
69
|
+
`PrefViewerTask: invalid type "${type}". Allowed types: ${allowed.join(", ")}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
this.type = t;
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
Object.freeze(this);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class PrefViewer extends HTMLElement {
|
|
79
|
+
initialized = false;
|
|
80
|
+
loaded = false;
|
|
81
|
+
loading = false;
|
|
82
|
+
#taskQueue = [];
|
|
83
|
+
|
|
84
|
+
#data = {
|
|
85
|
+
containers: {
|
|
86
|
+
model: {
|
|
87
|
+
name: "model",
|
|
88
|
+
assetContainer: null,
|
|
89
|
+
show: true,
|
|
90
|
+
storage: null,
|
|
91
|
+
visible: false,
|
|
92
|
+
size: null,
|
|
93
|
+
timeStamp: null,
|
|
94
|
+
changed: { pending: false, success: false },
|
|
95
|
+
},
|
|
96
|
+
environment: {
|
|
97
|
+
name: "environment",
|
|
98
|
+
assetContainer: null,
|
|
99
|
+
show: true,
|
|
100
|
+
storage: null,
|
|
101
|
+
visible: false,
|
|
102
|
+
size: null,
|
|
103
|
+
timeStamp: null,
|
|
104
|
+
changed: { pending: false, success: false },
|
|
105
|
+
},
|
|
106
|
+
materials: {
|
|
107
|
+
name: "materials",
|
|
108
|
+
assetContainer: null,
|
|
109
|
+
storage: null,
|
|
110
|
+
show: true,
|
|
111
|
+
visible: false,
|
|
112
|
+
size: null,
|
|
113
|
+
timeStamp: null,
|
|
114
|
+
changed: { pending: false, success: false },
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
options: {
|
|
118
|
+
camera: {
|
|
119
|
+
value: null,
|
|
120
|
+
locked: true,
|
|
121
|
+
changed: { pending: false, success: false },
|
|
122
|
+
},
|
|
123
|
+
materials: {
|
|
124
|
+
innerWall: {
|
|
125
|
+
value: null,
|
|
126
|
+
prefix: "innerWall",
|
|
127
|
+
changed: { pending: false, success: false },
|
|
128
|
+
},
|
|
129
|
+
outerWall: {
|
|
130
|
+
value: null,
|
|
131
|
+
prefix: "outerWall",
|
|
132
|
+
changed: { pending: false, success: false },
|
|
133
|
+
},
|
|
134
|
+
innerFloor: {
|
|
135
|
+
value: null,
|
|
136
|
+
prefix: "innerFloor",
|
|
137
|
+
changed: { pending: false, success: false },
|
|
138
|
+
},
|
|
139
|
+
outerFloor: {
|
|
140
|
+
value: null,
|
|
141
|
+
prefix: "outerFloor",
|
|
142
|
+
changed: { pending: false, success: false },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
64
146
|
};
|
|
65
147
|
|
|
66
148
|
// DOM elements
|
|
@@ -110,20 +192,26 @@ class PrefViewer extends HTMLElement {
|
|
|
110
192
|
case "scene":
|
|
111
193
|
this.loadScene(value);
|
|
112
194
|
break;
|
|
195
|
+
case "materials":
|
|
196
|
+
this.loadMaterials(value);
|
|
197
|
+
break;
|
|
198
|
+
case "options":
|
|
199
|
+
this.setOptions(value);
|
|
200
|
+
break;
|
|
113
201
|
case "show-model":
|
|
114
202
|
data = value.toLowerCase?.() === "true";
|
|
115
|
-
if (this
|
|
203
|
+
if (this.initialized) {
|
|
116
204
|
data ? this.showModel() : this.hideModel();
|
|
117
205
|
} else {
|
|
118
|
-
this.#model.show = data;
|
|
206
|
+
this.#data.containers.model.show = data;
|
|
119
207
|
}
|
|
120
208
|
break;
|
|
121
209
|
case "show-scene":
|
|
122
210
|
data = value.toLowerCase?.() === "true";
|
|
123
|
-
if (this
|
|
211
|
+
if (this.initialized) {
|
|
124
212
|
data ? this.showScene() : this.hideScene();
|
|
125
213
|
} else {
|
|
126
|
-
this.#environment.show = data;
|
|
214
|
+
this.#data.containers.environment.show = data;
|
|
127
215
|
}
|
|
128
216
|
break;
|
|
129
217
|
}
|
|
@@ -134,18 +222,19 @@ class PrefViewer extends HTMLElement {
|
|
|
134
222
|
const error = 'PrefViewer: provide "models" as array of model and environment';
|
|
135
223
|
console.error(error);
|
|
136
224
|
this.dispatchEvent(
|
|
137
|
-
new CustomEvent("
|
|
138
|
-
detail: { error: new Error(error) },
|
|
225
|
+
new CustomEvent("scene-error", {
|
|
139
226
|
bubbles: true,
|
|
227
|
+
cancelable: false,
|
|
140
228
|
composed: true,
|
|
229
|
+
detail: { error: new Error(error) },
|
|
141
230
|
})
|
|
142
231
|
);
|
|
143
232
|
return false;
|
|
144
233
|
}
|
|
145
234
|
|
|
146
235
|
this.#initializeBabylon();
|
|
147
|
-
this
|
|
148
|
-
this.#
|
|
236
|
+
this.initialized = true;
|
|
237
|
+
this.#processNextTask();
|
|
149
238
|
}
|
|
150
239
|
|
|
151
240
|
disconnectedCallback() {
|
|
@@ -174,24 +263,155 @@ class PrefViewer extends HTMLElement {
|
|
|
174
263
|
this.#wrapper.appendChild(this.#canvas);
|
|
175
264
|
this.shadowRoot.append(this.#wrapper);
|
|
176
265
|
}
|
|
177
|
-
|
|
178
|
-
|
|
266
|
+
|
|
267
|
+
#setStatusLoading() {
|
|
268
|
+
this.loaded = false;
|
|
269
|
+
this.loading = true;
|
|
270
|
+
if (this.hasAttribute("loaded")) {
|
|
271
|
+
this.removeAttribute("loaded");
|
|
272
|
+
}
|
|
273
|
+
this.setAttribute("loading", "");
|
|
274
|
+
this.dispatchEvent(
|
|
275
|
+
new CustomEvent("scene-loading", {
|
|
276
|
+
bubbles: true,
|
|
277
|
+
cancelable: false,
|
|
278
|
+
composed: true,
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
this.#engine.stopRenderLoop(this.#renderLoop);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async #setStatusLoaded() {
|
|
285
|
+
const toLoadDetail = {
|
|
286
|
+
container_model: !!this.#data.containers.model.changed.pending,
|
|
287
|
+
container_environment: !!this.#data.containers.environment.changed.pending,
|
|
288
|
+
container_materials: !!this.#data.containers.materials.changed.pending,
|
|
289
|
+
options_camera: !!this.#data.options.camera.changed.pending,
|
|
290
|
+
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.pending,
|
|
291
|
+
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.pending,
|
|
292
|
+
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.pending,
|
|
293
|
+
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.pending,
|
|
294
|
+
};
|
|
295
|
+
const loadedDetail = {
|
|
296
|
+
container_model: !!this.#data.containers.model.changed.success,
|
|
297
|
+
container_environment: !!this.#data.containers.environment.changed.success,
|
|
298
|
+
container_materials: !!this.#data.containers.materials.changed.success,
|
|
299
|
+
options_camera: !!this.#data.options.camera.changed.success,
|
|
300
|
+
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.success,
|
|
301
|
+
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.success,
|
|
302
|
+
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.success,
|
|
303
|
+
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.success,
|
|
304
|
+
};
|
|
305
|
+
const detail = {
|
|
306
|
+
tried: toLoadDetail,
|
|
307
|
+
success: loadedDetail,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
this.dispatchEvent(
|
|
311
|
+
new CustomEvent("scene-loaded", {
|
|
312
|
+
bubbles: true,
|
|
313
|
+
cancelable: false,
|
|
314
|
+
composed: true,
|
|
315
|
+
detail: detail,
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
await this.#scene.whenReadyAsync();
|
|
320
|
+
this.#engine.runRenderLoop(this.#renderLoop);
|
|
321
|
+
|
|
322
|
+
this.#resetChangedFlags();
|
|
323
|
+
|
|
324
|
+
if (this.hasAttribute("loading")) {
|
|
325
|
+
this.removeAttribute("loading");
|
|
326
|
+
}
|
|
327
|
+
this.setAttribute("loaded", "");
|
|
328
|
+
|
|
329
|
+
this.loaded = true;
|
|
330
|
+
this.loading = false;
|
|
331
|
+
|
|
332
|
+
this.#processNextTask();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Data
|
|
336
|
+
#checkCameraChanged(options) {
|
|
337
|
+
if (!options || !options.camera) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
const prev = this.#data.options.camera.value;
|
|
341
|
+
const changed = options.camera !== prev;
|
|
342
|
+
|
|
343
|
+
this.#data.options.camera.changed.pending = changed;
|
|
344
|
+
this.#data.options.camera.changed.success = false;
|
|
345
|
+
if (changed) {
|
|
346
|
+
this.#data.options.camera.changed.value = prev;
|
|
347
|
+
this.#data.options.camera.changed.locked = this.#data.options.camera.locked;
|
|
348
|
+
this.#data.options.camera.value = options.camera;
|
|
349
|
+
}
|
|
350
|
+
return changed;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
#checkMaterialsChanged(options) {
|
|
354
|
+
if (!options) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
let someChanged = false;
|
|
358
|
+
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
359
|
+
const key = `${material}Material`;
|
|
360
|
+
const state = this.#data.options.materials[material];
|
|
361
|
+
const prev = state.value;
|
|
362
|
+
const incoming = options[key];
|
|
363
|
+
const changed = !!incoming && incoming !== prev;
|
|
364
|
+
|
|
365
|
+
state.changed.pending = changed;
|
|
366
|
+
state.changed.success = false;
|
|
367
|
+
if (changed) {
|
|
368
|
+
state.changed.value = prev;
|
|
369
|
+
state.value = incoming;
|
|
370
|
+
}
|
|
371
|
+
someChanged = someChanged || changed;
|
|
372
|
+
});
|
|
373
|
+
return someChanged;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#storeChangedFlagsForContainer(container, success) {
|
|
377
|
+
if (success) {
|
|
378
|
+
container.timeStamp = container.changed.timeStamp;
|
|
379
|
+
container.size = container.changed.size;
|
|
380
|
+
container.changed.success = true;
|
|
381
|
+
} else {
|
|
382
|
+
container.changed.success = false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#resetChangedFlags() {
|
|
387
|
+
const reset = (node) => {
|
|
388
|
+
node.changed = { pending: false, success: false };
|
|
389
|
+
};
|
|
390
|
+
Object.values(this.#data.containers).forEach(reset);
|
|
391
|
+
Object.values(this.#data.options.materials).forEach(reset);
|
|
392
|
+
reset(this.#data.options.camera);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Babylon.js
|
|
179
396
|
async #initializeBabylon() {
|
|
180
397
|
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
398
|
+
this.#engine.disableUniformBuffers = true;
|
|
181
399
|
this.#scene = new Scene(this.#engine);
|
|
182
400
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
183
401
|
this.#createCamera();
|
|
184
402
|
this.#createLights();
|
|
185
403
|
this.#setupInteraction();
|
|
186
|
-
|
|
187
|
-
this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
|
|
188
|
-
this.#canvasResizeObserver.observe(this.#canvas);
|
|
189
|
-
|
|
190
404
|
await this.#createXRExperience();
|
|
191
|
-
|
|
405
|
+
this.#engine.runRenderLoop(this.#renderLoop);
|
|
406
|
+
this.#canvasResizeObserver.observe(this.#canvas);
|
|
192
407
|
}
|
|
193
408
|
|
|
194
|
-
|
|
409
|
+
// If this function is defined as '#renderLoop() {}' it is not executed in 'this.#engine.runRenderLoop(this.#renderLoop)'
|
|
410
|
+
#renderLoop = () => {
|
|
411
|
+
this.#scene && this.#scene.render();
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
#addStylesToARButton() {
|
|
195
415
|
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"}';
|
|
196
416
|
const style = document.createElement("style");
|
|
197
417
|
style.appendChild(document.createTextNode(css));
|
|
@@ -202,14 +422,14 @@ class PrefViewer extends HTMLElement {
|
|
|
202
422
|
if (this.#XRExperience) {
|
|
203
423
|
return true;
|
|
204
424
|
}
|
|
205
|
-
|
|
425
|
+
|
|
206
426
|
const sessionMode = "immersive-ar";
|
|
207
427
|
const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
|
|
208
428
|
if (!sessionSupported) {
|
|
209
429
|
console.info("PrefViewer: WebXR in mode AR is not supported");
|
|
210
430
|
return false;
|
|
211
431
|
}
|
|
212
|
-
|
|
432
|
+
|
|
213
433
|
try {
|
|
214
434
|
const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
|
|
215
435
|
ground.isVisible = false;
|
|
@@ -231,7 +451,6 @@ class PrefViewer extends HTMLElement {
|
|
|
231
451
|
xrInput: this.#XRExperience.input,
|
|
232
452
|
floorMeshes: [ground],
|
|
233
453
|
timeToTeleport: 1500,
|
|
234
|
-
useMainComponentOnly: true,
|
|
235
454
|
});
|
|
236
455
|
|
|
237
456
|
this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
|
|
@@ -241,7 +460,7 @@ class PrefViewer extends HTMLElement {
|
|
|
241
460
|
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
|
|
242
461
|
});
|
|
243
462
|
|
|
244
|
-
this
|
|
463
|
+
this.#addStylesToARButton();
|
|
245
464
|
} catch (error) {
|
|
246
465
|
console.warn("PrefViewer: failed to create WebXR experience", error);
|
|
247
466
|
this.#XRExperience = null;
|
|
@@ -251,15 +470,23 @@ class PrefViewer extends HTMLElement {
|
|
|
251
470
|
#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
252
471
|
|
|
253
472
|
#createCamera() {
|
|
254
|
-
this.#camera = new ArcRotateCamera("camera", 3 * Math.PI / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
473
|
+
this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
255
474
|
this.#camera.upperBetaLimit = Math.PI * 0.48;
|
|
256
475
|
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
257
476
|
this.#camera.lowerRadiusLimit = 5;
|
|
258
477
|
this.#camera.upperRadiusLimit = 20;
|
|
478
|
+
this.#camera.metadata = { locked: false };
|
|
259
479
|
this.#camera.attachControl(this.#canvas, true);
|
|
480
|
+
this.#scene.activeCamera = this.#camera;
|
|
260
481
|
}
|
|
261
482
|
|
|
262
483
|
#createLights() {
|
|
484
|
+
this.#initEnvironmentTexture();
|
|
485
|
+
|
|
486
|
+
if (this.#scene.environmentTexture) {
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
|
|
263
490
|
// 1) Stronger ambient fill
|
|
264
491
|
this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
|
|
265
492
|
this.#hemiLight.intensity = 0.6;
|
|
@@ -269,7 +496,7 @@ class PrefViewer extends HTMLElement {
|
|
|
269
496
|
this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
|
|
270
497
|
this.#dirLight.intensity = 0.6;
|
|
271
498
|
|
|
272
|
-
// 3) Soft shadows
|
|
499
|
+
// // 3) Soft shadows
|
|
273
500
|
this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
|
|
274
501
|
this.#shadowGen.useBlurExponentialShadowMap = true;
|
|
275
502
|
this.#shadowGen.blurKernel = 16;
|
|
@@ -281,12 +508,104 @@ class PrefViewer extends HTMLElement {
|
|
|
281
508
|
this.#cameraLight.intensity = 0.3;
|
|
282
509
|
}
|
|
283
510
|
|
|
511
|
+
#initEnvironmentTexture() {
|
|
512
|
+
return false;
|
|
513
|
+
if (this.#scene.environmentTexture) {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
const hdrTextureURI = "../src/environments/noon_grass.hdr";
|
|
517
|
+
const hdrTexture = new HDRCubeTexture(hdrTextureURI, this.#scene, 128);
|
|
518
|
+
hdrTexture.gammaSpace = true;
|
|
519
|
+
hdrTexture._noMipmap = false;
|
|
520
|
+
hdrTexture.level = 2.0;
|
|
521
|
+
this.#scene.environmentTexture = hdrTexture;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
#initIBLShadows() {
|
|
525
|
+
if (!this.#scene.environmentTexture) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let createIBLShadowPipeline = function (scene) {
|
|
530
|
+
const pipeline = new IblShadowsRenderPipeline(
|
|
531
|
+
"iblShadowsPipeline",
|
|
532
|
+
scene,
|
|
533
|
+
{
|
|
534
|
+
resolutionExp: 7,
|
|
535
|
+
sampleDirections: 2,
|
|
536
|
+
ssShadowsEnabled: true,
|
|
537
|
+
shadowRemanence: 0.8,
|
|
538
|
+
triPlanarVoxelization: true,
|
|
539
|
+
shadowOpacity: 0.8,
|
|
540
|
+
},
|
|
541
|
+
[scene.activeCamera]
|
|
542
|
+
);
|
|
543
|
+
pipeline.allowDebugPasses = false;
|
|
544
|
+
pipeline.gbufferDebugEnabled = true;
|
|
545
|
+
pipeline.importanceSamplingDebugEnabled = false;
|
|
546
|
+
pipeline.voxelDebugEnabled = false;
|
|
547
|
+
pipeline.voxelDebugDisplayMip = 1;
|
|
548
|
+
pipeline.voxelDebugAxis = 2;
|
|
549
|
+
pipeline.voxelTracingDebugEnabled = false;
|
|
550
|
+
pipeline.spatialBlurPassDebugEnabled = false;
|
|
551
|
+
pipeline.accumulationPassDebugEnabled = false;
|
|
552
|
+
return pipeline;
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
let iblShadowsPipeline = createIBLShadowPipeline(this.#scene);
|
|
556
|
+
|
|
557
|
+
this.#scene.meshes.forEach((mesh) => {
|
|
558
|
+
if (mesh.id.startsWith("__root__") || mesh.name === "hdri") {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
iblShadowsPipeline.addShadowCastingMesh(mesh);
|
|
562
|
+
iblShadowsPipeline.updateSceneBounds();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
this.#scene.materials.forEach((material) => {
|
|
566
|
+
iblShadowsPipeline.addShadowReceivingMaterial(material);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
#initShadows() {
|
|
571
|
+
if (!this.#scene.environmentTexture) {
|
|
572
|
+
this.#initIBLShadows();
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
this.#scene.meshes.forEach((mesh) => {
|
|
577
|
+
if (mesh.id.startsWith("__root__")) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
mesh.receiveShadows = true;
|
|
581
|
+
if (!mesh.name === "hdri") {
|
|
582
|
+
this.#shadowGen.addShadowCaster(mesh, true);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
#setMaxSimultaneousLights() {
|
|
588
|
+
let lightsNumber = 1; // Como mínimo una luz correspondiente a la textura de environmentTexture
|
|
589
|
+
this.#scene.lights.forEach((light) => {
|
|
590
|
+
if (light.isEnabled()) {
|
|
591
|
+
++lightsNumber;
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
if (this.#scene.materials) {
|
|
595
|
+
this.#scene.materials.forEach((material) => (material.maxSimultaneousLights = lightsNumber));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
284
599
|
#setupInteraction() {
|
|
285
600
|
this.#canvas.addEventListener("wheel", (event) => {
|
|
286
|
-
if (!this.#scene || !this.#camera)
|
|
287
|
-
|
|
601
|
+
if (!this.#scene || !this.#camera) {
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
288
605
|
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
289
|
-
this.#
|
|
606
|
+
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
607
|
+
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
608
|
+
}
|
|
290
609
|
event.preventDefault();
|
|
291
610
|
});
|
|
292
611
|
}
|
|
@@ -300,6 +619,27 @@ class PrefViewer extends HTMLElement {
|
|
|
300
619
|
}
|
|
301
620
|
|
|
302
621
|
// Utility methods for loading gltf/glb
|
|
622
|
+
async #getServerFileDataHeader(uri) {
|
|
623
|
+
return new Promise((resolve) => {
|
|
624
|
+
const xhr = new XMLHttpRequest();
|
|
625
|
+
xhr.open("HEAD", uri, true);
|
|
626
|
+
xhr.responseType = "blob";
|
|
627
|
+
xhr.onload = () => {
|
|
628
|
+
if (xhr.status === 200) {
|
|
629
|
+
const size = parseInt(xhr.getResponseHeader("Content-Length"));
|
|
630
|
+
const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
|
|
631
|
+
resolve([size, timeStamp]);
|
|
632
|
+
} else {
|
|
633
|
+
resolve([0, null]);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
xhr.onerror = () => {
|
|
637
|
+
resolve([0, null]);
|
|
638
|
+
};
|
|
639
|
+
xhr.send();
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
303
643
|
#transformUrl(url) {
|
|
304
644
|
return new Promise((resolve) => {
|
|
305
645
|
resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
|
|
@@ -312,21 +652,22 @@ class PrefViewer extends HTMLElement {
|
|
|
312
652
|
let decoded = "";
|
|
313
653
|
let blob = null;
|
|
314
654
|
let extension = null;
|
|
655
|
+
let size = raw.length;
|
|
315
656
|
try {
|
|
316
657
|
decoded = atob(raw);
|
|
317
658
|
} catch {
|
|
318
|
-
return { blob, extension };
|
|
659
|
+
return { blob, extension, size };
|
|
319
660
|
}
|
|
320
661
|
let isJson = false;
|
|
321
662
|
try {
|
|
322
663
|
JSON.parse(decoded);
|
|
323
664
|
isJson = true;
|
|
324
|
-
} catch {}
|
|
665
|
+
} catch { }
|
|
325
666
|
extension = isJson ? ".gltf" : ".glb";
|
|
326
667
|
const type = isJson ? "model/gltf+json" : "model/gltf-binary";
|
|
327
668
|
const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
|
|
328
669
|
blob = new Blob([array], { type });
|
|
329
|
-
return { blob, extension };
|
|
670
|
+
return { blob, extension, size };
|
|
330
671
|
}
|
|
331
672
|
|
|
332
673
|
async #initStorage(db, table) {
|
|
@@ -338,41 +679,133 @@ class PrefViewer extends HTMLElement {
|
|
|
338
679
|
|
|
339
680
|
// Methods for managing Asset Containers
|
|
340
681
|
#setVisibilityOfWallAndFloorInModel(show) {
|
|
341
|
-
if (this.#model.
|
|
342
|
-
|
|
343
|
-
const nodes = this.#model.container.getNodes();
|
|
344
|
-
this.#model.container
|
|
345
|
-
.getNodes()
|
|
346
|
-
.filter((filter) => names.includes(filter.name))
|
|
347
|
-
.forEach((node) => node.setEnabled(show !== undefined ? show : this.#environment.show));
|
|
682
|
+
if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
|
|
683
|
+
return false;
|
|
348
684
|
}
|
|
685
|
+
show = show !== undefined ? show : this.#data.containers.environment.visible;
|
|
686
|
+
const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
|
|
687
|
+
this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
|
|
349
688
|
}
|
|
350
689
|
|
|
351
|
-
#
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
group.visible = true;
|
|
690
|
+
#setOptionsMaterial(optionMaterial) {
|
|
691
|
+
if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
|
|
692
|
+
return false;
|
|
355
693
|
}
|
|
356
|
-
}
|
|
357
694
|
|
|
358
|
-
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
group.visible = false;
|
|
695
|
+
const material = this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) || null;
|
|
696
|
+
if (!material) {
|
|
697
|
+
return false;
|
|
362
698
|
}
|
|
699
|
+
|
|
700
|
+
const containers = [];
|
|
701
|
+
if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
|
|
702
|
+
containers.push(this.#data.containers.model.assetContainer);
|
|
703
|
+
}
|
|
704
|
+
if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
|
|
705
|
+
containers.push(this.#data.containers.environment.assetContainer);
|
|
706
|
+
}
|
|
707
|
+
if (containers.length === 0) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
let someSetted = false;
|
|
712
|
+
containers.forEach((container) =>
|
|
713
|
+
container.meshes
|
|
714
|
+
.filter((meshToFilter) => meshToFilter.name.startsWith(optionMaterial.prefix))
|
|
715
|
+
.forEach((mesh) => {
|
|
716
|
+
mesh.material = material;
|
|
717
|
+
someSetted = true;
|
|
718
|
+
})
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
if (someSetted) {
|
|
722
|
+
optionMaterial.changed.success = true;
|
|
723
|
+
} else if (optionMaterial.changed.pending) {
|
|
724
|
+
optionMaterial.value = optionMaterial.changed.value;
|
|
725
|
+
optionMaterial.changed.success = false;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return someSetted;
|
|
363
729
|
}
|
|
364
730
|
|
|
365
|
-
#
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.#shadowGen.addShadowCaster(mesh, true);
|
|
731
|
+
#setOptionsMaterials() {
|
|
732
|
+
let someSetted = false;
|
|
733
|
+
Object.values(this.#data.options.materials).forEach((material) => {
|
|
734
|
+
let settedMaterial = this.#setOptionsMaterial(material);
|
|
735
|
+
someSetted = someSetted || settedMaterial;
|
|
371
736
|
});
|
|
372
|
-
|
|
737
|
+
return someSetted;
|
|
373
738
|
}
|
|
374
739
|
|
|
375
|
-
|
|
740
|
+
#setOptionsCamera() {
|
|
741
|
+
if (!this.#data.options.camera.value && !this.#data.options.camera.changed.pending && !this.#data.containers.model.changed.pending && !this.#data.containers.environment.changed.pending) {
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
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;
|
|
746
|
+
if (!camera) {
|
|
747
|
+
if (this.#data.options.camera.changed.value && this.#data.options.camera.changed.value !== this.#data.options.camera.value) {
|
|
748
|
+
camera = this.#data.containers.model.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.changed.value) || this.#data.containers.environment.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.changed.value) || null;
|
|
749
|
+
}
|
|
750
|
+
if (camera) {
|
|
751
|
+
camera.metadata = { locked: this.#data.options.camera.changed.locked };
|
|
752
|
+
this.#data.options.camera.value = this.#data.options.camera.changed.value;
|
|
753
|
+
this.#data.options.camera.locked = this.#data.options.camera.changed.locked;
|
|
754
|
+
this.#data.options.camera.changed.success = false;
|
|
755
|
+
} else {
|
|
756
|
+
camera = this.#camera;
|
|
757
|
+
this.#data.options.camera.value = null;
|
|
758
|
+
this.#data.options.camera.locked = this.#camera.metadata.locked;
|
|
759
|
+
this.#data.options.camera.changed.success = false;
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
763
|
+
if (this.#data.options.camera.changed.pending) {
|
|
764
|
+
this.#data.options.camera.changed.success = true;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
|
|
768
|
+
camera.attachControl(this.#canvas, true);
|
|
769
|
+
}
|
|
770
|
+
this.#scene.activeCamera = camera;
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
#addContainer(container) {
|
|
775
|
+
if (!container.assetContainer || container.visible || !container.show) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
container.assetContainer.addAllToScene();
|
|
780
|
+
container.visible = true;
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
#removeContainer(container) {
|
|
785
|
+
if (!container.assetContainer || !container.visible) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
container.assetContainer.removeAllFromScene();
|
|
790
|
+
container.visible = false;
|
|
791
|
+
return true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
#replaceContainer(container, newAssetContainer) {
|
|
795
|
+
if (container.assetContainer) {
|
|
796
|
+
this.#removeContainer(container);
|
|
797
|
+
container.assetContainer.dispose();
|
|
798
|
+
container.assetContainer = null;
|
|
799
|
+
}
|
|
800
|
+
this.#scene.getEngine().releaseEffects();
|
|
801
|
+
container.assetContainer = newAssetContainer;
|
|
802
|
+
this.#addContainer(container);
|
|
803
|
+
return true;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async #loadAssetContainer(container) {
|
|
807
|
+
let storage = container?.storage;
|
|
808
|
+
|
|
376
809
|
if (!storage) {
|
|
377
810
|
return false;
|
|
378
811
|
}
|
|
@@ -383,6 +816,12 @@ class PrefViewer extends HTMLElement {
|
|
|
383
816
|
await this.#initStorage(storage.db, storage.table);
|
|
384
817
|
const object = await loadModel(storage.id, storage.table);
|
|
385
818
|
source = object.data;
|
|
819
|
+
if (object.timeStamp === container.timeStamp) {
|
|
820
|
+
container.changed = { pending: false, success: false };
|
|
821
|
+
return false;
|
|
822
|
+
} else {
|
|
823
|
+
container.changed = { pending: true, size: object.size, success: false, timeStamp: object.timeStamp };
|
|
824
|
+
}
|
|
386
825
|
}
|
|
387
826
|
|
|
388
827
|
if (!source) {
|
|
@@ -391,20 +830,39 @@ class PrefViewer extends HTMLElement {
|
|
|
391
830
|
|
|
392
831
|
let file = null;
|
|
393
832
|
|
|
394
|
-
let { blob, extension } = this.#decodeBase64(source);
|
|
833
|
+
let { blob, extension, size } = this.#decodeBase64(source);
|
|
395
834
|
if (blob && extension) {
|
|
396
|
-
file = new File([blob],
|
|
835
|
+
file = new File([blob], `${container.name}${extension}`, {
|
|
397
836
|
type: blob.type,
|
|
398
837
|
});
|
|
838
|
+
if (!container.changed.pending) {
|
|
839
|
+
if (container.timeStamp === null && container.size === size) {
|
|
840
|
+
container.changed = { pending: false, success: false };
|
|
841
|
+
return false;
|
|
842
|
+
} else {
|
|
843
|
+
container.changed = { pending: true, size: size, success: false, timeStamp: null };
|
|
844
|
+
}
|
|
845
|
+
}
|
|
399
846
|
} else {
|
|
400
847
|
const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
|
|
401
848
|
extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
|
|
849
|
+
const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
|
|
850
|
+
if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
|
|
851
|
+
container.changed = { pending: false, success: false };
|
|
852
|
+
return false;
|
|
853
|
+
} else {
|
|
854
|
+
container.changed = { pending: true, size: fileSize, success: false, timeStamp: fileTimeStamp };
|
|
855
|
+
}
|
|
402
856
|
}
|
|
403
857
|
|
|
858
|
+
// https://doc.babylonjs.com/typedoc/interfaces/BABYLON.LoadAssetContainerOptions
|
|
404
859
|
let options = {
|
|
405
860
|
pluginExtension: extension,
|
|
406
861
|
pluginOptions: {
|
|
407
862
|
gltf: {
|
|
863
|
+
compileMaterials: true,
|
|
864
|
+
loadAllMaterials: true,
|
|
865
|
+
loadOnlyMaterials: container.name === "materials",
|
|
408
866
|
preprocessUrlAsync: this.#transformUrl,
|
|
409
867
|
},
|
|
410
868
|
},
|
|
@@ -413,58 +871,198 @@ class PrefViewer extends HTMLElement {
|
|
|
413
871
|
return LoadAssetContainerAsync(file || source, this.#scene, options);
|
|
414
872
|
}
|
|
415
873
|
|
|
416
|
-
async #loadContainers(loadModel = true, loadEnvironment = true) {
|
|
417
|
-
|
|
874
|
+
async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
|
|
875
|
+
this.#engine.stopRenderLoop(this.#renderLoop);
|
|
418
876
|
|
|
419
|
-
promiseArray
|
|
420
|
-
promiseArray.push(
|
|
877
|
+
const promiseArray = [];
|
|
878
|
+
promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
|
|
879
|
+
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
|
|
880
|
+
promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
|
|
421
881
|
|
|
422
882
|
Promise.allSettled(promiseArray)
|
|
423
|
-
.then(
|
|
883
|
+
.then((values) => {
|
|
424
884
|
const modelContainer = values[0];
|
|
425
885
|
const environmentContainer = values[1];
|
|
886
|
+
const materialsContainer = values[2];
|
|
426
887
|
|
|
427
888
|
if (modelContainer.status === "fulfilled" && modelContainer.value) {
|
|
428
|
-
|
|
889
|
+
modelContainer.value.lights = [];
|
|
890
|
+
this.#replaceContainer(this.#data.containers.model, modelContainer.value);
|
|
891
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.model, true);
|
|
892
|
+
} else {
|
|
893
|
+
if (this.#data.containers.model.assetContainer && this.#data.containers.model.show !== this.#data.containers.model.visible) {
|
|
894
|
+
this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
|
|
895
|
+
}
|
|
896
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.model, false);
|
|
429
897
|
}
|
|
430
898
|
|
|
431
899
|
if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
|
|
432
|
-
this.#replaceContainer(this.#environment, environmentContainer.value);
|
|
900
|
+
this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
|
|
901
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.environment, true);
|
|
902
|
+
} else {
|
|
903
|
+
if (this.#data.containers.environment.assetContainer && this.#data.containers.environment.show !== this.#data.containers.environment.visible) {
|
|
904
|
+
this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
|
|
905
|
+
}
|
|
906
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.environment, false);
|
|
433
907
|
}
|
|
434
908
|
|
|
909
|
+
if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
|
|
910
|
+
this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
|
|
911
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.materials, true);
|
|
912
|
+
} else {
|
|
913
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.materials, false);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
this.#setOptionsMaterials();
|
|
917
|
+
this.#setOptionsCamera();
|
|
435
918
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
436
|
-
|
|
437
|
-
this.dispatchEvent(
|
|
438
|
-
new CustomEvent("model-loaded", {
|
|
439
|
-
detail: { success: "" },
|
|
440
|
-
bubbles: true,
|
|
441
|
-
composed: true,
|
|
442
|
-
})
|
|
443
|
-
);
|
|
444
919
|
})
|
|
445
920
|
.catch((error) => {
|
|
921
|
+
this.loaded = true;
|
|
446
922
|
console.error("PrefViewer: failed to load model", error);
|
|
447
923
|
this.dispatchEvent(
|
|
448
|
-
new CustomEvent("
|
|
449
|
-
detail: { error: error },
|
|
924
|
+
new CustomEvent("scene-error", {
|
|
450
925
|
bubbles: true,
|
|
926
|
+
cancelable: false,
|
|
451
927
|
composed: true,
|
|
928
|
+
detail: { error: error },
|
|
452
929
|
})
|
|
453
930
|
);
|
|
931
|
+
})
|
|
932
|
+
.finally(async () => {
|
|
933
|
+
this.#setMaxSimultaneousLights();
|
|
934
|
+
this.#initShadows();
|
|
935
|
+
await this.#setStatusLoaded();
|
|
454
936
|
});
|
|
455
937
|
}
|
|
456
938
|
|
|
939
|
+
// Tasks
|
|
940
|
+
#addTaskToQueue(value, type) {
|
|
941
|
+
this.#taskQueue.push(new PrefViewerTask(value, type));
|
|
942
|
+
if (this.initialized && !this.loading) {
|
|
943
|
+
this.#processNextTask();
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
#processNextTask() {
|
|
948
|
+
if (!this.#taskQueue.length) {
|
|
949
|
+
return false;
|
|
950
|
+
}
|
|
951
|
+
const task = this.#taskQueue[0];
|
|
952
|
+
this.#taskQueue.shift();
|
|
953
|
+
switch (task.type) {
|
|
954
|
+
case PrefViewerTask.Types.Config:
|
|
955
|
+
this.#processConfig(task.value);
|
|
956
|
+
break;
|
|
957
|
+
case PrefViewerTask.Types.Model:
|
|
958
|
+
this.#processModel(task.value);
|
|
959
|
+
break;
|
|
960
|
+
case PrefViewerTask.Types.Environment:
|
|
961
|
+
this.#processEnvironment(task.value);
|
|
962
|
+
break;
|
|
963
|
+
case PrefViewerTask.Types.Materials:
|
|
964
|
+
this.#processMaterials(task.value);
|
|
965
|
+
break;
|
|
966
|
+
case PrefViewerTask.Types.Options:
|
|
967
|
+
this.#processOptions(task.value);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
#processConfig(config) {
|
|
973
|
+
this.#setStatusLoading();
|
|
974
|
+
|
|
975
|
+
// Containers
|
|
976
|
+
const loadModel = !!config.model?.storage;
|
|
977
|
+
this.#data.containers.model.changed.pending = loadModel;
|
|
978
|
+
this.#data.containers.model.changed.success = false;
|
|
979
|
+
this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
|
|
980
|
+
this.#data.containers.model.storage = loadModel ? config.model.storage : this.#data.containers.model.storage;
|
|
981
|
+
this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
|
|
982
|
+
|
|
983
|
+
const loadEnvironment = !!config.scene?.storage;
|
|
984
|
+
this.#data.containers.environment.changed.pending = loadEnvironment;
|
|
985
|
+
this.#data.containers.environment.changed.success = false;
|
|
986
|
+
this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
|
|
987
|
+
this.#data.containers.environment.storage = loadEnvironment ? config.scene.storage : this.#data.containers.environment.storage;
|
|
988
|
+
this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
|
|
989
|
+
|
|
990
|
+
const loadMaterials = !!config.materials?.storage;
|
|
991
|
+
this.#data.containers.materials.changed.pending = loadMaterials;
|
|
992
|
+
this.#data.containers.materials.changed.success = false;
|
|
993
|
+
this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
|
|
994
|
+
this.#data.containers.materials.storage = loadMaterials ? config.materials.storage : this.#data.containers.materials.storage;
|
|
995
|
+
|
|
996
|
+
// Options
|
|
997
|
+
if (config.options) {
|
|
998
|
+
this.#checkCameraChanged(config.options);
|
|
999
|
+
this.#checkMaterialsChanged(config.options);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
this.#loadContainers(loadModel, loadEnvironment, loadMaterials);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
#processModel(model) {
|
|
1006
|
+
this.#setStatusLoading();
|
|
1007
|
+
|
|
1008
|
+
const loadModel = !!model.storage;
|
|
1009
|
+
this.#data.containers.model.changed.pending = loadModel;
|
|
1010
|
+
this.#data.containers.model.changed.success = false;
|
|
1011
|
+
this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
|
|
1012
|
+
this.#data.containers.model.storage = loadModel ? model.storage : this.#data.containers.model.storage;
|
|
1013
|
+
this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
1014
|
+
|
|
1015
|
+
this.initialized && this.#loadContainers(loadModel, false, false);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
#processEnvironment(environment) {
|
|
1019
|
+
this.#setStatusLoading();
|
|
1020
|
+
|
|
1021
|
+
const loadEnvironment = !!environment.storage;
|
|
1022
|
+
this.#data.containers.environment.changed.pending = loadEnvironment;
|
|
1023
|
+
this.#data.containers.environment.changed.success = false;
|
|
1024
|
+
this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
|
|
1025
|
+
this.#data.containers.environment.storage = loadEnvironment ? environment.storage : this.#data.containers.environment.storage;
|
|
1026
|
+
this.#data.containers.environment.show = environment.visible !== undefined ? environment.visible : this.#data.containers.environment.show;
|
|
1027
|
+
|
|
1028
|
+
this.#loadContainers(false, loadEnvironment, false);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
#processMaterials(materials) {
|
|
1032
|
+
this.#setStatusLoading();
|
|
1033
|
+
|
|
1034
|
+
const loadMaterials = !!materials.storage;
|
|
1035
|
+
this.#data.containers.materials.changed.pending = loadMaterials;
|
|
1036
|
+
this.#data.containers.materials.changed.success = false;
|
|
1037
|
+
this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
|
|
1038
|
+
this.#data.containers.materials.storage = loadMaterials ? materials.storage : this.#data.containers.materials.storage;
|
|
1039
|
+
|
|
1040
|
+
this.#loadContainers(false, false, loadMaterials);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
async #processOptions(options) {
|
|
1044
|
+
this.#setStatusLoading();
|
|
1045
|
+
|
|
1046
|
+
let someSetted = false;
|
|
1047
|
+
if (this.#checkCameraChanged(options)) {
|
|
1048
|
+
someSetted = someSetted || this.#setOptionsCamera();
|
|
1049
|
+
}
|
|
1050
|
+
if (this.#checkMaterialsChanged(options)) {
|
|
1051
|
+
someSetted = someSetted || this.#setOptionsMaterials();
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
await this.#setStatusLoaded();
|
|
1055
|
+
|
|
1056
|
+
return someSetted;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
457
1059
|
// Public Methods
|
|
458
1060
|
loadConfig(config) {
|
|
459
1061
|
config = typeof config === "string" ? JSON.parse(config) : config;
|
|
460
1062
|
if (!config) {
|
|
461
1063
|
return false;
|
|
462
1064
|
}
|
|
463
|
-
this.#
|
|
464
|
-
this.#model.show = config.model?.visible !== undefined ? config.model.visible : this.#model.show;
|
|
465
|
-
this.#environment.storage = config.scene?.storage || null;
|
|
466
|
-
this.#environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#environment.show;
|
|
467
|
-
this.#initialized && this.#loadContainers(true, true);
|
|
1065
|
+
this.#addTaskToQueue(config, "config");
|
|
468
1066
|
}
|
|
469
1067
|
|
|
470
1068
|
loadModel(model) {
|
|
@@ -472,9 +1070,7 @@ class PrefViewer extends HTMLElement {
|
|
|
472
1070
|
if (!model) {
|
|
473
1071
|
return false;
|
|
474
1072
|
}
|
|
475
|
-
this.#model
|
|
476
|
-
this.#model.show = model.visible !== undefined ? model.visible : this.#model.show;
|
|
477
|
-
this.#initialized && this.#loadContainers(true, false);
|
|
1073
|
+
this.#addTaskToQueue(model, "model");
|
|
478
1074
|
}
|
|
479
1075
|
|
|
480
1076
|
loadScene(scene) {
|
|
@@ -482,43 +1078,55 @@ class PrefViewer extends HTMLElement {
|
|
|
482
1078
|
if (!scene) {
|
|
483
1079
|
return false;
|
|
484
1080
|
}
|
|
485
|
-
this.#
|
|
486
|
-
|
|
487
|
-
|
|
1081
|
+
this.#addTaskToQueue(scene, "environment");
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
loadMaterials(materials) {
|
|
1085
|
+
materials = typeof materials === "string" ? JSON.parse(materials) : materials;
|
|
1086
|
+
if (!materials) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
this.#addTaskToQueue(materials, "materials");
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
setOptions(options) {
|
|
1093
|
+
options = typeof options === "string" ? JSON.parse(options) : options;
|
|
1094
|
+
if (!options) {
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
this.#addTaskToQueue(options, "options");
|
|
488
1098
|
}
|
|
489
1099
|
|
|
490
1100
|
showModel() {
|
|
491
|
-
this.#model.show = true;
|
|
492
|
-
this.#addContainer(this.#model);
|
|
1101
|
+
this.#data.containers.model.show = true;
|
|
1102
|
+
this.#addContainer(this.#data.containers.model);
|
|
493
1103
|
}
|
|
494
1104
|
|
|
495
1105
|
hideModel() {
|
|
496
|
-
this.#model.show = false;
|
|
497
|
-
this.#removeContainer(this.#model);
|
|
1106
|
+
this.#data.containers.model.show = false;
|
|
1107
|
+
this.#removeContainer(this.#data.containers.model);
|
|
498
1108
|
}
|
|
499
1109
|
|
|
500
1110
|
showScene() {
|
|
501
|
-
this.#environment.show = true;
|
|
502
|
-
this.#addContainer(this.#environment);
|
|
1111
|
+
this.#data.containers.environment.show = true;
|
|
1112
|
+
this.#addContainer(this.#data.containers.environment);
|
|
503
1113
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
504
1114
|
}
|
|
505
1115
|
|
|
506
1116
|
hideScene() {
|
|
507
|
-
this.#environment.show = false;
|
|
508
|
-
this.#removeContainer(this.#environment);
|
|
1117
|
+
this.#data.containers.environment.show = false;
|
|
1118
|
+
this.#removeContainer(this.#data.containers.environment);
|
|
509
1119
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
510
1120
|
}
|
|
511
1121
|
|
|
512
1122
|
downloadModelGLB() {
|
|
513
1123
|
const fileName = "model";
|
|
514
|
-
GLTF2Export.GLBAsync(this.#model.
|
|
515
|
-
glb.downloadFiles();
|
|
516
|
-
});
|
|
1124
|
+
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
517
1125
|
}
|
|
518
1126
|
|
|
519
1127
|
downloadModelUSDZ() {
|
|
520
1128
|
const fileName = "model";
|
|
521
|
-
USDZExportAsync(this.#model.
|
|
1129
|
+
USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
|
|
522
1130
|
if (response) {
|
|
523
1131
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
524
1132
|
}
|
|
@@ -536,9 +1144,7 @@ class PrefViewer extends HTMLElement {
|
|
|
536
1144
|
|
|
537
1145
|
downloadModelAndSceneGLB() {
|
|
538
1146
|
const fileName = "scene";
|
|
539
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) =>
|
|
540
|
-
glb.downloadFiles();
|
|
541
|
-
});
|
|
1147
|
+
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
542
1148
|
}
|
|
543
1149
|
}
|
|
544
1150
|
|