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.
- package/README.md +3 -3
- package/dist/Camera.d.ts +186 -49
- package/dist/Camera.js +343 -76
- package/dist/Color.d.ts +101 -1
- package/dist/Color.js +101 -1
- package/dist/EffectMotionError.d.ts +17 -0
- package/dist/EffectMotionError.js +17 -0
- package/dist/Entity.d.ts +684 -30
- package/dist/Entity.js +281 -27
- package/dist/Font.d.ts +108 -0
- package/dist/Font.js +95 -0
- package/dist/Image.d.ts +71 -0
- package/dist/Image.js +50 -0
- package/dist/Instance.d.ts +73 -11
- package/dist/Instance.js +44 -11
- package/dist/Motion.d.ts +328 -53
- package/dist/Motion.js +278 -46
- package/dist/Physics.d.ts +154 -20
- package/dist/Physics.js +92 -11
- package/dist/Projection.d.ts +36 -132
- package/dist/Projection.js +33 -292
- package/dist/Resource.d.ts +26 -0
- package/dist/Resource.js +41 -0
- package/dist/Runner.d.ts +714 -349
- package/dist/Runner.js +197 -150
- package/dist/Scene.d.ts +603 -175
- package/dist/Scene.js +596 -112
- package/dist/Timing.d.ts +170 -13
- package/dist/Timing.js +124 -6
- package/dist/Tree.d.ts +39 -0
- package/dist/Tree.js +127 -0
- package/dist/index.d.ts +54 -5
- package/dist/index.js +56 -5
- package/dist/particles/Particle.d.ts +2 -2
- package/dist/particles/ParticleField.d.ts +44 -42
- package/dist/particles/ParticleField.js +8 -9
- package/dist/particles/constructors.d.ts +5 -6
- package/dist/particles/constructors.js +8 -3
- package/dist/particles/legacy.d.ts +58 -0
- package/dist/particles/legacy.js +46 -0
- package/dist/particles/simulate.js +11 -3
- package/dist/particles/step.js +14 -10
- package/dist/types.d.ts +5 -0
- package/dist/types.js +1 -0
- package/package.json +58 -60
- package/dist/CameraHelpers.d.ts +0 -70
- package/dist/CameraHelpers.js +0 -239
- package/dist/CanvasExporter.d.ts +0 -12
- package/dist/CanvasExporter.js +0 -40
- package/dist/Fonts.d.ts +0 -41
- package/dist/Fonts.js +0 -27
- package/dist/Images.d.ts +0 -33
- package/dist/Images.js +0 -24
- package/dist/PngExporter.d.ts +0 -6
- package/dist/PngExporter.js +0 -85
- package/dist/Renderer.d.ts +0 -118
- package/dist/Renderer.js +0 -508
- package/dist/Shapes.d.ts +0 -11
- package/dist/Shapes.js +0 -11
- package/dist/demo.d.ts +0 -3
- package/dist/demo.js +0 -71
- package/dist/render/dof.d.ts +0 -27
- package/dist/render/dof.js +0 -37
- package/dist/render/paint.d.ts +0 -30
- package/dist/render/paint.js +0 -36
- package/dist/render/shapes.d.ts +0 -42
- package/dist/render/shapes.js +0 -310
- package/dist/shapes/Circle.d.ts +0 -32
- package/dist/shapes/Circle.js +0 -9
- package/dist/shapes/Ellipse.d.ts +0 -35
- package/dist/shapes/Ellipse.js +0 -10
- package/dist/shapes/Group.d.ts +0 -126
- package/dist/shapes/Group.js +0 -89
- package/dist/shapes/Hud.d.ts +0 -38
- package/dist/shapes/Hud.js +0 -35
- package/dist/shapes/Image.d.ts +0 -45
- package/dist/shapes/Image.js +0 -28
- package/dist/shapes/Line.d.ts +0 -47
- package/dist/shapes/Line.js +0 -35
- package/dist/shapes/Path.d.ts +0 -107
- package/dist/shapes/Path.js +0 -32
- package/dist/shapes/Rect.d.ts +0 -51
- package/dist/shapes/Rect.js +0 -22
- package/dist/shapes/Shape2D.d.ts +0 -49
- package/dist/shapes/Shape2D.js +0 -52
- package/dist/shapes/Shapes.d.ts +0 -11
- package/dist/shapes/Shapes.js +0 -11
- package/dist/shapes/Square.d.ts +0 -32
- package/dist/shapes/Square.js +0 -11
- package/dist/shapes/Text.d.ts +0 -51
- package/dist/shapes/Text.js +0 -24
package/package.json
CHANGED
|
@@ -1,61 +1,59 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
2
|
+
"name": "effect-motion",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Deterministic, frame-exact motion graphics in code, composed with Effect",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/julia-script/effect-motion.git",
|
|
10
|
+
"directory": "packages/motion"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/julia-script/effect-motion#readme",
|
|
13
|
+
"bugs": "https://github.com/julia-script/effect-motion/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"effect",
|
|
16
|
+
"motion",
|
|
17
|
+
"animation",
|
|
18
|
+
"motion-graphics",
|
|
19
|
+
"svg",
|
|
20
|
+
"deterministic"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./*": {
|
|
28
|
+
"types": "./dist/*.d.ts",
|
|
29
|
+
"default": "./dist/*.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.build.json",
|
|
37
|
+
"dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"check": "tsc --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"effect": ">=4.0.0-rc.115"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/chroma-js": "^3.1.2",
|
|
49
|
+
"@types/node": "^26.1.1",
|
|
50
|
+
"effect": "4.0.0-rc.115",
|
|
51
|
+
"happy-dom": "^20.10.6",
|
|
52
|
+
"tsx": "^4.23.0",
|
|
53
|
+
"typescript": "^7.0.2",
|
|
54
|
+
"vitest": "^4.1.10"
|
|
55
|
+
},
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"chroma-js": "^3.2.0"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/CameraHelpers.d.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import * as Duration from "effect/Duration";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import type { Camera } from "./Camera.js";
|
|
4
|
-
import * as Entity from "./Entity.js";
|
|
5
|
-
import * as Instance from "./Instance.js";
|
|
6
|
-
import * as Runner from "./Runner.js";
|
|
7
|
-
import * as Timing from "./Timing.js";
|
|
8
|
-
export type { CameraState } from "./Camera.js";
|
|
9
|
-
/**
|
|
10
|
-
* The public Camera surface: the entity/identity from Camera.js plus the
|
|
11
|
-
* directing helpers. This module (not Camera.ts) is what index exports as
|
|
12
|
-
* the `Camera` namespace — Runner/Renderer import the schema file
|
|
13
|
-
* directly, so helpers can depend on Motion/Scene without an import
|
|
14
|
-
* cycle.
|
|
15
|
-
*
|
|
16
|
-
* Naming rule (recorded in the camera-poi-helpers change): verbs that
|
|
17
|
-
* name their target (`lookAt`, `follow`) have no base/To pair — an
|
|
18
|
-
* optional duration selects instant vs eased. Value-animating helpers
|
|
19
|
-
* (`orbit`/`orbitTo`, `dolly`/`dollyTo`) keep the pair, exactly like
|
|
20
|
-
* `move`/`moveTo`.
|
|
21
|
-
*/
|
|
22
|
-
export { Camera, identity } from "./Camera.js";
|
|
23
|
-
type AnyInstance = Instance.Instance<any, any, any>;
|
|
24
|
-
/**
|
|
25
|
-
* A helper target: an Instance (position read live each frame), an Effect
|
|
26
|
-
* resolving to one (resolved once at helper start, then read live), or a
|
|
27
|
-
* plain position (inherently fixed — the no-entity escape hatch).
|
|
28
|
-
*/
|
|
29
|
-
export type CameraTarget = AnyInstance | Effect.Effect<AnyInstance, never, Runner.Runner> | Partial<Entity.Position>;
|
|
30
|
-
type CamData = (typeof Camera)["data"];
|
|
31
|
-
type CamTraits = (typeof Camera)["traits"];
|
|
32
|
-
type CamOrEffect<E = never, R = Runner.Runner> = Instance.InstanceOrEffect<"Camera", CamData, CamTraits, E, R>;
|
|
33
|
-
type CamInstance = Instance.Instance<"Camera", CamData, CamTraits>;
|
|
34
|
-
type CamEffect = Effect.Effect<CamInstance, never, Runner.Runner>;
|
|
35
|
-
/**
|
|
36
|
-
* Aim the camera at a target. No duration: set the point of interest this
|
|
37
|
-
* frame. With a duration: eased re-aim as a retargeted tween (lands
|
|
38
|
-
* exactly on a moving target, no terminal snap). `offset` shifts the aim
|
|
39
|
-
* relative to the target ("slightly above their head"). Dual:
|
|
40
|
-
* `lookAt(cam, target, ...)` or `cam.pipe(lookAt(target, ...))`.
|
|
41
|
-
*/
|
|
42
|
-
export declare const lookAt: ((target: CameraTarget, duration?: Duration.Input, timing?: Timing.TimingInput, offset?: Partial<Entity.Position>) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, target: CameraTarget, duration?: Duration.Input, timing?: Timing.TimingInput, offset?: Partial<Entity.Position>) => CamEffect);
|
|
43
|
-
/**
|
|
44
|
-
* Track a target for the duration: the point of interest is a hard
|
|
45
|
-
* per-frame copy of the target position (+offset). A plain animator — it
|
|
46
|
-
* pipes, `Scene.all`s, staggers, repeats. No timing input (lag is
|
|
47
|
-
* expressed by springing the POI instead). Ordering practice: within a
|
|
48
|
-
* tick, branches run in fork order — a follow forked before its target's
|
|
49
|
-
* animator reads the previous frame's position, a deterministic one-frame
|
|
50
|
-
* trail. Dual like `lookAt`.
|
|
51
|
-
*/
|
|
52
|
-
export declare const follow: ((target: CameraTarget, duration: Duration.Input, offset?: Partial<Entity.Position>) => (cam: CamOrEffect) => CamEffect) & ((cam: CamOrEffect, target: CameraTarget, duration: Duration.Input, offset?: Partial<Entity.Position>) => CamEffect);
|
|
53
|
-
/**
|
|
54
|
-
* Turntable the camera to an absolute azimuth around its point of
|
|
55
|
-
* interest (angle about the world-Y axis through the POI; 0 = directly +z
|
|
56
|
-
* of it). Position travels the arc — orientation comes entirely from the
|
|
57
|
-
* POI, so there is no orientation math to get wrong. Radius and height
|
|
58
|
-
* are preserved. Dies loudly without a POI. Dual like `moveTo`.
|
|
59
|
-
*/
|
|
60
|
-
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);
|
|
61
|
-
/** Like `orbitTo`, but from an explicit start azimuth. */
|
|
62
|
-
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);
|
|
63
|
-
/**
|
|
64
|
-
* Move the camera along its view axis to an absolute distance from the
|
|
65
|
-
* point of interest (in: toward it, out: away), aim unchanged. Dies
|
|
66
|
-
* loudly without a POI. Dual like `moveTo`.
|
|
67
|
-
*/
|
|
68
|
-
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);
|
|
69
|
-
/** Like `dollyTo`, but from an explicit start distance. */
|
|
70
|
-
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);
|
package/dist/CameraHelpers.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import * as Duration from "effect/Duration";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import { dual } from "effect/Function";
|
|
4
|
-
import * as Entity from "./Entity.js";
|
|
5
|
-
import * as Instance from "./Instance.js";
|
|
6
|
-
import * as Motion from "./Motion.js";
|
|
7
|
-
import * as Runner from "./Runner.js";
|
|
8
|
-
import * as Scene from "./Scene.js";
|
|
9
|
-
import * as Time from "./Time.js";
|
|
10
|
-
import * as Timing from "./Timing.js";
|
|
11
|
-
/**
|
|
12
|
-
* The public Camera surface: the entity/identity from Camera.js plus the
|
|
13
|
-
* directing helpers. This module (not Camera.ts) is what index exports as
|
|
14
|
-
* the `Camera` namespace — Runner/Renderer import the schema file
|
|
15
|
-
* directly, so helpers can depend on Motion/Scene without an import
|
|
16
|
-
* cycle.
|
|
17
|
-
*
|
|
18
|
-
* Naming rule (recorded in the camera-poi-helpers change): verbs that
|
|
19
|
-
* name their target (`lookAt`, `follow`) have no base/To pair — an
|
|
20
|
-
* optional duration selects instant vs eased. Value-animating helpers
|
|
21
|
-
* (`orbit`/`orbitTo`, `dolly`/`dollyTo`) keep the pair, exactly like
|
|
22
|
-
* `move`/`moveTo`.
|
|
23
|
-
*/
|
|
24
|
-
// explicit named re-exports (not `export *`) so the public Camera surface
|
|
25
|
-
// is enumerable at a glance: the entity + identity from the schema module,
|
|
26
|
-
// plus the helpers below
|
|
27
|
-
export { Camera, identity } from "./Camera.js";
|
|
28
|
-
// a target argument (vs a duration/timing in the same slot): instances,
|
|
29
|
-
// effects, or a position-like object — Durations are objects too, so
|
|
30
|
-
// exclude them explicitly
|
|
31
|
-
const isTargetArg = (v) => Instance.isInstance(v) ||
|
|
32
|
-
Effect.isEffect(v) ||
|
|
33
|
-
(typeof v === "object" &&
|
|
34
|
-
v !== null &&
|
|
35
|
-
!Duration.isDuration(v) &&
|
|
36
|
-
("x" in v || "y" in v || "z" in v));
|
|
37
|
-
// data-first iff the first arg is an instance AND the second is a target —
|
|
38
|
-
// plain firstArgIsInstance would misread `cam.pipe`-less pipeable calls
|
|
39
|
-
// whose TARGET is an instance (`lookAt(hero, "1 second")`)
|
|
40
|
-
const dataFirst = (args) => Instance.isInstance(args[0]) && isTargetArg(args[1]);
|
|
41
|
-
/**
|
|
42
|
-
* Resolve a target once (Effects yield their Instance), returning a
|
|
43
|
-
* per-frame position reader with the offset folded in: live for
|
|
44
|
-
* instances, fixed for plain positions.
|
|
45
|
-
*/
|
|
46
|
-
const targetReader = Effect.fnUntraced(function* (target, offset) {
|
|
47
|
-
const ox = offset?.x ?? 0;
|
|
48
|
-
const oy = offset?.y ?? 0;
|
|
49
|
-
const oz = offset?.z ?? 0;
|
|
50
|
-
if (Instance.isInstance(target) || Effect.isEffect(target)) {
|
|
51
|
-
const instance = yield* Instance.flatten(target);
|
|
52
|
-
const lens = Entity.traitOrDie(instance.entity, "~position");
|
|
53
|
-
return Scene.data(instance).pipe(Effect.map((data) => {
|
|
54
|
-
const p = lens.get(data);
|
|
55
|
-
return { x: p.x + ox, y: p.y + oy, z: p.z + oz };
|
|
56
|
-
}));
|
|
57
|
-
}
|
|
58
|
-
// AnyInstance's `any` params defeat narrowing — the guards above
|
|
59
|
-
// returned for instances/effects, so this is a plain position
|
|
60
|
-
const point = target;
|
|
61
|
-
const fixed = {
|
|
62
|
-
x: (point.x ?? 0) + ox,
|
|
63
|
-
y: (point.y ?? 0) + oy,
|
|
64
|
-
z: (point.z ?? 0) + oz,
|
|
65
|
-
};
|
|
66
|
-
return Effect.succeed(fixed);
|
|
67
|
-
});
|
|
68
|
-
const setPoi = (data, p) => Object.assign({}, data, { poiX: p.x, poiY: p.y, poiZ: p.z });
|
|
69
|
-
// the camera's WORLD position: x/y are pan-from-viewport-center
|
|
70
|
-
const worldPosition = Effect.fnUntraced(function* (cam) {
|
|
71
|
-
const { comp } = yield* Runner.Runner;
|
|
72
|
-
const data = (yield* Scene.data(cam));
|
|
73
|
-
return {
|
|
74
|
-
x: comp.width / 2 + data.x,
|
|
75
|
-
y: comp.height / 2 + data.y,
|
|
76
|
-
z: data.z ?? 0,
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
const lookAtImpl = Effect.fnUntraced(function* (camOrEffect, target, duration, timing, offset) {
|
|
80
|
-
const cam = yield* Instance.flatten(camOrEffect);
|
|
81
|
-
const read = yield* targetReader(target, offset);
|
|
82
|
-
if (duration === undefined) {
|
|
83
|
-
const p = yield* read;
|
|
84
|
-
yield* Scene.update(cam, (d) => setPoi(d, p));
|
|
85
|
-
return cam;
|
|
86
|
-
}
|
|
87
|
-
// eased re-aim: a RETARGETED tween — each frame interpolates from the
|
|
88
|
-
// start POI toward the target's CURRENT position, converging exactly
|
|
89
|
-
// onto a moving target at t = 1 (a plain-Position target degenerates to
|
|
90
|
-
// a fixed tween). Effectful per-frame read, so this runs its own loop
|
|
91
|
-
// rather than Motion.drive (whose callback is pure).
|
|
92
|
-
const runner = yield* Runner.Runner;
|
|
93
|
-
const timingFn = Timing.resolve(timing ?? "linear");
|
|
94
|
-
const data = (yield* Scene.data(cam));
|
|
95
|
-
let start;
|
|
96
|
-
if (data.poiX !== undefined &&
|
|
97
|
-
data.poiY !== undefined &&
|
|
98
|
-
data.poiZ !== undefined) {
|
|
99
|
-
start = { x: data.poiX, y: data.poiY, z: data.poiZ };
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
// no POI yet: seed on the camera's UNAIMED axis (straight down world
|
|
103
|
-
// -z) at the target's distance. resolveCamera derives zero aim for
|
|
104
|
-
// that point, so the explicit Euler alone carries the view — engaging
|
|
105
|
-
// POI mode is snap-free for ANY current orientation, and the tween
|
|
106
|
-
// takes over from there.
|
|
107
|
-
const world = yield* worldPosition(cam);
|
|
108
|
-
const first = yield* read;
|
|
109
|
-
const distance = Math.hypot(first.x - world.x, first.y - world.y, first.z - world.z);
|
|
110
|
-
start = { x: world.x, y: world.y, z: world.z - distance };
|
|
111
|
-
}
|
|
112
|
-
const frames = Math.max(1, Time.toFrames(duration, runner.settings.frameRate));
|
|
113
|
-
for (let i = 1; i <= frames; i++) {
|
|
114
|
-
const t = timingFn(i / frames);
|
|
115
|
-
const p = yield* read;
|
|
116
|
-
yield* Scene.update(cam, (d) => setPoi(d, {
|
|
117
|
-
x: start.x + (p.x - start.x) * t,
|
|
118
|
-
y: start.y + (p.y - start.y) * t,
|
|
119
|
-
z: start.z + (p.z - start.z) * t,
|
|
120
|
-
}));
|
|
121
|
-
yield* Scene.tick;
|
|
122
|
-
}
|
|
123
|
-
return cam;
|
|
124
|
-
});
|
|
125
|
-
/**
|
|
126
|
-
* Aim the camera at a target. No duration: set the point of interest this
|
|
127
|
-
* frame. With a duration: eased re-aim as a retargeted tween (lands
|
|
128
|
-
* exactly on a moving target, no terminal snap). `offset` shifts the aim
|
|
129
|
-
* relative to the target ("slightly above their head"). Dual:
|
|
130
|
-
* `lookAt(cam, target, ...)` or `cam.pipe(lookAt(target, ...))`.
|
|
131
|
-
*/
|
|
132
|
-
export const lookAt = dual(dataFirst, lookAtImpl);
|
|
133
|
-
/**
|
|
134
|
-
* Track a target for the duration: the point of interest is a hard
|
|
135
|
-
* per-frame copy of the target position (+offset). A plain animator — it
|
|
136
|
-
* pipes, `Scene.all`s, staggers, repeats. No timing input (lag is
|
|
137
|
-
* expressed by springing the POI instead). Ordering practice: within a
|
|
138
|
-
* tick, branches run in fork order — a follow forked before its target's
|
|
139
|
-
* animator reads the previous frame's position, a deterministic one-frame
|
|
140
|
-
* trail. Dual like `lookAt`.
|
|
141
|
-
*/
|
|
142
|
-
export const follow = dual(dataFirst, Effect.fnUntraced(function* (camOrEffect, target, duration, offset) {
|
|
143
|
-
const cam = yield* Instance.flatten(camOrEffect);
|
|
144
|
-
const read = yield* targetReader(target, offset);
|
|
145
|
-
const runner = yield* Runner.Runner;
|
|
146
|
-
const frames = Math.max(1, Time.toFrames(duration, runner.settings.frameRate));
|
|
147
|
-
for (let i = 1; i <= frames; i++) {
|
|
148
|
-
const p = yield* read;
|
|
149
|
-
yield* Scene.update(cam, (d) => setPoi(d, p));
|
|
150
|
-
yield* Scene.tick;
|
|
151
|
-
}
|
|
152
|
-
return cam;
|
|
153
|
-
}));
|
|
154
|
-
// orbit/dolly are defined relative to the POI — die loudly without one
|
|
155
|
-
const poiOrDie = (data) => {
|
|
156
|
-
if (data.poiX === undefined ||
|
|
157
|
-
data.poiY === undefined ||
|
|
158
|
-
data.poiZ === undefined) {
|
|
159
|
-
throw new Error("Camera: orbit/dolly need a point of interest — set one first (Camera.lookAt(target))");
|
|
160
|
-
}
|
|
161
|
-
return { x: data.poiX, y: data.poiY, z: data.poiZ };
|
|
162
|
-
};
|
|
163
|
-
const orbitImpl = Effect.fnUntraced(function* (camOrEffect, from, to, duration, timing) {
|
|
164
|
-
const cam = yield* Instance.flatten(camOrEffect);
|
|
165
|
-
const { comp } = yield* Runner.Runner;
|
|
166
|
-
const origin = { x: comp.width / 2, y: comp.height / 2 };
|
|
167
|
-
const startData = (yield* Scene.data(cam));
|
|
168
|
-
const poi = poiOrDie(startData);
|
|
169
|
-
const world = {
|
|
170
|
-
x: origin.x + startData.x,
|
|
171
|
-
z: startData.z ?? 0,
|
|
172
|
-
};
|
|
173
|
-
// azimuth 0 = directly +z of the POI (the resting side); radius = the
|
|
174
|
-
// current horizontal distance, preserved through the arc; height too
|
|
175
|
-
const radius = Math.hypot(world.x - poi.x, world.z - poi.z);
|
|
176
|
-
const startAzimuth = from ?? Math.atan2(world.x - poi.x, world.z - poi.z);
|
|
177
|
-
return yield* Motion.drive(cam, duration, timing ?? "linear", (t, d) => {
|
|
178
|
-
const data = d;
|
|
179
|
-
// POI read from live data: orbiting a moving POI stays centered on it
|
|
180
|
-
const p = poiOrDie(data);
|
|
181
|
-
const angle = startAzimuth + (to - startAzimuth) * t;
|
|
182
|
-
return Object.assign({}, d, {
|
|
183
|
-
x: p.x + radius * Math.sin(angle) - origin.x,
|
|
184
|
-
z: p.z + radius * Math.cos(angle),
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
/**
|
|
189
|
-
* Turntable the camera to an absolute azimuth around its point of
|
|
190
|
-
* interest (angle about the world-Y axis through the POI; 0 = directly +z
|
|
191
|
-
* of it). Position travels the arc — orientation comes entirely from the
|
|
192
|
-
* POI, so there is no orientation math to get wrong. Radius and height
|
|
193
|
-
* are preserved. Dies loudly without a POI. Dual like `moveTo`.
|
|
194
|
-
*/
|
|
195
|
-
export const orbitTo = dual((args) => Instance.isInstance(args[0]), ((cam, azimuth, duration, timing) => orbitImpl(cam, undefined, azimuth, duration, timing)));
|
|
196
|
-
/** Like `orbitTo`, but from an explicit start azimuth. */
|
|
197
|
-
export const orbit = dual((args) => Instance.isInstance(args[0]), orbitImpl);
|
|
198
|
-
const dollyImpl = Effect.fnUntraced(function* (camOrEffect, from, to, duration, timing) {
|
|
199
|
-
const cam = yield* Instance.flatten(camOrEffect);
|
|
200
|
-
const { comp } = yield* Runner.Runner;
|
|
201
|
-
const origin = { x: comp.width / 2, y: comp.height / 2 };
|
|
202
|
-
const startData = (yield* Scene.data(cam));
|
|
203
|
-
const poi = poiOrDie(startData);
|
|
204
|
-
const world = {
|
|
205
|
-
x: origin.x + startData.x,
|
|
206
|
-
y: origin.y + startData.y,
|
|
207
|
-
z: startData.z ?? 0,
|
|
208
|
-
};
|
|
209
|
-
const startDistance = Math.hypot(world.x - poi.x, world.y - poi.y, world.z - poi.z);
|
|
210
|
-
// the fixed unit direction POI → camera; a zero distance has no
|
|
211
|
-
// direction to dolly along
|
|
212
|
-
if (startDistance === 0) {
|
|
213
|
-
throw new Error("Camera: dolly from distance 0 — the camera sits ON its point of interest, so there is no view axis to move along");
|
|
214
|
-
}
|
|
215
|
-
const u = {
|
|
216
|
-
x: (world.x - poi.x) / startDistance,
|
|
217
|
-
y: (world.y - poi.y) / startDistance,
|
|
218
|
-
z: (world.z - poi.z) / startDistance,
|
|
219
|
-
};
|
|
220
|
-
const d0 = from ?? startDistance;
|
|
221
|
-
return yield* Motion.drive(cam, duration, timing ?? "linear", (t, d) => {
|
|
222
|
-
const data = d;
|
|
223
|
-
const p = poiOrDie(data);
|
|
224
|
-
const dist = d0 + (to - d0) * t;
|
|
225
|
-
return Object.assign({}, d, {
|
|
226
|
-
x: p.x + u.x * dist - origin.x,
|
|
227
|
-
y: p.y + u.y * dist - origin.y,
|
|
228
|
-
z: p.z + u.z * dist,
|
|
229
|
-
});
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
/**
|
|
233
|
-
* Move the camera along its view axis to an absolute distance from the
|
|
234
|
-
* point of interest (in: toward it, out: away), aim unchanged. Dies
|
|
235
|
-
* loudly without a POI. Dual like `moveTo`.
|
|
236
|
-
*/
|
|
237
|
-
export const dollyTo = dual((args) => Instance.isInstance(args[0]), ((cam, distance, duration, timing) => dollyImpl(cam, undefined, distance, duration, timing)));
|
|
238
|
-
/** Like `dollyTo`, but from an explicit start distance. */
|
|
239
|
-
export const dolly = dual((args) => Instance.isInstance(args[0]), dollyImpl);
|
package/dist/CanvasExporter.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import { EffectMotionError } from "./EffectMotionError.js";
|
|
3
|
-
import type * as Renderer from "./Renderer.js";
|
|
4
|
-
/**
|
|
5
|
-
* Blit a framebuffer onto a DOM canvas. The canvas buffer takes the
|
|
6
|
-
* framebuffer's PHYSICAL size (logical × dpr); CSS display size is left to
|
|
7
|
-
* the caller — the intrinsic aspect ratio is preserved by the attributes, so
|
|
8
|
-
* responsive styling (`width: 100%`) or a fixed `fb.logicalWidth`px both
|
|
9
|
-
* work. Writing CSS here would clobber caller styling (e.g. React inline
|
|
10
|
-
* styles are not re-applied on rerender).
|
|
11
|
-
*/
|
|
12
|
-
export declare const toCanvas: (fb: Renderer.Framebuffer, target: "string" | HTMLCanvasElement) => Effect.Effect<undefined, EffectMotionError, never>;
|
package/dist/CanvasExporter.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import { EffectMotionError } from "./EffectMotionError.js";
|
|
3
|
-
const resolveCanvas = Effect.fnUntraced(function* (target) {
|
|
4
|
-
if (typeof target === "string") {
|
|
5
|
-
if (typeof document === "undefined") {
|
|
6
|
-
return yield* Effect.fail(EffectMotionError.of("'document' is not available in the current environment"));
|
|
7
|
-
}
|
|
8
|
-
const maybeCanvas = document.querySelector(target);
|
|
9
|
-
if (maybeCanvas === null) {
|
|
10
|
-
return yield* Effect.fail(EffectMotionError.of(`Could not find canvas with selector '${target}'`));
|
|
11
|
-
}
|
|
12
|
-
if (!(maybeCanvas instanceof HTMLCanvasElement)) {
|
|
13
|
-
return yield* Effect.fail(EffectMotionError.of(`Could not find canvas with selector '${target}'`));
|
|
14
|
-
}
|
|
15
|
-
return maybeCanvas;
|
|
16
|
-
}
|
|
17
|
-
if (!(target instanceof HTMLCanvasElement)) {
|
|
18
|
-
return yield* Effect.fail(EffectMotionError.of(`'target' is not a HTMLCanvasElement`));
|
|
19
|
-
}
|
|
20
|
-
return target;
|
|
21
|
-
});
|
|
22
|
-
/**
|
|
23
|
-
* Blit a framebuffer onto a DOM canvas. The canvas buffer takes the
|
|
24
|
-
* framebuffer's PHYSICAL size (logical × dpr); CSS display size is left to
|
|
25
|
-
* the caller — the intrinsic aspect ratio is preserved by the attributes, so
|
|
26
|
-
* responsive styling (`width: 100%`) or a fixed `fb.logicalWidth`px both
|
|
27
|
-
* work. Writing CSS here would clobber caller styling (e.g. React inline
|
|
28
|
-
* styles are not re-applied on rerender).
|
|
29
|
-
*/
|
|
30
|
-
export const toCanvas = Effect.fnUntraced(function* (fb, target) {
|
|
31
|
-
const canvas = yield* resolveCanvas(target);
|
|
32
|
-
canvas.width = fb.width;
|
|
33
|
-
canvas.height = fb.height;
|
|
34
|
-
const ctx = canvas.getContext("2d");
|
|
35
|
-
if (ctx === null) {
|
|
36
|
-
return yield* Effect.fail(EffectMotionError.of("Could not get a 2D context from the canvas"));
|
|
37
|
-
}
|
|
38
|
-
const clamped = Uint8ClampedArray.from(fb.rgba);
|
|
39
|
-
ctx.putImageData(new ImageData(clamped, fb.width, fb.height), 0, 0);
|
|
40
|
-
});
|
package/dist/Fonts.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import * as Context from "effect/Context";
|
|
2
|
-
/**
|
|
3
|
-
* One loadable font face. `family` is the name text entities reference in
|
|
4
|
-
* `fontFamily`; `src` carries per-environment sources — browsers load
|
|
5
|
-
* `url`, offline rasterizers read `path`. Consumers skip sources they
|
|
6
|
-
* can't use. `weight`/`style` are browser variant-matching descriptors;
|
|
7
|
-
* file-based rasterizers read them from the font file itself. Declare one
|
|
8
|
-
* entry per face (e.g. Inter 400 and Inter 700 are two entries).
|
|
9
|
-
*/
|
|
10
|
-
export interface FontResource {
|
|
11
|
-
readonly family: string;
|
|
12
|
-
readonly src: {
|
|
13
|
-
readonly url?: string;
|
|
14
|
-
readonly path?: string;
|
|
15
|
-
};
|
|
16
|
-
/** CSS font-weight (e.g. 400, 700) */
|
|
17
|
-
readonly weight?: number;
|
|
18
|
-
readonly style?: "normal" | "italic";
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Scene annotation key declaring the fonts a scene's text depends on:
|
|
22
|
-
* `scene.annotate(Fonts.Fonts, [...])`. The runtime never reads it — the
|
|
23
|
-
* engine cannot measure text, so fonts cannot affect frame data. Consumers
|
|
24
|
-
* (the player, export tools) read it to prepare their environment before
|
|
25
|
-
* rendering.
|
|
26
|
-
*/
|
|
27
|
-
export declare const Fonts: Context.Reference<readonly FontResource[]>;
|
|
28
|
-
/** A scene's declared fonts — empty for scenes never annotated. */
|
|
29
|
-
export declare const get: (scene: {
|
|
30
|
-
readonly annotations: Context.Context<never>;
|
|
31
|
-
}) => ReadonlyArray<FontResource>;
|
|
32
|
-
/**
|
|
33
|
-
* A scene's declared fonts as a `family -> url` map for the ThorVG engine's
|
|
34
|
-
* `fonts` option. Only entries with a `src.url` are included — the ThorVG
|
|
35
|
-
* renderer fetches fonts by URL (no filesystem), so `path`-only entries are
|
|
36
|
-
* skipped. Merge this over the engine default so declared families load and a
|
|
37
|
-
* `sans-serif` entry can override the default sans.
|
|
38
|
-
*/
|
|
39
|
-
export declare const urlMap: (scene: {
|
|
40
|
-
readonly annotations: Context.Context<never>;
|
|
41
|
-
}) => Record<string, string>;
|
package/dist/Fonts.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import * as Context from "effect/Context";
|
|
2
|
-
/**
|
|
3
|
-
* Scene annotation key declaring the fonts a scene's text depends on:
|
|
4
|
-
* `scene.annotate(Fonts.Fonts, [...])`. The runtime never reads it — the
|
|
5
|
-
* engine cannot measure text, so fonts cannot affect frame data. Consumers
|
|
6
|
-
* (the player, export tools) read it to prepare their environment before
|
|
7
|
-
* rendering.
|
|
8
|
-
*/
|
|
9
|
-
export const Fonts = Context.Reference("motion/Fonts", { defaultValue: () => [] });
|
|
10
|
-
/** A scene's declared fonts — empty for scenes never annotated. */
|
|
11
|
-
export const get = (scene) => Context.get(scene.annotations, Fonts);
|
|
12
|
-
/**
|
|
13
|
-
* A scene's declared fonts as a `family -> url` map for the ThorVG engine's
|
|
14
|
-
* `fonts` option. Only entries with a `src.url` are included — the ThorVG
|
|
15
|
-
* renderer fetches fonts by URL (no filesystem), so `path`-only entries are
|
|
16
|
-
* skipped. Merge this over the engine default so declared families load and a
|
|
17
|
-
* `sans-serif` entry can override the default sans.
|
|
18
|
-
*/
|
|
19
|
-
export const urlMap = (scene) => {
|
|
20
|
-
const out = {};
|
|
21
|
-
for (const font of get(scene)) {
|
|
22
|
-
if (font.src.url !== undefined) {
|
|
23
|
-
out[font.family] = font.src.url;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return out;
|
|
27
|
-
};
|
package/dist/Images.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import * as Context from "effect/Context";
|
|
2
|
-
/**
|
|
3
|
-
* One loadable image asset. `name` is what `Shapes.Image` entities reference
|
|
4
|
-
* in their `image` field; `src` carries per-environment sources — browsers
|
|
5
|
-
* load `url`, file-based tooling may read `path` (reserved; url-only today,
|
|
6
|
-
* same contract as fonts). Consumers skip sources they can't use.
|
|
7
|
-
*/
|
|
8
|
-
export interface ImageResource {
|
|
9
|
-
readonly name: string;
|
|
10
|
-
readonly src: {
|
|
11
|
-
readonly url?: string;
|
|
12
|
-
readonly path?: string;
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Scene annotation key declaring the images a scene's Image entities depend
|
|
17
|
-
* on: `scene.annotate(Images.Images, [...])`. The runtime never reads it —
|
|
18
|
-
* decoded pixels cannot affect frame data. Consumers (the player, export
|
|
19
|
-
* tools) read it to load assets into their render session before playback.
|
|
20
|
-
*/
|
|
21
|
-
export declare const Images: Context.Reference<readonly ImageResource[]>;
|
|
22
|
-
/** A scene's declared images — empty for scenes never annotated. */
|
|
23
|
-
export declare const get: (scene: {
|
|
24
|
-
readonly annotations: Context.Context<never>;
|
|
25
|
-
}) => ReadonlyArray<ImageResource>;
|
|
26
|
-
/**
|
|
27
|
-
* A scene's declared images as a `name -> url` map for the render session's
|
|
28
|
-
* `images` option. Only entries with a `src.url` are included — loading is
|
|
29
|
-
* fetch-by-URL (no filesystem), so `path`-only entries are skipped.
|
|
30
|
-
*/
|
|
31
|
-
export declare const urlMap: (scene: {
|
|
32
|
-
readonly annotations: Context.Context<never>;
|
|
33
|
-
}) => Record<string, string>;
|
package/dist/Images.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import * as Context from "effect/Context";
|
|
2
|
-
/**
|
|
3
|
-
* Scene annotation key declaring the images a scene's Image entities depend
|
|
4
|
-
* on: `scene.annotate(Images.Images, [...])`. The runtime never reads it —
|
|
5
|
-
* decoded pixels cannot affect frame data. Consumers (the player, export
|
|
6
|
-
* tools) read it to load assets into their render session before playback.
|
|
7
|
-
*/
|
|
8
|
-
export const Images = Context.Reference("motion/Images", { defaultValue: () => [] });
|
|
9
|
-
/** A scene's declared images — empty for scenes never annotated. */
|
|
10
|
-
export const get = (scene) => Context.get(scene.annotations, Images);
|
|
11
|
-
/**
|
|
12
|
-
* A scene's declared images as a `name -> url` map for the render session's
|
|
13
|
-
* `images` option. Only entries with a `src.url` are included — loading is
|
|
14
|
-
* fetch-by-URL (no filesystem), so `path`-only entries are skipped.
|
|
15
|
-
*/
|
|
16
|
-
export const urlMap = (scene) => {
|
|
17
|
-
const out = {};
|
|
18
|
-
for (const image of get(scene)) {
|
|
19
|
-
if (image.src.url !== undefined) {
|
|
20
|
-
out[image.name] = image.src.url;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return out;
|
|
24
|
-
};
|
package/dist/PngExporter.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect";
|
|
2
|
-
import { FileSystem } from "effect/FileSystem";
|
|
3
|
-
import { EffectMotionError } from "./EffectMotionError.js";
|
|
4
|
-
import type * as Renderer from "./Renderer.js";
|
|
5
|
-
export declare const toBuffer: (fb: Renderer.Framebuffer) => Effect.Effect<Uint8Array<ArrayBuffer>, EffectMotionError, never>;
|
|
6
|
-
export declare const toFile: (fb: Renderer.Framebuffer, outPath: string) => Effect.Effect<void, EffectMotionError | import("effect/PlatformError").PlatformError, FileSystem>;
|