effect-motion 0.3.2 → 0.5.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.
Files changed (91) hide show
  1. package/README.md +2 -2
  2. package/dist/Camera.d.ts +186 -49
  3. package/dist/Camera.js +343 -76
  4. package/dist/Color.d.ts +101 -1
  5. package/dist/Color.js +101 -1
  6. package/dist/EffectMotionError.d.ts +17 -0
  7. package/dist/EffectMotionError.js +17 -0
  8. package/dist/Entity.d.ts +682 -38
  9. package/dist/Entity.js +271 -27
  10. package/dist/Font.d.ts +108 -0
  11. package/dist/Font.js +95 -0
  12. package/dist/Image.d.ts +71 -0
  13. package/dist/Image.js +50 -0
  14. package/dist/Instance.d.ts +73 -11
  15. package/dist/Instance.js +44 -11
  16. package/dist/Motion.d.ts +328 -53
  17. package/dist/Motion.js +278 -46
  18. package/dist/Physics.d.ts +154 -20
  19. package/dist/Physics.js +92 -11
  20. package/dist/Projection.d.ts +37 -132
  21. package/dist/Projection.js +33 -292
  22. package/dist/Resource.d.ts +26 -0
  23. package/dist/Resource.js +41 -0
  24. package/dist/Runner.d.ts +653 -330
  25. package/dist/Runner.js +208 -158
  26. package/dist/Scene.d.ts +621 -171
  27. package/dist/Scene.js +620 -89
  28. package/dist/Timing.d.ts +170 -13
  29. package/dist/Timing.js +124 -6
  30. package/dist/Tree.d.ts +39 -0
  31. package/dist/Tree.js +127 -0
  32. package/dist/index.d.ts +54 -5
  33. package/dist/index.js +56 -5
  34. package/dist/particles/Particle.d.ts +2 -2
  35. package/dist/particles/ParticleField.d.ts +8 -6
  36. package/dist/particles/ParticleField.js +8 -9
  37. package/dist/particles/constructors.d.ts +5 -6
  38. package/dist/particles/constructors.js +8 -3
  39. package/dist/particles/legacy.d.ts +58 -0
  40. package/dist/particles/legacy.js +46 -0
  41. package/dist/particles/simulate.js +13 -5
  42. package/dist/particles/step.js +14 -10
  43. package/dist/types.d.ts +5 -0
  44. package/dist/types.js +1 -0
  45. package/package.json +2 -4
  46. package/dist/CameraHelpers.d.ts +0 -70
  47. package/dist/CameraHelpers.js +0 -239
  48. package/dist/CanvasExporter.d.ts +0 -12
  49. package/dist/CanvasExporter.js +0 -40
  50. package/dist/Fonts.d.ts +0 -41
  51. package/dist/Fonts.js +0 -27
  52. package/dist/Images.d.ts +0 -33
  53. package/dist/Images.js +0 -24
  54. package/dist/PngExporter.d.ts +0 -6
  55. package/dist/PngExporter.js +0 -85
  56. package/dist/Renderer.d.ts +0 -118
  57. package/dist/Renderer.js +0 -381
  58. package/dist/Shapes.d.ts +0 -11
  59. package/dist/Shapes.js +0 -11
  60. package/dist/demo.d.ts +0 -3
  61. package/dist/demo.js +0 -65
  62. package/dist/render/dof.d.ts +0 -27
  63. package/dist/render/dof.js +0 -37
  64. package/dist/render/paint.d.ts +0 -30
  65. package/dist/render/paint.js +0 -36
  66. package/dist/render/shapes.d.ts +0 -42
  67. package/dist/render/shapes.js +0 -310
  68. package/dist/shapes/Circle.d.ts +0 -32
  69. package/dist/shapes/Circle.js +0 -9
  70. package/dist/shapes/Ellipse.d.ts +0 -35
  71. package/dist/shapes/Ellipse.js +0 -10
  72. package/dist/shapes/Group.d.ts +0 -116
  73. package/dist/shapes/Group.js +0 -82
  74. package/dist/shapes/Hud.d.ts +0 -38
  75. package/dist/shapes/Hud.js +0 -35
  76. package/dist/shapes/Image.d.ts +0 -45
  77. package/dist/shapes/Image.js +0 -28
  78. package/dist/shapes/Line.d.ts +0 -47
  79. package/dist/shapes/Line.js +0 -35
  80. package/dist/shapes/Path.d.ts +0 -107
  81. package/dist/shapes/Path.js +0 -32
  82. package/dist/shapes/Rect.d.ts +0 -51
  83. package/dist/shapes/Rect.js +0 -22
  84. package/dist/shapes/Shape2D.d.ts +0 -49
  85. package/dist/shapes/Shape2D.js +0 -52
  86. package/dist/shapes/Shapes.d.ts +0 -11
  87. package/dist/shapes/Shapes.js +0 -11
  88. package/dist/shapes/Square.d.ts +0 -32
  89. package/dist/shapes/Square.js +0 -11
  90. package/dist/shapes/Text.d.ts +0 -51
  91. package/dist/shapes/Text.js +0 -24
