@opsydyn/effect-three-foldkit 0.3.0 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +4 -2
  3. package/dist/assetRegistry.d.ts +49 -0
  4. package/dist/assetRegistry.d.ts.map +1 -0
  5. package/dist/assets.d.ts +18 -5
  6. package/dist/assets.d.ts.map +1 -1
  7. package/dist/boxSelect.d.ts +150 -0
  8. package/dist/boxSelect.d.ts.map +1 -0
  9. package/dist/dom/canvas.d.ts +7 -0
  10. package/dist/dom/canvas.d.ts.map +1 -1
  11. package/dist/dom/pointer.d.ts.map +1 -1
  12. package/dist/dom/webgl.d.ts.map +1 -1
  13. package/dist/dom/webgpu.d.ts.map +1 -1
  14. package/dist/element.d.ts +60 -0
  15. package/dist/element.d.ts.map +1 -1
  16. package/dist/environment.d.ts +6 -0
  17. package/dist/environment.d.ts.map +1 -1
  18. package/dist/index.d.ts +7 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +745 -60
  21. package/dist/index.js.map +19 -12
  22. package/dist/inspector.d.ts +24 -0
  23. package/dist/inspector.d.ts.map +1 -0
  24. package/dist/measuringTape.d.ts +76 -0
  25. package/dist/measuringTape.d.ts.map +1 -0
  26. package/dist/mount.d.ts.map +1 -1
  27. package/dist/pivotControls.d.ts +106 -0
  28. package/dist/pivotControls.d.ts.map +1 -0
  29. package/dist/transformGizmo.d.ts +158 -0
  30. package/dist/transformGizmo.d.ts.map +1 -0
  31. package/dist/view.d.ts +23 -0
  32. package/dist/view.d.ts.map +1 -1
  33. package/dist/viewportGizmo.d.ts +146 -0
  34. package/dist/viewportGizmo.d.ts.map +1 -0
  35. package/package.json +8 -8
  36. package/src/assetRegistry.ts +218 -0
  37. package/src/assets.ts +96 -39
  38. package/src/boxSelect.ts +158 -0
  39. package/src/dom/canvas.ts +12 -0
  40. package/src/dom/pointer.ts +14 -1
  41. package/src/dom/webgl.ts +15 -0
  42. package/src/dom/webgpu.ts +15 -0
  43. package/src/element.ts +6 -0
  44. package/src/environment.ts +6 -1
  45. package/src/index.ts +10 -0
  46. package/src/inspector.ts +112 -0
  47. package/src/measuringTape.ts +124 -0
  48. package/src/mount.ts +24 -1
  49. package/src/pivotControls.ts +118 -0
  50. package/src/transformGizmo.ts +131 -0
  51. package/src/view.ts +28 -0
  52. package/src/viewportGizmo.ts +119 -0
package/src/view.ts CHANGED
@@ -44,11 +44,17 @@ 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
 
50
53
  /** Detail of contact force events: the two bodies, and how hard they pushed. */
51
54
  export type ContactForceDetail = typeof SceneSchema.ContactForceDetail.Type;
55
+ export type PhysicsReachedDetail = typeof SceneSchema.PhysicsReachedDetail.Type;
56
+ export type AssetLoadedDetail = typeof SceneSchema.AssetLoadedDetail.Type;
57
+ export type AssetFailedDetail = typeof SceneSchema.AssetFailedDetail.Type;
52
58
 
53
59
  /** Detail of measured events: a node's world bounding box. */
54
60
  export type MeasuredDetail = typeof SceneSchema.MeasuredDetail.Type;
@@ -119,6 +125,11 @@ export type ViewConfig<Message> = {
119
125
  * the camera stays in the Model.
120
126
  */
121
127
  readonly onGesture?: (gesture: Gesture) => Message;
128
+ /**
129
+ * A gesture that moved has ended (released, or the pointer left): finish what it drew,
130
+ * such as a `BoxSelect` box. Not after a press that never moved.
131
+ */
132
+ readonly onGestureEnd?: (detail: GestureEndDetail) => Message;
122
133
  /**
123
134
  * A gesture is about to begin on a camera turning by itself (`autoRotate`): keep where
124
135
  * it got to with `autoRotated(model.camera, detail)` (a view's camera when `view` names
@@ -136,6 +147,12 @@ export type ViewConfig<Message> = {
136
147
  * set the threshold high.
137
148
  */
138
149
  readonly onContactForce?: (detail: ContactForceDetail) => Message;
150
+ /**
151
+ * A world on `physics({ until })` reached that step and waits there, with its run and a
152
+ * checksum of its bodies' poses (ADR-014): the moment to apply the inputs due at that
153
+ * step and move `until` on. It follows the collisions of the steps that led there.
154
+ */
155
+ readonly onPhysicsReached?: (detail: PhysicsReachedDetail) => Message;
139
156
  /**
140
157
  * A node listed in the scene's `measure` has a new world bounding box (Drei's `Bounds`
141
158
  * measuring, as a Message): fit the camera to it with `fitCamera`.
@@ -189,6 +206,13 @@ export type ViewConfig<Message> = {
189
206
  readonly onPerformanceChange?: (detail: PerformanceDetail) => Message;
190
207
  /** A scene update was rejected; the scene keeps its last good state. */
191
208
  readonly onError?: (detail: { readonly error: SceneError }) => Message;
209
+ /**
210
+ * An asset the scene asks for (`scene({ assets })`) has loaded and is lent under its id:
211
+ * at once, for one another scene loaded already. A model's parts and clips come with it.
212
+ */
213
+ readonly onAssetLoaded?: (detail: AssetLoadedDetail) => Message;
214
+ /** An asset the scene asks for could not be loaded, and why. */
215
+ readonly onAssetFailed?: (detail: AssetFailedDetail) => Message;
192
216
  /**
193
217
  * What each scene update did (operations by kind) and the GPU resources held afterwards.
194
218
  * Fires at startup and after every update that changed something, never for no-ops.
@@ -226,6 +250,7 @@ export const view = <Message>(
226
250
  Option.map(given(config.onDragEnd), element.OnSceneDragEnd),
227
251
  Option.map(given(config.onCollision), element.OnSceneCollision),
228
252
  Option.map(given(config.onContactForce), element.OnSceneContactForce),
253
+ Option.map(given(config.onPhysicsReached), element.OnScenePhysicsReached),
229
254
  Option.map(given(config.onMeasured), element.OnSceneMeasured),
230
255
  Option.map(given(config.onLabelMeasured), element.OnSceneLabelMeasured),
231
256
  Option.map(given(config.onXRSession), element.OnSceneXr),
@@ -239,8 +264,11 @@ export const view = <Message>(
239
264
  Option.map(given(config.onGesture), (toMessage) =>
240
265
  element.OnSceneGesture((detail) => toMessage(detail.gesture)),
241
266
  ),
267
+ Option.map(given(config.onGestureEnd), element.OnSceneGestureEnd),
242
268
  Option.map(given(config.onPerformanceChange), element.OnScenePerformance),
243
269
  Option.map(given(config.onError), element.OnSceneError),
270
+ Option.map(given(config.onAssetLoaded), element.OnSceneAssetLoaded),
271
+ Option.map(given(config.onAssetFailed), element.OnSceneAssetFailed),
244
272
  Option.map(given(config.onReconciled), element.OnSceneReconciled),
245
273
  Option.map(given(config.className), h.Class),
246
274
  ]);
@@ -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)] });