effect-motion 0.3.2 → 0.5.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 +2 -2
- 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 +682 -38
- package/dist/Entity.js +271 -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 +37 -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 +653 -330
- package/dist/Runner.js +208 -158
- package/dist/Scene.d.ts +621 -171
- package/dist/Scene.js +620 -89
- 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 +8 -6
- 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 +13 -5
- package/dist/particles/step.js +14 -10
- package/dist/types.d.ts +5 -0
- package/dist/types.js +1 -0
- package/package.json +2 -4
- 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 -381
- 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 -65
- 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 -116
- package/dist/shapes/Group.js +0 -82
- 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/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 { settings } = yield* Runner.Runner;
|
|
72
|
-
const data = (yield* Scene.data(cam));
|
|
73
|
-
return {
|
|
74
|
-
x: settings.width / 2 + data.x,
|
|
75
|
-
y: settings.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 { settings } = yield* Runner.Runner;
|
|
166
|
-
const origin = { x: settings.width / 2, y: settings.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 { settings } = yield* Runner.Runner;
|
|
201
|
-
const origin = { x: settings.width / 2, y: settings.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>;
|
package/dist/PngExporter.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { deflateSync } from "node:zlib";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import { FileSystem } from "effect/FileSystem";
|
|
4
|
-
import { EffectMotionError } from "./EffectMotionError.js";
|
|
5
|
-
const SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
6
|
-
const crcTable = (() => {
|
|
7
|
-
const t = new Uint32Array(256);
|
|
8
|
-
for (let n = 0; n < 256; n++) {
|
|
9
|
-
let c = n;
|
|
10
|
-
for (let k = 0; k < 8; k++) {
|
|
11
|
-
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
12
|
-
}
|
|
13
|
-
t[n] = c >>> 0;
|
|
14
|
-
}
|
|
15
|
-
return t;
|
|
16
|
-
})();
|
|
17
|
-
const crc32 = (bytes) => {
|
|
18
|
-
let c = 0xffffffff;
|
|
19
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
20
|
-
c = crcTable[(c ^ bytes[i]) & 0xff] ^ (c >>> 8);
|
|
21
|
-
}
|
|
22
|
-
return (c ^ 0xffffffff) >>> 0;
|
|
23
|
-
};
|
|
24
|
-
const chunk = (type, data) => {
|
|
25
|
-
const typeBytes = new Uint8Array([
|
|
26
|
-
type.charCodeAt(0),
|
|
27
|
-
type.charCodeAt(1),
|
|
28
|
-
type.charCodeAt(2),
|
|
29
|
-
type.charCodeAt(3),
|
|
30
|
-
]);
|
|
31
|
-
const body = new Uint8Array(typeBytes.length + data.length);
|
|
32
|
-
body.set(typeBytes, 0);
|
|
33
|
-
body.set(data, typeBytes.length);
|
|
34
|
-
const out = new Uint8Array(4 + body.length + 4);
|
|
35
|
-
const view = new DataView(out.buffer);
|
|
36
|
-
view.setUint32(0, data.length, false);
|
|
37
|
-
out.set(body, 4);
|
|
38
|
-
view.setUint32(4 + body.length, crc32(body), false);
|
|
39
|
-
return out;
|
|
40
|
-
};
|
|
41
|
-
/** Encode a raw RGBA8888 buffer (`width * height * 4` bytes) as a PNG. */
|
|
42
|
-
const encodePng = Effect.fnUntraced(function* (rgba, width, height) {
|
|
43
|
-
if (rgba.length !== width * height * 4) {
|
|
44
|
-
return yield* Effect.fail(EffectMotionError.of(`Could not encode PNG: buffer is ${rgba.length} bytes, expected ${width * height * 4} (${width}x${height} RGBA)`));
|
|
45
|
-
}
|
|
46
|
-
// prepend a filter byte (0 = none) to each scanline
|
|
47
|
-
const stride = width * 4;
|
|
48
|
-
const raw = new Uint8Array((stride + 1) * height);
|
|
49
|
-
for (let y = 0; y < height; y++) {
|
|
50
|
-
raw[y * (stride + 1)] = 0;
|
|
51
|
-
raw.set(rgba.subarray(y * stride, y * stride + stride), y * (stride + 1) + 1);
|
|
52
|
-
}
|
|
53
|
-
const ihdr = new Uint8Array(13);
|
|
54
|
-
const ihdrView = new DataView(ihdr.buffer);
|
|
55
|
-
ihdrView.setUint32(0, width, false);
|
|
56
|
-
ihdrView.setUint32(4, height, false);
|
|
57
|
-
ihdr[8] = 8; // bit depth
|
|
58
|
-
ihdr[9] = 6; // color type: RGBA
|
|
59
|
-
// [10] compression, [11] filter, [12] interlace all 0
|
|
60
|
-
const idat = new Uint8Array(deflateSync(raw));
|
|
61
|
-
const chunks = [
|
|
62
|
-
SIGNATURE,
|
|
63
|
-
chunk("IHDR", ihdr),
|
|
64
|
-
chunk("IDAT", idat),
|
|
65
|
-
chunk("IEND", new Uint8Array(0)),
|
|
66
|
-
];
|
|
67
|
-
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
68
|
-
const png = new Uint8Array(total);
|
|
69
|
-
let offset = 0;
|
|
70
|
-
for (const c of chunks) {
|
|
71
|
-
png.set(c, offset);
|
|
72
|
-
offset += c.length;
|
|
73
|
-
}
|
|
74
|
-
return png;
|
|
75
|
-
});
|
|
76
|
-
export const toBuffer = (fb) => {
|
|
77
|
-
return encodePng(fb.rgba, fb.width, fb.height);
|
|
78
|
-
};
|
|
79
|
-
export const toFile = (fb, outPath) => {
|
|
80
|
-
return Effect.gen(function* () {
|
|
81
|
-
const fs = yield* FileSystem;
|
|
82
|
-
const png = yield* toBuffer(fb);
|
|
83
|
-
yield* fs.writeFile(outPath, png);
|
|
84
|
-
});
|
|
85
|
-
};
|
package/dist/Renderer.d.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { OwnedPaint, RenderSession, ThorvgException, ThorvgWasm } from "@effect-motion/thorvg";
|
|
2
|
-
import * as Tvg from "@effect-motion/thorvg";
|
|
3
|
-
import type { Canvas } from "@effect-motion/thorvg/Canvas";
|
|
4
|
-
import * as Effect from "effect/Effect";
|
|
5
|
-
import type * as Scope from "effect/Scope";
|
|
6
|
-
import * as Color from "./Color.js";
|
|
7
|
-
import type * as Entity from "./Entity.js";
|
|
8
|
-
import * as Projection from "./Projection.js";
|
|
9
|
-
import type { Frame } from "./Scene.js";
|
|
10
|
-
/**
|
|
11
|
-
* The projection handed to each paint function — how the camera places a
|
|
12
|
-
* paintable this frame. `screen` is the projected billboard placement (an
|
|
13
|
-
* affine the paint fn applies via `setTransform`); `depth` is the view-space
|
|
14
|
-
* sort key; `scale` is the perspective scale (<= 0 means the anchor is behind
|
|
15
|
-
* the camera — cull, unless a `quad` or `segment` is present). `quad`, when
|
|
16
|
-
* present, is the projected, near-plane-clipped screen polygon of a tilted
|
|
17
|
-
* plane (3–5 vertices — see Projection.projectPlane); a shape that can tilt
|
|
18
|
-
* paints an exact path from these instead of applying the billboard affine.
|
|
19
|
-
* `segment`, when present, is the exact projected screen endpoints of a
|
|
20
|
-
* skeletal shape (Line) — each endpoint carries its own world depth, so the
|
|
21
|
-
* pair is projected per point (see Projection.projectSegment) and the paint
|
|
22
|
-
* fn draws it directly, skipping the billboard affine. `subpaths`, when
|
|
23
|
-
* present, is the projected screen geometry of a skeletal path (Path): every
|
|
24
|
-
* command point projected individually and near-plane-clipped per subpath
|
|
25
|
-
* (see Projection.projectPath) — the paint fn emits it directly at screen
|
|
26
|
-
* coordinates.
|
|
27
|
-
*/
|
|
28
|
-
export interface PaintProjection {
|
|
29
|
-
readonly screen: Projection.Affine;
|
|
30
|
-
readonly depth: number;
|
|
31
|
-
readonly scale: number;
|
|
32
|
-
readonly quad?: ReadonlyArray<Projection.Vec2>;
|
|
33
|
-
readonly segment?: readonly [Projection.Vec2, Projection.Vec2];
|
|
34
|
-
readonly subpaths?: Projection.ProjectedPath["subpaths"];
|
|
35
|
-
}
|
|
36
|
-
/** The frame's render metadata, handed to paint functions. */
|
|
37
|
-
export interface FrameMeta {
|
|
38
|
-
readonly frameRate: number;
|
|
39
|
-
readonly width: number;
|
|
40
|
-
readonly height: number;
|
|
41
|
-
readonly backgroundColor: Color.Color;
|
|
42
|
-
/**
|
|
43
|
-
* the active camera's view — world position `{x, y, z}`, Euler
|
|
44
|
-
* orientation `{rotX, rotY, rotZ}`, and `focalLength` (FOV). The renderer
|
|
45
|
-
* projects every instance through it. The resting camera (see
|
|
46
|
-
* Camera.identity) reproduces plain-2D placement for z=0 content.
|
|
47
|
-
*/
|
|
48
|
-
readonly camera: {
|
|
49
|
-
readonly x: number;
|
|
50
|
-
readonly y: number;
|
|
51
|
-
readonly z: number;
|
|
52
|
-
readonly rotX: number;
|
|
53
|
-
readonly rotY: number;
|
|
54
|
-
readonly rotZ: number;
|
|
55
|
-
readonly focalLength: number;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Paints one entity instance onto the shared ThorVG scene. It issues ThorVG
|
|
60
|
-
* C-API calls (make a shape, append geometry, style it, apply the projection,
|
|
61
|
-
* add it to `scene`) — there is no intermediate description value. A container
|
|
62
|
-
* (Group / root) paints nothing itself; its position has already composed into
|
|
63
|
-
* its children's world coordinates by the time this is called.
|
|
64
|
-
*/
|
|
65
|
-
export type PaintFunction<Ent extends Entity.AnyEntity> = (payload: {
|
|
66
|
-
readonly entity: Ent;
|
|
67
|
-
readonly id: string;
|
|
68
|
-
readonly data: Ent["data"]["Type"];
|
|
69
|
-
readonly projection: PaintProjection;
|
|
70
|
-
readonly canvas: Canvas;
|
|
71
|
-
readonly scene: OwnedPaint;
|
|
72
|
-
readonly meta: FrameMeta;
|
|
73
|
-
}) => Effect.Effect<void, ThorvgException, ThorvgWasm | RenderSession | Scope.Scope>;
|
|
74
|
-
/**
|
|
75
|
-
* A registry of paint functions keyed by entity name. `PaintFunctions<E>` is
|
|
76
|
-
* the exhaustive map over an entity union — a built-in with no paint function
|
|
77
|
-
* is a type error at the render call, not a runtime surprise (the old
|
|
78
|
-
* "coverage manifest" guarantee, kept without a Context registry).
|
|
79
|
-
*/
|
|
80
|
-
export type PaintFunctions<Entities extends Entity.AnyEntity> = {
|
|
81
|
-
readonly [K in Entities as K["name"]]: PaintFunction<K>;
|
|
82
|
-
};
|
|
83
|
-
/** RGBA8888 framebuffer plus its dimensions, straight from the SW canvas. */
|
|
84
|
-
export interface Framebuffer {
|
|
85
|
-
readonly rgba: Uint8Array;
|
|
86
|
-
/** physical pixel size of the rgba buffer (logical size × dpr) */
|
|
87
|
-
readonly width: number;
|
|
88
|
-
readonly height: number;
|
|
89
|
-
/**
|
|
90
|
-
* logical scene size — the resolution the buffer should be displayed at
|
|
91
|
-
* (CSS pixels). Equals width/height when rendered at dpr 1.
|
|
92
|
-
*/
|
|
93
|
-
readonly logicalWidth: number;
|
|
94
|
-
readonly logicalHeight: number;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Render one frame to an RGBA framebuffer, shared by both output adapters.
|
|
98
|
-
*
|
|
99
|
-
* Uses the RenderSession's canvas (resized in place to the frame's physical
|
|
100
|
-
* size, cleared of the previous frame), adds a root scene, folds the frame
|
|
101
|
-
* onto it via `Renderer.render`, then update/draw/sync and reads the SW
|
|
102
|
-
* framebuffer. The scene and every painted shape are scoped per frame; the
|
|
103
|
-
* canvas belongs to the session (a player mount, an export run) and is
|
|
104
|
-
* deleted when the session closes.
|
|
105
|
-
*
|
|
106
|
-
* The background is painted as a filled rect (not a canvas clear color) so it
|
|
107
|
-
* survives into the buffer the same way the SVG sink emitted a background
|
|
108
|
-
* rect.
|
|
109
|
-
*/
|
|
110
|
-
export declare const render: (frame: Frame, options?: {
|
|
111
|
-
/**
|
|
112
|
-
* device-pixel-ratio multiplier for high-dpi displays. The buffer is
|
|
113
|
-
* rasterized at `logical × dpr` while paint functions keep working in
|
|
114
|
-
* logical scene coordinates (the root scene is scaled). Callers display
|
|
115
|
-
* the buffer at the logical size. Default 1 (node/export paths).
|
|
116
|
-
*/
|
|
117
|
-
readonly dpr?: number;
|
|
118
|
-
}) => Effect.Effect<Framebuffer, ThorvgException, Tvg.ThorvgWasm | Tvg.RenderSession | Scope.Scope>;
|