reframe-video 0.1.3 → 0.3.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/assets/sfx/LICENSE.md +2 -1
- package/assets/sfx/bong_001.ogg +0 -0
- package/assets/sfx/click_001.ogg +0 -0
- package/assets/sfx/confirmation_002.ogg +0 -0
- package/assets/sfx/confirmation_003.ogg +0 -0
- package/assets/sfx/confirmation_004.ogg +0 -0
- package/assets/sfx/footstep_001.ogg +0 -0
- package/assets/sfx/footstep_002.ogg +0 -0
- package/assets/sfx/footstep_003.ogg +0 -0
- package/assets/sfx/glass_001.ogg +0 -0
- package/assets/sfx/maximize_001.ogg +0 -0
- package/assets/sfx/maximize_002.ogg +0 -0
- package/assets/sfx/maximize_005.ogg +0 -0
- package/assets/sfx/maximize_009.ogg +0 -0
- package/assets/sfx/open_001.ogg +0 -0
- package/assets/sfx/pluck_001.ogg +0 -0
- package/assets/sfx/pluck_002.ogg +0 -0
- package/assets/sfx/select_001.ogg +0 -0
- package/assets/sfx/select_002.ogg +0 -0
- package/assets/sfx/select_003.ogg +0 -0
- package/dist/bin.js +271 -49
- package/dist/browserEntry.js +179 -68
- package/dist/cli.js +445 -85
- package/dist/index.js +1187 -116
- package/dist/labels.js +606 -0
- package/dist/renderer-canvas.js +15 -0
- package/dist/trace-cli.js +9 -9
- package/dist/types/audio.d.ts +9 -0
- package/dist/types/characterPreset.d.ts +39 -0
- package/dist/types/compile.d.ts +1 -0
- package/dist/types/compose.d.ts +18 -2
- package/dist/types/composeComposition.d.ts +27 -0
- package/dist/types/devicePreset.d.ts +65 -0
- package/dist/types/dsl.d.ts +12 -1
- package/dist/types/evaluate.d.ts +32 -0
- package/dist/types/figure.d.ts +32 -0
- package/dist/types/index.d.ts +9 -3
- package/dist/types/interpolate.d.ts +3 -2
- package/dist/types/ir.d.ts +68 -0
- package/dist/types/motionOps.d.ts +36 -0
- package/dist/types/path.d.ts +7 -3
- package/dist/types/rig.d.ts +87 -0
- package/dist/types/validate.d.ts +4 -1
- package/guides/edsl-guide.md +54 -1
- package/guides/regen-contract.md +11 -0
- package/package.json +1 -1
- package/preview/index.html +56 -3
- package/preview/src/main.ts +1132 -46
- package/preview/src/panel.ts +478 -8
- package/preview/src/store.ts +323 -6
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// ../core/src/ir.ts
|
|
2
|
+
var DEFAULT_CROSSFADE = 0.5;
|
|
2
3
|
var DEFAULT_TO_DURATION = 0.5;
|
|
3
4
|
var DEFAULT_TWEEN_DURATION = 0.5;
|
|
4
5
|
var DEFAULT_MOTIONPATH_DURATION = 1;
|
|
@@ -14,40 +15,62 @@ function locate(segCount, u) {
|
|
|
14
15
|
return { i, t: scaled - i };
|
|
15
16
|
}
|
|
16
17
|
function controls(points, closed, i) {
|
|
17
|
-
const
|
|
18
|
+
const n3 = points.length;
|
|
18
19
|
const at = (k) => {
|
|
19
|
-
if (closed) return points[(k %
|
|
20
|
-
return points[Math.max(0, Math.min(
|
|
20
|
+
if (closed) return points[(k % n3 + n3) % n3];
|
|
21
|
+
return points[Math.max(0, Math.min(n3 - 1, k))];
|
|
21
22
|
};
|
|
22
23
|
return [at(i - 1), at(i), at(i + 1), at(i + 2)];
|
|
23
24
|
}
|
|
24
25
|
function segCountOf(points, closed) {
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
return closed ?
|
|
28
|
-
}
|
|
29
|
-
function pathPoint(points, closed, u) {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
32
|
-
if (
|
|
26
|
+
const n3 = points.length;
|
|
27
|
+
if (n3 < 2) return 0;
|
|
28
|
+
return closed ? n3 : n3 - 1;
|
|
29
|
+
}
|
|
30
|
+
function pathPoint(points, closed, u, curviness = 1) {
|
|
31
|
+
const n3 = points.length;
|
|
32
|
+
if (n3 === 0) return [0, 0];
|
|
33
|
+
if (n3 === 1) return [points[0][0], points[0][1]];
|
|
33
34
|
const segs = segCountOf(points, closed);
|
|
34
35
|
const { i, t } = locate(segs, u);
|
|
35
36
|
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
36
37
|
const t2 = t * t;
|
|
37
38
|
const t3 = t2 * t;
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
if (curviness === 1) {
|
|
40
|
+
const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
|
|
41
|
+
return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
|
|
42
|
+
}
|
|
43
|
+
const h00 = 2 * t3 - 3 * t2 + 1;
|
|
44
|
+
const h10 = t3 - 2 * t2 + t;
|
|
45
|
+
const h01 = -2 * t3 + 3 * t2;
|
|
46
|
+
const h11 = t3 - t2;
|
|
47
|
+
const k = curviness * 0.5;
|
|
48
|
+
const H = (a, b, c, d) => h00 * b + h10 * k * (c - a) + h01 * c + h11 * k * (d - b);
|
|
49
|
+
return [H(p0[0], p1[0], p2[0], p3[0]), H(p0[1], p1[1], p2[1], p3[1])];
|
|
40
50
|
}
|
|
41
|
-
function pathTangentAngle(points, closed, u) {
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
51
|
+
function pathTangentAngle(points, closed, u, curviness = 1) {
|
|
52
|
+
const n3 = points.length;
|
|
53
|
+
if (n3 < 2) return 0;
|
|
44
54
|
const segs = segCountOf(points, closed);
|
|
45
55
|
const { i, t } = locate(segs, u);
|
|
46
56
|
const [p0, p1, p2, p3] = controls(points, closed, i);
|
|
47
57
|
const t2 = t * t;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
let dx;
|
|
59
|
+
let dy;
|
|
60
|
+
if (curviness === 1) {
|
|
61
|
+
const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
|
|
62
|
+
dx = d(p0[0], p1[0], p2[0], p3[0]);
|
|
63
|
+
dy = d(p0[1], p1[1], p2[1], p3[1]);
|
|
64
|
+
} else {
|
|
65
|
+
const g00 = 6 * t2 - 6 * t;
|
|
66
|
+
const g10 = 3 * t2 - 4 * t + 1;
|
|
67
|
+
const g01 = -6 * t2 + 6 * t;
|
|
68
|
+
const g11 = 3 * t2 - 2 * t;
|
|
69
|
+
const k = curviness * 0.5;
|
|
70
|
+
const D = (a, b, c, e) => g00 * b + g10 * k * (c - a) + g01 * c + g11 * k * (e - b);
|
|
71
|
+
dx = D(p0[0], p1[0], p2[0], p3[0]);
|
|
72
|
+
dy = D(p0[1], p1[1], p2[1], p3[1]);
|
|
73
|
+
}
|
|
51
74
|
if (dx === 0 && dy === 0) return 0;
|
|
52
75
|
return Math.atan2(dy, dx) * 180 / Math.PI;
|
|
53
76
|
}
|
|
@@ -124,8 +147,8 @@ function compileScene(ir) {
|
|
|
124
147
|
const currentValue = (target, prop) => {
|
|
125
148
|
const v = current.get(key(target, prop));
|
|
126
149
|
if (v !== void 0) return v;
|
|
127
|
-
if (prop === "opacity" || prop === "scale" || prop === "progress") return 1;
|
|
128
|
-
if (prop === "rotation") return 0;
|
|
150
|
+
if (prop === "opacity" || prop === "scale" || prop === "progress" || prop === "scaleX" || prop === "scaleY") return 1;
|
|
151
|
+
if (prop === "rotation" || prop === "skewX" || prop === "skewY") return 0;
|
|
129
152
|
throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
|
|
130
153
|
};
|
|
131
154
|
const labelTimes = /* @__PURE__ */ new Map();
|
|
@@ -228,16 +251,17 @@ function compileScene(ir) {
|
|
|
228
251
|
const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
|
|
229
252
|
const points = tl.points;
|
|
230
253
|
const closed = tl.closed ?? false;
|
|
254
|
+
const curviness = tl.curviness ?? 1;
|
|
231
255
|
const autoRotate = tl.autoRotate ?? false;
|
|
232
256
|
const rotateOffset = tl.rotateOffset ?? 0;
|
|
233
257
|
let list = motionPaths.get(tl.target);
|
|
234
258
|
if (!list) motionPaths.set(tl.target, list = []);
|
|
235
|
-
list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
259
|
+
list.push({ t0: start, t1: start + duration, points, closed, curviness, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
|
|
236
260
|
if (points.length > 0) {
|
|
237
|
-
const [ex, ey] = pathPoint(points, closed, 1);
|
|
261
|
+
const [ex, ey] = pathPoint(points, closed, 1, curviness);
|
|
238
262
|
current.set(key(tl.target, "x"), ex);
|
|
239
263
|
current.set(key(tl.target, "y"), ey);
|
|
240
|
-
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
|
|
264
|
+
if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1, curviness) + rotateOffset);
|
|
241
265
|
}
|
|
242
266
|
return start + duration;
|
|
243
267
|
}
|
|
@@ -284,7 +308,7 @@ function compileScene(ir) {
|
|
|
284
308
|
}
|
|
285
309
|
|
|
286
310
|
// ../core/src/validate.ts
|
|
287
|
-
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "anchor"];
|
|
311
|
+
var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor"];
|
|
288
312
|
var PROPS_BY_TYPE = {
|
|
289
313
|
rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
|
|
290
314
|
ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
|
|
@@ -312,7 +336,18 @@ function validateScene(ir) {
|
|
|
312
336
|
problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
|
|
313
337
|
}
|
|
314
338
|
nodeById.set(node.id, node);
|
|
315
|
-
if (node.type === "group")
|
|
339
|
+
if (node.type === "group") {
|
|
340
|
+
const clip = node.props.clip;
|
|
341
|
+
if (clip) {
|
|
342
|
+
if (clip.kind !== "rect" && clip.kind !== "ellipse") {
|
|
343
|
+
problems.push(`group "${node.id}" clip: unknown kind "${clip.kind}" \u2014 use "rect" or "ellipse"`);
|
|
344
|
+
}
|
|
345
|
+
if (!(clip.width > 0) || !(clip.height > 0)) {
|
|
346
|
+
problems.push(`group "${node.id}" clip: width and height must be > 0`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
collect(node.children);
|
|
350
|
+
}
|
|
316
351
|
}
|
|
317
352
|
};
|
|
318
353
|
collect(ir.nodes);
|
|
@@ -395,6 +430,9 @@ function validateScene(ir) {
|
|
|
395
430
|
if (tl.duration !== void 0 && tl.duration <= 0) {
|
|
396
431
|
problems.push(`${path2}: motionPath "${tl.target}" duration must be > 0`);
|
|
397
432
|
}
|
|
433
|
+
if (tl.curviness !== void 0 && tl.curviness < 0) {
|
|
434
|
+
problems.push(`${path2}: motionPath "${tl.target}" curviness must be >= 0`);
|
|
435
|
+
}
|
|
398
436
|
break;
|
|
399
437
|
}
|
|
400
438
|
case "wait":
|
|
@@ -413,6 +451,13 @@ function validateScene(ir) {
|
|
|
413
451
|
if (tl.scale !== void 0 && tl.scale <= 0) {
|
|
414
452
|
problems.push(`${path2}: beat "${tl.name}" scale must be > 0`);
|
|
415
453
|
}
|
|
454
|
+
for (const id of tl.nodes ?? []) {
|
|
455
|
+
if (!nodeById.has(id)) {
|
|
456
|
+
problems.push(
|
|
457
|
+
`${path2}: beat "${tl.name}" owns unknown node "${id}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
416
461
|
tl.children.forEach((c, i) => checkTimeline(c, `${path2}.beat(${tl.name})[${i}]`));
|
|
417
462
|
break;
|
|
418
463
|
}
|
|
@@ -453,6 +498,36 @@ function validateScene(ir) {
|
|
|
453
498
|
}
|
|
454
499
|
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
455
500
|
}
|
|
501
|
+
var TRANSITIONS = ["cut", "crossfade"];
|
|
502
|
+
function validateComposition(comp) {
|
|
503
|
+
const problems = [];
|
|
504
|
+
if (comp.scenes.length === 0) problems.push("composition has no scenes");
|
|
505
|
+
const seen = /* @__PURE__ */ new Set();
|
|
506
|
+
for (const [i, entry] of comp.scenes.entries()) {
|
|
507
|
+
const where = `scenes[${i}]`;
|
|
508
|
+
try {
|
|
509
|
+
validateScene(entry.scene);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
if (err instanceof SceneValidationError) {
|
|
512
|
+
for (const p of err.problems) problems.push(`${where} (scene "${entry.scene.id}"): ${p}`);
|
|
513
|
+
} else throw err;
|
|
514
|
+
}
|
|
515
|
+
if (seen.has(entry.scene.id)) {
|
|
516
|
+
problems.push(`${where}: duplicate scene id "${entry.scene.id}" \u2014 scene ids must be unique in a composition`);
|
|
517
|
+
}
|
|
518
|
+
seen.add(entry.scene.id);
|
|
519
|
+
if (entry.transition !== void 0 && !TRANSITIONS.includes(entry.transition)) {
|
|
520
|
+
problems.push(`${where}: unknown transition "${entry.transition}" \u2014 valid: ${TRANSITIONS.join(", ")}`);
|
|
521
|
+
}
|
|
522
|
+
if (typeof entry.at === "string" && Number.isNaN(Number(entry.at))) {
|
|
523
|
+
problems.push(`${where}: "at" string "${entry.at}" is not a number (use "-0.5"/"+0.5" or a number)`);
|
|
524
|
+
}
|
|
525
|
+
if (typeof entry.at === "number" && entry.at < 0) {
|
|
526
|
+
problems.push(`${where}: absolute "at" must be >= 0`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (problems.length > 0) throw new SceneValidationError(problems);
|
|
530
|
+
}
|
|
456
531
|
|
|
457
532
|
// ../core/src/dsl.ts
|
|
458
533
|
function scene(input) {
|
|
@@ -463,6 +538,11 @@ function scene(input) {
|
|
|
463
538
|
}
|
|
464
539
|
return ir;
|
|
465
540
|
}
|
|
541
|
+
function composition(input) {
|
|
542
|
+
const ir = { version: 1, ...input };
|
|
543
|
+
validateComposition(ir);
|
|
544
|
+
return ir;
|
|
545
|
+
}
|
|
466
546
|
function rect(props) {
|
|
467
547
|
const { id, ...rest } = props;
|
|
468
548
|
return { type: "rect", id, props: rest };
|
|
@@ -522,11 +602,47 @@ function wiggle(target, prop, params, window = {}) {
|
|
|
522
602
|
return { target, prop, ...window, behavior: { kind: "named", name: "wiggle", params } };
|
|
523
603
|
}
|
|
524
604
|
|
|
605
|
+
// ../core/src/composeComposition.ts
|
|
606
|
+
function compileComposition(comp) {
|
|
607
|
+
const scenes = [];
|
|
608
|
+
let prevEnd = 0;
|
|
609
|
+
comp.scenes.forEach((entry, i) => {
|
|
610
|
+
const compiled = compileScene(entry.scene);
|
|
611
|
+
const duration2 = compiled.duration;
|
|
612
|
+
const transition = entry.transition ?? "cut";
|
|
613
|
+
const append = i === 0 ? 0 : prevEnd;
|
|
614
|
+
let start;
|
|
615
|
+
if (typeof entry.at === "number") {
|
|
616
|
+
start = entry.at;
|
|
617
|
+
} else if (typeof entry.at === "string") {
|
|
618
|
+
start = append + Number(entry.at);
|
|
619
|
+
} else if (transition === "crossfade" && i > 0) {
|
|
620
|
+
start = append - DEFAULT_CROSSFADE;
|
|
621
|
+
} else {
|
|
622
|
+
start = append;
|
|
623
|
+
}
|
|
624
|
+
start = Math.max(0, start);
|
|
625
|
+
const overlap = i > 0 ? Math.max(0, prevEnd - start) : 0;
|
|
626
|
+
scenes.push({ id: entry.scene.id, scene: entry.scene, compiled, start, duration: duration2, transition, overlap });
|
|
627
|
+
prevEnd = start + duration2;
|
|
628
|
+
});
|
|
629
|
+
const duration = scenes.reduce((max, s) => Math.max(max, s.start + s.duration), 0);
|
|
630
|
+
return { ir: comp, scenes, duration };
|
|
631
|
+
}
|
|
632
|
+
|
|
525
633
|
// ../core/src/compose.ts
|
|
526
634
|
var SCENE_PATCHABLE = ["background", "duration", "fps"];
|
|
527
635
|
function composeScene(base, ...overlays) {
|
|
528
636
|
const ir = structuredClone(base);
|
|
529
637
|
const report = { applied: [], orphans: [], warnings: [] };
|
|
638
|
+
const baseNodeIds = /* @__PURE__ */ new Set();
|
|
639
|
+
const collectBase = (nodes) => {
|
|
640
|
+
for (const node of nodes) {
|
|
641
|
+
baseNodeIds.add(node.id);
|
|
642
|
+
if (node.type === "group") collectBase(node.children);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
645
|
+
collectBase(base.nodes);
|
|
530
646
|
overlays.forEach((overlay, index) => {
|
|
531
647
|
const layer = overlay.name ?? `overlay-${index}`;
|
|
532
648
|
if (overlay.target !== void 0 && overlay.target !== ir.id) {
|
|
@@ -534,12 +650,12 @@ function composeScene(base, ...overlays) {
|
|
|
534
650
|
`${layer}: authored against scene "${overlay.target}" but composing onto "${ir.id}"`
|
|
535
651
|
);
|
|
536
652
|
}
|
|
537
|
-
applyOverlay(ir, overlay, layer, report);
|
|
653
|
+
applyOverlay(ir, overlay, layer, report, baseNodeIds);
|
|
538
654
|
});
|
|
539
655
|
validateScene(ir);
|
|
540
656
|
return { ir, report };
|
|
541
657
|
}
|
|
542
|
-
function applyOverlay(ir, overlay, layer, report) {
|
|
658
|
+
function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
|
|
543
659
|
const nodeById = /* @__PURE__ */ new Map();
|
|
544
660
|
const collect = (nodes) => {
|
|
545
661
|
for (const node of nodes) {
|
|
@@ -654,7 +770,7 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
654
770
|
to: ["duration", "ease", "stagger"],
|
|
655
771
|
tween: ["duration", "ease"],
|
|
656
772
|
wait: ["duration"],
|
|
657
|
-
motionPath: ["points", "duration", "ease"],
|
|
773
|
+
motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
|
|
658
774
|
beat: ["at", "gap", "scale", "duration", "order"]
|
|
659
775
|
};
|
|
660
776
|
let timingPatched = false;
|
|
@@ -692,6 +808,49 @@ function applyOverlay(ir, overlay, layer, report) {
|
|
|
692
808
|
nodeById.set(node.id, node);
|
|
693
809
|
applied(`addNodes.${node.id}`, "add-node");
|
|
694
810
|
}
|
|
811
|
+
for (const id of overlay.removeNodes ?? []) {
|
|
812
|
+
if (baseNodeIds.has(id)) {
|
|
813
|
+
orphan(
|
|
814
|
+
`removeNodes.${id}`,
|
|
815
|
+
`"${id}" is a base scene node \u2014 the scene owns it; hide it with opacity: 0 instead of removing`
|
|
816
|
+
);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
const index = ir.nodes.findIndex((n3) => n3.id === id);
|
|
820
|
+
if (index < 0) {
|
|
821
|
+
orphan(
|
|
822
|
+
`removeNodes.${id}`,
|
|
823
|
+
`unknown overlay-added node "${id}" \u2014 nothing to remove`
|
|
824
|
+
);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
ir.nodes.splice(index, 1);
|
|
828
|
+
nodeById.delete(id);
|
|
829
|
+
applied(`removeNodes.${id}`, "remove-node");
|
|
830
|
+
}
|
|
831
|
+
if (overlay.addTimeline && overlay.addTimeline.length > 0) {
|
|
832
|
+
const collectTargets = (tl, out) => {
|
|
833
|
+
if (tl.kind === "tween" || tl.kind === "motionPath") out.add(tl.target);
|
|
834
|
+
if ("children" in tl) tl.children.forEach((c) => collectTargets(c, out));
|
|
835
|
+
};
|
|
836
|
+
const valid = [];
|
|
837
|
+
overlay.addTimeline.forEach((frag, i) => {
|
|
838
|
+
const targets = /* @__PURE__ */ new Set();
|
|
839
|
+
collectTargets(frag, targets);
|
|
840
|
+
const missing = [...targets].filter((id) => !nodeById.has(id));
|
|
841
|
+
if (missing.length > 0) {
|
|
842
|
+
orphan(`addTimeline[${i}]`, `targets unknown node(s) ${missing.join(", ")} \u2014 known ids: ${knownIds()}`);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
valid.push(structuredClone(frag));
|
|
846
|
+
applied(`addTimeline[${i}]`, "add-timeline");
|
|
847
|
+
});
|
|
848
|
+
if (valid.length > 0) {
|
|
849
|
+
ir.timeline = ir.timeline ? { kind: "par", children: [ir.timeline, ...valid] } : valid.length === 1 ? valid[0] : { kind: "par", children: valid };
|
|
850
|
+
delete ir.duration;
|
|
851
|
+
ir.duration = compileScene(ir).duration;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
695
854
|
}
|
|
696
855
|
function formatComposeReport(report) {
|
|
697
856
|
const lines = [];
|
|
@@ -884,6 +1043,735 @@ function motionPreset(name, opts) {
|
|
|
884
1043
|
}
|
|
885
1044
|
}
|
|
886
1045
|
|
|
1046
|
+
// ../core/src/devicePreset.ts
|
|
1047
|
+
var DEVICE_PRESET_NAMES = ["phone", "tablet", "laptop", "browser", "watch", "monitor", "tv", "foldable", "terminal", "car"];
|
|
1048
|
+
var DARK = { body: "#15161C", bodyStroke: "#2A2D38", screen: "#0E0F15", detail: "#3A3D48", chrome: "#1B1D24", chromeText: "#9AA0AD" };
|
|
1049
|
+
var LIGHT = { body: "#E7E9EE", bodyStroke: "#C3C7D1", screen: "#FFFFFF", detail: "#AEB3C0", chrome: "#F2F3F6", chromeText: "#5B606C" };
|
|
1050
|
+
var SCREENS = {
|
|
1051
|
+
phone: { width: 352, height: 736, radius: 38 },
|
|
1052
|
+
tablet: { width: 544, height: 764, radius: 18 },
|
|
1053
|
+
laptop: { width: 840, height: 520, radius: 8, cy: -150 },
|
|
1054
|
+
browser: { width: 984, height: 568, radius: 6, cy: 24 },
|
|
1055
|
+
watch: { width: 184, height: 224, radius: 44 },
|
|
1056
|
+
monitor: { width: 1056, height: 600, radius: 6 },
|
|
1057
|
+
tv: { width: 1280, height: 720, radius: 8, cy: -24 },
|
|
1058
|
+
foldable: { width: 760, height: 560, radius: 20 },
|
|
1059
|
+
terminal: { width: 900, height: 560, radius: 6, cy: 18 },
|
|
1060
|
+
car: { width: 1e3, height: 520, radius: 24 }
|
|
1061
|
+
};
|
|
1062
|
+
var BOUNDS = {
|
|
1063
|
+
phone: { width: 392, height: 812 },
|
|
1064
|
+
tablet: { width: 600, height: 820 },
|
|
1065
|
+
laptop: { width: 1100, height: 650 },
|
|
1066
|
+
browser: { width: 1e3, height: 660 },
|
|
1067
|
+
watch: { width: 220, height: 300 },
|
|
1068
|
+
monitor: { width: 1120, height: 860 },
|
|
1069
|
+
tv: { width: 1340, height: 920 },
|
|
1070
|
+
foldable: { width: 800, height: 600 },
|
|
1071
|
+
terminal: { width: 916, height: 636 },
|
|
1072
|
+
car: { width: 1060, height: 600 }
|
|
1073
|
+
};
|
|
1074
|
+
var isLandscape = (name, o) => (name === "phone" || name === "tablet") && o.orientation === "landscape";
|
|
1075
|
+
function screenDims(name, o) {
|
|
1076
|
+
const d = SCREENS[name];
|
|
1077
|
+
const base = { cx: d.cx ?? 0, cy: d.cy ?? 0 };
|
|
1078
|
+
return isLandscape(name, o) ? { width: d.height, height: d.width, radius: d.radius, ...base } : { width: d.width, height: d.height, radius: d.radius, ...base };
|
|
1079
|
+
}
|
|
1080
|
+
function deviceScreen(name, opts = {}) {
|
|
1081
|
+
const d = screenDims(name, opts);
|
|
1082
|
+
return { x: 0, y: 0, width: d.width, height: d.height, radius: d.radius };
|
|
1083
|
+
}
|
|
1084
|
+
function deviceScreenCenter(name, opts = {}) {
|
|
1085
|
+
const d = screenDims(name, opts);
|
|
1086
|
+
return { x: d.cx, y: d.cy };
|
|
1087
|
+
}
|
|
1088
|
+
function deviceBounds(name, opts = {}) {
|
|
1089
|
+
const b = BOUNDS[name];
|
|
1090
|
+
return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
|
|
1091
|
+
}
|
|
1092
|
+
function screenGroup(id, p, o, cx, cy, dims, content) {
|
|
1093
|
+
return group({ id: `${id}-screen`, x: cx, y: cy, clip: { kind: "rect", x: -dims.width / 2, y: -dims.height / 2, width: dims.width, height: dims.height, radius: dims.radius } }, [
|
|
1094
|
+
rect({ id: `${id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: o.screen ?? p.screen }),
|
|
1095
|
+
group({ id: `${id}-content`, x: 0, y: 0 }, content)
|
|
1096
|
+
]);
|
|
1097
|
+
}
|
|
1098
|
+
function buildDevice(name, id, p, o, content) {
|
|
1099
|
+
const dims = screenDims(name, o);
|
|
1100
|
+
const sw = dims.width;
|
|
1101
|
+
const sh = dims.height;
|
|
1102
|
+
const screen = () => screenGroup(id, p, o, dims.cx, dims.cy, dims, content);
|
|
1103
|
+
switch (name) {
|
|
1104
|
+
case "phone":
|
|
1105
|
+
case "tablet": {
|
|
1106
|
+
const bezel = name === "phone" ? 20 : 28;
|
|
1107
|
+
const bodyW = sw + bezel * 2;
|
|
1108
|
+
const bodyH = sh + bezel * 2;
|
|
1109
|
+
const bodyR = name === "phone" ? 54 : 34;
|
|
1110
|
+
const land = isLandscape(name, o);
|
|
1111
|
+
const nodes = [
|
|
1112
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: bodyR }),
|
|
1113
|
+
screen()
|
|
1114
|
+
];
|
|
1115
|
+
if (name === "phone") {
|
|
1116
|
+
nodes.push(
|
|
1117
|
+
land ? rect({ id: `${id}-notch`, x: -sw / 2 + 16, y: 0, anchor: "center", width: 30, height: 96, fill: "#000000", radius: 15 }) : rect({ id: `${id}-notch`, x: 0, y: -sh / 2 + 16, anchor: "center", width: 96, height: 30, fill: "#000000", radius: 15 }),
|
|
1118
|
+
land ? rect({ id: `${id}-home`, x: sw / 2 - 4, y: 0, anchor: "center", width: 5, height: 120, fill: p.detail, radius: 3 }) : rect({ id: `${id}-home`, x: 0, y: sh / 2 - 18, anchor: "center", width: 120, height: 5, fill: p.detail, radius: 3 })
|
|
1119
|
+
);
|
|
1120
|
+
if (!land) {
|
|
1121
|
+
nodes.push(
|
|
1122
|
+
rect({ id: `${id}-pwr`, x: bodyW / 2, y: -bodyH * 0.1, anchor: "center", width: 4, height: 78, fill: p.detail, radius: 2 }),
|
|
1123
|
+
rect({ id: `${id}-volup`, x: -bodyW / 2, y: -bodyH * 0.16, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 }),
|
|
1124
|
+
rect({ id: `${id}-voldn`, x: -bodyW / 2, y: -bodyH * 0.16 + 60, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 })
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
} else {
|
|
1128
|
+
nodes.push(
|
|
1129
|
+
rect({ id: `${id}-camera`, x: land ? -sw / 2 - 14 : 0, y: land ? 0 : -sh / 2 - 14, anchor: "center", width: 8, height: 8, fill: p.detail, radius: 4 }),
|
|
1130
|
+
rect({ id: `${id}-pwr`, x: land ? -bodyW * 0.18 : bodyW * 0.18, y: land ? -bodyH / 2 : -bodyH / 2, anchor: "center", width: 60, height: 4, fill: p.detail, radius: 2 })
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
return nodes;
|
|
1134
|
+
}
|
|
1135
|
+
case "laptop": {
|
|
1136
|
+
const lidTop = dims.cy - (sh + 40) / 2;
|
|
1137
|
+
const keyRows = [0, 1, 2, 3].map(
|
|
1138
|
+
(r) => rect({ id: `${id}-keys${r}`, x: 0, y: 150 + r * 11, anchor: "center", width: 640 + r * 50, height: 6, fill: p.chrome, radius: 3 })
|
|
1139
|
+
);
|
|
1140
|
+
return [
|
|
1141
|
+
path({ id: `${id}-base`, x: 0, y: 0, d: "M -450 140 L 450 140 L 520 196 L -520 196 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
1142
|
+
rect({ id: `${id}-foot-l`, x: -360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
1143
|
+
rect({ id: `${id}-foot-r`, x: 360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
|
|
1144
|
+
...keyRows,
|
|
1145
|
+
rect({ id: `${id}-trackpad`, x: 0, y: 184, anchor: "center", width: 150, height: 8, fill: p.detail, radius: 4 }),
|
|
1146
|
+
rect({ id: `${id}-hinge`, x: 0, y: 134, anchor: "center", width: 900, height: 10, fill: p.detail, radius: 5 }),
|
|
1147
|
+
screen(),
|
|
1148
|
+
ellipse({ id: `${id}-webcam`, x: 0, y: lidTop + 14, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
1149
|
+
rect({ id: `${id}-lid`, x: 0, y: dims.cy, anchor: "center", width: sw + 40, height: sh + 40, stroke: p.bodyStroke, strokeWidth: 2, radius: 18 })
|
|
1150
|
+
];
|
|
1151
|
+
}
|
|
1152
|
+
case "browser": {
|
|
1153
|
+
const winW = sw + 16;
|
|
1154
|
+
const winH = sh + 92;
|
|
1155
|
+
const barY = -winH / 2 + 24;
|
|
1156
|
+
return [
|
|
1157
|
+
rect({ id: `${id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: p.chrome, stroke: p.bodyStroke, strokeWidth: 1.5, radius: 14 }),
|
|
1158
|
+
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 30, y: barY, anchor: "center", width: 13, height: 13, fill: "#FF5F57" }),
|
|
1159
|
+
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 54, y: barY, anchor: "center", width: 13, height: 13, fill: "#FEBC2E" }),
|
|
1160
|
+
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 78, y: barY, anchor: "center", width: 13, height: 13, fill: "#28C840" }),
|
|
1161
|
+
// an active tab tucked under the lights
|
|
1162
|
+
rect({ id: `${id}-tab`, x: -winW / 2 + 230, y: barY, anchor: "center", width: 190, height: 30, fill: o.screen ?? p.screen, radius: 8 }),
|
|
1163
|
+
text({ id: `${id}-tabtext`, x: -winW / 2 + 156, y: barY, anchor: "center-left", content: "Overview", fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
1164
|
+
rect({ id: `${id}-urlpill`, x: 96, y: barY, anchor: "center", width: 700, height: 26, fill: o.screen ?? p.screen, stroke: p.bodyStroke, strokeWidth: 1, radius: 13 }),
|
|
1165
|
+
rect({ id: `${id}-lock`, x: 96 - 330, y: barY, anchor: "center", width: 8, height: 10, fill: p.chromeText, radius: 2 }),
|
|
1166
|
+
text({ id: `${id}-urltext`, x: 96 - 312, y: barY, anchor: "center-left", content: urlText(o.url), fontFamily: "Inter", fontSize: 14, fill: p.chromeText }),
|
|
1167
|
+
screen()
|
|
1168
|
+
];
|
|
1169
|
+
}
|
|
1170
|
+
case "watch": {
|
|
1171
|
+
const bw = sw + 36;
|
|
1172
|
+
const bh = sh + 36;
|
|
1173
|
+
return [
|
|
1174
|
+
// straps (drawn behind the body) flaring out top & bottom
|
|
1175
|
+
path({ id: `${id}-bandtop`, x: 0, y: -bh / 2 + 4, d: "M -78 0 L 78 0 L 64 -86 L -64 -86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
1176
|
+
path({ id: `${id}-bandbot`, x: 0, y: bh / 2 - 4, d: "M -78 0 L 78 0 L 64 86 L -64 86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
|
|
1177
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bw, height: bh, fill: p.body, stroke: p.bodyStroke, strokeWidth: 3, radius: 60 }),
|
|
1178
|
+
screen(),
|
|
1179
|
+
rect({ id: `${id}-crown`, x: bw / 2, y: -20, anchor: "center", width: 14, height: 40, fill: p.detail, radius: 6 }),
|
|
1180
|
+
rect({ id: `${id}-button`, x: bw / 2 - 2, y: 40, anchor: "center", width: 8, height: 34, fill: p.detail, radius: 4 })
|
|
1181
|
+
];
|
|
1182
|
+
}
|
|
1183
|
+
case "monitor": {
|
|
1184
|
+
const panelW = sw + 44;
|
|
1185
|
+
const panelH = sh + 60;
|
|
1186
|
+
return [
|
|
1187
|
+
rect({ id: `${id}-panel`, x: 0, y: 0, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 16 }),
|
|
1188
|
+
screen(),
|
|
1189
|
+
ellipse({ id: `${id}-led`, x: panelW / 2 - 26, y: panelH / 2 - 16, anchor: "center", width: 6, height: 6, fill: "#28C840" }),
|
|
1190
|
+
rect({ id: `${id}-neck`, x: 0, y: panelH / 2 + 60, anchor: "center", width: 60, height: 120, fill: p.body }),
|
|
1191
|
+
path({ id: `${id}-stand`, x: 0, y: panelH / 2 + 60, d: "M -160 50 L 160 50 L 220 80 L -220 80 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
|
|
1192
|
+
];
|
|
1193
|
+
}
|
|
1194
|
+
case "tv": {
|
|
1195
|
+
const panelW = sw + 44;
|
|
1196
|
+
const panelH = sh + 48;
|
|
1197
|
+
const panelBottom = dims.cy + panelH / 2;
|
|
1198
|
+
return [
|
|
1199
|
+
rect({ id: `${id}-panel`, x: 0, y: dims.cy, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 12 }),
|
|
1200
|
+
screen(),
|
|
1201
|
+
ellipse({ id: `${id}-brand`, x: 0, y: panelBottom - 12, anchor: "center", width: 6, height: 6, fill: p.detail }),
|
|
1202
|
+
rect({ id: `${id}-neck`, x: 0, y: panelBottom + 48, anchor: "center", width: 64, height: 96, fill: p.body }),
|
|
1203
|
+
path({ id: `${id}-stand`, x: 0, y: panelBottom + 96, d: "M -210 0 L 210 0 L 270 34 L -270 34 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
|
|
1204
|
+
];
|
|
1205
|
+
}
|
|
1206
|
+
case "foldable": {
|
|
1207
|
+
const bodyW = sw + 40;
|
|
1208
|
+
const bodyH = sh + 40;
|
|
1209
|
+
return [
|
|
1210
|
+
rect({ id: `${id}-hinge-l`, x: -bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
1211
|
+
rect({ id: `${id}-hinge-r`, x: bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
|
|
1212
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 28 }),
|
|
1213
|
+
screen(),
|
|
1214
|
+
rect({ id: `${id}-crease`, x: 0, y: 0, anchor: "center", width: 4, height: sh, fill: p.bodyStroke, radius: 2, opacity: 0.5 }),
|
|
1215
|
+
ellipse({ id: `${id}-cam1`, x: -10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail }),
|
|
1216
|
+
ellipse({ id: `${id}-cam2`, x: 10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail })
|
|
1217
|
+
];
|
|
1218
|
+
}
|
|
1219
|
+
case "terminal": {
|
|
1220
|
+
const winW = sw + 16;
|
|
1221
|
+
const winH = sh + 76;
|
|
1222
|
+
return [
|
|
1223
|
+
rect({ id: `${id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: p.chrome, stroke: p.bodyStroke, strokeWidth: 1.5, radius: 12 }),
|
|
1224
|
+
ellipse({ id: `${id}-dot1`, x: -winW / 2 + 28, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FF5F57" }),
|
|
1225
|
+
ellipse({ id: `${id}-dot2`, x: -winW / 2 + 50, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FEBC2E" }),
|
|
1226
|
+
ellipse({ id: `${id}-dot3`, x: -winW / 2 + 72, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#28C840" }),
|
|
1227
|
+
rect({ id: `${id}-tab`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", width: 130, height: 24, fill: o.screen ?? p.screen, radius: 6 }),
|
|
1228
|
+
text({ id: `${id}-title`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", content: urlText(o.url ?? "zsh"), fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
|
|
1229
|
+
screen()
|
|
1230
|
+
];
|
|
1231
|
+
}
|
|
1232
|
+
case "car": {
|
|
1233
|
+
const bodyW = sw + 60;
|
|
1234
|
+
const bodyH = sh + 60;
|
|
1235
|
+
return [
|
|
1236
|
+
rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 40 }),
|
|
1237
|
+
ellipse({ id: `${id}-knob`, x: -bodyW / 2 + 18, y: 0, anchor: "center", width: 22, height: 22, fill: p.body, stroke: p.detail, strokeWidth: 3 }),
|
|
1238
|
+
screen(),
|
|
1239
|
+
ellipse({ id: `${id}-btn1`, x: -44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
1240
|
+
ellipse({ id: `${id}-btn2`, x: 0, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
|
|
1241
|
+
ellipse({ id: `${id}-btn3`, x: 44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail })
|
|
1242
|
+
];
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
var urlText = (url) => {
|
|
1247
|
+
const u = url ?? "reframe.video";
|
|
1248
|
+
return u.length > 70 ? `${u.slice(0, 67)}\u2026` : u;
|
|
1249
|
+
};
|
|
1250
|
+
function devicePreset(name, opts = {}) {
|
|
1251
|
+
const id = opts.id ?? "device";
|
|
1252
|
+
const p = opts.color === "light" ? LIGHT : DARK;
|
|
1253
|
+
const children = buildDevice(name, id, p, opts, opts.content ?? []);
|
|
1254
|
+
return group(
|
|
1255
|
+
{
|
|
1256
|
+
id,
|
|
1257
|
+
x: opts.x ?? 0,
|
|
1258
|
+
y: opts.y ?? 0,
|
|
1259
|
+
...opts.scale !== void 0 && opts.scale !== 1 && { scale: opts.scale },
|
|
1260
|
+
...opts.opacity !== void 0 && opts.opacity !== 1 && { opacity: opts.opacity }
|
|
1261
|
+
},
|
|
1262
|
+
children
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// ../core/src/rig.ts
|
|
1267
|
+
var DEFAULT_LINE = "#FFE3D2";
|
|
1268
|
+
var DEFAULT_FILL = "#0E1424";
|
|
1269
|
+
var LINE_W = 5;
|
|
1270
|
+
var GLOW_W = 16;
|
|
1271
|
+
var K = 0.5523;
|
|
1272
|
+
var n = (v) => Number(v.toFixed(2));
|
|
1273
|
+
function capsulePath(hw, len) {
|
|
1274
|
+
const yT = hw;
|
|
1275
|
+
const yB = Math.max(hw, len - hw);
|
|
1276
|
+
const k = hw * K;
|
|
1277
|
+
return `M ${n(-hw)} ${n(yT)} C ${n(-hw)} ${n(yT - k)} ${n(-k)} ${n(yT - hw)} 0 ${n(yT - hw)} C ${n(k)} ${n(yT - hw)} ${n(hw)} ${n(yT - k)} ${n(hw)} ${n(yT)} L ${n(hw)} ${n(yB)} C ${n(hw)} ${n(yB + k)} ${n(k)} ${n(yB + hw)} 0 ${n(yB + hw)} C ${n(-k)} ${n(yB + hw)} ${n(-hw)} ${n(yB + k)} ${n(-hw)} ${n(yB)} Z`;
|
|
1278
|
+
}
|
|
1279
|
+
function ovalPath(a, b, cx = 0, cy = 0) {
|
|
1280
|
+
const ka = n(a * K), kb = n(b * K);
|
|
1281
|
+
const t = n(cy - b), bo = n(cy + b), c = n(cy), A = n(a), L = n(-a), X = n(cx);
|
|
1282
|
+
return `M ${X} ${t} C ${n(cx + ka)} ${t} ${n(cx + A)} ${n(cy - kb)} ${n(cx + A)} ${c} C ${n(cx + A)} ${n(cy + kb)} ${n(cx + ka)} ${bo} ${X} ${bo} C ${n(cx - ka)} ${bo} ${n(cx + L)} ${n(cy + kb)} ${n(cx + L)} ${c} C ${n(cx + L)} ${n(cy - kb)} ${n(cx - ka)} ${t} ${X} ${t} Z`;
|
|
1283
|
+
}
|
|
1284
|
+
function boneShape(jointId, bone, o) {
|
|
1285
|
+
if (bone.shape) return bone.shape;
|
|
1286
|
+
const len = bone.length ?? 0;
|
|
1287
|
+
if (len <= 0) return [];
|
|
1288
|
+
const d = capsulePath((bone.width ?? 20) / 2, len);
|
|
1289
|
+
const nodes = [];
|
|
1290
|
+
if (o.glow) nodes.push(path({ id: `${jointId}-glow`, d, x: 0, y: 0, fill: "none", stroke: o.glow, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
1291
|
+
nodes.push(path({ id: `${jointId}-shape`, d, x: 0, y: 0, fill: o.fill, stroke: o.color, strokeWidth: LINE_W }));
|
|
1292
|
+
return nodes;
|
|
1293
|
+
}
|
|
1294
|
+
function buildBone(bone, id, o) {
|
|
1295
|
+
const jointId = `${id}-${bone.name}`;
|
|
1296
|
+
return group(
|
|
1297
|
+
{ id: jointId, x: bone.at[0], y: bone.at[1], rotation: bone.rotation ?? 0 },
|
|
1298
|
+
[...boneShape(jointId, bone, o), ...(bone.children ?? []).map((c) => buildBone(c, id, o))]
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
function rig(root, opts = {}) {
|
|
1302
|
+
const id = opts.id ?? "rig";
|
|
1303
|
+
const o = { color: opts.color ?? DEFAULT_LINE, fill: opts.fill ?? DEFAULT_FILL, glow: opts.glow };
|
|
1304
|
+
return group(
|
|
1305
|
+
{ id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
|
|
1306
|
+
[buildBone(root, id, o)]
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
function rigPose(id, pose) {
|
|
1310
|
+
const out = {};
|
|
1311
|
+
for (const [name, deg] of Object.entries(pose)) out[`${id}-${name}`] = { rotation: deg };
|
|
1312
|
+
return out;
|
|
1313
|
+
}
|
|
1314
|
+
function poseTo(id, pose, opts = {}) {
|
|
1315
|
+
const tweens = Object.entries(pose).map(
|
|
1316
|
+
([name, deg]) => tween(`${id}-${name}`, { rotation: deg }, { duration: opts.duration ?? 0.5, ease: opts.ease ?? "easeInOutCubic" })
|
|
1317
|
+
);
|
|
1318
|
+
return opts.stagger ? stagger(opts.stagger, ...tweens) : par(...tweens);
|
|
1319
|
+
}
|
|
1320
|
+
function ikReach(upper, lower, dx, dy, flip = false) {
|
|
1321
|
+
const D = Math.hypot(dx, dy);
|
|
1322
|
+
const cos2 = Math.max(-1, Math.min(1, (D * D - upper * upper - lower * lower) / (2 * upper * lower)));
|
|
1323
|
+
const theta2 = (flip ? -1 : 1) * Math.acos(cos2);
|
|
1324
|
+
const vx = -lower * Math.sin(theta2);
|
|
1325
|
+
const vy = upper + lower * Math.cos(theta2);
|
|
1326
|
+
const theta1 = Math.atan2(dy, dx) - Math.atan2(vy, vx);
|
|
1327
|
+
const deg = (r) => r * 180 / Math.PI;
|
|
1328
|
+
return [deg(theta1), deg(theta2)];
|
|
1329
|
+
}
|
|
1330
|
+
function humanoid(opts = {}) {
|
|
1331
|
+
const line2 = opts.color ?? DEFAULT_LINE;
|
|
1332
|
+
const fill = opts.fill ?? DEFAULT_FILL;
|
|
1333
|
+
const glow = opts.glow;
|
|
1334
|
+
const blob = (jid, a, b, cy) => {
|
|
1335
|
+
const d = ovalPath(a, b, 0, cy);
|
|
1336
|
+
const nodes = [];
|
|
1337
|
+
if (glow) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow, strokeWidth: GLOW_W, opacity: 0.18 }));
|
|
1338
|
+
nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
|
|
1339
|
+
return nodes;
|
|
1340
|
+
};
|
|
1341
|
+
const id = opts.id ?? "rig";
|
|
1342
|
+
const root = {
|
|
1343
|
+
name: "chest",
|
|
1344
|
+
at: [0, 0],
|
|
1345
|
+
shape: blob(`${id}-chest`, 44, 62, 22),
|
|
1346
|
+
children: [
|
|
1347
|
+
{ name: "head", at: [0, -42], rotation: 0, shape: blob(`${id}-head`, 40, 42, -34) },
|
|
1348
|
+
{ name: "armUpperL", at: [-42, -20], length: 60, width: 20, rotation: 10, children: [
|
|
1349
|
+
{ name: "armLowerL", at: [0, 60], length: 56, width: 16, rotation: 8 }
|
|
1350
|
+
] },
|
|
1351
|
+
{ name: "armUpperR", at: [42, -20], length: 60, width: 20, rotation: -10, children: [
|
|
1352
|
+
{ name: "armLowerR", at: [0, 60], length: 56, width: 16, rotation: -8 }
|
|
1353
|
+
] },
|
|
1354
|
+
{ name: "legUpperL", at: [-20, 76], length: 76, width: 26, rotation: 3, children: [
|
|
1355
|
+
{ name: "legLowerL", at: [0, 76], length: 72, width: 22, rotation: -2 }
|
|
1356
|
+
] },
|
|
1357
|
+
{ name: "legUpperR", at: [20, 76], length: 76, width: 26, rotation: -3, children: [
|
|
1358
|
+
{ name: "legLowerR", at: [0, 76], length: 72, width: 22, rotation: 2 }
|
|
1359
|
+
] }
|
|
1360
|
+
]
|
|
1361
|
+
};
|
|
1362
|
+
return rig(root, opts);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// ../core/src/characterPreset.ts
|
|
1366
|
+
var CHARACTER_PRESET_NAMES = ["walk", "run", "jump", "dance", "wave", "cheer"];
|
|
1367
|
+
var THIGH = 76;
|
|
1368
|
+
var SHIN = 72;
|
|
1369
|
+
var clamp012 = (x) => Math.max(0, Math.min(1, x));
|
|
1370
|
+
function makeRng2(seed) {
|
|
1371
|
+
let a = seed >>> 0 || 2654435769;
|
|
1372
|
+
return () => {
|
|
1373
|
+
a = a + 1831565813 | 0;
|
|
1374
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
1375
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
1376
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
var dur2 = (base, sp) => base / sp;
|
|
1380
|
+
function ctx2(o) {
|
|
1381
|
+
const rand = makeRng2((o.seed ?? 0) + 1);
|
|
1382
|
+
return {
|
|
1383
|
+
g: o.target,
|
|
1384
|
+
label: o.label,
|
|
1385
|
+
e: clamp012(o.energy ?? 0.5),
|
|
1386
|
+
sp: Math.max(0.25, o.speed ?? 1),
|
|
1387
|
+
cycles: Math.max(1, Math.round(o.cycles ?? 4)),
|
|
1388
|
+
facing: o.facing ?? 1,
|
|
1389
|
+
at: o.at ?? [0, 0],
|
|
1390
|
+
travel: o.travel,
|
|
1391
|
+
rand,
|
|
1392
|
+
jit: (amp) => (rand() - 0.5) * 2 * amp
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
var round = (v) => Math.round(v * 1e3) / 1e3;
|
|
1396
|
+
function footPos(p, stride, lift) {
|
|
1397
|
+
p = (p % 1 + 1) % 1;
|
|
1398
|
+
if (p < 0.5) {
|
|
1399
|
+
const u2 = p / 0.5;
|
|
1400
|
+
return [stride * (1 - 2 * u2), 138];
|
|
1401
|
+
}
|
|
1402
|
+
const u = (p - 0.5) / 0.5;
|
|
1403
|
+
return [-stride + 2 * stride * u, 138 - Math.sin(Math.PI * u) * lift];
|
|
1404
|
+
}
|
|
1405
|
+
function gaitPose(ph, stride, lift, armSwing, facing) {
|
|
1406
|
+
const fl = footPos(ph, stride, lift);
|
|
1407
|
+
const fr = footPos(ph + 0.5, stride, lift);
|
|
1408
|
+
const [hipL, kneeL] = ikReach(THIGH, SHIN, facing * fl[0], fl[1], facing < 0);
|
|
1409
|
+
const [hipR, kneeR] = ikReach(THIGH, SHIN, facing * fr[0], fr[1], facing < 0);
|
|
1410
|
+
const swing = Math.cos(2 * Math.PI * ph);
|
|
1411
|
+
return {
|
|
1412
|
+
legUpperL: round(hipL),
|
|
1413
|
+
legLowerL: round(kneeL),
|
|
1414
|
+
legUpperR: round(hipR),
|
|
1415
|
+
legLowerR: round(kneeR),
|
|
1416
|
+
armUpperR: round(-10 - armSwing * swing),
|
|
1417
|
+
armLowerR: -16,
|
|
1418
|
+
armUpperL: round(10 + armSwing * swing),
|
|
1419
|
+
armLowerL: 16
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
function gait(c, run) {
|
|
1423
|
+
const stride = (run ? 34 : 24) + (run ? 40 : 30) * c.e + c.jit(3);
|
|
1424
|
+
const lift = run ? 40 : 26;
|
|
1425
|
+
const armSwing = (run ? 26 : 16) + 20 * c.e;
|
|
1426
|
+
const halfDur = (run ? 0.26 : 0.42) + c.jit(0.02);
|
|
1427
|
+
const lean = run ? c.facing * -6 : 0;
|
|
1428
|
+
const steps = c.cycles * 2;
|
|
1429
|
+
const d = dur2(halfDur, c.sp);
|
|
1430
|
+
const intro = dur2(0.16, c.sp);
|
|
1431
|
+
const keys = [];
|
|
1432
|
+
for (let k = 0; k <= steps; k++) {
|
|
1433
|
+
const pose = { ...gaitPose(k / 2, stride, lift, armSwing, c.facing), chest: lean };
|
|
1434
|
+
keys.push(poseTo(c.g, pose, { duration: k === 0 ? intro : d, ease: k === 0 ? "easeOutQuad" : "linear" }));
|
|
1435
|
+
}
|
|
1436
|
+
const total = intro + steps * d;
|
|
1437
|
+
const travel = c.travel ?? stride * 2;
|
|
1438
|
+
const children = [seq(...keys)];
|
|
1439
|
+
if (travel !== 0) {
|
|
1440
|
+
children.push(tween(c.g, { x: c.at[0] + c.facing * travel * c.cycles }, { duration: total, ease: "linear", label: "travel" }));
|
|
1441
|
+
}
|
|
1442
|
+
return beat(run ? "run" : "walk", {}, [par(...children)]);
|
|
1443
|
+
}
|
|
1444
|
+
function jumpBeat(c) {
|
|
1445
|
+
const h = 120 + 150 * c.e;
|
|
1446
|
+
const [y0] = [c.at[1]];
|
|
1447
|
+
const CROUCH = { legUpperL: 18, legLowerL: 54, legUpperR: -18, legLowerR: 54, armUpperL: 28, armUpperR: -28 };
|
|
1448
|
+
const LAUNCH = { legUpperL: 0, legLowerL: 0, legUpperR: 0, legLowerR: 0, armUpperL: 150, armUpperR: -150 };
|
|
1449
|
+
const TUCK = { legUpperL: -28, legLowerL: 66, legUpperR: -28, legLowerR: 66, armUpperL: 124, armUpperR: -124 };
|
|
1450
|
+
const REST = { legUpperL: 3, legLowerL: -2, legUpperR: -3, legLowerR: 2, armUpperL: 10, armLowerL: 8, armUpperR: -10, armLowerR: -8 };
|
|
1451
|
+
const j = c.jit(0.03);
|
|
1452
|
+
return beat("jump", {}, [
|
|
1453
|
+
seq(
|
|
1454
|
+
par(poseTo(c.g, CROUCH, { duration: dur2(0.24, c.sp), ease: "easeOutQuad" }), tween(c.g, { y: y0 + 26 }, { duration: dur2(0.24, c.sp), ease: "easeOutQuad" })),
|
|
1455
|
+
par(poseTo(c.g, LAUNCH, { duration: dur2(0.22 + j, c.sp), ease: "easeOutCubic" }), tween(c.g, { y: y0 - h }, { duration: dur2(0.36, c.sp), ease: "easeOutCubic", label: "launch" })),
|
|
1456
|
+
poseTo(c.g, TUCK, { duration: dur2(0.22, c.sp) }),
|
|
1457
|
+
par(poseTo(c.g, CROUCH, { duration: dur2(0.28, c.sp), ease: "easeInQuad" }), tween(c.g, { y: y0 + 18 }, { duration: dur2(0.3, c.sp), ease: "easeInCubic", label: "land" })),
|
|
1458
|
+
par(poseTo(c.g, REST, { duration: dur2(0.45, c.sp), ease: "easeOutBack" }), tween(c.g, { y: y0 }, { duration: dur2(0.45, c.sp), ease: "easeOutBack" }))
|
|
1459
|
+
)
|
|
1460
|
+
]);
|
|
1461
|
+
}
|
|
1462
|
+
function danceBeat(c) {
|
|
1463
|
+
const y0 = c.at[1];
|
|
1464
|
+
const sway = 8 + 6 * c.e;
|
|
1465
|
+
const armUp = 130 + 30 * c.e;
|
|
1466
|
+
const A = { chest: sway, head: -sway * 0.5, armUpperR: -armUp, armLowerR: -20, armUpperL: 40, armLowerL: 30, legUpperL: 8, legUpperR: -2 };
|
|
1467
|
+
const B = { chest: -sway, head: sway * 0.5, armUpperL: armUp, armLowerL: 20, armUpperR: -40, armLowerR: -30, legUpperL: 2, legUpperR: -8 };
|
|
1468
|
+
const d = dur2(0.34, c.sp);
|
|
1469
|
+
const keys = [];
|
|
1470
|
+
for (let k = 0; k < c.cycles * 2; k++) {
|
|
1471
|
+
const pose = k % 2 === 0 ? A : B;
|
|
1472
|
+
keys.push(par(
|
|
1473
|
+
poseTo(c.g, pose, { duration: d, ease: "easeInOutQuad" }),
|
|
1474
|
+
tween(c.g, { y: y0 - (k % 2 === 0 ? 14 : 0) }, { duration: d, ease: "easeInOutQuad" })
|
|
1475
|
+
));
|
|
1476
|
+
}
|
|
1477
|
+
keys.push(tween(c.g, { y: y0 }, { duration: d }));
|
|
1478
|
+
return beat("dance", {}, [seq(...keys)]);
|
|
1479
|
+
}
|
|
1480
|
+
function waveBeat(c) {
|
|
1481
|
+
const n3 = 3 + Math.round(c.rand() * 2);
|
|
1482
|
+
const amp = 16 + 10 * c.e;
|
|
1483
|
+
const steps = [poseTo(c.g, { armUpperR: -150, armLowerR: -24 }, { duration: dur2(0.4, c.sp), ease: "easeOutBack" })];
|
|
1484
|
+
for (let k = 0; k < n3; k++) {
|
|
1485
|
+
steps.push(poseTo(c.g, { armLowerR: -24 + (k % 2 === 0 ? amp : -amp) }, { duration: dur2(0.22, c.sp), ease: "easeInOutQuad" }));
|
|
1486
|
+
}
|
|
1487
|
+
steps.push(poseTo(c.g, { armUpperR: -10, armLowerR: -8 }, { duration: dur2(0.4, c.sp), ease: "easeInOutCubic" }));
|
|
1488
|
+
return beat("wave", {}, [seq(...steps)]);
|
|
1489
|
+
}
|
|
1490
|
+
function cheerBeat(c) {
|
|
1491
|
+
const y0 = c.at[1];
|
|
1492
|
+
const UP = { armUpperL: 152, armLowerL: 8, armUpperR: -152, armLowerR: -8 };
|
|
1493
|
+
const d = dur2(0.3, c.sp);
|
|
1494
|
+
const keys = [poseTo(c.g, UP, { duration: dur2(0.35, c.sp), ease: "easeOutBack" })];
|
|
1495
|
+
for (let k = 0; k < c.cycles; k++) {
|
|
1496
|
+
keys.push(par(tween(c.g, { y: y0 - 28 }, { duration: d, ease: "easeOutQuad" }), poseTo(c.g, { armUpperL: 160, armUpperR: -160 }, { duration: d })));
|
|
1497
|
+
keys.push(par(tween(c.g, { y: y0 }, { duration: d, ease: "easeInQuad" }), poseTo(c.g, { armUpperL: 145, armUpperR: -145 }, { duration: d })));
|
|
1498
|
+
}
|
|
1499
|
+
return beat("cheer", {}, [seq(...keys)]);
|
|
1500
|
+
}
|
|
1501
|
+
function characterPreset(name, opts) {
|
|
1502
|
+
const c = ctx2(opts);
|
|
1503
|
+
let tl;
|
|
1504
|
+
switch (name) {
|
|
1505
|
+
case "walk":
|
|
1506
|
+
tl = gait(c, false);
|
|
1507
|
+
break;
|
|
1508
|
+
case "run":
|
|
1509
|
+
tl = gait(c, true);
|
|
1510
|
+
break;
|
|
1511
|
+
case "jump":
|
|
1512
|
+
tl = jumpBeat(c);
|
|
1513
|
+
break;
|
|
1514
|
+
case "dance":
|
|
1515
|
+
tl = danceBeat(c);
|
|
1516
|
+
break;
|
|
1517
|
+
case "wave":
|
|
1518
|
+
tl = waveBeat(c);
|
|
1519
|
+
break;
|
|
1520
|
+
case "cheer":
|
|
1521
|
+
tl = cheerBeat(c);
|
|
1522
|
+
break;
|
|
1523
|
+
default: {
|
|
1524
|
+
const _exhaustive = name;
|
|
1525
|
+
throw new Error(`unknown characterPreset "${_exhaustive}"`);
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
return c.label && tl.kind === "beat" ? { ...tl, name: c.label } : tl;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// ../core/src/figure.ts
|
|
1532
|
+
var K2 = 0.5523;
|
|
1533
|
+
var n2 = (v) => Number(v.toFixed(2));
|
|
1534
|
+
function limb(a, b, y0, y1) {
|
|
1535
|
+
const ka = n2(a * K2), kb = n2(b * K2);
|
|
1536
|
+
return `M ${-a} ${y0} C ${-a} ${n2(y0 - ka)} ${-ka} ${n2(y0 - a)} 0 ${n2(y0 - a)} C ${ka} ${n2(y0 - a)} ${a} ${n2(y0 - ka)} ${a} ${y0} L ${b} ${y1} C ${b} ${n2(y1 + kb)} ${kb} ${n2(y1 + b)} 0 ${n2(y1 + b)} C ${-kb} ${n2(y1 + b)} ${-b} ${n2(y1 + kb)} ${-b} ${y1} Z`;
|
|
1537
|
+
}
|
|
1538
|
+
function rrect(a, b, y0, y1, r) {
|
|
1539
|
+
return `M ${n2(-a + r)} ${y0} L ${n2(a - r)} ${y0} Q ${a} ${y0} ${a} ${n2(y0 + r)} L ${b} ${n2(y1 - r)} Q ${b} ${y1} ${n2(b - r)} ${y1} L ${n2(-b + r)} ${y1} Q ${-b} ${y1} ${-b} ${n2(y1 - r)} L ${-a} ${n2(y0 + r)} Q ${-a} ${y0} ${n2(-a + r)} ${y0} Z`;
|
|
1540
|
+
}
|
|
1541
|
+
function darken(hex, f) {
|
|
1542
|
+
const h = hex.replace("#", "");
|
|
1543
|
+
const v = parseInt(h.length === 3 ? [...h].map((c) => c + c).join("") : h, 16);
|
|
1544
|
+
const ch = (s) => Math.max(0, Math.min(255, Math.round((v >> s & 255) * (1 - f))));
|
|
1545
|
+
const hx = (x) => x.toString(16).padStart(2, "0");
|
|
1546
|
+
return `#${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
|
|
1547
|
+
}
|
|
1548
|
+
var DEF = {
|
|
1549
|
+
clean: { skin: "#E9B58E", hair: "#2B313F", top: "#E86C4A", pants: "#39425C", shoe: "#20242F", accent: "#E86C4A" },
|
|
1550
|
+
cute: { skin: "#FFD2A6", hair: "#5B4636", top: "#FF7E5F", pants: "#3E6F8E", shoe: "#272B38", accent: "#FF7E5F" }
|
|
1551
|
+
};
|
|
1552
|
+
function resolvePal(style, p = {}) {
|
|
1553
|
+
const d = DEF[style];
|
|
1554
|
+
const accent = p.accent ?? d.accent;
|
|
1555
|
+
const top = p.top ?? (style === "clean" ? accent : d.top);
|
|
1556
|
+
const skin = p.skin ?? d.skin;
|
|
1557
|
+
const hair = p.hair ?? d.hair;
|
|
1558
|
+
const pants = p.pants ?? d.pants;
|
|
1559
|
+
const shoe = p.shoe ?? d.shoe;
|
|
1560
|
+
return {
|
|
1561
|
+
skin,
|
|
1562
|
+
skinSh: darken(skin, 0.12),
|
|
1563
|
+
hair,
|
|
1564
|
+
hairSh: darken(hair, 0.14),
|
|
1565
|
+
top,
|
|
1566
|
+
topSh: darken(top, 0.12),
|
|
1567
|
+
pants,
|
|
1568
|
+
pantsSh: darken(pants, 0.14),
|
|
1569
|
+
shoe,
|
|
1570
|
+
shoeSh: darken(shoe, 0.22),
|
|
1571
|
+
eye: "#2B313F",
|
|
1572
|
+
cheek: "#FF9E7E",
|
|
1573
|
+
white: "#FFFFFF",
|
|
1574
|
+
mouth: "#8A4233"
|
|
1575
|
+
};
|
|
1576
|
+
}
|
|
1577
|
+
var fp = (id, d, fill, stroke, sw = 0, opacity = 1) => path({ id, d, x: 0, y: 0, fill, opacity, ...stroke && sw > 0 ? { stroke, strokeWidth: sw } : {} });
|
|
1578
|
+
function cleanParts(p, face) {
|
|
1579
|
+
const HC = -42;
|
|
1580
|
+
return {
|
|
1581
|
+
upperArm: (j) => [fp(`${j}-sleeve`, limb(12, 10, 2, 58), p.top)],
|
|
1582
|
+
forearm: (j) => [
|
|
1583
|
+
fp(`${j}-elbow`, ovalPath(10, 10, 0, 3), p.skin),
|
|
1584
|
+
fp(`${j}-fore`, limb(10, 8, 2, 48), p.skin),
|
|
1585
|
+
fp(`${j}-hand`, ovalPath(11, 12, 0, 50), p.skin)
|
|
1586
|
+
],
|
|
1587
|
+
thigh: (j) => [fp(`${j}-thigh`, limb(15, 13, 2, 72), p.pants)],
|
|
1588
|
+
shin: (j) => [
|
|
1589
|
+
fp(`${j}-knee`, ovalPath(13, 13, 0, 2), p.pants),
|
|
1590
|
+
fp(`${j}-shin`, limb(13, 11, 2, 62), p.pants),
|
|
1591
|
+
fp(`${j}-shoe`, ovalPath(15, 9, 4, 67), p.shoe)
|
|
1592
|
+
],
|
|
1593
|
+
torso: (j) => [
|
|
1594
|
+
fp(`${j}-shadow`, rrect(38, 26, -28, 52, 20), p.topSh),
|
|
1595
|
+
fp(`${j}-top`, rrect(40, 27, -30, 52, 22), p.top),
|
|
1596
|
+
fp(`${j}-pelvis`, rrect(29, 24, 46, 104, 14), p.pants)
|
|
1597
|
+
],
|
|
1598
|
+
head: (j) => [
|
|
1599
|
+
fp(`${j}-neck`, rrect(9, 9, 2, 22, 5), p.skin),
|
|
1600
|
+
fp(`${j}-skin`, ovalPath(42, 46, 0, HC), p.skin),
|
|
1601
|
+
fp(`${j}-hair`, ovalPath(44, 27, 0, HC - 31), p.hair),
|
|
1602
|
+
fp(`${j}-hairL`, ovalPath(8, 14, -39, HC - 18), p.hair),
|
|
1603
|
+
fp(`${j}-hairR`, ovalPath(8, 14, 39, HC - 18), p.hair),
|
|
1604
|
+
...face ? [fp(`${j}-eyeL`, ovalPath(5, 7, -14, HC + 2), p.eye), fp(`${j}-eyeR`, ovalPath(5, 7, 14, HC + 2), p.eye)] : []
|
|
1605
|
+
]
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
var CUTE_HAIR = "M -64 -54 C -78 -96 -50 -126 0 -126 C 50 -126 78 -96 64 -54 C 60 -34 48 -28 41 -33 C 35 -54 23 -60 9 -60 C 3 -60 -3 -60 -9 -60 C -23 -60 -35 -54 -41 -33 C -48 -28 -60 -34 -64 -54 Z";
|
|
1609
|
+
function cuteParts(p, face) {
|
|
1610
|
+
const HC = -50;
|
|
1611
|
+
return {
|
|
1612
|
+
upperArm: (j) => [fp(`${j}-sleeve`, limb(15, 13, 2, 58), p.top, p.topSh, 2.5)],
|
|
1613
|
+
forearm: (j) => [
|
|
1614
|
+
fp(`${j}-elbow`, ovalPath(12, 12, 0, 3), p.skin, p.skinSh, 2.5),
|
|
1615
|
+
fp(`${j}-fore`, limb(12, 10, 2, 46), p.skin, p.skinSh, 2.5),
|
|
1616
|
+
fp(`${j}-hand`, ovalPath(13, 14, 0, 50), p.skin, p.skinSh, 2.5)
|
|
1617
|
+
],
|
|
1618
|
+
thigh: (j) => [fp(`${j}-thigh`, limb(19, 16, 2, 72), p.pants, p.pantsSh, 2.5)],
|
|
1619
|
+
shin: (j) => [
|
|
1620
|
+
fp(`${j}-knee`, ovalPath(16, 16, 0, 2), p.pants, p.pantsSh, 2.5),
|
|
1621
|
+
fp(`${j}-shin`, limb(15, 12, 2, 60), p.pants, p.pantsSh, 2.5),
|
|
1622
|
+
fp(`${j}-shoe`, ovalPath(18, 11, 5, 66), p.shoe, darken(p.shoe, 0.25), 2.5)
|
|
1623
|
+
],
|
|
1624
|
+
torso: (j) => [
|
|
1625
|
+
fp(`${j}-shadow`, rrect(40, 34, -18, 52, 16), p.topSh),
|
|
1626
|
+
fp(`${j}-top`, rrect(42, 35, -20, 52, 18), p.top, p.topSh, 2.5),
|
|
1627
|
+
fp(`${j}-pelvis`, rrect(36, 30, 46, 104, 16), p.pants, p.pantsSh, 2.5)
|
|
1628
|
+
],
|
|
1629
|
+
head: (j) => [
|
|
1630
|
+
fp(`${j}-neck`, ovalPath(15, 12, 0, 12), p.skinSh),
|
|
1631
|
+
fp(`${j}-skin`, ovalPath(42, 46, 0, HC + 4), p.skin, p.skinSh, 2.5),
|
|
1632
|
+
fp(`${j}-cheekL`, ovalPath(11, 8, -40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
1633
|
+
fp(`${j}-cheekR`, ovalPath(11, 8, 40, HC + 22), p.cheek, void 0, 0, 0.5),
|
|
1634
|
+
fp(`${j}-hair`, CUTE_HAIR, p.hair, p.hairSh, 2),
|
|
1635
|
+
...face ? [
|
|
1636
|
+
fp(`${j}-eyeL`, ovalPath(10, 13, -25, HC + 8), p.eye),
|
|
1637
|
+
fp(`${j}-eyeR`, ovalPath(10, 13, 25, HC + 8), p.eye),
|
|
1638
|
+
fp(`${j}-glL`, ovalPath(3.4, 3.4, -28, HC + 3), p.white),
|
|
1639
|
+
fp(`${j}-glR`, ovalPath(3.4, 3.4, 22, HC + 3), p.white),
|
|
1640
|
+
path({ id: `${j}-mouth`, d: "M -15 0 Q 0 15 15 0", x: 0, y: HC + 28, fill: "none", stroke: p.mouth, strokeWidth: 5 })
|
|
1641
|
+
] : []
|
|
1642
|
+
]
|
|
1643
|
+
};
|
|
1644
|
+
}
|
|
1645
|
+
function buildSkeleton(id, S) {
|
|
1646
|
+
const arm = (side, x, r1, r2) => ({
|
|
1647
|
+
name: `armUpper${side}`,
|
|
1648
|
+
at: [x, -14],
|
|
1649
|
+
length: 60,
|
|
1650
|
+
width: 0,
|
|
1651
|
+
rotation: r1,
|
|
1652
|
+
shape: S.upperArm(`${id}-armUpper${side}`),
|
|
1653
|
+
children: [{ name: `armLower${side}`, at: [0, 60], length: 56, width: 0, rotation: r2, shape: S.forearm(`${id}-armLower${side}`) }]
|
|
1654
|
+
});
|
|
1655
|
+
const leg = (side, x, r1, r2) => ({
|
|
1656
|
+
name: `legUpper${side}`,
|
|
1657
|
+
at: [x, 76],
|
|
1658
|
+
length: 76,
|
|
1659
|
+
width: 0,
|
|
1660
|
+
rotation: r1,
|
|
1661
|
+
shape: S.thigh(`${id}-legUpper${side}`),
|
|
1662
|
+
children: [{ name: `legLower${side}`, at: [0, 76], length: 72, width: 0, rotation: r2, shape: S.shin(`${id}-legLower${side}`) }]
|
|
1663
|
+
});
|
|
1664
|
+
return {
|
|
1665
|
+
name: "chest",
|
|
1666
|
+
at: [0, 0],
|
|
1667
|
+
shape: S.torso(`${id}-chest`),
|
|
1668
|
+
children: [
|
|
1669
|
+
{ name: "head", at: [0, -42], rotation: 0, shape: S.head(`${id}-head`) },
|
|
1670
|
+
arm("L", -40, 8, 6),
|
|
1671
|
+
arm("R", 40, -8, -6),
|
|
1672
|
+
leg("L", -20, 3, -2),
|
|
1673
|
+
leg("R", 20, -3, 2)
|
|
1674
|
+
]
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1677
|
+
function figure(opts = {}) {
|
|
1678
|
+
const style = opts.style ?? "clean";
|
|
1679
|
+
const pal = resolvePal(style, opts.palette);
|
|
1680
|
+
const face = opts.face ?? true;
|
|
1681
|
+
const id = opts.id ?? "figure";
|
|
1682
|
+
const parts = style === "clean" ? cleanParts(pal, face) : cuteParts(pal, face);
|
|
1683
|
+
const rigOpts = { id };
|
|
1684
|
+
if (opts.x !== void 0) rigOpts.x = opts.x;
|
|
1685
|
+
if (opts.y !== void 0) rigOpts.y = opts.y;
|
|
1686
|
+
if (opts.scale !== void 0) rigOpts.scale = opts.scale;
|
|
1687
|
+
if (opts.opacity !== void 0) rigOpts.opacity = opts.opacity;
|
|
1688
|
+
return rig(buildSkeleton(id, parts), rigOpts);
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
// ../core/src/motionOps.ts
|
|
1692
|
+
var MOTION_OPS = ["rotate", "zoom", "ken-burns", "slide-in", "fade", "draw-on", "pulse"];
|
|
1693
|
+
var clamp013 = (n3) => Math.max(0, Math.min(1, n3));
|
|
1694
|
+
function settleEase2(e) {
|
|
1695
|
+
return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
|
|
1696
|
+
}
|
|
1697
|
+
function fromVec2(from, dist) {
|
|
1698
|
+
switch (from) {
|
|
1699
|
+
case "right":
|
|
1700
|
+
return [dist, 0];
|
|
1701
|
+
case "top":
|
|
1702
|
+
return [0, -dist];
|
|
1703
|
+
case "bottom":
|
|
1704
|
+
return [0, dist];
|
|
1705
|
+
default:
|
|
1706
|
+
return [-dist, 0];
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
var motionOpLabel = (name, target) => `op-${name}-${target}`;
|
|
1710
|
+
function motionOp(name, target, opts = {}) {
|
|
1711
|
+
const e = clamp013(opts.energy ?? 0.5);
|
|
1712
|
+
const sp = Math.max(0.25, opts.speed ?? 1);
|
|
1713
|
+
const amt = opts.amount ?? 1;
|
|
1714
|
+
const b = { scale: 1, x: 0, y: 0, rotation: 0, ...opts.base };
|
|
1715
|
+
const d = (base) => base / sp;
|
|
1716
|
+
const label = motionOpLabel(name, target);
|
|
1717
|
+
switch (name) {
|
|
1718
|
+
case "rotate":
|
|
1719
|
+
return { timeline: beat(label, {}, [tween(target, { rotation: b.rotation + 360 * amt }, { duration: d(1), ease: settleEase2(e) })]) };
|
|
1720
|
+
case "zoom": {
|
|
1721
|
+
const peak = b.scale * (1 + 0.22 * amt);
|
|
1722
|
+
return {
|
|
1723
|
+
timeline: beat(label, {}, [
|
|
1724
|
+
seq(
|
|
1725
|
+
tween(target, { scale: peak }, { duration: d(0.4), ease: "easeOutBack" }),
|
|
1726
|
+
tween(target, { scale: b.scale }, { duration: d(0.45), ease: "easeInOutQuad" })
|
|
1727
|
+
)
|
|
1728
|
+
])
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
case "ken-burns":
|
|
1732
|
+
return {
|
|
1733
|
+
timeline: beat(label, {}, [
|
|
1734
|
+
par(
|
|
1735
|
+
tween(target, { scale: b.scale * (1 + 0.1 * amt) }, { duration: d(3), ease: "easeInOutQuad" }),
|
|
1736
|
+
tween(target, { x: b.x + 26 * amt, y: b.y - 16 * amt }, { duration: d(3), ease: "easeInOutQuad" })
|
|
1737
|
+
)
|
|
1738
|
+
])
|
|
1739
|
+
};
|
|
1740
|
+
case "slide-in": {
|
|
1741
|
+
const [dx, dy] = fromVec2(opts.from ?? "left", 320 * amt);
|
|
1742
|
+
return {
|
|
1743
|
+
setup: { [target]: { x: b.x + dx, y: b.y + dy, opacity: 0 } },
|
|
1744
|
+
timeline: beat(label, {}, [
|
|
1745
|
+
par(
|
|
1746
|
+
tween(target, { x: b.x, y: b.y }, { duration: d(0.7), ease: settleEase2(e) }),
|
|
1747
|
+
tween(target, { opacity: 1 }, { duration: d(0.4), ease: "easeOutQuad" })
|
|
1748
|
+
)
|
|
1749
|
+
])
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
case "fade":
|
|
1753
|
+
return {
|
|
1754
|
+
setup: { [target]: { opacity: 0 } },
|
|
1755
|
+
timeline: beat(label, {}, [tween(target, { opacity: 1 }, { duration: d(0.6), ease: "easeOutQuad" })])
|
|
1756
|
+
};
|
|
1757
|
+
case "draw-on":
|
|
1758
|
+
return {
|
|
1759
|
+
setup: { [target]: { progress: 0 } },
|
|
1760
|
+
timeline: beat(label, {}, [tween(target, { progress: 1 }, { duration: d(1.3), ease: "easeInOutQuad" })])
|
|
1761
|
+
};
|
|
1762
|
+
case "pulse": {
|
|
1763
|
+
const hi = b.scale * (1 + 0.12 * amt);
|
|
1764
|
+
const pulses = 2 + Math.round(amt);
|
|
1765
|
+
const steps = [];
|
|
1766
|
+
for (let i = 0; i < pulses; i++) {
|
|
1767
|
+
steps.push(tween(target, { scale: hi }, { duration: d(0.22), ease: "easeOutQuad" }));
|
|
1768
|
+
steps.push(tween(target, { scale: b.scale }, { duration: d(0.22), ease: "easeInQuad" }));
|
|
1769
|
+
}
|
|
1770
|
+
return { timeline: beat(label, {}, [seq(...steps)]) };
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
887
1775
|
// ../core/src/audio.ts
|
|
888
1776
|
var SFX_DURATION = {
|
|
889
1777
|
whoosh: 0.35,
|
|
@@ -929,6 +1817,15 @@ function resolveAudioPlan(compiled) {
|
|
|
929
1817
|
});
|
|
930
1818
|
}
|
|
931
1819
|
cues.sort((a, b) => a.t - b.t);
|
|
1820
|
+
return {
|
|
1821
|
+
duration,
|
|
1822
|
+
bgm: resolveBgm(audio.bgm),
|
|
1823
|
+
cues,
|
|
1824
|
+
duckWindows: mergeDuckWindows(cues, duration),
|
|
1825
|
+
warnings
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function mergeDuckWindows(cues, duration) {
|
|
932
1829
|
const duckWindows = [];
|
|
933
1830
|
for (const cue of cues) {
|
|
934
1831
|
const window = { t0: cue.t, t1: Math.min(duration, cue.t + cue.duration) };
|
|
@@ -936,23 +1833,68 @@ function resolveAudioPlan(compiled) {
|
|
|
936
1833
|
if (last && window.t0 <= last.t1 + 0.1) last.t1 = Math.max(last.t1, window.t1);
|
|
937
1834
|
else duckWindows.push(window);
|
|
938
1835
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1836
|
+
return duckWindows;
|
|
1837
|
+
}
|
|
1838
|
+
function resolveBgm(b) {
|
|
1839
|
+
if (!b) return null;
|
|
1840
|
+
const duck = b.duck === false ? null : {
|
|
1841
|
+
depth: b.duck?.depth ?? 0.5,
|
|
1842
|
+
attack: b.duck?.attack ?? 0.05,
|
|
1843
|
+
release: b.duck?.release ?? 0.25
|
|
1844
|
+
};
|
|
1845
|
+
return {
|
|
1846
|
+
source: b.file ? { kind: "file", path: b.file } : { kind: "synth", name: b.synth ?? "ambient-pad" },
|
|
1847
|
+
gain: b.gain ?? 0.5,
|
|
1848
|
+
fadeIn: b.fadeIn ?? 0,
|
|
1849
|
+
fadeOut: b.fadeOut ?? 0,
|
|
1850
|
+
duck
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function resolveCompositionAudioPlan(comp) {
|
|
1854
|
+
const audio = comp.ir.audio;
|
|
1855
|
+
const duration = comp.duration;
|
|
1856
|
+
const warnings = [];
|
|
1857
|
+
const cues = [];
|
|
1858
|
+
for (const placement of comp.scenes) {
|
|
1859
|
+
const plan = resolveAudioPlan(placement.compiled);
|
|
1860
|
+
if (!plan) continue;
|
|
1861
|
+
if (plan.bgm) {
|
|
1862
|
+
warnings.push(`scene "${placement.id}": per-scene bgm ignored \u2014 set bgm at the composition level`);
|
|
1863
|
+
}
|
|
1864
|
+
for (const w of plan.warnings) warnings.push(`scene "${placement.id}": ${w}`);
|
|
1865
|
+
for (const cue of plan.cues) {
|
|
1866
|
+
const t = cue.t + placement.start;
|
|
1867
|
+
if (t >= duration) continue;
|
|
1868
|
+
cues.push({ ...cue, t });
|
|
1869
|
+
}
|
|
954
1870
|
}
|
|
955
|
-
|
|
1871
|
+
for (const [index, cue] of (audio?.cues ?? []).entries()) {
|
|
1872
|
+
if (typeof cue.at !== "number") {
|
|
1873
|
+
warnings.push(`composition cue[${index}]: "at" must be an absolute number (no composition labels) \u2014 dropped`);
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
const t = Math.max(0, cue.at + (cue.offset ?? 0));
|
|
1877
|
+
const cueDuration = cue.sfx ? SFX_DURATION[cue.sfx] : FILE_CUE_DURATION;
|
|
1878
|
+
if (t >= duration) {
|
|
1879
|
+
warnings.push(`composition cue[${index}] at ${t.toFixed(2)}s past the composition end \u2014 dropped`);
|
|
1880
|
+
continue;
|
|
1881
|
+
}
|
|
1882
|
+
cues.push({
|
|
1883
|
+
t,
|
|
1884
|
+
gain: cue.gain ?? 1,
|
|
1885
|
+
duration: cueDuration,
|
|
1886
|
+
source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
if (!audio?.bgm && cues.length === 0) return null;
|
|
1890
|
+
cues.sort((a, b) => a.t - b.t);
|
|
1891
|
+
return {
|
|
1892
|
+
duration,
|
|
1893
|
+
bgm: resolveBgm(audio?.bgm),
|
|
1894
|
+
cues,
|
|
1895
|
+
duckWindows: mergeDuckWindows(cues, duration),
|
|
1896
|
+
warnings
|
|
1897
|
+
};
|
|
956
1898
|
}
|
|
957
1899
|
|
|
958
1900
|
// ../core/src/behaviors.ts
|
|
@@ -976,8 +1918,8 @@ function valueNoise(x, seed) {
|
|
|
976
1918
|
const b = hash01(i + 1, seed) * 2 - 1;
|
|
977
1919
|
return a + (b - a) * u;
|
|
978
1920
|
}
|
|
979
|
-
function hash01(
|
|
980
|
-
let h =
|
|
1921
|
+
function hash01(n3, seed) {
|
|
1922
|
+
let h = n3 * 374761393 + seed * 668265263 | 0;
|
|
981
1923
|
h = h ^ h >>> 13 | 0;
|
|
982
1924
|
h = Math.imul(h, 1274126177);
|
|
983
1925
|
h = (h ^ h >>> 16) >>> 0;
|
|
@@ -1070,8 +2012,8 @@ function isColor(v) {
|
|
|
1070
2012
|
function parseColor(hex) {
|
|
1071
2013
|
let h = hex.slice(1);
|
|
1072
2014
|
if (h.length <= 4) h = [...h].map((c) => c + c).join("");
|
|
1073
|
-
const
|
|
1074
|
-
return [
|
|
2015
|
+
const n3 = parseInt(h.padEnd(8, "f"), 16);
|
|
2016
|
+
return [n3 >>> 24 & 255, n3 >>> 16 & 255, n3 >>> 8 & 255, n3 & 255];
|
|
1075
2017
|
}
|
|
1076
2018
|
function formatColor([r, g, b, a]) {
|
|
1077
2019
|
const hex = (v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0");
|
|
@@ -1091,26 +2033,86 @@ function lerpValue(from, to2, u) {
|
|
|
1091
2033
|
a[3] + (b[3] - a[3]) * u
|
|
1092
2034
|
]);
|
|
1093
2035
|
}
|
|
2036
|
+
if (looksLikePath(from) && looksLikePath(to2)) {
|
|
2037
|
+
const a = tokenizePath(from);
|
|
2038
|
+
const b = tokenizePath(to2);
|
|
2039
|
+
if (a && b && morphCompatible(a, b)) return morphPath(a, b, u);
|
|
2040
|
+
return u < 0.5 ? from : to2;
|
|
2041
|
+
}
|
|
1094
2042
|
return to2;
|
|
1095
2043
|
}
|
|
2044
|
+
var PATH_BODY = /^[\sMmLlHhVvCcSsQqTtAaZz0-9.,eE+-]+$/;
|
|
2045
|
+
function looksLikePath(v) {
|
|
2046
|
+
return typeof v === "string" && /^\s*[Mm]/.test(v) && PATH_BODY.test(v);
|
|
2047
|
+
}
|
|
2048
|
+
var PATH_TOKEN = /([MmLlHhVvCcSsQqTtAaZz])|(-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/g;
|
|
2049
|
+
function tokenizePath(d) {
|
|
2050
|
+
const out = [];
|
|
2051
|
+
let cur = null;
|
|
2052
|
+
let m;
|
|
2053
|
+
PATH_TOKEN.lastIndex = 0;
|
|
2054
|
+
while (m = PATH_TOKEN.exec(d)) {
|
|
2055
|
+
if (m[1]) out.push(cur = { cmd: m[1], nums: [] });
|
|
2056
|
+
else if (m[2]) {
|
|
2057
|
+
if (!cur) return null;
|
|
2058
|
+
cur.nums.push(parseFloat(m[2]));
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return out.length ? out : null;
|
|
2062
|
+
}
|
|
2063
|
+
function morphCompatible(a, b) {
|
|
2064
|
+
if (a.length !== b.length) return false;
|
|
2065
|
+
for (let i = 0; i < a.length; i++) {
|
|
2066
|
+
const ca = a[i];
|
|
2067
|
+
const cb = b[i];
|
|
2068
|
+
if (ca.cmd !== cb.cmd || ca.nums.length !== cb.nums.length) return false;
|
|
2069
|
+
if (ca.cmd === "A" || ca.cmd === "a") return false;
|
|
2070
|
+
}
|
|
2071
|
+
return true;
|
|
2072
|
+
}
|
|
2073
|
+
var fmtNum = (v) => {
|
|
2074
|
+
const r = Number(v.toFixed(3));
|
|
2075
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
2076
|
+
};
|
|
2077
|
+
function morphPath(a, b, u) {
|
|
2078
|
+
let s = "";
|
|
2079
|
+
for (let i = 0; i < a.length; i++) {
|
|
2080
|
+
const an = a[i].nums;
|
|
2081
|
+
const bn = b[i].nums;
|
|
2082
|
+
s += (i ? " " : "") + a[i].cmd;
|
|
2083
|
+
for (let j = 0; j < an.length; j++) s += " " + fmtNum(an[j] + (bn[j] - an[j]) * u);
|
|
2084
|
+
}
|
|
2085
|
+
return s;
|
|
2086
|
+
}
|
|
1096
2087
|
|
|
1097
2088
|
// ../core/src/evaluate.ts
|
|
1098
2089
|
var IDENTITY = [1, 0, 0, 1, 0, 0];
|
|
1099
|
-
function multiply(m,
|
|
2090
|
+
function multiply(m, n3) {
|
|
1100
2091
|
return [
|
|
1101
|
-
m[0] *
|
|
1102
|
-
m[1] *
|
|
1103
|
-
m[0] *
|
|
1104
|
-
m[1] *
|
|
1105
|
-
m[0] *
|
|
1106
|
-
m[1] *
|
|
2092
|
+
m[0] * n3[0] + m[2] * n3[1],
|
|
2093
|
+
m[1] * n3[0] + m[3] * n3[1],
|
|
2094
|
+
m[0] * n3[2] + m[2] * n3[3],
|
|
2095
|
+
m[1] * n3[2] + m[3] * n3[3],
|
|
2096
|
+
m[0] * n3[4] + m[2] * n3[5] + m[4],
|
|
2097
|
+
m[1] * n3[4] + m[3] * n3[5] + m[5]
|
|
1107
2098
|
];
|
|
1108
2099
|
}
|
|
1109
|
-
function localMatrix(x, y, rotationDeg, scale) {
|
|
2100
|
+
function localMatrix(x, y, rotationDeg, scale, scaleX = 1, scaleY = 1, skewXDeg = 0, skewYDeg = 0) {
|
|
1110
2101
|
const r = rotationDeg * Math.PI / 180;
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
2102
|
+
if (scaleX === 1 && scaleY === 1 && skewXDeg === 0 && skewYDeg === 0) {
|
|
2103
|
+
const cos = Math.cos(r) * scale;
|
|
2104
|
+
const sin = Math.sin(r) * scale;
|
|
2105
|
+
return [cos, sin, -sin, cos, x, y];
|
|
2106
|
+
}
|
|
2107
|
+
const c = Math.cos(r);
|
|
2108
|
+
const s = Math.sin(r);
|
|
2109
|
+
const tx = Math.tan(skewXDeg * Math.PI / 180);
|
|
2110
|
+
const ty = Math.tan(skewYDeg * Math.PI / 180);
|
|
2111
|
+
const R = [c, s, -s, c, 0, 0];
|
|
2112
|
+
const K3 = [1, ty, tx, 1, 0, 0];
|
|
2113
|
+
const S = [scale * scaleX, 0, 0, scale * scaleY, 0, 0];
|
|
2114
|
+
const m = multiply(R, multiply(K3, S));
|
|
2115
|
+
return [m[0], m[1], m[2], m[3], x, y];
|
|
1114
2116
|
}
|
|
1115
2117
|
var ANCHOR_FACTORS = {
|
|
1116
2118
|
"top-left": [0, 0],
|
|
@@ -1135,53 +2137,86 @@ function behaviorEnvelope(b, t) {
|
|
|
1135
2137
|
if (Number.isFinite(until) && ramp > 0) envelope = Math.min(envelope, (until - t) / ramp);
|
|
1136
2138
|
return Math.max(0, Math.min(1, envelope));
|
|
1137
2139
|
}
|
|
1138
|
-
function
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
2140
|
+
function sampleProp(compiled, t, target, prop, fallback) {
|
|
2141
|
+
let value = compiled.initialValues.get(`${target}.${prop}`) ?? fallback;
|
|
2142
|
+
let segStart = Number.NEGATIVE_INFINITY;
|
|
2143
|
+
const segs = compiled.segments.get(`${target}.${prop}`);
|
|
2144
|
+
if (segs) {
|
|
2145
|
+
let active;
|
|
2146
|
+
for (const seg of segs) {
|
|
2147
|
+
if (seg.t0 <= t) active = seg;
|
|
2148
|
+
else break;
|
|
2149
|
+
}
|
|
2150
|
+
if (active) {
|
|
2151
|
+
segStart = active.t0;
|
|
2152
|
+
if (t >= active.t1) {
|
|
2153
|
+
value = active.to;
|
|
2154
|
+
} else {
|
|
2155
|
+
const u = resolveEase(active.ease)((t - active.t0) / (active.t1 - active.t0));
|
|
2156
|
+
value = lerpValue(active.from, active.to, u);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
if (prop === "x" || prop === "y" || prop === "rotation") {
|
|
2161
|
+
const drivers = compiled.motionPaths.get(target);
|
|
2162
|
+
if (drivers) {
|
|
1145
2163
|
let active;
|
|
1146
|
-
for (const
|
|
1147
|
-
if (
|
|
2164
|
+
for (const d of drivers) {
|
|
2165
|
+
if (d.t0 <= t) active = d;
|
|
1148
2166
|
else break;
|
|
1149
2167
|
}
|
|
1150
|
-
if (active) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
value = lerpValue(active.from, active.to, u);
|
|
1157
|
-
}
|
|
2168
|
+
if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
|
|
2169
|
+
const span = active.t1 - active.t0;
|
|
2170
|
+
const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
|
|
2171
|
+
if (prop === "x") value = pathPoint(active.points, active.closed, u, active.curviness)[0];
|
|
2172
|
+
else if (prop === "y") value = pathPoint(active.points, active.closed, u, active.curviness)[1];
|
|
2173
|
+
else value = pathTangentAngle(active.points, active.closed, u, active.curviness) + active.rotateOffset;
|
|
1158
2174
|
}
|
|
1159
2175
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (d.t0 <= t) active = d;
|
|
1166
|
-
else break;
|
|
1167
|
-
}
|
|
1168
|
-
if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
|
|
1169
|
-
const span = active.t1 - active.t0;
|
|
1170
|
-
const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
|
|
1171
|
-
if (prop === "x") value = pathPoint(active.points, active.closed, u)[0];
|
|
1172
|
-
else if (prop === "y") value = pathPoint(active.points, active.closed, u)[1];
|
|
1173
|
-
else value = pathTangentAngle(active.points, active.closed, u) + active.rotateOffset;
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
2176
|
+
}
|
|
2177
|
+
for (const b of compiled.ir.behaviors ?? []) {
|
|
2178
|
+
if (b.target === target && b.prop === prop && typeof value === "number") {
|
|
2179
|
+
const envelope = behaviorEnvelope(b, t);
|
|
2180
|
+
if (envelope > 0) value = value + envelope * sampleBehavior(b.behavior, t);
|
|
1176
2181
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
2182
|
+
}
|
|
2183
|
+
return value;
|
|
2184
|
+
}
|
|
2185
|
+
function nodeParentMatrix(compiled, id, t) {
|
|
2186
|
+
const num = (target, prop, fallback) => {
|
|
2187
|
+
const v = sampleProp(compiled, t, target, prop, fallback);
|
|
2188
|
+
return typeof v === "number" ? v : fallback;
|
|
2189
|
+
};
|
|
2190
|
+
let result = null;
|
|
2191
|
+
const walk = (node, parent) => {
|
|
2192
|
+
if (node.id === id) {
|
|
2193
|
+
result = parent;
|
|
2194
|
+
return true;
|
|
1182
2195
|
}
|
|
1183
|
-
|
|
2196
|
+
if (node.type === "group") {
|
|
2197
|
+
const m = multiply(
|
|
2198
|
+
parent,
|
|
2199
|
+
localMatrix(
|
|
2200
|
+
num(node.id, "x", node.props.x),
|
|
2201
|
+
num(node.id, "y", node.props.y),
|
|
2202
|
+
num(node.id, "rotation", node.props.rotation ?? 0),
|
|
2203
|
+
num(node.id, "scale", node.props.scale ?? 1),
|
|
2204
|
+
num(node.id, "scaleX", node.props.scaleX ?? 1),
|
|
2205
|
+
num(node.id, "scaleY", node.props.scaleY ?? 1),
|
|
2206
|
+
num(node.id, "skewX", node.props.skewX ?? 0),
|
|
2207
|
+
num(node.id, "skewY", node.props.skewY ?? 0)
|
|
2208
|
+
)
|
|
2209
|
+
);
|
|
2210
|
+
for (const child of node.children) if (walk(child, m)) return true;
|
|
2211
|
+
}
|
|
2212
|
+
return false;
|
|
1184
2213
|
};
|
|
2214
|
+
for (const node of compiled.ir.nodes) if (walk(node, IDENTITY)) break;
|
|
2215
|
+
return result;
|
|
2216
|
+
}
|
|
2217
|
+
function evaluate(compiled, t) {
|
|
2218
|
+
const ops = [];
|
|
2219
|
+
const valueAt = (target, prop, fallback) => sampleProp(compiled, t, target, prop, fallback);
|
|
1185
2220
|
const num = (target, prop, fallback) => {
|
|
1186
2221
|
const v = valueAt(target, prop, fallback);
|
|
1187
2222
|
return typeof v === "number" ? v : fallback;
|
|
@@ -1194,8 +2229,9 @@ function evaluate(compiled, t) {
|
|
|
1194
2229
|
const v = valueAt(target, prop, base ?? "");
|
|
1195
2230
|
return v === "" && base === void 0 ? void 0 : String(v);
|
|
1196
2231
|
};
|
|
1197
|
-
const walk = (node, parent, parentOpacity) => {
|
|
2232
|
+
const walk = (node, parent, parentOpacity, clips) => {
|
|
1198
2233
|
const id = node.id;
|
|
2234
|
+
const clipSpread = clips.length > 0 ? { clips } : void 0;
|
|
1199
2235
|
if (node.type === "line") {
|
|
1200
2236
|
const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
|
|
1201
2237
|
if (opacity2 <= 0) return;
|
|
@@ -1212,7 +2248,8 @@ function evaluate(compiled, t) {
|
|
|
1212
2248
|
x2: x1 + (num(id, "x2", node.props.x2) - x1) * progress,
|
|
1213
2249
|
y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
|
|
1214
2250
|
stroke: str(id, "stroke", node.props.stroke),
|
|
1215
|
-
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1)
|
|
2251
|
+
strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
|
|
2252
|
+
...clipSpread
|
|
1216
2253
|
});
|
|
1217
2254
|
return;
|
|
1218
2255
|
}
|
|
@@ -1224,13 +2261,19 @@ function evaluate(compiled, t) {
|
|
|
1224
2261
|
num(id, "x", node.props.x),
|
|
1225
2262
|
num(id, "y", node.props.y),
|
|
1226
2263
|
num(id, "rotation", node.props.rotation ?? 0),
|
|
1227
|
-
num(id, "scale", node.props.scale ?? 1)
|
|
2264
|
+
num(id, "scale", node.props.scale ?? 1),
|
|
2265
|
+
num(id, "scaleX", node.props.scaleX ?? 1),
|
|
2266
|
+
num(id, "scaleY", node.props.scaleY ?? 1),
|
|
2267
|
+
num(id, "skewX", node.props.skewX ?? 0),
|
|
2268
|
+
num(id, "skewY", node.props.skewY ?? 0)
|
|
1228
2269
|
)
|
|
1229
2270
|
);
|
|
1230
2271
|
switch (node.type) {
|
|
1231
|
-
case "group":
|
|
1232
|
-
|
|
2272
|
+
case "group": {
|
|
2273
|
+
const childClips = node.props.clip ? [...clips, { transform: matrix, shape: node.props.clip }] : clips;
|
|
2274
|
+
for (const child of node.children) walk(child, matrix, opacity, childClips);
|
|
1233
2275
|
return;
|
|
2276
|
+
}
|
|
1234
2277
|
case "rect":
|
|
1235
2278
|
case "ellipse": {
|
|
1236
2279
|
const width = num(id, "width", node.props.width);
|
|
@@ -1250,7 +2293,8 @@ function evaluate(compiled, t) {
|
|
|
1250
2293
|
offsetY: -height * ay,
|
|
1251
2294
|
...fill !== void 0 && { fill },
|
|
1252
2295
|
...stroke !== void 0 && { stroke, strokeWidth },
|
|
1253
|
-
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) }
|
|
2296
|
+
...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
|
|
2297
|
+
...clipSpread
|
|
1254
2298
|
});
|
|
1255
2299
|
return;
|
|
1256
2300
|
}
|
|
@@ -1267,7 +2311,8 @@ function evaluate(compiled, t) {
|
|
|
1267
2311
|
width,
|
|
1268
2312
|
height,
|
|
1269
2313
|
offsetX: -width * ax,
|
|
1270
|
-
offsetY: -height * ay
|
|
2314
|
+
offsetY: -height * ay,
|
|
2315
|
+
...clipSpread
|
|
1271
2316
|
});
|
|
1272
2317
|
return;
|
|
1273
2318
|
}
|
|
@@ -1284,7 +2329,8 @@ function evaluate(compiled, t) {
|
|
|
1284
2329
|
d: str(id, "d", node.props.d),
|
|
1285
2330
|
progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
|
|
1286
2331
|
...fill !== void 0 && { fill },
|
|
1287
|
-
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) }
|
|
2332
|
+
...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
|
|
2333
|
+
...clipSpread
|
|
1288
2334
|
});
|
|
1289
2335
|
return;
|
|
1290
2336
|
}
|
|
@@ -1307,13 +2353,14 @@ function evaluate(compiled, t) {
|
|
|
1307
2353
|
fill: str(id, "fill", node.props.fill ?? "#ffffff"),
|
|
1308
2354
|
letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
|
|
1309
2355
|
align: TEXT_ALIGN[ax] ?? "left",
|
|
1310
|
-
baseline: TEXT_BASELINE[ay] ?? "top"
|
|
2356
|
+
baseline: TEXT_BASELINE[ay] ?? "top",
|
|
2357
|
+
...clipSpread
|
|
1311
2358
|
});
|
|
1312
2359
|
return;
|
|
1313
2360
|
}
|
|
1314
2361
|
}
|
|
1315
2362
|
};
|
|
1316
|
-
for (const node of compiled.ir.nodes) walk(node, IDENTITY, 1);
|
|
2363
|
+
for (const node of compiled.ir.nodes) walk(node, IDENTITY, 1, []);
|
|
1317
2364
|
return ops;
|
|
1318
2365
|
}
|
|
1319
2366
|
|
|
@@ -1364,29 +2411,29 @@ function sketchToTimeline(sketch, nodeIds) {
|
|
|
1364
2411
|
const steps = [];
|
|
1365
2412
|
events.forEach((ev, i) => {
|
|
1366
2413
|
const node = nodeIds[i % nodeIds.length];
|
|
1367
|
-
const
|
|
2414
|
+
const dur3 = Math.max(0.05, ev.t1 - ev.t0);
|
|
1368
2415
|
const ease = easeFor(ev.easing);
|
|
1369
2416
|
let motion;
|
|
1370
2417
|
switch (ev.kind) {
|
|
1371
2418
|
case "enter":
|
|
1372
|
-
motion = tween(node, { opacity: 1 }, { duration:
|
|
2419
|
+
motion = tween(node, { opacity: 1 }, { duration: dur3, ease });
|
|
1373
2420
|
break;
|
|
1374
2421
|
case "exit":
|
|
1375
|
-
motion = tween(node, { opacity: 0 }, { duration:
|
|
2422
|
+
motion = tween(node, { opacity: 0 }, { duration: dur3, ease });
|
|
1376
2423
|
break;
|
|
1377
2424
|
case "emphasis": {
|
|
1378
2425
|
const peak = 1 + Math.max(0.08, Math.min(0.5, ev.magnitude));
|
|
1379
2426
|
motion = seq(
|
|
1380
|
-
tween(node, { scale: peak }, { duration:
|
|
1381
|
-
tween(node, { scale: 1 }, { duration:
|
|
2427
|
+
tween(node, { scale: peak }, { duration: dur3 / 2, ease: "easeOutCubic" }),
|
|
2428
|
+
tween(node, { scale: 1 }, { duration: dur3 / 2, ease: "easeInOutQuad" })
|
|
1382
2429
|
);
|
|
1383
2430
|
break;
|
|
1384
2431
|
}
|
|
1385
2432
|
case "scale":
|
|
1386
|
-
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration:
|
|
2433
|
+
motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration: dur3, ease });
|
|
1387
2434
|
break;
|
|
1388
2435
|
case "move":
|
|
1389
|
-
motion = tween(node, { opacity: 1 }, { duration:
|
|
2436
|
+
motion = tween(node, { opacity: 1 }, { duration: dur3, ease });
|
|
1390
2437
|
break;
|
|
1391
2438
|
}
|
|
1392
2439
|
steps.push(ev.t0 > 0 ? seq(wait(ev.t0), motion) : motion);
|
|
@@ -1394,38 +2441,61 @@ function sketchToTimeline(sketch, nodeIds) {
|
|
|
1394
2441
|
return par(...steps);
|
|
1395
2442
|
}
|
|
1396
2443
|
export {
|
|
2444
|
+
CHARACTER_PRESET_NAMES,
|
|
2445
|
+
DEFAULT_CROSSFADE,
|
|
1397
2446
|
DEFAULT_FPS,
|
|
1398
2447
|
DEFAULT_MOTIONPATH_DURATION,
|
|
1399
2448
|
DEFAULT_TO_DURATION,
|
|
1400
2449
|
DEFAULT_TWEEN_DURATION,
|
|
2450
|
+
DEVICE_PRESET_NAMES,
|
|
1401
2451
|
EASE_NAMES,
|
|
2452
|
+
MOTION_OPS,
|
|
1402
2453
|
PRESET_NAMES,
|
|
1403
2454
|
PROPS_BY_TYPE,
|
|
1404
2455
|
SFX_DURATION,
|
|
1405
2456
|
SceneValidationError,
|
|
1406
2457
|
beat,
|
|
2458
|
+
characterPreset,
|
|
1407
2459
|
collectImageSrcs,
|
|
2460
|
+
compileComposition,
|
|
1408
2461
|
compileScene,
|
|
1409
2462
|
composeScene,
|
|
2463
|
+
composition,
|
|
2464
|
+
deviceBounds,
|
|
2465
|
+
devicePreset,
|
|
2466
|
+
deviceScreen,
|
|
2467
|
+
deviceScreenCenter,
|
|
1410
2468
|
ellipse,
|
|
1411
2469
|
evaluate,
|
|
2470
|
+
figure,
|
|
1412
2471
|
formatComposeReport,
|
|
1413
2472
|
group,
|
|
2473
|
+
humanoid,
|
|
2474
|
+
ikReach,
|
|
1414
2475
|
image,
|
|
1415
2476
|
isColor,
|
|
1416
2477
|
lerpValue,
|
|
1417
2478
|
line,
|
|
2479
|
+
motionOp,
|
|
2480
|
+
motionOpLabel,
|
|
1418
2481
|
motionPath,
|
|
1419
2482
|
motionPreset,
|
|
2483
|
+
nodeParentMatrix,
|
|
1420
2484
|
oscillate,
|
|
2485
|
+
ovalPath,
|
|
1421
2486
|
par,
|
|
1422
2487
|
path,
|
|
1423
2488
|
pathPoint,
|
|
1424
2489
|
pathTangentAngle,
|
|
2490
|
+
poseTo,
|
|
1425
2491
|
rect,
|
|
1426
2492
|
resolveAudioPlan,
|
|
2493
|
+
resolveCompositionAudioPlan,
|
|
1427
2494
|
resolveEase,
|
|
2495
|
+
rig,
|
|
2496
|
+
rigPose,
|
|
1428
2497
|
sampleBehavior,
|
|
2498
|
+
sampleProp,
|
|
1429
2499
|
scene,
|
|
1430
2500
|
seq,
|
|
1431
2501
|
sketchToTimeline,
|
|
@@ -1433,6 +2503,7 @@ export {
|
|
|
1433
2503
|
text,
|
|
1434
2504
|
to,
|
|
1435
2505
|
tween,
|
|
2506
|
+
validateComposition,
|
|
1436
2507
|
validateScene,
|
|
1437
2508
|
wait,
|
|
1438
2509
|
wiggle
|