@expofp/renderer 3.42.0 → 3.42.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/events/pointer.d.ts +18 -1
- package/dist/events/pointer.js +43 -0
- package/dist/space/Scene.js +4 -0
- package/dist/space/clipping.d.ts +17 -0
- package/dist/space/clipping.js +3 -2
- package/dist/types/events.d.ts +4 -2
- package/package.json +2 -2
package/dist/events/pointer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ThreeEvent } from '@react-three/fiber';
|
|
1
|
+
import { type RootState, type ThreeEvent } from '@react-three/fiber';
|
|
2
2
|
import { type Intersection, type Object3D, type Ray, type Raycaster } from 'three';
|
|
3
3
|
import { type PlanPoint, type Space } from '../space/Space';
|
|
4
4
|
import { type LayerDef, type RenderableDef } from '../types/defs';
|
|
@@ -100,6 +100,23 @@ export declare function resolveHit(hit: Intersection): RenderableDef | undefined
|
|
|
100
100
|
* @returns The nearest geometry under the pointer, or — where there is none — the floor beneath it.
|
|
101
101
|
*/
|
|
102
102
|
export declare function missedPoint(raycaster: Raycaster, scene: Object3D, space: Space): PlanPoint;
|
|
103
|
+
/**
|
|
104
|
+
* Drops the hits that geometry with no handlers hides, so a booth behind a wall or a model, opaque
|
|
105
|
+
* or translucent, is not picked. R3F's `events.filter`, which runs on its nearest-first hits.
|
|
106
|
+
*
|
|
107
|
+
* R3F raycasts only interactive layers, so it cannot see what stands in front of them. This casts
|
|
108
|
+
* the same ray at the whole scene and cuts the hits at the nearest object no interactive layer
|
|
109
|
+
* holds. A wall and a logo on a booth's roof differ only in distance: the logo lies on the roof, and
|
|
110
|
+
* the wall stands apart from it. So the cut keeps a hit up to {@link ELEVATION_PRECISION} behind
|
|
111
|
+
* the occluder, the least separation at which the depth buffer draws two surfaces in order. Nearer
|
|
112
|
+
* than that, the occluder is drawn on the hit, not in front of it. A click with every hit dropped is
|
|
113
|
+
* a miss, as a click on the occluder was before.
|
|
114
|
+
* @param hits - R3F's hits, nearest first.
|
|
115
|
+
* @param state - The root's state, holding the ray already aimed at the pointer.
|
|
116
|
+
* @param space - The scene's space, whose scale turns the plan-unit separation into a world one.
|
|
117
|
+
* @returns The hits in front of the nearest occluder.
|
|
118
|
+
*/
|
|
119
|
+
export declare function occludeHits(hits: Intersection[], state: RootState, space: Space): Intersection[];
|
|
103
120
|
/**
|
|
104
121
|
* Where a ray crosses the plan plane — the floor under the pointer, whatever is drawn above it.
|
|
105
122
|
*
|
package/dist/events/pointer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ELEVATION_PRECISION } from '../space/clipping';
|
|
1
2
|
import { useSpace } from '../space/Space';
|
|
2
3
|
/**
|
|
3
4
|
* The props that make a layer's group interactive: one dispatching handler per handler the def
|
|
@@ -128,6 +129,35 @@ export function missedPoint(raycaster, scene, space) {
|
|
|
128
129
|
return space.worldToPlan(nearest.point);
|
|
129
130
|
return planePoint(raycaster.ray, space);
|
|
130
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Drops the hits that geometry with no handlers hides, so a booth behind a wall or a model, opaque
|
|
134
|
+
* or translucent, is not picked. R3F's `events.filter`, which runs on its nearest-first hits.
|
|
135
|
+
*
|
|
136
|
+
* R3F raycasts only interactive layers, so it cannot see what stands in front of them. This casts
|
|
137
|
+
* the same ray at the whole scene and cuts the hits at the nearest object no interactive layer
|
|
138
|
+
* holds. A wall and a logo on a booth's roof differ only in distance: the logo lies on the roof, and
|
|
139
|
+
* the wall stands apart from it. So the cut keeps a hit up to {@link ELEVATION_PRECISION} behind
|
|
140
|
+
* the occluder, the least separation at which the depth buffer draws two surfaces in order. Nearer
|
|
141
|
+
* than that, the occluder is drawn on the hit, not in front of it. A click with every hit dropped is
|
|
142
|
+
* a miss, as a click on the occluder was before.
|
|
143
|
+
* @param hits - R3F's hits, nearest first.
|
|
144
|
+
* @param state - The root's state, holding the ray already aimed at the pointer.
|
|
145
|
+
* @param space - The scene's space, whose scale turns the plan-unit separation into a world one.
|
|
146
|
+
* @returns The hits in front of the nearest occluder.
|
|
147
|
+
*/
|
|
148
|
+
export function occludeHits(hits, state, space) {
|
|
149
|
+
if (hits.length === 0)
|
|
150
|
+
return hits;
|
|
151
|
+
const { interaction } = state.internal;
|
|
152
|
+
const occluder = state.raycaster
|
|
153
|
+
.intersectObject(state.scene, true)
|
|
154
|
+
.find((hit) => !isInteractive(hit.object, interaction));
|
|
155
|
+
if (!occluder)
|
|
156
|
+
return hits;
|
|
157
|
+
// Along the ray, a pitched view only lengthens the gap between two surfaces, never shortens it.
|
|
158
|
+
const cut = occluder.distance + ELEVATION_PRECISION * space.scale;
|
|
159
|
+
return hits.filter((hit) => hit.distance <= cut);
|
|
160
|
+
}
|
|
131
161
|
/**
|
|
132
162
|
* Where a ray crosses the plan plane — the floor under the pointer, whatever is drawn above it.
|
|
133
163
|
*
|
|
@@ -155,6 +185,19 @@ export function planePoint(ray, space) {
|
|
|
155
185
|
function isDrag(event) {
|
|
156
186
|
return event.delta * event.delta > DRAG_SLOP_SQ_PX;
|
|
157
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Whether an object sits in an interactive layer: it, or an ancestor, carries R3F handlers.
|
|
190
|
+
* @param object - The object hit.
|
|
191
|
+
* @param interaction - R3F's objects with handlers.
|
|
192
|
+
* @returns Whether R3F raycasts it for events.
|
|
193
|
+
*/
|
|
194
|
+
function isInteractive(object, interaction) {
|
|
195
|
+
for (let node = object; node; node = node.parent) {
|
|
196
|
+
if (interaction.includes(node))
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
158
201
|
/**
|
|
159
202
|
* Squared pointer travel, in px², above which a release is a drag and not a click — so ~3.9 px of
|
|
160
203
|
* slop. Squared because that is how the imperative renderer's `dragThreshold` compared it, and this
|
package/dist/space/Scene.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useThree } from '@react-three/fiber';
|
|
3
3
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
4
4
|
import { LayoutDriver } from '../events/LayoutDriver';
|
|
5
|
+
import { occludeHits } from '../events/pointer';
|
|
5
6
|
import { Background } from '../objects/Background';
|
|
6
7
|
import { TextureBudget, TextureBudgetContext } from '../objects/images/texture-budget';
|
|
7
8
|
import { Layer } from '../objects/layer/index';
|
|
@@ -83,6 +84,7 @@ function SceneGraph(props) {
|
|
|
83
84
|
const { viewbox, staticTransform, rootLayer } = sceneDef;
|
|
84
85
|
const { width, height, dpr } = useCanvasSize();
|
|
85
86
|
const invalidate = useThree((state) => state.invalidate);
|
|
87
|
+
const setEvents = useThree((state) => state.setEvents);
|
|
86
88
|
const space = useSpace();
|
|
87
89
|
const [scheduler] = useState(createMountScheduler);
|
|
88
90
|
// One budget for the scene's lifetime; the limit is synced rather than the budget rebuilt, so an
|
|
@@ -117,6 +119,8 @@ function SceneGraph(props) {
|
|
|
117
119
|
if (!memoryLimit)
|
|
118
120
|
logger.debug('no memory limit set, image atlases upload at full size');
|
|
119
121
|
}, [budget, memoryLimit]);
|
|
122
|
+
// Here rather than on either mount, so the renderer's own canvas and a host's both pick through it.
|
|
123
|
+
useEffect(() => setEvents({ filter: (hits, state) => occludeHits(hits, state, space) }), [setEvents, space]);
|
|
120
124
|
useEffect(() => {
|
|
121
125
|
printScene(sceneDef, logger.debug);
|
|
122
126
|
}, [sceneDef]);
|
package/dist/space/clipping.d.ts
CHANGED
|
@@ -52,4 +52,21 @@ export declare function nearPlane(distance: number, scale: number): number | und
|
|
|
52
52
|
* @returns The far plane in world units.
|
|
53
53
|
*/
|
|
54
54
|
export declare function farPlane(maxDistance: number): number;
|
|
55
|
+
/**
|
|
56
|
+
* Minimum z-axis separation between overlapping surfaces, in **plan** units. Picking reads it too:
|
|
57
|
+
* geometry closer than this to a hit is drawn on it, so it does not occlude it (see `occludeHits`).
|
|
58
|
+
*
|
|
59
|
+
* **For plan data providers:** shapes sharing an x/y footprint — an elevated platform over a floor
|
|
60
|
+
* area, layered decorations at one position — must be separated by at least this much on z to be
|
|
61
|
+
* ordered correctly. No minimum horizontal separation is required: screen-pixel density and depth
|
|
62
|
+
* precision both scale with camera distance and with the scene's scale, so any separation visible on
|
|
63
|
+
* screen already has depth margin, at any viewport size, zoom, scale or camera rotation.
|
|
64
|
+
*
|
|
65
|
+
* Deliberately the renderer's own constant rather than `@expofp/geometry`'s `ELEVATION_TOLERANCE`,
|
|
66
|
+
* which happens to hold the same number. They answer different questions — geometry's is "are these
|
|
67
|
+
* two elevations the same point?", this one is "must these two elevations still draw in the right
|
|
68
|
+
* order?" — and importing it would let a tuning of the geometry predicate silently re-tune the depth
|
|
69
|
+
* buffer.
|
|
70
|
+
*/
|
|
71
|
+
export declare const ELEVATION_PRECISION = 0.1;
|
|
55
72
|
//# sourceMappingURL=clipping.d.ts.map
|
package/dist/space/clipping.js
CHANGED
|
@@ -72,7 +72,8 @@ export function farPlane(maxDistance) {
|
|
|
72
72
|
return Math.max(maxDistance, 0.1) * 2;
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
* Minimum z-axis separation between overlapping surfaces, in **plan** units.
|
|
75
|
+
* Minimum z-axis separation between overlapping surfaces, in **plan** units. Picking reads it too:
|
|
76
|
+
* geometry closer than this to a hit is drawn on it, so it does not occlude it (see `occludeHits`).
|
|
76
77
|
*
|
|
77
78
|
* **For plan data providers:** shapes sharing an x/y footprint — an elevated platform over a floor
|
|
78
79
|
* area, layered decorations at one position — must be separated by at least this much on z to be
|
|
@@ -86,7 +87,7 @@ export function farPlane(maxDistance) {
|
|
|
86
87
|
* order?" — and importing it would let a tuning of the geometry predicate silently re-tune the depth
|
|
87
88
|
* buffer.
|
|
88
89
|
*/
|
|
89
|
-
const ELEVATION_PRECISION = 0.1;
|
|
90
|
+
export const ELEVATION_PRECISION = 0.1;
|
|
90
91
|
/**
|
|
91
92
|
* Fraction of the camera's distance kept clear of the near plane at close and default zoom, so
|
|
92
93
|
* depth-buffer levels are not spent on the empty space between the camera and the plan.
|
package/dist/types/events.d.ts
CHANGED
|
@@ -70,8 +70,10 @@ export interface PointerMissedEventData {
|
|
|
70
70
|
* defs, arrive in the order their slots were claimed (mount order), not paint order;
|
|
71
71
|
* `Pointer/Overlap` pins that.
|
|
72
72
|
*
|
|
73
|
-
* **
|
|
74
|
-
*
|
|
73
|
+
* **Everything else occludes.** Geometry in a layer with no handlers is never hit itself, but it
|
|
74
|
+
* hides the defs behind it, opaque or translucent: a booth behind a wall or a model receives no
|
|
75
|
+
* event, and a click there is a miss. Geometry coplanar with a def, such as its label, does not
|
|
76
|
+
* hide it.
|
|
75
77
|
*
|
|
76
78
|
* **Handlers return nothing.** One that mutates defs calls `update()` afterward, like any other code.
|
|
77
79
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expofp/renderer",
|
|
3
|
-
"version": "3.42.
|
|
3
|
+
"version": "3.42.1",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/expofp/efp-app.git",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"troika-three-text": "^0.52.5",
|
|
43
43
|
"tslib": "^2.3.0",
|
|
44
44
|
"webgl-memory": "^1.1.3",
|
|
45
|
-
"@expofp/geometry": "3.42.
|
|
45
|
+
"@expofp/geometry": "3.42.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@react-three/test-renderer": "^9.1.0",
|