@opsydyn/effect-three 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 (143) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/README.md +15 -1
  3. package/dist/{index-8cf8t6by.js → index-re3rrfwd.js} +2935 -104
  4. package/dist/index-re3rrfwd.js.map +56 -0
  5. package/dist/index-ttwy0znv.js +145 -0
  6. package/dist/index-ttwy0znv.js.map +12 -0
  7. package/dist/index.js +557 -147
  8. package/dist/index.js.map +8 -6
  9. package/dist/kernel/bodies.d.ts +20 -1
  10. package/dist/kernel/bodies.d.ts.map +1 -1
  11. package/dist/kernel/boxSelect.d.ts +22 -0
  12. package/dist/kernel/boxSelect.d.ts.map +1 -0
  13. package/dist/kernel/build.d.ts +165 -7
  14. package/dist/kernel/build.d.ts.map +1 -1
  15. package/dist/kernel/cameraShake.d.ts.map +1 -1
  16. package/dist/kernel/describeScene.d.ts +50 -0
  17. package/dist/kernel/describeScene.d.ts.map +1 -0
  18. package/dist/kernel/description.d.ts +223 -1
  19. package/dist/kernel/description.d.ts.map +1 -1
  20. package/dist/kernel/diff.d.ts +15 -0
  21. package/dist/kernel/diff.d.ts.map +1 -1
  22. package/dist/kernel/equality.d.ts +11 -1
  23. package/dist/kernel/equality.d.ts.map +1 -1
  24. package/dist/kernel/gizmo.d.ts +69 -0
  25. package/dist/kernel/gizmo.d.ts.map +1 -0
  26. package/dist/kernel/history.d.ts +61 -0
  27. package/dist/kernel/history.d.ts.map +1 -0
  28. package/dist/kernel/index.d.ts +19 -6
  29. package/dist/kernel/index.d.ts.map +1 -1
  30. package/dist/kernel/index.js +124 -3
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/kernel/inspect.d.ts +85 -0
  33. package/dist/kernel/inspect.d.ts.map +1 -0
  34. package/dist/kernel/lineGeometry.d.ts +25 -0
  35. package/dist/kernel/lineGeometry.d.ts.map +1 -0
  36. package/dist/kernel/lockstep.d.ts +114 -0
  37. package/dist/kernel/lockstep.d.ts.map +1 -0
  38. package/dist/kernel/loop.d.ts.map +1 -1
  39. package/dist/kernel/materials.d.ts.map +1 -1
  40. package/dist/kernel/paintOrder.d.ts +53 -0
  41. package/dist/kernel/paintOrder.d.ts.map +1 -0
  42. package/dist/kernel/physics.d.ts +39 -0
  43. package/dist/kernel/physics.d.ts.map +1 -1
  44. package/dist/kernel/pivot.d.ts +54 -0
  45. package/dist/kernel/pivot.d.ts.map +1 -0
  46. package/dist/kernel/pointBuffers.d.ts +17 -0
  47. package/dist/kernel/pointBuffers.d.ts.map +1 -0
  48. package/dist/kernel/pointer.d.ts +31 -1
  49. package/dist/kernel/pointer.d.ts.map +1 -1
  50. package/dist/kernel/preview.d.ts +32 -0
  51. package/dist/kernel/preview.d.ts.map +1 -0
  52. package/dist/kernel/renderKit.d.ts +21 -1
  53. package/dist/kernel/renderKit.d.ts.map +1 -1
  54. package/dist/kernel/renderer.d.ts +6 -0
  55. package/dist/kernel/renderer.d.ts.map +1 -1
  56. package/dist/kernel/sceneChanges.d.ts +87 -0
  57. package/dist/kernel/sceneChanges.d.ts.map +1 -0
  58. package/dist/kernel/sceneGraph.d.ts +9 -1
  59. package/dist/kernel/sceneGraph.d.ts.map +1 -1
  60. package/dist/kernel/sceneIndex.d.ts +5 -0
  61. package/dist/kernel/sceneIndex.d.ts.map +1 -1
  62. package/dist/kernel/seeded.d.ts +9 -0
  63. package/dist/kernel/seeded.d.ts.map +1 -0
  64. package/dist/kernel/shots.d.ts +26 -0
  65. package/dist/kernel/shots.d.ts.map +1 -0
  66. package/dist/kernel/tape.d.ts +38 -0
  67. package/dist/kernel/tape.d.ts.map +1 -0
  68. package/dist/kernel/timeline.d.ts +60 -0
  69. package/dist/kernel/timeline.d.ts.map +1 -0
  70. package/dist/kernel/trail.d.ts +31 -0
  71. package/dist/kernel/trail.d.ts.map +1 -0
  72. package/dist/kernel/viewportGizmo.d.ts +25 -0
  73. package/dist/kernel/viewportGizmo.d.ts.map +1 -0
  74. package/dist/runtime/Inspector.d.ts +20 -0
  75. package/dist/runtime/Inspector.d.ts.map +1 -0
  76. package/dist/runtime/SceneFile.d.ts +462 -0
  77. package/dist/runtime/SceneFile.d.ts.map +1 -0
  78. package/dist/runtime/SceneSchema.d.ts +404 -0
  79. package/dist/runtime/SceneSchema.d.ts.map +1 -1
  80. package/dist/runtime/errors.d.ts +9 -0
  81. package/dist/runtime/errors.d.ts.map +1 -1
  82. package/dist/runtime/index.d.ts +3 -1
  83. package/dist/runtime/index.d.ts.map +1 -1
  84. package/dist/webgl/index.d.ts.map +1 -1
  85. package/dist/webgl/index.js +228 -3
  86. package/dist/webgl/index.js.map +6 -4
  87. package/dist/webgl/line.d.ts +4 -0
  88. package/dist/webgl/line.d.ts.map +1 -0
  89. package/dist/webgl/points.d.ts +4 -0
  90. package/dist/webgl/points.d.ts.map +1 -0
  91. package/dist/webgpu/index.d.ts.map +1 -1
  92. package/dist/webgpu/index.js +206 -26
  93. package/dist/webgpu/index.js.map +6 -4
  94. package/dist/webgpu/line.d.ts +4 -0
  95. package/dist/webgpu/line.d.ts.map +1 -0
  96. package/dist/webgpu/points.d.ts +4 -0
  97. package/dist/webgpu/points.d.ts.map +1 -0
  98. package/package.json +4 -2
  99. package/src/kernel/bodies.ts +299 -17
  100. package/src/kernel/boxSelect.ts +73 -0
  101. package/src/kernel/build.ts +374 -1
  102. package/src/kernel/cameraShake.ts +3 -13
  103. package/src/kernel/describeScene.ts +224 -0
  104. package/src/kernel/description.ts +243 -0
  105. package/src/kernel/diff.ts +39 -0
  106. package/src/kernel/equality.ts +123 -0
  107. package/src/kernel/gizmo.ts +478 -0
  108. package/src/kernel/history.ts +118 -0
  109. package/src/kernel/index.ts +110 -1
  110. package/src/kernel/inspect.ts +214 -0
  111. package/src/kernel/lineGeometry.ts +75 -0
  112. package/src/kernel/lockstep.ts +237 -0
  113. package/src/kernel/loop.ts +7 -0
  114. package/src/kernel/materials.ts +3 -0
  115. package/src/kernel/paintOrder.ts +274 -0
  116. package/src/kernel/physics.ts +86 -1
  117. package/src/kernel/pivot.ts +418 -0
  118. package/src/kernel/pointBuffers.ts +115 -0
  119. package/src/kernel/pointer.ts +98 -17
  120. package/src/kernel/preview.ts +467 -0
  121. package/src/kernel/renderKit.ts +23 -1
  122. package/src/kernel/renderer.ts +6 -0
  123. package/src/kernel/sceneChanges.ts +265 -0
  124. package/src/kernel/sceneGraph.ts +354 -11
  125. package/src/kernel/sceneIndex.ts +8 -0
  126. package/src/kernel/seeded.ts +18 -0
  127. package/src/kernel/shots.ts +167 -0
  128. package/src/kernel/tape.ts +124 -0
  129. package/src/kernel/timeline.ts +93 -0
  130. package/src/kernel/trail.ts +170 -0
  131. package/src/kernel/viewportGizmo.ts +194 -0
  132. package/src/runtime/Inspector.ts +43 -0
  133. package/src/runtime/SceneFile.ts +46 -0
  134. package/src/runtime/SceneSchema.ts +319 -0
  135. package/src/runtime/errors.ts +6 -0
  136. package/src/runtime/index.ts +3 -1
  137. package/src/webgl/index.ts +4 -0
  138. package/src/webgl/line.ts +66 -0
  139. package/src/webgl/points.ts +205 -0
  140. package/src/webgpu/index.ts +4 -0
  141. package/src/webgpu/line.ts +67 -0
  142. package/src/webgpu/points.ts +193 -0
  143. package/dist/index-8cf8t6by.js.map +0 -41
