@pascal-app/viewer 1.0.0-beta.5 → 1.0.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/LICENSE +21 -0
- package/README.md +67 -0
- package/dist/capture/asset-url.d.ts +2 -0
- package/dist/capture/asset-url.d.ts.map +1 -0
- package/dist/capture/asset-url.js +13 -0
- package/dist/capture/capture-runtime.d.ts +43 -0
- package/dist/capture/capture-runtime.d.ts.map +1 -0
- package/dist/capture/capture-runtime.js +198 -0
- package/dist/capture/frame.d.ts +4 -0
- package/dist/capture/frame.d.ts.map +1 -0
- package/dist/capture/frame.js +21 -0
- package/dist/capture/index.d.ts +12 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +11 -0
- package/dist/capture/layer-visibility.d.ts +5 -0
- package/dist/capture/layer-visibility.d.ts.map +1 -0
- package/dist/capture/layer-visibility.js +11 -0
- package/dist/capture/layers/clay-matcap.d.ts +3 -0
- package/dist/capture/layers/clay-matcap.d.ts.map +1 -0
- package/dist/capture/layers/clay-matcap.js +26 -0
- package/dist/capture/layers/device-motion-layer.d.ts +7 -0
- package/dist/capture/layers/device-motion-layer.d.ts.map +1 -0
- package/dist/capture/layers/device-motion-layer.js +76 -0
- package/dist/capture/layers/point-cloud-layer.d.ts +15 -0
- package/dist/capture/layers/point-cloud-layer.d.ts.map +1 -0
- package/dist/capture/layers/point-cloud-layer.js +106 -0
- package/dist/capture/layers/room-model-layer.d.ts +9 -0
- package/dist/capture/layers/room-model-layer.d.ts.map +1 -0
- package/dist/capture/layers/room-model-layer.js +71 -0
- package/dist/capture/layers/surface-mesh-data.d.ts +9 -0
- package/dist/capture/layers/surface-mesh-data.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-data.js +74 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts +7 -0
- package/dist/capture/layers/surface-mesh-layer.d.ts.map +1 -0
- package/dist/capture/layers/surface-mesh-layer.js +16 -0
- package/dist/capture/preview.d.ts +3 -0
- package/dist/capture/preview.d.ts.map +1 -0
- package/dist/capture/preview.js +2 -0
- package/dist/capture/source-state.d.ts +22 -0
- package/dist/capture/source-state.d.ts.map +1 -0
- package/dist/capture/source-state.js +182 -0
- package/dist/capture/stream-rendering.d.ts +14 -0
- package/dist/capture/stream-rendering.d.ts.map +1 -0
- package/dist/capture/stream-rendering.js +70 -0
- package/dist/capture/trajectory.d.ts +20 -0
- package/dist/capture/trajectory.d.ts.map +1 -0
- package/dist/capture/trajectory.js +77 -0
- package/dist/capture/use-json-artifact.d.ts +2 -0
- package/dist/capture/use-json-artifact.d.ts.map +1 -0
- package/dist/capture/use-json-artifact.js +42 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts +14 -0
- package/dist/components/viewer/batched-mesh-spike.d.ts.map +1 -0
- package/dist/components/viewer/batched-mesh-spike.js +64 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +4 -3
- package/dist/components/viewer/glb-scene.d.ts.map +1 -1
- package/dist/components/viewer/glb-scene.js +3 -1
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +70 -62
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +123 -89
- package/dist/components/viewer/perf-monitor.d.ts +6 -1
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +307 -116
- package/dist/components/viewer/perf-panel.d.ts +3 -0
- package/dist/components/viewer/perf-panel.d.ts.map +1 -0
- package/dist/components/viewer/perf-panel.js +178 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts +15 -0
- package/dist/components/viewer/pointer-raycast-layers.d.ts.map +1 -0
- package/dist/components/viewer/pointer-raycast-layers.js +28 -0
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +130 -83
- package/dist/components/viewer/registered-systems.d.ts +14 -5
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +22 -10
- package/dist/components/viewer/scene-atmosphere.d.ts +30 -0
- package/dist/components/viewer/scene-atmosphere.d.ts.map +1 -0
- package/dist/components/viewer/scene-atmosphere.js +89 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -1
- package/dist/components/viewer/scene-environment.js +19 -5
- package/dist/components/viewer/scene-ground-replacement.d.ts +5 -0
- package/dist/components/viewer/scene-ground-replacement.d.ts.map +1 -0
- package/dist/components/viewer/scene-ground-replacement.js +35 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +8 -16
- package/dist/components/viewer/viewer-camera.d.ts.map +1 -1
- package/dist/components/viewer/viewer-camera.js +4 -1
- package/dist/components/viewer/viewer-presentations.d.ts +67 -0
- package/dist/components/viewer/viewer-presentations.d.ts.map +1 -0
- package/dist/components/viewer/viewer-presentations.js +120 -0
- package/dist/hooks/use-library-materials-version.d.ts +7 -0
- package/dist/hooks/use-library-materials-version.d.ts.map +1 -0
- package/dist/hooks/use-library-materials-version.js +10 -0
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +1 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -7
- package/dist/lib/csg-utils.d.ts +1 -0
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +6 -1
- package/dist/lib/gpu-perf.d.ts +0 -2
- package/dist/lib/gpu-perf.d.ts.map +1 -1
- package/dist/lib/gpu-perf.js +7 -21
- package/dist/lib/isolation.d.ts +14 -30
- package/dist/lib/isolation.d.ts.map +1 -1
- package/dist/lib/isolation.js +62 -66
- package/dist/lib/layer-pass.d.ts +28 -0
- package/dist/lib/layer-pass.d.ts.map +1 -0
- package/dist/lib/layer-pass.js +254 -0
- package/dist/lib/materials.d.ts +12 -0
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +52 -17
- package/dist/lib/merged-outline-node.d.ts +17 -8
- package/dist/lib/merged-outline-node.d.ts.map +1 -1
- package/dist/lib/merged-outline-node.js +194 -52
- package/dist/lib/perf-actions.d.ts +55 -0
- package/dist/lib/perf-actions.d.ts.map +1 -0
- package/dist/lib/perf-actions.js +211 -0
- package/dist/lib/perf-observers.d.ts +2 -0
- package/dist/lib/perf-observers.d.ts.map +1 -0
- package/dist/lib/perf-observers.js +17 -0
- package/dist/lib/perf-panel-store.d.ts +71 -0
- package/dist/lib/perf-panel-store.d.ts.map +1 -0
- package/dist/lib/perf-panel-store.js +29 -0
- package/dist/lib/perf-tracks.d.ts +43 -0
- package/dist/lib/perf-tracks.d.ts.map +1 -0
- package/dist/lib/perf-tracks.js +113 -0
- package/dist/lib/pointer-events.d.ts +7 -0
- package/dist/lib/pointer-events.d.ts.map +1 -0
- package/dist/lib/pointer-events.js +680 -0
- package/dist/lib/post-processing-resources.d.ts +12 -0
- package/dist/lib/post-processing-resources.d.ts.map +1 -0
- package/dist/lib/post-processing-resources.js +19 -0
- package/dist/lib/renderer-capability.d.ts.map +1 -1
- package/dist/lib/renderer-capability.js +4 -2
- package/dist/lib/scene-visibility.d.ts +2 -1
- package/dist/lib/scene-visibility.d.ts.map +1 -1
- package/dist/lib/scene-visibility.js +25 -2
- package/dist/lib/snapshot-pipeline.d.ts +3 -1
- package/dist/lib/snapshot-pipeline.d.ts.map +1 -1
- package/dist/lib/snapshot-pipeline.js +32 -16
- package/dist/store/use-viewer.d.ts +5 -3
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +2 -0
- package/dist/systems/ceiling/ceiling-system.js +1 -1
- package/dist/systems/door/door-animation-system.d.ts.map +1 -1
- package/dist/systems/door/door-animation-system.js +6 -9
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +13 -2
- package/dist/systems/floor-elevation/floor-elevation-system.d.ts.map +1 -1
- package/dist/systems/floor-elevation/floor-elevation-system.js +7 -3
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +7 -2
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +1 -1
- package/dist/systems/level/level-utils.d.ts +9 -0
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +14 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts +8 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.d.ts.map +1 -0
- package/dist/systems/perf-action-settle/perf-action-settle-system.js +32 -0
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1196 -58
- package/dist/systems/scan/scan-system.js +6 -5
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +1 -2
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +9 -8
- package/dist/systems/surface-hole-geometry.d.ts +1 -1
- package/dist/systems/surface-hole-geometry.d.ts.map +1 -1
- package/dist/systems/surface-hole-geometry.js +1 -1
- package/dist/systems/wall/level-miter-cache.d.ts.map +1 -1
- package/dist/systems/wall/wall-build-lifecycle.d.ts +9 -0
- package/dist/systems/wall/wall-build-lifecycle.d.ts.map +1 -0
- package/dist/systems/wall/wall-build-lifecycle.js +74 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts +57 -0
- package/dist/systems/wall/wall-cutout-cache.d.ts.map +1 -0
- package/dist/systems/wall/wall-cutout-cache.js +304 -0
- package/dist/systems/wall/wall-cutout.d.ts +12 -1
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +24 -161
- package/dist/systems/wall/wall-material-variant.d.ts +31 -0
- package/dist/systems/wall/wall-material-variant.d.ts.map +1 -0
- package/dist/systems/wall/wall-material-variant.js +10 -0
- package/dist/systems/wall/wall-materials.d.ts +6 -0
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +29 -13
- package/dist/systems/wall/wall-placeholder-sweep.d.ts +50 -0
- package/dist/systems/wall/wall-placeholder-sweep.d.ts.map +1 -0
- package/dist/systems/wall/wall-placeholder-sweep.js +51 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts +5 -0
- package/dist/systems/wall/wall-rebuild-notifications.d.ts.map +1 -0
- package/dist/systems/wall/wall-rebuild-notifications.js +23 -0
- package/dist/systems/wall/wall-system.d.ts +11 -4
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +405 -153
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +9 -8
- package/dist/systems/window/window-system.d.ts +1 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +16 -2
- package/dist/systems/zone/zone-system.d.ts.map +1 -1
- package/dist/systems/zone/zone-system.js +28 -2
- package/package.json +17 -4
- package/dist/lib/polygon-union.d.ts +0 -4
- package/dist/lib/polygon-union.d.ts.map +0 -1
- 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
|
+
}
|