@preference-sl/pref-viewer 2.11.0-beta.2 → 2.11.0-beta.4
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 +6 -6
- package/src/babylonjs-animation-controller.js +195 -0
- package/src/babylonjs-animation-opening-menu.js +360 -0
- package/src/babylonjs-animation-opening.js +496 -0
- package/src/babylonjs-controller.js +19 -14
- package/src/index.js +6 -4
- package/src/pref-viewer-2d.js +10 -9
- package/src/pref-viewer-3d.js +10 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@preference-sl/pref-viewer",
|
|
3
|
-
"version": "2.11.0-beta.
|
|
3
|
+
"version": "2.11.0-beta.4",
|
|
4
4
|
"description": "Web Component to preview GLTF models with Babylon.js",
|
|
5
5
|
"author": "Alex Moreno Palacio <amoreno@preference.es>",
|
|
6
6
|
"scripts": {
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"sideEffects": false,
|
|
31
31
|
"files": [
|
|
32
32
|
"src",
|
|
33
|
-
"src/models",
|
|
34
33
|
"index.d.ts"
|
|
35
34
|
],
|
|
36
35
|
"dependencies": {
|
|
37
|
-
"@babylonjs/core": "^8.
|
|
38
|
-
"@babylonjs/
|
|
39
|
-
"@babylonjs/
|
|
36
|
+
"@babylonjs/core": "^8.36.1",
|
|
37
|
+
"@babylonjs/gui": "^8.36.1",
|
|
38
|
+
"@babylonjs/loaders": "^8.36.1",
|
|
39
|
+
"@babylonjs/serializers": "^8.36.1",
|
|
40
40
|
"@panzoom/panzoom": "^4.6.0",
|
|
41
|
-
"babylonjs-gltf2interface": "^8.
|
|
41
|
+
"babylonjs-gltf2interface": "^8.36.1",
|
|
42
42
|
"idb": "^8.0.3",
|
|
43
43
|
"is-svg": "^6.1.0"
|
|
44
44
|
},
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Color3, HighlightLayer, Mesh, PickingInfo, PointerEventTypes, Scene } from "@babylonjs/core";
|
|
2
|
+
import { AdvancedDynamicTexture } from "@babylonjs/gui";
|
|
3
|
+
import { OpeningAnimation } from "./babylonjs-animation-opening.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* BabylonJSAnimationController - Manages animation playback and interactive highlighting for model containers in Babylon.js scenes.
|
|
7
|
+
*
|
|
8
|
+
* Responsibilities:
|
|
9
|
+
* - Detects and groups opening/closing animations in the scene.
|
|
10
|
+
* - Tracks animated transformation nodes and their relationships to meshes.
|
|
11
|
+
* - Highlights animated nodes and their child meshes when hovered.
|
|
12
|
+
* - Displays and disposes the animation control menu (GUI) for animated nodes.
|
|
13
|
+
* - Provides API for interaction and animation control.
|
|
14
|
+
*
|
|
15
|
+
* @class
|
|
16
|
+
*/
|
|
17
|
+
export default class BabylonJSAnimationController {
|
|
18
|
+
#scene = null;
|
|
19
|
+
#animatedNodes = [];
|
|
20
|
+
#highlightLayer = null;
|
|
21
|
+
#highlightColor = new Color3(0, 1, 0); // Color para resaltar los elementos animados (Verde)
|
|
22
|
+
#advancedDynamicTexture = null;
|
|
23
|
+
#openingAnimations = [];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a new BabylonJSAnimationController for a Babylon.js scene.
|
|
27
|
+
* @param {Scene} scene - The Babylon.js scene instance.
|
|
28
|
+
*/
|
|
29
|
+
constructor(scene) {
|
|
30
|
+
this.#scene = scene;
|
|
31
|
+
this.#initializeAnimations();
|
|
32
|
+
this.#setupPointerObservers();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Detects and stores animatable objects and animated nodes in the scene.
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
#initializeAnimations() {
|
|
40
|
+
if (!this.#scene.animationGroups.length) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.#getAnimatedNodes();
|
|
44
|
+
this.#getOpeneingAnimations();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Collects the IDs of all nodes targeted by any animation group in the scene.
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
#getAnimatedNodes() {
|
|
52
|
+
this.#scene.animationGroups.forEach((animationGroup) => {
|
|
53
|
+
if (!animationGroup._targetedAnimations.length) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
animationGroup._targetedAnimations.forEach((targetedAnimation) => {
|
|
57
|
+
if (!this.#animatedNodes.includes(targetedAnimation.target.id)) {
|
|
58
|
+
this.#animatedNodes.push(targetedAnimation.target.id);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Groups opening and closing animations by node name and creates OpeningAnimation instances.
|
|
66
|
+
* @private
|
|
67
|
+
* @description
|
|
68
|
+
* Uses animation group names with the pattern "animation_open_<name>" and "animation_close_<name>".
|
|
69
|
+
*/
|
|
70
|
+
#getOpeneingAnimations() {
|
|
71
|
+
const openings = {};
|
|
72
|
+
this.#scene.animationGroups.forEach((animationGroup) => {
|
|
73
|
+
const match = animationGroup.name.match(/^animation_(open|close)_(.+)$/);
|
|
74
|
+
if (!match) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const [, type, openingName] = match;
|
|
78
|
+
if (!openings[openingName]) {
|
|
79
|
+
openings[openingName] = { name: openingName, animationOpen: null, animationClose: null };
|
|
80
|
+
}
|
|
81
|
+
if (type === "open") {
|
|
82
|
+
openings[openingName].animationOpen = animationGroup;
|
|
83
|
+
} else if (type === "close") {
|
|
84
|
+
openings[openingName].animationClose = animationGroup;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
Object.values(openings).forEach((opening) => {
|
|
89
|
+
this.#openingAnimations.push(new OpeningAnimation(opening.name, opening.animationOpen, opening.animationClose));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Finds the OpeningAnimation instance associated with a given node ID.
|
|
95
|
+
* @private
|
|
96
|
+
* @param {string} nodeId - The node identifier.
|
|
97
|
+
* @returns {OpeningAnimation|null} The matching OpeningAnimation instance or null.
|
|
98
|
+
*/
|
|
99
|
+
#getOpeningAnimationByNode(nodeId) {
|
|
100
|
+
return this.#openingAnimations.find((openingAnimation) => openingAnimation.isAnimationForNode(nodeId));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Determines if a mesh belongs to a node targeted by an animation.
|
|
105
|
+
* @private
|
|
106
|
+
* @param {Mesh} mesh - The mesh to check.
|
|
107
|
+
* @returns {string|false} The animated node ID if found, otherwise false.
|
|
108
|
+
*/
|
|
109
|
+
#getNodeAnimatedByMesh = function (mesh) {
|
|
110
|
+
let nodeId = false;
|
|
111
|
+
let node = mesh;
|
|
112
|
+
while (node.parent !== null && !nodeId) {
|
|
113
|
+
node = node.parent;
|
|
114
|
+
if (this.#animatedNodes.includes(node.id)) {
|
|
115
|
+
nodeId = node.id;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return nodeId;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Highlights meshes that are children of an animated node when hovered.
|
|
123
|
+
* @private
|
|
124
|
+
* @param {PickingInfo} pickingInfo - Raycast info from pointer position.
|
|
125
|
+
*/
|
|
126
|
+
#hightlightMeshesForAnimation(pickingInfo) {
|
|
127
|
+
if (!this.#highlightLayer) {
|
|
128
|
+
this.#highlightLayer = new HighlightLayer("hl_animations", this.#scene);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.#highlightLayer.removeAllMeshes();
|
|
132
|
+
if (!pickingInfo?.hit && !pickingInfo?.pickedMesh) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nodeId = this.#getNodeAnimatedByMesh(pickingInfo.pickedMesh);
|
|
137
|
+
if (!nodeId) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const transformNode = this.#scene.getTransformNodeByID(nodeId);
|
|
142
|
+
const nodeMeshes = transformNode.getChildMeshes();
|
|
143
|
+
if (nodeMeshes.length) {
|
|
144
|
+
nodeMeshes.forEach((mesh) => this.#highlightLayer.addMesh(mesh, this.#highlightColor));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Sets up pointer observers to highlight animated nodes on hover and show the animation menu on click.
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
#setupPointerObservers() {
|
|
153
|
+
this.#scene.onPointerObservable.add((pointerInfo) => {
|
|
154
|
+
if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
|
|
155
|
+
const pickingInfo = this.#scene.pick(pointerInfo.event.clientX, pointerInfo.event.clientY);
|
|
156
|
+
this.#hightlightMeshesForAnimation(pickingInfo);
|
|
157
|
+
}
|
|
158
|
+
if (pointerInfo.type === PointerEventTypes.POINTERUP) {
|
|
159
|
+
// Remove any previously created Babylon GUI
|
|
160
|
+
if (this.#advancedDynamicTexture) {
|
|
161
|
+
this.#advancedDynamicTexture.dispose();
|
|
162
|
+
this.#advancedDynamicTexture = null;
|
|
163
|
+
}
|
|
164
|
+
const pickingInfo = this.#scene.pick(pointerInfo.event.clientX, pointerInfo.event.clientY);
|
|
165
|
+
this.#showMenu(pickingInfo);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Displays the animation control menu for the animated node under the pointer.
|
|
172
|
+
* @private
|
|
173
|
+
* @param {PickingInfo} pickingInfo - Raycast info from pointer position.
|
|
174
|
+
* @description
|
|
175
|
+
* Creates the GUI if needed and invokes OpeningAnimation.showControls.
|
|
176
|
+
*/
|
|
177
|
+
#showMenu(pickingInfo) {
|
|
178
|
+
if (!pickingInfo?.hit && !pickingInfo?.pickedMesh) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const nodeId = this.#getNodeAnimatedByMesh(pickingInfo.pickedMesh);
|
|
183
|
+
if (!nodeId) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const openingAnimation = this.#getOpeningAnimationByNode(nodeId);
|
|
187
|
+
if (!openingAnimation) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!this.#advancedDynamicTexture) {
|
|
191
|
+
this.#advancedDynamicTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI_Animation");
|
|
192
|
+
}
|
|
193
|
+
openingAnimation.showControls(this.#advancedDynamicTexture);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { AdvancedDynamicTexture, Button, Control, Image, Slider, StackPanel } from "@babylonjs/gui";
|
|
2
|
+
import { OpeningAnimation } from "./babylonjs-animation-opening.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OpeningAnimationMenu - Manages and renders the animation control menu for opening/closing animations in a Babylon.js scene.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Renders a GUI menu with buttons for controlling animation playback (open, close, pause, go to opened/closed, loop).
|
|
9
|
+
* - Updates button states and slider position based on animation state, progress, and loop mode.
|
|
10
|
+
* - Handles user interactions and invokes provided callbacks for animation actions.
|
|
11
|
+
* - Synchronizes the slider value with animation progress, avoiding callback loops.
|
|
12
|
+
*
|
|
13
|
+
* Public Setters:
|
|
14
|
+
* - set animationState(state): Updates the animation state and button states.
|
|
15
|
+
* - set animationProgress(progress): Updates the animation progress and slider value.
|
|
16
|
+
* - set animationLoop(loop): Updates the loop mode and loop button states.
|
|
17
|
+
*
|
|
18
|
+
* Private Methods:
|
|
19
|
+
* - #createMenu(): Initializes and renders the menu UI.
|
|
20
|
+
* - #addButton(name, imageURL, enabled, active, visible, callback): Adds a button to the menu with specified properties.
|
|
21
|
+
* - #createButtons(): Creates all control buttons and sets their initial states.
|
|
22
|
+
* - #getButtonByName(name): Retrieves a button control by its name.
|
|
23
|
+
* - #setButtonState(name, enabled, active, visible): Updates the visual state of a button.
|
|
24
|
+
* - #getPlayerButtonsState(): Returns the state (enabled, active, visible) for playback control buttons.
|
|
25
|
+
* - #getLoopButtonsState(): Returns the state for loop control buttons.
|
|
26
|
+
* - #getButtonsState(): Combines player and loop button states.
|
|
27
|
+
* - #setPlayerButtonsState(): Updates all playback control buttons.
|
|
28
|
+
* - #setLoopButtonsState(): Updates all loop control buttons.
|
|
29
|
+
* - #createSlider(): Creates and configures the animation progress slider.
|
|
30
|
+
*
|
|
31
|
+
* Usage Example:
|
|
32
|
+
* const menu = new OpeningAnimationMenu(adt, state, progress, loop, {
|
|
33
|
+
* onOpen: () => { ... },
|
|
34
|
+
* onClose: () => { ... },
|
|
35
|
+
* onPause: () => { ... },
|
|
36
|
+
* onGoToOpened: () => { ... },
|
|
37
|
+
* onGoToClosed: () => { ... },
|
|
38
|
+
* onToggleLoop: () => { ... },
|
|
39
|
+
* onSetAnimationProgress: (progress) => { ... }
|
|
40
|
+
* });
|
|
41
|
+
* menu.animationState = OpeningAnimation.states.opening;
|
|
42
|
+
* menu.animationProgress = 0.5;
|
|
43
|
+
* menu.animationLoop = true;
|
|
44
|
+
*/
|
|
45
|
+
export class OpeningAnimationMenu {
|
|
46
|
+
#animationState = OpeningAnimation.states.closed;
|
|
47
|
+
#animationProgress = 0;
|
|
48
|
+
#animationLoop = false;
|
|
49
|
+
#callbacks = null;
|
|
50
|
+
|
|
51
|
+
// GUI Elements
|
|
52
|
+
#advancedDynamicTexture = null;
|
|
53
|
+
#mainPanel = null;
|
|
54
|
+
#secondaryPanel = null;
|
|
55
|
+
#slider = null;
|
|
56
|
+
|
|
57
|
+
// Style properties
|
|
58
|
+
#buttonSize = 28;
|
|
59
|
+
#buttonLoopPaddingLeft = 3;
|
|
60
|
+
#colorActive = "#6BA53A";
|
|
61
|
+
#colorEnabled = "#333333";
|
|
62
|
+
#colorDisabled = "#777777";
|
|
63
|
+
#colorIcon = "#FFFFFF";
|
|
64
|
+
#colorBorder = "#FFFFFF";
|
|
65
|
+
#sliderThumbWidth = 20;
|
|
66
|
+
#sliderBarOffset = 10;
|
|
67
|
+
#icon = {
|
|
68
|
+
close: `<svg id="play-backwards" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M16,18.86V4.86l-11,7,11,7Z"/></svg>`,
|
|
69
|
+
closed: `<svg id="skip-backward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M20,5V19L13,12M6,5V19H4V5M13,5V19L6,12"/></svg>`,
|
|
70
|
+
open: `<svg id="play" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M8,5.14v14l11-7-11-7Z"/></svg>`,
|
|
71
|
+
opened: `<svg id="skip-forward" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M4,5V19L11,12M18,5V19H20V5M11,5V19L18,12"/></svg>`,
|
|
72
|
+
pause: `<svg id="pause" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M14,19H18V5H14M6,19H10V5H6V19Z"/></svg>`,
|
|
73
|
+
repeat: `<svg id="repeat" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M17,17H7V14L3,18L7,22V19H19V13H17M7,7H17V10L21,6L17,2V5H5V11H7V7Z"/></svg>`,
|
|
74
|
+
repeatOff: `<svg id="repeat-off" width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill="${this.#colorIcon}" d="M2,5.27L3.28,4L20,20.72L18.73,22L15.73,19H7V22L3,18L7,14V17H13.73L7,10.27V11H5V8.27L2,5.27M17,13H19V17.18L17,15.18V13M17,5V2L21,6L17,10V7H8.82L6.82,5H17Z"/></svg>`,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
#isSettingSliderValue = false;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Constructs the OpeningAnimationMenu and initializes the menu UI.
|
|
81
|
+
* @param {AdvancedDynamicTexture} advancedDynamicTexture - Babylon.js GUI texture for rendering controls.
|
|
82
|
+
* @param {number} animationState - Current animation state (enum).
|
|
83
|
+
* @param {number} animationProgress - Current animation progress (0 to 1).
|
|
84
|
+
* @param {boolean} animationLoop - Whether the animation is set to loop.
|
|
85
|
+
* @param {object} callbacks - Callback functions for menu actions (play, pause, open, close, etc.).
|
|
86
|
+
* @public
|
|
87
|
+
*/
|
|
88
|
+
constructor(advancedDynamicTexture, animationState, animationProgress, animationLoop, callbacks) {
|
|
89
|
+
this.#advancedDynamicTexture = advancedDynamicTexture;
|
|
90
|
+
this.#animationState = animationState;
|
|
91
|
+
this.#animationProgress = animationProgress;
|
|
92
|
+
this.#animationLoop = animationLoop;
|
|
93
|
+
this.#callbacks = callbacks;
|
|
94
|
+
|
|
95
|
+
this.#createMenu(animationState);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Initializes and renders the menu UI, including buttons and slider.
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
#createMenu() {
|
|
103
|
+
if (!this.#advancedDynamicTexture) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.#mainPanel = new StackPanel();
|
|
107
|
+
this.#mainPanel.isVertical = true;
|
|
108
|
+
this.#secondaryPanel = new StackPanel();
|
|
109
|
+
this.#secondaryPanel.isVertical = false;
|
|
110
|
+
this.#secondaryPanel.height = `${this.#buttonSize}px`;
|
|
111
|
+
this.#mainPanel.addControl(this.#secondaryPanel);
|
|
112
|
+
|
|
113
|
+
this.#advancedDynamicTexture.addControl(this.#mainPanel);
|
|
114
|
+
this.#mainPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
115
|
+
this.#mainPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM;
|
|
116
|
+
|
|
117
|
+
this.#createButtons();
|
|
118
|
+
this.#createSlider();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Internal helper to add a button to the menu.
|
|
123
|
+
* Sets button appearance and attaches the callback for user interaction.
|
|
124
|
+
* @private
|
|
125
|
+
* @param {string} name - Button identifier.
|
|
126
|
+
* @param {string} imageURL - SVG image data URL for the button icon.
|
|
127
|
+
* @param {boolean} enabled - Whether the button is enabled.
|
|
128
|
+
* @param {boolean} active - Whether the button is visually active.
|
|
129
|
+
* @param {boolean} visible - Whether the button is visible.
|
|
130
|
+
* @param {function} callback - Callback to invoke on button click.
|
|
131
|
+
*/
|
|
132
|
+
#addButton(name, imageURL, enabled = true, active = false, visible = true, callback) {
|
|
133
|
+
const buttonProps = {
|
|
134
|
+
background: active ? this.#colorActive : enabled ? this.#colorEnabled : this.#colorDisabled,
|
|
135
|
+
color: this.#colorBorder,
|
|
136
|
+
cornerRadius: 0,
|
|
137
|
+
height: `${this.#buttonSize}px`,
|
|
138
|
+
hoverCursor: "pointer",
|
|
139
|
+
width: `${this.#buttonSize}px`,
|
|
140
|
+
isVisible: visible,
|
|
141
|
+
};
|
|
142
|
+
const button = Button.CreateImageOnlyButton(`button_animation_${name}`, imageURL);
|
|
143
|
+
Object.assign(button, buttonProps);
|
|
144
|
+
button.image.stretch = Image.STRETCH_UNIFORM;
|
|
145
|
+
button.onPointerUpObservable.add(() => {
|
|
146
|
+
if (callback && typeof callback === "function") {
|
|
147
|
+
callback();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
this.#secondaryPanel.addControl(button);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Creates all control buttons and sets their initial states.
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
#createButtons() {
|
|
158
|
+
const buttonsState = this.#getButtonsState();
|
|
159
|
+
this.#addButton("closed", `data:image/svg+xml,${encodeURIComponent(this.#icon.closed)}`, buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible, this.#callbacks.onGoToClosed);
|
|
160
|
+
this.#addButton("close", `data:image/svg+xml,${encodeURIComponent(this.#icon.close)}`, buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible, this.#callbacks.onClose);
|
|
161
|
+
this.#addButton("pause", `data:image/svg+xml,${encodeURIComponent(this.#icon.pause)}`, buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible, this.#callbacks.onPause);
|
|
162
|
+
this.#addButton("open", `data:image/svg+xml,${encodeURIComponent(this.#icon.open)}`, buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible, this.#callbacks.onOpen);
|
|
163
|
+
this.#addButton("opened", `data:image/svg+xml,${encodeURIComponent(this.#icon.opened)}`, buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible, this.#callbacks.onGoToOpened);
|
|
164
|
+
this.#addButton("repeat", `data:image/svg+xml,${encodeURIComponent(this.#icon.repeat)}`, buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible, this.#callbacks.onToggleLoop);
|
|
165
|
+
this.#addButton("repeatOff", `data:image/svg+xml,${encodeURIComponent(this.#icon.repeatOff)}`, buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible, this.#callbacks.onToggleLoop);
|
|
166
|
+
|
|
167
|
+
// Adjust padding for loop buttons
|
|
168
|
+
this.#getButtonByName("repeat").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
|
|
169
|
+
this.#getButtonByName("repeat").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
|
|
170
|
+
this.#getButtonByName("repeatOff").paddingLeft = `${this.#buttonLoopPaddingLeft}px`;
|
|
171
|
+
this.#getButtonByName("repeatOff").width = `${this.#buttonSize + this.#buttonLoopPaddingLeft}px`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Retrieves a button control by its name.
|
|
176
|
+
* @private
|
|
177
|
+
* @param {string} name - Button identifier.
|
|
178
|
+
* @returns {Button|null} The button control or null if not found.
|
|
179
|
+
*/
|
|
180
|
+
#getButtonByName(name) {
|
|
181
|
+
return this.#advancedDynamicTexture.getControlByName(`button_animation_${name}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Updates the visual state of a button (enabled, active, visible).
|
|
186
|
+
* @private
|
|
187
|
+
* @param {string} name - Button identifier.
|
|
188
|
+
* @param {boolean} enabled
|
|
189
|
+
* @param {boolean} active
|
|
190
|
+
* @param {boolean} visible
|
|
191
|
+
*/
|
|
192
|
+
#setButtonState(name, enabled, active, visible = true) {
|
|
193
|
+
const button = this.#getButtonByName(name);
|
|
194
|
+
if (!button) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
button.background = active ? this.#colorActive : enabled ? this.#colorEnabled : this.#colorDisabled;
|
|
198
|
+
button.isVisible = visible;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Returns the state (enabled, active, visible) for playback control buttons.
|
|
203
|
+
* @private
|
|
204
|
+
* @returns {object}
|
|
205
|
+
*/
|
|
206
|
+
#getPlayerButtonsState() {
|
|
207
|
+
const buttonsState = {
|
|
208
|
+
opened: {
|
|
209
|
+
enabled: this.#animationState !== OpeningAnimation.states.opened,
|
|
210
|
+
active: false,
|
|
211
|
+
visible: true,
|
|
212
|
+
},
|
|
213
|
+
open: {
|
|
214
|
+
enabled: this.#animationState !== OpeningAnimation.states.opened && this.#animationState !== OpeningAnimation.states.opening,
|
|
215
|
+
active: this.#animationState === OpeningAnimation.states.opening,
|
|
216
|
+
visible: true,
|
|
217
|
+
},
|
|
218
|
+
pause: {
|
|
219
|
+
enabled: this.#animationState !== OpeningAnimation.states.paused && this.#animationState !== OpeningAnimation.states.closed && this.#animationState !== OpeningAnimation.states.opened,
|
|
220
|
+
active: this.#animationState === OpeningAnimation.states.paused,
|
|
221
|
+
visible: true,
|
|
222
|
+
},
|
|
223
|
+
close: {
|
|
224
|
+
enabled: this.#animationState !== OpeningAnimation.states.closed && this.#animationState !== OpeningAnimation.states.closing,
|
|
225
|
+
active: this.#animationState === OpeningAnimation.states.closing,
|
|
226
|
+
visible: true,
|
|
227
|
+
},
|
|
228
|
+
closed: {
|
|
229
|
+
enabled: this.#animationState !== OpeningAnimation.states.closed,
|
|
230
|
+
active: false,
|
|
231
|
+
visible: true,
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
return buttonsState;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns the state for loop control buttons.
|
|
239
|
+
* @private
|
|
240
|
+
* @returns {object}
|
|
241
|
+
*/
|
|
242
|
+
#getLoopButtonsState() {
|
|
243
|
+
const buttonsState = {
|
|
244
|
+
repeat: {
|
|
245
|
+
enabled: this.#animationLoop,
|
|
246
|
+
active: false,
|
|
247
|
+
visible: this.#animationLoop,
|
|
248
|
+
},
|
|
249
|
+
repeatOff: {
|
|
250
|
+
enabled: !this.#animationLoop,
|
|
251
|
+
active: false,
|
|
252
|
+
visible: !this.#animationLoop,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
return buttonsState;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Combines player and loop button states.
|
|
260
|
+
* @private
|
|
261
|
+
* @returns {object}
|
|
262
|
+
*/
|
|
263
|
+
#getButtonsState() {
|
|
264
|
+
return Object.assign(this.#getPlayerButtonsState(), this.#getLoopButtonsState());
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Updates all playback control buttons according to current animation state.
|
|
269
|
+
* @private
|
|
270
|
+
*/
|
|
271
|
+
#setPlayerButtonsState() {
|
|
272
|
+
const buttonsState = this.#getPlayerButtonsState();
|
|
273
|
+
this.#setButtonState("opened", buttonsState.opened.enabled, buttonsState.opened.active, buttonsState.opened.visible);
|
|
274
|
+
this.#setButtonState("open", buttonsState.open.enabled, buttonsState.open.active, buttonsState.open.visible);
|
|
275
|
+
this.#setButtonState("pause", buttonsState.pause.enabled, buttonsState.pause.active, buttonsState.pause.visible);
|
|
276
|
+
this.#setButtonState("close", buttonsState.close.enabled, buttonsState.close.active, buttonsState.close.visible);
|
|
277
|
+
this.#setButtonState("closed", buttonsState.closed.enabled, buttonsState.closed.active, buttonsState.closed.visible);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Updates all loop control buttons according to current loop mode.
|
|
282
|
+
* @private
|
|
283
|
+
*/
|
|
284
|
+
#setLoopButtonsState() {
|
|
285
|
+
const buttonsState = this.#getLoopButtonsState();
|
|
286
|
+
this.#setButtonState("repeat", buttonsState.repeat.enabled, buttonsState.repeat.active, buttonsState.repeat.visible);
|
|
287
|
+
this.#setButtonState("repeatOff", buttonsState.repeatOff.enabled, buttonsState.repeatOff.active, buttonsState.repeatOff.visible);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Creates and configures the animation progress slider.
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
#createSlider() {
|
|
295
|
+
const sliderProps = {
|
|
296
|
+
minimum: 0,
|
|
297
|
+
maximum: 1,
|
|
298
|
+
value: this.#animationProgress,
|
|
299
|
+
height: `${this.#buttonSize}px`,
|
|
300
|
+
width: `${this.#buttonSize * 7 + this.#buttonLoopPaddingLeft}px`, // Width based on number of buttons visible
|
|
301
|
+
barOffset: `${this.#sliderBarOffset}px`,
|
|
302
|
+
isThumbCircle: true,
|
|
303
|
+
thumbWidth: `${this.#sliderThumbWidth}px`,
|
|
304
|
+
background: this.#colorDisabled,
|
|
305
|
+
color: this.#colorEnabled,
|
|
306
|
+
borderColor: this.#colorBorder,
|
|
307
|
+
thumbColor: this.#colorEnabled,
|
|
308
|
+
};
|
|
309
|
+
this.#slider = new Slider("slider_animation_progress");
|
|
310
|
+
Object.assign(this.#slider, sliderProps);
|
|
311
|
+
this.#slider.onValueChangedObservable.add((value) => {
|
|
312
|
+
if (this.#isSettingSliderValue) {
|
|
313
|
+
this.#isSettingSliderValue = false;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (this.#callbacks && typeof this.#callbacks.onSetAnimationProgress === "function") {
|
|
317
|
+
this.#callbacks.onSetAnimationProgress(value);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
this.#mainPanel.addControl(this.#slider);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* ---------------------------
|
|
325
|
+
* Public setters
|
|
326
|
+
* ---------------------------
|
|
327
|
+
*/
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Sets the animation loop mode and updates loop button states.
|
|
331
|
+
* @public
|
|
332
|
+
* @param {boolean} loop
|
|
333
|
+
*/
|
|
334
|
+
set animationLoop(loop) {
|
|
335
|
+
this.#animationLoop = loop;
|
|
336
|
+
this.#setLoopButtonsState();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Sets the animation progress value and updates the slider position.
|
|
341
|
+
* When called, the slider value is updated programmatically without triggering the slider's value change callback.
|
|
342
|
+
* @public
|
|
343
|
+
* @param {number} progress - The new animation progress value (between 0 and 1).
|
|
344
|
+
*/
|
|
345
|
+
set animationProgress(progress) {
|
|
346
|
+
this.#animationProgress = progress;
|
|
347
|
+
this.#isSettingSliderValue = true;
|
|
348
|
+
this.#slider.value = progress;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Sets the animation state and updates playback button states.
|
|
353
|
+
* @public
|
|
354
|
+
* @param {number} state - The new animation state (enum).
|
|
355
|
+
*/
|
|
356
|
+
set animationState(state) {
|
|
357
|
+
this.#animationState = state;
|
|
358
|
+
this.#setPlayerButtonsState();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { AdvancedDynamicTexture } from "@babylonjs/gui";
|
|
2
|
+
import { OpeningAnimationMenu } from "./babylonjs-animation-opening-menu.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* OpeningAnimation - Manages open/close animations for a model part (e.g., a door) in a Babylon.js scene.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Controls playback of opening and closing AnimationGroups.
|
|
9
|
+
* - Tracks animation state (paused, closed, opened, opening, closing).
|
|
10
|
+
* - Synchronizes animation progress and UI controls.
|
|
11
|
+
* - Handles loop mode and progress threshold logic.
|
|
12
|
+
* - Provides methods for play, pause, go to opened/closed, and progress control.
|
|
13
|
+
* - Manages the animation control menu (OpeningAnimationMenu) and its callbacks.
|
|
14
|
+
*
|
|
15
|
+
* Public Methods:
|
|
16
|
+
* - isAnimationForNode(node): Checks if the animation affects the given node.
|
|
17
|
+
* - playOpen(): Starts the opening animation.
|
|
18
|
+
* - playClose(): Starts the closing animation.
|
|
19
|
+
* - pause(): Pauses the current animation.
|
|
20
|
+
* - goToOpened(): Moves animation to the fully opened state.
|
|
21
|
+
* - goToClosed(): Moves animation to the fully closed state.
|
|
22
|
+
* - showControls(advancedDynamicTexture): Displays the animation control menu.
|
|
23
|
+
* - hideControls(): Hides the animation control menu.
|
|
24
|
+
* - isControlsVisible(): Returns true if the control menu is visible for this animation.
|
|
25
|
+
*
|
|
26
|
+
* Public Properties:
|
|
27
|
+
* - state: Returns the current animation state.
|
|
28
|
+
*
|
|
29
|
+
* Private Methods:
|
|
30
|
+
* - #getNodesFromAnimationGroups(): Collects node IDs affected by the animation groups.
|
|
31
|
+
* - #onOpened(): Handles end of opening animation.
|
|
32
|
+
* - #onClosed(): Handles end of closing animation.
|
|
33
|
+
* - #goToOpened(useLoop): Sets state to opened and optionally loops to close.
|
|
34
|
+
* - #goToClosed(useLoop): Sets state to closed and optionally loops to open.
|
|
35
|
+
* - #goToFrameAndPause(frame): Moves to a specific frame and pauses.
|
|
36
|
+
* - #getCurrentFrame(): Gets the current frame based on state.
|
|
37
|
+
* - #getFrameFromProgress(progress): Calculates frame from progress value.
|
|
38
|
+
* - #getProgress(): Calculates progress (0-1) from current frame.
|
|
39
|
+
* - #checkProgress(progress): Applies threshold logic to progress.
|
|
40
|
+
* - #updateControlsSlider(): Updates the slider in the control menu.
|
|
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
|
+
*/
|
|
51
|
+
export class OpeningAnimation {
|
|
52
|
+
static states = {
|
|
53
|
+
paused: 0,
|
|
54
|
+
closed: 1,
|
|
55
|
+
opened: 2,
|
|
56
|
+
opening: 3,
|
|
57
|
+
closing: 4,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
#openAnimation = null;
|
|
61
|
+
#closeAnimation = null;
|
|
62
|
+
|
|
63
|
+
#nodes = [];
|
|
64
|
+
#state = OpeningAnimation.states.closed;
|
|
65
|
+
#lastPausedFrame = 0;
|
|
66
|
+
#startFrame = 0;
|
|
67
|
+
#endFrame = 0;
|
|
68
|
+
#speedRatio = 1.0;
|
|
69
|
+
#loop = false;
|
|
70
|
+
|
|
71
|
+
#advancedDynamicTexture = null;
|
|
72
|
+
#menu = null;
|
|
73
|
+
#progressThreshold = 0.025;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Creates a new OpeningAnimation instance for managing open/close animations of a model part.
|
|
77
|
+
* @param {string} name - The identifier for this animation (e.g., door name).
|
|
78
|
+
* @param {AnimationGroup} openAnimationGroup - Babylon.js AnimationGroup for the opening animation.
|
|
79
|
+
* @param {AnimationGroup} closeAnimationGroup - Babylon.js AnimationGroup for the closing animation.
|
|
80
|
+
* @description
|
|
81
|
+
* Initializes internal state, sets up frame ranges, collects affected nodes, and attaches end-of-animation observers.
|
|
82
|
+
*/
|
|
83
|
+
constructor(name, openAnimationGroup, closeAnimationGroup) {
|
|
84
|
+
this.name = name;
|
|
85
|
+
this.#openAnimation = openAnimationGroup;
|
|
86
|
+
this.#closeAnimation = closeAnimationGroup;
|
|
87
|
+
|
|
88
|
+
this.#openAnimation.stop();
|
|
89
|
+
this.#openAnimation._loopAnimation = false;
|
|
90
|
+
this.#closeAnimation.stop();
|
|
91
|
+
this.#closeAnimation._loopAnimation = false;
|
|
92
|
+
|
|
93
|
+
this.#startFrame = this.#openAnimation.from;
|
|
94
|
+
this.#endFrame = this.#openAnimation.to;
|
|
95
|
+
this.#speedRatio = this.#openAnimation.speedRatio || 1.0;
|
|
96
|
+
|
|
97
|
+
this.#getNodesFromAnimationGroups();
|
|
98
|
+
this.#openAnimation.onAnimationGroupEndObservable.add(this.#onOpened.bind(this));
|
|
99
|
+
this.#closeAnimation.onAnimationGroupEndObservable.add(this.#onClosed.bind(this));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Collects node IDs affected by the opening and closing animation groups.
|
|
104
|
+
* Populates the #nodes array with unique node identifiers.
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
#getNodesFromAnimationGroups() {
|
|
108
|
+
[this.#openAnimation, this.#closeAnimation].forEach((animationGroup) => {
|
|
109
|
+
animationGroup._targetedAnimations.forEach((targetedAnimation) => {
|
|
110
|
+
if (!this.#nodes.includes(targetedAnimation.target.id)) {
|
|
111
|
+
this.#nodes.push(targetedAnimation.target.id);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handles the end of the opening animation group.
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
#onOpened() {
|
|
122
|
+
this.#goToOpened(true);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Handles the end of the closing animation group.
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
#onClosed() {
|
|
130
|
+
this.#goToClosed(true);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Moves the animation to the fully opened state, optionally looping to close.
|
|
135
|
+
* @private
|
|
136
|
+
* @param {boolean} useLoop - If true, starts closing animation after opening.
|
|
137
|
+
*/
|
|
138
|
+
#goToOpened(useLoop = false) {
|
|
139
|
+
this.#lastPausedFrame = this.#endFrame;
|
|
140
|
+
|
|
141
|
+
if (this.#openAnimation._isStarted && !this.#openAnimation._isPaused) {
|
|
142
|
+
this.#openAnimation.pause();
|
|
143
|
+
}
|
|
144
|
+
this.#openAnimation.goToFrame(this.#endFrame);
|
|
145
|
+
|
|
146
|
+
this.#closeAnimation.pause();
|
|
147
|
+
this.#closeAnimation.goToFrame(this.#startFrame);
|
|
148
|
+
|
|
149
|
+
this.#state = OpeningAnimation.states.opened;
|
|
150
|
+
this.#updateControls();
|
|
151
|
+
|
|
152
|
+
if (this.#loop && useLoop) {
|
|
153
|
+
this.playClose();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Moves the animation to the fully closed state, optionally looping to open.
|
|
159
|
+
* @private
|
|
160
|
+
* @param {boolean} useLoop - If true, starts opening animation after closing.
|
|
161
|
+
*/
|
|
162
|
+
#goToClosed(useLoop = false) {
|
|
163
|
+
this.#lastPausedFrame = this.#startFrame;
|
|
164
|
+
|
|
165
|
+
if (this.#closeAnimation._isStarted && !this.#closeAnimation._isPaused) {
|
|
166
|
+
this.#closeAnimation.pause();
|
|
167
|
+
}
|
|
168
|
+
this.#closeAnimation.goToFrame(this.#endFrame - this.#startFrame);
|
|
169
|
+
|
|
170
|
+
this.#openAnimation.pause();
|
|
171
|
+
this.#openAnimation.goToFrame(this.#startFrame);
|
|
172
|
+
|
|
173
|
+
this.#state = OpeningAnimation.states.closed;
|
|
174
|
+
this.#updateControls();
|
|
175
|
+
|
|
176
|
+
if (this.#loop && useLoop) {
|
|
177
|
+
this.playOpen();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Moves the animation to a specific frame and pauses.
|
|
183
|
+
* @private
|
|
184
|
+
* @param {number} frame - The frame to go to and pause.
|
|
185
|
+
*/
|
|
186
|
+
#goToFrameAndPause(frame) {
|
|
187
|
+
if (!frame) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (this.#state === OpeningAnimation.states.opening) {
|
|
192
|
+
this.#openAnimation.pause();
|
|
193
|
+
}
|
|
194
|
+
if (this.#state === OpeningAnimation.states.closing) {
|
|
195
|
+
this.#closeAnimation.pause();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (this.#openAnimation._isStarted && this.#openAnimation._isPaused) {
|
|
199
|
+
this.#openAnimation.goToFrame(frame);
|
|
200
|
+
} else {
|
|
201
|
+
this.#openAnimation.start();
|
|
202
|
+
this.#openAnimation.pause();
|
|
203
|
+
this.#openAnimation.goToFrame(frame);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.#lastPausedFrame = frame;
|
|
207
|
+
this.#state = OpeningAnimation.states.paused;
|
|
208
|
+
this.#updateControls();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Gets the current frame of the animation based on its state.
|
|
213
|
+
* @private
|
|
214
|
+
* @returns {number} The current frame.
|
|
215
|
+
*/
|
|
216
|
+
#getCurrentFrame() {
|
|
217
|
+
let currentFrame;
|
|
218
|
+
if (this.#state === OpeningAnimation.states.opening) {
|
|
219
|
+
currentFrame = this.#openAnimation.getCurrentFrame();
|
|
220
|
+
} else if (this.#state === OpeningAnimation.states.closing) {
|
|
221
|
+
currentFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
|
|
222
|
+
} else {
|
|
223
|
+
currentFrame = this.#lastPausedFrame;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Ensure currentFrame is within startFrame and endFrame
|
|
227
|
+
if (currentFrame < this.#startFrame) {
|
|
228
|
+
currentFrame = this.#startFrame;
|
|
229
|
+
} else if (currentFrame > this.#endFrame) {
|
|
230
|
+
currentFrame = this.#endFrame;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return currentFrame;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Calculates the frame number from a normalized progress value (0-1).
|
|
238
|
+
* @private
|
|
239
|
+
* @param {number} progress - Progress value between 0 and 1.
|
|
240
|
+
* @returns {number} The corresponding frame.
|
|
241
|
+
*/
|
|
242
|
+
#getFrameFromProgress(progress) {
|
|
243
|
+
const frame = this.#startFrame + (this.#endFrame - this.#startFrame) * progress;
|
|
244
|
+
return frame;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Calculates the normalized progress (0-1) from the current frame.
|
|
249
|
+
* @private
|
|
250
|
+
* @returns {number} Progress value.
|
|
251
|
+
*/
|
|
252
|
+
#getProgress() {
|
|
253
|
+
const currentFrame = this.#getCurrentFrame();
|
|
254
|
+
const progress = (currentFrame - this.#startFrame) / (this.#endFrame - this.#startFrame);
|
|
255
|
+
return progress;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Applies threshold logic to the progress value to snap to 0 or 1 if near the ends.
|
|
260
|
+
* Prevents floating point errors from leaving the animation in an in-between state.
|
|
261
|
+
* @private
|
|
262
|
+
* @param {number} progress - Progress value.
|
|
263
|
+
* @returns {number} Thresholded progress.
|
|
264
|
+
*/
|
|
265
|
+
#checkProgress(progress) {
|
|
266
|
+
if (progress <= this.#progressThreshold) {
|
|
267
|
+
progress = 0;
|
|
268
|
+
} else if (progress >= 1 - this.#progressThreshold) {
|
|
269
|
+
progress = 1;
|
|
270
|
+
}
|
|
271
|
+
return progress;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Updates the slider value in the animation control menu to match the current progress.
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
#updateControlsSlider() {
|
|
279
|
+
if (!this.isControlsVisible()) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.#menu.animationProgress = this.#getProgress();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Updates all controls in the animation menu (buttons, slider) to reflect the current state and progress.
|
|
287
|
+
* @private
|
|
288
|
+
*/
|
|
289
|
+
#updateControls() {
|
|
290
|
+
if (!this.isControlsVisible()) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!this.#menu) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
this.#menu.animationState = this.#state;
|
|
297
|
+
this.#menu.animationProgress = this.#getProgress();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* ---------------------------
|
|
302
|
+
* Public methods
|
|
303
|
+
* ---------------------------
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Checks if the animation affects the given node.
|
|
308
|
+
* @param {string} node - Node identifier.
|
|
309
|
+
* @returns {boolean}
|
|
310
|
+
*/
|
|
311
|
+
isAnimationForNode(node) {
|
|
312
|
+
return this.#nodes.includes(node);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Starts the opening animation.
|
|
317
|
+
* @public
|
|
318
|
+
*/
|
|
319
|
+
playOpen() {
|
|
320
|
+
if (this.#state === OpeningAnimation.states.opening || this.#state === OpeningAnimation.states.opened) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (this.#state === OpeningAnimation.states.closing) {
|
|
324
|
+
this.#lastPausedFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
|
|
325
|
+
this.#closeAnimation.pause();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (this.#openAnimation._isStarted && this.#openAnimation._isPaused) {
|
|
329
|
+
this.#openAnimation.goToFrame(this.#lastPausedFrame);
|
|
330
|
+
this.#openAnimation.restart();
|
|
331
|
+
} else {
|
|
332
|
+
this.#openAnimation.start(false, this.#speedRatio, this.#lastPausedFrame, this.#endFrame, undefined);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.#state = OpeningAnimation.states.opening;
|
|
336
|
+
this.#updateControls();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Starts the closing animation.
|
|
341
|
+
* @public
|
|
342
|
+
*/
|
|
343
|
+
playClose() {
|
|
344
|
+
if (this.#state === OpeningAnimation.states.closing || this.#state === OpeningAnimation.states.closed) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (this.#state === OpeningAnimation.states.opening) {
|
|
348
|
+
this.#lastPausedFrame = this.#openAnimation.getCurrentFrame();
|
|
349
|
+
this.#openAnimation.pause();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (this.#closeAnimation._isStarted && this.#closeAnimation._isPaused) {
|
|
353
|
+
this.#closeAnimation.goToFrame(this.#endFrame - this.#lastPausedFrame);
|
|
354
|
+
this.#closeAnimation.restart();
|
|
355
|
+
} else {
|
|
356
|
+
this.#closeAnimation.start(false, this.#speedRatio, this.#endFrame - this.#lastPausedFrame, this.#endFrame, undefined);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
this.#state = OpeningAnimation.states.closing;
|
|
360
|
+
this.#updateControls();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Pauses the current animation.
|
|
365
|
+
* @public
|
|
366
|
+
*/
|
|
367
|
+
pause() {
|
|
368
|
+
if (this.#state === OpeningAnimation.states.opening) {
|
|
369
|
+
this.#lastPausedFrame = this.#openAnimation.getCurrentFrame();
|
|
370
|
+
this.#openAnimation.pause();
|
|
371
|
+
}
|
|
372
|
+
if (this.#state === OpeningAnimation.states.closing) {
|
|
373
|
+
this.#lastPausedFrame = this.#endFrame - this.#closeAnimation.getCurrentFrame();
|
|
374
|
+
this.#closeAnimation.pause();
|
|
375
|
+
}
|
|
376
|
+
this.#state = OpeningAnimation.states.paused;
|
|
377
|
+
this.#updateControls();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Moves animation to the fully opened state.
|
|
382
|
+
* @public
|
|
383
|
+
*/
|
|
384
|
+
goToOpened() {
|
|
385
|
+
this.#goToOpened(false);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Moves animation to the fully closed state.
|
|
390
|
+
* @public
|
|
391
|
+
*/
|
|
392
|
+
goToClosed() {
|
|
393
|
+
this.#goToClosed(false);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Displays the animation control menu and sets up callbacks.
|
|
398
|
+
* Synchronizes slider and button states with animation.
|
|
399
|
+
* @public
|
|
400
|
+
* @param {AdvancedDynamicTexture} advancedDynamicTexture - Babylon.js GUI texture.
|
|
401
|
+
*/
|
|
402
|
+
showControls(advancedDynamicTexture) {
|
|
403
|
+
this.#advancedDynamicTexture = advancedDynamicTexture;
|
|
404
|
+
this.#advancedDynamicTexture.metadata = { animationName: this.name };
|
|
405
|
+
const controlCallbacks = {
|
|
406
|
+
onGoToOpened: () => {
|
|
407
|
+
if (this.#state === OpeningAnimation.states.opened) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
this.goToOpened();
|
|
411
|
+
},
|
|
412
|
+
onOpen: () => {
|
|
413
|
+
if (this.#state === OpeningAnimation.states.opened || this.#state === OpeningAnimation.states.opening) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
this.playOpen();
|
|
417
|
+
},
|
|
418
|
+
onPause: () => {
|
|
419
|
+
if (this.#state === OpeningAnimation.states.paused || this.#state === OpeningAnimation.states.closed || this.#state === OpeningAnimation.states.opened) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
this.pause();
|
|
423
|
+
},
|
|
424
|
+
onClose: () => {
|
|
425
|
+
if (this.#state === OpeningAnimation.states.closed || this.#state === OpeningAnimation.states.closing) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.playClose();
|
|
429
|
+
},
|
|
430
|
+
onGoToClosed: () => {
|
|
431
|
+
if (this.#state === OpeningAnimation.states.closed) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
this.goToClosed();
|
|
435
|
+
},
|
|
436
|
+
onSetAnimationProgress: (progress) => {
|
|
437
|
+
progress = this.#checkProgress(progress);
|
|
438
|
+
if (progress === 0) {
|
|
439
|
+
this.goToClosed();
|
|
440
|
+
} else if (progress === 1) {
|
|
441
|
+
this.goToOpened();
|
|
442
|
+
} else {
|
|
443
|
+
const frame = this.#getFrameFromProgress(progress);
|
|
444
|
+
this.#goToFrameAndPause(frame);
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
onToggleLoop: () => {
|
|
448
|
+
this.#loop = !this.#loop;
|
|
449
|
+
this.#menu.animationLoop = this.#loop;
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
this.#menu = new OpeningAnimationMenu(this.#advancedDynamicTexture, this.#state, this.#getProgress(), this.#loop, controlCallbacks);
|
|
453
|
+
|
|
454
|
+
// Attach to Babylon.js scene render loop for real-time updates
|
|
455
|
+
this.#openAnimation._scene.onBeforeRenderObservable.add(this.#updateControlsSlider.bind(this));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Hides the animation control menu and removes observers.
|
|
460
|
+
* @public
|
|
461
|
+
*/
|
|
462
|
+
hideControls() {
|
|
463
|
+
if (!this.isControlsVisible()) {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
// Remove the observer when controls are hidden
|
|
467
|
+
this.#openAnimation._scene.onBeforeRenderObservable.removeCallback(this.#updateControlsSlider.bind(this));
|
|
468
|
+
this.#advancedDynamicTexture.dispose();
|
|
469
|
+
this.#advancedDynamicTexture = null;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Checks if the animation controls menu is currently visible for this animation instance.
|
|
474
|
+
* @public
|
|
475
|
+
* @returns {boolean} True if controls are visible for this animation; otherwise, false.
|
|
476
|
+
*/
|
|
477
|
+
isControlsVisible() {
|
|
478
|
+
return !!(this.#advancedDynamicTexture && this.#advancedDynamicTexture.metadata?.animationName === this.name && this.#menu);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* ---------------------------
|
|
483
|
+
* Public properties
|
|
484
|
+
* ---------------------------
|
|
485
|
+
*/
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Returns the current animation state.
|
|
489
|
+
* @public
|
|
490
|
+
* @returns {number}
|
|
491
|
+
*/
|
|
492
|
+
|
|
493
|
+
get state() {
|
|
494
|
+
return this.#state;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ArcRotateCamera, AssetContainer, Color4, DirectionalLight, Engine, HDRCubeTexture, HemisphericLight, IblShadowsRenderPipeline, LoadAssetContainerAsync, MeshBuilder, PointLight, Scene, ShadowGenerator, Tools, Vector3, WebXRDefaultExperience, WebXRFeatureName, WebXRSessionManager } from "@babylonjs/core";
|
|
2
2
|
import { DracoCompression } from "@babylonjs/core/Meshes/Compression/dracoCompression";
|
|
3
3
|
import "@babylonjs/loaders";
|
|
4
4
|
import "@babylonjs/loaders/glTF/2.0/Extensions/KHR_draco_mesh_compression";
|
|
5
5
|
import { USDZExportAsync, GLTF2Export } from "@babylonjs/serializers";
|
|
6
|
+
|
|
6
7
|
import GLTFResolver from "./gltf-resolver.js";
|
|
8
|
+
import { ContainerData, 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.
|
|
@@ -76,6 +79,7 @@ export default class BabylonJSController {
|
|
|
76
79
|
#options = {};
|
|
77
80
|
|
|
78
81
|
#gltfResolver = null; // GLTFResolver instance
|
|
82
|
+
#babylonJSAnimationController = null; // AnimationController instance
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
85
|
* Constructs a new BabylonJSController instance.
|
|
@@ -414,7 +418,7 @@ export default class BabylonJSController {
|
|
|
414
418
|
/**
|
|
415
419
|
* Applies material options from the configuration to the relevant meshes.
|
|
416
420
|
* @private
|
|
417
|
-
* @param {
|
|
421
|
+
* @param {MaterialData} optionMaterial - Material option containing value, nodePrefixes, nodeNames, and state.
|
|
418
422
|
* @returns {boolean} True if any mesh material was set, false otherwise.
|
|
419
423
|
*/
|
|
420
424
|
#setOptionsMaterial(optionMaterial) {
|
|
@@ -542,7 +546,7 @@ export default class BabylonJSController {
|
|
|
542
546
|
* Finds and returns the asset container object by its name.
|
|
543
547
|
* @private
|
|
544
548
|
* @param {string} name - The name of the container to find.
|
|
545
|
-
* @returns {
|
|
549
|
+
* @returns {ContainerData|null} The matching container object, or null if not found.
|
|
546
550
|
*/
|
|
547
551
|
#findContainerByName(name) {
|
|
548
552
|
return Object.values(this.#containers).find((container) => container.state.name === name) || null;
|
|
@@ -595,7 +599,7 @@ export default class BabylonJSController {
|
|
|
595
599
|
/**
|
|
596
600
|
* Adds the asset container to the Babylon.js scene if it should be shown and is not already visible.
|
|
597
601
|
* @private
|
|
598
|
-
* @param {
|
|
602
|
+
* @param {ContainerData} container - The container object containing asset state and metadata.
|
|
599
603
|
* @returns {boolean} True if the container was added, false otherwise.
|
|
600
604
|
*/
|
|
601
605
|
#addContainer(container) {
|
|
@@ -611,7 +615,7 @@ export default class BabylonJSController {
|
|
|
611
615
|
/**
|
|
612
616
|
* Removes the asset container from the Babylon.js scene if it is currently visible.
|
|
613
617
|
* @private
|
|
614
|
-
* @param {
|
|
618
|
+
* @param {ContainerData} container - The container object containing asset state and metadata.
|
|
615
619
|
* @returns {boolean} True if the container was removed, false otherwise.
|
|
616
620
|
*/
|
|
617
621
|
#removeContainer(container) {
|
|
@@ -627,9 +631,9 @@ export default class BabylonJSController {
|
|
|
627
631
|
/**
|
|
628
632
|
* Replaces the asset container in the Babylon.js scene with a new one.
|
|
629
633
|
* @private
|
|
630
|
-
* @param {
|
|
631
|
-
* @param {
|
|
632
|
-
* @returns {boolean} True if the container was replaced, false otherwise.
|
|
634
|
+
* @param {ContainerData} container - The container object containing asset state and metadata.
|
|
635
|
+
* @param {AssetContainer} newAssetContainer - The new asset container to add to the scene.
|
|
636
|
+
* @returns {boolean} True if the container was replaced and added, false otherwise.
|
|
633
637
|
*/
|
|
634
638
|
#replaceContainer(container, newAssetContainer) {
|
|
635
639
|
if (container.assetContainer) {
|
|
@@ -639,8 +643,7 @@ export default class BabylonJSController {
|
|
|
639
643
|
}
|
|
640
644
|
this.#scene.getEngine().releaseEffects();
|
|
641
645
|
container.assetContainer = newAssetContainer;
|
|
642
|
-
this.#addContainer(container);
|
|
643
|
-
return true;
|
|
646
|
+
return this.#addContainer(container);
|
|
644
647
|
}
|
|
645
648
|
/**
|
|
646
649
|
* Sets the visibility of wall and floor meshes in the model container based on the provided value or environment visibility.
|
|
@@ -663,7 +666,6 @@ export default class BabylonJSController {
|
|
|
663
666
|
* @private
|
|
664
667
|
* @returns {void}
|
|
665
668
|
*/
|
|
666
|
-
#stopR;
|
|
667
669
|
#stopRender() {
|
|
668
670
|
this.#engine.stopRenderLoop(this.#renderLoop.bind(this));
|
|
669
671
|
}
|
|
@@ -681,8 +683,8 @@ export default class BabylonJSController {
|
|
|
681
683
|
/**
|
|
682
684
|
* Loads an asset container (model, environment, materials, etc.) using the provided container state.
|
|
683
685
|
* @private
|
|
684
|
-
* @param {
|
|
685
|
-
* @returns {Promise<[
|
|
686
|
+
* @param {ContainerData} container - The container object containing asset state and metadata.
|
|
687
|
+
* @returns {Promise<[ContainerData, AssetContainer|boolean]>} Resolves to an array with the container and the loaded asset container, or false if loading fails.
|
|
686
688
|
* @description
|
|
687
689
|
* Resolves the asset source using GLTFResolver, prepares plugin options, and loads the asset into the Babylon.js scene.
|
|
688
690
|
* Updates the container's cache data and returns the container along with the loaded asset container or false if loading fails.
|
|
@@ -753,7 +755,10 @@ export default class BabylonJSController {
|
|
|
753
755
|
if (container.state.name === "model") {
|
|
754
756
|
assetContainer.lights = [];
|
|
755
757
|
}
|
|
756
|
-
this.#replaceContainer(container, assetContainer);
|
|
758
|
+
const replacedAndAdded = this.#replaceContainer(container, assetContainer);
|
|
759
|
+
if (replacedAndAdded && container.state.name === "model") {
|
|
760
|
+
this.#babylonJSAnimationController = new BabylonJSAnimationController(this.#scene);
|
|
761
|
+
}
|
|
757
762
|
container.state.setSuccess(true);
|
|
758
763
|
} else {
|
|
759
764
|
if (container.assetContainer && container.state.mustBeShown !== container.state.isVisible && container.state.name === "materials") {
|
package/src/index.js
CHANGED
|
@@ -270,7 +270,7 @@ class PrefViewer extends HTMLElement {
|
|
|
270
270
|
|
|
271
271
|
const customEventOptions = {
|
|
272
272
|
bubbles: true,
|
|
273
|
-
cancelable:
|
|
273
|
+
cancelable: true,
|
|
274
274
|
composed: true,
|
|
275
275
|
};
|
|
276
276
|
if (detail) {
|
|
@@ -294,7 +294,7 @@ class PrefViewer extends HTMLElement {
|
|
|
294
294
|
|
|
295
295
|
const customEventOptions = {
|
|
296
296
|
bubbles: true,
|
|
297
|
-
cancelable:
|
|
297
|
+
cancelable: true,
|
|
298
298
|
composed: true,
|
|
299
299
|
};
|
|
300
300
|
this.dispatchEvent(new CustomEvent("drawing-loading", customEventOptions));
|
|
@@ -316,7 +316,7 @@ class PrefViewer extends HTMLElement {
|
|
|
316
316
|
|
|
317
317
|
const customEventOptions = {
|
|
318
318
|
bubbles: true,
|
|
319
|
-
cancelable:
|
|
319
|
+
cancelable: true,
|
|
320
320
|
composed: true,
|
|
321
321
|
};
|
|
322
322
|
if (detail) {
|
|
@@ -333,9 +333,11 @@ class PrefViewer extends HTMLElement {
|
|
|
333
333
|
* @returns {void}
|
|
334
334
|
*/
|
|
335
335
|
#on2DZoomChanged(event) {
|
|
336
|
+
event.stopPropagation();
|
|
337
|
+
event.preventDefault();
|
|
336
338
|
const customEventOptions = {
|
|
337
339
|
bubbles: true,
|
|
338
|
-
cancelable:
|
|
340
|
+
cancelable: true,
|
|
339
341
|
composed: true,
|
|
340
342
|
detail: event.detail,
|
|
341
343
|
};
|
package/src/pref-viewer-2d.js
CHANGED
|
@@ -221,9 +221,9 @@ export class PrefViewer2D extends HTMLElement {
|
|
|
221
221
|
const error = "PrefViewer2D: Error in SVG provided for loading. Ensure the SVG is a URL to an SVG file, a string containing a SVG, or a string containing a base64-encoded SVG.";
|
|
222
222
|
const detail = { error: new Error(error) };
|
|
223
223
|
const customEventOptions = {
|
|
224
|
-
bubbles: true,
|
|
225
|
-
cancelable:
|
|
226
|
-
composed:
|
|
224
|
+
bubbles: true, // indicates whether the event bubbles up through the DOM tree or not
|
|
225
|
+
cancelable: true, // indicates whether the event can be canceled, and therefore prevented as if the event never happened
|
|
226
|
+
composed: false, // indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM
|
|
227
227
|
detail: detail,
|
|
228
228
|
};
|
|
229
229
|
this.dispatchEvent(new CustomEvent("drawing-error", customEventOptions));
|
|
@@ -238,8 +238,8 @@ export class PrefViewer2D extends HTMLElement {
|
|
|
238
238
|
#onLoaded() {
|
|
239
239
|
const customEventOptions = {
|
|
240
240
|
bubbles: true,
|
|
241
|
-
cancelable:
|
|
242
|
-
composed:
|
|
241
|
+
cancelable: true,
|
|
242
|
+
composed: false,
|
|
243
243
|
};
|
|
244
244
|
this.dispatchEvent(new CustomEvent("drawing-loaded", customEventOptions));
|
|
245
245
|
|
|
@@ -258,8 +258,8 @@ export class PrefViewer2D extends HTMLElement {
|
|
|
258
258
|
#onLoading() {
|
|
259
259
|
const customEventOptions = {
|
|
260
260
|
bubbles: true,
|
|
261
|
-
cancelable:
|
|
262
|
-
composed:
|
|
261
|
+
cancelable: true,
|
|
262
|
+
composed: false,
|
|
263
263
|
};
|
|
264
264
|
this.dispatchEvent(new CustomEvent("drawing-loading", customEventOptions));
|
|
265
265
|
|
|
@@ -285,10 +285,11 @@ export class PrefViewer2D extends HTMLElement {
|
|
|
285
285
|
#onPanzoomChanged(state) {
|
|
286
286
|
const customEventOptions = {
|
|
287
287
|
bubbles: true,
|
|
288
|
-
cancelable:
|
|
289
|
-
composed:
|
|
288
|
+
cancelable: true,
|
|
289
|
+
composed: false,
|
|
290
290
|
detail: state,
|
|
291
291
|
};
|
|
292
|
+
customEventOptions.detail.desde2d = "true";
|
|
292
293
|
this.dispatchEvent(new CustomEvent("drawing-zoom-changed", customEventOptions));
|
|
293
294
|
}
|
|
294
295
|
|
package/src/pref-viewer-3d.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContainerData, MaterialData
|
|
1
|
+
import { CameraData, ContainerData, MaterialData } from "./pref-viewer-3d-data.js";
|
|
2
2
|
import BabylonJSController from "./babylonjs-controller.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -303,9 +303,9 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
303
303
|
*/
|
|
304
304
|
#onLoading() {
|
|
305
305
|
const customEventOptions = {
|
|
306
|
-
bubbles: true,
|
|
307
|
-
cancelable:
|
|
308
|
-
composed:
|
|
306
|
+
bubbles: true, // indicates whether the event bubbles up through the DOM tree or not
|
|
307
|
+
cancelable: true, // indicates whether the event can be canceled, and therefore prevented as if the event never happened
|
|
308
|
+
composed: false, // indicates whether or not the event will propagate across the shadow DOM boundary into the standard DOM
|
|
309
309
|
};
|
|
310
310
|
this.dispatchEvent(new CustomEvent("scene-loading", customEventOptions));
|
|
311
311
|
|
|
@@ -351,8 +351,8 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
351
351
|
|
|
352
352
|
const customEventOptions = {
|
|
353
353
|
bubbles: true,
|
|
354
|
-
cancelable:
|
|
355
|
-
composed:
|
|
354
|
+
cancelable: true,
|
|
355
|
+
composed: false,
|
|
356
356
|
detail: detail,
|
|
357
357
|
};
|
|
358
358
|
this.dispatchEvent(new CustomEvent("scene-loaded", customEventOptions));
|
|
@@ -375,8 +375,8 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
375
375
|
#onSettingOptions() {
|
|
376
376
|
const customEventOptions = {
|
|
377
377
|
bubbles: true,
|
|
378
|
-
cancelable:
|
|
379
|
-
composed:
|
|
378
|
+
cancelable: true,
|
|
379
|
+
composed: false,
|
|
380
380
|
};
|
|
381
381
|
this.dispatchEvent(new CustomEvent("scene-setting-options", customEventOptions));
|
|
382
382
|
|
|
@@ -416,8 +416,8 @@ export class PrefViewer3D extends HTMLElement {
|
|
|
416
416
|
|
|
417
417
|
const customEventOptions = {
|
|
418
418
|
bubbles: true,
|
|
419
|
-
cancelable:
|
|
420
|
-
composed:
|
|
419
|
+
cancelable: true,
|
|
420
|
+
composed: false,
|
|
421
421
|
detail: detail,
|
|
422
422
|
};
|
|
423
423
|
this.dispatchEvent(new CustomEvent("scene-set-options", customEventOptions));
|