@preference-sl/pref-viewer 2.11.0-beta.9 → 2.12.0-beta.1

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 CHANGED
@@ -1,53 +1,64 @@
1
1
  {
2
- "name": "@preference-sl/pref-viewer",
3
- "version": "2.11.0-beta.9",
4
- "description": "Web Component to preview GLTF models with Babylon.js",
5
- "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
- "scripts": {
7
- "release": "standard-version --releaseCommitMessageFormat \"chore(release): {{currentTag}} [skip ci]\"",
8
- "release:beta": "standard-version --prerelease beta --releaseCommitMessageFormat \"chore(release): {{currentTag}} [skip ci]\"",
9
- "build": "esbuild src/index.js --bundle --platform=node --outfile=dist/bundle.js --sourcemap",
10
- "start": "npm run build && http-server -c-1 . -p 8080",
11
- "test": "vitest"
12
- },
13
- "repository": {
14
- "type": "git",
15
- "url": "git+https://bitbucket.org/preferencesl/pref-viewer.git"
16
- },
17
- "publishConfig": {
18
- "access": "public"
19
- },
20
- "license": "MIT",
21
- "type": "module",
22
- "main": "src/index.js",
23
- "types": "index.d.ts",
24
- "exports": {
25
- ".": {
26
- "import": "./src/index.js",
27
- "require": "./src/index.js"
2
+ "name": "@preference-sl/pref-viewer",
3
+ "version": "2.12.0-beta.1",
4
+ "description": "Web Component to preview GLTF models with Babylon.js",
5
+ "author": "Alex Moreno Palacio <amoreno@preference.es>",
6
+ "scripts": {
7
+ "build:prefweb": "webpack --config webpack.config.cjs --mode production",
8
+ "build:prefweb:dev": "webpack --config webpack.config.cjs --mode development",
9
+ "release": "standard-version --releaseCommitMessageFormat \"chore(release): {{currentTag}} [skip ci]\"",
10
+ "release:beta": "standard-version --prerelease beta --releaseCommitMessageFormat \"chore(release): {{currentTag}} [skip ci]\"",
11
+ "build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/bundle.js --sourcemap",
12
+ "start": "npm run build && http-server -c-1 . -p 8080",
13
+ "test": "vitest"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://bitbucket.org/preferencesl/pref-viewer.git"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "license": "MIT",
23
+ "type": "module",
24
+ "main": "src/index.js",
25
+ "types": "index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "import": "./src/index.js",
29
+ "require": "./src/index.js"
30
+ }
31
+ },
32
+ "sideEffects": false,
33
+ "files": [
34
+ "src",
35
+ "index.d.ts"
36
+ ],
37
+ "dependencies": {
38
+ "@babylonjs/core": "^8.39.2",
39
+ "@babylonjs/loaders": "^8.39.2",
40
+ "@babylonjs/serializers": "^8.39.2",
41
+ "@panzoom/panzoom": "^4.6.0",
42
+ "babylonjs-gltf2interface": "^8.39.2",
43
+ "buffer": "^6.0.3",
44
+ "idb": "^8.0.3",
45
+ "is-svg": "^6.1.0",
46
+ "jszip": "^3.10.1",
47
+ "stream": "^0.0.3",
48
+ "string_decoder": "^1.3.0"
49
+ },
50
+ "devDependencies": {
51
+ "@babel/core": "^7.22.0",
52
+ "@babel/preset-env": "^7.22.0",
53
+ "babel-loader": "^9.2.1",
54
+ "clean-webpack-plugin": "^4.0.0",
55
+ "esbuild": "^0.25.10",
56
+ "http-server": "^14.1.1",
57
+ "jsdom": "^26.1.0",
58
+ "standard-version": "^9.5.0",
59
+ "terser-webpack-plugin": "^5.3.6",
60
+ "vitest": "^3.2.3",
61
+ "webpack": "^5.88.2",
62
+ "webpack-cli": "^5.1.4"
28
63
  }
29
- },
30
- "sideEffects": false,
31
- "files": [
32
- "src",
33
- "index.d.ts"
34
- ],
35
- "dependencies": {
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
- "@panzoom/panzoom": "^4.6.0",
41
- "babylonjs-gltf2interface": "^8.36.1",
42
- "idb": "^8.0.3",
43
- "is-svg": "^6.1.0",
44
- "jszip": "^3.10.1"
45
- },
46
- "devDependencies": {
47
- "esbuild": "^0.25.10",
48
- "http-server": "^14.1.1",
49
- "jsdom": "^26.1.0",
50
- "standard-version": "^9.5.0",
51
- "vitest": "^3.2.3"
52
- }
53
64
  }
