@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.
@@ -0,0 +1,145 @@
1
+ import { InstancedMesh, Mesh, Quaternion, UtilityLayerRenderer, Vector3 } from "@babylonjs/core";
2
+
3
+ /**
4
+ * Owns one animated-mesh overlay group, its utility scene and transform observers.
5
+ * Groups retain independent caches and release only their own rendering resources.
6
+ * @class BabylonJSAnimationOverlayController
7
+ */
8
+ export default class BabylonJSAnimationOverlayController {
9
+ #utilityLayer;
10
+ #color;
11
+ #baseToOverlayBase = new Map();
12
+ #pairs = [];
13
+ #observers = [];
14
+ #position = new Vector3();
15
+ #scale = new Vector3();
16
+ #rotation = new Quaternion();
17
+ #disposed = false;
18
+
19
+ /**
20
+ * Creates presentation meshes and follows their original world transforms.
21
+ * @public
22
+ * @param {import("@babylonjs/core").Scene} scene - Host scene used by the utility renderer.
23
+ * @param {Array<import("@babylonjs/core").AbstractMesh>} meshes - Original meshes to highlight.
24
+ * @param {import("@babylonjs/core").Color3} color - Highlight color for the group.
25
+ */
26
+ constructor(scene, meshes, color) {
27
+ this.#color = color;
28
+ this.#utilityLayer = new UtilityLayerRenderer(scene, undefined, undefined);
29
+ for (const mesh of meshes) {
30
+ this.#createOverlay(mesh);
31
+ }
32
+ for (const pair of this.#pairs) {
33
+ if (!pair.original.getWorldMatrix) {
34
+ continue;
35
+ }
36
+ pair.original.computeWorldMatrix(true);
37
+ this.#syncPair(pair);
38
+ const observer = pair.original.onAfterWorldMatrixUpdateObservable?.add(() => this.#syncPair(pair));
39
+ if (observer) {
40
+ this.#observers.push({ mesh: pair.original, observer });
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Resolves the source geometry mesh for an instance or returns a regular mesh unchanged.
47
+ * @private
48
+ * @static
49
+ * @param {import("@babylonjs/core").AbstractMesh} mesh - Original mesh.
50
+ * @returns {import("@babylonjs/core").Mesh} Mesh supplying the overlay geometry.
51
+ */
52
+ static #getBaseMesh(mesh) {
53
+ return mesh instanceof InstancedMesh ? mesh.sourceMesh : mesh;
54
+ }
55
+
56
+ /**
57
+ * Creates or reuses the group's presentation base for a source mesh.
58
+ * @private
59
+ * @param {import("@babylonjs/core").Mesh} baseMesh - Source mesh to clone.
60
+ * @returns {import("@babylonjs/core").Mesh} Cached overlay base.
61
+ */
62
+ #ensureOverlayBase(baseMesh) {
63
+ let overlayBase = this.#baseToOverlayBase.get(baseMesh);
64
+ if (overlayBase) {
65
+ return overlayBase;
66
+ }
67
+ // Register the clone in its owning scene from creation; changing _scene later
68
+ // leaves Babylon's node and root-node registries in the original scene.
69
+ overlayBase = new Mesh(baseMesh.name + "_overlayBase", this.#utilityLayer.utilityLayerScene, null, baseMesh, true, false);
70
+ overlayBase.isPickable = false;
71
+ overlayBase.isVisible = false;
72
+ overlayBase.setEnabled(true);
73
+ overlayBase.renderOutline = false;
74
+ overlayBase.outlineColor = this.#color;
75
+ overlayBase.outlineWidth = 1.5;
76
+ overlayBase.renderOverlay = true;
77
+ overlayBase.overlayColor = this.#color;
78
+ overlayBase.overlayAlpha = 0.25;
79
+ overlayBase.visibility = 1;
80
+ overlayBase.renderingGroupId = 0;
81
+ this.#baseToOverlayBase.set(baseMesh, overlayBase);
82
+ return overlayBase;
83
+ }
84
+
85
+ /**
86
+ * Adds a presentation mesh or an instance of a cached presentation base.
87
+ * @private
88
+ * @param {import("@babylonjs/core").AbstractMesh} mesh - Original mesh to present.
89
+ * @returns {void}
90
+ */
91
+ #createOverlay(mesh) {
92
+ if (!mesh.geometry && !mesh.getChildMeshes) {
93
+ return;
94
+ }
95
+ const baseMesh = BabylonJSAnimationOverlayController.#getBaseMesh(mesh);
96
+ const overlayBase = this.#ensureOverlayBase(baseMesh);
97
+ const overlay = mesh instanceof InstancedMesh ? overlayBase.createInstance(mesh.name + "_overlayInst") : overlayBase;
98
+ if (mesh instanceof InstancedMesh) {
99
+ overlay.isPickable = false;
100
+ } else {
101
+ overlay.isVisible = true;
102
+ }
103
+ overlay.parent = null;
104
+ overlay.renderingGroupId = 0;
105
+ this.#pairs.push({ original: mesh, overlay });
106
+ }
107
+
108
+ /**
109
+ * Copies an original world transform to its overlay using reusable group-local scratch vectors.
110
+ * @private
111
+ * @param {{original:object,overlay:object}} pair - Original and presentation mesh pair.
112
+ * @returns {void}
113
+ */
114
+ #syncPair(pair) {
115
+ pair.original.getWorldMatrix().decompose(this.#scale, this.#rotation, this.#position);
116
+ pair.overlay.position.copyFrom(this.#position);
117
+ pair.overlay.scaling.copyFrom(this.#scale);
118
+ pair.overlay.rotationQuaternion = pair.overlay.rotationQuaternion || new Quaternion();
119
+ pair.overlay.rotationQuaternion.copyFrom(this.#rotation);
120
+ pair.overlay.computeWorldMatrix(true);
121
+ }
122
+
123
+ /**
124
+ * Removes transform observers and disposes each owned mesh and the utility renderer once.
125
+ * @public
126
+ * @returns {void}
127
+ */
128
+ dispose() {
129
+ if (this.#disposed) {
130
+ return;
131
+ }
132
+ this.#disposed = true;
133
+ for (const { mesh, observer } of this.#observers) {
134
+ mesh.onAfterWorldMatrixUpdateObservable?.remove(observer);
135
+ }
136
+ const overlays = new Set([...this.#pairs.map(pair => pair.overlay), ...this.#baseToOverlayBase.values()]);
137
+ for (const overlay of overlays) {
138
+ overlay.dispose();
139
+ }
140
+ this.#utilityLayer.dispose();
141
+ this.#observers.length = 0;
142
+ this.#pairs.length = 0;
143
+ this.#baseToOverlayBase.clear();
144
+ }
145
+ }