@preference-sl/pref-viewer 2.17.0-beta.0 → 2.17.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/README.md +48 -16
- package/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/babylonjs-animation-controller.js +12 -163
- package/src/babylonjs-animation-opening-menu.js +8 -1
- package/src/babylonjs-animation-opening.js +21 -1
- package/src/babylonjs-animation-overlay-controller.js +145 -0
- package/src/babylonjs-controller.js +332 -177
- package/src/babylonjs-selection-controller.js +215 -0
- package/src/babylonjs-view-cube-controller.js +169 -77
- package/src/file-storage.js +13 -0
- package/src/gltf-storage.js +97 -0
- package/src/localization/i18n.js +147 -74
- package/src/material-utils.js +67 -60
- package/src/panzoom-controller.js +14 -1
- package/src/pref-viewer-2d.js +16 -4
- package/src/pref-viewer-3d-data.js +166 -0
- package/src/pref-viewer-3d.js +11 -0
- package/src/pref-viewer-dialog.js +7 -1
- package/src/pref-viewer-menu-3d.js +55 -35
- package/src/pref-viewer.js +56 -20
- package/src/render-math.js +130 -117
- package/src/svg-resolver.js +5 -4
package/README.md
CHANGED
|
@@ -274,6 +274,8 @@ Rapid selections retarget the transition from the displayed pose. Canvas pointer
|
|
|
274
274
|
|
|
275
275
|
The default orbital camera starts on the **Inner (+Z)** side before the ViewCube is created, so the loading indicator does not first show Outer and then switch to Inner when model framing completes. The initial fit uses the same model bounds and projection-aware calculation as the ViewCube, with the shared framing factor (`BabylonJSController.config.camera.fitMarginFactor`) of 0.15. The resulting fit matches a cube destination for the same orientation, projection and canvas dimensions; the initial orientation is exact Inner, with no elevation, so selecting Inner immediately after loading preserves the view. The initial fit preserves its orbit angles; later model reloads preserve the user's current orientation. Explicitly selected cameras from the model or environment retain their authored view, which may differ from Inner.
|
|
276
276
|
|
|
277
|
+
Camera selection through `options.camera` uses `null` for the interactive default camera. A named camera such as `"Inner"` is resolved from the model first, then the environment, and is locked by default. If that name is absent from both assets, the viewer activates its default camera with `locked = false` and reconnects canvas controls, even after a previously locked imported camera. A missing name does not retain the previous imported view. Omitting `options.camera` preserves the current selection.
|
|
278
|
+
|
|
277
279
|
The shared internal setting `BabylonJSController.config.camera.fitMarginFactor` accepts finite values from **0 to 0.5, inclusive**. It reserves a total fraction of the viewport along the limiting axis; the model's projected bounding envelope can occupy `1 - fitMarginFactor` of that axis. It is applied when automatic or ViewCube framing runs, rather than continuously changing the current zoom.
|
|
278
280
|
|
|
279
281
|
| fitMarginFactor | Space available for the model | Margin at each opposing edge of the limiting axis |
|
|
@@ -319,19 +321,19 @@ These are sampled ray checks, not continuous volume collision. Small surfaces be
|
|
|
319
321
|
| --- | --- |
|
|
320
322
|
| `showModel()` / `hideModel()` | Shows or hides the loaded model. |
|
|
321
323
|
| `showScene()` / `hideScene()` | Shows or hides the loaded scene/environment. |
|
|
322
|
-
| `syncVisibility(name, isVisible)` | Synchronizes `model` or `scene` visibility with the corresponding reflected attribute. |
|
|
323
324
|
| `zoomCenter()` | Centers the 2D drawing view. |
|
|
324
325
|
| `zoomExtentsAll()` | Fits all 2D drawing content in view. |
|
|
325
326
|
| `zoomIn()` / `zoomOut()` | Controls 2D drawing zoom. |
|
|
326
|
-
|
|
327
|
+
|
|
328
|
+
These zoom methods operate only in 2D mode. In 3D, use canvas navigation and the ViewCube to orient and refit the model. Switching modes retains the semantic selection snapshot.
|
|
327
329
|
|
|
328
330
|
### Semantic selection and turnable state
|
|
329
331
|
|
|
330
332
|
| Method | Description |
|
|
331
333
|
| --- | --- |
|
|
332
|
-
| `setSelectableElements(elements)` | Defines and renders selectable
|
|
333
|
-
| `setSelectedElements(elements)` | Defines and renders selected highlights in red (`#FF0000`, opacity `0.5`). Accepts strings or objects with `id`/`Id` and optional `highlights`/`Highlights`. |
|
|
334
|
-
| `applySelectionState(state)` | Applies selectable and selected state to the semantic snapshot and
|
|
334
|
+
| `setSelectableElements(elements)` | Defines and renders selectable 2D/3D ids in yellow (`#FFFF00`, opacity `0.5`). Accepts strings or objects with `id`/`Id`. |
|
|
335
|
+
| `setSelectedElements(elements)` | Defines and renders selected 2D/3D highlights in red (`#FF0000`, opacity `0.5`). Accepts strings or objects with `id`/`Id` and optional `highlights`/`Highlights`. |
|
|
336
|
+
| `applySelectionState(state)` | Applies selectable and selected state to the semantic snapshot and both renderers in one call. Accepts `selectableIds`/`selectables` and `selectedItems`/`selectedIds`/`selected`; state supplied before drawing or model load is reapplied after that renderer loads. |
|
|
335
337
|
| `clearSelectedElements()` | Clears selected elements while preserving the selectable set. |
|
|
336
338
|
| `clearSelectableElements()` | Clears selectable elements while preserving the selected set. |
|
|
337
339
|
| `clearSelectionState()` | Clears all semantic selection state. |
|
|
@@ -344,16 +346,26 @@ These are sampled ray checks, not continuous volume collision. Small surfaces be
|
|
|
344
346
|
| `toggleTurnable(options)` | Toggles semantic turnable state. |
|
|
345
347
|
| `getTurnableState()` | Returns `{ running, speed, direction }`. |
|
|
346
348
|
|
|
347
|
-
|
|
349
|
+
In 2D, selection colors are rendered through disposable SVG clones; the loaded base SVG and shared definitions are not modified. In 3D, model meshes use Babylon overlays without mutating shared materials; every mesh instance with the semantic id is updated independently. Selected ids and their highlights take red priority over yellow selectable state. Reapplying state is idempotent, and replacing a drawing or model reapplies the current semantic snapshot.
|
|
350
|
+
|
|
351
|
+
3D model loading disables Babylon's automatic glTF mesh instancing so repeated geometry can have independent overlays while materials remain shared. Environment loading retains instancing. This can increase draw calls for models with many repeated parts. Clearing selection restores the original overlay properties.
|
|
352
|
+
|
|
353
|
+
3D clicks report eligible crossed ids independently of the selectable set; the host decides which ids to select. Only a final `_primitiveN` suffix is removed, and hits are sorted by distance before duplicate ids are removed.
|
|
354
|
+
|
|
355
|
+
The 3D click threshold defaults to 6 CSS pixels and is defined by the internal `BabylonJSController.config.pointerPicking.clickMaxDistancePx` setting. Movement beyond the threshold at any point cancels the click, including drags returning to their starting point. Cancelled pointers and multi-pointer gestures emit no click. Hidden, disabled, non-pickable meshes, groups and environment geometry are excluded.
|
|
356
|
+
|
|
357
|
+
The turnable methods store and publish semantic state for host integration. They do not start a camera rotation or a render-loop animation. Hosts can read the snapshot through `getTurnableState()` or consume it in `pref-viewer-3d-turnable-changed`.
|
|
348
358
|
|
|
349
359
|
### Animation controls
|
|
350
360
|
|
|
351
361
|
| Method | Description |
|
|
352
362
|
| --- | --- |
|
|
353
363
|
| `getAnimations()` | Returns available model animations with state, progress, loop, and affected node ids. |
|
|
354
|
-
| `playAnimation(name, action)` | Controls a named animation
|
|
355
|
-
| `setAnimationProgress(name, progress)` |
|
|
356
|
-
| `setAnimationLoop(name, loop)` | Enables or disables looping for a named animation. |
|
|
364
|
+
| `playAnimation(name, action)` | Controls a named opening animation with `open`, `close`, `pause`, `goToOpened` or `goToClosed`. Returns whether the animation and action were accepted. |
|
|
365
|
+
| `setAnimationProgress(name, progress)` | Seeks to normalized progress from `0` to `1` and pauses playback. Returns `false` when the animation is absent. |
|
|
366
|
+
| `setAnimationLoop(name, loop)` | Enables or disables ping-pong looping for a named animation. Returns `false` when the animation is absent. |
|
|
367
|
+
|
|
368
|
+
Opening animations are discovered from paired glTF animation groups named `animation_open_<name>` and `animation_close_<name>`. State values are `0` (paused), `1` (closed), `2` (opened), `3` (opening) and `4` (closing). Enable `options.render.showAnimationMenu` to create the animation controller. Disabling that setting releases the controller: `getAnimations()` returns an empty array and imperative animation actions return `false`. A controller that is enabled can still receive imperative actions while its controls are closed.
|
|
357
369
|
|
|
358
370
|
### Dialogs and downloads
|
|
359
371
|
|
|
@@ -398,18 +410,18 @@ These setters are convenience bridges for framework bindings and property-based
|
|
|
398
410
|
|
|
399
411
|
## Events
|
|
400
412
|
|
|
401
|
-
The component emits custom events so host applications can integrate loading indicators, error states, and viewer controls.
|
|
413
|
+
The component emits custom events so host applications can integrate loading indicators, error states, and viewer controls. Root-dispatched lifecycle, selection-state and viewport events include `detail.timestamp` in Unix milliseconds. Generic root events also include `detail.mode`, which is always the current `<pref-viewer>` mode (`"2d"` or `"3d"`); scoped 2D/3D and legacy `drawing-*`/`scene-*` events omit it. `pref-viewer-element-click` is an exception: its detail remains exactly `{ ids: string[] }`, without mode or timestamp. Renderer events received directly from child components can have different metadata from their root forwarding.
|
|
402
414
|
|
|
403
415
|
### General Events
|
|
404
416
|
|
|
405
417
|
| Event | Emitted by | Legacy alias | Description |
|
|
406
418
|
| --- | --- | --- | --- |
|
|
407
|
-
| `pref-viewer-initialized` | `<pref-viewer>` | None | The root component
|
|
419
|
+
| `pref-viewer-initialized` | `<pref-viewer>` | None | The root component is connected and ready to process queued work. Detail includes `isInitialized`, `mode` and `timestamp`; it does not mean assets have finished loading. |
|
|
408
420
|
| `pref-viewer-loading` | `<pref-viewer>` | None | A 2D or 3D load operation has started. Includes `detail.mode` with the current root mode. |
|
|
409
421
|
| `pref-viewer-loaded` | `<pref-viewer>` | None | A 2D or 3D load operation completed. Includes `detail.mode` with the current root mode. |
|
|
410
422
|
| `pref-viewer-error` | `<pref-viewer>` | None | A 2D or 3D operation failed. Includes `detail.mode` with the current root mode. |
|
|
411
|
-
| `pref-viewer-selection-state-changed` | `<pref-viewer>` | None | The semantic selection state changed. |
|
|
412
|
-
| `pref-viewer-element-click` | `<pref-viewer>` | None | A semantic
|
|
423
|
+
| `pref-viewer-selection-state-changed` | `<pref-viewer>` | None | The semantic selection state changed. Detail includes `selectableIds`, `selectedIds`, `selectedItems`, `mode` and `timestamp`. |
|
|
424
|
+
| `pref-viewer-element-click` | `<pref-viewer>` | None | A semantic 2D or 3D element was clicked or forwarded by the host. In 2D, geometry without its own id resolves the nearest id-bearing ancestor. In 3D, a primary click that moves no more than 6 CSS pixels reports every crossed visible, enabled, pickable model mesh, nearest first, from `mesh.id`; only a final `_primitiveN` suffix is removed and duplicate ids are omitted. Empty/background clicks do not emit. `detail` is exactly `{ ids: string[] }`, independently of the current selectable set. |
|
|
413
425
|
|
|
414
426
|
### 3D Events
|
|
415
427
|
|
|
@@ -421,8 +433,8 @@ The component emits custom events so host applications can integrate loading ind
|
|
|
421
433
|
| `pref-viewer-3d-options-applying` | `<pref-viewer-3d>` | `scene-setting-options` | 3D viewer options are being applied. |
|
|
422
434
|
| `pref-viewer-3d-options-applied` | `<pref-viewer-3d>` | `scene-set-options` | 3D viewer options were applied. |
|
|
423
435
|
| `pref-viewer-3d-render-settings-change-requested` | `<pref-viewer-menu-3d>` | None | The 3D settings menu requested render-setting changes. |
|
|
424
|
-
| `pref-viewer-3d-animation-changed` |
|
|
425
|
-
| `pref-viewer-3d-turnable-changed` | `<pref-viewer>` | None | The semantic turnable state changed. |
|
|
436
|
+
| `pref-viewer-3d-animation-changed` | `<pref-viewer-3d>` via its opening animation controller | None | An opening animation state, progress or loop changed. Detail is `{ name, state, progress, loop }`. The event bubbles across the shadow boundary. |
|
|
437
|
+
| `pref-viewer-3d-turnable-changed` | `<pref-viewer>` | None | The semantic turnable state changed. Detail includes `state: { running, speed, direction }` and `timestamp`. |
|
|
426
438
|
|
|
427
439
|
### 2D Events
|
|
428
440
|
|
|
@@ -435,7 +447,7 @@ The component emits custom events so host applications can integrate loading ind
|
|
|
435
447
|
|
|
436
448
|
### Internal Listeners
|
|
437
449
|
|
|
438
|
-
`<pref-viewer>` listens to `pref-viewer-2d-viewport-changed` from `<pref-viewer-2d>`, `pref-viewer-3d-loaded` and `pref-viewer-3d-error` from `<pref-viewer-3d>`, and `pref-viewer-3d-render-settings-change-requested` from `<pref-viewer-menu-3d>`. `BabylonJSController` listens to `pref-viewer-3d-animation-changed` from the nearest `<pref-viewer-3d>` host. The root also conditionally subscribes to the internal, non-bubbling `view-cube-layout` event while the cube is active in 3D. It carries measured layout dimensions for the render menu and is not a new public root event.
|
|
450
|
+
`<pref-viewer>` listens to `pref-viewer-2d-viewport-changed` from `<pref-viewer-2d>`, `pref-viewer-3d-loaded` and `pref-viewer-3d-error` from `<pref-viewer-3d>`, and `pref-viewer-3d-render-settings-change-requested` from `<pref-viewer-menu-3d>`. Internal `pref-viewer-2d-element-click` and `pref-viewer-3d-element-click` events are intercepted and forwarded through the single public `pref-viewer-element-click` contract. `BabylonJSController` listens to `pref-viewer-3d-animation-changed` from the nearest `<pref-viewer-3d>` host. The root also conditionally subscribes to the internal, non-bubbling `view-cube-layout` event while the cube is active in 3D. It carries measured layout dimensions for the render menu and is not a new public root event.
|
|
439
451
|
|
|
440
452
|
Example:
|
|
441
453
|
|
|
@@ -485,6 +497,8 @@ viewer.setOptions({
|
|
|
485
497
|
|
|
486
498
|
Render settings are persisted in `localStorage` under the `pref-viewer/render-settings` key when changed through the runtime settings flow. The menu is always synchronized from the effective controller settings after changes from the menu, `setOptions`, or `loadConfig`.
|
|
487
499
|
|
|
500
|
+
The showroom daylight background uses a shared linear HDR color calibrated with exposure and ACES tone mapping for the current light studio gray target, `#d9d9d9`, in a scene-less view. Both the static look and time-of-day cycle use this same daylight value; the cycle retains its dark night endpoint. Scene geometry can cover the background. The internal `BabylonJSController.config.look.showroom.backgroundDay` value is not a public background-color option and must be recalibrated if tone mapping or exposure changes.
|
|
501
|
+
|
|
488
502
|
## Runtime styles
|
|
489
503
|
|
|
490
504
|
Runtime styles can be passed through the `styles` attribute, the `styles` property setter, `setStyles(styles)`, or `loadConfig({ styles })`.
|
|
@@ -540,6 +554,18 @@ npm start
|
|
|
540
554
|
|
|
541
555
|
The playground is available from `playground/index.html` at `http://localhost:8080/` and serves the project through `http-server` after bundling the local source. Use it for manual ViewCube, wheel-zoom and modal checks with representative model/environment assets. The separate ViewCube test HTML/script and its generated fixture bundle have been removed; automated tests remain.
|
|
542
556
|
|
|
557
|
+
### 3D selection implementation and maintenance
|
|
558
|
+
|
|
559
|
+
`src/babylonjs-selection-controller.js` encapsulates semantic picking helpers as public static methods of `BabylonJSSelectionController`: `normalizeMeshId`, `isPrimaryClickGesture`, `isPickableModelMesh`, `getModelMeshSet` and `collectPickedMeshIds`. The Babylon controller and tests call these methods through the class. Instance methods retain selection state and manage reversible overlays. These internal implementation helpers do not change the `<pref-viewer>` public API.
|
|
560
|
+
|
|
561
|
+
JavaScript classes, methods and named helper functions outside tests are documented with JSDoc, including parameters and return values where applicable. This also covers existing helpers and webpack configuration callbacks. Keep those comments and this README aligned with the current implementation. Edited code files use Windows CRLF line endings.
|
|
562
|
+
|
|
563
|
+
Shared calculations live in the static `RenderMath` and `MaterialUtils` classes. `I18n` owns the shared locale and translation subscribers, with private lookup helpers and public static methods. These modules export their classes only; viewer components and tests call their static methods directly. Former named function/constant exports and utility reexports from `BabylonJSController` have been removed. These helpers are internal modules, not additional package-root API exports. `BabylonJSController` encapsulates resize application, metadata path parsing and debug serialization as private methods, passing scene mesh snapshots explicitly to material diagnostics.
|
|
564
|
+
|
|
565
|
+
`BabylonJSAnimationOverlayController` owns one opening-highlight group: its utility scene, cached overlay bases, transformed presentation meshes and observers. It creates clones directly in the utility scene, hides geometry bases used only by instances, and releases only its own resources through an idempotent `dispose()`. These animation overlays remain separate from semantic 3D selection overlays. The temporary legacy storage module and opening-control callbacks retain their existing structure.
|
|
566
|
+
|
|
567
|
+
Focused regressions for this work live in `tests/babylonjs-selection-controller.test.js`, `tests/babylonjs-animation-overlay-controller.test.js`, `tests/encapsulated-utilities.test.js`, `tests/pref-viewer-menu-3d.test.js`, `tests/babylonjs-controller-wheel.test.js` and `tests/pref-viewer.test.js`. They cover picking and repeated model parts, reversible overlays, root state synchronization, animation resource ownership, localization, menu playback and diagnostics. Use `npm test -- --run --maxWorkers=2` for a bounded full-suite run; validate both builds and actual rendering when changing overlays.
|
|
568
|
+
|
|
543
569
|
### Camera implementation and maintenance
|
|
544
570
|
|
|
545
571
|
- `src/babylonjs-view-cube-controller.js`: dedicated `BabylonJSViewCubeController` for SVG regions, orientation/projection feedback, transitions and cleanup. It delegates model bounds and destination-fit calculations to BabylonJSCameraFraming. Cube-only helpers and constants remain private; region generation remains a public static utility. Its constructor JSDoc documents the host object and callbacks.
|
|
@@ -572,6 +598,8 @@ The following variables are defined in the root rule of `PrefViewerViewCubeStyle
|
|
|
572
598
|
|
|
573
599
|
Both feedback colors default to `var(--pref-viewer-color-primary, ...)`, preserving supported runtime primary-color updates. Text size and spacing apply in the SVG coordinate system and scale with its viewport. Active-view borders remain transparent; hover borders remain fully opaque regardless of fill opacity. Geometry and input behavior remain owned by the controller.
|
|
574
600
|
|
|
601
|
+
ViewCube listener registration and hover updates are private instance methods. Surface creation, region construction, hit-region lookup, extent interpolation and SVG projection are private static helpers; per-update projection values and interpolation progress are passed explicitly. The render menu also encapsulates hue-channel calculation and lighting playback as private methods. Playback uses one callback bound to the menu instance and cancels its scheduled frame on stop or disconnection.
|
|
602
|
+
|
|
575
603
|
## Package scripts
|
|
576
604
|
|
|
577
605
|
| Script | Description |
|
|
@@ -607,11 +635,15 @@ export function ProductViewer() {
|
|
|
607
635
|
- The component is a Web Component, so it can be used from plain HTML, React, Vue, Angular, or any framework that supports custom elements.
|
|
608
636
|
- Give the element an explicit width and height. Babylon.js needs a measurable canvas container.
|
|
609
637
|
- Use object-based JavaScript APIs for complex payloads. Attributes must be valid JSON strings.
|
|
638
|
+
- Root load/configuration methods enqueue work and return `void` on accepted input, rather than a completion promise. Use the loading/loaded/error events for completion. Malformed JSON strings can throw synchronously.
|
|
639
|
+
- `loadConfig({ forceReload: true })` bypasses 3D asset cache metadata checks. A supplied `timestamp` that differs from the last supplied timestamp also forces the 3D reload; repeating the same value does not. These controls apply to the 3D configuration flow, not SVG cache invalidation.
|
|
610
640
|
- Remote assets must be reachable by the browser and must satisfy the expected CORS policy.
|
|
611
641
|
- glTF files with external resources are resolved and cached by the viewer when possible.
|
|
612
642
|
- Avoid repeatedly replacing large assets if only visibility, camera, lighting, render, or material options changed.
|
|
613
643
|
- Use `pref-viewer-initialized` only as a component-readiness signal. If a host generates model data asynchronously, wait for the host data-ready signal before calling `loadConfig()`.
|
|
614
644
|
- For PrefWeb integration, consume the webpack UMD build (`bundle-umd.js`) and prefer development UMD builds while debugging source maps.
|
|
645
|
+
- Babylon's initial PBR BRDF preparation keeps demand rendering awake and waits at most two seconds, so a missing readiness signal does not indefinitely block a queued model load. `getDebugInfo()` provides camera, rendering and asset-readiness diagnostics.
|
|
646
|
+
- Release versioning and npm publication run in Bitbucket Pipelines: `develop` publishes the `beta` tag and `main` publishes `latest`. Do not run the release scripts locally.
|
|
615
647
|
|
|
616
648
|
## License
|
|
617
649
|
|
package/index.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ export interface PrefViewerSelectionInput {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export interface PrefViewerElementClickDetail {
|
|
97
|
+
/** Semantic 2D ids, or distance-ordered, deduplicated 3D mesh ids with final _primitiveN removed. */
|
|
97
98
|
ids: string[];
|
|
98
99
|
}
|
|
99
100
|
|
|
@@ -271,7 +272,7 @@ export declare class PrefViewer extends HTMLElement {
|
|
|
271
272
|
zoomIn(): void;
|
|
272
273
|
zoomOut(): void;
|
|
273
274
|
|
|
274
|
-
// ── Semantic selection ──
|
|
275
|
+
// ── Semantic selection synchronized to both 2D and 3D renderers ──
|
|
275
276
|
setSelectableElements(elements?: PrefViewerElementDescriptor[]): PrefViewerSelectionState;
|
|
276
277
|
setSelectedElements(elements?: Array<string | PrefViewerSelectionDescriptor>): PrefViewerSelectionState;
|
|
277
278
|
applySelectionState(state?: PrefViewerSelectionInput): PrefViewerSelectionState;
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { AssetContainer, Color3, HighlightLayer,
|
|
1
|
+
import { AssetContainer, Color3, HighlightLayer, Mesh, PickingInfo, Scene } from "@babylonjs/core";
|
|
2
2
|
import { PrefViewerStyleVariables } from "./styles.js";
|
|
3
3
|
import OpeningAnimation from "./babylonjs-animation-opening.js";
|
|
4
|
+
import BabylonJSAnimationOverlayController from "./babylonjs-animation-overlay-controller.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* BabylonJSAnimationController - Manages animation playback, highlighting, and interactive controls for animated nodes in Babylon.js scenes.
|
|
@@ -48,7 +49,7 @@ export default class BabylonJSAnimationController {
|
|
|
48
49
|
#useHighlightLayer = true;
|
|
49
50
|
#lastHighlightedMeshId = null;
|
|
50
51
|
#lastHighlightedNodeIds = [];
|
|
51
|
-
|
|
52
|
+
|
|
52
53
|
// Static property to store global highlight enabled state
|
|
53
54
|
static #globalHighlightEnabled = true;
|
|
54
55
|
static #instanceId = 0;
|
|
@@ -70,6 +71,7 @@ export default class BabylonJSAnimationController {
|
|
|
70
71
|
/**
|
|
71
72
|
* Detects and stores animatable objects and animated nodes in the scene.
|
|
72
73
|
* @private
|
|
74
|
+
* @returns {void}
|
|
73
75
|
*/
|
|
74
76
|
#initializeAnimations() {
|
|
75
77
|
this.hideMenu(); // Clean up any existing menus
|
|
@@ -83,6 +85,7 @@ export default class BabylonJSAnimationController {
|
|
|
83
85
|
/**
|
|
84
86
|
* Collects the IDs of all nodes targeted by any animation group in the scene.
|
|
85
87
|
* @private
|
|
88
|
+
* @returns {void}
|
|
86
89
|
*/
|
|
87
90
|
#getAnimatedNodes() {
|
|
88
91
|
this.#assetContainer.animationGroups.forEach((animationGroup) => {
|
|
@@ -103,6 +106,7 @@ export default class BabylonJSAnimationController {
|
|
|
103
106
|
* @private
|
|
104
107
|
* @description
|
|
105
108
|
* Uses animation group names with the pattern "animation_open_<name>" and "animation_close_<name>".
|
|
109
|
+
* @returns {void}
|
|
106
110
|
*/
|
|
107
111
|
#getOpeningAnimations() {
|
|
108
112
|
const openings = {};
|
|
@@ -131,7 +135,7 @@ export default class BabylonJSAnimationController {
|
|
|
131
135
|
* Finds the OpeningAnimation instance associated with a given node ID.
|
|
132
136
|
* @private
|
|
133
137
|
* @param {string} nodeId - The node identifier.
|
|
134
|
-
* @returns {OpeningAnimation|
|
|
138
|
+
* @returns {OpeningAnimation|undefined} The matching OpeningAnimation instance, or undefined when absent.
|
|
135
139
|
*/
|
|
136
140
|
#getOpeningAnimationByNode(nodeId) {
|
|
137
141
|
return this.#openingAnimations.find((openingAnimation) => openingAnimation.isAnimationForNode(nodeId));
|
|
@@ -172,163 +176,7 @@ export default class BabylonJSAnimationController {
|
|
|
172
176
|
* - Removes overlay when mouse moves away from highlighted meshes
|
|
173
177
|
*/
|
|
174
178
|
#addGroupOutlineOverlayForInstances(meshes) {
|
|
175
|
-
|
|
176
|
-
const OUTLINE_ENABLED = false; // Draw outline on individual meshes
|
|
177
|
-
const OUTLINE_COLOR = this.#highlightColor;
|
|
178
|
-
const OUTLINE_WIDTH = 1.5;
|
|
179
|
-
const OVERLAY_ENABLED = true; // Draw semi-transparent fill
|
|
180
|
-
const OVERLAY_COLOR = this.#highlightColor;
|
|
181
|
-
const OVERLAY_ALPHA = 0.25;
|
|
182
|
-
const RENDERING_GROUP_ID = 0; // MUST be > 0 to render on top of main scene
|
|
183
|
-
|
|
184
|
-
// Use UtilityLayerRenderer for rendering
|
|
185
|
-
const utilityLayer = new UtilityLayerRenderer(this.#scene, undefined, undefined);
|
|
186
|
-
const uScene = utilityLayer.utilityLayerScene;
|
|
187
|
-
|
|
188
|
-
// Cache map: baseMesh -> overlayBaseMesh to reuse for InstancedMeshes
|
|
189
|
-
const baseToOverlayBase = new Map();
|
|
190
|
-
|
|
191
|
-
// Pairs of { original mesh, overlay mesh } for tracking
|
|
192
|
-
const pairs = [];
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Gets the base mesh from either a regular mesh or an InstancedMesh
|
|
196
|
-
* @param {Mesh} mesh - The mesh to check
|
|
197
|
-
* @returns {Mesh} The base mesh or the mesh itself
|
|
198
|
-
*/
|
|
199
|
-
function getBaseMesh(mesh) {
|
|
200
|
-
return mesh instanceof InstancedMesh ? mesh.sourceMesh : mesh;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Creates or retrieves an overlay base mesh in the utility scene
|
|
205
|
-
* Reuses the same base for all instances of the same source mesh
|
|
206
|
-
* @param {Mesh} baseMesh - The base mesh to clone
|
|
207
|
-
* @returns {Mesh} The overlay base mesh
|
|
208
|
-
*/
|
|
209
|
-
function ensureOverlayBase(baseMesh) {
|
|
210
|
-
let overlayBase = baseToOverlayBase.get(baseMesh);
|
|
211
|
-
if (overlayBase) {
|
|
212
|
-
return overlayBase;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Clone the source mesh into the utility scene (deep clone materials/geometry)
|
|
216
|
-
overlayBase = baseMesh.clone(baseMesh.name + "_overlayBase", null, true, false);
|
|
217
|
-
overlayBase._scene = uScene;
|
|
218
|
-
overlayBase.isPickable = false;
|
|
219
|
-
overlayBase.setEnabled(true);
|
|
220
|
-
|
|
221
|
-
// Configure visual appearance for the overlay
|
|
222
|
-
overlayBase.renderOutline = OUTLINE_ENABLED; // Only draw outline if enabled
|
|
223
|
-
overlayBase.outlineColor = OUTLINE_COLOR;
|
|
224
|
-
overlayBase.outlineWidth = OUTLINE_WIDTH;
|
|
225
|
-
|
|
226
|
-
// Overlay color and transparency (semi-transparent colored fill)
|
|
227
|
-
overlayBase.renderOverlay = OVERLAY_ENABLED;
|
|
228
|
-
overlayBase.overlayColor = OVERLAY_COLOR;
|
|
229
|
-
overlayBase.overlayAlpha = OVERLAY_ALPHA;
|
|
230
|
-
|
|
231
|
-
overlayBase.visibility = 1;
|
|
232
|
-
|
|
233
|
-
// Ensure always-on-top rendering or not
|
|
234
|
-
overlayBase.renderingGroupId = RENDERING_GROUP_ID;
|
|
235
|
-
|
|
236
|
-
baseToOverlayBase.set(baseMesh, overlayBase);
|
|
237
|
-
return overlayBase;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Create overlay meshes for each input mesh
|
|
241
|
-
meshes.forEach((mesh) => {
|
|
242
|
-
// Skip meshes without geometry
|
|
243
|
-
if (!mesh.geometry && !mesh.getChildMeshes) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const baseMesh = getBaseMesh(mesh);
|
|
248
|
-
const overlayBase = ensureOverlayBase(baseMesh);
|
|
249
|
-
|
|
250
|
-
let overlay;
|
|
251
|
-
if (mesh instanceof InstancedMesh) {
|
|
252
|
-
// For instances, create a new instance of the overlay base
|
|
253
|
-
overlay = overlayBase.createInstance(mesh.name + "_overlayInst");
|
|
254
|
-
overlay._scene = uScene;
|
|
255
|
-
overlay.isPickable = false;
|
|
256
|
-
} else {
|
|
257
|
-
// For regular meshes, use the overlay base directly (avoids double cloning)
|
|
258
|
-
overlay = overlayBase;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Remove parent to allow independent world matrix control
|
|
262
|
-
overlay.parent = null;
|
|
263
|
-
overlay.renderingGroupId = RENDERING_GROUP_ID;
|
|
264
|
-
|
|
265
|
-
pairs.push({ original: mesh, overlay });
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Sync world matrix transformations
|
|
269
|
-
const pos = new Vector3();
|
|
270
|
-
const scl = new Vector3();
|
|
271
|
-
const rot = new Quaternion();
|
|
272
|
-
|
|
273
|
-
// Track all observers for cleanup
|
|
274
|
-
const observers = [];
|
|
275
|
-
|
|
276
|
-
// Subscribe to world matrix update events
|
|
277
|
-
// This is more efficient than updating every frame
|
|
278
|
-
pairs.forEach(({ original, overlay }) => {
|
|
279
|
-
// Skip if mesh has no geometry to sync
|
|
280
|
-
if (!original.getWorldMatrix) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Initial sync: copy the current world matrix
|
|
285
|
-
original.computeWorldMatrix(true);
|
|
286
|
-
original.getWorldMatrix().decompose(scl, rot, pos);
|
|
287
|
-
|
|
288
|
-
overlay.position.copyFrom(pos);
|
|
289
|
-
overlay.scaling.copyFrom(scl);
|
|
290
|
-
overlay.rotationQuaternion = overlay.rotationQuaternion || new Quaternion();
|
|
291
|
-
overlay.rotationQuaternion.copyFrom(rot);
|
|
292
|
-
overlay.computeWorldMatrix(true);
|
|
293
|
-
|
|
294
|
-
// Subscribe to world matrix changes
|
|
295
|
-
// Only updates when the original mesh's transformation actually changes
|
|
296
|
-
const obs = original.onAfterWorldMatrixUpdateObservable?.add(() => {
|
|
297
|
-
original.getWorldMatrix().decompose(scl, rot, pos);
|
|
298
|
-
|
|
299
|
-
overlay.position.copyFrom(pos);
|
|
300
|
-
overlay.scaling.copyFrom(scl);
|
|
301
|
-
overlay.rotationQuaternion.copyFrom(rot);
|
|
302
|
-
overlay.computeWorldMatrix(true);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
if (obs) {
|
|
306
|
-
observers.push({ mesh: original, observer: obs });
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// Cleanup API
|
|
311
|
-
return {
|
|
312
|
-
dispose() {
|
|
313
|
-
// Remove world matrix update observers
|
|
314
|
-
observers.forEach(({ mesh, observer }) => {
|
|
315
|
-
mesh.onAfterWorldMatrixUpdateObservable?.remove(observer);
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// Dispose overlay instances and meshes
|
|
319
|
-
pairs.forEach(({ original, overlay }) => {
|
|
320
|
-
overlay.dispose();
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// Dispose overlay base meshes
|
|
324
|
-
baseToOverlayBase.forEach((overlayBase) => {
|
|
325
|
-
overlayBase.dispose();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
// Dispose utility layer scene/resources; without this each rebuild leaks GPU/JS resources.
|
|
329
|
-
utilityLayer.dispose();
|
|
330
|
-
},
|
|
331
|
-
};
|
|
179
|
+
return new BabylonJSAnimationOverlayController(this.#scene, meshes, this.#highlightColor);
|
|
332
180
|
}
|
|
333
181
|
|
|
334
182
|
/**
|
|
@@ -430,6 +278,7 @@ export default class BabylonJSAnimationController {
|
|
|
430
278
|
* Cleans up the highlight layer, animation menu, and internal animation/node lists.
|
|
431
279
|
* Should be called when the controller is no longer needed to prevent memory leaks.
|
|
432
280
|
* @public
|
|
281
|
+
* @returns {void}
|
|
433
282
|
*/
|
|
434
283
|
dispose() {
|
|
435
284
|
if (this.#highlightLayer) {
|
|
@@ -476,14 +325,14 @@ export default class BabylonJSAnimationController {
|
|
|
476
325
|
}
|
|
477
326
|
return { changed: false, highlighted: false };
|
|
478
327
|
}
|
|
479
|
-
|
|
328
|
+
|
|
480
329
|
let changed = false;
|
|
481
330
|
let highlighted = false;
|
|
482
331
|
|
|
483
332
|
// Check if we're hovering the same mesh to avoid recreating overlays/highlights
|
|
484
333
|
const pickedMeshId = !pickingInfo?.hit || !pickingInfo?.pickedMesh?.id ? null : pickingInfo.pickedMesh.id;
|
|
485
334
|
const pickedMesh = pickingInfo?.pickedMesh ? pickingInfo.pickedMesh : null;
|
|
486
|
-
|
|
335
|
+
|
|
487
336
|
if (this.#lastHighlightedMeshId === pickedMeshId) {
|
|
488
337
|
// No need to update if hovering the same mesh
|
|
489
338
|
if (this.#lastHighlightedMeshId !== null) {
|
|
@@ -651,7 +500,7 @@ export default class BabylonJSAnimationController {
|
|
|
651
500
|
this.hideMenu();
|
|
652
501
|
return;
|
|
653
502
|
}
|
|
654
|
-
|
|
503
|
+
|
|
655
504
|
const openingAnimations = [];
|
|
656
505
|
nodeIds.forEach((nodeId) => {
|
|
657
506
|
const openingAnimation = this.#getOpeningAnimationByNode(nodeId);
|
|
@@ -117,11 +117,12 @@ export default class OpeningAnimationMenu {
|
|
|
117
117
|
* Sets button appearance and attaches the callback for user interaction.
|
|
118
118
|
* @private
|
|
119
119
|
* @param {string} name - Button identifier.
|
|
120
|
-
* @param {string}
|
|
120
|
+
* @param {string} imageSVG - SVG markup for the button icon.
|
|
121
121
|
* @param {boolean} enabled - Whether the button is enabled.
|
|
122
122
|
* @param {boolean} active - Whether the button is visually active.
|
|
123
123
|
* @param {boolean} visible - Whether the button is visible.
|
|
124
124
|
* @param {function} callback - Callback to invoke on button click.
|
|
125
|
+
* @returns {void}
|
|
125
126
|
*/
|
|
126
127
|
#addButton(name, imageSVG, enabled = true, active = false, visible = true, callback) {
|
|
127
128
|
const stateClass = active ? "active" : enabled ? "enabled" : "disabled";
|
|
@@ -183,6 +184,7 @@ export default class OpeningAnimationMenu {
|
|
|
183
184
|
/**
|
|
184
185
|
* Creates all control buttons and sets their initial states.
|
|
185
186
|
* @private
|
|
187
|
+
* @returns {void}
|
|
186
188
|
*/
|
|
187
189
|
#createButtons() {
|
|
188
190
|
const buttonsState = this.#getButtonsState();
|
|
@@ -198,6 +200,7 @@ export default class OpeningAnimationMenu {
|
|
|
198
200
|
/**
|
|
199
201
|
* Creates and configures the animation progress slider.
|
|
200
202
|
* @private
|
|
203
|
+
* @returns {void}
|
|
201
204
|
*/
|
|
202
205
|
#createSlider() {
|
|
203
206
|
this.#slider = document.createElement("input");
|
|
@@ -216,6 +219,7 @@ export default class OpeningAnimationMenu {
|
|
|
216
219
|
/**
|
|
217
220
|
* Initializes and renders the menu UI, including buttons and slider.
|
|
218
221
|
* @private
|
|
222
|
+
* @returns {void}
|
|
219
223
|
*/
|
|
220
224
|
#createMenu() {
|
|
221
225
|
this.#containerMain = document.createElement("div");
|
|
@@ -334,6 +338,7 @@ export default class OpeningAnimationMenu {
|
|
|
334
338
|
* @param {boolean} enabled
|
|
335
339
|
* @param {boolean} active
|
|
336
340
|
* @param {boolean} visible
|
|
341
|
+
* @returns {void}
|
|
337
342
|
*/
|
|
338
343
|
#setButtonState(name, enabled, active, visible = true) {
|
|
339
344
|
const button = this.#getButtonByName(name);
|
|
@@ -350,6 +355,7 @@ export default class OpeningAnimationMenu {
|
|
|
350
355
|
/**
|
|
351
356
|
* Updates all loop control buttons according to current loop mode.
|
|
352
357
|
* @private
|
|
358
|
+
* @returns {void}
|
|
353
359
|
*/
|
|
354
360
|
#setLoopButtonsState() {
|
|
355
361
|
const buttonsState = this.#getLoopButtonsState();
|
|
@@ -360,6 +366,7 @@ export default class OpeningAnimationMenu {
|
|
|
360
366
|
/**
|
|
361
367
|
* Updates all playback control buttons according to current animation state.
|
|
362
368
|
* @private
|
|
369
|
+
* @returns {void}
|
|
363
370
|
*/
|
|
364
371
|
#setPlayerButtonsState() {
|
|
365
372
|
const buttonsState = this.#getPlayerButtonsState();
|
|
@@ -108,6 +108,11 @@ export default class OpeningAnimation {
|
|
|
108
108
|
this.#goToClosed();
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Binds opening and closing completion handlers once for stable subscription and removal.
|
|
113
|
+
* @private
|
|
114
|
+
* @returns {void}
|
|
115
|
+
*/
|
|
111
116
|
#bindHandlers() {
|
|
112
117
|
this.#handlers.onOpened = this.#onOpened.bind(this);
|
|
113
118
|
this.#handlers.onClosed = this.#onClosed.bind(this);
|
|
@@ -118,6 +123,7 @@ export default class OpeningAnimation {
|
|
|
118
123
|
* Collects node IDs affected by the opening and closing animation groups.
|
|
119
124
|
* Populates the #nodes array with unique node identifiers.
|
|
120
125
|
* @private
|
|
126
|
+
* @returns {void}
|
|
121
127
|
*/
|
|
122
128
|
#getNodesFromAnimationGroups() {
|
|
123
129
|
[this.#openAnimation, this.#closeAnimation].forEach((animationGroup) => {
|
|
@@ -132,6 +138,7 @@ export default class OpeningAnimation {
|
|
|
132
138
|
/**
|
|
133
139
|
* Handles the end of the opening animation group.
|
|
134
140
|
* @private
|
|
141
|
+
* @returns {void}
|
|
135
142
|
*/
|
|
136
143
|
#onOpened() {
|
|
137
144
|
this.#goToOpened(true);
|
|
@@ -140,6 +147,7 @@ export default class OpeningAnimation {
|
|
|
140
147
|
/**
|
|
141
148
|
* Handles the end of the closing animation group.
|
|
142
149
|
* @private
|
|
150
|
+
* @returns {void}
|
|
143
151
|
*/
|
|
144
152
|
#onClosed() {
|
|
145
153
|
this.#goToClosed(true);
|
|
@@ -149,6 +157,7 @@ export default class OpeningAnimation {
|
|
|
149
157
|
* Moves the animation to the fully opened state, optionally looping to close.
|
|
150
158
|
* @private
|
|
151
159
|
* @param {boolean} useLoop - If true, starts closing animation after opening.
|
|
160
|
+
* @returns {void}
|
|
152
161
|
*/
|
|
153
162
|
#goToOpened(useLoop = false) {
|
|
154
163
|
this.#lastPausedFrame = this.#endFrame;
|
|
@@ -178,6 +187,7 @@ export default class OpeningAnimation {
|
|
|
178
187
|
* Moves the animation to the fully closed state, optionally looping to open.
|
|
179
188
|
* @private
|
|
180
189
|
* @param {boolean} useLoop - If true, starts opening animation after closing.
|
|
190
|
+
* @returns {void}
|
|
181
191
|
*/
|
|
182
192
|
#goToClosed(useLoop = false) {
|
|
183
193
|
this.#lastPausedFrame = this.#startFrame;
|
|
@@ -207,6 +217,7 @@ export default class OpeningAnimation {
|
|
|
207
217
|
* Moves the animation to a specific frame and pauses.
|
|
208
218
|
* @private
|
|
209
219
|
* @param {number} frame - The frame to go to and pause.
|
|
220
|
+
* @returns {void}
|
|
210
221
|
*/
|
|
211
222
|
#goToFrameAndPause(frame) {
|
|
212
223
|
if (!frame) {
|
|
@@ -350,6 +361,7 @@ export default class OpeningAnimation {
|
|
|
350
361
|
/**
|
|
351
362
|
* Updates the slider value in the animation control menu to match the current progress.
|
|
352
363
|
* @private
|
|
364
|
+
* @returns {void}
|
|
353
365
|
*/
|
|
354
366
|
#updateControlsSlider() {
|
|
355
367
|
if (!this.isControlsVisible()) {
|
|
@@ -362,6 +374,7 @@ export default class OpeningAnimation {
|
|
|
362
374
|
* Broadcasts the latest animation state and, when the menu is visible, syncs its controls
|
|
363
375
|
* (buttons and progress slider) with the current state/progress values.
|
|
364
376
|
* @private
|
|
377
|
+
* @returns {void}
|
|
365
378
|
*/
|
|
366
379
|
#updateControls() {
|
|
367
380
|
this.#notifyStateChange();
|
|
@@ -412,6 +425,7 @@ export default class OpeningAnimation {
|
|
|
412
425
|
/**
|
|
413
426
|
* Starts the opening animation.
|
|
414
427
|
* @public
|
|
428
|
+
* @returns {void}
|
|
415
429
|
*/
|
|
416
430
|
playOpen() {
|
|
417
431
|
if (this.#state === OpeningAnimation.states.opening || this.#state === OpeningAnimation.states.opened) {
|
|
@@ -437,6 +451,7 @@ export default class OpeningAnimation {
|
|
|
437
451
|
/**
|
|
438
452
|
* Starts the closing animation.
|
|
439
453
|
* @public
|
|
454
|
+
* @returns {void}
|
|
440
455
|
*/
|
|
441
456
|
playClose() {
|
|
442
457
|
if (this.#state === OpeningAnimation.states.closing || this.#state === OpeningAnimation.states.closed) {
|
|
@@ -462,6 +477,7 @@ export default class OpeningAnimation {
|
|
|
462
477
|
/**
|
|
463
478
|
* Pauses the current animation.
|
|
464
479
|
* @public
|
|
480
|
+
* @returns {void}
|
|
465
481
|
*/
|
|
466
482
|
pause() {
|
|
467
483
|
if (this.#state === OpeningAnimation.states.opening) {
|
|
@@ -479,6 +495,7 @@ export default class OpeningAnimation {
|
|
|
479
495
|
/**
|
|
480
496
|
* Moves animation to the fully opened state.
|
|
481
497
|
* @public
|
|
498
|
+
* @returns {void}
|
|
482
499
|
*/
|
|
483
500
|
goToOpened() {
|
|
484
501
|
this.#goToOpened(false);
|
|
@@ -487,6 +504,7 @@ export default class OpeningAnimation {
|
|
|
487
504
|
/**
|
|
488
505
|
* Moves animation to the fully closed state.
|
|
489
506
|
* @public
|
|
507
|
+
* @returns {void}
|
|
490
508
|
*/
|
|
491
509
|
goToClosed() {
|
|
492
510
|
this.#goToClosed(false);
|
|
@@ -498,6 +516,7 @@ export default class OpeningAnimation {
|
|
|
498
516
|
* @public
|
|
499
517
|
* @param {HTMLCanvasElement} canvas - The canvas element for rendering.
|
|
500
518
|
* @param {Array<OpeningAnimation>} openingAnimations - Array of OpeningAnimation instances to manage.
|
|
519
|
+
* @returns {void}
|
|
501
520
|
*/
|
|
502
521
|
showControls(canvas, openingAnimations = []) {
|
|
503
522
|
const controlCallbacks = {
|
|
@@ -562,6 +581,7 @@ export default class OpeningAnimation {
|
|
|
562
581
|
/**
|
|
563
582
|
* Hides the animation control menu and removes observers.
|
|
564
583
|
* @public
|
|
584
|
+
* @returns {void}
|
|
565
585
|
*/
|
|
566
586
|
hideControls() {
|
|
567
587
|
if (!this.isControlsVisible()) {
|
|
@@ -590,7 +610,7 @@ export default class OpeningAnimation {
|
|
|
590
610
|
/**
|
|
591
611
|
* Returns the current animation state.
|
|
592
612
|
* @public
|
|
593
|
-
* @returns {number}
|
|
613
|
+
* @returns {number} 0 paused, 1 closed, 2 opened, 3 opening or 4 closing.
|
|
594
614
|
*/
|
|
595
615
|
get state() {
|
|
596
616
|
return this.#state;
|