@preference-sl/pref-viewer 2.14.0-beta.3 → 2.14.0-beta.5
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/babylonjs-controller.js +89 -10
- package/src/pref-viewer.js +11 -3
package/package.json
CHANGED
|
@@ -114,6 +114,7 @@ export default class BabylonJSController {
|
|
|
114
114
|
#options = {};
|
|
115
115
|
|
|
116
116
|
#gltfResolver = null; // GLTFResolver instance
|
|
117
|
+
#loadGeneration = 0; // incremented per #loadContainers call to discard stale deferred results
|
|
117
118
|
#babylonJSAnimationController = null; // AnimationController instance
|
|
118
119
|
|
|
119
120
|
#renderPipelines = {
|
|
@@ -2400,23 +2401,51 @@ export default class BabylonJSController {
|
|
|
2400
2401
|
|
|
2401
2402
|
/**
|
|
2402
2403
|
* Loads all asset containers (model, environment, materials, etc.) and adds them to the scene.
|
|
2403
|
-
*
|
|
2404
|
-
*
|
|
2405
|
-
*
|
|
2406
|
-
*
|
|
2407
|
-
*
|
|
2408
|
-
*
|
|
2404
|
+
*
|
|
2405
|
+
* Uses **two-phase progressive loading** when the environment container is pending:
|
|
2406
|
+
* Phase 1 – Load model + materials, apply them, and start the render loop so the user sees
|
|
2407
|
+
* the configured product immediately.
|
|
2408
|
+
* Phase 2 – The environment (scene with trees, walls, floor…) was already downloading in
|
|
2409
|
+
* parallel. Once it resolves, briefly stop rendering, splice it in, reapply
|
|
2410
|
+
* camera/IBL/visibility options, and restart.
|
|
2411
|
+
*
|
|
2412
|
+
* When the environment is *not* pending (e.g. only the model changed), the original
|
|
2413
|
+
* single-phase behaviour is preserved — all containers settle immediately and are applied
|
|
2414
|
+
* together.
|
|
2415
|
+
*
|
|
2416
|
+
* A generation counter (`#loadGeneration`) guards Phase 2: if a newer `load()` call starts
|
|
2417
|
+
* while the environment is still downloading, the stale result is disposed instead of applied.
|
|
2418
|
+
*
|
|
2419
|
+
* @private
|
|
2420
|
+
* @param {boolean} [force=false] - Bypass cached size/timestamp checks.
|
|
2421
|
+
* @returns {Promise<{success: boolean, error: any}>}
|
|
2409
2422
|
*/
|
|
2410
2423
|
async #loadContainers(force = false) {
|
|
2424
|
+
const generation = ++this.#loadGeneration;
|
|
2411
2425
|
this.#detachAnimationChangedListener();
|
|
2412
2426
|
await this.#stopRender();
|
|
2413
2427
|
|
|
2414
2428
|
let oldModelMetadata = { ...(this.#containers.model?.state?.metadata ?? {}) };
|
|
2415
2429
|
let newModelMetadata = {};
|
|
2416
2430
|
|
|
2417
|
-
|
|
2431
|
+
// Kick off ALL container loads in parallel — the heavy environment download starts now.
|
|
2432
|
+
const allLoadPromises = new Map();
|
|
2418
2433
|
Object.values(this.#containers).forEach((container) => {
|
|
2419
|
-
|
|
2434
|
+
allLoadPromises.set(container.state.name, this.#loadAssetContainer(container, force));
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
// When the environment is pending (heavy scene geometry) we defer it to Phase 2 so
|
|
2438
|
+
// the model can render first. Non-pending environments resolve immediately with
|
|
2439
|
+
// [container, false] and are handled in Phase 1 like any other container.
|
|
2440
|
+
const environmentPending = this.#containers.environment?.state?.isPending === true;
|
|
2441
|
+
const priorityPromises = [];
|
|
2442
|
+
let deferredPromise = null;
|
|
2443
|
+
allLoadPromises.forEach((promise, name) => {
|
|
2444
|
+
if (name === "environment" && environmentPending) {
|
|
2445
|
+
deferredPromise = promise;
|
|
2446
|
+
} else {
|
|
2447
|
+
priorityPromises.push(promise);
|
|
2448
|
+
}
|
|
2420
2449
|
});
|
|
2421
2450
|
|
|
2422
2451
|
let detail = {
|
|
@@ -2424,13 +2453,18 @@ export default class BabylonJSController {
|
|
|
2424
2453
|
error: null,
|
|
2425
2454
|
};
|
|
2426
2455
|
|
|
2427
|
-
|
|
2456
|
+
// ── Phase 1: Priority containers (model + materials) ────────────────────
|
|
2457
|
+
await Promise.allSettled(priorityPromises)
|
|
2428
2458
|
.then(async (values) => {
|
|
2429
2459
|
// Scene may have been disposed (disconnectedCallback) while async loading was in
|
|
2430
2460
|
// progress. Abort cleanly: #replaceContainer already guards the GPU calls, but
|
|
2431
2461
|
// we skip the post-load option/visibility calls too to avoid further null-derefs.
|
|
2432
2462
|
if (!this.#scene) {
|
|
2433
2463
|
values.forEach((result) => { result.value?.[1]?.dispose(); });
|
|
2464
|
+
if (deferredPromise) {
|
|
2465
|
+
deferredPromise.then((r) => { r?.[1]?.dispose?.(); }).catch(() => {});
|
|
2466
|
+
deferredPromise = null;
|
|
2467
|
+
}
|
|
2434
2468
|
return;
|
|
2435
2469
|
}
|
|
2436
2470
|
this.#disposeAnimationController();
|
|
@@ -2469,7 +2503,10 @@ export default class BabylonJSController {
|
|
|
2469
2503
|
detail.error = error;
|
|
2470
2504
|
})
|
|
2471
2505
|
.finally(async () => {
|
|
2472
|
-
|
|
2506
|
+
if (!deferredPromise) {
|
|
2507
|
+
// No deferred work — single-phase path (original behaviour).
|
|
2508
|
+
this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
|
|
2509
|
+
}
|
|
2473
2510
|
this.#setMaxSimultaneousLights();
|
|
2474
2511
|
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
|
|
2475
2512
|
// Apply stored highlight settings so fresh page loads respect persisted state
|
|
@@ -2482,6 +2519,48 @@ export default class BabylonJSController {
|
|
|
2482
2519
|
}
|
|
2483
2520
|
await this.#startRender();
|
|
2484
2521
|
});
|
|
2522
|
+
|
|
2523
|
+
// ── Phase 2: Deferred environment (already downloading in parallel) ─────
|
|
2524
|
+
if (deferredPromise) {
|
|
2525
|
+
let deferredResult;
|
|
2526
|
+
try {
|
|
2527
|
+
deferredResult = await deferredPromise;
|
|
2528
|
+
} catch (error) {
|
|
2529
|
+
console.error("PrefViewer: failed to load environment progressively", error);
|
|
2530
|
+
deferredResult = [this.#containers.environment, null];
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
// A newer load() was triggered while we waited — discard stale results.
|
|
2534
|
+
if (this.#loadGeneration !== generation) {
|
|
2535
|
+
deferredResult?.[1]?.dispose?.();
|
|
2536
|
+
return detail;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
if (this.#scene) {
|
|
2540
|
+
const [container, assetContainer] = deferredResult;
|
|
2541
|
+
if (assetContainer) {
|
|
2542
|
+
this.#detachAnimationChangedListener();
|
|
2543
|
+
await this.#stopRender();
|
|
2544
|
+
this.#assetContainer_retagCameras(assetContainer);
|
|
2545
|
+
this.#replaceContainer(container, assetContainer);
|
|
2546
|
+
container.state.setSuccess(true);
|
|
2547
|
+
this.#setOptions_Camera();
|
|
2548
|
+
await this.#setOptions_IBL();
|
|
2549
|
+
this.#setVisibilityOfWallAndFloorInModel();
|
|
2550
|
+
this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
|
|
2551
|
+
this.#setMaxSimultaneousLights();
|
|
2552
|
+
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#containers.model.assetContainer);
|
|
2553
|
+
if (this.#babylonJSAnimationController?.hasAnimations?.()) {
|
|
2554
|
+
this.#attachAnimationChangedListener();
|
|
2555
|
+
}
|
|
2556
|
+
await this.#startRender();
|
|
2557
|
+
} else {
|
|
2558
|
+
container.state.setSuccess(false);
|
|
2559
|
+
this.#checkModelMetadata(oldModelMetadata, newModelMetadata);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2485
2564
|
return detail;
|
|
2486
2565
|
}
|
|
2487
2566
|
|
package/src/pref-viewer.js
CHANGED
|
@@ -302,14 +302,22 @@ export default class PrefViewer extends HTMLElement {
|
|
|
302
302
|
if (this.#menu3D) {
|
|
303
303
|
this.#menu3D.removeEventListener("pref-viewer-menu-3d-apply", this.#handlers.onMenuApply);
|
|
304
304
|
this.#menu3D.remove();
|
|
305
|
+
this.#menu3D = null;
|
|
305
306
|
}
|
|
306
|
-
|
|
307
|
+
if (this.#handlers.onViewerHoverStart) {
|
|
308
|
+
this.#wrapper.removeEventListener("mouseenter", this.#handlers.onViewerHoverStart);
|
|
309
|
+
this.#handlers.onViewerHoverStart = null;
|
|
310
|
+
}
|
|
311
|
+
if (this.#handlers.onViewerHoverEnd) {
|
|
312
|
+
this.#wrapper.removeEventListener("mouseleave", this.#handlers.onViewerHoverEnd);
|
|
313
|
+
this.#handlers.onViewerHoverEnd = null;
|
|
314
|
+
}
|
|
315
|
+
|
|
307
316
|
// Check if menu should be shown (default: true if not specified)
|
|
308
317
|
const showMenuAttr = this.getAttribute("show-menu");
|
|
309
318
|
const showMenu = showMenuAttr === null || showMenuAttr === "true";
|
|
310
|
-
|
|
319
|
+
|
|
311
320
|
if (!showMenu) {
|
|
312
|
-
this.#menu3D = null;
|
|
313
321
|
return;
|
|
314
322
|
}
|
|
315
323
|
|