@preference-sl/pref-viewer 2.11.0-beta.9 → 2.11.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 +61 -50
- package/src/babylonjs-animation-controller.js +58 -76
- package/src/babylonjs-animation-opening-menu.js +189 -154
- package/src/babylonjs-animation-opening.js +67 -36
- package/src/babylonjs-controller.js +189 -60
- package/src/file-storage.js +11 -2
- package/src/index.js +18 -912
- package/src/panzoom-controller.js +92 -55
- package/src/pref-viewer-2d.js +15 -3
- package/src/pref-viewer-3d-data.js +3 -2
- package/src/pref-viewer-3d.js +11 -4
- package/src/pref-viewer-dialog.js +16 -9
- 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
- package/src/css/pref-viewer-2d.css +0 -39
- package/src/css/pref-viewer-3d.css +0 -28
- package/src/css/pref-viewer-dialog.css +0 -105
- package/src/css/pref-viewer.css +0 -11
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { OpeningAnimationMenu } from "./babylonjs-animation-opening-menu.js";
|
|
1
|
+
import OpeningAnimationMenu from "./babylonjs-animation-opening-menu.js";
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* OpeningAnimation - Manages open/close animations for a model part (e.g., a door) in a Babylon.js scene.
|
|
@@ -13,13 +12,14 @@ import { OpeningAnimationMenu } from "./babylonjs-animation-opening-menu.js";
|
|
|
13
12
|
* - Manages the animation control menu (OpeningAnimationMenu) and its callbacks.
|
|
14
13
|
*
|
|
15
14
|
* Public Methods:
|
|
15
|
+
* - dispose(): Disposes the OpeningAnimation instance and releases all associated resources.
|
|
16
16
|
* - isAnimationForNode(node): Checks if the animation affects the given node.
|
|
17
17
|
* - playOpen(): Starts the opening animation.
|
|
18
18
|
* - playClose(): Starts the closing animation.
|
|
19
19
|
* - pause(): Pauses the current animation.
|
|
20
20
|
* - goToOpened(): Moves animation to the fully opened state.
|
|
21
21
|
* - goToClosed(): Moves animation to the fully closed state.
|
|
22
|
-
* - showControls(
|
|
22
|
+
* - showControls(canvas): Displays the animation control menu.
|
|
23
23
|
* - hideControls(): Hides the animation control menu.
|
|
24
24
|
* - isControlsVisible(): Returns true if the control menu is visible for this animation.
|
|
25
25
|
*
|
|
@@ -39,16 +39,8 @@ import { OpeningAnimationMenu } from "./babylonjs-animation-opening-menu.js";
|
|
|
39
39
|
* - #checkProgress(progress): Applies threshold logic to progress.
|
|
40
40
|
* - #updateControlsSlider(): Updates the slider in the control menu.
|
|
41
41
|
* - #updateControls(): Updates all controls in the menu.
|
|
42
|
-
*
|
|
43
|
-
* Usage Example:
|
|
44
|
-
* const anim = new OpeningAnimation("door", openGroup, closeGroup);
|
|
45
|
-
* anim.playOpen();
|
|
46
|
-
* anim.pause();
|
|
47
|
-
* anim.goToClosed();
|
|
48
|
-
* anim.showControls(adt);
|
|
49
|
-
* anim.hideControls();
|
|
50
42
|
*/
|
|
51
|
-
export class OpeningAnimation {
|
|
43
|
+
export default class OpeningAnimation {
|
|
52
44
|
static states = {
|
|
53
45
|
paused: 0,
|
|
54
46
|
closed: 1,
|
|
@@ -57,21 +49,26 @@ export class OpeningAnimation {
|
|
|
57
49
|
closing: 4,
|
|
58
50
|
};
|
|
59
51
|
|
|
52
|
+
name = "";
|
|
60
53
|
#openAnimation = null;
|
|
61
54
|
#closeAnimation = null;
|
|
62
|
-
|
|
63
55
|
#nodes = [];
|
|
56
|
+
#menu = null;
|
|
57
|
+
|
|
64
58
|
#state = OpeningAnimation.states.closed;
|
|
65
59
|
#lastPausedFrame = 0;
|
|
66
60
|
#startFrame = 0;
|
|
67
61
|
#endFrame = 0;
|
|
68
62
|
#speedRatio = 1.0;
|
|
69
63
|
#loop = false;
|
|
70
|
-
|
|
71
|
-
#advancedDynamicTexture = null;
|
|
72
|
-
#menu = null;
|
|
73
64
|
#progressThreshold = 0.025;
|
|
74
65
|
|
|
66
|
+
#handlers = {
|
|
67
|
+
onOpened: null,
|
|
68
|
+
onClosed: null,
|
|
69
|
+
updateControlsSlider: null,
|
|
70
|
+
};
|
|
71
|
+
|
|
75
72
|
/**
|
|
76
73
|
* Creates a new OpeningAnimation instance for managing open/close animations of a model part.
|
|
77
74
|
* @param {string} name - The identifier for this animation (e.g., door name).
|
|
@@ -95,8 +92,15 @@ export class OpeningAnimation {
|
|
|
95
92
|
this.#speedRatio = this.#openAnimation.speedRatio || 1.0;
|
|
96
93
|
|
|
97
94
|
this.#getNodesFromAnimationGroups();
|
|
98
|
-
this.#
|
|
99
|
-
this.#
|
|
95
|
+
this.#bindHandlers();
|
|
96
|
+
this.#openAnimation.onAnimationGroupEndObservable.add(this.#handlers.onOpened);
|
|
97
|
+
this.#closeAnimation.onAnimationGroupEndObservable.add(this.#handlers.onClosed);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#bindHandlers() {
|
|
101
|
+
this.#handlers.onOpened = this.#onOpened.bind(this);
|
|
102
|
+
this.#handlers.onClosed = this.#onClosed.bind(this);
|
|
103
|
+
this.#handlers.updateControlsSlider = this.#updateControlsSlider.bind(this);
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/**
|
|
@@ -138,13 +142,18 @@ export class OpeningAnimation {
|
|
|
138
142
|
#goToOpened(useLoop = false) {
|
|
139
143
|
this.#lastPausedFrame = this.#endFrame;
|
|
140
144
|
|
|
141
|
-
if (this.#openAnimation._isStarted && !this.#openAnimation._isPaused)
|
|
145
|
+
if (this.#openAnimation._isStarted && !this.#openAnimation._isPaused){
|
|
142
146
|
this.#openAnimation.pause();
|
|
143
147
|
}
|
|
144
|
-
|
|
148
|
+
this.#openAnimation.goToFrame(this.#endFrame);
|
|
145
149
|
|
|
146
|
-
this.#closeAnimation.
|
|
147
|
-
|
|
150
|
+
if (this.#closeAnimation._isStarted && this.#closeAnimation._isPaused) {
|
|
151
|
+
this.#closeAnimation.goToFrame(this.#startFrame);
|
|
152
|
+
} else {
|
|
153
|
+
this.#closeAnimation.start();
|
|
154
|
+
this.#closeAnimation.pause();
|
|
155
|
+
this.#closeAnimation.goToFrame(this.#startFrame);
|
|
156
|
+
}
|
|
148
157
|
|
|
149
158
|
this.#state = OpeningAnimation.states.opened;
|
|
150
159
|
this.#updateControls();
|
|
@@ -167,8 +176,13 @@ export class OpeningAnimation {
|
|
|
167
176
|
}
|
|
168
177
|
this.#closeAnimation.goToFrame(this.#endFrame - this.#startFrame);
|
|
169
178
|
|
|
170
|
-
this.#openAnimation.
|
|
171
|
-
|
|
179
|
+
if (this.#openAnimation._isStarted && this.#openAnimation._isPaused) {
|
|
180
|
+
this.#openAnimation.goToFrame(this.#startFrame);
|
|
181
|
+
} else {
|
|
182
|
+
this.#openAnimation.start();
|
|
183
|
+
this.#openAnimation.pause();
|
|
184
|
+
this.#openAnimation.goToFrame(this.#startFrame);
|
|
185
|
+
}
|
|
172
186
|
|
|
173
187
|
this.#state = OpeningAnimation.states.closed;
|
|
174
188
|
this.#updateControls();
|
|
@@ -303,6 +317,25 @@ export class OpeningAnimation {
|
|
|
303
317
|
* ---------------------------
|
|
304
318
|
*/
|
|
305
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Disposes the OpeningAnimation instance and releases all associated resources.
|
|
322
|
+
* @public
|
|
323
|
+
* @returns {void}
|
|
324
|
+
*/
|
|
325
|
+
dispose() {
|
|
326
|
+
this.hideControls();
|
|
327
|
+
if (this.#openAnimation) {
|
|
328
|
+
this.#openAnimation.onAnimationGroupEndObservable.removeCallback(this.#handlers.onOpened);
|
|
329
|
+
this.#openAnimation = null;
|
|
330
|
+
}
|
|
331
|
+
if (this.#closeAnimation) {
|
|
332
|
+
this.#closeAnimation.onAnimationGroupEndObservable.removeCallback(this.#handlers.onClosed);
|
|
333
|
+
this.#closeAnimation = null;
|
|
334
|
+
}
|
|
335
|
+
this.#nodes = [];
|
|
336
|
+
this.name = "";
|
|
337
|
+
}
|
|
338
|
+
|
|
306
339
|
/**
|
|
307
340
|
* Checks if the animation affects the given node.
|
|
308
341
|
* @param {string} node - Node identifier.
|
|
@@ -320,6 +353,7 @@ export class OpeningAnimation {
|
|
|
320
353
|
if (this.#state === OpeningAnimation.states.opening || this.#state === OpeningAnimation.states.opened) {
|
|
321
354
|
return;
|
|
322
355
|
}
|
|
356
|
+
|
|
323
357
|
if (this.#state === OpeningAnimation.states.closing) {
|
|
324
358
|
this.#lastPausedFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
|
|
325
359
|
this.#closeAnimation.pause();
|
|
@@ -344,6 +378,7 @@ export class OpeningAnimation {
|
|
|
344
378
|
if (this.#state === OpeningAnimation.states.closing || this.#state === OpeningAnimation.states.closed) {
|
|
345
379
|
return;
|
|
346
380
|
}
|
|
381
|
+
|
|
347
382
|
if (this.#state === OpeningAnimation.states.opening) {
|
|
348
383
|
this.#lastPausedFrame = this.#openAnimation.getCurrentFrame();
|
|
349
384
|
this.#openAnimation.pause();
|
|
@@ -397,11 +432,9 @@ export class OpeningAnimation {
|
|
|
397
432
|
* Displays the animation control menu and sets up callbacks.
|
|
398
433
|
* Synchronizes slider and button states with animation.
|
|
399
434
|
* @public
|
|
400
|
-
* @param {
|
|
435
|
+
* @param {HTMLCanvasElement} canvas - The canvas element for rendering.
|
|
401
436
|
*/
|
|
402
|
-
showControls(
|
|
403
|
-
this.#advancedDynamicTexture = advancedDynamicTexture;
|
|
404
|
-
this.#advancedDynamicTexture.metadata = { animationName: this.name };
|
|
437
|
+
showControls(canvas) {
|
|
405
438
|
const controlCallbacks = {
|
|
406
439
|
onGoToOpened: () => {
|
|
407
440
|
if (this.#state === OpeningAnimation.states.opened) {
|
|
@@ -449,10 +482,10 @@ export class OpeningAnimation {
|
|
|
449
482
|
this.#menu.animationLoop = this.#loop;
|
|
450
483
|
},
|
|
451
484
|
};
|
|
452
|
-
this.#menu = new OpeningAnimationMenu(this
|
|
485
|
+
this.#menu = new OpeningAnimationMenu(this.name, canvas, this.#state, this.#getProgress(), this.#loop, controlCallbacks);
|
|
453
486
|
|
|
454
487
|
// Attach to Babylon.js scene render loop for real-time updates
|
|
455
|
-
this.#openAnimation._scene.onBeforeRenderObservable.add(this.#updateControlsSlider
|
|
488
|
+
this.#openAnimation._scene.onBeforeRenderObservable.add(this.#handlers.updateControlsSlider);
|
|
456
489
|
}
|
|
457
490
|
|
|
458
491
|
/**
|
|
@@ -463,10 +496,9 @@ export class OpeningAnimation {
|
|
|
463
496
|
if (!this.isControlsVisible()) {
|
|
464
497
|
return;
|
|
465
498
|
}
|
|
466
|
-
|
|
467
|
-
this.#
|
|
468
|
-
this.#
|
|
469
|
-
this.#advancedDynamicTexture = null;
|
|
499
|
+
this.#openAnimation?._scene?.onBeforeRenderObservable.removeCallback(this.#handlers.updateControlsSlider);
|
|
500
|
+
this.#menu.dispose();
|
|
501
|
+
this.#menu = null;
|
|
470
502
|
}
|
|
471
503
|
|
|
472
504
|
/**
|
|
@@ -475,7 +507,7 @@ export class OpeningAnimation {
|
|
|
475
507
|
* @returns {boolean} True if controls are visible for this animation; otherwise, false.
|
|
476
508
|
*/
|
|
477
509
|
isControlsVisible() {
|
|
478
|
-
return !!(this.#
|
|
510
|
+
return !!(this.#menu?.isVisible);
|
|
479
511
|
}
|
|
480
512
|
|
|
481
513
|
/**
|
|
@@ -489,7 +521,6 @@ export class OpeningAnimation {
|
|
|
489
521
|
* @public
|
|
490
522
|
* @returns {number}
|
|
491
523
|
*/
|
|
492
|
-
|
|
493
524
|
get state() {
|
|
494
525
|
return this.#state;
|
|
495
526
|
}
|
|
@@ -1,24 +1,31 @@
|
|
|
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";
|
|
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";
|
|
6
7
|
|
|
7
8
|
import GLTFResolver from "./gltf-resolver.js";
|
|
8
9
|
import { MaterialData } from "./pref-viewer-3d-data.js";
|
|
9
10
|
import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
|
-
* 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.
|
|
13
14
|
*
|
|
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.
|
|
18
|
+
*
|
|
19
|
+
* Key Responsibilities:
|
|
15
20
|
* - Initializes and manages the Babylon.js engine, scene, camera, lights, and asset containers.
|
|
16
21
|
* - Handles loading, replacing, and disposing of 3D models, environments, and materials.
|
|
17
22
|
* - Configures advanced rendering features such as Draco mesh compression, shadows, and image-based lighting (IBL).
|
|
18
23
|
* - Integrates Babylon.js WebXR experience for augmented reality (AR) support.
|
|
19
|
-
* - Provides methods for downloading models and scenes in GLB,
|
|
24
|
+
* - Provides methods for downloading models and scenes in GLB, glTF (ZIP), and USDZ formats.
|
|
20
25
|
* - Manages camera and material options, container visibility, and user interactions.
|
|
21
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.
|
|
22
29
|
*
|
|
23
30
|
* Usage:
|
|
24
31
|
* - Instantiate: const controller = new BabylonJSController(canvas, containers, options);
|
|
@@ -26,7 +33,7 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
26
33
|
* - Load assets: await controller.load();
|
|
27
34
|
* - Set camera/material options: controller.setCameraOptions(), controller.setMaterialOptions();
|
|
28
35
|
* - Control visibility: controller.setContainerVisibility(name, show);
|
|
29
|
-
* - Download assets: controller.
|
|
36
|
+
* - Download assets: controller.downloadGLB(), controller.downloadGLTF(), controller.downloadUSDZ();
|
|
30
37
|
* - Disable rendering: controller.disable();
|
|
31
38
|
*
|
|
32
39
|
* Public Methods:
|
|
@@ -40,30 +47,46 @@ import BabylonJSAnimationController from "./babylonjs-animation-controller.js";
|
|
|
40
47
|
* - downloadGLTF(content): Downloads the current scene, model, or environment as a glTF ZIP file.
|
|
41
48
|
* - downloadUSDZ(content): Downloads the current scene, model, or environment as a USDZ file.
|
|
42
49
|
*
|
|
43
|
-
* Private Methods:
|
|
50
|
+
* Private Methods (using ECMAScript private fields):
|
|
51
|
+
* - #bindHandlers(): Pre-binds reusable event handlers to preserve stable references.
|
|
44
52
|
* - #configureDracoCompression(): Sets up Draco mesh compression.
|
|
45
53
|
* - #renderLoop(): Babylon.js render loop callback.
|
|
46
54
|
* - #addStylesToARButton(): Styles AR button.
|
|
47
55
|
* - #createXRExperience(): Initializes WebXR AR experience.
|
|
48
|
-
* - #createCamera()
|
|
49
|
-
* - #
|
|
50
|
-
* - #
|
|
51
|
-
* - #
|
|
52
|
-
* - #
|
|
53
|
-
* - #
|
|
54
|
-
* - #
|
|
55
|
-
* - #
|
|
56
|
-
* - #
|
|
57
|
-
* - #
|
|
58
|
-
* - #
|
|
59
|
-
* - #
|
|
60
|
-
* - #
|
|
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.
|
|
61
89
|
* - #openDownloadDialog(): Opens the modal download dialog.
|
|
62
|
-
*
|
|
63
|
-
* Notes:
|
|
64
|
-
* - Designed for integration with PrefViewer and GLTFResolver.
|
|
65
|
-
* - Supports advanced Babylon.js features for product visualization and configurators.
|
|
66
|
-
* - All resource management and rendering operations are performed asynchronously for performance.
|
|
67
90
|
*/
|
|
68
91
|
export default class BabylonJSController {
|
|
69
92
|
// Canvas HTML element
|
|
@@ -90,6 +113,12 @@ export default class BabylonJSController {
|
|
|
90
113
|
#gltfResolver = null; // GLTFResolver instance
|
|
91
114
|
#babylonJSAnimationController = null; // AnimationController instance
|
|
92
115
|
|
|
116
|
+
#handlers = {
|
|
117
|
+
onKeyUp: null,
|
|
118
|
+
onPointerObservable: null,
|
|
119
|
+
renderLoop: null,
|
|
120
|
+
};
|
|
121
|
+
|
|
93
122
|
/**
|
|
94
123
|
* Constructs a new BabylonJSController instance.
|
|
95
124
|
* Initializes the canvas, asset containers, and options for the Babylon.js scene.
|
|
@@ -112,6 +141,18 @@ export default class BabylonJSController {
|
|
|
112
141
|
};
|
|
113
142
|
});
|
|
114
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);
|
|
115
156
|
}
|
|
116
157
|
|
|
117
158
|
/**
|
|
@@ -401,13 +442,34 @@ export default class BabylonJSController {
|
|
|
401
442
|
}
|
|
402
443
|
|
|
403
444
|
/**
|
|
404
|
-
*
|
|
445
|
+
* Handles pointer events observed on the Babylon.js scene.
|
|
446
|
+
* @private
|
|
447
|
+
* @param {PointerInfo} info - The pointer event information from Babylon.js.
|
|
448
|
+
* @returns {void}
|
|
449
|
+
*/
|
|
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.
|
|
405
463
|
* @private
|
|
406
464
|
* @returns {void}
|
|
407
465
|
*/
|
|
408
466
|
#enableInteraction() {
|
|
409
|
-
this.#canvas
|
|
410
|
-
|
|
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
|
+
}
|
|
411
473
|
}
|
|
412
474
|
|
|
413
475
|
/**
|
|
@@ -416,8 +478,52 @@ export default class BabylonJSController {
|
|
|
416
478
|
* @returns {void}
|
|
417
479
|
*/
|
|
418
480
|
#disableInteraction() {
|
|
419
|
-
this.#canvas
|
|
420
|
-
|
|
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
|
+
}
|
|
421
527
|
}
|
|
422
528
|
|
|
423
529
|
/**
|
|
@@ -433,21 +539,39 @@ export default class BabylonJSController {
|
|
|
433
539
|
this.#engine = this.#scene = this.#camera = null;
|
|
434
540
|
this.#hemiLight = this.#dirLight = this.#cameraLight = null;
|
|
435
541
|
this.#shadowGen = null;
|
|
436
|
-
|
|
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
|
+
}
|
|
437
561
|
}
|
|
438
562
|
|
|
439
563
|
/**
|
|
440
564
|
* Handles mouse wheel events on the Babylon.js canvas for zooming the camera.
|
|
441
565
|
* @private
|
|
442
566
|
* @param {WheelEvent} event - The mouse wheel event.
|
|
443
|
-
* @
|
|
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.
|
|
444
569
|
*/
|
|
445
|
-
#onMouseWheel(event) {
|
|
446
|
-
if (!this.#scene
|
|
570
|
+
#onMouseWheel(event, pickInfo) {
|
|
571
|
+
if (!this.#scene?.activeCamera) {
|
|
447
572
|
return false;
|
|
448
573
|
}
|
|
449
|
-
//
|
|
450
|
-
//this.#camera.target = pick.hit ? pick.pickedPoint.clone() : this.#camera.target;
|
|
574
|
+
//this.#scene.activeCamera.target = pickInfo.hit ? pickInfo.pickedPoint.clone() : this.#scene.activeCamera.target;
|
|
451
575
|
if (!this.#scene.activeCamera.metadata?.locked) {
|
|
452
576
|
this.#scene.activeCamera.inertialRadiusOffset -= event.deltaY * this.#scene.activeCamera.wheelPrecision * 0.001;
|
|
453
577
|
}
|
|
@@ -455,24 +579,35 @@ export default class BabylonJSController {
|
|
|
455
579
|
}
|
|
456
580
|
|
|
457
581
|
/**
|
|
458
|
-
* Handles
|
|
582
|
+
* Handles pointer up events on the Babylon.js scene.
|
|
459
583
|
* @private
|
|
460
|
-
* @param {
|
|
584
|
+
* @param {PointerEvent} event - The pointer up event.
|
|
585
|
+
* @param {PickInfo} pickInfo - The result of the scene pick operation.
|
|
461
586
|
* @returns {void}
|
|
462
587
|
*/
|
|
463
|
-
#
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
break;
|
|
470
|
-
default:
|
|
471
|
-
break;
|
|
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);
|
|
472
594
|
}
|
|
473
595
|
}
|
|
474
596
|
}
|
|
475
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
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
476
611
|
/**
|
|
477
612
|
* Applies material options from the configuration to the relevant meshes.
|
|
478
613
|
* @private
|
|
@@ -739,7 +874,7 @@ export default class BabylonJSController {
|
|
|
739
874
|
* @returns {void}
|
|
740
875
|
*/
|
|
741
876
|
#stopRender() {
|
|
742
|
-
this.#engine.stopRenderLoop(this.#renderLoop
|
|
877
|
+
this.#engine.stopRenderLoop(this.#handlers.renderLoop);
|
|
743
878
|
}
|
|
744
879
|
/**
|
|
745
880
|
* Starts the Babylon.js render loop for the current scene.
|
|
@@ -749,7 +884,7 @@ export default class BabylonJSController {
|
|
|
749
884
|
*/
|
|
750
885
|
async #startRender() {
|
|
751
886
|
await this.#scene.whenReadyAsync();
|
|
752
|
-
this.#engine.runRenderLoop(this.#renderLoop
|
|
887
|
+
this.#engine.runRenderLoop(this.#handlers.renderLoop);
|
|
753
888
|
}
|
|
754
889
|
|
|
755
890
|
/**
|
|
@@ -820,10 +955,7 @@ export default class BabylonJSController {
|
|
|
820
955
|
|
|
821
956
|
await Promise.allSettled(promiseArray)
|
|
822
957
|
.then((values) => {
|
|
823
|
-
|
|
824
|
-
this.#babylonJSAnimationController.dispose();
|
|
825
|
-
this.#babylonJSAnimationController = null;
|
|
826
|
-
}
|
|
958
|
+
this.#disposeAnimationController();
|
|
827
959
|
values.forEach((result) => {
|
|
828
960
|
const container = result.value ? result.value[0] : null;
|
|
829
961
|
const assetContainer = result.value ? result.value[1] : null;
|
|
@@ -893,7 +1025,6 @@ export default class BabylonJSController {
|
|
|
893
1025
|
#downloadZip(files, name = "files", comment = "", addDateInName = false) {
|
|
894
1026
|
name = addDateInName ? this.#addDateToName(name) : name;
|
|
895
1027
|
|
|
896
|
-
const JSZip = require("jszip");
|
|
897
1028
|
const zip = new JSZip();
|
|
898
1029
|
zip.comment = comment;
|
|
899
1030
|
|
|
@@ -992,7 +1123,7 @@ export default class BabylonJSController {
|
|
|
992
1123
|
*/
|
|
993
1124
|
async enable() {
|
|
994
1125
|
this.#configureDracoCompression();
|
|
995
|
-
this.#engine = new Engine(this.#canvas, true, { alpha: true });
|
|
1126
|
+
this.#engine = new Engine(this.#canvas, true, { alpha: true, stencil: true, preserveDrawingBuffer: false });
|
|
996
1127
|
this.#engine.disableUniformBuffers = true;
|
|
997
1128
|
this.#scene = new Scene(this.#engine);
|
|
998
1129
|
this.#scene.clearColor = new Color4(1, 1, 1, 1);
|
|
@@ -1014,10 +1145,8 @@ export default class BabylonJSController {
|
|
|
1014
1145
|
disable() {
|
|
1015
1146
|
this.#canvasResizeObserver.disconnect();
|
|
1016
1147
|
this.#disableInteraction();
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
this.#babylonJSAnimationController = null;
|
|
1020
|
-
}
|
|
1148
|
+
this.#disposeAnimationController();
|
|
1149
|
+
this.#disposeXRExperience();
|
|
1021
1150
|
this.#disposeEngine();
|
|
1022
1151
|
}
|
|
1023
1152
|
|
package/src/file-storage.js
CHANGED
|
@@ -174,7 +174,12 @@ export class FileStorage {
|
|
|
174
174
|
xhr.onload = () => {
|
|
175
175
|
if (xhr.status === 200) {
|
|
176
176
|
const blob = xhr.response;
|
|
177
|
-
const
|
|
177
|
+
const lastModified = xhr.getResponseHeader("Last-Modified");
|
|
178
|
+
let timeStamp = null;
|
|
179
|
+
if (lastModified) {
|
|
180
|
+
const parsed = new Date(lastModified);
|
|
181
|
+
timeStamp = Number.isNaN(parsed.valueOf()) ? null : parsed.toISOString();
|
|
182
|
+
}
|
|
178
183
|
file = { blob: blob, timeStamp: timeStamp };
|
|
179
184
|
resolve(file);
|
|
180
185
|
} else {
|
|
@@ -204,7 +209,11 @@ export class FileStorage {
|
|
|
204
209
|
xhr.responseType = "blob";
|
|
205
210
|
xhr.onload = () => {
|
|
206
211
|
if (xhr.status === 200) {
|
|
207
|
-
|
|
212
|
+
const lastModified = xhr.getResponseHeader("Last-Modified");
|
|
213
|
+
if (lastModified) {
|
|
214
|
+
const parsed = new Date(lastModified);
|
|
215
|
+
timeStamp = Number.isNaN(parsed.valueOf()) ? null : parsed.toISOString();
|
|
216
|
+
}
|
|
208
217
|
resolve(timeStamp);
|
|
209
218
|
} else {
|
|
210
219
|
resolve(timeStamp);
|