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