@pascal-app/viewer 1.0.0-beta.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/dist/capture/asset-url.d.ts +2 -0
  4. package/dist/capture/asset-url.d.ts.map +1 -0
  5. package/dist/capture/asset-url.js +13 -0
  6. package/dist/capture/capture-runtime.d.ts +43 -0
  7. package/dist/capture/capture-runtime.d.ts.map +1 -0
  8. package/dist/capture/capture-runtime.js +228 -0
  9. package/dist/capture/frame.d.ts +4 -0
  10. package/dist/capture/frame.d.ts.map +1 -0
  11. package/dist/capture/frame.js +21 -0
  12. package/dist/capture/index.d.ts +12 -0
  13. package/dist/capture/index.d.ts.map +1 -0
  14. package/dist/capture/index.js +11 -0
  15. package/dist/capture/layer-visibility.d.ts +5 -0
  16. package/dist/capture/layer-visibility.d.ts.map +1 -0
  17. package/dist/capture/layer-visibility.js +11 -0
  18. package/dist/capture/layers/clay-matcap.d.ts +3 -0
  19. package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
  20. package/dist/capture/layers/clay-matcap.js +26 -0
  21. package/dist/capture/layers/device-motion-layer.d.ts +7 -0
  22. package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
  23. package/dist/capture/layers/device-motion-layer.js +78 -0
  24. package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
  25. package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
  26. package/dist/capture/layers/point-cloud-layer.js +106 -0
  27. package/dist/capture/layers/room-model-layer.d.ts +9 -0
  28. package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
  29. package/dist/capture/layers/room-model-layer.js +71 -0
  30. package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
  31. package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
  32. package/dist/capture/layers/surface-mesh-data.js +74 -0
  33. package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
  34. package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
  35. package/dist/capture/layers/surface-mesh-layer.js +16 -0
  36. package/dist/capture/preview.d.ts +3 -0
  37. package/dist/capture/preview.d.ts.map +1 -0
  38. package/dist/capture/preview.js +2 -0
  39. package/dist/capture/source-state.d.ts +22 -0
  40. package/dist/capture/source-state.d.ts.map +1 -0
  41. package/dist/capture/source-state.js +182 -0
  42. package/dist/capture/stream-rendering.d.ts +14 -0
  43. package/dist/capture/stream-rendering.d.ts.map +1 -0
  44. package/dist/capture/stream-rendering.js +69 -0
  45. package/dist/capture/trajectory.d.ts +20 -0
  46. package/dist/capture/trajectory.d.ts.map +1 -0
  47. package/dist/capture/trajectory.js +77 -0
  48. package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
  49. package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
  50. package/dist/components/viewer/batched-mesh-spike.js +64 -0
  51. package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
  52. package/dist/components/viewer/frame-limiter.js +4 -3
  53. package/dist/components/viewer/index.d.ts.map +1 -1
  54. package/dist/components/viewer/index.js +70 -62
  55. package/dist/components/viewer/lights.d.ts.map +1 -1
  56. package/dist/components/viewer/lights.js +123 -89
  57. package/dist/components/viewer/perf-monitor.d.ts +6 -1
  58. package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
  59. package/dist/components/viewer/perf-monitor.js +307 -116
  60. package/dist/components/viewer/perf-panel.d.ts +3 -0
  61. package/dist/components/viewer/perf-panel.d.ts.map +1 -0
  62. package/dist/components/viewer/perf-panel.js +178 -0
  63. package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
  64. package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
  65. package/dist/components/viewer/pointer-raycast-layers.js +28 -0
  66. package/dist/components/viewer/post-processing.d.ts.map +1 -1
  67. package/dist/components/viewer/post-processing.js +124 -83
  68. package/dist/components/viewer/registered-systems.d.ts +14 -5
  69. package/dist/components/viewer/registered-systems.d.ts.map +1 -1
  70. package/dist/components/viewer/registered-systems.js +22 -10
  71. package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
  72. package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
  73. package/dist/components/viewer/scene-atmosphere.js +89 -0
  74. package/dist/components/viewer/scene-environment.d.ts.map +1 -1
  75. package/dist/components/viewer/scene-environment.js +19 -5
  76. package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
  77. package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
  78. package/dist/components/viewer/scene-ground-replacement.js +35 -0
  79. package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
  80. package/dist/components/viewer/viewer-camera.js +4 -1
  81. package/dist/components/viewer/viewer-presentations.d.ts +67 -0
  82. package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
  83. package/dist/components/viewer/viewer-presentations.js +120 -0
  84. package/dist/hooks/use-library-materials-version.d.ts +7 -0
  85. package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
  86. package/dist/hooks/use-library-materials-version.js +10 -0
  87. package/dist/hooks/use-node-events.d.ts.map +1 -1
  88. package/dist/hooks/use-node-events.js +1 -0
  89. package/dist/index.d.ts +15 -6
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +15 -6
  92. package/dist/lib/csg-utils.d.ts +1 -0
  93. package/dist/lib/csg-utils.d.ts.map +1 -1
  94. package/dist/lib/csg-utils.js +6 -1
  95. package/dist/lib/gpu-perf.d.ts +0 -2
  96. package/dist/lib/gpu-perf.d.ts.map +1 -1
  97. package/dist/lib/gpu-perf.js +7 -21
  98. package/dist/lib/layer-pass.d.ts +28 -0
  99. package/dist/lib/layer-pass.d.ts.map +1 -0
  100. package/dist/lib/layer-pass.js +254 -0
  101. package/dist/lib/materials.d.ts +12 -0
  102. package/dist/lib/materials.d.ts.map +1 -1
  103. package/dist/lib/materials.js +51 -17
  104. package/dist/lib/merged-outline-node.d.ts +17 -8
  105. package/dist/lib/merged-outline-node.d.ts.map +1 -1
  106. package/dist/lib/merged-outline-node.js +194 -52
  107. package/dist/lib/perf-actions.d.ts +55 -0
  108. package/dist/lib/perf-actions.d.ts.map +1 -0
  109. package/dist/lib/perf-actions.js +211 -0
  110. package/dist/lib/perf-observers.d.ts +2 -0
  111. package/dist/lib/perf-observers.d.ts.map +1 -0
  112. package/dist/lib/perf-observers.js +17 -0
  113. package/dist/lib/perf-panel-store.d.ts +71 -0
  114. package/dist/lib/perf-panel-store.d.ts.map +1 -0
  115. package/dist/lib/perf-panel-store.js +29 -0
  116. package/dist/lib/perf-tracks.d.ts +43 -0
  117. package/dist/lib/perf-tracks.d.ts.map +1 -0
  118. package/dist/lib/perf-tracks.js +113 -0
  119. package/dist/lib/pointer-events.d.ts +7 -0
  120. package/dist/lib/pointer-events.d.ts.map +1 -0
  121. package/dist/lib/pointer-events.js +680 -0
  122. package/dist/lib/post-processing-resources.d.ts +12 -0
  123. package/dist/lib/post-processing-resources.d.ts.map +1 -0
  124. package/dist/lib/post-processing-resources.js +19 -0
  125. package/dist/lib/renderer-capability.d.ts.map +1 -1
  126. package/dist/lib/renderer-capability.js +4 -2
  127. package/dist/lib/scene-visibility.d.ts +2 -1
  128. package/dist/lib/scene-visibility.d.ts.map +1 -1
  129. package/dist/lib/scene-visibility.js +22 -1
  130. package/dist/store/use-viewer.d.ts +2 -2
  131. package/dist/store/use-viewer.d.ts.map +1 -1
  132. package/dist/systems/ceiling/ceiling-system.js +1 -1
  133. package/dist/systems/door/door-animation-system.d.ts.map +1 -1
  134. package/dist/systems/door/door-animation-system.js +6 -9
  135. package/dist/systems/door/door-system.d.ts.map +1 -1
  136. package/dist/systems/door/door-system.js +13 -2
  137. package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
  138. package/dist/systems/geometry/geometry-system.js +3 -2
  139. package/dist/systems/level/level-system.d.ts.map +1 -1
  140. package/dist/systems/level/level-system.js +1 -1
  141. package/dist/systems/level/level-utils.d.ts +9 -0
  142. package/dist/systems/level/level-utils.d.ts.map +1 -1
  143. package/dist/systems/level/level-utils.js +14 -0
  144. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
  145. package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
  146. package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
  147. package/dist/systems/roof/roof-system.d.ts.map +1 -1
  148. package/dist/systems/roof/roof-system.js +1196 -58
  149. package/dist/systems/scan/scan-system.js +6 -5
  150. package/dist/systems/slab/slab-system.d.ts.map +1 -1
  151. package/dist/systems/slab/slab-system.js +1 -2
  152. package/dist/systems/stair/stair-system.d.ts.map +1 -1
  153. package/dist/systems/stair/stair-system.js +9 -8
  154. package/dist/systems/surface-hole-geometry.d.ts +1 -1
  155. package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
  156. package/dist/systems/surface-hole-geometry.js +1 -1
  157. package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
  158. package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
  159. package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
  160. package/dist/systems/wall/wall-build-lifecycle.js +74 -0
  161. package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
  162. package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
  163. package/dist/systems/wall/wall-cutout-cache.js +304 -0
  164. package/dist/systems/wall/wall-cutout.d.ts +12 -1
  165. package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
  166. package/dist/systems/wall/wall-cutout.js +24 -161
  167. package/dist/systems/wall/wall-material-variant.d.ts +31 -0
  168. package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
  169. package/dist/systems/wall/wall-material-variant.js +10 -0
  170. package/dist/systems/wall/wall-materials.d.ts +6 -0
  171. package/dist/systems/wall/wall-materials.d.ts.map +1 -1
  172. package/dist/systems/wall/wall-materials.js +29 -13
  173. package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
  174. package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
  175. package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
  176. package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
  177. package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
  178. package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
  179. package/dist/systems/wall/wall-system.d.ts +11 -4
  180. package/dist/systems/wall/wall-system.d.ts.map +1 -1
  181. package/dist/systems/wall/wall-system.js +405 -153
  182. package/dist/systems/window/window-animation-system.d.ts.map +1 -1
  183. package/dist/systems/window/window-animation-system.js +9 -8
  184. package/dist/systems/window/window-system.d.ts +1 -0
  185. package/dist/systems/window/window-system.d.ts.map +1 -1
  186. package/dist/systems/window/window-system.js +16 -2
  187. package/package.json +17 -4
  188. package/dist/lib/polygon-union.d.ts +0 -4
  189. package/dist/lib/polygon-union.d.ts.map +0 -1
  190. package/dist/lib/polygon-union.js +0 -289
