@opsydyn/effect-three-foldkit 0.2.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 +18 -1
- package/README.md +2 -1
- package/dist/boxSelect.d.ts +150 -0
- package/dist/boxSelect.d.ts.map +1 -0
- package/dist/dom/webgl.d.ts.map +1 -1
- package/dist/dom/webgpu.d.ts.map +1 -1
- package/dist/element.d.ts +104 -2
- package/dist/element.d.ts.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +535 -32
- package/dist/index.js.map +13 -7
- package/dist/inspector.d.ts +24 -0
- package/dist/inspector.d.ts.map +1 -0
- package/dist/measuringTape.d.ts +76 -0
- package/dist/measuringTape.d.ts.map +1 -0
- package/dist/pivotControls.d.ts +106 -0
- package/dist/pivotControls.d.ts.map +1 -0
- package/dist/transformGizmo.d.ts +158 -0
- package/dist/transformGizmo.d.ts.map +1 -0
- package/dist/view.d.ts +7 -0
- package/dist/view.d.ts.map +1 -1
- package/dist/viewportGizmo.d.ts +146 -0
- package/dist/viewportGizmo.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/boxSelect.ts +158 -0
- package/src/dom/webgl.ts +15 -0
- package/src/dom/webgpu.ts +15 -0
- package/src/element.ts +2 -0
- package/src/index.ts +7 -0
- package/src/inspector.ts +112 -0
- package/src/measuringTape.ts +124 -0
- package/src/pivotControls.ts +118 -0
- package/src/transformGizmo.ts +131 -0
- package/src/view.ts +9 -0
- package/src/viewportGizmo.ts +119 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transform gizmo as a Foldkit submodel (roadmap, "Beyond parity" 5): the core's pure
|
|
3
|
+
* gizmo (`gizmoNodes`, `pressGizmo`, `dragGizmo`, after three's `TransformControls`)
|
|
4
|
+
* with Messages for the scene's pointer events and OutMessages for what the parent does
|
|
5
|
+
* with them. The parent owns where its objects are; the submodel owns only the gizmo:
|
|
6
|
+
*
|
|
7
|
+
* - `Pressed` (a press on one of its handles, with the object's placement and the
|
|
8
|
+
* camera) takes hold;
|
|
9
|
+
* - `Dragged` reports `Moved { placement }` for the parent to apply;
|
|
10
|
+
* - `Released` lets go and reports `Finished { placement }`, the moment to record a step
|
|
11
|
+
* of history;
|
|
12
|
+
* - `Hovered` highlights a handle; `ChoseMode` and `ChoseSpace` switch them.
|
|
13
|
+
*
|
|
14
|
+
* `handleOf(model, key)` says whether a hit is on a handle, and `nodes(model, placement,
|
|
15
|
+
* camera)` draws them, for the parent's scene and view.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
type Camera,
|
|
21
|
+
dragGizmo,
|
|
22
|
+
type Gizmo,
|
|
23
|
+
gizmo,
|
|
24
|
+
type GizmoHandle,
|
|
25
|
+
gizmoHandleOf,
|
|
26
|
+
gizmoNodes,
|
|
27
|
+
hoverGizmo,
|
|
28
|
+
type Placement,
|
|
29
|
+
pressGizmo,
|
|
30
|
+
releaseGizmo,
|
|
31
|
+
SceneSchema,
|
|
32
|
+
type SceneNode,
|
|
33
|
+
type Vec3,
|
|
34
|
+
} from '@opsydyn/effect-three';
|
|
35
|
+
import { Option, Schema } from 'effect';
|
|
36
|
+
import { defineMessageUnion } from 'foldkit/message';
|
|
37
|
+
import type { ReturnWithOutMessage } from 'foldkit/update';
|
|
38
|
+
|
|
39
|
+
export const Model = Schema.Struct({
|
|
40
|
+
/** The key its handles are drawn under: `${key}:x`, `${key}:xy` and so on. */
|
|
41
|
+
key: Schema.String,
|
|
42
|
+
gizmo: SceneSchema.Gizmo,
|
|
43
|
+
/** The placement the drag last reported, so `Released` can report where it ended. */
|
|
44
|
+
last: Schema.Option(SceneSchema.Placement),
|
|
45
|
+
});
|
|
46
|
+
export type Model = typeof Model.Type;
|
|
47
|
+
|
|
48
|
+
const Handle = Schema.Literals(['x', 'y', 'z', 'xy', 'yz', 'xz', 'xyz']);
|
|
49
|
+
|
|
50
|
+
export const Message = defineMessageUnion({
|
|
51
|
+
Pressed: {
|
|
52
|
+
handle: Handle,
|
|
53
|
+
origin: SceneSchema.Vec3,
|
|
54
|
+
direction: SceneSchema.Vec3,
|
|
55
|
+
placement: SceneSchema.Placement,
|
|
56
|
+
camera: SceneSchema.Camera,
|
|
57
|
+
},
|
|
58
|
+
Dragged: { origin: SceneSchema.Vec3, direction: SceneSchema.Vec3 },
|
|
59
|
+
Released: {},
|
|
60
|
+
Hovered: { handle: Schema.NullOr(Handle) },
|
|
61
|
+
ChoseMode: { mode: Schema.Literals(['translate', 'rotate', 'scale']) },
|
|
62
|
+
ChoseSpace: { space: Schema.Literals(['world', 'local']) },
|
|
63
|
+
});
|
|
64
|
+
export type Message = typeof Message.Type;
|
|
65
|
+
|
|
66
|
+
export const OutMessage = defineMessageUnion({
|
|
67
|
+
/** The object's placement now, during a drag. */
|
|
68
|
+
Moved: { placement: SceneSchema.Placement },
|
|
69
|
+
/** The drag is over, the object here: a step for history. */
|
|
70
|
+
Finished: { placement: SceneSchema.Placement },
|
|
71
|
+
});
|
|
72
|
+
export type OutMessage = typeof OutMessage.Type;
|
|
73
|
+
|
|
74
|
+
/** A gizmo drawn under `key`, with the core's defaults or the options given. */
|
|
75
|
+
export const init = (key: string, options: Parameters<typeof gizmo>[0] = {}): Model => ({
|
|
76
|
+
key,
|
|
77
|
+
gizmo: gizmo(options),
|
|
78
|
+
last: Option.none(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
type Return = ReturnWithOutMessage<Model, Message, OutMessage>;
|
|
82
|
+
|
|
83
|
+
const withGizmo = (model: Model, next: Gizmo): Model => ({ ...model, gizmo: next });
|
|
84
|
+
|
|
85
|
+
const finished = (model: Model): Return => ({
|
|
86
|
+
model: { ...withGizmo(model, releaseGizmo(model.gizmo)), last: Option.none() },
|
|
87
|
+
...Option.match(model.last, {
|
|
88
|
+
onNone: () => ({}),
|
|
89
|
+
onSome: (placement) => ({ outMessage: OutMessage.Finished({ placement }) }),
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const dragged = (model: Model, origin: Vec3, direction: Vec3): Return => {
|
|
94
|
+
const placement = Option.fromNullishOr(dragGizmo(model.gizmo, { origin, direction }));
|
|
95
|
+
return Option.match(placement, {
|
|
96
|
+
onNone: () => ({ model }),
|
|
97
|
+
onSome: (placement) => ({
|
|
98
|
+
model: { ...model, last: Option.some(placement) },
|
|
99
|
+
outMessage: OutMessage.Moved({ placement }),
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const update = (model: Model, message: Message): Return =>
|
|
105
|
+
Message.match<Return>(message, {
|
|
106
|
+
Pressed: ({ handle, origin, direction, placement, camera }) => ({
|
|
107
|
+
model: {
|
|
108
|
+
...withGizmo(
|
|
109
|
+
model,
|
|
110
|
+
pressGizmo(model.gizmo, placement, handle, { origin, direction }, camera),
|
|
111
|
+
),
|
|
112
|
+
last: Option.none(),
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
Dragged: ({ origin, direction }) => dragged(model, origin, direction),
|
|
116
|
+
Released: () => finished(model),
|
|
117
|
+
Hovered: ({ handle }) => ({ model: withGizmo(model, hoverGizmo(model.gizmo, handle)) }),
|
|
118
|
+
ChoseMode: ({ mode }) => ({ model: withGizmo(model, { ...model.gizmo, mode }) }),
|
|
119
|
+
ChoseSpace: ({ space }) => ({ model: withGizmo(model, { ...model.gizmo, space }) }),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/** Whether a hit `key` is one of this gizmo's handles, and which. */
|
|
123
|
+
export const handleOf = (model: Model, key: string): Option.Option<GizmoHandle> =>
|
|
124
|
+
Option.fromNullishOr(gizmoHandleOf(model.key, key));
|
|
125
|
+
|
|
126
|
+
/** Whether the gizmo is held: the parent leaves the camera alone while it is. */
|
|
127
|
+
export const held = (model: Model): boolean => model.gizmo.grab !== null;
|
|
128
|
+
|
|
129
|
+
/** The handles for `placement`, seen from `camera`, for the parent's scene. */
|
|
130
|
+
export const nodes = (model: Model, placement: Placement, camera: Camera): SceneNode =>
|
|
131
|
+
gizmoNodes(model.key, model.gizmo, placement, camera);
|
package/src/view.ts
CHANGED
|
@@ -44,6 +44,9 @@ export type DragDetail = typeof SceneSchema.DragDetail.Type;
|
|
|
44
44
|
*/
|
|
45
45
|
export type Gesture = typeof SceneSchema.Gesture.Type;
|
|
46
46
|
|
|
47
|
+
/** Detail of the end of a gesture that moved: the press's button, modifiers, mesh and view. */
|
|
48
|
+
export type GestureEndDetail = typeof SceneSchema.GestureEndDetail.Type;
|
|
49
|
+
|
|
47
50
|
/** Detail of collision events: the two bodies' keys, and whether they began touching. */
|
|
48
51
|
export type CollisionDetail = typeof SceneSchema.CollisionDetail.Type;
|
|
49
52
|
|
|
@@ -119,6 +122,11 @@ export type ViewConfig<Message> = {
|
|
|
119
122
|
* the camera stays in the Model.
|
|
120
123
|
*/
|
|
121
124
|
readonly onGesture?: (gesture: Gesture) => Message;
|
|
125
|
+
/**
|
|
126
|
+
* A gesture that moved has ended (released, or the pointer left): finish what it drew,
|
|
127
|
+
* such as a `BoxSelect` box. Not after a press that never moved.
|
|
128
|
+
*/
|
|
129
|
+
readonly onGestureEnd?: (detail: GestureEndDetail) => Message;
|
|
122
130
|
/**
|
|
123
131
|
* A gesture is about to begin on a camera turning by itself (`autoRotate`): keep where
|
|
124
132
|
* it got to with `autoRotated(model.camera, detail)` (a view's camera when `view` names
|
|
@@ -239,6 +247,7 @@ export const view = <Message>(
|
|
|
239
247
|
Option.map(given(config.onGesture), (toMessage) =>
|
|
240
248
|
element.OnSceneGesture((detail) => toMessage(detail.gesture)),
|
|
241
249
|
),
|
|
250
|
+
Option.map(given(config.onGestureEnd), element.OnSceneGestureEnd),
|
|
242
251
|
Option.map(given(config.onPerformanceChange), element.OnScenePerformance),
|
|
243
252
|
Option.map(given(config.onError), element.OnSceneError),
|
|
244
253
|
Option.map(given(config.onReconciled), element.OnSceneReconciled),
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A viewport gizmo as a Foldkit submodel (roadmap, "Beyond parity" 5), after Drei's
|
|
3
|
+
* `GizmoHelper` with `GizmoViewport`: the core's `viewportGizmoNodes` in a scene's
|
|
4
|
+
* `hud`, its heads highlighted under the pointer, and a press on one turning the camera
|
|
5
|
+
* to look from that axis. The parent owns the camera; the submodel owns the highlight:
|
|
6
|
+
*
|
|
7
|
+
* - `Entered` and `Left` (the scene's pointer enter and leave, with the hit key)
|
|
8
|
+
* highlight a head, and clear it;
|
|
9
|
+
* - `Pressed` (a pointer down, with the hit key and the camera) reports
|
|
10
|
+
* `Turned { camera }`, the camera looking from that head's axis, for the parent to
|
|
11
|
+
* keep. A camera with `damping` eases round to it, as Drei's tween does.
|
|
12
|
+
*
|
|
13
|
+
* `hud(model, camera)` is the scene's HUD with the gizmo in it; `nodes` the gizmo alone,
|
|
14
|
+
* for a HUD with more in it.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import {
|
|
19
|
+
type Camera,
|
|
20
|
+
cameraAlong,
|
|
21
|
+
hud as describeHud,
|
|
22
|
+
type Hud,
|
|
23
|
+
type HudAlignment,
|
|
24
|
+
SceneSchema,
|
|
25
|
+
type SceneNode,
|
|
26
|
+
type ViewAxis,
|
|
27
|
+
viewportAxisOf,
|
|
28
|
+
viewportGizmoNodes,
|
|
29
|
+
} from '@opsydyn/effect-three';
|
|
30
|
+
import { Option, Schema } from 'effect';
|
|
31
|
+
import { defineMessageUnion } from 'foldkit/message';
|
|
32
|
+
import type { ReturnWithOutMessage } from 'foldkit/update';
|
|
33
|
+
|
|
34
|
+
const Axis = Schema.Literals(['x', 'y', 'z', '-x', '-y', '-z']);
|
|
35
|
+
|
|
36
|
+
export const Model = Schema.Struct({
|
|
37
|
+
/** The key its nodes are drawn under: `${key}:x`, `${key}:-y` and so on. */
|
|
38
|
+
key: Schema.String,
|
|
39
|
+
/** The head under the pointer. */
|
|
40
|
+
hovered: Schema.NullOr(Axis),
|
|
41
|
+
/** Leaves out the negative heads. */
|
|
42
|
+
hideNegativeAxes: Schema.Boolean,
|
|
43
|
+
});
|
|
44
|
+
export type Model = typeof Model.Type;
|
|
45
|
+
|
|
46
|
+
export const Message = defineMessageUnion({
|
|
47
|
+
Entered: { key: Schema.String },
|
|
48
|
+
Left: { key: Schema.String },
|
|
49
|
+
Pressed: { key: Schema.String, camera: SceneSchema.Camera },
|
|
50
|
+
});
|
|
51
|
+
export type Message = typeof Message.Type;
|
|
52
|
+
|
|
53
|
+
export const OutMessage = defineMessageUnion({
|
|
54
|
+
/** The camera, looking from the axis of the head pressed. */
|
|
55
|
+
Turned: { camera: SceneSchema.Camera },
|
|
56
|
+
});
|
|
57
|
+
export type OutMessage = typeof OutMessage.Type;
|
|
58
|
+
|
|
59
|
+
/** A gizmo drawn under `key`, every head shown unless `hideNegativeAxes`. */
|
|
60
|
+
export const init = (
|
|
61
|
+
key: string,
|
|
62
|
+
options: { readonly hideNegativeAxes?: boolean } = {},
|
|
63
|
+
): Model => ({
|
|
64
|
+
key,
|
|
65
|
+
hovered: null,
|
|
66
|
+
hideNegativeAxes: options.hideNegativeAxes ?? false,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
type Return = ReturnWithOutMessage<Model, Message, OutMessage>;
|
|
70
|
+
|
|
71
|
+
/** Which head a hit `key` is on, if it is one of this gizmo's. */
|
|
72
|
+
export const axisOf = (model: Model, key: string): Option.Option<ViewAxis> =>
|
|
73
|
+
Option.fromNullishOr(viewportAxisOf(model.key, key));
|
|
74
|
+
|
|
75
|
+
const entered = (model: Model, key: string): Model =>
|
|
76
|
+
Option.match(axisOf(model, key), {
|
|
77
|
+
onNone: () => model,
|
|
78
|
+
onSome: (axis) => ({ ...model, hovered: axis }),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/** Leaving a head clears the highlight; leaving anything else changes nothing. */
|
|
82
|
+
const left = (model: Model, key: string): Model =>
|
|
83
|
+
Option.match(axisOf(model, key), {
|
|
84
|
+
onNone: () => model,
|
|
85
|
+
onSome: () => ({ ...model, hovered: null }),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const pressed = (model: Model, key: string, camera: Camera): Return =>
|
|
89
|
+
Option.match(axisOf(model, key), {
|
|
90
|
+
onNone: () => ({ model }),
|
|
91
|
+
onSome: (axis) => ({
|
|
92
|
+
model,
|
|
93
|
+
outMessage: OutMessage.Turned({ camera: cameraAlong(camera, axis) }),
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export const update = (model: Model, message: Message): Return =>
|
|
98
|
+
Message.match<Return>(message, {
|
|
99
|
+
Entered: ({ key }) => ({ model: entered(model, key) }),
|
|
100
|
+
Left: ({ key }) => ({ model: left(model, key) }),
|
|
101
|
+
Pressed: ({ key, camera }) => pressed(model, key, camera),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
/** The gizmo for `camera`, for a HUD. */
|
|
105
|
+
export const nodes = (model: Model, camera: Camera): SceneNode =>
|
|
106
|
+
viewportGizmoNodes(model.key, camera, {
|
|
107
|
+
hovered: model.hovered,
|
|
108
|
+
hideNegativeAxes: model.hideNegativeAxes,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
/** A HUD holding only the gizmo, 80 pixels in from the bottom right unless placed. */
|
|
112
|
+
export const hud = (
|
|
113
|
+
model: Model,
|
|
114
|
+
camera: Camera,
|
|
115
|
+
placement: {
|
|
116
|
+
readonly alignment?: HudAlignment;
|
|
117
|
+
readonly margin?: readonly [number, number];
|
|
118
|
+
} = {},
|
|
119
|
+
): Hud => describeHud({ ...placement, children: [nodes(model, camera)] });
|