@preference-sl/pref-viewer 2.10.0-beta.20 → 2.10.0-beta.21
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 +1 -1
- package/src/index.js +252 -82
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -39,7 +39,23 @@
|
|
|
39
39
|
* </pref-viewer>
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
Engine,
|
|
44
|
+
Scene,
|
|
45
|
+
ArcRotateCamera,
|
|
46
|
+
Vector3,
|
|
47
|
+
Color4,
|
|
48
|
+
HemisphericLight,
|
|
49
|
+
DirectionalLight,
|
|
50
|
+
PointLight,
|
|
51
|
+
ShadowGenerator,
|
|
52
|
+
LoadAssetContainerAsync,
|
|
53
|
+
Tools,
|
|
54
|
+
WebXRSessionManager,
|
|
55
|
+
WebXRDefaultExperience,
|
|
56
|
+
MeshBuilder,
|
|
57
|
+
WebXRFeatureName,
|
|
58
|
+
} from "@babylonjs/core";
|
|
43
59
|
import "@babylonjs/loaders";
|
|
44
60
|
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
45
61
|
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
@@ -129,6 +145,38 @@ class PrefViewer extends HTMLElement {
|
|
|
129
145
|
#shadowGen = null;
|
|
130
146
|
#XRExperience = null;
|
|
131
147
|
|
|
148
|
+
// -------------------------
|
|
149
|
+
// Logger util + timers
|
|
150
|
+
// -------------------------
|
|
151
|
+
#LOG_LEVEL = (window?.PREFV_LOG_LEVEL ?? "debug"); // 'debug' | 'info' | 'warn' | 'error'
|
|
152
|
+
#timeMarks = new Map();
|
|
153
|
+
|
|
154
|
+
#log(level, msg, extra) {
|
|
155
|
+
const order = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
156
|
+
const cur = order[this.#LOG_LEVEL] ?? 0;
|
|
157
|
+
const now = new Date().toISOString();
|
|
158
|
+
if ((order[level] ?? 0) < cur) return;
|
|
159
|
+
const line = `[PrefViewer][${level.toUpperCase()}][${now}] ${msg}`;
|
|
160
|
+
try {
|
|
161
|
+
if (extra !== undefined) console[level](line, extra);
|
|
162
|
+
else console[level](line);
|
|
163
|
+
} catch {
|
|
164
|
+
console.log(line, extra);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
#timeStart(label) {
|
|
168
|
+
this.#timeMarks.set(label, performance.now());
|
|
169
|
+
this.#log("debug", `⏱️ start ${label}`);
|
|
170
|
+
}
|
|
171
|
+
#timeEnd(label) {
|
|
172
|
+
const t0 = this.#timeMarks.get(label);
|
|
173
|
+
if (t0 !== undefined) {
|
|
174
|
+
const dt = (performance.now() - t0).toFixed(1);
|
|
175
|
+
this.#log("debug", `⏱️ end ${label} +${dt}ms`);
|
|
176
|
+
this.#timeMarks.delete(label);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
132
180
|
constructor() {
|
|
133
181
|
super();
|
|
134
182
|
this.attachShadow({ mode: "open" });
|
|
@@ -151,6 +199,7 @@ class PrefViewer extends HTMLElement {
|
|
|
151
199
|
}
|
|
152
200
|
|
|
153
201
|
attributeChangedCallback(name, _old, value) {
|
|
202
|
+
this.#log("debug", `attributeChangedCallback: ${name}`, value);
|
|
154
203
|
let data = null;
|
|
155
204
|
switch (name) {
|
|
156
205
|
case "config":
|
|
@@ -182,8 +231,10 @@ class PrefViewer extends HTMLElement {
|
|
|
182
231
|
}
|
|
183
232
|
|
|
184
233
|
connectedCallback() {
|
|
234
|
+
this.#log("info", "connectedCallback");
|
|
185
235
|
if (!this.hasAttribute("config")) {
|
|
186
236
|
const error = 'PrefViewer: provide "models" as array of model and environment';
|
|
237
|
+
this.#log("error", error);
|
|
187
238
|
console.error(error);
|
|
188
239
|
this.dispatchEvent(
|
|
189
240
|
new CustomEvent("scene-error", {
|
|
@@ -202,6 +253,7 @@ class PrefViewer extends HTMLElement {
|
|
|
202
253
|
}
|
|
203
254
|
|
|
204
255
|
disconnectedCallback() {
|
|
256
|
+
this.#log("info", "disconnectedCallback → dispose engine");
|
|
205
257
|
this.#disposeEngine();
|
|
206
258
|
this.#canvasResizeObserver.disconnect();
|
|
207
259
|
}
|
|
@@ -235,6 +287,7 @@ class PrefViewer extends HTMLElement {
|
|
|
235
287
|
this.removeAttribute("loaded");
|
|
236
288
|
}
|
|
237
289
|
this.setAttribute("loading", "");
|
|
290
|
+
this.#log("info", "Escena → loading");
|
|
238
291
|
this.dispatchEvent(
|
|
239
292
|
new CustomEvent("scene-loading", {
|
|
240
293
|
bubbles: true,
|
|
@@ -278,6 +331,7 @@ class PrefViewer extends HTMLElement {
|
|
|
278
331
|
this.removeAttribute("loading");
|
|
279
332
|
}
|
|
280
333
|
this.setAttribute("loaded", "");
|
|
334
|
+
this.#log("info", "Escena → loaded", detail);
|
|
281
335
|
this.dispatchEvent(
|
|
282
336
|
new CustomEvent("scene-loaded", {
|
|
283
337
|
bubbles: true,
|
|
@@ -289,6 +343,7 @@ class PrefViewer extends HTMLElement {
|
|
|
289
343
|
}
|
|
290
344
|
|
|
291
345
|
#setStatusOptionsLoading() {
|
|
346
|
+
this.#log("info", "Opciones → loading");
|
|
292
347
|
this.dispatchEvent(
|
|
293
348
|
new CustomEvent("options-loading", {
|
|
294
349
|
bubbles: true,
|
|
@@ -317,6 +372,7 @@ class PrefViewer extends HTMLElement {
|
|
|
317
372
|
success: loadedDetail,
|
|
318
373
|
};
|
|
319
374
|
|
|
375
|
+
this.#log("info", "Opciones → loaded", detail);
|
|
320
376
|
this.dispatchEvent(
|
|
321
377
|
new CustomEvent("options-loaded", {
|
|
322
378
|
bubbles: true,
|
|
@@ -332,26 +388,31 @@ class PrefViewer extends HTMLElement {
|
|
|
332
388
|
if (!options || !options.camera) {
|
|
333
389
|
return false;
|
|
334
390
|
}
|
|
335
|
-
|
|
336
391
|
const changed = options.camera !== this.#data.options.camera.value;
|
|
337
|
-
this.#data.options.camera.changed = changed
|
|
392
|
+
this.#data.options.camera.changed = changed
|
|
393
|
+
? { oldValue: this.#data.options.camera.value, oldLocked: this.#data.options.camera.locked, success: false }
|
|
394
|
+
: false;
|
|
338
395
|
if (changed) this.#data.options.camera.value = options.camera;
|
|
339
|
-
|
|
396
|
+
this.#log("debug", `#checkCameraChanged → ${changed}`, {
|
|
397
|
+
new: this.#data.options.camera.value,
|
|
398
|
+
old: this.#data.options.camera.changed?.oldValue ?? null,
|
|
399
|
+
});
|
|
340
400
|
return changed;
|
|
341
401
|
}
|
|
342
402
|
|
|
343
403
|
#checkMaterialsChanged(options) {
|
|
344
|
-
if (!options)
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
404
|
+
if (!options) return false;
|
|
347
405
|
let someChanged = false;
|
|
348
406
|
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
349
407
|
const key = `${material}Material`;
|
|
350
408
|
const materialChanged = options[key] && options[key] !== this.#data.options.materials[material].value ? true : false;
|
|
351
|
-
this.#data.options.materials[material].changed = materialChanged
|
|
409
|
+
this.#data.options.materials[material].changed = materialChanged
|
|
410
|
+
? { oldValue: this.#data.options.materials[material].value, success: false }
|
|
411
|
+
: false;
|
|
352
412
|
this.#data.options.materials[material].value = materialChanged ? options[key] : this.#data.options.materials[material].value;
|
|
353
413
|
someChanged = someChanged || this.#data.options.materials[material].changed;
|
|
354
414
|
});
|
|
415
|
+
this.#log("debug", `#checkMaterialsChanged → ${!!someChanged}`);
|
|
355
416
|
return someChanged;
|
|
356
417
|
}
|
|
357
418
|
|
|
@@ -369,41 +430,47 @@ class PrefViewer extends HTMLElement {
|
|
|
369
430
|
|
|
370
431
|
// Babylon.js
|
|
371
432
|
async #initializeBabylon() {
|
|
433
|
+
this.#timeStart("initializeBabylon");
|
|
372
434
|
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
373
|
-
this.#engine.disableUniformBuffers = true; //
|
|
374
|
-
|
|
435
|
+
this.#engine.disableUniformBuffers = true; // evita GL_MAX_*_UNIFORM_BUFFERS (workaround)
|
|
375
436
|
this.#scene = new Scene(this.#engine);
|
|
376
437
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
377
438
|
this.#createCamera();
|
|
378
|
-
this.#createLights();
|
|
439
|
+
this.#createLights(); // luces desactivadas
|
|
379
440
|
this.#setupInteraction();
|
|
380
441
|
|
|
442
|
+
this.#log("info", "Engine y Scene creados", {
|
|
443
|
+
webgl: this.#engine.webGLVersion,
|
|
444
|
+
disableUBO: this.#engine.disableUniformBuffers,
|
|
445
|
+
});
|
|
446
|
+
|
|
381
447
|
this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
|
|
382
448
|
this.#canvasResizeObserver.observe(this.#canvas);
|
|
383
449
|
|
|
384
450
|
await this.#createXRExperience();
|
|
451
|
+
this.#timeEnd("initializeBabylon");
|
|
385
452
|
}
|
|
386
453
|
|
|
387
454
|
addStylesToARButton() {
|
|
388
|
-
const css =
|
|
455
|
+
const css =
|
|
456
|
+
'.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"}';
|
|
389
457
|
const style = document.createElement("style");
|
|
390
458
|
style.appendChild(document.createTextNode(css));
|
|
391
459
|
this.#wrapper.appendChild(style);
|
|
392
460
|
}
|
|
393
461
|
|
|
394
462
|
async #createXRExperience() {
|
|
395
|
-
if (this.#XRExperience)
|
|
396
|
-
return true;
|
|
397
|
-
}
|
|
463
|
+
if (this.#XRExperience) return true;
|
|
398
464
|
|
|
399
465
|
const sessionMode = "immersive-ar";
|
|
400
466
|
const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
|
|
401
467
|
if (!sessionSupported) {
|
|
402
|
-
|
|
468
|
+
this.#log("info", `WebXR no soportado para ${sessionMode}`);
|
|
403
469
|
return false;
|
|
404
470
|
}
|
|
405
471
|
|
|
406
472
|
try {
|
|
473
|
+
this.#log("debug", "Creando XR DefaultExperience…");
|
|
407
474
|
const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
|
|
408
475
|
ground.isVisible = false;
|
|
409
476
|
|
|
@@ -418,6 +485,7 @@ class PrefViewer extends HTMLElement {
|
|
|
418
485
|
};
|
|
419
486
|
|
|
420
487
|
this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
|
|
488
|
+
this.#log("info", "XR DefaultExperience creado");
|
|
421
489
|
|
|
422
490
|
const featuresManager = this.#XRExperience.baseExperience.featuresManager;
|
|
423
491
|
featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
|
|
@@ -428,14 +496,19 @@ class PrefViewer extends HTMLElement {
|
|
|
428
496
|
|
|
429
497
|
this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
|
|
430
498
|
// Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
|
|
431
|
-
this.#
|
|
499
|
+
this.#log("debug", "XR Ready → copiando pose inicial a xrCamera");
|
|
500
|
+
this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(
|
|
501
|
+
this.#XRExperience.baseExperience._nonVRCamera
|
|
502
|
+
);
|
|
432
503
|
this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
|
|
433
|
-
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(
|
|
504
|
+
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(
|
|
505
|
+
this.#XRExperience.baseExperience.camera
|
|
506
|
+
);
|
|
434
507
|
});
|
|
435
508
|
|
|
436
509
|
this.addStylesToARButton();
|
|
437
510
|
} catch (error) {
|
|
438
|
-
|
|
511
|
+
this.#log("warn", "Falló la creación de la experiencia WebXR", error);
|
|
439
512
|
this.#XRExperience = null;
|
|
440
513
|
}
|
|
441
514
|
}
|
|
@@ -448,42 +521,30 @@ class PrefViewer extends HTMLElement {
|
|
|
448
521
|
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
449
522
|
this.#camera.lowerRadiusLimit = 5;
|
|
450
523
|
this.#camera.upperRadiusLimit = 20;
|
|
451
|
-
this.#camera.metadata = { locked: false }
|
|
524
|
+
this.#camera.metadata = { locked: false };
|
|
452
525
|
this.#camera.attachControl(this.#canvas, true);
|
|
453
526
|
this.#scene.activeCamera = this.#camera;
|
|
527
|
+
this.#log("debug", "Cámara creada y asignada como activa", {
|
|
528
|
+
name: this.#camera.name,
|
|
529
|
+
locked: this.#camera.metadata.locked,
|
|
530
|
+
});
|
|
454
531
|
}
|
|
455
532
|
|
|
533
|
+
// [LIGHTS OFF] — sin luces del componente
|
|
456
534
|
#createLights() {
|
|
457
|
-
|
|
458
|
-
this.#
|
|
459
|
-
this.#
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
this.#dirLight = new DirectionalLight("dirLight", new Vector3(-10, 10, -10), this.#scene);
|
|
463
|
-
this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
|
|
464
|
-
this.#dirLight.intensity = 0.6;
|
|
465
|
-
|
|
466
|
-
// 3) Soft shadows
|
|
467
|
-
this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
|
|
468
|
-
this.#shadowGen.useBlurExponentialShadowMap = true;
|
|
469
|
-
this.#shadowGen.blurKernel = 16;
|
|
470
|
-
this.#shadowGen.darkness = 0.5;
|
|
471
|
-
|
|
472
|
-
// 4) Camera‐attached headlight
|
|
473
|
-
this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
|
|
474
|
-
this.#cameraLight.parent = this.#camera;
|
|
475
|
-
this.#cameraLight.intensity = 0.3;
|
|
535
|
+
this.#hemiLight = null;
|
|
536
|
+
this.#dirLight = null;
|
|
537
|
+
this.#cameraLight = null;
|
|
538
|
+
this.#shadowGen = null;
|
|
539
|
+
this.#log("info", "Luces internas desactivadas (#createLights). No se crean sombras.");
|
|
476
540
|
}
|
|
477
541
|
|
|
478
542
|
#setupInteraction() {
|
|
479
543
|
this.#canvas.addEventListener("wheel", (event) => {
|
|
480
|
-
if (!this.#scene || !this.#camera)
|
|
481
|
-
return false;
|
|
482
|
-
}
|
|
483
|
-
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
484
|
-
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
544
|
+
if (!this.#scene || !this.#camera) return false;
|
|
485
545
|
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
486
|
-
this.#scene.activeCamera.inertialRadiusOffset -=
|
|
546
|
+
this.#scene.activeCamera.inertialRadiusOffset -=
|
|
547
|
+
event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
487
548
|
}
|
|
488
549
|
event.preventDefault();
|
|
489
550
|
});
|
|
@@ -492,15 +553,17 @@ class PrefViewer extends HTMLElement {
|
|
|
492
553
|
#disposeEngine() {
|
|
493
554
|
if (!this.#engine) return;
|
|
494
555
|
this.#shadowGen?.dispose();
|
|
495
|
-
this.#scene?.lights?.slice().forEach(l => l.dispose());
|
|
556
|
+
this.#scene?.lights?.slice().forEach((l) => l.dispose());
|
|
496
557
|
this.#engine.dispose();
|
|
497
558
|
this.#engine = this.#scene = this.#camera = null;
|
|
498
559
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
499
560
|
this.#shadowGen = null;
|
|
561
|
+
this.#log("info", "Engine y recursos Babylon eliminados");
|
|
500
562
|
}
|
|
501
563
|
|
|
502
564
|
// Utility methods for loading gltf/glb
|
|
503
565
|
async #getServerFileDataHeader(uri) {
|
|
566
|
+
this.#log("debug", `HEAD ${uri}`);
|
|
504
567
|
return new Promise((resolve) => {
|
|
505
568
|
const xhr = new XMLHttpRequest();
|
|
506
569
|
xhr.open("HEAD", uri, true);
|
|
@@ -509,12 +572,15 @@ class PrefViewer extends HTMLElement {
|
|
|
509
572
|
if (xhr.status === 200) {
|
|
510
573
|
const size = parseInt(xhr.getResponseHeader("Content-Length"));
|
|
511
574
|
const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
|
|
575
|
+
this.#log("debug", "HEAD ok", { size, timeStamp });
|
|
512
576
|
resolve([size, timeStamp]);
|
|
513
577
|
} else {
|
|
578
|
+
this.#log("warn", "HEAD non-200", { status: xhr.status });
|
|
514
579
|
resolve([0, null]);
|
|
515
580
|
}
|
|
516
581
|
};
|
|
517
582
|
xhr.onerror = () => {
|
|
583
|
+
this.#log("warn", "HEAD error");
|
|
518
584
|
resolve([0, null]);
|
|
519
585
|
};
|
|
520
586
|
xhr.send();
|
|
@@ -528,6 +594,7 @@ class PrefViewer extends HTMLElement {
|
|
|
528
594
|
}
|
|
529
595
|
|
|
530
596
|
#decodeBase64(base64) {
|
|
597
|
+
this.#log("debug", "Decodificando Base64…");
|
|
531
598
|
const [, payload] = base64.split(",");
|
|
532
599
|
const raw = payload || base64;
|
|
533
600
|
let decoded = "";
|
|
@@ -537,17 +604,19 @@ class PrefViewer extends HTMLElement {
|
|
|
537
604
|
try {
|
|
538
605
|
decoded = atob(raw);
|
|
539
606
|
} catch {
|
|
607
|
+
this.#log("warn", "Base64 inválido (atob falló)");
|
|
540
608
|
return { blob, extension, size };
|
|
541
609
|
}
|
|
542
610
|
let isJson = false;
|
|
543
611
|
try {
|
|
544
612
|
JSON.parse(decoded);
|
|
545
613
|
isJson = true;
|
|
546
|
-
} catch {
|
|
614
|
+
} catch {}
|
|
547
615
|
extension = isJson ? ".gltf" : ".glb";
|
|
548
616
|
const type = isJson ? "model/gltf+json" : "model/gltf-binary";
|
|
549
617
|
const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
|
|
550
618
|
blob = new Blob([array], { type });
|
|
619
|
+
this.#log("debug", "Base64 decodificado", { extension, size });
|
|
551
620
|
return { blob, extension, size };
|
|
552
621
|
}
|
|
553
622
|
|
|
@@ -556,6 +625,7 @@ class PrefViewer extends HTMLElement {
|
|
|
556
625
|
return true;
|
|
557
626
|
}
|
|
558
627
|
await initDb(db, table);
|
|
628
|
+
this.#log("debug", "IndexedDB inicializado/abierto", { db, table });
|
|
559
629
|
}
|
|
560
630
|
|
|
561
631
|
// Methods for managing Asset Containers
|
|
@@ -565,7 +635,11 @@ class PrefViewer extends HTMLElement {
|
|
|
565
635
|
}
|
|
566
636
|
show = show !== undefined ? show : this.#data.containers.environment.visible;
|
|
567
637
|
const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
|
|
568
|
-
this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) =>
|
|
638
|
+
const meshes = this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) =>
|
|
639
|
+
prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))
|
|
640
|
+
);
|
|
641
|
+
meshes.forEach((mesh) => mesh.setEnabled(show));
|
|
642
|
+
this.#log("debug", "setVisibilityOfWallAndFloorInModel", { show, count: meshes.length });
|
|
569
643
|
}
|
|
570
644
|
|
|
571
645
|
#setOptionsMaterial(optionMaterial) {
|
|
@@ -573,16 +647,27 @@ class PrefViewer extends HTMLElement {
|
|
|
573
647
|
return false;
|
|
574
648
|
}
|
|
575
649
|
|
|
576
|
-
const material =
|
|
650
|
+
const material =
|
|
651
|
+
this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) ||
|
|
652
|
+
null;
|
|
577
653
|
if (!material) {
|
|
654
|
+
this.#log("warn", "Material no encontrado en contenedor 'materials'", { name: optionMaterial.value });
|
|
578
655
|
return false;
|
|
579
656
|
}
|
|
580
657
|
|
|
581
658
|
const containers = [];
|
|
582
|
-
if (
|
|
659
|
+
if (
|
|
660
|
+
this.#data.containers.model.assetContainer &&
|
|
661
|
+
(this.#data.containers.model.changed || this.#data.containers.materials.changed || optionMaterial.changed)
|
|
662
|
+
) {
|
|
583
663
|
containers.push(this.#data.containers.model.assetContainer);
|
|
584
664
|
}
|
|
585
|
-
if (
|
|
665
|
+
if (
|
|
666
|
+
this.#data.containers.environment.assetContainer &&
|
|
667
|
+
(this.#data.containers.environment.changed ||
|
|
668
|
+
this.#data.containers.materials.changed ||
|
|
669
|
+
optionMaterial.changed)
|
|
670
|
+
) {
|
|
586
671
|
containers.push(this.#data.containers.environment.assetContainer);
|
|
587
672
|
}
|
|
588
673
|
if (containers.length === 0) {
|
|
@@ -600,9 +685,10 @@ class PrefViewer extends HTMLElement {
|
|
|
600
685
|
);
|
|
601
686
|
|
|
602
687
|
if (someSetted) {
|
|
603
|
-
optionMaterial.changed.success = true;
|
|
688
|
+
optionMaterial.changed && (optionMaterial.changed.success = true);
|
|
689
|
+
this.#log("debug", "Material aplicado", { prefix: optionMaterial.prefix, name: optionMaterial.value });
|
|
604
690
|
} else {
|
|
605
|
-
optionMaterial.value = optionMaterial.changed.
|
|
691
|
+
optionMaterial.value = optionMaterial.changed?.oldValue ?? optionMaterial.value;
|
|
606
692
|
}
|
|
607
693
|
|
|
608
694
|
return someSetted;
|
|
@@ -618,14 +704,34 @@ class PrefViewer extends HTMLElement {
|
|
|
618
704
|
}
|
|
619
705
|
|
|
620
706
|
#setOptionsCamera() {
|
|
621
|
-
if (
|
|
707
|
+
if (
|
|
708
|
+
!this.#data.options.camera.value &&
|
|
709
|
+
!this.#data.options.camera.changed &&
|
|
710
|
+
!this.#data.containers.model.changed &&
|
|
711
|
+
!this.#data.containers.environment.changed
|
|
712
|
+
) {
|
|
622
713
|
return false;
|
|
623
714
|
}
|
|
624
715
|
|
|
625
|
-
let camera =
|
|
716
|
+
let camera =
|
|
717
|
+
this.#data.containers.model.assetContainer?.cameras.find(
|
|
718
|
+
(thisCamera) => thisCamera.name === this.#data.options.camera.value
|
|
719
|
+
) ||
|
|
720
|
+
this.#data.containers.environment.assetContainer?.cameras.find(
|
|
721
|
+
(thisCamera) => thisCamera.name === this.#data.options.camera.value
|
|
722
|
+
) ||
|
|
723
|
+
null;
|
|
724
|
+
|
|
626
725
|
if (!camera) {
|
|
627
726
|
if (this.#data.options.camera.changed?.oldValue && this.#data.options.camera.changed?.oldValue !== this.#data.options.camera.value) {
|
|
628
|
-
camera =
|
|
727
|
+
camera =
|
|
728
|
+
this.#data.containers.model.assetContainer?.cameras.find(
|
|
729
|
+
(thisCamera) => thisCamera.name === this.#data.options.camera.changed.oldValue
|
|
730
|
+
) ||
|
|
731
|
+
this.#data.containers.environment.assetContainer?.cameras.find(
|
|
732
|
+
(thisCamera) => thisCamera.name === this.#data.options.camera.changed.oldValue
|
|
733
|
+
) ||
|
|
734
|
+
null;
|
|
629
735
|
}
|
|
630
736
|
if (camera) {
|
|
631
737
|
camera.metadata = { locked: this.#data.options.camera.changed.oldLocked };
|
|
@@ -636,14 +742,19 @@ class PrefViewer extends HTMLElement {
|
|
|
636
742
|
this.#data.options.camera.value = null;
|
|
637
743
|
this.#data.options.camera.locked = this.#camera.metadata.locked;
|
|
638
744
|
}
|
|
639
|
-
this.#data.options.camera.changed.success = false;
|
|
745
|
+
this.#data.options.camera.changed && (this.#data.options.camera.changed.success = false);
|
|
640
746
|
} else {
|
|
641
747
|
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
642
748
|
}
|
|
749
|
+
|
|
643
750
|
if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
|
|
644
751
|
camera.attachControl(this.#canvas, true);
|
|
645
752
|
}
|
|
646
753
|
this.#scene.activeCamera = camera;
|
|
754
|
+
this.#log("debug", "Cámara actualizada", {
|
|
755
|
+
active: this.#scene?.activeCamera?.name ?? null,
|
|
756
|
+
locked: this.#scene?.activeCamera?.metadata?.locked ?? null,
|
|
757
|
+
});
|
|
647
758
|
return true;
|
|
648
759
|
}
|
|
649
760
|
|
|
@@ -651,6 +762,7 @@ class PrefViewer extends HTMLElement {
|
|
|
651
762
|
if (container.assetContainer && !container.visible && container.show) {
|
|
652
763
|
container.assetContainer.addAllToScene();
|
|
653
764
|
container.visible = true;
|
|
765
|
+
this.#log("debug", `Añadido a escena: ${container.name}`);
|
|
654
766
|
}
|
|
655
767
|
}
|
|
656
768
|
|
|
@@ -658,6 +770,7 @@ class PrefViewer extends HTMLElement {
|
|
|
658
770
|
if (container.assetContainer && container.visible) {
|
|
659
771
|
container.assetContainer.removeAllFromScene();
|
|
660
772
|
container.visible = false;
|
|
773
|
+
this.#log("debug", `Eliminado de escena: ${container.name}`);
|
|
661
774
|
}
|
|
662
775
|
}
|
|
663
776
|
|
|
@@ -665,24 +778,26 @@ class PrefViewer extends HTMLElement {
|
|
|
665
778
|
// 1) quita y destruye el anterior si existía
|
|
666
779
|
const old = container.assetContainer;
|
|
667
780
|
if (old) {
|
|
668
|
-
if (container.visible) {
|
|
669
|
-
|
|
781
|
+
if (container.visible) {
|
|
782
|
+
old.removeAllFromScene();
|
|
783
|
+
}
|
|
784
|
+
old.dispose();
|
|
670
785
|
}
|
|
671
786
|
|
|
672
787
|
// 2) asigna el nuevo y prepara
|
|
673
788
|
container.assetContainer = newAssetContainer;
|
|
674
789
|
|
|
675
|
-
//
|
|
676
|
-
container.assetContainer.materials?.forEach(m => {
|
|
790
|
+
// Limitar luces por material (no usamos luces, poner 0 asegura shaders más simples)
|
|
791
|
+
container.assetContainer.materials?.forEach((m) => {
|
|
677
792
|
if ("maxSimultaneousLights" in m) {
|
|
678
|
-
m.maxSimultaneousLights =
|
|
793
|
+
m.maxSimultaneousLights = 0; // [LIGHTS OFF]
|
|
679
794
|
}
|
|
680
795
|
});
|
|
681
796
|
|
|
682
|
-
// 3) sombras
|
|
683
|
-
container.assetContainer.meshes.forEach(mesh => {
|
|
684
|
-
mesh.receiveShadows =
|
|
685
|
-
this.#shadowGen.addShadowCaster(mesh, true);
|
|
797
|
+
// 3) sombras sólo si existe generador (con luces OFF es null)
|
|
798
|
+
container.assetContainer.meshes.forEach((mesh) => {
|
|
799
|
+
mesh.receiveShadows = !!this.#shadowGen;
|
|
800
|
+
if (this.#shadowGen) this.#shadowGen.addShadowCaster(mesh, true);
|
|
686
801
|
});
|
|
687
802
|
|
|
688
803
|
// 4) añade a escena
|
|
@@ -690,29 +805,46 @@ class PrefViewer extends HTMLElement {
|
|
|
690
805
|
|
|
691
806
|
// 5) fuerza recompilación con defines correctos del nuevo estado
|
|
692
807
|
this.#scene.getEngine().releaseEffects();
|
|
808
|
+
|
|
809
|
+
this.#log("debug", `Container reemplazado: ${container.name}`, {
|
|
810
|
+
meshes: container.assetContainer.meshes?.length ?? 0,
|
|
811
|
+
materials: container.assetContainer.materials?.length ?? 0,
|
|
812
|
+
});
|
|
693
813
|
}
|
|
694
814
|
|
|
695
815
|
async #loadAssetContainer(container) {
|
|
696
|
-
|
|
816
|
+
const storage = container?.storage;
|
|
697
817
|
|
|
698
818
|
if (!storage) {
|
|
819
|
+
this.#log("debug", `Sin storage para "${container?.name}"`);
|
|
699
820
|
return false;
|
|
700
821
|
}
|
|
701
822
|
|
|
823
|
+
this.#timeStart(`load:${container.name}`);
|
|
702
824
|
let source = storage.url || null;
|
|
703
825
|
|
|
704
826
|
if (storage.db && storage.table && storage.id) {
|
|
827
|
+
this.#log("info", `Cargando ${container.name} desde IndexedDB`, {
|
|
828
|
+
db: storage.db,
|
|
829
|
+
table: storage.table,
|
|
830
|
+
id: storage.id,
|
|
831
|
+
});
|
|
705
832
|
await this.#initStorage(storage.db, storage.table);
|
|
706
833
|
const object = await loadModel(storage.id, storage.table);
|
|
707
834
|
source = object.data;
|
|
708
835
|
if (object.timeStamp === container.timeStamp) {
|
|
836
|
+
this.#log("debug", `${container.name}: sin cambios en IndexedDB`);
|
|
837
|
+
this.#timeEnd(`load:${container.name}`);
|
|
709
838
|
return false;
|
|
710
839
|
} else {
|
|
711
840
|
container.changed = { timeStamp: object.timeStamp, size: object.size, success: false };
|
|
841
|
+
this.#log("debug", `${container.name}: cambios detectados`, container.changed);
|
|
712
842
|
}
|
|
713
843
|
}
|
|
714
844
|
|
|
715
845
|
if (!source) {
|
|
846
|
+
this.#log("warn", `${container.name}: no hay source`);
|
|
847
|
+
this.#timeEnd(`load:${container.name}`);
|
|
716
848
|
return false;
|
|
717
849
|
}
|
|
718
850
|
|
|
@@ -725,6 +857,8 @@ class PrefViewer extends HTMLElement {
|
|
|
725
857
|
});
|
|
726
858
|
if (!container.changed) {
|
|
727
859
|
if (container.timeStamp === null && container.size === size) {
|
|
860
|
+
this.#log("debug", `${container.name}: Base64 sin cambios`);
|
|
861
|
+
this.#timeEnd(`load:${container.name}`);
|
|
728
862
|
return false;
|
|
729
863
|
} else {
|
|
730
864
|
container.changed = { timeStamp: null, size: size, success: false };
|
|
@@ -735,6 +869,8 @@ class PrefViewer extends HTMLElement {
|
|
|
735
869
|
extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
|
|
736
870
|
const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
|
|
737
871
|
if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
|
|
872
|
+
this.#log("debug", `${container.name}: URL sin cambios`);
|
|
873
|
+
this.#timeEnd(`load:${container.name}`);
|
|
738
874
|
return false;
|
|
739
875
|
} else {
|
|
740
876
|
container.changed = { timeStamp: fileTimeStamp, size: fileSize, success: false };
|
|
@@ -750,11 +886,21 @@ class PrefViewer extends HTMLElement {
|
|
|
750
886
|
},
|
|
751
887
|
},
|
|
752
888
|
};
|
|
889
|
+
this.#log("info", `LoadAssetContainerAsync ${container.name}`, { extension, changed: container.changed });
|
|
753
890
|
|
|
754
|
-
|
|
891
|
+
try {
|
|
892
|
+
const result = await LoadAssetContainerAsync(file || source, this.#scene, options);
|
|
893
|
+
this.#timeEnd(`load:${container.name}`);
|
|
894
|
+
return result;
|
|
895
|
+
} catch (e) {
|
|
896
|
+
this.#timeEnd(`load:${container.name}`);
|
|
897
|
+
this.#log("error", `LoadAssetContainerAsync falló para ${container.name}`, e);
|
|
898
|
+
throw e;
|
|
899
|
+
}
|
|
755
900
|
}
|
|
756
901
|
|
|
757
902
|
async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
|
|
903
|
+
this.#log("info", "loadContainers()", { loadModel, loadEnvironment, loadMaterials });
|
|
758
904
|
const promiseArray = [];
|
|
759
905
|
promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
|
|
760
906
|
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
|
|
@@ -768,12 +914,20 @@ class PrefViewer extends HTMLElement {
|
|
|
768
914
|
const environmentContainer = values[1];
|
|
769
915
|
const materialsContainer = values[2];
|
|
770
916
|
|
|
917
|
+
this.#log(
|
|
918
|
+
"debug",
|
|
919
|
+
"Resultados Promise.allSettled",
|
|
920
|
+
values.map((v) => ({ status: v.status, hasValue: !!v.value }))
|
|
921
|
+
);
|
|
922
|
+
|
|
771
923
|
if (modelContainer.status === "fulfilled" && modelContainer.value) {
|
|
772
924
|
this.#stripImportedLights(modelContainer.value);
|
|
773
925
|
this.#replaceContainer(this.#data.containers.model, modelContainer.value);
|
|
774
926
|
this.#storeChangedFlagsForContainer(this.#data.containers.model);
|
|
775
927
|
} else {
|
|
776
|
-
this.#data.containers.model.show
|
|
928
|
+
this.#data.containers.model.show
|
|
929
|
+
? this.#addContainer(this.#data.containers.model)
|
|
930
|
+
: this.#removeContainer(this.#data.containers.model);
|
|
777
931
|
}
|
|
778
932
|
|
|
779
933
|
if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
|
|
@@ -781,7 +935,9 @@ class PrefViewer extends HTMLElement {
|
|
|
781
935
|
this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
|
|
782
936
|
this.#storeChangedFlagsForContainer(this.#data.containers.environment);
|
|
783
937
|
} else {
|
|
784
|
-
this.#data.containers.environment.show
|
|
938
|
+
this.#data.containers.environment.show
|
|
939
|
+
? this.#addContainer(this.#data.containers.environment)
|
|
940
|
+
: this.#removeContainer(this.#data.containers.environment);
|
|
785
941
|
}
|
|
786
942
|
|
|
787
943
|
if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
|
|
@@ -795,10 +951,11 @@ class PrefViewer extends HTMLElement {
|
|
|
795
951
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
796
952
|
this.#setStatusSceneLoaded();
|
|
797
953
|
this.#resetChangedFlags();
|
|
954
|
+
this.#log("info", "Escena cargada");
|
|
798
955
|
})
|
|
799
956
|
.catch((error) => {
|
|
800
957
|
this.loaded = true;
|
|
801
|
-
|
|
958
|
+
this.#log("error", "Failed to load containers", error);
|
|
802
959
|
this.dispatchEvent(
|
|
803
960
|
new CustomEvent("scene-error", {
|
|
804
961
|
bubbles: true,
|
|
@@ -811,25 +968,27 @@ class PrefViewer extends HTMLElement {
|
|
|
811
968
|
}
|
|
812
969
|
|
|
813
970
|
#stripImportedLights(container) {
|
|
814
|
-
|
|
815
|
-
if (container
|
|
816
|
-
|
|
817
|
-
container.lights.slice().forEach(l => l.dispose());
|
|
818
|
-
}
|
|
971
|
+
const n = container?.lights?.length ?? 0;
|
|
972
|
+
if (n) container.lights.slice().forEach((l) => l.dispose());
|
|
973
|
+
this.#log("debug", `stripImportedLights(): ${n} → 0`);
|
|
819
974
|
}
|
|
820
975
|
|
|
821
976
|
// Public Methods
|
|
822
977
|
loadConfig(config) {
|
|
978
|
+
this.#log("info", "loadConfig()", typeof config === "string" ? "[string]" : config);
|
|
823
979
|
config = typeof config === "string" ? JSON.parse(config) : config;
|
|
824
980
|
if (!config) {
|
|
981
|
+
this.#log("warn", "loadConfig() → config vacío/nulo");
|
|
825
982
|
return false;
|
|
826
983
|
}
|
|
827
984
|
|
|
828
985
|
// Containers
|
|
829
986
|
this.#data.containers.model.storage = config.model?.storage || null;
|
|
830
|
-
this.#data.containers.model.show =
|
|
987
|
+
this.#data.containers.model.show =
|
|
988
|
+
config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
|
|
831
989
|
this.#data.containers.environment.storage = config.scene?.storage || null;
|
|
832
|
-
this.#data.containers.environment.show =
|
|
990
|
+
this.#data.containers.environment.show =
|
|
991
|
+
config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
|
|
833
992
|
this.#data.containers.materials.storage = config.materials?.storage || null;
|
|
834
993
|
|
|
835
994
|
// Options
|
|
@@ -842,6 +1001,7 @@ class PrefViewer extends HTMLElement {
|
|
|
842
1001
|
}
|
|
843
1002
|
|
|
844
1003
|
setOptions(options) {
|
|
1004
|
+
this.#log("info", "setOptions()", options);
|
|
845
1005
|
if (!options) {
|
|
846
1006
|
return false;
|
|
847
1007
|
}
|
|
@@ -863,22 +1023,28 @@ class PrefViewer extends HTMLElement {
|
|
|
863
1023
|
}
|
|
864
1024
|
|
|
865
1025
|
loadModel(model) {
|
|
1026
|
+
this.#log("info", "loadModel()", typeof model === "string" ? "[string]" : model);
|
|
866
1027
|
model = typeof model === "string" ? JSON.parse(model) : model;
|
|
867
1028
|
if (!model) {
|
|
1029
|
+
this.#log("warn", "loadModel() → model vacío/nulo");
|
|
868
1030
|
return false;
|
|
869
1031
|
}
|
|
870
1032
|
this.#data.containers.model.storage = model.storage || null;
|
|
871
|
-
this.#data.containers.model.show =
|
|
1033
|
+
this.#data.containers.model.show =
|
|
1034
|
+
model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
872
1035
|
this.initialized && this.#loadContainers(true, false, false);
|
|
873
1036
|
}
|
|
874
1037
|
|
|
875
1038
|
loadScene(scene) {
|
|
1039
|
+
this.#log("info", "loadScene()", typeof scene === "string" ? "[string]" : scene);
|
|
876
1040
|
scene = typeof scene === "string" ? JSON.parse(scene) : scene;
|
|
877
1041
|
if (!scene) {
|
|
1042
|
+
this.#log("warn", "loadScene() → scene vacío/nulo");
|
|
878
1043
|
return false;
|
|
879
1044
|
}
|
|
880
1045
|
this.#data.containers.environment.storage = scene.storage || null;
|
|
881
|
-
this.#data.containers.environment.show =
|
|
1046
|
+
this.#data.containers.environment.show =
|
|
1047
|
+
scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
|
|
882
1048
|
this.initialized && this.#loadContainers(false, true, false);
|
|
883
1049
|
}
|
|
884
1050
|
|
|
@@ -906,7 +1072,9 @@ class PrefViewer extends HTMLElement {
|
|
|
906
1072
|
|
|
907
1073
|
downloadModelGLB() {
|
|
908
1074
|
const fileName = "model";
|
|
909
|
-
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, {
|
|
1075
|
+
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, {
|
|
1076
|
+
exportWithoutWaitingForScene: true,
|
|
1077
|
+
}).then((glb) => glb.downloadFiles());
|
|
910
1078
|
}
|
|
911
1079
|
|
|
912
1080
|
downloadModelUSDZ() {
|
|
@@ -929,7 +1097,9 @@ class PrefViewer extends HTMLElement {
|
|
|
929
1097
|
|
|
930
1098
|
downloadModelAndSceneGLB() {
|
|
931
1099
|
const fileName = "scene";
|
|
932
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) =>
|
|
1100
|
+
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) =>
|
|
1101
|
+
glb.downloadFiles()
|
|
1102
|
+
);
|
|
933
1103
|
}
|
|
934
1104
|
}
|
|
935
1105
|
|