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
package/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # effect-motion
2
2
 
3
- Deterministic, frame-exact motion graphics in code, composed with [Effect](https://effect.website).
3
+ Frame-exact motion graphics in code, composed with [Effect](https://effect.website).
4
4
 
5
- A scene is an Effect generator program: instantiate entities, then tween or spring their properties, composing motions sequentially or in parallel. Scenes are **deterministic** — seeded randomness and a frame-locked clock make every run byte-identical — and **finite**, which is what lets a scene be scrubbed and replayed like a video.
5
+ A scene is an Effect generator program: instantiate entities, then tween or spring their properties, composing motions sequentially or in parallel. Scenes are **frame-exact** — animation is counted in whole frames at the scene's frame rate, never read from a wall clock — and **finite**, which is what lets a scene be scrubbed and replayed like a video.
6
6
 
7
7
  ## Install
8
8
 
9
9
  `effect` is a peer dependency — install it alongside:
10
10
 
11
11
  ```bash
12
- pnpm add effect-motion effect
12
+ bun add effect-motion effect
13
13
  ```
14
14
 
15
15
  ## Write a scene
package/dist/Camera.d.ts CHANGED
@@ -1,14 +1,18 @@
1
- import * as Schema from "effect/Schema";
2
- import * as Entity from "./Entity.js";
3
- import * as Projection from "./Projection.js";
1
+ import { Effect } from "effect";
2
+ import * as Duration from "effect/Duration";
3
+ import * as Instance from "./Instance.js";
4
+ import * as Motion from "./Motion.js";
5
+ import type * as Projection from "./Projection.js";
6
+ import * as Runner from "./Runner.js";
7
+ import * as Timing from "./Timing.js";
4
8
  /**
5
9
  * The camera is view state, not a shape — it is never registered with a sink
6
10
  * and never drawn. It exists as an ordinary Instance so the existing
7
11
  * animators drive it for free: `camera.pipe(moveTo({ z: -800 }))`,
8
12
  * `tween("rotY", ...)`, `spring`, `Scene.fork`, etc.
9
13
  *
10
- * `~position` (now x/y/z) is the camera's world position; `rotX/rotY/rotZ`
11
- * are its Euler orientation; `focalLength` sets the field of view. At rest
14
+ * `position` is the camera's world position, `rotation` its Euler
15
+ * orientation, and `focalLength` sets the field of view. At rest
12
16
  * the camera sits a focal-length back on +z looking down world -z, so a
13
17
  * world point at z=0 projects to plain-2D screen coordinates — see
14
18
  * `Projection.ts`. The sink reads these off `FrameMeta.camera` and projects
@@ -22,50 +26,183 @@ import * as Projection from "./Projection.js";
22
26
  * instance, so by the time animators or the renderer read the data they are
23
27
  * always concrete.
24
28
  */
25
- declare const fields: {
26
- x: Schema.withConstructorDefault<Schema.Number>;
27
- y: Schema.withConstructorDefault<Schema.Number>;
28
- z: Schema.withConstructorDefault<Schema.Number>;
29
- rotX: Schema.withConstructorDefault<Schema.Number>;
30
- rotY: Schema.withConstructorDefault<Schema.Number>;
31
- rotZ: Schema.withConstructorDefault<Schema.Number>;
32
- focalLength: Schema.withConstructorDefault<Schema.Number>;
33
- focusDistance: Schema.withConstructorDefault<Schema.Number>;
34
- aperture: Schema.withConstructorDefault<Schema.Number>;
35
- poiX: Schema.optionalKey<Schema.Number>;
36
- poiY: Schema.optionalKey<Schema.Number>;
37
- poiZ: Schema.optionalKey<Schema.Number>;
38
- };
39
- type CameraData = Schema.Struct<typeof fields>["Type"];
40
- export declare const Camera: Entity.Entity<"Camera", Schema.Struct<{
41
- x: Schema.withConstructorDefault<Schema.Number>;
42
- y: Schema.withConstructorDefault<Schema.Number>;
43
- z: Schema.withConstructorDefault<Schema.Number>;
44
- rotX: Schema.withConstructorDefault<Schema.Number>;
45
- rotY: Schema.withConstructorDefault<Schema.Number>;
46
- rotZ: Schema.withConstructorDefault<Schema.Number>;
47
- focalLength: Schema.withConstructorDefault<Schema.Number>;
48
- focusDistance: Schema.withConstructorDefault<Schema.Number>;
49
- aperture: Schema.withConstructorDefault<Schema.Number>;
50
- poiX: Schema.optionalKey<Schema.Number>;
51
- poiY: Schema.optionalKey<Schema.Number>;
52
- poiZ: Schema.optionalKey<Schema.Number>;
53
- }>, {
54
- readonly "~position": {
55
- get: (data: CameraData) => {
56
- x: number;
57
- y: number;
58
- z: number;
59
- };
60
- set: (data: CameraData, value: Entity.Position) => CameraData;
61
- };
62
- }>;
29
+ /** the camera view as a frame carries it */
30
+ export type CameraState = Projection.CameraView & Projection.PointOfInterest;
63
31
  /**
64
- * The identity view for a comp of the given width: resting camera, no
65
- * rotation, the width-relative default focal length (AE's 50mm equivalent).
66
- * Projects z=0 content to its plain-2D screen position at scale 1.
32
+ * What the camera helpers accept as something to aim at.
33
+ *
34
+ * @remarks
35
+ * An instance is read LIVE each frame, so aiming tracks it as it moves — the
36
+ * usual case. A plain position is fixed, for aiming at a spot where no
37
+ * entity exists.
67
38
  */
68
- export declare const identity: (width: number) => CameraState;
69
- /** The camera view carried on each frame (POI rides along when set). */
70
- export type CameraState = Projection.CameraView & Projection.PointOfInterest;
39
+ export type CameraTarget = Instance.Instance | Effect.Effect<Instance.Instance, never, Runner.Runner> | Partial<Motion.Position>;
40
+ type CamOrEffect<E = never, R = Runner.Runner> = Instance.InstanceOrEffect<"Camera", E, R>;
41
+ type CamInstance = Instance.Instance<"Camera">;
42
+ type CamEffect = Effect.Effect<CamInstance, never, Runner.Runner>;
43
+ /**
44
+ * Point the camera at something.
45
+ *
46
+ * @remarks
47
+ * Aiming is expressed as a point of interest rather than as rotation
48
+ * angles, which is what makes it composable: the target can be a live
49
+ * instance, and the camera keeps facing it as it moves. There is no
50
+ * `lookAtTo` — the verb already names its target — so an optional
51
+ * `duration` selects between the two behaviors:
52
+ *
53
+ * - **Omitted** — aim snaps this frame. Use it to establish the shot before
54
+ * anything moves.
55
+ * - **Given** — the aim eases over that time as a RETARGETED tween: each
56
+ * frame interpolates toward the target's current position, so it converges
57
+ * exactly onto a moving target with no snap at the end.
58
+ *
59
+ * Setting a point of interest is also the prerequisite for
60
+ * {@link orbitTo} and {@link dollyTo}, which are both defined relative to it.
61
+ *
62
+ * @param target - An instance to track, or a fixed world position.
63
+ * @param duration - Omit to snap; give a time to ease the re-aim.
64
+ * @param timing - An easing name or function.
65
+ * @param offset - Shifts the aim relative to the target, e.g. slightly above.
66
+ * @defaultValue `timing` — `"linear"`
67
+ * @returns The camera, so animators chain.
68
+ *
69
+ * @example
70
+ * Snap to establish, then ease across to a second subject.
71
+ * ```typescript
72
+ * const camera = yield* Scene.camera;
73
+ * yield* camera.pipe(Camera.lookAt(hero));
74
+ * yield* camera.pipe(Camera.lookAt(villain, "1 second", "easeInOutCubic"));
75
+ * ```
76
+ */
77
+ export declare const lookAt: ((target: CameraTarget, duration?: Duration.Input, timing?: Timing.TimingInput, offset?: Partial<Motion.Position>) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, target: CameraTarget, duration?: Duration.Input, timing?: Timing.TimingInput, offset?: Partial<Motion.Position>) => CamEffect);
78
+ /**
79
+ * Keep the camera locked onto a moving target for `duration`.
80
+ *
81
+ * @remarks
82
+ * Where {@link lookAt} with a duration EASES toward a target and stops,
83
+ * `follow` holds the aim on it: every frame copies the target's position, so
84
+ * the subject stays pinned while it moves. Run it concurrently with the
85
+ * subject's own animation — typically inside `Scene.all` or a `Scene.fork`.
86
+ *
87
+ * There is no timing parameter, because tracking is a hard per-frame copy
88
+ * rather than an interpolation. For a lagging, weighted camera, animate the
89
+ * point of interest with a spring instead.
90
+ *
91
+ * Ordering note: within a frame, branches run in the order they were forked.
92
+ * A follow forked BEFORE its target's animator reads the previous frame's
93
+ * position — a deterministic one-frame trail, not a bug.
94
+ *
95
+ * @param target - An instance to track, or a fixed world position.
96
+ * @param duration - How long to keep tracking.
97
+ * @param offset - Shifts the aim relative to the target.
98
+ * @returns The camera, so animators chain.
99
+ *
100
+ * @example
101
+ * Track the runner for the two seconds it is crossing frame.
102
+ * ```typescript
103
+ * yield* Scene.all([
104
+ * runner.pipe(Motion.moveTo({ x: 900 }, "2 seconds")),
105
+ * camera.pipe(Camera.follow(runner, "2 seconds")),
106
+ * ]);
107
+ * ```
108
+ */
109
+ export declare const follow: ((target: CameraTarget, duration: Duration.Input, offset?: Partial<Motion.Position>) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, target: CameraTarget, duration: Duration.Input, offset?: Partial<Motion.Position>) => CamEffect);
110
+ /**
111
+ * Swing the camera around its point of interest, like a turntable.
112
+ *
113
+ * @remarks
114
+ * The camera travels an arc at a fixed radius and height while continuing to
115
+ * face the subject — the standard "orbit the product" move. Only the
116
+ * position is animated; the aim follows from the point of interest, so there
117
+ * is no orientation math to get wrong.
118
+ *
119
+ * `azimuth` is an ABSOLUTE angle in radians about the world-Y axis through
120
+ * the point of interest, where 0 is directly in front (+z of it). A full
121
+ * turn is `Math.PI * 2`; the sign chooses direction.
122
+ *
123
+ * Requires a point of interest — call {@link lookAt} first, or this fails
124
+ * loudly telling you so.
125
+ *
126
+ * @param azimuth - Target angle in radians; 0 is directly in front.
127
+ * @param duration - How long, in scene time.
128
+ * @param timing - An easing name or function.
129
+ * @defaultValue `timing` — `"linear"`
130
+ * @returns The camera, so animators chain.
131
+ * @see {@link orbit} to start from an explicit angle.
132
+ *
133
+ * @example
134
+ * A quarter turn around a subject.
135
+ * ```typescript
136
+ * yield* camera.pipe(
137
+ * Camera.lookAt(subject),
138
+ * Camera.orbitTo(Math.PI / 2, "2 seconds", "easeInOutCubic"),
139
+ * );
140
+ * ```
141
+ */
142
+ export declare const orbitTo: ((azimuth: number, duration: Duration.Input, timing?: Timing.TimingInput) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, azimuth: number, duration: Duration.Input, timing?: Timing.TimingInput) => CamEffect);
143
+ /**
144
+ * Like {@link orbitTo}, but starting from an explicit azimuth.
145
+ *
146
+ * @remarks
147
+ * Stating both ends is what makes a full revolution expressible: `orbit(0,
148
+ * Math.PI * 2, …)` sweeps all the way around, whereas `orbitTo(Math.PI * 2)`
149
+ * from a resting camera would already be at its target and not move.
150
+ *
151
+ * @param from - Starting angle in radians.
152
+ * @param to - Target angle in radians.
153
+ * @param duration - How long, in scene time.
154
+ * @param timing - An easing name or function.
155
+ * @returns The camera, so animators chain.
156
+ *
157
+ * @example
158
+ * One full revolution.
159
+ * ```typescript
160
+ * yield* camera.pipe(Camera.orbit(0, Math.PI * 2, "4 seconds"));
161
+ * ```
162
+ */
163
+ export declare const orbit: ((from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => CamEffect);
164
+ /**
165
+ * Move the camera toward or away from its point of interest along the view
166
+ * axis.
167
+ *
168
+ * @remarks
169
+ * A push-in or pull-back that keeps the subject centered and the aim
170
+ * unchanged — only the distance changes. Distinct from tweening the camera's
171
+ * `z`, which moves along the WORLD axis and would slide the subject off
172
+ * center once the camera is aimed obliquely.
173
+ *
174
+ * Requires a point of interest — call {@link lookAt} first.
175
+ *
176
+ * @param distance - Target distance from the point of interest; smaller is
177
+ * closer.
178
+ * @param duration - How long, in scene time.
179
+ * @param timing - An easing name or function.
180
+ * @defaultValue `timing` — `"linear"`
181
+ * @returns The camera, so animators chain.
182
+ * @see {@link dolly} to start from an explicit distance.
183
+ *
184
+ * @example
185
+ * Push in on a subject.
186
+ * ```typescript
187
+ * yield* camera.pipe(
188
+ * Camera.lookAt(subject),
189
+ * Camera.dollyTo(300, "1500 millis", "easeInOutCubic"),
190
+ * );
191
+ * ```
192
+ */
193
+ export declare const dollyTo: ((distance: number, duration: Duration.Input, timing?: Timing.TimingInput) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, distance: number, duration: Duration.Input, timing?: Timing.TimingInput) => CamEffect);
194
+ /**
195
+ * Like {@link dollyTo}, but starting from an explicit distance.
196
+ *
197
+ * @remarks
198
+ * For a push-in that begins further out than the camera currently sits —
199
+ * the move starts at `from` regardless of where the camera was left.
200
+ *
201
+ * @param from - Starting distance from the point of interest.
202
+ * @param to - Target distance.
203
+ * @param duration - How long, in scene time.
204
+ * @param timing - An easing name or function.
205
+ * @returns The camera, so animators chain.
206
+ */
207
+ export declare const dolly: ((from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, from: number, to: number, duration: Duration.Input, timing?: Timing.TimingInput) => CamEffect);
71
208
  export {};