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
package/dist/index.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- export * as Camera from "./Camera";
2
- export * as Entity from "./Entity";
3
- export * as Fonts from "./Fonts";
4
- export * as Instance from "./Instance";
5
- export * as Motion from "./Motion";
6
- export * as Phaser from "./Phaser";
7
- export * as Physics from "./Physics";
8
- export * as Particles from "./particles";
9
- export * as Renderer from "./Renderer";
10
- export * as Scene from "./Scene";
11
- export * as Shapes from "./shapes";
12
- export * as Svg from "./svg";
13
- export * as Timing from "./Timing";
1
+ export * as Camera from "./CameraHelpers.js";
2
+ export * as Color from "./Color.js";
3
+ export * as Entity from "./Entity.js";
4
+ export * as Fonts from "./Fonts.js";
5
+ export * as Images from "./Images.js";
6
+ export * as Instance from "./Instance.js";
7
+ export * as Motion from "./Motion.js";
8
+ export * as Phaser from "./Phaser.js";
9
+ export * as Physics from "./Physics.js";
10
+ export * as Particles from "./particles/index.js";
11
+ export * as Renderer from "./Renderer.js";
12
+ export * as Scene from "./Scene.js";
13
+ export * as Shapes from "./Shapes.js";
14
+ export * as Timing from "./Timing.js";
package/dist/index.js CHANGED
@@ -1,13 +1,14 @@
1
- export * as Camera from "./Camera";
2
- export * as Entity from "./Entity";
3
- export * as Fonts from "./Fonts";
4
- export * as Instance from "./Instance";
5
- export * as Motion from "./Motion";
6
- export * as Phaser from "./Phaser";
7
- export * as Physics from "./Physics";
8
- export * as Particles from "./particles";
9
- export * as Renderer from "./Renderer";
10
- export * as Scene from "./Scene";
11
- export * as Shapes from "./shapes";
12
- export * as Svg from "./svg";
13
- export * as Timing from "./Timing";
1
+ export * as Camera from "./CameraHelpers.js";
2
+ export * as Color from "./Color.js";
3
+ export * as Entity from "./Entity.js";
4
+ export * as Fonts from "./Fonts.js";
5
+ export * as Images from "./Images.js";
6
+ export * as Instance from "./Instance.js";
7
+ export * as Motion from "./Motion.js";
8
+ export * as Phaser from "./Phaser.js";
9
+ export * as Physics from "./Physics.js";
10
+ export * as Particles from "./particles/index.js";
11
+ export * as Renderer from "./Renderer.js";
12
+ export * as Scene from "./Scene.js";
13
+ export * as Shapes from "./Shapes.js";
14
+ export * as Timing from "./Timing.js";
@@ -1,4 +1,5 @@
1
- import type * as Prng from "./Prng";
1
+ import type * as Color from "../Color.js";
2
+ import type * as Prng from "./Prng.js";
2
3
  /**
3
4
  * A single particle's live state. Held in a flat buffer on the
4
5
  * ParticleField instance — never its own entity/instance/fiber. Position
@@ -25,7 +26,7 @@ export interface Particle {
25
26
  size: number;
26
27
  /** baseline opacity drawn at birth; the over-life curve multiplies it */
27
28
  opacity: number;
28
- color: string;
29
+ color: Color.Color;
29
30
  rng: Prng.PrngState;
30
31
  /** false = dead slot, reusable by the next emission */
31
32
  alive: boolean;
