effect-motion 0.2.0 → 0.3.1

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 (103) hide show
  1. package/dist/Camera.d.ts +50 -28
  2. package/dist/Camera.js +71 -19
  3. package/dist/CameraHelpers.d.ts +70 -0
  4. package/dist/CameraHelpers.js +239 -0
  5. package/dist/CanvasExporter.d.ts +12 -0
  6. package/dist/CanvasExporter.js +40 -0
  7. package/dist/Color.d.ts +428 -0
  8. package/dist/Color.js +535 -0
  9. package/dist/EffectMotionError.d.ts +10 -0
  10. package/dist/EffectMotionError.js +6 -0
  11. package/dist/Entity.d.ts +9 -8
  12. package/dist/Entity.js +1 -4
  13. package/dist/Fonts.d.ts +10 -0
  14. package/dist/Fonts.js +16 -0
  15. package/dist/Images.d.ts +33 -0
  16. package/dist/Images.js +24 -0
  17. package/dist/Instance.d.ts +4 -4
  18. package/dist/Motion.d.ts +24 -7
  19. package/dist/Motion.js +54 -9
  20. package/dist/Phaser.d.ts +1 -1
  21. package/dist/Phaser.js +13 -9
  22. package/dist/Physics.d.ts +3 -3
  23. package/dist/Physics.js +4 -4
  24. package/dist/PngExporter.d.ts +6 -0
  25. package/dist/PngExporter.js +85 -0
  26. package/dist/Projection.d.ts +225 -0
  27. package/dist/Projection.js +473 -0
  28. package/dist/Renderer.d.ts +102 -53
  29. package/dist/Renderer.js +369 -78
  30. package/dist/Runner.d.ts +126 -42
  31. package/dist/Runner.js +42 -17
  32. package/dist/Scene.d.ts +88 -59
  33. package/dist/Scene.js +24 -15
  34. package/dist/Shapes.d.ts +11 -0
  35. package/dist/Shapes.js +11 -0
  36. package/dist/demo.d.ts +3 -186
  37. package/dist/demo.js +24 -35
  38. package/dist/index.d.ts +14 -13
  39. package/dist/index.js +14 -13
  40. package/dist/particles/Particle.d.ts +4 -3
  41. package/dist/particles/ParticleField.d.ts +11 -48
  42. package/dist/particles/ParticleField.js +5 -4
  43. package/dist/particles/constructors.d.ts +8 -7
  44. package/dist/particles/constructors.js +2 -2
  45. package/dist/particles/index.d.ts +5 -6
  46. package/dist/particles/index.js +5 -6
  47. package/dist/particles/overLife.d.ts +1 -1
  48. package/dist/particles/overLife.js +1 -1
  49. package/dist/particles/simulate.d.ts +2 -2
  50. package/dist/particles/simulate.js +6 -6
  51. package/dist/particles/step.d.ts +2 -2
  52. package/dist/particles/step.js +5 -3
  53. package/dist/render/dof.d.ts +27 -0
  54. package/dist/render/dof.js +37 -0
  55. package/dist/render/paint.d.ts +30 -0
  56. package/dist/render/paint.js +36 -0
  57. package/dist/render/shapes.d.ts +42 -0
  58. package/dist/render/shapes.js +310 -0
  59. package/dist/shapes/Circle.d.ts +10 -15
  60. package/dist/shapes/Circle.js +2 -2
  61. package/dist/shapes/Ellipse.d.ts +10 -16
  62. package/dist/shapes/Ellipse.js +2 -2
  63. package/dist/shapes/Group.d.ts +5 -39
  64. package/dist/shapes/Group.js +4 -4
  65. package/dist/shapes/Hud.d.ts +38 -0
  66. package/dist/shapes/Hud.js +35 -0
  67. package/dist/shapes/Image.d.ts +45 -0
  68. package/dist/shapes/Image.js +28 -0
  69. package/dist/shapes/Line.d.ts +12 -12
  70. package/dist/shapes/Line.js +11 -6
  71. package/dist/shapes/Path.d.ts +87 -18
  72. package/dist/shapes/Path.js +25 -6
  73. package/dist/shapes/Rect.d.ts +33 -23
  74. package/dist/shapes/Rect.js +14 -2
  75. package/dist/shapes/Shape2D.d.ts +13 -4
  76. package/dist/shapes/Shape2D.js +16 -5
  77. package/dist/shapes/Shapes.d.ts +11 -0
  78. package/dist/shapes/Shapes.js +11 -0
  79. package/dist/shapes/Square.d.ts +10 -15
  80. package/dist/shapes/Square.js +2 -2
  81. package/dist/shapes/Text.d.ts +10 -19
  82. package/dist/shapes/Text.js +2 -2
  83. package/package.json +13 -3
  84. package/dist/particles/render.d.ts +0 -13
  85. package/dist/particles/render.js +0 -33
  86. package/dist/shapes/Layer.d.ts +0 -9
  87. package/dist/shapes/Layer.js +0 -23
  88. package/dist/shapes/index.d.ts +0 -10
  89. package/dist/shapes/index.js +0 -10
  90. package/dist/svg/SvgDomRenderer.d.ts +0 -28
  91. package/dist/svg/SvgDomRenderer.js +0 -50
  92. package/dist/svg/SvgNode.d.ts +0 -13
  93. package/dist/svg/SvgNode.js +0 -18
  94. package/dist/svg/SvgRenderer.d.ts +0 -22
  95. package/dist/svg/SvgRenderer.js +0 -20
  96. package/dist/svg/camera.d.ts +0 -21
  97. package/dist/svg/camera.js +0 -43
  98. package/dist/svg/index.d.ts +0 -6
  99. package/dist/svg/index.js +0 -6
  100. package/dist/svg/layers.d.ts +0 -18
  101. package/dist/svg/layers.js +0 -13
  102. package/dist/svg/shapes.d.ts +0 -1089
  103. package/dist/svg/shapes.js +0 -119
