@expofp/renderer 3.2.5 → 3.27.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.
Files changed (228) hide show
  1. package/README.md +111 -0
  2. package/dist/Renderer.d.ts +88 -0
  3. package/dist/Renderer.js +53 -0
  4. package/dist/controls/CameraControls.d.ts +102 -0
  5. package/dist/controls/CameraControls.js +239 -0
  6. package/dist/controls/camera-api.d.ts +206 -0
  7. package/dist/controls/camera-api.js +181 -0
  8. package/dist/controls/camera-move.d.ts +13 -0
  9. package/dist/controls/camera-move.js +18 -0
  10. package/dist/controls/camera-state.d.ts +40 -0
  11. package/dist/controls/camera-state.js +65 -0
  12. package/dist/controls/gestures.d.ts +70 -0
  13. package/dist/controls/gestures.js +84 -0
  14. package/dist/controls/handlers/Inertia.d.ts +111 -0
  15. package/dist/controls/handlers/Inertia.js +215 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +32 -0
  18. package/dist/controls/handlers/Pitch.d.ts +94 -0
  19. package/dist/controls/handlers/Pitch.js +191 -0
  20. package/dist/controls/handlers/Roll.d.ts +103 -0
  21. package/dist/controls/handlers/Roll.js +182 -0
  22. package/dist/controls/handlers/Zoom.d.ts +15 -0
  23. package/dist/controls/handlers/Zoom.js +26 -0
  24. package/dist/controls/index.d.ts +56 -0
  25. package/dist/controls/index.js +58 -0
  26. package/dist/controls/zoom-to.d.ts +89 -0
  27. package/dist/controls/zoom-to.js +91 -0
  28. package/dist/core/InstanceUniforms.d.ts +93 -0
  29. package/dist/core/InstanceUniforms.js +266 -0
  30. package/dist/core/SquareDataTexture.d.ts +277 -0
  31. package/dist/core/SquareDataTexture.js +476 -0
  32. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  33. package/dist/core/batch/BatchedMesh.js +149 -0
  34. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  35. package/dist/core/instanced/FrustumCulling.js +246 -0
  36. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  37. package/dist/core/instanced/InstancedMesh.js +497 -0
  38. package/dist/core/instanced/Instances.d.ts +119 -0
  39. package/dist/core/instanced/Instances.js +230 -0
  40. package/dist/core/instanced/Raycasting.d.ts +118 -0
  41. package/dist/core/instanced/Raycasting.js +134 -0
  42. package/dist/core/instanced/instanced-material.d.ts +77 -0
  43. package/dist/core/instanced/instanced-material.js +224 -0
  44. package/dist/core/lines/BatchedLines.d.ts +135 -0
  45. package/dist/core/lines/BatchedLines.js +213 -0
  46. package/dist/core/lines/FrustumCulling.d.ts +43 -0
  47. package/dist/core/lines/FrustumCulling.js +85 -0
  48. package/dist/core/lines/Raycasting.d.ts +44 -0
  49. package/dist/core/lines/Raycasting.js +91 -0
  50. package/dist/core/lines/lines-material.d.ts +47 -0
  51. package/dist/core/lines/lines-material.js +115 -0
  52. package/dist/core/shader-source.d.ts +50 -0
  53. package/dist/core/shader-source.js +84 -0
  54. package/dist/core/text/BatchedText.d.ts +227 -0
  55. package/dist/core/text/BatchedText.js +467 -0
  56. package/dist/core/text/MemberTexture.d.ts +90 -0
  57. package/dist/core/text/MemberTexture.js +194 -0
  58. package/dist/core/text/Text.d.ts +73 -0
  59. package/dist/core/text/Text.js +215 -0
  60. package/dist/core/text/batched-text-material.d.ts +56 -0
  61. package/dist/core/text/batched-text-material.js +121 -0
  62. package/dist/core/text/glyph-packing.d.ts +17 -0
  63. package/dist/core/text/glyph-packing.js +61 -0
  64. package/dist/core/typed-array.d.ts +16 -0
  65. package/dist/core/typed-array.js +20 -0
  66. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  67. package/dist/debug/components/DebugPanelProvider.js +38 -0
  68. package/dist/debug/components/DebugTools.d.ts +22 -0
  69. package/dist/debug/components/DebugTools.js +28 -0
  70. package/dist/debug/components/Gui.d.ts +20 -0
  71. package/dist/debug/components/Gui.js +32 -0
  72. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  73. package/dist/debug/components/MemoryInfo.js +42 -0
  74. package/dist/debug/components/Panel.d.ts +31 -0
  75. package/dist/debug/components/Panel.js +98 -0
  76. package/dist/debug/components/Stats.d.ts +9 -0
  77. package/dist/debug/components/Stats.js +24 -0
  78. package/dist/debug/debug-context.d.ts +41 -0
  79. package/dist/debug/debug-context.js +59 -0
  80. package/dist/debug/index.d.ts +51 -0
  81. package/dist/debug/index.js +64 -0
  82. package/dist/events/LayoutDriver.d.ts +31 -0
  83. package/dist/events/LayoutDriver.js +88 -0
  84. package/dist/events/PointerMissed.d.ts +23 -0
  85. package/dist/events/PointerMissed.js +58 -0
  86. package/dist/events/def-culling.d.ts +46 -0
  87. package/dist/events/def-culling.js +69 -0
  88. package/dist/events/layout-targets.d.ts +104 -0
  89. package/dist/events/layout-targets.js +245 -0
  90. package/dist/events/pointer.d.ts +118 -0
  91. package/dist/events/pointer.js +168 -0
  92. package/dist/external/ExternalCanvas.d.ts +56 -0
  93. package/dist/external/ExternalCanvas.js +178 -0
  94. package/dist/external/external-camera.d.ts +17 -0
  95. package/dist/external/external-camera.js +82 -0
  96. package/dist/external/external-events.d.ts +14 -0
  97. package/dist/external/external-events.js +33 -0
  98. package/dist/index.d.ts +11 -783
  99. package/dist/index.js +12 -8031
  100. package/dist/objects/Background.d.ts +31 -0
  101. package/dist/objects/Background.js +37 -0
  102. package/dist/objects/Lighting.d.ts +20 -0
  103. package/dist/objects/Lighting.js +85 -0
  104. package/dist/objects/containers/SlotContainer.d.ts +266 -0
  105. package/dist/objects/containers/SlotContainer.js +251 -0
  106. package/dist/objects/containers/hooks.d.ts +46 -0
  107. package/dist/objects/containers/hooks.js +118 -0
  108. package/dist/objects/containers/index.d.ts +6 -0
  109. package/dist/objects/containers/index.js +4 -0
  110. package/dist/objects/containers/keys.d.ts +10 -0
  111. package/dist/objects/containers/keys.js +18 -0
  112. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  113. package/dist/objects/containers/mesh-writes.js +97 -0
  114. package/dist/objects/containers/props.d.ts +23 -0
  115. package/dist/objects/containers/props.js +1 -0
  116. package/dist/objects/images/ImageInstance.d.ts +29 -0
  117. package/dist/objects/images/ImageInstance.js +83 -0
  118. package/dist/objects/images/Images.d.ts +37 -0
  119. package/dist/objects/images/Images.js +204 -0
  120. package/dist/objects/images/atlas-packer.d.ts +128 -0
  121. package/dist/objects/images/atlas-packer.js +315 -0
  122. package/dist/objects/images/image-container.d.ts +51 -0
  123. package/dist/objects/images/image-container.js +70 -0
  124. package/dist/objects/images/index.d.ts +2 -0
  125. package/dist/objects/images/index.js +1 -0
  126. package/dist/objects/images/texture-budget.d.ts +120 -0
  127. package/dist/objects/images/texture-budget.js +215 -0
  128. package/dist/objects/layer/Layer.d.ts +27 -0
  129. package/dist/objects/layer/Layer.js +114 -0
  130. package/dist/objects/layer/index.d.ts +2 -0
  131. package/dist/objects/layer/index.js +1 -0
  132. package/dist/objects/layer/mount-scheduler.d.ts +102 -0
  133. package/dist/objects/layer/mount-scheduler.js +145 -0
  134. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  135. package/dist/objects/layer/visibility-gui.js +93 -0
  136. package/dist/objects/lines/LineInstance.d.ts +21 -0
  137. package/dist/objects/lines/LineInstance.js +73 -0
  138. package/dist/objects/lines/Lines.d.ts +17 -0
  139. package/dist/objects/lines/Lines.js +29 -0
  140. package/dist/objects/lines/index.d.ts +2 -0
  141. package/dist/objects/lines/index.js +1 -0
  142. package/dist/objects/lines/line-container.d.ts +35 -0
  143. package/dist/objects/lines/line-container.js +40 -0
  144. package/dist/objects/materials/ColorMaterial.d.ts +85 -0
  145. package/dist/objects/materials/ColorMaterial.js +89 -0
  146. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  147. package/dist/objects/materials/LineMaterial.js +72 -0
  148. package/dist/objects/materials/ModelMaterial.d.ts +41 -0
  149. package/dist/objects/materials/ModelMaterial.js +84 -0
  150. package/dist/objects/materials/TextureMaterial.d.ts +30 -0
  151. package/dist/objects/materials/TextureMaterial.js +29 -0
  152. package/dist/objects/materials/dim.d.ts +123 -0
  153. package/dist/objects/materials/dim.js +213 -0
  154. package/dist/objects/materials/shared.d.ts +6 -0
  155. package/dist/objects/materials/shared.js +51 -0
  156. package/dist/objects/models/ModelInstance.d.ts +22 -0
  157. package/dist/objects/models/ModelInstance.js +33 -0
  158. package/dist/objects/models/Models.d.ts +22 -0
  159. package/dist/objects/models/Models.js +177 -0
  160. package/dist/objects/models/gltf-cache.d.ts +17 -0
  161. package/dist/objects/models/gltf-cache.js +50 -0
  162. package/dist/objects/models/index.d.ts +2 -0
  163. package/dist/objects/models/index.js +1 -0
  164. package/dist/objects/models/model-asset.d.ts +48 -0
  165. package/dist/objects/models/model-asset.js +77 -0
  166. package/dist/objects/models/model-container.d.ts +139 -0
  167. package/dist/objects/models/model-container.js +227 -0
  168. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  169. package/dist/objects/shapes/ShapeInstance.js +40 -0
  170. package/dist/objects/shapes/Shapes.d.ts +21 -0
  171. package/dist/objects/shapes/Shapes.js +120 -0
  172. package/dist/objects/shapes/index.d.ts +2 -0
  173. package/dist/objects/shapes/index.js +1 -0
  174. package/dist/objects/shapes/shape-container.d.ts +165 -0
  175. package/dist/objects/shapes/shape-container.js +272 -0
  176. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  177. package/dist/objects/shapes/shape-geometry.js +94 -0
  178. package/dist/objects/text/TextInstance.d.ts +22 -0
  179. package/dist/objects/text/TextInstance.js +95 -0
  180. package/dist/objects/text/Texts.d.ts +30 -0
  181. package/dist/objects/text/Texts.js +41 -0
  182. package/dist/objects/text/index.d.ts +2 -0
  183. package/dist/objects/text/index.js +1 -0
  184. package/dist/objects/text/text-container.d.ts +105 -0
  185. package/dist/objects/text/text-container.js +206 -0
  186. package/dist/objects/text/text-layout.d.ts +83 -0
  187. package/dist/objects/text/text-layout.js +154 -0
  188. package/dist/space/Camera.d.ts +103 -0
  189. package/dist/space/Camera.js +321 -0
  190. package/dist/space/Scene.d.ts +44 -0
  191. package/dist/space/Scene.js +112 -0
  192. package/dist/space/Space.d.ts +288 -0
  193. package/dist/space/Space.js +329 -0
  194. package/dist/space/VisibleRect.d.ts +72 -0
  195. package/dist/space/VisibleRect.js +129 -0
  196. package/dist/space/camera-handle.d.ts +67 -0
  197. package/dist/space/camera-handle.js +63 -0
  198. package/dist/space/camera-kind.d.ts +26 -0
  199. package/dist/space/camera-kind.js +30 -0
  200. package/dist/space/canvas-size.d.ts +24 -0
  201. package/dist/space/canvas-size.js +20 -0
  202. package/dist/space/clipping.d.ts +55 -0
  203. package/dist/space/clipping.js +108 -0
  204. package/dist/space/layout-measure.d.ts +65 -0
  205. package/dist/space/layout-measure.js +173 -0
  206. package/dist/space/viewport.d.ts +112 -0
  207. package/dist/space/viewport.js +67 -0
  208. package/dist/types/defs.d.ts +169 -0
  209. package/dist/types/defs.js +1 -0
  210. package/dist/types/events.d.ts +183 -0
  211. package/dist/types/events.js +1 -0
  212. package/dist/types/guards.d.ts +14 -0
  213. package/dist/types/guards.js +42 -0
  214. package/dist/update/index.d.ts +3 -0
  215. package/dist/update/index.js +2 -0
  216. package/dist/update/slot-writers.d.ts +30 -0
  217. package/dist/update/slot-writers.js +72 -0
  218. package/dist/update/store.d.ts +41 -0
  219. package/dist/update/store.js +98 -0
  220. package/dist/utils/arrays.d.ts +13 -0
  221. package/dist/utils/arrays.js +20 -0
  222. package/dist/utils/colors.d.ts +19 -0
  223. package/dist/utils/colors.js +27 -0
  224. package/dist/utils/logging.d.ts +26 -0
  225. package/dist/utils/logging.js +47 -0
  226. package/dist/utils/math.d.ts +24 -0
  227. package/dist/utils/math.js +27 -0
  228. package/package.json +67 -19
