@preference-sl/pref-viewer 2.10.0-beta.9 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -5
- package/src/gltf-storage.js +167 -193
- package/src/index.js +501 -425
package/src/index.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* style="width:800px; height:600px;">
|
|
23
23
|
* </pref-viewer>
|
|
24
24
|
* ```
|
|
25
|
-
*
|
|
25
|
+
*
|
|
26
26
|
* Load scene a URL:
|
|
27
27
|
* ```html
|
|
28
28
|
* <pref-viewer
|
|
@@ -39,147 +39,112 @@
|
|
|
39
39
|
* </pref-viewer>
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName } from "@babylonjs/core";
|
|
42
|
+
import { Engine, Scene, ArcRotateCamera, Vector3, Color4, HemisphericLight, DirectionalLight, PointLight, ShadowGenerator, LoadAssetContainerAsync, Tools, WebXRSessionManager, WebXRDefaultExperience, MeshBuilder, WebXRFeatureName, HDRCubeTexture, IblShadowsRenderPipeline } from "@babylonjs/core";
|
|
43
43
|
import "@babylonjs/loaders";
|
|
44
44
|
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
45
45
|
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
46
46
|
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
47
47
|
import { initDb, loadModel } from "./gltf-storage.js";
|
|
48
48
|
|
|
49
|
-
class
|
|
50
|
-
static
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
class PrefViewerTask {
|
|
50
|
+
static Types = Object.freeze({
|
|
51
|
+
Config: "config",
|
|
52
|
+
Environment: "environment",
|
|
53
|
+
Materials: "materials",
|
|
54
|
+
Model: "model",
|
|
55
|
+
Options: "options",
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* value: any payload for the task
|
|
60
|
+
* type: must match one of PrefViewerTask.Types values (case-insensitive)
|
|
61
|
+
*/
|
|
62
|
+
constructor(value, type) {
|
|
63
|
+
this.value = value;
|
|
64
|
+
|
|
65
|
+
const t = typeof type === "string" ? type.toLowerCase() : String(type).toLowerCase();
|
|
66
|
+
const allowed = Object.values(PrefViewerTask.Types);
|
|
67
|
+
if (!allowed.includes(t)) {
|
|
68
|
+
throw new TypeError(
|
|
69
|
+
`PrefViewerTask: invalid type "${type}". Allowed types: ${allowed.join(", ")}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
this.type = t;
|
|
54
73
|
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
Object.freeze(this);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class PrefViewer extends HTMLElement {
|
|
79
|
+
initialized = false;
|
|
80
|
+
loaded = false;
|
|
81
|
+
loading = false;
|
|
82
|
+
#taskQueue = [];
|
|
57
83
|
|
|
58
84
|
#data = {
|
|
59
85
|
containers: {
|
|
60
86
|
model: {
|
|
61
87
|
name: "model",
|
|
62
|
-
|
|
88
|
+
assetContainer: null,
|
|
63
89
|
show: true,
|
|
64
90
|
storage: null,
|
|
65
91
|
visible: false,
|
|
66
92
|
size: null,
|
|
67
|
-
|
|
68
|
-
changed: false,
|
|
93
|
+
timeStamp: null,
|
|
94
|
+
changed: { pending: false, success: false },
|
|
69
95
|
},
|
|
70
96
|
environment: {
|
|
71
97
|
name: "environment",
|
|
72
|
-
|
|
98
|
+
assetContainer: null,
|
|
73
99
|
show: true,
|
|
74
100
|
storage: null,
|
|
75
101
|
visible: false,
|
|
76
102
|
size: null,
|
|
77
|
-
|
|
78
|
-
changed: false,
|
|
103
|
+
timeStamp: null,
|
|
104
|
+
changed: { pending: false, success: false },
|
|
79
105
|
},
|
|
80
106
|
materials: {
|
|
81
107
|
name: "materials",
|
|
82
|
-
|
|
108
|
+
assetContainer: null,
|
|
83
109
|
storage: null,
|
|
84
110
|
show: true,
|
|
85
111
|
visible: false,
|
|
86
112
|
size: null,
|
|
87
|
-
|
|
88
|
-
changed: false,
|
|
113
|
+
timeStamp: null,
|
|
114
|
+
changed: { pending: false, success: false },
|
|
89
115
|
},
|
|
90
116
|
},
|
|
91
117
|
options: {
|
|
92
118
|
camera: {
|
|
93
119
|
value: null,
|
|
94
120
|
locked: true,
|
|
95
|
-
changed: false,
|
|
121
|
+
changed: { pending: false, success: false },
|
|
96
122
|
},
|
|
97
123
|
materials: {
|
|
98
124
|
innerWall: {
|
|
99
125
|
value: null,
|
|
100
126
|
prefix: "innerWall",
|
|
101
|
-
changed: false,
|
|
127
|
+
changed: { pending: false, success: false },
|
|
102
128
|
},
|
|
103
129
|
outerWall: {
|
|
104
130
|
value: null,
|
|
105
131
|
prefix: "outerWall",
|
|
106
|
-
changed: false,
|
|
132
|
+
changed: { pending: false, success: false },
|
|
107
133
|
},
|
|
108
134
|
innerFloor: {
|
|
109
135
|
value: null,
|
|
110
136
|
prefix: "innerFloor",
|
|
111
|
-
changed: false,
|
|
137
|
+
changed: { pending: false, success: false },
|
|
112
138
|
},
|
|
113
139
|
outerFloor: {
|
|
114
140
|
value: null,
|
|
115
141
|
prefix: "outerFloor",
|
|
116
|
-
changed: false,
|
|
142
|
+
changed: { pending: false, success: false },
|
|
117
143
|
},
|
|
118
144
|
},
|
|
119
145
|
},
|
|
120
146
|
};
|
|
121
147
|
|
|
122
|
-
#log(level, message, context) {
|
|
123
|
-
const levels = PrefViewer.LOG_LEVELS;
|
|
124
|
-
const current = levels[this.#logLevel] ?? levels[PrefViewer.DEFAULT_LOG_LEVEL];
|
|
125
|
-
const incoming = levels[level] ?? levels.info;
|
|
126
|
-
if (incoming > current || current === levels.none) return;
|
|
127
|
-
|
|
128
|
-
const logger = console[level] ?? console.log;
|
|
129
|
-
if (context !== undefined) {
|
|
130
|
-
logger(`${PrefViewer.LOG_PREFIX}: ${message}`, context);
|
|
131
|
-
} else {
|
|
132
|
-
logger(`${PrefViewer.LOG_PREFIX}: ${message}`);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
#setLogLevel(level) {
|
|
137
|
-
const value = String(level || "").toLowerCase();
|
|
138
|
-
this.#logLevel = (value in PrefViewer.LOG_LEVELS) ? value : PrefViewer.DEFAULT_LOG_LEVEL;
|
|
139
|
-
this.#logInfo("Log level set", { level: this.#logLevel });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#logDebug(message, context) {
|
|
143
|
-
this.#log("debug", message, context);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
#logInfo(message, context) {
|
|
147
|
-
this.#log("info", message, context);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
#logWarn(message, context) {
|
|
151
|
-
this.#log("warn", message, context);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
#logError(message, context) {
|
|
155
|
-
this.#log("error", message, context);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
#summarizeValue(value) {
|
|
159
|
-
if (typeof value === "string" && value.length > 150) {
|
|
160
|
-
return `${value.slice(0, 150)}… (${value.length} chars)`;
|
|
161
|
-
}
|
|
162
|
-
return value;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
#describeStorage(storage) {
|
|
166
|
-
if (!storage) {
|
|
167
|
-
return "none";
|
|
168
|
-
}
|
|
169
|
-
if (storage.db && storage.table && storage.id) {
|
|
170
|
-
return `IndexedDB(${storage.db}/${storage.table}#${storage.id})`;
|
|
171
|
-
}
|
|
172
|
-
if (typeof storage.url === "string") {
|
|
173
|
-
return storage.url.startsWith("data:") ? "data-url" : storage.url;
|
|
174
|
-
}
|
|
175
|
-
return "unknown";
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
static get observedAttributes() {
|
|
179
|
-
// Añadimos "log-level" para controlar logs fuera del objeto config
|
|
180
|
-
return ["config", "model", "scene", "show-model", "show-scene", "log-level"];
|
|
181
|
-
}
|
|
182
|
-
|
|
183
148
|
// DOM elements
|
|
184
149
|
#wrapper = null;
|
|
185
150
|
#canvas = null;
|
|
@@ -196,7 +161,6 @@ class PrefViewer extends HTMLElement {
|
|
|
196
161
|
|
|
197
162
|
constructor() {
|
|
198
163
|
super();
|
|
199
|
-
this.#logDebug("Constructing PrefViewer instance");
|
|
200
164
|
this.attachShadow({ mode: "open" });
|
|
201
165
|
this.#createCanvas();
|
|
202
166
|
this.#wrapCanvas();
|
|
@@ -212,13 +176,11 @@ class PrefViewer extends HTMLElement {
|
|
|
212
176
|
};
|
|
213
177
|
}
|
|
214
178
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
179
|
+
static get observedAttributes() {
|
|
180
|
+
return ["config", "model", "scene", "show-model", "show-scene"];
|
|
181
|
+
}
|
|
220
182
|
|
|
221
|
-
|
|
183
|
+
attributeChangedCallback(name, _old, value) {
|
|
222
184
|
let data = null;
|
|
223
185
|
switch (name) {
|
|
224
186
|
case "config":
|
|
@@ -230,10 +192,15 @@ class PrefViewer extends HTMLElement {
|
|
|
230
192
|
case "scene":
|
|
231
193
|
this.loadScene(value);
|
|
232
194
|
break;
|
|
195
|
+
case "materials":
|
|
196
|
+
this.loadMaterials(value);
|
|
197
|
+
break;
|
|
198
|
+
case "options":
|
|
199
|
+
this.setOptions(value);
|
|
200
|
+
break;
|
|
233
201
|
case "show-model":
|
|
234
202
|
data = value.toLowerCase?.() === "true";
|
|
235
|
-
|
|
236
|
-
if (this.#initialized) {
|
|
203
|
+
if (this.initialized) {
|
|
237
204
|
data ? this.showModel() : this.hideModel();
|
|
238
205
|
} else {
|
|
239
206
|
this.#data.containers.model.show = data;
|
|
@@ -241,8 +208,7 @@ class PrefViewer extends HTMLElement {
|
|
|
241
208
|
break;
|
|
242
209
|
case "show-scene":
|
|
243
210
|
data = value.toLowerCase?.() === "true";
|
|
244
|
-
|
|
245
|
-
if (this.#initialized) {
|
|
211
|
+
if (this.initialized) {
|
|
246
212
|
data ? this.showScene() : this.hideScene();
|
|
247
213
|
} else {
|
|
248
214
|
this.#data.containers.environment.show = data;
|
|
@@ -254,33 +220,30 @@ class PrefViewer extends HTMLElement {
|
|
|
254
220
|
connectedCallback() {
|
|
255
221
|
if (!this.hasAttribute("config")) {
|
|
256
222
|
const error = 'PrefViewer: provide "models" as array of model and environment';
|
|
257
|
-
|
|
223
|
+
console.error(error);
|
|
258
224
|
this.dispatchEvent(
|
|
259
|
-
new CustomEvent("
|
|
260
|
-
detail: { error: new Error(error) },
|
|
225
|
+
new CustomEvent("scene-error", {
|
|
261
226
|
bubbles: true,
|
|
227
|
+
cancelable: false,
|
|
262
228
|
composed: true,
|
|
229
|
+
detail: { error: new Error(error) },
|
|
263
230
|
})
|
|
264
231
|
);
|
|
265
232
|
return false;
|
|
266
233
|
}
|
|
267
234
|
|
|
268
|
-
this.#logDebug("Connected to DOM, initializing Babylon");
|
|
269
235
|
this.#initializeBabylon();
|
|
270
|
-
this
|
|
271
|
-
this.#
|
|
272
|
-
this.#logInfo("Initialization completed", { initialized: this.#initialized });
|
|
236
|
+
this.initialized = true;
|
|
237
|
+
this.#processNextTask();
|
|
273
238
|
}
|
|
274
239
|
|
|
275
240
|
disconnectedCallback() {
|
|
276
|
-
this.#logDebug("Disconnected from DOM, disposing resources");
|
|
277
241
|
this.#disposeEngine();
|
|
278
242
|
this.#canvasResizeObserver.disconnect();
|
|
279
243
|
}
|
|
280
244
|
|
|
281
245
|
// Web Component
|
|
282
246
|
#createCanvas() {
|
|
283
|
-
this.#logDebug("Creating rendering canvas");
|
|
284
247
|
this.#canvas = document.createElement("canvas");
|
|
285
248
|
Object.assign(this.#canvas.style, {
|
|
286
249
|
width: "100%",
|
|
@@ -288,11 +251,9 @@ class PrefViewer extends HTMLElement {
|
|
|
288
251
|
display: "block",
|
|
289
252
|
outline: "none",
|
|
290
253
|
});
|
|
291
|
-
this.#logDebug("Canvas element created and styled");
|
|
292
254
|
}
|
|
293
255
|
|
|
294
256
|
#wrapCanvas() {
|
|
295
|
-
this.#logDebug("Wrapping canvas inside container div");
|
|
296
257
|
this.#wrapper = document.createElement("div");
|
|
297
258
|
Object.assign(this.#wrapper.style, {
|
|
298
259
|
width: "100%",
|
|
@@ -301,94 +262,171 @@ class PrefViewer extends HTMLElement {
|
|
|
301
262
|
});
|
|
302
263
|
this.#wrapper.appendChild(this.#canvas);
|
|
303
264
|
this.shadowRoot.append(this.#wrapper);
|
|
304
|
-
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#setStatusLoading() {
|
|
268
|
+
this.loaded = false;
|
|
269
|
+
this.loading = true;
|
|
270
|
+
if (this.hasAttribute("loaded")) {
|
|
271
|
+
this.removeAttribute("loaded");
|
|
272
|
+
}
|
|
273
|
+
this.setAttribute("loading", "");
|
|
274
|
+
this.dispatchEvent(
|
|
275
|
+
new CustomEvent("scene-loading", {
|
|
276
|
+
bubbles: true,
|
|
277
|
+
cancelable: false,
|
|
278
|
+
composed: true,
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
this.#engine.stopRenderLoop(this.#renderLoop);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async #setStatusLoaded() {
|
|
285
|
+
const toLoadDetail = {
|
|
286
|
+
container_model: !!this.#data.containers.model.changed.pending,
|
|
287
|
+
container_environment: !!this.#data.containers.environment.changed.pending,
|
|
288
|
+
container_materials: !!this.#data.containers.materials.changed.pending,
|
|
289
|
+
options_camera: !!this.#data.options.camera.changed.pending,
|
|
290
|
+
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.pending,
|
|
291
|
+
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.pending,
|
|
292
|
+
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.pending,
|
|
293
|
+
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.pending,
|
|
294
|
+
};
|
|
295
|
+
const loadedDetail = {
|
|
296
|
+
container_model: !!this.#data.containers.model.changed.success,
|
|
297
|
+
container_environment: !!this.#data.containers.environment.changed.success,
|
|
298
|
+
container_materials: !!this.#data.containers.materials.changed.success,
|
|
299
|
+
options_camera: !!this.#data.options.camera.changed.success,
|
|
300
|
+
options_innerWallMaterial: !!this.#data.options.materials.innerWall.changed.success,
|
|
301
|
+
options_outerWallMaterial: !!this.#data.options.materials.outerWall.changed.success,
|
|
302
|
+
options_innerFloorMaterial: !!this.#data.options.materials.innerFloor.changed.success,
|
|
303
|
+
options_outerFloorMaterial: !!this.#data.options.materials.outerFloor.changed.success,
|
|
304
|
+
};
|
|
305
|
+
const detail = {
|
|
306
|
+
tried: toLoadDetail,
|
|
307
|
+
success: loadedDetail,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
this.dispatchEvent(
|
|
311
|
+
new CustomEvent("scene-loaded", {
|
|
312
|
+
bubbles: true,
|
|
313
|
+
cancelable: false,
|
|
314
|
+
composed: true,
|
|
315
|
+
detail: detail,
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
await this.#scene.whenReadyAsync();
|
|
320
|
+
this.#engine.runRenderLoop(this.#renderLoop);
|
|
321
|
+
|
|
322
|
+
this.#resetChangedFlags();
|
|
323
|
+
|
|
324
|
+
if (this.hasAttribute("loading")) {
|
|
325
|
+
this.removeAttribute("loading");
|
|
326
|
+
}
|
|
327
|
+
this.setAttribute("loaded", "");
|
|
328
|
+
|
|
329
|
+
this.loaded = true;
|
|
330
|
+
this.loading = false;
|
|
331
|
+
|
|
332
|
+
this.#processNextTask();
|
|
305
333
|
}
|
|
306
334
|
|
|
307
335
|
// Data
|
|
308
336
|
#checkCameraChanged(options) {
|
|
309
337
|
if (!options || !options.camera) {
|
|
310
|
-
this.#logDebug("Camera options not provided or unchanged");
|
|
311
338
|
return false;
|
|
312
339
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
340
|
+
const prev = this.#data.options.camera.value;
|
|
341
|
+
const changed = options.camera !== prev;
|
|
342
|
+
|
|
343
|
+
this.#data.options.camera.changed.pending = changed;
|
|
344
|
+
this.#data.options.camera.changed.success = false;
|
|
345
|
+
if (changed) {
|
|
346
|
+
this.#data.options.camera.changed.value = prev;
|
|
347
|
+
this.#data.options.camera.changed.locked = this.#data.options.camera.locked;
|
|
348
|
+
this.#data.options.camera.value = options.camera;
|
|
349
|
+
}
|
|
350
|
+
return changed;
|
|
317
351
|
}
|
|
318
352
|
|
|
319
353
|
#checkMaterialsChanged(options) {
|
|
320
354
|
if (!options) {
|
|
321
|
-
this.#logDebug("Material options not provided");
|
|
322
355
|
return false;
|
|
323
356
|
}
|
|
324
357
|
let someChanged = false;
|
|
325
358
|
Object.keys(this.#data.options.materials).forEach((material) => {
|
|
326
359
|
const key = `${material}Material`;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
changed
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
360
|
+
const state = this.#data.options.materials[material];
|
|
361
|
+
const prev = state.value;
|
|
362
|
+
const incoming = options[key];
|
|
363
|
+
const changed = !!incoming && incoming !== prev;
|
|
364
|
+
|
|
365
|
+
state.changed.pending = changed;
|
|
366
|
+
state.changed.success = false;
|
|
367
|
+
if (changed) {
|
|
368
|
+
state.changed.value = prev;
|
|
369
|
+
state.value = incoming;
|
|
370
|
+
}
|
|
371
|
+
someChanged = someChanged || changed;
|
|
337
372
|
});
|
|
338
373
|
return someChanged;
|
|
339
374
|
}
|
|
340
375
|
|
|
341
|
-
#storeChangedFlagsForContainer(container) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
376
|
+
#storeChangedFlagsForContainer(container, success) {
|
|
377
|
+
if (success) {
|
|
378
|
+
container.timeStamp = container.changed.timeStamp;
|
|
379
|
+
container.size = container.changed.size;
|
|
380
|
+
container.changed.success = true;
|
|
381
|
+
} else {
|
|
382
|
+
container.changed.success = false;
|
|
383
|
+
}
|
|
345
384
|
}
|
|
346
385
|
|
|
347
386
|
#resetChangedFlags() {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
this.#
|
|
387
|
+
const reset = (node) => {
|
|
388
|
+
node.changed = { pending: false, success: false };
|
|
389
|
+
};
|
|
390
|
+
Object.values(this.#data.containers).forEach(reset);
|
|
391
|
+
Object.values(this.#data.options.materials).forEach(reset);
|
|
392
|
+
reset(this.#data.options.camera);
|
|
352
393
|
}
|
|
353
394
|
|
|
354
395
|
// Babylon.js
|
|
355
396
|
async #initializeBabylon() {
|
|
356
|
-
this.#logInfo("Initializing Babylon engine and scene");
|
|
357
397
|
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
398
|
+
this.#engine.disableUniformBuffers = true;
|
|
358
399
|
this.#scene = new Scene(this.#engine);
|
|
359
400
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
360
401
|
this.#createCamera();
|
|
361
402
|
this.#createLights();
|
|
362
403
|
this.#setupInteraction();
|
|
363
|
-
|
|
364
|
-
this.#engine.runRenderLoop(() => this.#scene && this.#scene.render());
|
|
365
|
-
this.#canvasResizeObserver.observe(this.#canvas);
|
|
366
|
-
this.#logDebug("Engine render loop started and resize observer attached");
|
|
367
|
-
|
|
368
404
|
await this.#createXRExperience();
|
|
369
|
-
this.#
|
|
405
|
+
this.#engine.runRenderLoop(this.#renderLoop);
|
|
406
|
+
this.#canvasResizeObserver.observe(this.#canvas);
|
|
370
407
|
}
|
|
371
408
|
|
|
372
|
-
|
|
373
|
-
|
|
409
|
+
// If this function is defined as '#renderLoop() {}' it is not executed in 'this.#engine.runRenderLoop(this.#renderLoop)'
|
|
410
|
+
#renderLoop = () => {
|
|
411
|
+
this.#scene && this.#scene.render();
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
#addStylesToARButton() {
|
|
374
415
|
const css = '.babylonVRicon { color: #868686; border-color: #868686; border-style: solid; margin-left: 10px; height: 50px; width: 80px; background-color: rgba(51,51,51,0.7); background-image: url(data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%222048%22%20height%3D%221152%22%20viewBox%3D%220%200%202048%201152%22%20version%3D%221.1%22%3E%3Cpath%20transform%3D%22rotate%28180%201024%2C576.0000000000001%29%22%20d%3D%22m1109%2C896q17%2C0%2030%2C-12t13%2C-30t-12.5%2C-30.5t-30.5%2C-12.5l-170%2C0q-18%2C0%20-30.5%2C12.5t-12.5%2C30.5t13%2C30t30%2C12l170%2C0zm-85%2C256q59%2C0%20132.5%2C-1.5t154.5%2C-5.5t164.5%2C-11.5t163%2C-20t150%2C-30t124.5%2C-41.5q23%2C-11%2042%2C-24t38%2C-30q27%2C-25%2041%2C-61.5t14%2C-72.5l0%2C-257q0%2C-123%20-47%2C-232t-128%2C-190t-190%2C-128t-232%2C-47l-81%2C0q-37%2C0%20-68.5%2C14t-60.5%2C34.5t-55.5%2C45t-53%2C45t-53%2C34.5t-55.5%2C14t-55.5%2C-14t-53%2C-34.5t-53%2C-45t-55.5%2C-45t-60.5%2C-34.5t-68.5%2C-14l-81%2C0q-123%2C0%20-232%2C47t-190%2C128t-128%2C190t-47%2C232l0%2C257q0%2C68%2038%2C115t97%2C73q54%2C24%20124.5%2C41.5t150%2C30t163%2C20t164.5%2C11.5t154.5%2C5.5t132.5%2C1.5zm939%2C-298q0%2C39%20-24.5%2C67t-58.5%2C42q-54%2C23%20-122%2C39.5t-143.5%2C28t-155.5%2C19t-157%2C11t-148.5%2C5t-129.5%2C1.5q-59%2C0%20-130%2C-1.5t-148%2C-5t-157%2C-11t-155.5%2C-19t-143.5%2C-28t-122%2C-39.5q-34%2C-14%20-58.5%2C-42t-24.5%2C-67l0%2C-257q0%2C-106%2040.5%2C-199t110%2C-162.5t162.5%2C-109.5t199%2C-40l81%2C0q27%2C0%2052%2C14t50%2C34.5t51%2C44.5t55.5%2C44.5t63.5%2C34.5t74%2C14t74%2C-14t63.5%2C-34.5t55.5%2C-44.5t51%2C-44.5t50%2C-34.5t52%2C-14l14%2C0q37%2C0%2070%2C0.5t64.5%2C4.5t63.5%2C12t68%2C23q71%2C30%20128.5%2C78.5t98.5%2C110t63.5%2C133.5t22.5%2C149l0%2C257z%22%20fill%3D%22white%22%20/%3E%3C/svg%3E%0A); background-size: 80%; background-repeat:no-repeat; background-position: center; border: none; outline: none; transition: transform 0.125s ease-out } .babylonVRicon:hover { transform: scale(1.05) } .babylonVRicon:active {background-color: rgba(51,51,51,1) } .babylonVRicon:focus {background-color: rgba(51,51,51,1) }.babylonVRicon.vrdisplaypresenting { background-image: none;} .vrdisplaypresenting::after { content: "EXIT"} .xr-error::after { content: "ERROR"}';
|
|
375
416
|
const style = document.createElement("style");
|
|
376
417
|
style.appendChild(document.createTextNode(css));
|
|
377
418
|
this.#wrapper.appendChild(style);
|
|
378
|
-
this.#logDebug("AR button styles applied");
|
|
379
419
|
}
|
|
380
420
|
|
|
381
421
|
async #createXRExperience() {
|
|
382
422
|
if (this.#XRExperience) {
|
|
383
|
-
this.#logDebug("XR experience already created, skipping");
|
|
384
423
|
return true;
|
|
385
424
|
}
|
|
386
425
|
|
|
387
|
-
this.#logDebug("Attempting to create XR experience");
|
|
388
426
|
const sessionMode = "immersive-ar";
|
|
389
427
|
const sessionSupported = await WebXRSessionManager.IsSessionSupportedAsync(sessionMode);
|
|
390
428
|
if (!sessionSupported) {
|
|
391
|
-
|
|
429
|
+
console.info("PrefViewer: WebXR in mode AR is not supported");
|
|
392
430
|
return false;
|
|
393
431
|
}
|
|
394
432
|
|
|
@@ -407,7 +445,6 @@ class PrefViewer extends HTMLElement {
|
|
|
407
445
|
};
|
|
408
446
|
|
|
409
447
|
this.#XRExperience = await WebXRDefaultExperience.CreateAsync(this.#scene, options);
|
|
410
|
-
this.#logInfo("XR experience created successfully", { sessionMode });
|
|
411
448
|
|
|
412
449
|
const featuresManager = this.#XRExperience.baseExperience.featuresManager;
|
|
413
450
|
featuresManager.enableFeature(WebXRFeatureName.TELEPORTATION, "stable", {
|
|
@@ -415,50 +452,41 @@ class PrefViewer extends HTMLElement {
|
|
|
415
452
|
floorMeshes: [ground],
|
|
416
453
|
timeToTeleport: 1500,
|
|
417
454
|
});
|
|
418
|
-
this.#logDebug("XR teleportation feature enabled");
|
|
419
455
|
|
|
420
456
|
this.#XRExperience.baseExperience.sessionManager.onXRReady.add(() => {
|
|
421
457
|
// Set the initial position of xrCamera: use nonVRCamera, which contains a copy of the original this.#scene.activeCamera before entering XR
|
|
422
458
|
this.#XRExperience.baseExperience.camera.setTransformationFromNonVRCamera(this.#XRExperience.baseExperience._nonVRCamera);
|
|
423
459
|
this.#XRExperience.baseExperience.camera.setTarget(Vector3.Zero());
|
|
424
460
|
this.#XRExperience.baseExperience.onInitialXRPoseSetObservable.notifyObservers(this.#XRExperience.baseExperience.camera);
|
|
425
|
-
this.#logDebug("XR session ready and camera transformed");
|
|
426
461
|
});
|
|
427
462
|
|
|
428
|
-
this
|
|
463
|
+
this.#addStylesToARButton();
|
|
429
464
|
} catch (error) {
|
|
430
|
-
|
|
465
|
+
console.warn("PrefViewer: failed to create WebXR experience", error);
|
|
431
466
|
this.#XRExperience = null;
|
|
432
467
|
}
|
|
433
468
|
}
|
|
434
469
|
|
|
435
|
-
#canvasResizeObserver = new ResizeObserver(() =>
|
|
436
|
-
if (this.#engine) {
|
|
437
|
-
this.#logDebug("Resize observer triggered");
|
|
438
|
-
this.#engine.resize();
|
|
439
|
-
}
|
|
440
|
-
});
|
|
470
|
+
#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
441
471
|
|
|
442
472
|
#createCamera() {
|
|
443
|
-
this.#logDebug("Creating default camera");
|
|
444
473
|
this.#camera = new ArcRotateCamera("camera", (3 * Math.PI) / 2, Math.PI * 0.47, 10, Vector3.Zero(), this.#scene);
|
|
445
474
|
this.#camera.upperBetaLimit = Math.PI * 0.48;
|
|
446
475
|
this.#camera.lowerBetaLimit = Math.PI * 0.25;
|
|
447
476
|
this.#camera.lowerRadiusLimit = 5;
|
|
448
477
|
this.#camera.upperRadiusLimit = 20;
|
|
449
|
-
this.#camera.metadata = { locked: false }
|
|
450
|
-
this.#camera = this.#camera;
|
|
478
|
+
this.#camera.metadata = { locked: false };
|
|
451
479
|
this.#camera.attachControl(this.#canvas, true);
|
|
452
|
-
this.#
|
|
453
|
-
upperBetaLimit: this.#camera.upperBetaLimit,
|
|
454
|
-
lowerBetaLimit: this.#camera.lowerBetaLimit,
|
|
455
|
-
lowerRadiusLimit: this.#camera.lowerRadiusLimit,
|
|
456
|
-
upperRadiusLimit: this.#camera.upperRadiusLimit,
|
|
457
|
-
});
|
|
480
|
+
this.#scene.activeCamera = this.#camera;
|
|
458
481
|
}
|
|
459
482
|
|
|
460
483
|
#createLights() {
|
|
461
|
-
this.#
|
|
484
|
+
this.#initEnvironmentTexture();
|
|
485
|
+
|
|
486
|
+
if (this.#scene.environmentTexture) {
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
|
|
462
490
|
// 1) Stronger ambient fill
|
|
463
491
|
this.#hemiLight = new HemisphericLight("hemiLight", new Vector3(-10, 10, -10), this.#scene);
|
|
464
492
|
this.#hemiLight.intensity = 0.6;
|
|
@@ -468,7 +496,7 @@ class PrefViewer extends HTMLElement {
|
|
|
468
496
|
this.#dirLight.position = new Vector3(5, 4, 5); // light is IN FRONT + ABOVE + to the RIGHT
|
|
469
497
|
this.#dirLight.intensity = 0.6;
|
|
470
498
|
|
|
471
|
-
// 3) Soft shadows
|
|
499
|
+
// // 3) Soft shadows
|
|
472
500
|
this.#shadowGen = new ShadowGenerator(1024, this.#dirLight);
|
|
473
501
|
this.#shadowGen.useBlurExponentialShadowMap = true;
|
|
474
502
|
this.#shadowGen.blurKernel = 16;
|
|
@@ -478,52 +506,120 @@ class PrefViewer extends HTMLElement {
|
|
|
478
506
|
this.#cameraLight = new PointLight("pl", this.#camera.position, this.#scene);
|
|
479
507
|
this.#cameraLight.parent = this.#camera;
|
|
480
508
|
this.#cameraLight.intensity = 0.3;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
#initEnvironmentTexture() {
|
|
512
|
+
return false;
|
|
513
|
+
if (this.#scene.environmentTexture) {
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
const hdrTextureURI = "../src/environments/noon_grass.hdr";
|
|
517
|
+
const hdrTexture = new HDRCubeTexture(hdrTextureURI, this.#scene, 128);
|
|
518
|
+
hdrTexture.gammaSpace = true;
|
|
519
|
+
hdrTexture._noMipmap = false;
|
|
520
|
+
hdrTexture.level = 2.0;
|
|
521
|
+
this.#scene.environmentTexture = hdrTexture;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
#initIBLShadows() {
|
|
525
|
+
if (!this.#scene.environmentTexture) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
let createIBLShadowPipeline = function (scene) {
|
|
530
|
+
const pipeline = new IblShadowsRenderPipeline(
|
|
531
|
+
"iblShadowsPipeline",
|
|
532
|
+
scene,
|
|
533
|
+
{
|
|
534
|
+
resolutionExp: 7,
|
|
535
|
+
sampleDirections: 2,
|
|
536
|
+
ssShadowsEnabled: true,
|
|
537
|
+
shadowRemanence: 0.8,
|
|
538
|
+
triPlanarVoxelization: true,
|
|
539
|
+
shadowOpacity: 0.8,
|
|
540
|
+
},
|
|
541
|
+
[scene.activeCamera]
|
|
542
|
+
);
|
|
543
|
+
pipeline.allowDebugPasses = false;
|
|
544
|
+
pipeline.gbufferDebugEnabled = true;
|
|
545
|
+
pipeline.importanceSamplingDebugEnabled = false;
|
|
546
|
+
pipeline.voxelDebugEnabled = false;
|
|
547
|
+
pipeline.voxelDebugDisplayMip = 1;
|
|
548
|
+
pipeline.voxelDebugAxis = 2;
|
|
549
|
+
pipeline.voxelTracingDebugEnabled = false;
|
|
550
|
+
pipeline.spatialBlurPassDebugEnabled = false;
|
|
551
|
+
pipeline.accumulationPassDebugEnabled = false;
|
|
552
|
+
return pipeline;
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
let iblShadowsPipeline = createIBLShadowPipeline(this.#scene);
|
|
556
|
+
|
|
557
|
+
this.#scene.meshes.forEach((mesh) => {
|
|
558
|
+
if (mesh.id.startsWith("__root__") || mesh.name === "hdri") {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
iblShadowsPipeline.addShadowCastingMesh(mesh);
|
|
562
|
+
iblShadowsPipeline.updateSceneBounds();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
this.#scene.materials.forEach((material) => {
|
|
566
|
+
iblShadowsPipeline.addShadowReceivingMaterial(material);
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
#initShadows() {
|
|
571
|
+
if (!this.#scene.environmentTexture) {
|
|
572
|
+
this.#initIBLShadows();
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
this.#scene.meshes.forEach((mesh) => {
|
|
577
|
+
if (mesh.id.startsWith("__root__")) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
mesh.receiveShadows = true;
|
|
581
|
+
if (!mesh.name === "hdri") {
|
|
582
|
+
this.#shadowGen.addShadowCaster(mesh, true);
|
|
583
|
+
}
|
|
486
584
|
});
|
|
487
585
|
}
|
|
488
586
|
|
|
587
|
+
#setMaxSimultaneousLights() {
|
|
588
|
+
let lightsNumber = 1; // Como mínimo una luz correspondiente a la textura de environmentTexture
|
|
589
|
+
this.#scene.lights.forEach((light) => {
|
|
590
|
+
if (light.isEnabled()) {
|
|
591
|
+
++lightsNumber;
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
if (this.#scene.materials) {
|
|
595
|
+
this.#scene.materials.forEach((material) => (material.maxSimultaneousLights = lightsNumber));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
489
599
|
#setupInteraction() {
|
|
490
|
-
this.#logDebug("Setting up canvas interaction listeners");
|
|
491
600
|
this.#canvas.addEventListener("wheel", (event) => {
|
|
492
601
|
if (!this.#scene || !this.#camera) {
|
|
493
|
-
this.#logWarn("Wheel interaction ignored; scene or camera missing");
|
|
494
602
|
return false;
|
|
495
603
|
}
|
|
496
604
|
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
497
605
|
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
498
606
|
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
499
607
|
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
500
|
-
this.#logDebug("Processed wheel interaction", {
|
|
501
|
-
deltaY: event.deltaY,
|
|
502
|
-
inertialRadiusOffset: this.#scene.activeCamera.inertialRadiusOffset,
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
this.#logDebug("Wheel interaction ignored because camera is locked");
|
|
506
608
|
}
|
|
507
609
|
event.preventDefault();
|
|
508
610
|
});
|
|
509
611
|
}
|
|
510
612
|
|
|
511
613
|
#disposeEngine() {
|
|
512
|
-
if (!this.#engine)
|
|
513
|
-
this.#logDebug("Dispose engine called but engine already null");
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
this.#logDebug("Disposing Babylon resources");
|
|
614
|
+
if (!this.#engine) return;
|
|
517
615
|
this.#engine.dispose();
|
|
518
616
|
this.#engine = this.#scene = this.#camera = null;
|
|
519
617
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
520
618
|
this.#shadowGen = null;
|
|
521
|
-
this.#logDebug("Babylon resources disposed");
|
|
522
619
|
}
|
|
523
620
|
|
|
524
621
|
// Utility methods for loading gltf/glb
|
|
525
622
|
async #getServerFileDataHeader(uri) {
|
|
526
|
-
this.#logDebug("Requesting server file header", { uri });
|
|
527
623
|
return new Promise((resolve) => {
|
|
528
624
|
const xhr = new XMLHttpRequest();
|
|
529
625
|
xhr.open("HEAD", uri, true);
|
|
@@ -531,17 +627,14 @@ class PrefViewer extends HTMLElement {
|
|
|
531
627
|
xhr.onload = () => {
|
|
532
628
|
if (xhr.status === 200) {
|
|
533
629
|
const size = parseInt(xhr.getResponseHeader("Content-Length"));
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
resolve(size, timestamp);
|
|
630
|
+
const timeStamp = new Date(xhr.getResponseHeader("Last-Modified")).toISOString();
|
|
631
|
+
resolve([size, timeStamp]);
|
|
537
632
|
} else {
|
|
538
|
-
|
|
539
|
-
resolve(0, null);
|
|
633
|
+
resolve([0, null]);
|
|
540
634
|
}
|
|
541
635
|
};
|
|
542
636
|
xhr.onerror = () => {
|
|
543
|
-
|
|
544
|
-
resolve(0, null);
|
|
637
|
+
resolve([0, null]);
|
|
545
638
|
};
|
|
546
639
|
xhr.send();
|
|
547
640
|
});
|
|
@@ -549,14 +642,11 @@ class PrefViewer extends HTMLElement {
|
|
|
549
642
|
|
|
550
643
|
#transformUrl(url) {
|
|
551
644
|
return new Promise((resolve) => {
|
|
552
|
-
|
|
553
|
-
this.#logDebug("Transformed URL", { original: url, transformed });
|
|
554
|
-
resolve(transformed);
|
|
645
|
+
resolve(url.replace(/^blob:[^/]+\//i, "").replace(/\\/g, "/"));
|
|
555
646
|
});
|
|
556
647
|
}
|
|
557
648
|
|
|
558
649
|
#decodeBase64(base64) {
|
|
559
|
-
this.#logDebug("Decoding Base64 payload", { length: base64 ? base64.length : 0 });
|
|
560
650
|
const [, payload] = base64.split(",");
|
|
561
651
|
const raw = payload || base64;
|
|
562
652
|
let decoded = "";
|
|
@@ -566,7 +656,6 @@ class PrefViewer extends HTMLElement {
|
|
|
566
656
|
try {
|
|
567
657
|
decoded = atob(raw);
|
|
568
658
|
} catch {
|
|
569
|
-
this.#logWarn("Failed to decode Base64 string");
|
|
570
659
|
return { blob, extension, size };
|
|
571
660
|
}
|
|
572
661
|
let isJson = false;
|
|
@@ -578,62 +667,44 @@ class PrefViewer extends HTMLElement {
|
|
|
578
667
|
const type = isJson ? "model/gltf+json" : "model/gltf-binary";
|
|
579
668
|
const array = Uint8Array.from(decoded, (c) => c.charCodeAt(0));
|
|
580
669
|
blob = new Blob([array], { type });
|
|
581
|
-
this.#logDebug("Decoded Base64 payload", { isJson, size, extension });
|
|
582
670
|
return { blob, extension, size };
|
|
583
671
|
}
|
|
584
672
|
|
|
585
673
|
async #initStorage(db, table) {
|
|
586
|
-
this.#logDebug("Initializing storage access", { db, table });
|
|
587
674
|
if (window.gltfDB && window.gltfDB.name === db && window.gltfDB.objectStoreNames.contains(table)) {
|
|
588
|
-
this.#logDebug("Reusing existing IndexedDB connection", { db, table });
|
|
589
675
|
return true;
|
|
590
676
|
}
|
|
591
677
|
await initDb(db, table);
|
|
592
|
-
this.#logDebug("IndexedDB initialized", { db, table });
|
|
593
678
|
}
|
|
594
679
|
|
|
595
680
|
// Methods for managing Asset Containers
|
|
596
681
|
#setVisibilityOfWallAndFloorInModel(show) {
|
|
597
682
|
if (!this.#data.containers.model.assetContainer || !this.#data.containers.model.visible) {
|
|
598
|
-
this.#logDebug("Skipping wall/floor visibility update", {
|
|
599
|
-
hasModel: !!this.#data.containers.model.assetContainer,
|
|
600
|
-
modelVisible: this.#data.containers.model.visible,
|
|
601
|
-
});
|
|
602
683
|
return false;
|
|
603
684
|
}
|
|
604
685
|
show = show !== undefined ? show : this.#data.containers.environment.visible;
|
|
605
686
|
const prefixes = Object.values(this.#data.options.materials).map((material) => material.prefix);
|
|
606
687
|
this.#data.containers.model.assetContainer.meshes.filter((meshToFilter) => prefixes.some((prefix) => meshToFilter.name.startsWith(prefix))).forEach((mesh) => mesh.setEnabled(show));
|
|
607
|
-
this.#logDebug("Updated wall and floor visibility", { show });
|
|
608
688
|
}
|
|
609
689
|
|
|
610
690
|
#setOptionsMaterial(optionMaterial) {
|
|
611
691
|
if (!optionMaterial || !optionMaterial.prefix || !optionMaterial.value) {
|
|
612
|
-
this.#logWarn("Material option invalid", { optionMaterial });
|
|
613
692
|
return false;
|
|
614
693
|
}
|
|
615
694
|
|
|
616
|
-
this.#logDebug("Applying material option", {
|
|
617
|
-
prefix: optionMaterial.prefix,
|
|
618
|
-
value: optionMaterial.value,
|
|
619
|
-
changed: optionMaterial.changed,
|
|
620
|
-
});
|
|
621
|
-
|
|
622
695
|
const material = this.#data.containers.materials.assetContainer?.materials.find((mat) => mat.name === optionMaterial.value) || null;
|
|
623
696
|
if (!material) {
|
|
624
|
-
this.#logWarn("Requested material not found", { value: optionMaterial.value });
|
|
625
697
|
return false;
|
|
626
698
|
}
|
|
627
699
|
|
|
628
700
|
const containers = [];
|
|
629
|
-
if (this.#data.containers.model.assetContainer && (this.#data.containers.model.
|
|
701
|
+
if (this.#data.containers.model.assetContainer && (this.#data.containers.model.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
|
|
630
702
|
containers.push(this.#data.containers.model.assetContainer);
|
|
631
703
|
}
|
|
632
|
-
if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.
|
|
704
|
+
if (this.#data.containers.environment.assetContainer && (this.#data.containers.environment.changed.pending || this.#data.containers.materials.changed.pending || optionMaterial.changed.pending)) {
|
|
633
705
|
containers.push(this.#data.containers.environment.assetContainer);
|
|
634
706
|
}
|
|
635
707
|
if (containers.length === 0) {
|
|
636
|
-
this.#logDebug("No containers required material update", { prefix: optionMaterial.prefix });
|
|
637
708
|
return false;
|
|
638
709
|
}
|
|
639
710
|
|
|
@@ -644,145 +715,116 @@ class PrefViewer extends HTMLElement {
|
|
|
644
715
|
.forEach((mesh) => {
|
|
645
716
|
mesh.material = material;
|
|
646
717
|
someSetted = true;
|
|
647
|
-
this.#logDebug("Assigned material to mesh", { mesh: mesh.name, material: material.name });
|
|
648
718
|
})
|
|
649
719
|
);
|
|
650
720
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
721
|
+
if (someSetted) {
|
|
722
|
+
optionMaterial.changed.success = true;
|
|
723
|
+
} else if (optionMaterial.changed.pending) {
|
|
724
|
+
optionMaterial.value = optionMaterial.changed.value;
|
|
725
|
+
optionMaterial.changed.success = false;
|
|
726
|
+
}
|
|
657
727
|
|
|
658
728
|
return someSetted;
|
|
659
729
|
}
|
|
660
730
|
|
|
661
731
|
#setOptionsMaterials() {
|
|
662
|
-
if (!this.#data.containers.materials.assetContainer) {
|
|
663
|
-
this.#logDebug("Skipping materials update; materials container is missing");
|
|
664
|
-
return false;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
this.#logDebug("Applying material options batch");
|
|
668
732
|
let someSetted = false;
|
|
669
733
|
Object.values(this.#data.options.materials).forEach((material) => {
|
|
670
734
|
let settedMaterial = this.#setOptionsMaterial(material);
|
|
671
735
|
someSetted = someSetted || settedMaterial;
|
|
672
736
|
});
|
|
673
|
-
this.#logDebug("Material batch processing finished", { appliedAny: someSetted });
|
|
674
737
|
return someSetted;
|
|
675
738
|
}
|
|
676
739
|
|
|
677
740
|
#setOptionsCamera() {
|
|
678
|
-
if (!this.#data.options.camera.value
|
|
679
|
-
this.#logDebug("No camera option update necessary", {
|
|
680
|
-
value: this.#data.options.camera.value,
|
|
681
|
-
changed: this.#data.options.camera.changed,
|
|
682
|
-
modelChanged: this.#data.containers.model.assetContainer?.changed,
|
|
683
|
-
});
|
|
741
|
+
if (!this.#data.options.camera.value && !this.#data.options.camera.changed.pending && !this.#data.containers.model.changed.pending && !this.#data.containers.environment.changed.pending) {
|
|
684
742
|
return false;
|
|
685
743
|
}
|
|
686
744
|
|
|
687
|
-
let camera = this.#data.containers.model.assetContainer?.cameras.find((
|
|
745
|
+
let camera = this.#data.containers.model.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.value) || this.#data.containers.environment.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.value) || null;
|
|
688
746
|
if (!camera) {
|
|
689
|
-
this.#
|
|
690
|
-
|
|
747
|
+
if (this.#data.options.camera.changed.value && this.#data.options.camera.changed.value !== this.#data.options.camera.value) {
|
|
748
|
+
camera = this.#data.containers.model.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.changed.value) || this.#data.containers.environment.assetContainer?.cameras.find((thisCamera) => thisCamera.name === this.#data.options.camera.changed.value) || null;
|
|
749
|
+
}
|
|
750
|
+
if (camera) {
|
|
751
|
+
camera.metadata = { locked: this.#data.options.camera.changed.locked };
|
|
752
|
+
this.#data.options.camera.value = this.#data.options.camera.changed.value;
|
|
753
|
+
this.#data.options.camera.locked = this.#data.options.camera.changed.locked;
|
|
754
|
+
this.#data.options.camera.changed.success = false;
|
|
755
|
+
} else {
|
|
756
|
+
camera = this.#camera;
|
|
757
|
+
this.#data.options.camera.value = null;
|
|
758
|
+
this.#data.options.camera.locked = this.#camera.metadata.locked;
|
|
759
|
+
this.#data.options.camera.changed.success = false;
|
|
760
|
+
}
|
|
761
|
+
} else {
|
|
762
|
+
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
763
|
+
if (this.#data.options.camera.changed.pending) {
|
|
764
|
+
this.#data.options.camera.changed.success = true;
|
|
765
|
+
}
|
|
691
766
|
}
|
|
692
|
-
|
|
693
|
-
camera.metadata = { locked: this.#data.options.camera.locked };
|
|
694
|
-
if (!this.#data.options.camera.locked) {
|
|
767
|
+
if (!this.#data.options.camera.locked && this.#data.options.camera.value !== null) {
|
|
695
768
|
camera.attachControl(this.#canvas, true);
|
|
696
|
-
this.#logDebug("Attached unlocked camera control", { camera: camera.name });
|
|
697
|
-
} else {
|
|
698
|
-
this.#logDebug("Using locked camera configuration", { camera: camera.name });
|
|
699
769
|
}
|
|
700
770
|
this.#scene.activeCamera = camera;
|
|
701
|
-
this.#logDebug("Active camera set", { camera: camera.name });
|
|
702
|
-
|
|
703
771
|
return true;
|
|
704
772
|
}
|
|
705
773
|
|
|
706
774
|
#addContainer(container) {
|
|
707
|
-
if (container.assetContainer
|
|
708
|
-
|
|
709
|
-
container.visible = true;
|
|
710
|
-
this.#logDebug("Added container to scene", { name: container.name });
|
|
711
|
-
} else {
|
|
712
|
-
this.#logDebug("Skipped adding container", {
|
|
713
|
-
name: container?.name,
|
|
714
|
-
hasAssetContainer: !!container?.assetContainer,
|
|
715
|
-
visible: container?.visible,
|
|
716
|
-
show: container?.show,
|
|
717
|
-
});
|
|
775
|
+
if (!container.assetContainer || container.visible || !container.show) {
|
|
776
|
+
return false;
|
|
718
777
|
}
|
|
778
|
+
|
|
779
|
+
container.assetContainer.addAllToScene();
|
|
780
|
+
container.visible = true;
|
|
781
|
+
return true;
|
|
719
782
|
}
|
|
720
783
|
|
|
721
784
|
#removeContainer(container) {
|
|
722
|
-
if (container.assetContainer
|
|
723
|
-
|
|
724
|
-
container.visible = false;
|
|
725
|
-
this.#logDebug("Removed container from scene", { name: container.name });
|
|
726
|
-
} else {
|
|
727
|
-
this.#logDebug("Skipped removing container", {
|
|
728
|
-
name: container?.name,
|
|
729
|
-
hasAssetContainer: !!container?.assetContainer,
|
|
730
|
-
visible: container?.visible,
|
|
731
|
-
});
|
|
785
|
+
if (!container.assetContainer || !container.visible) {
|
|
786
|
+
return false;
|
|
732
787
|
}
|
|
788
|
+
|
|
789
|
+
container.assetContainer.removeAllFromScene();
|
|
790
|
+
container.visible = false;
|
|
791
|
+
return true;
|
|
733
792
|
}
|
|
734
793
|
|
|
735
794
|
#replaceContainer(container, newAssetContainer) {
|
|
736
|
-
|
|
737
|
-
|
|
795
|
+
if (container.assetContainer) {
|
|
796
|
+
this.#removeContainer(container);
|
|
797
|
+
container.assetContainer.dispose();
|
|
798
|
+
container.assetContainer = null;
|
|
799
|
+
}
|
|
800
|
+
this.#scene.getEngine().releaseEffects();
|
|
738
801
|
container.assetContainer = newAssetContainer;
|
|
739
|
-
container.assetContainer.meshes.forEach((mesh) => {
|
|
740
|
-
mesh.receiveShadows = true;
|
|
741
|
-
this.#shadowGen.addShadowCaster(mesh, true);
|
|
742
|
-
this.#logDebug("Configured mesh for shadows", { container: container.name, mesh: mesh.name });
|
|
743
|
-
});
|
|
744
802
|
this.#addContainer(container);
|
|
745
|
-
|
|
746
|
-
name: container.name,
|
|
747
|
-
meshCount: container.assetContainer.meshes.length,
|
|
748
|
-
});
|
|
803
|
+
return true;
|
|
749
804
|
}
|
|
750
805
|
|
|
751
806
|
async #loadAssetContainer(container) {
|
|
752
|
-
|
|
753
|
-
this.#logDebug("Requested asset container load", {
|
|
754
|
-
container: container?.name,
|
|
755
|
-
storage: this.#describeStorage(storage),
|
|
756
|
-
});
|
|
807
|
+
let storage = container?.storage;
|
|
757
808
|
|
|
758
809
|
if (!storage) {
|
|
759
|
-
this.#logWarn("No storage configuration provided for container", { container: container?.name });
|
|
760
810
|
return false;
|
|
761
811
|
}
|
|
762
812
|
|
|
763
813
|
let source = storage.url || null;
|
|
764
814
|
|
|
765
815
|
if (storage.db && storage.table && storage.id) {
|
|
766
|
-
this.#logDebug("Loading container from IndexedDB", {
|
|
767
|
-
container: container.name,
|
|
768
|
-
db: storage.db,
|
|
769
|
-
table: storage.table,
|
|
770
|
-
id: storage.id,
|
|
771
|
-
});
|
|
772
816
|
await this.#initStorage(storage.db, storage.table);
|
|
773
817
|
const object = await loadModel(storage.id, storage.table);
|
|
774
818
|
source = object.data;
|
|
775
|
-
if (object.
|
|
776
|
-
|
|
819
|
+
if (object.timeStamp === container.timeStamp) {
|
|
820
|
+
container.changed = { pending: false, success: false };
|
|
777
821
|
return false;
|
|
778
822
|
} else {
|
|
779
|
-
container.changed = {
|
|
780
|
-
this.#logDebug("IndexedDB model marked as changed", { container: container.name, metadata: container.changed });
|
|
823
|
+
container.changed = { pending: true, size: object.size, success: false, timeStamp: object.timeStamp };
|
|
781
824
|
}
|
|
782
825
|
}
|
|
783
826
|
|
|
784
827
|
if (!source) {
|
|
785
|
-
this.#logWarn("No source resolved for container", { container: container.name });
|
|
786
828
|
return false;
|
|
787
829
|
}
|
|
788
830
|
|
|
@@ -790,139 +832,166 @@ class PrefViewer extends HTMLElement {
|
|
|
790
832
|
|
|
791
833
|
let { blob, extension, size } = this.#decodeBase64(source);
|
|
792
834
|
if (blob && extension) {
|
|
793
|
-
this.#logDebug("Source detected as Base64", { container: container.name, extension });
|
|
794
835
|
file = new File([blob], `${container.name}${extension}`, {
|
|
795
836
|
type: blob.type,
|
|
796
837
|
});
|
|
797
|
-
if (!container.changed) {
|
|
798
|
-
if (container.
|
|
799
|
-
|
|
838
|
+
if (!container.changed.pending) {
|
|
839
|
+
if (container.timeStamp === null && container.size === size) {
|
|
840
|
+
container.changed = { pending: false, success: false };
|
|
800
841
|
return false;
|
|
801
842
|
} else {
|
|
802
|
-
container.changed = {
|
|
803
|
-
this.#logDebug("Base64 model marked as changed", { container: container.name, size });
|
|
843
|
+
container.changed = { pending: true, size: size, success: false, timeStamp: null };
|
|
804
844
|
}
|
|
805
845
|
}
|
|
806
846
|
} else {
|
|
807
847
|
const extMatch = source.match(/\.(gltf|glb)(\?|#|$)/i);
|
|
808
848
|
extension = extMatch ? `.${extMatch[1].toLowerCase()}` : ".gltf";
|
|
809
|
-
const
|
|
810
|
-
if (container.
|
|
811
|
-
|
|
812
|
-
container: container.name,
|
|
813
|
-
fileTimestamp,
|
|
814
|
-
fileSize,
|
|
815
|
-
});
|
|
849
|
+
const [fileSize, fileTimeStamp] = await this.#getServerFileDataHeader(source);
|
|
850
|
+
if (container.size === fileSize && container.timeStamp === fileTimeStamp) {
|
|
851
|
+
container.changed = { pending: false, success: false };
|
|
816
852
|
return false;
|
|
817
853
|
} else {
|
|
818
|
-
container.changed = {
|
|
819
|
-
this.#logDebug("Remote model marked as changed", { container: container.name, metadata: container.changed });
|
|
854
|
+
container.changed = { pending: true, size: fileSize, success: false, timeStamp: fileTimeStamp };
|
|
820
855
|
}
|
|
821
856
|
}
|
|
822
857
|
|
|
858
|
+
// https://doc.babylonjs.com/typedoc/interfaces/BABYLON.LoadAssetContainerOptions
|
|
823
859
|
let options = {
|
|
824
860
|
pluginExtension: extension,
|
|
825
861
|
pluginOptions: {
|
|
826
862
|
gltf: {
|
|
863
|
+
compileMaterials: true,
|
|
827
864
|
loadAllMaterials: true,
|
|
865
|
+
loadOnlyMaterials: container.name === "materials",
|
|
828
866
|
preprocessUrlAsync: this.#transformUrl,
|
|
829
867
|
},
|
|
830
868
|
},
|
|
831
869
|
};
|
|
832
870
|
|
|
833
|
-
this.#logInfo("Loading asset container", { container: container.name, extension });
|
|
834
871
|
return LoadAssetContainerAsync(file || source, this.#scene, options);
|
|
835
872
|
}
|
|
836
873
|
|
|
837
874
|
async #loadContainers(loadModel = true, loadEnvironment = true, loadMaterials = true) {
|
|
838
|
-
this.#
|
|
875
|
+
this.#engine.stopRenderLoop(this.#renderLoop);
|
|
876
|
+
|
|
839
877
|
const promiseArray = [];
|
|
840
878
|
promiseArray.push(loadModel ? this.#loadAssetContainer(this.#data.containers.model) : false);
|
|
841
879
|
promiseArray.push(loadEnvironment ? this.#loadAssetContainer(this.#data.containers.environment) : false);
|
|
842
880
|
promiseArray.push(loadMaterials ? this.#loadAssetContainer(this.#data.containers.materials) : false);
|
|
843
881
|
|
|
844
882
|
Promise.allSettled(promiseArray)
|
|
845
|
-
.then(
|
|
883
|
+
.then((values) => {
|
|
846
884
|
const modelContainer = values[0];
|
|
847
885
|
const environmentContainer = values[1];
|
|
848
886
|
const materialsContainer = values[2];
|
|
849
887
|
|
|
850
888
|
if (modelContainer.status === "fulfilled" && modelContainer.value) {
|
|
889
|
+
modelContainer.value.lights = [];
|
|
851
890
|
this.#replaceContainer(this.#data.containers.model, modelContainer.value);
|
|
852
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.model);
|
|
853
|
-
this.#logInfo("Model container loaded successfully");
|
|
891
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.model, true);
|
|
854
892
|
} else {
|
|
855
|
-
this.#data.containers.model.
|
|
856
|
-
|
|
893
|
+
if (this.#data.containers.model.assetContainer && this.#data.containers.model.show !== this.#data.containers.model.visible) {
|
|
894
|
+
this.#data.containers.model.show ? this.#addContainer(this.#data.containers.model) : this.#removeContainer(this.#data.containers.model);
|
|
895
|
+
}
|
|
896
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.model, false);
|
|
857
897
|
}
|
|
858
898
|
|
|
859
899
|
if (environmentContainer.status === "fulfilled" && environmentContainer.value) {
|
|
860
900
|
this.#replaceContainer(this.#data.containers.environment, environmentContainer.value);
|
|
861
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.environment);
|
|
862
|
-
this.#logInfo("Environment container loaded successfully");
|
|
901
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.environment, true);
|
|
863
902
|
} else {
|
|
864
|
-
this.#data.containers.environment.
|
|
865
|
-
|
|
903
|
+
if (this.#data.containers.environment.assetContainer && this.#data.containers.environment.show !== this.#data.containers.environment.visible) {
|
|
904
|
+
this.#data.containers.environment.show ? this.#addContainer(this.#data.containers.environment) : this.#removeContainer(this.#data.containers.environment);
|
|
905
|
+
}
|
|
906
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.environment, false);
|
|
866
907
|
}
|
|
867
908
|
|
|
868
909
|
if (materialsContainer.status === "fulfilled" && materialsContainer.value) {
|
|
869
910
|
this.#replaceContainer(this.#data.containers.materials, materialsContainer.value);
|
|
870
|
-
this.#storeChangedFlagsForContainer(this.#data.containers.materials);
|
|
871
|
-
this.#logInfo("Materials container loaded successfully");
|
|
911
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.materials, true);
|
|
872
912
|
} else {
|
|
873
|
-
this.#
|
|
913
|
+
this.#storeChangedFlagsForContainer(this.#data.containers.materials, false);
|
|
874
914
|
}
|
|
875
915
|
|
|
876
916
|
this.#setOptionsMaterials();
|
|
877
917
|
this.#setOptionsCamera();
|
|
878
918
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
879
|
-
|
|
880
|
-
this.#resetChangedFlags();
|
|
881
|
-
|
|
882
|
-
this.#logInfo("Containers load routine completed");
|
|
883
|
-
this.dispatchEvent(
|
|
884
|
-
new CustomEvent("model-loaded", {
|
|
885
|
-
detail: { success: "" },
|
|
886
|
-
bubbles: true,
|
|
887
|
-
composed: true,
|
|
888
|
-
})
|
|
889
|
-
);
|
|
890
|
-
this.#logDebug("Dispatched model-loaded event");
|
|
891
919
|
})
|
|
892
920
|
.catch((error) => {
|
|
893
|
-
this
|
|
921
|
+
this.loaded = true;
|
|
922
|
+
console.error("PrefViewer: failed to load model", error);
|
|
894
923
|
this.dispatchEvent(
|
|
895
|
-
new CustomEvent("
|
|
896
|
-
detail: { error: error },
|
|
924
|
+
new CustomEvent("scene-error", {
|
|
897
925
|
bubbles: true,
|
|
926
|
+
cancelable: false,
|
|
898
927
|
composed: true,
|
|
928
|
+
detail: { error: error },
|
|
899
929
|
})
|
|
900
930
|
);
|
|
931
|
+
})
|
|
932
|
+
.finally(async () => {
|
|
933
|
+
this.#setMaxSimultaneousLights();
|
|
934
|
+
this.#initShadows();
|
|
935
|
+
await this.#setStatusLoaded();
|
|
901
936
|
});
|
|
902
937
|
}
|
|
903
938
|
|
|
904
|
-
//
|
|
905
|
-
|
|
906
|
-
this.#
|
|
907
|
-
if (
|
|
908
|
-
|
|
909
|
-
config = JSON.parse(config);
|
|
910
|
-
} catch (error) {
|
|
911
|
-
this.#logError("Failed to parse config JSON", { error });
|
|
912
|
-
throw error;
|
|
913
|
-
}
|
|
939
|
+
// Tasks
|
|
940
|
+
#addTaskToQueue(value, type) {
|
|
941
|
+
this.#taskQueue.push(new PrefViewerTask(value, type));
|
|
942
|
+
if (this.initialized && !this.loading) {
|
|
943
|
+
this.#processNextTask();
|
|
914
944
|
}
|
|
915
|
-
|
|
916
|
-
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
#processNextTask() {
|
|
948
|
+
if (!this.#taskQueue.length) {
|
|
917
949
|
return false;
|
|
918
950
|
}
|
|
951
|
+
const task = this.#taskQueue[0];
|
|
952
|
+
this.#taskQueue.shift();
|
|
953
|
+
switch (task.type) {
|
|
954
|
+
case PrefViewerTask.Types.Config:
|
|
955
|
+
this.#processConfig(task.value);
|
|
956
|
+
break;
|
|
957
|
+
case PrefViewerTask.Types.Model:
|
|
958
|
+
this.#processModel(task.value);
|
|
959
|
+
break;
|
|
960
|
+
case PrefViewerTask.Types.Environment:
|
|
961
|
+
this.#processEnvironment(task.value);
|
|
962
|
+
break;
|
|
963
|
+
case PrefViewerTask.Types.Materials:
|
|
964
|
+
this.#processMaterials(task.value);
|
|
965
|
+
break;
|
|
966
|
+
case PrefViewerTask.Types.Options:
|
|
967
|
+
this.#processOptions(task.value);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
#processConfig(config) {
|
|
973
|
+
this.#setStatusLoading();
|
|
919
974
|
|
|
920
975
|
// Containers
|
|
921
|
-
|
|
976
|
+
const loadModel = !!config.model?.storage;
|
|
977
|
+
this.#data.containers.model.changed.pending = loadModel;
|
|
978
|
+
this.#data.containers.model.changed.success = false;
|
|
979
|
+
this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
|
|
980
|
+
this.#data.containers.model.storage = loadModel ? config.model.storage : this.#data.containers.model.storage;
|
|
922
981
|
this.#data.containers.model.show = config.model?.visible !== undefined ? config.model.visible : this.#data.containers.model.show;
|
|
923
|
-
|
|
982
|
+
|
|
983
|
+
const loadEnvironment = !!config.scene?.storage;
|
|
984
|
+
this.#data.containers.environment.changed.pending = loadEnvironment;
|
|
985
|
+
this.#data.containers.environment.changed.success = false;
|
|
986
|
+
this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
|
|
987
|
+
this.#data.containers.environment.storage = loadEnvironment ? config.scene.storage : this.#data.containers.environment.storage;
|
|
924
988
|
this.#data.containers.environment.show = config.scene?.visible !== undefined ? config.scene.visible : this.#data.containers.environment.show;
|
|
925
|
-
|
|
989
|
+
|
|
990
|
+
const loadMaterials = !!config.materials?.storage;
|
|
991
|
+
this.#data.containers.materials.changed.pending = loadMaterials;
|
|
992
|
+
this.#data.containers.materials.changed.success = false;
|
|
993
|
+
this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
|
|
994
|
+
this.#data.containers.materials.storage = loadMaterials ? config.materials.storage : this.#data.containers.materials.storage;
|
|
926
995
|
|
|
927
996
|
// Options
|
|
928
997
|
if (config.options) {
|
|
@@ -930,145 +999,152 @@ class PrefViewer extends HTMLElement {
|
|
|
930
999
|
this.#checkMaterialsChanged(config.options);
|
|
931
1000
|
}
|
|
932
1001
|
|
|
933
|
-
this.#
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1002
|
+
this.#loadContainers(loadModel, loadEnvironment, loadMaterials);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
#processModel(model) {
|
|
1006
|
+
this.#setStatusLoading();
|
|
1007
|
+
|
|
1008
|
+
const loadModel = !!model.storage;
|
|
1009
|
+
this.#data.containers.model.changed.pending = loadModel;
|
|
1010
|
+
this.#data.containers.model.changed.success = false;
|
|
1011
|
+
this.#data.containers.model.changed.storage = this.#data.containers.model.storage;
|
|
1012
|
+
this.#data.containers.model.storage = loadModel ? model.storage : this.#data.containers.model.storage;
|
|
1013
|
+
this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
937
1014
|
|
|
938
|
-
this
|
|
1015
|
+
this.initialized && this.#loadContainers(loadModel, false, false);
|
|
939
1016
|
}
|
|
940
1017
|
|
|
941
|
-
|
|
942
|
-
this.#
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1018
|
+
#processEnvironment(environment) {
|
|
1019
|
+
this.#setStatusLoading();
|
|
1020
|
+
|
|
1021
|
+
const loadEnvironment = !!environment.storage;
|
|
1022
|
+
this.#data.containers.environment.changed.pending = loadEnvironment;
|
|
1023
|
+
this.#data.containers.environment.changed.success = false;
|
|
1024
|
+
this.#data.containers.environment.changed.storage = this.#data.containers.environment.storage;
|
|
1025
|
+
this.#data.containers.environment.storage = loadEnvironment ? environment.storage : this.#data.containers.environment.storage;
|
|
1026
|
+
this.#data.containers.environment.show = environment.visible !== undefined ? environment.visible : this.#data.containers.environment.show;
|
|
1027
|
+
|
|
1028
|
+
this.#loadContainers(false, loadEnvironment, false);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
#processMaterials(materials) {
|
|
1032
|
+
this.#setStatusLoading();
|
|
1033
|
+
|
|
1034
|
+
const loadMaterials = !!materials.storage;
|
|
1035
|
+
this.#data.containers.materials.changed.pending = loadMaterials;
|
|
1036
|
+
this.#data.containers.materials.changed.success = false;
|
|
1037
|
+
this.#data.containers.materials.changed.storage = this.#data.containers.materials.storage;
|
|
1038
|
+
this.#data.containers.materials.storage = loadMaterials ? materials.storage : this.#data.containers.materials.storage;
|
|
1039
|
+
|
|
1040
|
+
this.#loadContainers(false, false, loadMaterials);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
async #processOptions(options) {
|
|
1044
|
+
this.#setStatusLoading();
|
|
1045
|
+
|
|
947
1046
|
let someSetted = false;
|
|
948
1047
|
if (this.#checkCameraChanged(options)) {
|
|
949
|
-
this.#logDebug("Camera options changed via setOptions");
|
|
950
1048
|
someSetted = someSetted || this.#setOptionsCamera();
|
|
951
1049
|
}
|
|
952
1050
|
if (this.#checkMaterialsChanged(options)) {
|
|
953
|
-
this.#logDebug("Material options changed via setOptions");
|
|
954
1051
|
someSetted = someSetted || this.#setOptionsMaterials();
|
|
955
1052
|
}
|
|
956
|
-
|
|
957
|
-
this.#
|
|
958
|
-
|
|
1053
|
+
|
|
1054
|
+
await this.#setStatusLoaded();
|
|
1055
|
+
|
|
959
1056
|
return someSetted;
|
|
960
1057
|
}
|
|
961
1058
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
} catch (error) {
|
|
968
|
-
this.#logError("Failed to parse model JSON", { error });
|
|
969
|
-
throw error;
|
|
970
|
-
}
|
|
1059
|
+
// Public Methods
|
|
1060
|
+
loadConfig(config) {
|
|
1061
|
+
config = typeof config === "string" ? JSON.parse(config) : config;
|
|
1062
|
+
if (!config) {
|
|
1063
|
+
return false;
|
|
971
1064
|
}
|
|
1065
|
+
this.#addTaskToQueue(config, "config");
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
loadModel(model) {
|
|
1069
|
+
model = typeof model === "string" ? JSON.parse(model) : model;
|
|
972
1070
|
if (!model) {
|
|
973
|
-
this.#logWarn("No model payload provided");
|
|
974
1071
|
return false;
|
|
975
1072
|
}
|
|
976
|
-
this.#
|
|
977
|
-
this.#data.containers.model.show = model.visible !== undefined ? model.visible : this.#data.containers.model.show;
|
|
978
|
-
this.#logDebug("Model configuration updated", {
|
|
979
|
-
storage: this.#describeStorage(this.#data.containers.model.storage),
|
|
980
|
-
show: this.#data.containers.model.show,
|
|
981
|
-
});
|
|
982
|
-
this.#initialized && this.#loadContainers(true, false, false);
|
|
1073
|
+
this.#addTaskToQueue(model, "model");
|
|
983
1074
|
}
|
|
984
1075
|
|
|
985
1076
|
loadScene(scene) {
|
|
986
|
-
|
|
987
|
-
if (typeof scene === "string") {
|
|
988
|
-
try {
|
|
989
|
-
scene = JSON.parse(scene);
|
|
990
|
-
} catch (error) {
|
|
991
|
-
this.#logError("Failed to parse scene JSON", { error });
|
|
992
|
-
throw error;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
1077
|
+
scene = typeof scene === "string" ? JSON.parse(scene) : scene;
|
|
995
1078
|
if (!scene) {
|
|
996
|
-
this.#logWarn("No scene payload provided");
|
|
997
1079
|
return false;
|
|
998
1080
|
}
|
|
999
|
-
this.#
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1081
|
+
this.#addTaskToQueue(scene, "environment");
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
loadMaterials(materials) {
|
|
1085
|
+
materials = typeof materials === "string" ? JSON.parse(materials) : materials;
|
|
1086
|
+
if (!materials) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
1089
|
+
this.#addTaskToQueue(materials, "materials");
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
setOptions(options) {
|
|
1093
|
+
options = typeof options === "string" ? JSON.parse(options) : options;
|
|
1094
|
+
if (!options) {
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
this.#addTaskToQueue(options, "options");
|
|
1006
1098
|
}
|
|
1007
1099
|
|
|
1008
1100
|
showModel() {
|
|
1009
1101
|
this.#data.containers.model.show = true;
|
|
1010
1102
|
this.#addContainer(this.#data.containers.model);
|
|
1011
|
-
this.#logInfo("Model visibility set to true");
|
|
1012
1103
|
}
|
|
1013
1104
|
|
|
1014
1105
|
hideModel() {
|
|
1015
1106
|
this.#data.containers.model.show = false;
|
|
1016
1107
|
this.#removeContainer(this.#data.containers.model);
|
|
1017
|
-
this.#logInfo("Model visibility set to false");
|
|
1018
1108
|
}
|
|
1019
1109
|
|
|
1020
1110
|
showScene() {
|
|
1021
1111
|
this.#data.containers.environment.show = true;
|
|
1022
1112
|
this.#addContainer(this.#data.containers.environment);
|
|
1023
1113
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
1024
|
-
this.#logInfo("Scene visibility set to true");
|
|
1025
1114
|
}
|
|
1026
1115
|
|
|
1027
1116
|
hideScene() {
|
|
1028
1117
|
this.#data.containers.environment.show = false;
|
|
1029
1118
|
this.#removeContainer(this.#data.containers.environment);
|
|
1030
1119
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
1031
|
-
this.#logInfo("Scene visibility set to false");
|
|
1032
1120
|
}
|
|
1033
1121
|
|
|
1034
1122
|
downloadModelGLB() {
|
|
1035
1123
|
const fileName = "model";
|
|
1036
|
-
this.#
|
|
1037
|
-
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1038
|
-
this.#logDebug("Model GLB export ready", { fileName });
|
|
1039
|
-
glb.downloadFiles();
|
|
1040
|
-
});
|
|
1124
|
+
GLTF2Export.GLBAsync(this.#data.containers.model.assetContainer, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
1041
1125
|
}
|
|
1042
1126
|
|
|
1043
1127
|
downloadModelUSDZ() {
|
|
1044
1128
|
const fileName = "model";
|
|
1045
|
-
this.#logInfo("Initiating USDZ download for model", { fileName });
|
|
1046
1129
|
USDZExportAsync(this.#data.containers.model.assetContainer).then((response) => {
|
|
1047
1130
|
if (response) {
|
|
1048
1131
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
1049
|
-
this.#logDebug("Model USDZ export ready", { fileName });
|
|
1050
1132
|
}
|
|
1051
1133
|
});
|
|
1052
1134
|
}
|
|
1053
1135
|
|
|
1054
1136
|
downloadModelAndSceneUSDZ() {
|
|
1055
1137
|
const fileName = "scene";
|
|
1056
|
-
this.#logInfo("Initiating USDZ download for scene", { fileName });
|
|
1057
1138
|
USDZExportAsync(this.#scene).then((response) => {
|
|
1058
1139
|
if (response) {
|
|
1059
1140
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
1060
|
-
this.#logDebug("Scene USDZ export ready", { fileName });
|
|
1061
1141
|
}
|
|
1062
1142
|
});
|
|
1063
1143
|
}
|
|
1064
1144
|
|
|
1065
1145
|
downloadModelAndSceneGLB() {
|
|
1066
1146
|
const fileName = "scene";
|
|
1067
|
-
this.#
|
|
1068
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1069
|
-
this.#logDebug("Scene GLB export ready", { fileName });
|
|
1070
|
-
glb.downloadFiles();
|
|
1071
|
-
});
|
|
1147
|
+
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
1072
1148
|
}
|
|
1073
1149
|
}
|
|
1074
1150
|
|