@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.
Files changed (280) hide show
  1. package/README.md +145 -0
  2. package/dist/Renderer.d.ts +102 -0
  3. package/dist/Renderer.js +96 -0
  4. package/dist/controls/CameraControls.d.ts +108 -0
  5. package/dist/controls/CameraControls.js +203 -0
  6. package/dist/controls/Controls.d.ts +53 -0
  7. package/dist/controls/Controls.js +54 -0
  8. package/dist/controls/camera-api.d.ts +226 -0
  9. package/dist/controls/camera-api.js +179 -0
  10. package/dist/controls/gestures.d.ts +71 -0
  11. package/dist/controls/gestures.js +98 -0
  12. package/dist/controls/handlers/DoubleTapZoom.d.ts +23 -0
  13. package/dist/controls/handlers/DoubleTapZoom.js +82 -0
  14. package/dist/controls/handlers/Inertia.d.ts +60 -0
  15. package/dist/controls/handlers/Inertia.js +222 -0
  16. package/dist/controls/handlers/Pan.d.ts +23 -0
  17. package/dist/controls/handlers/Pan.js +212 -0
  18. package/dist/controls/handlers/Pitch.d.ts +19 -0
  19. package/dist/controls/handlers/Pitch.js +200 -0
  20. package/dist/controls/handlers/Roll.d.ts +30 -0
  21. package/dist/controls/handlers/Roll.js +196 -0
  22. package/dist/controls/handlers/Zoom.d.ts +28 -0
  23. package/dist/controls/handlers/Zoom.js +34 -0
  24. package/dist/controls/hooks.d.ts +136 -0
  25. package/dist/controls/hooks.js +200 -0
  26. package/dist/controls/index.d.ts +9 -0
  27. package/dist/controls/index.js +7 -0
  28. package/dist/controls/zoom-to.d.ts +89 -0
  29. package/dist/controls/zoom-to.js +91 -0
  30. package/dist/core/InstanceUniforms.d.ts +108 -0
  31. package/dist/core/InstanceUniforms.js +298 -0
  32. package/dist/core/SquareDataTexture.d.ts +277 -0
  33. package/dist/core/SquareDataTexture.js +476 -0
  34. package/dist/core/batch/BatchedMesh.d.ts +74 -0
  35. package/dist/core/batch/BatchedMesh.js +155 -0
  36. package/dist/core/instanced/FrustumCulling.d.ts +141 -0
  37. package/dist/core/instanced/FrustumCulling.js +246 -0
  38. package/dist/core/instanced/InstancedMesh.d.ts +302 -0
  39. package/dist/core/instanced/InstancedMesh.js +497 -0
  40. package/dist/core/instanced/Instances.d.ts +119 -0
  41. package/dist/core/instanced/Instances.js +230 -0
  42. package/dist/core/instanced/Raycasting.d.ts +118 -0
  43. package/dist/core/instanced/Raycasting.js +134 -0
  44. package/dist/core/instanced/instanced-material.d.ts +77 -0
  45. package/dist/core/instanced/instanced-material.js +228 -0
  46. package/dist/core/lines/BatchedLines.d.ts +137 -0
  47. package/dist/core/lines/BatchedLines.js +215 -0
  48. package/dist/core/lines/FrustumCullingLines.d.ts +44 -0
  49. package/dist/core/lines/FrustumCullingLines.js +85 -0
  50. package/dist/core/lines/RaycastingLines.d.ts +45 -0
  51. package/dist/core/lines/RaycastingLines.js +91 -0
  52. package/dist/core/lines/lines-material.d.ts +47 -0
  53. package/dist/core/lines/lines-material.js +124 -0
  54. package/dist/core/shader-source.d.ts +70 -0
  55. package/dist/core/shader-source.js +110 -0
  56. package/dist/core/sprites/BatchedSprites.d.ts +115 -0
  57. package/dist/core/sprites/BatchedSprites.js +173 -0
  58. package/dist/core/sprites/RaycastingSprites.d.ts +41 -0
  59. package/dist/core/sprites/RaycastingSprites.js +71 -0
  60. package/dist/core/sprites/sprite-material.d.ts +59 -0
  61. package/dist/core/sprites/sprite-material.js +192 -0
  62. package/dist/core/text/BatchedText.d.ts +233 -0
  63. package/dist/core/text/BatchedText.js +473 -0
  64. package/dist/core/text/MemberTexture.d.ts +90 -0
  65. package/dist/core/text/MemberTexture.js +289 -0
  66. package/dist/core/text/Text.d.ts +88 -0
  67. package/dist/core/text/Text.js +230 -0
  68. package/dist/core/text/batched-text-material.d.ts +56 -0
  69. package/dist/core/text/batched-text-material.js +123 -0
  70. package/dist/core/text/glyph-packing.d.ts +17 -0
  71. package/dist/core/text/glyph-packing.js +61 -0
  72. package/dist/core/typed-array.d.ts +16 -0
  73. package/dist/core/typed-array.js +20 -0
  74. package/dist/debug/components/DebugPanelProvider.d.ts +20 -0
  75. package/dist/debug/components/DebugPanelProvider.js +50 -0
  76. package/dist/debug/components/DebugTools.d.ts +22 -0
  77. package/dist/debug/components/DebugTools.js +28 -0
  78. package/dist/debug/components/Gui.d.ts +22 -0
  79. package/dist/debug/components/Gui.js +42 -0
  80. package/dist/debug/components/MemoryInfo.d.ts +22 -0
  81. package/dist/debug/components/MemoryInfo.js +42 -0
  82. package/dist/debug/components/Panel.d.ts +31 -0
  83. package/dist/debug/components/Panel.js +104 -0
  84. package/dist/debug/components/Stats.d.ts +9 -0
  85. package/dist/debug/components/Stats.js +24 -0
  86. package/dist/debug/debug-chunk.d.ts +26 -0
  87. package/dist/debug/debug-chunk.js +32 -0
  88. package/dist/debug/debug-context.d.ts +41 -0
  89. package/dist/debug/debug-context.js +59 -0
  90. package/dist/debug/index.d.ts +41 -0
  91. package/dist/debug/index.js +43 -0
  92. package/dist/events/LayoutDriver.d.ts +36 -0
  93. package/dist/events/LayoutDriver.js +192 -0
  94. package/dist/events/PointerMissed.d.ts +23 -0
  95. package/dist/events/PointerMissed.js +58 -0
  96. package/dist/events/def-culling.d.ts +46 -0
  97. package/dist/events/def-culling.js +69 -0
  98. package/dist/events/layout-targets.d.ts +151 -0
  99. package/dist/events/layout-targets.js +397 -0
  100. package/dist/events/placement/box.d.ts +59 -0
  101. package/dist/events/placement/box.js +96 -0
  102. package/dist/events/placement/grid.d.ts +161 -0
  103. package/dist/events/placement/grid.js +235 -0
  104. package/dist/events/placement/index.d.ts +2 -0
  105. package/dist/events/placement/index.js +1 -0
  106. package/dist/events/placement/pass.d.ts +70 -0
  107. package/dist/events/placement/pass.js +617 -0
  108. package/dist/events/pointer.d.ts +194 -0
  109. package/dist/events/pointer.js +356 -0
  110. package/dist/external/ExternalCanvas.d.ts +56 -0
  111. package/dist/external/ExternalCanvas.js +177 -0
  112. package/dist/external/external-camera.d.ts +18 -0
  113. package/dist/external/external-camera.js +103 -0
  114. package/dist/external/external-events.d.ts +14 -0
  115. package/dist/external/external-events.js +33 -0
  116. package/dist/index.d.ts +13 -0
  117. package/dist/index.js +14 -0
  118. package/dist/objects/Background.d.ts +31 -0
  119. package/dist/objects/Background.js +37 -0
  120. package/dist/objects/containers/SlotContainer.d.ts +312 -0
  121. package/dist/objects/containers/SlotContainer.js +311 -0
  122. package/dist/objects/containers/hooks.d.ts +61 -0
  123. package/dist/objects/containers/hooks.js +196 -0
  124. package/dist/objects/containers/index.d.ts +6 -0
  125. package/dist/objects/containers/index.js +4 -0
  126. package/dist/objects/containers/keys.d.ts +10 -0
  127. package/dist/objects/containers/keys.js +18 -0
  128. package/dist/objects/containers/mesh-writes.d.ts +106 -0
  129. package/dist/objects/containers/mesh-writes.js +97 -0
  130. package/dist/objects/containers/props.d.ts +33 -0
  131. package/dist/objects/containers/props.js +1 -0
  132. package/dist/objects/images/ImageInstance.d.ts +29 -0
  133. package/dist/objects/images/ImageInstance.js +152 -0
  134. package/dist/objects/images/Images.d.ts +49 -0
  135. package/dist/objects/images/Images.js +394 -0
  136. package/dist/objects/images/atlas-packer.d.ts +129 -0
  137. package/dist/objects/images/atlas-packer.js +287 -0
  138. package/dist/objects/images/image-container.d.ts +74 -0
  139. package/dist/objects/images/image-container.js +100 -0
  140. package/dist/objects/images/index.d.ts +2 -0
  141. package/dist/objects/images/index.js +1 -0
  142. package/dist/objects/images/pack-sources.d.ts +190 -0
  143. package/dist/objects/images/pack-sources.js +272 -0
  144. package/dist/objects/images/texture-budget.d.ts +120 -0
  145. package/dist/objects/images/texture-budget.js +215 -0
  146. package/dist/objects/images/worker/atlas-worker.d.ts +56 -0
  147. package/dist/objects/images/worker/atlas-worker.js +123 -0
  148. package/dist/objects/images/worker/client.d.ts +62 -0
  149. package/dist/objects/images/worker/client.js +249 -0
  150. package/dist/objects/images/worker/index.d.ts +10 -0
  151. package/dist/objects/images/worker/index.js +1 -0
  152. package/dist/objects/layer/Layer.d.ts +28 -0
  153. package/dist/objects/layer/Layer.js +134 -0
  154. package/dist/objects/layer/index.d.ts +2 -0
  155. package/dist/objects/layer/index.js +1 -0
  156. package/dist/objects/layer/layer-context.d.ts +74 -0
  157. package/dist/objects/layer/layer-context.js +82 -0
  158. package/dist/objects/layer/mount-scheduler.d.ts +131 -0
  159. package/dist/objects/layer/mount-scheduler.js +289 -0
  160. package/dist/objects/layer/visibility-gui.d.ts +19 -0
  161. package/dist/objects/layer/visibility-gui.js +93 -0
  162. package/dist/objects/lighting/Lighting.d.ts +92 -0
  163. package/dist/objects/lighting/Lighting.js +384 -0
  164. package/dist/objects/lighting/OcclusionPasses.d.ts +23 -0
  165. package/dist/objects/lighting/OcclusionPasses.js +271 -0
  166. package/dist/objects/lighting/index.d.ts +11 -0
  167. package/dist/objects/lighting/index.js +10 -0
  168. package/dist/objects/lighting/lighting-gui.d.ts +24 -0
  169. package/dist/objects/lighting/lighting-gui.js +114 -0
  170. package/dist/objects/lighting/occluder.d.ts +34 -0
  171. package/dist/objects/lighting/occluder.js +62 -0
  172. package/dist/objects/lighting/occlusion-targets.d.ts +143 -0
  173. package/dist/objects/lighting/occlusion-targets.js +276 -0
  174. package/dist/objects/lines/LineInstance.d.ts +21 -0
  175. package/dist/objects/lines/LineInstance.js +73 -0
  176. package/dist/objects/lines/Lines.d.ts +17 -0
  177. package/dist/objects/lines/Lines.js +31 -0
  178. package/dist/objects/lines/index.d.ts +2 -0
  179. package/dist/objects/lines/index.js +1 -0
  180. package/dist/objects/lines/line-container.d.ts +35 -0
  181. package/dist/objects/lines/line-container.js +40 -0
  182. package/dist/objects/materials/ColorMaterial.d.ts +92 -0
  183. package/dist/objects/materials/ColorMaterial.js +124 -0
  184. package/dist/objects/materials/LineMaterial.d.ts +44 -0
  185. package/dist/objects/materials/LineMaterial.js +74 -0
  186. package/dist/objects/materials/ModelMaterial.d.ts +53 -0
  187. package/dist/objects/materials/ModelMaterial.js +111 -0
  188. package/dist/objects/materials/TextureMaterial.d.ts +33 -0
  189. package/dist/objects/materials/TextureMaterial.js +48 -0
  190. package/dist/objects/materials/effects/ao/ambient-occlusion-pass.d.ts +77 -0
  191. package/dist/objects/materials/effects/ao/ambient-occlusion-pass.js +296 -0
  192. package/dist/objects/materials/effects/ao/ambient-occlusion-patch.d.ts +29 -0
  193. package/dist/objects/materials/effects/ao/ambient-occlusion-patch.js +115 -0
  194. package/dist/objects/materials/effects/ao/blur-pass.d.ts +61 -0
  195. package/dist/objects/materials/effects/ao/blur-pass.js +101 -0
  196. package/dist/objects/materials/effects/ao/index.d.ts +129 -0
  197. package/dist/objects/materials/effects/ao/index.js +72 -0
  198. package/dist/objects/materials/effects/color-dim.d.ts +115 -0
  199. package/dist/objects/materials/effects/color-dim.js +206 -0
  200. package/dist/objects/materials/effects/contact-shadow.d.ts +100 -0
  201. package/dist/objects/materials/effects/contact-shadow.js +167 -0
  202. package/dist/objects/materials/effects/effects-context.d.ts +53 -0
  203. package/dist/objects/materials/effects/effects-context.js +24 -0
  204. package/dist/objects/materials/shared.d.ts +24 -0
  205. package/dist/objects/materials/shared.js +72 -0
  206. package/dist/objects/models/ModelInstance.d.ts +22 -0
  207. package/dist/objects/models/ModelInstance.js +33 -0
  208. package/dist/objects/models/Models.d.ts +22 -0
  209. package/dist/objects/models/Models.js +186 -0
  210. package/dist/objects/models/gltf-cache.d.ts +17 -0
  211. package/dist/objects/models/gltf-cache.js +50 -0
  212. package/dist/objects/models/index.d.ts +2 -0
  213. package/dist/objects/models/index.js +1 -0
  214. package/dist/objects/models/model-asset.d.ts +48 -0
  215. package/dist/objects/models/model-asset.js +77 -0
  216. package/dist/objects/models/model-container.d.ts +123 -0
  217. package/dist/objects/models/model-container.js +207 -0
  218. package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
  219. package/dist/objects/shapes/ShapeInstance.js +39 -0
  220. package/dist/objects/shapes/Shapes.d.ts +21 -0
  221. package/dist/objects/shapes/Shapes.js +131 -0
  222. package/dist/objects/shapes/index.d.ts +2 -0
  223. package/dist/objects/shapes/index.js +1 -0
  224. package/dist/objects/shapes/shape-container.d.ts +141 -0
  225. package/dist/objects/shapes/shape-container.js +242 -0
  226. package/dist/objects/shapes/shape-geometry.d.ts +27 -0
  227. package/dist/objects/shapes/shape-geometry.js +94 -0
  228. package/dist/objects/text/TextInstance.d.ts +22 -0
  229. package/dist/objects/text/TextInstance.js +95 -0
  230. package/dist/objects/text/Texts.d.ts +30 -0
  231. package/dist/objects/text/Texts.js +43 -0
  232. package/dist/objects/text/index.d.ts +2 -0
  233. package/dist/objects/text/index.js +1 -0
  234. package/dist/objects/text/text-container.d.ts +121 -0
  235. package/dist/objects/text/text-container.js +229 -0
  236. package/dist/objects/text/text-layout.d.ts +92 -0
  237. package/dist/objects/text/text-layout.js +180 -0
  238. package/dist/space/Camera.d.ts +103 -0
  239. package/dist/space/Camera.js +321 -0
  240. package/dist/space/Scene.d.ts +47 -0
  241. package/dist/space/Scene.js +127 -0
  242. package/dist/space/Space.d.ts +325 -0
  243. package/dist/space/Space.js +359 -0
  244. package/dist/space/VisibleRect.d.ts +45 -0
  245. package/dist/space/VisibleRect.js +129 -0
  246. package/dist/space/camera-handle.d.ts +67 -0
  247. package/dist/space/camera-handle.js +63 -0
  248. package/dist/space/camera-kind.d.ts +26 -0
  249. package/dist/space/camera-kind.js +30 -0
  250. package/dist/space/canvas-size.d.ts +24 -0
  251. package/dist/space/canvas-size.js +20 -0
  252. package/dist/space/clipping.d.ts +69 -0
  253. package/dist/space/clipping.js +130 -0
  254. package/dist/space/layout-measure.d.ts +67 -0
  255. package/dist/space/layout-measure.js +175 -0
  256. package/dist/space/viewport.d.ts +112 -0
  257. package/dist/space/viewport.js +67 -0
  258. package/dist/types/defs.d.ts +234 -0
  259. package/dist/types/defs.js +1 -0
  260. package/dist/types/events.d.ts +198 -0
  261. package/dist/types/events.js +1 -0
  262. package/dist/types/guards.d.ts +21 -0
  263. package/dist/types/guards.js +52 -0
  264. package/dist/update/index.d.ts +3 -0
  265. package/dist/update/index.js +2 -0
  266. package/dist/update/slot-writers.d.ts +30 -0
  267. package/dist/update/slot-writers.js +76 -0
  268. package/dist/update/store.d.ts +41 -0
  269. package/dist/update/store.js +104 -0
  270. package/dist/utils/arrays.d.ts +13 -0
  271. package/dist/utils/arrays.js +20 -0
  272. package/dist/utils/colors.d.ts +19 -0
  273. package/dist/utils/colors.js +27 -0
  274. package/dist/utils/hot-state.d.ts +41 -0
  275. package/dist/utils/hot-state.js +37 -0
  276. package/dist/utils/logging.d.ts +29 -0
  277. package/dist/utils/logging.js +55 -0
  278. package/dist/utils/math.d.ts +52 -0
  279. package/dist/utils/math.js +76 -0
  280. package/package.json +79 -0