@@ -1,28 +1,35 @@
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";
1
+ import { Color3, HighlightLayer, Mesh, PickingInfo, Scene } from "@babylonjs/core";
2
+ import { PrefViewerColors } from "./styles.js";
3
+ import OpeningAnimation from "./babylonjs-animation-opening.js";
4
4
 
5
5
  /**
6
- * BabylonJSAnimationController - Manages animation playback and interactive highlighting for model containers in Babylon.js scenes.
6
+ * BabylonJSAnimationController - Manages animation playback, highlighting, and interactive controls for animated nodes in Babylon.js scenes.
7
7
  *
8
- * Responsibilities:
8
+ * Summary:
9
+ * This class detects, groups, and manages opening/closing animations for scene nodes, provides interactive highlighting of animated nodes and their meshes, and displays a menu for animation control. It is designed for integration with product configurators and interactive 3D applications using Babylon.js.
10
+ *
11
+ * Key features:
9
12
  * - Detects and groups opening/closing animations in the scene.
10
13
  * - 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
+ * - Highlights animated nodes and their child meshes on pointer hover.
15
+ * - Displays and disposes the animation control menu for animated nodes.
16
+ * - Provides public API for highlighting, showing the animation menu, and disposing resources.
17
+ * - Cleans up all resources and observers to prevent memory leaks.
14
18
  *
15
19
  * Public Methods:
16
20
  * - dispose(): Disposes all resources managed by the animation controller.
21
+ * - hightlightMeshes(pickingInfo): Highlights meshes that are children of an animated node when hovered.
22
+ * - hideMenu(): Hides and disposes the animation control menu if it exists.
23
+ * - showMenu(pickingInfo): Displays the animation control menu for the animated node under the pointer.
17
24
  *
18
25
  * @class
19
26
  */
20
27
  export default class BabylonJSAnimationController {
21
28
  #scene = null;
29
+ #canvas = null;
22
30
  #animatedNodes = [];
23
31
  #highlightLayer = null;
24
- #highlightColor = new Color3(0, 1, 0); // Color para resaltar los elementos animados (Verde)
25
- #advancedDynamicTexture = null;
32
+ #highlightColor = Color3.FromHexString(PrefViewerColors.primary);
26
33
  #openingAnimations = [];
27
34
 
28
35
  /**
@@ -31,8 +38,8 @@ export default class BabylonJSAnimationController {
31
38
  */
32
39
  constructor(scene) {
33
40
  this.#scene = scene;
41
+ this.#canvas = this.#scene._engine._renderingCanvas;
34
42
  this.#initializeAnimations();
35
- this.#setupPointerObservers();
36
43
  }
37
44
 
38
45
  /**
@@ -40,11 +47,12 @@ export default class BabylonJSAnimationController {
40
47
  * @private
41
48
  */
42
49
  #initializeAnimations() {
50
+ this.hideMenu(); // Clean up any existing menus
43
51
  if (!this.#scene.animationGroups.length) {
44
52
  return;
45
53
  }
46
54
  this.#getAnimatedNodes();
47
- this.#getOpeneingAnimations();
55
+ this.#getOpeningAnimations();
48
56
  }
49
57
 
50
58
  /**
@@ -71,7 +79,7 @@ export default class BabylonJSAnimationController {
71
79
  * @description
72
80
  * Uses animation group names with the pattern "animation_open_<name>" and "animation_close_<name>".
73
81
  */
74
- #getOpeneingAnimations() {
82
+ #getOpeningAnimations() {
75
83
  const openings = {};
76
84
  this.#scene.animationGroups.forEach((animationGroup) => {
77
85
  const match = animationGroup.name.match(/^animation_(open|close)_(.+)$/);
@@ -105,29 +113,53 @@ export default class BabylonJSAnimationController {
105
113
  }
106
114
 