@@ -68,7 +69,7 @@ export interface EmitterConfig {
68
69
  /** shared downward acceleration (px/sec²); not ranged */
69
70
  readonly gravity: number;
70
71
  /** colors drawn from uniformly at birth */
71
- readonly palette: ReadonlyArray<string>;
72
+ readonly palette: ReadonlyArray<Color.Color>;
72
73
  /**
73
74
  * fill mode only — the rectangular region particles scatter across and
74
75
  * wrap around, in the field's local coordinates. The animator defaults
@@ -1,8 +1,10 @@
1
1
  import * as Schema from "effect/Schema";
2
- import * as Entity from "../Entity";
2
+ import * as Color from "../Color.js";
3
+ import * as Entity from "../Entity.js";
3
4
  export declare const ParticleField: Entity.Entity<"particles/ParticleField", Schema.Struct<{
4
5
  x: Schema.withConstructorDefault<Schema.Number>;
5
6
  y: Schema.withConstructorDefault<Schema.Number>;
7
+ z: Schema.withConstructorDefault<Schema.Number>;
6
8
  opacity: Schema.withConstructorDefault<Schema.Number>;
7
9
  speed: Schema.withConstructorDefault<Schema.Tuple<readonly [Schema.Number, Schema.Number]>>;
8
10
  angle: Schema.withConstructorDefault<Schema.Tuple<readonly [Schema.Number, Schema.Number]>>;
@@ -10,7 +12,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
10
12
  size: Schema.withConstructorDefault<Schema.Tuple<readonly [Schema.Number, Schema.Number]>>;
11
13
  opacityRange: Schema.withConstructorDefault<Schema.Tuple<readonly [Schema.Number, Schema.Number]>>;
12
14
  gravity: Schema.withConstructorDefault<Schema.Number>;
13
- palette: Schema.withConstructorDefault<Schema.$Array<Schema.String>>;
15
+ palette: Schema.withConstructorDefault<Schema.$Array<typeof Color.Color>>;
14
16
  region: Schema.optionalKey<Schema.Struct<{
15
17
  readonly w: Schema.Number;
16
18
  readonly h: Schema.Number;
@@ -36,7 +38,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
36
38
  readonly life: Schema.Number;
37
39
  readonly size: Schema.Number;
38
40
  readonly opacity: Schema.Number;
39
- readonly color: Schema.String;
41
+ readonly color: typeof Color.Color;
40
42
  readonly rng: Schema.Number;
41
43
  readonly alive: Schema.Boolean;
42
44
  readonly wrap: Schema.Boolean;
@@ -45,6 +47,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
45
47
  readonly "~position": Entity.TraitLens<{
46
48
  readonly x: number;
47
49
  readonly y: number;
50
+ readonly z: number;
48
51
  readonly opacity: number;
49
52
  readonly speed: readonly [number, number];
50
53
  readonly angle: readonly [number, number];
@@ -52,7 +55,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
52
55
  readonly size: readonly [number, number];
53
56
  readonly opacityRange: readonly [number, number];
54
57
  readonly gravity: number;
55
- readonly palette: readonly string[];
58
+ readonly palette: readonly Color.Color[];
56
59
  readonly region?: Schema.Struct.ReadonlySide<{
57
60
  readonly w: Schema.Number;
58
61
  readonly h: Schema.Number;
@@ -78,7 +81,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
78
81
  readonly life: Schema.Number;
79
82
  readonly size: Schema.Number;
80
83
  readonly opacity: Schema.Number;
81
- readonly color: Schema.String;
84
+ readonly color: typeof Color.Color;
82
85
  readonly rng: Schema.Number;
83
86
  readonly alive: Schema.Boolean;
84
87
  readonly wrap: Schema.Boolean;
@@ -87,6 +90,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
87
90
  readonly "~opacity": Entity.TraitLens<{
88
91
  readonly x: number;
89
92
  readonly y: number;
93
+ readonly z: number;
90
94
  readonly opacity: number;
91
95
  readonly speed: readonly [number, number];
92
96
  readonly angle: readonly [number, number];
@@ -94,7 +98,7 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
94
98
  readonly size: readonly [number, number];
95
99
  readonly opacityRange: readonly [number, number];
96
100
  readonly gravity: number;
97
- readonly palette: readonly string[];
101
+ readonly palette: readonly Color.Color[];
98
102
  readonly region?: Schema.Struct.ReadonlySide<{
99
103
  readonly w: Schema.Number;
100
104
  readonly h: Schema.Number;
@@ -120,51 +124,10 @@ export declare const ParticleField: Entity.Entity<"particles/ParticleField", Sch
120
124
  readonly life: Schema.Number;
121
125
  readonly size: Schema.Number;
122
126
  readonly opacity: Schema.Number;
123
- readonly color: Schema.String;
127
+ readonly color: typeof Color.Color;
124
128
  readonly rng: Schema.Number;
125
129
  readonly alive: Schema.Boolean;
126
130
  readonly wrap: Schema.Boolean;
127
131
  }, "Type">[];
128
132
  }, number>;
129
- }, {
130
- readonly x?: number;
131
- readonly y?: number;
132
- readonly opacity?: number;
133
- readonly speed?: readonly [number, number];
134
- readonly angle?: readonly [number, number];
135
- readonly life?: readonly [number, number];
136
- readonly size?: readonly [number, number];
137
- readonly opacityRange?: readonly [number, number];
138
- readonly gravity?: number;
139
- readonly palette?: readonly string[];
140
- readonly region?: Schema.Struct.ReadonlyMakeIn<{
141
- readonly w: Schema.Number;
142
- readonly h: Schema.Number;
143
- }>;
144
- readonly drift?: readonly [number, number];
145
- readonly sizeOverLife?: {
146
- readonly from: number;
147
- readonly to: number;
148
- readonly ease?: string;
149
- };
150
- readonly opacityOverLife?: {
151
- readonly from: number;
152
- readonly to: number;
153
- readonly ease?: string;
154
- };
155
- readonly capacity?: number;
156
- readonly buffer?: readonly Schema.Struct.ReadonlyMakeIn<{
157
- readonly x: Schema.Number;
158
- readonly y: Schema.Number;
159
- readonly vx: Schema.Number;
160
- readonly vy: Schema.Number;
161
- readonly age: Schema.Number;
162
- readonly life: Schema.Number;
163
- readonly size: Schema.Number;
164
- readonly opacity: Schema.Number;
165
- readonly color: Schema.String;
166
- readonly rng: Schema.Number;
167
- readonly alive: Schema.Boolean;
168
- readonly wrap: Schema.Boolean;
169
- }>[];
170
133
  }>;
@@ -1,7 +1,8 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
- import * as Entity from "../Entity";
4
- import * as Shape2D from "../shapes/Shape2D";
3
+ import * as Color from "../Color.js";
4
+ import * as Entity from "../Entity.js";
5
+ import * as Shape2D from "../shapes/Shape2D.js";
5
6
  /**
6
7
  * A ParticleField is ONE entity/instance backing many particles. Its data
7
8
  * holds the emitter config plus a fixed-capacity buffer of live particle
@@ -31,7 +32,7 @@ const ParticleSchema = Schema.Struct({
31
32
  life: Schema.Number,
32
33
  size: Schema.Number,
33
34
  opacity: Schema.Number,
34
- color: Schema.String,
35
+ color: Color.Color,
35
36
  rng: Schema.Number,
36
37
  alive: Schema.Boolean,
37
38
  wrap: Schema.Boolean,
@@ -53,7 +54,7 @@ export const ParticleField = Entity.make("particles/ParticleField", {
53
54
  // `opacity` (the ~opacity trait above, which fades the whole field).
54
55
  opacityRange: defaultedRange([1, 1]),
55
56
  gravity: Shape2D.defaultedNumber(0),
56
- palette: Schema.Array(Schema.String).pipe(Schema.withConstructorDefault(Effect.sync(() => ["white"]))),
57
+ palette: Schema.Array(Color.Color).pipe(Schema.withConstructorDefault(Effect.sync(() => [Color.white]))),
57
58
  // fill mode: the region particles spread across and wrap within
58
59
  // (defaults to the frame in `simulate`) and their drift speed range
59
60
  region: Schema.optionalKey(Schema.Struct({ w: Schema.Number, h: Schema.Number })),
@@ -1,8 +1,9 @@
1
1
  import type * as Effect from "effect/Effect";
2
- import type * as Instance from "../Instance";
3
- import type * as Runner from "../Runner";
4
- import type { Range } from "./Particle";
5
- import { ParticleField } from "./ParticleField";
2
+ import type * as Color from "../Color.js";
3
+ import type * as Instance from "../Instance.js";
4
+ import type * as Runner from "../Runner.js";
5
+ import type { Range } from "./Particle.js";
6
+ import { ParticleField } from "./ParticleField.js";
6
7
  /**
7
8
  * Two typed front doors onto the SAME `ParticleField` entity. The field's
8
9
  * emission model is chosen at `simulate`, but the properties that make sense
@@ -38,7 +39,7 @@ interface CommonInput {
38
39
  /** per-particle birth opacity range (0..1); over-life curve multiplies it */
39
40
  readonly opacityRange?: Range;
40
41
  /** colors drawn from uniformly at birth */
41
- readonly palette?: ReadonlyArray<string>;
42
+ readonly palette?: ReadonlyArray<Color.Color>;
42
43
  /** shared downward acceleration (px/sec²) */
43
44
  readonly gravity?: number;
44
45
  readonly sizeOverLife?: OverLifeInput;
@@ -69,10 +70,10 @@ export interface FieldInput extends CommonInput {
69
70
  * Create a source emitter field. Simulate it with `{ burst: n }` (all at
70
71
  * once) or `{ rate: n }` (a continuous stream).
71
72
  */
72
- export declare const emitter: (props: EmitterInput) => Effect.Effect<EmitterField, unknown, typeof ParticleField | Runner.Runner>;
73
+ export declare const emitter: (props: EmitterInput) => Effect.Effect<EmitterField, never, Runner.Runner>;
73
74
  /**
74
75
  * Create a floating field: particles spread evenly across the region,
75
76
  * drifting and wrapping at the edges. Simulate it with `{ fill: n }`.
76
77
  */
77
- export declare const field: (props: FieldInput) => Effect.Effect<FloatField, unknown, typeof ParticleField | Runner.Runner>;
78
+ export declare const field: (props: FieldInput) => Effect.Effect<FloatField, never, Runner.Runner>;
78
79
  export {};
@@ -1,5 +1,5 @@
1
- import * as Scene from "../Scene";
2
- import { ParticleField } from "./ParticleField";
1
+ import * as Scene from "../Scene.js";
2
+ import { ParticleField } from "./ParticleField.js";
3
3
  /**
4
4
  * Create a source emitter field. Simulate it with `{ burst: n }` (all at
5
5
  * once) or `{ rate: n }` (a continuous stream).
@@ -1,6 +1,5 @@
1
- export * from "./constructors";
2
- export * from "./Particle";
3
- export * from "./ParticleField";
4
- export * as Prng from "./Prng";
5
- export { particleField as render } from "./render";
6
- export * from "./simulate";
1
+ export * from "./constructors.js";
2
+ export * from "./Particle.js";
3
+ export * from "./ParticleField.js";
4
+ export * as Prng from "./Prng.js";
5
+ export * from "./simulate.js";
@@ -1,6 +1,5 @@
1
- export * from "./constructors";
2
- export * from "./Particle";
3
- export * from "./ParticleField";
4
- export * as Prng from "./Prng";
5
- export { particleField as render } from "./render";
6
- export * from "./simulate";
1
+ export * from "./constructors.js";
2
+ export * from "./Particle.js";
3
+ export * from "./ParticleField.js";
4
+ export * as Prng from "./Prng.js";
5
+ export * from "./simulate.js";
@@ -1,4 +1,4 @@
1
- import type { OverLife, Particle } from "./Particle";
1
+ import type { OverLife, Particle } from "./Particle.js";
2
2
  /**
3
3
  * Evaluate an over-life curve at a particle's current age. Purely a
4
4
  * function of `age/life` (clamped to [0,1]) eased by the configured timing
@@ -1,4 +1,4 @@
1
- import * as Timing from "../Timing";
1
+ import * as Timing from "../Timing.js";
2
2
  /**
3
3
  * Evaluate an over-life curve at a particle's current age. Purely a
4
4
  * function of `age/life` (clamped to [0,1]) eased by the configured timing
@@ -1,7 +1,7 @@
1
1
  import type * as Duration from "effect/Duration";
2
2
  import * as Effect from "effect/Effect";
3
- import * as Runner from "../Runner";
4
- import type { EmitterField, FloatField } from "./constructors";
3
+ import * as Runner from "../Runner.js";
4
+ import type { EmitterField, FloatField } from "./constructors.js";
5
5
  /**
6
6
  * Emission model for a single `simulate` call:
7
7
  * - `{ burst: n }` — birth `n` particles on the FIRST frame, then none
@@ -1,12 +1,12 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import { dual } from "effect/Function";
3
3
  import * as Random from "effect/Random";
4
- import * as Instance from "../Instance";
5
- import * as Runner from "../Runner";
6
- import * as Scene from "../Scene";
7
- import * as Time from "../Time";
8
- import * as Prng from "./Prng";
9
- import { step } from "./step";
4
+ import * as Instance from "../Instance.js";
5
+ import * as Runner from "../Runner.js";
6
+ import * as Scene from "../Scene.js";
7
+ import * as Time from "../Time.js";
8
+ import * as Prng from "./Prng.js";
9
+ import { step } from "./step.js";
10
10
  // how many particles to birth on frame `i` (1-based) of this call
11
11
  const birthsForFrame = (emission, i, fps) => {
12
12
  if ("burst" in emission) {
@@ -1,5 +1,5 @@
1
- import type { EmitterConfig, Particle } from "./Particle";
2
- import * as Prng from "./Prng";
1
+ import type { EmitterConfig, Particle } from "./Particle.js";
2
+ import * as Prng from "./Prng.js";
3
3
  /** birth one particle from a seed + config (fixed draw order). */
4
4
  export declare const birth: (seed: Prng.PrngState, config: EmitterConfig) => Particle;
5
5
  /**
@@ -1,4 +1,6 @@
1
- import * as Prng from "./Prng";
1
+ /** biome-ignore-all lint/style/useConst: let declaration is necessary for destructuring assignment */
2
+ import * as Color from "../Color.js";
3
+ import * as Prng from "./Prng.js";
2
4
  /**
3
5
  * The per-frame simulation as a PURE fold: `(buffer, dt, births) → buffer`.
4
6
  * No external entropy — the only randomness is each particle's own carried
@@ -41,7 +43,7 @@ export const birth = (seed, config) => {
41
43
  [rng, opacity] = drawOpacity(rng, config);
42
44
  // angle 0 = straight up; clockwise-positive
43
45
  const rad = angle * DEG;
44
- const color = config.palette[Math.min(config.palette.length - 1, Math.floor(colorPick))] ?? "white";
46
+ const color = config.palette[Math.min(config.palette.length - 1, Math.floor(colorPick))] ?? Color.white;
45
47
  return {
46
48
  x: config.x,
47
49
  y: config.y,
@@ -85,7 +87,7 @@ export const birthFill = (seed, config) => {
85
87
  let opacity;
86
88
  [rng, opacity] = drawOpacity(rng, config);
87
89
  const rad = dir * DEG;
88
- const color = config.palette[Math.min(config.palette.length - 1, Math.floor(colorPick))] ?? "white";
90
+ const color = config.palette[Math.min(config.palette.length - 1, Math.floor(colorPick))] ?? Color.white;
89
91
  return {
90
92
  // scattered across the region, offset by the field's own origin
91
93
  x: config.x + px,
@@ -0,0 +1,27 @@
1
+ import type * as Projection from "../Projection.js";
2
+ /**
3
+ * Depth-of-field circle of confusion (design D2 of camera-depth-of-field):
4
+ * how much gaussian blur a paintable at `depth` receives under `camera`.
5
+ * Pure arithmetic on frame data — deterministic by construction.
6
+ */
7
+ /** defensive ceiling: no author intends a 100px blur; huge sigmas are bugs */
8
+ export declare const MAX_SIGMA = 64;
9
+ /** quantization step (px): paintables within a step share one blur pass */
10
+ export declare const SIGMA_STEP = 0.5;
11
+ /** below this, blur is visually nil — treat as sharp (no blur pass at all) */
12
+ export declare const SHARP_THRESHOLD = 0.25;
13
+ /**
14
+ * Blur sigma for a paintable at view-space `depth`: zero at the focus plane,
15
+ * growing with distance from it, scaled by aperture and focal length (longer
16
+ * lens = shallower field). AE-flavored, not thin-lens-accurate — the contract
17
+ * is the shape of the curve, not the constants.
18
+ */
19
+ export declare const circleOfConfusion: (depth: number, camera: Projection.CameraView) => number;
20
+ /**
21
+ * Quantize a sigma so contiguous depth-sorted runs share blur passes: 0 for
22
+ * visually-sharp values, otherwise the nearest step (never above the ceiling).
23
+ * ponytail: 0.5px steps keep pass counts low and banding invisible; the
24
+ * upgrade path is per-paintable exact sigma (one blur pass each) if a smooth
25
+ * depth gradient ever shows stepping.
26
+ */
27
+ export declare const quantizeSigma: (sigma: number) => number;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Depth-of-field circle of confusion (design D2 of camera-depth-of-field):
3
+ * how much gaussian blur a paintable at `depth` receives under `camera`.
4
+ * Pure arithmetic on frame data — deterministic by construction.
5
+ */
6
+ /** defensive ceiling: no author intends a 100px blur; huge sigmas are bugs */
7
+ export const MAX_SIGMA = 64;
8
+ /** quantization step (px): paintables within a step share one blur pass */
9
+ export const SIGMA_STEP = 0.5;
10
+ /** below this, blur is visually nil — treat as sharp (no blur pass at all) */
11
+ export const SHARP_THRESHOLD = 0.25;
12
+ /**
13
+ * Blur sigma for a paintable at view-space `depth`: zero at the focus plane,
14
+ * growing with distance from it, scaled by aperture and focal length (longer
15
+ * lens = shallower field). AE-flavored, not thin-lens-accurate — the contract
16
+ * is the shape of the curve, not the constants.
17
+ */
18
+ export const circleOfConfusion = (depth, camera) => {
19
+ if (camera.aperture <= 0 || depth <= 0 || camera.focusDistance <= 0) {
20
+ return 0;
21
+ }
22
+ const sigma = (camera.aperture *
23
+ camera.focalLength *
24
+ Math.abs(depth - camera.focusDistance)) /
25
+ (depth * camera.focusDistance);
26
+ return Math.min(sigma, MAX_SIGMA);
27
+ };
28
+ /**
29
+ * Quantize a sigma so contiguous depth-sorted runs share blur passes: 0 for
30
+ * visually-sharp values, otherwise the nearest step (never above the ceiling).
31
+ * ponytail: 0.5px steps keep pass counts low and banding invisible; the
32
+ * upgrade path is per-paintable exact sigma (one blur pass each) if a smooth
33
+ * depth gradient ever shows stepping.
34
+ */
35
+ export const quantizeSigma = (sigma) => sigma < SHARP_THRESHOLD
36
+ ? 0
37
+ : Math.min(Math.round(sigma / SIGMA_STEP) * SIGMA_STEP, MAX_SIGMA);
@@ -0,0 +1,30 @@
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>;
@@ -0,0 +1,36 @@
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
+ });
@@ -0,0 +1,42 @@
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>;