@@ -0,0 +1,98 @@
1
+ import { useThree } from '@react-three/fiber';
2
+ import { EventManager, Pan, Rotate, Tap } from 'mjolnir.js';
3
+ import { useEffect } from 'react';
4
+ import { hotState } from '../utils/hot-state';
5
+ /**
6
+ * Subscribes to one of the canvas's gestures for as long as the component is mounted.
7
+ *
8
+ * **Two fingers are why any of this exists.** camera-controls recognizes a pinch and an orbit, but
9
+ * neither of the two-finger gestures the plan needs: a twist that turns the plan about the point
10
+ * between the fingers, and a vertical drag that tilts it. Both come off `mjolnir.js`, whose
11
+ * recognizer configuration is the imperative renderer's verbatim — as does the double tap that
12
+ * `Zoom` zooms on, which camera-controls does not recognize either.
13
+ *
14
+ * The recognizer itself is shared: `Roll`, `Pitch` and `Zoom` want one on the same canvas, and two would
15
+ * mean two sets of pointer listeners racing over the same fingers. It is built on the first
16
+ * subscription and torn down with the last, since it attaches its listeners in its constructor and
17
+ * offers no disconnect. Every recognizer stays dormant until something subscribes to its event, so
18
+ * mounting `Roll` is literally what turns `rotate` on.
19
+ * @param type - The gesture event to listen for, e.g. `rotatestart`.
20
+ * @param handler - What to run when it fires. Re-subscribes whenever its identity changes, so build
21
+ * it where the React Compiler can cache it against what it captures.
22
+ */
23
+ export function useGesture(type, handler) {
24
+ // The canvas, not `state.events.connected`: R3F points its own pointer events at the canvas
25
+ // unless a caller names an `eventSource`, which this renderer never does.
26
+ const element = useThree((state) => state.gl.domElement);
27
+ useEffect(() => {
28
+ const manager = acquireGestureManager(element);
29
+ manager.on(type, handler);
30
+ return () => {
31
+ manager.off(type, handler);
32
+ releaseGestureManager(element);
33
+ };
34
+ }, [element, type, handler]);
35
+ }
36
+ /**
37
+ * Takes out a share in the canvas's recognizer, building it if this is the first.
38
+ *
39
+ * The plumbing behind {@link useGesture}, which is what a component uses; exported so the sharing
40
+ * and the teardown can be pinned by a spec. Every call must be paired with a release.
41
+ * @param element - The element to recognize on.
42
+ * @returns The manager.
43
+ */
44
+ export function acquireGestureManager(element) {
45
+ const held = managers.get(element);
46
+ if (held) {
47
+ held.refs++;
48
+ return held.manager;
49
+ }
50
+ const manager = new EventManager(element, {
51
+ recognizers: [
52
+ Rotate,
53
+ [Pan, { event: 'pitch', pointers: 2 }, 'rotate'],
54
+ // A mouse's pointer events feed the recognizer as a finger's do, so one double tap is both
55
+ // the double-click and the double-tap. MapLibre's tap tolerances rather than the
56
+ // recognizer's own: a finger's second tap rarely lands within 10px of its first, and with
57
+ // the default `posThreshold` a real double tap on a phone was never recognized.
58
+ [Tap, { event: 'doubletap', taps: 2, interval: 500, time: 500, posThreshold: 30 }],
59
+ ],
60
+ });
61
+ managers.set(element, { manager, refs: 1 });
62
+ return manager;
63
+ }
64
+ /**
65
+ * Gives back a share taken out by {@link acquireGestureManager}, tearing the recognizer down once
66
+ * the last one is returned.
67
+ * @param element - The element it was acquired for.
68
+ */
69
+ export function releaseGestureManager(element) {
70
+ const held = managers.get(element);
71
+ if (!held)
72
+ return;
73
+ if (--held.refs > 0)
74
+ return;
75
+ managers.delete(element);
76
+ held.manager.destroy();
77
+ }
78
+ /**
79
+ * Where one of a gesture's pointers sits on the canvas, in css pixels from its top-left corner.
80
+ *
81
+ * The one place a DOM event crosses into {@link CanvasPointLike}, which is why the assertion is
82
+ * written here rather than at each handler: `offsetX`/`offsetY` are canvas coordinates by
83
+ * definition, being measured from the target's own padding edge.
84
+ * @param event - One of the pointer events a gesture carries.
85
+ * @returns The point on the canvas.
86
+ */
87
+ export function canvasPoint(event) {
88
+ return { x: event.offsetX, y: event.offsetY };
89
+ }
90
+ /**
91
+ * The live recognizers by the element they listen on, so the handlers sharing a canvas share one.
92
+ * Weak, so a canvas that goes away takes its entry with it.
93
+ *
94
+ * Kept across hot updates, because the ref count is the whole point of sharing: a fresh map would
95
+ * attach a second recognizer to a canvas that already has one, and every pan and pinch would be
96
+ * handled twice (see {@link hotState}).
97
+ */
98
+ const managers = hotState(import.meta.hot, 'managers', () => new WeakMap());
@@ -0,0 +1,23 @@
1
+ /** Props for {@link DoubleTapZoom}. */
2
+ export interface DoubleTapZoomProps {
3
+ /** What a double tap multiplies the zoom factor by. */
4
+ factor: number;
5
+ }
6
+ /**
7
+ * A double-click or double-tap: zooms by `factor` about the point under the pointer, which keeps
8
+ * its place on the canvas throughout.
9
+ *
10
+ * Mounted by `Zoom`, and only when a double-click is meant to zoom. One double tap is both the
11
+ * double-click and the double-tap: the gesture recognizer reads a mouse's pointer events as it
12
+ * reads a finger's.
13
+ *
14
+ * Run as a user transition, so it eases the way a wheel tick does — by `controlSmoothTime`, with the
15
+ * target on the dolly's clock — and reports itself as the user's hand.
16
+ *
17
+ * A component of its own rather than a branch inside `Zoom`, because subscribing is a hook and a
18
+ * hook cannot be switched off conditionally — mounting this is what switches it on.
19
+ * @param props - The zoom factor to multiply by.
20
+ * @returns Nothing; this renders no scene node.
21
+ */
22
+ export declare function DoubleTapZoom(props: DoubleTapZoomProps): null;
23
+ //# sourceMappingURL=DoubleTapZoom.d.ts.map
@@ -0,0 +1,82 @@
1
+ import { useThree } from '@react-three/fiber';
2
+ import { Spherical, Vector3 } from 'three';
3
+ import { useSpace, } from '../../space/Space';
4
+ import { clamp } from '../../utils/math';
5
+ import { useGesture } from '../gestures';
6
+ import { useCameraControls } from '../hooks';
7
+ /**
8
+ * A double-click or double-tap: zooms by `factor` about the point under the pointer, which keeps
9
+ * its place on the canvas throughout.
10
+ *
11
+ * Mounted by `Zoom`, and only when a double-click is meant to zoom. One double tap is both the
12
+ * double-click and the double-tap: the gesture recognizer reads a mouse's pointer events as it
13
+ * reads a finger's.
14
+ *
15
+ * Run as a user transition, so it eases the way a wheel tick does — by `controlSmoothTime`, with the
16
+ * target on the dolly's clock — and reports itself as the user's hand.
17
+ *
18
+ * A component of its own rather than a branch inside `Zoom`, because subscribing is a hook and a
19
+ * hook cannot be switched off conditionally — mounting this is what switches it on.
20
+ * @param props - The zoom factor to multiply by.
21
+ * @returns Nothing; this renders no scene node.
22
+ */
23
+ export function DoubleTapZoom(props) {
24
+ const { factor } = props;
25
+ const get = useThree((state) => state.get);
26
+ const invalidate = useThree((state) => state.invalidate);
27
+ const space = useSpace();
28
+ // Read, not written: `userTransition` takes the move as a callback, which does the writing.
29
+ const controls = useCameraControls();
30
+ const onDoubleTap = (event) => {
31
+ if (!controls)
32
+ return;
33
+ // mjolnir measures the tap from the target's own padding edge, as `canvasPoint` does.
34
+ const around = space.canvasToPlan(event.offsetCenter, get().camera);
35
+ if (!around)
36
+ return;
37
+ void controls.userTransition(() => zoomAbout(controls, space, factor, around));
38
+ // The move happens outside a React commit, so a demand frame loop has to be asked for a frame.
39
+ invalidate();
40
+ };
41
+ useGesture('doubletap', onDoubleTap);
42
+ return null;
43
+ }
44
+ /**
45
+ * Zooms by a factor about a point on the plan, which keeps its place on the canvas.
46
+ *
47
+ * Scaling the whole camera about the point, orientation untouched, is what leaves the point where
48
+ * it was: the target slides toward it in the proportion the distance shrinks. Exact under a visible
49
+ * rect too, whose offset rides in the projection this leaves alone. It holds **during** the ease
50
+ * only inside a user transition, which puts the target on the dolly's clock.
51
+ *
52
+ * **Scaled from where the camera is now, sized from where it is heading.** A double tap often lands
53
+ * while the last one is still easing. The point under the finger is found through the camera as it
54
+ * is drawn, so it is the camera's current stance that has to be scaled about it — that keeps the
55
+ * point under the finger from here on. The zoom is sized from the distance the camera is heading
56
+ * to, so a second double tap zooms a full step past the first rather than a step past wherever the
57
+ * first had got to, as successive wheel notches do. The clamped distance sets the proportion, so a
58
+ * zoom stopped short by the camera's limits does not slide the target as far as the one asked for.
59
+ * @param controls - The controls to move.
60
+ * @param space - The scene's space.
61
+ * @param factor - What to multiply the zoom factor by.
62
+ * @param around - The point to zoom about, in plan units.
63
+ * @returns When the move has settled.
64
+ */
65
+ function zoomAbout(controls, space, factor, around) {
66
+ const from = controls.distance;
67
+ // The zoom factor is inversely proportional to the distance.
68
+ const to = clamp(controls.getSpherical(_spherical, true).radius / factor, controls.minDistance, controls.maxDistance);
69
+ const pivot = space.planToWorld(around, _pivot);
70
+ const target = controls
71
+ .getTarget(_target, false)
72
+ .sub(pivot)
73
+ .multiplyScalar(to / from)
74
+ .add(pivot);
75
+ return Promise.all([
76
+ controls.moveTo(target.x, target.y, target.z, true),
77
+ controls.dollyTo(to, true),
78
+ ]);
79
+ }
80
+ const _pivot = new Vector3();
81
+ const _target = new Vector3();
82
+ const _spherical = new Spherical();
@@ -0,0 +1,60 @@
1
+ /**
2
+ * How the glide a motion leaves behind is measured, and how it slows.
3
+ *
4
+ * The speed is taken from the motion's own samples over the last stretch of the interaction, so
5
+ * the threshold is in the motion's units per millisecond: css pixels for a pan.
6
+ */
7
+ export interface InertiaConfig {
8
+ /**
9
+ * How far back the interaction is sampled to measure its speed, in milliseconds; defaults to
10
+ * `50`. The oldest sample at least this old is the one differenced against, so a flick that ended
11
+ * in a stall glides at the stall's speed rather than the flick's.
12
+ */
13
+ minSampleMs?: number;
14
+ /** How long the glide lasts, in milliseconds; defaults to `1000`. */
15
+ durationMs?: number;
16
+ /**
17
+ * The speed below which a lifted pointer leaves no glide at all, in the motion's units per
18
+ * millisecond; defaults to `0.08`, sized for a pan in css pixels.
19
+ */
20
+ speedThreshold?: number;
21
+ /**
22
+ * How the glide slows, mapping `[0, 1]` to `[0, 1]`; defaults to {@link easeInertia}.
23
+ *
24
+ * Called every frame of the glide with the time **remaining**, so it is read as "how much of the
25
+ * original speed is left" and should return `1` at `1` and `0` at `0`. Hoist it: an inline
26
+ * function is a fresh identity on every render, which re-syncs the glide's options needlessly.
27
+ */
28
+ ease?: (t: number) => number;
29
+ }
30
+ /** Props for {@link Inertia}: the motion it carries on, and how. */
31
+ export interface InertiaProps extends InertiaConfig {
32
+ /**
33
+ * How far the motion moved since the last call, or `undefined` when this step of the interaction
34
+ * was not one of its own. Called on every `control` event the controls report, so it measures the
35
+ * motion directly rather than inferring it from where the camera went.
36
+ */
37
+ sample: () => readonly number[] | undefined;
38
+ /**
39
+ * Moves the camera by one step of the glide, in the same units as the samples.
40
+ * @returns Whether it moved; `false` ends the glide.
41
+ */
42
+ apply: (step: readonly number[]) => boolean;
43
+ }
44
+ /**
45
+ * The glide a lifted pointer leaves behind, for one kind of motion.
46
+ *
47
+ * MapLibre's shape: each kind of motion measures itself while the user drives it, and when the
48
+ * interaction ends it glides on at the speed it ended with — rather than one glide reading the
49
+ * camera after the fact, where a pan, a pinch's slide toward the fingers and a twist's turn about
50
+ * them all arrive mixed into the same target. Generic over the motion: the caller says how to
51
+ * sample it and how to apply a step of it, and this owns the timing.
52
+ *
53
+ * Runs its frame callback **ahead of the controls' own** (see {@link INERTIA_PRIORITY}), so the
54
+ * camera it moves is the one that frame draws rather than the next. It asks for the next frame
55
+ * itself, which is what keeps a glide alive under the on-demand loop.
56
+ * @param props - What the glide carries on, and how it is measured and slows.
57
+ * @returns Nothing; this renders no scene node.
58
+ */
59
+ export declare function Inertia(props: InertiaProps): null;
60
+ //# sourceMappingURL=Inertia.d.ts.map
@@ -0,0 +1,222 @@
1
+ import { useFrame, useThree } from '@react-three/fiber';
2
+ import { useEffect, useState } from 'react';
3
+ import { useControlsEffect } from '../hooks';
4
+ /**
5
+ * The glide a lifted pointer leaves behind, for one kind of motion.
6
+ *
7
+ * MapLibre's shape: each kind of motion measures itself while the user drives it, and when the
8
+ * interaction ends it glides on at the speed it ended with — rather than one glide reading the
9
+ * camera after the fact, where a pan, a pinch's slide toward the fingers and a twist's turn about
10
+ * them all arrive mixed into the same target. Generic over the motion: the caller says how to
11
+ * sample it and how to apply a step of it, and this owns the timing.
12
+ *
13
+ * Runs its frame callback **ahead of the controls' own** (see {@link INERTIA_PRIORITY}), so the
14
+ * camera it moves is the one that frame draws rather than the next. It asks for the next frame
15
+ * itself, which is what keeps a glide alive under the on-demand loop.
16
+ * @param props - What the glide carries on, and how it is measured and 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 { sample, apply } = props;
25
+ const invalidate = useThree((state) => state.invalidate);
26
+ const [inertia] = useState(() => new InertiaController({ minSampleMs, durationMs, speedThreshold, ease }, sample, apply));
27
+ // Assignments only, so a caller passing an inline `ease` or a fresh `sample` re-syncs fields and
28
+ // disturbs nothing else — in particular it never re-subscribes, which is why this is its own
29
+ // effect.
30
+ useEffect(() => {
31
+ inertia.configure({ minSampleMs, durationMs, speedThreshold, ease }, sample, apply);
32
+ }, [inertia, minSampleMs, durationMs, speedThreshold, ease, sample, apply]);
33
+ useControlsEffect((controls) => {
34
+ inertia.connect(controls);
35
+ return () => inertia.disconnect();
36
+ });
37
+ useFrame((_state, delta) => {
38
+ if (inertia.update(delta))
39
+ invalidate();
40
+ }, INERTIA_PRIORITY);
41
+ return null;
42
+ }
43
+ /**
44
+ * The default glide easing: an exponential ease-in, so most of the speed is shed early and the last
45
+ * of it trails off.
46
+ * @param t - How much of the glide's time is left, from `1` to `0`.
47
+ * @returns How much of the original speed to carry, from `1` to `0`.
48
+ */
49
+ function easeInertia(t) {
50
+ return Math.pow(2, 10 * (t - 1));
51
+ }
52
+ /**
53
+ * Carries a motion on after the pointer lifts.
54
+ *
55
+ * Framework-free on purpose: everything here is arithmetic over a running total and a list of
56
+ * timestamps, which is the half worth testing without a canvas around it. {@link Inertia} is the
57
+ * React half — when to connect, when to configure, when to step.
58
+ *
59
+ * **The speed comes from the motion's own samples.** Each `control` event adds the motion's delta
60
+ * to a running total and records it; when the pointer lifts, the oldest record at least
61
+ * `minSampleMs` old is diffed against the total. A flick that ended in a stall therefore glides at
62
+ * the stall's speed, because the records covering the stall are the recent ones. A step the motion
63
+ * sat out adds nothing, so an interaction it never took part in glides nowhere.
64
+ */
65
+ class InertiaController {
66
+ minSampleMs;
67
+ durationMs;
68
+ speedThreshold;
69
+ ease;
70
+ sample;
71
+ apply;
72
+ controls;
73
+ total = [];
74
+ records = [];
75
+ velocity = [];
76
+ step = [];
77
+ active = false;
78
+ elapsedMs = 0;
79
+ prevFactor = 1;
80
+ /**
81
+ * Takes every value rather than defaulting any: what an omitted option means is {@link Inertia}'s
82
+ * to answer, and answering it twice is how the two drift apart.
83
+ * @param options - How the glide is measured and how it slows.
84
+ * @param sample - How far the motion moved since the last call.
85
+ * @param apply - Moves the camera by one step of the glide.
86
+ */
87
+ constructor(options, sample, apply) {
88
+ this.minSampleMs = options.minSampleMs;
89
+ this.durationMs = options.durationMs;
90
+ this.speedThreshold = options.speedThreshold;
91
+ this.ease = options.ease;
92
+ this.sample = sample;
93
+ this.apply = apply;
94
+ }
95
+ /**
96
+ * Sets the motion, and how the glide is measured and slows.
97
+ * @param options - How the glide is measured and how it slows.
98
+ * @param sample - How far the motion moved since the last call.
99
+ * @param apply - Moves the camera by one step of the glide.
100
+ */
101
+ configure(options, sample, apply) {
102
+ this.minSampleMs = options.minSampleMs;
103
+ this.durationMs = options.durationMs;
104
+ this.speedThreshold = options.speedThreshold;
105
+ this.ease = options.ease;
106
+ this.sample = sample;
107
+ this.apply = apply;
108
+ }
109
+ /**
110
+ * Starts watching a controls instance for the interactions a glide follows.
111
+ * @param controls - The controls to watch.
112
+ */
113
+ connect(controls) {
114
+ if (this.controls)
115
+ this.disconnect();
116
+ this.controls = controls;
117
+ controls.addEventListener('controlstart', this.onControlStart);
118
+ controls.addEventListener('control', this.onControl);
119
+ controls.addEventListener('controlend', this.onControlEnd);
120
+ }
121
+ /** Stops watching, and abandons any glide in flight. */
122
+ disconnect() {
123
+ const controls = this.controls;
124
+ if (!controls)
125
+ return;
126
+ this.active = false;
127
+ this.records.length = 0;
128
+ controls.removeEventListener('controlstart', this.onControlStart);
129
+ controls.removeEventListener('control', this.onControl);
130
+ controls.removeEventListener('controlend', this.onControlEnd);
131
+ this.controls = undefined;
132
+ }
133
+ /**
134
+ * Advances a glide in flight by one frame.
135
+ *
136
+ * The speed applied is the average of this frame's easing factor and the last one's, which
137
+ * integrates the curve over the frame rather than sampling it at one end — so the distance
138
+ * covered does not depend on how the frames happened to fall.
139
+ * @param delta - Time since the last frame, in seconds.
140
+ * @returns Whether the camera moved, and so whether another frame is wanted.
141
+ */
142
+ update(delta) {
143
+ if (!this.active)
144
+ return false;
145
+ const deltaMs = delta * 1000;
146
+ this.elapsedMs += deltaMs;
147
+ const t = Math.min(1, this.elapsedMs / this.durationMs);
148
+ const factor = this.ease(1 - t); // 1 -> 0
149
+ const avgFactor = (factor + this.prevFactor) * 0.5;
150
+ this.prevFactor = factor;
151
+ for (let i = 0; i < this.velocity.length; i++) {
152
+ this.step[i] = this.velocity[i] * avgFactor * deltaMs;
153
+ }
154
+ if (!this.apply(this.step)) {
155
+ this.active = false;
156
+ return false;
157
+ }
158
+ if (factor <= REST_FACTOR || t >= 1)
159
+ this.active = false;
160
+ return true;
161
+ }
162
+ onControlStart = () => {
163
+ this.active = false;
164
+ this.records.length = 0;
165
+ this.total.fill(0);
166
+ };
167
+ onControl = () => {
168
+ const delta = this.sample();
169
+ const now = performance.now();
170
+ if (delta) {
171
+ for (let i = 0; i < delta.length; i++)
172
+ this.total[i] = (this.total[i] ?? 0) + delta[i];
173
+ }
174
+ this.records.push({ t: now, total: this.total.slice() });
175
+ const cutoff = now - SAMPLE_WINDOW_MS;
176
+ while (this.records.length && this.records[0].t < cutoff)
177
+ this.records.shift();
178
+ };
179
+ onControlEnd = () => {
180
+ const now = performance.now();
181
+ const oldest = now - this.minSampleMs;
182
+ for (let i = this.records.length - 1; i >= 0; i--) {
183
+ const record = this.records[i];
184
+ // The first record old enough to measure against — or the oldest there is, when the whole
185
+ // interaction was shorter than the sampling window.
186
+ if (record.t >= oldest && i !== 0)
187
+ continue;
188
+ const dt = now - record.t;
189
+ if (dt <= 0)
190
+ return;
191
+ let speedSquared = 0;
192
+ this.velocity.length = this.total.length;
193
+ this.step.length = this.total.length;
194
+ for (let axis = 0; axis < this.total.length; axis++) {
195
+ this.velocity[axis] = (this.total[axis] - (record.total[axis] ?? 0)) / dt;
196
+ speedSquared += this.velocity[axis] * this.velocity[axis];
197
+ }
198
+ if (Math.sqrt(speedSquared) <= this.speedThreshold)
199
+ return;
200
+ this.elapsedMs = 0;
201
+ this.prevFactor = 1;
202
+ this.active = true;
203
+ return;
204
+ }
205
+ };
206
+ }
207
+ /**
208
+ * The frame priority the glide steps at: ahead of the controls, which run at `-1`.
209
+ *
210
+ * Negative on both counts, so neither takes over rendering from R3F — a positive priority would.
211
+ */
212
+ const INERTIA_PRIORITY = -2;
213
+ /** How much of an interaction is kept to measure its speed from, in milliseconds. */
214
+ const SAMPLE_WINDOW_MS = 300;
215
+ /** The easing factor below which what is left of a glide is not worth another frame. */
216
+ const REST_FACTOR = 0.02;
217
+ /** How far back an interaction is sampled to measure the speed it should glide at, in milliseconds. */
218
+ const DEFAULT_SAMPLE_MS = 50;
219
+ /** How long a glide lasts, in milliseconds. */
220
+ const DEFAULT_DURATION_MS = 1000;
221
+ /** The speed below which a lifted pointer leaves no glide, in the motion's units per millisecond. */
222
+ 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 PanInertia}, 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