@opsydyn/effect-three 0.3.0 → 0.4.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/CHANGELOG.md +15 -0
- package/README.md +12 -0
- package/dist/{index-8cf8t6by.js → index-h3vf50fn.js} +2113 -83
- package/dist/index-h3vf50fn.js.map +53 -0
- package/dist/index-ttwy0znv.js +145 -0
- package/dist/index-ttwy0znv.js.map +12 -0
- package/dist/index.js +488 -147
- package/dist/index.js.map +8 -6
- package/dist/kernel/boxSelect.d.ts +22 -0
- package/dist/kernel/boxSelect.d.ts.map +1 -0
- package/dist/kernel/build.d.ts +142 -6
- package/dist/kernel/build.d.ts.map +1 -1
- package/dist/kernel/cameraShake.d.ts.map +1 -1
- package/dist/kernel/describeScene.d.ts +50 -0
- package/dist/kernel/describeScene.d.ts.map +1 -0
- package/dist/kernel/description.d.ts +155 -1
- package/dist/kernel/description.d.ts.map +1 -1
- package/dist/kernel/diff.d.ts +15 -0
- package/dist/kernel/diff.d.ts.map +1 -1
- package/dist/kernel/equality.d.ts +9 -1
- package/dist/kernel/equality.d.ts.map +1 -1
- package/dist/kernel/gizmo.d.ts +69 -0
- package/dist/kernel/gizmo.d.ts.map +1 -0
- package/dist/kernel/history.d.ts +61 -0
- package/dist/kernel/history.d.ts.map +1 -0
- package/dist/kernel/index.d.ts +13 -2
- package/dist/kernel/index.d.ts.map +1 -1
- package/dist/kernel/index.js +106 -3
- package/dist/kernel/index.js.map +1 -1
- package/dist/kernel/inspect.d.ts +85 -0
- package/dist/kernel/inspect.d.ts.map +1 -0
- package/dist/kernel/lineGeometry.d.ts +25 -0
- package/dist/kernel/lineGeometry.d.ts.map +1 -0
- package/dist/kernel/lockstep.d.ts +114 -0
- package/dist/kernel/lockstep.d.ts.map +1 -0
- package/dist/kernel/loop.d.ts.map +1 -1
- package/dist/kernel/materials.d.ts.map +1 -1
- package/dist/kernel/pivot.d.ts +54 -0
- package/dist/kernel/pivot.d.ts.map +1 -0
- package/dist/kernel/pointBuffers.d.ts +17 -0
- package/dist/kernel/pointBuffers.d.ts.map +1 -0
- package/dist/kernel/pointer.d.ts +12 -0
- package/dist/kernel/pointer.d.ts.map +1 -1
- package/dist/kernel/renderKit.d.ts +21 -1
- package/dist/kernel/renderKit.d.ts.map +1 -1
- package/dist/kernel/renderer.d.ts +6 -0
- package/dist/kernel/renderer.d.ts.map +1 -1
- package/dist/kernel/sceneChanges.d.ts +87 -0
- package/dist/kernel/sceneChanges.d.ts.map +1 -0
- package/dist/kernel/sceneGraph.d.ts +6 -0
- package/dist/kernel/sceneGraph.d.ts.map +1 -1
- package/dist/kernel/seeded.d.ts +9 -0
- package/dist/kernel/seeded.d.ts.map +1 -0
- package/dist/kernel/shots.d.ts +26 -0
- package/dist/kernel/shots.d.ts.map +1 -0
- package/dist/kernel/tape.d.ts +38 -0
- package/dist/kernel/tape.d.ts.map +1 -0
- package/dist/kernel/trail.d.ts +31 -0
- package/dist/kernel/trail.d.ts.map +1 -0
- package/dist/kernel/viewportGizmo.d.ts +25 -0
- package/dist/kernel/viewportGizmo.d.ts.map +1 -0
- package/dist/runtime/Inspector.d.ts +20 -0
- package/dist/runtime/Inspector.d.ts.map +1 -0
- package/dist/runtime/SceneFile.d.ts +448 -0
- package/dist/runtime/SceneFile.d.ts.map +1 -0
- package/dist/runtime/SceneSchema.d.ts +318 -0
- package/dist/runtime/SceneSchema.d.ts.map +1 -1
- package/dist/runtime/errors.d.ts +9 -0
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +3 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/webgl/index.d.ts.map +1 -1
- package/dist/webgl/index.js +228 -3
- package/dist/webgl/index.js.map +6 -4
- package/dist/webgl/line.d.ts +4 -0
- package/dist/webgl/line.d.ts.map +1 -0
- package/dist/webgl/points.d.ts +4 -0
- package/dist/webgl/points.d.ts.map +1 -0
- package/dist/webgpu/index.d.ts.map +1 -1
- package/dist/webgpu/index.js +206 -26
- package/dist/webgpu/index.js.map +6 -4
- package/dist/webgpu/line.d.ts +4 -0
- package/dist/webgpu/line.d.ts.map +1 -0
- package/dist/webgpu/points.d.ts +4 -0
- package/dist/webgpu/points.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/kernel/boxSelect.ts +73 -0
- package/src/kernel/build.ts +332 -0
- package/src/kernel/cameraShake.ts +3 -13
- package/src/kernel/describeScene.ts +224 -0
- package/src/kernel/description.ts +175 -0
- package/src/kernel/diff.ts +39 -0
- package/src/kernel/equality.ts +101 -0
- package/src/kernel/gizmo.ts +478 -0
- package/src/kernel/history.ts +118 -0
- package/src/kernel/index.ts +97 -0
- package/src/kernel/inspect.ts +214 -0
- package/src/kernel/lineGeometry.ts +75 -0
- package/src/kernel/lockstep.ts +237 -0
- package/src/kernel/loop.ts +5 -0
- package/src/kernel/materials.ts +3 -0
- package/src/kernel/pivot.ts +418 -0
- package/src/kernel/pointBuffers.ts +115 -0
- package/src/kernel/pointer.ts +55 -12
- package/src/kernel/renderKit.ts +23 -1
- package/src/kernel/renderer.ts +6 -0
- package/src/kernel/sceneChanges.ts +265 -0
- package/src/kernel/sceneGraph.ts +319 -8
- package/src/kernel/seeded.ts +18 -0
- package/src/kernel/shots.ts +167 -0
- package/src/kernel/tape.ts +124 -0
- package/src/kernel/trail.ts +170 -0
- package/src/kernel/viewportGizmo.ts +194 -0
- package/src/runtime/Inspector.ts +43 -0
- package/src/runtime/SceneFile.ts +46 -0
- package/src/runtime/SceneSchema.ts +247 -0
- package/src/runtime/errors.ts +6 -0
- package/src/runtime/index.ts +3 -1
- package/src/webgl/index.ts +4 -0
- package/src/webgl/line.ts +66 -0
- package/src/webgl/points.ts +205 -0
- package/src/webgpu/index.ts +4 -0
- package/src/webgpu/line.ts +67 -0
- package/src/webgpu/points.ts +193 -0
- package/dist/index-8cf8t6by.js.map +0 -41
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pivot controls as data, after Drei's `PivotControls` (`bf6f4ad`,
|
|
3
|
+
* `src/web/pivotControls/`): every handle at once, turned with the object and sized in
|
|
4
|
+
* scene units — an arrow along each axis, a square sliding in each plane, a quarter arc
|
|
5
|
+
* turning round each axis, and a sphere scaling along each. Drei's listen to the pointer
|
|
6
|
+
* and move a group themselves; here `pivotNodes` describes the handles from the Model, a
|
|
7
|
+
* press on one (`pressPivot`, with the point the press hit) and the drag rays after it
|
|
8
|
+
* (`dragPivot`) return the next placement, and the Model keeps both.
|
|
9
|
+
*
|
|
10
|
+
* The maths is Drei's: an arrow and a sphere follow the point on their axis nearest the
|
|
11
|
+
* ray (`calculateOffset`); a square and an arc follow where the ray's line meets their
|
|
12
|
+
* plane, a square by its offset along the plane's two axes, an arc by the angle turned
|
|
13
|
+
* about the object (`calculateAngle`); a sphere scales by 2 to the power of a fifth of
|
|
14
|
+
* the distance dragged, in handle sizes. Snapping is Drei's shift: ten degrees, and
|
|
15
|
+
* scales to a tenth.
|
|
16
|
+
*
|
|
17
|
+
* Placements are in the object's parent's space, which is taken to be the world, as for
|
|
18
|
+
* `gizmoNodes`.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { Euler, Quaternion, Vector3 } from 'three';
|
|
23
|
+
|
|
24
|
+
import { basic, cone, cylinder, fixed, group, mesh, plane, sphere } from './build.ts';
|
|
25
|
+
import type { SceneNode, Vec3 } from './description.ts';
|
|
26
|
+
import type { Placement } from './gizmo.ts';
|
|
27
|
+
import type { Ray } from './pointer.ts';
|
|
28
|
+
|
|
29
|
+
type Axis = 'x' | 'y' | 'z';
|
|
30
|
+
|
|
31
|
+
/** A handle: what it does, and the axis it is on (a square's and an arc's normal). */
|
|
32
|
+
export type PivotHandle = `${'arrow' | 'slider' | 'rotator' | 'sphere'}-${Axis}`;
|
|
33
|
+
|
|
34
|
+
/** A drag in progress: the handle, the placement it began at and the frame it moves in. */
|
|
35
|
+
export type PivotGrab = {
|
|
36
|
+
readonly handle: PivotHandle;
|
|
37
|
+
readonly start: Placement;
|
|
38
|
+
/** Where the press hit the handle. */
|
|
39
|
+
readonly point: Vec3;
|
|
40
|
+
/** The handle's directions in the world: its axis, or its plane's two and normal. */
|
|
41
|
+
readonly e1: Vec3;
|
|
42
|
+
readonly e2: Vec3;
|
|
43
|
+
readonly normal: Vec3;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type Pivot = {
|
|
47
|
+
/** The handles' size in scene units: an arrow's length (Drei's `scale`, 1). */
|
|
48
|
+
readonly scale: number;
|
|
49
|
+
/** Whether the object hides the handles behind it (Drei's `depthTest`, true). */
|
|
50
|
+
readonly depthTest: boolean;
|
|
51
|
+
/** Leave out kinds of handle, as Drei's `disable*`. */
|
|
52
|
+
readonly disableAxes: boolean;
|
|
53
|
+
readonly disableSliders: boolean;
|
|
54
|
+
readonly disableRotations: boolean;
|
|
55
|
+
readonly disableScaling: boolean;
|
|
56
|
+
/** The handle under the pointer, drawn in Drei's hovered yellow. */
|
|
57
|
+
readonly hovered: PivotHandle | null;
|
|
58
|
+
readonly grab: PivotGrab | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Pivot controls with Drei's defaults: size 1, depth tested, every handle shown. */
|
|
62
|
+
export const pivot = (options: Partial<Omit<Pivot, 'hovered' | 'grab'>> = {}): Pivot => ({
|
|
63
|
+
scale: options.scale ?? 1,
|
|
64
|
+
depthTest: options.depthTest ?? true,
|
|
65
|
+
disableAxes: options.disableAxes ?? false,
|
|
66
|
+
disableSliders: options.disableSliders ?? false,
|
|
67
|
+
disableRotations: options.disableRotations ?? false,
|
|
68
|
+
disableScaling: options.disableScaling ?? false,
|
|
69
|
+
hovered: null,
|
|
70
|
+
grab: null,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const unit: Record<Axis, Vector3> = {
|
|
74
|
+
x: new Vector3(1, 0, 0),
|
|
75
|
+
y: new Vector3(0, 1, 0),
|
|
76
|
+
z: new Vector3(0, 0, 1),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Each plane's two directions, by its normal, in Drei's order. */
|
|
80
|
+
const planeOf: Record<Axis, readonly [Axis, Axis]> = {
|
|
81
|
+
z: ['x', 'y'],
|
|
82
|
+
y: ['z', 'x'],
|
|
83
|
+
x: ['y', 'z'],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const turnOf = (rotation: Vec3): Quaternion =>
|
|
87
|
+
new Quaternion().setFromEuler(new Euler(rotation[0], rotation[1], rotation[2]));
|
|
88
|
+
|
|
89
|
+
const toVec3 = (vector: Vector3): Vec3 => [vector.x, vector.y, vector.z];
|
|
90
|
+
|
|
91
|
+
const kindOf = (handle: PivotHandle) =>
|
|
92
|
+
handle.split('-')[0] as 'arrow' | 'slider' | 'rotator' | 'sphere';
|
|
93
|
+
const axisOf = (handle: PivotHandle): Axis => handle.split('-')[1] as Axis;
|
|
94
|
+
|
|
95
|
+
/** The handle's frame in the world: turned with the object, as Drei's group is. */
|
|
96
|
+
const frameOf = (handle: PivotHandle, placement: Placement) => {
|
|
97
|
+
const turn = turnOf(placement.rotation);
|
|
98
|
+
const axis = axisOf(handle);
|
|
99
|
+
const [first, second] = planeOf[axis];
|
|
100
|
+
const kind = kindOf(handle);
|
|
101
|
+
const flat = kind === 'slider' || kind === 'rotator';
|
|
102
|
+
const e1 = (flat ? unit[first] : unit[axis]).clone().applyQuaternion(turn);
|
|
103
|
+
const e2 = (flat ? unit[second] : unit[axis]).clone().applyQuaternion(turn);
|
|
104
|
+
return { e1, e2, normal: e1.clone().cross(e2).normalize() };
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** A press on `handle` at `point`, with the object at `placement`: takes hold. */
|
|
108
|
+
export const pressPivot = (
|
|
109
|
+
state: Pivot,
|
|
110
|
+
placement: Placement,
|
|
111
|
+
handle: PivotHandle,
|
|
112
|
+
point: Vec3,
|
|
113
|
+
): Pivot => {
|
|
114
|
+
const { e1, e2, normal } = frameOf(handle, placement);
|
|
115
|
+
return {
|
|
116
|
+
...state,
|
|
117
|
+
grab: {
|
|
118
|
+
handle,
|
|
119
|
+
start: placement,
|
|
120
|
+
point,
|
|
121
|
+
e1: toVec3(e1),
|
|
122
|
+
e2: toVec3(e2),
|
|
123
|
+
normal: toVec3(kindOf(handle) === 'arrow' || kindOf(handle) === 'sphere' ? e1 : normal),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const releasePivot = (state: Pivot): Pivot => ({ ...state, grab: null });
|
|
129
|
+
|
|
130
|
+
export const hoverPivot = (state: Pivot, handle: PivotHandle | null): Pivot =>
|
|
131
|
+
state.hovered === handle ? state : { ...state, hovered: handle };
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Drei's `calculateOffset`: how far along `direction` from `point` the line nearest the
|
|
135
|
+
* ray is, solving for the closest points of the two lines.
|
|
136
|
+
*/
|
|
137
|
+
const offsetAlong = (point: Vector3, direction: Vector3, ray: Ray): number => {
|
|
138
|
+
const origin = new Vector3(...ray.origin);
|
|
139
|
+
const toward = new Vector3(...ray.direction);
|
|
140
|
+
const e1 = direction.dot(direction);
|
|
141
|
+
const e2 = direction.dot(point) - direction.dot(origin);
|
|
142
|
+
const e3 = direction.dot(toward);
|
|
143
|
+
if (e3 === 0) return -e2 / e1;
|
|
144
|
+
const v1 = toward
|
|
145
|
+
.clone()
|
|
146
|
+
.multiplyScalar(e1 / e3)
|
|
147
|
+
.sub(direction);
|
|
148
|
+
const v2 = toward
|
|
149
|
+
.clone()
|
|
150
|
+
.multiplyScalar(e2 / e3)
|
|
151
|
+
.add(origin)
|
|
152
|
+
.sub(point);
|
|
153
|
+
return -v1.dot(v2) / v1.dot(v1);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** Where the ray's line meets the plane through `at` with `normal`, if it does. */
|
|
157
|
+
const meetPlane = (ray: Ray, at: Vector3, normal: Vector3): Vector3 | undefined => {
|
|
158
|
+
const origin = new Vector3(...ray.origin);
|
|
159
|
+
const toward = new Vector3(...ray.direction);
|
|
160
|
+
const facing = normal.dot(toward);
|
|
161
|
+
if (facing === 0) return undefined;
|
|
162
|
+
return origin.addScaledVector(toward, normal.dot(at.clone().sub(origin)) / facing);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/** Drei's `calculateAngle`: from the press to `to`, round `at`, in the plane of e1 and e2. */
|
|
166
|
+
const angleBetween = (from: Vector3, to: Vector3, at: Vector3, e1: Vector3, e2: Vector3) => {
|
|
167
|
+
const a = from.clone().sub(at);
|
|
168
|
+
const b = to.clone().sub(at);
|
|
169
|
+
return Math.atan2(b.dot(e2), b.dot(e1)) - Math.atan2(a.dot(e2), a.dot(e1));
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const vectorsOf = (grab: PivotGrab) => ({
|
|
173
|
+
point: new Vector3(...grab.point),
|
|
174
|
+
e1: new Vector3(...grab.e1),
|
|
175
|
+
e2: new Vector3(...grab.e2),
|
|
176
|
+
normal: new Vector3(...grab.normal),
|
|
177
|
+
at: new Vector3(...grab.start.position),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const moved = (start: Placement, by: Vector3): Placement => ({
|
|
181
|
+
...start,
|
|
182
|
+
position: toVec3(new Vector3(...start.position).add(by)),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const arrowDrag = (grab: PivotGrab, ray: Ray): Placement => {
|
|
186
|
+
const { point, e1 } = vectorsOf(grab);
|
|
187
|
+
return moved(grab.start, e1.clone().multiplyScalar(offsetAlong(point, e1, ray)));
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const sliderDrag = (grab: PivotGrab, ray: Ray): Placement => {
|
|
191
|
+
const { point, e1, e2, normal, at } = vectorsOf(grab);
|
|
192
|
+
const meet = meetPlane(ray, at, normal);
|
|
193
|
+
if (meet === undefined) return grab.start;
|
|
194
|
+
const offset = meet.sub(point);
|
|
195
|
+
return moved(
|
|
196
|
+
grab.start,
|
|
197
|
+
e1.multiplyScalar(offset.dot(e1)).add(e2.multiplyScalar(offset.dot(e2))),
|
|
198
|
+
);
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const tenDegrees = Math.PI / 18;
|
|
202
|
+
|
|
203
|
+
const rotatorDrag = (grab: PivotGrab, ray: Ray, snap: boolean): Placement => {
|
|
204
|
+
const { point, e1, e2, normal, at } = vectorsOf(grab);
|
|
205
|
+
const meet = meetPlane(ray, at, normal);
|
|
206
|
+
if (meet === undefined) return grab.start;
|
|
207
|
+
const turned = angleBetween(point, meet, at, e1, e2);
|
|
208
|
+
const angle = snap ? Math.round(turned / tenDegrees) * tenDegrees : turned;
|
|
209
|
+
const turn = new Quaternion()
|
|
210
|
+
.setFromAxisAngle(normal, angle)
|
|
211
|
+
.multiply(turnOf(grab.start.rotation));
|
|
212
|
+
const euler = new Euler().setFromQuaternion(turn.normalize());
|
|
213
|
+
return { ...grab.start, rotation: [euler.x, euler.y, euler.z] };
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const sphereDrag = (state: Pivot, grab: PivotGrab, ray: Ray, snap: boolean): Placement => {
|
|
217
|
+
const { point, e1 } = vectorsOf(grab);
|
|
218
|
+
const grown = 2 ** ((offsetAlong(point, e1, ray) / state.scale) * 0.2);
|
|
219
|
+
// Drei never lets a scale fall below 1e-5, which would break the object's matrix.
|
|
220
|
+
const factor = Math.max(snap ? Math.round(grown * 10) / 10 : grown, 1e-5);
|
|
221
|
+
const index = { x: 0, y: 1, z: 2 }[axisOf(grab.handle)];
|
|
222
|
+
const scale: Vec3 = [
|
|
223
|
+
grab.start.scale[0] * (index === 0 ? factor : 1),
|
|
224
|
+
grab.start.scale[1] * (index === 1 ? factor : 1),
|
|
225
|
+
grab.start.scale[2] * (index === 2 ? factor : 1),
|
|
226
|
+
];
|
|
227
|
+
return { ...grab.start, scale };
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The placement a drag ray gives, or `undefined` while nothing is held. `snap` rounds
|
|
232
|
+
* turns to ten degrees and scales to tenths, as Drei's shift does.
|
|
233
|
+
*/
|
|
234
|
+
export const dragPivot = (
|
|
235
|
+
state: Pivot,
|
|
236
|
+
ray: Ray,
|
|
237
|
+
options: { readonly snap?: boolean } = {},
|
|
238
|
+
): Placement | undefined => {
|
|
239
|
+
const grab = state.grab;
|
|
240
|
+
if (grab === null) return undefined;
|
|
241
|
+
const snap = options.snap ?? false;
|
|
242
|
+
const kind = kindOf(grab.handle);
|
|
243
|
+
if (kind === 'arrow') return arrowDrag(grab, ray);
|
|
244
|
+
if (kind === 'slider') return sliderDrag(grab, ray);
|
|
245
|
+
if (kind === 'rotator') return rotatorDrag(grab, ray, snap);
|
|
246
|
+
return sphereDrag(state, grab, ray, snap);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// DRAWING
|
|
250
|
+
|
|
251
|
+
/** Drei's `axisColors` and `hoveredColor`. */
|
|
252
|
+
const colours: Record<Axis, number> = { x: 0xff2060, y: 0x20df80, z: 0x2080ff };
|
|
253
|
+
const hoveredColour = 0xffff40;
|
|
254
|
+
|
|
255
|
+
const handles = ['arrow', 'slider', 'rotator', 'sphere'] as const;
|
|
256
|
+
|
|
257
|
+
/** The handle a hit names, if it is one of this pivot's. */
|
|
258
|
+
export const pivotHandleOf = (key: string, hit: string): PivotHandle | undefined => {
|
|
259
|
+
const [owner, name] = hit.split(':');
|
|
260
|
+
if (owner !== key) return undefined;
|
|
261
|
+
return handles
|
|
262
|
+
.flatMap((kind) => (['x', 'y', 'z'] as const).map((axis): PivotHandle => `${kind}-${axis}`))
|
|
263
|
+
.find((handle) => handle === name);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const lookOf = (state: Pivot, handle: PivotHandle) =>
|
|
267
|
+
basic({
|
|
268
|
+
color:
|
|
269
|
+
state.grab?.handle === handle || state.hovered === handle
|
|
270
|
+
? hoveredColour
|
|
271
|
+
: colours[axisOf(handle)],
|
|
272
|
+
depthTest: state.depthTest,
|
|
273
|
+
side: 'double',
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* What a hit shape is drawn with: nothing to see, never hiding anything, still picked
|
|
278
|
+
* (Drei's arrows carry an invisible cylinder the same way).
|
|
279
|
+
*/
|
|
280
|
+
const unseen = basic({ opacity: 0, transparent: true, depthTest: false });
|
|
281
|
+
|
|
282
|
+
/** Turns +y onto each axis, for the arrows, stems and spheres, which are drawn along y. */
|
|
283
|
+
const pointAlong: Record<Axis, Vec3> = {
|
|
284
|
+
x: [0, 0, -Math.PI / 2],
|
|
285
|
+
y: [0, 0, 0],
|
|
286
|
+
z: [Math.PI / 2, 0, 0],
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const along = (axis: Axis, distance: number): Vec3 => {
|
|
290
|
+
const { x, y, z } = unit[axis];
|
|
291
|
+
return [x * distance, y * distance, z * distance];
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/** Drei's `AxisArrow`: a stem of 4/5 of the size and a cone of 1/5, 1/20 wide. */
|
|
295
|
+
const arrowNodes = (key: string, state: Pivot, axis: Axis): ReadonlyArray<SceneNode> => {
|
|
296
|
+
const handle: PivotHandle = `arrow-${axis}`;
|
|
297
|
+
const size = state.scale;
|
|
298
|
+
const coneLength = size / 5;
|
|
299
|
+
const stem = size - coneLength;
|
|
300
|
+
const material = lookOf(state, handle);
|
|
301
|
+
return [
|
|
302
|
+
mesh({
|
|
303
|
+
key: `${key}:${handle}:stem`,
|
|
304
|
+
position: along(axis, stem / 2),
|
|
305
|
+
rotation: fixed(pointAlong[axis]),
|
|
306
|
+
geometry: cylinder({ radiusTop: size / 80, radiusBottom: size / 80, height: stem }),
|
|
307
|
+
material,
|
|
308
|
+
}),
|
|
309
|
+
// Drei's hit cylinder: 1.4 cone widths round, the arrow's whole length.
|
|
310
|
+
mesh({
|
|
311
|
+
key: `${key}:${handle}:hit`,
|
|
312
|
+
position: along(axis, size / 2),
|
|
313
|
+
rotation: fixed(pointAlong[axis]),
|
|
314
|
+
geometry: cylinder({ radiusTop: size * 0.07, radiusBottom: size * 0.07, height: size }),
|
|
315
|
+
material: unseen,
|
|
316
|
+
interactive: true,
|
|
317
|
+
}),
|
|
318
|
+
mesh({
|
|
319
|
+
key: `${key}:${handle}:cone`,
|
|
320
|
+
position: along(axis, stem + coneLength / 2),
|
|
321
|
+
rotation: fixed(pointAlong[axis]),
|
|
322
|
+
geometry: cone({ radius: size / 20, height: coneLength, radialSegments: 24 }),
|
|
323
|
+
material,
|
|
324
|
+
interactive: true,
|
|
325
|
+
}),
|
|
326
|
+
];
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
/** A rotation onto the plane of an axis's slider and arc: x onto its first, y its second. */
|
|
330
|
+
const ontoPlane: Record<Axis, Vec3> = {
|
|
331
|
+
z: [0, 0, 0],
|
|
332
|
+
y: [-Math.PI / 2, 0, -Math.PI / 2],
|
|
333
|
+
x: [Math.PI / 2, Math.PI / 2, 0],
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
/** Drei's `PlaneSlider`: a square 0.225 of the size, its middle 1.7/7 out along both. */
|
|
337
|
+
const sliderNode = (key: string, state: Pivot, axis: Axis): SceneNode => {
|
|
338
|
+
const handle: PivotHandle = `slider-${axis}`;
|
|
339
|
+
const out = (state.scale / 7) * 1.7;
|
|
340
|
+
const side = state.scale * 0.225;
|
|
341
|
+
return group({
|
|
342
|
+
key: `${key}:${handle}`,
|
|
343
|
+
rotation: fixed(ontoPlane[axis]),
|
|
344
|
+
children: [
|
|
345
|
+
mesh({
|
|
346
|
+
key: `${key}:${handle}:square`,
|
|
347
|
+
position: [out, out, 0],
|
|
348
|
+
geometry: plane({ width: side, height: side }),
|
|
349
|
+
material: lookOf(state, handle),
|
|
350
|
+
interactive: true,
|
|
351
|
+
}),
|
|
352
|
+
],
|
|
353
|
+
});
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const arcSegments = 8;
|
|
357
|
+
|
|
358
|
+
/** Drei's `AxisRotator`: a quarter circle of radius 0.65 of the size, from its first to its second. */
|
|
359
|
+
const rotatorNode = (key: string, state: Pivot, axis: Axis): SceneNode => {
|
|
360
|
+
const handle: PivotHandle = `rotator-${axis}`;
|
|
361
|
+
const radius = state.scale * 0.65;
|
|
362
|
+
const step = Math.PI / 2 / arcSegments;
|
|
363
|
+
const chord = 2 * radius * Math.sin(step / 2);
|
|
364
|
+
// A cylinder lies along y: each turned to the arc's tangent at its middle. The thin one
|
|
365
|
+
// shows; a thick, unseen one round it is what the pointer finds, as Drei's line is
|
|
366
|
+
// picked a few pixels wide.
|
|
367
|
+
const segment = (index: number, part: 'line' | 'hit'): SceneNode => {
|
|
368
|
+
const middle = step * (index + 0.5);
|
|
369
|
+
const thickness = part === 'line' ? radius / 50 : state.scale * 0.03;
|
|
370
|
+
return mesh({
|
|
371
|
+
key: `${key}:${handle}:${part}:${index}`,
|
|
372
|
+
position: [Math.cos(middle) * radius, Math.sin(middle) * radius, 0],
|
|
373
|
+
rotation: fixed([0, 0, middle]),
|
|
374
|
+
geometry: cylinder({ radiusTop: thickness, radiusBottom: thickness, height: chord }),
|
|
375
|
+
material: part === 'line' ? lookOf(state, handle) : unseen,
|
|
376
|
+
interactive: part === 'hit',
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
const indices = Array.from({ length: arcSegments }, (_, index) => index);
|
|
380
|
+
return group({
|
|
381
|
+
key: `${key}:${handle}`,
|
|
382
|
+
rotation: fixed(ontoPlane[axis]),
|
|
383
|
+
children: [
|
|
384
|
+
...indices.map((index) => segment(index, 'line')),
|
|
385
|
+
...indices.map((index) => segment(index, 'hit')),
|
|
386
|
+
],
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
/** Drei's `ScalingSphere`: a ball 1/22.5 of the size, 1.2 sizes out along its axis. */
|
|
391
|
+
const sphereNode = (key: string, state: Pivot, axis: Axis): SceneNode =>
|
|
392
|
+
mesh({
|
|
393
|
+
key: `${key}:sphere-${axis}`,
|
|
394
|
+
position: along(axis, state.scale * 1.2),
|
|
395
|
+
geometry: sphere({ radius: state.scale / 22.5, widthSegments: 12, heightSegments: 12 }),
|
|
396
|
+
material: lookOf(state, `sphere-${axis}`),
|
|
397
|
+
interactive: true,
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const axesOf: ReadonlyArray<Axis> = ['x', 'y', 'z'];
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The handles for `placement`: a group at the object, turned with it (and not scaled),
|
|
404
|
+
* its handles keyed `${key}:arrow-x`, `${key}:slider-z`, `${key}:rotator-y`,
|
|
405
|
+
* `${key}:sphere-x` and so on, each pickable.
|
|
406
|
+
*/
|
|
407
|
+
export const pivotNodes = (key: string, state: Pivot, placement: Placement): SceneNode =>
|
|
408
|
+
group({
|
|
409
|
+
key,
|
|
410
|
+
position: placement.position,
|
|
411
|
+
rotation: fixed(placement.rotation),
|
|
412
|
+
children: [
|
|
413
|
+
...(state.disableAxes ? [] : axesOf.flatMap((axis) => arrowNodes(key, state, axis))),
|
|
414
|
+
...(state.disableSliders ? [] : axesOf.map((axis) => sliderNode(key, state, axis))),
|
|
415
|
+
...(state.disableRotations ? [] : axesOf.map((axis) => rotatorNode(key, state, axis))),
|
|
416
|
+
...(state.disableScaling ? [] : axesOf.map((axis) => sphereNode(key, state, axis))),
|
|
417
|
+
],
|
|
418
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The buffers a render kit draws points from, made from their look: given points as
|
|
3
|
+
* they are, Drei's sparkles and stars scattered as Drei scatters them (`Sparkles.tsx`,
|
|
4
|
+
* `Stars.tsx` at `bf6f4ad`) but from a seed, so the same look gives the same buffers.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { Color, Spherical, Vector3 } from 'three';
|
|
9
|
+
|
|
10
|
+
import type { PointLook } from './description.ts';
|
|
11
|
+
import { seededRandom } from './seeded.ts';
|
|
12
|
+
|
|
13
|
+
/** Per-point attributes: three numbers a point for positions, colours and noise. */
|
|
14
|
+
export type PointBuffers = {
|
|
15
|
+
readonly count: number;
|
|
16
|
+
readonly positions: Float32Array;
|
|
17
|
+
readonly colors: Float32Array;
|
|
18
|
+
readonly sizes: Float32Array;
|
|
19
|
+
/** Sparkles only: each mote's drift speed, opacity and noise. */
|
|
20
|
+
readonly speeds: Float32Array;
|
|
21
|
+
readonly opacities: Float32Array;
|
|
22
|
+
readonly noise: Float32Array;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const linear = new Color();
|
|
26
|
+
|
|
27
|
+
const dots = (look: Extract<PointLook, { readonly _tag: 'Dots' }>): PointBuffers => {
|
|
28
|
+
const count = look.positions.length;
|
|
29
|
+
const colors = new Float32Array(count * 3);
|
|
30
|
+
for (let point = 0; point < count; point++) {
|
|
31
|
+
linear.setHex(look.colors?.[point] ?? look.color).toArray(colors, point * 3);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
count,
|
|
35
|
+
positions: Float32Array.from(look.positions.flat()),
|
|
36
|
+
colors,
|
|
37
|
+
sizes: Float32Array.from({ length: count }, (_, point) => look.sizes?.[point] ?? 1),
|
|
38
|
+
speeds: new Float32Array(0),
|
|
39
|
+
opacities: new Float32Array(0),
|
|
40
|
+
noise: new Float32Array(0),
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Drei's `Sparkles`: positions spread through the box, random sizes unless given. */
|
|
45
|
+
const sparkles = (look: Extract<PointLook, { readonly _tag: 'Sparkles' }>): PointBuffers => {
|
|
46
|
+
const random = seededRandom(look.seed);
|
|
47
|
+
// three's `MathUtils.randFloatSpread`, as Drei spreads them, from the seed.
|
|
48
|
+
const spread = (range: number) => range * (0.5 - random());
|
|
49
|
+
const { count } = look;
|
|
50
|
+
const positions = new Float32Array(count * 3);
|
|
51
|
+
for (let point = 0; point < count; point++) {
|
|
52
|
+
positions[point * 3] = spread(look.scale[0]);
|
|
53
|
+
positions[point * 3 + 1] = spread(look.scale[1]);
|
|
54
|
+
positions[point * 3 + 2] = spread(look.scale[2]);
|
|
55
|
+
}
|
|
56
|
+
const colors = new Float32Array(count * 3);
|
|
57
|
+
linear.setHex(look.color);
|
|
58
|
+
for (let point = 0; point < count; point++) linear.toArray(colors, point * 3);
|
|
59
|
+
const noise = new Float32Array(count * 3);
|
|
60
|
+
for (let point = 0; point < count; point++) noise.set(look.noise, point * 3);
|
|
61
|
+
return {
|
|
62
|
+
count,
|
|
63
|
+
positions,
|
|
64
|
+
colors,
|
|
65
|
+
sizes: Float32Array.from({ length: count }, () => look.size ?? random()),
|
|
66
|
+
speeds: new Float32Array(count).fill(look.speed),
|
|
67
|
+
opacities: new Float32Array(count).fill(look.opacity),
|
|
68
|
+
noise,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Drei's `Stars`: a shell of stars, nearer ones later, hues round the colour wheel. */
|
|
73
|
+
const stars = (look: Extract<PointLook, { readonly _tag: 'Stars' }>): PointBuffers => {
|
|
74
|
+
const random = seededRandom(look.seed);
|
|
75
|
+
const { count } = look;
|
|
76
|
+
const positions = new Float32Array(count * 3);
|
|
77
|
+
const colors = new Float32Array(count * 3);
|
|
78
|
+
const sizes = Float32Array.from({ length: count }, () => (0.5 + 0.5 * random()) * look.factor);
|
|
79
|
+
const place = new Vector3();
|
|
80
|
+
const spherical = new Spherical();
|
|
81
|
+
const increment = look.depth / count;
|
|
82
|
+
let radius = look.radius + look.depth;
|
|
83
|
+
for (let star = 0; star < count; star++) {
|
|
84
|
+
radius -= increment * random();
|
|
85
|
+
place.setFromSpherical(
|
|
86
|
+
spherical.set(radius, Math.acos(1 - random() * 2), random() * 2 * Math.PI),
|
|
87
|
+
);
|
|
88
|
+
place.toArray(positions, star * 3);
|
|
89
|
+
linear.setHSL(star / count, look.saturation, 0.9).toArray(colors, star * 3);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
count,
|
|
93
|
+
positions,
|
|
94
|
+
colors,
|
|
95
|
+
sizes,
|
|
96
|
+
speeds: new Float32Array(0),
|
|
97
|
+
opacities: new Float32Array(0),
|
|
98
|
+
noise: new Float32Array(0),
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** The buffers for a look. */
|
|
103
|
+
export const pointBuffers = (look: PointLook): PointBuffers => {
|
|
104
|
+
switch (look._tag) {
|
|
105
|
+
case 'Dots':
|
|
106
|
+
return dots(look);
|
|
107
|
+
case 'Sparkles':
|
|
108
|
+
return sparkles(look);
|
|
109
|
+
case 'Stars':
|
|
110
|
+
return stars(look);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Whether a look moves on its own: sparkles drift and stars twinkle in scene time. */
|
|
115
|
+
export const animatedLook = (look: PointLook): boolean => look._tag !== 'Dots';
|
package/src/kernel/pointer.ts
CHANGED
|
@@ -209,6 +209,18 @@ export type SceneEvent =
|
|
|
209
209
|
}
|
|
210
210
|
/** A drag, wheel or pinch on the canvas, when the scene claims gestures. */
|
|
211
211
|
| { readonly _tag: 'Gesture'; readonly gesture: Gesture }
|
|
212
|
+
/**
|
|
213
|
+
* A gesture that moved has ended: released, or the pointer left. Only after a `Drag`
|
|
214
|
+
* or `Pinch`, with the press's button, modifiers, mesh and view: the moment to finish
|
|
215
|
+
* what the gesture drew, such as a selection box.
|
|
216
|
+
*/
|
|
217
|
+
| {
|
|
218
|
+
readonly _tag: 'GestureEnd';
|
|
219
|
+
readonly button: number;
|
|
220
|
+
readonly modifiers: Modifiers;
|
|
221
|
+
readonly key?: string;
|
|
222
|
+
readonly view?: string;
|
|
223
|
+
}
|
|
212
224
|
/**
|
|
213
225
|
* A gesture is about to begin on a camera that has turned by itself (`autoRotate`):
|
|
214
226
|
* where it got to, so the Model can keep it (`autoRotated(camera, detail)`) and the
|
|
@@ -405,14 +417,17 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
405
417
|
const rayOrigin = new Vector3();
|
|
406
418
|
const rayDirection = new Vector3();
|
|
407
419
|
|
|
408
|
-
/**
|
|
409
|
-
|
|
420
|
+
/**
|
|
421
|
+
* The nearest keyed hit in `on` (the scene's graph or its HUD's) along the raycaster's
|
|
422
|
+
* ray, reporting `view` when there is one.
|
|
423
|
+
*/
|
|
424
|
+
const nearestHit = (on: SceneGraph, view: string | undefined): Hit | undefined => {
|
|
410
425
|
// Recursive, so an interactive group makes every mesh inside it pickable.
|
|
411
|
-
for (const intersection of raycaster.intersectObjects(
|
|
426
|
+
for (const intersection of raycaster.intersectObjects(on.interactiveObjects(), true)) {
|
|
412
427
|
// Hidden things are not hit: a level of detail not showing, as three's `LOD.raycast`.
|
|
413
428
|
if (!shown(intersection.object)) continue;
|
|
414
|
-
const owner = keyedOwner(
|
|
415
|
-
const key = owner === undefined ? undefined :
|
|
429
|
+
const owner = keyedOwner(on, intersection.object);
|
|
430
|
+
const key = owner === undefined ? undefined : on.keyOf(owner);
|
|
416
431
|
if (owner !== undefined && key !== undefined) {
|
|
417
432
|
normal.set(0, 0, 0);
|
|
418
433
|
if (intersection.face) {
|
|
@@ -421,13 +436,13 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
421
436
|
const instance =
|
|
422
437
|
intersection.instanceId === undefined
|
|
423
438
|
? undefined
|
|
424
|
-
:
|
|
439
|
+
: on.instanceKeyOf(intersection.object, intersection.instanceId);
|
|
425
440
|
return {
|
|
426
441
|
key,
|
|
427
442
|
...(instance === undefined ? {} : { instance }),
|
|
428
443
|
...partOf(owner, intersection.object),
|
|
429
444
|
...(view === undefined ? {} : { view }),
|
|
430
|
-
path: pathOf(
|
|
445
|
+
path: pathOf(on, owner),
|
|
431
446
|
point: toVec3(intersection.point),
|
|
432
447
|
distance: intersection.distance,
|
|
433
448
|
normal: toVec3(normal),
|
|
@@ -439,6 +454,17 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
439
454
|
return undefined;
|
|
440
455
|
};
|
|
441
456
|
|
|
457
|
+
/** A hit on the HUD's interactive meshes, which are over everything else. */
|
|
458
|
+
const hudHit = (x: number, y: number): Hit | undefined => {
|
|
459
|
+
const hud = graph.hud();
|
|
460
|
+
if (hud === undefined || hud.graph.interactiveObjects().length === 0) return undefined;
|
|
461
|
+
hud.graph.scene.updateMatrixWorld();
|
|
462
|
+
hud.graph.camera.updateMatrixWorld();
|
|
463
|
+
ndc.set(x, y);
|
|
464
|
+
raycaster.setFromCamera(ndc, hud.graph.camera);
|
|
465
|
+
return nearestHit(hud.graph, undefined);
|
|
466
|
+
};
|
|
467
|
+
|
|
442
468
|
const currentRay = (): Ray => ({
|
|
443
469
|
origin: toVec3(raycaster.ray.origin),
|
|
444
470
|
direction: toVec3(raycaster.ray.direction),
|
|
@@ -472,10 +498,12 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
472
498
|
holdCameras: graph.holdCameras,
|
|
473
499
|
turned: graph.turned,
|
|
474
500
|
pick: (x, y) => {
|
|
501
|
+
const hud = hudHit(x, y);
|
|
502
|
+
if (hud !== undefined) return hud;
|
|
475
503
|
if (graph.interactiveObjects().length === 0) return undefined;
|
|
476
504
|
const view = aim(x, y);
|
|
477
505
|
if (view === false) return undefined;
|
|
478
|
-
return nearestHit(view);
|
|
506
|
+
return nearestHit(graph, view);
|
|
479
507
|
},
|
|
480
508
|
pickRay: (ray) => {
|
|
481
509
|
if (graph.interactiveObjects().length === 0) return undefined;
|
|
@@ -484,7 +512,7 @@ export const createPicker = (graph: SceneGraph): Picker => {
|
|
|
484
512
|
rayOrigin.fromArray(ray.origin),
|
|
485
513
|
rayDirection.fromArray(ray.direction).normalize(),
|
|
486
514
|
);
|
|
487
|
-
return nearestHit(undefined);
|
|
515
|
+
return nearestHit(graph, undefined);
|
|
488
516
|
},
|
|
489
517
|
viewport: graph.viewport,
|
|
490
518
|
};
|
|
@@ -532,6 +560,8 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
532
560
|
const pressedPointers = new Map<number, Point>();
|
|
533
561
|
let primary: number | undefined;
|
|
534
562
|
let gesturePress: GesturePress | undefined;
|
|
563
|
+
// Whether the gesture press has moved: only then does its release end a gesture.
|
|
564
|
+
let gestureMoved = false;
|
|
535
565
|
let hovered: string | undefined;
|
|
536
566
|
// The latest press, kept after release: double-click and context-menu events follow it.
|
|
537
567
|
let press: Press | undefined;
|
|
@@ -624,6 +654,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
624
654
|
pressedPointers.clear();
|
|
625
655
|
primary = id;
|
|
626
656
|
pressedPointers.set(id, { x: event.x, y: event.y });
|
|
657
|
+
gestureMoved = false;
|
|
627
658
|
const region = picker.region(event.x, event.y);
|
|
628
659
|
reportTurned(region, events);
|
|
629
660
|
// Cameras turning by themselves hold still while it is pressed, as `OrbitControls`.
|
|
@@ -641,6 +672,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
641
672
|
const from = pressedPointers.get(id);
|
|
642
673
|
if (from === undefined || gesturePress === undefined) return;
|
|
643
674
|
pressedPointers.set(id, to);
|
|
675
|
+
gestureMoved = true;
|
|
644
676
|
const { view, width, height, x, y } = gesturePress;
|
|
645
677
|
const region = { ...(view === undefined ? {} : { view }), width, height, x, y };
|
|
646
678
|
const moved = pixels(from, to);
|
|
@@ -680,11 +712,22 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
680
712
|
});
|
|
681
713
|
};
|
|
682
714
|
|
|
683
|
-
const endGestures = (): void => {
|
|
715
|
+
const endGestures = (events: Array<SceneEvent>): void => {
|
|
684
716
|
if (gesturePress !== undefined) picker.holdCameras(false);
|
|
717
|
+
if (gesturePress !== undefined && gestureMoved) {
|
|
718
|
+
const { button, modifiers, key, view } = gesturePress;
|
|
719
|
+
events.push({
|
|
720
|
+
_tag: 'GestureEnd',
|
|
721
|
+
button,
|
|
722
|
+
modifiers,
|
|
723
|
+
...(key === undefined ? {} : { key }),
|
|
724
|
+
...(view === undefined ? {} : { view }),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
685
727
|
pressedPointers.clear();
|
|
686
728
|
primary = undefined;
|
|
687
729
|
gesturePress = undefined;
|
|
730
|
+
gestureMoved = false;
|
|
688
731
|
};
|
|
689
732
|
|
|
690
733
|
/**
|
|
@@ -714,7 +757,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
714
757
|
if (isSecondary(event, id)) return secondary(event, id);
|
|
715
758
|
const events: Array<SceneEvent> = [];
|
|
716
759
|
if (event._tag === 'PointerLeave') {
|
|
717
|
-
endGestures();
|
|
760
|
+
endGestures(events);
|
|
718
761
|
// A cancelled or abandoned press still ends its drag, at the last known position.
|
|
719
762
|
if (position !== undefined) endDrag(captured(), position.x, position.y, events);
|
|
720
763
|
dragged = false;
|
|
@@ -748,7 +791,7 @@ export const createPointerTracker = (picker: Picker): PointerTracker => {
|
|
|
748
791
|
release(hit, event, events);
|
|
749
792
|
// Any release ends capture: a mouse's buttons share one pointer.
|
|
750
793
|
endDrag(drag, event.x, event.y, events);
|
|
751
|
-
endGestures();
|
|
794
|
+
endGestures(events);
|
|
752
795
|
break;
|
|
753
796
|
case 'DoubleClick':
|
|
754
797
|
if (hit !== undefined && pressed(hit)) {
|