107
115
  /**
108
- * Determines if a mesh belongs to a node targeted by an animation.
116
+ * Finds all animated node IDs associated with a given mesh by traversing its parent hierarchy.
109
117
  * @private
110
118
  * @param {Mesh} mesh - The mesh to check.
111
- * @returns {string|false} The animated node ID if found, otherwise false.
119
+ * @returns {Array<string>} Array of animated node IDs associated with the mesh.
112
120
  */
113
- #getNodeAnimatedByMesh = function (mesh) {
114
- let nodeId = false;
121
+ #getNodesAnimatedByMesh = function (mesh) {
122
+ let nodeId = [];
115
123
  let node = mesh;
116
- while (node.parent !== null && !nodeId) {
124
+ while (node.parent !== null) {
117
125
  node = node.parent;
118
- if (this.#animatedNodes.includes(node.id)) {
119
- nodeId = node.id;
126
+ if (this.#animatedNodes.includes(node.id) && !nodeId.includes(node.id)) {
127
+ nodeId.push(node.id);
120
128
  }
121
129
  }
122
130
  return nodeId;
123
131
  };
124
132
 
133
+ /**
134
+ * ---------------------------
135
+ * Public methods
136
+ * ---------------------------
137
+ */
138
+
139
+ /**
140
+ * Disposes all resources managed by the animation controller.
141
+ * Cleans up the highlight layer, animation menu, and internal animation/node lists.
142
+ * Should be called when the controller is no longer needed to prevent memory leaks.
143
+ * @public
144
+ */
145
+ dispose() {
146
+ if (this.#highlightLayer) {
147
+ this.#highlightLayer.removeAllMeshes();
148
+ this.#highlightLayer.dispose();
149
+ this.#highlightLayer = null;
150
+ }
151
+ this.hideMenu();
152
+ this.#animatedNodes = [];
153
+ this.#openingAnimations.forEach((openingAnimation) => openingAnimation.dispose());
154
+ this.#openingAnimations = [];
155
+ }
156
+
125
157
  /**
126
158
  * Highlights meshes that are children of an animated node when hovered.
127
- * @private
159
+ * @public
128
160
  * @param {PickingInfo} pickingInfo - Raycast info from pointer position.
129
161
  */
130
- #hightlightMeshesForAnimation(pickingInfo) {
162
+ hightlightMeshes(pickingInfo) {
131
163
  if (!this.#highlightLayer) {
132
164
  this.#highlightLayer = new HighlightLayer("hl_animations", this.#scene);
133
165
  }
@@ -137,99 +169,67 @@ export default class BabylonJSAnimationController {
137
169
  return;
138
170
  }
139
171
 
140
- const nodeId = this.#getNodeAnimatedByMesh(pickingInfo.pickedMesh);
141
- if (!nodeId) {
172
+ const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
173
+ if (!nodeIds.length) {
142
174
  return;
143
175
  }
144
176
 
145
- const transformNode = this.#scene.getTransformNodeByID(nodeId);
146
- const nodeMeshes = transformNode.getChildMeshes();
147
- if (nodeMeshes.length) {
148
- nodeMeshes.forEach((mesh) => this.#highlightLayer.addMesh(mesh, this.#highlightColor));
149
- }
150
- }
177
+ const transformNodes = [];
178
+ nodeIds.forEach((nodeId) => {
179
+ const transformNode = this.#scene.getTransformNodeByID(nodeId);
180
+ if (transformNode) {
181
+ transformNodes.push(transformNode);
182
+ }
183
+ });
151
184
 
152
- /**
153
- * Sets up pointer observers to highlight animated nodes on hover and show the animation menu on click.
154
- * @private
155
- */
156
- #setupPointerObservers() {
157
- if (this.#openingAnimations.length === 0) {
158
- return;
159
- }
160
- this.#scene.onPointerObservable.add(this.#onAnimationPointerObservable.bind(this));
185
+ transformNodes.forEach((transformNode) => {
186
+ const nodeMeshes = transformNode.getChildMeshes();
187
+ if (nodeMeshes.length) {
188
+ nodeMeshes.forEach((mesh) => {
189
+ if (!this.#highlightLayer.hasMesh(mesh)) {
190
+ this.#highlightLayer.addMesh(mesh, this.#highlightColor);
191
+ }
192
+ });
193
+ }
194
+ });
161
195
  }
162
196
 
163
197
  /**
164
- * Handles pointer events in the Babylon.js scene for animation interaction.
165
- * On pointer move, highlights meshes belonging to animated nodes under the cursor.
166
- * On pointer up (click), disposes any existing GUI and displays the animation control menu for the selected node.
167
- *
168
- * @private
169
- * @param {PointerInfo} pointerInfo - The pointer event information from Babylon.js.
198
+ * Hides and disposes the animation control menu if it exists.
199
+ * @public
200
+ * @returns {void}
170
201
  */
171
- #onAnimationPointerObservable(pointerInfo) {
172
- if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
173
- const pickingInfo = this.#scene.pick(pointerInfo.event.clientX, pointerInfo.event.clientY);
174
- this.#hightlightMeshesForAnimation(pickingInfo);
175
- }
176
- if (pointerInfo.type === PointerEventTypes.POINTERUP) {
177
- // Remove any previously created Babylon GUI
178
- if (this.#advancedDynamicTexture) {
179
- this.#advancedDynamicTexture.dispose();
180
- this.#advancedDynamicTexture = null;
181
- }
182
- const pickingInfo = this.#scene.pick(pointerInfo.event.clientX, pointerInfo.event.clientY);
183
- this.#showMenu(pickingInfo);
184
- }
202
+ hideMenu() {
203
+ this.#openingAnimations.forEach((openingAnimation) => openingAnimation.hideControls());
204
+ this.#canvas.parentElement.querySelectorAll("div.pref-viewer-3d.animation-menu").forEach((menu) => menu.remove());
185
205
  }
186
206
 
187
207
  /**
188
208
  * Displays the animation control menu for the animated node under the pointer.
189
- * @private
209
+ * @public
190
210
  * @param {PickingInfo} pickingInfo - Raycast info from pointer position.
191
- * @description
192
- * Creates the GUI if needed and invokes OpeningAnimation.showControls.
193
211
  */
194
- #showMenu(pickingInfo) {
212
+ showMenu(pickingInfo) {
195
213
  if (!pickingInfo?.hit && !pickingInfo?.pickedMesh) {
196
214
  return;
197
215
  }
198
216
 
199
- const nodeId = this.#getNodeAnimatedByMesh(pickingInfo.pickedMesh);
200
- if (!nodeId) {
201
- return;
202
- }
203
- const openingAnimation = this.#getOpeningAnimationByNode(nodeId);
204
- if (!openingAnimation) {
217
+ this.hideMenu();
218
+
219
+ const nodeIds = this.#getNodesAnimatedByMesh(pickingInfo.pickedMesh);
220
+ if (!nodeIds.length) {
205
221
  return;
206
222
  }
207
- if (!this.#advancedDynamicTexture) {
208
- this.#advancedDynamicTexture = AdvancedDynamicTexture.CreateFullscreenUI("UI_Animation");
209
- }
210
- openingAnimation.showControls(this.#advancedDynamicTexture);
211
- }
223
+ const openingAnimations = [];
224
+ nodeIds.forEach((nodeId) => {
225
+ const openingAnimation = this.#getOpeningAnimationByNode(nodeId);
226
+ if (!openingAnimation) {
227
+ return;
228
+ }
229
+ openingAnimations.push(openingAnimation);
230
+ });
212
231
 
213
- /**
214
- * Disposes all resources managed by the animation controller.
215
- * Cleans up the highlight layer, GUI texture, and internal animation/node lists.
216
- * Should be called when the controller is no longer needed to prevent memory leaks.
217
- * @public
218
- */
219
- dispose() {
220
- if (this.#highlightLayer) {
221
- this.#highlightLayer.dispose();
222
- this.#highlightLayer = null;
223
- }
224
- if (this.#advancedDynamicTexture) {
225
- this.#advancedDynamicTexture.dispose();
226
- this.#advancedDynamicTexture = null;
227
- }
228
- this.#animatedNodes = [];
229
- this.#openingAnimations = [];
230
- const observer = this.#scene.onPointerObservable._observers.find((observer) => observer.callback.name.includes("#onAnimationPointerObservable"));
231
- if (observer) {
232
- this.#scene.onPointerObservable.remove(observer);
233
- }
232
+ const startedAnimation = openingAnimations.find((animation) => animation.state !== OpeningAnimation.states.closed);
233
+ startedAnimation ? startedAnimation.showControls(this.#canvas, openingAnimations) : openingAnimations[0].showControls(this.#canvas, openingAnimations);
234
234
  }
235
235
  }