@@ -1,310 +0,0 @@
1
- import * as Tvg from "@effect-motion/thorvg";
2
- import * as Effect from "effect/Effect";
3
- import * as Color from "../Color.js";
4
- import { renderOpacity, renderSize } from "../particles/overLife.js";
5
- import { ParticleField } from "../particles/ParticleField.js";
6
- import * as Circle from "../shapes/Circle.js";
7
- import * as Ellipse from "../shapes/Ellipse.js";
8
- import * as Group from "../shapes/Group.js";
9
- import * as Hud from "../shapes/Hud.js";
10
- import * as Image from "../shapes/Image.js";
11
- import * as Line from "../shapes/Line.js";
12
- import * as Path from "../shapes/Path.js";
13
- import * as Rect from "../shapes/Rect.js";
14
- import * as Square from "../shapes/Square.js";
15
- import * as Text from "../shapes/Text.js";
16
- import { applyStyle, finishPaint } from "./paint.js";
17
- /**
18
- * ThorVG paint functions for the built-in shapes — this file is the coverage
19
- * manifest: `paints` is typed `PaintFunctions<...>`, so a built-in missing a
20
- * paint function is a type error, not a runtime surprise.
21
- */
22
- export const circle = ({ data, scene, projection, }) => Effect.gen(function* () {
23
- const shape = yield* Tvg.Shape.make();
24
- yield* Tvg.Shape.appendCircle(shape, data.x, data.y, data.radius, data.radius);
25
- yield* applyStyle(shape, data);
26
- yield* finishPaint(shape, scene, projection);
27
- });
28
- export const rect = ({ data, scene, projection, }) => Effect.gen(function* () {
29
- const shape = yield* Tvg.Shape.make();
30
- const quad = projection.quad;
31
- if (quad !== undefined) {
32
- // a tilted plane: the vertices are already projected to screen
33
- // (near-plane-clipped, 3–5 points — true perspective foreshortening,
34
- // not an affine), so paint the exact polygon and skip the billboard
35
- // transform
36
- for (const [i, p] of quad.entries()) {
37
- yield* i === 0
38
- ? Tvg.Shape.moveTo(shape, p.x, p.y)
39
- : Tvg.Shape.lineTo(shape, p.x, p.y);
40
- }
41
- yield* Tvg.Shape.close(shape);
42
- yield* applyStyle(shape, data);
43
- yield* Tvg.Scene.add(scene, shape);
44
- return;
45
- }
46
- // SVG lone-radius semantics: one set radius applies to both axes
47
- yield* Tvg.Shape.appendRect(shape, data.x, data.y, data.width, data.height, data.rx ?? data.ry ?? 0, data.ry ?? data.rx ?? 0);
48
- yield* applyStyle(shape, data);
49
- yield* finishPaint(shape, scene, projection);
50
- });
51
- export const square = ({ data, scene, projection, }) => Effect.gen(function* () {
52
- const shape = yield* Tvg.Shape.make();
53
- yield* Tvg.Shape.appendRect(shape, data.x, data.y, data.size, data.size);
54
- yield* applyStyle(shape, data);
55
- yield* finishPaint(shape, scene, projection);
56
- });
57
- export const ellipse = ({ data, scene, projection, }) => Effect.gen(function* () {
58
- const shape = yield* Tvg.Shape.make();
59
- yield* Tvg.Shape.appendCircle(shape, data.x, data.y, data.rx, data.ry);
60
- yield* applyStyle(shape, data);
61
- yield* finishPaint(shape, scene, projection);
62
- });
63
- export const line = ({ data, scene, projection, }) => Effect.gen(function* () {
64
- const shape = yield* Tvg.Shape.make();
65
- const segment = projection.segment;
66
- if (segment !== undefined) {
67
- // skeletal path: the endpoints are already projected to screen
68
- // (near-plane-clipped, per-endpoint perspective), so draw the
69
- // exact segment and skip the billboard transform — stroke width
70
- // scales by the segment's midpoint perspective scale instead
71
- yield* Tvg.Shape.moveTo(shape, segment[0].x, segment[0].y);
72
- yield* Tvg.Shape.lineTo(shape, segment[1].x, segment[1].y);
73
- }
74
- else {
75
- yield* Tvg.Shape.moveTo(shape, data.x, data.y);
76
- yield* Tvg.Shape.lineTo(shape, data.x2, data.y2);
77
- }
78
- // a line has no fill; stroke defaults come from its schema
79
- if (data.stroke !== undefined) {
80
- const { r, g, b, a } = Color.bytes(data.stroke);
81
- yield* Tvg.Shape.setStrokeColor(shape, r, g, b, a);
82
- }
83
- yield* Tvg.Shape.setStrokeWidth(shape, segment !== undefined
84
- ? data.strokeWidth * projection.scale
85
- : data.strokeWidth);
86
- if (data.opacity !== 1) {
87
- yield* Tvg.Paint.setOpacity(shape, Math.round(data.opacity * 255));
88
- }
89
- if (segment !== undefined) {
90
- yield* Tvg.Scene.add(scene, shape);
91
- return;
92
- }
93
- yield* finishPaint(shape, scene, projection);
94
- });
95
- export const path = ({ data, scene, projection, }) => Effect.gen(function* () {
96
- // the flatten step already split, projected, and near-plane-clipped the
97
- // command points (see Projection.projectPath) — geometry arrives in
98
- // screen space, so emit it directly and apply no transform. A path with
99
- // nothing visible was culled before paint, so subpaths is present here.
100
- const subpaths = projection.subpaths;
101
- if (subpaths === undefined) {
102
- return;
103
- }
104
- const shape = yield* Tvg.Shape.make();
105
- for (const subpath of subpaths) {
106
- for (const [i, p] of subpath.points.entries()) {
107
- yield* i === 0
108
- ? Tvg.Shape.moveTo(shape, p.x, p.y)
109
- : Tvg.Shape.lineTo(shape, p.x, p.y);
110
- }
111
- // ponytail: a near-clipped closed subpath strokes along the synthetic
112
- // clip edge too — accepted; split stroke/fill geometry if it matters
113
- if (subpath.closed) {
114
- yield* Tvg.Shape.close(shape);
115
- }
116
- }
117
- yield* applyStyle(shape, data);
118
- // stroke width scales by the path's perspective scale (its mean visible
119
- // depth), overriding the unscaled width applyStyle set
120
- if (data.strokeWidth !== undefined) {
121
- yield* Tvg.Shape.setStrokeWidth(shape, data.strokeWidth * projection.scale);
122
- }
123
- yield* Tvg.Scene.add(scene, shape);
124
- });
125
- // A group paints nothing itself: its position has already composed into its
126
- // children's world coordinates during flatten, and each child was emitted as
127
- // its own paintable. This is a no-op, present only so the coverage map is
128
- // exhaustive.
129
- export const group = () => Effect.void;
130
- // A Hud is a container like Group: it paints nothing itself — its meaning
131
- // (identity-camera projection, top-tier order) lives in the renderer's
132
- // flatten/sort, not in a paint.
133
- export const hud = () => Effect.void;
134
- export const image = ({ data, scene, projection, }) => Effect.gen(function* () {
135
- // the session decoded the asset once at open; a missing name (never
136
- // declared, or its fetch/decode failed) is a soft skip — this instance
137
- // paints nothing and the rest of the frame renders
138
- const { pictures } = yield* Tvg.RenderSession;
139
- const source = pictures.get(data.image);
140
- if (source === undefined) {
141
- return;
142
- }
143
- // duplicates share the decoded surface (spike-verified ~42µs each), so
144
- // per-frame cost is a handle copy, not a decode
145
- const picture = yield* Tvg.Paint.duplicate(source);
146
- // declared size scales per axis via the transform — NOT Picture.setSize,
147
- // which preserves the source aspect (uniform min-factor scale, probe-
148
- // verified: 8×8 sized to 40×20 renders 20×20). A lone dimension is
149
- // ignored (aspect math would need the natural size, which frame data
150
- // never sees).
151
- let sx = 1;
152
- let sy = 1;
153
- if (data.width !== undefined && data.height !== undefined) {
154
- const natural = yield* Tvg.Picture.getSize(picture);
155
- if (natural.width > 0 && natural.height > 0) {
156
- sx = data.width / natural.width;
157
- sy = data.height / natural.height;
158
- }
159
- }
160
- if (data.opacity !== 1) {
161
- yield* Tvg.Paint.setOpacity(picture, Math.round(data.opacity * 255));
162
- }
163
- // a picture draws from its own origin, unlike shapes which bake
164
- // data.x/y into geometry — compose scale-to-declared-size and the local
165
- // anchor translate into the projection affine so (data.x, data.y) is
166
- // the top-left, like Rect: screen ∘ translate(x, y) ∘ scale(sx, sy)
167
- const m = projection.screen;
168
- const composed = {
169
- a: m.a * sx,
170
- b: m.b * sx,
171
- c: m.c * sy,
172
- d: m.d * sy,
173
- e: m.a * data.x + m.c * data.y + m.e,
174
- f: m.b * data.x + m.d * data.y + m.f,
175
- };
176
- const isIdentity = composed.a === 1 &&
177
- composed.b === 0 &&
178
- composed.c === 0 &&
179
- composed.d === 1 &&
180
- composed.e === 0 &&
181
- composed.f === 0;
182
- if (!isIdentity) {
183
- yield* Tvg.Paint.setTransform(picture, composed);
184
- }
185
- yield* Tvg.Scene.add(scene, picture);
186
- // ponytail: billboard only — a tilted image needs a projective
187
- // setTransform (full 3×3 bottom row) mapping the projected quad; add
188
- // when a scene needs a tilted image.
189
- });
190
- export const text = ({ data, scene, projection, }) => Effect.gen(function* () {
191
- const glyphs = yield* Tvg.Text.make();
192
- // setFont fails ("insufficient condition") when the family isn't loaded
193
- // into the engine yet — a missing/not-yet-fetched font. That must NOT
194
- // abort the frame (it would blank every other paint too): swallow it so
195
- // this one text simply doesn't draw and the rest of the frame renders.
196
- yield* Tvg.Text.setFont(glyphs, data.fontFamily).pipe(Effect.ignore);
197
- yield* Tvg.Text.setText(glyphs, data.text);
198
- const { r, g, b } = Color.bytes(data.fill);
199
- yield* Tvg.Text.setColor(glyphs, r, g, b);
200
- if (data.opacity !== 1) {
201
- yield* Tvg.Paint.setOpacity(glyphs, Math.round(data.opacity * 255));
202
- }
203
- // ThorVG QUIRK (verified against this build): text inside a nested scene
204
- // renders ONLY when positioned by a plain `translate` — `set_transform`,
205
- // `scale`, and `text_align` on a scene-child text all produce nothing. So
206
- // text can't use the projection's full affine or ThorVG's align. Instead:
207
- // - fold the perspective scale into the FONT SIZE (setTextSize scales the
208
- // glyphs; scale-transform doesn't work),
209
- // - apply textAnchor/baseline as a local offset from an ESTIMATED box,
210
- // - position with translate(screenX, screenY).
211
- // ponytail: this handles translate+uniform-scale cameras (the common
212
- // case). A rotated/sheared camera on text isn't expressed (text stays
213
- // axis-aligned); revisit if a scene tilts text in 3D.
214
- const scale = projection.scale > 0 ? projection.scale : 1;
215
- const size = data.fontSize * scale;
216
- yield* Tvg.Text.setSize(glyphs, size);
217
- // estimated text box (proportional font, ~0.6 advance/size; ascent/cap
218
- // factors) — used because ThorVG align doesn't work on scene-child text
219
- const estWidth = size * data.text.length * AVG_CHAR_WIDTH;
220
- const dx = data.textAnchor === "middle"
221
- ? -estWidth / 2
222
- : data.textAnchor === "end"
223
- ? -estWidth
224
- : 0;
225
- // text origin y is the baseline (bottom of glyphs); shift UP by ~half cap
226
- // height to center (middle), or up by the full ascent so the given point
227
- // is the TOP (hanging); leave the baseline at y (auto)
228
- const dy = data.baseline === "middle"
229
- ? -size * CAP_CENTER
230
- : data.baseline === "hanging"
231
- ? -size * ASCENT
232
- : 0;
233
- // where the anchor point (data.x, data.y) lands on screen under the camera
234
- const m = projection.screen;
235
- const screenX = m.a * data.x + m.c * data.y + m.e;
236
- const screenY = m.b * data.x + m.d * data.y + m.f;
237
- yield* Tvg.Paint.translate(glyphs, screenX + dx, screenY + dy);
238
- yield* Tvg.Scene.add(scene, glyphs);
239
- });
240
- // Text-box estimates (see the `text` paint fn): mean advance / cap-center /
241
- // ascent as fractions of font size, for the default sans. Used because ThorVG's
242
- // text align/transform don't work on scene-child text in this build.
243
- const AVG_CHAR_WIDTH = 0.6;
244
- const CAP_CENTER = 0.35;
245
- const ASCENT = 0.8;
246
- /**
247
- * ParticleField: live particles batched into one shape PER (color, opacity)
248
- * bucket — ThorVG fills a shape uniformly, so particles that share a color and
249
- * (quantized) over-life opacity are appended into the same shape and filled
250
- * once. Sized/faded by the over-life curves; dead slots emit nothing.
251
- * ponytail: one shape per distinct (color, opacity-bucket) — far fewer than one
252
- * per particle, but a fully instanced path is the upgrade if this is a wall.
253
- */
254
- export const particleField = ({ data, scene, projection, }) => Effect.gen(function* () {
255
- // group live particles by fill color + quantized opacity (24 buckets), so
256
- // each bucket is one uniformly-filled shape. Circles are appended in the
257
- // field's local coords (data.x/y offsets the whole field).
258
- const buckets = new Map();
259
- for (const p of data.buffer) {
260
- if (!p.alive) {
261
- continue;
262
- }
263
- const r = renderSize(p, data.sizeOverLife);
264
- if (r <= 0) {
265
- continue;
266
- }
267
- const o = renderOpacity(p, data.opacityOverLife) *
268
- data.opacity;
269
- const alpha = Math.max(0, Math.min(255, Math.round(o * 255)));
270
- if (alpha === 0) {
271
- continue;
272
- }
273
- // quantize alpha to keep the bucket count bounded (~24 steps)
274
- const qAlpha = Math.round(alpha / 11) * 11;
275
- const key = `${Color.toHex(p.color)}\0${qAlpha}`;
276
- let bucket = buckets.get(key);
277
- if (bucket === undefined) {
278
- bucket = { color: p.color, alpha: qAlpha, circles: [] };
279
- buckets.set(key, bucket);
280
- }
281
- bucket.circles.push([data.x + p.x, data.y + p.y, r]);
282
- }
283
- for (const { color, alpha, circles } of buckets.values()) {
284
- const shape = yield* Tvg.Shape.make();
285
- for (const [cx, cy, r] of circles) {
286
- yield* Tvg.Shape.appendCircle(shape, cx, cy, r, r);
287
- }
288
- const { r, g, b } = Color.bytes(color);
289
- yield* Tvg.Shape.setFillColor(shape, r, g, b, alpha);
290
- yield* finishPaint(shape, scene, projection);
291
- }
292
- });
293
- /**
294
- * The exhaustive paint-function map for every built-in entity. Typed
295
- * `PaintFunctions<...>` so a missing built-in fails to type-check (the old
296
- * "coverage manifest" guarantee, without a Context registry).
297
- */
298
- export const builtinPaints = {
299
- [Circle.Circle.name]: circle,
300
- [Rect.Rect.name]: rect,
301
- [Square.Square.name]: square,
302
- [Ellipse.Ellipse.name]: ellipse,
303
- [Line.Line.name]: line,
304
- [Path.Path.name]: path,
305
- [Group.Group.name]: group,
306
- [Hud.Hud.name]: hud,
307
- [Text.Text.name]: text,
308
- [Image.Image.name]: image,
309
- [ParticleField.name]: particleField,
310
- };
@@ -1,32 +0,0 @@
1
- import * as Entity from "../Entity.js";
2
- export declare const Circle: Entity.Entity<"shapes/Circle", import("effect/Schema").Struct<{
3
- x: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
4
- y: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
5
- z: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
6
- opacity: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
7
- fill: import("effect/Schema").withConstructorDefault<typeof import("../Color.js").Color>;
8
- stroke: import("effect/Schema").optionalKey<typeof import("../Color.js").Color>;
9
- strokeWidth: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
10
- radius: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
11
- }>, {
12
- readonly "~position": Entity.TraitLens<{
13
- readonly x: number;
14
- readonly y: number;
15
- readonly z: number;
16
- readonly opacity: number;
17
- readonly fill: import("../Color.js").Color;
18
- readonly stroke?: import("../Color.js").Color;
19
- readonly strokeWidth?: number;
20
- readonly radius: number;
21
- }, Entity.Position>;
22
- readonly "~opacity": Entity.TraitLens<{
23
- readonly x: number;
24
- readonly y: number;
25
- readonly z: number;
26
- readonly opacity: number;
27
- readonly fill: import("../Color.js").Color;
28
- readonly stroke?: import("../Color.js").Color;
29
- readonly strokeWidth?: number;
30
- readonly radius: number;
31
- }, number>;
32
- }>;
@@ -1,9 +0,0 @@
1
- import * as Entity from "../Entity.js";
2
- import * as Shape2D from "./Shape2D.js";
3
- export const Circle = Entity.make("shapes/Circle", {
4
- ...Shape2D.filled,
5
- radius: Shape2D.defaultedNumber(10),
6
- }, {
7
- "~position": Shape2D.positionLens(),
8
- "~opacity": Shape2D.opacityLens(),
9
- });
@@ -1,35 +0,0 @@
1
- import * as Entity from "../Entity.js";
2
- export declare const Ellipse: Entity.Entity<"shapes/Ellipse", import("effect/Schema").Struct<{
3
- x: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
4
- y: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
5
- z: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
6
- opacity: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
7
- fill: import("effect/Schema").withConstructorDefault<typeof import("../Color.js").Color>;
8
- stroke: import("effect/Schema").optionalKey<typeof import("../Color.js").Color>;
9
- strokeWidth: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
10
- rx: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
11
- ry: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
12
- }>, {
13
- readonly "~position": Entity.TraitLens<{
14
- readonly x: number;
15
- readonly y: number;
16
- readonly z: number;
17
- readonly opacity: number;
18
- readonly fill: import("../Color.js").Color;
19
- readonly stroke?: import("../Color.js").Color;
20
- readonly strokeWidth?: number;
21
- readonly rx: number;
22
- readonly ry: number;
23
- }, Entity.Position>;
24
- readonly "~opacity": Entity.TraitLens<{
25
- readonly x: number;
26
- readonly y: number;
27
- readonly z: number;
28
- readonly opacity: number;
29
- readonly fill: import("../Color.js").Color;
30
- readonly stroke?: import("../Color.js").Color;
31
- readonly strokeWidth?: number;
32
- readonly rx: number;
33
- readonly ry: number;
34
- }, number>;
35
- }>;
@@ -1,10 +0,0 @@
1
- import * as Entity from "../Entity.js";
2
- import * as Shape2D from "./Shape2D.js";
3
- export const Ellipse = Entity.make("shapes/Ellipse", {
4
- ...Shape2D.filled,
5
- rx: Shape2D.defaultedNumber(20),
6
- ry: Shape2D.defaultedNumber(10),
7
- }, {
8
- "~position": Shape2D.positionLens(),
9
- "~opacity": Shape2D.opacityLens(),
10
- });
@@ -1,116 +0,0 @@
1
- import * as Schema from "effect/Schema";
2
- import * as Entity from "../Entity.js";
3
- export declare const TransformMatrix: Schema.Struct<{
4
- readonly a: Schema.Number;
5
- readonly b: Schema.Number;
6
- readonly c: Schema.Number;
7
- readonly d: Schema.Number;
8
- readonly e: Schema.Number;
9
- readonly f: Schema.Number;
10
- }>;
11
- export type TransformMatrix = typeof TransformMatrix.Type;
12
- export declare const identityTransform: {
13
- a: number;
14
- b: number;
15
- c: number;
16
- d: number;
17
- e: number;
18
- f: number;
19
- };
20
- export declare const multiplyTransforms: (left: TransformMatrix, right: TransformMatrix) => TransformMatrix;
21
- export declare const TransformOperation: Schema.TaggedUnion<{
22
- readonly "transform/matrix": Schema.TaggedStruct<"transform/matrix", {
23
- readonly a: Schema.Number;
24
- readonly b: Schema.Number;
25
- readonly c: Schema.Number;
26
- readonly d: Schema.Number;
27
- readonly e: Schema.Number;
28
- readonly f: Schema.Number;
29
- }>;
30
- readonly "transform/scale": Schema.TaggedStruct<"transform/scale", {
31
- readonly x: Schema.Number;
32
- readonly y: Schema.Number;
33
- }>;
34
- readonly "transform/translate": Schema.TaggedStruct<"transform/translate", {
35
- readonly x: Schema.Number;
36
- readonly y: Schema.Number;
37
- }>;
38
- }>;
39
- export declare const Transform: Schema.decodeTo<Schema.Struct<{
40
- readonly a: Schema.Number;
41
- readonly b: Schema.Number;
42
- readonly c: Schema.Number;
43
- readonly d: Schema.Number;
44
- readonly e: Schema.Number;
45
- readonly f: Schema.Number;
46
- }>, Schema.$Array<Schema.TaggedUnion<{
47
- readonly "transform/matrix": Schema.TaggedStruct<"transform/matrix", {
48
- readonly a: Schema.Number;
49
- readonly b: Schema.Number;
50
- readonly c: Schema.Number;
51
- readonly d: Schema.Number;
52
- readonly e: Schema.Number;
53
- readonly f: Schema.Number;
54
- }>;
55
- readonly "transform/scale": Schema.TaggedStruct<"transform/scale", {
56
- readonly x: Schema.Number;
57
- readonly y: Schema.Number;
58
- }>;
59
- readonly "transform/translate": Schema.TaggedStruct<"transform/translate", {
60
- readonly x: Schema.Number;
61
- readonly y: Schema.Number;
62
- }>;
63
- }>>, never, never>;
64
- export declare const Group: Entity.Entity<"shapes/Group", Schema.Struct<{
65
- x: Schema.withConstructorDefault<Schema.Number>;
66
- y: Schema.withConstructorDefault<Schema.Number>;
67
- z: Schema.withConstructorDefault<Schema.Number>;
68
- opacity: Schema.withConstructorDefault<Schema.Number>;
69
- transform: Schema.withConstructorDefault<Schema.Struct<{
70
- readonly a: Schema.Number;
71
- readonly b: Schema.Number;
72
- readonly c: Schema.Number;
73
- readonly d: Schema.Number;
74
- readonly e: Schema.Number;
75
- readonly f: Schema.Number;
76
- }>>;
77
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
78
- }>, {
79
- "~position": Entity.TraitLens<Schema.Struct.ReadonlySide<{
80
- x: Schema.withConstructorDefault<Schema.Number>;
81
- y: Schema.withConstructorDefault<Schema.Number>;
82
- z: Schema.withConstructorDefault<Schema.Number>;
83
- opacity: Schema.withConstructorDefault<Schema.Number>;
84
- transform: Schema.withConstructorDefault<Schema.Struct<{
85
- readonly a: Schema.Number;
86
- readonly b: Schema.Number;
87
- readonly c: Schema.Number;
88
- readonly d: Schema.Number;
89
- readonly e: Schema.Number;
90
- readonly f: Schema.Number;
91
- }>>;
92
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
93
- }, "Type">, Entity.Position>;
94
- "~opacity": Entity.TraitLens<Schema.Struct.ReadonlySide<{
95
- x: Schema.withConstructorDefault<Schema.Number>;
96
- y: Schema.withConstructorDefault<Schema.Number>;
97
- z: Schema.withConstructorDefault<Schema.Number>;
98
- opacity: Schema.withConstructorDefault<Schema.Number>;
99
- transform: Schema.withConstructorDefault<Schema.Struct<{
100
- readonly a: Schema.Number;
101
- readonly b: Schema.Number;
102
- readonly c: Schema.Number;
103
- readonly d: Schema.Number;
104
- readonly e: Schema.Number;
105
- readonly f: Schema.Number;
106
- }>>;
107
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
108
- }, "Type">, number>;
109
- }>;
110
- /** The group's x/y position composed outside its normalized local transform. */
111
- export declare const resolvedTransform: (data: {
112
- readonly x: number;
113
- readonly y: number;
114
- readonly transform: TransformMatrix;
115
- }) => TransformMatrix;
116
- export declare const isIdentityTransform: (matrix: TransformMatrix) => boolean;
@@ -1,82 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import * as SchemaGetter from "effect/SchemaGetter";
4
- import * as Entity from "../Entity.js";
5
- import * as Shape2D from "./Shape2D.js";
6
- export const TransformMatrix = Schema.Struct({
7
- a: Schema.Number,
8
- b: Schema.Number,
9
- c: Schema.Number,
10
- d: Schema.Number,
11
- e: Schema.Number,
12
- f: Schema.Number,
13
- });
14
- export const identityTransform = {
15
- a: 1,
16
- b: 0,
17
- c: 0,
18
- d: 1,
19
- e: 0,
20
- f: 0,
21
- };
22
- export const multiplyTransforms = (left, right) => ({
23
- a: left.a * right.a + left.c * right.b,
24
- b: left.b * right.a + left.d * right.b,
25
- c: left.a * right.c + left.c * right.d,
26
- d: left.b * right.c + left.d * right.d,
27
- e: left.a * right.e + left.c * right.f + left.e,
28
- f: left.b * right.e + left.d * right.f + left.f,
29
- });
30
- export const TransformOperation = Schema.TaggedUnion({
31
- "transform/translate": { x: Schema.Number, y: Schema.Number },
32
- "transform/scale": { x: Schema.Number, y: Schema.Number },
33
- "transform/matrix": TransformMatrix.fields,
34
- });
35
- const TransformOperations = Schema.Array(TransformOperation);
36
- const operationMatrix = (operation) => TransformOperation.match(operation, {
37
- "transform/translate": ({ x, y }) => ({
38
- ...identityTransform,
39
- e: x,
40
- f: y,
41
- }),
42
- "transform/scale": ({ x, y }) => ({
43
- ...identityTransform,
44
- a: x,
45
- d: y,
46
- }),
47
- "transform/matrix": ({ a, b, c, d, e, f }) => ({ a, b, c, d, e, f }),
48
- });
49
- export const Transform = TransformOperations.pipe(Schema.decodeTo(TransformMatrix, {
50
- // Post-multiplication preserves the authored transform-list order.
51
- decode: SchemaGetter.transform((operations) => operations.reduce((matrix, operation) => multiplyTransforms(matrix, operationMatrix(operation)), identityTransform)),
52
- encode: SchemaGetter.transform((matrix) => [
53
- { _tag: "transform/matrix", ...matrix },
54
- ]),
55
- }));
56
- const fields = {
57
- ...Shape2D.position,
58
- ...Shape2D.opacity,
59
- transform: TransformMatrix.pipe(Schema.withConstructorDefault(Effect.succeed(identityTransform))),
60
- children: Schema.Array(Schema.String).pipe(Schema.withConstructorDefault(Effect.sync(() => []))),
61
- };
62
- const decodeTransform = Schema.decodeUnknownSync(Transform);
63
- const _normalizeInput = (input) => Array.isArray(input.transform)
64
- ? { ...input, transform: decodeTransform(input.transform) }
65
- : input;
66
- // A container: positions and structures its children, paints nothing.
67
- // Transform operations are normalized once to a target-independent affine
68
- // matrix; render targets never interpret the operation list.
69
- const traits = {
70
- // moving a group moves the subtree (children keep local coordinates)
71
- "~position": Shape2D.positionLens(),
72
- "~opacity": Shape2D.opacityLens(),
73
- };
74
- export const Group = Entity.make("shapes/Group", fields, traits);
75
- /** The group's x/y position composed outside its normalized local transform. */
76
- export const resolvedTransform = (data) => multiplyTransforms({ ...identityTransform, e: data.x, f: data.y }, data.transform);
77
- export const isIdentityTransform = (matrix) => matrix.a === 1 &&
78
- matrix.b === 0 &&
79
- matrix.c === 0 &&
80
- matrix.d === 1 &&
81
- matrix.e === 0 &&
82
- matrix.f === 0;
@@ -1,38 +0,0 @@
1
- import * as Schema from "effect/Schema";
2
- import * as Entity from "../Entity.js";
3
- /**
4
- * A screen-space container: its subtree is projected through the IDENTITY
5
- * camera instead of the active one, so HUD content ignores camera movement,
6
- * zoom, shake — and depth of field (the identity camera's aperture is 0).
7
- * HUD content always paints on top of world content.
8
- *
9
- * `x`/`y` compose into the children in screen coordinates (slide a whole
10
- * lower-third in with one tween). There is deliberately no `z`: the
11
- * container has no world depth. Children may still use their own `z` for
12
- * deliberate in-HUD depth; it defaults to 0 (flat, plain-2D placement).
13
- *
14
- * A Hud must be a top-level child of the root — nesting it inside world
15
- * content would compose world offsets into screen coordinates and is a loud
16
- * defect. A Hud inside a Hud behaves as a plain group.
17
- */
18
- declare const fields: {
19
- x: Schema.withConstructorDefault<Schema.Number>;
20
- y: Schema.withConstructorDefault<Schema.Number>;
21
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
22
- };
23
- type HudData = Schema.Struct<typeof fields>["Type"];
24
- export declare const Hud: Entity.Entity<"shapes/Hud", Schema.Struct<{
25
- x: Schema.withConstructorDefault<Schema.Number>;
26
- y: Schema.withConstructorDefault<Schema.Number>;
27
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
28
- }>, {
29
- readonly "~position": {
30
- get: (data: HudData) => {
31
- x: number;
32
- y: number;
33
- z: number;
34
- };
35
- set: (data: HudData, value: Entity.Position) => HudData;
36
- };
37
- }>;
38
- export {};
@@ -1,35 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import * as Entity from "../Entity.js";
4
- import * as Shape2D from "./Shape2D.js";
5
- /**
6
- * A screen-space container: its subtree is projected through the IDENTITY
7
- * camera instead of the active one, so HUD content ignores camera movement,
8
- * zoom, shake — and depth of field (the identity camera's aperture is 0).
9
- * HUD content always paints on top of world content.
10
- *
11
- * `x`/`y` compose into the children in screen coordinates (slide a whole
12
- * lower-third in with one tween). There is deliberately no `z`: the
13
- * container has no world depth. Children may still use their own `z` for
14
- * deliberate in-HUD depth; it defaults to 0 (flat, plain-2D placement).
15
- *
16
- * A Hud must be a top-level child of the root — nesting it inside world
17
- * content would compose world offsets into screen coordinates and is a loud
18
- * defect. A Hud inside a Hud behaves as a plain group.
19
- */
20
- const fields = {
21
- x: Shape2D.defaultedNumber(0),
22
- y: Shape2D.defaultedNumber(0),
23
- children: Schema.Array(Schema.String).pipe(Schema.withConstructorDefault(Effect.sync(() => []))),
24
- };
25
- export const Hud = Entity.make("shapes/Hud", fields, {
26
- // screen-space position; z is pinned 0 (no world depth on the container)
27
- "~position": {
28
- get: (data) => ({ x: data.x, y: data.y, z: 0 }),
29
- set: (data, value) => ({
30
- ...data,
31
- x: value.x,
32
- y: value.y,
33
- }),
34
- },
35
- });