@expofp/renderer 3.2.5 → 3.27.0
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 +111 -0
- package/dist/Renderer.d.ts +88 -0
- package/dist/Renderer.js +53 -0
- package/dist/controls/CameraControls.d.ts +102 -0
- package/dist/controls/CameraControls.js +239 -0
- package/dist/controls/camera-api.d.ts +206 -0
- package/dist/controls/camera-api.js +181 -0
- package/dist/controls/camera-move.d.ts +13 -0
- package/dist/controls/camera-move.js +18 -0
- package/dist/controls/camera-state.d.ts +40 -0
- package/dist/controls/camera-state.js +65 -0
- package/dist/controls/gestures.d.ts +70 -0
- package/dist/controls/gestures.js +84 -0
- package/dist/controls/handlers/Inertia.d.ts +111 -0
- package/dist/controls/handlers/Inertia.js +215 -0
- package/dist/controls/handlers/Pan.d.ts +23 -0
- package/dist/controls/handlers/Pan.js +32 -0
- package/dist/controls/handlers/Pitch.d.ts +94 -0
- package/dist/controls/handlers/Pitch.js +191 -0
- package/dist/controls/handlers/Roll.d.ts +103 -0
- package/dist/controls/handlers/Roll.js +182 -0
- package/dist/controls/handlers/Zoom.d.ts +15 -0
- package/dist/controls/handlers/Zoom.js +26 -0
- package/dist/controls/index.d.ts +56 -0
- package/dist/controls/index.js +58 -0
- package/dist/controls/zoom-to.d.ts +89 -0
- package/dist/controls/zoom-to.js +91 -0
- package/dist/core/InstanceUniforms.d.ts +93 -0
- package/dist/core/InstanceUniforms.js +266 -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 +149 -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 +224 -0
- package/dist/core/lines/BatchedLines.d.ts +135 -0
- package/dist/core/lines/BatchedLines.js +213 -0
- package/dist/core/lines/FrustumCulling.d.ts +43 -0
- package/dist/core/lines/FrustumCulling.js +85 -0
- package/dist/core/lines/Raycasting.d.ts +44 -0
- package/dist/core/lines/Raycasting.js +91 -0
- package/dist/core/lines/lines-material.d.ts +47 -0
- package/dist/core/lines/lines-material.js +115 -0
- package/dist/core/shader-source.d.ts +50 -0
- package/dist/core/shader-source.js +84 -0
- package/dist/core/text/BatchedText.d.ts +227 -0
- package/dist/core/text/BatchedText.js +467 -0
- package/dist/core/text/MemberTexture.d.ts +90 -0
- package/dist/core/text/MemberTexture.js +194 -0
- package/dist/core/text/Text.d.ts +73 -0
- package/dist/core/text/Text.js +215 -0
- package/dist/core/text/batched-text-material.d.ts +56 -0
- package/dist/core/text/batched-text-material.js +121 -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 +38 -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 +20 -0
- package/dist/debug/components/Gui.js +32 -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 +98 -0
- package/dist/debug/components/Stats.d.ts +9 -0
- package/dist/debug/components/Stats.js +24 -0
- package/dist/debug/debug-context.d.ts +41 -0
- package/dist/debug/debug-context.js +59 -0
- package/dist/debug/index.d.ts +51 -0
- package/dist/debug/index.js +64 -0
- package/dist/events/LayoutDriver.d.ts +31 -0
- package/dist/events/LayoutDriver.js +88 -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 +104 -0
- package/dist/events/layout-targets.js +245 -0
- package/dist/events/pointer.d.ts +118 -0
- package/dist/events/pointer.js +168 -0
- package/dist/external/ExternalCanvas.d.ts +56 -0
- package/dist/external/ExternalCanvas.js +178 -0
- package/dist/external/external-camera.d.ts +17 -0
- package/dist/external/external-camera.js +82 -0
- package/dist/external/external-events.d.ts +14 -0
- package/dist/external/external-events.js +33 -0
- package/dist/index.d.ts +11 -783
- package/dist/index.js +12 -8031
- package/dist/objects/Background.d.ts +31 -0
- package/dist/objects/Background.js +37 -0
- package/dist/objects/Lighting.d.ts +20 -0
- package/dist/objects/Lighting.js +85 -0
- package/dist/objects/containers/SlotContainer.d.ts +266 -0
- package/dist/objects/containers/SlotContainer.js +251 -0
- package/dist/objects/containers/hooks.d.ts +46 -0
- package/dist/objects/containers/hooks.js +118 -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 +23 -0
- package/dist/objects/containers/props.js +1 -0
- package/dist/objects/images/ImageInstance.d.ts +29 -0
- package/dist/objects/images/ImageInstance.js +83 -0
- package/dist/objects/images/Images.d.ts +37 -0
- package/dist/objects/images/Images.js +204 -0
- package/dist/objects/images/atlas-packer.d.ts +128 -0
- package/dist/objects/images/atlas-packer.js +315 -0
- package/dist/objects/images/image-container.d.ts +51 -0
- package/dist/objects/images/image-container.js +70 -0
- package/dist/objects/images/index.d.ts +2 -0
- package/dist/objects/images/index.js +1 -0
- package/dist/objects/images/texture-budget.d.ts +120 -0
- package/dist/objects/images/texture-budget.js +215 -0
- package/dist/objects/layer/Layer.d.ts +27 -0
- package/dist/objects/layer/Layer.js +114 -0
- package/dist/objects/layer/index.d.ts +2 -0
- package/dist/objects/layer/index.js +1 -0
- package/dist/objects/layer/mount-scheduler.d.ts +102 -0
- package/dist/objects/layer/mount-scheduler.js +145 -0
- package/dist/objects/layer/visibility-gui.d.ts +19 -0
- package/dist/objects/layer/visibility-gui.js +93 -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 +29 -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 +85 -0
- package/dist/objects/materials/ColorMaterial.js +89 -0
- package/dist/objects/materials/LineMaterial.d.ts +44 -0
- package/dist/objects/materials/LineMaterial.js +72 -0
- package/dist/objects/materials/ModelMaterial.d.ts +41 -0
- package/dist/objects/materials/ModelMaterial.js +84 -0
- package/dist/objects/materials/TextureMaterial.d.ts +30 -0
- package/dist/objects/materials/TextureMaterial.js +29 -0
- package/dist/objects/materials/dim.d.ts +123 -0
- package/dist/objects/materials/dim.js +213 -0
- package/dist/objects/materials/shared.d.ts +6 -0
- package/dist/objects/materials/shared.js +51 -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 +177 -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 +139 -0
- package/dist/objects/models/model-container.js +227 -0
- package/dist/objects/shapes/ShapeInstance.d.ts +26 -0
- package/dist/objects/shapes/ShapeInstance.js +40 -0
- package/dist/objects/shapes/Shapes.d.ts +21 -0
- package/dist/objects/shapes/Shapes.js +120 -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 +165 -0
- package/dist/objects/shapes/shape-container.js +272 -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 +41 -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 +105 -0
- package/dist/objects/text/text-container.js +206 -0
- package/dist/objects/text/text-layout.d.ts +83 -0
- package/dist/objects/text/text-layout.js +154 -0
- package/dist/space/Camera.d.ts +103 -0
- package/dist/space/Camera.js +321 -0
- package/dist/space/Scene.d.ts +44 -0
- package/dist/space/Scene.js +112 -0
- package/dist/space/Space.d.ts +288 -0
- package/dist/space/Space.js +329 -0
- package/dist/space/VisibleRect.d.ts +72 -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 +55 -0
- package/dist/space/clipping.js +108 -0
- package/dist/space/layout-measure.d.ts +65 -0
- package/dist/space/layout-measure.js +173 -0
- package/dist/space/viewport.d.ts +112 -0
- package/dist/space/viewport.js +67 -0
- package/dist/types/defs.d.ts +169 -0
- package/dist/types/defs.js +1 -0
- package/dist/types/events.d.ts +183 -0
- package/dist/types/events.js +1 -0
- package/dist/types/guards.d.ts +14 -0
- package/dist/types/guards.js +42 -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 +72 -0
- package/dist/update/store.d.ts +41 -0
- package/dist/update/store.js +98 -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/logging.d.ts +26 -0
- package/dist/utils/logging.js +47 -0
- package/dist/utils/math.d.ts +24 -0
- package/dist/utils/math.js +27 -0
- package/package.json +67 -19
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { degToRad } from '@expofp/geometry';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Vector2 } from 'three';
|
|
4
|
+
import { CameraControlsImpl, useCameraControls, useControlsEffect } from '../CameraControls';
|
|
5
|
+
import { canvasPoint, useGesture } from '../gestures';
|
|
6
|
+
/**
|
|
7
|
+
* Pitching: a right-drag, and two fingers dragged vertically together.
|
|
8
|
+
*
|
|
9
|
+
* The right-drag is camera-controls' own polar rotation. The vertical drag is not, and the hard
|
|
10
|
+
* part is not applying it but telling it apart from the pinch and the twist that share two fingers.
|
|
11
|
+
* That judgement lives in {@link beginsVertically}, and until it is settled the gesture does
|
|
12
|
+
* nothing.
|
|
13
|
+
*
|
|
14
|
+
* The pitch **limits** are not here. They constrain the camera whether or not this handler is
|
|
15
|
+
* mounted — the imperative renderer applied them from its pitch handler's constructor for exactly
|
|
16
|
+
* that reason — so they are `Camera`'s, alongside the zoom bounds and the clipping planes derived
|
|
17
|
+
* from both.
|
|
18
|
+
*
|
|
19
|
+
* Mounting is enabling. The mouse action shares a field with `Roll`, so it is set and cleared with
|
|
20
|
+
* `|=` and `&= ~`, which are idempotent where an exclusive-or would not be.
|
|
21
|
+
* @returns Nothing; this renders no scene node.
|
|
22
|
+
*/
|
|
23
|
+
export function Pitch() {
|
|
24
|
+
// Read, not written: the gesture below takes the controls as an argument and does the writing.
|
|
25
|
+
const controls = useCameraControls();
|
|
26
|
+
const [gesture] = useState(() => new PitchGesture());
|
|
27
|
+
useControlsEffect((controls) => {
|
|
28
|
+
controls.mouseButtons.right |= ACTION.ROTATE_POLAR;
|
|
29
|
+
return () => {
|
|
30
|
+
controls.mouseButtons.right &= ~ACTION.ROTATE_POLAR;
|
|
31
|
+
// Unmounting mid-gesture would otherwise leave the two-finger action switched off for
|
|
32
|
+
// whichever handler owns it.
|
|
33
|
+
gesture.end(controls);
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
// Each of these is cached by the React Compiler against what it captures, so `useGesture`
|
|
37
|
+
// re-subscribes only when one of those actually moves — in practice, never.
|
|
38
|
+
const onPitchStart = (event) => gesture.start(event);
|
|
39
|
+
const onPitch = (event) => {
|
|
40
|
+
if (controls)
|
|
41
|
+
gesture.move(event, controls);
|
|
42
|
+
};
|
|
43
|
+
const onPitchEnd = () => gesture.end(controls);
|
|
44
|
+
useGesture('pitchstart', onPitchStart);
|
|
45
|
+
useGesture('pitch', onPitch);
|
|
46
|
+
useGesture('pitchend', onPitchEnd);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Two fingers dragged vertically together, tilting the plan.
|
|
51
|
+
*
|
|
52
|
+
* A class rather than fields on the component, for two reasons that agree: the React Compiler
|
|
53
|
+
* refuses a direct write to anything `useState` returned, and the gesture's arbitration is worth
|
|
54
|
+
* testing without a canvas around it. {@link Pitch} is the React half.
|
|
55
|
+
*/
|
|
56
|
+
export class PitchGesture {
|
|
57
|
+
verdict = {};
|
|
58
|
+
lastPoints;
|
|
59
|
+
prevTwoFingerAction;
|
|
60
|
+
p0 = new Vector2();
|
|
61
|
+
p1 = new Vector2();
|
|
62
|
+
/**
|
|
63
|
+
* Remembers where the fingers started, and rules the gesture out when they are stacked one above
|
|
64
|
+
* the other — from there a vertical drag is indistinguishable from a pinch.
|
|
65
|
+
* @param event - The gesture event that began the drag.
|
|
66
|
+
*/
|
|
67
|
+
start(event) {
|
|
68
|
+
const pointers = byPointerId(event);
|
|
69
|
+
if (pointers.length !== 2)
|
|
70
|
+
return;
|
|
71
|
+
this.p0.copy(canvasPoint(pointers[0]));
|
|
72
|
+
this.p1.copy(canvasPoint(pointers[1]));
|
|
73
|
+
this.lastPoints = [new Vector2().copy(this.p0), new Vector2().copy(this.p1)];
|
|
74
|
+
if (isVertical(this.p0.sub(this.p1)))
|
|
75
|
+
this.verdict.isValid = false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Tilts the plan by how far the fingers moved down the screen, once the gesture has been judged
|
|
79
|
+
* a pitch.
|
|
80
|
+
* @param event - The gesture event carrying the drag so far.
|
|
81
|
+
* @param controls - The controls to tilt.
|
|
82
|
+
*/
|
|
83
|
+
move(event, controls) {
|
|
84
|
+
const lastPoints = this.lastPoints;
|
|
85
|
+
if (!lastPoints)
|
|
86
|
+
return;
|
|
87
|
+
const pointers = byPointerId(event);
|
|
88
|
+
if (pointers.length !== 2)
|
|
89
|
+
return;
|
|
90
|
+
// From here on `p0` and `p1` are movements rather than positions.
|
|
91
|
+
this.p0.copy(canvasPoint(pointers[0])).sub(lastPoints[0]);
|
|
92
|
+
this.p1.copy(canvasPoint(pointers[1])).sub(lastPoints[1]);
|
|
93
|
+
if (!beginsVertically(this.p0, this.p1, event.timeStamp, this.verdict))
|
|
94
|
+
return;
|
|
95
|
+
// The pinch would otherwise dolly the plan while it is being tilted. Captured rather than
|
|
96
|
+
// assumed, so whatever the zoom handler put there is what comes back at the end of the gesture.
|
|
97
|
+
if (this.prevTwoFingerAction === undefined) {
|
|
98
|
+
this.prevTwoFingerAction = controls.touches.two;
|
|
99
|
+
controls.touches.two = ACTION.NONE;
|
|
100
|
+
}
|
|
101
|
+
lastPoints[0].add(this.p0);
|
|
102
|
+
lastPoints[1].add(this.p1);
|
|
103
|
+
const yDeltaAverage = (this.p0.y + this.p1.y) / 2;
|
|
104
|
+
const deltaAngle = degToRad(yDeltaAverage * DEGREES_PER_PIXEL);
|
|
105
|
+
void controls.rotatePolarTo(controls.polarAngle + deltaAngle, false);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Puts back the two-finger action this suppressed, if it suppressed one, and forgets the gesture.
|
|
109
|
+
*
|
|
110
|
+
* Nothing is restored when the captured action was already `NONE`: there was nothing to suppress,
|
|
111
|
+
* and writing it back would claim a field this handler does not own.
|
|
112
|
+
* @param controls - The controls to restore, if they are still there.
|
|
113
|
+
*/
|
|
114
|
+
end(controls) {
|
|
115
|
+
if (controls && this.prevTwoFingerAction)
|
|
116
|
+
controls.touches.two = this.prevTwoFingerAction;
|
|
117
|
+
this.prevTwoFingerAction = undefined;
|
|
118
|
+
this.verdict = {};
|
|
119
|
+
this.lastPoints = undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Whether a two-finger gesture is a pitch, judged from how the fingers first moved.
|
|
124
|
+
*
|
|
125
|
+
* Judged once and then held: the verdict is returned unchanged as soon as it has one, so a drag
|
|
126
|
+
* that started as a pitch stays one even if the fingers wander, and one that did not can never
|
|
127
|
+
* become one. A `false` verdict is what makes a pinch or a twist stay a pinch or a twist.
|
|
128
|
+
*
|
|
129
|
+
* The two fingers rarely start moving on the same frame, so one moving alone is not by itself a
|
|
130
|
+
* verdict — the other is given {@link ALLOWED_SINGLE_TOUCH_TIME} to follow, and only when it fails
|
|
131
|
+
* to is the gesture ruled out.
|
|
132
|
+
* @param a - How far the first finger moved since the last event.
|
|
133
|
+
* @param b - How far the second finger moved since the last event.
|
|
134
|
+
* @param timeStamp - When this event happened, in milliseconds.
|
|
135
|
+
* @param verdict - How far the judgement has got; updated in place.
|
|
136
|
+
* @returns Whether this is a pitch — `false` both for "not a pitch" and for "not yet decided", the
|
|
137
|
+
* two cases a caller treats alike by doing nothing.
|
|
138
|
+
*/
|
|
139
|
+
export function beginsVertically(a, b, timeStamp, verdict) {
|
|
140
|
+
verdict.isValid = judge(a, b, timeStamp, verdict);
|
|
141
|
+
return verdict.isValid === true;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether a movement is more down-the-screen than across it.
|
|
145
|
+
* @param vector - The movement to judge.
|
|
146
|
+
* @returns Whether it is vertical.
|
|
147
|
+
*/
|
|
148
|
+
export function isVertical(vector) {
|
|
149
|
+
return Math.abs(vector.y) > Math.abs(vector.x);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The judgement itself, which unlike {@link beginsVertically} distinguishes "not a pitch" from
|
|
153
|
+
* "the fingers have not said yet".
|
|
154
|
+
* @param a - How far the first finger moved since the last event.
|
|
155
|
+
* @param b - How far the second finger moved since the last event.
|
|
156
|
+
* @param timeStamp - When this event happened, in milliseconds.
|
|
157
|
+
* @param verdict - How far the judgement has got; its `firstMove` is set when one finger moves alone.
|
|
158
|
+
* @returns Whether this is a pitch, or `undefined` while the fingers have not said.
|
|
159
|
+
*/
|
|
160
|
+
function judge(a, b, timeStamp, verdict) {
|
|
161
|
+
if (verdict.isValid !== undefined)
|
|
162
|
+
return verdict.isValid;
|
|
163
|
+
const movedA = a.length() >= MOVE_THRESHOLD_PX;
|
|
164
|
+
const movedB = b.length() >= MOVE_THRESHOLD_PX;
|
|
165
|
+
// Neither finger has moved a meaningful amount; wait.
|
|
166
|
+
if (!movedA && !movedB)
|
|
167
|
+
return undefined;
|
|
168
|
+
// One finger has moved and the other has not. Wait a moment for the second, then give up.
|
|
169
|
+
if (!movedA || !movedB) {
|
|
170
|
+
verdict.firstMove ??= timeStamp;
|
|
171
|
+
return timeStamp - verdict.firstMove < ALLOWED_SINGLE_TOUCH_TIME ? undefined : false;
|
|
172
|
+
}
|
|
173
|
+
const sameDirection = a.y > 0 === b.y > 0;
|
|
174
|
+
return isVertical(a) && isVertical(b) && sameDirection;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The gesture's pointers in a stable order, so "the first finger" means the same finger from one
|
|
178
|
+
* event to the next and its movement can be diffed.
|
|
179
|
+
* @param event - The gesture event.
|
|
180
|
+
* @returns The pointers ordered by id — a copy, so the recognizer's own array is left alone.
|
|
181
|
+
*/
|
|
182
|
+
function byPointerId(event) {
|
|
183
|
+
return event.pointers.slice().sort((a, b) => a.pointerId - b.pointerId);
|
|
184
|
+
}
|
|
185
|
+
const ACTION = CameraControlsImpl.ACTION;
|
|
186
|
+
/** How far the plan tilts per pixel the fingers travel down the screen, in degrees. */
|
|
187
|
+
const DEGREES_PER_PIXEL = -0.5;
|
|
188
|
+
/** How far a finger must travel before it counts as having moved, in pixels. */
|
|
189
|
+
const MOVE_THRESHOLD_PX = 2;
|
|
190
|
+
/** How long the second finger has to join the first before the gesture is ruled out, in milliseconds. */
|
|
191
|
+
const ALLOWED_SINGLE_TOUCH_TIME = 100;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type MjolnirGestureEvent } from 'mjolnir.js';
|
|
2
|
+
import { type GestureContext } from '../gestures';
|
|
3
|
+
/** Options for the roll handler, which are also its props. */
|
|
4
|
+
export interface RollConfig {
|
|
5
|
+
/**
|
|
6
|
+
* How far two fingers must twist before the plan starts turning, in pixels along the
|
|
7
|
+
* circumference of the circle they describe; defaults to `25`.
|
|
8
|
+
*
|
|
9
|
+
* Measured along the circumference rather than in degrees — the Mapbox rule — so the same
|
|
10
|
+
* threshold asks for a wide turn when the fingers are close together and a slight one when they
|
|
11
|
+
* are far apart, which is what stops a pinch from turning the plan by accident.
|
|
12
|
+
*/
|
|
13
|
+
threshold?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Rolling: a right-drag, and a two-finger twist.
|
|
17
|
+
*
|
|
18
|
+
* The right-drag is camera-controls' own azimuth rotation. The twist is not — it turns the plan
|
|
19
|
+
* about **the point between the fingers** rather than about whatever the camera is looking at,
|
|
20
|
+
* which is what makes two fingers feel like they are holding the plan itself. camera-controls can
|
|
21
|
+
* only orbit its target, so the twist is recognized off the canvas's gesture manager and applied
|
|
22
|
+
* by {@link RollGesture}.
|
|
23
|
+
*
|
|
24
|
+
* Mounting is enabling. The mouse action is a bit of a shared field — `Pitch` owns the other one —
|
|
25
|
+
* so it is set and cleared with `|=` and `&= ~` rather than assigned. Both are idempotent, which an
|
|
26
|
+
* exclusive-or (the imperative renderer's) is not: `StrictMode` re-running an effect would flip a
|
|
27
|
+
* bit back on.
|
|
28
|
+
* @param props - How far a twist must travel before it turns the plan.
|
|
29
|
+
* @returns Nothing; this renders no scene node.
|
|
30
|
+
*/
|
|
31
|
+
export declare function Roll(props: RollConfig): null;
|
|
32
|
+
/**
|
|
33
|
+
* A two-finger twist, turning the plan about the point between the fingers.
|
|
34
|
+
*
|
|
35
|
+
* A class rather than fields on the component, for two reasons that agree: the React Compiler
|
|
36
|
+
* refuses a direct write to anything `useState` returned, and this half — where the fingers are,
|
|
37
|
+
* how far they have turned, where the pivot lands — is worth testing without a canvas around it.
|
|
38
|
+
* {@link Roll} is the React half: when to subscribe, and what the scene currently holds.
|
|
39
|
+
*/
|
|
40
|
+
export declare class RollGesture {
|
|
41
|
+
private isRolling;
|
|
42
|
+
private startVector?;
|
|
43
|
+
private minDiameter;
|
|
44
|
+
private prevAngle;
|
|
45
|
+
private vector;
|
|
46
|
+
private p0;
|
|
47
|
+
private p1;
|
|
48
|
+
private pivotPlan;
|
|
49
|
+
private pivotWorld;
|
|
50
|
+
private targetWorld;
|
|
51
|
+
private cameraPosition;
|
|
52
|
+
private cameraForward;
|
|
53
|
+
private rotation;
|
|
54
|
+
/**
|
|
55
|
+
* Remembers where the fingers started, which the twist is measured against.
|
|
56
|
+
* @param event - The gesture event that began the twist.
|
|
57
|
+
*/
|
|
58
|
+
start(event: MjolnirGestureEvent): void;
|
|
59
|
+
/**
|
|
60
|
+
* Turns the plan by however far the fingers have twisted since the last event, once the gesture
|
|
61
|
+
* has passed its threshold.
|
|
62
|
+
* @param event - The gesture event carrying the twist so far.
|
|
63
|
+
* @param threshold - How far a twist must travel before it takes, in pixels of circumference.
|
|
64
|
+
* @param context - What the scene currently holds.
|
|
65
|
+
*/
|
|
66
|
+
move(event: MjolnirGestureEvent, threshold: number, context: GestureContext): void;
|
|
67
|
+
/** Forgets the gesture, so the next twist measures its threshold afresh. */
|
|
68
|
+
end(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Whether the twist so far is still inside its threshold, narrowing the remembered diameter as it
|
|
71
|
+
* goes.
|
|
72
|
+
*
|
|
73
|
+
* The narrowest the fingers have been is what the threshold is sized against, rather than where
|
|
74
|
+
* they are now, so pinching in and out during a twist does not make it progressively touchier.
|
|
75
|
+
* @param threshold - The distance along the circumference, in pixels.
|
|
76
|
+
* @returns Whether the plan should stay put.
|
|
77
|
+
*/
|
|
78
|
+
private belowThreshold;
|
|
79
|
+
/**
|
|
80
|
+
* Puts the pivot under the point between the fingers, and reads the camera's stance around it.
|
|
81
|
+
*
|
|
82
|
+
* The pivot is found on the plan and converted back out to the world, rather than crossed with
|
|
83
|
+
* the world's own `z = 0` plane. The two agree under the viewbox fit; only this one stays right
|
|
84
|
+
* for a host that placed the plan itself, at a tilt or off the world origin.
|
|
85
|
+
* @param center - The point between the fingers, in css pixels from the canvas's top-left corner.
|
|
86
|
+
* @param context - What the scene currently holds.
|
|
87
|
+
* @returns Whether a pivot was found — a camera looking along the plan never meets it.
|
|
88
|
+
*/
|
|
89
|
+
private setPivot;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* How far a twist must turn before it takes, in degrees, for fingers this far apart.
|
|
93
|
+
*
|
|
94
|
+
* The threshold is a distance along the circumference of the circle the fingers describe — the
|
|
95
|
+
* Mapbox rule — so the angle it works out to is wide when the fingers are close together and narrow
|
|
96
|
+
* when they are far apart. That is what keeps a pinch from turning the plan by accident while
|
|
97
|
+
* leaving a deliberate two-handed twist responsive.
|
|
98
|
+
* @param minDiameter - The narrowest the fingers have been during the gesture, in pixels.
|
|
99
|
+
* @param threshold - The distance along the circumference, in pixels.
|
|
100
|
+
* @returns The angle in degrees; infinite while the fingers are together, so nothing turns.
|
|
101
|
+
*/
|
|
102
|
+
export declare function rollThresholdDegrees(minDiameter: number, threshold: number): number;
|
|
103
|
+
//# sourceMappingURL=Roll.d.ts.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { degToRad, Point, radToDeg } from '@expofp/geometry';
|
|
2
|
+
import { useThree } from '@react-three/fiber';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Matrix4, Vector2, Vector3 } from 'three';
|
|
5
|
+
import { useSpace } from '../../space/Space';
|
|
6
|
+
import { CameraControlsImpl, useCameraControls, useControlsEffect } from '../CameraControls';
|
|
7
|
+
import { canvasPoint, useGesture } from '../gestures';
|
|
8
|
+
/**
|
|
9
|
+
* Rolling: a right-drag, and a two-finger twist.
|
|
10
|
+
*
|
|
11
|
+
* The right-drag is camera-controls' own azimuth rotation. The twist is not — it turns the plan
|
|
12
|
+
* about **the point between the fingers** rather than about whatever the camera is looking at,
|
|
13
|
+
* which is what makes two fingers feel like they are holding the plan itself. camera-controls can
|
|
14
|
+
* only orbit its target, so the twist is recognized off the canvas's gesture manager and applied
|
|
15
|
+
* by {@link RollGesture}.
|
|
16
|
+
*
|
|
17
|
+
* Mounting is enabling. The mouse action is a bit of a shared field — `Pitch` owns the other one —
|
|
18
|
+
* so it is set and cleared with `|=` and `&= ~` rather than assigned. Both are idempotent, which an
|
|
19
|
+
* exclusive-or (the imperative renderer's) is not: `StrictMode` re-running an effect would flip a
|
|
20
|
+
* bit back on.
|
|
21
|
+
* @param props - How far a twist must travel before it turns the plan.
|
|
22
|
+
* @returns Nothing; this renders no scene node.
|
|
23
|
+
*/
|
|
24
|
+
export function Roll(props) {
|
|
25
|
+
const threshold = props.threshold ?? DEFAULT_THRESHOLD;
|
|
26
|
+
const get = useThree((state) => state.get);
|
|
27
|
+
// Read, not written: the gesture below takes the controls as an argument and does the writing, so
|
|
28
|
+
// nothing here ever assigns to them — which is the line the React Compiler draws.
|
|
29
|
+
const controls = useCameraControls();
|
|
30
|
+
const space = useSpace();
|
|
31
|
+
const [gesture] = useState(() => new RollGesture());
|
|
32
|
+
useControlsEffect((controls) => {
|
|
33
|
+
controls.mouseButtons.right |= ACTION.ROTATE_AZIMUTH;
|
|
34
|
+
return () => {
|
|
35
|
+
controls.mouseButtons.right &= ~ACTION.ROTATE_AZIMUTH;
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
// Each of these is cached by the React Compiler against what it captures, so `useGesture`
|
|
39
|
+
// re-subscribes only when one of those actually moves — in practice, never.
|
|
40
|
+
const onRotateStart = (event) => gesture.start(event);
|
|
41
|
+
const onRotate = (event) => {
|
|
42
|
+
if (controls)
|
|
43
|
+
gesture.move(event, threshold, { controls, camera: get().camera, space });
|
|
44
|
+
};
|
|
45
|
+
const onRotateEnd = () => gesture.end();
|
|
46
|
+
useGesture('rotatestart', onRotateStart);
|
|
47
|
+
useGesture('rotate', onRotate);
|
|
48
|
+
useGesture('rotateend', onRotateEnd);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A two-finger twist, turning the plan about the point between the fingers.
|
|
53
|
+
*
|
|
54
|
+
* A class rather than fields on the component, for two reasons that agree: the React Compiler
|
|
55
|
+
* refuses a direct write to anything `useState` returned, and this half — where the fingers are,
|
|
56
|
+
* how far they have turned, where the pivot lands — is worth testing without a canvas around it.
|
|
57
|
+
* {@link Roll} is the React half: when to subscribe, and what the scene currently holds.
|
|
58
|
+
*/
|
|
59
|
+
export class RollGesture {
|
|
60
|
+
isRolling = false;
|
|
61
|
+
startVector;
|
|
62
|
+
minDiameter = 0;
|
|
63
|
+
prevAngle = 0;
|
|
64
|
+
vector = new Vector2();
|
|
65
|
+
p0 = new Vector2();
|
|
66
|
+
p1 = new Vector2();
|
|
67
|
+
pivotPlan = new Point();
|
|
68
|
+
pivotWorld = new Vector3();
|
|
69
|
+
targetWorld = new Vector3();
|
|
70
|
+
cameraPosition = new Vector3();
|
|
71
|
+
cameraForward = new Vector3();
|
|
72
|
+
rotation = new Matrix4();
|
|
73
|
+
/**
|
|
74
|
+
* Remembers where the fingers started, which the twist is measured against.
|
|
75
|
+
* @param event - The gesture event that began the twist.
|
|
76
|
+
*/
|
|
77
|
+
start(event) {
|
|
78
|
+
const { pointers } = event;
|
|
79
|
+
this.p0.copy(canvasPoint(pointers[0]));
|
|
80
|
+
this.p1.copy(canvasPoint(pointers[1]));
|
|
81
|
+
this.startVector = new Vector2().copy(this.p0).sub(this.p1);
|
|
82
|
+
this.minDiameter = this.startVector.length();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Turns the plan by however far the fingers have twisted since the last event, once the gesture
|
|
86
|
+
* has passed its threshold.
|
|
87
|
+
* @param event - The gesture event carrying the twist so far.
|
|
88
|
+
* @param threshold - How far a twist must travel before it takes, in pixels of circumference.
|
|
89
|
+
* @param context - What the scene currently holds.
|
|
90
|
+
*/
|
|
91
|
+
move(event, threshold, context) {
|
|
92
|
+
const { controls } = context;
|
|
93
|
+
const { pointers } = event;
|
|
94
|
+
if (!this.isRolling) {
|
|
95
|
+
if (pointers.length !== 2)
|
|
96
|
+
return;
|
|
97
|
+
this.p0.copy(canvasPoint(pointers[0]));
|
|
98
|
+
this.p1.copy(canvasPoint(pointers[1]));
|
|
99
|
+
this.vector.copy(this.p0).sub(this.p1);
|
|
100
|
+
if (this.belowThreshold(threshold))
|
|
101
|
+
return;
|
|
102
|
+
this.isRolling = true;
|
|
103
|
+
this.prevAngle = event.rotation;
|
|
104
|
+
}
|
|
105
|
+
// Negated: the recognizer measures the twist clockwise on screen, and the azimuth it drives
|
|
106
|
+
// grows the other way.
|
|
107
|
+
const deltaAngle = -degToRad(event.rotation - this.prevAngle);
|
|
108
|
+
this.prevAngle = event.rotation;
|
|
109
|
+
if (Math.abs(deltaAngle) < MIN_ROLL_STEP)
|
|
110
|
+
return;
|
|
111
|
+
// mjolnir measures the midpoint from the target's own padding edge, as `canvasPoint` does.
|
|
112
|
+
if (!this.setPivot(event.offsetCenter, context))
|
|
113
|
+
return;
|
|
114
|
+
// The plan's own up axis is world z, so turning the plan is a rotation about it.
|
|
115
|
+
this.rotation.makeRotationZ(deltaAngle);
|
|
116
|
+
this.cameraPosition.sub(this.pivotWorld).applyMatrix4(this.rotation).add(this.pivotWorld);
|
|
117
|
+
this.cameraForward.applyMatrix4(this.rotation);
|
|
118
|
+
this.targetWorld.copy(this.cameraPosition).add(this.cameraForward);
|
|
119
|
+
void controls.setLookAt(this.cameraPosition.x, this.cameraPosition.y, this.cameraPosition.z, this.targetWorld.x, this.targetWorld.y, this.targetWorld.z, false);
|
|
120
|
+
}
|
|
121
|
+
/** Forgets the gesture, so the next twist measures its threshold afresh. */
|
|
122
|
+
end() {
|
|
123
|
+
this.isRolling = false;
|
|
124
|
+
this.startVector = undefined;
|
|
125
|
+
this.minDiameter = 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether the twist so far is still inside its threshold, narrowing the remembered diameter as it
|
|
129
|
+
* goes.
|
|
130
|
+
*
|
|
131
|
+
* The narrowest the fingers have been is what the threshold is sized against, rather than where
|
|
132
|
+
* they are now, so pinching in and out during a twist does not make it progressively touchier.
|
|
133
|
+
* @param threshold - The distance along the circumference, in pixels.
|
|
134
|
+
* @returns Whether the plan should stay put.
|
|
135
|
+
*/
|
|
136
|
+
belowThreshold(threshold) {
|
|
137
|
+
this.minDiameter = Math.min(this.minDiameter, this.vector.length());
|
|
138
|
+
const startVector = this.startVector;
|
|
139
|
+
if (!startVector)
|
|
140
|
+
return false;
|
|
141
|
+
return (radToDeg(this.vector.angleTo(startVector)) < rollThresholdDegrees(this.minDiameter, threshold));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Puts the pivot under the point between the fingers, and reads the camera's stance around it.
|
|
145
|
+
*
|
|
146
|
+
* The pivot is found on the plan and converted back out to the world, rather than crossed with
|
|
147
|
+
* the world's own `z = 0` plane. The two agree under the viewbox fit; only this one stays right
|
|
148
|
+
* for a host that placed the plan itself, at a tilt or off the world origin.
|
|
149
|
+
* @param center - The point between the fingers, in css pixels from the canvas's top-left corner.
|
|
150
|
+
* @param context - What the scene currently holds.
|
|
151
|
+
* @returns Whether a pivot was found — a camera looking along the plan never meets it.
|
|
152
|
+
*/
|
|
153
|
+
setPivot(center, context) {
|
|
154
|
+
const { controls, camera, space } = context;
|
|
155
|
+
if (!space.canvasToPlan(center, camera, this.pivotPlan))
|
|
156
|
+
return false;
|
|
157
|
+
space.planToWorld(this.pivotPlan, this.pivotWorld);
|
|
158
|
+
controls.getPosition(this.cameraPosition);
|
|
159
|
+
controls.getTarget(this.targetWorld);
|
|
160
|
+
this.cameraForward.copy(this.targetWorld).sub(this.cameraPosition);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* How far a twist must turn before it takes, in degrees, for fingers this far apart.
|
|
166
|
+
*
|
|
167
|
+
* The threshold is a distance along the circumference of the circle the fingers describe — the
|
|
168
|
+
* Mapbox rule — so the angle it works out to is wide when the fingers are close together and narrow
|
|
169
|
+
* when they are far apart. That is what keeps a pinch from turning the plan by accident while
|
|
170
|
+
* leaving a deliberate two-handed twist responsive.
|
|
171
|
+
* @param minDiameter - The narrowest the fingers have been during the gesture, in pixels.
|
|
172
|
+
* @param threshold - The distance along the circumference, in pixels.
|
|
173
|
+
* @returns The angle in degrees; infinite while the fingers are together, so nothing turns.
|
|
174
|
+
*/
|
|
175
|
+
export function rollThresholdDegrees(minDiameter, threshold) {
|
|
176
|
+
return (threshold / (Math.PI * minDiameter)) * 360;
|
|
177
|
+
}
|
|
178
|
+
const ACTION = CameraControlsImpl.ACTION;
|
|
179
|
+
/** The smallest turn worth applying, in radians — below this the twist is recognizer noise. */
|
|
180
|
+
const MIN_ROLL_STEP = 0.001;
|
|
181
|
+
/** How far a two-finger twist must travel before it turns the plan, in pixels of circumference. */
|
|
182
|
+
const DEFAULT_THRESHOLD = 25;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zooming: the wheel, and a two-finger pinch.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here beyond the actions — camera-controls recognizes both gestures itself, and `Controls`
|
|
5
|
+
* has already told it to dolly toward the cursor rather than the centre. The two-finger action pans
|
|
6
|
+
* as well as pinches, which is what makes a pinch feel like it holds the plan under the fingers;
|
|
7
|
+
* the twist half of a two-finger gesture is `Roll`'s, and comes off the gesture recognizer rather
|
|
8
|
+
* than from here, so the two never contend for `touches.two`.
|
|
9
|
+
*
|
|
10
|
+
* Mounting is enabling. The actions are set when this mounts and returned to `NONE` when it
|
|
11
|
+
* unmounts, which is the whole of what the imperative renderer's `enable()` / `disable()` did.
|
|
12
|
+
* @returns Nothing; this renders no scene node.
|
|
13
|
+
*/
|
|
14
|
+
export declare function Zoom(): null;
|
|
15
|
+
//# sourceMappingURL=Zoom.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CameraControlsImpl, useControlsEffect } from '../CameraControls';
|
|
2
|
+
/**
|
|
3
|
+
* Zooming: the wheel, and a two-finger pinch.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here beyond the actions — camera-controls recognizes both gestures itself, and `Controls`
|
|
6
|
+
* has already told it to dolly toward the cursor rather than the centre. The two-finger action pans
|
|
7
|
+
* as well as pinches, which is what makes a pinch feel like it holds the plan under the fingers;
|
|
8
|
+
* the twist half of a two-finger gesture is `Roll`'s, and comes off the gesture recognizer rather
|
|
9
|
+
* than from here, so the two never contend for `touches.two`.
|
|
10
|
+
*
|
|
11
|
+
* Mounting is enabling. The actions are set when this mounts and returned to `NONE` when it
|
|
12
|
+
* unmounts, which is the whole of what the imperative renderer's `enable()` / `disable()` did.
|
|
13
|
+
* @returns Nothing; this renders no scene node.
|
|
14
|
+
*/
|
|
15
|
+
export function Zoom() {
|
|
16
|
+
useControlsEffect((controls) => {
|
|
17
|
+
controls.mouseButtons.wheel = ACTION.DOLLY;
|
|
18
|
+
controls.touches.two = ACTION.TOUCH_DOLLY_SCREEN_PAN;
|
|
19
|
+
return () => {
|
|
20
|
+
controls.mouseButtons.wheel = ACTION.NONE;
|
|
21
|
+
controls.touches.two = ACTION.NONE;
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const ACTION = CameraControlsImpl.ACTION;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type PanConfig } from './handlers/Pan';
|
|
3
|
+
import { type RollConfig } from './handlers/Roll';
|
|
4
|
+
export { type CameraAPI, type CameraAxes, CameraCommands, type TransitionOptions, useCameraAPI, type ZoomToOptions, } from './camera-api';
|
|
5
|
+
export { useCameraMoveStart } from './camera-move';
|
|
6
|
+
export { type CameraState, useCameraState } from './camera-state';
|
|
7
|
+
export { bearing, CameraControls, CameraControlsImpl, useCameraControls, useControlsEffect, } from './CameraControls';
|
|
8
|
+
export type { InertiaConfig } from './handlers/Inertia';
|
|
9
|
+
export type { PanConfig } from './handlers/Pan';
|
|
10
|
+
export type { RollConfig } from './handlers/Roll';
|
|
11
|
+
/**
|
|
12
|
+
* What the user may do with the plan.
|
|
13
|
+
*
|
|
14
|
+
* Each handler takes `false` to switch it off, `true` or nothing for its defaults, and — where it
|
|
15
|
+
* has any — an options object, whose own defaults the handler applies.
|
|
16
|
+
*
|
|
17
|
+
* **Pan and zoom default on; roll and pitch default to whether this is a development build**, so
|
|
18
|
+
* the renderer's own Storybook and dev server carry them and a production plan does not until a
|
|
19
|
+
* caller asks. An explicit value always wins, so a host driving roll from a user-facing setting
|
|
20
|
+
* never has to think about the dev default.
|
|
21
|
+
*
|
|
22
|
+
* The pitch and zoom **limits** are not here: they hold whether or not the matching gesture does,
|
|
23
|
+
* which makes them the camera's — see `CameraProps`.
|
|
24
|
+
*/
|
|
25
|
+
export interface ControlsConfig {
|
|
26
|
+
/** Left-drag and one-finger drag to pan the plan. Defaults on. */
|
|
27
|
+
pan?: boolean | PanConfig;
|
|
28
|
+
/** Wheel and two-finger pinch to zoom, toward the cursor. Defaults on. */
|
|
29
|
+
zoom?: boolean;
|
|
30
|
+
/** Right-drag and two-finger twist to turn the plan. Defaults to a development build. */
|
|
31
|
+
roll?: boolean | RollConfig;
|
|
32
|
+
/** Right-drag and two-finger vertical drag to tilt the plan. Defaults to a development build. */
|
|
33
|
+
pitch?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Props for {@link Controls}. */
|
|
36
|
+
export interface ControlsProps {
|
|
37
|
+
/** What the user may do with the plan; see {@link ControlsConfig}. */
|
|
38
|
+
config?: ControlsConfig;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The scene's camera controls, and the gesture handlers that drive them.
|
|
42
|
+
*
|
|
43
|
+
* Registered as R3F's default controls (`makeDefault`) so everything else reaches them through
|
|
44
|
+
* `state.controls`. Every mouse and touch action starts switched off, and each handler mounted
|
|
45
|
+
* below claims the ones it needs — so what a user can do is exactly the set of handlers standing
|
|
46
|
+
* here, and turning one off is unmounting it.
|
|
47
|
+
*
|
|
48
|
+
* **How far the camera may go is not decided here.** The zoom and pitch bounds constrain a
|
|
49
|
+
* programmatic move as much as a dragged one, so they belong to the camera; `Camera` writes them,
|
|
50
|
+
* beside the clipping planes derived from the same numbers. This component is only about what the
|
|
51
|
+
* user may do.
|
|
52
|
+
* @param props - What the user may do with the plan.
|
|
53
|
+
* @returns The controls and their handlers.
|
|
54
|
+
*/
|
|
55
|
+
export declare function Controls(props: ControlsProps): ReactNode;
|
|
56
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { CameraControls } from './CameraControls';
|
|
4
|
+
import { Pan } from './handlers/Pan';
|
|
5
|
+
import { Pitch } from './handlers/Pitch';
|
|
6
|
+
import { Roll } from './handlers/Roll';
|
|
7
|
+
import { Zoom } from './handlers/Zoom';
|
|
8
|
+
export { CameraCommands, useCameraAPI, } from './camera-api';
|
|
9
|
+
export { useCameraMoveStart } from './camera-move';
|
|
10
|
+
export { useCameraState } from './camera-state';
|
|
11
|
+
export { bearing, CameraControls, CameraControlsImpl, useCameraControls, useControlsEffect, } from './CameraControls';
|
|
12
|
+
/**
|
|
13
|
+
* The scene's camera controls, and the gesture handlers that drive them.
|
|
14
|
+
*
|
|
15
|
+
* Registered as R3F's default controls (`makeDefault`) so everything else reaches them through
|
|
16
|
+
* `state.controls`. Every mouse and touch action starts switched off, and each handler mounted
|
|
17
|
+
* below claims the ones it needs — so what a user can do is exactly the set of handlers standing
|
|
18
|
+
* here, and turning one off is unmounting it.
|
|
19
|
+
*
|
|
20
|
+
* **How far the camera may go is not decided here.** The zoom and pitch bounds constrain a
|
|
21
|
+
* programmatic move as much as a dragged one, so they belong to the camera; `Camera` writes them,
|
|
22
|
+
* beside the clipping planes derived from the same numbers. This component is only about what the
|
|
23
|
+
* user may do.
|
|
24
|
+
* @param props - What the user may do with the plan.
|
|
25
|
+
* @returns The controls and their handlers.
|
|
26
|
+
*/
|
|
27
|
+
export function Controls(props) {
|
|
28
|
+
const controlsRef = useRef(null);
|
|
29
|
+
const config = props.config ?? {};
|
|
30
|
+
const pan = config.pan ?? true;
|
|
31
|
+
const zoom = config.zoom ?? true;
|
|
32
|
+
const roll = config.roll ?? DEV;
|
|
33
|
+
const pitch = config.pitch ?? DEV;
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
// Straight down by default. Set through `rotateTo` rather than by assigning `polarAngle`, which
|
|
36
|
+
// skips `Spherical.makeSafe` — and a polar angle of exactly zero causes trouble with roll on
|
|
37
|
+
// mobile. Nothing sets a target: the scene's fit puts the plan's centre on the world origin,
|
|
38
|
+
// which is where the controls already orbit.
|
|
39
|
+
void controlsRef.current.rotateTo(0, 0);
|
|
40
|
+
}, []);
|
|
41
|
+
return (_jsxs(_Fragment, { children: [_jsx(CameraControls, { ref: controlsRef, makeDefault: true, events: true, dollyToCursor: true, controlSmoothTime: { rotate: 0, truck: 0 } }), pan !== false && _jsx(Pan, { ...optionsOf(pan) }), zoom !== false && _jsx(Zoom, {}), roll !== false && _jsx(Roll, { ...optionsOf(roll) }), pitch !== false && _jsx(Pitch, {})] }));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The options a handler was configured with, if it was given any rather than a bare flag.
|
|
45
|
+
* @param config - What the caller wrote for it.
|
|
46
|
+
* @returns The options, or an empty set for a handler switched on without any.
|
|
47
|
+
*/
|
|
48
|
+
function optionsOf(config) {
|
|
49
|
+
return typeof config === 'object' ? config : {};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Whether this is a development build, which is what roll and pitch default to.
|
|
53
|
+
*
|
|
54
|
+
* Typed inline rather than through `vite/client`, matching `stories/support/screenshot.ts`: the
|
|
55
|
+
* library build compiles without those types, and the optional chain keeps a consumer whose bundler
|
|
56
|
+
* defines no `import.meta.env` from throwing on it.
|
|
57
|
+
*/
|
|
58
|
+
const DEV = import.meta.env?.DEV === true;
|