@preference-sl/pref-viewer 2.10.0-beta.2 → 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/gltf-storage.js +176 -70
- package/src/index.js +732 -139
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";
|
|
@@ -47,22 +63,76 @@ import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompre
|
|
|
47
63
|
import { initDb, loadModel } from "./gltf-storage.js";
|
|
48
64
|
|
|
49
65
|
class PrefViewer extends HTMLElement {
|
|
50
|
-
|
|
66
|
+
initialized = false;
|
|
67
|
+
loaded = false;
|
|
68
|
+
loading = false;
|
|
51
69
|
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
#data = {
|
|
71
|
+
containers: {
|
|
72
|
+
model: {
|
|
73
|
+
name: "model",
|
|
74
|
+
container: null,
|
|
75
|
+
show: true,
|
|
76
|
+
storage: null,
|
|
77
|
+
visible: false,
|
|
78
|
+
size: null,
|
|
79
|
+
timeStamp: null,
|
|
80
|
+
changed: false,
|
|
81
|
+
},
|
|
82
|
+
environment: {
|
|
83
|
+
name: "environment",
|
|
84
|
+
container: null,
|
|
85
|
+
show: true,
|
|
86
|
+
storage: null,
|
|
87
|
+
visible: false,
|
|
88
|
+
size: null,
|
|
89
|
+
timeStamp: null,
|
|
90
|
+
changed: false,
|
|
91
|
+
},
|
|
92
|
+
materials: {
|
|
93
|
+
name: "materials",
|
|
94
|
+
container: null,
|
|
95
|
+
storage: null,
|
|
96
|
+
show: true,
|
|
97
|
+
visible: false,
|
|
98
|
+
size: null,
|
|
99
|
+
timeStamp: null,
|
|
100
|
+
changed: false,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
options: {
|
|
104
|
+
camera: {
|
|
105
|
+
value: null,
|
|
106
|
+
locked: true,
|
|
107
|
+
changed: false,
|
|
108
|
+
},
|
|
109
|
+
materials: {
|
|
110
|
+
innerWall: {
|
|
111
|
+
value: null,
|
|
112
|
+
prefix: "innerWall",
|
|
113
|
+
changed: false,
|
|
114
|
+
},
|
|
115
|
+
outerWall: {
|
|
116
|
+
value: null,
|
|
117
|
+
prefix: "outerWall",
|
|
118
|
+
changed: false,
|
|
119
|
+
},
|
|
120
|
+
innerFloor: {
|
|
121
|
+
value: null,
|
|
122
|
+
prefix: "innerFloor",
|
|
123
|
+
changed: false,
|
|
124
|
+
},
|
|
125
|
+
outerFloor: {
|
|
126
|
+
value: null,
|
|
127
|
+
prefix: "outerFloor",
|
|
128
|
+
changed: false,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
64
132
|
};
|
|
65
133
|
|
|
134
|
+
// DOM elements
|
|
135
|
+
#wrapper = null;
|
|
66
136
|
#canvas = null;
|
|
67
137
|
|
|
68
138
|
// Babylon.js core objects
|
|
@@ -75,6 +145,38 @@ class PrefViewer extends HTMLElement {
|
|
|
75
145
|
#shadowGen = null;
|
|
76
146
|
#XRExperience = null;
|
|
77
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
|
+
|
|
78
180
|
constructor() {
|
|
79
181
|
super();
|
|
80
182
|
this.attachShadow({ mode: "open" });
|
|
@@ -97,6 +199,7 @@ class PrefViewer extends HTMLElement {
|
|
|
97
199
|
}
|
|
98
200
|
|
|
99
201
|
attributeChangedCallback(name, _old, value) {
|
|
202
|
+
this.#log("debug", `attributeChangedCallback: ${name}`, value);
|
|
100
203
|
let data = null;
|
|
101
204
|
switch (name) {
|
|
102
205
|
case "config":
|
|
@@ -110,43 +213,47 @@ class PrefViewer extends HTMLElement {
|
|
|
110
213
|
break;
|
|
111
214
|
case "show-model":
|
|
112
215
|
data = value.toLowerCase?.() === "true";
|
|
113
|
-
if (this
|
|
216
|
+
if (this.initialized) {
|
|
114
217
|
data ? this.showModel() : this.hideModel();
|
|
115
218
|
} else {
|
|
116
|
-
this.#model.show = data;
|
|
219
|
+
this.#data.containers.model.show = data;
|
|
117
220
|
}
|
|
118
221
|
break;
|
|
119
222
|
case "show-scene":
|
|
120
223
|
data = value.toLowerCase?.() === "true";
|
|
121
|
-
if (this
|
|
224
|
+
if (this.initialized) {
|
|
122
225
|
data ? this.showScene() : this.hideScene();
|
|
123
226
|
} else {
|
|
124
|
-
this.#environment.show = data;
|
|
227
|
+
this.#data.containers.environment.show = data;
|
|
125
228
|
}
|
|
126
229
|
break;
|
|
127
230
|
}
|
|
128
231
|
}
|
|
129
232
|
|
|
130
233
|
connectedCallback() {
|
|
234
|
+
this.#log("info", "connectedCallback");
|
|
131
235
|
if (!this.hasAttribute("config")) {
|
|
132
236
|
const error = 'PrefViewer: provide "models" as array of model and environment';
|
|
237
|
+
this.#log("error", error);
|
|
133
238
|
console.error(error);
|
|
134
239
|
this.dispatchEvent(
|
|
135
|
-
new CustomEvent("
|
|
136
|
-
detail: { error: new Error(error) },
|
|
240
|
+
new CustomEvent("scene-error", {
|
|
137
241
|
bubbles: true,
|
|
242
|
+
cancelable: false,
|
|
138
243
|
composed: true,
|
|
244
|
+
detail: { error: new Error(error) },
|
|
139
245
|
})
|
|
140
246
|
);
|
|
141
247
|
return false;
|
|
142
248
|
}
|
|
143
249
|
|
|
144
250
|
this.#initializeBabylon();
|
|
145
|
-
this
|
|
146
|
-
this.#
|
|
251
|
+
this.initialized = true;
|
|
252
|
+
this.#loadContainers(true, true, true);
|
|
147
253
|
}
|
|
148
254
|
|
|
149
255
|
disconnectedCallback() {
|
|
256
|
+
this.#log("info", "disconnectedCallback → dispose engine");
|
|
150
257
|
this.#disposeEngine();
|
|
151
258
|
this.#canvasResizeObserver.disconnect();
|
|
152
259
|
}
|
|
@@ -163,55 +270,245 @@ class PrefViewer extends HTMLElement {
|
|
|
163
270
|
}
|
|
164
271
|
|
|
165
272
|
#wrapCanvas() {
|
|
166
|
-
|
|
167
|
-
Object.assign(wrapper.style, {
|
|
273
|
+
this.#wrapper = document.createElement("div");
|
|
274
|
+
Object.assign(this.#wrapper.style, {
|
|
168
275
|
width: "100%",
|
|
169
276
|
height: "100%",
|
|
170
277
|
position: "relative",
|
|
171
278
|
});
|
|
172
|
-
wrapper.appendChild(this.#canvas);
|
|
173
|
-
this.shadowRoot.append(wrapper);
|
|
279
|
+
this.#wrapper.appendChild(this.#canvas);
|
|
280
|
+
this.shadowRoot.append(this.#wrapper);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
#setStatusSceneLoading() {
|
|
284
|
+
this.loaded = false;
|
|
285
|
+
this.loading = true;
|
|
286
|
+
if (this.hasAttribute("loaded")) {
|
|
287
|
+
this.removeAttribute("loaded");
|
|
288
|
+
}
|
|
289
|
+
this.setAttribute("loading", "");
|
|
290
|
+
this.#log("info", "Escena → loading");
|
|
291
|
+
this.dispatchEvent(
|
|
292
|
+
new CustomEvent("scene-loading", {
|
|
293
|
+
bubbles: true,
|
|
294
|
+
cancelable: false,
|
|
295
|
+
composed: true,
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
#setStatusSceneLoaded() {
|
|
301
|
+
this.loaded = true;
|
|
302
|
+
this.loading = false;
|
|
303
|
+
|
|
304
|
+
const toLoadDetail = {
|
|
305
|
+
container_model: !!this.#data.containers.model.changed,
|
|
306
|
+
container_environment: !!this.#data.containers.environment.changed,
|
|
307
|
+
container_materials: !!this.#data.containers.materials.changed,
|
|
308
|
+
options_camera: !!this.#data.options.camera.changed,
|
|
309
|
+
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed,
|
|
310
|
+
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
|
|
311
|
+
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
|
|
312
|
+
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
|
|
313
|
+
};
|
|
314
|
+
const loadedDetail = {
|
|
315
|
+
container_model: !!this.#data.containers.model.changed?.success,
|
|
316
|
+
container_environment: !!this.#data.containers.environment.changed?.success,
|
|
317
|
+
container_materials: !!this.#data.containers.materials.changed?.success,
|
|
318
|
+
options_camera: !!this.#data.options.camera.changed?.success,
|
|
319
|
+
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
|
|
320
|
+
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
|
|
321
|
+
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
|
|
322
|
+
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const detail = {
|
|
326
|
+
tried: toLoadDetail,
|
|
327
|
+
success: loadedDetail,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
if (this.hasAttribute("loading")) {
|
|
331
|
+
this.removeAttribute("loading");
|
|
332
|
+
}
|
|
333
|
+
this.setAttribute("loaded", "");
|
|
334
|
+
this.#log("info", "Escena → loaded", detail);
|
|
335
|
+
this.dispatchEvent(
|
|
336
|
+
new CustomEvent("scene-loaded", {
|
|
337
|
+
bubbles: true,
|
|
338
|
+
cancelable: false,
|
|
339
|
+
composed: true,
|
|
340
|
+
detail: detail,
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#setStatusOptionsLoading() {
|
|
346
|
+
this.#log("info", "Opciones → loading");
|
|
347
|
+
this.dispatchEvent(
|
|
348
|
+
new CustomEvent("options-loading", {
|
|
349
|
+
bubbles: true,
|
|
350
|
+
cancelable: false,
|
|
351
|
+
composed: true,
|
|
352
|
+
})
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#setStatusOptionsLoaded() {
|
|
357
|
+
const toLoadDetail = {
|
|
358
|
+
innerWallMaterial: !!this.#data.options.materials.innerWall.changed,
|
|
359
|
+
outerWallMaterial: !!this.#data.options.materials.outerWall.changed,
|
|
360
|
+
innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed,
|
|
361
|
+
outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed,
|
|
362
|
+
};
|
|
363
|
+
const loadedDetail = {
|
|
364
|
+
innerWallMaterial: !!this.#data.options.materials.innerWall.changed?.success,
|
|
365
|
+
outerWallMaterial: !!this.#data.options.materials.outerWall.changed?.success,
|
|
366
|
+
innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed?.success,
|
|
367
|
+
outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed?.success,
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const detail = {
|
|
371
|
+
tried: toLoadDetail,
|
|
372
|
+
success: loadedDetail,
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
this.#log("info", "Opciones → loaded", detail);
|
|
376
|
+
this.dispatchEvent(
|
|
377
|
+
new CustomEvent("options-loaded", {
|
|
378
|
+
bubbles: true,
|
|
379
|
+
cancelable: false,
|
|
380
|
+
composed: true,
|
|
381
|
+
detail: detail,
|
|
382
|
+
})
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Data
|
|
387
|
+
#checkCameraChanged(options) {
|
|
388
|
+
if (!options || !options.camera) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
const changed = options.camera !== this.#data.options.camera.value;
|
|
392
|
+
this.#data.options.camera.changed = changed
|
|
393
|
+
? { oldValue: this.#data.options.camera.value, oldLocked: this.#data.options.camera.locked, success: false }
|
|
394
|
+
: false;
|
|
395
|
+
if (changed) this.#data.options.camera.value = options.camera;
|
|
396
|
+
this.#log("debug", `#checkCameraChanged → ${changed}`, {
|
|
397
|
+
new: this.#data.options.camera.value,
|
|
398
|
+
old: this.#data.options.camera.changed?.oldValue ?? null,
|
|
399
|
+
});
|
|
400
|
+
return changed;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
#checkMaterialsChanged(options) {
|
|
404
|
+
if (!options) return false;
|
|
405
|
+
let someChanged = false;
|
|
406
|
+
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
407
|
+
const key = `${material}Material`;
|
|
408
|
+
const materialChanged = options[key] && options[key] !== this.#data.options.materials[material].value ? true : false;
|
|
409
|
+
this.#data.options.materials[material].changed = materialChanged
|
|
410
|
+
? { oldValue: this.#data.options.materials[material].value, success: false }
|
|
411
|
+
: false;
|
|
412
|
+
this.#data.options.materials[material].value = materialChanged ? options[key] : this.#data.options.materials[material].value;
|
|
413
|
+
someChanged = someChanged || this.#data.options.materials[material].changed;
|
|
414
|
+
});
|
|
415
|
+
this.#log("debug", `#checkMaterialsChanged → ${!!someChanged}`);
|
|
416
|
+
return someChanged;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
#storeChangedFlagsForContainer(container) {
|
|
420
|
+
container.timeStamp = container.changed.timeStamp;
|
|
421
|
+
container.size = container.changed.size;
|
|
422
|
+
container.changed.success = true;
|
|
174
423
|
}
|
|
175
|
-
|
|
176
|
-
|
|
424
|
+
|
|
425
|
+
#resetChangedFlags() {
|
|
426
|
+
Object.values(this.#data.containers).forEach((container) => (container.changed = false));
|
|
427
|
+
Object.values(this.#data.options.materials).forEach((material) => (material.changed = false));
|
|
428
|
+
this.#data.options.camera.changed = false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Babylon.js
|
|
177
432
|
async #initializeBabylon() {
|
|
433
|
+
this.#timeStart("initializeBabylon");
|
|
178
434
|
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
435
|
+
this.#engine.disableUniformBuffers = true; // evita GL_MAX_*_UNIFORM_BUFFERS (workaround)
|
|
179
436
|
this.#scene = new Scene(this.#engine);
|
|
180
437
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
181
438
|
this.#createCamera();
|
|
182
|
-
this.#createLights();
|
|
439
|
+
this.#createLights(); // luces desactivadas
|
|
183
440
|
this.#setupInteraction();
|
|
184
|
-
|
|
441
|
+
|
|
442
|
+
this.#log("info", "Engine y Scene creados", {
|
|
443
|
+
webgl: this.#engine.webGLVersion,
|
|
444
|
+
disableUBO: this.#engine.disableUniformBuffers,
|
|
445
|
+
});
|
|
446
|
+
|
|
185
447
|
this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
|
|
186
448
|
this.#canvasResizeObserver.observe(this.#canvas);
|
|
187
449
|
|
|
188
450
|
await this.#createXRExperience();
|
|
451
|
+
this.#timeEnd("initializeBabylon");
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
addStylesToARButton() {
|
|
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"}';
|
|
457
|
+
const style = document.createElement("style");
|
|
458
|
+
style.appendChild(document.createTextNode(css));
|
|
459
|
+
this.#wrapper.appendChild(style);
|
|
189
460
|
}
|
|
190
461
|
|
|
191
462
|
async #createXRExperience() {
|
|
192
|
-
if (this.#XRExperience)
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
463
|
+
if (this.#XRExperience) return true;
|
|
464
|
+
|
|
196
465
|
const sessionMode = "immersive-ar";
|
|
197
466
|
const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
|
|
198
467
|
if (!sessionSupported) {
|
|
199
|
-
|
|
468
|
+
this.#log("info", `WebXR no soportado para ${sessionMode}`);
|
|
200
469
|
return false;
|
|
201
470
|
}
|
|
202
471
|
|
|
203
|
-
const options = {
|
|
204
|
-
uiOptions: {
|
|
205
|
-
sessionMode: sessionMode,
|
|
206
|
-
renderTarget: "xrLayer",
|
|
207
|
-
referenceSpaceType: "local",
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
|
|
211
472
|
try {
|
|
212
|
-
|
|
473
|
+
this.#log("debug", "Creando XR DefaultExperience…");
|
|
474
|
+
const ground = MeshBuilder.CreateGround("ground", { width: 1000, height: 1000 }, this.#scene);
|
|
475
|
+
ground.isVisible = false;
|
|
476
|
+
|
|
477
|
+
const options = {
|
|
478
|
+
floorMeshes: [ground],
|
|
479
|
+
uiOptions: {
|
|
480
|
+
sessionMode: sessionMode,
|
|
481
|
+
renderTarget: "xrLayer",
|
|
482
|
+
referenceSpaceType: "local",
|
|
483
|
+
},
|
|
484
|
+
optionalFeatures: true,
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
|
|
488
|
+
this.#log("info", "XR DefaultExperience creado");
|
|
489
|
+
|
|
490
|
+
const featuresManager = this.#XRExperience.baseExperience.featuresManager;
|
|
491
|
+
featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
|
|
492
|
+
xrInput: this.#XRExperience.input,
|
|
493
|
+
floorMeshes: [ground],
|
|
494
|
+
timeToTeleport: 1500,
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
|
|
498
|
+
// Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
|
|
499
|
+
this.#log("debug", "XR Ready → copiando pose inicial a xrCamera");
|
|
500
|
+
this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(
|
|
501
|
+
this.#XRExperience.baseExperience._nonVRCamera
|
|
502
|
+
);
|
|
503
|
+
this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
|
|
504
|
+
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(
|
|
505
|
+
this.#XRExperience.baseExperience.camera
|
|
506
|
+
);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
this.addStylesToARButton();
|
|
213
510
|
} catch (error) {
|
|
214
|
-
|
|
511
|
+
this.#log("warn", "Falló la creación de la experiencia WebXR", error);
|
|
215
512
|
this.#XRExperience = null;
|
|
216
513
|
}
|
|
217
514
|
}
|
|
@@ -219,55 +516,77 @@ class PrefViewer extends HTMLElement {
|
|
|
219
516
|
#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
220
517
|
|
|
221
518
|
#createCamera() {
|
|
222
|
-
this.#camera = new ArcRotateCamera("camera", 3 * Math.PI / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
519
|
+
this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
223
520
|
this.#camera.upperBetaLimit = Math.PI * 0.48;
|
|
224
521
|
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
225
522
|
this.#camera.lowerRadiusLimit = 5;
|
|
226
523
|
this.#camera.upperRadiusLimit = 20;
|
|
524
|
+
this.#camera.metadata = { locked: false };
|
|
227
525
|
this.#camera.attachControl(this.#canvas, true);
|
|
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
|
+
});
|
|
228
531
|
}
|
|
229
532
|
|
|
533
|
+
// [LIGHTS OFF] — sin luces del componente
|
|
230
534
|
#createLights() {
|
|
231
|
-
|
|
232
|
-
this.#
|
|
233
|
-
this.#
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
this.#dirLight = new DirectionalLight("dirLight", new Vector3(-10, 10, -10), this.#scene);
|
|
237
|
-
this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
|
|
238
|
-
this.#dirLight.intensity = 0.6;
|
|
239
|
-
|
|
240
|
-
// 3) Soft shadows
|
|
241
|
-
this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
|
|
242
|
-
this.#shadowGen.useBlurExponentialShadowMap = true;
|
|
243
|
-
this.#shadowGen.blurKernel = 16;
|
|
244
|
-
this.#shadowGen.darkness = 0.5;
|
|
245
|
-
|
|
246
|
-
// 4) Camera‐attached headlight
|
|
247
|
-
this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
|
|
248
|
-
this.#cameraLight.parent = this.#camera;
|
|
249
|
-
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.");
|
|
250
540
|
}
|
|
251
541
|
|
|
252
542
|
#setupInteraction() {
|
|
253
543
|
this.#canvas.addEventListener("wheel", (event) => {
|
|
254
|
-
if (!this.#scene || !this.#camera) return;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
544
|
+
if (!this.#scene || !this.#camera) return false;
|
|
545
|
+
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
546
|
+
this.#scene.activeCamera.inertialRadiusOffset -=
|
|
547
|
+
event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
548
|
+
}
|
|
258
549
|
event.preventDefault();
|
|
259
550
|
});
|
|
260
551
|
}
|
|
261
552
|
|
|
262
553
|
#disposeEngine() {
|
|
263
554
|
if (!this.#engine) return;
|
|
555
|
+
this.#shadowGen?.dispose();
|
|
556
|
+
this.#scene?.lights?.slice().forEach((l) => l.dispose());
|
|
264
557
|
this.#engine.dispose();
|
|
265
558
|
this.#engine = this.#scene = this.#camera = null;
|
|
266
559
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
267
560
|
this.#shadowGen = null;
|
|
561
|
+
this.#log("info", "Engine y recursos Babylon eliminados");
|
|
268
562
|
}
|
|
269
563
|
|
|
270
564
|
// Utility methods for loading gltf/glb
|
|
565
|
+
async #getServerFileDataHeader(uri) {
|
|
566
|
+
this.#log("debug", `HEAD ${uri}`);
|
|
567
|
+
return new Promise((resolve) => {
|
|
568
|
+
const xhr = new XMLHttpRequest();
|
|
569
|
+
xhr.open("HEAD", uri, true);
|
|
570
|
+
xhr.responseType = "blob";
|
|
571
|
+
xhr.onload = () => {
|
|
572
|
+
if (xhr.status === 200) {
|
|
573
|
+
const size = parseInt(xhr.getResponseHeader("Content-Length"));
|
|
574
|
+
const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
|
|
575
|
+
this.#log("debug", "HEAD ok", { size, timeStamp });
|
|
576
|
+
resolve([size, timeStamp]);
|
|
577
|
+
} else {
|
|
578
|
+
this.#log("warn", "HEAD non-200", { status: xhr.status });
|
|
579
|
+
resolve([0, null]);
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
xhr.onerror = () => {
|
|
583
|
+
this.#log("warn", "HEAD error");
|
|
584
|
+
resolve([0, null]);
|
|
585
|
+
};
|
|
586
|
+
xhr.send();
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
271
590
|
#transformUrl(url) {
|
|
272
591
|
return new Promise((resolve) => {
|
|
273
592
|
resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
|
|
@@ -275,15 +594,18 @@ class PrefViewer extends HTMLElement {
|
|
|
275
594
|
}
|
|
276
595
|
|
|
277
596
|
#decodeBase64(base64) {
|
|
597
|
+
this.#log("debug", "Decodificando Base64…");
|
|
278
598
|
const [, payload] = base64.split(",");
|
|
279
599
|
const raw = payload || base64;
|
|
280
600
|
let decoded = "";
|
|
281
601
|
let blob = null;
|
|
282
602
|
let extension = null;
|
|
603
|
+
let size = raw.length;
|
|
283
604
|
try {
|
|
284
605
|
decoded = atob(raw);
|
|
285
606
|
} catch {
|
|
286
|
-
|
|
607
|
+
this.#log("warn", "Base64 inválido (atob falló)");
|
|
608
|
+
return { blob, extension, size };
|
|
287
609
|
}
|
|
288
610
|
let isJson = false;
|
|
289
611
|
try {
|
|
@@ -294,7 +616,8 @@ class PrefViewer extends HTMLElement {
|
|
|
294
616
|
const type = isJson ? "model/gltf+json" : "model/gltf-binary";
|
|
295
617
|
const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
|
|
296
618
|
blob = new Blob([array], { type });
|
|
297
|
-
|
|
619
|
+
this.#log("debug", "Base64 decodificado", { extension, size });
|
|
620
|
+
return { blob, extension, size };
|
|
298
621
|
}
|
|
299
622
|
|
|
300
623
|
async #initStorage(db, table) {
|
|
@@ -302,191 +625,461 @@ class PrefViewer extends HTMLElement {
|
|
|
302
625
|
return true;
|
|
303
626
|
}
|
|
304
627
|
await initDb(db, table);
|
|
628
|
+
this.#log("debug", "IndexedDB inicializado/abierto", { db, table });
|
|
305
629
|
}
|
|
306
630
|
|
|
307
631
|
// Methods for managing Asset Containers
|
|
308
632
|
#setVisibilityOfWallAndFloorInModel(show) {
|
|
309
|
-
if (this.#model.
|
|
310
|
-
|
|
311
|
-
const nodes = this.#model.container.getNodes();
|
|
312
|
-
this.#model.container
|
|
313
|
-
.getNodes()
|
|
314
|
-
.filter((filter) => names.includes(filter.name))
|
|
315
|
-
.forEach((node) => node.setEnabled(show !== undefined ? show : this.#environment.show));
|
|
633
|
+
if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
|
|
634
|
+
return false;
|
|
316
635
|
}
|
|
636
|
+
show = show !== undefined ? show : this.#data.containers.environment.visible;
|
|
637
|
+
const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
|
|
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 });
|
|
317
643
|
}
|
|
318
644
|
|
|
319
|
-
#
|
|
320
|
-
if (
|
|
321
|
-
|
|
322
|
-
|
|
645
|
+
#setOptionsMaterial(optionMaterial) {
|
|
646
|
+
if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const material =
|
|
651
|
+
this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) ||
|
|
652
|
+
null;
|
|
653
|
+
if (!material) {
|
|
654
|
+
this.#log("warn", "Material no encontrado en contenedor 'materials'", { name: optionMaterial.value });
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const containers = [];
|
|
659
|
+
if (
|
|
660
|
+
this.#data.containers.model.assetContainer &&
|
|
661
|
+
(this.#data.containers.model.changed || this.#data.containers.materials.changed || optionMaterial.changed)
|
|
662
|
+
) {
|
|
663
|
+
containers.push(this.#data.containers.model.assetContainer);
|
|
664
|
+
}
|
|
665
|
+
if (
|
|
666
|
+
this.#data.containers.environment.assetContainer &&
|
|
667
|
+
(this.#data.containers.environment.changed ||
|
|
668
|
+
this.#data.containers.materials.changed ||
|
|
669
|
+
optionMaterial.changed)
|
|
670
|
+
) {
|
|
671
|
+
containers.push(this.#data.containers.environment.assetContainer);
|
|
323
672
|
}
|
|
673
|
+
if (containers.length === 0) {
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
let someSetted = false;
|
|
678
|
+
containers.forEach((container) =>
|
|
679
|
+
container.meshes
|
|
680
|
+
.filter((meshToFilter) => meshToFilter.name.startsWith(optionMaterial.prefix))
|
|
681
|
+
.forEach((mesh) => {
|
|
682
|
+
mesh.material = material;
|
|
683
|
+
someSetted = true;
|
|
684
|
+
})
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
if (someSetted) {
|
|
688
|
+
optionMaterial.changed && (optionMaterial.changed.success = true);
|
|
689
|
+
this.#log("debug", "Material aplicado", { prefix: optionMaterial.prefix, name: optionMaterial.value });
|
|
690
|
+
} else {
|
|
691
|
+
optionMaterial.value = optionMaterial.changed?.oldValue ?? optionMaterial.value;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return someSetted;
|
|
324
695
|
}
|
|
325
696
|
|
|
326
|
-
#
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
697
|
+
#setOptionsMaterials() {
|
|
698
|
+
let someSetted = false;
|
|
699
|
+
Object.values(this.#data.options.materials).forEach((material) => {
|
|
700
|
+
let settedMaterial = this.#setOptionsMaterial(material);
|
|
701
|
+
someSetted = someSetted || settedMaterial;
|
|
702
|
+
});
|
|
703
|
+
return someSetted;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
#setOptionsCamera() {
|
|
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
|
+
) {
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
|
|
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
|
+
|
|
725
|
+
if (!camera) {
|
|
726
|
+
if (this.#data.options.camera.changed?.oldValue && this.#data.options.camera.changed?.oldValue !== this.#data.options.camera.value) {
|
|
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;
|
|
735
|
+
}
|
|
736
|
+
if (camera) {
|
|
737
|
+
camera.metadata = { locked: this.#data.options.camera.changed.oldLocked };
|
|
738
|
+
this.#data.options.camera.value = this.#data.options.camera.changed.oldValue;
|
|
739
|
+
this.#data.options.camera.locked = this.#data.options.camera.changed.oldLocked;
|
|
740
|
+
} else {
|
|
741
|
+
camera = this.#camera;
|
|
742
|
+
this.#data.options.camera.value = null;
|
|
743
|
+
this.#data.options.camera.locked = this.#camera.metadata.locked;
|
|
744
|
+
}
|
|
745
|
+
this.#data.options.camera.changed && (this.#data.options.camera.changed.success = false);
|
|
746
|
+
} else {
|
|
747
|
+
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
|
|
751
|
+
camera.attachControl(this.#canvas, true);
|
|
752
|
+
}
|
|
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
|
+
});
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#addContainer(container) {
|
|
762
|
+
if (container.assetContainer && !container.visible && container.show) {
|
|
763
|
+
container.assetContainer.addAllToScene();
|
|
764
|
+
container.visible = true;
|
|
765
|
+
this.#log("debug", `Añadido a escena: ${container.name}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
#removeContainer(container) {
|
|
770
|
+
if (container.assetContainer && container.visible) {
|
|
771
|
+
container.assetContainer.removeAllFromScene();
|
|
772
|
+
container.visible = false;
|
|
773
|
+
this.#log("debug", `Eliminado de escena: ${container.name}`);
|
|
330
774
|
}
|
|
331
775
|
}
|
|
332
776
|
|
|
333
|
-
#replaceContainer(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
777
|
+
#replaceContainer(container, newAssetContainer) {
|
|
778
|
+
// 1) quita y destruye el anterior si existía
|
|
779
|
+
const old = container.assetContainer;
|
|
780
|
+
if (old) {
|
|
781
|
+
if (container.visible) {
|
|
782
|
+
old.removeAllFromScene();
|
|
783
|
+
}
|
|
784
|
+
old.dispose();
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// 2) asigna el nuevo y prepara
|
|
788
|
+
container.assetContainer = newAssetContainer;
|
|
789
|
+
|
|
790
|
+
// Limitar luces por material (no usamos luces, poner 0 asegura shaders más simples)
|
|
791
|
+
container.assetContainer.materials?.forEach((m) => {
|
|
792
|
+
if ("maxSimultaneousLights" in m) {
|
|
793
|
+
m.maxSimultaneousLights = 0; // [LIGHTS OFF]
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
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);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
// 4) añade a escena
|
|
804
|
+
this.#addContainer(container);
|
|
805
|
+
|
|
806
|
+
// 5) fuerza recompilación con defines correctos del nuevo estado
|
|
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,
|
|
339
812
|
});
|
|
340
|
-
this.#addContainer(group);
|
|
341
813
|
}
|
|
342
814
|
|
|
343
|
-
async #loadAssetContainer(
|
|
815
|
+
async #loadAssetContainer(container) {
|
|
816
|
+
const storage = container?.storage;
|
|
817
|
+
|
|
344
818
|
if (!storage) {
|
|
819
|
+
this.#log("debug", `Sin storage para "${container?.name}"`);
|
|
345
820
|
return false;
|
|
346
821
|
}
|
|
347
822
|
|
|
823
|
+
this.#timeStart(`load:${container.name}`);
|
|
348
824
|
let source = storage.url || null;
|
|
349
825
|
|
|
350
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
|
+
});
|
|
351
832
|
await this.#initStorage(storage.db, storage.table);
|
|
352
833
|
const object = await loadModel(storage.id, storage.table);
|
|
353
834
|
source = object.data;
|
|
835
|
+
if (object.timeStamp === container.timeStamp) {
|
|
836
|
+
this.#log("debug", `${container.name}: sin cambios en IndexedDB`);
|
|
837
|
+
this.#timeEnd(`load:${container.name}`);
|
|
838
|
+
return false;
|
|
839
|
+
} else {
|
|
840
|
+
container.changed = { timeStamp: object.timeStamp, size: object.size, success: false };
|
|
841
|
+
this.#log("debug", `${container.name}: cambios detectados`, container.changed);
|
|
842
|
+
}
|
|
354
843
|
}
|
|
355
844
|
|
|
356
845
|
if (!source) {
|
|
846
|
+
this.#log("warn", `${container.name}: no hay source`);
|
|
847
|
+
this.#timeEnd(`load:${container.name}`);
|
|
357
848
|
return false;
|
|
358
849
|
}
|
|
359
850
|
|
|
360
851
|
let file = null;
|
|
361
852
|
|
|
362
|
-
let { blob, extension } = this.#decodeBase64(source);
|
|
853
|
+
let { blob, extension, size } = this.#decodeBase64(source);
|
|
363
854
|
if (blob && extension) {
|
|
364
|
-
file = new File([blob],
|
|
855
|
+
file = new File([blob], `${container.name}${extension}`, {
|
|
365
856
|
type: blob.type,
|
|
366
857
|
});
|
|
858
|
+
if (!container.changed) {
|
|
859
|
+
if (container.timeStamp === null && container.size === size) {
|
|
860
|
+
this.#log("debug", `${container.name}: Base64 sin cambios`);
|
|
861
|
+
this.#timeEnd(`load:${container.name}`);
|
|
862
|
+
return false;
|
|
863
|
+
} else {
|
|
864
|
+
container.changed = { timeStamp: null, size: size, success: false };
|
|
865
|
+
}
|
|
866
|
+
}
|
|
367
867
|
} else {
|
|
368
868
|
const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
|
|
369
869
|
extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
|
|
870
|
+
const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
|
|
871
|
+
if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
|
|
872
|
+
this.#log("debug", `${container.name}: URL sin cambios`);
|
|
873
|
+
this.#timeEnd(`load:${container.name}`);
|
|
874
|
+
return false;
|
|
875
|
+
} else {
|
|
876
|
+
container.changed = { timeStamp: fileTimeStamp, size: fileSize, success: false };
|
|
877
|
+
}
|
|
370
878
|
}
|
|
371
879
|
|
|
372
880
|
let options = {
|
|
373
881
|
pluginExtension: extension,
|
|
374
882
|
pluginOptions: {
|
|
375
883
|
gltf: {
|
|
884
|
+
loadAllMaterials: true,
|
|
376
885
|
preprocessUrlAsync: this.#transformUrl,
|
|
377
886
|
},
|
|
378
887
|
},
|
|
379
888
|
};
|
|
889
|
+
this.#log("info", `LoadAssetContainerAsync ${container.name}`, { extension, changed: container.changed });
|
|
380
890
|
|
|
381
|
-
|
|
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
|
+
}
|
|
382
900
|
}
|
|
383
901
|
|
|
384
|
-
async #loadContainers(loadModel = true, loadEnvironment = true) {
|
|
902
|
+
async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
|
|
903
|
+
this.#log("info", "loadContainers()", { loadModel, loadEnvironment, loadMaterials });
|
|
385
904
|
const promiseArray = [];
|
|
905
|
+
promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
|
|
906
|
+
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
|
|
907
|
+
promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
|
|
386
908
|
|
|
387
|
-
|
|
388
|
-
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#environment.storage) : false);
|
|
909
|
+
this.#setStatusSceneLoading();
|
|
389
910
|
|
|
390
911
|
Promise.allSettled(promiseArray)
|
|
391
912
|
.then(async (values) => {
|
|
392
913
|
const modelContainer = values[0];
|
|
393
914
|
const environmentContainer = values[1];
|
|
915
|
+
const materialsContainer = values[2];
|
|
916
|
+
|
|
917
|
+
this.#log(
|
|
918
|
+
"debug",
|
|
919
|
+
"Resultados Promise.allSettled",
|
|
920
|
+
values.map((v) => ({ status: v.status, hasValue: !!v.value }))
|
|
921
|
+
);
|
|
394
922
|
|
|
395
923
|
if (modelContainer.status === "fulfilled" && modelContainer.value) {
|
|
396
|
-
this.#
|
|
924
|
+
this.#stripImportedLights(modelContainer.value);
|
|
925
|
+
this.#replaceContainer(this.#data.containers.model, modelContainer.value);
|
|
926
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.model);
|
|
927
|
+
} else {
|
|
928
|
+
this.#data.containers.model.show
|
|
929
|
+
? this.#addContainer(this.#data.containers.model)
|
|
930
|
+
: this.#removeContainer(this.#data.containers.model);
|
|
397
931
|
}
|
|
398
932
|
|
|
399
933
|
if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
|
|
400
|
-
this.#
|
|
934
|
+
this.#stripImportedLights(environmentContainer.value);
|
|
935
|
+
this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
|
|
936
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.environment);
|
|
937
|
+
} else {
|
|
938
|
+
this.#data.containers.environment.show
|
|
939
|
+
? this.#addContainer(this.#data.containers.environment)
|
|
940
|
+
: this.#removeContainer(this.#data.containers.environment);
|
|
401
941
|
}
|
|
402
942
|
|
|
403
|
-
|
|
943
|
+
if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
|
|
944
|
+
this.#stripImportedLights(materialsContainer.value);
|
|
945
|
+
this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
|
|
946
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.materials);
|
|
947
|
+
}
|
|
404
948
|
|
|
405
|
-
this
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
);
|
|
949
|
+
this.#setOptionsMaterials();
|
|
950
|
+
this.#setOptionsCamera();
|
|
951
|
+
this.#setVisibilityOfWallAndFloorInModel();
|
|
952
|
+
this.#setStatusSceneLoaded();
|
|
953
|
+
this.#resetChangedFlags();
|
|
954
|
+
this.#log("info", "Escena cargada");
|
|
412
955
|
})
|
|
413
956
|
.catch((error) => {
|
|
414
|
-
|
|
957
|
+
this.loaded = true;
|
|
958
|
+
this.#log("error", "Failed to load containers", error);
|
|
415
959
|
this.dispatchEvent(
|
|
416
|
-
new CustomEvent("
|
|
417
|
-
detail: { error: error },
|
|
960
|
+
new CustomEvent("scene-error", {
|
|
418
961
|
bubbles: true,
|
|
962
|
+
cancelable: false,
|
|
419
963
|
composed: true,
|
|
964
|
+
detail: { error: error },
|
|
420
965
|
})
|
|
421
966
|
);
|
|
422
967
|
});
|
|
423
968
|
}
|
|
424
969
|
|
|
970
|
+
#stripImportedLights(container) {
|
|
971
|
+
const n = container?.lights?.length ?? 0;
|
|
972
|
+
if (n) container.lights.slice().forEach((l) => l.dispose());
|
|
973
|
+
this.#log("debug", `stripImportedLights(): ${n} → 0`);
|
|
974
|
+
}
|
|
975
|
+
|
|
425
976
|
// Public Methods
|
|
426
977
|
loadConfig(config) {
|
|
978
|
+
this.#log("info", "loadConfig()", typeof config === "string" ? "[string]" : config);
|
|
427
979
|
config = typeof config === "string" ? JSON.parse(config) : config;
|
|
428
980
|
if (!config) {
|
|
981
|
+
this.#log("warn", "loadConfig() → config vacío/nulo");
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// Containers
|
|
986
|
+
this.#data.containers.model.storage = config.model?.storage || null;
|
|
987
|
+
this.#data.containers.model.show =
|
|
988
|
+
config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
|
|
989
|
+
this.#data.containers.environment.storage = config.scene?.storage || null;
|
|
990
|
+
this.#data.containers.environment.show =
|
|
991
|
+
config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
|
|
992
|
+
this.#data.containers.materials.storage = config.materials?.storage || null;
|
|
993
|
+
|
|
994
|
+
// Options
|
|
995
|
+
if (config.options) {
|
|
996
|
+
this.#checkCameraChanged(config.options);
|
|
997
|
+
this.#checkMaterialsChanged(config.options);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
this.initialized && this.#loadContainers(true, true, true);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
setOptions(options) {
|
|
1004
|
+
this.#log("info", "setOptions()", options);
|
|
1005
|
+
if (!options) {
|
|
429
1006
|
return false;
|
|
430
1007
|
}
|
|
431
|
-
|
|
432
|
-
this.#
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
1008
|
+
|
|
1009
|
+
this.#setStatusOptionsLoading();
|
|
1010
|
+
|
|
1011
|
+
let someSetted = false;
|
|
1012
|
+
if (this.#checkCameraChanged(options)) {
|
|
1013
|
+
someSetted = someSetted || this.#setOptionsCamera();
|
|
1014
|
+
}
|
|
1015
|
+
if (this.#checkMaterialsChanged(options)) {
|
|
1016
|
+
someSetted = someSetted || this.#setOptionsMaterials();
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
this.#setStatusOptionsLoaded();
|
|
1020
|
+
this.#resetChangedFlags();
|
|
1021
|
+
|
|
1022
|
+
return someSetted;
|
|
436
1023
|
}
|
|
437
1024
|
|
|
438
1025
|
loadModel(model) {
|
|
1026
|
+
this.#log("info", "loadModel()", typeof model === "string" ? "[string]" : model);
|
|
439
1027
|
model = typeof model === "string" ? JSON.parse(model) : model;
|
|
440
1028
|
if (!model) {
|
|
1029
|
+
this.#log("warn", "loadModel() → model vacío/nulo");
|
|
441
1030
|
return false;
|
|
442
1031
|
}
|
|
443
|
-
this.#model.storage = model.storage || null;
|
|
444
|
-
this.#model.show =
|
|
445
|
-
|
|
1032
|
+
this.#data.containers.model.storage = model.storage || null;
|
|
1033
|
+
this.#data.containers.model.show =
|
|
1034
|
+
model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
1035
|
+
this.initialized && this.#loadContainers(true, false, false);
|
|
446
1036
|
}
|
|
447
1037
|
|
|
448
1038
|
loadScene(scene) {
|
|
1039
|
+
this.#log("info", "loadScene()", typeof scene === "string" ? "[string]" : scene);
|
|
449
1040
|
scene = typeof scene === "string" ? JSON.parse(scene) : scene;
|
|
450
1041
|
if (!scene) {
|
|
1042
|
+
this.#log("warn", "loadScene() → scene vacío/nulo");
|
|
451
1043
|
return false;
|
|
452
1044
|
}
|
|
453
|
-
this.#environment.storage = scene.storage || null;
|
|
454
|
-
this.#environment.show =
|
|
455
|
-
|
|
1045
|
+
this.#data.containers.environment.storage = scene.storage || null;
|
|
1046
|
+
this.#data.containers.environment.show =
|
|
1047
|
+
scene.visible !== undefined ? scene.visible : this.#data.containers.environment.show;
|
|
1048
|
+
this.initialized && this.#loadContainers(false, true, false);
|
|
456
1049
|
}
|
|
457
1050
|
|
|
458
1051
|
showModel() {
|
|
459
|
-
this.#model.show = true;
|
|
460
|
-
this.#addContainer(this.#model);
|
|
1052
|
+
this.#data.containers.model.show = true;
|
|
1053
|
+
this.#addContainer(this.#data.containers.model);
|
|
461
1054
|
}
|
|
462
1055
|
|
|
463
1056
|
hideModel() {
|
|
464
|
-
this.#model.show = false;
|
|
465
|
-
this.#removeContainer(this.#model);
|
|
1057
|
+
this.#data.containers.model.show = false;
|
|
1058
|
+
this.#removeContainer(this.#data.containers.model);
|
|
466
1059
|
}
|
|
467
1060
|
|
|
468
1061
|
showScene() {
|
|
469
|
-
this.#environment.show = true;
|
|
470
|
-
this.#addContainer(this.#environment);
|
|
1062
|
+
this.#data.containers.environment.show = true;
|
|
1063
|
+
this.#addContainer(this.#data.containers.environment);
|
|
471
1064
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
472
1065
|
}
|
|
473
1066
|
|
|
474
1067
|
hideScene() {
|
|
475
|
-
this.#environment.show = false;
|
|
476
|
-
this.#removeContainer(this.#environment);
|
|
1068
|
+
this.#data.containers.environment.show = false;
|
|
1069
|
+
this.#removeContainer(this.#data.containers.environment);
|
|
477
1070
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
478
1071
|
}
|
|
479
1072
|
|
|
480
1073
|
downloadModelGLB() {
|
|
481
1074
|
const fileName = "model";
|
|
482
|
-
GLTF2Export.GLBAsync(this.#model.
|
|
483
|
-
|
|
484
|
-
});
|
|
1075
|
+
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, {
|
|
1076
|
+
exportWithoutWaitingForScene: true,
|
|
1077
|
+
}).then((glb) => glb.downloadFiles());
|
|
485
1078
|
}
|
|
486
1079
|
|
|
487
1080
|
downloadModelUSDZ() {
|
|
488
1081
|
const fileName = "model";
|
|
489
|
-
USDZExportAsync(this.#model.
|
|
1082
|
+
USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
|
|
490
1083
|
if (response) {
|
|
491
1084
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
492
1085
|
}
|
|
@@ -504,9 +1097,9 @@ class PrefViewer extends HTMLElement {
|
|
|
504
1097
|
|
|
505
1098
|
downloadModelAndSceneGLB() {
|
|
506
1099
|
const fileName = "scene";
|
|
507
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) =>
|
|
508
|
-
glb.downloadFiles()
|
|
509
|
-
|
|
1100
|
+
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) =>
|
|
1101
|
+
glb.downloadFiles()
|
|
1102
|
+
);
|
|
510
1103
|
}
|
|
511
1104
|
}
|
|
512
1105
|
|