@preference-sl/pref-viewer 2.11.0-beta.2 → 2.11.0-beta.20
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 +61 -49
- package/src/babylonjs-animation-controller.js +217 -0
- package/src/babylonjs-animation-opening-menu.js +395 -0
- package/src/babylonjs-animation-opening.js +527 -0
- package/src/babylonjs-controller.js +481 -98
- package/src/file-storage.js +11 -2
- package/src/index.js +18 -772
- package/src/panzoom-controller.js +92 -54
- package/src/pref-viewer-2d.js +25 -12
- package/src/pref-viewer-3d-data.js +3 -2
- package/src/pref-viewer-3d.js +93 -21
- package/src/pref-viewer-dialog.js +146 -0
- package/src/pref-viewer-task.js +1 -1
- package/src/pref-viewer.js +934 -0
- package/src/styles.js +334 -0
- package/src/svg-resolver.js +23 -0
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
1
|
+
import { ArcRotateCamera, AssetContainer, Camera, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointerEventTypes, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager, WebXRState } from "@babylonjs/core";
|
|
2
|
+
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression.js";
|
|
3
3
|
import "@babylonjs/loaders";
|
|
4
|
-
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
4
|
+
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression.js";
|
|
5
5
|
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
6
|
+
import JSZip from "jszip";
|
|
7
|
+
|
|
6
8
|
import GLTFResolver from "./gltf-resolver.js";
|
|
9
|
+
import { MaterialData } from "./pref-viewer-3d-data.js";
|
|
10
|
+
import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
|
-
* BabylonJSController - Main controller for managing Babylon.js 3D scenes, assets, and interactions.
|
|
13
|
+
* BabylonJSController - Main controller for managing Babylon.js 3D scenes, assets, rendering, and user interactions in PrefViewer.
|
|
14
|
+
*
|
|
15
|
+
* Summary:
|
|
16
|
+
* Provides a high-level API for initializing, loading, displaying, and exporting 3D models and environments using Babylon.js.
|
|
17
|
+
* Manages advanced rendering features, AR integration, asset containers, and user interaction logic.
|
|
10
18
|
*
|
|
11
|
-
* Responsibilities:
|
|
19
|
+
* Key Responsibilities:
|
|
12
20
|
* - Initializes and manages the Babylon.js engine, scene, camera, lights, and asset containers.
|
|
13
21
|
* - Handles loading, replacing, and disposing of 3D models, environments, and materials.
|
|
14
22
|
* - Configures advanced rendering features such as Draco mesh compression, shadows, and image-based lighting (IBL).
|
|
15
23
|
* - Integrates Babylon.js WebXR experience for augmented reality (AR) support.
|
|
16
|
-
* - Provides methods for downloading models and scenes in GLB and USDZ formats.
|
|
24
|
+
* - Provides methods for downloading models and scenes in GLB, glTF (ZIP), and USDZ formats.
|
|
17
25
|
* - Manages camera and material options, container visibility, and user interactions.
|
|
18
26
|
* - Observes canvas resize events and updates the engine accordingly.
|
|
27
|
+
* - Designed for integration with PrefViewer and GLTFResolver.
|
|
28
|
+
* - All resource management and rendering operations are performed asynchronously for performance.
|
|
19
29
|
*
|
|
20
30
|
* Usage:
|
|
21
31
|
* - Instantiate: const controller = new BabylonJSController(canvas, containers, options);
|
|
@@ -23,7 +33,7 @@ import GLTFResolver from "./gltf-resolver.js";
|
|
|
23
33
|
* - Load assets: await controller.load();
|
|
24
34
|
* - Set camera/material options: controller.setCameraOptions(), controller.setMaterialOptions();
|
|
25
35
|
* - Control visibility: controller.setContainerVisibility(name, show);
|
|
26
|
-
* - Download assets: controller.
|
|
36
|
+
* - Download assets: controller.downloadGLB(), controller.downloadGLTF(), controller.downloadUSDZ();
|
|
27
37
|
* - Disable rendering: controller.disable();
|
|
28
38
|
*
|
|
29
39
|
* Public Methods:
|
|
@@ -33,25 +43,50 @@ import GLTFResolver from "./gltf-resolver.js";
|
|
|
33
43
|
* - setCameraOptions(): Applies camera options from configuration.
|
|
34
44
|
* - setMaterialOptions(): Applies material options from configuration.
|
|
35
45
|
* - setContainerVisibility(name, show): Shows or hides a container by name.
|
|
36
|
-
* -
|
|
46
|
+
* - downloadGLB(content): Downloads the current scene, model, or environment as a GLB file.
|
|
47
|
+
* - downloadGLTF(content): Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
48
|
+
* - downloadUSDZ(content): Downloads the current scene, model, or environment as a USDZ file.
|
|
37
49
|
*
|
|
38
|
-
* Private Methods:
|
|
50
|
+
* Private Methods (using ECMAScript private fields):
|
|
51
|
+
* - #bindHandlers(): Pre-binds reusable event handlers to preserve stable references.
|
|
39
52
|
* - #configureDracoCompression(): Sets up Draco mesh compression.
|
|
40
|
-
* - #
|
|
41
|
-
* - #setupInteraction(): Sets up canvas interaction handlers.
|
|
42
|
-
* - #disposeEngine(): Disposes engine and resources.
|
|
43
|
-
* - #setOptionsMaterial(), #setOptions_Materials(), #setOptions_Camera(): Applies material/camera options.
|
|
44
|
-
* - #findContainerByName(), #addContainer(), #removeContainer(), #replaceContainer(): Container management.
|
|
45
|
-
* - #setVisibilityOfWallAndFloorInModel(): Controls wall/floor mesh visibility.
|
|
46
|
-
* - #stopRender(), #startRender(): Render loop control.
|
|
47
|
-
* - #loadAssetContainer(), #loadContainers(): Asset loading.
|
|
53
|
+
* - #renderLoop(): Babylon.js render loop callback.
|
|
48
54
|
* - #addStylesToARButton(): Styles AR button.
|
|
49
55
|
* - #createXRExperience(): Initializes WebXR AR experience.
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* -
|
|
53
|
-
* -
|
|
54
|
-
* -
|
|
56
|
+
* - #createCamera(): Creates and configures the main camera.
|
|
57
|
+
* - #createLights(): Creates and configures scene lights and shadows.
|
|
58
|
+
* - #initializeEnvironmentTexture(): Loads and sets the HDR environment texture.
|
|
59
|
+
* - #initializeIBLShadows(): Sets up IBL shadow pipeline and assigns meshes/materials.
|
|
60
|
+
* - #initializeShadows(): Sets up standard or IBL shadows for meshes.
|
|
61
|
+
* - #setMaxSimultaneousLights(): Updates max simultaneous lights for all materials.
|
|
62
|
+
* - #onPointerObservable(info): Handles pointer events and dispatches to pointer/mouse handlers.
|
|
63
|
+
* - #onPointerUp(event, pickInfo): Handles pointer up events (e.g., right-click for animation menu).
|
|
64
|
+
* - #onPointerMove(event, pickInfo): Handles pointer move events (e.g., mesh highlighting).
|
|
65
|
+
* - #onMouseWheel(event, pickInfo): Handles mouse wheel events for camera zoom.
|
|
66
|
+
* - #onKeyUp(event): Handles keyup events for download dialog and shortcuts.
|
|
67
|
+
* - #enableInteraction(): Adds canvas and scene interaction event listeners.
|
|
68
|
+
* - #disableInteraction(): Removes canvas and scene interaction event listeners.
|
|
69
|
+
* - #disposeAnimationController(): Disposes the animation controller if it exists.
|
|
70
|
+
* - #disposeXRExperience(): Disposes the Babylon.js WebXR experience if it exists.
|
|
71
|
+
* - #disposeEngine(): Disposes engine and releases all resources.
|
|
72
|
+
* - #setOptionsMaterial(optionMaterial): Applies a material option to relevant meshes.
|
|
73
|
+
* - #setOptions_Materials(): Applies all material options from configuration.
|
|
74
|
+
* - #setOptions_Camera(): Applies camera options from configuration.
|
|
75
|
+
* - #findContainerByName(name): Finds a container by its name.
|
|
76
|
+
* - #addContainer(container, updateVisibility): Adds a container to the scene and updates visibility.
|
|
77
|
+
* - #removeContainer(container, updateVisibility): Removes a container from the scene and updates visibility.
|
|
78
|
+
* - #replaceContainer(container, newAssetContainer): Replaces a container in the scene.
|
|
79
|
+
* - #getPrefViewer3DComponent(): Caches and retrieves the parent PREF-VIEWER-3D element.
|
|
80
|
+
* - #getPrefViewerComponent(): Caches and retrieves the parent PREF-VIEWER element.
|
|
81
|
+
* - #updateVisibilityAttributeInComponents(name, isVisible): Updates parent visibility attributes.
|
|
82
|
+
* - #setVisibilityOfWallAndFloorInModel(show): Controls wall/floor mesh visibility.
|
|
83
|
+
* - #stopRender(): Stops the Babylon.js render loop.
|
|
84
|
+
* - #startRender(): Starts the Babylon.js render loop.
|
|
85
|
+
* - #loadAssetContainer(container): Loads an asset container asynchronously.
|
|
86
|
+
* - #loadContainers(): Loads all asset containers and adds them to the scene.
|
|
87
|
+
* - #addDateToName(name): Appends the current date/time to a name string.
|
|
88
|
+
* - #downloadZip(files, name, comment, addDateInName): Generates and downloads a ZIP file.
|
|
89
|
+
* - #openDownloadDialog(): Opens the modal download dialog.
|
|
55
90
|
*/
|
|
56
91
|
export default class BabylonJSController {
|
|
57
92
|
// Canvas HTML element
|
|
@@ -76,6 +111,13 @@ export default class BabylonJSController {
|
|
|
76
111
|
#options = {};
|
|
77
112
|
|
|
78
113
|
#gltfResolver = null; // GLTFResolver instance
|
|
114
|
+
#babylonJSAnimationController = null; // AnimationController instance
|
|
115
|
+
|
|
116
|
+
#handlers = {
|
|
117
|
+
onKeyUp: null,
|
|
118
|
+
onPointerObservable: null,
|
|
119
|
+
renderLoop: null,
|
|
120
|
+
};
|
|
79
121
|
|
|
80
122
|
/**
|
|
81
123
|
* Constructs a new BabylonJSController instance.
|
|
@@ -99,6 +141,18 @@ export default class BabylonJSController {
|
|
|
99
141
|
};
|
|
100
142
|
});
|
|
101
143
|
this.#options = options;
|
|
144
|
+
this.#bindHandlers();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Pre-binds reusable event handlers to preserve stable references.
|
|
149
|
+
* @private
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
#bindHandlers() {
|
|
153
|
+
this.#handlers.onKeyUp = this.#onKeyUp.bind(this);
|
|
154
|
+
this.#handlers.onPointerObservable = this.#onPointerObservable.bind(this);
|
|
155
|
+
this.#handlers.renderLoop = this.#renderLoop.bind(this);
|
|
102
156
|
}
|
|
103
157
|
|
|
104
158
|
/**
|
|
@@ -290,29 +344,42 @@ export default class BabylonJSController {
|
|
|
290
344
|
return false;
|
|
291
345
|
}
|
|
292
346
|
|
|
293
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Creates and configures the IBL shadow render pipeline for the Babylon.js scene.
|
|
349
|
+
* Sets recommended options for resolution, sampling, opacity, and disables debug passes.
|
|
350
|
+
* Accepts an optional camera array for pipeline targeting.
|
|
351
|
+
* @private
|
|
352
|
+
* @param {Scene} scene - The Babylon.js scene instance.
|
|
353
|
+
* @param {Camera[]} [cameras] - Optional array of cameras to target with the pipeline.
|
|
354
|
+
* @returns {IblShadowsRenderPipeline} The configured IBL shadow pipeline.
|
|
355
|
+
*/
|
|
356
|
+
let createIBLShadowPipeline = function (scene, cameras = [scene.activeCamera]) {
|
|
294
357
|
const pipeline = new IblShadowsRenderPipeline(
|
|
295
358
|
"iblShadowsPipeline",
|
|
296
359
|
scene,
|
|
297
360
|
{
|
|
298
|
-
resolutionExp:
|
|
299
|
-
sampleDirections:
|
|
361
|
+
resolutionExp: 8, // Higher resolution for better shadow quality
|
|
362
|
+
sampleDirections: 4, // More sample directions for smoother shadows
|
|
300
363
|
ssShadowsEnabled: true,
|
|
301
|
-
shadowRemanence: 0.
|
|
364
|
+
shadowRemanence: 0.85,
|
|
302
365
|
triPlanarVoxelization: true,
|
|
303
|
-
shadowOpacity: 0.
|
|
366
|
+
shadowOpacity: 0.85,
|
|
304
367
|
},
|
|
305
|
-
|
|
368
|
+
cameras
|
|
306
369
|
);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
370
|
+
// Disable all debug passes for performance
|
|
371
|
+
const pipelineProps = {
|
|
372
|
+
allowDebugPasses: false,
|
|
373
|
+
gbufferDebugEnabled: false,
|
|
374
|
+
importanceSamplingDebugEnabled: false,
|
|
375
|
+
voxelDebugEnabled: false,
|
|
376
|
+
voxelDebugDisplayMip: 0,
|
|
377
|
+
voxelDebugAxis: 0,
|
|
378
|
+
voxelTracingDebugEnabled: false,
|
|
379
|
+
spatialBlurPassDebugEnabled: false,
|
|
380
|
+
accumulationPassDebugEnabled: false,
|
|
381
|
+
};
|
|
382
|
+
Object.assign(pipeline, pipelineProps);
|
|
316
383
|
return pipeline;
|
|
317
384
|
};
|
|
318
385
|
|
|
@@ -375,24 +442,88 @@ export default class BabylonJSController {
|
|
|
375
442
|
}
|
|
376
443
|
|
|
377
444
|
/**
|
|
378
|
-
*
|
|
379
|
-
* Adds a wheel event listener to control camera zoom based on mouse wheel input.
|
|
380
|
-
* Prevents zoom if the active camera is locked.
|
|
445
|
+
* Handles pointer events observed on the Babylon.js scene.
|
|
381
446
|
* @private
|
|
447
|
+
* @param {PointerInfo} info - The pointer event information from Babylon.js.
|
|
382
448
|
* @returns {void}
|
|
383
449
|
*/
|
|
384
|
-
#
|
|
385
|
-
this.#
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
450
|
+
#onPointerObservable(info) {
|
|
451
|
+
const pickInfo = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
452
|
+
if (info.type === PointerEventTypes.POINTERUP) {
|
|
453
|
+
this.#onPointerUp(info.event, pickInfo);
|
|
454
|
+
} else if (info.type === PointerEventTypes.POINTERMOVE) {
|
|
455
|
+
this.#onPointerMove(info.event, pickInfo);
|
|
456
|
+
} else if (info.type === PointerEventTypes.POINTERWHEEL) {
|
|
457
|
+
this.#onMouseWheel(info.event, pickInfo);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Sets up interaction handlers for the Babylon.js canvas and scene.
|
|
463
|
+
* @private
|
|
464
|
+
* @returns {void}
|
|
465
|
+
*/
|
|
466
|
+
#enableInteraction() {
|
|
467
|
+
if (this.#canvas) {
|
|
468
|
+
this.#canvas.addEventListener("keyup", this.#handlers.onKeyUp);
|
|
469
|
+
}
|
|
470
|
+
if (this.#scene) {
|
|
471
|
+
this.#scene.onPointerObservable.add(this.#handlers.onPointerObservable);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Removes interaction event listeners from the Babylon.js canvas.
|
|
477
|
+
* @private
|
|
478
|
+
* @returns {void}
|
|
479
|
+
*/
|
|
480
|
+
#disableInteraction() {
|
|
481
|
+
if (this.#canvas) {
|
|
482
|
+
this.#canvas.removeEventListener("keyup", this.#handlers.onKeyUp);
|
|
483
|
+
}
|
|
484
|
+
if (this.#scene !== null) {
|
|
485
|
+
this.#scene.onPointerObservable.removeCallback(this.#handlers.onPointerObservable);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Disposes the BabylonJSAnimationController instance if it exists.
|
|
491
|
+
* @private
|
|
492
|
+
* @returns {void}
|
|
493
|
+
*/
|
|
494
|
+
#disposeAnimationController() {
|
|
495
|
+
if (this.#babylonJSAnimationController) {
|
|
496
|
+
this.#babylonJSAnimationController.dispose();
|
|
497
|
+
this.#babylonJSAnimationController = null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Disposes the Babylon.js WebXR experience if it exists.
|
|
503
|
+
* @private
|
|
504
|
+
* @returns {void}
|
|
505
|
+
*/
|
|
506
|
+
#disposeXRExperience() {
|
|
507
|
+
if (!this.#XRExperience) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (this.#XRExperience.baseExperience.state === WebXRState.IN_XR) {
|
|
512
|
+
this.#XRExperience.baseExperience
|
|
513
|
+
.exitXRAsync()
|
|
514
|
+
.then(() => {
|
|
515
|
+
this.#XRExperience.dispose();
|
|
516
|
+
this.#XRExperience = null;
|
|
517
|
+
})
|
|
518
|
+
.catch((error) => {
|
|
519
|
+
console.warn("Error exiting XR experience:", error);
|
|
520
|
+
this.#XRExperience.dispose();
|
|
521
|
+
this.#XRExperience = null;
|
|
522
|
+
});
|
|
523
|
+
} else {
|
|
524
|
+
this.#XRExperience.dispose();
|
|
525
|
+
this.#XRExperience = null;
|
|
526
|
+
}
|
|
396
527
|
}
|
|
397
528
|
|
|
398
529
|
/**
|
|
@@ -408,13 +539,79 @@ export default class BabylonJSController {
|
|
|
408
539
|
this.#engine = this.#scene = this.#camera = null;
|
|
409
540
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
410
541
|
this.#shadowGen = null;
|
|
411
|
-
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Handles keyup events on the Babylon.js canvas for triggering model and scene downloads.
|
|
546
|
+
* @private
|
|
547
|
+
* @param {KeyboardEvent} event - The keyup event.
|
|
548
|
+
* @returns {void}
|
|
549
|
+
*/
|
|
550
|
+
#onKeyUp(event) {
|
|
551
|
+
// CTRL + ALT + letter
|
|
552
|
+
if (event.ctrlKey && event.altKey && event.key !== undefined) {
|
|
553
|
+
switch (event.key.toLowerCase()) {
|
|
554
|
+
case "d":
|
|
555
|
+
this.#openDownloadDialog();
|
|
556
|
+
break;
|
|
557
|
+
default:
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Handles mouse wheel events on the Babylon.js canvas for zooming the camera.
|
|
565
|
+
* @private
|
|
566
|
+
* @param {WheelEvent} event - The mouse wheel event.
|
|
567
|
+
* @param {Object} pickInfo - The result of the scene pick operation (not used in this method).
|
|
568
|
+
* @returns {void|false} Returns false if there is no active camera; otherwise, void.
|
|
569
|
+
*/
|
|
570
|
+
#onMouseWheel(event, pickInfo) {
|
|
571
|
+
if (!this.#scene?.activeCamera) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
//this.#scene.activeCamera.target = pickInfo.hit ? pickInfo.pickedPoint.clone() : this.#scene.activeCamera.target;
|
|
575
|
+
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
576
|
+
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
577
|
+
}
|
|
578
|
+
event.preventDefault();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Handles pointer up events on the Babylon.js scene.
|
|
583
|
+
* @private
|
|
584
|
+
* @param {PointerEvent} event - The pointer up event.
|
|
585
|
+
* @param {PickInfo} pickInfo - The result of the scene pick operation.
|
|
586
|
+
* @returns {void}
|
|
587
|
+
*/
|
|
588
|
+
#onPointerUp(event, pickInfo) {
|
|
589
|
+
if (this.#babylonJSAnimationController) {
|
|
590
|
+
this.#babylonJSAnimationController.hideMenu();
|
|
591
|
+
// Right click for showing animation menu
|
|
592
|
+
if (event.button === 2) {
|
|
593
|
+
this.#babylonJSAnimationController.showMenu(pickInfo);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Handles pointer move events on the Babylon.js scene.
|
|
600
|
+
* @private
|
|
601
|
+
* @param {PointerEvent} event - The pointer move event.
|
|
602
|
+
* @param {PickInfo} pickInfo - The result of the scene pick operation.
|
|
603
|
+
* @returns {void}
|
|
604
|
+
*/
|
|
605
|
+
#onPointerMove(event, pickInfo) {
|
|
606
|
+
if (this.#babylonJSAnimationController) {
|
|
607
|
+
this.#babylonJSAnimationController.hightlightMeshes(pickInfo);
|
|
608
|
+
}
|
|
412
609
|
}
|
|
413
610
|
|
|
414
611
|
/**
|
|
415
612
|
* Applies material options from the configuration to the relevant meshes.
|
|
416
613
|
* @private
|
|
417
|
-
* @param {
|
|
614
|
+
* @param {MaterialData} optionMaterial - Material option containing value, nodePrefixes, nodeNames, and state.
|
|
418
615
|
* @returns {boolean} True if any mesh material was set, false otherwise.
|
|
419
616
|
*/
|
|
420
617
|
#setOptionsMaterial(optionMaterial) {
|
|
@@ -435,7 +632,7 @@ export default class BabylonJSController {
|
|
|
435
632
|
if (container.state.name === "materials") {
|
|
436
633
|
return;
|
|
437
634
|
}
|
|
438
|
-
if (container.assetContainer && (container.isPending || materialContainer.isPending || optionMaterial.isPending)) {
|
|
635
|
+
if (container.assetContainer && (container.state.isPending || materialContainer.state.isPending || optionMaterial.isPending)) {
|
|
439
636
|
assetContainersToProcess.push(container.assetContainer);
|
|
440
637
|
}
|
|
441
638
|
});
|
|
@@ -487,7 +684,7 @@ export default class BabylonJSController {
|
|
|
487
684
|
const modelContainer = this.#containers.model;
|
|
488
685
|
const environmentContainer = this.#containers.environment;
|
|
489
686
|
|
|
490
|
-
if (!cameraState.isPending && !modelContainer.isPending && !environmentContainer.isPending) {
|
|
687
|
+
if (!cameraState.isPending && !modelContainer.state.isPending && !environmentContainer.state.isPending) {
|
|
491
688
|
return false;
|
|
492
689
|
}
|
|
493
690
|
|
|
@@ -542,7 +739,7 @@ export default class BabylonJSController {
|
|
|
542
739
|
* Finds and returns the asset container object by its name.
|
|
543
740
|
* @private
|
|
544
741
|
* @param {string} name - The name of the container to find.
|
|
545
|
-
* @returns {
|
|
742
|
+
* @returns {Object|null} The matching container object, or null if not found.
|
|
546
743
|
*/
|
|
547
744
|
#findContainerByName(name) {
|
|
548
745
|
return Object.values(this.#containers).find((container) => container.state.name === name) || null;
|
|
@@ -567,6 +764,13 @@ export default class BabylonJSController {
|
|
|
567
764
|
*/
|
|
568
765
|
#getPrefViewerComponent() {
|
|
569
766
|
if (this.#prefViewer === undefined) {
|
|
767
|
+
if (this.#prefViewer3D === undefined) {
|
|
768
|
+
this.#getPrefViewer3DComponent();
|
|
769
|
+
}
|
|
770
|
+
if (!this.#prefViewer3D) {
|
|
771
|
+
this.#prefViewer = null;
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
570
774
|
const rootNode = this.#prefViewer3D ? this.#prefViewer3D.getRootNode().host : null;
|
|
571
775
|
this.#prefViewer = rootNode && rootNode.nodeName === "PREF-VIEWER" ? rootNode : null;
|
|
572
776
|
}
|
|
@@ -579,7 +783,7 @@ export default class BabylonJSController {
|
|
|
579
783
|
* @param {boolean} isVisible - True to show the container, false to hide it.
|
|
580
784
|
* @returns {void}
|
|
581
785
|
*/
|
|
582
|
-
#
|
|
786
|
+
#updateVisibilityAttributeInComponents(name, isVisible) {
|
|
583
787
|
// Cache references to parent custom elements
|
|
584
788
|
this.#getPrefViewer3DComponent();
|
|
585
789
|
this.#getPrefViewerComponent();
|
|
@@ -596,13 +800,16 @@ export default class BabylonJSController {
|
|
|
596
800
|
* Adds the asset container to the Babylon.js scene if it should be shown and is not already visible.
|
|
597
801
|
* @private
|
|
598
802
|
* @param {object} container - The container object containing asset state and metadata.
|
|
803
|
+
* @param {boolean} [updateVisibility=true] - If true, updates the visibility attribute in parent components.
|
|
599
804
|
* @returns {boolean} True if the container was added, false otherwise.
|
|
600
805
|
*/
|
|
601
|
-
#addContainer(container) {
|
|
806
|
+
#addContainer(container, updateVisibility = true) {
|
|
602
807
|
if (!container.assetContainer || container.state.isVisible || !container.state.mustBeShown) {
|
|
603
808
|
return false;
|
|
604
809
|
}
|
|
605
|
-
|
|
810
|
+
if (updateVisibility) {
|
|
811
|
+
this.#updateVisibilityAttributeInComponents(container.state.name, true);
|
|
812
|
+
}
|
|
606
813
|
container.assetContainer.addAllToScene();
|
|
607
814
|
container.state.visible = true;
|
|
608
815
|
return true;
|
|
@@ -612,13 +819,16 @@ export default class BabylonJSController {
|
|
|
612
819
|
* Removes the asset container from the Babylon.js scene if it is currently visible.
|
|
613
820
|
* @private
|
|
614
821
|
* @param {object} container - The container object containing asset state and metadata.
|
|
822
|
+
* @param {boolean} [updateVisibility=true] - If true, updates the visibility attribute in parent components.
|
|
615
823
|
* @returns {boolean} True if the container was removed, false otherwise.
|
|
616
824
|
*/
|
|
617
|
-
#removeContainer(container) {
|
|
825
|
+
#removeContainer(container, updateVisibility = true) {
|
|
618
826
|
if (!container.assetContainer || !container.state.isVisible) {
|
|
619
827
|
return false;
|
|
620
828
|
}
|
|
621
|
-
|
|
829
|
+
if (updateVisibility) {
|
|
830
|
+
this.#updateVisibilityAttributeInComponents(container.state.name, false);
|
|
831
|
+
}
|
|
622
832
|
container.assetContainer.removeAllFromScene();
|
|
623
833
|
container.state.visible = false;
|
|
624
834
|
return true;
|
|
@@ -628,20 +838,20 @@ export default class BabylonJSController {
|
|
|
628
838
|
* Replaces the asset container in the Babylon.js scene with a new one.
|
|
629
839
|
* @private
|
|
630
840
|
* @param {object} container - The container object containing asset state and metadata.
|
|
631
|
-
* @param {
|
|
632
|
-
* @returns {boolean} True if the container was replaced, false otherwise.
|
|
841
|
+
* @param {AssetContainer} newAssetContainer - The new asset container to add to the scene.
|
|
842
|
+
* @returns {boolean} True if the container was replaced and added, false otherwise.
|
|
633
843
|
*/
|
|
634
844
|
#replaceContainer(container, newAssetContainer) {
|
|
635
845
|
if (container.assetContainer) {
|
|
636
|
-
this.#removeContainer(container);
|
|
846
|
+
this.#removeContainer(container, false);
|
|
637
847
|
container.assetContainer.dispose();
|
|
638
848
|
container.assetContainer = null;
|
|
639
849
|
}
|
|
640
850
|
this.#scene.getEngine().releaseEffects();
|
|
641
851
|
container.assetContainer = newAssetContainer;
|
|
642
|
-
this.#addContainer(container);
|
|
643
|
-
return true;
|
|
852
|
+
return this.#addContainer(container, false);
|
|
644
853
|
}
|
|
854
|
+
|
|
645
855
|
/**
|
|
646
856
|
* Sets the visibility of wall and floor meshes in the model container based on the provided value or environment visibility.
|
|
647
857
|
* @private
|
|
@@ -663,9 +873,8 @@ export default class BabylonJSController {
|
|
|
663
873
|
* @private
|
|
664
874
|
* @returns {void}
|
|
665
875
|
*/
|
|
666
|
-
#stopR;
|
|
667
876
|
#stopRender() {
|
|
668
|
-
this.#engine.stopRenderLoop(this.#renderLoop
|
|
877
|
+
this.#engine.stopRenderLoop(this.#handlers.renderLoop);
|
|
669
878
|
}
|
|
670
879
|
/**
|
|
671
880
|
* Starts the Babylon.js render loop for the current scene.
|
|
@@ -675,7 +884,7 @@ export default class BabylonJSController {
|
|
|
675
884
|
*/
|
|
676
885
|
async #startRender() {
|
|
677
886
|
await this.#scene.whenReadyAsync();
|
|
678
|
-
this.#engine.runRenderLoop(this.#renderLoop
|
|
887
|
+
this.#engine.runRenderLoop(this.#handlers.renderLoop);
|
|
679
888
|
}
|
|
680
889
|
|
|
681
890
|
/**
|
|
@@ -746,6 +955,7 @@ export default class BabylonJSController {
|
|
|
746
955
|
|
|
747
956
|
await Promise.allSettled(promiseArray)
|
|
748
957
|
.then((values) => {
|
|
958
|
+
this.#disposeAnimationController();
|
|
749
959
|
values.forEach((result) => {
|
|
750
960
|
const container = result.value ? result.value[0] : null;
|
|
751
961
|
const assetContainer = result.value ? result.value[1] : null;
|
|
@@ -777,12 +987,127 @@ export default class BabylonJSController {
|
|
|
777
987
|
.finally(async () => {
|
|
778
988
|
this.#setMaxSimultaneousLights();
|
|
779
989
|
this.#initializeShadows();
|
|
990
|
+
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#scene);
|
|
780
991
|
this.#startRender();
|
|
781
992
|
});
|
|
782
|
-
|
|
783
993
|
return detail;
|
|
784
994
|
}
|
|
785
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Appends the current date and time to the provided name string in the format: name_YYYY-MM-DD_HH.mm.ss
|
|
998
|
+
* @private
|
|
999
|
+
* @param {string} name - The base name to which the date and time will be appended.
|
|
1000
|
+
* @returns {string} The name with the appended date and time.
|
|
1001
|
+
*/
|
|
1002
|
+
#addDateToName(name) {
|
|
1003
|
+
const now = new Date();
|
|
1004
|
+
const year = now.getFullYear();
|
|
1005
|
+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
1006
|
+
const day = now.getDate().toString().padStart(2, "0");
|
|
1007
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
1008
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
1009
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
1010
|
+
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
1011
|
+
return name;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Generates and downloads a ZIP file with the specified folder name and files.
|
|
1016
|
+
* @private
|
|
1017
|
+
* @param {Object} files - An object where keys are file names and values are file contents.
|
|
1018
|
+
* @param {string} [name="files"] - The name for the root folder inside the ZIP.
|
|
1019
|
+
* @param {string} [comment=""] - A comment to include in the ZIP file.
|
|
1020
|
+
* @param {boolean} [addDateInName=false] - Whether to append the current date/time to the folder name.
|
|
1021
|
+
* @returns {void}
|
|
1022
|
+
* @description
|
|
1023
|
+
* Uses JSZip to create the ZIP and triggers a browser download.
|
|
1024
|
+
*/
|
|
1025
|
+
#downloadZip(files, name = "files", comment = "", addDateInName = false) {
|
|
1026
|
+
name = addDateInName ? this.#addDateToName(name) : name;
|
|
1027
|
+
|
|
1028
|
+
const zip = new JSZip();
|
|
1029
|
+
zip.comment = comment;
|
|
1030
|
+
|
|
1031
|
+
const rootFolder = zip.folder(name);
|
|
1032
|
+
Object.keys(files).forEach((key) => {
|
|
1033
|
+
rootFolder.file(key, files[key]);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
zip.generateAsync({ type: "blob" }).then((content) => {
|
|
1037
|
+
const zipName = `${name}.zip`;
|
|
1038
|
+
const a = document.createElement("a");
|
|
1039
|
+
const url = window.URL.createObjectURL(content);
|
|
1040
|
+
a.href = url;
|
|
1041
|
+
a.download = zipName;
|
|
1042
|
+
a.click();
|
|
1043
|
+
window.URL.revokeObjectURL(url);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
/**
|
|
1048
|
+
* Opens a modal dialog for downloading the 3D scene, model, or environment.
|
|
1049
|
+
* @private
|
|
1050
|
+
* @returns {void}
|
|
1051
|
+
*/
|
|
1052
|
+
#openDownloadDialog() {
|
|
1053
|
+
this.#getPrefViewerComponent();
|
|
1054
|
+
if (!this.#prefViewer) {
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
const header = "Download 3D Scene";
|
|
1059
|
+
const content = `
|
|
1060
|
+
<form slot="content" id="download-dialog-form" style="display:flex;flex-direction:column;gap:16px;">
|
|
1061
|
+
<h4>Content</h4>
|
|
1062
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
1063
|
+
<label><input type="radio" name="content" value="1"> Model</label>
|
|
1064
|
+
<label><input type="radio" name="content" value="2"> Scene</label>
|
|
1065
|
+
<label><input type="radio" name="content" value="0" checked> Both</label>
|
|
1066
|
+
</div>
|
|
1067
|
+
<h4>Format</h4>
|
|
1068
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
1069
|
+
<label><input type="radio" name="format" value="gltf" checked> glTF (ZIP)</label>
|
|
1070
|
+
<label><input type="radio" name="format" value="glb"> GLB</label>
|
|
1071
|
+
<label><input type="radio" name="format" value="usdz"> USDZ</label>
|
|
1072
|
+
</div>
|
|
1073
|
+
</form>`;
|
|
1074
|
+
const footer = `
|
|
1075
|
+
<button type="button" class="primary" id="download-dialog-download">Download</button>
|
|
1076
|
+
<button type="button" id="download-dialog-cancel">Cancel</button>`;
|
|
1077
|
+
|
|
1078
|
+
const dialog = this.#prefViewer.openDialog(header, content, footer);
|
|
1079
|
+
|
|
1080
|
+
if (!dialog) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Button event handlers
|
|
1085
|
+
const form = dialog.querySelector("#download-dialog-form");
|
|
1086
|
+
const downloadButton = dialog.querySelector("#download-dialog-download");
|
|
1087
|
+
const cancelButton = dialog.querySelector("#download-dialog-cancel");
|
|
1088
|
+
|
|
1089
|
+
downloadButton.onclick = () => {
|
|
1090
|
+
const contentValue = form.content.value;
|
|
1091
|
+
const formatValue = form.format.value;
|
|
1092
|
+
switch (formatValue) {
|
|
1093
|
+
case "glb":
|
|
1094
|
+
this.downloadGLB(Number(contentValue));
|
|
1095
|
+
break;
|
|
1096
|
+
case "gltf":
|
|
1097
|
+
this.downloadGLTF(Number(contentValue));
|
|
1098
|
+
break;
|
|
1099
|
+
case "usdz":
|
|
1100
|
+
this.downloadUSDZ(Number(contentValue));
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
this.#prefViewer.closeDialog();
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
cancelButton.onclick = () => {
|
|
1107
|
+
this.#prefViewer.closeDialog();
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
786
1111
|
/**
|
|
787
1112
|
* ---------------------------
|
|
788
1113
|
* Public methods
|
|
@@ -798,13 +1123,13 @@ export default class BabylonJSController {
|
|
|
798
1123
|
*/
|
|
799
1124
|
async enable() {
|
|
800
1125
|
this.#configureDracoCompression();
|
|
801
|
-
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
1126
|
+
this.#engine = new Engine(this.#canvas, true, { alpha: true, stencil: true, preserveDrawingBuffer: false });
|
|
802
1127
|
this.#engine.disableUniformBuffers = true;
|
|
803
1128
|
this.#scene = new Scene(this.#engine);
|
|
804
1129
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
805
1130
|
this.#createCamera();
|
|
806
1131
|
this.#createLights();
|
|
807
|
-
this.#
|
|
1132
|
+
this.#enableInteraction();
|
|
808
1133
|
await this.#createXRExperience();
|
|
809
1134
|
this.#startRender();
|
|
810
1135
|
this.#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
@@ -818,8 +1143,11 @@ export default class BabylonJSController {
|
|
|
818
1143
|
* @returns {void}
|
|
819
1144
|
*/
|
|
820
1145
|
disable() {
|
|
821
|
-
this.#disposeEngine();
|
|
822
1146
|
this.#canvasResizeObserver.disconnect();
|
|
1147
|
+
this.#disableInteraction();
|
|
1148
|
+
this.#disposeAnimationController();
|
|
1149
|
+
this.#disposeXRExperience();
|
|
1150
|
+
this.#disposeEngine();
|
|
823
1151
|
}
|
|
824
1152
|
|
|
825
1153
|
/**
|
|
@@ -883,50 +1211,105 @@ export default class BabylonJSController {
|
|
|
883
1211
|
}
|
|
884
1212
|
|
|
885
1213
|
/**
|
|
886
|
-
*
|
|
1214
|
+
* Downloads the current scene, model, or environment as a GLB file.
|
|
887
1215
|
* @public
|
|
1216
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
888
1217
|
* @returns {void}
|
|
889
1218
|
*/
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1219
|
+
downloadGLB(content = 0) {
|
|
1220
|
+
let fileName = "";
|
|
1221
|
+
let container = null;
|
|
1222
|
+
switch (content) {
|
|
1223
|
+
case 0:
|
|
1224
|
+
fileName = "full-scene";
|
|
1225
|
+
container = this.#scene;
|
|
1226
|
+
break;
|
|
1227
|
+
case 1:
|
|
1228
|
+
fileName = "model";
|
|
1229
|
+
container = this.#containers.model.assetContainer;
|
|
1230
|
+
break;
|
|
1231
|
+
case 2:
|
|
1232
|
+
fileName = "scene";
|
|
1233
|
+
container = this.#containers.environment.assetContainer;
|
|
1234
|
+
break;
|
|
1235
|
+
default:
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
fileName = this.#addDateToName(fileName);
|
|
1239
|
+
GLTF2Export.GLBAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1240
|
+
if (glb) {
|
|
1241
|
+
glb.downloadFiles();
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
893
1244
|
}
|
|
894
1245
|
|
|
895
1246
|
/**
|
|
896
|
-
*
|
|
1247
|
+
* Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
897
1248
|
* @public
|
|
1249
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
898
1250
|
* @returns {void}
|
|
899
1251
|
*/
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1252
|
+
downloadGLTF(content = 0) {
|
|
1253
|
+
let fileName = "";
|
|
1254
|
+
let container = null;
|
|
1255
|
+
let comment = "";
|
|
1256
|
+
switch (content) {
|
|
1257
|
+
case 0:
|
|
1258
|
+
fileName = "full-scene";
|
|
1259
|
+
comment = "Export of the complete scene in glTF format, including the model and the environment.";
|
|
1260
|
+
container = this.#scene;
|
|
1261
|
+
break;
|
|
1262
|
+
case 1:
|
|
1263
|
+
fileName = "model";
|
|
1264
|
+
comment = "Export of model scene in glTF format.";
|
|
1265
|
+
container = this.#containers.model.assetContainer;
|
|
1266
|
+
break;
|
|
1267
|
+
case 2:
|
|
1268
|
+
fileName = "scene";
|
|
1269
|
+
comment = "Export of the environment scene in glTF format.";
|
|
1270
|
+
container = this.#containers.environment.assetContainer;
|
|
1271
|
+
break;
|
|
1272
|
+
default:
|
|
1273
|
+
break;
|
|
1274
|
+
}
|
|
1275
|
+
comment += "\nPrefViewer by Preference, S.L.\npreference.com\n";
|
|
1276
|
+
GLTF2Export.GLTFAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((gltf) => {
|
|
1277
|
+
if (gltf?.files) {
|
|
1278
|
+
this.#downloadZip(gltf.files, fileName, comment, true);
|
|
905
1279
|
}
|
|
906
1280
|
});
|
|
907
1281
|
}
|
|
908
1282
|
|
|
909
1283
|
/**
|
|
910
|
-
*
|
|
1284
|
+
* Downloads the current scene, model, or environment as a USDZ file.
|
|
911
1285
|
* @public
|
|
1286
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
912
1287
|
* @returns {void}
|
|
913
1288
|
*/
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1289
|
+
downloadUSDZ(content = 0) {
|
|
1290
|
+
let fileName = "";
|
|
1291
|
+
let container = null;
|
|
1292
|
+
switch (content) {
|
|
1293
|
+
case 0:
|
|
1294
|
+
fileName = "full-scene";
|
|
1295
|
+
container = this.#scene;
|
|
1296
|
+
break;
|
|
1297
|
+
case 1:
|
|
1298
|
+
fileName = "model";
|
|
1299
|
+
container = this.#containers.model.assetContainer;
|
|
1300
|
+
break;
|
|
1301
|
+
case 2:
|
|
1302
|
+
fileName = "scene";
|
|
1303
|
+
container = this.#containers.environment.assetContainer;
|
|
1304
|
+
break;
|
|
1305
|
+
default:
|
|
1306
|
+
break;
|
|
1307
|
+
}
|
|
1308
|
+
fileName = this.#addDateToName(fileName);
|
|
1309
|
+
USDZExportAsync(container).then((response) => {
|
|
917
1310
|
if (response) {
|
|
918
1311
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
919
1312
|
}
|
|
920
1313
|
});
|
|
921
1314
|
}
|
|
922
|
-
|
|
923
|
-
/**
|
|
924
|
-
* Initiates download of the entire scene (model and environment) as a GLB file.
|
|
925
|
-
* @public
|
|
926
|
-
* @returns {void}
|
|
927
|
-
*/
|
|
928
|
-
downloadModelAndSceneGLB() {
|
|
929
|
-
const fileName = "scene";
|
|
930
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
931
|
-
}
|
|
932
1315
|
}
|