@preference-sl/pref-viewer 2.11.0-beta.1 → 2.11.0-beta.11
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 +12 -8
- package/src/babylonjs-animation-controller.js +235 -0
- package/src/babylonjs-animation-opening-menu.js +360 -0
- package/src/babylonjs-animation-opening.js +496 -0
- package/src/babylonjs-controller.js +343 -86
- package/src/css/pref-viewer-2d.css +39 -0
- package/src/css/pref-viewer-3d.css +28 -0
- package/src/css/pref-viewer-dialog.css +105 -0
- package/src/css/pref-viewer.css +11 -0
- package/src/file-storage.js +166 -39
- package/src/index.js +318 -81
- package/src/pref-viewer-2d.js +67 -47
- package/src/pref-viewer-3d.js +328 -84
- package/src/pref-viewer-dialog.js +140 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ArcRotateCamera, AssetContainer, Camera, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager } from "@babylonjs/core";
|
|
2
|
+
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
2
3
|
import "@babylonjs/loaders";
|
|
3
|
-
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
4
4
|
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
5
|
-
import {
|
|
5
|
+
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
6
|
+
|
|
6
7
|
import GLTFResolver from "./gltf-resolver.js";
|
|
8
|
+
import { MaterialData } from "./pref-viewer-3d-data.js";
|
|
9
|
+
import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* BabylonJSController - Main controller for managing Babylon.js 3D scenes, assets, and interactions.
|
|
@@ -13,7 +16,7 @@ import GLTFResolver from "./gltf-resolver.js";
|
|
|
13
16
|
* - Handles loading, replacing, and disposing of 3D models, environments, and materials.
|
|
14
17
|
* - Configures advanced rendering features such as Draco mesh compression, shadows, and image-based lighting (IBL).
|
|
15
18
|
* - Integrates Babylon.js WebXR experience for augmented reality (AR) support.
|
|
16
|
-
* - Provides methods for downloading models and scenes in GLB and USDZ formats.
|
|
19
|
+
* - Provides methods for downloading models and scenes in GLB, GLTF, and USDZ formats.
|
|
17
20
|
* - Manages camera and material options, container visibility, and user interactions.
|
|
18
21
|
* - Observes canvas resize events and updates the engine accordingly.
|
|
19
22
|
*
|
|
@@ -23,7 +26,7 @@ import GLTFResolver from "./gltf-resolver.js";
|
|
|
23
26
|
* - Load assets: await controller.load();
|
|
24
27
|
* - Set camera/material options: controller.setCameraOptions(), controller.setMaterialOptions();
|
|
25
28
|
* - Control visibility: controller.setContainerVisibility(name, show);
|
|
26
|
-
* - Download assets: controller.downloadModelGLB(), controller.downloadModelUSDZ(),
|
|
29
|
+
* - Download assets: controller.downloadModelGLB(), controller.downloadModelGLTF(), controller.downloadModelUSDZ(), controller.downloadModelAndSceneGLB(), controller.downloadModelAndSceneGLTF(), controller.downloadModelAndSceneUSDZ();
|
|
27
30
|
* - Disable rendering: controller.disable();
|
|
28
31
|
*
|
|
29
32
|
* Public Methods:
|
|
@@ -33,20 +36,29 @@ import GLTFResolver from "./gltf-resolver.js";
|
|
|
33
36
|
* - setCameraOptions(): Applies camera options from configuration.
|
|
34
37
|
* - setMaterialOptions(): Applies material options from configuration.
|
|
35
38
|
* - setContainerVisibility(name, show): Shows or hides a container by name.
|
|
36
|
-
* -
|
|
39
|
+
* - downloadGLB(content): Downloads the current scene, model, or environment as a GLB file.
|
|
40
|
+
* - downloadGLTF(content): Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
41
|
+
* - downloadUSDZ(content): Downloads the current scene, model, or environment as a USDZ file.
|
|
37
42
|
*
|
|
38
43
|
* Private Methods:
|
|
39
44
|
* - #configureDracoCompression(): Sets up Draco mesh compression.
|
|
45
|
+
* - #renderLoop(): Babylon.js render loop callback.
|
|
46
|
+
* - #addStylesToARButton(): Styles AR button.
|
|
47
|
+
* - #createXRExperience(): Initializes WebXR AR experience.
|
|
40
48
|
* - #createCamera(), #createLights(), #initializeEnvironmentTexture(), #initializeIBLShadows(), #initializeShadows(): Scene setup.
|
|
41
|
-
* - #
|
|
49
|
+
* - #setMaxSimultaneousLights(): Updates max simultaneous lights for materials.
|
|
50
|
+
* - #enableInteraction(), #disableInteraction(): Canvas interaction handlers.
|
|
42
51
|
* - #disposeEngine(): Disposes engine and resources.
|
|
52
|
+
* - #onMouseWheel(event), #onKeyUp(event): Canvas event handlers.
|
|
43
53
|
* - #setOptionsMaterial(), #setOptions_Materials(), #setOptions_Camera(): Applies material/camera options.
|
|
44
54
|
* - #findContainerByName(), #addContainer(), #removeContainer(), #replaceContainer(): Container management.
|
|
55
|
+
* - #getPrefViewer3DComponent(), #getPrefViewerComponent(): Custom element references.
|
|
56
|
+
* - #updateVisibilityAttributeInComponents(): Updates parent visibility attributes.
|
|
45
57
|
* - #setVisibilityOfWallAndFloorInModel(): Controls wall/floor mesh visibility.
|
|
46
58
|
* - #stopRender(), #startRender(): Render loop control.
|
|
47
59
|
* - #loadAssetContainer(), #loadContainers(): Asset loading.
|
|
48
|
-
* - #
|
|
49
|
-
* - #
|
|
60
|
+
* - #downloadZip(): Generates and downloads a ZIP file.
|
|
61
|
+
* - #openDownloadDialog(): Opens the modal download dialog.
|
|
50
62
|
*
|
|
51
63
|
* Notes:
|
|
52
64
|
* - Designed for integration with PrefViewer and GLTFResolver.
|
|
@@ -76,6 +88,7 @@ export default class BabylonJSController {
|
|
|
76
88
|
#options = {};
|
|
77
89
|
|
|
78
90
|
#gltfResolver = null; // GLTFResolver instance
|
|
91
|
+
#babylonJSAnimationController = null; // AnimationController instance
|
|
79
92
|
|
|
80
93
|
/**
|
|
81
94
|
* Constructs a new BabylonJSController instance.
|
|
@@ -290,29 +303,42 @@ export default class BabylonJSController {
|
|
|
290
303
|
return false;
|
|
291
304
|
}
|
|
292
305
|
|
|
293
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Creates and configures the IBL shadow render pipeline for the Babylon.js scene.
|
|
308
|
+
* Sets recommended options for resolution, sampling, opacity, and disables debug passes.
|
|
309
|
+
* Accepts an optional camera array for pipeline targeting.
|
|
310
|
+
* @private
|
|
311
|
+
* @param {Scene} scene - The Babylon.js scene instance.
|
|
312
|
+
* @param {Camera[]} [cameras] - Optional array of cameras to target with the pipeline.
|
|
313
|
+
* @returns {IblShadowsRenderPipeline} The configured IBL shadow pipeline.
|
|
314
|
+
*/
|
|
315
|
+
let createIBLShadowPipeline = function (scene, cameras = [scene.activeCamera]) {
|
|
294
316
|
const pipeline = new IblShadowsRenderPipeline(
|
|
295
317
|
"iblShadowsPipeline",
|
|
296
318
|
scene,
|
|
297
319
|
{
|
|
298
|
-
resolutionExp:
|
|
299
|
-
sampleDirections:
|
|
320
|
+
resolutionExp: 8, // Higher resolution for better shadow quality
|
|
321
|
+
sampleDirections: 4, // More sample directions for smoother shadows
|
|
300
322
|
ssShadowsEnabled: true,
|
|
301
|
-
shadowRemanence: 0.
|
|
323
|
+
shadowRemanence: 0.85,
|
|
302
324
|
triPlanarVoxelization: true,
|
|
303
|
-
shadowOpacity: 0.
|
|
325
|
+
shadowOpacity: 0.85,
|
|
304
326
|
},
|
|
305
|
-
|
|
327
|
+
cameras
|
|
306
328
|
);
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
329
|
+
// Disable all debug passes for performance
|
|
330
|
+
const pipelineProps = {
|
|
331
|
+
allowDebugPasses: false,
|
|
332
|
+
gbufferDebugEnabled: false,
|
|
333
|
+
importanceSamplingDebugEnabled: false,
|
|
334
|
+
voxelDebugEnabled: false,
|
|
335
|
+
voxelDebugDisplayMip: 0,
|
|
336
|
+
voxelDebugAxis: 0,
|
|
337
|
+
voxelTracingDebugEnabled: false,
|
|
338
|
+
spatialBlurPassDebugEnabled: false,
|
|
339
|
+
accumulationPassDebugEnabled: false,
|
|
340
|
+
};
|
|
341
|
+
Object.assign(pipeline, pipelineProps);
|
|
316
342
|
return pipeline;
|
|
317
343
|
};
|
|
318
344
|
|
|
@@ -376,23 +402,22 @@ export default class BabylonJSController {
|
|
|
376
402
|
|
|
377
403
|
/**
|
|
378
404
|
* Sets up interaction handlers for the Babylon.js canvas.
|
|
379
|
-
* Adds a wheel event listener to control camera zoom based on mouse wheel input.
|
|
380
|
-
* Prevents zoom if the active camera is locked.
|
|
381
405
|
* @private
|
|
382
406
|
* @returns {void}
|
|
383
407
|
*/
|
|
384
|
-
#
|
|
385
|
-
this.#canvas.addEventListener("wheel", (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
408
|
+
#enableInteraction() {
|
|
409
|
+
this.#canvas.addEventListener("wheel", this.#onMouseWheel.bind(this));
|
|
410
|
+
this.#canvas.addEventListener("keyup", this.#onKeyUp.bind(this));
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Removes interaction event listeners from the Babylon.js canvas.
|
|
415
|
+
* @private
|
|
416
|
+
* @returns {void}
|
|
417
|
+
*/
|
|
418
|
+
#disableInteraction() {
|
|
419
|
+
this.#canvas.removeEventListener("wheel", this.#onMouseWheel.bind(this));
|
|
420
|
+
this.#canvas.removeEventListener("keyup", this.#onKeyUp.bind(this));
|
|
396
421
|
}
|
|
397
422
|
|
|
398
423
|
/**
|
|
@@ -411,10 +436,47 @@ export default class BabylonJSController {
|
|
|
411
436
|
this.#XRExperience = null;
|
|
412
437
|
}
|
|
413
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Handles mouse wheel events on the Babylon.js canvas for zooming the camera.
|
|
441
|
+
* @private
|
|
442
|
+
* @param {WheelEvent} event - The mouse wheel event.
|
|
443
|
+
* @returns {void|false}
|
|
444
|
+
*/
|
|
445
|
+
#onMouseWheel(event) {
|
|
446
|
+
if (!this.#scene || !this.#camera) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
//const pick = this.#scene.pick(this.#scene.pointerX, this.#scene.pointerY);
|
|
450
|
+
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
451
|
+
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
452
|
+
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
453
|
+
}
|
|
454
|
+
event.preventDefault();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Handles keyup events on the Babylon.js canvas for triggering model and scene downloads.
|
|
459
|
+
* @private
|
|
460
|
+
* @param {KeyboardEvent} event - The keyup event.
|
|
461
|
+
* @returns {void}
|
|
462
|
+
*/
|
|
463
|
+
#onKeyUp(event) {
|
|
464
|
+
// CTRL + ALT + letter
|
|
465
|
+
if (event.ctrlKey && event.altKey && event.key !== undefined) {
|
|
466
|
+
switch (event.key.toLowerCase()) {
|
|
467
|
+
case "d":
|
|
468
|
+
this.#openDownloadDialog();
|
|
469
|
+
break;
|
|
470
|
+
default:
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
414
476
|
/**
|
|
415
477
|
* Applies material options from the configuration to the relevant meshes.
|
|
416
478
|
* @private
|
|
417
|
-
* @param {
|
|
479
|
+
* @param {MaterialData} optionMaterial - Material option containing value, nodePrefixes, nodeNames, and state.
|
|
418
480
|
* @returns {boolean} True if any mesh material was set, false otherwise.
|
|
419
481
|
*/
|
|
420
482
|
#setOptionsMaterial(optionMaterial) {
|
|
@@ -435,7 +497,7 @@ export default class BabylonJSController {
|
|
|
435
497
|
if (container.state.name === "materials") {
|
|
436
498
|
return;
|
|
437
499
|
}
|
|
438
|
-
if (container.assetContainer && (container.isPending || materialContainer.isPending || optionMaterial.isPending)) {
|
|
500
|
+
if (container.assetContainer && (container.state.isPending || materialContainer.state.isPending || optionMaterial.isPending)) {
|
|
439
501
|
assetContainersToProcess.push(container.assetContainer);
|
|
440
502
|
}
|
|
441
503
|
});
|
|
@@ -487,7 +549,7 @@ export default class BabylonJSController {
|
|
|
487
549
|
const modelContainer = this.#containers.model;
|
|
488
550
|
const environmentContainer = this.#containers.environment;
|
|
489
551
|
|
|
490
|
-
if (!cameraState.isPending && !modelContainer.isPending && !environmentContainer.isPending) {
|
|
552
|
+
if (!cameraState.isPending && !modelContainer.state.isPending && !environmentContainer.state.isPending) {
|
|
491
553
|
return false;
|
|
492
554
|
}
|
|
493
555
|
|
|
@@ -542,7 +604,7 @@ export default class BabylonJSController {
|
|
|
542
604
|
* Finds and returns the asset container object by its name.
|
|
543
605
|
* @private
|
|
544
606
|
* @param {string} name - The name of the container to find.
|
|
545
|
-
* @returns {
|
|
607
|
+
* @returns {Object|null} The matching container object, or null if not found.
|
|
546
608
|
*/
|
|
547
609
|
#findContainerByName(name) {
|
|
548
610
|
return Object.values(this.#containers).find((container) => container.state.name === name) || null;
|
|
@@ -567,6 +629,13 @@ export default class BabylonJSController {
|
|
|
567
629
|
*/
|
|
568
630
|
#getPrefViewerComponent() {
|
|
569
631
|
if (this.#prefViewer === undefined) {
|
|
632
|
+
if (this.#prefViewer3D === undefined) {
|
|
633
|
+
this.#getPrefViewer3DComponent();
|
|
634
|
+
}
|
|
635
|
+
if (!this.#prefViewer3D) {
|
|
636
|
+
this.#prefViewer = null;
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
570
639
|
const rootNode = this.#prefViewer3D ? this.#prefViewer3D.getRootNode().host : null;
|
|
571
640
|
this.#prefViewer = rootNode && rootNode.nodeName === "PREF-VIEWER" ? rootNode : null;
|
|
572
641
|
}
|
|
@@ -579,7 +648,7 @@ export default class BabylonJSController {
|
|
|
579
648
|
* @param {boolean} isVisible - True to show the container, false to hide it.
|
|
580
649
|
* @returns {void}
|
|
581
650
|
*/
|
|
582
|
-
#
|
|
651
|
+
#updateVisibilityAttributeInComponents(name, isVisible) {
|
|
583
652
|
// Cache references to parent custom elements
|
|
584
653
|
this.#getPrefViewer3DComponent();
|
|
585
654
|
this.#getPrefViewerComponent();
|
|
@@ -596,13 +665,16 @@ export default class BabylonJSController {
|
|
|
596
665
|
* Adds the asset container to the Babylon.js scene if it should be shown and is not already visible.
|
|
597
666
|
* @private
|
|
598
667
|
* @param {object} container - The container object containing asset state and metadata.
|
|
668
|
+
* @param {boolean} [updateVisibility=true] - If true, updates the visibility attribute in parent components.
|
|
599
669
|
* @returns {boolean} True if the container was added, false otherwise.
|
|
600
670
|
*/
|
|
601
|
-
#addContainer(container) {
|
|
671
|
+
#addContainer(container, updateVisibility = true) {
|
|
602
672
|
if (!container.assetContainer || container.state.isVisible || !container.state.mustBeShown) {
|
|
603
673
|
return false;
|
|
604
674
|
}
|
|
605
|
-
|
|
675
|
+
if (updateVisibility) {
|
|
676
|
+
this.#updateVisibilityAttributeInComponents(container.state.name, true);
|
|
677
|
+
}
|
|
606
678
|
container.assetContainer.addAllToScene();
|
|
607
679
|
container.state.visible = true;
|
|
608
680
|
return true;
|
|
@@ -612,13 +684,16 @@ export default class BabylonJSController {
|
|
|
612
684
|
* Removes the asset container from the Babylon.js scene if it is currently visible.
|
|
613
685
|
* @private
|
|
614
686
|
* @param {object} container - The container object containing asset state and metadata.
|
|
687
|
+
* @param {boolean} [updateVisibility=true] - If true, updates the visibility attribute in parent components.
|
|
615
688
|
* @returns {boolean} True if the container was removed, false otherwise.
|
|
616
689
|
*/
|
|
617
|
-
#removeContainer(container) {
|
|
690
|
+
#removeContainer(container, updateVisibility = true) {
|
|
618
691
|
if (!container.assetContainer || !container.state.isVisible) {
|
|
619
692
|
return false;
|
|
620
693
|
}
|
|
621
|
-
|
|
694
|
+
if (updateVisibility) {
|
|
695
|
+
this.#updateVisibilityAttributeInComponents(container.state.name, false);
|
|
696
|
+
}
|
|
622
697
|
container.assetContainer.removeAllFromScene();
|
|
623
698
|
container.state.visible = false;
|
|
624
699
|
return true;
|
|
@@ -628,20 +703,20 @@ export default class BabylonJSController {
|
|
|
628
703
|
* Replaces the asset container in the Babylon.js scene with a new one.
|
|
629
704
|
* @private
|
|
630
705
|
* @param {object} container - The container object containing asset state and metadata.
|
|
631
|
-
* @param {
|
|
632
|
-
* @returns {boolean} True if the container was replaced, false otherwise.
|
|
706
|
+
* @param {AssetContainer} newAssetContainer - The new asset container to add to the scene.
|
|
707
|
+
* @returns {boolean} True if the container was replaced and added, false otherwise.
|
|
633
708
|
*/
|
|
634
709
|
#replaceContainer(container, newAssetContainer) {
|
|
635
710
|
if (container.assetContainer) {
|
|
636
|
-
this.#removeContainer(container);
|
|
711
|
+
this.#removeContainer(container, false);
|
|
637
712
|
container.assetContainer.dispose();
|
|
638
713
|
container.assetContainer = null;
|
|
639
714
|
}
|
|
640
715
|
this.#scene.getEngine().releaseEffects();
|
|
641
716
|
container.assetContainer = newAssetContainer;
|
|
642
|
-
this.#addContainer(container);
|
|
643
|
-
return true;
|
|
717
|
+
return this.#addContainer(container, false);
|
|
644
718
|
}
|
|
719
|
+
|
|
645
720
|
/**
|
|
646
721
|
* Sets the visibility of wall and floor meshes in the model container based on the provided value or environment visibility.
|
|
647
722
|
* @private
|
|
@@ -663,7 +738,6 @@ export default class BabylonJSController {
|
|
|
663
738
|
* @private
|
|
664
739
|
* @returns {void}
|
|
665
740
|
*/
|
|
666
|
-
#stopR;
|
|
667
741
|
#stopRender() {
|
|
668
742
|
this.#engine.stopRenderLoop(this.#renderLoop.bind(this));
|
|
669
743
|
}
|
|
@@ -725,11 +799,11 @@ export default class BabylonJSController {
|
|
|
725
799
|
/**
|
|
726
800
|
* Loads all asset containers (model, environment, materials, etc.) and adds them to the scene.
|
|
727
801
|
* @private
|
|
728
|
-
* @returns {Promise<boolean>} Resolves to
|
|
802
|
+
* @returns {Promise<{success: boolean, error: any}>} Resolves to an object indicating if loading succeeded and any error encountered.
|
|
729
803
|
* @description
|
|
730
804
|
* Waits for all containers to load in parallel, then replaces or adds them to the scene as needed.
|
|
731
805
|
* Applies material and camera options, sets wall/floor visibility, and initializes lights and shadows.
|
|
732
|
-
* Returns
|
|
806
|
+
* Returns an object with success status and error details.
|
|
733
807
|
*/
|
|
734
808
|
async #loadContainers() {
|
|
735
809
|
this.#stopRender();
|
|
@@ -739,10 +813,17 @@ export default class BabylonJSController {
|
|
|
739
813
|
promiseArray.push(this.#loadAssetContainer(container));
|
|
740
814
|
});
|
|
741
815
|
|
|
742
|
-
let
|
|
816
|
+
let detail = {
|
|
817
|
+
success: false,
|
|
818
|
+
error: null,
|
|
819
|
+
};
|
|
743
820
|
|
|
744
821
|
await Promise.allSettled(promiseArray)
|
|
745
822
|
.then((values) => {
|
|
823
|
+
if (this.#babylonJSAnimationController) {
|
|
824
|
+
this.#babylonJSAnimationController.dispose();
|
|
825
|
+
this.#babylonJSAnimationController = null;
|
|
826
|
+
}
|
|
746
827
|
values.forEach((result) => {
|
|
747
828
|
const container = result.value ? result.value[0] : null;
|
|
748
829
|
const assetContainer = result.value ? result.value[1] : null;
|
|
@@ -763,21 +844,137 @@ export default class BabylonJSController {
|
|
|
763
844
|
this.#setOptions_Materials();
|
|
764
845
|
this.#setOptions_Camera();
|
|
765
846
|
this.#setVisibilityOfWallAndFloorInModel();
|
|
766
|
-
success = true;
|
|
847
|
+
detail.success = true;
|
|
767
848
|
})
|
|
768
849
|
.catch((error) => {
|
|
769
850
|
this.loaded = true;
|
|
770
851
|
console.error("PrefViewer: failed to load model", error);
|
|
771
|
-
success = false;
|
|
852
|
+
detail.success = false;
|
|
853
|
+
detail.error = error;
|
|
772
854
|
})
|
|
773
855
|
.finally(async () => {
|
|
774
856
|
this.#setMaxSimultaneousLights();
|
|
775
857
|
this.#initializeShadows();
|
|
858
|
+
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#scene);
|
|
776
859
|
this.#startRender();
|
|
777
|
-
return success;
|
|
778
860
|
});
|
|
861
|
+
return detail;
|
|
862
|
+
}
|
|
779
863
|
|
|
780
|
-
|
|
864
|
+
/**
|
|
865
|
+
* Appends the current date and time to the provided name string in the format: name_YYYY-MM-DD_HH.mm.ss
|
|
866
|
+
* @private
|
|
867
|
+
* @param {string} name - The base name to which the date and time will be appended.
|
|
868
|
+
* @returns {string} The name with the appended date and time.
|
|
869
|
+
*/
|
|
870
|
+
#addDateToName(name) {
|
|
871
|
+
const now = new Date();
|
|
872
|
+
const year = now.getFullYear();
|
|
873
|
+
const month = (now.getMonth() + 1).toString().padStart(2, "0");
|
|
874
|
+
const day = now.getDate().toString().padStart(2, "0");
|
|
875
|
+
const hours = now.getHours().toString().padStart(2, "0");
|
|
876
|
+
const minutes = now.getMinutes().toString().padStart(2, "0");
|
|
877
|
+
const seconds = now.getSeconds().toString().padStart(2, "0");
|
|
878
|
+
name = `${name}_${year}-${month}-${day}_${hours}.${minutes}.${seconds}`;
|
|
879
|
+
return name;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Generates and downloads a ZIP file with the specified folder name and files.
|
|
884
|
+
* @private
|
|
885
|
+
* @param {Object} files - An object where keys are file names and values are file contents.
|
|
886
|
+
* @param {string} [name="files"] - The name for the root folder inside the ZIP.
|
|
887
|
+
* @param {string} [comment=""] - A comment to include in the ZIP file.
|
|
888
|
+
* @param {boolean} [addDateInName=false] - Whether to append the current date/time to the folder name.
|
|
889
|
+
* @returns {void}
|
|
890
|
+
* @description
|
|
891
|
+
* Uses JSZip to create the ZIP and triggers a browser download.
|
|
892
|
+
*/
|
|
893
|
+
#downloadZip(files, name = "files", comment = "", addDateInName = false) {
|
|
894
|
+
name = addDateInName ? this.#addDateToName(name) : name;
|
|
895
|
+
|
|
896
|
+
const JSZip = require("jszip");
|
|
897
|
+
const zip = new JSZip();
|
|
898
|
+
zip.comment = comment;
|
|
899
|
+
|
|
900
|
+
const rootFolder = zip.folder(name);
|
|
901
|
+
Object.keys(files).forEach((key) => {
|
|
902
|
+
rootFolder.file(key, files[key]);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
zip.generateAsync({ type: "blob" }).then((content) => {
|
|
906
|
+
const zipName = `${name}.zip`;
|
|
907
|
+
const a = document.createElement("a");
|
|
908
|
+
const url = window.URL.createObjectURL(content);
|
|
909
|
+
a.href = url;
|
|
910
|
+
a.download = zipName;
|
|
911
|
+
a.click();
|
|
912
|
+
window.URL.revokeObjectURL(url);
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Opens a modal dialog for downloading the 3D scene, model, or environment.
|
|
918
|
+
* @private
|
|
919
|
+
* @returns {void}
|
|
920
|
+
*/
|
|
921
|
+
#openDownloadDialog() {
|
|
922
|
+
this.#getPrefViewerComponent();
|
|
923
|
+
if (!this.#prefViewer) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const header = "Download 3D Scene";
|
|
928
|
+
const content = `
|
|
929
|
+
<form slot="content" id="download-dialog-form" style="display:flex;flex-direction:column;gap:16px;">
|
|
930
|
+
<h4>Content</h4>
|
|
931
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
932
|
+
<label><input type="radio" name="content" value="1"> Model</label>
|
|
933
|
+
<label><input type="radio" name="content" value="2"> Scene</label>
|
|
934
|
+
<label><input type="radio" name="content" value="0" checked> Both</label>
|
|
935
|
+
</div>
|
|
936
|
+
<h4>Format</h4>
|
|
937
|
+
<div style="display:flex;flex-direction:row;gap:16px;margin:0 8px 0 8px;">
|
|
938
|
+
<label><input type="radio" name="format" value="gltf" checked> glTF (ZIP)</label>
|
|
939
|
+
<label><input type="radio" name="format" value="glb"> GLB</label>
|
|
940
|
+
<label><input type="radio" name="format" value="usdz"> USDZ</label>
|
|
941
|
+
</div>
|
|
942
|
+
</form>`;
|
|
943
|
+
const footer = `
|
|
944
|
+
<button type="button" class="primary" id="download-dialog-download">Download</button>
|
|
945
|
+
<button type="button" id="download-dialog-cancel">Cancel</button>`;
|
|
946
|
+
|
|
947
|
+
const dialog = this.#prefViewer.openDialog(header, content, footer);
|
|
948
|
+
|
|
949
|
+
if (!dialog) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Button event handlers
|
|
954
|
+
const form = dialog.querySelector("#download-dialog-form");
|
|
955
|
+
const downloadButton = dialog.querySelector("#download-dialog-download");
|
|
956
|
+
const cancelButton = dialog.querySelector("#download-dialog-cancel");
|
|
957
|
+
|
|
958
|
+
downloadButton.onclick = () => {
|
|
959
|
+
const contentValue = form.content.value;
|
|
960
|
+
const formatValue = form.format.value;
|
|
961
|
+
switch (formatValue) {
|
|
962
|
+
case "glb":
|
|
963
|
+
this.downloadGLB(Number(contentValue));
|
|
964
|
+
break;
|
|
965
|
+
case "gltf":
|
|
966
|
+
this.downloadGLTF(Number(contentValue));
|
|
967
|
+
break;
|
|
968
|
+
case "usdz":
|
|
969
|
+
this.downloadUSDZ(Number(contentValue));
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
this.#prefViewer.closeDialog();
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
cancelButton.onclick = () => {
|
|
976
|
+
this.#prefViewer.closeDialog();
|
|
977
|
+
};
|
|
781
978
|
}
|
|
782
979
|
|
|
783
980
|
/**
|
|
@@ -801,7 +998,7 @@ export default class BabylonJSController {
|
|
|
801
998
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
802
999
|
this.#createCamera();
|
|
803
1000
|
this.#createLights();
|
|
804
|
-
this.#
|
|
1001
|
+
this.#enableInteraction();
|
|
805
1002
|
await this.#createXRExperience();
|
|
806
1003
|
this.#startRender();
|
|
807
1004
|
this.#canvasResizeObserver = new ResizeObserver(() => this.#engine && this.#engine.resize());
|
|
@@ -815,8 +1012,13 @@ export default class BabylonJSController {
|
|
|
815
1012
|
* @returns {void}
|
|
816
1013
|
*/
|
|
817
1014
|
disable() {
|
|
818
|
-
this.#disposeEngine();
|
|
819
1015
|
this.#canvasResizeObserver.disconnect();
|
|
1016
|
+
this.#disableInteraction();
|
|
1017
|
+
if (this.#babylonJSAnimationController) {
|
|
1018
|
+
this.#babylonJSAnimationController.dispose();
|
|
1019
|
+
this.#babylonJSAnimationController = null;
|
|
1020
|
+
}
|
|
1021
|
+
this.#disposeEngine();
|
|
820
1022
|
}
|
|
821
1023
|
|
|
822
1024
|
/**
|
|
@@ -880,50 +1082,105 @@ export default class BabylonJSController {
|
|
|
880
1082
|
}
|
|
881
1083
|
|
|
882
1084
|
/**
|
|
883
|
-
*
|
|
1085
|
+
* Downloads the current scene, model, or environment as a GLB file.
|
|
884
1086
|
* @public
|
|
1087
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
885
1088
|
* @returns {void}
|
|
886
1089
|
*/
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1090
|
+
downloadGLB(content = 0) {
|
|
1091
|
+
let fileName = "";
|
|
1092
|
+
let container = null;
|
|
1093
|
+
switch (content) {
|
|
1094
|
+
case 0:
|
|
1095
|
+
fileName = "full-scene";
|
|
1096
|
+
container = this.#scene;
|
|
1097
|
+
break;
|
|
1098
|
+
case 1:
|
|
1099
|
+
fileName = "model";
|
|
1100
|
+
container = this.#containers.model.assetContainer;
|
|
1101
|
+
break;
|
|
1102
|
+
case 2:
|
|
1103
|
+
fileName = "scene";
|
|
1104
|
+
container = this.#containers.environment.assetContainer;
|
|
1105
|
+
break;
|
|
1106
|
+
default:
|
|
1107
|
+
break;
|
|
1108
|
+
}
|
|
1109
|
+
fileName = this.#addDateToName(fileName);
|
|
1110
|
+
GLTF2Export.GLBAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((glb) => {
|
|
1111
|
+
if (glb) {
|
|
1112
|
+
glb.downloadFiles();
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
890
1115
|
}
|
|
891
1116
|
|
|
892
1117
|
/**
|
|
893
|
-
*
|
|
1118
|
+
* Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
894
1119
|
* @public
|
|
1120
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
895
1121
|
* @returns {void}
|
|
896
1122
|
*/
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1123
|
+
downloadGLTF(content = 0) {
|
|
1124
|
+
let fileName = "";
|
|
1125
|
+
let container = null;
|
|
1126
|
+
let comment = "";
|
|
1127
|
+
switch (content) {
|
|
1128
|
+
case 0:
|
|
1129
|
+
fileName = "full-scene";
|
|
1130
|
+
comment = "Export of the complete scene in glTF format, including the model and the environment.";
|
|
1131
|
+
container = this.#scene;
|
|
1132
|
+
break;
|
|
1133
|
+
case 1:
|
|
1134
|
+
fileName = "model";
|
|
1135
|
+
comment = "Export of model scene in glTF format.";
|
|
1136
|
+
container = this.#containers.model.assetContainer;
|
|
1137
|
+
break;
|
|
1138
|
+
case 2:
|
|
1139
|
+
fileName = "scene";
|
|
1140
|
+
comment = "Export of the environment scene in glTF format.";
|
|
1141
|
+
container = this.#containers.environment.assetContainer;
|
|
1142
|
+
break;
|
|
1143
|
+
default:
|
|
1144
|
+
break;
|
|
1145
|
+
}
|
|
1146
|
+
comment += "\nPrefViewer by Preference, S.L.\npreference.com\n";
|
|
1147
|
+
GLTF2Export.GLTFAsync(container, fileName, { exportWithoutWaitingForScene: true }).then((gltf) => {
|
|
1148
|
+
if (gltf?.files) {
|
|
1149
|
+
this.#downloadZip(gltf.files, fileName, comment, true);
|
|
902
1150
|
}
|
|
903
1151
|
});
|
|
904
1152
|
}
|
|
905
1153
|
|
|
906
1154
|
/**
|
|
907
|
-
*
|
|
1155
|
+
* Downloads the current scene, model, or environment as a USDZ file.
|
|
908
1156
|
* @public
|
|
1157
|
+
* @param {number} content - 0 for full scene, 1 for model, 2 for environment.
|
|
909
1158
|
* @returns {void}
|
|
910
1159
|
*/
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1160
|
+
downloadUSDZ(content = 0) {
|
|
1161
|
+
let fileName = "";
|
|
1162
|
+
let container = null;
|
|
1163
|
+
switch (content) {
|
|
1164
|
+
case 0:
|
|
1165
|
+
fileName = "full-scene";
|
|
1166
|
+
container = this.#scene;
|
|
1167
|
+
break;
|
|
1168
|
+
case 1:
|
|
1169
|
+
fileName = "model";
|
|
1170
|
+
container = this.#containers.model.assetContainer;
|
|
1171
|
+
break;
|
|
1172
|
+
case 2:
|
|
1173
|
+
fileName = "scene";
|
|
1174
|
+
container = this.#containers.environment.assetContainer;
|
|
1175
|
+
break;
|
|
1176
|
+
default:
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
fileName = this.#addDateToName(fileName);
|
|
1180
|
+
USDZExportAsync(container).then((response) => {
|
|
914
1181
|
if (response) {
|
|
915
1182
|
Tools.Download(new Blob([response], { type: "model/vnd.usdz+zip" }), `${fileName}.usdz`);
|
|
916
1183
|
}
|
|
917
1184
|
});
|
|
918
1185
|
}
|
|
919
|
-
|
|
920
|
-
/**
|
|
921
|
-
* Initiates download of the entire scene (model and environment) as a GLB file.
|
|
922
|
-
* @public
|
|
923
|
-
* @returns {void}
|
|
924
|
-
*/
|
|
925
|
-
downloadModelAndSceneGLB() {
|
|
926
|
-
const fileName = "scene";
|
|
927
|
-
GLTF2Export.GLBAsync(this.#scene, fileName, { exportWithoutWaitingForScene: true }).then((glb) => glb.downloadFiles());
|
|
928
|
-
}
|
|
929
1186
|
}
|