@@ -0,0 +1,194 @@
1
+ /**
2
+ * A viewport gizmo as data, after Drei's `GizmoHelper` with `GizmoViewport` (`bf6f4ad`,
3
+ * `src/core/GizmoHelper.tsx`, `src/core/GizmoViewport.tsx`): three coloured axes, turned
4
+ * as the camera sees the world, with a head at each end. Drei draws it in a `Hud` and,
5
+ * when a head is pressed, tweens the camera to look from that axis; here
6
+ * `viewportGizmoNodes` describes it for a scene's `hud`, `viewportAxisOf` names the head
7
+ * a hit is on, and `cameraAlong` returns the camera looking from that axis, for the
8
+ * Model to keep (a camera's `damping` eases it round, as Drei's tween does).
9
+ *
10
+ * The positive heads carry their letters, drawn with bars, as Drei's canvas textures
11
+ * carry theirs; the negative heads are smaller and fainter, as Drei's.
12
+ *
13
+ * @module
14
+ */
15
+ import { Euler, Matrix4, Quaternion, Vector3 } from 'three';
16
+
17
+ import { basic, billboard, box, circle, fixed, group, mesh } from './build.ts';
18
+ import type { Camera, SceneNode, Vec3 } from './description.ts';
19
+
20
+ /** An axis end: a positive or negative world axis. */
21
+ export type ViewAxis = 'x' | 'y' | 'z' | '-x' | '-y' | '-z';
22
+
23
+ export type ViewportGizmoOptions = {
24
+ /** Pixels from the middle to a head: Drei's `scale` of 40. */
25
+ readonly size: number;
26
+ /** The head under the pointer, drawn larger, as Drei's. */
27
+ readonly hovered: ViewAxis | null;
28
+ /** Leaves out the negative heads (Drei's `hideNegativeAxes`). */
29
+ readonly hideNegativeAxes: boolean;
30
+ };
31
+
32
+ /** Drei's `axisColors`. */
33
+ const colours = { x: 0xff2060, y: 0x20df80, z: 0x2080ff } as const;
34
+ /** Drei's `labelColor`. */
35
+ const ink = 0x000000;
36
+
37
+ const directions: Record<ViewAxis, Vec3> = {
38
+ x: [1, 0, 0],
39
+ y: [0, 1, 0],
40
+ z: [0, 0, 1],
41
+ '-x': [-1, 0, 0],
42
+ '-y': [0, -1, 0],
43
+ '-z': [0, 0, -1],
44
+ };
45
+
46
+ const axes = ['x', 'y', 'z', '-x', '-y', '-z'] as const;
47
+
48
+ /** How the camera is turned: three's `Object3D.lookAt` for a camera, up the world's y. */
49
+ const cameraTurn = (camera: Camera): Quaternion =>
50
+ new Quaternion().setFromRotationMatrix(
51
+ new Matrix4().lookAt(
52
+ new Vector3(...camera.position),
53
+ new Vector3(...camera.lookAt),
54
+ new Vector3(0, 1, 0),
55
+ ),
56
+ );
57
+
58
+ /** The gizmo's turn: the camera's undone, so the axes show as the camera sees the world. */
59
+ const gizmoTurn = (camera: Camera): Vec3 => {
60
+ const turn = new Euler().setFromQuaternion(cameraTurn(camera).invert());
61
+ return [turn.x, turn.y, turn.z];
62
+ };
63
+
64
+ /** A bar of a letter: length and angle (radians from across) in head units, at `at`. */
65
+ const stroke = (key: string, length: number, angle: number, at: readonly [number, number]) =>
66
+ mesh({
67
+ key,
68
+ position: [at[0], at[1], 0.01],
69
+ rotation: fixed([0, 0, angle]),
70
+ geometry: box({ width: length, height: 0.07, depth: 0.001 }),
71
+ material: basic({ color: ink }),
72
+ });
73
+
74
+ /** The letters, in bars, about the middle of a head 0.5 across. */
75
+ const letters: Record<
76
+ 'x' | 'y' | 'z',
77
+ ReadonlyArray<readonly [number, number, readonly [number, number]]>
78
+ > = {
79
+ x: [
80
+ [0.34, Math.PI / 4, [0, 0]],
81
+ [0.34, -Math.PI / 4, [0, 0]],
82
+ ],
83
+ y: [
84
+ [0.14, Math.PI / 2, [0, -0.07]],
85
+ [0.17, Math.PI / 3, [0.04, 0.06]],
86
+ [0.17, -Math.PI / 3, [-0.04, 0.06]],
87
+ ],
88
+ z: [
89
+ [0.22, 0, [0, 0.11]],
90
+ [0.22, 0, [0, -0.11]],
91
+ [0.3, Math.PI / 4, [0, 0]],
92
+ ],
93
+ };
94
+
95
+ const isPositive = (axis: ViewAxis): axis is 'x' | 'y' | 'z' => axis.length === 1;
96
+
97
+ const colourOf = (axis: ViewAxis): number =>
98
+ colours[isPositive(axis) ? axis : (axis.slice(1) as 'x' | 'y' | 'z')];
99
+
100
+ /** Drei's head sizes: a labelled head whole, a bare one three quarters, 1.2 under the pointer. */
101
+ const headScale = (axis: ViewAxis, hovered: ViewAxis | null): number =>
102
+ (isPositive(axis) ? 1 : 0.75) * (axis === hovered ? 1.2 : 1);
103
+
104
+ const labelOf = (key: string, axis: ViewAxis): ReadonlyArray<SceneNode> =>
105
+ isPositive(axis)
106
+ ? letters[axis].map(([length, angle, at], index) =>
107
+ stroke(`${key}:${axis}:letter:${index}`, length, angle, at),
108
+ )
109
+ : [];
110
+
111
+ /** A head: a disc facing the eye, in its axis's colour, with its letter on it. */
112
+ const head = (key: string, axis: ViewAxis, hovered: ViewAxis | null): SceneNode => {
113
+ const scale = headScale(axis, hovered);
114
+ return group({
115
+ key: `${key}:${axis}`,
116
+ position: directions[axis],
117
+ rotation: billboard(),
118
+ scale: [scale, scale, scale],
119
+ interactive: true,
120
+ children: [
121
+ mesh({
122
+ key: `${key}:${axis}:disc`,
123
+ geometry: circle({ radius: 0.25, segments: 32 }),
124
+ material: basic({
125
+ color: colourOf(axis),
126
+ opacity: isPositive(axis) ? 1 : 0.75,
127
+ transparent: !isPositive(axis),
128
+ }),
129
+ }),
130
+ ...labelOf(key, axis),
131
+ ],
132
+ });
133
+ };
134
+
135
+ /** An axis's bar from the middle, as Drei's `Axis` (0.8 × 0.05 × 0.05). */
136
+ const bar = (key: string, axis: 'x' | 'y' | 'z'): SceneNode => {
137
+ const [x, y, z] = directions[axis];
138
+ return mesh({
139
+ key: `${key}:${axis}:bar`,
140
+ position: [x * 0.4, y * 0.4, z * 0.4],
141
+ geometry: box({
142
+ width: axis === 'x' ? 0.8 : 0.05,
143
+ height: axis === 'y' ? 0.8 : 0.05,
144
+ depth: axis === 'z' ? 0.8 : 0.05,
145
+ }),
146
+ material: basic({ color: colours[axis] }),
147
+ });
148
+ };
149
+
150
+ /**
151
+ * The gizmo for `camera`, keyed under `key` (`${key}:x`, `${key}:-y` and so on), for a
152
+ * scene's `hud`: Drei's defaults, 40 pixels to a head, every head shown.
153
+ */
154
+ export const viewportGizmoNodes = (
155
+ key: string,
156
+ camera: Camera,
157
+ options: Partial<ViewportGizmoOptions> = {},
158
+ ): SceneNode => {
159
+ const size = options.size ?? 40;
160
+ const hovered = options.hovered ?? null;
161
+ const shown = options.hideNegativeAxes === true ? axes.filter(isPositive) : axes;
162
+ return group({
163
+ key,
164
+ rotation: fixed(gizmoTurn(camera)),
165
+ scale: [size, size, size],
166
+ children: [
167
+ bar(key, 'x'),
168
+ bar(key, 'y'),
169
+ bar(key, 'z'),
170
+ ...shown.map((axis) => head(key, axis, hovered)),
171
+ ],
172
+ });
173
+ };
174
+
175
+ /** The head a hit `key` (or any part of one) is on, if it is one of this gizmo's. */
176
+ export const viewportAxisOf = (key: string, hit: string): ViewAxis | undefined => {
177
+ const [owner, axis] = hit.split(':');
178
+ if (owner !== key) return undefined;
179
+ return axes.find((candidate) => candidate === axis);
180
+ };
181
+
182
+ /**
183
+ * `camera` looking from `axis` at what it looks at, as far away as it was: Drei's
184
+ * `tweenCamera` to that head, where it ends. Looking straight up or down, it stays a
185
+ * hair off the pole toward +z, as `OrbitControls` keeps its camera off them.
186
+ */
187
+ export const cameraAlong = <C extends Camera>(camera: C, axis: ViewAxis): C => {
188
+ const target = new Vector3(...camera.lookAt);
189
+ const distance = new Vector3(...camera.position).distanceTo(target);
190
+ const direction = new Vector3(...directions[axis]);
191
+ if (axis === 'y' || axis === '-y') direction.z = 1e-6;
192
+ const position = direction.normalize().multiplyScalar(distance).add(target);
193
+ return { ...camera, position: [position.x, position.y, position.z] };
194
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * An inspector generated from schemas (roadmap, "Beyond parity" 4): the fields of any
3
+ * value with a schema (a scene node, a material, a Model's own records) as an inspector
4
+ * shows them, and edits that are checked by the same schema, so an inspector can never
5
+ * put a value the schema rejects into a Model.
6
+ *
7
+ * `fields(schema, value)` lists them (numbers, text, switches, choices, colours, vectors,
8
+ * and read-only kinds and lists); `edit(schema, value, path, next)` puts `next` at
9
+ * `path` and decodes the result, failing with the schema's own account of what is
10
+ * wrong. Colours are fields typed `SceneSchema.Rgb`; vectors, `SceneSchema.Vec3`.
11
+ *
12
+ * @module
13
+ */
14
+ import { Result, Schema } from 'effect';
15
+
16
+ import {
17
+ type FieldPath,
18
+ type InspectableAst,
19
+ type InspectedField,
20
+ inspectFields,
21
+ setAt,
22
+ } from '../kernel/inspect.ts';
23
+ import * as SceneSchema from './SceneSchema.ts';
24
+
25
+ const hints = { colours: [SceneSchema.Rgb.ast], vectors: [SceneSchema.Vec3.ast] };
26
+
27
+ /** The AST, read through the shapes the inspector understands. */
28
+ const inspectable = (schema: Schema.Top): InspectableAst => schema.ast;
29
+
30
+ /** The fields of `value`, as `schema` describes it, in the schema's order. */
31
+ export const fields = (schema: Schema.Top, value: unknown): ReadonlyArray<InspectedField> =>
32
+ inspectFields(inspectable(schema), value, hints);
33
+
34
+ /** `value` with `next` at `path`, if `schema` accepts the result; why not, if it does not. */
35
+ export const edit = <A>(
36
+ schema: Schema.Decoder<A>,
37
+ value: A,
38
+ path: FieldPath,
39
+ next: unknown,
40
+ ): Result.Result<A, string> =>
41
+ Schema.decodeUnknownResult(schema)(setAt(value, path, next)).pipe(
42
+ Result.mapError((error) => error.message),
43
+ );
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Scenes saved and loaded as JSON (roadmap, "Beyond parity" 4): a description in a
3
+ * small versioned envelope, written and read through `SceneSchema`, so a file is either
4
+ * a well-typed description or a typed failure saying where it is not. Fields added since
5
+ * a file was written decode at their defaults, as stored descriptions always have.
6
+ *
7
+ * Decoding checks shapes, not the scene's own rules: duplicate keys and unknown
8
+ * behaviours are rejected whole when the scene is applied (`InvalidScene`), against the
9
+ * behaviours the app has registered.
10
+ *
11
+ * @module
12
+ */
13
+ import { Result, Schema } from 'effect';
14
+
15
+ import type { SceneDescription } from '../kernel/index.ts';
16
+ import { UnreadableScene } from './errors.ts';
17
+ import * as SceneSchema from './SceneSchema.ts';
18
+
19
+ /**
20
+ * A saved scene: what it is (`format`), the envelope's `version` (a later one decodes
21
+ * older files, migrating them), and the description.
22
+ */
23
+ export const SceneFile = Schema.Struct({
24
+ format: Schema.Literal('effect-three/scene'),
25
+ version: Schema.Literal(1),
26
+ scene: SceneSchema.SceneDescription,
27
+ });
28
+ export type SceneFile = typeof SceneFile.Type;
29
+
30
+ const asJson = Schema.fromJsonString(SceneFile, { space: 2 });
31
+
32
+ /** The scene as the text of a scene file, indented for reading and diffing. */
33
+ export const save = (scene: SceneDescription): string =>
34
+ Schema.encodeSync(asJson)(SceneFile.make({ format: 'effect-three/scene', version: 1, scene }));
35
+
36
+ const unreadable = (error: Schema.SchemaError) => new UnreadableScene({ issue: error.message });
37
+
38
+ /**
39
+ * The scene a file's text describes, or why it cannot be read: a `Result`, which an
40
+ * Effect can `yield*` directly.
41
+ */
42
+ export const read = (text: string): Result.Result<SceneDescription, UnreadableScene> =>
43
+ Schema.decodeUnknownResult(asJson)(text).pipe(
44
+ Result.map((file) => file.scene),
45
+ Result.mapError(unreadable),
46
+ );