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/Renderer.js
DELETED
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import * as Tvg from "@effect-motion/thorvg";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as CameraMod from "./Camera.js";
|
|
4
|
-
import * as Color from "./Color.js";
|
|
5
|
-
import * as Projection from "./Projection.js";
|
|
6
|
-
import { circleOfConfusion, quantizeSigma } from "./render/dof.js";
|
|
7
|
-
import { builtinPaints } from "./render/shapes.js";
|
|
8
|
-
import { Hud } from "./shapes/Hud.js";
|
|
9
|
-
// in frame data, a `children: string[]` field means child instance ids
|
|
10
|
-
const childIdsOf = (data) => {
|
|
11
|
-
const children = data?.children;
|
|
12
|
-
return Array.isArray(children) ? children : [];
|
|
13
|
-
};
|
|
14
|
-
/**
|
|
15
|
-
* Split a Path's command list into world-space subpaths: `M` starts one,
|
|
16
|
-
* `L` extends it, `Z` closes it. Command points are LOCAL to the path's
|
|
17
|
-
* anchor; `z` absent means 0. An `L` directly after a `Z` starts a new open
|
|
18
|
-
* subpath from the closed subpath's start point (SVG semantics).
|
|
19
|
-
*/
|
|
20
|
-
const pathSubpaths = (commands, anchor) => {
|
|
21
|
-
const subpaths = [];
|
|
22
|
-
let current = [];
|
|
23
|
-
let lastMove = anchor;
|
|
24
|
-
const world = (p) => ({
|
|
25
|
-
x: anchor.x + p.x,
|
|
26
|
-
y: anchor.y + p.y,
|
|
27
|
-
z: anchor.z + (p.z ?? 0),
|
|
28
|
-
});
|
|
29
|
-
const flush = (closed) => {
|
|
30
|
-
if (current.length >= 2) {
|
|
31
|
-
subpaths.push({ points: current, closed });
|
|
32
|
-
}
|
|
33
|
-
current = [];
|
|
34
|
-
};
|
|
35
|
-
for (const command of commands) {
|
|
36
|
-
switch (command._tag) {
|
|
37
|
-
case "M": {
|
|
38
|
-
flush(false);
|
|
39
|
-
lastMove = world(command);
|
|
40
|
-
current = [lastMove];
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
case "L": {
|
|
44
|
-
if (current.length === 0) {
|
|
45
|
-
current.push(lastMove);
|
|
46
|
-
}
|
|
47
|
-
current.push(world(command));
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case "Z": {
|
|
51
|
-
flush(true);
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
flush(false);
|
|
57
|
-
return subpaths;
|
|
58
|
-
};
|
|
59
|
-
// a hidden instance ($visible false) and its subtree are skipped entirely
|
|
60
|
-
const isVisible = (frame, id) => frame.instances[id]?.$visible !== false;
|
|
61
|
-
/**
|
|
62
|
-
* Fold a frame onto the shared ThorVG canvas + scene: flatten the instance
|
|
63
|
-
* tree to a depth-sorted draw list, project each paintable through the
|
|
64
|
-
* frame's camera, and paint far→near. The pipeline (flatten, world-offset
|
|
65
|
-
* composition, projection, quad, stable-id depth sort, visibility skip,
|
|
66
|
-
* cycle/duplicate defects) is target-agnostic; only the per-entity paint is
|
|
67
|
-
* ThorVG-specific.
|
|
68
|
-
*/
|
|
69
|
-
const renderToCanvas = (frame, canvas, scene,
|
|
70
|
-
// blur sigmas are canvas-pixel amounts; the scene is authored in logical
|
|
71
|
-
// units, so depth-of-field scales them by the device-pixel ratio
|
|
72
|
-
dpr = 1) => Effect.gen(function* () {
|
|
73
|
-
const origin = {
|
|
74
|
-
x: frame.width / 2,
|
|
75
|
-
y: frame.height / 2,
|
|
76
|
-
};
|
|
77
|
-
// the effective view: a camera with a point of interest auto-orients
|
|
78
|
-
// toward it here (explicit Euler composes after — see resolveCamera);
|
|
79
|
-
// the frame's camera data is never mutated
|
|
80
|
-
const camera = Projection.resolveCamera(frame.camera, origin);
|
|
81
|
-
// HUD subtrees project through the identity camera: camera-independent
|
|
82
|
-
// placement, and structurally exempt from depth of field (aperture 0)
|
|
83
|
-
const identityCamera = CameraMod.identity(frame.width);
|
|
84
|
-
const visited = new Set();
|
|
85
|
-
const paintables = [];
|
|
86
|
-
// Flatten the tree to a draw list. A container (a node with a
|
|
87
|
-
// `children` field) is NOT a paint-order boundary: its position
|
|
88
|
-
// composes into its children's world coordinates, and each child is
|
|
89
|
-
// emitted into the same flat list. `offset` is the accumulated world
|
|
90
|
-
// translation from ancestor containers. `hud` marks an ancestor Hud:
|
|
91
|
-
// the subtree projects through the identity camera and paints in the
|
|
92
|
-
// top tier. `inWorldContainer` marks any ordinary container above —
|
|
93
|
-
// a Hud there would compose world offsets into screen coordinates,
|
|
94
|
-
// which is incoherent and dies loudly.
|
|
95
|
-
const flatten = (id, offset, hud, inWorldContainer) => Effect.gen(function* () {
|
|
96
|
-
if (visited.has(id)) {
|
|
97
|
-
return yield* Effect.die(new Error(`Renderer: instance "${id}" is referenced more than once (duplicate parent or cycle)`));
|
|
98
|
-
}
|
|
99
|
-
visited.add(id);
|
|
100
|
-
const entry = frame.instances[id];
|
|
101
|
-
if (entry === undefined) {
|
|
102
|
-
return yield* Effect.die(new Error(`Renderer: unknown instance id "${id}"`));
|
|
103
|
-
}
|
|
104
|
-
const isHud = entry.entity.name === Hud.name;
|
|
105
|
-
if (isHud && inWorldContainer) {
|
|
106
|
-
return yield* Effect.die(new Error(`Renderer: Hud "${id}" is nested inside world content — a Hud must be a top-level child of the root (or of another Hud)`));
|
|
107
|
-
}
|
|
108
|
-
const subtreeHud = hud || isHud;
|
|
109
|
-
const effectiveCamera = subtreeHud ? identityCamera : camera;
|
|
110
|
-
const data = entry.data;
|
|
111
|
-
// world anchor = ancestor offset + this node's own position
|
|
112
|
-
const world = {
|
|
113
|
-
x: offset.x + (data.x ?? 0),
|
|
114
|
-
y: offset.y + (data.y ?? 0),
|
|
115
|
-
z: offset.z + (data.z ?? 0),
|
|
116
|
-
};
|
|
117
|
-
const childIds = childIdsOf(entry.data).filter((childId) => isVisible(frame, childId));
|
|
118
|
-
if (childIds.length > 0) {
|
|
119
|
-
// a pure container: contribute position, recurse, paint
|
|
120
|
-
// nothing itself (the root, Groups, Huds). ponytail: only
|
|
121
|
-
// translation composes down — a Group's 2D affine transform
|
|
122
|
-
// is not yet threaded into child world coords.
|
|
123
|
-
yield* Effect.all(childIds.map((childId) => flatten(childId, world, subtreeHud, inWorldContainer || !subtreeHud)));
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
// a skeletal path leaf (Path): every command point is an
|
|
127
|
-
// independent world point (anchor + local), projected per point
|
|
128
|
-
// with per-subpath near-plane clipping — the paint fn emits the
|
|
129
|
-
// exact screen subpaths. ponytail: no viewport clip yet — ThorVG
|
|
130
|
-
// stroke cost scales with the path's full extent, offscreen
|
|
131
|
-
// included; upgrade is per-span clipSegmentToRect with the same
|
|
132
|
-
// splitting the near clip uses.
|
|
133
|
-
if (Array.isArray(data.commands)) {
|
|
134
|
-
const projected = Projection.projectPath(effectiveCamera, pathSubpaths(data.commands, world), origin);
|
|
135
|
-
if (projected === undefined) {
|
|
136
|
-
// entirely behind the near plane — cull
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
// anchor the billboard affine on the first visible point, as
|
|
140
|
-
// the segment branch does — path paints ignore it (their
|
|
141
|
-
// geometry is already screen-space) but the field stays coherent
|
|
142
|
-
const first = projected.subpaths[0]?.points[0] ?? origin;
|
|
143
|
-
paintables.push({
|
|
144
|
-
id,
|
|
145
|
-
entry,
|
|
146
|
-
projection: {
|
|
147
|
-
screen: Projection.billboardAffine({
|
|
148
|
-
x: first.x,
|
|
149
|
-
y: first.y,
|
|
150
|
-
depth: projected.depth,
|
|
151
|
-
scale: projected.scale,
|
|
152
|
-
}, { x: data.x ?? 0, y: data.y ?? 0 }),
|
|
153
|
-
depth: projected.depth,
|
|
154
|
-
scale: projected.scale,
|
|
155
|
-
subpaths: projected.subpaths,
|
|
156
|
-
},
|
|
157
|
-
hud: subtreeHud,
|
|
158
|
-
});
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
// a skeletal leaf (Line): both endpoints are independent world
|
|
162
|
-
// points — project the pair per point (near-plane-clipped) and
|
|
163
|
-
// hand the exact screen segment to the paint fn. Unconditional
|
|
164
|
-
// for any x2/y2 leaf: the identity invariant makes the flat
|
|
165
|
-
// case pixel-identical to the old billboard path.
|
|
166
|
-
if (typeof data.x2 === "number" && typeof data.y2 === "number") {
|
|
167
|
-
const seg = Projection.projectSegment(effectiveCamera, world, {
|
|
168
|
-
x: offset.x + data.x2,
|
|
169
|
-
y: offset.y + data.y2,
|
|
170
|
-
z: offset.z + (data.z2 ?? 0),
|
|
171
|
-
}, origin);
|
|
172
|
-
if (seg === undefined) {
|
|
173
|
-
// entirely behind the near plane — cull
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
// clip to the viewport before painting: ThorVG stroke cost
|
|
177
|
-
// scales with the path's full extent, offscreen included.
|
|
178
|
-
// The margin covers the scaled stroke (caps/joins) so the
|
|
179
|
-
// visible pixels are untouched by the clip.
|
|
180
|
-
const strokeMargin = (entry.data.strokeWidth ?? 1) *
|
|
181
|
-
seg.scale +
|
|
182
|
-
1;
|
|
183
|
-
const clipped = Projection.clipSegmentToRect(seg.a, seg.b, { x: -strokeMargin, y: -strokeMargin }, {
|
|
184
|
-
x: frame.width + strokeMargin,
|
|
185
|
-
y: frame.height + strokeMargin,
|
|
186
|
-
});
|
|
187
|
-
if (clipped === undefined) {
|
|
188
|
-
// entirely offscreen — cull
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
// ponytail: one depth/scale for the whole segment (its
|
|
192
|
-
// visible midpoint) — a depth-spanning line blurs and sorts
|
|
193
|
-
// as one unit, the same ceiling as a tilted plane's quad.
|
|
194
|
-
// Upgrade: split the segment where its (linear) view depth
|
|
195
|
-
// crosses DoF bucket boundaries → gradient blur along the
|
|
196
|
-
// line plus per-piece sort keys.
|
|
197
|
-
paintables.push({
|
|
198
|
-
id,
|
|
199
|
-
entry,
|
|
200
|
-
projection: {
|
|
201
|
-
screen: Projection.billboardAffine({ x: seg.a.x, y: seg.a.y, depth: seg.depth, scale: seg.scale }, { x: data.x ?? 0, y: data.y ?? 0 }),
|
|
202
|
-
depth: seg.depth,
|
|
203
|
-
scale: seg.scale,
|
|
204
|
-
segment: clipped,
|
|
205
|
-
},
|
|
206
|
-
hud: subtreeHud,
|
|
207
|
-
});
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// a leaf paintable: project its world anchor for placement +
|
|
211
|
-
// depth, but anchor the billboard affine on the shape's own
|
|
212
|
-
// LOCAL coordinates — the shape paints at local (data.x/y), so
|
|
213
|
-
// the transform maps local → screen. The composed ancestor
|
|
214
|
-
// offset lives in the transform's translation.
|
|
215
|
-
const proj = Projection.project(effectiveCamera, world, origin);
|
|
216
|
-
// A rectangular plane with any nonzero rotation tilts: project
|
|
217
|
-
// its four corners so the paint fn can emit an exact path. The
|
|
218
|
-
// pivot is the plane's world anchor, so rotation spins it in
|
|
219
|
-
// place. Billboards (no rotation, or non-rect shapes) skip this.
|
|
220
|
-
const rotX = data.rotX ?? 0;
|
|
221
|
-
const rotY = data.rotY ?? 0;
|
|
222
|
-
const rotZ = data.rotZ ?? 0;
|
|
223
|
-
const tilted = (rotX !== 0 || rotY !== 0 || rotZ !== 0) &&
|
|
224
|
-
data.width !== undefined &&
|
|
225
|
-
data.height !== undefined;
|
|
226
|
-
const quad = tilted
|
|
227
|
-
? Projection.projectPlane(effectiveCamera, Projection.planeCorners({
|
|
228
|
-
x: data.x ?? 0,
|
|
229
|
-
y: data.y ?? 0,
|
|
230
|
-
width: data.width,
|
|
231
|
-
height: data.height,
|
|
232
|
-
}, { rotX, rotY, rotZ }, world), origin)
|
|
233
|
-
: undefined;
|
|
234
|
-
if (quad !== undefined && quad.length < 3) {
|
|
235
|
-
// the tilted plane is entirely behind the near plane — cull
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
paintables.push({
|
|
239
|
-
id,
|
|
240
|
-
entry,
|
|
241
|
-
projection: {
|
|
242
|
-
screen: Projection.billboardAffine(proj, {
|
|
243
|
-
x: data.x ?? 0,
|
|
244
|
-
y: data.y ?? 0,
|
|
245
|
-
}),
|
|
246
|
-
depth: proj.depth,
|
|
247
|
-
scale: proj.scale,
|
|
248
|
-
...(quad !== undefined ? { quad } : {}),
|
|
249
|
-
},
|
|
250
|
-
hud: subtreeHud,
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
const rootEntry = frame.instances[frame.root];
|
|
254
|
-
if (rootEntry === undefined) {
|
|
255
|
-
return yield* Effect.die(new Error(`Renderer: missing root instance "${frame.root}"`));
|
|
256
|
-
}
|
|
257
|
-
visited.add(frame.root);
|
|
258
|
-
yield* Effect.all(childIdsOf(rootEntry.data)
|
|
259
|
-
.filter((childId) => isVisible(frame, childId))
|
|
260
|
-
.map((childId) => flatten(childId, { x: 0, y: 0, z: 0 }, false, false)));
|
|
261
|
-
// painter's order: two tiers — world content by depth (farthest
|
|
262
|
-
// first), then HUD content by depth among itself, each with a stable
|
|
263
|
-
// id tie-break so equal-depth paintables paint deterministically.
|
|
264
|
-
// ponytail: naive O(n log n) per frame — swap for a spatial structure
|
|
265
|
-
// only if a scene with thousands of objects proves it.
|
|
266
|
-
paintables.sort((a, b) => (a.hud ? 1 : 0) - (b.hud ? 1 : 0) ||
|
|
267
|
-
b.projection.depth - a.projection.depth ||
|
|
268
|
-
(a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
269
|
-
const meta = {
|
|
270
|
-
frameRate: frame.frameRate,
|
|
271
|
-
width: frame.width,
|
|
272
|
-
height: frame.height,
|
|
273
|
-
backgroundColor: frame.backgroundColor,
|
|
274
|
-
camera: frame.camera,
|
|
275
|
-
};
|
|
276
|
-
// Depth of field (camera-depth-of-field D3): with aperture > 0,
|
|
277
|
-
// contiguous depth-sorted runs sharing a quantized blur sigma paint
|
|
278
|
-
// into nested scenes that get a gaussian-blur effect; sharp runs paint
|
|
279
|
-
// into the root as always, so aperture 0 (the default) takes the
|
|
280
|
-
// unchanged single-scene path. Buckets are added to the root at run
|
|
281
|
-
// boundaries, preserving painter's order exactly. Sigma is scaled by
|
|
282
|
-
// dpr because the blur operates in canvas pixels while the scene is
|
|
283
|
-
// authored in logical units.
|
|
284
|
-
const aperture = frame.camera.aperture;
|
|
285
|
-
let bucketScene = scene;
|
|
286
|
-
let bucketSigma = 0;
|
|
287
|
-
const closeBucket = Effect.gen(function* () {
|
|
288
|
-
if (bucketSigma > 0) {
|
|
289
|
-
// direction 0 = both axes, border 0 = duplicate, quality 75
|
|
290
|
-
// (upstream default) — verified in the scene-blur spike
|
|
291
|
-
yield* Tvg.Scene.addGaussianBlur(bucketScene, bucketSigma * dpr, 0, 0, 75);
|
|
292
|
-
yield* Tvg.Scene.add(scene, bucketScene);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
// paint far→near. A paintable whose anchor is behind the camera
|
|
296
|
-
// (scale <= 0) is culled here so paint functions never see an invalid
|
|
297
|
-
// placement — EXCEPT a tilted plane carrying a quad: its polygon is
|
|
298
|
-
// already near-plane-clipped, and it can be visible (near part in
|
|
299
|
-
// front) while its anchor corner is behind.
|
|
300
|
-
for (const { id, entry, projection, hud } of paintables) {
|
|
301
|
-
if (projection.scale <= 0 && projection.quad === undefined) {
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
// HUD content is structurally sharp: its effective camera is the
|
|
305
|
-
// identity camera (aperture 0), so it never enters a blur bucket
|
|
306
|
-
const sigma = aperture > 0 && !hud
|
|
307
|
-
? quantizeSigma(circleOfConfusion(projection.depth, frame.camera))
|
|
308
|
-
: 0;
|
|
309
|
-
if (sigma !== bucketSigma) {
|
|
310
|
-
yield* closeBucket;
|
|
311
|
-
bucketScene = sigma === 0 ? scene : yield* Tvg.Scene.make();
|
|
312
|
-
bucketSigma = sigma;
|
|
313
|
-
}
|
|
314
|
-
// the concrete paint fn for this entity name; the map is exhaustive
|
|
315
|
-
// over Entities by construction (PaintFunctions<Entities>). The
|
|
316
|
-
// specific member depends on the instance's entity, known only at
|
|
317
|
-
// runtime — hence the cast to the erased paint-fn type.
|
|
318
|
-
const paint = builtinPaints[entry.entity.name];
|
|
319
|
-
if (paint === undefined) {
|
|
320
|
-
return yield* Effect.die(new Error(`Renderer: no paint function for entity "${entry.entity.name}"`));
|
|
321
|
-
}
|
|
322
|
-
yield* paint({
|
|
323
|
-
entity: entry.entity,
|
|
324
|
-
id,
|
|
325
|
-
data: entry.data,
|
|
326
|
-
projection,
|
|
327
|
-
canvas,
|
|
328
|
-
scene: bucketScene,
|
|
329
|
-
meta,
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
yield* closeBucket;
|
|
333
|
-
});
|
|
334
|
-
/**
|
|
335
|
-
* Render one frame to an RGBA framebuffer, shared by both output adapters.
|
|
336
|
-
*
|
|
337
|
-
* Uses the RenderSession's canvas (resized in place to the frame's physical
|
|
338
|
-
* size, cleared of the previous frame), adds a root scene, folds the frame
|
|
339
|
-
* onto it via `Renderer.render`, then update/draw/sync and reads the SW
|
|
340
|
-
* framebuffer. The scene and every painted shape are scoped per frame; the
|
|
341
|
-
* canvas belongs to the session (a player mount, an export run) and is
|
|
342
|
-
* deleted when the session closes.
|
|
343
|
-
*
|
|
344
|
-
* The background is painted as a filled rect (not a canvas clear color) so it
|
|
345
|
-
* survives into the buffer the same way the SVG sink emitted a background
|
|
346
|
-
* rect.
|
|
347
|
-
*/
|
|
348
|
-
export const render = (frame, options) => Effect.gen(function* () {
|
|
349
|
-
const logicalWidth = frame.width;
|
|
350
|
-
const logicalHeight = frame.height;
|
|
351
|
-
const dpr = options?.dpr ?? 1;
|
|
352
|
-
const width = Math.round(logicalWidth * dpr);
|
|
353
|
-
const height = Math.round(logicalHeight * dpr);
|
|
354
|
-
const canvas = yield* Tvg.Session.canvasSized(width, height);
|
|
355
|
-
const scene = yield* Tvg.Scene.make();
|
|
356
|
-
// background as a filled rect covering the viewport (mirrors the SVG
|
|
357
|
-
// sink's background rect; survives into the raster buffer)
|
|
358
|
-
const bg = yield* Tvg.Shape.make();
|
|
359
|
-
yield* Tvg.Shape.appendRect(bg, 0, 0, logicalWidth, logicalHeight);
|
|
360
|
-
const { r, g, b, a } = Color.bytes(frame.backgroundColor);
|
|
361
|
-
yield* Tvg.Shape.setFillColor(bg, r, g, b, a);
|
|
362
|
-
yield* Tvg.Scene.add(scene, bg);
|
|
363
|
-
yield* renderToCanvas(frame, canvas, scene, dpr);
|
|
364
|
-
// scale the whole subtree to physical pixels; children keep their own
|
|
365
|
-
// logical affines (parent transforms compose in the scene graph)
|
|
366
|
-
if (dpr !== 1) {
|
|
367
|
-
yield* Tvg.Paint.scale(scene, dpr);
|
|
368
|
-
}
|
|
369
|
-
yield* Tvg.Canvas.add(canvas, scene);
|
|
370
|
-
yield* Tvg.Canvas.update(canvas);
|
|
371
|
-
yield* Tvg.Canvas.draw(canvas);
|
|
372
|
-
yield* Tvg.Canvas.sync(canvas);
|
|
373
|
-
const buffer = yield* Tvg.Canvas.render(canvas);
|
|
374
|
-
return {
|
|
375
|
-
rgba: new Uint8Array(buffer),
|
|
376
|
-
width,
|
|
377
|
-
height,
|
|
378
|
-
logicalWidth,
|
|
379
|
-
logicalHeight,
|
|
380
|
-
};
|
|
381
|
-
});
|
package/dist/Shapes.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { Circle } from "./shapes/Circle.js";
|
|
2
|
-
export { Ellipse } from "./shapes/Ellipse.js";
|
|
3
|
-
export * from "./shapes/Group.js";
|
|
4
|
-
export { Hud } from "./shapes/Hud.js";
|
|
5
|
-
export { Image } from "./shapes/Image.js";
|
|
6
|
-
export { Line } from "./shapes/Line.js";
|
|
7
|
-
export { Path } from "./shapes/Path.js";
|
|
8
|
-
export { Rect } from "./shapes/Rect.js";
|
|
9
|
-
export * as Shape2D from "./shapes/Shape2D.js";
|
|
10
|
-
export { Square } from "./shapes/Square.js";
|
|
11
|
-
export { Text } from "./shapes/Text.js";
|
package/dist/Shapes.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { Circle } from "./shapes/Circle.js";
|
|
2
|
-
export { Ellipse } from "./shapes/Ellipse.js";
|
|
3
|
-
export * from "./shapes/Group.js";
|
|
4
|
-
export { Hud } from "./shapes/Hud.js";
|
|
5
|
-
export { Image } from "./shapes/Image.js";
|
|
6
|
-
export { Line } from "./shapes/Line.js";
|
|
7
|
-
export { Path } from "./shapes/Path.js";
|
|
8
|
-
export { Rect } from "./shapes/Rect.js";
|
|
9
|
-
export * as Shape2D from "./shapes/Shape2D.js";
|
|
10
|
-
export { Square } from "./shapes/Square.js";
|
|
11
|
-
export { Text } from "./shapes/Text.js";
|
package/dist/demo.d.ts
DELETED
package/dist/demo.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { NodeServices } from "@effect/platform-node";
|
|
2
|
-
import { Session } from "@effect-motion/thorvg";
|
|
3
|
-
import { EngineNode } from "@effect-motion/thorvg/node";
|
|
4
|
-
import { Effect, Layer, Schedule, Stream } from "effect";
|
|
5
|
-
import * as Color from "./Color.js";
|
|
6
|
-
import * as Motion from "./Motion.js";
|
|
7
|
-
import * as Physics from "./Physics.js";
|
|
8
|
-
import * as PngExporter from "./PngExporter.js";
|
|
9
|
-
import * as Renderer from "./Renderer.js";
|
|
10
|
-
import * as Scene from "./Scene.js";
|
|
11
|
-
import * as Shapes from "./Shapes.js";
|
|
12
|
-
// children live in the group's local coordinates: one motion moves them all
|
|
13
|
-
export const scene = Scene.make(function* () {
|
|
14
|
-
const duo = yield* Scene.instantiate(Shapes.Group, {
|
|
15
|
-
x: 70,
|
|
16
|
-
y: 200,
|
|
17
|
-
children: [
|
|
18
|
-
Scene.instantiate(Shapes.Circle, {
|
|
19
|
-
x: 0,
|
|
20
|
-
y: 0,
|
|
21
|
-
radius: 14,
|
|
22
|
-
fill: Color.hex("#e53170"),
|
|
23
|
-
}),
|
|
24
|
-
Scene.instantiate(Shapes.Square, {
|
|
25
|
-
x: 20,
|
|
26
|
-
y: -16,
|
|
27
|
-
size: 28,
|
|
28
|
-
fill: Color.hex("#a786df"),
|
|
29
|
-
}),
|
|
30
|
-
],
|
|
31
|
-
});
|
|
32
|
-
yield* Motion.wait("1.5 seconds");
|
|
33
|
-
yield* duo.pipe(Motion.moveTo({ x: 380 }, "1.5 seconds", "easeInOutCubic"), Motion.moveTo({ x: 70 }, "1.5 seconds", "easeInOutCubic"), Motion.wait("1.5 seconds"), Physics.springTo({ y: 80 }, "jump"), Motion.fadeTo(0.15, "1 second"));
|
|
34
|
-
});
|
|
35
|
-
// schedule-driven composition: a background pulse loops for the scene's
|
|
36
|
-
// duration while three staggered dots define its actual length
|
|
37
|
-
export const staggered = Scene.make(function* () {
|
|
38
|
-
const stage = yield* Scene.instantiate(Shapes.Group, { x: 70, y: 150 });
|
|
39
|
-
yield* Scene.background(Scene.repeat(stage.pipe(Motion.moveTo({ y: 130 }, "500 millis", "easeInOutCubic"), Motion.moveTo({ y: 150 }, "500 millis", "easeInOutCubic")), Schedule.forever));
|
|
40
|
-
const dot = (x) => Effect.gen(function* () {
|
|
41
|
-
const circle = yield* Scene.instantiate(Shapes.Circle, {
|
|
42
|
-
x,
|
|
43
|
-
y: 0,
|
|
44
|
-
radius: 10,
|
|
45
|
-
fill: Color.hex("#e53170"),
|
|
46
|
-
});
|
|
47
|
-
// reparent the freshly-born dot from root into the stage group
|
|
48
|
-
yield* Scene.appendChild(stage, circle);
|
|
49
|
-
yield* Motion.tweenTo(circle, { x: x + 300 }, "1 second", "easeInOutCubic");
|
|
50
|
-
});
|
|
51
|
-
yield* Scene.stagger([dot(0), dot(25), dot(50)], Schedule.spaced("250 millis"));
|
|
52
|
-
});
|
|
53
|
-
// render the middle frame of the duo scene to a PNG through the single ThorVG
|
|
54
|
-
// renderer (Node adapter) — the end-to-end path: Scene.stream → renderToPng.
|
|
55
|
-
const movie = Effect.gen(function* () {
|
|
56
|
-
const frames = yield* Scene.stream(scene, { width: 500, height: 300 }).pipe(Stream.runCollect);
|
|
57
|
-
const list = [...frames];
|
|
58
|
-
const mid = list[Math.floor(list.length / 2)];
|
|
59
|
-
const framebuffer = yield* Renderer.render(mid);
|
|
60
|
-
const _png = yield* PngExporter.toBuffer(framebuffer);
|
|
61
|
-
yield* PngExporter.toFile(framebuffer, "output.png");
|
|
62
|
-
});
|
|
63
|
-
Effect.runPromise(movie.pipe(Effect.scoped, Effect.provide(Layer.provideMerge(
|
|
64
|
-
// a demo scene with declared assets would add fonts/images maps here
|
|
65
|
-
Session.layer({ width: 500, height: 300 }), EngineNode.layer("sw"))), Effect.provide(NodeServices.layer)));
|
package/dist/render/dof.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
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;
|
package/dist/render/dof.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
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);
|
package/dist/render/paint.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { OwnedPaint, ThorvgException } from "@effect-motion/thorvg";
|
|
2
|
-
import * as Tvg from "@effect-motion/thorvg";
|
|
3
|
-
import * as Effect from "effect/Effect";
|
|
4
|
-
import type * as Scope from "effect/Scope";
|
|
5
|
-
import * as Color from "../Color.js";
|
|
6
|
-
import type { PaintProjection } from "../Renderer.js";
|
|
7
|
-
/**
|
|
8
|
-
* Shared paint helpers: style application, projection, and the attach step
|
|
9
|
-
* every leaf paint function runs. Keeping the ThorVG call sequence in one
|
|
10
|
-
* place means each shape's paint function is just "make geometry, then
|
|
11
|
-
* `finishPaint`".
|
|
12
|
-
*/
|
|
13
|
-
/** The style fields a filled/stroked shape carries in its data. */
|
|
14
|
-
export interface StyleData {
|
|
15
|
-
readonly fill?: Color.Color;
|
|
16
|
-
readonly stroke?: Color.Color;
|
|
17
|
-
readonly strokeWidth?: number;
|
|
18
|
-
readonly opacity: number;
|
|
19
|
-
}
|
|
20
|
-
/** Apply fill/stroke/opacity from shape data onto a ThorVG shape paint. */
|
|
21
|
-
export declare const applyStyle: (shape: OwnedPaint, data: StyleData) => Effect.Effect<void, ThorvgException, Tvg.ThorvgWasm>;
|
|
22
|
-
/**
|
|
23
|
-
* Apply the billboard projection affine to a shape, then add it to the scene.
|
|
24
|
-
* The identity affine (resting camera, z=0) is skipped so plain-2D scenes
|
|
25
|
-
* issue the minimal draw calls. Ownership transfers to the scene on add.
|
|
26
|
-
*
|
|
27
|
-
* Callers must NOT have applied any translate/rotate/scale to the shape —
|
|
28
|
-
* `setTransform` is the single, final transform (design D3).
|
|
29
|
-
*/
|
|
30
|
-
export declare const finishPaint: (shape: OwnedPaint, scene: OwnedPaint, projection: PaintProjection) => Effect.Effect<void, ThorvgException, Tvg.ThorvgWasm | Scope.Scope>;
|
package/dist/render/paint.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import * as Tvg from "@effect-motion/thorvg";
|
|
2
|
-
import * as Effect from "effect/Effect";
|
|
3
|
-
import * as Color from "../Color.js";
|
|
4
|
-
/** Apply fill/stroke/opacity from shape data onto a ThorVG shape paint. */
|
|
5
|
-
export const applyStyle = (shape, data) => Effect.gen(function* () {
|
|
6
|
-
if (data.fill !== undefined) {
|
|
7
|
-
const { r, g, b, a } = Color.bytes(data.fill);
|
|
8
|
-
yield* Tvg.Shape.setFillColor(shape, r, g, b, a);
|
|
9
|
-
}
|
|
10
|
-
if (data.stroke !== undefined) {
|
|
11
|
-
const { r, g, b, a } = Color.bytes(data.stroke);
|
|
12
|
-
yield* Tvg.Shape.setStrokeColor(shape, r, g, b, a);
|
|
13
|
-
}
|
|
14
|
-
if (data.strokeWidth !== undefined) {
|
|
15
|
-
yield* Tvg.Shape.setStrokeWidth(shape, data.strokeWidth);
|
|
16
|
-
}
|
|
17
|
-
// opacity 1 is the ThorVG default; only set when it changes something
|
|
18
|
-
if (data.opacity !== 1) {
|
|
19
|
-
yield* Tvg.Paint.setOpacity(shape, Math.round(data.opacity * 255));
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
const isIdentity = (m) => m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0;
|
|
23
|
-
/**
|
|
24
|
-
* Apply the billboard projection affine to a shape, then add it to the scene.
|
|
25
|
-
* The identity affine (resting camera, z=0) is skipped so plain-2D scenes
|
|
26
|
-
* issue the minimal draw calls. Ownership transfers to the scene on add.
|
|
27
|
-
*
|
|
28
|
-
* Callers must NOT have applied any translate/rotate/scale to the shape —
|
|
29
|
-
* `setTransform` is the single, final transform (design D3).
|
|
30
|
-
*/
|
|
31
|
-
export const finishPaint = (shape, scene, projection) => Effect.gen(function* () {
|
|
32
|
-
if (!isIdentity(projection.screen)) {
|
|
33
|
-
yield* Tvg.Paint.setTransform(shape, projection.screen);
|
|
34
|
-
}
|
|
35
|
-
yield* Tvg.Scene.add(scene, shape);
|
|
36
|
-
});
|
package/dist/render/shapes.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { ParticleField } from "../particles/ParticleField.js";
|
|
2
|
-
import type { PaintFunction, PaintFunctions } from "../Renderer.js";
|
|
3
|
-
import * as Circle from "../shapes/Circle.js";
|
|
4
|
-
import * as Ellipse from "../shapes/Ellipse.js";
|
|
5
|
-
import * as Group from "../shapes/Group.js";
|
|
6
|
-
import * as Hud from "../shapes/Hud.js";
|
|
7
|
-
import * as Image from "../shapes/Image.js";
|
|
8
|
-
import * as Line from "../shapes/Line.js";
|
|
9
|
-
import * as Path from "../shapes/Path.js";
|
|
10
|
-
import * as Rect from "../shapes/Rect.js";
|
|
11
|
-
import * as Square from "../shapes/Square.js";
|
|
12
|
-
import * as Text from "../shapes/Text.js";
|
|
13
|
-
/**
|
|
14
|
-
* ThorVG paint functions for the built-in shapes — this file is the coverage
|
|
15
|
-
* manifest: `paints` is typed `PaintFunctions<...>`, so a built-in missing a
|
|
16
|
-
* paint function is a type error, not a runtime surprise.
|
|
17
|
-
*/
|
|
18
|
-
export declare const circle: PaintFunction<typeof Circle.Circle>;
|
|
19
|
-
export declare const rect: PaintFunction<typeof Rect.Rect>;
|
|
20
|
-
export declare const square: PaintFunction<typeof Square.Square>;
|
|
21
|
-
export declare const ellipse: PaintFunction<typeof Ellipse.Ellipse>;
|
|
22
|
-
export declare const line: PaintFunction<typeof Line.Line>;
|
|
23
|
-
export declare const path: PaintFunction<typeof Path.Path>;
|
|
24
|
-
export declare const group: PaintFunction<typeof Group.Group>;
|
|
25
|
-
export declare const hud: PaintFunction<typeof Hud.Hud>;
|
|
26
|
-
export declare const image: PaintFunction<typeof Image.Image>;
|
|
27
|
-
export declare const text: PaintFunction<typeof Text.Text>;
|
|
28
|
-
/**
|
|
29
|
-
* ParticleField: live particles batched into one shape PER (color, opacity)
|
|
30
|
-
* bucket — ThorVG fills a shape uniformly, so particles that share a color and
|
|
31
|
-
* (quantized) over-life opacity are appended into the same shape and filled
|
|
32
|
-
* once. Sized/faded by the over-life curves; dead slots emit nothing.
|
|
33
|
-
* ponytail: one shape per distinct (color, opacity-bucket) — far fewer than one
|
|
34
|
-
* per particle, but a fully instanced path is the upgrade if this is a wall.
|
|
35
|
-
*/
|
|
36
|
-
export declare const particleField: PaintFunction<typeof ParticleField>;
|
|
37
|
-
/**
|
|
38
|
-
* The exhaustive paint-function map for every built-in entity. Typed
|
|
39
|
-
* `PaintFunctions<...>` so a missing built-in fails to type-check (the old
|
|
40
|
-
* "coverage manifest" guarantee, without a Context registry).
|
|
41
|
-
*/
|
|
42
|
-
export declare const builtinPaints: PaintFunctions<typeof Circle.Circle | typeof Rect.Rect | typeof Square.Square | typeof Ellipse.Ellipse | typeof Line.Line | typeof Path.Path | typeof Group.Group | typeof Hud.Hud | typeof Text.Text | typeof Image.Image | typeof ParticleField>;
|