effect-motion 0.4.0 → 0.6.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 +3 -3
  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 +684 -30
  9. package/dist/Entity.js +281 -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 +36 -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 +714 -349
  25. package/dist/Runner.js +197 -150
  26. package/dist/Scene.d.ts +603 -175
  27. package/dist/Scene.js +596 -112
  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 +44 -42
  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 +11 -3
  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 +58 -60
  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 -508
  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 -71
  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 -126
  73. package/dist/shapes/Group.js +0 -89
  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,30 +0,0 @@
1
- import type { OwnedPaint, ThorvgException } from "@effect-motion/thorvg";
2
- import * as Tvg from "@effect-motion/thorvg";
3
- import * as Effect from "effect/Effect";
4
- import type * as Scope from "effect/Scope";
5
- import * as Color from "../Color.js";
6
- import type { PaintProjection } from "../Renderer.js";
7
- /**
8
- * Shared paint helpers: style application, projection, and the attach step
9
- * every leaf paint function runs. Keeping the ThorVG call sequence in one
10
- * place means each shape's paint function is just "make geometry, then
11
- * `finishPaint`".
12
- */
13
- /** The style fields a filled/stroked shape carries in its data. */
14
- export interface StyleData {
15
- readonly fill?: Color.Color;
16
- readonly stroke?: Color.Color;
17
- readonly strokeWidth?: number;
18
- readonly opacity: number;
19
- }
20
- /** Apply fill/stroke/opacity from shape data onto a ThorVG shape paint. */
21
- export declare const applyStyle: (shape: OwnedPaint, data: StyleData) => Effect.Effect<void, ThorvgException, Tvg.ThorvgWasm>;
22
- /**
23
- * Apply the billboard projection affine to a shape, then add it to the scene.
24
- * The identity affine (resting camera, z=0) is skipped so plain-2D scenes
25
- * issue the minimal draw calls. Ownership transfers to the scene on add.
26
- *
27
- * Callers must NOT have applied any translate/rotate/scale to the shape —
28
- * `setTransform` is the single, final transform (design D3).
29
- */
30
- export declare const finishPaint: (shape: OwnedPaint, scene: OwnedPaint, projection: PaintProjection) => Effect.Effect<void, ThorvgException, Tvg.ThorvgWasm | Scope.Scope>;
@@ -1,36 +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
- /** Apply fill/stroke/opacity from shape data onto a ThorVG shape paint. */
5
- export const applyStyle = (shape, data) => Effect.gen(function* () {
6
- if (data.fill !== undefined) {
7
- const { r, g, b, a } = Color.bytes(data.fill);
8
- yield* Tvg.Shape.setFillColor(shape, r, g, b, a);
9
- }
10
- if (data.stroke !== undefined) {
11
- const { r, g, b, a } = Color.bytes(data.stroke);
12
- yield* Tvg.Shape.setStrokeColor(shape, r, g, b, a);
13
- }
14
- if (data.strokeWidth !== undefined) {
15
- yield* Tvg.Shape.setStrokeWidth(shape, data.strokeWidth);
16
- }
17
- // opacity 1 is the ThorVG default; only set when it changes something
18
- if (data.opacity !== 1) {
19
- yield* Tvg.Paint.setOpacity(shape, Math.round(data.opacity * 255));
20
- }
21
- });
22
- const isIdentity = (m) => m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0;
23
- /**
24
- * Apply the billboard projection affine to a shape, then add it to the scene.
25
- * The identity affine (resting camera, z=0) is skipped so plain-2D scenes
26
- * issue the minimal draw calls. Ownership transfers to the scene on add.
27
- *
28
- * Callers must NOT have applied any translate/rotate/scale to the shape —
29
- * `setTransform` is the single, final transform (design D3).
30
- */
31
- export const finishPaint = (shape, scene, projection) => Effect.gen(function* () {
32
- if (!isIdentity(projection.screen)) {
33
- yield* Tvg.Paint.setTransform(shape, projection.screen);
34
- }
35
- yield* Tvg.Scene.add(scene, shape);
36
- });
@@ -1,42 +0,0 @@
1
- import { ParticleField } from "../particles/ParticleField.js";
2
- import type { PaintFunction, PaintFunctions } from "../Renderer.js";
3
- import * as Circle from "../shapes/Circle.js";
4
- import * as Ellipse from "../shapes/Ellipse.js";
5
- import * as Group from "../shapes/Group.js";
6
- import * as Hud from "../shapes/Hud.js";
7
- import * as Image from "../shapes/Image.js";
8
- import * as Line from "../shapes/Line.js";
9
- import * as Path from "../shapes/Path.js";
10
- import * as Rect from "../shapes/Rect.js";
11
- import * as Square from "../shapes/Square.js";
12
- import * as Text from "../shapes/Text.js";
13
- /**
14
- * ThorVG paint functions for the built-in shapes — this file is the coverage
15
- * manifest: `paints` is typed `PaintFunctions<...>`, so a built-in missing a
16
- * paint function is a type error, not a runtime surprise.
17
- */
18
- export declare const circle: PaintFunction<typeof Circle.Circle>;
19
- export declare const rect: PaintFunction<typeof Rect.Rect>;
20
- export declare const square: PaintFunction<typeof Square.Square>;
21
- export declare const ellipse: PaintFunction<typeof Ellipse.Ellipse>;
22
- export declare const line: PaintFunction<typeof Line.Line>;
23
- export declare const path: PaintFunction<typeof Path.Path>;
24
- export declare const group: PaintFunction<typeof Group.Group>;
25
- export declare const hud: PaintFunction<typeof Hud.Hud>;
26
- export declare const image: PaintFunction<typeof Image.Image>;
27
- export declare const text: PaintFunction<typeof Text.Text>;
28
- /**
29
- * ParticleField: live particles batched into one shape PER (color, opacity)
30
- * bucket — ThorVG fills a shape uniformly, so particles that share a color and
31
- * (quantized) over-life opacity are appended into the same shape and filled
32
- * once. Sized/faded by the over-life curves; dead slots emit nothing.
33
- * ponytail: one shape per distinct (color, opacity-bucket) — far fewer than one
34
- * per particle, but a fully instanced path is the upgrade if this is a wall.
35
- */
36
- export declare const particleField: PaintFunction<typeof ParticleField>;
37
- /**
38
- * The exhaustive paint-function map for every built-in entity. Typed
39
- * `PaintFunctions<...>` so a missing built-in fails to type-check (the old
40
- * "coverage manifest" guarantee, without a Context registry).
41
- */
42
- export declare const builtinPaints: PaintFunctions<typeof Circle.Circle | typeof Rect.Rect | typeof Square.Square | typeof Ellipse.Ellipse | typeof Line.Line | typeof Path.Path | typeof Group.Group | typeof Hud.Hud | typeof Text.Text | typeof Image.Image | typeof ParticleField>;
@@ -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,126 +0,0 @@
1
- import * as Schema from "effect/Schema";
2
- import * as Color from "../Color.js";
3
- import * as Entity from "../Entity.js";
4
- export declare const TransformMatrix: Schema.Struct<{
5
- readonly a: Schema.Number;
6
- readonly b: Schema.Number;
7
- readonly c: Schema.Number;
8
- readonly d: Schema.Number;
9
- readonly e: Schema.Number;
10
- readonly f: Schema.Number;
11
- }>;
12
- export type TransformMatrix = typeof TransformMatrix.Type;
13
- export declare const identityTransform: {
14
- a: number;
15
- b: number;
16
- c: number;
17
- d: number;
18
- e: number;
19
- f: number;
20
- };
21
- export declare const multiplyTransforms: (left: TransformMatrix, right: TransformMatrix) => TransformMatrix;
22
- export declare const TransformOperation: Schema.TaggedUnion<{
23
- readonly "transform/matrix": Schema.TaggedStruct<"transform/matrix", {
24
- readonly a: Schema.Number;
25
- readonly b: Schema.Number;
26
- readonly c: Schema.Number;
27
- readonly d: Schema.Number;
28
- readonly e: Schema.Number;
29
- readonly f: Schema.Number;
30
- }>;
31
- readonly "transform/scale": Schema.TaggedStruct<"transform/scale", {
32
- readonly x: Schema.Number;
33
- readonly y: Schema.Number;
34
- }>;
35
- readonly "transform/translate": Schema.TaggedStruct<"transform/translate", {
36
- readonly x: Schema.Number;
37
- readonly y: Schema.Number;
38
- }>;
39
- }>;
40
- export declare const Transform: Schema.decodeTo<Schema.Struct<{
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
- }>, Schema.$Array<Schema.TaggedUnion<{
48
- readonly "transform/matrix": Schema.TaggedStruct<"transform/matrix", {
49
- readonly a: Schema.Number;
50
- readonly b: Schema.Number;
51
- readonly c: Schema.Number;
52
- readonly d: Schema.Number;
53
- readonly e: Schema.Number;
54
- readonly f: Schema.Number;
55
- }>;
56
- readonly "transform/scale": Schema.TaggedStruct<"transform/scale", {
57
- readonly x: Schema.Number;
58
- readonly y: Schema.Number;
59
- }>;
60
- readonly "transform/translate": Schema.TaggedStruct<"transform/translate", {
61
- readonly x: Schema.Number;
62
- readonly y: Schema.Number;
63
- }>;
64
- }>>, never, never>;
65
- export declare const Group: Entity.Entity<"shapes/Group", Schema.Struct<{
66
- x: Schema.withConstructorDefault<Schema.Number>;
67
- y: Schema.withConstructorDefault<Schema.Number>;
68
- z: Schema.withConstructorDefault<Schema.Number>;
69
- opacity: Schema.withConstructorDefault<Schema.Number>;
70
- transform: Schema.withConstructorDefault<Schema.Struct<{
71
- readonly a: Schema.Number;
72
- readonly b: Schema.Number;
73
- readonly c: Schema.Number;
74
- readonly d: Schema.Number;
75
- readonly e: Schema.Number;
76
- readonly f: Schema.Number;
77
- }>>;
78
- width: Schema.optionalKey<Schema.Number>;
79
- height: Schema.optionalKey<Schema.Number>;
80
- backgroundColor: Schema.optionalKey<typeof Color.Color>;
81
- children: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
82
- }>, {
83
- "~position": Entity.TraitLens<{
84
- readonly x: number;
85
- readonly y: number;
86
- readonly z: number;
87
- readonly opacity: number;
88
- readonly transform: Schema.Struct.ReadonlySide<{
89
- readonly a: Schema.Number;
90
- readonly b: Schema.Number;
91
- readonly c: Schema.Number;
92
- readonly d: Schema.Number;
93
- readonly e: Schema.Number;
94
- readonly f: Schema.Number;
95
- }, "Type">;
96
- readonly width?: number;
97
- readonly height?: number;
98
- readonly backgroundColor?: Color.Color;
99
- readonly children: readonly string[];
100
- }, Entity.Position>;
101
- "~opacity": Entity.TraitLens<{
102
- readonly x: number;
103
- readonly y: number;
104
- readonly z: number;
105
- readonly opacity: number;
106
- readonly transform: Schema.Struct.ReadonlySide<{
107
- readonly a: Schema.Number;
108
- readonly b: Schema.Number;
109
- readonly c: Schema.Number;
110
- readonly d: Schema.Number;
111
- readonly e: Schema.Number;
112
- readonly f: Schema.Number;
113
- }, "Type">;
114
- readonly width?: number;
115
- readonly height?: number;
116
- readonly backgroundColor?: Color.Color;
117
- readonly children: readonly string[];
118
- }, number>;
119
- }>;
120
- /** The group's x/y position composed outside its normalized local transform. */
121
- export declare const resolvedTransform: (data: {
122
- readonly x: number;
123
- readonly y: number;
124
- readonly transform: TransformMatrix;
125
- }) => TransformMatrix;
126
- export declare const isIdentityTransform: (matrix: TransformMatrix) => boolean;