@@ -0,0 +1,84 @@
1
+ import { useThree } from '@react-three/fiber';
2
+ import { EventManager, Pan, Rotate } from 'mjolnir.js';
3
+ import { useEffect } from 'react';
4
+ /**
5
+ * Subscribes to one of the canvas's gestures for as long as the component is mounted.
6
+ *
7
+ * **Two fingers are why any of this exists.** camera-controls recognizes a pinch and an orbit, but
8
+ * neither of the two-finger gestures the plan needs: a twist that turns the plan about the point
9
+ * between the fingers, and a vertical drag that tilts it. Both come off `mjolnir.js`, whose
10
+ * recognizer configuration is the imperative renderer's verbatim.
11
+ *
12
+ * The recognizer itself is shared: `Roll` and `Pitch` want one on the same canvas, and two would
13
+ * mean two sets of pointer listeners racing over the same fingers. It is built on the first
14
+ * subscription and torn down with the last, since it attaches its listeners in its constructor and
15
+ * offers no disconnect. Every recognizer stays dormant until something subscribes to its event, so
16
+ * mounting `Roll` is literally what turns `rotate` on.
17
+ * @param type - The gesture event to listen for, e.g. `rotatestart`.
18
+ * @param handler - What to run when it fires. Re-subscribes whenever its identity changes, so build
19
+ * it where the React Compiler can cache it against what it captures.
20
+ */
21
+ export function useGesture(type, handler) {
22
+ // The canvas, not `state.events.connected`: R3F points its own pointer events at the canvas
23
+ // unless a caller names an `eventSource`, which this renderer never does.
24
+ const element = useThree((state) => state.gl.domElement);
25
+ useEffect(() => {
26
+ const manager = acquireGestureManager(element);
27
+ manager.on(type, handler);
28
+ return () => {
29
+ manager.off(type, handler);
30
+ releaseGestureManager(element);
31
+ };
32
+ }, [element, type, handler]);
33
+ }
34
+ /**
35
+ * Takes out a share in the canvas's recognizer, building it if this is the first.
36
+ *
37
+ * The plumbing behind {@link useGesture}, which is what a component uses; exported so the sharing
38
+ * and the teardown can be pinned by a spec. Every call must be paired with a release.
39
+ * @param element - The element to recognize on.
40
+ * @returns The manager.
41
+ */
42
+ export function acquireGestureManager(element) {
43
+ const held = managers.get(element);
44
+ if (held) {
45
+ held.refs++;
46
+ return held.manager;
47
+ }
48
+ const manager = new EventManager(element, {
49
+ recognizers: [Rotate, [Pan, { event: 'pitch', pointers: 2 }, 'rotate']],
50
+ });
51
+ managers.set(element, { manager, refs: 1 });
52
+ return manager;
53
+ }
54
+ /**
55
+ * Gives back a share taken out by {@link acquireGestureManager}, tearing the recognizer down once
56
+ * the last one is returned.
57
+ * @param element - The element it was acquired for.
58
+ */
59
+ export function releaseGestureManager(element) {
60
+ const held = managers.get(element);
61
+ if (!held)
62
+ return;
63
+ if (--held.refs > 0)
64
+ return;
65
+ managers.delete(element);
66
+ held.manager.destroy();
67
+ }
68
+ /**
69
+ * Where one of a gesture's pointers sits on the canvas, in css pixels from its top-left corner.
70
+ *
71
+ * The one place a DOM event crosses into {@link CanvasPointLike}, which is why the assertion is
72
+ * written here rather than at each handler: `offsetX`/`offsetY` are canvas coordinates by
73
+ * definition, being measured from the target's own padding edge.
74
+ * @param event - One of the pointer events a gesture carries.
75
+ * @returns The point on the canvas.
76
+ */
77
+ export function canvasPoint(event) {
78
+ return { x: event.offsetX, y: event.offsetY };
79
+ }
80
+ /**
81
+ * The live recognizers by the element they listen on, so the handlers sharing a canvas share one.
82
+ * Weak, so a canvas that goes away takes its entry with it.
83
+ */
84
+ const managers = new WeakMap();
@@ -0,0 +1,111 @@
1
+ import { type CameraControlsImpl } from '../CameraControls';
2
+ /**
3
+ * How the glide a pan leaves behind is measured, and how it slows.
4
+ *
5
+ * The speed is taken from where the camera's target actually traveled over the last stretch of the
6
+ * drag, so these are all expressed against that: world units and milliseconds.
7
+ */
8
+ export interface InertiaConfig {
9
+ /**
10
+ * How far back the drag is sampled to measure its speed, in milliseconds; defaults to `50`. The
11
+ * oldest sample at least this old is the one differenced against, so a flick that ended in a
12
+ * stall glides at the stall's speed rather than the flick's.
13
+ */
14
+ minSampleMs?: number;
15
+ /** How long the glide lasts, in milliseconds; defaults to `1000`. */
16
+ durationMs?: number;
17
+ /**
18
+ * The speed below which a lifted pointer leaves no glide at all, in world units per millisecond;
19
+ * defaults to `0.08`.
20
+ */
21
+ speedThreshold?: number;
22
+ /**
23
+ * How the glide slows, mapping `[0, 1]` to `[0, 1]`; defaults to {@link easeInertia}.
24
+ *
25
+ * Called every frame of the glide with the time **remaining**, so it is read as "how much of the
26
+ * original speed is left" and should return `1` at `1` and `0` at `0`. Hoist it: an inline
27
+ * function is a fresh identity on every render, which re-syncs the glide's options needlessly.
28
+ */
29
+ ease?: (t: number) => number;
30
+ }
31
+ /**
32
+ * The glide a lifted pointer leaves behind.
33
+ *
34
+ * Mounted by {@link Pan}, and only when a pan is meant to keep moving. It watches the controls'
35
+ * own interaction events to learn how fast the plan was traveling when the pointer left, then eases
36
+ * that speed away over the following second.
37
+ *
38
+ * Runs its frame callback **ahead of the controls' own** (see {@link INERTIA_PRIORITY}), so the
39
+ * target it moves is the one that frame draws rather than the next — a frame tighter than the
40
+ * imperative renderer, which updated the controller before its handlers. It asks for the next frame
41
+ * itself, which is what keeps a glide alive under the on-demand loop.
42
+ * @param props - How the glide is measured and how it slows.
43
+ * @returns Nothing; this renders no scene node.
44
+ */
45
+ export declare function Inertia(props: InertiaConfig): null;
46
+ /**
47
+ * The default glide easing: an exponential ease-in, so most of the speed is shed early and the last
48
+ * of it trails off.
49
+ * @param t - How much of the glide's time is left, from `1` to `0`.
50
+ * @returns How much of the original speed to carry, from `1` to `0`.
51
+ */
52
+ export declare function easeInertia(t: number): number;
53
+ /**
54
+ * Carries a pan on after the pointer lifts.
55
+ *
56
+ * Framework-free on purpose: everything here is arithmetic over the camera's target and a list of
57
+ * timestamps, which is the half worth testing without a canvas around it. {@link Inertia} is the
58
+ * React half — when to connect, when to configure, when to step.
59
+ *
60
+ * **The speed comes from where the target actually went**, not from the pointer. Every interaction
61
+ * frame records the target; when the pointer lifts, the oldest sample at least `minSampleMs` old is
62
+ * diffed against the current target. A flick that ended in a stall therefore glides at the
63
+ * stall's speed, because the samples covering the stall are the recent ones.
64
+ */
65
+ export declare class InertiaController {
66
+ private minSampleMs;
67
+ private durationMs;
68
+ private speedThreshold;
69
+ private ease;
70
+ private controls?;
71
+ private samples;
72
+ private target;
73
+ private delta;
74
+ private velocity;
75
+ private active;
76
+ private elapsedMs;
77
+ private prevFactor;
78
+ /**
79
+ * Takes every value rather than defaulting any: what an omitted option means is `resolveControls`'s
80
+ * to answer, and answering it twice is how the two drift apart.
81
+ * @param options - How the glide is measured and how it slows.
82
+ */
83
+ constructor(options: Required<InertiaConfig>);
84
+ /**
85
+ * Sets how the glide is measured and how it slows. Every field is optional, so a partial update
86
+ * leaves the rest as it was.
87
+ * @param options - The values to change.
88
+ */
89
+ configure(options: InertiaConfig): void;
90
+ /**
91
+ * Starts watching a controls instance for the interactions a glide follows.
92
+ * @param controls - The controls to watch and to move.
93
+ */
94
+ connect(controls: CameraControlsImpl): void;
95
+ /** Stops watching, and abandons any glide in flight. */
96
+ disconnect(): void;
97
+ /**
98
+ * Advances a glide in flight by one frame.
99
+ *
100
+ * The speed applied is the average of this frame's easing factor and the last one's, which
101
+ * integrates the curve over the frame rather than sampling it at one end — so the distance
102
+ * covered does not depend on how the frames happened to fall.
103
+ * @param delta - Time since the last frame, in seconds.
104
+ * @returns Whether the camera moved, and so whether another frame is wanted.
105
+ */
106
+ update(delta: number): boolean;
107
+ private onControlStart;
108
+ private onControl;
109
+ private onControlEnd;
110
+ }
111
+ //# sourceMappingURL=Inertia.d.ts.map
@@ -0,0 +1,215 @@
1
+ import { useFrame, useThree } from '@react-three/fiber';
2
+ import { useEffect, useState } from 'react';
3
+ import { Vector3 } from 'three';
4
+ import { useControlsEffect } from '../CameraControls';
5
+ /**
6
+ * The glide a lifted pointer leaves behind.
7
+ *
8
+ * Mounted by {@link Pan}, and only when a pan is meant to keep moving. It watches the controls'
9
+ * own interaction events to learn how fast the plan was traveling when the pointer left, then eases
10
+ * that speed away over the following second.
11
+ *
12
+ * Runs its frame callback **ahead of the controls' own** (see {@link INERTIA_PRIORITY}), so the
13
+ * target it moves is the one that frame draws rather than the next — a frame tighter than the
14
+ * imperative renderer, which updated the controller before its handlers. It asks for the next frame
15
+ * itself, which is what keeps a glide alive under the on-demand loop.
16
+ * @param props - How the glide is measured and how it slows.
17
+ * @returns Nothing; this renders no scene node.
18
+ */
19
+ export function Inertia(props) {
20
+ const minSampleMs = props.minSampleMs ?? DEFAULT_SAMPLE_MS;
21
+ const durationMs = props.durationMs ?? DEFAULT_DURATION_MS;
22
+ const speedThreshold = props.speedThreshold ?? DEFAULT_SPEED_THRESHOLD;
23
+ const ease = props.ease ?? easeInertia;
24
+ const invalidate = useThree((state) => state.invalidate);
25
+ const [inertia] = useState(() => new InertiaController({ minSampleMs, durationMs, speedThreshold, ease }));
26
+ // Assignments only, so a caller passing an inline `ease` re-syncs four fields and disturbs
27
+ // nothing else — in particular it never re-subscribes, which is why this is its own effect.
28
+ useEffect(() => {
29
+ inertia.configure({ minSampleMs, durationMs, speedThreshold, ease });
30
+ }, [inertia, minSampleMs, durationMs, speedThreshold, ease]);
31
+ useControlsEffect((controls) => {
32
+ inertia.connect(controls);
33
+ return () => inertia.disconnect();
34
+ });
35
+ useFrame((_state, delta) => {
36
+ if (inertia.update(delta))
37
+ invalidate();
38
+ }, INERTIA_PRIORITY);
39
+ return null;
40
+ }
41
+ /**
42
+ * The default glide easing: an exponential ease-in, so most of the speed is shed early and the last
43
+ * of it trails off.
44
+ * @param t - How much of the glide's time is left, from `1` to `0`.
45
+ * @returns How much of the original speed to carry, from `1` to `0`.
46
+ */
47
+ export function easeInertia(t) {
48
+ return Math.pow(2, 10 * (t - 1));
49
+ }
50
+ /**
51
+ * Carries a pan on after the pointer lifts.
52
+ *
53
+ * Framework-free on purpose: everything here is arithmetic over the camera's target and a list of
54
+ * timestamps, which is the half worth testing without a canvas around it. {@link Inertia} is the
55
+ * React half — when to connect, when to configure, when to step.
56
+ *
57
+ * **The speed comes from where the target actually went**, not from the pointer. Every interaction
58
+ * frame records the target; when the pointer lifts, the oldest sample at least `minSampleMs` old is
59
+ * diffed against the current target. A flick that ended in a stall therefore glides at the
60
+ * stall's speed, because the samples covering the stall are the recent ones.
61
+ */
62
+ export class InertiaController {
63
+ minSampleMs;
64
+ durationMs;
65
+ speedThreshold;
66
+ ease;
67
+ controls;
68
+ samples = [];
69
+ target = new Vector3();
70
+ delta = new Vector3();
71
+ velocity = new Vector3();
72
+ active = false;
73
+ elapsedMs = 0;
74
+ prevFactor = 1;
75
+ /**
76
+ * Takes every value rather than defaulting any: what an omitted option means is `resolveControls`'s
77
+ * to answer, and answering it twice is how the two drift apart.
78
+ * @param options - How the glide is measured and how it slows.
79
+ */
80
+ constructor(options) {
81
+ this.minSampleMs = options.minSampleMs;
82
+ this.durationMs = options.durationMs;
83
+ this.speedThreshold = options.speedThreshold;
84
+ this.ease = options.ease;
85
+ }
86
+ /**
87
+ * Sets how the glide is measured and how it slows. Every field is optional, so a partial update
88
+ * leaves the rest as it was.
89
+ * @param options - The values to change.
90
+ */
91
+ configure(options) {
92
+ if (options.minSampleMs !== undefined)
93
+ this.minSampleMs = options.minSampleMs;
94
+ if (options.durationMs !== undefined)
95
+ this.durationMs = options.durationMs;
96
+ if (options.speedThreshold !== undefined)
97
+ this.speedThreshold = options.speedThreshold;
98
+ if (options.ease !== undefined)
99
+ this.ease = options.ease;
100
+ }
101
+ /**
102
+ * Starts watching a controls instance for the interactions a glide follows.
103
+ * @param controls - The controls to watch and to move.
104
+ */
105
+ connect(controls) {
106
+ if (this.controls)
107
+ this.disconnect();
108
+ this.controls = controls;
109
+ controls.addEventListener('controlstart', this.onControlStart);
110
+ controls.addEventListener('control', this.onControl);
111
+ controls.addEventListener('controlend', this.onControlEnd);
112
+ }
113
+ /** Stops watching, and abandons any glide in flight. */
114
+ disconnect() {
115
+ const controls = this.controls;
116
+ if (!controls)
117
+ return;
118
+ this.active = false;
119
+ this.samples.length = 0;
120
+ controls.removeEventListener('controlstart', this.onControlStart);
121
+ controls.removeEventListener('control', this.onControl);
122
+ controls.removeEventListener('controlend', this.onControlEnd);
123
+ this.controls = undefined;
124
+ }
125
+ /**
126
+ * Advances a glide in flight by one frame.
127
+ *
128
+ * The speed applied is the average of this frame's easing factor and the last one's, which
129
+ * integrates the curve over the frame rather than sampling it at one end — so the distance
130
+ * covered does not depend on how the frames happened to fall.
131
+ * @param delta - Time since the last frame, in seconds.
132
+ * @returns Whether the camera moved, and so whether another frame is wanted.
133
+ */
134
+ update(delta) {
135
+ const controls = this.controls;
136
+ if (!this.active || !controls)
137
+ return false;
138
+ const deltaMs = delta * 1000;
139
+ this.elapsedMs += deltaMs;
140
+ const t = Math.min(1, this.elapsedMs / this.durationMs);
141
+ const factor = this.ease(1 - t); // 1 -> 0
142
+ const avgFactor = (factor + this.prevFactor) * 0.5;
143
+ this.prevFactor = factor;
144
+ // The current target rather than the one being eased toward: a glide is a chain of immediate
145
+ // moves, so the two agree — asking for the end value would compound with any transition the
146
+ // controls happen to be running.
147
+ controls.getTarget(this.target, false);
148
+ this.delta.copy(this.velocity).multiplyScalar(avgFactor * deltaMs);
149
+ this.target.add(this.delta);
150
+ // Only the target is moved: camera-controls carries the camera along with it, which is why the
151
+ // imperative renderer's parallel arithmetic on the camera position was never read back.
152
+ void controls.moveTo(this.target.x, this.target.y, this.target.z, false);
153
+ if (factor <= REST_FACTOR || t >= 1)
154
+ this.active = false;
155
+ return true;
156
+ }
157
+ onControlStart = () => {
158
+ this.active = false;
159
+ this.samples.length = 0;
160
+ };
161
+ onControl = () => {
162
+ const controls = this.controls;
163
+ if (!controls)
164
+ return;
165
+ const now = performance.now();
166
+ controls.getTarget(this.target, false);
167
+ this.samples.push({ t: now, target: this.target.clone() });
168
+ const cutoff = now - SAMPLE_WINDOW_MS;
169
+ while (this.samples.length && this.samples[0].t < cutoff)
170
+ this.samples.shift();
171
+ };
172
+ onControlEnd = () => {
173
+ const controls = this.controls;
174
+ if (!controls)
175
+ return;
176
+ const now = performance.now();
177
+ const oldest = now - this.minSampleMs;
178
+ for (let i = this.samples.length - 1; i >= 0; i--) {
179
+ const sample = this.samples[i];
180
+ // The first sample old enough to measure against — or the oldest there is, when the whole
181
+ // drag was shorter than the sampling window.
182
+ if (sample.t >= oldest && i !== 0)
183
+ continue;
184
+ const dt = now - sample.t;
185
+ if (dt <= 0)
186
+ return;
187
+ controls.getTarget(this.target, false);
188
+ this.velocity.subVectors(this.target, sample.target).divideScalar(dt);
189
+ // A pan never changes elevation, and a glide must not introduce one.
190
+ this.velocity.z = 0;
191
+ if (this.velocity.length() <= this.speedThreshold)
192
+ return;
193
+ this.elapsedMs = 0;
194
+ this.prevFactor = 1;
195
+ this.active = true;
196
+ return;
197
+ }
198
+ };
199
+ }
200
+ /**
201
+ * The frame priority the glide steps at: ahead of the controls, which run at `-1`.
202
+ *
203
+ * Negative on both counts, so neither takes over rendering from R3F — a positive priority would.
204
+ */
205
+ const INERTIA_PRIORITY = -2;
206
+ /** How much of a drag is kept to measure its speed from, in milliseconds. */
207
+ const SAMPLE_WINDOW_MS = 300;
208
+ /** The easing factor below which what is left of a glide is not worth another frame. */
209
+ const REST_FACTOR = 0.02;
210
+ /** How far back a drag is sampled to measure the speed it should glide at, in milliseconds. */
211
+ const DEFAULT_SAMPLE_MS = 50;
212
+ /** How long a glide lasts, in milliseconds. */
213
+ const DEFAULT_DURATION_MS = 1000;
214
+ /** The speed below which a lifted pointer leaves no glide, in world units per millisecond. */
215
+ const DEFAULT_SPEED_THRESHOLD = 0.08;
@@ -0,0 +1,23 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type InertiaConfig } from './Inertia';
3
+ /** Options for the pan handler, which are also its props. */
4
+ export interface PanConfig {
5
+ /** Whether a pan keeps gliding after the pointer lifts, and how. Defaults on. */
6
+ inertia?: boolean | InertiaConfig;
7
+ }
8
+ /**
9
+ * Panning: a left-drag, and a one-finger drag.
10
+ *
11
+ * Screen-pan rather than truck, so the plan tracks the pointer at any pitch instead of sliding
12
+ * along the camera's own axes — the imperative renderer's choice, and what makes a tilted plan drag
13
+ * the way a map does.
14
+ *
15
+ * Owns the glide a lifted pointer leaves by rendering {@link Inertia}, which is the one piece of
16
+ * panning camera-controls does not do itself.
17
+ *
18
+ * Mounting is enabling: the actions are set on mount and returned to `NONE` on unmount.
19
+ * @param props - Whether a pan glides, and how.
20
+ * @returns The glide, when there is one.
21
+ */
22
+ export declare function Pan(props: PanConfig): ReactNode;
23
+ //# sourceMappingURL=Pan.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CameraControlsImpl, useControlsEffect } from '../CameraControls';
3
+ import { Inertia } from './Inertia';
4
+ /**
5
+ * Panning: a left-drag, and a one-finger drag.
6
+ *
7
+ * Screen-pan rather than truck, so the plan tracks the pointer at any pitch instead of sliding
8
+ * along the camera's own axes — the imperative renderer's choice, and what makes a tilted plan drag
9
+ * the way a map does.
10
+ *
11
+ * Owns the glide a lifted pointer leaves by rendering {@link Inertia}, which is the one piece of
12
+ * panning camera-controls does not do itself.
13
+ *
14
+ * Mounting is enabling: the actions are set on mount and returned to `NONE` on unmount.
15
+ * @param props - Whether a pan glides, and how.
16
+ * @returns The glide, when there is one.
17
+ */
18
+ export function Pan(props) {
19
+ const inertia = props.inertia ?? true;
20
+ useControlsEffect((controls) => {
21
+ controls.mouseButtons.left = ACTION.SCREEN_PAN;
22
+ controls.touches.one = ACTION.TOUCH_SCREEN_PAN;
23
+ return () => {
24
+ controls.mouseButtons.left = ACTION.NONE;
25
+ controls.touches.one = ACTION.NONE;
26
+ };
27
+ });
28
+ if (inertia === false)
29
+ return null;
30
+ return _jsx(Inertia, { ...(inertia === true ? {} : inertia) });
31
+ }
32
+ const ACTION = CameraControlsImpl.ACTION;
@@ -0,0 +1,94 @@
1
+ import { type Point2Like } from '@expofp/geometry';
2
+ import { type MjolnirGestureEvent } from 'mjolnir.js';
3
+ import { Vector2 } from 'three';
4
+ import { CameraControlsImpl } from '../CameraControls';
5
+ /**
6
+ * How far a two-finger gesture has got toward being judged a pitch.
7
+ *
8
+ * Split out from the gesture that holds it so {@link beginsVertically} can be exercised on its own:
9
+ * the arbitration is the subtle part, and it is pure arithmetic over two movements and a clock.
10
+ */
11
+ export interface PitchVerdict {
12
+ /** Whether this gesture is a pitch — `undefined` while the fingers have not said. */
13
+ isValid?: boolean;
14
+ /** When the first of the two fingers moved, so the second can be given a moment to follow. */
15
+ firstMove?: number;
16
+ }
17
+ /**
18
+ * Pitching: a right-drag, and two fingers dragged vertically together.
19
+ *
20
+ * The right-drag is camera-controls' own polar rotation. The vertical drag is not, and the hard
21
+ * part is not applying it but telling it apart from the pinch and the twist that share two fingers.
22
+ * That judgement lives in {@link beginsVertically}, and until it is settled the gesture does
23
+ * nothing.
24
+ *
25
+ * The pitch **limits** are not here. They constrain the camera whether or not this handler is
26
+ * mounted — the imperative renderer applied them from its pitch handler's constructor for exactly
27
+ * that reason — so they are `Camera`'s, alongside the zoom bounds and the clipping planes derived
28
+ * from both.
29
+ *
30
+ * Mounting is enabling. The mouse action shares a field with `Roll`, so it is set and cleared with
31
+ * `|=` and `&= ~`, which are idempotent where an exclusive-or would not be.
32
+ * @returns Nothing; this renders no scene node.
33
+ */
34
+ export declare function Pitch(): null;
35
+ /**
36
+ * Two fingers dragged vertically together, tilting the plan.
37
+ *
38
+ * A class rather than fields on the component, for two reasons that agree: the React Compiler
39
+ * refuses a direct write to anything `useState` returned, and the gesture's arbitration is worth
40
+ * testing without a canvas around it. {@link Pitch} is the React half.
41
+ */
42
+ export declare class PitchGesture {
43
+ private verdict;
44
+ private lastPoints?;
45
+ private prevTwoFingerAction?;
46
+ private p0;
47
+ private p1;
48
+ /**
49
+ * Remembers where the fingers started, and rules the gesture out when they are stacked one above
50
+ * the other — from there a vertical drag is indistinguishable from a pinch.
51
+ * @param event - The gesture event that began the drag.
52
+ */
53
+ start(event: MjolnirGestureEvent): void;
54
+ /**
55
+ * Tilts the plan by how far the fingers moved down the screen, once the gesture has been judged
56
+ * a pitch.
57
+ * @param event - The gesture event carrying the drag so far.
58
+ * @param controls - The controls to tilt.
59
+ */
60
+ move(event: MjolnirGestureEvent, controls: CameraControlsImpl): void;
61
+ /**
62
+ * Puts back the two-finger action this suppressed, if it suppressed one, and forgets the gesture.
63
+ *
64
+ * Nothing is restored when the captured action was already `NONE`: there was nothing to suppress,
65
+ * and writing it back would claim a field this handler does not own.
66
+ * @param controls - The controls to restore, if they are still there.
67
+ */
68
+ end(controls: CameraControlsImpl | undefined): void;
69
+ }
70
+ /**
71
+ * Whether a two-finger gesture is a pitch, judged from how the fingers first moved.
72
+ *
73
+ * Judged once and then held: the verdict is returned unchanged as soon as it has one, so a drag
74
+ * that started as a pitch stays one even if the fingers wander, and one that did not can never
75
+ * become one. A `false` verdict is what makes a pinch or a twist stay a pinch or a twist.
76
+ *
77
+ * The two fingers rarely start moving on the same frame, so one moving alone is not by itself a
78
+ * verdict — the other is given {@link ALLOWED_SINGLE_TOUCH_TIME} to follow, and only when it fails
79
+ * to is the gesture ruled out.
80
+ * @param a - How far the first finger moved since the last event.
81
+ * @param b - How far the second finger moved since the last event.
82
+ * @param timeStamp - When this event happened, in milliseconds.
83
+ * @param verdict - How far the judgement has got; updated in place.
84
+ * @returns Whether this is a pitch — `false` both for "not a pitch" and for "not yet decided", the
85
+ * two cases a caller treats alike by doing nothing.
86
+ */
87
+ export declare function beginsVertically(a: Vector2, b: Vector2, timeStamp: number, verdict: PitchVerdict): boolean;
88
+ /**
89
+ * Whether a movement is more down-the-screen than across it.
90
+ * @param vector - The movement to judge.
91
+ * @returns Whether it is vertical.
92
+ */
93
+ export declare function isVertical(vector: Point2Like): boolean;
94
+ //# sourceMappingURL=Pitch.d.ts.map