@expofp/renderer 0.0.0-experimental.cf063fc
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,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
|