@@ -0,0 +1,680 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2019-2025 Poimandres
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ *
24
+ * Vendored from R3F 9.6.1:
25
+ * https://raw.githubusercontent.com/pmndrs/react-three-fiber/v9.6.1/packages/fiber/src/core/events.ts
26
+ * Ray collection is cached; camera-drag moves are throttled before stock dispatch.
27
+ * The throttle is a vendored-manager policy; dev ?stockEvents uses untouched R3F.
28
+ */
29
+ import { events as createWebEvents, getRootState, } from '@react-three/fiber';
30
+ import * as THREE from 'three';
31
+ import { acceleratedRaycast } from 'three-mesh-bvh';
32
+ import useViewer from '../store/use-viewer';
33
+ // Keep navigation feedback at 10 Hz while avoiding raycasts for intervening moves.
34
+ const CAMERA_DRAG_MOVE_INTERVAL_MS = 100;
35
+ const stockIntersectObject = THREE.Raycaster.prototype.intersectObject;
36
+ const raycasterKeys = new Set(['ray', 'near', 'far', 'camera', 'layers', 'params', 'firstHitOnly']);
37
+ const pureRaycast = Symbol('pureRaycast');
38
+ const warnedRaycastNames = new Set();
39
+ const supportedRaycasts = new Set([
40
+ THREE.Object3D.prototype.raycast,
41
+ THREE.Mesh.prototype.raycast,
42
+ THREE.SkinnedMesh.prototype.raycast,
43
+ THREE.InstancedMesh.prototype.raycast,
44
+ THREE.BatchedMesh.prototype.raycast,
45
+ THREE.Line.prototype.raycast,
46
+ THREE.LineSegments.prototype.raycast,
47
+ THREE.Points.prototype.raycast,
48
+ THREE.Sprite.prototype.raycast,
49
+ THREE.LOD.prototype.raycast,
50
+ acceleratedRaycast,
51
+ ]);
52
+ /** Opt in only when the raycast appends hits without reading prior hits or mutating scene/query state. */
53
+ export function markPureRaycast(raycast) {
54
+ Object.defineProperty(raycast, pureRaycast, { value: true });
55
+ return raycast;
56
+ }
57
+ function isSupportedRaycast(raycast) {
58
+ // Arity admits the repo's many no-ops. Closure-mutating no-arg raycasts are out of scope:
59
+ // JavaScript cannot identify their effects without executing them or requiring every no-op to opt in.
60
+ return (raycast.length < 2 ||
61
+ supportedRaycasts.has(raycast) ||
62
+ raycast[pureRaycast] === true);
63
+ }
64
+ export function choosePointerEvents(search = typeof window !== 'undefined' ? window.location.search : '') {
65
+ return process.env.NODE_ENV !== 'production' && new URLSearchParams(search).has('stockEvents')
66
+ ? createWebEvents
67
+ : createPascalPointerEvents;
68
+ }
69
+ function querySnapshot(raycaster) {
70
+ if (Object.getPrototypeOf(raycaster) !== THREE.Raycaster.prototype ||
71
+ raycaster.intersectObject !== stockIntersectObject ||
72
+ Reflect.ownKeys(raycaster).some((key) => typeof key !== 'string' || !raycasterKeys.has(key)))
73
+ return undefined;
74
+ const { ray, camera, layers, near, far, params } = raycaster;
75
+ const snapshot = [
76
+ raycaster,
77
+ camera,
78
+ near,
79
+ far,
80
+ layers.mask,
81
+ ray.origin.x,
82
+ ray.origin.y,
83
+ ray.origin.z,
84
+ ray.direction.x,
85
+ ray.direction.y,
86
+ ray.direction.z,
87
+ raycaster.firstHitOnly,
88
+ ...camera.matrixWorld.elements,
89
+ ...camera.matrixWorldInverse.elements,
90
+ ...camera.projectionMatrix.elements,
91
+ ...camera.projectionMatrixInverse.elements,
92
+ ];
93
+ // Unknown parameter objects can hide mutable query state; use stock recursion for those.
94
+ for (const key of Reflect.ownKeys(params)) {
95
+ const descriptor = Object.getOwnPropertyDescriptor(params, key);
96
+ const value = descriptor.value;
97
+ if (typeof key !== 'string' ||
98
+ descriptor.get ||
99
+ !value ||
100
+ Object.getPrototypeOf(value) !== Object.prototype)
101
+ return undefined;
102
+ snapshot.push(key, value);
103
+ for (const field of Reflect.ownKeys(value)) {
104
+ const entry = Object.getOwnPropertyDescriptor(value, field);
105
+ if (typeof field !== 'string' ||
106
+ entry.get ||
107
+ (entry.value !== null &&
108
+ !['number', 'string', 'boolean', 'undefined'].includes(typeof entry.value)))
109
+ return undefined;
110
+ snapshot.push(field, entry.value);
111
+ }
112
+ }
113
+ return snapshot;
114
+ }
115
+ function distanceOrder(a, b) {
116
+ return a.distance - b.distance;
117
+ }
118
+ function createCachedRaycast() {
119
+ const queries = new WeakMap();
120
+ const activeQueries = [];
121
+ const rootHits = [];
122
+ let generation = 0;
123
+ let revision = 0;
124
+ let unsupportedObject;
125
+ function collect(object, raycaster, query) {
126
+ // Only R3F-managed objects can also appear as independently queried event roots.
127
+ const managed = object.__r3f !== undefined;
128
+ const cached = managed ? query.subtrees.get(object) : undefined;
129
+ const { hits } = query;
130
+ if (cached?.generation === generation) {
131
+ for (let i = cached.start; i < cached.end; i++)
132
+ hits.push(hits[i]);
133
+ return true;
134
+ }
135
+ const start = hits.length;
136
+ // Three's runtime accepts false as a recursion barrier, although its declaration says void.
137
+ let result;
138
+ if (object.layers.test(raycaster.layers)) {
139
+ if (!isSupportedRaycast(object.raycast)) {
140
+ unsupportedObject = object;
141
+ return false;
142
+ }
143
+ result = object.raycast(raycaster, hits);
144
+ }
145
+ if (result !== false) {
146
+ const children = object.children;
147
+ for (let i = 0, length = children.length; i < length; i++) {
148
+ if (!collect(children[i], raycaster, query))
149
+ return false;
150
+ }
151
+ }
152
+ if (cached) {
153
+ cached.generation = generation;
154
+ cached.start = start;
155
+ cached.end = hits.length;
156
+ }
157
+ else if (managed) {
158
+ query.subtrees.set(object, { generation, start, end: hits.length });
159
+ }
160
+ return true;
161
+ }
162
+ return {
163
+ get unsupportedObject() {
164
+ return unsupportedObject;
165
+ },
166
+ clear() {
167
+ unsupportedObject = undefined;
168
+ generation++;
169
+ revision = 0;
170
+ for (const query of activeQueries) {
171
+ query.hits.length = 0;
172
+ query.snapshot = undefined;
173
+ }
174
+ activeQueries.length = 0;
175
+ rootHits.length = 0;
176
+ },
177
+ intersectObject(object, state) {
178
+ const { raycaster } = state;
179
+ let query = queries.get(state);
180
+ if (!query) {
181
+ query = {
182
+ generation: -1,
183
+ snapshot: undefined,
184
+ revision: -1,
185
+ fallback: false,
186
+ subtrees: new WeakMap(),
187
+ hits: [],
188
+ };
189
+ queries.set(state, query);
190
+ }
191
+ if (query.generation !== generation) {
192
+ query.generation = generation;
193
+ query.snapshot = querySnapshot(raycaster);
194
+ query.fallback = !query.snapshot;
195
+ query.revision = revision;
196
+ activeQueries.push(query);
197
+ }
198
+ else if (query.revision !== revision && !query.fallback) {
199
+ // Any layer can mutate another query; cached replay alone cannot change it.
200
+ const snapshot = querySnapshot(raycaster);
201
+ query.fallback =
202
+ !snapshot ||
203
+ snapshot.length !== query.snapshot.length ||
204
+ snapshot.some((value, index) => !Object.is(value, query.snapshot[index]));
205
+ query.revision = revision;
206
+ }
207
+ if (query.fallback)
208
+ return undefined;
209
+ let range = query.subtrees.get(object);
210
+ if (range?.generation !== generation) {
211
+ revision++;
212
+ if (!collect(object, raycaster, query))
213
+ return undefined;
214
+ range = query.subtrees.get(object);
215
+ }
216
+ // Sorting must not disturb subtree emission order, including equal-distance hits.
217
+ rootHits.length = 0;
218
+ for (let i = range.start; i < range.end; i++)
219
+ rootHits.push(query.hits[i]);
220
+ if (rootHits.length > 1)
221
+ rootHits.sort(distanceOrder);
222
+ return rootHits;
223
+ },
224
+ };
225
+ }
226
+ export function createPascalPointerEvents(store) {
227
+ const manager = createWebEvents(store);
228
+ const { handlePointer } = createEvents(store);
229
+ for (const name of Object.keys(manager.handlers)) {
230
+ manager.handlers[name] = handlePointer(name);
231
+ }
232
+ const move = manager.handlers.onPointerMove;
233
+ let lastCameraDragMove = Number.NEGATIVE_INFINITY;
234
+ manager.handlers.onPointerMove = (event) => {
235
+ if (useViewer.getState().cameraDragging) {
236
+ const now = performance.now();
237
+ if (now - lastCameraDragMove < CAMERA_DRAG_MOVE_INTERVAL_MS)
238
+ return;
239
+ lastCameraDragMove = now;
240
+ }
241
+ else {
242
+ lastCameraDragMove = Number.NEGATIVE_INFINITY;
243
+ }
244
+ move(event);
245
+ };
246
+ return manager;
247
+ }
248
+ function makeId(event) {
249
+ // biome-ignore lint/style/useTemplate: Keep the vendored dispatch identical to R3F 9.6.1.
250
+ return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
251
+ }
252
+ /**
253
+ * Release pointer captures.
254
+ * This is called by releasePointerCapture in the API, and when an object is removed.
255
+ */
256
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
257
+ const captureData = captures.get(obj);
258
+ if (captureData) {
259
+ captures.delete(obj);
260
+ // If this was the last capturing object for this pointer
261
+ if (captures.size === 0) {
262
+ capturedMap.delete(pointerId);
263
+ captureData.target.releasePointerCapture(pointerId);
264
+ }
265
+ }
266
+ }
267
+ function createEvents(store) {
268
+ const stats = {
269
+ events: 0,
270
+ cachedEvents: 0,
271
+ fallbackEvents: 0,
272
+ lastFallbackReason: null,
273
+ };
274
+ if (typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('perf')) {
275
+ ;
276
+ window.__pointerEvents = {
277
+ stats: () => ({
278
+ ...stats,
279
+ lastFallbackReason: stats.lastFallbackReason ? { ...stats.lastFallbackReason } : null,
280
+ }),
281
+ };
282
+ }
283
+ // Nested pointer events from raycast/compute callbacks need separate scratch storage.
284
+ const collectors = [];
285
+ let collectionDepth = 0;
286
+ /** Calculates delta */
287
+ function calculateDistance(event) {
288
+ const { internal } = store.getState();
289
+ const dx = event.offsetX - internal.initialClick[0];
290
+ const dy = event.offsetY - internal.initialClick[1];
291
+ return Math.round(Math.sqrt(dx * dx + dy * dy));
292
+ }
293
+ /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
294
+ function filterPointerEvents(objects) {
295
+ return objects.filter((obj) => {
296
+ const handlers = obj.__r3f?.handlers;
297
+ return (handlers &&
298
+ (handlers.onPointerMove ||
299
+ handlers.onPointerOver ||
300
+ handlers.onPointerEnter ||
301
+ handlers.onPointerOut ||
302
+ handlers.onPointerLeave));
303
+ });
304
+ }
305
+ function intersect(event, filter) {
306
+ stats.events++;
307
+ const state = store.getState();
308
+ const duplicates = new Set();
309
+ const intersections = [];
310
+ // Allow callers to eliminate event objects
311
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
312
+ // Reset all raycaster cameras to undefined
313
+ for (let i = 0; i < eventsObjects.length; i++) {
314
+ const state = getRootState(eventsObjects[i]);
315
+ if (state) {
316
+ state.raycaster.camera = undefined;
317
+ }
318
+ }
319
+ if (!state.previousRoot) {
320
+ // Make sure root-level pointer and ray are set up
321
+ state.events.compute?.(event, state);
322
+ }
323
+ collectors[collectionDepth] ??= createCachedRaycast();
324
+ const collector = collectors[collectionDepth++];
325
+ let hits = [];
326
+ let stock = false;
327
+ const length = eventsObjects.length;
328
+ try {
329
+ for (let i = 0; i < length; i++) {
330
+ if (!(i in eventsObjects))
331
+ continue;
332
+ const obj = eventsObjects[i];
333
+ const layer = getRootState(obj);
334
+ if (!layer?.events.enabled || layer.raycaster.camera === null)
335
+ continue;
336
+ if (layer.raycaster.camera === undefined) {
337
+ // A layer compute may mutate another layer or opaque plugin state.
338
+ collector.clear();
339
+ layer.events.compute?.(event, layer, layer.previousRoot?.getState());
340
+ if (layer.raycaster.camera === undefined)
341
+ layer.raycaster.camera = null;
342
+ }
343
+ if (layer.raycaster.camera) {
344
+ const rootHits = stock
345
+ ? layer.raycaster.intersectObject(obj, true)
346
+ : collector.intersectObject(obj, layer);
347
+ if (!rootHits) {
348
+ const unsupported = collector.unsupportedObject;
349
+ const reason = {
350
+ fnName: unsupported
351
+ ? unsupported.raycast.name || '(anonymous)'
352
+ : '(unsupported query)',
353
+ objectName: (unsupported ?? obj).name,
354
+ objectType: (unsupported ?? obj).type,
355
+ };
356
+ stats.fallbackEvents++;
357
+ stats.lastFallbackReason = reason;
358
+ if (unsupported &&
359
+ process.env.NODE_ENV === 'development' &&
360
+ !warnedRaycastNames.has(reason.fnName)) {
361
+ warnedRaycastNames.add(reason.fnName);
362
+ console.warn(`[pointer-events] Stock fallback for raycast "${reason.fnName}" on ${reason.objectType} "${reason.objectName}". Use markPureRaycast only after verifying it appends hits without reading prior hits or mutating scene/query state.`);
363
+ }
364
+ // Keep this event-wide flag outside the collector: portal compute also clears its cache.
365
+ stock = true;
366
+ collector.clear();
367
+ if (unsupported) {
368
+ // Completed pure roots already match stock order. Retry only the interrupted root
369
+ // with its own accumulator before invoking any unsupported user code.
370
+ i--;
371
+ }
372
+ else {
373
+ hits.length = 0;
374
+ i = -1;
375
+ }
376
+ continue;
377
+ }
378
+ for (let j = 0; j < rootHits.length; j++)
379
+ hits.push(rootHits[j]);
380
+ }
381
+ }
382
+ }
383
+ finally {
384
+ if (!stock)
385
+ stats.cachedEvents++;
386
+ // User filters and dispatch can perform independent raycasts or change the scene.
387
+ collector.clear();
388
+ collectionDepth--;
389
+ }
390
+ hits = hits
391
+ .sort((a, b) => {
392
+ const aState = getRootState(a.object);
393
+ const bState = getRootState(b.object);
394
+ if (!aState || !bState)
395
+ return a.distance - b.distance;
396
+ return bState.events.priority - aState.events.priority || a.distance - b.distance;
397
+ })
398
+ .filter((item) => {
399
+ const id = makeId(item);
400
+ if (duplicates.has(id))
401
+ return false;
402
+ duplicates.add(id);
403
+ return true;
404
+ });
405
+ // https://github.com/mrdoob/three.js/issues/16031
406
+ // Allow custom userland intersect sort order, this likely only makes sense on the root filter
407
+ if (state.events.filter)
408
+ hits = state.events.filter(hits, state);
409
+ // Bubble up the events, find the event source (eventObject)
410
+ for (const hit of hits) {
411
+ let eventObject = hit.object;
412
+ // Bubble event up
413
+ while (eventObject) {
414
+ if (eventObject.__r3f?.eventCount)
415
+ intersections.push({ ...hit, eventObject });
416
+ eventObject = eventObject.parent;
417
+ }
418
+ }
419
+ // If the interaction is captured, make all capturing targets part of the intersect.
420
+ if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
421
+ for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
422
+ if (!duplicates.has(makeId(captureData.intersection)))
423
+ intersections.push(captureData.intersection);
424
+ }
425
+ }
426
+ return intersections;
427
+ }
428
+ /** Handles intersections by forwarding them to handlers */
429
+ function handleIntersects(intersections, event, delta, callback) {
430
+ // If anything has been found, forward it to the event listeners
431
+ if (intersections.length) {
432
+ const localState = { stopped: false };
433
+ for (const hit of intersections) {
434
+ let state = getRootState(hit.object);
435
+ // If the object is not managed by R3F, it might be parented to an element which is.
436
+ // Traverse upwards until we find a managed parent and use its state instead.
437
+ if (!state) {
438
+ hit.object.traverseAncestors((obj) => {
439
+ const parentState = getRootState(obj);
440
+ if (parentState) {
441
+ state = parentState;
442
+ return false;
443
+ }
444
+ });
445
+ }
446
+ if (state) {
447
+ const { raycaster, pointer, camera, internal } = state;
448
+ const unprojectedPoint = new THREE.Vector3(pointer.x, pointer.y, 0).unproject(camera);
449
+ const hasPointerCapture = (id) => internal.capturedMap.get(id)?.has(hit.eventObject) ?? false;
450
+ const setPointerCapture = (id) => {
451
+ const captureData = { intersection: hit, target: event.target };
452
+ if (internal.capturedMap.has(id)) {
453
+ // if the pointerId was previously captured, we add the hit to the
454
+ // event capturedMap.
455
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
456
+ }
457
+ else {
458
+ // if the pointerId was not previously captured, we create a map
459
+ // containing the hitObject, and the hit. hitObject is used for
460
+ // faster access.
461
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
462
+ }
463
+ // Call the original event now
464
+ ;
465
+ event.target.setPointerCapture(id);
466
+ };
467
+ const releasePointerCapture = (id) => {
468
+ const captures = internal.capturedMap.get(id);
469
+ if (captures) {
470
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
471
+ }
472
+ };
473
+ // Add native event props
474
+ // R3F copies arbitrary native event properties into its public event payload.
475
+ let extractEventProps = {};
476
+ // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
477
+ for (let prop in event) {
478
+ let property = event[prop];
479
+ // Only copy over atomics, leave functions alone as these should be
480
+ // called as event.nativeEvent.fn()
481
+ if (typeof property !== 'function')
482
+ extractEventProps[prop] = property;
483
+ }
484
+ let raycastEvent = {
485
+ ...hit,
486
+ ...extractEventProps,
487
+ pointer,
488
+ intersections,
489
+ stopped: localState.stopped,
490
+ delta,
491
+ unprojectedPoint,
492
+ ray: raycaster.ray,
493
+ camera: camera,
494
+ // Hijack stopPropagation, which just sets a flag
495
+ stopPropagation() {
496
+ // https://github.com/pmndrs/react-three-fiber/issues/596
497
+ // Events are not allowed to stop propagation if the pointer has been captured
498
+ const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
499
+ // We only authorize stopPropagation...
500
+ if (
501
+ // ...if this pointer hasn't been captured
502
+ !capturesForPointer ||
503
+ // ... or if the hit object is capturing the pointer
504
+ capturesForPointer.has(hit.eventObject)) {
505
+ raycastEvent.stopped = localState.stopped = true;
506
+ // Propagation is stopped, remove all other hover records
507
+ // An event handler is only allowed to flush other handlers if it is hovered itself
508
+ if (internal.hovered.size &&
509
+ Array.from(internal.hovered.values()).find((i) => i.eventObject === hit.eventObject)) {
510
+ // Objects cannot flush out higher up objects that have already caught the event
511
+ const higher = intersections.slice(0, intersections.indexOf(hit));
512
+ cancelPointer([...higher, hit]);
513
+ }
514
+ }
515
+ },
516
+ // there should be a distinction between target and currentTarget
517
+ target: { hasPointerCapture, setPointerCapture, releasePointerCapture },
518
+ currentTarget: { hasPointerCapture, setPointerCapture, releasePointerCapture },
519
+ nativeEvent: event,
520
+ };
521
+ // Call subscribers
522
+ callback(raycastEvent);
523
+ // Event bubbling may be interrupted by stopPropagation
524
+ if (localState.stopped === true)
525
+ break;
526
+ }
527
+ }
528
+ }
529
+ return intersections;
530
+ }
531
+ function cancelPointer(intersections) {
532
+ const { internal } = store.getState();
533
+ for (const hoveredObj of internal.hovered.values()) {
534
+ // When no objects were hit or the hovered object wasn't found underneath the cursor
535
+ // we call onPointerOut and delete the object from the hovered-elements map
536
+ if (!intersections.length ||
537
+ !intersections.find((hit) => hit.object === hoveredObj.object &&
538
+ hit.index === hoveredObj.index &&
539
+ hit.instanceId === hoveredObj.instanceId)) {
540
+ const eventObject = hoveredObj.eventObject;
541
+ const instance = eventObject.__r3f;
542
+ internal.hovered.delete(makeId(hoveredObj));
543
+ if (instance?.eventCount) {
544
+ const handlers = instance.handlers;
545
+ // Clear out intersects, they are outdated by now
546
+ const data = { ...hoveredObj, intersections };
547
+ handlers.onPointerOut?.(data);
548
+ handlers.onPointerLeave?.(data);
549
+ }
550
+ }
551
+ }
552
+ }
553
+ function pointerMissed(event, objects) {
554
+ for (let i = 0; i < objects.length; i++) {
555
+ const instance = objects[i].__r3f;
556
+ instance?.handlers.onPointerMissed?.(event);
557
+ }
558
+ }
559
+ function handlePointer(name) {
560
+ // Deal with cancelation
561
+ switch (name) {
562
+ case 'onPointerLeave':
563
+ case 'onPointerCancel':
564
+ return () => cancelPointer([]);
565
+ case 'onLostPointerCapture':
566
+ return (event) => {
567
+ const { internal } = store.getState();
568
+ if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
569
+ // If the object event interface had onLostPointerCapture, we'd call it here on every
570
+ // object that's getting removed. We call it on the next frame because onLostPointerCapture
571
+ // fires before onPointerUp. Otherwise pointerUp would never be called if the event didn't
572
+ // happen in the object it originated from, leaving components in a in-between state.
573
+ requestAnimationFrame(() => {
574
+ // Only release if pointer-up didn't do it already
575
+ if (internal.capturedMap.has(event.pointerId)) {
576
+ internal.capturedMap.delete(event.pointerId);
577
+ cancelPointer([]);
578
+ }
579
+ });
580
+ }
581
+ };
582
+ }
583
+ // Any other pointer goes here ...
584
+ return function handleEvent(event) {
585
+ const { onPointerMissed, internal } = store.getState();
586
+ // prepareRay(event)
587
+ internal.lastEvent.current = event;
588
+ // Get fresh intersects
589
+ const isPointerMove = name === 'onPointerMove';
590
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
591
+ const filter = isPointerMove ? filterPointerEvents : undefined;
592
+ const hits = intersect(event, filter);
593
+ const delta = isClickEvent ? calculateDistance(event) : 0;
594
+ // Save initial coordinates on pointer-down
595
+ if (name === 'onPointerDown') {
596
+ internal.initialClick = [event.offsetX, event.offsetY];
597
+ internal.initialHits = hits.map((hit) => hit.eventObject);
598
+ }
599
+ // If a click yields no results, pass it back to the user as a miss
600
+ // Missed events have to come first in order to establish user-land side-effect clean up
601
+ if (isClickEvent && !hits.length) {
602
+ if (delta <= 2) {
603
+ pointerMissed(event, internal.interaction);
604
+ if (onPointerMissed)
605
+ onPointerMissed(event);
606
+ }
607
+ }
608
+ // Take care of unhover
609
+ if (isPointerMove)
610
+ cancelPointer(hits);
611
+ function onIntersect(data) {
612
+ const eventObject = data.eventObject;
613
+ const instance = eventObject.__r3f;
614
+ // Check presence of handlers
615
+ if (!instance?.eventCount)
616
+ return;
617
+ const handlers = instance.handlers;
618
+ /*
619
+ MAYBE TODO, DELETE IF NOT:
620
+ Check if the object is captured, captured events should not have intersects running in parallel
621
+ But wouldn't it be better to just replace capturedMap with a single entry?
622
+ Also, are we OK with straight up making picking up multiple objects impossible?
623
+
624
+ const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
625
+ if (pointerId !== undefined) {
626
+ const capturedMeshSet = internal.capturedMap.get(pointerId)
627
+ if (capturedMeshSet) {
628
+ const captured = capturedMeshSet.get(eventObject)
629
+ if (captured && captured.localState.stopped) return
630
+ }
631
+ }*/
632
+ if (isPointerMove) {
633
+ // Move event ...
634
+ if (handlers.onPointerOver ||
635
+ handlers.onPointerEnter ||
636
+ handlers.onPointerOut ||
637
+ handlers.onPointerLeave) {
638
+ // When enter or out is present take care of hover-state
639
+ const id = makeId(data);
640
+ const hoveredItem = internal.hovered.get(id);
641
+ if (!hoveredItem) {
642
+ // If the object wasn't previously hovered, book it and call its handler
643
+ internal.hovered.set(id, data);
644
+ handlers.onPointerOver?.(data);
645
+ handlers.onPointerEnter?.(data);
646
+ }
647
+ else if (hoveredItem.stopped) {
648
+ // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
649
+ data.stopPropagation();
650
+ }
651
+ }
652
+ // Call mouse move
653
+ handlers.onPointerMove?.(data);
654
+ }
655
+ else {
656
+ // All other events ...
657
+ const handler = handlers[name];
658
+ if (handler) {
659
+ // Forward all events back to their respective handlers with the exception of click events,
660
+ // which must use the initial target
661
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
662
+ // Missed events have to come first
663
+ pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
664
+ // Now call the handler
665
+ handler(data);
666
+ }
667
+ }
668
+ else {
669
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
670
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
671
+ pointerMissed(event, internal.interaction.filter((object) => !internal.initialHits.includes(object)));
672
+ }
673
+ }
674
+ }
675
+ }
676
+ handleIntersects(hits, event, delta, onIntersect);
677
+ };
678
+ }
679
+ return { handlePointer };
680
+ }