@@ -0,0 +1,310 @@
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,37 +1,32 @@
1
- import * as Entity from "../Entity";
1
+ import * as Entity from "../Entity.js";
2
2
  export declare const Circle: Entity.Entity<"shapes/Circle", import("effect/Schema").Struct<{
3
3
  x: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
4
4
  y: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
5
+ z: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
5
6
  opacity: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
6
- fill: import("effect/Schema").withConstructorDefault<import("effect/Schema").String>;
7
- stroke: import("effect/Schema").optionalKey<import("effect/Schema").String>;
7
+ fill: import("effect/Schema").withConstructorDefault<typeof import("../Color.js").Color>;
8
+ stroke: import("effect/Schema").optionalKey<typeof import("../Color.js").Color>;
8
9
  strokeWidth: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
9
10
  radius: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
10
11
  }>, {
11
12
  readonly "~position": Entity.TraitLens<{
12
13
  readonly x: number;
13
14
  readonly y: number;
15
+ readonly z: number;
14
16
  readonly opacity: number;
15
- readonly fill: string;
16
- readonly stroke?: string;
17
+ readonly fill: import("../Color.js").Color;
18
+ readonly stroke?: import("../Color.js").Color;
17
19
  readonly strokeWidth?: number;
18
20
  readonly radius: number;
19
21
  }, Entity.Position>;
20
22
  readonly "~opacity": Entity.TraitLens<{
21
23
  readonly x: number;
22
24
  readonly y: number;
25
+ readonly z: number;
23
26
  readonly opacity: number;
24
- readonly fill: string;
25
- readonly stroke?: string;
27
+ readonly fill: import("../Color.js").Color;
28
+ readonly stroke?: import("../Color.js").Color;
26
29
  readonly strokeWidth?: number;
27
30
  readonly radius: number;
28
31
  }, number>;
29
- }, {
30
- readonly x?: number;
31
- readonly y?: number;
32
- readonly opacity?: number;
33
- readonly fill?: string;
34
- readonly stroke?: string;
35
- readonly strokeWidth?: number;
36
- readonly radius?: number;
37
32
  }>;
