@react-three-dom/core 0.5.0 → 0.6.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/dist/index.js CHANGED
@@ -1,3988 +1,4 @@
1
- import { Box3, Object3D, Vector3, Matrix4, Frustum, Raycaster, Vector2, BufferGeometry, Material, Color, MeshBasicMaterial, DoubleSide, Mesh, BoxGeometry, EdgesGeometry, LineBasicMaterial, LineSegments, InstancedMesh, PerspectiveCamera, OrthographicCamera } from 'three';
2
- import { useRef, useEffect, useCallback } from 'react';
3
- import { useThree, useFrame } from '@react-three/fiber';
4
- import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh';
5
-
6
- // src/version.ts
7
- var version = "0.5.0";
8
-
9
- // src/debug.ts
10
- var _enabled = false;
11
- function enableDebug(on = true) {
12
- _enabled = on;
13
- }
14
- function isDebugEnabled() {
15
- return _enabled || typeof window !== "undefined" && !!window.__R3F_DOM_DEBUG__;
16
- }
17
- function r3fLog(area, msg, data) {
18
- if (!_enabled && !(typeof window !== "undefined" && window.__R3F_DOM_DEBUG__)) {
19
- return;
20
- }
21
- if (data !== void 0) {
22
- console.log(`[r3f-dom:${area}]`, msg, data);
23
- } else {
24
- console.log(`[r3f-dom:${area}]`, msg);
25
- }
26
- }
27
- function extractMetadata(obj) {
28
- const meta = {
29
- uuid: obj.uuid,
30
- name: obj.name,
31
- type: obj.type,
32
- visible: obj.visible,
33
- testId: obj.userData?.testId,
34
- position: [obj.position.x, obj.position.y, obj.position.z],
35
- rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],
36
- scale: [obj.scale.x, obj.scale.y, obj.scale.z],
37
- parentUuid: obj.parent?.uuid ?? null,
38
- childrenUuids: obj.children.map((c) => c.uuid),
39
- boundsDirty: true
40
- };
41
- extractStaticFields(obj, meta);
42
- return meta;
43
- }
44
- function extractStaticFields(obj, meta) {
45
- try {
46
- if ("geometry" in obj) {
47
- const geom = obj.geometry;
48
- if (geom instanceof BufferGeometry) {
49
- meta.geometryType = geom.type;
50
- const posAttr = geom.getAttribute("position");
51
- if (posAttr) {
52
- meta.vertexCount = posAttr.count;
53
- if (geom.index) {
54
- meta.triangleCount = Math.floor(geom.index.count / 3);
55
- } else {
56
- meta.triangleCount = Math.floor(posAttr.count / 3);
57
- }
58
- }
59
- }
60
- }
61
- } catch {
62
- r3fLog("store", `extractMetadata: geometry access failed for "${obj.name || obj.uuid}"`);
63
- }
64
- try {
65
- if ("material" in obj) {
66
- const mat = obj.material;
67
- if (mat instanceof Material) {
68
- meta.materialType = mat.type;
69
- } else if (Array.isArray(mat) && mat.length > 0) {
70
- meta.materialType = mat[0].type + (mat.length > 1 ? ` (+${mat.length - 1})` : "");
71
- }
72
- }
73
- } catch {
74
- r3fLog("store", `extractMetadata: material access failed for "${obj.name || obj.uuid}"`);
75
- }
76
- try {
77
- if (obj instanceof InstancedMesh) {
78
- meta.instanceCount = obj.count;
79
- }
80
- } catch {
81
- }
82
- try {
83
- if (obj instanceof PerspectiveCamera) {
84
- meta.fov = obj.fov;
85
- meta.near = obj.near;
86
- meta.far = obj.far;
87
- meta.zoom = obj.zoom;
88
- } else if (obj instanceof OrthographicCamera) {
89
- meta.near = obj.near;
90
- meta.far = obj.far;
91
- meta.zoom = obj.zoom;
92
- }
93
- } catch {
94
- }
95
- }
96
- function updateDynamicFields(obj, meta) {
97
- let changed = false;
98
- if (meta.visible !== obj.visible) {
99
- meta.visible = obj.visible;
100
- changed = true;
101
- }
102
- if (meta.name !== obj.name) {
103
- meta.name = obj.name;
104
- changed = true;
105
- }
106
- const testId = obj.userData?.testId;
107
- if (meta.testId !== testId) {
108
- meta.testId = testId;
109
- changed = true;
110
- }
111
- const p = obj.position;
112
- if (meta.position[0] !== p.x || meta.position[1] !== p.y || meta.position[2] !== p.z) {
113
- meta.position[0] = p.x;
114
- meta.position[1] = p.y;
115
- meta.position[2] = p.z;
116
- changed = true;
117
- }
118
- const r = obj.rotation;
119
- if (meta.rotation[0] !== r.x || meta.rotation[1] !== r.y || meta.rotation[2] !== r.z) {
120
- meta.rotation[0] = r.x;
121
- meta.rotation[1] = r.y;
122
- meta.rotation[2] = r.z;
123
- changed = true;
124
- }
125
- const s = obj.scale;
126
- if (meta.scale[0] !== s.x || meta.scale[1] !== s.y || meta.scale[2] !== s.z) {
127
- meta.scale[0] = s.x;
128
- meta.scale[1] = s.y;
129
- meta.scale[2] = s.z;
130
- changed = true;
131
- }
132
- const parentUuid = obj.parent?.uuid ?? null;
133
- if (meta.parentUuid !== parentUuid) {
134
- meta.parentUuid = parentUuid;
135
- changed = true;
136
- }
137
- const children = obj.children;
138
- const cached = meta.childrenUuids;
139
- if (cached.length !== children.length) {
140
- meta.childrenUuids = children.map((c) => c.uuid);
141
- changed = true;
142
- } else {
143
- for (let i = 0; i < cached.length; i++) {
144
- if (cached[i] !== children[i].uuid) {
145
- meta.childrenUuids = children.map((c) => c.uuid);
146
- changed = true;
147
- break;
148
- }
149
- }
150
- }
151
- return changed;
152
- }
153
- var _box3 = new Box3();
154
- function inspectObject(obj, metadata, options) {
155
- let worldMatrix = Array(16).fill(0);
156
- let boundsMin = [0, 0, 0];
157
- let boundsMax = [0, 0, 0];
158
- try {
159
- obj.updateWorldMatrix(true, false);
160
- worldMatrix = Array.from(obj.matrixWorld.elements);
161
- _box3.setFromObject(obj);
162
- boundsMin = [_box3.min.x, _box3.min.y, _box3.min.z];
163
- boundsMax = [_box3.max.x, _box3.max.y, _box3.max.z];
164
- } catch {
165
- r3fLog("store", `inspectObject: world matrix / bounds failed for "${obj.name || obj.uuid}"`);
166
- }
167
- const inspection = {
168
- metadata,
169
- worldMatrix,
170
- bounds: { min: boundsMin, max: boundsMax },
171
- userData: {}
172
- };
173
- try {
174
- inspection.userData = { ...obj.userData };
175
- } catch {
176
- r3fLog("store", `inspectObject: userData copy failed for "${obj.name || obj.uuid}"`);
177
- }
178
- try {
179
- if ("geometry" in obj) {
180
- const geom = obj.geometry;
181
- if (geom instanceof BufferGeometry && geom.attributes) {
182
- const geoInspection = {
183
- type: geom.type,
184
- attributes: {}
185
- };
186
- for (const [name, attr] of Object.entries(geom.attributes)) {
187
- if (!attr) continue;
188
- geoInspection.attributes[name] = {
189
- itemSize: attr.itemSize,
190
- count: attr.count
191
- };
192
- }
193
- if (geom.index) {
194
- geoInspection.index = { count: geom.index.count };
195
- }
196
- if (options?.includeGeometryData) {
197
- const posAttr = geom.getAttribute("position");
198
- if (posAttr?.array) {
199
- geoInspection.positionData = Array.from(posAttr.array);
200
- }
201
- if (geom.index?.array) {
202
- geoInspection.indexData = Array.from(geom.index.array);
203
- }
204
- }
205
- geom.computeBoundingSphere();
206
- const sphere = geom.boundingSphere;
207
- if (sphere) {
208
- geoInspection.boundingSphere = {
209
- center: [sphere.center.x, sphere.center.y, sphere.center.z],
210
- radius: sphere.radius
211
- };
212
- }
213
- inspection.geometry = geoInspection;
214
- }
215
- }
216
- } catch {
217
- r3fLog("store", `inspectObject: geometry inspection failed for "${obj.name || obj.uuid}"`);
218
- }
219
- try {
220
- if ("material" in obj) {
221
- const rawMat = obj.material;
222
- if (!rawMat) throw new Error("disposed");
223
- const mat = Array.isArray(rawMat) ? rawMat[0] : rawMat;
224
- if (mat instanceof Material) {
225
- const matInspection = {
226
- type: mat.type,
227
- transparent: mat.transparent,
228
- opacity: mat.opacity,
229
- side: mat.side
230
- };
231
- if ("color" in mat && mat.color instanceof Color) {
232
- matInspection.color = "#" + mat.color.getHexString();
233
- }
234
- if ("map" in mat) {
235
- const map = mat.map;
236
- if (map) {
237
- matInspection.map = map.name || map.uuid || "unnamed";
238
- }
239
- }
240
- if ("uniforms" in mat) {
241
- const uniforms = mat.uniforms;
242
- matInspection.uniforms = {};
243
- for (const [key, uniform] of Object.entries(uniforms)) {
244
- const val = uniform.value;
245
- if (val === null || val === void 0) {
246
- matInspection.uniforms[key] = val;
247
- } else if (typeof val === "number" || typeof val === "boolean" || typeof val === "string") {
248
- matInspection.uniforms[key] = val;
249
- } else if (typeof val === "object" && "toArray" in val) {
250
- matInspection.uniforms[key] = val.toArray();
251
- } else {
252
- matInspection.uniforms[key] = `[${typeof val}]`;
253
- }
254
- }
255
- }
256
- inspection.material = matInspection;
257
- }
258
- }
259
- } catch {
260
- r3fLog("store", `inspectObject: material inspection failed for "${obj.name || obj.uuid}"`);
261
- }
262
- return inspection;
263
- }
264
- var ObjectStore = class {
265
- constructor() {
266
- // Tier 1: metadata for every tracked object
267
- this._metaByObject = /* @__PURE__ */ new WeakMap();
268
- this._objectByUuid = /* @__PURE__ */ new Map();
269
- this._objectsByTestId = /* @__PURE__ */ new Map();
270
- this._objectsByName = /* @__PURE__ */ new Map();
271
- // Flat list for amortized iteration
272
- this._flatList = [];
273
- this._flatListDirty = true;
274
- // Priority dirty queue: objects that changed and need immediate sync
275
- this._dirtyQueue = /* @__PURE__ */ new Set();
276
- // Event listeners
277
- this._listeners = [];
278
- // Track the root scene(s) for scoping
279
- this._trackedRoots = /* @__PURE__ */ new WeakSet();
280
- // ---- Async registration state ----
281
- this._asyncRegQueue = [];
282
- this._asyncRegHandle = null;
283
- this._asyncRegBatchSize = 1e3;
284
- }
285
- // -------------------------------------------------------------------------
286
- // Registration
287
- // -------------------------------------------------------------------------
288
- /**
289
- * Register a single object into the store.
290
- * Populates Tier 1 metadata and all indexes.
291
- * Tags the object with `__r3fdom_tracked = true` for O(1) scene membership checks.
292
- */
293
- register(obj) {
294
- if (obj.userData?.__r3fdom_internal) {
295
- return extractMetadata(obj);
296
- }
297
- const existing = this._metaByObject.get(obj);
298
- if (existing) return existing;
299
- const meta = extractMetadata(obj);
300
- this._metaByObject.set(obj, meta);
301
- this._objectByUuid.set(meta.uuid, obj);
302
- this._flatListDirty = true;
303
- obj.userData.__r3fdom_tracked = true;
304
- if (meta.testId) {
305
- this._objectsByTestId.set(meta.testId, obj);
306
- }
307
- if (meta.name) {
308
- let nameSet = this._objectsByName.get(meta.name);
309
- if (!nameSet) {
310
- nameSet = /* @__PURE__ */ new Set();
311
- this._objectsByName.set(meta.name, nameSet);
312
- }
313
- nameSet.add(obj);
314
- }
315
- this._emit({ type: "add", object: obj, metadata: meta });
316
- if (meta.testId) {
317
- r3fLog("store", `Registered "${meta.testId}" (${meta.type})`);
318
- }
319
- return meta;
320
- }
321
- /**
322
- * Register an entire subtree (object + all descendants) synchronously.
323
- * Prefer `registerTreeAsync` for large scenes (100k+) to avoid blocking.
324
- */
325
- registerTree(root) {
326
- this._trackedRoots.add(root);
327
- root.traverse((obj) => {
328
- try {
329
- this.register(obj);
330
- } catch (err) {
331
- r3fLog("store", `registerTree: failed to register "${obj.name || obj.uuid}"`, err);
332
- }
333
- });
334
- }
335
- /**
336
- * Register an entire subtree asynchronously using requestIdleCallback.
337
- * Processes ~1000 objects per idle slice to avoid blocking the main thread.
338
- *
339
- * IMPORTANT: install patchObject3D BEFORE calling this so that objects
340
- * added to the scene during async registration are caught by the patch.
341
- *
342
- * Returns a cancel function. Also cancelled automatically by dispose().
343
- */
344
- registerTreeAsync(root) {
345
- this._trackedRoots.add(root);
346
- const queue = [];
347
- root.traverse((obj) => queue.push(obj));
348
- this._asyncRegQueue = queue;
349
- r3fLog("store", `registerTreeAsync: ${queue.length} objects queued`);
350
- this._scheduleRegChunk();
351
- return () => this._cancelAsyncRegistration();
352
- }
353
- _scheduleRegChunk() {
354
- if (this._asyncRegQueue.length === 0) {
355
- this._asyncRegHandle = null;
356
- r3fLog("store", `registerTreeAsync complete: ${this.getCount()} objects registered`);
357
- return;
358
- }
359
- const callback = (deadline) => {
360
- const hasTime = deadline ? () => deadline.timeRemaining() > 1 : () => true;
361
- let processed = 0;
362
- while (this._asyncRegQueue.length > 0 && processed < this._asyncRegBatchSize && hasTime()) {
363
- const obj = this._asyncRegQueue.shift();
364
- try {
365
- this.register(obj);
366
- } catch (err) {
367
- r3fLog("store", `registerTreeAsync: failed to register "${obj.name || obj.uuid}"`, err);
368
- }
369
- processed++;
370
- }
371
- this._scheduleRegChunk();
372
- };
373
- if (typeof requestIdleCallback === "function") {
374
- this._asyncRegHandle = requestIdleCallback(callback, { timeout: 50 });
375
- } else {
376
- this._asyncRegHandle = setTimeout(callback, 4);
377
- }
378
- }
379
- _cancelAsyncRegistration() {
380
- if (this._asyncRegHandle !== null) {
381
- if (typeof cancelIdleCallback === "function") {
382
- cancelIdleCallback(this._asyncRegHandle);
383
- } else {
384
- clearTimeout(this._asyncRegHandle);
385
- }
386
- this._asyncRegHandle = null;
387
- }
388
- this._asyncRegQueue = [];
389
- }
390
- /**
391
- * Unregister a single object from the store.
392
- * Clears the `__r3fdom_tracked` flag.
393
- */
394
- unregister(obj) {
395
- const meta = this._metaByObject.get(obj);
396
- if (!meta) return;
397
- this._metaByObject.delete(obj);
398
- this._objectByUuid.delete(meta.uuid);
399
- this._dirtyQueue.delete(obj);
400
- this._flatListDirty = true;
401
- delete obj.userData.__r3fdom_tracked;
402
- delete obj.userData.__r3fdom_manual;
403
- if (meta.testId) {
404
- this._objectsByTestId.delete(meta.testId);
405
- }
406
- if (meta.name) {
407
- const nameSet = this._objectsByName.get(meta.name);
408
- if (nameSet) {
409
- nameSet.delete(obj);
410
- if (nameSet.size === 0) {
411
- this._objectsByName.delete(meta.name);
412
- }
413
- }
414
- }
415
- if (meta.testId) {
416
- r3fLog("store", `Unregistered "${meta.testId}" (${meta.type})`);
417
- }
418
- this._emit({ type: "remove", object: obj, metadata: meta });
419
- }
420
- /**
421
- * Unregister an entire subtree (object + all descendants).
422
- */
423
- /** Mark a root as tracked without traversing/registering its children. */
424
- addTrackedRoot(root) {
425
- this._trackedRoots.add(root);
426
- }
427
- unregisterTree(root) {
428
- root.traverse((obj) => {
429
- this.unregister(obj);
430
- });
431
- this._trackedRoots.delete(root);
432
- }
433
- // -------------------------------------------------------------------------
434
- // Tier 1: Update (compare-and-set, returns true if changed)
435
- // -------------------------------------------------------------------------
436
- /**
437
- * Refresh dynamic Tier 1 fields from the live Three.js object.
438
- * Only reads transform, visibility, children count, and parent —
439
- * skips static fields (geometry, material) that don't change per-frame.
440
- * Mutates metadata in-place to avoid allocation.
441
- * Returns true if any values changed.
442
- */
443
- update(obj) {
444
- const meta = this._metaByObject.get(obj);
445
- if (!meta) return false;
446
- let changed;
447
- try {
448
- const prevTestId = meta.testId;
449
- const prevName = meta.name;
450
- changed = updateDynamicFields(obj, meta);
451
- if (changed) {
452
- if (prevTestId !== meta.testId) {
453
- r3fLog("store", `testId changed: "${prevTestId}" \u2192 "${meta.testId}" (${meta.type})`);
454
- if (prevTestId) this._objectsByTestId.delete(prevTestId);
455
- if (meta.testId) this._objectsByTestId.set(meta.testId, obj);
456
- }
457
- if (prevName !== meta.name) {
458
- if (prevName) {
459
- const nameSet = this._objectsByName.get(prevName);
460
- if (nameSet) {
461
- nameSet.delete(obj);
462
- if (nameSet.size === 0) this._objectsByName.delete(prevName);
463
- }
464
- }
465
- if (meta.name) {
466
- let nameSet = this._objectsByName.get(meta.name);
467
- if (!nameSet) {
468
- nameSet = /* @__PURE__ */ new Set();
469
- this._objectsByName.set(meta.name, nameSet);
470
- }
471
- nameSet.add(obj);
472
- }
473
- }
474
- this._emit({ type: "update", object: obj, metadata: meta });
475
- }
476
- } catch (err) {
477
- r3fLog("store", `update: updateDynamicFields failed for "${obj.name || obj.uuid}"`, err);
478
- return false;
479
- }
480
- return changed;
481
- }
482
- // -------------------------------------------------------------------------
483
- // Tier 2: On-demand inspection (never cached)
484
- // -------------------------------------------------------------------------
485
- /**
486
- * Compute full inspection data from a live Three.js object.
487
- * This reads geometry buffers, material properties, world bounds, etc.
488
- * Cost: 0.1–2ms depending on geometry complexity.
489
- * Pass { includeGeometryData: true } to include vertex positions and triangle indices (higher cost for large meshes).
490
- */
491
- inspect(idOrUuid, options) {
492
- const obj = this.getObject3D(idOrUuid);
493
- if (!obj) return null;
494
- const meta = this._metaByObject.get(obj);
495
- if (!meta) return null;
496
- return inspectObject(obj, meta, options);
497
- }
498
- // -------------------------------------------------------------------------
499
- // Lookups (O(1))
500
- // -------------------------------------------------------------------------
501
- /** Get metadata by testId. O(1). */
502
- getByTestId(testId) {
503
- const obj = this._objectsByTestId.get(testId);
504
- if (!obj) return null;
505
- return this._metaByObject.get(obj) ?? null;
506
- }
507
- /** Get metadata by uuid. O(1). */
508
- getByUuid(uuid) {
509
- const obj = this._objectByUuid.get(uuid);
510
- if (!obj) return null;
511
- return this._metaByObject.get(obj) ?? null;
512
- }
513
- /** Get metadata by name (returns array since names aren't unique). O(1). */
514
- getByName(name) {
515
- const objs = this._objectsByName.get(name);
516
- if (!objs) return [];
517
- const results = [];
518
- for (const obj of objs) {
519
- const meta = this._metaByObject.get(obj);
520
- if (meta) results.push(meta);
521
- }
522
- return results;
523
- }
524
- /** Get direct children of an object by testId or uuid. Returns empty array if not found. */
525
- getChildren(idOrUuid) {
526
- const meta = this.getByTestId(idOrUuid) ?? this.getByUuid(idOrUuid);
527
- if (!meta) return [];
528
- const results = [];
529
- for (const childUuid of meta.childrenUuids) {
530
- const childMeta = this.getByUuid(childUuid);
531
- if (childMeta) results.push(childMeta);
532
- }
533
- return results;
534
- }
535
- /** Get parent of an object by testId or uuid. Returns null if not found or if root. */
536
- getParent(idOrUuid) {
537
- const meta = this.getByTestId(idOrUuid) ?? this.getByUuid(idOrUuid);
538
- if (!meta || meta.parentUuid === null) return null;
539
- return this.getByUuid(meta.parentUuid);
540
- }
541
- /**
542
- * Batch lookup: get metadata for multiple objects by testId or uuid.
543
- * Returns a Map from the requested id to its metadata (or null if not found).
544
- * Single round-trip — much more efficient than calling getByTestId/getByUuid
545
- * in a loop for BIM/CAD scenes with many objects.
546
- * O(k) where k is the number of requested ids.
547
- */
548
- getObjects(ids) {
549
- const results = /* @__PURE__ */ new Map();
550
- for (const id of ids) {
551
- const meta = this.getByTestId(id) ?? this.getByUuid(id);
552
- results.set(id, meta);
553
- }
554
- return results;
555
- }
556
- /**
557
- * Get all objects of a given Three.js type (e.g. "Mesh", "Group", "Line").
558
- * Linear scan — O(n) where n is total tracked objects.
559
- */
560
- getByType(type) {
561
- const results = [];
562
- for (const obj of this.getFlatList()) {
563
- const meta = this._metaByObject.get(obj);
564
- if (meta && meta.type === type) results.push(meta);
565
- }
566
- return results;
567
- }
568
- /**
569
- * Get all objects with a given geometry type (e.g. "BoxGeometry", "BufferGeometry").
570
- * Linear scan — O(n). Only meshes/points/lines have geometryType.
571
- */
572
- getByGeometryType(type) {
573
- const results = [];
574
- for (const obj of this.getFlatList()) {
575
- const meta = this._metaByObject.get(obj);
576
- if (meta && meta.geometryType === type) results.push(meta);
577
- }
578
- return results;
579
- }
580
- /**
581
- * Get all objects with a given material type (e.g. "MeshStandardMaterial").
582
- * Linear scan — O(n). Only meshes/points/lines have materialType.
583
- */
584
- getByMaterialType(type) {
585
- const results = [];
586
- for (const obj of this.getFlatList()) {
587
- const meta = this._metaByObject.get(obj);
588
- if (meta && meta.materialType === type) results.push(meta);
589
- }
590
- return results;
591
- }
592
- /**
593
- * Get all objects that have a specific userData key.
594
- * If `value` is provided, only returns objects where `userData[key]` matches.
595
- * Uses JSON.stringify for deep equality on complex values.
596
- * Linear scan — O(n).
597
- */
598
- getByUserData(key, value) {
599
- const results = [];
600
- const checkValue = value !== void 0;
601
- const valueJson = checkValue ? JSON.stringify(value) : "";
602
- for (const obj of this.getFlatList()) {
603
- if (!(key in obj.userData)) continue;
604
- if (checkValue && JSON.stringify(obj.userData[key]) !== valueJson) continue;
605
- const meta = this._metaByObject.get(obj);
606
- if (meta) results.push(meta);
607
- }
608
- return results;
609
- }
610
- /**
611
- * Count objects of a given Three.js type.
612
- * More efficient than getByType().length — no array allocation.
613
- * Linear scan — O(n).
614
- */
615
- getCountByType(type) {
616
- let count = 0;
617
- for (const obj of this.getFlatList()) {
618
- const meta = this._metaByObject.get(obj);
619
- if (meta && meta.type === type) count++;
620
- }
621
- return count;
622
- }
623
- /** Get the raw Three.js Object3D by testId or uuid. */
624
- getObject3D(idOrUuid) {
625
- return this._objectsByTestId.get(idOrUuid) ?? this._objectByUuid.get(idOrUuid) ?? null;
626
- }
627
- /** Get metadata for a known Object3D reference. */
628
- getMetadata(obj) {
629
- return this._metaByObject.get(obj) ?? null;
630
- }
631
- /** Check if an object is registered. */
632
- has(obj) {
633
- return this._metaByObject.has(obj);
634
- }
635
- /** Total number of tracked objects. */
636
- getCount() {
637
- return this._objectByUuid.size;
638
- }
639
- /** Check if a root scene is tracked. */
640
- isTrackedRoot(obj) {
641
- return this._trackedRoots.has(obj);
642
- }
643
- /**
644
- * Check if an object belongs to a tracked scene.
645
- * Fast path: checks the `__r3fdom_tracked` flag set during register (O(1)).
646
- * Fallback: walks up the parent chain to find a tracked root.
647
- * The fallback is needed for newly added objects that aren't registered yet.
648
- */
649
- isInTrackedScene(obj) {
650
- if (obj.userData?.__r3fdom_tracked) return true;
651
- let current = obj.parent;
652
- while (current) {
653
- if (current.userData?.__r3fdom_tracked) return true;
654
- if (this._trackedRoots.has(current)) return true;
655
- current = current.parent;
656
- }
657
- return false;
658
- }
659
- // -------------------------------------------------------------------------
660
- // Flat list for amortized iteration
661
- // -------------------------------------------------------------------------
662
- /**
663
- * Get a flat array of all tracked objects for amortized batch processing.
664
- * Rebuilds lazily when the list is dirty (objects added/removed).
665
- */
666
- getFlatList() {
667
- if (this._flatListDirty) {
668
- this._flatList = Array.from(this._objectByUuid.values());
669
- this._flatListDirty = false;
670
- }
671
- return this._flatList;
672
- }
673
- // -------------------------------------------------------------------------
674
- // Priority dirty queue
675
- // -------------------------------------------------------------------------
676
- /** Mark an object as dirty (needs priority sync next frame). */
677
- markDirty(obj) {
678
- if (this._metaByObject.has(obj)) {
679
- this._dirtyQueue.add(obj);
680
- }
681
- }
682
- /**
683
- * Drain the dirty queue, returning all objects that need priority sync.
684
- * Clears the queue after draining.
685
- */
686
- drainDirtyQueue() {
687
- if (this._dirtyQueue.size === 0) return [];
688
- const objects = Array.from(this._dirtyQueue);
689
- this._dirtyQueue.clear();
690
- return objects;
691
- }
692
- /** Number of objects currently in the dirty queue. */
693
- getDirtyCount() {
694
- return this._dirtyQueue.size;
695
- }
696
- // -------------------------------------------------------------------------
697
- // Event system
698
- // -------------------------------------------------------------------------
699
- /** Subscribe to store events (add, remove, update). */
700
- subscribe(listener) {
701
- this._listeners.push(listener);
702
- return () => {
703
- const idx = this._listeners.indexOf(listener);
704
- if (idx !== -1) this._listeners.splice(idx, 1);
705
- };
706
- }
707
- _emit(event) {
708
- for (const listener of this._listeners) {
709
- listener(event);
710
- }
711
- }
712
- // -------------------------------------------------------------------------
713
- // GC: sweep orphaned objects
714
- // -------------------------------------------------------------------------
715
- /**
716
- * Sweep objects in `_objectByUuid` that are no longer in any tracked scene.
717
- * This catches objects that were removed from the scene graph without
718
- * triggering the patched Object3D.remove (e.g. direct `.children` splice,
719
- * or the remove hook failing silently).
720
- *
721
- * At BIM scale, call this periodically (e.g. every 30s or after a floor
722
- * load/unload) to prevent memory leaks from retained Object3D references.
723
- *
724
- * Returns the number of orphans cleaned up.
725
- */
726
- sweepOrphans() {
727
- let swept = 0;
728
- for (const [uuid, obj] of this._objectByUuid) {
729
- if (!obj.parent && !this._trackedRoots.has(obj)) {
730
- this.unregister(obj);
731
- swept++;
732
- r3fLog("store", `sweepOrphans: removed orphan "${obj.name || uuid}"`);
733
- }
734
- }
735
- return swept;
736
- }
737
- // -------------------------------------------------------------------------
738
- // Cleanup
739
- // -------------------------------------------------------------------------
740
- /** Remove all tracked objects and reset state. */
741
- dispose() {
742
- this._cancelAsyncRegistration();
743
- for (const obj of this._objectByUuid.values()) {
744
- if (obj.userData) delete obj.userData.__r3fdom_tracked;
745
- }
746
- this._objectByUuid.clear();
747
- this._objectsByTestId.clear();
748
- this._objectsByName.clear();
749
- this._flatList = [];
750
- this._flatListDirty = true;
751
- this._dirtyQueue.clear();
752
- this._listeners = [];
753
- }
754
- };
755
-
756
- // src/mirror/CustomElements.ts
757
- var TAG_MAP = {
758
- // Scenes
759
- Scene: "three-scene",
760
- // Groups / containers
761
- Group: "three-group",
762
- LOD: "three-group",
763
- Bone: "three-group",
764
- // Meshes
765
- Mesh: "three-mesh",
766
- SkinnedMesh: "three-mesh",
767
- InstancedMesh: "three-mesh",
768
- LineSegments: "three-mesh",
769
- Line: "three-mesh",
770
- LineLoop: "three-mesh",
771
- Points: "three-mesh",
772
- Sprite: "three-mesh",
773
- // Lights
774
- AmbientLight: "three-light",
775
- DirectionalLight: "three-light",
776
- HemisphereLight: "three-light",
777
- PointLight: "three-light",
778
- RectAreaLight: "three-light",
779
- SpotLight: "three-light",
780
- LightProbe: "three-light",
781
- // Cameras
782
- PerspectiveCamera: "three-camera",
783
- OrthographicCamera: "three-camera",
784
- ArrayCamera: "three-camera",
785
- CubeCamera: "three-camera",
786
- // Helpers (map to object as fallback)
787
- BoxHelper: "three-object",
788
- ArrowHelper: "three-object",
789
- AxesHelper: "three-object",
790
- GridHelper: "three-object",
791
- SkeletonHelper: "three-object"
792
- };
793
- var ALL_TAGS = [
794
- "three-scene",
795
- "three-group",
796
- "three-mesh",
797
- "three-light",
798
- "three-camera",
799
- "three-object"
800
- ];
801
- var DEFAULT_TAG = "three-object";
802
- function getTagForType(type) {
803
- return TAG_MAP[type] ?? DEFAULT_TAG;
804
- }
805
- var _store = null;
806
- var ThreeElement = class extends HTMLElement {
807
- // -------------------------------------------------------------------------
808
- // Tier 1: Lightweight cached metadata (always available)
809
- // -------------------------------------------------------------------------
810
- /**
811
- * Returns the Tier 1 cached metadata for this object.
812
- * Instant, no computation. Returns null if the element is not linked.
813
- */
814
- get metadata() {
815
- const uuid = this.dataset.uuid;
816
- if (!uuid || !_store) return null;
817
- return _store.getByUuid(uuid);
818
- }
819
- // -------------------------------------------------------------------------
820
- // Tier 2: On-demand heavy inspection (reads live Three.js object)
821
- // -------------------------------------------------------------------------
822
- /**
823
- * Performs a full inspection of the linked Three.js object.
824
- * Reads geometry buffers, material properties, world bounds, etc.
825
- * Cost: 0.1–2ms depending on geometry complexity.
826
- */
827
- inspect() {
828
- const uuid = this.dataset.uuid;
829
- if (!uuid || !_store) return null;
830
- return _store.inspect(uuid);
831
- }
832
- // -------------------------------------------------------------------------
833
- // Raw Three.js object reference
834
- // -------------------------------------------------------------------------
835
- /**
836
- * Returns the raw Three.js Object3D linked to this DOM element.
837
- * Allows direct access to any Three.js property or method.
838
- */
839
- get object3D() {
840
- const uuid = this.dataset.uuid;
841
- if (!uuid || !_store) return null;
842
- return _store.getObject3D(uuid);
843
- }
844
- // -------------------------------------------------------------------------
845
- // Convenience shortcuts (read from Tier 1 metadata)
846
- // -------------------------------------------------------------------------
847
- /**
848
- * Local position as [x, y, z].
849
- */
850
- get position() {
851
- return this.metadata?.position ?? null;
852
- }
853
- /**
854
- * Local euler rotation as [x, y, z] in radians.
855
- */
856
- get rotation() {
857
- return this.metadata?.rotation ?? null;
858
- }
859
- /**
860
- * Local scale as [x, y, z].
861
- */
862
- get scale() {
863
- return this.metadata?.scale ?? null;
864
- }
865
- /**
866
- * Whether the object is visible (does not check parent chain).
867
- */
868
- get visible() {
869
- return this.metadata?.visible ?? false;
870
- }
871
- /**
872
- * The testId from userData.testId, if set.
873
- */
874
- get testId() {
875
- return this.metadata?.testId;
876
- }
877
- /**
878
- * World-space bounding box. Computed on-demand (Tier 2).
879
- */
880
- get bounds() {
881
- const inspection = this.inspect();
882
- return inspection?.bounds ?? null;
883
- }
884
- // -------------------------------------------------------------------------
885
- // Interaction methods
886
- // -------------------------------------------------------------------------
887
- /**
888
- * Trigger a deterministic click on the linked 3D object.
889
- * Projects the object center to screen coordinates and dispatches pointer events.
890
- */
891
- click() {
892
- const uuid = this.dataset.uuid;
893
- if (!uuid) {
894
- console.warn("[react-three-dom] Cannot click: element has no data-uuid");
895
- return;
896
- }
897
- if (typeof window.__R3F_DOM__?.click === "function") {
898
- window.__R3F_DOM__.click(uuid);
899
- } else {
900
- console.warn("[react-three-dom] Cannot click: bridge API not initialized");
901
- }
902
- }
903
- /**
904
- * Trigger a deterministic hover on the linked 3D object.
905
- */
906
- hover() {
907
- const uuid = this.dataset.uuid;
908
- if (!uuid) {
909
- console.warn("[react-three-dom] Cannot hover: element has no data-uuid");
910
- return;
911
- }
912
- if (typeof window.__R3F_DOM__?.hover === "function") {
913
- window.__R3F_DOM__.hover(uuid);
914
- } else {
915
- console.warn("[react-three-dom] Cannot hover: bridge API not initialized");
916
- }
917
- }
918
- // -------------------------------------------------------------------------
919
- // Console-friendly output
920
- // -------------------------------------------------------------------------
921
- /**
922
- * Returns a readable string representation for console output.
923
- */
924
- toString() {
925
- const tag = this.tagName.toLowerCase();
926
- const name = this.dataset.name ? ` name="${this.dataset.name}"` : "";
927
- const testId = this.dataset.testId ? ` testId="${this.dataset.testId}"` : "";
928
- const type = this.dataset.type ? ` type="${this.dataset.type}"` : "";
929
- return `<${tag}${name}${testId}${type}>`;
930
- }
931
- };
932
- var _registered = false;
933
- function ensureCustomElements(store) {
934
- if (_registered) return;
935
- if (typeof customElements === "undefined") return;
936
- _store = store;
937
- for (const tag of ALL_TAGS) {
938
- if (!customElements.get(tag)) {
939
- const elementClass = class extends ThreeElement {
940
- };
941
- Object.defineProperty(elementClass, "name", { value: `ThreeElement_${tag}` });
942
- customElements.define(tag, elementClass);
943
- }
944
- }
945
- _registered = true;
946
- }
947
-
948
- // src/mirror/attributes.ts
949
- var ATTRIBUTE_MAP = {
950
- "data-uuid": (m) => m.uuid,
951
- "data-name": (m) => m.name || void 0,
952
- "data-type": (m) => m.type,
953
- "data-visible": (m) => String(m.visible),
954
- "data-test-id": (m) => m.testId,
955
- "data-geometry": (m) => m.geometryType,
956
- "data-material": (m) => m.materialType,
957
- "data-position": (m) => serializeTuple(m.position),
958
- "data-rotation": (m) => serializeTuple(m.rotation),
959
- "data-scale": (m) => serializeTuple(m.scale),
960
- "data-vertex-count": (m) => m.vertexCount !== void 0 ? String(m.vertexCount) : void 0,
961
- "data-triangle-count": (m) => m.triangleCount !== void 0 ? String(m.triangleCount) : void 0,
962
- "data-instance-count": (m) => m.instanceCount !== void 0 ? String(m.instanceCount) : void 0,
963
- "data-fov": (m) => m.fov !== void 0 ? String(m.fov) : void 0,
964
- "data-near": (m) => m.near !== void 0 ? String(m.near) : void 0,
965
- "data-far": (m) => m.far !== void 0 ? String(m.far) : void 0,
966
- "data-zoom": (m) => m.zoom !== void 0 ? String(m.zoom) : void 0
967
- };
968
- var MANAGED_ATTRIBUTES = Object.keys(ATTRIBUTE_MAP);
969
- function serializeTuple(tuple) {
970
- return `${round(tuple[0])},${round(tuple[1])},${round(tuple[2])}`;
971
- }
972
- function round(n) {
973
- const rounded = Math.round(n * 1e4) / 1e4;
974
- return String(rounded === 0 ? 0 : rounded);
975
- }
976
- function computeAttributes(meta) {
977
- const attrs = /* @__PURE__ */ new Map();
978
- for (const [key, extractor] of Object.entries(ATTRIBUTE_MAP)) {
979
- const value = extractor(meta);
980
- if (value !== void 0) {
981
- attrs.set(key, value);
982
- }
983
- }
984
- return attrs;
985
- }
986
- function applyAttributes(element, meta, prevAttrs) {
987
- let writeCount = 0;
988
- const newAttrs = computeAttributes(meta);
989
- for (const [key, value] of newAttrs) {
990
- const prev = prevAttrs.get(key);
991
- if (prev !== value) {
992
- element.setAttribute(key, value);
993
- prevAttrs.set(key, value);
994
- writeCount++;
995
- }
996
- }
997
- for (const key of prevAttrs.keys()) {
998
- if (!newAttrs.has(key)) {
999
- element.removeAttribute(key);
1000
- prevAttrs.delete(key);
1001
- writeCount++;
1002
- }
1003
- }
1004
- return writeCount;
1005
- }
1006
-
1007
- // src/mirror/DomMirror.ts
1008
- var DomMirror = class {
1009
- constructor(store, maxNodes = 2e3) {
1010
- this._rootElement = null;
1011
- // Materialized nodes indexed by uuid
1012
- this._nodes = /* @__PURE__ */ new Map();
1013
- // LRU doubly-linked list for eviction (head = most recently used, tail = least)
1014
- this._lruHead = null;
1015
- this._lruTail = null;
1016
- this._lruSize = 0;
1017
- // UUID → parent UUID mapping for DOM tree structure
1018
- this._parentMap = /* @__PURE__ */ new Map();
1019
- /** When true, mirror elements use pointer-events: auto so DevTools element picker can select them. */
1020
- this._inspectMode = false;
1021
- /** Async materialization state for inspect mode */
1022
- this._asyncQueue = [];
1023
- this._asyncIdleHandle = null;
1024
- this._asyncBatchSize = 200;
1025
- this._store = store;
1026
- this._maxNodes = maxNodes;
1027
- }
1028
- // -------------------------------------------------------------------------
1029
- // Initialization
1030
- // -------------------------------------------------------------------------
1031
- /**
1032
- * Set the root DOM element where the mirror tree will be appended.
1033
- * Typically a hidden div: <div id="three-dom-root" style="display:none">
1034
- */
1035
- setRoot(rootElement) {
1036
- this._rootElement = rootElement;
1037
- }
1038
- /**
1039
- * Enable or disable "inspect mode". When turning on, kicks off async
1040
- * chunked materialization so the full tree becomes browsable in the
1041
- * Elements tab without blocking the main thread.
1042
- *
1043
- * At BIM scale (100k-200k objects) the old synchronous loop would freeze
1044
- * the page for 2-10s. The new approach uses requestIdleCallback to
1045
- * spread work across idle frames (~200 nodes per idle slice, ~5ms each).
1046
- */
1047
- setInspectMode(on) {
1048
- if (this._inspectMode === on) return;
1049
- this._inspectMode = on;
1050
- if (on) {
1051
- this._startAsyncMaterialization();
1052
- } else {
1053
- this._cancelAsyncMaterialization();
1054
- }
1055
- r3fLog("inspect", "setInspectMode", { on, nodeCount: this._nodes.size });
1056
- }
1057
- /** Whether inspect mode is currently enabled. */
1058
- getInspectMode() {
1059
- return this._inspectMode;
1060
- }
1061
- /**
1062
- * Build the initial DOM tree from the scene.
1063
- * Materializes the top 2 levels of the scene hierarchy.
1064
- */
1065
- buildInitialTree(scene) {
1066
- if (!this._rootElement) return;
1067
- this.materializeSubtree(scene.uuid, 2);
1068
- }
1069
- // -------------------------------------------------------------------------
1070
- // Materialization
1071
- // -------------------------------------------------------------------------
1072
- /**
1073
- * Create a DOM node for a single object.
1074
- * If already materialized, touches the LRU and returns the existing element.
1075
- */
1076
- materialize(uuid) {
1077
- const existing = this._nodes.get(uuid);
1078
- if (existing) {
1079
- this._lruTouch(existing.lruNode);
1080
- return existing.element;
1081
- }
1082
- const meta = this._store.getByUuid(uuid);
1083
- if (!meta) return null;
1084
- if (this._lruSize >= this._maxNodes) {
1085
- this._evictLRU();
1086
- }
1087
- const tag = getTagForType(meta.type);
1088
- const element = document.createElement(tag);
1089
- element.style.cssText = "display:contents;";
1090
- const prevAttrs = /* @__PURE__ */ new Map();
1091
- applyAttributes(element, meta, prevAttrs);
1092
- const lruNode = { uuid, prev: null, next: null };
1093
- this._lruPush(lruNode);
1094
- const node = { element, prevAttrs, lruNode };
1095
- this._nodes.set(uuid, node);
1096
- this._parentMap.set(uuid, meta.parentUuid);
1097
- this._insertIntoDom(uuid, meta.parentUuid, element);
1098
- return element;
1099
- }
1100
- /**
1101
- * Materialize a subtree starting from the given uuid, up to the specified depth.
1102
- * depth=0 materializes just the node, depth=1 includes direct children, etc.
1103
- */
1104
- materializeSubtree(uuid, depth) {
1105
- this.materialize(uuid);
1106
- if (depth <= 0) return;
1107
- const meta = this._store.getByUuid(uuid);
1108
- if (!meta) return;
1109
- for (const childUuid of meta.childrenUuids) {
1110
- this.materializeSubtree(childUuid, depth - 1);
1111
- }
1112
- }
1113
- /**
1114
- * Remove a DOM node but keep JS metadata in the ObjectStore.
1115
- * Called by LRU eviction or when an object is removed from the scene.
1116
- * Also dematerializes any materialized descendants so they don't become
1117
- * orphaned entries in the LRU / _nodes maps.
1118
- */
1119
- dematerialize(uuid) {
1120
- const node = this._nodes.get(uuid);
1121
- if (!node) return;
1122
- const descendants = this._collectMaterializedDescendants(uuid);
1123
- for (const descUuid of descendants) {
1124
- const descNode = this._nodes.get(descUuid);
1125
- if (descNode) {
1126
- this._lruRemove(descNode.lruNode);
1127
- this._nodes.delete(descUuid);
1128
- this._parentMap.delete(descUuid);
1129
- }
1130
- }
1131
- node.element.remove();
1132
- this._lruRemove(node.lruNode);
1133
- this._nodes.delete(uuid);
1134
- this._parentMap.delete(uuid);
1135
- }
1136
- /**
1137
- * Collect all materialized descendants of a uuid by walking _parentMap.
1138
- */
1139
- _collectMaterializedDescendants(parentUuid) {
1140
- const result = [];
1141
- for (const [childUuid, pUuid] of this._parentMap) {
1142
- if (pUuid === parentUuid) {
1143
- result.push(childUuid);
1144
- result.push(...this._collectMaterializedDescendants(childUuid));
1145
- }
1146
- }
1147
- return result;
1148
- }
1149
- // -------------------------------------------------------------------------
1150
- // Structural updates (called by Object3D.add/remove patch)
1151
- // -------------------------------------------------------------------------
1152
- /**
1153
- * Called when a new object is added to the tracked scene.
1154
- * Only materializes if the parent is already materialized (lazy expansion).
1155
- */
1156
- onObjectAdded(obj) {
1157
- const parentUuid = obj.parent?.uuid;
1158
- if (!parentUuid) return;
1159
- const parentNode = this._nodes.get(parentUuid);
1160
- if (parentNode) {
1161
- this.materialize(obj.uuid);
1162
- }
1163
- if (parentNode) {
1164
- const parentMeta = this._store.getByUuid(parentUuid);
1165
- if (parentMeta) {
1166
- applyAttributes(parentNode.element, parentMeta, parentNode.prevAttrs);
1167
- }
1168
- }
1169
- }
1170
- /**
1171
- * Called when an object is removed from the tracked scene.
1172
- * Dematerializes the object and all its descendants.
1173
- */
1174
- onObjectRemoved(obj) {
1175
- obj.traverse((child) => {
1176
- this.dematerialize(child.uuid);
1177
- });
1178
- }
1179
- // -------------------------------------------------------------------------
1180
- // Attribute sync (called per-frame by ThreeDom)
1181
- // -------------------------------------------------------------------------
1182
- /**
1183
- * Sync Tier 1 attributes for an object if it's materialized.
1184
- * No-op if the object has no materialized DOM node.
1185
- * Returns the number of DOM writes performed.
1186
- */
1187
- syncAttributes(obj) {
1188
- const node = this._nodes.get(obj.uuid);
1189
- if (!node) return 0;
1190
- const meta = this._store.getMetadata(obj);
1191
- if (!meta) return 0;
1192
- return applyAttributes(node.element, meta, node.prevAttrs);
1193
- }
1194
- /**
1195
- * Sync attributes by uuid (when you don't have the Object3D ref).
1196
- */
1197
- syncAttributesByUuid(uuid) {
1198
- const node = this._nodes.get(uuid);
1199
- if (!node) return 0;
1200
- const meta = this._store.getByUuid(uuid);
1201
- if (!meta) return 0;
1202
- return applyAttributes(node.element, meta, node.prevAttrs);
1203
- }
1204
- // -------------------------------------------------------------------------
1205
- // Querying
1206
- // -------------------------------------------------------------------------
1207
- /**
1208
- * Get the root DOM element.
1209
- */
1210
- getRootElement() {
1211
- return this._rootElement;
1212
- }
1213
- /**
1214
- * Get the materialized DOM element for an object, if it exists.
1215
- */
1216
- getElement(uuid) {
1217
- const node = this._nodes.get(uuid);
1218
- if (node) {
1219
- this._lruTouch(node.lruNode);
1220
- return node.element;
1221
- }
1222
- return null;
1223
- }
1224
- /**
1225
- * Get or lazily materialize a DOM element for an object.
1226
- * Also materializes the ancestor chain so the element is correctly
1227
- * nested in the DOM tree. Used by InspectController so that
1228
- * hover/click always produces a valid mirror element regardless
1229
- * of whether async materialization has reached it yet.
1230
- */
1231
- getOrMaterialize(uuid) {
1232
- const existing = this._nodes.get(uuid);
1233
- if (existing) {
1234
- this._lruTouch(existing.lruNode);
1235
- return existing.element;
1236
- }
1237
- this._materializeAncestorChain(uuid);
1238
- return this.materialize(uuid);
1239
- }
1240
- /**
1241
- * Check if an object has a materialized DOM node.
1242
- */
1243
- isMaterialized(uuid) {
1244
- return this._nodes.has(uuid);
1245
- }
1246
- /**
1247
- * Query the mirror DOM using a CSS selector.
1248
- * Falls back to searching the ObjectStore if no materialized nodes match,
1249
- * then materializes the matching objects.
1250
- */
1251
- querySelector(selector) {
1252
- if (!this._rootElement) return null;
1253
- const existing = this._rootElement.querySelector(selector);
1254
- if (existing) return existing;
1255
- const uuid = this._findUuidBySelector(selector);
1256
- if (uuid) {
1257
- return this.materialize(uuid);
1258
- }
1259
- return null;
1260
- }
1261
- /**
1262
- * Query all matching elements in the mirror DOM.
1263
- */
1264
- querySelectorAll(selector) {
1265
- if (!this._rootElement) return [];
1266
- const uuids = this._findAllUuidsBySelector(selector);
1267
- const elements = [];
1268
- for (const uuid of uuids) {
1269
- const el = this.materialize(uuid);
1270
- if (el) elements.push(el);
1271
- }
1272
- return elements;
1273
- }
1274
- // -------------------------------------------------------------------------
1275
- // Configuration
1276
- // -------------------------------------------------------------------------
1277
- /**
1278
- * Set the maximum number of materialized DOM nodes.
1279
- * If current count exceeds the new max, excess nodes are evicted immediately.
1280
- */
1281
- setMaxNodes(max) {
1282
- this._maxNodes = max;
1283
- while (this._lruSize > this._maxNodes) {
1284
- this._evictLRU();
1285
- }
1286
- }
1287
- /** Current number of materialized DOM nodes. */
1288
- getMaterializedCount() {
1289
- return this._nodes.size;
1290
- }
1291
- /** Maximum allowed materialized DOM nodes. */
1292
- getMaxNodes() {
1293
- return this._maxNodes;
1294
- }
1295
- // -------------------------------------------------------------------------
1296
- // Cleanup
1297
- // -------------------------------------------------------------------------
1298
- /**
1299
- * Remove all materialized DOM nodes and reset state.
1300
- */
1301
- dispose() {
1302
- this._cancelAsyncMaterialization();
1303
- for (const [, node] of this._nodes) {
1304
- node.element.remove();
1305
- }
1306
- this._nodes.clear();
1307
- this._parentMap.clear();
1308
- this._lruHead = null;
1309
- this._lruTail = null;
1310
- this._lruSize = 0;
1311
- if (this._rootElement) {
1312
- this._rootElement.innerHTML = "";
1313
- }
1314
- }
1315
- // -------------------------------------------------------------------------
1316
- // Private: DOM insertion
1317
- // -------------------------------------------------------------------------
1318
- /**
1319
- * Insert a newly created element into the correct position in the DOM tree.
1320
- */
1321
- _insertIntoDom(_uuid, parentUuid, element) {
1322
- if (!this._rootElement) return;
1323
- if (!parentUuid) {
1324
- this._rootElement.appendChild(element);
1325
- return;
1326
- }
1327
- const parentNode = this._nodes.get(parentUuid);
1328
- if (parentNode) {
1329
- parentNode.element.appendChild(element);
1330
- } else {
1331
- this._rootElement.appendChild(element);
1332
- }
1333
- }
1334
- // -------------------------------------------------------------------------
1335
- // Private: Ancestor chain materialization
1336
- // -------------------------------------------------------------------------
1337
- /**
1338
- * Materialize all ancestors of a uuid from root down, so the target
1339
- * element will be correctly nested when materialized.
1340
- */
1341
- _materializeAncestorChain(uuid) {
1342
- const chain = [];
1343
- let currentUuid = uuid;
1344
- while (currentUuid) {
1345
- if (this._nodes.has(currentUuid)) break;
1346
- const meta = this._store.getByUuid(currentUuid);
1347
- if (!meta) break;
1348
- chain.push(currentUuid);
1349
- currentUuid = meta.parentUuid;
1350
- }
1351
- for (let i = chain.length - 1; i > 0; i--) {
1352
- this.materialize(chain[i]);
1353
- }
1354
- }
1355
- // -------------------------------------------------------------------------
1356
- // Private: Async chunked materialization for inspect mode
1357
- // -------------------------------------------------------------------------
1358
- _startAsyncMaterialization() {
1359
- this._cancelAsyncMaterialization();
1360
- const flatList = this._store.getFlatList();
1361
- this._asyncQueue = [];
1362
- for (const obj of flatList) {
1363
- if (obj.userData?.__r3fdom_internal) continue;
1364
- if (this._nodes.has(obj.uuid)) continue;
1365
- this._asyncQueue.push(obj.uuid);
1366
- }
1367
- if (this._asyncQueue.length === 0) return;
1368
- r3fLog("inspect", `Async materialization started: ${this._asyncQueue.length} objects queued`);
1369
- this._scheduleAsyncChunk();
1370
- }
1371
- _cancelAsyncMaterialization() {
1372
- if (this._asyncIdleHandle !== null) {
1373
- if (typeof cancelIdleCallback === "function") {
1374
- cancelIdleCallback(this._asyncIdleHandle);
1375
- } else {
1376
- clearTimeout(this._asyncIdleHandle);
1377
- }
1378
- this._asyncIdleHandle = null;
1379
- }
1380
- this._asyncQueue = [];
1381
- }
1382
- _scheduleAsyncChunk() {
1383
- if (this._asyncQueue.length === 0) {
1384
- this._asyncIdleHandle = null;
1385
- r3fLog("inspect", `Async materialization complete: ${this._nodes.size} nodes materialized`);
1386
- return;
1387
- }
1388
- const callback = (deadline) => {
1389
- const hasTimeRemaining = deadline ? () => deadline.timeRemaining() > 2 : () => true;
1390
- let processed = 0;
1391
- while (this._asyncQueue.length > 0 && processed < this._asyncBatchSize && hasTimeRemaining()) {
1392
- const uuid = this._asyncQueue.shift();
1393
- if (!this._nodes.has(uuid)) {
1394
- this.materialize(uuid);
1395
- }
1396
- processed++;
1397
- }
1398
- this._scheduleAsyncChunk();
1399
- };
1400
- if (typeof requestIdleCallback === "function") {
1401
- this._asyncIdleHandle = requestIdleCallback(callback, { timeout: 100 });
1402
- } else {
1403
- this._asyncIdleHandle = setTimeout(callback, 16);
1404
- }
1405
- }
1406
- // -------------------------------------------------------------------------
1407
- // Private: Selector → UUID resolution
1408
- // -------------------------------------------------------------------------
1409
- /**
1410
- * Parse common CSS selector patterns and resolve to a uuid from the store.
1411
- * Supports:
1412
- * [data-test-id="value"]
1413
- * [data-name="value"]
1414
- * [data-uuid="value"]
1415
- * three-mesh, three-light, etc. (by tag/type)
1416
- */
1417
- _findUuidBySelector(selector) {
1418
- const testIdMatch = selector.match(/\[data-test-id=["']([^"']+)["']\]/);
1419
- if (testIdMatch) {
1420
- const meta = this._store.getByTestId(testIdMatch[1]);
1421
- return meta?.uuid ?? null;
1422
- }
1423
- const uuidMatch = selector.match(/\[data-uuid=["']([^"']+)["']\]/);
1424
- if (uuidMatch) {
1425
- const meta = this._store.getByUuid(uuidMatch[1]);
1426
- return meta?.uuid ?? null;
1427
- }
1428
- const nameMatch = selector.match(/\[data-name=["']([^"']+)["']\]/);
1429
- if (nameMatch) {
1430
- const metas = this._store.getByName(nameMatch[1]);
1431
- return metas.length > 0 ? metas[0].uuid : null;
1432
- }
1433
- return null;
1434
- }
1435
- /**
1436
- * Find all UUIDs matching a selector pattern.
1437
- */
1438
- _findAllUuidsBySelector(selector) {
1439
- const uuids = [];
1440
- const testIdMatch = selector.match(/\[data-test-id=["']([^"']+)["']\]/);
1441
- if (testIdMatch) {
1442
- const meta = this._store.getByTestId(testIdMatch[1]);
1443
- if (meta) uuids.push(meta.uuid);
1444
- return uuids;
1445
- }
1446
- const nameMatch = selector.match(/\[data-name=["']([^"']+)["']\]/);
1447
- if (nameMatch) {
1448
- const metas = this._store.getByName(nameMatch[1]);
1449
- for (const m of metas) uuids.push(m.uuid);
1450
- return uuids;
1451
- }
1452
- const tagMatch = selector.match(/^(three-(?:scene|group|mesh|light|camera|object))$/);
1453
- if (tagMatch) {
1454
- const targetTag = tagMatch[1];
1455
- const allObjects = this._store.getFlatList();
1456
- for (const obj of allObjects) {
1457
- const meta = this._store.getMetadata(obj);
1458
- if (meta && getTagForType(meta.type) === targetTag) {
1459
- uuids.push(meta.uuid);
1460
- }
1461
- }
1462
- return uuids;
1463
- }
1464
- return uuids;
1465
- }
1466
- // -------------------------------------------------------------------------
1467
- // Private: LRU doubly-linked list operations
1468
- // -------------------------------------------------------------------------
1469
- /** Add a node to the front (most recently used). */
1470
- _lruPush(node) {
1471
- node.prev = null;
1472
- node.next = this._lruHead;
1473
- if (this._lruHead) {
1474
- this._lruHead.prev = node;
1475
- }
1476
- this._lruHead = node;
1477
- if (!this._lruTail) {
1478
- this._lruTail = node;
1479
- }
1480
- this._lruSize++;
1481
- }
1482
- /** Remove a node from the list. */
1483
- _lruRemove(node) {
1484
- if (node.prev) {
1485
- node.prev.next = node.next;
1486
- } else {
1487
- this._lruHead = node.next;
1488
- }
1489
- if (node.next) {
1490
- node.next.prev = node.prev;
1491
- } else {
1492
- this._lruTail = node.prev;
1493
- }
1494
- node.prev = null;
1495
- node.next = null;
1496
- this._lruSize--;
1497
- }
1498
- /** Move a node to the front (most recently used). */
1499
- _lruTouch(node) {
1500
- if (this._lruHead === node) return;
1501
- this._lruRemove(node);
1502
- this._lruPush(node);
1503
- }
1504
- /** Evict the least recently used node. */
1505
- _evictLRU() {
1506
- if (!this._lruTail) return;
1507
- const uuid = this._lruTail.uuid;
1508
- this.dematerialize(uuid);
1509
- }
1510
- };
1511
- var _patched = false;
1512
- var _originalAdd = null;
1513
- var _originalRemove = null;
1514
- var _activePairs = [];
1515
- function findTrackingPair(obj) {
1516
- for (const pair of _activePairs) {
1517
- if (pair.store.isInTrackedScene(obj)) {
1518
- return pair;
1519
- }
1520
- }
1521
- return null;
1522
- }
1523
- function registerSubtree(obj, store, mirror, instanceKey) {
1524
- obj.traverse((child) => {
1525
- if (!store.has(child) && shouldRegister(instanceKey, child)) {
1526
- ensureAncestorChain(child, store, mirror);
1527
- store.register(child);
1528
- mirror.onObjectAdded(child);
1529
- }
1530
- });
1531
- }
1532
- function patchObject3D(store, mirror, instanceKey = "") {
1533
- _activePairs.push({ store, mirror, instanceKey });
1534
- if (!_patched) {
1535
- r3fLog("patch", "Patching Object3D.prototype.add and .remove");
1536
- _originalAdd = Object3D.prototype.add;
1537
- _originalRemove = Object3D.prototype.remove;
1538
- Object3D.prototype.add = function patchedAdd(...objects) {
1539
- _originalAdd.call(this, ...objects);
1540
- const pair = findTrackingPair(this);
1541
- if (pair) {
1542
- for (const obj of objects) {
1543
- if (obj === this) continue;
1544
- try {
1545
- r3fLog("patch", `patchedAdd: "${obj.name || obj.type}" added to "${this.name || this.type}"`);
1546
- registerSubtree(obj, pair.store, pair.mirror, pair.instanceKey);
1547
- } catch (err) {
1548
- r3fLog("patch", `patchedAdd: failed to register "${obj.name || obj.type}"`, err);
1549
- }
1550
- }
1551
- pair.store.update(this);
1552
- pair.store.markDirty(this);
1553
- }
1554
- return this;
1555
- };
1556
- Object3D.prototype.remove = function patchedRemove(...objects) {
1557
- const pair = findTrackingPair(this);
1558
- if (pair) {
1559
- for (const obj of objects) {
1560
- if (obj === this) continue;
1561
- try {
1562
- r3fLog("patch", `patchedRemove: "${obj.name || obj.type}" removed from "${this.name || this.type}"`);
1563
- pair.mirror.onObjectRemoved(obj);
1564
- obj.traverse((child) => {
1565
- pair.store.unregister(child);
1566
- });
1567
- } catch (err) {
1568
- r3fLog("patch", `patchedRemove: failed to unregister "${obj.name || obj.type}"`, err);
1569
- }
1570
- }
1571
- pair.store.update(this);
1572
- pair.store.markDirty(this);
1573
- }
1574
- _originalRemove.call(this, ...objects);
1575
- return this;
1576
- };
1577
- _patched = true;
1578
- }
1579
- return () => {
1580
- const idx = _activePairs.findIndex(
1581
- (p) => p.store === store && p.mirror === mirror
1582
- );
1583
- if (idx !== -1) {
1584
- _activePairs.splice(idx, 1);
1585
- }
1586
- if (_activePairs.length === 0 && _patched) {
1587
- restoreObject3D();
1588
- }
1589
- };
1590
- }
1591
- function restoreObject3D() {
1592
- if (!_patched) return;
1593
- r3fLog("patch", "Restoring original Object3D.prototype.add and .remove");
1594
- if (_originalAdd) {
1595
- Object3D.prototype.add = _originalAdd;
1596
- _originalAdd = null;
1597
- }
1598
- if (_originalRemove) {
1599
- Object3D.prototype.remove = _originalRemove;
1600
- _originalRemove = null;
1601
- }
1602
- _patched = false;
1603
- }
1604
- function isPatched() {
1605
- return _patched;
1606
- }
1607
-
1608
- // src/snapshot/snapshot.ts
1609
- function buildNodeTree(store, meta) {
1610
- const children = [];
1611
- for (const childUuid of meta.childrenUuids) {
1612
- const childMeta = store.getByUuid(childUuid);
1613
- if (childMeta) {
1614
- children.push(buildNodeTree(store, childMeta));
1615
- }
1616
- }
1617
- const node = {
1618
- uuid: meta.uuid,
1619
- name: meta.name,
1620
- type: meta.type,
1621
- testId: meta.testId,
1622
- visible: meta.visible,
1623
- position: [...meta.position],
1624
- rotation: [...meta.rotation],
1625
- scale: [...meta.scale],
1626
- children
1627
- };
1628
- if (meta.geometryType) node.geometryType = meta.geometryType;
1629
- if (meta.materialType) node.materialType = meta.materialType;
1630
- if (meta.vertexCount != null) node.vertexCount = meta.vertexCount;
1631
- if (meta.triangleCount != null) node.triangleCount = meta.triangleCount;
1632
- if (meta.instanceCount != null) node.instanceCount = meta.instanceCount;
1633
- return node;
1634
- }
1635
- function findRoot(store) {
1636
- const allObjects = store.getFlatList();
1637
- for (const obj of allObjects) {
1638
- const meta = store.getMetadata(obj);
1639
- if (meta && meta.parentUuid === null) {
1640
- return meta;
1641
- }
1642
- }
1643
- return null;
1644
- }
1645
- function createSnapshot(store) {
1646
- const rootMeta = findRoot(store);
1647
- const tree = rootMeta ? buildNodeTree(store, rootMeta) : {
1648
- uuid: "",
1649
- name: "empty",
1650
- type: "Scene",
1651
- visible: true,
1652
- position: [0, 0, 0],
1653
- rotation: [0, 0, 0],
1654
- scale: [1, 1, 1],
1655
- children: []
1656
- };
1657
- return {
1658
- timestamp: Date.now(),
1659
- objectCount: store.getCount(),
1660
- tree
1661
- };
1662
- }
1663
- function createFlatSnapshot(store) {
1664
- const objects = [];
1665
- const allObjects = store.getFlatList();
1666
- for (const obj of allObjects) {
1667
- const meta = store.getMetadata(obj);
1668
- if (meta) {
1669
- objects.push({ ...meta });
1670
- }
1671
- }
1672
- return {
1673
- timestamp: Date.now(),
1674
- objectCount: objects.length,
1675
- objects
1676
- };
1677
- }
1678
- var _vec3 = /* @__PURE__ */ new Vector3();
1679
- var _vec3B = /* @__PURE__ */ new Vector3();
1680
- var _box32 = /* @__PURE__ */ new Box3();
1681
- var _frustum = /* @__PURE__ */ new Frustum();
1682
- var _projMatrix = /* @__PURE__ */ new Matrix4();
1683
- function ndcToScreen(ndc, size) {
1684
- return {
1685
- x: (ndc.x + 1) / 2 * size.width,
1686
- y: (-ndc.y + 1) / 2 * size.height
1687
- };
1688
- }
1689
- function isNdcOnScreen(ndc) {
1690
- return ndc.x >= -1 && ndc.x <= 1 && ndc.y >= -1 && ndc.y <= 1 && ndc.z >= -1 && ndc.z <= 1;
1691
- }
1692
- function isInFrontOfCamera(ndc) {
1693
- return ndc.z >= -1 && ndc.z <= 1;
1694
- }
1695
- function getWorldCenter(obj) {
1696
- _box32.setFromObject(obj);
1697
- if (_box32.isEmpty()) {
1698
- obj.getWorldPosition(_vec3);
1699
- return _vec3;
1700
- }
1701
- _box32.getCenter(_vec3);
1702
- return _vec3;
1703
- }
1704
- function getBboxCorners(obj) {
1705
- _box32.setFromObject(obj);
1706
- if (_box32.isEmpty()) return [];
1707
- const { min, max } = _box32;
1708
- return [
1709
- new Vector3(min.x, min.y, min.z),
1710
- new Vector3(min.x, min.y, max.z),
1711
- new Vector3(min.x, max.y, min.z),
1712
- new Vector3(min.x, max.y, max.z),
1713
- new Vector3(max.x, min.y, min.z),
1714
- new Vector3(max.x, min.y, max.z),
1715
- new Vector3(max.x, max.y, min.z),
1716
- new Vector3(max.x, max.y, max.z)
1717
- ];
1718
- }
1719
- function getBboxFaceCenters(obj) {
1720
- _box32.setFromObject(obj);
1721
- if (_box32.isEmpty()) return [];
1722
- const center = _box32.getCenter(new Vector3());
1723
- const { min, max } = _box32;
1724
- return [
1725
- new Vector3(min.x, center.y, center.z),
1726
- // -X face
1727
- new Vector3(max.x, center.y, center.z),
1728
- // +X face
1729
- new Vector3(center.x, min.y, center.z),
1730
- // -Y face
1731
- new Vector3(center.x, max.y, center.z),
1732
- // +Y face
1733
- new Vector3(center.x, center.y, min.z),
1734
- // -Z face
1735
- new Vector3(center.x, center.y, max.z)
1736
- // +Z face
1737
- ];
1738
- }
1739
- function tryProjectPoint(worldPoint, camera, size) {
1740
- _vec3B.copy(worldPoint).project(camera);
1741
- if (!isInFrontOfCamera(_vec3B)) return null;
1742
- const screen = ndcToScreen(_vec3B, size);
1743
- return {
1744
- screen,
1745
- ndc: { x: _vec3B.x, y: _vec3B.y },
1746
- ndcZ: _vec3B.z,
1747
- onScreen: isNdcOnScreen(_vec3B)
1748
- };
1749
- }
1750
- function projectToScreen(obj, camera, size) {
1751
- obj.updateWorldMatrix(true, false);
1752
- camera.updateWorldMatrix(true, false);
1753
- const center = getWorldCenter(obj);
1754
- const centerResult = tryProjectPoint(center, camera, size);
1755
- if (centerResult && centerResult.onScreen) {
1756
- return {
1757
- point: centerResult.screen,
1758
- strategy: "center",
1759
- ndc: centerResult.ndc
1760
- };
1761
- }
1762
- const faceCenters = getBboxFaceCenters(obj);
1763
- const faceResult = findBestOnScreenPoint(faceCenters, camera, size);
1764
- if (faceResult) {
1765
- return {
1766
- point: faceResult.screen,
1767
- strategy: "face-center",
1768
- ndc: faceResult.ndc
1769
- };
1770
- }
1771
- const corners = getBboxCorners(obj);
1772
- const cornerResult = findBestOnScreenPoint(corners, camera, size);
1773
- if (cornerResult) {
1774
- return {
1775
- point: cornerResult.screen,
1776
- strategy: "corner",
1777
- ndc: cornerResult.ndc
1778
- };
1779
- }
1780
- obj.getWorldPosition(_vec3);
1781
- const originResult = tryProjectPoint(_vec3.clone(), camera, size);
1782
- if (originResult && originResult.onScreen) {
1783
- return {
1784
- point: originResult.screen,
1785
- strategy: "fallback-origin",
1786
- ndc: originResult.ndc
1787
- };
1788
- }
1789
- if (centerResult) {
1790
- return {
1791
- point: centerResult.screen,
1792
- strategy: "center",
1793
- ndc: centerResult.ndc
1794
- };
1795
- }
1796
- return null;
1797
- }
1798
- function findBestOnScreenPoint(candidates, camera, size) {
1799
- let bestResult = null;
1800
- let bestDistSq = Infinity;
1801
- const halfW = size.width / 2;
1802
- const halfH = size.height / 2;
1803
- for (const point of candidates) {
1804
- const result = tryProjectPoint(point, camera, size);
1805
- if (!result || !result.onScreen) continue;
1806
- const dx = result.screen.x - halfW;
1807
- const dy = result.screen.y - halfH;
1808
- const distSq = dx * dx + dy * dy;
1809
- if (distSq < bestDistSq) {
1810
- bestDistSq = distSq;
1811
- bestResult = { screen: result.screen, ndc: result.ndc };
1812
- }
1813
- }
1814
- return bestResult;
1815
- }
1816
- function isInFrustum(obj, camera) {
1817
- camera.updateWorldMatrix(true, false);
1818
- obj.updateWorldMatrix(true, false);
1819
- _projMatrix.multiplyMatrices(
1820
- camera.projectionMatrix,
1821
- camera.matrixWorldInverse
1822
- );
1823
- _frustum.setFromProjectionMatrix(_projMatrix);
1824
- _box32.setFromObject(obj);
1825
- if (_box32.isEmpty()) {
1826
- obj.getWorldPosition(_vec3);
1827
- return _frustum.containsPoint(_vec3);
1828
- }
1829
- return _frustum.intersectsBox(_box32);
1830
- }
1831
- function screenDeltaToWorld(dx, dy, obj, camera, size) {
1832
- obj.getWorldPosition(_vec3);
1833
- _vec3.project(camera);
1834
- const depth = _vec3.z;
1835
- const start = new Vector3(0, 0, depth).unproject(camera);
1836
- const right = new Vector3(2 / size.width, 0, depth).unproject(camera);
1837
- const up = new Vector3(0, -2 / size.height, depth).unproject(camera);
1838
- const rightDir = right.sub(start);
1839
- const upDir = up.sub(start);
1840
- return new Vector3().addScaledVector(rightDir, dx).addScaledVector(upDir, dy);
1841
- }
1842
- function projectAllSamplePoints(obj, camera, size) {
1843
- obj.updateWorldMatrix(true, false);
1844
- camera.updateWorldMatrix(true, false);
1845
- const points = [];
1846
- const candidates = [];
1847
- candidates.push(getWorldCenter(obj).clone());
1848
- candidates.push(...getBboxFaceCenters(obj));
1849
- candidates.push(...getBboxCorners(obj));
1850
- for (const wsPt of candidates) {
1851
- const result = tryProjectPoint(wsPt, camera, size);
1852
- if (result && result.onScreen) {
1853
- points.push(result.screen);
1854
- }
1855
- }
1856
- return points;
1857
- }
1858
-
1859
- // src/interactions/dispatch.ts
1860
- var _nextPointerId = 1e3;
1861
- function allocPointerId() {
1862
- return _nextPointerId++;
1863
- }
1864
- function toClientCoords(canvas, point) {
1865
- const rect = canvas.getBoundingClientRect();
1866
- return {
1867
- clientX: rect.left + point.x,
1868
- clientY: rect.top + point.y
1869
- };
1870
- }
1871
- function makePointerInit(canvas, point, pointerId, overrides) {
1872
- const { clientX, clientY } = toClientCoords(canvas, point);
1873
- return {
1874
- bubbles: true,
1875
- cancelable: true,
1876
- composed: true,
1877
- clientX,
1878
- clientY,
1879
- screenX: clientX,
1880
- screenY: clientY,
1881
- pointerId,
1882
- pointerType: "mouse",
1883
- isPrimary: true,
1884
- button: 0,
1885
- buttons: 1,
1886
- width: 1,
1887
- height: 1,
1888
- pressure: 0.5,
1889
- ...overrides
1890
- };
1891
- }
1892
- function dispatchClick(canvas, point) {
1893
- withSafePointerCapture(() => {
1894
- const pointerId = allocPointerId();
1895
- canvas.dispatchEvent(
1896
- new PointerEvent("pointerdown", makePointerInit(canvas, point, pointerId))
1897
- );
1898
- canvas.dispatchEvent(
1899
- new PointerEvent(
1900
- "pointerup",
1901
- makePointerInit(canvas, point, pointerId, { buttons: 0, pressure: 0 })
1902
- )
1903
- );
1904
- canvas.dispatchEvent(
1905
- new MouseEvent("click", {
1906
- bubbles: true,
1907
- cancelable: true,
1908
- ...toClientCoords(canvas, point),
1909
- button: 0
1910
- })
1911
- );
1912
- });
1913
- }
1914
- function dispatchHover(canvas, point) {
1915
- const pointerId = allocPointerId();
1916
- const init = makePointerInit(canvas, point, pointerId, {
1917
- buttons: 0,
1918
- pressure: 0
1919
- });
1920
- canvas.dispatchEvent(new PointerEvent("pointermove", init));
1921
- canvas.dispatchEvent(new PointerEvent("pointerover", init));
1922
- canvas.dispatchEvent(new PointerEvent("pointerenter", { ...init, bubbles: false }));
1923
- }
1924
- async function dispatchDrag(canvas, start, end, options = {}) {
1925
- const { steps = 10, stepDelayMs = 0 } = options;
1926
- const pointerId = allocPointerId();
1927
- withSafePointerCapture(() => {
1928
- canvas.dispatchEvent(
1929
- new PointerEvent("pointerdown", makePointerInit(canvas, start, pointerId))
1930
- );
1931
- });
1932
- for (let i = 1; i <= steps; i++) {
1933
- const t = i / steps;
1934
- const intermediate = {
1935
- x: start.x + (end.x - start.x) * t,
1936
- y: start.y + (end.y - start.y) * t
1937
- };
1938
- if (stepDelayMs > 0) {
1939
- await sleep(stepDelayMs);
1940
- }
1941
- canvas.dispatchEvent(
1942
- new PointerEvent(
1943
- "pointermove",
1944
- makePointerInit(canvas, intermediate, pointerId)
1945
- )
1946
- );
1947
- }
1948
- withSafePointerCapture(() => {
1949
- canvas.dispatchEvent(
1950
- new PointerEvent(
1951
- "pointerup",
1952
- makePointerInit(canvas, end, pointerId, { buttons: 0, pressure: 0 })
1953
- )
1954
- );
1955
- });
1956
- }
1957
- function dispatchDoubleClick(canvas, point) {
1958
- withSafePointerCapture(() => {
1959
- const pointerId = allocPointerId();
1960
- const coords = toClientCoords(canvas, point);
1961
- canvas.dispatchEvent(
1962
- new PointerEvent("pointerdown", makePointerInit(canvas, point, pointerId))
1963
- );
1964
- canvas.dispatchEvent(
1965
- new PointerEvent(
1966
- "pointerup",
1967
- makePointerInit(canvas, point, pointerId, { buttons: 0, pressure: 0 })
1968
- )
1969
- );
1970
- canvas.dispatchEvent(
1971
- new MouseEvent("click", {
1972
- bubbles: true,
1973
- cancelable: true,
1974
- ...coords,
1975
- button: 0,
1976
- detail: 1
1977
- })
1978
- );
1979
- canvas.dispatchEvent(
1980
- new PointerEvent("pointerdown", makePointerInit(canvas, point, pointerId))
1981
- );
1982
- canvas.dispatchEvent(
1983
- new PointerEvent(
1984
- "pointerup",
1985
- makePointerInit(canvas, point, pointerId, { buttons: 0, pressure: 0 })
1986
- )
1987
- );
1988
- canvas.dispatchEvent(
1989
- new MouseEvent("click", {
1990
- bubbles: true,
1991
- cancelable: true,
1992
- ...coords,
1993
- button: 0,
1994
- detail: 2
1995
- })
1996
- );
1997
- canvas.dispatchEvent(
1998
- new MouseEvent("dblclick", {
1999
- bubbles: true,
2000
- cancelable: true,
2001
- ...coords,
2002
- button: 0,
2003
- detail: 2
2004
- })
2005
- );
2006
- });
2007
- }
2008
- function dispatchContextMenu(canvas, point) {
2009
- withSafePointerCapture(() => {
2010
- const pointerId = allocPointerId();
2011
- canvas.dispatchEvent(
2012
- new PointerEvent(
2013
- "pointerdown",
2014
- makePointerInit(canvas, point, pointerId, { button: 2, buttons: 2 })
2015
- )
2016
- );
2017
- canvas.dispatchEvent(
2018
- new PointerEvent(
2019
- "pointerup",
2020
- makePointerInit(canvas, point, pointerId, {
2021
- button: 2,
2022
- buttons: 0,
2023
- pressure: 0
2024
- })
2025
- )
2026
- );
2027
- canvas.dispatchEvent(
2028
- new MouseEvent("contextmenu", {
2029
- bubbles: true,
2030
- cancelable: true,
2031
- ...toClientCoords(canvas, point),
2032
- button: 2
2033
- })
2034
- );
2035
- });
2036
- }
2037
- function dispatchWheel(canvas, point, options = {}) {
2038
- const { deltaY = 100, deltaX = 0, deltaMode = 0 } = options;
2039
- const coords = toClientCoords(canvas, point);
2040
- canvas.dispatchEvent(
2041
- new WheelEvent("wheel", {
2042
- bubbles: true,
2043
- cancelable: true,
2044
- ...coords,
2045
- deltaX,
2046
- deltaY,
2047
- deltaZ: 0,
2048
- deltaMode
2049
- })
2050
- );
2051
- }
2052
- function dispatchPointerMiss(canvas, point = { x: 1, y: 1 }) {
2053
- dispatchClick(canvas, point);
2054
- }
2055
- function dispatchUnhover(canvas) {
2056
- const pointerId = allocPointerId();
2057
- const offScreen = { x: -9999, y: -9999 };
2058
- const init = makePointerInit(canvas, offScreen, pointerId, {
2059
- buttons: 0,
2060
- pressure: 0
2061
- });
2062
- canvas.dispatchEvent(new PointerEvent("pointermove", init));
2063
- canvas.dispatchEvent(new PointerEvent("pointerout", init));
2064
- canvas.dispatchEvent(new PointerEvent("pointerleave", { ...init, bubbles: false }));
2065
- }
2066
- function withSafePointerCapture(fn) {
2067
- const original = Element.prototype.releasePointerCapture;
2068
- Element.prototype.releasePointerCapture = function safeRelease(pointerId) {
2069
- try {
2070
- original.call(this, pointerId);
2071
- } catch {
2072
- }
2073
- };
2074
- try {
2075
- return fn();
2076
- } finally {
2077
- Element.prototype.releasePointerCapture = original;
2078
- }
2079
- }
2080
- function sleep(ms) {
2081
- return new Promise((resolve) => setTimeout(resolve, ms));
2082
- }
2083
- var _raycaster = /* @__PURE__ */ new Raycaster();
2084
- var _ndc = /* @__PURE__ */ new Vector2();
2085
- function screenToNdc(point, size) {
2086
- _ndc.set(
2087
- point.x / size.width * 2 - 1,
2088
- -(point.y / size.height) * 2 + 1
2089
- );
2090
- return _ndc;
2091
- }
2092
- function getObjectLabel(obj) {
2093
- const testId = obj.userData?.testId;
2094
- if (testId) return `testId="${testId}"`;
2095
- if (obj.name) return `name="${obj.name}"`;
2096
- return `uuid="${obj.uuid.slice(0, 8)}\u2026"`;
2097
- }
2098
- function isTargetOrDescendant(candidate, target) {
2099
- let current = candidate;
2100
- while (current) {
2101
- if (current === target) return true;
2102
- current = current.parent;
2103
- }
2104
- return false;
2105
- }
2106
- function findScene(obj) {
2107
- let current = obj;
2108
- while (current) {
2109
- if (current.isScene) return current;
2110
- current = current.parent;
2111
- }
2112
- return null;
2113
- }
2114
- function verifyRaycastHit(point, target, camera, size) {
2115
- const scene = findScene(target);
2116
- if (!scene) {
2117
- return { hit: false, occluderLabel: "object not in scene" };
2118
- }
2119
- const ndc = screenToNdc(point, size);
2120
- _raycaster.setFromCamera(ndc, camera);
2121
- const intersections = _raycaster.intersectObjects(scene.children, true);
2122
- if (intersections.length === 0) {
2123
- return { hit: true };
2124
- }
2125
- const firstHit = intersections[0].object;
2126
- if (isTargetOrDescendant(firstHit, target)) {
2127
- return { hit: true };
2128
- }
2129
- const targetHit = intersections.find(
2130
- (i) => isTargetOrDescendant(i.object, target)
2131
- );
2132
- if (targetHit) {
2133
- return {
2134
- hit: false,
2135
- occluder: firstHit,
2136
- occluderLabel: getObjectLabel(firstHit)
2137
- };
2138
- }
2139
- return {
2140
- hit: false,
2141
- occluder: firstHit,
2142
- occluderLabel: getObjectLabel(firstHit)
2143
- };
2144
- }
2145
- function verifyRaycastHitMultiPoint(points, target, camera, size) {
2146
- let lastResult = { hit: false };
2147
- for (const point of points) {
2148
- const result = verifyRaycastHit(point, target, camera, size);
2149
- if (result.hit) return result;
2150
- lastResult = result;
2151
- }
2152
- return lastResult;
2153
- }
2154
-
2155
- // src/interactions/resolve.ts
2156
- var _store2 = null;
2157
- var _camera = null;
2158
- var _gl = null;
2159
- var _size = null;
2160
- function setInteractionState(store, camera, gl, size) {
2161
- _store2 = store;
2162
- _camera = camera;
2163
- _gl = gl;
2164
- _size = size;
2165
- }
2166
- function clearInteractionState() {
2167
- _store2 = null;
2168
- _camera = null;
2169
- _gl = null;
2170
- _size = null;
2171
- }
2172
- function getStore() {
2173
- if (!_store2) {
2174
- throw new Error(
2175
- "[react-three-dom] Interaction state not initialized. Is <ThreeDom> mounted?"
2176
- );
2177
- }
2178
- return _store2;
2179
- }
2180
- function getCamera() {
2181
- if (!_camera) {
2182
- throw new Error(
2183
- "[react-three-dom] Camera not available. Is <ThreeDom> mounted?"
2184
- );
2185
- }
2186
- return _camera;
2187
- }
2188
- function getRenderer() {
2189
- if (!_gl) {
2190
- throw new Error(
2191
- "[react-three-dom] Renderer not available. Is <ThreeDom> mounted?"
2192
- );
2193
- }
2194
- return _gl;
2195
- }
2196
- function getCanvasSize() {
2197
- if (!_size) {
2198
- throw new Error(
2199
- "[react-three-dom] Canvas size not available. Is <ThreeDom> mounted?"
2200
- );
2201
- }
2202
- return _size;
2203
- }
2204
- function resolveObject(idOrUuid) {
2205
- const store = getStore();
2206
- const obj = store.getObject3D(idOrUuid);
2207
- if (!obj) {
2208
- throw new Error(
2209
- `[react-three-dom] Object "${idOrUuid}" not found. Check that the object has userData.testId="${idOrUuid}" or uuid="${idOrUuid}".`
2210
- );
2211
- }
2212
- return obj;
2213
- }
2214
-
2215
- // src/interactions/click.ts
2216
- function click3D(idOrUuid, options = {}) {
2217
- const { verify = true } = options;
2218
- const obj = resolveObject(idOrUuid);
2219
- const camera = getCamera();
2220
- const gl = getRenderer();
2221
- const size = getCanvasSize();
2222
- r3fLog("click", `click3D("${idOrUuid}") \u2014 resolving projection`);
2223
- const projection = projectToScreen(obj, camera, size);
2224
- if (!projection) {
2225
- throw new Error(
2226
- `[react-three-dom] click3D("${idOrUuid}") failed: object is not visible on screen. It may be behind the camera or outside the viewport.`
2227
- );
2228
- }
2229
- const canvas = gl.domElement;
2230
- dispatchClick(canvas, projection.point);
2231
- let raycast;
2232
- if (verify) {
2233
- raycast = verifyRaycastHit(projection.point, obj, camera, size);
2234
- if (!raycast.hit && raycast.occluderLabel) {
2235
- console.warn(
2236
- `[react-three-dom] click3D("${idOrUuid}") dispatched at (${Math.round(projection.point.x)}, ${Math.round(projection.point.y)}) but raycast hit ${raycast.occluderLabel} instead. The object may be occluded.`
2237
- );
2238
- }
2239
- }
2240
- return {
2241
- dispatched: true,
2242
- raycast,
2243
- screenPoint: projection.point,
2244
- strategy: projection.strategy
2245
- };
2246
- }
2247
- function doubleClick3D(idOrUuid, options = {}) {
2248
- const { verify = true } = options;
2249
- const obj = resolveObject(idOrUuid);
2250
- const camera = getCamera();
2251
- const gl = getRenderer();
2252
- const size = getCanvasSize();
2253
- const projection = projectToScreen(obj, camera, size);
2254
- if (!projection) {
2255
- throw new Error(
2256
- `[react-three-dom] doubleClick3D("${idOrUuid}") failed: object is not visible on screen.`
2257
- );
2258
- }
2259
- const canvas = gl.domElement;
2260
- dispatchDoubleClick(canvas, projection.point);
2261
- let raycast;
2262
- if (verify) {
2263
- raycast = verifyRaycastHit(projection.point, obj, camera, size);
2264
- if (!raycast.hit && raycast.occluderLabel) {
2265
- console.warn(
2266
- `[react-three-dom] doubleClick3D("${idOrUuid}") dispatched at (${Math.round(projection.point.x)}, ${Math.round(projection.point.y)}) but raycast hit ${raycast.occluderLabel} instead.`
2267
- );
2268
- }
2269
- }
2270
- return {
2271
- dispatched: true,
2272
- raycast,
2273
- screenPoint: projection.point,
2274
- strategy: projection.strategy
2275
- };
2276
- }
2277
- function contextMenu3D(idOrUuid, options = {}) {
2278
- const { verify = true } = options;
2279
- const obj = resolveObject(idOrUuid);
2280
- const camera = getCamera();
2281
- const gl = getRenderer();
2282
- const size = getCanvasSize();
2283
- const projection = projectToScreen(obj, camera, size);
2284
- if (!projection) {
2285
- throw new Error(
2286
- `[react-three-dom] contextMenu3D("${idOrUuid}") failed: object is not visible on screen.`
2287
- );
2288
- }
2289
- const canvas = gl.domElement;
2290
- dispatchContextMenu(canvas, projection.point);
2291
- let raycast;
2292
- if (verify) {
2293
- raycast = verifyRaycastHit(projection.point, obj, camera, size);
2294
- if (!raycast.hit && raycast.occluderLabel) {
2295
- console.warn(
2296
- `[react-three-dom] contextMenu3D("${idOrUuid}") dispatched at (${Math.round(projection.point.x)}, ${Math.round(projection.point.y)}) but raycast hit ${raycast.occluderLabel} instead.`
2297
- );
2298
- }
2299
- }
2300
- return {
2301
- dispatched: true,
2302
- raycast,
2303
- screenPoint: projection.point,
2304
- strategy: projection.strategy
2305
- };
2306
- }
2307
-
2308
- // src/interactions/hover.ts
2309
- function hover3D(idOrUuid, options = {}) {
2310
- const { verify = true } = options;
2311
- const obj = resolveObject(idOrUuid);
2312
- const camera = getCamera();
2313
- const gl = getRenderer();
2314
- const size = getCanvasSize();
2315
- r3fLog("hover", `hover3D("${idOrUuid}") \u2014 resolving projection`);
2316
- const projection = projectToScreen(obj, camera, size);
2317
- if (!projection) {
2318
- throw new Error(
2319
- `[react-three-dom] hover3D("${idOrUuid}") failed: object is not visible on screen. It may be behind the camera or outside the viewport.`
2320
- );
2321
- }
2322
- const canvas = gl.domElement;
2323
- dispatchHover(canvas, projection.point);
2324
- let raycast;
2325
- if (verify) {
2326
- raycast = verifyRaycastHit(projection.point, obj, camera, size);
2327
- if (!raycast.hit && raycast.occluderLabel) {
2328
- console.warn(
2329
- `[react-three-dom] hover3D("${idOrUuid}") dispatched at (${Math.round(projection.point.x)}, ${Math.round(projection.point.y)}) but raycast hit ${raycast.occluderLabel} instead.`
2330
- );
2331
- }
2332
- }
2333
- return {
2334
- dispatched: true,
2335
- raycast,
2336
- screenPoint: projection.point,
2337
- strategy: projection.strategy
2338
- };
2339
- }
2340
- function unhover3D() {
2341
- const gl = getRenderer();
2342
- dispatchUnhover(gl.domElement);
2343
- }
2344
- async function drag3D(idOrUuid, delta, options = {}) {
2345
- const { mode = "world", ...dragOptions } = options;
2346
- const obj = resolveObject(idOrUuid);
2347
- const camera = getCamera();
2348
- const gl = getRenderer();
2349
- const size = getCanvasSize();
2350
- r3fLog("drag", `drag3D("${idOrUuid}") mode=${mode}`, delta);
2351
- const projection = projectToScreen(obj, camera, size);
2352
- if (!projection) {
2353
- throw new Error(
2354
- `[react-three-dom] drag3D("${idOrUuid}") failed: object is not visible on screen. It may be behind the camera or outside the viewport.`
2355
- );
2356
- }
2357
- const startPoint = projection.point;
2358
- let endPoint;
2359
- if (mode === "screen" && "dx" in delta) {
2360
- const screenDelta = delta;
2361
- endPoint = {
2362
- x: startPoint.x + screenDelta.dx,
2363
- y: startPoint.y + screenDelta.dy
2364
- };
2365
- } else {
2366
- const worldDelta = delta;
2367
- const worldPos = new Vector3();
2368
- obj.getWorldPosition(worldPos);
2369
- const targetPos = worldPos.clone().add(new Vector3(worldDelta.x, worldDelta.y, worldDelta.z));
2370
- targetPos.project(camera);
2371
- endPoint = {
2372
- x: (targetPos.x + 1) / 2 * size.width,
2373
- y: (-targetPos.y + 1) / 2 * size.height
2374
- };
2375
- }
2376
- const canvas = gl.domElement;
2377
- await dispatchDrag(canvas, startPoint, endPoint, dragOptions);
2378
- return {
2379
- dispatched: true,
2380
- startPoint,
2381
- endPoint,
2382
- strategy: projection.strategy
2383
- };
2384
- }
2385
- function previewDragWorldDelta(idOrUuid, screenDx, screenDy) {
2386
- const obj = resolveObject(idOrUuid);
2387
- const camera = getCamera();
2388
- const size = getCanvasSize();
2389
- return screenDeltaToWorld(screenDx, screenDy, obj, camera, size);
2390
- }
2391
-
2392
- // src/interactions/wheel.ts
2393
- function wheel3D(idOrUuid, options = {}) {
2394
- const obj = resolveObject(idOrUuid);
2395
- const camera = getCamera();
2396
- const gl = getRenderer();
2397
- const size = getCanvasSize();
2398
- const projection = projectToScreen(obj, camera, size);
2399
- if (!projection) {
2400
- throw new Error(
2401
- `[react-three-dom] wheel3D("${idOrUuid}") failed: object is not visible on screen.`
2402
- );
2403
- }
2404
- const canvas = gl.domElement;
2405
- dispatchWheel(canvas, projection.point, options);
2406
- return {
2407
- dispatched: true,
2408
- screenPoint: projection.point,
2409
- strategy: projection.strategy
2410
- };
2411
- }
2412
-
2413
- // src/interactions/pointerMiss.ts
2414
- function pointerMiss3D(options = {}) {
2415
- const gl = getRenderer();
2416
- const canvas = gl.domElement;
2417
- dispatchPointerMiss(canvas, options.point);
2418
- }
2419
-
2420
- // src/interactions/drawPath.ts
2421
- var _nextDrawPointerId = 5e3;
2422
- function makeDrawPointerInit(canvas, point, pointerId, pointerType, overrides) {
2423
- const rect = canvas.getBoundingClientRect();
2424
- const clientX = rect.left + point.x;
2425
- const clientY = rect.top + point.y;
2426
- return {
2427
- bubbles: true,
2428
- cancelable: true,
2429
- composed: true,
2430
- clientX,
2431
- clientY,
2432
- screenX: clientX,
2433
- screenY: clientY,
2434
- pointerId,
2435
- pointerType,
2436
- isPrimary: true,
2437
- button: 0,
2438
- buttons: 1,
2439
- width: 1,
2440
- height: 1,
2441
- pressure: point.pressure ?? 0.5,
2442
- ...overrides
2443
- };
2444
- }
2445
- async function drawPath(points, options = {}) {
2446
- if (points.length < 2) {
2447
- throw new Error(
2448
- `[react-three-dom] drawPath requires at least 2 points, got ${points.length}.`
2449
- );
2450
- }
2451
- const {
2452
- stepDelayMs = 0,
2453
- pointerType = "mouse",
2454
- clickAtEnd = false,
2455
- canvas: explicitCanvas
2456
- } = options;
2457
- const canvas = explicitCanvas ?? getRenderer().domElement;
2458
- const pointerId = _nextDrawPointerId++;
2459
- let eventCount = 0;
2460
- r3fLog("draw", `drawPath \u2014 ${points.length} points, delay=${stepDelayMs}ms, pointerType=${pointerType}`);
2461
- const first = points[0];
2462
- canvas.dispatchEvent(
2463
- new PointerEvent("pointerdown", makeDrawPointerInit(canvas, first, pointerId, pointerType))
2464
- );
2465
- eventCount++;
2466
- for (let i = 1; i < points.length; i++) {
2467
- if (stepDelayMs > 0) {
2468
- await sleep2(stepDelayMs);
2469
- }
2470
- canvas.dispatchEvent(
2471
- new PointerEvent(
2472
- "pointermove",
2473
- makeDrawPointerInit(canvas, points[i], pointerId, pointerType)
2474
- )
2475
- );
2476
- eventCount++;
2477
- }
2478
- const last = points[points.length - 1];
2479
- canvas.dispatchEvent(
2480
- new PointerEvent(
2481
- "pointerup",
2482
- makeDrawPointerInit(canvas, last, pointerId, pointerType, {
2483
- buttons: 0,
2484
- pressure: 0
2485
- })
2486
- )
2487
- );
2488
- eventCount++;
2489
- if (clickAtEnd) {
2490
- const rect = canvas.getBoundingClientRect();
2491
- canvas.dispatchEvent(
2492
- new MouseEvent("click", {
2493
- bubbles: true,
2494
- cancelable: true,
2495
- clientX: rect.left + last.x,
2496
- clientY: rect.top + last.y,
2497
- button: 0
2498
- })
2499
- );
2500
- eventCount++;
2501
- }
2502
- r3fLog("draw", `drawPath complete \u2014 ${eventCount} events dispatched`);
2503
- return {
2504
- eventCount,
2505
- pointCount: points.length,
2506
- startPoint: first,
2507
- endPoint: last
2508
- };
2509
- }
2510
- function linePath(start, end, steps = 10, pressure = 0.5) {
2511
- const points = [];
2512
- const totalSteps = steps + 1;
2513
- for (let i = 0; i <= totalSteps; i++) {
2514
- const t = i / totalSteps;
2515
- points.push({
2516
- x: start.x + (end.x - start.x) * t,
2517
- y: start.y + (end.y - start.y) * t,
2518
- pressure
2519
- });
2520
- }
2521
- return points;
2522
- }
2523
- function curvePath(start, control, end, steps = 20, pressure = 0.5) {
2524
- const points = [];
2525
- for (let i = 0; i <= steps; i++) {
2526
- const t = i / steps;
2527
- const invT = 1 - t;
2528
- points.push({
2529
- x: invT * invT * start.x + 2 * invT * t * control.x + t * t * end.x,
2530
- y: invT * invT * start.y + 2 * invT * t * control.y + t * t * end.y,
2531
- pressure
2532
- });
2533
- }
2534
- return points;
2535
- }
2536
- function rectPath(topLeft, bottomRight, pointsPerSide = 5, pressure = 0.5) {
2537
- const topRight = { x: bottomRight.x, y: topLeft.y };
2538
- const bottomLeft = { x: topLeft.x, y: bottomRight.y };
2539
- const sides = [
2540
- [topLeft, topRight],
2541
- [topRight, bottomRight],
2542
- [bottomRight, bottomLeft],
2543
- [bottomLeft, topLeft]
2544
- ];
2545
- const points = [];
2546
- for (const [from, to] of sides) {
2547
- for (let i = 0; i < pointsPerSide; i++) {
2548
- const t = i / pointsPerSide;
2549
- points.push({
2550
- x: from.x + (to.x - from.x) * t,
2551
- y: from.y + (to.y - from.y) * t,
2552
- pressure
2553
- });
2554
- }
2555
- }
2556
- points.push({ x: topLeft.x, y: topLeft.y, pressure });
2557
- return points;
2558
- }
2559
- function circlePath(center, radiusX, radiusY, steps = 36, pressure = 0.5) {
2560
- const ry = radiusY ?? radiusX;
2561
- const points = [];
2562
- for (let i = 0; i <= steps; i++) {
2563
- const angle = i / steps * Math.PI * 2;
2564
- points.push({
2565
- x: center.x + Math.cos(angle) * radiusX,
2566
- y: center.y + Math.sin(angle) * ry,
2567
- pressure
2568
- });
2569
- }
2570
- return points;
2571
- }
2572
- function sleep2(ms) {
2573
- return new Promise((resolve) => setTimeout(resolve, ms));
2574
- }
2575
-
2576
- // src/highlight/SelectionManager.ts
2577
- var SelectionManager = class {
2578
- constructor() {
2579
- /** Currently selected objects (ordered by selection time). */
2580
- this._selected = [];
2581
- /** Listeners notified on selection change. */
2582
- this._listeners = [];
2583
- }
2584
- // -----------------------------------------------------------------------
2585
- // Selection API
2586
- // -----------------------------------------------------------------------
2587
- /** Select a single object (clears previous selection). */
2588
- select(obj) {
2589
- if (this._selected.length === 1 && this._selected[0] === obj) return;
2590
- this._selected = [obj];
2591
- this._notify();
2592
- }
2593
- /** Add an object to the current selection (multi-select). */
2594
- addToSelection(obj) {
2595
- if (this._selected.includes(obj)) return;
2596
- this._selected.push(obj);
2597
- this._notify();
2598
- }
2599
- /** Remove an object from the selection. */
2600
- removeFromSelection(obj) {
2601
- const idx = this._selected.indexOf(obj);
2602
- if (idx === -1) return;
2603
- this._selected.splice(idx, 1);
2604
- this._notify();
2605
- }
2606
- /** Toggle an object in/out of the selection. */
2607
- toggleSelection(obj) {
2608
- if (this._selected.includes(obj)) {
2609
- this.removeFromSelection(obj);
2610
- } else {
2611
- this.addToSelection(obj);
2612
- }
2613
- }
2614
- /** Clear all selections. */
2615
- clearSelection() {
2616
- if (this._selected.length === 0) return;
2617
- this._selected = [];
2618
- this._notify();
2619
- }
2620
- /** Get the currently selected objects. */
2621
- getSelected() {
2622
- return this._selected;
2623
- }
2624
- /** Get the primary (first) selected object, or null. */
2625
- getPrimary() {
2626
- return this._selected[0] ?? null;
2627
- }
2628
- /** Check if an object is selected. */
2629
- isSelected(obj) {
2630
- return this._selected.includes(obj);
2631
- }
2632
- /** Number of selected objects. */
2633
- get count() {
2634
- return this._selected.length;
2635
- }
2636
- // -----------------------------------------------------------------------
2637
- // Event system
2638
- // -----------------------------------------------------------------------
2639
- /** Subscribe to selection changes. Returns unsubscribe function. */
2640
- subscribe(listener) {
2641
- this._listeners.push(listener);
2642
- return () => {
2643
- const idx = this._listeners.indexOf(listener);
2644
- if (idx !== -1) this._listeners.splice(idx, 1);
2645
- };
2646
- }
2647
- _notify() {
2648
- const snapshot = [...this._selected];
2649
- for (const listener of this._listeners) {
2650
- listener(snapshot);
2651
- }
2652
- }
2653
- // -----------------------------------------------------------------------
2654
- // Cleanup
2655
- // -----------------------------------------------------------------------
2656
- dispose() {
2657
- this._selected = [];
2658
- this._listeners = [];
2659
- }
2660
- };
2661
- var HOVER_FILL_COLOR = 7317724;
2662
- var HOVER_FILL_OPACITY = 0.66;
2663
- var SELECTION_FILL_COLOR = 7317724;
2664
- var SELECTION_FILL_OPACITY = 0.75;
2665
- var SELECTION_BBOX_COLOR = 7317724;
2666
- var SELECTION_BBOX_OPACITY = 0.3;
2667
- var _box33 = /* @__PURE__ */ new Box3();
2668
- function hasRenderableGeometry(obj) {
2669
- return obj.isMesh === true || obj.isLine === true || obj.isPoints === true;
2670
- }
2671
- function collectHighlightTargets(obj) {
2672
- if (hasRenderableGeometry(obj)) return [obj];
2673
- const targets = [];
2674
- obj.traverse((child) => {
2675
- if (child === obj) return;
2676
- if (child.userData?.__r3fdom_internal) return;
2677
- if (hasRenderableGeometry(child)) targets.push(child);
2678
- });
2679
- return targets;
2680
- }
2681
- function markInternal(obj) {
2682
- obj.userData.__r3fdom_internal = true;
2683
- obj.raycast = () => {
2684
- };
2685
- obj.traverse((child) => {
2686
- child.userData.__r3fdom_internal = true;
2687
- child.raycast = () => {
2688
- };
2689
- });
2690
- }
2691
- function _syncGroupTransform(source, highlightRoot) {
2692
- source.updateWorldMatrix(true, false);
2693
- source.matrixWorld.decompose(
2694
- highlightRoot.position,
2695
- highlightRoot.quaternion,
2696
- highlightRoot.scale
2697
- );
2698
- }
2699
- function _attachRenderSync(source, highlightRoot) {
2700
- highlightRoot.matrixAutoUpdate = false;
2701
- highlightRoot.updateMatrixWorld = (force) => {
2702
- source.updateWorldMatrix(true, false);
2703
- highlightRoot.matrixWorld.copy(source.matrixWorld);
2704
- for (const child of highlightRoot.children) {
2705
- child.updateMatrixWorld(force);
2706
- }
2707
- };
2708
- }
2709
- function createHighlightMesh(source, fillColor, fillOpacity) {
2710
- const geom = source.geometry;
2711
- if (!geom) return null;
2712
- const group = new Object3D();
2713
- const disposables = [];
2714
- const fillMat = new MeshBasicMaterial({
2715
- color: fillColor,
2716
- transparent: true,
2717
- opacity: fillOpacity,
2718
- depthTest: false,
2719
- depthWrite: false,
2720
- side: DoubleSide
2721
- });
2722
- disposables.push(fillMat);
2723
- const fillMesh = new Mesh(geom, fillMat);
2724
- fillMesh.scale.setScalar(1.005);
2725
- fillMesh.raycast = () => {
2726
- };
2727
- group.add(fillMesh);
2728
- source.updateWorldMatrix(true, false);
2729
- source.matrixWorld.decompose(group.position, group.quaternion, group.scale);
2730
- markInternal(group);
2731
- _attachRenderSync(source, group);
2732
- return { root: group, disposables };
2733
- }
2734
- function createBoundingBoxHighlight(obj) {
2735
- _box33.makeEmpty();
2736
- const targets = collectHighlightTargets(obj);
2737
- if (targets.length === 0) return null;
2738
- for (const target of targets) {
2739
- target.updateWorldMatrix(true, false);
2740
- const geom = target.geometry;
2741
- if (geom) {
2742
- if (!geom.boundingBox) geom.computeBoundingBox();
2743
- if (geom.boundingBox) {
2744
- const childBox = geom.boundingBox.clone();
2745
- childBox.applyMatrix4(target.matrixWorld);
2746
- _box33.union(childBox);
2747
- }
2748
- }
2749
- }
2750
- if (_box33.isEmpty()) return null;
2751
- const size = _box33.getSize(new Vector3());
2752
- const center = _box33.getCenter(new Vector3());
2753
- const disposables = [];
2754
- const boxGeom = new BoxGeometry(size.x, size.y, size.z);
2755
- disposables.push(boxGeom);
2756
- const fillMat = new MeshBasicMaterial({
2757
- color: SELECTION_BBOX_COLOR,
2758
- transparent: true,
2759
- opacity: SELECTION_BBOX_OPACITY,
2760
- depthTest: false,
2761
- depthWrite: false,
2762
- side: DoubleSide
2763
- });
2764
- disposables.push(fillMat);
2765
- const fillMesh = new Mesh(boxGeom, fillMat);
2766
- fillMesh.raycast = () => {
2767
- };
2768
- const edgesGeom = new EdgesGeometry(boxGeom);
2769
- disposables.push(edgesGeom);
2770
- const edgeMat = new LineBasicMaterial({
2771
- color: SELECTION_BBOX_COLOR,
2772
- transparent: true,
2773
- opacity: 0.5,
2774
- depthTest: false,
2775
- depthWrite: false
2776
- });
2777
- disposables.push(edgeMat);
2778
- const edgeMesh = new LineSegments(edgesGeom, edgeMat);
2779
- edgeMesh.raycast = () => {
2780
- };
2781
- const group = new Object3D();
2782
- group.add(fillMesh);
2783
- group.add(edgeMesh);
2784
- group.position.copy(center);
2785
- group.renderOrder = 998;
2786
- markInternal(group);
2787
- group.matrixAutoUpdate = false;
2788
- group.updateMatrixWorld = (force) => {
2789
- _box33.makeEmpty();
2790
- for (const target of targets) {
2791
- target.updateWorldMatrix(true, false);
2792
- const g = target.geometry;
2793
- if (g) {
2794
- if (!g.boundingBox) g.computeBoundingBox();
2795
- if (g.boundingBox) {
2796
- const childBox = g.boundingBox.clone();
2797
- childBox.applyMatrix4(target.matrixWorld);
2798
- _box33.union(childBox);
2799
- }
2800
- }
2801
- }
2802
- if (!_box33.isEmpty()) {
2803
- const s = _box33.getSize(new Vector3());
2804
- const c = _box33.getCenter(new Vector3());
2805
- group.position.copy(c);
2806
- group.scale.set(
2807
- s.x / size.x || 1,
2808
- s.y / size.y || 1,
2809
- s.z / size.z || 1
2810
- );
2811
- }
2812
- group.updateMatrix();
2813
- group.matrixWorld.copy(group.matrix);
2814
- for (const child of group.children) {
2815
- child.updateMatrixWorld(force);
2816
- }
2817
- };
2818
- return { root: group, disposables };
2819
- }
2820
- var Highlighter = class {
2821
- constructor(_options = {}) {
2822
- this._scene = null;
2823
- this._unsubscribe = null;
2824
- this._hoverEntries = [];
2825
- this._hoverTarget = null;
2826
- this._selectedEntries = /* @__PURE__ */ new Map();
2827
- this._hoverPollId = null;
2828
- this._lastHoveredUuid = null;
2829
- this._store = null;
2830
- }
2831
- // -----------------------------------------------------------------------
2832
- // Lifecycle
2833
- // -----------------------------------------------------------------------
2834
- /** Bind to a scene and selection manager, start hover polling. */
2835
- attach(scene, selectionManager, _camera2, _renderer, store) {
2836
- this.detach();
2837
- this._scene = scene;
2838
- this._store = store;
2839
- this._unsubscribe = selectionManager.subscribe((selected) => {
2840
- this._syncSelectionHighlights(selected);
2841
- });
2842
- this._syncSelectionHighlights([...selectionManager.getSelected()]);
2843
- this._startHoverPolling();
2844
- }
2845
- /** Unbind from the scene, stop polling, and remove all highlights. */
2846
- detach() {
2847
- if (this._unsubscribe) {
2848
- this._unsubscribe();
2849
- this._unsubscribe = null;
2850
- }
2851
- this._stopHoverPolling();
2852
- this.clearHoverHighlight();
2853
- this._clearAllSelectionHighlights();
2854
- this._scene = null;
2855
- this._store = null;
2856
- }
2857
- // -----------------------------------------------------------------------
2858
- // Per-frame update
2859
- // -----------------------------------------------------------------------
2860
- /** Sync highlight group transforms to their source objects. Call each frame. */
2861
- update() {
2862
- for (const entry of this._hoverEntries) {
2863
- _syncGroupTransform(entry.source, entry.group.root);
2864
- }
2865
- for (const selEntry of this._selectedEntries.values()) {
2866
- for (const mg of selEntry.meshGroups) {
2867
- const src = mg.root.userData.__r3fdom_source;
2868
- if (src) {
2869
- _syncGroupTransform(src, mg.root);
2870
- }
2871
- }
2872
- }
2873
- }
2874
- // -----------------------------------------------------------------------
2875
- // Public API: hover highlight
2876
- // -----------------------------------------------------------------------
2877
- /** Show a hover highlight on the given object (replaces any previous hover). */
2878
- showHoverHighlight(obj) {
2879
- if (obj === this._hoverTarget) return;
2880
- this._clearHoverVisuals();
2881
- if (!this._scene) return;
2882
- this._hoverTarget = obj;
2883
- const targets = collectHighlightTargets(obj);
2884
- for (const target of targets) {
2885
- const hg = createHighlightMesh(target, HOVER_FILL_COLOR, HOVER_FILL_OPACITY);
2886
- if (hg) {
2887
- hg.root.renderOrder = 997;
2888
- this._scene.add(hg.root);
2889
- this._hoverEntries.push({ source: target, group: hg });
2890
- }
2891
- }
2892
- }
2893
- /** Remove the current hover highlight. */
2894
- clearHoverHighlight() {
2895
- this._clearHoverVisuals();
2896
- this._lastHoveredUuid = null;
2897
- }
2898
- _clearHoverVisuals() {
2899
- for (const entry of this._hoverEntries) {
2900
- this._disposeHighlightGroup(entry.group);
2901
- }
2902
- this._hoverEntries = [];
2903
- this._hoverTarget = null;
2904
- }
2905
- // -----------------------------------------------------------------------
2906
- // Public API: queries
2907
- // -----------------------------------------------------------------------
2908
- /** Check if an object currently has a selection highlight. */
2909
- isHighlighted(obj) {
2910
- return this._selectedEntries.has(obj);
2911
- }
2912
- /** Remove all hover and selection highlights. */
2913
- clearAll() {
2914
- this.clearHoverHighlight();
2915
- this._clearAllSelectionHighlights();
2916
- }
2917
- // -----------------------------------------------------------------------
2918
- // Internal: selection highlights
2919
- // -----------------------------------------------------------------------
2920
- _syncSelectionHighlights(selected) {
2921
- if (!this._scene) return;
2922
- const selectedSet = new Set(selected);
2923
- for (const [obj] of this._selectedEntries) {
2924
- if (!selectedSet.has(obj)) {
2925
- this._removeSelectionHighlight(obj);
2926
- }
2927
- }
2928
- for (const obj of selected) {
2929
- if (!this._selectedEntries.has(obj)) {
2930
- this._addSelectionHighlight(obj);
2931
- }
2932
- }
2933
- }
2934
- _addSelectionHighlight(obj) {
2935
- if (!this._scene) return;
2936
- const targets = collectHighlightTargets(obj);
2937
- const meshGroups = [];
2938
- for (const target of targets) {
2939
- const hg = createHighlightMesh(target, SELECTION_FILL_COLOR, SELECTION_FILL_OPACITY);
2940
- if (hg) {
2941
- hg.root.userData.__r3fdom_source = target;
2942
- hg.root.renderOrder = 999;
2943
- this._scene.add(hg.root);
2944
- meshGroups.push(hg);
2945
- }
2946
- }
2947
- let bboxGroup = null;
2948
- if (targets.length > 1 && obj.type !== "Group") {
2949
- bboxGroup = createBoundingBoxHighlight(obj);
2950
- if (bboxGroup) {
2951
- this._scene.add(bboxGroup.root);
2952
- }
2953
- }
2954
- if (meshGroups.length > 0 || bboxGroup) {
2955
- this._selectedEntries.set(obj, { source: obj, meshGroups, bboxGroup });
2956
- }
2957
- }
2958
- _removeSelectionHighlight(obj) {
2959
- const entry = this._selectedEntries.get(obj);
2960
- if (!entry) return;
2961
- for (const mg of entry.meshGroups) {
2962
- this._disposeHighlightGroup(mg);
2963
- }
2964
- if (entry.bboxGroup) {
2965
- this._disposeHighlightGroup(entry.bboxGroup);
2966
- }
2967
- this._selectedEntries.delete(obj);
2968
- }
2969
- _clearAllSelectionHighlights() {
2970
- for (const entry of this._selectedEntries.values()) {
2971
- for (const mg of entry.meshGroups) {
2972
- this._disposeHighlightGroup(mg);
2973
- }
2974
- if (entry.bboxGroup) {
2975
- this._disposeHighlightGroup(entry.bboxGroup);
2976
- }
2977
- }
2978
- this._selectedEntries.clear();
2979
- }
2980
- // -----------------------------------------------------------------------
2981
- // Internal: DevTools hover polling
2982
- // -----------------------------------------------------------------------
2983
- _startHoverPolling() {
2984
- this._hoverPollId = setInterval(() => {
2985
- this._pollDevToolsHover();
2986
- }, 100);
2987
- }
2988
- _stopHoverPolling() {
2989
- if (this._hoverPollId) {
2990
- clearInterval(this._hoverPollId);
2991
- this._hoverPollId = null;
2992
- }
2993
- }
2994
- _pollDevToolsHover() {
2995
- if (!this._store) return;
2996
- try {
2997
- const hoveredEl = window.__r3fdom_hovered__;
2998
- const uuid = hoveredEl?.getAttribute?.("data-uuid") ?? null;
2999
- if (uuid === this._lastHoveredUuid) return;
3000
- this._lastHoveredUuid = uuid;
3001
- if (!uuid) {
3002
- this._clearHoverVisuals();
3003
- return;
3004
- }
3005
- const obj = this._store.getObject3D(uuid);
3006
- if (obj) {
3007
- this.showHoverHighlight(obj);
3008
- } else {
3009
- this._clearHoverVisuals();
3010
- }
3011
- } catch {
3012
- }
3013
- }
3014
- // -----------------------------------------------------------------------
3015
- // Internal: cleanup
3016
- // -----------------------------------------------------------------------
3017
- _disposeHighlightGroup(hg) {
3018
- hg.root.removeFromParent();
3019
- for (const d of hg.disposables) {
3020
- d.dispose();
3021
- }
3022
- }
3023
- /** Detach and release all resources. */
3024
- dispose() {
3025
- this.detach();
3026
- }
3027
- };
3028
-
3029
- // src/highlight/selectionDisplayTarget.ts
3030
- function isFirstMeshInGroup(obj) {
3031
- const parent = obj.parent;
3032
- if (!parent || parent.type !== "Group") return false;
3033
- const firstMesh = parent.children.find((c) => getTagForType(c.type) === "three-mesh");
3034
- return firstMesh === obj;
3035
- }
3036
- function resolveSelectionDisplayTarget(getObject3D, uuid) {
3037
- const obj = getObject3D(uuid);
3038
- if (!obj) return null;
3039
- if (getTagForType(obj.type) !== "three-mesh") return uuid;
3040
- if (isFirstMeshInGroup(obj) && obj.parent) return obj.parent.uuid;
3041
- return uuid;
3042
- }
3043
-
3044
- // src/highlight/InspectController.ts
3045
- var RAYCAST_THROTTLE_MS = 50;
3046
- var HOVER_REVEAL_DEBOUNCE_MS = 300;
3047
- var InspectController = class {
3048
- constructor(opts) {
3049
- this._active = false;
3050
- this._lastRaycastTime = 0;
3051
- this._hoveredObject = null;
3052
- this._hoverRevealTimer = null;
3053
- this._overlay = null;
3054
- this._boundPointerMove = null;
3055
- this._boundPointerDown = null;
3056
- this._boundContextMenu = null;
3057
- this._camera = opts.camera;
3058
- this._renderer = opts.renderer;
3059
- this._selectionManager = opts.selectionManager;
3060
- this._highlighter = opts.highlighter;
3061
- this._raycastAccelerator = opts.raycastAccelerator;
3062
- this._mirror = opts.mirror;
3063
- this._store = opts.store;
3064
- }
3065
- get active() {
3066
- return this._active;
3067
- }
3068
- /** Update the camera reference (e.g. after a camera switch). */
3069
- updateCamera(camera) {
3070
- this._camera = camera;
3071
- }
3072
- // -----------------------------------------------------------------------
3073
- // Enable / disable
3074
- // -----------------------------------------------------------------------
3075
- /** Activate inspect mode — creates overlay on top of canvas. */
3076
- enable() {
3077
- if (this._active) return;
3078
- this._active = true;
3079
- const canvas = this._renderer.domElement;
3080
- const parent = canvas.parentElement;
3081
- if (!parent) return;
3082
- const overlay = document.createElement("div");
3083
- overlay.dataset.r3fdomInspect = "true";
3084
- overlay.style.cssText = [
3085
- "position:absolute",
3086
- "inset:0",
3087
- "z-index:999999",
3088
- "cursor:crosshair",
3089
- "background:transparent"
3090
- ].join(";");
3091
- const parentPos = getComputedStyle(parent).position;
3092
- if (parentPos === "static") {
3093
- parent.style.position = "relative";
3094
- }
3095
- this._boundPointerMove = this._onPointerMove.bind(this);
3096
- this._boundPointerDown = this._onPointerDown.bind(this);
3097
- this._boundContextMenu = (e) => e.preventDefault();
3098
- overlay.addEventListener("pointermove", this._boundPointerMove);
3099
- overlay.addEventListener("pointerdown", this._boundPointerDown);
3100
- overlay.addEventListener("contextmenu", this._boundContextMenu);
3101
- parent.appendChild(overlay);
3102
- this._overlay = overlay;
3103
- r3fLog("inspect", "Inspect mode enabled \u2014 hover to highlight, click to select");
3104
- }
3105
- /** Deactivate inspect mode — removes overlay and clears all inspect state. */
3106
- disable() {
3107
- if (!this._active) return;
3108
- this._active = false;
3109
- if (this._overlay) {
3110
- if (this._boundPointerMove) this._overlay.removeEventListener("pointermove", this._boundPointerMove);
3111
- if (this._boundPointerDown) this._overlay.removeEventListener("pointerdown", this._boundPointerDown);
3112
- if (this._boundContextMenu) this._overlay.removeEventListener("contextmenu", this._boundContextMenu);
3113
- this._overlay.remove();
3114
- this._overlay = null;
3115
- }
3116
- this._boundPointerMove = null;
3117
- this._boundPointerDown = null;
3118
- this._boundContextMenu = null;
3119
- this._hoveredObject = null;
3120
- this._cancelHoverReveal();
3121
- this._highlighter.clearHoverHighlight();
3122
- window.__r3fdom_selected_element__ = null;
3123
- r3fLog("inspect", "InspectController disabled");
3124
- }
3125
- /** Disable and release all resources. */
3126
- dispose() {
3127
- this.disable();
3128
- }
3129
- // -----------------------------------------------------------------------
3130
- // Raycasting (delegated to RaycastAccelerator)
3131
- // -----------------------------------------------------------------------
3132
- _raycastFromEvent(e) {
3133
- return this._raycastAccelerator.raycastAtMouse(
3134
- e,
3135
- this._camera,
3136
- this._renderer.domElement
3137
- );
3138
- }
3139
- /**
3140
- * Resolve a raw raycast hit to the logical selection target.
3141
- * Walks up to find the best Group parent if applicable.
3142
- */
3143
- _resolveTarget(hit) {
3144
- const displayUuid = resolveSelectionDisplayTarget(
3145
- (id) => this._store.getObject3D(id),
3146
- hit.uuid
3147
- );
3148
- if (!displayUuid) return hit;
3149
- return this._store.getObject3D(displayUuid) ?? hit;
3150
- }
3151
- // -----------------------------------------------------------------------
3152
- // Event handlers
3153
- // -----------------------------------------------------------------------
3154
- _onPointerMove(e) {
3155
- e.stopPropagation();
3156
- e.preventDefault();
3157
- const now = performance.now();
3158
- if (now - this._lastRaycastTime < RAYCAST_THROTTLE_MS) return;
3159
- this._lastRaycastTime = now;
3160
- const hit = this._raycastFromEvent(e);
3161
- if (!hit) {
3162
- if (this._hoveredObject) {
3163
- this._hoveredObject = null;
3164
- this._highlighter.clearHoverHighlight();
3165
- this._cancelHoverReveal();
3166
- }
3167
- return;
3168
- }
3169
- if (hit === this._hoveredObject) return;
3170
- this._hoveredObject = hit;
3171
- this._highlighter.showHoverHighlight(hit);
3172
- this._scheduleHoverReveal(hit);
3173
- }
3174
- /**
3175
- * After hovering an object for HOVER_REVEAL_DEBOUNCE_MS, auto-reveal its
3176
- * mirror element in the Elements tab.
3177
- */
3178
- _scheduleHoverReveal(target) {
3179
- this._cancelHoverReveal();
3180
- this._hoverRevealTimer = setTimeout(() => {
3181
- const mirrorEl = this._mirror.getOrMaterialize(target.uuid);
3182
- if (mirrorEl) {
3183
- window.__r3fdom_selected_element__ = mirrorEl;
3184
- }
3185
- }, HOVER_REVEAL_DEBOUNCE_MS);
3186
- }
3187
- _cancelHoverReveal() {
3188
- if (this._hoverRevealTimer) {
3189
- clearTimeout(this._hoverRevealTimer);
3190
- this._hoverRevealTimer = null;
3191
- }
3192
- }
3193
- _onPointerDown(e) {
3194
- e.stopPropagation();
3195
- e.preventDefault();
3196
- const hit = this._raycastFromEvent(e);
3197
- if (!hit) {
3198
- this._selectionManager.clearSelection();
3199
- return;
3200
- }
3201
- const target = this._resolveTarget(hit);
3202
- if (!target) return;
3203
- this._selectionManager.select(target);
3204
- const mirrorEl = this._mirror.getOrMaterialize(target.uuid);
3205
- if (mirrorEl) {
3206
- window.__r3fdom_selected_element__ = mirrorEl;
3207
- }
3208
- r3fLog("inspect", "Object selected via canvas click", {
3209
- uuid: target.uuid.slice(0, 8),
3210
- name: target.name || "(unnamed)",
3211
- type: target.type
3212
- });
3213
- }
3214
- };
3215
- var _bvhPatched = false;
3216
- function ensureBVHPatched() {
3217
- if (_bvhPatched) return;
3218
- _bvhPatched = true;
3219
- BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
3220
- BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
3221
- Mesh.prototype.raycast = acceleratedRaycast;
3222
- r3fLog("raycast", "three-mesh-bvh patched into Three.js");
3223
- }
3224
- var _raycaster2 = /* @__PURE__ */ new Raycaster();
3225
- var _mouse = /* @__PURE__ */ new Vector2();
3226
- function isRaycastable(obj) {
3227
- if (obj.userData?.__r3fdom_internal) return false;
3228
- if (!obj.visible) return false;
3229
- const isMeshLike = obj.isMesh === true || obj.isLine === true || obj.isPoints === true;
3230
- if (!isMeshLike) return false;
3231
- const geom = obj.geometry;
3232
- if (geom) {
3233
- const posAttr = geom.getAttribute("position");
3234
- if (posAttr && !posAttr.array) return false;
3235
- }
3236
- return true;
3237
- }
3238
- var RaycastAccelerator = class {
3239
- constructor(store) {
3240
- this._targets = [];
3241
- this._dirty = true;
3242
- this._unsubscribe = null;
3243
- this._bvhBuiltFor = /* @__PURE__ */ new WeakSet();
3244
- this._store = store;
3245
- ensureBVHPatched();
3246
- this._unsubscribe = store.subscribe(() => {
3247
- this._dirty = true;
3248
- });
3249
- }
3250
- /** Force a target list rebuild on the next raycast. */
3251
- markDirty() {
3252
- this._dirty = true;
3253
- }
3254
- _rebuild() {
3255
- this._dirty = false;
3256
- const flatList = this._store.getFlatList();
3257
- const targets = [];
3258
- for (let i = 0; i < flatList.length; i++) {
3259
- const obj = flatList[i];
3260
- if (isRaycastable(obj)) {
3261
- targets.push(obj);
3262
- }
3263
- }
3264
- this._targets = targets;
3265
- let bvhBudget = 50;
3266
- for (let i = 0; i < targets.length && bvhBudget > 0; i++) {
3267
- const obj = targets[i];
3268
- if (obj.isMesh) {
3269
- const geom = obj.geometry;
3270
- if (geom && !this._bvhBuiltFor.has(geom) && !geom.boundsTree) {
3271
- this._ensureBVH(obj);
3272
- bvhBudget--;
3273
- }
3274
- }
3275
- }
3276
- if (bvhBudget === 0) {
3277
- this._dirty = true;
3278
- }
3279
- }
3280
- /**
3281
- * Build a BVH for a mesh's geometry if it doesn't have one yet.
3282
- * Uses indirect mode to avoid modifying the index buffer.
3283
- * Skips disposed geometries and does NOT mark failed builds so they
3284
- * can be retried (e.g. after geometry is re-uploaded).
3285
- */
3286
- _ensureBVH(obj) {
3287
- if (!obj.isMesh) return;
3288
- const geom = obj.geometry;
3289
- if (!geom || this._bvhBuiltFor.has(geom)) return;
3290
- const posAttr = geom.getAttribute("position");
3291
- if (!posAttr || !posAttr.array) return;
3292
- if (geom.boundsTree) {
3293
- this._bvhBuiltFor.add(geom);
3294
- return;
3295
- }
3296
- try {
3297
- geom.computeBoundsTree({ indirect: true });
3298
- this._bvhBuiltFor.add(geom);
3299
- } catch {
3300
- r3fLog("raycast", `BVH build failed for geometry, will retry next rebuild`);
3301
- }
3302
- }
3303
- /**
3304
- * Raycast from mouse position against only raycastable meshes.
3305
- * Returns the closest non-internal hit, or null.
3306
- */
3307
- raycastAtMouse(e, camera, canvas) {
3308
- if (this._dirty) this._rebuild();
3309
- const rect = canvas.getBoundingClientRect();
3310
- _mouse.x = (e.clientX - rect.left) / rect.width * 2 - 1;
3311
- _mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
3312
- _raycaster2.setFromCamera(_mouse, camera);
3313
- _raycaster2.firstHitOnly = true;
3314
- const intersections = _raycaster2.intersectObjects(this._targets, false);
3315
- _raycaster2.firstHitOnly = false;
3316
- for (const intersection of intersections) {
3317
- if (intersection.object.userData?.__r3fdom_internal) continue;
3318
- return intersection.object;
3319
- }
3320
- return null;
3321
- }
3322
- /**
3323
- * Raycast from NDC coordinates. Used by raycastVerify.
3324
- */
3325
- raycastAtNdc(ndcX, ndcY, camera) {
3326
- if (this._dirty) this._rebuild();
3327
- _mouse.set(ndcX, ndcY);
3328
- _raycaster2.setFromCamera(_mouse, camera);
3329
- return _raycaster2.intersectObjects(this._targets, false);
3330
- }
3331
- /** Current number of raycastable targets. */
3332
- get targetCount() {
3333
- if (this._dirty) this._rebuild();
3334
- return this._targets.length;
3335
- }
3336
- /** Unsubscribe from the store and release the target list. */
3337
- dispose() {
3338
- if (this._unsubscribe) {
3339
- this._unsubscribe();
3340
- this._unsubscribe = null;
3341
- }
3342
- this._targets = [];
3343
- }
3344
- };
3345
-
3346
- // src/bridge/ThreeDom.tsx
3347
- var _stores = /* @__PURE__ */ new Map();
3348
- var _mirrors = /* @__PURE__ */ new Map();
3349
- var _selectionManagers = /* @__PURE__ */ new Map();
3350
- var _highlighters = /* @__PURE__ */ new Map();
3351
- var _inspectControllers = /* @__PURE__ */ new Map();
3352
- var _filters = /* @__PURE__ */ new Map();
3353
- var _modes = /* @__PURE__ */ new Map();
3354
- function shouldRegister(instanceKey, obj) {
3355
- const mode = _modes.get(instanceKey);
3356
- if (mode === "manual") return false;
3357
- const filter = _filters.get(instanceKey);
3358
- if (filter) return filter(obj);
3359
- return true;
3360
- }
3361
- function ensureAncestorChain(obj, store, mirror) {
3362
- const chain = [];
3363
- let cursor = obj.parent;
3364
- while (cursor) {
3365
- if (store.has(cursor)) break;
3366
- chain.push(cursor);
3367
- cursor = cursor.parent;
3368
- }
3369
- for (let i = chain.length - 1; i >= 0; i--) {
3370
- const ancestor = chain[i];
3371
- store.register(ancestor);
3372
- mirror.onObjectAdded(ancestor);
3373
- mirror.materialize(ancestor.uuid);
3374
- }
3375
- }
3376
- function getStore2(canvasId = "") {
3377
- return _stores.get(canvasId) ?? null;
3378
- }
3379
- function getMirror(canvasId = "") {
3380
- return _mirrors.get(canvasId) ?? null;
3381
- }
3382
- function getSelectionManager(canvasId = "") {
3383
- return _selectionManagers.get(canvasId) ?? null;
3384
- }
3385
- function getHighlighter(canvasId = "") {
3386
- return _highlighters.get(canvasId) ?? null;
3387
- }
3388
- function getInspectController(canvasId = "") {
3389
- return _inspectControllers.get(canvasId) ?? null;
3390
- }
3391
- function getCanvasIds() {
3392
- return Array.from(_stores.keys());
3393
- }
3394
- function exposeGlobalAPI(store, gl, cameraRef, selMgr, inspCtrl, mirror, canvasId, isPrimary = true) {
3395
- const api = {
3396
- _ready: true,
3397
- canvasId,
3398
- getByTestId: (id) => store.getByTestId(id),
3399
- getByUuid: (uuid) => store.getByUuid(uuid),
3400
- getByName: (name) => store.getByName(name),
3401
- getChildren: (idOrUuid) => store.getChildren(idOrUuid),
3402
- getParent: (idOrUuid) => store.getParent(idOrUuid),
3403
- getCount: () => store.getCount(),
3404
- getByType: (type) => store.getByType(type),
3405
- getByGeometryType: (type) => store.getByGeometryType(type),
3406
- getByMaterialType: (type) => store.getByMaterialType(type),
3407
- getByUserData: (key, value) => store.getByUserData(key, value),
3408
- getCountByType: (type) => store.getCountByType(type),
3409
- getObjects: (ids) => {
3410
- const map = store.getObjects(ids);
3411
- const result = {};
3412
- for (const [id, meta] of map) {
3413
- result[id] = meta;
3414
- }
3415
- return result;
3416
- },
3417
- snapshot: () => createSnapshot(store),
3418
- inspect: (idOrUuid, options) => store.inspect(idOrUuid, options),
3419
- click: (idOrUuid) => {
3420
- click3D(idOrUuid);
3421
- },
3422
- doubleClick: (idOrUuid) => {
3423
- doubleClick3D(idOrUuid);
3424
- },
3425
- contextMenu: (idOrUuid) => {
3426
- contextMenu3D(idOrUuid);
3427
- },
3428
- hover: (idOrUuid) => {
3429
- hover3D(idOrUuid);
3430
- },
3431
- unhover: () => {
3432
- unhover3D();
3433
- },
3434
- drag: async (idOrUuid, delta) => {
3435
- await drag3D(idOrUuid, delta);
3436
- },
3437
- wheel: (idOrUuid, options) => {
3438
- wheel3D(idOrUuid, options);
3439
- },
3440
- pointerMiss: () => {
3441
- pointerMiss3D();
3442
- },
3443
- drawPath: async (points, options) => {
3444
- const result = await drawPath(points, options);
3445
- return { eventCount: result.eventCount, pointCount: result.pointCount };
3446
- },
3447
- select: (idOrUuid) => {
3448
- const obj = store.getObject3D(idOrUuid);
3449
- if (obj && selMgr) selMgr.select(obj);
3450
- },
3451
- clearSelection: () => {
3452
- selMgr?.clearSelection();
3453
- },
3454
- getSelection: () => selMgr ? selMgr.getSelected().map((o) => o.uuid) : [],
3455
- getObject3D: (idOrUuid) => store.getObject3D(idOrUuid),
3456
- getSelectionDisplayTarget: (uuid) => resolveSelectionDisplayTarget((id) => store.getObject3D(id), uuid) ?? uuid,
3457
- setInspectMode: (on) => {
3458
- r3fLog("inspect", "Global API setInspectMode called", { on });
3459
- const ctrl = inspCtrl ?? _inspectControllers.get(canvasId ?? "");
3460
- if (on) {
3461
- ctrl?.enable();
3462
- mirror?.setInspectMode(true);
3463
- } else {
3464
- ctrl?.disable();
3465
- mirror?.setInspectMode(false);
3466
- }
3467
- },
3468
- getInspectMode: () => {
3469
- const ctrl = inspCtrl ?? _inspectControllers.get(canvasId ?? "");
3470
- return ctrl?.active ?? false;
3471
- },
3472
- sweepOrphans: () => store.sweepOrphans(),
3473
- getDiagnostics: () => ({
3474
- version,
3475
- ready: true,
3476
- objectCount: store.getCount(),
3477
- meshCount: store.getCountByType("Mesh"),
3478
- groupCount: store.getCountByType("Group"),
3479
- lightCount: store.getCountByType("DirectionalLight") + store.getCountByType("PointLight") + store.getCountByType("SpotLight") + store.getCountByType("AmbientLight") + store.getCountByType("HemisphereLight"),
3480
- cameraCount: store.getCountByType("PerspectiveCamera") + store.getCountByType("OrthographicCamera"),
3481
- materializedDomNodes: mirror?.getMaterializedCount() ?? 0,
3482
- maxDomNodes: mirror?.getMaxNodes() ?? 0,
3483
- canvasWidth: gl.domElement.width,
3484
- canvasHeight: gl.domElement.height,
3485
- webglRenderer: (() => {
3486
- try {
3487
- const ctx = gl.getContext();
3488
- const dbg = ctx.getExtension("WEBGL_debug_renderer_info");
3489
- return dbg ? ctx.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : "unknown";
3490
- } catch {
3491
- return "unknown";
3492
- }
3493
- })(),
3494
- dirtyQueueSize: store.getDirtyCount()
3495
- }),
3496
- getCameraState: () => {
3497
- const cam = cameraRef.current;
3498
- const dir = new Vector3(0, 0, -1).applyQuaternion(cam.quaternion);
3499
- const target = [
3500
- cam.position.x + dir.x * 100,
3501
- cam.position.y + dir.y * 100,
3502
- cam.position.z + dir.z * 100
3503
- ];
3504
- const state = {
3505
- type: cam.type,
3506
- position: [cam.position.x, cam.position.y, cam.position.z],
3507
- rotation: [cam.rotation.x, cam.rotation.y, cam.rotation.z],
3508
- target,
3509
- near: cam.near,
3510
- far: cam.far,
3511
- zoom: cam.zoom
3512
- };
3513
- if (cam.type === "PerspectiveCamera") {
3514
- const pc = cam;
3515
- state.fov = pc.fov;
3516
- state.aspect = pc.aspect;
3517
- } else if (cam.type === "OrthographicCamera") {
3518
- const oc = cam;
3519
- state.left = oc.left;
3520
- state.right = oc.right;
3521
- state.top = oc.top;
3522
- state.bottom = oc.bottom;
3523
- }
3524
- return state;
3525
- },
3526
- r3fRegister: (obj) => {
3527
- if (store.has(obj)) return;
3528
- if (!store.isInTrackedScene(obj)) {
3529
- console.warn(
3530
- `[react-three-dom] r3fRegister: object "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" is not in a tracked scene. Add it to the scene graph first.`
3531
- );
3532
- return;
3533
- }
3534
- obj.userData.__r3fdom_manual = true;
3535
- ensureAncestorChain(obj, store, mirror);
3536
- let count = 0;
3537
- obj.traverse((child) => {
3538
- if (!store.has(child)) {
3539
- store.register(child);
3540
- mirror?.onObjectAdded(child);
3541
- mirror?.materialize(child.uuid);
3542
- count++;
3543
- }
3544
- });
3545
- r3fLog("bridge", `r3fRegister: "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" (${obj.type}) \u2014 ${count} objects`);
3546
- },
3547
- r3fUnregister: (obj) => {
3548
- if (!store.has(obj)) return;
3549
- if (!obj.userData?.__r3fdom_manual) {
3550
- r3fLog("bridge", `r3fUnregister skipped \u2014 "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" was auto-registered`);
3551
- return;
3552
- }
3553
- delete obj.userData.__r3fdom_manual;
3554
- mirror?.onObjectRemoved(obj);
3555
- obj.traverse((child) => store.unregister(child));
3556
- r3fLog("bridge", `r3fUnregister: "${obj.userData?.testId || obj.name || obj.uuid.slice(0, 8)}" (${obj.type})`);
3557
- },
3558
- fuzzyFind: (query, limit = 5) => {
3559
- const q = query.toLowerCase();
3560
- const results = [];
3561
- for (const obj of store.getFlatList()) {
3562
- if (results.length >= limit) break;
3563
- const meta = store.getMetadata(obj);
3564
- if (!meta) continue;
3565
- const testId = meta.testId?.toLowerCase() ?? "";
3566
- const name = meta.name?.toLowerCase() ?? "";
3567
- if (testId.includes(q) || name.includes(q) || meta.uuid.startsWith(q)) {
3568
- results.push(meta);
3569
- }
3570
- }
3571
- return results;
3572
- },
3573
- version
3574
- };
3575
- if (isPrimary) {
3576
- window.__R3F_DOM__ = api;
3577
- }
3578
- if (canvasId) {
3579
- if (!window.__R3F_DOM_INSTANCES__) window.__R3F_DOM_INSTANCES__ = {};
3580
- if (window.__R3F_DOM_INSTANCES__[canvasId]) {
3581
- console.warn(
3582
- `[react-three-dom] Duplicate canvasId "${canvasId}" \u2014 the previous bridge instance will be overwritten. Each <ThreeDom> must have a unique canvasId.`
3583
- );
3584
- }
3585
- window.__R3F_DOM_INSTANCES__[canvasId] = api;
3586
- }
3587
- }
3588
- function removeGlobalAPI(onlyIfEquals, canvasId) {
3589
- r3fLog("bridge", "removeGlobalAPI called (deferred)");
3590
- const removeFromRegistry = (ref) => {
3591
- if (canvasId && window.__R3F_DOM_INSTANCES__) {
3592
- if (!ref || window.__R3F_DOM_INSTANCES__[canvasId] === ref) {
3593
- delete window.__R3F_DOM_INSTANCES__[canvasId];
3594
- if (Object.keys(window.__R3F_DOM_INSTANCES__).length === 0) {
3595
- delete window.__R3F_DOM_INSTANCES__;
3596
- }
3597
- }
3598
- }
3599
- };
3600
- if (onlyIfEquals !== void 0) {
3601
- const ref = onlyIfEquals;
3602
- queueMicrotask(() => {
3603
- if (window.__R3F_DOM__ === ref) {
3604
- delete window.__R3F_DOM__;
3605
- r3fLog("bridge", "Global API removed");
3606
- } else {
3607
- r3fLog("bridge", "Global API not removed \u2014 replaced by new instance (Strict Mode remount)");
3608
- }
3609
- removeFromRegistry(ref);
3610
- });
3611
- } else {
3612
- delete window.__R3F_DOM__;
3613
- removeFromRegistry();
3614
- r3fLog("bridge", "Global API removed (immediate)");
3615
- }
3616
- }
3617
- function createStubBridge(error, canvasId) {
3618
- return {
3619
- _ready: false,
3620
- _error: error,
3621
- canvasId,
3622
- getByTestId: () => null,
3623
- getByUuid: () => null,
3624
- getByName: () => [],
3625
- getChildren: () => [],
3626
- getParent: () => null,
3627
- getCount: () => 0,
3628
- getByType: () => [],
3629
- getByGeometryType: () => [],
3630
- getByMaterialType: () => [],
3631
- getByUserData: () => [],
3632
- getCountByType: () => 0,
3633
- getObjects: (ids) => {
3634
- const result = {};
3635
- for (const id of ids) result[id] = null;
3636
- return result;
3637
- },
3638
- snapshot: () => ({
3639
- timestamp: 0,
3640
- objectCount: 0,
3641
- tree: {
3642
- uuid: "",
3643
- name: "",
3644
- type: "Scene",
3645
- visible: true,
3646
- position: [0, 0, 0],
3647
- rotation: [0, 0, 0],
3648
- scale: [1, 1, 1],
3649
- children: []
3650
- }
3651
- }),
3652
- inspect: () => null,
3653
- click: () => {
3654
- },
3655
- doubleClick: () => {
3656
- },
3657
- contextMenu: () => {
3658
- },
3659
- hover: () => {
3660
- },
3661
- unhover: () => {
3662
- },
3663
- drag: async () => {
3664
- },
3665
- wheel: () => {
3666
- },
3667
- pointerMiss: () => {
3668
- },
3669
- drawPath: async () => ({ eventCount: 0, pointCount: 0 }),
3670
- select: () => {
3671
- },
3672
- clearSelection: () => {
3673
- },
3674
- getSelection: () => [],
3675
- getObject3D: () => null,
3676
- getSelectionDisplayTarget: (uuid) => uuid,
3677
- setInspectMode: () => {
3678
- },
3679
- getInspectMode: () => false,
3680
- r3fRegister: () => {
3681
- },
3682
- r3fUnregister: () => {
3683
- },
3684
- sweepOrphans: () => 0,
3685
- getDiagnostics: () => ({
3686
- version,
3687
- ready: false,
3688
- error: error ?? void 0,
3689
- objectCount: 0,
3690
- meshCount: 0,
3691
- groupCount: 0,
3692
- lightCount: 0,
3693
- cameraCount: 0,
3694
- materializedDomNodes: 0,
3695
- maxDomNodes: 0,
3696
- canvasWidth: 0,
3697
- canvasHeight: 0,
3698
- webglRenderer: "unavailable",
3699
- dirtyQueueSize: 0
3700
- }),
3701
- getCameraState: () => ({
3702
- type: "unknown",
3703
- position: [0, 0, 0],
3704
- rotation: [0, 0, 0],
3705
- target: [0, 0, -100],
3706
- near: 0.1,
3707
- far: 1e3,
3708
- zoom: 1
3709
- }),
3710
- fuzzyFind: () => [],
3711
- version
3712
- };
3713
- }
3714
- function ThreeDom({
3715
- canvasId,
3716
- primary,
3717
- root = "#three-dom-root",
3718
- mode = "auto",
3719
- filter,
3720
- batchSize = 500,
3721
- syncBudgetMs = 0.5,
3722
- maxDomNodes = 2e3,
3723
- initialDepth = 3,
3724
- enabled = true,
3725
- debug = false,
3726
- inspect: inspectProp = false
3727
- } = {}) {
3728
- const isPrimary = primary ?? canvasId === void 0;
3729
- const instanceKey = canvasId ?? "";
3730
- const scene = useThree((s) => s.scene);
3731
- const camera = useThree((s) => s.camera);
3732
- const gl = useThree((s) => s.gl);
3733
- const size = useThree((s) => s.size);
3734
- const cursorRef = useRef(0);
3735
- const lastSweepRef = useRef(0);
3736
- const cameraRef = useRef(camera);
3737
- cameraRef.current = camera;
3738
- useEffect(() => {
3739
- if (!enabled) return;
3740
- if (debug) enableDebug(true);
3741
- r3fLog("setup", "ThreeDom effect started", { enabled, debug, root, maxDomNodes });
3742
- const canvas = gl.domElement;
3743
- canvas.setAttribute("data-r3f-canvas", canvasId ?? "true");
3744
- const canvasParent = canvas.parentElement;
3745
- let rootElement = null;
3746
- let createdRoot = false;
3747
- if (typeof root === "string") {
3748
- rootElement = document.querySelector(root);
3749
- } else {
3750
- rootElement = root;
3751
- }
3752
- if (!rootElement) {
3753
- rootElement = document.createElement("div");
3754
- rootElement.id = typeof root === "string" ? root.replace(/^#/, "") : "three-dom-root";
3755
- createdRoot = true;
3756
- }
3757
- canvasParent.style.position = canvasParent.style.position || "relative";
3758
- canvasParent.appendChild(rootElement);
3759
- rootElement.style.cssText = "width:0;height:0;overflow:hidden;pointer-events:none;opacity:0;";
3760
- let store = null;
3761
- let mirror = null;
3762
- let unpatch = null;
3763
- let cancelAsyncReg = null;
3764
- let selectionManager = null;
3765
- let highlighter = null;
3766
- let raycastAccelerator = null;
3767
- let inspectController = null;
3768
- let currentApi;
3769
- try {
3770
- const webglContext = gl.getContext();
3771
- if (!webglContext || webglContext.isContextLost?.()) {
3772
- const msg = "WebGL context not available. For headless Chromium, add --enable-webgl and optionally --use-gl=angle --use-angle=swiftshader-webgl to launch args.";
3773
- const stubApi = createStubBridge(msg, canvasId);
3774
- if (isPrimary) window.__R3F_DOM__ = stubApi;
3775
- if (canvasId) {
3776
- if (!window.__R3F_DOM_INSTANCES__) window.__R3F_DOM_INSTANCES__ = {};
3777
- window.__R3F_DOM_INSTANCES__[canvasId] = stubApi;
3778
- }
3779
- r3fLog("setup", msg);
3780
- return () => {
3781
- removeGlobalAPI(stubApi, canvasId);
3782
- canvas.removeAttribute("data-r3f-canvas");
3783
- if (createdRoot && rootElement?.parentNode) {
3784
- rootElement.parentNode.removeChild(rootElement);
3785
- }
3786
- if (debug) enableDebug(false);
3787
- };
3788
- }
3789
- store = new ObjectStore();
3790
- mirror = new DomMirror(store, maxDomNodes);
3791
- mirror.setRoot(rootElement);
3792
- r3fLog("setup", "Store and mirror created");
3793
- ensureCustomElements(store);
3794
- _modes.set(instanceKey, mode);
3795
- _filters.set(instanceKey, filter ?? null);
3796
- unpatch = patchObject3D(store, mirror, instanceKey);
3797
- setInteractionState(store, camera, gl, size);
3798
- r3fLog("setup", `Object3D patched (mode=${mode}), interaction state set`);
3799
- if (mode === "auto") {
3800
- if (filter) {
3801
- store.addTrackedRoot(scene);
3802
- store.register(scene);
3803
- mirror.onObjectAdded(scene);
3804
- scene.traverse((obj) => {
3805
- if (obj === scene) return;
3806
- if (filter(obj)) {
3807
- ensureAncestorChain(obj, store, mirror);
3808
- store.register(obj);
3809
- mirror.onObjectAdded(obj);
3810
- }
3811
- });
3812
- } else {
3813
- store.registerTree(scene);
3814
- }
3815
- if (!store.has(camera)) {
3816
- const camMeta = store.register(camera);
3817
- camMeta.parentUuid = scene.uuid;
3818
- mirror.materialize(camera.uuid);
3819
- }
3820
- mirror.materializeSubtree(scene.uuid, initialDepth);
3821
- if (!filter) {
3822
- cancelAsyncReg = store.registerTreeAsync(scene);
3823
- }
3824
- } else {
3825
- store.addTrackedRoot(scene);
3826
- store.register(scene);
3827
- mirror.onObjectAdded(scene);
3828
- }
3829
- r3fLog("setup", `Scene registered (mode=${mode}): ${store.getCount()} objects`);
3830
- selectionManager = new SelectionManager();
3831
- highlighter = new Highlighter();
3832
- highlighter.attach(scene, selectionManager, camera, gl, store);
3833
- raycastAccelerator = new RaycastAccelerator(store);
3834
- inspectController = new InspectController({
3835
- camera,
3836
- renderer: gl,
3837
- selectionManager,
3838
- highlighter,
3839
- raycastAccelerator,
3840
- mirror,
3841
- store
3842
- });
3843
- _selectionManagers.set(instanceKey, selectionManager);
3844
- _highlighters.set(instanceKey, highlighter);
3845
- _inspectControllers.set(instanceKey, inspectController);
3846
- exposeGlobalAPI(store, gl, cameraRef, selectionManager, inspectController, mirror, canvasId, isPrimary);
3847
- r3fLog("bridge", `exposeGlobalAPI called \u2014 bridge is live, _ready=true${canvasId ? `, canvasId="${canvasId}"` : ""}`);
3848
- currentApi = canvasId ? window.__R3F_DOM_INSTANCES__?.[canvasId] : window.__R3F_DOM__;
3849
- _stores.set(instanceKey, store);
3850
- _mirrors.set(instanceKey, mirror);
3851
- if (inspectProp) {
3852
- inspectController.enable();
3853
- }
3854
- if (debug) {
3855
- const inspectStatus = inspectProp ? "on" : "off";
3856
- console.log(
3857
- "%c[react-three-dom]%c v" + version + " \u2014 " + store.getCount() + " objects \xB7 inspect: " + inspectStatus + "\n %c\u2192%c Toggle inspect: %c__R3F_DOM__.setInspectMode(true|false)%c\n %c\u2192%c Hover 3D objects to highlight + auto-reveal in Elements tab",
3858
- "background:#1a73e8;color:#fff;padding:2px 6px;border-radius:3px;font-weight:bold",
3859
- "color:inherit",
3860
- "color:#1a73e8",
3861
- "color:inherit",
3862
- "color:#e8a317;font-family:monospace",
3863
- "color:inherit",
3864
- "color:#1a73e8",
3865
- "color:inherit"
3866
- );
3867
- }
3868
- } catch (err) {
3869
- const errorMsg = err instanceof Error ? err.message : String(err);
3870
- r3fLog("setup", "ThreeDom setup failed", err);
3871
- console.error("[react-three-dom] Setup failed:", err);
3872
- const stubApi = createStubBridge(errorMsg, canvasId);
3873
- if (isPrimary) window.__R3F_DOM__ = stubApi;
3874
- if (canvasId) {
3875
- if (!window.__R3F_DOM_INSTANCES__) window.__R3F_DOM_INSTANCES__ = {};
3876
- window.__R3F_DOM_INSTANCES__[canvasId] = stubApi;
3877
- }
3878
- currentApi = stubApi;
3879
- }
3880
- return () => {
3881
- r3fLog("setup", "ThreeDom cleanup started");
3882
- if (cancelAsyncReg) cancelAsyncReg();
3883
- if (inspectController) inspectController.dispose();
3884
- if (raycastAccelerator) raycastAccelerator.dispose();
3885
- if (highlighter) highlighter.dispose();
3886
- if (unpatch) unpatch();
3887
- removeGlobalAPI(currentApi, canvasId);
3888
- clearInteractionState();
3889
- if (selectionManager) selectionManager.dispose();
3890
- if (mirror) mirror.dispose();
3891
- if (store) store.dispose();
3892
- canvas.removeAttribute("data-r3f-canvas");
3893
- if (createdRoot && rootElement?.parentNode) {
3894
- rootElement.parentNode.removeChild(rootElement);
3895
- }
3896
- _stores.delete(instanceKey);
3897
- _mirrors.delete(instanceKey);
3898
- _selectionManagers.delete(instanceKey);
3899
- _highlighters.delete(instanceKey);
3900
- _inspectControllers.delete(instanceKey);
3901
- _modes.delete(instanceKey);
3902
- _filters.delete(instanceKey);
3903
- if (debug) enableDebug(false);
3904
- r3fLog("setup", "ThreeDom cleanup complete");
3905
- };
3906
- }, [scene, camera, gl, enabled, root, maxDomNodes, initialDepth, debug, inspectProp, canvasId, isPrimary, instanceKey]);
3907
- useFrame(() => {
3908
- const _store3 = _stores.get(instanceKey);
3909
- const _mirror = _mirrors.get(instanceKey);
3910
- const _highlighter = _highlighters.get(instanceKey);
3911
- const _inspectController = _inspectControllers.get(instanceKey);
3912
- if (!enabled || !_store3 || !_mirror) return;
3913
- try {
3914
- setInteractionState(_store3, camera, gl, size);
3915
- if (_inspectController) _inspectController.updateCamera(camera);
3916
- const store = _store3;
3917
- const mirror = _mirror;
3918
- const start = performance.now();
3919
- const dirtyObjects = store.drainDirtyQueue();
3920
- for (const obj of dirtyObjects) {
3921
- store.update(obj);
3922
- mirror.syncAttributes(obj);
3923
- }
3924
- const budgetRemaining = syncBudgetMs - (performance.now() - start);
3925
- if (budgetRemaining > 0.1) {
3926
- const objects = store.getFlatList();
3927
- if (objects.length > 0) {
3928
- const end = Math.min(cursorRef.current + batchSize, objects.length);
3929
- for (let i = cursorRef.current; i < end; i++) {
3930
- if (performance.now() - start > syncBudgetMs) break;
3931
- const obj = objects[i];
3932
- const changed = store.update(obj);
3933
- if (changed) mirror.syncAttributes(obj);
3934
- }
3935
- cursorRef.current = end >= objects.length ? 0 : end;
3936
- }
3937
- }
3938
- if (_highlighter) _highlighter.update();
3939
- const now = performance.now();
3940
- if (now - lastSweepRef.current > 3e4) {
3941
- lastSweepRef.current = now;
3942
- store.sweepOrphans();
3943
- }
3944
- } catch (err) {
3945
- r3fLog("sync", "Per-frame sync error", err);
3946
- }
3947
- });
3948
- return null;
3949
- }
3950
- function getAPI(canvasId) {
3951
- if (canvasId) {
3952
- return window.__R3F_DOM_INSTANCES__?.[canvasId];
3953
- }
3954
- return window.__R3F_DOM__;
3955
- }
3956
- function useR3FRegister(ref, canvasId) {
3957
- const trackedObj = useRef(null);
3958
- const canvasIdRef = useRef(canvasId);
3959
- canvasIdRef.current = canvasId;
3960
- const register = useCallback((obj) => {
3961
- const api = getAPI(canvasIdRef.current);
3962
- if (!api) return false;
3963
- api.r3fRegister(obj);
3964
- trackedObj.current = obj;
3965
- return true;
3966
- }, []);
3967
- const unregister = useCallback(() => {
3968
- if (!trackedObj.current) return;
3969
- const api = getAPI(canvasIdRef.current);
3970
- api?.r3fUnregister(trackedObj.current);
3971
- trackedObj.current = null;
3972
- }, []);
3973
- useEffect(() => {
3974
- const obj = ref.current;
3975
- if (obj) register(obj);
3976
- return () => unregister();
3977
- }, [ref, register, unregister]);
3978
- useFrame(() => {
3979
- const current = ref.current;
3980
- if (current === trackedObj.current) return;
3981
- unregister();
3982
- if (current) register(current);
3983
- });
3984
- }
3985
-
3986
- export { DomMirror, Highlighter, InspectController, MANAGED_ATTRIBUTES, ObjectStore, RaycastAccelerator, SelectionManager, TAG_MAP, ThreeDom, ThreeElement, applyAttributes, circlePath, click3D, computeAttributes, contextMenu3D, createFlatSnapshot, createSnapshot, curvePath, dispatchClick, dispatchContextMenu, dispatchDoubleClick, dispatchDrag, dispatchHover, dispatchPointerMiss, dispatchUnhover, dispatchWheel, doubleClick3D, drag3D, drawPath, enableDebug, ensureCustomElements, getCanvasIds, getHighlighter, getInspectController, getMirror, getSelectionManager, getStore2 as getStore, getTagForType, hover3D, isDebugEnabled, isInFrustum, isPatched, linePath, patchObject3D, pointerMiss3D, previewDragWorldDelta, projectAllSamplePoints, projectToScreen, r3fLog, rectPath, resolveObject, restoreObject3D, screenDeltaToWorld, unhover3D, useR3FRegister, verifyRaycastHit, verifyRaycastHitMultiPoint, version, wheel3D };
3987
- //# sourceMappingURL=index.js.map
1
+ import {Box3,Vector3,Frustum,Matrix4,Raycaster,Vector2,Object3D,BufferGeometry,Material,Color,MeshBasicMaterial,DoubleSide,Mesh,BoxGeometry,EdgesGeometry,LineBasicMaterial,LineSegments,InstancedMesh,PerspectiveCamera,OrthographicCamera}from'three';import {useRef,useEffect,useCallback}from'react';import {useThree,useFrame}from'@react-three/fiber';var $="0.6.0";var Le=false;function ne(t=true){Le=t;}function rr(){return Le||typeof window<"u"&&!!window.__R3F_DOM_DEBUG__}function d(t,e,r){!Le&&!(typeof window<"u"&&window.__R3F_DOM_DEBUG__)||(r!==void 0?console.log(`[r3f-dom:${t}]`,e,r):console.log(`[r3f-dom:${t}]`,e));}function _t(t){let e={uuid:t.uuid,name:t.name,type:t.type,visible:t.visible,testId:t.userData?.testId,position:[t.position.x,t.position.y,t.position.z],rotation:[t.rotation.x,t.rotation.y,t.rotation.z],scale:[t.scale.x,t.scale.y,t.scale.z],parentUuid:t.parent?.uuid??null,childrenUuids:t.children.map(r=>r.uuid),boundsDirty:true};return cr(t,e),e}function cr(t,e){try{if("geometry"in t){let r=t.geometry;if(r instanceof BufferGeometry){e.geometryType=r.type;let n=r.getAttribute("position");n&&(e.vertexCount=n.count,r.index?e.triangleCount=Math.floor(r.index.count/3):e.triangleCount=Math.floor(n.count/3));}}}catch{d("store",`extractMetadata: geometry access failed for "${t.name||t.uuid}"`);}try{if("material"in t){let r=t.material;r instanceof Material?e.materialType=r.type:Array.isArray(r)&&r.length>0&&(e.materialType=r[0].type+(r.length>1?` (+${r.length-1})`:""));}}catch{d("store",`extractMetadata: material access failed for "${t.name||t.uuid}"`);}try{t instanceof InstancedMesh&&(e.instanceCount=t.count);}catch{}try{t instanceof PerspectiveCamera?(e.fov=t.fov,e.near=t.near,e.far=t.far,e.zoom=t.zoom):t instanceof OrthographicCamera&&(e.near=t.near,e.far=t.far,e.zoom=t.zoom);}catch{}}function lr(t,e){let r=false;e.visible!==t.visible&&(e.visible=t.visible,r=true),e.name!==t.name&&(e.name=t.name,r=true);let n=t.userData?.testId;e.testId!==n&&(e.testId=n,r=true);let i=t.position;(e.position[0]!==i.x||e.position[1]!==i.y||e.position[2]!==i.z)&&(e.position[0]=i.x,e.position[1]=i.y,e.position[2]=i.z,r=true);let a=t.rotation;(e.rotation[0]!==a.x||e.rotation[1]!==a.y||e.rotation[2]!==a.z)&&(e.rotation[0]=a.x,e.rotation[1]=a.y,e.rotation[2]=a.z,r=true);let o=t.scale;(e.scale[0]!==o.x||e.scale[1]!==o.y||e.scale[2]!==o.z)&&(e.scale[0]=o.x,e.scale[1]=o.y,e.scale[2]=o.z,r=true);let c=t.parent?.uuid??null;e.parentUuid!==c&&(e.parentUuid=c,r=true);let u=t.children,s=e.childrenUuids;if(s.length!==u.length)e.childrenUuids=u.map(l=>l.uuid),r=true;else for(let l=0;l<s.length;l++)if(s[l]!==u[l].uuid){e.childrenUuids=u.map(p=>p.uuid),r=true;break}return r}var N=new Box3;function ur(t,e,r){let n=Array(16).fill(0),i=[0,0,0],a=[0,0,0];try{t.updateWorldMatrix(!0,!1),n=Array.from(t.matrixWorld.elements),N.setFromObject(t),i=[N.min.x,N.min.y,N.min.z],a=[N.max.x,N.max.y,N.max.z];}catch{d("store",`inspectObject: world matrix / bounds failed for "${t.name||t.uuid}"`);}let o={metadata:e,worldMatrix:n,bounds:{min:i,max:a},userData:{}};try{o.userData={...t.userData};}catch{d("store",`inspectObject: userData copy failed for "${t.name||t.uuid}"`);}try{if("geometry"in t){let c=t.geometry;if(c instanceof BufferGeometry&&c.attributes){let u={type:c.type,attributes:{}};for(let[l,p]of Object.entries(c.attributes))p&&(u.attributes[l]={itemSize:p.itemSize,count:p.count});if(c.index&&(u.index={count:c.index.count}),r?.includeGeometryData){let l=c.getAttribute("position");l?.array&&(u.positionData=Array.from(l.array)),c.index?.array&&(u.indexData=Array.from(c.index.array));}c.computeBoundingSphere();let s=c.boundingSphere;s&&(u.boundingSphere={center:[s.center.x,s.center.y,s.center.z],radius:s.radius}),o.geometry=u;}}}catch{d("store",`inspectObject: geometry inspection failed for "${t.name||t.uuid}"`);}try{if("material"in t){let c=t.material;if(!c)throw new Error("disposed");let u=Array.isArray(c)?c[0]:c;if(u instanceof Material){let s={type:u.type,transparent:u.transparent,opacity:u.opacity,side:u.side};if("color"in u&&u.color instanceof Color&&(s.color="#"+u.color.getHexString()),"map"in u){let l=u.map;l&&(s.map=l.name||l.uuid||"unnamed");}if("uniforms"in u){let l=u.uniforms;s.uniforms={};for(let[p,g]of Object.entries(l)){let h=g.value;h==null||typeof h=="number"||typeof h=="boolean"||typeof h=="string"?s.uniforms[p]=h:typeof h=="object"&&"toArray"in h?s.uniforms[p]=h.toArray():s.uniforms[p]=`[${typeof h}]`;}}o.material=s;}}}catch{d("store",`inspectObject: material inspection failed for "${t.name||t.uuid}"`);}return o}var ie=class{constructor(){this._metaByObject=new WeakMap;this._objectByUuid=new Map;this._objectsByTestId=new Map;this._objectsByName=new Map;this._flatList=[];this._flatListDirty=true;this._dirtyQueue=new Set;this._listeners=[];this._trackedRoots=new WeakSet;this._asyncRegQueue=[];this._asyncRegHandle=null;this._asyncRegBatchSize=1e3;}register(e){if(e.userData?.__r3fdom_internal)return _t(e);let r=this._metaByObject.get(e);if(r)return r;let n=_t(e);if(this._metaByObject.set(e,n),this._objectByUuid.set(n.uuid,e),this._flatListDirty=true,e.userData.__r3fdom_tracked=true,n.testId&&this._objectsByTestId.set(n.testId,e),n.name){let i=this._objectsByName.get(n.name);i||(i=new Set,this._objectsByName.set(n.name,i)),i.add(e);}return this._emit({type:"add",object:e,metadata:n}),n.testId&&d("store",`Registered "${n.testId}" (${n.type})`),n}registerTree(e){this._trackedRoots.add(e),e.traverse(r=>{try{this.register(r);}catch(n){d("store",`registerTree: failed to register "${r.name||r.uuid}"`,n);}});}registerTreeAsync(e){this._trackedRoots.add(e);let r=[];return e.traverse(n=>r.push(n)),this._asyncRegQueue=r,d("store",`registerTreeAsync: ${r.length} objects queued`),this._scheduleRegChunk(),()=>this._cancelAsyncRegistration()}_scheduleRegChunk(){if(this._asyncRegQueue.length===0){this._asyncRegHandle=null,d("store",`registerTreeAsync complete: ${this.getCount()} objects registered`);return}let e=r=>{let n=r?()=>r.timeRemaining()>1:()=>true,i=0;for(;this._asyncRegQueue.length>0&&i<this._asyncRegBatchSize&&n();){let a=this._asyncRegQueue.shift();try{this.register(a);}catch(o){d("store",`registerTreeAsync: failed to register "${a.name||a.uuid}"`,o);}i++;}this._scheduleRegChunk();};typeof requestIdleCallback=="function"?this._asyncRegHandle=requestIdleCallback(e,{timeout:50}):this._asyncRegHandle=setTimeout(e,4);}_cancelAsyncRegistration(){this._asyncRegHandle!==null&&(typeof cancelIdleCallback=="function"?cancelIdleCallback(this._asyncRegHandle):clearTimeout(this._asyncRegHandle),this._asyncRegHandle=null),this._asyncRegQueue=[];}unregister(e){let r=this._metaByObject.get(e);if(r){if(this._metaByObject.delete(e),this._objectByUuid.delete(r.uuid),this._dirtyQueue.delete(e),this._flatListDirty=true,delete e.userData.__r3fdom_tracked,delete e.userData.__r3fdom_manual,r.testId&&this._objectsByTestId.delete(r.testId),r.name){let n=this._objectsByName.get(r.name);n&&(n.delete(e),n.size===0&&this._objectsByName.delete(r.name));}r.testId&&d("store",`Unregistered "${r.testId}" (${r.type})`),this._emit({type:"remove",object:e,metadata:r});}}addTrackedRoot(e){this._trackedRoots.add(e);}unregisterTree(e){e.traverse(r=>{this.unregister(r);}),this._trackedRoots.delete(e);}update(e){let r=this._metaByObject.get(e);if(!r)return false;let n;try{let i=r.testId,a=r.name;if(n=lr(e,r),n){if(i!==r.testId&&(d("store",`testId changed: "${i}" \u2192 "${r.testId}" (${r.type})`),i&&this._objectsByTestId.delete(i),r.testId&&this._objectsByTestId.set(r.testId,e)),a!==r.name){if(a){let o=this._objectsByName.get(a);o&&(o.delete(e),o.size===0&&this._objectsByName.delete(a));}if(r.name){let o=this._objectsByName.get(r.name);o||(o=new Set,this._objectsByName.set(r.name,o)),o.add(e);}}this._emit({type:"update",object:e,metadata:r});}}catch(i){return d("store",`update: updateDynamicFields failed for "${e.name||e.uuid}"`,i),false}return n}inspect(e,r){let n=this.getObject3D(e);if(!n)return null;let i=this._metaByObject.get(n);return i?ur(n,i,r):null}getByTestId(e){let r=this._objectsByTestId.get(e);return r?this._metaByObject.get(r)??null:null}getByUuid(e){let r=this._objectByUuid.get(e);return r?this._metaByObject.get(r)??null:null}getByName(e){let r=this._objectsByName.get(e);if(!r)return [];let n=[];for(let i of r){let a=this._metaByObject.get(i);a&&n.push(a);}return n}getChildren(e){let r=this.getByTestId(e)??this.getByUuid(e);if(!r)return [];let n=[];for(let i of r.childrenUuids){let a=this.getByUuid(i);a&&n.push(a);}return n}getParent(e){let r=this.getByTestId(e)??this.getByUuid(e);return !r||r.parentUuid===null?null:this.getByUuid(r.parentUuid)}getObjects(e){let r=new Map;for(let n of e){let i=this.getByTestId(n)??this.getByUuid(n);r.set(n,i);}return r}getByType(e){let r=[];for(let n of this.getFlatList()){let i=this._metaByObject.get(n);i&&i.type===e&&r.push(i);}return r}getByGeometryType(e){let r=[];for(let n of this.getFlatList()){let i=this._metaByObject.get(n);i&&i.geometryType===e&&r.push(i);}return r}getByMaterialType(e){let r=[];for(let n of this.getFlatList()){let i=this._metaByObject.get(n);i&&i.materialType===e&&r.push(i);}return r}getByUserData(e,r){let n=[],i=r!==void 0,a=i?JSON.stringify(r):"";for(let o of this.getFlatList()){if(!(e in o.userData)||i&&JSON.stringify(o.userData[e])!==a)continue;let c=this._metaByObject.get(o);c&&n.push(c);}return n}getCountByType(e){let r=0;for(let n of this.getFlatList()){let i=this._metaByObject.get(n);i&&i.type===e&&r++;}return r}getObject3D(e){return this._objectsByTestId.get(e)??this._objectByUuid.get(e)??null}getMetadata(e){return this._metaByObject.get(e)??null}has(e){return this._metaByObject.has(e)}getCount(){return this._objectByUuid.size}isTrackedRoot(e){return this._trackedRoots.has(e)}isInTrackedScene(e){if(e.userData?.__r3fdom_tracked)return true;let r=e.parent;for(;r;){if(r.userData?.__r3fdom_tracked||this._trackedRoots.has(r))return true;r=r.parent;}return false}getFlatList(){return this._flatListDirty&&(this._flatList=Array.from(this._objectByUuid.values()),this._flatListDirty=false),this._flatList}markDirty(e){this._metaByObject.has(e)&&this._dirtyQueue.add(e);}drainDirtyQueue(){if(this._dirtyQueue.size===0)return [];let e=Array.from(this._dirtyQueue);return this._dirtyQueue.clear(),e}getDirtyCount(){return this._dirtyQueue.size}subscribe(e){return this._listeners.push(e),()=>{let r=this._listeners.indexOf(e);r!==-1&&this._listeners.splice(r,1);}}_emit(e){for(let r of this._listeners)r(e);}sweepOrphans(){let e=0;for(let[r,n]of this._objectByUuid)!n.parent&&!this._trackedRoots.has(n)&&(this.unregister(n),e++,d("store",`sweepOrphans: removed orphan "${n.name||r}"`));return e}dispose(){this._cancelAsyncRegistration();for(let e of this._objectByUuid.values())e.userData&&delete e.userData.__r3fdom_tracked;this._objectByUuid.clear(),this._objectsByTestId.clear(),this._objectsByName.clear(),this._flatList=[],this._flatListDirty=true,this._dirtyQueue.clear(),this._listeners=[];}};var Ot={Scene:"three-scene",Group:"three-group",LOD:"three-group",Bone:"three-group",Mesh:"three-mesh",SkinnedMesh:"three-mesh",InstancedMesh:"three-mesh",LineSegments:"three-mesh",Line:"three-mesh",LineLoop:"three-mesh",Points:"three-mesh",Sprite:"three-mesh",AmbientLight:"three-light",DirectionalLight:"three-light",HemisphereLight:"three-light",PointLight:"three-light",RectAreaLight:"three-light",SpotLight:"three-light",LightProbe:"three-light",PerspectiveCamera:"three-camera",OrthographicCamera:"three-camera",ArrayCamera:"three-camera",CubeCamera:"three-camera",BoxHelper:"three-object",ArrowHelper:"three-object",AxesHelper:"three-object",GridHelper:"three-object",SkeletonHelper:"three-object"},dr=["three-scene","three-group","three-mesh","three-light","three-camera","three-object"],hr="three-object";function G(t){return Ot[t]??hr}var F=null;var ve=class extends HTMLElement{get metadata(){let e=this.dataset.uuid;return !e||!F?null:F.getByUuid(e)}inspect(){let e=this.dataset.uuid;return !e||!F?null:F.inspect(e)}get object3D(){let e=this.dataset.uuid;return !e||!F?null:F.getObject3D(e)}get position(){return this.metadata?.position??null}get rotation(){return this.metadata?.rotation??null}get scale(){return this.metadata?.scale??null}get visible(){return this.metadata?.visible??false}get testId(){return this.metadata?.testId}get bounds(){return this.inspect()?.bounds??null}click(){let e=this.dataset.uuid;if(!e){console.warn("[react-three-dom] Cannot click: element has no data-uuid");return}typeof window.__R3F_DOM__?.click=="function"?window.__R3F_DOM__.click(e):console.warn("[react-three-dom] Cannot click: bridge API not initialized");}hover(){let e=this.dataset.uuid;if(!e){console.warn("[react-three-dom] Cannot hover: element has no data-uuid");return}typeof window.__R3F_DOM__?.hover=="function"?window.__R3F_DOM__.hover(e):console.warn("[react-three-dom] Cannot hover: bridge API not initialized");}toString(){let e=this.tagName.toLowerCase(),r=this.dataset.name?` name="${this.dataset.name}"`:"",n=this.dataset.testId?` testId="${this.dataset.testId}"`:"",i=this.dataset.type?` type="${this.dataset.type}"`:"";return `<${e}${r}${n}${i}>`}},xt=false;function ze(t){if(!xt&&!(typeof customElements>"u")){F=t;for(let e of dr)if(!customElements.get(e)){let r=class extends ve{};Object.defineProperty(r,"name",{value:`ThreeElement_${e}`}),customElements.define(e,r);}xt=true;}}var Mt={"data-uuid":t=>t.uuid,"data-name":t=>t.name||void 0,"data-type":t=>t.type,"data-visible":t=>String(t.visible),"data-test-id":t=>t.testId,"data-geometry":t=>t.geometryType,"data-material":t=>t.materialType,"data-position":t=>ke(t.position),"data-rotation":t=>ke(t.rotation),"data-scale":t=>ke(t.scale),"data-vertex-count":t=>t.vertexCount!==void 0?String(t.vertexCount):void 0,"data-triangle-count":t=>t.triangleCount!==void 0?String(t.triangleCount):void 0,"data-instance-count":t=>t.instanceCount!==void 0?String(t.instanceCount):void 0,"data-fov":t=>t.fov!==void 0?String(t.fov):void 0,"data-near":t=>t.near!==void 0?String(t.near):void 0,"data-far":t=>t.far!==void 0?String(t.far):void 0,"data-zoom":t=>t.zoom!==void 0?String(t.zoom):void 0},pr=Object.keys(Mt);function ke(t){return `${We(t[0])},${We(t[1])},${We(t[2])}`}function We(t){let e=Math.round(t*1e4)/1e4;return String(e===0?0:e)}function wt(t){let e=new Map;for(let[r,n]of Object.entries(Mt)){let i=n(t);i!==void 0&&e.set(r,i);}return e}function Y(t,e,r){let n=0,i=wt(e);for(let[a,o]of i)r.get(a)!==o&&(t.setAttribute(a,o),r.set(a,o),n++);for(let a of r.keys())i.has(a)||(t.removeAttribute(a),r.delete(a),n++);return n}var oe=class{constructor(e,r=2e3){this._rootElement=null;this._nodes=new Map;this._lruHead=null;this._lruTail=null;this._lruSize=0;this._parentMap=new Map;this._inspectMode=false;this._asyncQueue=[];this._asyncIdleHandle=null;this._asyncBatchSize=200;this._store=e,this._maxNodes=r;}setRoot(e){this._rootElement=e;}setInspectMode(e){this._inspectMode!==e&&(this._inspectMode=e,e?this._startAsyncMaterialization():this._cancelAsyncMaterialization(),d("inspect","setInspectMode",{on:e,nodeCount:this._nodes.size}));}getInspectMode(){return this._inspectMode}buildInitialTree(e){this._rootElement&&this.materializeSubtree(e.uuid,2);}materialize(e){let r=this._nodes.get(e);if(r)return this._lruTouch(r.lruNode),r.element;let n=this._store.getByUuid(e);if(!n)return null;this._lruSize>=this._maxNodes&&this._evictLRU();let i=G(n.type),a=document.createElement(i);a.style.cssText="display:contents;";let o=new Map;Y(a,n,o);let c={uuid:e,prev:null,next:null};this._lruPush(c);let u={element:a,prevAttrs:o,lruNode:c};return this._nodes.set(e,u),this._parentMap.set(e,n.parentUuid),this._insertIntoDom(e,n.parentUuid,a),a}materializeSubtree(e,r){if(this.materialize(e),r<=0)return;let n=this._store.getByUuid(e);if(n)for(let i of n.childrenUuids)this.materializeSubtree(i,r-1);}dematerialize(e){let r=this._nodes.get(e);if(!r)return;let n=this._collectMaterializedDescendants(e);for(let i of n){let a=this._nodes.get(i);a&&(this._lruRemove(a.lruNode),this._nodes.delete(i),this._parentMap.delete(i));}r.element.remove(),this._lruRemove(r.lruNode),this._nodes.delete(e),this._parentMap.delete(e);}_collectMaterializedDescendants(e){let r=[];for(let[n,i]of this._parentMap)i===e&&(r.push(n),r.push(...this._collectMaterializedDescendants(n)));return r}onObjectAdded(e){let r=e.parent?.uuid;if(!r)return;let n=this._nodes.get(r);if(n&&this.materialize(e.uuid),n){let i=this._store.getByUuid(r);i&&Y(n.element,i,n.prevAttrs);}}onObjectRemoved(e){e.traverse(r=>{this.dematerialize(r.uuid);});}syncAttributes(e){let r=this._nodes.get(e.uuid);if(!r)return 0;let n=this._store.getMetadata(e);return n?Y(r.element,n,r.prevAttrs):0}syncAttributesByUuid(e){let r=this._nodes.get(e);if(!r)return 0;let n=this._store.getByUuid(e);return n?Y(r.element,n,r.prevAttrs):0}getRootElement(){return this._rootElement}getElement(e){let r=this._nodes.get(e);return r?(this._lruTouch(r.lruNode),r.element):null}getOrMaterialize(e){let r=this._nodes.get(e);return r?(this._lruTouch(r.lruNode),r.element):(this._materializeAncestorChain(e),this.materialize(e))}isMaterialized(e){return this._nodes.has(e)}querySelector(e){if(!this._rootElement)return null;let r=this._rootElement.querySelector(e);if(r)return r;let n=this._findUuidBySelector(e);return n?this.materialize(n):null}querySelectorAll(e){if(!this._rootElement)return [];let r=this._findAllUuidsBySelector(e),n=[];for(let i of r){let a=this.materialize(i);a&&n.push(a);}return n}setMaxNodes(e){for(this._maxNodes=e;this._lruSize>this._maxNodes;)this._evictLRU();}getMaterializedCount(){return this._nodes.size}getMaxNodes(){return this._maxNodes}dispose(){this._cancelAsyncMaterialization();for(let[,e]of this._nodes)e.element.remove();this._nodes.clear(),this._parentMap.clear(),this._lruHead=null,this._lruTail=null,this._lruSize=0,this._rootElement&&(this._rootElement.innerHTML="");}_insertIntoDom(e,r,n){if(!this._rootElement)return;if(!r){this._rootElement.appendChild(n);return}let i=this._nodes.get(r);i?i.element.appendChild(n):this._rootElement.appendChild(n);}_materializeAncestorChain(e){let r=[],n=e;for(;n&&!this._nodes.has(n);){let i=this._store.getByUuid(n);if(!i)break;r.push(n),n=i.parentUuid;}for(let i=r.length-1;i>0;i--)this.materialize(r[i]);}_startAsyncMaterialization(){this._cancelAsyncMaterialization();let e=this._store.getFlatList();this._asyncQueue=[];for(let r of e)r.userData?.__r3fdom_internal||this._nodes.has(r.uuid)||this._asyncQueue.push(r.uuid);this._asyncQueue.length!==0&&(d("inspect",`Async materialization started: ${this._asyncQueue.length} objects queued`),this._scheduleAsyncChunk());}_cancelAsyncMaterialization(){this._asyncIdleHandle!==null&&(typeof cancelIdleCallback=="function"?cancelIdleCallback(this._asyncIdleHandle):clearTimeout(this._asyncIdleHandle),this._asyncIdleHandle=null),this._asyncQueue=[];}_scheduleAsyncChunk(){if(this._asyncQueue.length===0){this._asyncIdleHandle=null,d("inspect",`Async materialization complete: ${this._nodes.size} nodes materialized`);return}let e=r=>{let n=r?()=>r.timeRemaining()>2:()=>true,i=0;for(;this._asyncQueue.length>0&&i<this._asyncBatchSize&&n();){let a=this._asyncQueue.shift();this._nodes.has(a)||this.materialize(a),i++;}this._scheduleAsyncChunk();};typeof requestIdleCallback=="function"?this._asyncIdleHandle=requestIdleCallback(e,{timeout:100}):this._asyncIdleHandle=setTimeout(e,16);}_findUuidBySelector(e){let r=e.match(/\[data-test-id=["']([^"']+)["']\]/);if(r)return this._store.getByTestId(r[1])?.uuid??null;let n=e.match(/\[data-uuid=["']([^"']+)["']\]/);if(n)return this._store.getByUuid(n[1])?.uuid??null;let i=e.match(/\[data-name=["']([^"']+)["']\]/);if(i){let a=this._store.getByName(i[1]);return a.length>0?a[0].uuid:null}return null}_findAllUuidsBySelector(e){let r=[],n=e.match(/\[data-test-id=["']([^"']+)["']\]/);if(n){let o=this._store.getByTestId(n[1]);return o&&r.push(o.uuid),r}let i=e.match(/\[data-name=["']([^"']+)["']\]/);if(i){let o=this._store.getByName(i[1]);for(let c of o)r.push(c.uuid);return r}let a=e.match(/^(three-(?:scene|group|mesh|light|camera|object))$/);if(a){let o=a[1],c=this._store.getFlatList();for(let u of c){let s=this._store.getMetadata(u);s&&G(s.type)===o&&r.push(s.uuid);}return r}return r}_lruPush(e){e.prev=null,e.next=this._lruHead,this._lruHead&&(this._lruHead.prev=e),this._lruHead=e,this._lruTail||(this._lruTail=e),this._lruSize++;}_lruRemove(e){e.prev?e.prev.next=e.next:this._lruHead=e.next,e.next?e.next.prev=e.prev:this._lruTail=e.prev,e.prev=null,e.next=null,this._lruSize--;}_lruTouch(e){this._lruHead!==e&&(this._lruRemove(e),this._lruPush(e));}_evictLRU(){if(!this._lruTail)return;let e=this._lruTail.uuid;this.dematerialize(e);}};var K=false,ae=null,ce=null,se=[];function St(t){for(let e of se)if(e.store.isInTrackedScene(t))return e;return null}function mr(t,e,r,n){t.traverse(i=>{!e.has(i)&&jt(n,i)&&(De(i,e,r),e.register(i),r.onObjectAdded(i));});}function $e(t,e,r=""){return se.push({store:t,mirror:e,instanceKey:r}),K||(d("patch","Patching Object3D.prototype.add and .remove"),ae=Object3D.prototype.add,ce=Object3D.prototype.remove,Object3D.prototype.add=function(...i){ae.call(this,...i);let a=St(this);if(a){for(let o of i)if(o!==this)try{d("patch",`patchedAdd: "${o.name||o.type}" added to "${this.name||this.type}"`),mr(o,a.store,a.mirror,a.instanceKey);}catch(c){d("patch",`patchedAdd: failed to register "${o.name||o.type}"`,c);}a.store.update(this),a.store.markDirty(this);}return this},Object3D.prototype.remove=function(...i){let a=St(this);if(a){for(let o of i)if(o!==this)try{d("patch",`patchedRemove: "${o.name||o.type}" removed from "${this.name||this.type}"`),a.mirror.onObjectRemoved(o),o.traverse(c=>{a.store.unregister(c);});}catch(c){d("patch",`patchedRemove: failed to unregister "${o.name||o.type}"`,c);}a.store.update(this),a.store.markDirty(this);}return ce.call(this,...i),this},K=true),()=>{let n=se.findIndex(i=>i.store===t&&i.mirror===e);n!==-1&&se.splice(n,1),se.length===0&&K&&Ct();}}function Ct(){K&&(d("patch","Restoring original Object3D.prototype.add and .remove"),ae&&(Object3D.prototype.add=ae,ae=null),ce&&(Object3D.prototype.remove=ce,ce=null),K=false);}function fr(){return K}function Rt(t,e){let r=[];for(let i of e.childrenUuids){let a=t.getByUuid(i);a&&r.push(Rt(t,a));}let n={uuid:e.uuid,name:e.name,type:e.type,testId:e.testId,visible:e.visible,position:[...e.position],rotation:[...e.rotation],scale:[...e.scale],children:r};return e.geometryType&&(n.geometryType=e.geometryType),e.materialType&&(n.materialType=e.materialType),e.vertexCount!=null&&(n.vertexCount=e.vertexCount),e.triangleCount!=null&&(n.triangleCount=e.triangleCount),e.instanceCount!=null&&(n.instanceCount=e.instanceCount),n}function gr(t){let e=t.getFlatList();for(let r of e){let n=t.getMetadata(r);if(n&&n.parentUuid===null)return n}return null}function Ne(t){let e=gr(t),r=e?Rt(t,e):{uuid:"",name:"empty",type:"Scene",visible:true,position:[0,0,0],rotation:[0,0,0],scale:[1,1,1],children:[]};return {timestamp:Date.now(),objectCount:t.getCount(),tree:r}}function yr(t){let e=[],r=t.getFlatList();for(let n of r){let i=t.getMetadata(n);i&&e.push({...i});}return {timestamp:Date.now(),objectCount:e.length,objects:e}}var P=new Vector3,U=new Vector3,S=new Box3,Fe=new Frustum,Tt=new Matrix4;function Dr(t,e){return {x:(t.x+1)/2*e.width,y:(-t.y+1)/2*e.height}}function xr(t){return t.x>=-1&&t.x<=1&&t.y>=-1&&t.y<=1&&t.z>=-1&&t.z<=1}function Or(t){return t.z>=-1&&t.z<=1}function Et(t){return S.setFromObject(t),S.isEmpty()?(t.getWorldPosition(P),P):(S.getCenter(P),P)}function It(t){if(S.setFromObject(t),S.isEmpty())return [];let{min:e,max:r}=S;return [new Vector3(e.x,e.y,e.z),new Vector3(e.x,e.y,r.z),new Vector3(e.x,r.y,e.z),new Vector3(e.x,r.y,r.z),new Vector3(r.x,e.y,e.z),new Vector3(r.x,e.y,r.z),new Vector3(r.x,r.y,e.z),new Vector3(r.x,r.y,r.z)]}function At(t){if(S.setFromObject(t),S.isEmpty())return [];let e=S.getCenter(new Vector3),{min:r,max:n}=S;return [new Vector3(r.x,e.y,e.z),new Vector3(n.x,e.y,e.z),new Vector3(e.x,r.y,e.z),new Vector3(e.x,n.y,e.z),new Vector3(e.x,e.y,r.z),new Vector3(e.x,e.y,n.z)]}function xe(t,e,r){return U.copy(t).project(e),Or(U)?{screen:Dr(U,r),ndc:{x:U.x,y:U.y},ndcZ:U.z,onScreen:xr(U)}:null}function E(t,e,r){t.updateWorldMatrix(true,false),e.updateWorldMatrix(true,false);let n=Et(t),i=xe(n,e,r);if(i&&i.onScreen)return {point:i.screen,strategy:"center",ndc:i.ndc};let a=At(t),o=Pt(a,e,r);if(o)return {point:o.screen,strategy:"face-center",ndc:o.ndc};let c=It(t),u=Pt(c,e,r);if(u)return {point:u.screen,strategy:"corner",ndc:u.ndc};t.getWorldPosition(P);let s=xe(P.clone(),e,r);return s&&s.onScreen?{point:s.screen,strategy:"fallback-origin",ndc:s.ndc}:i?{point:i.screen,strategy:"center",ndc:i.ndc}:null}function Pt(t,e,r){let n=null,i=1/0,a=r.width/2,o=r.height/2;for(let c of t){let u=xe(c,e,r);if(!u||!u.onScreen)continue;let s=u.screen.x-a,l=u.screen.y-o,p=s*s+l*l;p<i&&(i=p,n={screen:u.screen,ndc:u.ndc});}return n}function Mr(t,e){return e.updateWorldMatrix(true,false),t.updateWorldMatrix(true,false),Tt.multiplyMatrices(e.projectionMatrix,e.matrixWorldInverse),Fe.setFromProjectionMatrix(Tt),S.setFromObject(t),S.isEmpty()?(t.getWorldPosition(P),Fe.containsPoint(P)):Fe.intersectsBox(S)}function Ge(t,e,r,n,i){r.getWorldPosition(P),P.project(n);let a=P.z,o=new Vector3(0,0,a).unproject(n),c=new Vector3(2/i.width,0,a).unproject(n),u=new Vector3(0,-2/i.height,a).unproject(n),s=c.sub(o),l=u.sub(o);return new Vector3().addScaledVector(s,t).addScaledVector(l,e)}function wr(t,e,r){t.updateWorldMatrix(true,false),e.updateWorldMatrix(true,false);let n=[],i=[];i.push(Et(t).clone()),i.push(...At(t)),i.push(...It(t));for(let a of i){let o=xe(a,e,r);o&&o.onScreen&&n.push(o.screen);}return n}var Sr=1e3;function J(){return Sr++}function ue(t,e){let r=t.getBoundingClientRect();return {clientX:r.left+e.x,clientY:r.top+e.y}}function C(t,e,r,n){let{clientX:i,clientY:a}=ue(t,e);return {bubbles:true,cancelable:true,composed:true,clientX:i,clientY:a,screenX:i,screenY:a,pointerId:r,pointerType:"mouse",isPrimary:true,button:0,buttons:1,width:1,height:1,pressure:.5,...n}}function Z(t,e){le(()=>{let r=J();t.dispatchEvent(new PointerEvent("pointerdown",C(t,e,r))),t.dispatchEvent(new PointerEvent("pointerup",C(t,e,r,{buttons:0,pressure:0}))),t.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,...ue(t,e),button:0}));});}function de(t,e){let r=J(),n=C(t,e,r,{buttons:0,pressure:0});t.dispatchEvent(new PointerEvent("pointermove",n)),t.dispatchEvent(new PointerEvent("pointerover",n)),t.dispatchEvent(new PointerEvent("pointerenter",{...n,bubbles:false}));}async function Ue(t,e,r,n={}){let{steps:i=10,stepDelayMs:a=0}=n,o=J();le(()=>{t.dispatchEvent(new PointerEvent("pointerdown",C(t,e,o)));});for(let c=1;c<=i;c++){let u=c/i,s={x:e.x+(r.x-e.x)*u,y:e.y+(r.y-e.y)*u};a>0&&await Cr(a),t.dispatchEvent(new PointerEvent("pointermove",C(t,s,o)));}le(()=>{t.dispatchEvent(new PointerEvent("pointerup",C(t,r,o,{buttons:0,pressure:0})));});}function he(t,e){le(()=>{let r=J(),n=ue(t,e);t.dispatchEvent(new PointerEvent("pointerdown",C(t,e,r))),t.dispatchEvent(new PointerEvent("pointerup",C(t,e,r,{buttons:0,pressure:0}))),t.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,...n,button:0,detail:1})),t.dispatchEvent(new PointerEvent("pointerdown",C(t,e,r))),t.dispatchEvent(new PointerEvent("pointerup",C(t,e,r,{buttons:0,pressure:0}))),t.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,...n,button:0,detail:2})),t.dispatchEvent(new MouseEvent("dblclick",{bubbles:!0,cancelable:!0,...n,button:0,detail:2}));});}function pe(t,e){le(()=>{let r=J();t.dispatchEvent(new PointerEvent("pointerdown",C(t,e,r,{button:2,buttons:2}))),t.dispatchEvent(new PointerEvent("pointerup",C(t,e,r,{button:2,buttons:0,pressure:0}))),t.dispatchEvent(new MouseEvent("contextmenu",{bubbles:!0,cancelable:!0,...ue(t,e),button:2}));});}function Ve(t,e,r={}){let{deltaY:n=100,deltaX:i=0,deltaMode:a=0}=r,o=ue(t,e);t.dispatchEvent(new WheelEvent("wheel",{bubbles:true,cancelable:true,...o,deltaX:i,deltaY:n,deltaZ:0,deltaMode:a}));}function Qe(t,e={x:1,y:1}){Z(t,e);}function qe(t){let e=J(),n=C(t,{x:-9999,y:-9999},e,{buttons:0,pressure:0});t.dispatchEvent(new PointerEvent("pointermove",n)),t.dispatchEvent(new PointerEvent("pointerout",n)),t.dispatchEvent(new PointerEvent("pointerleave",{...n,bubbles:false}));}function le(t){let e=Element.prototype.releasePointerCapture;Element.prototype.releasePointerCapture=function(n){try{e.call(this,n);}catch{}};try{return t()}finally{Element.prototype.releasePointerCapture=e;}}function Cr(t){return new Promise(e=>setTimeout(e,t))}var Bt=new Raycaster,Ht=new Vector2;function Tr(t,e){return Ht.set(t.x/e.width*2-1,-(t.y/e.height)*2+1),Ht}function Lt(t){let e=t.userData?.testId;return e?`testId="${e}"`:t.name?`name="${t.name}"`:`uuid="${t.uuid.slice(0,8)}\u2026"`}function zt(t,e){let r=t;for(;r;){if(r===e)return true;r=r.parent;}return false}function Pr(t){let e=t;for(;e;){if(e.isScene)return e;e=e.parent;}return null}function z(t,e,r,n){let i=Pr(e);if(!i)return {hit:false,occluderLabel:"object not in scene"};let a=Tr(t,n);Bt.setFromCamera(a,r);let o=Bt.intersectObjects(i.children,true);if(o.length===0)return {hit:true};let c=o[0].object;return zt(c,e)?{hit:true}:o.find(s=>zt(s.object,e))?{hit:false,occluder:c,occluderLabel:Lt(c)}:{hit:false,occluder:c,occluderLabel:Lt(c)}}function Er(t,e,r,n){let i={hit:false};for(let a of t){let o=z(a,e,r,n);if(o.hit)return o;i=o;}return i}var Oe=null,Me=null,we=null,Se=null;function Ye(t,e,r,n){Oe=t,Me=e,we=r,Se=n;}function kt(){Oe=null,Me=null,we=null,Se=null;}function Ir(){if(!Oe)throw new Error("[react-three-dom] Interaction state not initialized. Is <ThreeDom> mounted?");return Oe}function O(){if(!Me)throw new Error("[react-three-dom] Camera not available. Is <ThreeDom> mounted?");return Me}function _(){if(!we)throw new Error("[react-three-dom] Renderer not available. Is <ThreeDom> mounted?");return we}function M(){if(!Se)throw new Error("[react-three-dom] Canvas size not available. Is <ThreeDom> mounted?");return Se}function R(t){let r=Ir().getObject3D(t);if(!r)throw new Error(`[react-three-dom] Object "${t}" not found. Check that the object has userData.testId="${t}" or uuid="${t}".`);return r}function Xe(t,e={}){let{verify:r=true}=e,n=R(t),i=O(),a=_(),o=M();d("click",`click3D("${t}") \u2014 resolving projection`);let c=E(n,i,o);if(!c)throw new Error(`[react-three-dom] click3D("${t}") failed: object is not visible on screen. It may be behind the camera or outside the viewport.`);let u=a.domElement;Z(u,c.point);let s;return r&&(s=z(c.point,n,i,o),!s.hit&&s.occluderLabel&&console.warn(`[react-three-dom] click3D("${t}") dispatched at (${Math.round(c.point.x)}, ${Math.round(c.point.y)}) but raycast hit ${s.occluderLabel} instead. The object may be occluded.`)),{dispatched:true,raycast:s,screenPoint:c.point,strategy:c.strategy}}function Ke(t,e={}){let{verify:r=true}=e,n=R(t),i=O(),a=_(),o=M(),c=E(n,i,o);if(!c)throw new Error(`[react-three-dom] doubleClick3D("${t}") failed: object is not visible on screen.`);let u=a.domElement;he(u,c.point);let s;return r&&(s=z(c.point,n,i,o),!s.hit&&s.occluderLabel&&console.warn(`[react-three-dom] doubleClick3D("${t}") dispatched at (${Math.round(c.point.x)}, ${Math.round(c.point.y)}) but raycast hit ${s.occluderLabel} instead.`)),{dispatched:true,raycast:s,screenPoint:c.point,strategy:c.strategy}}function Je(t,e={}){let{verify:r=true}=e,n=R(t),i=O(),a=_(),o=M(),c=E(n,i,o);if(!c)throw new Error(`[react-three-dom] contextMenu3D("${t}") failed: object is not visible on screen.`);let u=a.domElement;pe(u,c.point);let s;return r&&(s=z(c.point,n,i,o),!s.hit&&s.occluderLabel&&console.warn(`[react-three-dom] contextMenu3D("${t}") dispatched at (${Math.round(c.point.x)}, ${Math.round(c.point.y)}) but raycast hit ${s.occluderLabel} instead.`)),{dispatched:true,raycast:s,screenPoint:c.point,strategy:c.strategy}}function Ze(t,e={}){let{verify:r=true}=e,n=R(t),i=O(),a=_(),o=M();d("hover",`hover3D("${t}") \u2014 resolving projection`);let c=E(n,i,o);if(!c)throw new Error(`[react-three-dom] hover3D("${t}") failed: object is not visible on screen. It may be behind the camera or outside the viewport.`);let u=a.domElement;de(u,c.point);let s;return r&&(s=z(c.point,n,i,o),!s.hit&&s.occluderLabel&&console.warn(`[react-three-dom] hover3D("${t}") dispatched at (${Math.round(c.point.x)}, ${Math.round(c.point.y)}) but raycast hit ${s.occluderLabel} instead.`)),{dispatched:true,raycast:s,screenPoint:c.point,strategy:c.strategy}}function et(){let t=_();qe(t.domElement);}async function tt(t,e,r={}){let{mode:n="world",...i}=r,a=R(t),o=O(),c=_(),u=M();d("drag",`drag3D("${t}") mode=${n}`,e);let s=E(a,o,u);if(!s)throw new Error(`[react-three-dom] drag3D("${t}") failed: object is not visible on screen. It may be behind the camera or outside the viewport.`);let l=s.point,p;if(n==="screen"&&"dx"in e){let h=e;p={x:l.x+h.dx,y:l.y+h.dy};}else {let h=e,m=new Vector3;a.getWorldPosition(m);let b=m.clone().add(new Vector3(h.x,h.y,h.z));b.project(o),p={x:(b.x+1)/2*u.width,y:(-b.y+1)/2*u.height};}let g=c.domElement;return await Ue(g,l,p,i),{dispatched:true,startPoint:l,endPoint:p,strategy:s.strategy}}function Ar(t,e,r){let n=R(t),i=O(),a=M();return Ge(e,r,n,i,a)}function rt(t,e={}){let r=R(t),n=O(),i=_(),a=M(),o=E(r,n,a);if(!o)throw new Error(`[react-three-dom] wheel3D("${t}") failed: object is not visible on screen.`);let c=i.domElement;return Ve(c,o.point,e),{dispatched:true,screenPoint:o.point,strategy:o.strategy}}function nt(t={}){let r=_().domElement;Qe(r,t.point);}var Br=5e3;function it(t,e,r,n,i){let a=t.getBoundingClientRect(),o=a.left+e.x,c=a.top+e.y;return {bubbles:true,cancelable:true,composed:true,clientX:o,clientY:c,screenX:o,screenY:c,pointerId:r,pointerType:n,isPrimary:true,button:0,buttons:1,width:1,height:1,pressure:e.pressure??.5,...i}}async function ot(t,e={}){if(t.length<2)throw new Error(`[react-three-dom] drawPath requires at least 2 points, got ${t.length}.`);let{stepDelayMs:r=0,pointerType:n="mouse",clickAtEnd:i=false,canvas:a}=e,o=a??_().domElement,c=Br++,u=0;d("draw",`drawPath \u2014 ${t.length} points, delay=${r}ms, pointerType=${n}`);let s=t[0];o.dispatchEvent(new PointerEvent("pointerdown",it(o,s,c,n))),u++;for(let p=1;p<t.length;p++)r>0&&await Wr(r),o.dispatchEvent(new PointerEvent("pointermove",it(o,t[p],c,n))),u++;let l=t[t.length-1];if(o.dispatchEvent(new PointerEvent("pointerup",it(o,l,c,n,{buttons:0,pressure:0}))),u++,i){let p=o.getBoundingClientRect();o.dispatchEvent(new MouseEvent("click",{bubbles:true,cancelable:true,clientX:p.left+l.x,clientY:p.top+l.y,button:0})),u++;}return d("draw",`drawPath complete \u2014 ${u} events dispatched`),{eventCount:u,pointCount:t.length,startPoint:s,endPoint:l}}function Hr(t,e,r=10,n=.5){let i=[],a=r+1;for(let o=0;o<=a;o++){let c=o/a;i.push({x:t.x+(e.x-t.x)*c,y:t.y+(e.y-t.y)*c,pressure:n});}return i}function Lr(t,e,r,n=20,i=.5){let a=[];for(let o=0;o<=n;o++){let c=o/n,u=1-c;a.push({x:u*u*t.x+2*u*c*e.x+c*c*r.x,y:u*u*t.y+2*u*c*e.y+c*c*r.y,pressure:i});}return a}function zr(t,e,r=5,n=.5){let i={x:e.x,y:t.y},a={x:t.x,y:e.y},o=[[t,i],[i,e],[e,a],[a,t]],c=[];for(let[u,s]of o)for(let l=0;l<r;l++){let p=l/r;c.push({x:u.x+(s.x-u.x)*p,y:u.y+(s.y-u.y)*p,pressure:n});}return c.push({x:t.x,y:t.y,pressure:n}),c}function kr(t,e,r,n=36,i=.5){let a=r??e,o=[];for(let c=0;c<=n;c++){let u=c/n*Math.PI*2;o.push({x:t.x+Math.cos(u)*e,y:t.y+Math.sin(u)*a,pressure:i});}return o}function Wr(t){return new Promise(e=>setTimeout(e,t))}var Ce=new Vector3;function je(t,e,r){return Ce.set(t.x,t.y,t.z).project(e),Ce.z>1?null:{x:(Ce.x+1)/2*r.width,y:(-Ce.y+1)/2*r.height}}function Re(t,e={}){let r=O(),n=_(),i=M(),a=n.domElement,o=je(t,r,i);return o?(d("interaction",`clickAtWorld(${t.x}, ${t.y}, ${t.z}) \u2192 screen(${Math.round(o.x)}, ${Math.round(o.y)})`),Z(a,o),{dispatched:true,screenPoint:o,behindCamera:false}):(d("interaction",`clickAtWorld(${t.x}, ${t.y}, ${t.z}) \u2014 behind camera, skipped`),{dispatched:false,screenPoint:{x:0,y:0},behindCamera:true})}function st(t,e={}){let r=O(),n=_(),i=M(),a=n.domElement,o=je(t,r,i);return o?(d("interaction",`doubleClickAtWorld(${t.x}, ${t.y}, ${t.z}) \u2192 screen(${Math.round(o.x)}, ${Math.round(o.y)})`),he(a,o),{dispatched:true,screenPoint:o,behindCamera:false}):(d("interaction",`doubleClickAtWorld(${t.x}, ${t.y}, ${t.z}) \u2014 behind camera, skipped`),{dispatched:false,screenPoint:{x:0,y:0},behindCamera:true})}function at(t,e={}){let r=O(),n=_(),i=M(),a=n.domElement,o=je(t,r,i);return o?(d("interaction",`contextMenuAtWorld(${t.x}, ${t.y}, ${t.z}) \u2192 screen(${Math.round(o.x)}, ${Math.round(o.y)})`),pe(a,o),{dispatched:true,screenPoint:o,behindCamera:false}):(d("interaction",`contextMenuAtWorld(${t.x}, ${t.y}, ${t.z}) \u2014 behind camera, skipped`),{dispatched:false,screenPoint:{x:0,y:0},behindCamera:true})}function ct(t,e={}){let r=O(),n=_(),i=M(),a=n.domElement,o=je(t,r,i);return o?(d("interaction",`hoverAtWorld(${t.x}, ${t.y}, ${t.z}) \u2192 screen(${Math.round(o.x)}, ${Math.round(o.y)})`),de(a,o),{dispatched:true,screenPoint:o,behindCamera:false}):(d("interaction",`hoverAtWorld(${t.x}, ${t.y}, ${t.z}) \u2014 behind camera, skipped`),{dispatched:false,screenPoint:{x:0,y:0},behindCamera:true})}async function lt(t,e={}){let{delayMs:r=50,...n}=e,i=[];for(let a=0;a<t.length;a++)i.push(Re(t[a],n)),r>0&&a<t.length-1&&await new Promise(o=>setTimeout(o,r));return i}var me=class{constructor(){this._selected=[];this._listeners=[];}select(e){this._selected.length===1&&this._selected[0]===e||(this._selected=[e],this._notify());}addToSelection(e){this._selected.includes(e)||(this._selected.push(e),this._notify());}removeFromSelection(e){let r=this._selected.indexOf(e);r!==-1&&(this._selected.splice(r,1),this._notify());}toggleSelection(e){this._selected.includes(e)?this.removeFromSelection(e):this.addToSelection(e);}clearSelection(){this._selected.length!==0&&(this._selected=[],this._notify());}getSelected(){return this._selected}getPrimary(){return this._selected[0]??null}isSelected(e){return this._selected.includes(e)}get count(){return this._selected.length}subscribe(e){return this._listeners.push(e),()=>{let r=this._listeners.indexOf(e);r!==-1&&this._listeners.splice(r,1);}}_notify(){let e=[...this._selected];for(let r of this._listeners)r(e);}dispose(){this._selected=[],this._listeners=[];}};var Qr=7317724,qr=.66,Yr=7317724,Xr=.75,$t=7317724,Kr=.3,B=new Box3;function Nt(t){return t.isMesh===true||t.isLine===true||t.isPoints===true}function ut(t){if(Nt(t))return [t];let e=[];return t.traverse(r=>{r!==t&&(r.userData?.__r3fdom_internal||Nt(r)&&e.push(r));}),e}function Yt(t){t.userData.__r3fdom_internal=true,t.raycast=()=>{},t.traverse(e=>{e.userData.__r3fdom_internal=true,e.raycast=()=>{};});}function Ft(t,e){t.updateWorldMatrix(true,false),t.matrixWorld.decompose(e.position,e.quaternion,e.scale);}function Jr(t,e){e.matrixAutoUpdate=false,e.updateMatrixWorld=r=>{t.updateWorldMatrix(true,false),e.matrixWorld.copy(t.matrixWorld);for(let n of e.children)n.updateMatrixWorld(r);};}function Gt(t,e,r){let n=t.geometry;if(!n)return null;let i=new Object3D,a=[],o=new MeshBasicMaterial({color:e,transparent:true,opacity:r,depthTest:false,depthWrite:false,side:DoubleSide});a.push(o);let c=new Mesh(n,o);return c.scale.setScalar(1.005),c.raycast=()=>{},i.add(c),t.updateWorldMatrix(true,false),t.matrixWorld.decompose(i.position,i.quaternion,i.scale),Yt(i),Jr(t,i),{root:i,disposables:a}}function Zr(t){B.makeEmpty();let e=ut(t);if(e.length===0)return null;for(let g of e){g.updateWorldMatrix(true,false);let h=g.geometry;if(h&&(h.boundingBox||h.computeBoundingBox(),h.boundingBox)){let m=h.boundingBox.clone();m.applyMatrix4(g.matrixWorld),B.union(m);}}if(B.isEmpty())return null;let r=B.getSize(new Vector3),n=B.getCenter(new Vector3),i=[],a=new BoxGeometry(r.x,r.y,r.z);i.push(a);let o=new MeshBasicMaterial({color:$t,transparent:true,opacity:Kr,depthTest:false,depthWrite:false,side:DoubleSide});i.push(o);let c=new Mesh(a,o);c.raycast=()=>{};let u=new EdgesGeometry(a);i.push(u);let s=new LineBasicMaterial({color:$t,transparent:true,opacity:.5,depthTest:false,depthWrite:false});i.push(s);let l=new LineSegments(u,s);l.raycast=()=>{};let p=new Object3D;return p.add(c),p.add(l),p.position.copy(n),p.renderOrder=998,Yt(p),p.matrixAutoUpdate=false,p.updateMatrixWorld=g=>{B.makeEmpty();for(let h of e){h.updateWorldMatrix(true,false);let m=h.geometry;if(m&&(m.boundingBox||m.computeBoundingBox(),m.boundingBox)){let b=m.boundingBox.clone();b.applyMatrix4(h.matrixWorld),B.union(b);}}if(!B.isEmpty()){let h=B.getSize(new Vector3),m=B.getCenter(new Vector3);p.position.copy(m),p.scale.set(h.x/r.x||1,h.y/r.y||1,h.z/r.z||1);}p.updateMatrix(),p.matrixWorld.copy(p.matrix);for(let h of p.children)h.updateMatrixWorld(g);},{root:p,disposables:i}}var fe=class{constructor(e={}){this._scene=null;this._unsubscribe=null;this._hoverEntries=[];this._hoverTarget=null;this._selectedEntries=new Map;this._hoverPollId=null;this._lastHoveredUuid=null;this._store=null;}attach(e,r,n,i,a){this.detach(),this._scene=e,this._store=a,this._unsubscribe=r.subscribe(o=>{this._syncSelectionHighlights(o);}),this._syncSelectionHighlights([...r.getSelected()]),this._startHoverPolling();}detach(){this._unsubscribe&&(this._unsubscribe(),this._unsubscribe=null),this._stopHoverPolling(),this.clearHoverHighlight(),this._clearAllSelectionHighlights(),this._scene=null,this._store=null;}update(){for(let e of this._hoverEntries)Ft(e.source,e.group.root);for(let e of this._selectedEntries.values())for(let r of e.meshGroups){let n=r.root.userData.__r3fdom_source;n&&Ft(n,r.root);}}showHoverHighlight(e){if(e===this._hoverTarget||(this._clearHoverVisuals(),!this._scene))return;this._hoverTarget=e;let r=ut(e);for(let n of r){let i=Gt(n,Qr,qr);i&&(i.root.renderOrder=997,this._scene.add(i.root),this._hoverEntries.push({source:n,group:i}));}}clearHoverHighlight(){this._clearHoverVisuals(),this._lastHoveredUuid=null;}_clearHoverVisuals(){for(let e of this._hoverEntries)this._disposeHighlightGroup(e.group);this._hoverEntries=[],this._hoverTarget=null;}isHighlighted(e){return this._selectedEntries.has(e)}clearAll(){this.clearHoverHighlight(),this._clearAllSelectionHighlights();}_syncSelectionHighlights(e){if(!this._scene)return;let r=new Set(e);for(let[n]of this._selectedEntries)r.has(n)||this._removeSelectionHighlight(n);for(let n of e)this._selectedEntries.has(n)||this._addSelectionHighlight(n);}_addSelectionHighlight(e){if(!this._scene)return;let r=ut(e),n=[];for(let a of r){let o=Gt(a,Yr,Xr);o&&(o.root.userData.__r3fdom_source=a,o.root.renderOrder=999,this._scene.add(o.root),n.push(o));}let i=null;r.length>1&&e.type!=="Group"&&(i=Zr(e),i&&this._scene.add(i.root)),(n.length>0||i)&&this._selectedEntries.set(e,{source:e,meshGroups:n,bboxGroup:i});}_removeSelectionHighlight(e){let r=this._selectedEntries.get(e);if(r){for(let n of r.meshGroups)this._disposeHighlightGroup(n);r.bboxGroup&&this._disposeHighlightGroup(r.bboxGroup),this._selectedEntries.delete(e);}}_clearAllSelectionHighlights(){for(let e of this._selectedEntries.values()){for(let r of e.meshGroups)this._disposeHighlightGroup(r);e.bboxGroup&&this._disposeHighlightGroup(e.bboxGroup);}this._selectedEntries.clear();}_startHoverPolling(){this._hoverPollId=setInterval(()=>{this._pollDevToolsHover();},100);}_stopHoverPolling(){this._hoverPollId&&(clearInterval(this._hoverPollId),this._hoverPollId=null);}_pollDevToolsHover(){if(this._store)try{let r=window.__r3fdom_hovered__?.getAttribute?.("data-uuid")??null;if(r===this._lastHoveredUuid)return;if(this._lastHoveredUuid=r,!r){this._clearHoverVisuals();return}let n=this._store.getObject3D(r);n?this.showHoverHighlight(n):this._clearHoverVisuals();}catch{}}_disposeHighlightGroup(e){e.root.removeFromParent();for(let r of e.disposables)r.dispose();}dispose(){this.detach();}};function en(t){let e=t.parent;return !e||e.type!=="Group"?false:e.children.find(n=>G(n.type)==="three-mesh")===t}function Pe(t,e){let r=t(e);return r?G(r.type)!=="three-mesh"?e:en(r)&&r.parent?r.parent.uuid:e:null}var tn=50,rn=300,ge=class{constructor(e){this._active=false;this._lastRaycastTime=0;this._hoveredObject=null;this._hoverRevealTimer=null;this._overlay=null;this._boundPointerMove=null;this._boundPointerDown=null;this._boundContextMenu=null;this._camera=e.camera,this._renderer=e.renderer,this._selectionManager=e.selectionManager,this._highlighter=e.highlighter,this._raycastAccelerator=e.raycastAccelerator,this._mirror=e.mirror,this._store=e.store;}get active(){return this._active}updateCamera(e){this._camera=e;}enable(){if(this._active)return;this._active=true;let r=this._renderer.domElement.parentElement;if(!r)return;let n=document.createElement("div");n.dataset.r3fdomInspect="true",n.style.cssText=["position:absolute","inset:0","z-index:999999","cursor:crosshair","background:transparent"].join(";"),getComputedStyle(r).position==="static"&&(r.style.position="relative"),this._boundPointerMove=this._onPointerMove.bind(this),this._boundPointerDown=this._onPointerDown.bind(this),this._boundContextMenu=a=>a.preventDefault(),n.addEventListener("pointermove",this._boundPointerMove),n.addEventListener("pointerdown",this._boundPointerDown),n.addEventListener("contextmenu",this._boundContextMenu),r.appendChild(n),this._overlay=n,d("inspect","Inspect mode enabled \u2014 hover to highlight, click to select");}disable(){this._active&&(this._active=false,this._overlay&&(this._boundPointerMove&&this._overlay.removeEventListener("pointermove",this._boundPointerMove),this._boundPointerDown&&this._overlay.removeEventListener("pointerdown",this._boundPointerDown),this._boundContextMenu&&this._overlay.removeEventListener("contextmenu",this._boundContextMenu),this._overlay.remove(),this._overlay=null),this._boundPointerMove=null,this._boundPointerDown=null,this._boundContextMenu=null,this._hoveredObject=null,this._cancelHoverReveal(),this._highlighter.clearHoverHighlight(),window.__r3fdom_selected_element__=null,d("inspect","InspectController disabled"));}dispose(){this.disable();}_raycastFromEvent(e){return this._raycastAccelerator.raycastAtMouse(e,this._camera,this._renderer.domElement)}_resolveTarget(e){let r=Pe(n=>this._store.getObject3D(n),e.uuid);return r?this._store.getObject3D(r)??e:e}_onPointerMove(e){e.stopPropagation(),e.preventDefault();let r=performance.now();if(r-this._lastRaycastTime<tn)return;this._lastRaycastTime=r;let n=this._raycastFromEvent(e);if(!n){this._hoveredObject&&(this._hoveredObject=null,this._highlighter.clearHoverHighlight(),this._cancelHoverReveal());return}n!==this._hoveredObject&&(this._hoveredObject=n,this._highlighter.showHoverHighlight(n),this._scheduleHoverReveal(n));}_scheduleHoverReveal(e){this._cancelHoverReveal(),this._hoverRevealTimer=setTimeout(()=>{let r=this._mirror.getOrMaterialize(e.uuid);r&&(window.__r3fdom_selected_element__=r);},rn);}_cancelHoverReveal(){this._hoverRevealTimer&&(clearTimeout(this._hoverRevealTimer),this._hoverRevealTimer=null);}_onPointerDown(e){e.stopPropagation(),e.preventDefault();let r=this._raycastFromEvent(e);if(!r){this._selectionManager.clearSelection();return}let n=this._resolveTarget(r);if(!n)return;this._selectionManager.select(n);let i=this._mirror.getOrMaterialize(n.uuid);i&&(window.__r3fdom_selected_element__=i),d("inspect","Object selected via canvas click",{uuid:n.uuid.slice(0,8),name:n.name||"(unnamed)",type:n.type});}};var dt=false,Ee=null;function an(){return dt?Promise.resolve():Ee||(Ee=import('three-mesh-bvh').then(t=>{BufferGeometry.prototype.computeBoundsTree=t.computeBoundsTree,BufferGeometry.prototype.disposeBoundsTree=t.disposeBoundsTree,Mesh.prototype.raycast=t.acceleratedRaycast,dt=true,d("raycast","three-mesh-bvh patched into Three.js");}),Ee)}var ee=new Raycaster,ye=new Vector2;function cn(t){if(t.userData?.__r3fdom_internal||!t.visible||!(t.isMesh===true||t.isLine===true||t.isPoints===true))return false;let r=t.geometry;if(r){let n=r.getAttribute("position");if(n&&!n.array)return false}return true}var be=class{constructor(e){this._targets=[];this._dirty=true;this._unsubscribe=null;this._bvhBuiltFor=new WeakSet;this._store=e,an(),this._unsubscribe=e.subscribe(()=>{this._dirty=true;});}markDirty(){this._dirty=true;}_rebuild(){this._dirty=false;let e=this._store.getFlatList(),r=[];for(let n=0;n<e.length;n++){let i=e[n];cn(i)&&r.push(i);}if(this._targets=r,dt){let n=50;for(let i=0;i<r.length&&n>0;i++){let a=r[i];if(a.isMesh){let o=a.geometry;o&&!this._bvhBuiltFor.has(o)&&!o.boundsTree&&(this._ensureBVH(a),n--);}}n===0&&(this._dirty=true);}}_ensureBVH(e){if(!e.isMesh)return;let r=e.geometry;if(!r||this._bvhBuiltFor.has(r))return;let n=r.getAttribute("position");if(!(!n||!n.array)){if(r.boundsTree){this._bvhBuiltFor.add(r);return}try{r.computeBoundsTree({indirect:!0}),this._bvhBuiltFor.add(r);}catch{d("raycast","BVH build failed for geometry, will retry next rebuild");}}}raycastAtMouse(e,r,n){this._dirty&&this._rebuild();let i=n.getBoundingClientRect();ye.x=(e.clientX-i.left)/i.width*2-1,ye.y=-((e.clientY-i.top)/i.height)*2+1,ee.setFromCamera(ye,r),ee.firstHitOnly=true;let a=ee.intersectObjects(this._targets,false);ee.firstHitOnly=false;for(let o of a)if(!o.object.userData?.__r3fdom_internal)return o.object;return null}raycastAtNdc(e,r,n){return this._dirty&&this._rebuild(),ye.set(e,r),ee.setFromCamera(ye,n),ee.intersectObjects(this._targets,false)}get targetCount(){return this._dirty&&this._rebuild(),this._targets.length}dispose(){this._unsubscribe&&(this._unsubscribe(),this._unsubscribe=null),this._targets=[];}};var _e=new Map,Ae=new Map,pt=new Map,Be=new Map,te=new Map,mt=new Map,ft=new Map;function jt(t,e){if(ft.get(t)==="manual")return false;let n=mt.get(t);return n?n(e):true}function De(t,e,r){let n=[],i=t.parent;for(;i&&!e.has(i);)n.push(i),i=i.parent;for(let a=n.length-1;a>=0;a--){let o=n[a];e.register(o),r.onObjectAdded(o),r.materialize(o.uuid);}}function hn(t=""){return _e.get(t)??null}function pn(t=""){return Ae.get(t)??null}function mn(t=""){return pt.get(t)??null}function fn(t=""){return Be.get(t)??null}function gn(t=""){return te.get(t)??null}function yn(){return Array.from(_e.keys())}function bn(t,e,r,n,i,a,o,c=true){let u={_ready:true,canvasId:o,getByTestId:s=>t.getByTestId(s),getByUuid:s=>t.getByUuid(s),getByName:s=>t.getByName(s),getChildren:s=>t.getChildren(s),getParent:s=>t.getParent(s),getCount:()=>t.getCount(),getByType:s=>t.getByType(s),getByGeometryType:s=>t.getByGeometryType(s),getByMaterialType:s=>t.getByMaterialType(s),getByUserData:(s,l)=>t.getByUserData(s,l),getCountByType:s=>t.getCountByType(s),getObjects:s=>{let l=t.getObjects(s),p={};for(let[g,h]of l)p[g]=h;return p},snapshot:()=>Ne(t),inspect:(s,l)=>t.inspect(s,l),click:s=>{Xe(s);},doubleClick:s=>{Ke(s);},contextMenu:s=>{Je(s);},hover:s=>{Ze(s);},unhover:()=>{et();},drag:async(s,l)=>{await tt(s,l);},wheel:(s,l)=>{rt(s,l);},pointerMiss:()=>{nt();},drawPath:async(s,l)=>{let p=await ot(s,l);return {eventCount:p.eventCount,pointCount:p.pointCount}},clickAtWorld:(s,l)=>Re(s,l),doubleClickAtWorld:(s,l)=>st(s,l),contextMenuAtWorld:(s,l)=>at(s,l),hoverAtWorld:(s,l)=>ct(s,l),clickAtWorldSequence:(s,l)=>lt(s,l),select:s=>{let l=t.getObject3D(s);l&&n&&n.select(l);},clearSelection:()=>{n?.clearSelection();},getSelection:()=>n?n.getSelected().map(s=>s.uuid):[],getObject3D:s=>t.getObject3D(s),getSelectionDisplayTarget:s=>Pe(l=>t.getObject3D(l),s)??s,setInspectMode:s=>{d("inspect","Global API setInspectMode called",{on:s});let l=i??te.get(o??"");s?(l?.enable(),a?.setInspectMode(true)):(l?.disable(),a?.setInspectMode(false));},getInspectMode:()=>(i??te.get(o??""))?.active??false,sweepOrphans:()=>t.sweepOrphans(),getDiagnostics:()=>({version:$,ready:true,objectCount:t.getCount(),meshCount:t.getCountByType("Mesh"),groupCount:t.getCountByType("Group"),lightCount:t.getCountByType("DirectionalLight")+t.getCountByType("PointLight")+t.getCountByType("SpotLight")+t.getCountByType("AmbientLight")+t.getCountByType("HemisphereLight"),cameraCount:t.getCountByType("PerspectiveCamera")+t.getCountByType("OrthographicCamera"),materializedDomNodes:a?.getMaterializedCount()??0,maxDomNodes:a?.getMaxNodes()??0,canvasWidth:e.domElement.width,canvasHeight:e.domElement.height,webglRenderer:(()=>{try{let s=e.getContext(),l=s.getExtension("WEBGL_debug_renderer_info");return l?s.getParameter(l.UNMASKED_RENDERER_WEBGL):"unknown"}catch{return "unknown"}})(),dirtyQueueSize:t.getDirtyCount()}),getCameraState:()=>{let s=r.current,l=new Vector3(0,0,-1).applyQuaternion(s.quaternion),p=[s.position.x+l.x*100,s.position.y+l.y*100,s.position.z+l.z*100],g={type:s.type,position:[s.position.x,s.position.y,s.position.z],rotation:[s.rotation.x,s.rotation.y,s.rotation.z],target:p,near:s.near,far:s.far,zoom:s.zoom};if(s.type==="PerspectiveCamera"){let h=s;g.fov=h.fov,g.aspect=h.aspect;}else if(s.type==="OrthographicCamera"){let h=s;g.left=h.left,g.right=h.right,g.top=h.top,g.bottom=h.bottom;}return g},r3fRegister:s=>{if(t.has(s))return;if(!t.isInTrackedScene(s)){console.warn(`[react-three-dom] r3fRegister: object "${s.userData?.testId||s.name||s.uuid.slice(0,8)}" is not in a tracked scene. Add it to the scene graph first.`);return}s.userData.__r3fdom_manual=true,De(s,t,a);let l=0;s.traverse(p=>{t.has(p)||(t.register(p),a?.onObjectAdded(p),a?.materialize(p.uuid),l++);}),d("bridge",`r3fRegister: "${s.userData?.testId||s.name||s.uuid.slice(0,8)}" (${s.type}) \u2014 ${l} objects`);},r3fUnregister:s=>{if(t.has(s)){if(!s.userData?.__r3fdom_manual){d("bridge",`r3fUnregister skipped \u2014 "${s.userData?.testId||s.name||s.uuid.slice(0,8)}" was auto-registered`);return}delete s.userData.__r3fdom_manual,a?.onObjectRemoved(s),s.traverse(l=>t.unregister(l)),d("bridge",`r3fUnregister: "${s.userData?.testId||s.name||s.uuid.slice(0,8)}" (${s.type})`);}},fuzzyFind:(s,l=5)=>{let p=s.toLowerCase(),g=[];for(let h of t.getFlatList()){if(g.length>=l)break;let m=t.getMetadata(h);if(!m)continue;let b=m.testId?.toLowerCase()??"",I=m.name?.toLowerCase()??"";(b.includes(p)||I.includes(p)||m.uuid.startsWith(p))&&g.push(m);}return g},version:$};c&&(window.__R3F_DOM__=u),o&&(window.__R3F_DOM_INSTANCES__||(window.__R3F_DOM_INSTANCES__={}),window.__R3F_DOM_INSTANCES__[o]&&console.warn(`[react-three-dom] Duplicate canvasId "${o}" \u2014 the previous bridge instance will be overwritten. Each <ThreeDom> must have a unique canvasId.`),window.__R3F_DOM_INSTANCES__[o]=u);}function Kt(t,e){d("bridge","removeGlobalAPI called (deferred)");let r=n=>{e&&window.__R3F_DOM_INSTANCES__&&(!n||window.__R3F_DOM_INSTANCES__[e]===n)&&(delete window.__R3F_DOM_INSTANCES__[e],Object.keys(window.__R3F_DOM_INSTANCES__).length===0&&delete window.__R3F_DOM_INSTANCES__);};if(t!==void 0){let n=t;queueMicrotask(()=>{window.__R3F_DOM__===n?(delete window.__R3F_DOM__,d("bridge","Global API removed")):d("bridge","Global API not removed \u2014 replaced by new instance (Strict Mode remount)"),r(n);});}else delete window.__R3F_DOM__,r(),d("bridge","Global API removed (immediate)");}function Jt(t,e){return {_ready:false,_error:t,canvasId:e,getByTestId:()=>null,getByUuid:()=>null,getByName:()=>[],getChildren:()=>[],getParent:()=>null,getCount:()=>0,getByType:()=>[],getByGeometryType:()=>[],getByMaterialType:()=>[],getByUserData:()=>[],getCountByType:()=>0,getObjects:r=>{let n={};for(let i of r)n[i]=null;return n},snapshot:()=>({timestamp:0,objectCount:0,tree:{uuid:"",name:"",type:"Scene",visible:true,position:[0,0,0],rotation:[0,0,0],scale:[1,1,1],children:[]}}),inspect:()=>null,click:()=>{},doubleClick:()=>{},contextMenu:()=>{},hover:()=>{},unhover:()=>{},drag:async()=>{},wheel:()=>{},pointerMiss:()=>{},drawPath:async()=>({eventCount:0,pointCount:0}),clickAtWorld:()=>({dispatched:false,screenPoint:{x:0,y:0},behindCamera:false}),doubleClickAtWorld:()=>({dispatched:false,screenPoint:{x:0,y:0},behindCamera:false}),contextMenuAtWorld:()=>({dispatched:false,screenPoint:{x:0,y:0},behindCamera:false}),hoverAtWorld:()=>({dispatched:false,screenPoint:{x:0,y:0},behindCamera:false}),clickAtWorldSequence:async()=>[],select:()=>{},clearSelection:()=>{},getSelection:()=>[],getObject3D:()=>null,getSelectionDisplayTarget:r=>r,setInspectMode:()=>{},getInspectMode:()=>false,r3fRegister:()=>{},r3fUnregister:()=>{},sweepOrphans:()=>0,getDiagnostics:()=>({version:$,ready:false,error:t??void 0,objectCount:0,meshCount:0,groupCount:0,lightCount:0,cameraCount:0,materializedDomNodes:0,maxDomNodes:0,canvasWidth:0,canvasHeight:0,webglRenderer:"unavailable",dirtyQueueSize:0}),getCameraState:()=>({type:"unknown",position:[0,0,0],rotation:[0,0,0],target:[0,0,-100],near:.1,far:1e3,zoom:1}),fuzzyFind:()=>[],version:$}}function _n({canvasId:t,primary:e,root:r="#three-dom-root",mode:n="auto",filter:i,batchSize:a=500,syncBudgetMs:o=.5,maxDomNodes:c=2e3,initialDepth:u=3,enabled:s=true,debug:l=false,inspect:p=false}={}){let g=e??t===void 0,h=t??"",m=useThree(x=>x.scene),b=useThree(x=>x.camera),I=useThree(x=>x.gl),gt=useThree(x=>x.size),He=useRef(0),yt=useRef(0),bt=useRef(b);return bt.current=b,useEffect(()=>{if(!s)return;l&&ne(true),d("setup","ThreeDom effect started",{enabled:s,debug:l,root:r,maxDomNodes:c});let x=I.domElement;x.setAttribute("data-r3f-canvas",t??"true");let V=x.parentElement,v=null,Q=false;typeof r=="string"?v=document.querySelector(r):v=r,v||(v=document.createElement("div"),v.id=typeof r=="string"?r.replace(/^#/,""):"three-dom-root",Q=true),V.style.position=V.style.position||"relative",V.appendChild(v),v.style.cssText="width:0;height:0;overflow:hidden;pointer-events:none;opacity:0;";let f=null,D=null,q=null,re=null,L=null,H=null,j=null,A=null,k;try{let T=I.getContext();if(!T||T.isContextLost?.()){let w="WebGL context not available. For headless Chromium, add --enable-webgl and optionally --use-gl=angle --use-angle=swiftshader-webgl to launch args.",W=Jt(w,t);return g&&(window.__R3F_DOM__=W),t&&(window.__R3F_DOM_INSTANCES__||(window.__R3F_DOM_INSTANCES__={}),window.__R3F_DOM_INSTANCES__[t]=W),d("setup",w),()=>{Kt(W,t),x.removeAttribute("data-r3f-canvas"),Q&&v?.parentNode&&v.parentNode.removeChild(v),l&&ne(!1);}}if(f=new ie,D=new oe(f,c),D.setRoot(v),d("setup","Store and mirror created"),ze(f),ft.set(h,n),mt.set(h,i??null),q=$e(f,D,h),Ye(f,b,I,gt),d("setup",`Object3D patched (mode=${n}), interaction state set`),n==="auto"){if(i?(f.addTrackedRoot(m),f.register(m),D.onObjectAdded(m),m.traverse(w=>{w!==m&&i(w)&&(De(w,f,D),f.register(w),D.onObjectAdded(w));})):f.registerTree(m),!f.has(b)){let w=f.register(b);w.parentUuid=m.uuid,D.materialize(b.uuid);}D.materializeSubtree(m.uuid,u),i||(re=f.registerTreeAsync(m));}else f.addTrackedRoot(m),f.register(m),D.onObjectAdded(m);if(d("setup",`Scene registered (mode=${n}): ${f.getCount()} objects`),L=new me,H=new fe,H.attach(m,L,b,I,f),j=new be(f),A=new ge({camera:b,renderer:I,selectionManager:L,highlighter:H,raycastAccelerator:j,mirror:D,store:f}),pt.set(h,L),Be.set(h,H),te.set(h,A),bn(f,I,bt,L,A,D,t,g),d("bridge",`exposeGlobalAPI called \u2014 bridge is live, _ready=true${t?`, canvasId="${t}"`:""}`),k=t?window.__R3F_DOM_INSTANCES__?.[t]:window.__R3F_DOM__,_e.set(h,f),Ae.set(h,D),p&&A.enable(),l){let w=p?"on":"off";console.log("%c[react-three-dom]%c v"+$+" \u2014 "+f.getCount()+" objects \xB7 inspect: "+w+`
2
+ %c\u2192%c Toggle inspect: %c__R3F_DOM__.setInspectMode(true|false)%c
3
+ %c\u2192%c Hover 3D objects to highlight + auto-reveal in Elements tab`,"background:#1a73e8;color:#fff;padding:2px 6px;border-radius:3px;font-weight:bold","color:inherit","color:#1a73e8","color:inherit","color:#e8a317;font-family:monospace","color:inherit","color:#1a73e8","color:inherit");}}catch(T){let w=T instanceof Error?T.message:String(T);d("setup","ThreeDom setup failed",T),console.error("[react-three-dom] Setup failed:",T);let W=Jt(w,t);g&&(window.__R3F_DOM__=W),t&&(window.__R3F_DOM_INSTANCES__||(window.__R3F_DOM_INSTANCES__={}),window.__R3F_DOM_INSTANCES__[t]=W),k=W;}return ()=>{d("setup","ThreeDom cleanup started"),re&&re(),A&&A.dispose(),j&&j.dispose(),H&&H.dispose(),q&&q(),Kt(k,t),kt(),L&&L.dispose(),D&&D.dispose(),f&&f.dispose(),x.removeAttribute("data-r3f-canvas"),Q&&v?.parentNode&&v.parentNode.removeChild(v),_e.delete(h),Ae.delete(h),pt.delete(h),Be.delete(h),te.delete(h),ft.delete(h),mt.delete(h),l&&ne(false),d("setup","ThreeDom cleanup complete");}},[m,b,I,s,r,c,u,l,p,t,g,h]),useFrame(()=>{let x=_e.get(h),V=Ae.get(h),v=Be.get(h),Q=te.get(h);if(!(!s||!x||!V))try{Ye(x,b,I,gt),Q&&Q.updateCamera(b);let f=x,D=V,q=performance.now(),re=f.drainDirtyQueue();for(let j of re)f.update(j),D.syncAttributes(j);if(o-(performance.now()-q)>.1){let j=f.getFlatList();if(j.length>0){let A=Math.min(He.current+a,j.length);for(let k=He.current;k<A&&!(performance.now()-q>o);k++){let T=j[k];f.update(T)&&D.syncAttributes(T);}He.current=A>=j.length?0:A;}}v&&v.update();let H=performance.now();H-yt.current>3e4&&(yt.current=H,f.sweepOrphans());}catch(f){d("sync","Per-frame sync error",f);}}),null}function tr(t){return t?window.__R3F_DOM_INSTANCES__?.[t]:window.__R3F_DOM__}function xn(t,e){let r=useRef(null),n=useRef(e);n.current=e;let i=useCallback(o=>{let c=tr(n.current);return c?(c.r3fRegister(o),r.current=o,true):false},[]),a=useCallback(()=>{if(!r.current)return;tr(n.current)?.r3fUnregister(r.current),r.current=null;},[]);useEffect(()=>{let o=t.current;return o&&i(o),()=>a()},[t,i,a]),useFrame(()=>{let o=t.current;o!==r.current&&(a(),o&&i(o));});}export{oe as DomMirror,fe as Highlighter,ge as InspectController,pr as MANAGED_ATTRIBUTES,ie as ObjectStore,be as RaycastAccelerator,me as SelectionManager,Ot as TAG_MAP,_n as ThreeDom,ve as ThreeElement,Y as applyAttributes,kr as circlePath,Xe as click3D,Re as clickAtWorld,lt as clickAtWorldSequence,wt as computeAttributes,Je as contextMenu3D,at as contextMenuAtWorld,yr as createFlatSnapshot,Ne as createSnapshot,Lr as curvePath,Z as dispatchClick,pe as dispatchContextMenu,he as dispatchDoubleClick,Ue as dispatchDrag,de as dispatchHover,Qe as dispatchPointerMiss,qe as dispatchUnhover,Ve as dispatchWheel,Ke as doubleClick3D,st as doubleClickAtWorld,tt as drag3D,ot as drawPath,ne as enableDebug,ze as ensureCustomElements,yn as getCanvasIds,fn as getHighlighter,gn as getInspectController,pn as getMirror,mn as getSelectionManager,hn as getStore,G as getTagForType,Ze as hover3D,ct as hoverAtWorld,rr as isDebugEnabled,Mr as isInFrustum,fr as isPatched,Hr as linePath,$e as patchObject3D,nt as pointerMiss3D,Ar as previewDragWorldDelta,wr as projectAllSamplePoints,E as projectToScreen,d as r3fLog,zr as rectPath,R as resolveObject,Ct as restoreObject3D,Ge as screenDeltaToWorld,et as unhover3D,xn as useR3FRegister,z as verifyRaycastHit,Er as verifyRaycastHitMultiPoint,$ as version,rt as wheel3D};//# sourceMappingURL=index.js.map
3988
4
  //# sourceMappingURL=index.js.map