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