@@ -1,5 +1,5 @@
1
- import * as Entity from "../Entity";
2
- import * as Shape2D from "./Shape2D";
1
+ import * as Entity from "../Entity.js";
2
+ import * as Shape2D from "./Shape2D.js";
3
3
  export const Circle = Entity.make("shapes/Circle", {
4
4
  ...Shape2D.filled,
5
5
  radius: Shape2D.defaultedNumber(10),
@@ -1,10 +1,11 @@
1
- import * as Entity from "../Entity";
1
+ import * as Entity from "../Entity.js";
2
2
  export declare const Ellipse: Entity.Entity<"shapes/Ellipse", import("effect/Schema").Struct<{
3
3
  x: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
4
4
  y: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
5
+ z: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
5
6
  opacity: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
6
- fill: import("effect/Schema").withConstructorDefault<import("effect/Schema").String>;
7
- stroke: import("effect/Schema").optionalKey<import("effect/Schema").String>;
7
+ fill: import("effect/Schema").withConstructorDefault<typeof import("../Color.js").Color>;
8
+ stroke: import("effect/Schema").optionalKey<typeof import("../Color.js").Color>;
8
9
  strokeWidth: import("effect/Schema").optionalKey<import("effect/Schema").Number>;
9
10
  rx: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
10
11
  ry: import("effect/Schema").withConstructorDefault<import("effect/Schema").Number>;
@@ -12,9 +13,10 @@ export declare const Ellipse: Entity.Entity<"shapes/Ellipse", import("effect/Sch
12
13
  readonly "~position": Entity.TraitLens<{
13
14
  readonly x: number;
14
15
  readonly y: number;
16
+ readonly z: number;
15
17
  readonly opacity: number;
16
- readonly fill: string;
17
- readonly stroke?: string;
18
+ readonly fill: import("../Color.js").Color;
19
+ readonly stroke?: import("../Color.js").Color;
18
20
  readonly strokeWidth?: number;
19
21
  readonly rx: number;
20
22
  readonly ry: number;
@@ -22,20 +24,12 @@ export declare const Ellipse: Entity.Entity<"shapes/Ellipse", import("effect/Sch
22
24
  readonly "~opacity": Entity.TraitLens<{
23
25
  readonly x: number;
24
26
  readonly y: number;
27
+ readonly z: number;
25
28
  readonly opacity: number;
26
- readonly fill: string;
27
- readonly stroke?: string;
29
+ readonly fill: import("../Color.js").Color;
30
+ readonly stroke?: import("../Color.js").Color;
28
31
  readonly strokeWidth?: number;
29
32
  readonly rx: number;
30
33
  readonly ry: number;
31
34
  }, number>;
32
- }, {
33
- readonly x?: number;
34
- readonly y?: number;
35
- readonly opacity?: number;
36
- readonly fill?: string;
37
- readonly stroke?: string;
38
- readonly strokeWidth?: number;
39
- readonly rx?: number;
40
- readonly ry?: number;
41
35
  }>;
@@ -1,5 +1,5 @@
1
- import * as Entity from "../Entity";
2
- import * as Shape2D from "./Shape2D";
1
+ import * as Entity from "../Entity.js";
2
+ import * as Shape2D from "./Shape2D.js";
3
3
  export const Ellipse = Entity.make("shapes/Ellipse", {
4
4
  ...Shape2D.filled,
5
5
  rx: Shape2D.defaultedNumber(20),
@@ -1,5 +1,5 @@
1
1
  import * as Schema from "effect/Schema";
2
- import * as Entity from "../Entity";
2
+ import * as Entity from "../Entity.js";
3
3
  export declare const TransformMatrix: Schema.Struct<{
4
4
  readonly a: Schema.Number;
5
5
  readonly b: Schema.Number;
@@ -36,24 +36,6 @@ export declare const TransformOperation: Schema.TaggedUnion<{
36
36
  readonly y: Schema.Number;
37
37
  }>;
38
38
  }>;
39
- declare const TransformOperations: Schema.$Array<Schema.TaggedUnion<{
40
- readonly "transform/matrix": Schema.TaggedStruct<"transform/matrix", {
41
- readonly a: Schema.Number;
42
- readonly b: Schema.Number;
43
- readonly c: Schema.Number;
44
- readonly d: Schema.Number;
45
- readonly e: Schema.Number;
46
- readonly f: Schema.Number;
47
- }>;
48
- readonly "transform/scale": Schema.TaggedStruct<"transform/scale", {
49
- readonly x: Schema.Number;
50
- readonly y: Schema.Number;
51
- }>;
52
- readonly "transform/translate": Schema.TaggedStruct<"transform/translate", {
53
- readonly x: Schema.Number;
54
- readonly y: Schema.Number;
55
- }>;
56
- }>>;
57
39
  export declare const Transform: Schema.decodeTo<Schema.Struct<{
58
40
  readonly a: Schema.Number;
59
41
  readonly b: Schema.Number;
@@ -79,27 +61,10 @@ export declare const Transform: Schema.decodeTo<Schema.Struct<{
79
61
  readonly y: Schema.Number;
80
62
  }>;
81
63
  }>>, never, never>;
82
- declare const fields: {
83
- x: Schema.withConstructorDefault<Schema.Number>;
84
- y: Schema.withConstructorDefault<Schema.Number>;
85
- opacity: Schema.withConstructorDefault<Schema.Number>;
86
- transform: Schema.withConstructorDefault<Schema.Struct<{
87
- readonly a: Schema.Number;
88
- readonly b: Schema.Number;
89
- readonly c: Schema.Number;
90
- readonly d: Schema.Number;
91
- readonly e: Schema.Number;
92
- readonly f: Schema.Number;
93
- }>>;
94
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
95
- };
96
- type StoredInput = Schema.Struct<typeof fields>["~type.make.in"];
97
- type GroupInput = Omit<StoredInput, "transform"> & {
98
- readonly transform?: typeof TransformOperations.Type;
99
- };
100
64
  export declare const Group: Entity.Entity<"shapes/Group", Schema.Struct<{
101
65
  x: Schema.withConstructorDefault<Schema.Number>;
102
66
  y: Schema.withConstructorDefault<Schema.Number>;
67
+ z: Schema.withConstructorDefault<Schema.Number>;
103
68
  opacity: Schema.withConstructorDefault<Schema.Number>;
104
69
  transform: Schema.withConstructorDefault<Schema.Struct<{
105
70
  readonly a: Schema.Number;
@@ -114,6 +79,7 @@ export declare const Group: Entity.Entity<"shapes/Group", Schema.Struct<{
114
79
  "~position": Entity.TraitLens<Schema.Struct.ReadonlySide<{
115
80
  x: Schema.withConstructorDefault<Schema.Number>;
116
81
  y: Schema.withConstructorDefault<Schema.Number>;
82
+ z: Schema.withConstructorDefault<Schema.Number>;
117
83
  opacity: Schema.withConstructorDefault<Schema.Number>;
118
84
  transform: Schema.withConstructorDefault<Schema.Struct<{
119
85
  readonly a: Schema.Number;
@@ -128,6 +94,7 @@ export declare const Group: Entity.Entity<"shapes/Group", Schema.Struct<{
128
94
  "~opacity": Entity.TraitLens<Schema.Struct.ReadonlySide<{
129
95
  x: Schema.withConstructorDefault<Schema.Number>;
130
96
  y: Schema.withConstructorDefault<Schema.Number>;
97
+ z: Schema.withConstructorDefault<Schema.Number>;
131
98
  opacity: Schema.withConstructorDefault<Schema.Number>;
132
99
  transform: Schema.withConstructorDefault<Schema.Struct<{
133
100
  readonly a: Schema.Number;
@@ -139,7 +106,7 @@ export declare const Group: Entity.Entity<"shapes/Group", Schema.Struct<{
139
106
  }>>;
140
107
  children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
141
108
  }, "Type">, number>;
142
- }, GroupInput>;
109
+ }>;
143
110
  /** The group's x/y position composed outside its normalized local transform. */
144
111
  export declare const resolvedTransform: (data: {
145
112
  readonly x: number;
@@ -147,4 +114,3 @@ export declare const resolvedTransform: (data: {
147
114
  readonly transform: TransformMatrix;
148
115
  }) => TransformMatrix;
149
116
  export declare const isIdentityTransform: (matrix: TransformMatrix) => boolean;
150
- export {};
@@ -1,8 +1,8 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
3
  import * as SchemaGetter from "effect/SchemaGetter";
4
- import * as Entity from "../Entity";
5
- import * as Shape2D from "./Shape2D";
4
+ import * as Entity from "../Entity.js";
5
+ import * as Shape2D from "./Shape2D.js";
6
6
  export const TransformMatrix = Schema.Struct({
7
7
  a: Schema.Number,
8
8
  b: Schema.Number,
@@ -60,7 +60,7 @@ const fields = {
60
60
  children: Schema.Array(Schema.String).pipe(Schema.withConstructorDefault(Effect.sync(() => []))),
61
61
  };
62
62
  const decodeTransform = Schema.decodeUnknownSync(Transform);
63
- const normalizeInput = (input) => Array.isArray(input.transform)
63
+ const _normalizeInput = (input) => Array.isArray(input.transform)
64
64
  ? { ...input, transform: decodeTransform(input.transform) }
65
65
  : input;
66
66
  // A container: positions and structures its children, paints nothing.
@@ -71,7 +71,7 @@ const traits = {
71
71
  "~position": Shape2D.positionLens(),
72
72
  "~opacity": Shape2D.opacityLens(),
73
73
  };
74
- export const Group = Entity.make("shapes/Group", fields, traits, { normalize: normalizeInput });
74
+ export const Group = Entity.make("shapes/Group", fields, traits);
75
75
  /** The group's x/y position composed outside its normalized local transform. */
76
76
  export const resolvedTransform = (data) => multiplyTransforms({ ...identityTransform, e: data.x, f: data.y }, data.transform);
77
77
  export const isIdentityTransform = (matrix) => matrix.a === 1 &&
@@ -0,0 +1,38 @@
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 {};
@@ -0,0 +1,35 @@
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
+ });
@@ -0,0 +1,45 @@
1
+ import * as Schema from "effect/Schema";
2
+ import * as Entity from "../Entity.js";
3
+ /**
4
+ * A raster/vector image leaf. `image` names an asset declared via the
5
+ * `Images` annotation; the render session decodes it once and every frame
6
+ * reuses the decoded picture. An undeclared or failed asset paints nothing
7
+ * (soft skip — the rest of the frame renders).
8
+ *
9
+ * `width`/`height` are optional and undefaulted: set BOTH to draw at that
10
+ * size (numeric, so they tween); leave both absent to draw at the source's
11
+ * natural size. A lone dimension is ignored (natural size used) — aspect
12
+ * math would need the natural size, which frame data never sees.
13
+ *
14
+ * Billboard only: no orientation fields. ponytail: tilting an image needs a
15
+ * projective setTransform (full 3×3, we hardcode the bottom row) mapping the
16
+ * projected quad; add when a scene needs a tilted image.
17
+ */
18
+ export declare const Image: Entity.Entity<"shapes/Image", Schema.Struct<{
19
+ x: Schema.withConstructorDefault<Schema.Number>;
20
+ y: Schema.withConstructorDefault<Schema.Number>;
21
+ z: Schema.withConstructorDefault<Schema.Number>;
22
+ opacity: Schema.withConstructorDefault<Schema.Number>;
23
+ image: Schema.String;
24
+ width: Schema.optionalKey<Schema.Number>;
25
+ height: Schema.optionalKey<Schema.Number>;
26
+ }>, {
27
+ readonly "~position": Entity.TraitLens<{
28
+ readonly x: number;
29
+ readonly y: number;
30
+ readonly z: number;
31
+ readonly opacity: number;
32
+ readonly image: string;
33
+ readonly width?: number;
34
+ readonly height?: number;
35
+ }, Entity.Position>;
36
+ readonly "~opacity": Entity.TraitLens<{
37
+ readonly x: number;
38
+ readonly y: number;
39
+ readonly z: number;
40
+ readonly opacity: number;
41
+ readonly image: string;
42
+ readonly width?: number;
43
+ readonly height?: number;
44
+ }, number>;
45
+ }>;