@babylonjs/shared-ui-components 9.14.0 → 9.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,576 @@
1
+ import { Observable } from "@babylonjs/core/Misc/observable.js";
2
+ import { Logger } from "@babylonjs/core/Misc/logger.js";
3
+ import { FindSmartAssetKeyForObject } from "@babylonjs/core/SmartAssets/smartAssetManager.pure.js";
4
+ const OverrideManagerKey = Symbol("babylonjs:overrideManager");
5
+ const OverrideManagerInternals = new WeakMap();
6
+ const OnOverrideManagerCreatedObservable = new Observable();
7
+ /**
8
+ * Creates a new OverrideManager state object and attaches it to the scene.
9
+ *
10
+ * Internal: callers should use {@link GetOverrideManager} which returns the
11
+ * existing manager when one is already attached.
12
+ * @param scene - The scene this manager operates on.
13
+ * @returns The created override manager state.
14
+ */
15
+ function CreateOverrideManager(scene) {
16
+ const manager = {
17
+ scene,
18
+ onChangedObservable: new Observable(),
19
+ };
20
+ const internal = {
21
+ overrides: [],
22
+ originalValues: new Map(),
23
+ sceneDisposeObserver: null,
24
+ };
25
+ OverrideManagerInternals.set(manager, internal);
26
+ if (!scene.metadata) {
27
+ scene.metadata = {};
28
+ }
29
+ scene.metadata[OverrideManagerKey] = manager;
30
+ // Auto-dispose when the scene is disposed so the manager doesn't outlive it.
31
+ internal.sceneDisposeObserver = scene.onDisposeObservable.add(() => DisposeOverrideManager(manager));
32
+ OnOverrideManagerCreatedObservable.notifyObservers(manager);
33
+ return manager;
34
+ }
35
+ /**
36
+ * Returns the OverrideManager attached to the given scene, creating and
37
+ * attaching one if none exists.
38
+ * @param scene - The scene to look up or attach a manager to.
39
+ * @returns The existing or newly created OverrideManager.
40
+ */
41
+ export function GetOverrideManager(scene) {
42
+ const existing = scene.metadata?.[OverrideManagerKey];
43
+ if (existing) {
44
+ return existing;
45
+ }
46
+ return CreateOverrideManager(scene);
47
+ }
48
+ /**
49
+ * Adds an observer that is notified whenever an OverrideManager is created.
50
+ * @param callback - The callback to invoke with each newly created manager.
51
+ * @returns The observer registration.
52
+ */
53
+ export function AddOverrideManagerCreatedObserver(callback) {
54
+ return OnOverrideManagerCreatedObservable.add((manager) => callback(manager));
55
+ }
56
+ /**
57
+ * Adds an override entry and immediately applies it.
58
+ * If an override with the same target coordinates already exists, it is replaced.
59
+ *
60
+ * When the caller has already mutated the target (e.g. an Inspector edit),
61
+ * pass `{ originalValue }` containing the property's prior value — this seeds
62
+ * the original-value map (so {@link RemoveOverride} can restore it) and skips
63
+ * the redundant apply step.
64
+ * @param scene - The scene whose override registry to update.
65
+ * @param entry - The override to add.
66
+ * @param options - Optional behavior modifiers; see {@link AddOverrideOptions}.
67
+ */
68
+ export function AddOverride(scene, entry, options) {
69
+ const manager = GetOverrideManager(scene);
70
+ const internal = GetOverrideInternals(manager);
71
+ RemoveMatchingOverride(internal, entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
72
+ internal.overrides.push(entry);
73
+ if (options && "originalValue" in options) {
74
+ // Caller already applied the new value. Seed the captured original from
75
+ // their pre-edit snapshot — otherwise ApplyOverrideEntry would capture
76
+ // the post-edit value and RemoveOverride would have nothing to restore.
77
+ const origKey = MakeOriginalValueKey(entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
78
+ if (!internal.originalValues.has(origKey)) {
79
+ internal.originalValues.set(origKey, CloneValue(options.originalValue));
80
+ }
81
+ }
82
+ else {
83
+ ApplyOverrideEntry(manager, internal, entry);
84
+ }
85
+ manager.onChangedObservable.notifyObservers();
86
+ }
87
+ /**
88
+ * Removes a single override matching the given coordinates. Restores the
89
+ * original value if one was captured.
90
+ * @param scene - The scene whose override registry to update.
91
+ * @param targetType - The target type.
92
+ * @param targetName - The target object name.
93
+ * @param targetIndex - The target index among same-named siblings.
94
+ * @param propertyPath - The property path to un-override.
95
+ * @returns True if an override was removed.
96
+ */
97
+ export function RemoveOverride(scene, targetType, targetName, targetIndex, propertyPath) {
98
+ const manager = GetOverrideManager(scene);
99
+ const internal = GetOverrideInternals(manager);
100
+ const idx = FindOverrideIndex(internal, targetType, targetName, targetIndex, propertyPath);
101
+ if (idx < 0) {
102
+ return false;
103
+ }
104
+ internal.overrides.splice(idx, 1);
105
+ const origKey = MakeOriginalValueKey(targetType, targetName, targetIndex, propertyPath);
106
+ const original = internal.originalValues.get(origKey);
107
+ if (original !== undefined) {
108
+ const target = ResolveTarget(manager.scene, targetType, targetName, targetIndex);
109
+ if (target) {
110
+ SetNestedProperty(target, propertyPath, original);
111
+ }
112
+ internal.originalValues.delete(origKey);
113
+ }
114
+ manager.onChangedObservable.notifyObservers();
115
+ return true;
116
+ }
117
+ /**
118
+ * Returns all overrides currently registered with the scene.
119
+ * @param scene - The scene whose override registry to read.
120
+ * @returns A read-only array of override entries.
121
+ */
122
+ export function GetOverrides(scene) {
123
+ return GetOverrideInternals(GetOverrideManager(scene)).overrides;
124
+ }
125
+ /**
126
+ * Removes all overrides, optionally restoring original values.
127
+ * @param scene - The scene whose override registry to clear.
128
+ * @param restoreOriginals - If true, restores all captured original values.
129
+ */
130
+ export function ClearOverrides(scene, restoreOriginals = false) {
131
+ const manager = GetOverrideManager(scene);
132
+ const internal = GetOverrideInternals(manager);
133
+ if (restoreOriginals) {
134
+ // Snapshot the entries so we can restore each original without firing
135
+ // an onChangedObservable notification per entry; consumers only need
136
+ // one signal that the registry was emptied.
137
+ const entries = [...internal.overrides];
138
+ internal.overrides.length = 0;
139
+ for (const entry of entries) {
140
+ const origKey = MakeOriginalValueKey(entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
141
+ const original = internal.originalValues.get(origKey);
142
+ if (original !== undefined) {
143
+ const target = ResolveTarget(manager.scene, entry.targetType, entry.targetName, entry.targetIndex);
144
+ if (target) {
145
+ SetNestedProperty(target, entry.propertyPath, original);
146
+ }
147
+ }
148
+ }
149
+ internal.originalValues.clear();
150
+ manager.onChangedObservable.notifyObservers();
151
+ return;
152
+ }
153
+ internal.overrides.length = 0;
154
+ internal.originalValues.clear();
155
+ manager.onChangedObservable.notifyObservers();
156
+ }
157
+ /**
158
+ * Updates the target coordinates on the override matching a specific (type,
159
+ * old-name, old-index) so it follows an entity rename. Used by capture services
160
+ * to keep overrides attached to a specific object after the user renames it.
161
+ *
162
+ * Only the override at the exact `(targetType, oldName, oldIndex)` slot is
163
+ * updated, so other same-named siblings keep their own overrides untouched.
164
+ *
165
+ * @param scene - The scene whose override registry to update.
166
+ * @param targetType - The target type.
167
+ * @param oldName - The previous name of the renamed entity.
168
+ * @param oldIndex - The previous index of the renamed entity among same-named siblings.
169
+ * @param newName - The new name of the renamed entity.
170
+ * @param newIndex - The new index of the renamed entity among same-named siblings.
171
+ */
172
+ export function RenameOverrideTarget(scene, targetType, oldName, oldIndex, newName, newIndex) {
173
+ const manager = GetOverrideManager(scene);
174
+ const internal = GetOverrideInternals(manager);
175
+ let changed = false;
176
+ for (let i = 0; i < internal.overrides.length; i++) {
177
+ const entry = internal.overrides[i];
178
+ if (entry.targetType === targetType && entry.targetName === oldName && entry.targetIndex === oldIndex) {
179
+ internal.overrides[i] = { ...entry, targetName: newName, targetIndex: newIndex };
180
+ changed = true;
181
+ }
182
+ }
183
+ // Update original-value keys to match the new identity
184
+ const oldPrefix = `${targetType}::${oldName}::${oldIndex}::`;
185
+ const newPrefix = `${targetType}::${newName}::${newIndex}::`;
186
+ for (const [origKey, value] of Array.from(internal.originalValues.entries())) {
187
+ if (origKey.startsWith(oldPrefix)) {
188
+ const propertyPath = origKey.substring(oldPrefix.length);
189
+ internal.originalValues.set(newPrefix + propertyPath, value);
190
+ internal.originalValues.delete(origKey);
191
+ }
192
+ }
193
+ if (changed) {
194
+ manager.onChangedObservable.notifyObservers();
195
+ }
196
+ }
197
+ /**
198
+ * Rewrites override *values* that reference an entity by name when that entity
199
+ * has been renamed. Mirrors {@link RenameOverrideTarget} but operates on the
200
+ * `value` field rather than the `targetName` field, so overrides whose value
201
+ * is `"ref:oldName"` (material/light/camera references) or `"texture:oldName"`
202
+ * (non-SmartAsset texture references) follow the rename instead of silently
203
+ * pointing at a non-existent entity.
204
+ *
205
+ * SmartAsset texture references (`"samTexture:<key>"`) are unaffected because
206
+ * the SmartAsset key is decoupled from the texture's runtime `name` field.
207
+ *
208
+ * @param scene - The scene whose override registry to update.
209
+ * @param valueScheme - Which encoded-reference prefix to rewrite: `"ref"` for
210
+ * material/light/camera references, `"texture"` for non-SAM textures.
211
+ * @param oldName - The previous name embedded in the reference.
212
+ * @param newName - The new name embedded in the reference.
213
+ */
214
+ export function RenameOverrideValueReferences(scene, valueScheme, oldName, newName) {
215
+ if (oldName === newName) {
216
+ return;
217
+ }
218
+ const manager = GetOverrideManager(scene);
219
+ const internal = GetOverrideInternals(manager);
220
+ const oldValue = `${valueScheme}:${oldName}`;
221
+ const newValue = `${valueScheme}:${newName}`;
222
+ let changed = false;
223
+ for (let i = 0; i < internal.overrides.length; i++) {
224
+ const entry = internal.overrides[i];
225
+ if (entry.value === oldValue) {
226
+ internal.overrides[i] = { ...entry, value: newValue };
227
+ changed = true;
228
+ }
229
+ }
230
+ if (changed) {
231
+ manager.onChangedObservable.notifyObservers();
232
+ }
233
+ }
234
+ // ── Application ──
235
+ /**
236
+ * Applies all overrides to their current targets in the scene.
237
+ *
238
+ * Call this after any scene mutation that might have invalidated previously
239
+ * applied state (asset reload, object recreation, project load). The override
240
+ * manager does not auto-subscribe to other scene subsystems — coordination is
241
+ * the caller's responsibility, which keeps the override system independent.
242
+ * @param scene - The scene whose overrides to apply.
243
+ */
244
+ export function ApplyAllOverrides(scene) {
245
+ const manager = GetOverrideManager(scene);
246
+ const internal = GetOverrideInternals(manager);
247
+ for (const entry of internal.overrides) {
248
+ ApplyOverrideEntry(manager, internal, entry);
249
+ }
250
+ }
251
+ // ── Serialization ──
252
+ /**
253
+ * Serializes all overrides to a JSON-compatible array.
254
+ * The on-disk shape is identical to the in-memory `IOverrideEntry`.
255
+ * @param scene - The scene whose overrides to serialize.
256
+ * @returns An array of override entries (shallow copies).
257
+ */
258
+ export function SerializeOverrides(scene) {
259
+ const internal = GetOverrideInternals(GetOverrideManager(scene));
260
+ return internal.overrides.map((o) => ({ ...o }));
261
+ }
262
+ /**
263
+ * Loads overrides from a serialized array and applies them.
264
+ * @param scene - The scene whose override registry to populate.
265
+ * @param data - Array of override entries.
266
+ */
267
+ export function DeserializeAndApplyOverrides(scene, data) {
268
+ if (!Array.isArray(data)) {
269
+ throw new Error("OverrideManager: Expected an array of override entries.");
270
+ }
271
+ for (const entry of data) {
272
+ if (!entry.targetType || entry.targetName === undefined || typeof entry.targetIndex !== "number" || !entry.propertyPath || entry.value === undefined) {
273
+ Logger.Warn("OverrideManager: Skipping invalid override entry.");
274
+ continue;
275
+ }
276
+ AddOverride(scene, entry);
277
+ }
278
+ }
279
+ // ── Lifecycle ──
280
+ /**
281
+ * Disposes the manager, clearing all overrides and detaching it from its scene.
282
+ * Safe to call multiple times; subsequent calls are no-ops. Automatically invoked when the
283
+ * owning scene is disposed.
284
+ * @param manager - The override manager state.
285
+ */
286
+ export function DisposeOverrideManager(manager) {
287
+ const internal = OverrideManagerInternals.get(manager);
288
+ if (!internal) {
289
+ return;
290
+ }
291
+ OverrideManagerInternals.delete(manager);
292
+ if (internal.sceneDisposeObserver) {
293
+ manager.scene.onDisposeObservable.remove(internal.sceneDisposeObserver);
294
+ internal.sceneDisposeObserver = null;
295
+ }
296
+ internal.overrides.length = 0;
297
+ internal.originalValues.clear();
298
+ manager.onChangedObservable.clear();
299
+ if (manager.scene.metadata) {
300
+ delete manager.scene.metadata[OverrideManagerKey];
301
+ }
302
+ }
303
+ // ── Private ──
304
+ function GetOverrideInternals(manager) {
305
+ const internal = OverrideManagerInternals.get(manager);
306
+ if (!internal) {
307
+ throw new Error("OverrideManager: Unknown manager state.");
308
+ }
309
+ return internal;
310
+ }
311
+ /**
312
+ * Applies a single override entry to its target, capturing the original value
313
+ * on the first application so {@link RemoveOverride} can restore it later.
314
+ * @param manager - The override manager owning the entry.
315
+ * @param internal - The manager's internal state.
316
+ * @param entry - The override to apply.
317
+ */
318
+ function ApplyOverrideEntry(manager, internal, entry) {
319
+ const target = ResolveTarget(manager.scene, entry.targetType, entry.targetName, entry.targetIndex);
320
+ if (!target) {
321
+ Logger.Warn(`OverrideManager: target not found for type="${entry.targetType}" name="${entry.targetName}" index=${entry.targetIndex} prop="${entry.propertyPath}"`);
322
+ return; // Target not loaded yet — override will be applied on next ApplyAllOverrides
323
+ }
324
+ // Capture original value before first override
325
+ const origKey = MakeOriginalValueKey(entry.targetType, entry.targetName, entry.targetIndex, entry.propertyPath);
326
+ if (!internal.originalValues.has(origKey)) {
327
+ const currentValue = GetNestedProperty(target, entry.propertyPath);
328
+ if (currentValue !== undefined) {
329
+ internal.originalValues.set(origKey, CloneValue(currentValue));
330
+ }
331
+ }
332
+ const resolvedValue = ResolveOverrideValue(manager.scene, entry.value);
333
+ SetNestedProperty(target, entry.propertyPath, resolvedValue);
334
+ }
335
+ /**
336
+ * Locates a scene object by (targetType, targetName, targetIndex). The scene
337
+ * collection is filtered to objects matching `targetName`; the N-th survivor
338
+ * (per `targetIndex`) is returned. Falls back to the first match if the index
339
+ * is out of range — useful when the scene shape has changed since capture.
340
+ * @param scene - The scene to search.
341
+ * @param targetType - The override target type.
342
+ * @param targetName - The target object name (or "" for scene-level).
343
+ * @param targetIndex - The target's position among same-named siblings.
344
+ * @returns The matching scene object, or null if not found.
345
+ */
346
+ function ResolveTarget(scene, targetType, targetName, targetIndex) {
347
+ // Scene-level overrides target the scene itself
348
+ if (targetType === "scene") {
349
+ return scene;
350
+ }
351
+ const collection = GetCollection(scene, targetType);
352
+ if (!collection) {
353
+ return null;
354
+ }
355
+ const matches = collection.filter((obj) => obj.name === targetName);
356
+ if (matches.length === 0) {
357
+ return null;
358
+ }
359
+ return (matches[targetIndex] ?? matches[0]);
360
+ }
361
+ /**
362
+ * Returns the scene collection corresponding to an override target type.
363
+ * @param scene - The scene to inspect.
364
+ * @param targetType - The target type.
365
+ * @returns The collection, or null if the type has no collection.
366
+ */
367
+ function GetCollection(scene, targetType) {
368
+ switch (targetType) {
369
+ case "meshes":
370
+ return scene.meshes;
371
+ case "materials":
372
+ return scene.materials;
373
+ case "textures":
374
+ return scene.textures;
375
+ case "lights":
376
+ return scene.lights;
377
+ case "cameras":
378
+ return scene.cameras;
379
+ case "animationGroups":
380
+ return scene.animationGroups;
381
+ default:
382
+ return null;
383
+ }
384
+ }
385
+ /**
386
+ * Resolves an override value, expanding string references like "ref:name",
387
+ * "samTexture:key", or "texture:name" into the actual scene object they refer to.
388
+ * @param scene - The scene used to look up references.
389
+ * @param value - The serialized override value.
390
+ * @returns The runtime value to assign to the target property.
391
+ */
392
+ function ResolveOverrideValue(scene, value) {
393
+ if (typeof value === "string") {
394
+ if (value.startsWith("ref:")) {
395
+ return ResolveObjectReference(scene, value.substring(4));
396
+ }
397
+ if (value.startsWith("samTexture:")) {
398
+ return ResolveSamTextureReference(scene, value.substring(11));
399
+ }
400
+ if (value.startsWith("texture:")) {
401
+ return ResolveTextureReference(scene, value.substring(8));
402
+ }
403
+ }
404
+ // Number arrays are passed through as-is. SetNestedProperty will use
405
+ // the live target's `fromArray` method (Vector3, Color3, etc.) to push
406
+ // values in-place, preserving the math instance identity.
407
+ return value;
408
+ }
409
+ /**
410
+ * Resolves a "ref:name" value by looking up a material, light, or camera
411
+ * in the scene by name.
412
+ * @param scene - The scene to search.
413
+ * @param name - The object name to resolve.
414
+ * @returns The matching material, light, or camera, or undefined if not found.
415
+ */
416
+ function ResolveObjectReference(scene, name) {
417
+ const mat = scene.materials.find((m) => m.name === name);
418
+ if (mat) {
419
+ return mat;
420
+ }
421
+ const light = scene.lights.find((l) => l.name === name);
422
+ if (light) {
423
+ return light;
424
+ }
425
+ const camera = scene.cameras.find((c) => c.name === name);
426
+ if (camera) {
427
+ return camera;
428
+ }
429
+ Logger.Warn(`OverrideManager: Object reference "${name}" not found in scene.`);
430
+ return undefined;
431
+ }
432
+ /**
433
+ * Resolves a "texture:name" value by looking up a texture in the scene by name.
434
+ * @param scene - The scene to search.
435
+ * @param name - The texture name to resolve.
436
+ * @returns The matching texture, or undefined if not found.
437
+ */
438
+ function ResolveTextureReference(scene, name) {
439
+ const tex = scene.textures.find((t) => t.name === name);
440
+ if (tex) {
441
+ return tex;
442
+ }
443
+ Logger.Warn(`OverrideManager: Texture reference "${name}" not found.`);
444
+ return undefined;
445
+ }
446
+ /**
447
+ * Resolves a "samTexture:key" value by looking up a SmartAsset-tracked texture
448
+ * by its registry key. The SAM key is stable across save/load whereas the
449
+ * texture's `name` (for SAM textures, the blob URL) changes on every reload,
450
+ * so this is the only reliable way to round-trip texture references on
451
+ * user-uploaded SmartAsset textures.
452
+ * @param scene - The scene to search.
453
+ * @param key - The SmartAsset key to resolve.
454
+ * @returns The matching texture, or undefined if not found.
455
+ */
456
+ function ResolveSamTextureReference(scene, key) {
457
+ const tex = scene.textures.find((t) => FindSmartAssetKeyForObject(scene, t) === key);
458
+ if (tex) {
459
+ return tex;
460
+ }
461
+ Logger.Warn(`OverrideManager: SmartAsset texture "${key}" not found.`);
462
+ return undefined;
463
+ }
464
+ /**
465
+ * Finds the index of an override matching the given coordinates.
466
+ * @param internal - The manager's internal state.
467
+ * @param targetType - The target type.
468
+ * @param targetName - The target object name.
469
+ * @param targetIndex - The target index among same-named siblings.
470
+ * @param propertyPath - The property path.
471
+ * @returns The matching index, or -1 if none found.
472
+ */
473
+ function FindOverrideIndex(internal, targetType, targetName, targetIndex, propertyPath) {
474
+ return internal.overrides.findIndex((o) => o.targetType === targetType && o.targetName === targetName && o.targetIndex === targetIndex && o.propertyPath === propertyPath);
475
+ }
476
+ /**
477
+ * Removes any existing override that matches the given coordinates. Used by
478
+ * {@link AddOverride} to enforce one entry per (type, name, index, property).
479
+ * @param internal - The manager's internal state.
480
+ * @param targetType - The target type.
481
+ * @param targetName - The target object name.
482
+ * @param targetIndex - The target index among same-named siblings.
483
+ * @param propertyPath - The property path.
484
+ */
485
+ function RemoveMatchingOverride(internal, targetType, targetName, targetIndex, propertyPath) {
486
+ const idx = FindOverrideIndex(internal, targetType, targetName, targetIndex, propertyPath);
487
+ if (idx >= 0) {
488
+ internal.overrides.splice(idx, 1);
489
+ }
490
+ }
491
+ /**
492
+ * Creates a unique key for storing original values.
493
+ * @param targetType - The override target type.
494
+ * @param targetName - The target object name.
495
+ * @param targetIndex - The target index among same-named siblings.
496
+ * @param propertyPath - The property path.
497
+ * @returns A composite string key uniquely identifying the original value slot.
498
+ */
499
+ function MakeOriginalValueKey(targetType, targetName, targetIndex, propertyPath) {
500
+ return `${targetType}::${targetName}::${targetIndex}::${propertyPath}`;
501
+ }
502
+ /**
503
+ * Gets a nested property from an object using a dot-separated path.
504
+ * @param obj - The root object to traverse.
505
+ * @param path - The dot-separated property path.
506
+ * @returns The value at the path, or undefined if any segment is missing.
507
+ */
508
+ function GetNestedProperty(obj, path) {
509
+ const parts = path.split(".");
510
+ let current = obj;
511
+ for (const part of parts) {
512
+ if (current === null || current === undefined || typeof current !== "object") {
513
+ return undefined;
514
+ }
515
+ current = current[part];
516
+ }
517
+ return current;
518
+ }
519
+ /**
520
+ * Sets a nested property on an object using a dot-separated path.
521
+ *
522
+ * When the value is a number array and the existing property is a Babylon
523
+ * math type (Vector*, Quaternion, Color3/4, Matrix), uses the math type's
524
+ * `fromArray` method to mutate it in place — preserving the live instance
525
+ * identity that consumers may already hold references to. Otherwise falls
526
+ * back to direct property replacement.
527
+ * @param obj - The root object to mutate.
528
+ * @param path - The dot-separated property path.
529
+ * @param value - The new value to assign.
530
+ */
531
+ function SetNestedProperty(obj, path, value) {
532
+ const parts = path.split(".");
533
+ let current = obj;
534
+ for (let i = 0; i < parts.length - 1; i++) {
535
+ if (current === null || current === undefined || typeof current !== "object") {
536
+ return;
537
+ }
538
+ current = current[parts[i]];
539
+ }
540
+ if (current === null || current === undefined || typeof current !== "object") {
541
+ return;
542
+ }
543
+ const lastPart = parts[parts.length - 1];
544
+ const existing = current[lastPart];
545
+ if (Array.isArray(value) && existing && typeof existing === "object" && typeof existing.fromArray === "function") {
546
+ existing.fromArray(value);
547
+ return;
548
+ }
549
+ current[lastPart] = value;
550
+ }
551
+ /**
552
+ * Snapshots a value for original-value tracking.
553
+ *
554
+ * Scene entities (textures, materials, meshes, etc.) are stored by reference
555
+ * because cloning them would register unwanted duplicates in the scene.
556
+ * Plain math types (Vector3, Color3, etc.) are cloned so mutations to the
557
+ * live object don't corrupt the saved original.
558
+ * @param value - The value to snapshot.
559
+ * @returns The snapshot value (cloned for plain math types, by reference for entities).
560
+ */
561
+ function CloneValue(value) {
562
+ if (value === null || value === undefined) {
563
+ return value;
564
+ }
565
+ if (typeof value !== "object") {
566
+ return value;
567
+ }
568
+ if (typeof value.getScene === "function") {
569
+ return value;
570
+ }
571
+ if ("clone" in value && typeof value.clone === "function") {
572
+ return value.clone();
573
+ }
574
+ return { ...value };
575
+ }
576
+ //# sourceMappingURL=overrideManager.js.map