@expofp/renderer 0.0.0-experimental.2d6c681
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/README.md +145 -0
- package/dist/Renderer.d.ts +102 -0
- package/dist/Renderer.js +96 -0
- package/dist/controls/CameraControls.d.ts +108 -0
- package/dist/controls/CameraControls.js +203 -0
- package/dist/controls/Controls.d.ts +53 -0
- package/dist/controls/Controls.js +54 -0
- package/dist/controls/camera-api.d.ts +226 -0
- package/dist/controls/camera-api.js +179 -0
- package/dist/controls/gestures.d.ts +71 -0
- package/dist/controls/gestures.js +98 -0
- package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
- package/dist/controls/handlers/DoubleTapZoom.js +82 -0
- package/dist/controls/handlers/Inertia.d.ts +60 -0
- package/dist/controls/handlers/Inertia.js +222 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +212 -0
- package/dist/controls/handlers/Pitch.d.ts +19 -0
- package/dist/controls/handlers/Pitch.js +200 -0
- package/dist/controls/handlers/Roll.d.ts +30 -0
- package/dist/controls/handlers/Roll.js +196 -0
- package/dist/controls/handlers/Zoom.d.ts +28 -0
- package/dist/controls/handlers/Zoom.js +34 -0
- package/dist/controls/hooks.d.ts +136 -0
- package/dist/controls/hooks.js +200 -0
- package/dist/controls/index.d.ts +9 -0
- package/dist/controls/index.js +7 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +108 -0
- package/dist/core/InstanceUniforms.js +298 -0
- package/dist/core/SquareDataTexture.d.ts +277 -0
- package/dist/core/SquareDataTexture.js +476 -0
- package/dist/core/batch/BatchedMesh.d.ts +74 -0
- package/dist/core/batch/BatchedMesh.js +155 -0
- package/dist/core/instanced/FrustumCulling.d.ts +141 -0
- package/dist/core/instanced/FrustumCulling.js +246 -0
- package/dist/core/instanced/InstancedMesh.d.ts +302 -0
- package/dist/core/instanced/InstancedMesh.js +497 -0
- package/dist/core/instanced/Instances.d.ts +119 -0
- package/dist/core/instanced/Instances.js +230 -0
- package/dist/core/instanced/Raycasting.d.ts +118 -0
- package/dist/core/instanced/Raycasting.js +134 -0
- package/dist/core/instanced/instanced-material.d.ts +77 -0
- package/dist/core/instanced/instanced-material.js +228 -0
- package/dist/core/lines/BatchedLines.d.ts +137 -0
- package/dist/core/lines/BatchedLines.js +215 -0
- package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
- package/dist/core/lines/FrustumCullingLines.js +85 -0
- package/dist/core/lines/RaycastingLines.d.ts +45 -0
- package/dist/core/lines/RaycastingLines.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +124 -0
- package/dist/core/shader-source.d.ts +70 -0
- package/dist/core/shader-source.js +110 -0
- package/dist/core/sprites/BatchedSprites.d.ts +115 -0
- package/dist/core/sprites/BatchedSprites.js +173 -0
- package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
- package/dist/core/sprites/RaycastingSprites.js +71 -0
- package/dist/core/sprites/sprite-material.d.ts +59 -0
- package/dist/core/sprites/sprite-material.js +192 -0
- package/dist/core/text/BatchedText.d.ts +233 -0
- package/dist/core/text/BatchedText.js +473 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +289 -0
- package/dist/core/text/Text.d.ts +88 -0
- package/dist/core/text/Text.js +230 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +123 -0
- package/dist/core/text/glyph-packing.d.ts +17 -0
- package/dist/core/text/glyph-packing.js +61 -0
- package/dist/core/typed-array.d.ts +16 -0
- package/dist/core/typed-array.js +20 -0
- package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
- package/dist/debug/components/DebugPanelProvider.js +50 -0
- package/dist/debug/components/DebugTools.d.ts +22 -0
- package/dist/debug/components/DebugTools.js +28 -0
- package/dist/debug/components/Gui.d.ts +22 -0
- package/dist/debug/components/Gui.js +42 -0
- package/dist/debug/components/MemoryInfo.d.ts +22 -0
- package/dist/debug/components/MemoryInfo.js +42 -0
- package/dist/debug/components/Panel.d.ts +31 -0
- package/dist/debug/components/Panel.js +104 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-chunk.d.ts +26 -0
- package/dist/debug/debug-chunk.js +32 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +41 -0
- package/dist/debug/index.js +43 -0
- package/dist/events/LayoutDriver.d.ts +36 -0
- package/dist/events/LayoutDriver.js +192 -0
- package/dist/events/PointerMissed.d.ts +23 -0
- package/dist/events/PointerMissed.js +58 -0
- package/dist/events/def-culling.d.ts +46 -0
- package/dist/events/def-culling.js +69 -0
- package/dist/events/layout-targets.d.ts +151 -0
- package/dist/events/layout-targets.js +397 -0
- package/dist/events/placement/box.d.ts +59 -0
- package/dist/events/placement/box.js +96 -0
- package/dist/events/placement/grid.d.ts +161 -0
- package/dist/events/placement/grid.js +235 -0
- package/dist/events/placement/index.d.ts +2 -0
- package/dist/events/placement/index.js +1 -0
- package/dist/events/placement/pass.d.ts +70 -0
- package/dist/events/placement/pass.js +617 -0
- package/dist/events/pointer.d.ts +194 -0
- package/dist/events/pointer.js +356 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +177 -0
- package/dist/external/external-camera.d.ts +18 -0
- package/dist/external/external-camera.js +103 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +14 -0
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/containers/SlotContainer.d.ts +312 -0
- package/dist/objects/containers/SlotContainer.js +311 -0
- package/dist/objects/containers/hooks.d.ts +61 -0
- package/dist/objects/containers/hooks.js +196 -0
- package/dist/objects/containers/index.d.ts +6 -0
- package/dist/objects/containers/index.js +4 -0
- package/dist/objects/containers/keys.d.ts +10 -0
- package/dist/objects/containers/keys.js +18 -0
- package/dist/objects/containers/mesh-writes.d.ts +106 -0
- package/dist/objects/containers/mesh-writes.js +97 -0
- package/dist/objects/containers/props.d.ts +33 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +152 -0
- package/dist/objects/images/Images.d.ts +49 -0
- package/dist/objects/images/Images.js +394 -0
- package/dist/objects/images/atlas-packer.d.ts +129 -0
- package/dist/objects/images/atlas-packer.js +287 -0
- package/dist/objects/images/image-container.d.ts +74 -0
- package/dist/objects/images/image-container.js +100 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/pack-sources.d.ts +190 -0
- package/dist/objects/images/pack-sources.js +272 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
- package/dist/objects/images/worker/atlas-worker.js +123 -0
- package/dist/objects/images/worker/client.d.ts +62 -0
- package/dist/objects/images/worker/client.js +249 -0
- package/dist/objects/images/worker/index.d.ts +10 -0
- package/dist/objects/images/worker/index.js +1 -0
- package/dist/objects/layer/Layer.d.ts +28 -0
- package/dist/objects/layer/Layer.js +134 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/layer-context.d.ts +74 -0
- package/dist/objects/layer/layer-context.js +82 -0
- package/dist/objects/layer/mount-scheduler.d.ts +131 -0
- package/dist/objects/layer/mount-scheduler.js +289 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -0
- package/dist/objects/lighting/Lighting.d.ts +92 -0
- package/dist/objects/lighting/Lighting.js +384 -0
- package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
- package/dist/objects/lighting/OcclusionPasses.js +271 -0
- package/dist/objects/lighting/index.d.ts +11 -0
- package/dist/objects/lighting/index.js +10 -0
- package/dist/objects/lighting/lighting-gui.d.ts +24 -0
- package/dist/objects/lighting/lighting-gui.js +114 -0
- package/dist/objects/lighting/occluder.d.ts +34 -0
- package/dist/objects/lighting/occluder.js +62 -0
- package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
- package/dist/objects/lighting/occlusion-targets.js +276 -0
- package/dist/objects/lines/LineInstance.d.ts +21 -0
- package/dist/objects/lines/LineInstance.js +73 -0
- package/dist/objects/lines/Lines.d.ts +17 -0
- package/dist/objects/lines/Lines.js +31 -0
- package/dist/objects/lines/index.d.ts +2 -0
- package/dist/objects/lines/index.js +1 -0
- package/dist/objects/lines/line-container.d.ts +35 -0
- package/dist/objects/lines/line-container.js +40 -0
- package/dist/objects/materials/ColorMaterial.d.ts +92 -0
- package/dist/objects/materials/ColorMaterial.js +124 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +74 -0
- package/dist/objects/materials/ModelMaterial.d.ts +53 -0
- package/dist/objects/materials/ModelMaterial.js +111 -0
- package/dist/objects/materials/TextureMaterial.d.ts +33 -0
- package/dist/objects/materials/TextureMaterial.js +48 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
- package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
- package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
- package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
- package/dist/objects/materials/effects/ao/index.d.ts +129 -0
- package/dist/objects/materials/effects/ao/index.js +72 -0
- package/dist/objects/materials/effects/color-dim.d.ts +115 -0
- package/dist/objects/materials/effects/color-dim.js +206 -0
- package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
- package/dist/objects/materials/effects/contact-shadow.js +167 -0
- package/dist/objects/materials/effects/effects-context.d.ts +53 -0
- package/dist/objects/materials/effects/effects-context.js +24 -0
- package/dist/objects/materials/shared.d.ts +24 -0
- package/dist/objects/materials/shared.js +72 -0
- package/dist/objects/models/ModelInstance.d.ts +22 -0
- package/dist/objects/models/ModelInstance.js +33 -0
- package/dist/objects/models/Models.d.ts +22 -0
- package/dist/objects/models/Models.js +186 -0
- package/dist/objects/models/gltf-cache.d.ts +17 -0
- package/dist/objects/models/gltf-cache.js +50 -0
- package/dist/objects/models/index.d.ts +2 -0
- package/dist/objects/models/index.js +1 -0
- package/dist/objects/models/model-asset.d.ts +48 -0
- package/dist/objects/models/model-asset.js +77 -0
- package/dist/objects/models/model-container.d.ts +123 -0
- package/dist/objects/models/model-container.js +207 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +39 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +131 -0
- package/dist/objects/shapes/index.d.ts +2 -0
- package/dist/objects/shapes/index.js +1 -0
- package/dist/objects/shapes/shape-container.d.ts +141 -0
- package/dist/objects/shapes/shape-container.js +242 -0
- package/dist/objects/shapes/shape-geometry.d.ts +27 -0
- package/dist/objects/shapes/shape-geometry.js +94 -0
- package/dist/objects/text/TextInstance.d.ts +22 -0
- package/dist/objects/text/TextInstance.js +95 -0
- package/dist/objects/text/Texts.d.ts +30 -0
- package/dist/objects/text/Texts.js +43 -0
- package/dist/objects/text/index.d.ts +2 -0
- package/dist/objects/text/index.js +1 -0
- package/dist/objects/text/text-container.d.ts +121 -0
- package/dist/objects/text/text-container.js +229 -0
- package/dist/objects/text/text-layout.d.ts +92 -0
- package/dist/objects/text/text-layout.js +180 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +47 -0
- package/dist/space/Scene.js +127 -0
- package/dist/space/Space.d.ts +325 -0
- package/dist/space/Space.js +359 -0
- package/dist/space/VisibleRect.d.ts +45 -0
- package/dist/space/VisibleRect.js +129 -0
- package/dist/space/camera-handle.d.ts +67 -0
- package/dist/space/camera-handle.js +63 -0
- package/dist/space/camera-kind.d.ts +26 -0
- package/dist/space/camera-kind.js +30 -0
- package/dist/space/canvas-size.d.ts +24 -0
- package/dist/space/canvas-size.js +20 -0
- package/dist/space/clipping.d.ts +69 -0
- package/dist/space/clipping.js +130 -0
- package/dist/space/layout-measure.d.ts +67 -0
- package/dist/space/layout-measure.js +175 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +234 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +198 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +21 -0
- package/dist/types/guards.js +52 -0
- package/dist/update/index.d.ts +3 -0
- package/dist/update/index.js +2 -0
- package/dist/update/slot-writers.d.ts +30 -0
- package/dist/update/slot-writers.js +76 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +104 -0
- package/dist/utils/arrays.d.ts +13 -0
- package/dist/utils/arrays.js +20 -0
- package/dist/utils/colors.d.ts +19 -0
- package/dist/utils/colors.js +27 -0
- package/dist/utils/hot-state.d.ts +41 -0
- package/dist/utils/hot-state.js +37 -0
- package/dist/utils/logging.d.ts +29 -0
- package/dist/utils/logging.js +55 -0
- package/dist/utils/math.d.ts +52 -0
- package/dist/utils/math.js +76 -0
- package/package.json +79 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking: which def the pointer is on, and where on the plan.
|
|
3
|
+
*
|
|
4
|
+
* Two hooks are the way in — {@link usePointerProps} makes a layer's group interactive, and
|
|
5
|
+
* {@link usePickFilter} makes the canvas pick the way the renderer draws. Nothing outside this module
|
|
6
|
+
* calls anything else here; the rest is exported so the sibling spec can drive each piece on its own
|
|
7
|
+
* rather than through a mounted canvas.
|
|
8
|
+
*/
|
|
9
|
+
import { type RootState, type ThreeEvent } from '@react-three/fiber';
|
|
10
|
+
import { type Intersection, type Object3D, type Ray, type Raycaster } from 'three';
|
|
11
|
+
import { type PlanPoint, type Space } from '../space/Space';
|
|
12
|
+
import { type LayerDef, type RenderableDef } from '../types/defs';
|
|
13
|
+
import { type PointerHandlerName } from '../types/events';
|
|
14
|
+
/**
|
|
15
|
+
* What picking asks of a mesh's owner, in the owner's own terms — instances and defs, never
|
|
16
|
+
* intersections. Pulling the instance out of an intersection is this module's work, because the
|
|
17
|
+
* intersection is three's; a container only answers about what it allocated.
|
|
18
|
+
*
|
|
19
|
+
* One interface rather than two registrations, because both answers have one owner and arrive at one
|
|
20
|
+
* moment. `SlotContainer` satisfies it as it stands, so a container registers itself.
|
|
21
|
+
*/
|
|
22
|
+
export interface HitSource {
|
|
23
|
+
/**
|
|
24
|
+
* The def a mesh instance belongs to, or `undefined` when none does — an instance released between
|
|
25
|
+
* the raycast and the dispatch, or a mesh whose picking is not implemented.
|
|
26
|
+
* @param instance - The instance the mesh reported.
|
|
27
|
+
* @returns The def.
|
|
28
|
+
*/
|
|
29
|
+
defAt(instance: number | undefined): RenderableDef | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Where a def sits in this mesh's draw order, counted from the one drawn first.
|
|
32
|
+
* @param def - The def to place.
|
|
33
|
+
* @returns Its place in the draw order.
|
|
34
|
+
*/
|
|
35
|
+
paintOrderOf(def: RenderableDef): number;
|
|
36
|
+
}
|
|
37
|
+
/** The picking props {@link usePointerProps} builds for a layer's `<group>`. */
|
|
38
|
+
export interface PointerGroupProps {
|
|
39
|
+
/** Refuses the raycast traversal into the subtree while the layer is off screen. */
|
|
40
|
+
raycast(): false | undefined;
|
|
41
|
+
/** Dispatches a click in the layer's subtree; attached only when the def declares one. */
|
|
42
|
+
onClick?: (event: ThreeEvent<MouseEvent>) => void;
|
|
43
|
+
/** Dispatches a move over the layer's subtree; attached only when the def declares one. */
|
|
44
|
+
onPointerMove?: (event: ThreeEvent<MouseEvent>) => void;
|
|
45
|
+
/** Dispatches the pointer leaving a def; attached only when the def declares one. */
|
|
46
|
+
onPointerOut?: (event: ThreeEvent<MouseEvent>) => void;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The props that make a layer's group interactive: one dispatching handler per handler the def
|
|
50
|
+
* declares, and the raycast gate that un-picks the layer while it is off screen.
|
|
51
|
+
*
|
|
52
|
+
* Presence is the contract, in both directions. R3F raycasts only objects carrying handlers — and
|
|
53
|
+
* raycasts them with their whole subtree — so a handler is attached exactly when the def declares
|
|
54
|
+
* one and the layer is on screen: a layer wanting clicks alone must not be raycast on every pointer
|
|
55
|
+
* move, and a layer that is hidden, or under something hidden, must not be raycast at all. Which
|
|
56
|
+
* function a handler currently is stays out of it: dispatch looks the handler up by name on the def
|
|
57
|
+
* at event time, so swapping one takes effect with no render.
|
|
58
|
+
*
|
|
59
|
+
* Handler presence is read here as values (`layer.onClick !== undefined`) rather than through
|
|
60
|
+
* `layer` inside a callback: memoization compares what a render *read*, and a property reached only
|
|
61
|
+
* from inside a function is not that — a compiled cache keyed on the def object, whose identity
|
|
62
|
+
* never changes, would keep serving a group with no handler attached.
|
|
63
|
+
*
|
|
64
|
+
* The `raycast` gate covers the direction handler absence cannot: an interactive *ancestor* is
|
|
65
|
+
* raycast with everything beneath it, and `false` is three's own signal to stop descending —
|
|
66
|
+
* `Raycaster`'s `intersect()` keeps walking a subtree only while each object's `raycast` returns
|
|
67
|
+
* something other than `false`. Visibility is read at raycast time rather than swapped per render,
|
|
68
|
+
* since R3F ignores a prop set to `undefined` and a blocking version would stick.
|
|
69
|
+
* @param layer - The layer the group renders.
|
|
70
|
+
* @param visible - Whether the layer is on screen, ancestors included.
|
|
71
|
+
* @returns Props to spread onto the layer's `<group>`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function usePointerProps(layer: LayerDef, visible: boolean): PointerGroupProps;
|
|
74
|
+
/**
|
|
75
|
+
* Makes the canvas pick the way the renderer draws, for as long as the component is mounted.
|
|
76
|
+
*
|
|
77
|
+
* R3F's `events.filter` is the one hook into picking that sees the whole hit list, which is why both
|
|
78
|
+
* corrections live there: {@link occludeHits} drops what hidden geometry stands in front of, and
|
|
79
|
+
* {@link orderCoplanarHits} settles what the depth buffer never ordered. Both are the renderer's own
|
|
80
|
+
* reading of its scene, so the whole arrangement is here rather than at whichever component mounts
|
|
81
|
+
* it — a caller wires picking by calling this, and nothing outside this module has to know that there
|
|
82
|
+
* are two halves or which order they run in.
|
|
83
|
+
*/
|
|
84
|
+
export declare function usePickFilter(): void;
|
|
85
|
+
/**
|
|
86
|
+
* Hands one of R3F's intersection events to a layer's handler, as the narrow event the public API
|
|
87
|
+
* describes: the def that was hit, and where the pointer sits on the plan plane.
|
|
88
|
+
*
|
|
89
|
+
* Two hits are dropped rather than reported. One the renderer cannot trace to a def — a released
|
|
90
|
+
* slot, or a mesh whose picking is not implemented — because a handler has nothing to act on without
|
|
91
|
+
* it. And a click that ends a drag: R3F fires `onClick` on release whenever the press landed on the
|
|
92
|
+
* same object, which on a pannable plan would select whatever the pan started over.
|
|
93
|
+
*
|
|
94
|
+
* The handler is called with no receiver, so a handler shared across layers reads `event.def` rather
|
|
95
|
+
* than the layer it was declared on.
|
|
96
|
+
* @param layer - The layer whose handler should run.
|
|
97
|
+
* @param name - Which handler to run.
|
|
98
|
+
* @param event - R3F's intersection event.
|
|
99
|
+
* @param space - The scene's space, which the event's world coordinates are reported through.
|
|
100
|
+
*/
|
|
101
|
+
export declare function dispatchPointer(layer: LayerDef, name: PointerHandlerName, event: ThreeEvent<MouseEvent>, space: Space): void;
|
|
102
|
+
/**
|
|
103
|
+
* Registers what speaks for a mesh's intersections, for as long as the mesh is mounted — so the
|
|
104
|
+
* dispatch and ordering paths stay free of per-mesh-type knowledge.
|
|
105
|
+
* @param mesh - The mesh whose hits the source speaks for.
|
|
106
|
+
* @param source - What answers for them.
|
|
107
|
+
* @returns An unregister function, safe to call after another source replaced this one.
|
|
108
|
+
*/
|
|
109
|
+
export declare function registerHitSource(mesh: Object3D, source: HitSource): () => void;
|
|
110
|
+
/**
|
|
111
|
+
* The def an intersection hit.
|
|
112
|
+
*
|
|
113
|
+
* A mesh with no registered source reports nothing, which is how a primitive whose picking is not
|
|
114
|
+
* implemented behaves as if its layer were not interactive: the hit is dropped and no handler runs.
|
|
115
|
+
* @param hit - The intersection to trace.
|
|
116
|
+
* @returns The def, or `undefined` when the hit cannot be traced to one.
|
|
117
|
+
*/
|
|
118
|
+
export declare function resolveHit(hit: Intersection): RenderableDef | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Where a missed click landed, in plan units.
|
|
121
|
+
*
|
|
122
|
+
* **A miss is not an empty canvas.** It means no handler took the click: a layer declaring no
|
|
123
|
+
* pointer handlers is never raycast by R3F in the first place, so the pointer may be sitting
|
|
124
|
+
* squarely on a wall, a model or a booth that simply does not listen. Searching the scene here is
|
|
125
|
+
* what makes the reported position agree with what the user is looking at — the same answer a hit
|
|
126
|
+
* event carries, arrived at a moment later and with no def to name.
|
|
127
|
+
*
|
|
128
|
+
* The search prunes hidden content for free: {@link usePointerProps} refuses the traversal into a
|
|
129
|
+
* hidden layer's subtree, which is three's own signal to stop descending.
|
|
130
|
+
* @param raycaster - The scene's raycaster, holding the ray R3F cast for this click.
|
|
131
|
+
* @param scene - The scene to search.
|
|
132
|
+
* @param space - The scene's space.
|
|
133
|
+
* @returns The nearest geometry under the pointer, or — where there is none — the floor beneath it.
|
|
134
|
+
*/
|
|
135
|
+
export declare function missedPoint(raycaster: Raycaster, scene: Object3D, space: Space): PlanPoint;
|
|
136
|
+
/**
|
|
137
|
+
* Drops the hits that geometry with no handlers hides, so a booth behind a wall or a model, opaque
|
|
138
|
+
* or translucent, is not picked. The first half of R3F's `events.filter`, which runs on its
|
|
139
|
+
* nearest-first hits; {@link orderCoplanarHits} is the second, over what survives.
|
|
140
|
+
*
|
|
141
|
+
* R3F raycasts only interactive layers, so it cannot see what stands in front of them. This casts
|
|
142
|
+
* the same ray at the whole scene and cuts the hits at the nearest object no interactive layer
|
|
143
|
+
* holds. A wall and a logo on a booth's roof differ only in distance: the logo lies on the roof, and
|
|
144
|
+
* the wall stands apart from it. So the cut keeps a hit up to {@link depthSeparation} behind the
|
|
145
|
+
* occluder, the least separation at which the depth buffer draws two surfaces in order. Nearer
|
|
146
|
+
* than that, the occluder is drawn on the hit, not in front of it. A click with every hit dropped is
|
|
147
|
+
* a miss, as a click on the occluder was before.
|
|
148
|
+
* @param hits - R3F's hits, nearest first.
|
|
149
|
+
* @param state - The root's state, holding the ray already aimed at the pointer.
|
|
150
|
+
* @param space - The scene's space, whose scale turns the plan-unit separation into a world one.
|
|
151
|
+
* @returns The hits in front of the nearest occluder.
|
|
152
|
+
*/
|
|
153
|
+
export declare function occludeHits(hits: Intersection[], state: RootState, space: Space): Intersection[];
|
|
154
|
+
/**
|
|
155
|
+
* Reorders the hits the depth buffer never separated, so the one drawn last is picked first. R3F's
|
|
156
|
+
* `events.filter`, after {@link occludeHits} has cut away what is hidden.
|
|
157
|
+
*
|
|
158
|
+
* Three sorts its intersections by distance alone, and two coplanar polygons are not at two
|
|
159
|
+
* distances — they are at one distance the ray-triangle math resolves a little differently for each
|
|
160
|
+
* of them. Which one R3F dispatches first is that float noise, so a click on the overlap selects
|
|
161
|
+
* one shape on one frame and its neighbor on the next. Sorting stably does not help: the distances
|
|
162
|
+
* genuinely differ rather than tying, and the order three produced them in is a mesh's free list,
|
|
163
|
+
* which `SlotContainer` keeps deliberately apart from paint order.
|
|
164
|
+
*
|
|
165
|
+
* So the tie is settled by what the user is already looking at. Hits within
|
|
166
|
+
* {@link depthSeparation} of the nearest of their run are one depth, because that is the separation
|
|
167
|
+
* below which the depth buffer stops ordering surfaces and draw order alone decides what is on top
|
|
168
|
+
* — the same rule {@link occludeHits} cuts by. Within a run they are ordered by where they draw,
|
|
169
|
+
* last first, along the three keys three's own painter sort reads in the same order: the layer
|
|
170
|
+
* group they hang under, the batch's place in that layer, then the instance's place in the batch.
|
|
171
|
+
*
|
|
172
|
+
* A run is measured from its own first hit rather than from the hit before it, so a stack of
|
|
173
|
+
* surfaces each just inside the separation is still cut into runs instead of chaining into one.
|
|
174
|
+
* @param hits - R3F's hits, nearest first.
|
|
175
|
+
* @param space - The scene's space, whose scale turns the plan-unit separation into a world one.
|
|
176
|
+
* @returns The hits in pick order.
|
|
177
|
+
*/
|
|
178
|
+
export declare function orderCoplanarHits(hits: Intersection[], space: Space): Intersection[];
|
|
179
|
+
/**
|
|
180
|
+
* Where a ray crosses the plan plane — the floor under the pointer, whatever is drawn above it.
|
|
181
|
+
*
|
|
182
|
+
* The fallback for a click that reached neither a handler nor any geometry at all: past the edge of
|
|
183
|
+
* the plan there is nothing to report but the ground the ray carries on to.
|
|
184
|
+
*
|
|
185
|
+
* Both the crossing and the fallback are taken through the scene's space, because R3F casts its rays
|
|
186
|
+
* in world coordinates and a scene fitted to the canvas draws a plan unit as some other number of
|
|
187
|
+
* those. Without it the reported position is the world one wearing plan units' name.
|
|
188
|
+
* @param ray - The ray the pointer cast, in world space.
|
|
189
|
+
* @param space - The scene's space.
|
|
190
|
+
* @returns The crossing, in plan units, or — for a ray running parallel to the plan, which crosses
|
|
191
|
+
* it nowhere — where the ray started.
|
|
192
|
+
*/
|
|
193
|
+
export declare function planePoint(ray: Ray, space: Space): PlanPoint;
|
|
194
|
+
//# sourceMappingURL=pointer.d.ts.map
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picking: which def the pointer is on, and where on the plan.
|
|
3
|
+
*
|
|
4
|
+
* Two hooks are the way in — {@link usePointerProps} makes a layer's group interactive, and
|
|
5
|
+
* {@link usePickFilter} makes the canvas pick the way the renderer draws. Nothing outside this module
|
|
6
|
+
* calls anything else here; the rest is exported so the sibling spec can drive each piece on its own
|
|
7
|
+
* rather than through a mounted canvas.
|
|
8
|
+
*/
|
|
9
|
+
import { useThree } from '@react-three/fiber';
|
|
10
|
+
import { useEffect } from 'react';
|
|
11
|
+
import { depthSeparation } from '../space/clipping';
|
|
12
|
+
import { useSpace } from '../space/Space';
|
|
13
|
+
import { hotState } from '../utils/hot-state';
|
|
14
|
+
/**
|
|
15
|
+
* The props that make a layer's group interactive: one dispatching handler per handler the def
|
|
16
|
+
* declares, and the raycast gate that un-picks the layer while it is off screen.
|
|
17
|
+
*
|
|
18
|
+
* Presence is the contract, in both directions. R3F raycasts only objects carrying handlers — and
|
|
19
|
+
* raycasts them with their whole subtree — so a handler is attached exactly when the def declares
|
|
20
|
+
* one and the layer is on screen: a layer wanting clicks alone must not be raycast on every pointer
|
|
21
|
+
* move, and a layer that is hidden, or under something hidden, must not be raycast at all. Which
|
|
22
|
+
* function a handler currently is stays out of it: dispatch looks the handler up by name on the def
|
|
23
|
+
* at event time, so swapping one takes effect with no render.
|
|
24
|
+
*
|
|
25
|
+
* Handler presence is read here as values (`layer.onClick !== undefined`) rather than through
|
|
26
|
+
* `layer` inside a callback: memoization compares what a render *read*, and a property reached only
|
|
27
|
+
* from inside a function is not that — a compiled cache keyed on the def object, whose identity
|
|
28
|
+
* never changes, would keep serving a group with no handler attached.
|
|
29
|
+
*
|
|
30
|
+
* The `raycast` gate covers the direction handler absence cannot: an interactive *ancestor* is
|
|
31
|
+
* raycast with everything beneath it, and `false` is three's own signal to stop descending —
|
|
32
|
+
* `Raycaster`'s `intersect()` keeps walking a subtree only while each object's `raycast` returns
|
|
33
|
+
* something other than `false`. Visibility is read at raycast time rather than swapped per render,
|
|
34
|
+
* since R3F ignores a prop set to `undefined` and a blocking version would stick.
|
|
35
|
+
* @param layer - The layer the group renders.
|
|
36
|
+
* @param visible - Whether the layer is on screen, ancestors included.
|
|
37
|
+
* @returns Props to spread onto the layer's `<group>`.
|
|
38
|
+
*/
|
|
39
|
+
export function usePointerProps(layer, visible) {
|
|
40
|
+
// Pointer positions are reported on the plan, and R3F casts its rays in world coordinates.
|
|
41
|
+
const space = useSpace();
|
|
42
|
+
const handlesClick = visible && layer.onClick !== undefined;
|
|
43
|
+
const handlesPointerMove = visible && layer.onPointerMove !== undefined;
|
|
44
|
+
const handlesPointerOut = visible && layer.onPointerOut !== undefined;
|
|
45
|
+
function handler(name) {
|
|
46
|
+
return (event) => dispatchPointer(layer, name, event, space);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
raycast: () => (visible ? undefined : false),
|
|
50
|
+
onClick: handlesClick ? handler('onClick') : undefined,
|
|
51
|
+
onPointerMove: handlesPointerMove ? handler('onPointerMove') : undefined,
|
|
52
|
+
onPointerOut: handlesPointerOut ? handler('onPointerOut') : undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Makes the canvas pick the way the renderer draws, for as long as the component is mounted.
|
|
57
|
+
*
|
|
58
|
+
* R3F's `events.filter` is the one hook into picking that sees the whole hit list, which is why both
|
|
59
|
+
* corrections live there: {@link occludeHits} drops what hidden geometry stands in front of, and
|
|
60
|
+
* {@link orderCoplanarHits} settles what the depth buffer never ordered. Both are the renderer's own
|
|
61
|
+
* reading of its scene, so the whole arrangement is here rather than at whichever component mounts
|
|
62
|
+
* it — a caller wires picking by calling this, and nothing outside this module has to know that there
|
|
63
|
+
* are two halves or which order they run in.
|
|
64
|
+
*/
|
|
65
|
+
export function usePickFilter() {
|
|
66
|
+
const setEvents = useThree((state) => state.setEvents);
|
|
67
|
+
// Picking is reported on the plan, and R3F casts its rays in world coordinates.
|
|
68
|
+
const space = useSpace();
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
setEvents({
|
|
71
|
+
filter: (hits, state) => {
|
|
72
|
+
// Cut first, then order: dropping what is hidden cannot move the survivors along the ray,
|
|
73
|
+
// and ordering hits that were about to be dropped is work for nothing.
|
|
74
|
+
const visible = occludeHits(hits, state, space);
|
|
75
|
+
return orderCoplanarHits(visible, space);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}, [setEvents, space]);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Hands one of R3F's intersection events to a layer's handler, as the narrow event the public API
|
|
82
|
+
* describes: the def that was hit, and where the pointer sits on the plan plane.
|
|
83
|
+
*
|
|
84
|
+
* Two hits are dropped rather than reported. One the renderer cannot trace to a def — a released
|
|
85
|
+
* slot, or a mesh whose picking is not implemented — because a handler has nothing to act on without
|
|
86
|
+
* it. And a click that ends a drag: R3F fires `onClick` on release whenever the press landed on the
|
|
87
|
+
* same object, which on a pannable plan would select whatever the pan started over.
|
|
88
|
+
*
|
|
89
|
+
* The handler is called with no receiver, so a handler shared across layers reads `event.def` rather
|
|
90
|
+
* than the layer it was declared on.
|
|
91
|
+
* @param layer - The layer whose handler should run.
|
|
92
|
+
* @param name - Which handler to run.
|
|
93
|
+
* @param event - R3F's intersection event.
|
|
94
|
+
* @param space - The scene's space, which the event's world coordinates are reported through.
|
|
95
|
+
*/
|
|
96
|
+
export function dispatchPointer(layer, name, event, space) {
|
|
97
|
+
const handler = layer[name];
|
|
98
|
+
if (!handler)
|
|
99
|
+
return;
|
|
100
|
+
if (name === 'onClick' && isDrag(event))
|
|
101
|
+
return;
|
|
102
|
+
const def = resolveHit(event);
|
|
103
|
+
if (!def)
|
|
104
|
+
return;
|
|
105
|
+
handler({
|
|
106
|
+
def,
|
|
107
|
+
// The ray's own intersection with the geometry, which three computed against the real
|
|
108
|
+
// triangles — so an elevated def reports the point on *it*, elevation and all, rather than the
|
|
109
|
+
// ground the ray carries on to behind it. R3F reports it in world coordinates; the cast is the
|
|
110
|
+
// assertion of which space that is.
|
|
111
|
+
point: space.worldToPlan(event.point),
|
|
112
|
+
stopPropagation: () => event.stopPropagation(),
|
|
113
|
+
nativeEvent: event.nativeEvent,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Registers what speaks for a mesh's intersections, for as long as the mesh is mounted — so the
|
|
118
|
+
* dispatch and ordering paths stay free of per-mesh-type knowledge.
|
|
119
|
+
* @param mesh - The mesh whose hits the source speaks for.
|
|
120
|
+
* @param source - What answers for them.
|
|
121
|
+
* @returns An unregister function, safe to call after another source replaced this one.
|
|
122
|
+
*/
|
|
123
|
+
export function registerHitSource(mesh, source) {
|
|
124
|
+
sources.set(mesh, source);
|
|
125
|
+
return () => {
|
|
126
|
+
if (sources.get(mesh) === source)
|
|
127
|
+
sources.delete(mesh);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The def an intersection hit.
|
|
132
|
+
*
|
|
133
|
+
* A mesh with no registered source reports nothing, which is how a primitive whose picking is not
|
|
134
|
+
* implemented behaves as if its layer were not interactive: the hit is dropped and no handler runs.
|
|
135
|
+
* @param hit - The intersection to trace.
|
|
136
|
+
* @returns The def, or `undefined` when the hit cannot be traced to one.
|
|
137
|
+
*/
|
|
138
|
+
export function resolveHit(hit) {
|
|
139
|
+
return sources.get(hit.object)?.defAt(hit.instanceId);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Where a missed click landed, in plan units.
|
|
143
|
+
*
|
|
144
|
+
* **A miss is not an empty canvas.** It means no handler took the click: a layer declaring no
|
|
145
|
+
* pointer handlers is never raycast by R3F in the first place, so the pointer may be sitting
|
|
146
|
+
* squarely on a wall, a model or a booth that simply does not listen. Searching the scene here is
|
|
147
|
+
* what makes the reported position agree with what the user is looking at — the same answer a hit
|
|
148
|
+
* event carries, arrived at a moment later and with no def to name.
|
|
149
|
+
*
|
|
150
|
+
* The search prunes hidden content for free: {@link usePointerProps} refuses the traversal into a
|
|
151
|
+
* hidden layer's subtree, which is three's own signal to stop descending.
|
|
152
|
+
* @param raycaster - The scene's raycaster, holding the ray R3F cast for this click.
|
|
153
|
+
* @param scene - The scene to search.
|
|
154
|
+
* @param space - The scene's space.
|
|
155
|
+
* @returns The nearest geometry under the pointer, or — where there is none — the floor beneath it.
|
|
156
|
+
*/
|
|
157
|
+
export function missedPoint(raycaster, scene, space) {
|
|
158
|
+
// Sorted nearest-first by three, so the first hit is the one the user is looking at.
|
|
159
|
+
const [nearest] = raycaster.intersectObject(scene, true);
|
|
160
|
+
if (nearest)
|
|
161
|
+
return space.worldToPlan(nearest.point);
|
|
162
|
+
return planePoint(raycaster.ray, space);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Drops the hits that geometry with no handlers hides, so a booth behind a wall or a model, opaque
|
|
166
|
+
* or translucent, is not picked. The first half of R3F's `events.filter`, which runs on its
|
|
167
|
+
* nearest-first hits; {@link orderCoplanarHits} is the second, over what survives.
|
|
168
|
+
*
|
|
169
|
+
* R3F raycasts only interactive layers, so it cannot see what stands in front of them. This casts
|
|
170
|
+
* the same ray at the whole scene and cuts the hits at the nearest object no interactive layer
|
|
171
|
+
* holds. A wall and a logo on a booth's roof differ only in distance: the logo lies on the roof, and
|
|
172
|
+
* the wall stands apart from it. So the cut keeps a hit up to {@link depthSeparation} behind the
|
|
173
|
+
* occluder, the least separation at which the depth buffer draws two surfaces in order. Nearer
|
|
174
|
+
* than that, the occluder is drawn on the hit, not in front of it. A click with every hit dropped is
|
|
175
|
+
* a miss, as a click on the occluder was before.
|
|
176
|
+
* @param hits - R3F's hits, nearest first.
|
|
177
|
+
* @param state - The root's state, holding the ray already aimed at the pointer.
|
|
178
|
+
* @param space - The scene's space, whose scale turns the plan-unit separation into a world one.
|
|
179
|
+
* @returns The hits in front of the nearest occluder.
|
|
180
|
+
*/
|
|
181
|
+
export function occludeHits(hits, state, space) {
|
|
182
|
+
if (hits.length === 0)
|
|
183
|
+
return hits;
|
|
184
|
+
const { interaction } = state.internal;
|
|
185
|
+
const occluder = state.raycaster
|
|
186
|
+
.intersectObject(state.scene, true)
|
|
187
|
+
.find((hit) => !isInteractive(hit.object, interaction));
|
|
188
|
+
if (!occluder)
|
|
189
|
+
return hits;
|
|
190
|
+
// Along the ray, a pitched view only lengthens the gap between two surfaces, never shortens it.
|
|
191
|
+
const cut = occluder.distance + depthSeparation(space.scale);
|
|
192
|
+
return hits.filter((hit) => hit.distance <= cut);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Reorders the hits the depth buffer never separated, so the one drawn last is picked first. R3F's
|
|
196
|
+
* `events.filter`, after {@link occludeHits} has cut away what is hidden.
|
|
197
|
+
*
|
|
198
|
+
* Three sorts its intersections by distance alone, and two coplanar polygons are not at two
|
|
199
|
+
* distances — they are at one distance the ray-triangle math resolves a little differently for each
|
|
200
|
+
* of them. Which one R3F dispatches first is that float noise, so a click on the overlap selects
|
|
201
|
+
* one shape on one frame and its neighbor on the next. Sorting stably does not help: the distances
|
|
202
|
+
* genuinely differ rather than tying, and the order three produced them in is a mesh's free list,
|
|
203
|
+
* which `SlotContainer` keeps deliberately apart from paint order.
|
|
204
|
+
*
|
|
205
|
+
* So the tie is settled by what the user is already looking at. Hits within
|
|
206
|
+
* {@link depthSeparation} of the nearest of their run are one depth, because that is the separation
|
|
207
|
+
* below which the depth buffer stops ordering surfaces and draw order alone decides what is on top
|
|
208
|
+
* — the same rule {@link occludeHits} cuts by. Within a run they are ordered by where they draw,
|
|
209
|
+
* last first, along the three keys three's own painter sort reads in the same order: the layer
|
|
210
|
+
* group they hang under, the batch's place in that layer, then the instance's place in the batch.
|
|
211
|
+
*
|
|
212
|
+
* A run is measured from its own first hit rather than from the hit before it, so a stack of
|
|
213
|
+
* surfaces each just inside the separation is still cut into runs instead of chaining into one.
|
|
214
|
+
* @param hits - R3F's hits, nearest first.
|
|
215
|
+
* @param space - The scene's space, whose scale turns the plan-unit separation into a world one.
|
|
216
|
+
* @returns The hits in pick order.
|
|
217
|
+
*/
|
|
218
|
+
export function orderCoplanarHits(hits, space) {
|
|
219
|
+
if (hits.length < 2)
|
|
220
|
+
return hits;
|
|
221
|
+
const separation = depthSeparation(space.scale);
|
|
222
|
+
const ordered = [];
|
|
223
|
+
for (let start = 0; start < hits.length;) {
|
|
224
|
+
let end = start + 1;
|
|
225
|
+
while (end < hits.length) {
|
|
226
|
+
const gap = hits[end].distance - hits[start].distance;
|
|
227
|
+
// A step *back* toward the camera ends the run rather than extending it. R3F sorts by event
|
|
228
|
+
// priority first and distance second, so a second root's hits follow the first root's as their
|
|
229
|
+
// own nearest-first list — two scenes, whose hits must not be ordered against each other
|
|
230
|
+
// however near the numbers land.
|
|
231
|
+
if (gap < 0 || gap > separation)
|
|
232
|
+
break;
|
|
233
|
+
end++;
|
|
234
|
+
}
|
|
235
|
+
const run = hits.slice(start, end);
|
|
236
|
+
// Reversed, so the hit drawn last comes first. `sort` is stable, so hits nothing here orders —
|
|
237
|
+
// a primitive whose picking is not implemented, two triangles of one instance — keep the
|
|
238
|
+
// nearest-first order three gave them rather than being shuffled by a comparator with nothing
|
|
239
|
+
// to say about them.
|
|
240
|
+
if (run.length > 1)
|
|
241
|
+
run.sort((a, b) => compareDrawOrder(b, a));
|
|
242
|
+
ordered.push(...run);
|
|
243
|
+
start = end;
|
|
244
|
+
}
|
|
245
|
+
return ordered;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Where a ray crosses the plan plane — the floor under the pointer, whatever is drawn above it.
|
|
249
|
+
*
|
|
250
|
+
* The fallback for a click that reached neither a handler nor any geometry at all: past the edge of
|
|
251
|
+
* the plan there is nothing to report but the ground the ray carries on to.
|
|
252
|
+
*
|
|
253
|
+
* Both the crossing and the fallback are taken through the scene's space, because R3F casts its rays
|
|
254
|
+
* in world coordinates and a scene fitted to the canvas draws a plan unit as some other number of
|
|
255
|
+
* those. Without it the reported position is the world one wearing plan units' name.
|
|
256
|
+
* @param ray - The ray the pointer cast, in world space.
|
|
257
|
+
* @param space - The scene's space.
|
|
258
|
+
* @returns The crossing, in plan units, or — for a ray running parallel to the plan, which crosses
|
|
259
|
+
* it nowhere — where the ray started.
|
|
260
|
+
*/
|
|
261
|
+
export function planePoint(ray, space) {
|
|
262
|
+
return space.rayToPlan(ray) ?? space.worldToPlan(ray.origin);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Whether a press-and-release traveled far enough to be a drag rather than a click.
|
|
266
|
+
*
|
|
267
|
+
* R3F's `delta` is the pointer's travel since the press, rounded to whole pixels.
|
|
268
|
+
* @param event - The click event to judge.
|
|
269
|
+
* @returns Whether to treat it as a drag.
|
|
270
|
+
*/
|
|
271
|
+
function isDrag(event) {
|
|
272
|
+
return event.delta * event.delta > DRAG_SLOP_SQ_PX;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Compares two hits by where they draw, earliest first.
|
|
276
|
+
*
|
|
277
|
+
* The three keys are three's own, read in three's order: a mesh inherits the `renderOrder` of its
|
|
278
|
+
* nearest ancestor group as its group order, which outranks its own `renderOrder`, which outranks
|
|
279
|
+
* anything within the mesh. Here that reads as the layer, then the batch, then the instance.
|
|
280
|
+
*
|
|
281
|
+
* **The layer is a key of its own because the two numberings are not one scale.** A layer's
|
|
282
|
+
* `<group>` carries its place in the whole tree's draw order, subdivided inside `[0, 2)`, while a
|
|
283
|
+
* batch mesh carries its kind's index among its layer's batches — `0`, `1`, `2`. So a batch is never
|
|
284
|
+
* comparable to a batch of another layer, and the mesh numbers alone would put the second batch of
|
|
285
|
+
* an early layer above the first batch of a late one. Collapsing them would mean giving every batch
|
|
286
|
+
* its layer's place plus a fraction, which is exactly the mantissa `Layer` keeps free for nesting.
|
|
287
|
+
*
|
|
288
|
+
* Three's own comparator is unreachable: `painterSortStable` is private to its WebGL renderer, and
|
|
289
|
+
* `renderer.renderLists` holds only the per-object answer — the instance key, the one this bug is
|
|
290
|
+
* about, exists nowhere in it.
|
|
291
|
+
*
|
|
292
|
+
* Two hits on one mesh share both object keys, and two hits on different meshes are nearly always
|
|
293
|
+
* separated by one of them, since a layer's place is unique in the tree and a batch's is unique in its
|
|
294
|
+
* layer. So the branches are exclusive in practice, which is also what keeps one mesh's places from
|
|
295
|
+
* ever being compared against another's — those count from zero independently and mean nothing
|
|
296
|
+
* against each other. Where two meshes do tie on both — the background quad draws level with the
|
|
297
|
+
* first layer, and three settles that by `material.id` — this reports nothing and the stable sort
|
|
298
|
+
* leaves them nearest-first.
|
|
299
|
+
* @param a - The first hit.
|
|
300
|
+
* @param b - The second hit.
|
|
301
|
+
* @returns Negative when `a` draws first, positive when `b` does, zero when nothing orders them.
|
|
302
|
+
*/
|
|
303
|
+
function compareDrawOrder(a, b) {
|
|
304
|
+
if (a.object !== b.object) {
|
|
305
|
+
return (groupOrderOf(a.object) - groupOrderOf(b.object) || a.object.renderOrder - b.object.renderOrder);
|
|
306
|
+
}
|
|
307
|
+
const source = sources.get(a.object);
|
|
308
|
+
if (!source)
|
|
309
|
+
return 0;
|
|
310
|
+
const first = source.defAt(a.instanceId);
|
|
311
|
+
const second = source.defAt(b.instanceId);
|
|
312
|
+
// A hit traced to no def reports the first place rather than the last: nothing is known about
|
|
313
|
+
// where it drew, and that must not outrank a def whose place is known.
|
|
314
|
+
return (first ? source.paintOrderOf(first) : 0) - (second ? source.paintOrderOf(second) : 0);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* The group order three would give an object: the `renderOrder` of its nearest ancestor group,
|
|
318
|
+
* which for anything a layer holds is that layer's place in the tree's draw order.
|
|
319
|
+
* @param object - The object to place.
|
|
320
|
+
* @returns The group order, or `0` for an object under no group at all.
|
|
321
|
+
*/
|
|
322
|
+
function groupOrderOf(object) {
|
|
323
|
+
for (let node = object.parent; node; node = node.parent) {
|
|
324
|
+
if (node.isGroup)
|
|
325
|
+
return node.renderOrder;
|
|
326
|
+
}
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Whether an object sits in an interactive layer: it, or an ancestor, carries R3F handlers.
|
|
331
|
+
* @param object - The object hit.
|
|
332
|
+
* @param interaction - R3F's objects with handlers.
|
|
333
|
+
* @returns Whether R3F raycasts it for events.
|
|
334
|
+
*/
|
|
335
|
+
function isInteractive(object, interaction) {
|
|
336
|
+
for (let node = object; node; node = node.parent) {
|
|
337
|
+
if (interaction.includes(node))
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Squared pointer travel, in px², above which a release is a drag and not a click — so ~3.9 px of
|
|
344
|
+
* slop. Squared because that is how the imperative renderer's `dragThreshold` compared it, and this
|
|
345
|
+
* reproduces its feel exactly; a threshold in whole pixels would be four times looser.
|
|
346
|
+
*/
|
|
347
|
+
const DRAG_SLOP_SQ_PX = 15;
|
|
348
|
+
/**
|
|
349
|
+
* What speaks for each mounted container's mesh, keyed weakly by mesh identity — so nothing has to
|
|
350
|
+
* be cleaned up per renderer, and two renderers holding the same defs never collide.
|
|
351
|
+
*
|
|
352
|
+
* Kept across hot updates, because a container registers from a mount effect that a re-render does
|
|
353
|
+
* not re-run: a fresh map would leave every mesh in the scene unpickable until a full reload, which
|
|
354
|
+
* is what editing anything in this module's import graph used to do (see {@link hotState}).
|
|
355
|
+
*/
|
|
356
|
+
const sources = hotState(import.meta.hot, 'sources', () => new WeakMap());
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* What a host that owns the canvas hands over — a MapLibre custom layer in practice. `Renderer`
|
|
4
|
+
* carries all three as optional props ({@link RendererProps} extends the partial of this), and
|
|
5
|
+
* `gl` arriving is what selects external mode, exactly as it selected the imperative renderer's.
|
|
6
|
+
*/
|
|
7
|
+
export interface ExternalCanvasProps {
|
|
8
|
+
/**
|
|
9
|
+
* The host's WebGL context. Rendering shares the host's context and canvas (`gl.canvas`) inside
|
|
10
|
+
* the host's own frame loop.
|
|
11
|
+
*/
|
|
12
|
+
gl: WebGLRenderingContext | WebGL2RenderingContext;
|
|
13
|
+
/**
|
|
14
|
+
* Asks the host for a frame — `map.triggerRepaint()` under MapLibre. The demand loop's other
|
|
15
|
+
* half: the host owns every frame, so anything that would invalidate the renderer's own canvas —
|
|
16
|
+
* an `update()`, a texture upload, a re-layout backlog — asks the host instead.
|
|
17
|
+
*/
|
|
18
|
+
requestFrame: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* Subscribes the renderer's per-frame draw to the host's render hook. The host calls the
|
|
21
|
+
* subscribed function once per frame it draws — from a MapLibre custom layer's `render` — with
|
|
22
|
+
* its combined view-projection matrix, sixteen elements column-major.
|
|
23
|
+
* @param draw - Draws one frame with the host's matrix.
|
|
24
|
+
* @returns An unsubscribe function.
|
|
25
|
+
*/
|
|
26
|
+
onFrame: (draw: (projectionMatrix: ArrayLike<number>) => void) => () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `<Canvas>`'s stand-in for a host that already owns the canvas: everything the Canvas component
|
|
30
|
+
* does — boot an R3F root, configure the renderer, the events, the size — minus the one thing it
|
|
31
|
+
* cannot share, creating the canvas element. It knows nothing of the scene: `children` render into
|
|
32
|
+
* the root verbatim, so `Renderer` composes one tree and only the mount differs.
|
|
33
|
+
*
|
|
34
|
+
* The bootstrap is imperative because the host's contract is: there is no React tree to mount into
|
|
35
|
+
* until a context exists. R3F's `<Canvas>` cannot serve here — it creates and owns its own canvas
|
|
36
|
+
* element inside a wrapper it renders, and has no way to adopt a foreign one — so this takes R3F's
|
|
37
|
+
* own escape hatch for exactly that, `createRoot(canvas)`, which is all `<Canvas>` wraps anyway.
|
|
38
|
+
* It is configured once per context, while the tree re-renders through props like any other.
|
|
39
|
+
*
|
|
40
|
+
* The camera half is the mount's as well, because the host owns it: one bare camera carries
|
|
41
|
+
* whatever matrix the host handed the frame hook, decomposed into a rigid view and a projection so
|
|
42
|
+
* that everything reading a camera's transform — culling, raycasts, lighting — works unchanged.
|
|
43
|
+
* There is no viewbox fit, no controls and no camera commands; the tree above simply mounts none
|
|
44
|
+
* of them.
|
|
45
|
+
*
|
|
46
|
+
* The demand loop inverts. R3F's own `invalidate` is a no-op under `frameloop: 'never'`, so the
|
|
47
|
+
* store's copy is replaced with the host's `requestFrame` — and every host frame redraws in full,
|
|
48
|
+
* as a shared canvas requires, starting from `resetState()` so three's GL-state tracking never
|
|
49
|
+
* trusts what the host's own drawing left behind.
|
|
50
|
+
* @param props - The host's hooks, and the tree to render into the root.
|
|
51
|
+
* @returns Nothing; the tree renders on the host's canvas, not in this one.
|
|
52
|
+
*/
|
|
53
|
+
export declare function ExternalCanvas(props: ExternalCanvasProps & {
|
|
54
|
+
children?: ReactNode;
|
|
55
|
+
}): null;
|
|
56
|
+
//# sourceMappingURL=ExternalCanvas.d.ts.map
|