fui-studio-mcp 0.1.1 → 0.1.3
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 +8 -2
- package/dist/{chunk-NDQB3ETD.js → chunk-NYLRWVKF.js} +2241 -250
- package/dist/{chunk-EFZHSV66.js → chunk-ZCNEK4RK.js} +1 -1
- package/dist/{dist-RTISZO3Q.js → dist-3XKU7BN6.js} +1 -1
- package/dist/index.js +167 -7
- package/dist/{node-3VDUDD3U.js → node-VANCEYKL.js} +2 -2
- package/dist/runtime.global.js +2425 -2270
- package/package.json +2 -2
|
@@ -276,17 +276,17 @@ function emptyProject(name = "untitled") {
|
|
|
276
276
|
return Project.parse({ name, canvas: { origin: "center" } });
|
|
277
277
|
}
|
|
278
278
|
var ROOT_TYPES = /* @__PURE__ */ new Set(["canvas", "layer", "ograf"]);
|
|
279
|
-
function layerAssignment(
|
|
280
|
-
const typeOf = new Map(
|
|
279
|
+
function layerAssignment(graph2) {
|
|
280
|
+
const typeOf = new Map(graph2.nodes.map((n) => [n.id, n.type]));
|
|
281
281
|
const sources = /* @__PURE__ */ new Map();
|
|
282
|
-
for (const e of
|
|
282
|
+
for (const e of graph2.edges) {
|
|
283
283
|
if (!typeOf.has(e.from) || !typeOf.has(e.to)) continue;
|
|
284
284
|
const list = sources.get(e.to);
|
|
285
285
|
if (list) list.push(e.from);
|
|
286
286
|
else sources.set(e.to, [e.from]);
|
|
287
287
|
}
|
|
288
288
|
const reached = /* @__PURE__ */ new Map();
|
|
289
|
-
for (const layer of
|
|
289
|
+
for (const layer of graph2.nodes) {
|
|
290
290
|
if (layer.type !== "layer") continue;
|
|
291
291
|
const stack = [...sources.get(layer.id) ?? []];
|
|
292
292
|
while (stack.length > 0) {
|
|
@@ -303,25 +303,25 @@ function layerAssignment(graph) {
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
const out = /* @__PURE__ */ new Map();
|
|
306
|
-
for (const n of
|
|
306
|
+
for (const n of graph2.nodes) {
|
|
307
307
|
const r = ROOT_TYPES.has(n.type) ? void 0 : reached.get(n.id);
|
|
308
308
|
out.set(n.id, r && r.size === 1 ? [...r][0] : void 0);
|
|
309
309
|
}
|
|
310
310
|
return out;
|
|
311
311
|
}
|
|
312
|
-
function assignLayers(
|
|
313
|
-
const assignment = layerAssignment(
|
|
314
|
-
for (const n of
|
|
312
|
+
function assignLayers(graph2) {
|
|
313
|
+
const assignment = layerAssignment(graph2);
|
|
314
|
+
for (const n of graph2.nodes) {
|
|
315
315
|
const layer = assignment.get(n.id);
|
|
316
316
|
if (layer) n.layer = layer;
|
|
317
317
|
else delete n.layer;
|
|
318
318
|
}
|
|
319
|
-
return
|
|
319
|
+
return graph2;
|
|
320
320
|
}
|
|
321
|
-
function adoptions(
|
|
322
|
-
const typeOf = new Map(
|
|
321
|
+
function adoptions(graph2, nodeId) {
|
|
322
|
+
const typeOf = new Map(graph2.nodes.map((n) => [n.id, n]));
|
|
323
323
|
const sources = /* @__PURE__ */ new Map();
|
|
324
|
-
for (const e of
|
|
324
|
+
for (const e of graph2.edges) {
|
|
325
325
|
const list = sources.get(e.to);
|
|
326
326
|
if (list) list.push(e.from);
|
|
327
327
|
else sources.set(e.to, [e.from]);
|
|
@@ -335,7 +335,7 @@ function adoptions(graph, nodeId) {
|
|
|
335
335
|
cone.add(id);
|
|
336
336
|
stack.push(...sources.get(id) ?? []);
|
|
337
337
|
}
|
|
338
|
-
const assignment = layerAssignment(
|
|
338
|
+
const assignment = layerAssignment(graph2);
|
|
339
339
|
const out = /* @__PURE__ */ new Map();
|
|
340
340
|
for (const id of cone) {
|
|
341
341
|
const layer = assignment.get(id);
|
|
@@ -354,11 +354,11 @@ var MIGRATIONS = {
|
|
|
354
354
|
* the outline starts at width 0, so rendered output is byte-identical.
|
|
355
355
|
*/
|
|
356
356
|
1: (doc) => {
|
|
357
|
-
const
|
|
357
|
+
const graph2 = doc.graph;
|
|
358
358
|
const textIds = new Set(
|
|
359
|
-
(
|
|
359
|
+
(graph2?.nodes ?? []).filter((n) => V1_TEXT_TYPES.has(n.type)).map((n) => n.id)
|
|
360
360
|
);
|
|
361
|
-
for (const n of
|
|
361
|
+
for (const n of graph2?.nodes ?? []) {
|
|
362
362
|
if (!textIds.has(n.id)) continue;
|
|
363
363
|
const params = { ...n.params ?? {} };
|
|
364
364
|
const stroke = params.stroke;
|
|
@@ -373,7 +373,7 @@ var MIGRATIONS = {
|
|
|
373
373
|
params.strokeWidth = 0;
|
|
374
374
|
n.params = params;
|
|
375
375
|
}
|
|
376
|
-
for (const e of
|
|
376
|
+
for (const e of graph2?.edges ?? []) {
|
|
377
377
|
if (textIds.has(e.to) && e.toPort === "stroke") e.toPort = "fill";
|
|
378
378
|
}
|
|
379
379
|
return { ...doc, version: 2 };
|
|
@@ -384,8 +384,8 @@ var MIGRATIONS = {
|
|
|
384
384
|
* them stamped in explicitly, so nothing rerenders.
|
|
385
385
|
*/
|
|
386
386
|
2: (doc) => {
|
|
387
|
-
const
|
|
388
|
-
for (const n of
|
|
387
|
+
const graph2 = doc.graph;
|
|
388
|
+
for (const n of graph2?.nodes ?? []) {
|
|
389
389
|
if (n.type !== "text") continue;
|
|
390
390
|
const params = { ...n.params ?? {} };
|
|
391
391
|
params.fontFamily ??= "IBM Plex Mono";
|
|
@@ -411,8 +411,8 @@ var MIGRATIONS = {
|
|
|
411
411
|
"center-y": { angle: 0, alignX: false },
|
|
412
412
|
bottom: { angle: 90, alignX: false }
|
|
413
413
|
};
|
|
414
|
-
const
|
|
415
|
-
for (const n of
|
|
414
|
+
const graph2 = doc.graph;
|
|
415
|
+
for (const n of graph2?.nodes ?? []) {
|
|
416
416
|
if (n.type !== "align") continue;
|
|
417
417
|
const params = { ...n.params ?? {} };
|
|
418
418
|
const mode = typeof params.mode === "string" ? params.mode : "center-x";
|
|
@@ -432,8 +432,8 @@ var MIGRATIONS = {
|
|
|
432
432
|
/** v4 → v5: Rect's default height became 120 (square). Old docs that leaned
|
|
433
433
|
* on the 80px default get it stamped in, so nothing rerenders. */
|
|
434
434
|
4: (doc) => {
|
|
435
|
-
const
|
|
436
|
-
for (const n of
|
|
435
|
+
const graph2 = doc.graph;
|
|
436
|
+
for (const n of graph2?.nodes ?? []) {
|
|
437
437
|
if (n.type !== "rect") continue;
|
|
438
438
|
const params = { ...n.params ?? {} };
|
|
439
439
|
params.h ??= 80;
|
|
@@ -448,8 +448,8 @@ var MIGRATIONS = {
|
|
|
448
448
|
* walked the narrow version and re-saved are past helping, but a v5 file
|
|
449
449
|
* opened today keeps its labels at width 0. */
|
|
450
450
|
5: (doc) => {
|
|
451
|
-
const
|
|
452
|
-
for (const n of
|
|
451
|
+
const graph2 = doc.graph;
|
|
452
|
+
for (const n of graph2?.nodes ?? []) {
|
|
453
453
|
if (!TEXT_TYPES.has(n.type)) continue;
|
|
454
454
|
const params = { ...n.params ?? {} };
|
|
455
455
|
params.strokeWidth ??= 0;
|
|
@@ -460,8 +460,8 @@ var MIGRATIONS = {
|
|
|
460
460
|
/** v6 → v7: Boolean wears its input's style by default now. Old booleans
|
|
461
461
|
* were dressed by their own Style params; they keep that. */
|
|
462
462
|
6: (doc) => {
|
|
463
|
-
const
|
|
464
|
-
for (const n of
|
|
463
|
+
const graph2 = doc.graph;
|
|
464
|
+
for (const n of graph2?.nodes ?? []) {
|
|
465
465
|
if (n.type !== "boolean") continue;
|
|
466
466
|
const params = { ...n.params ?? {} };
|
|
467
467
|
params.keepStyle ??= false;
|
|
@@ -473,8 +473,8 @@ var MIGRATIONS = {
|
|
|
473
473
|
* to the new default; typed ones map to a stable small number (the scatter
|
|
474
474
|
* rerolls once — the knob was never semantic). */
|
|
475
475
|
7: (doc) => {
|
|
476
|
-
const
|
|
477
|
-
for (const n of
|
|
476
|
+
const graph2 = doc.graph;
|
|
477
|
+
for (const n of graph2?.nodes ?? []) {
|
|
478
478
|
if (n.type !== "jitter") continue;
|
|
479
479
|
const params = { ...n.params ?? {} };
|
|
480
480
|
if (typeof params.salt === "string") {
|
|
@@ -489,8 +489,8 @@ var MIGRATIONS = {
|
|
|
489
489
|
* documents that leaned on the implicit custom-(0,0) default get it
|
|
490
490
|
* stamped in, so nothing starts spinning in place that used to orbit. */
|
|
491
491
|
8: (doc) => {
|
|
492
|
-
const
|
|
493
|
-
for (const n of
|
|
492
|
+
const graph2 = doc.graph;
|
|
493
|
+
for (const n of graph2?.nodes ?? []) {
|
|
494
494
|
if (n.type !== "transform") continue;
|
|
495
495
|
const params = { ...n.params ?? {} };
|
|
496
496
|
params.pivot ??= "custom";
|
|
@@ -505,8 +505,8 @@ var MIGRATIONS = {
|
|
|
505
505
|
9: (doc) => {
|
|
506
506
|
const canvas = doc.canvas;
|
|
507
507
|
const centered = canvas?.origin === "center";
|
|
508
|
-
const
|
|
509
|
-
for (const n of
|
|
508
|
+
const graph2 = doc.graph;
|
|
509
|
+
for (const n of graph2?.nodes ?? []) {
|
|
510
510
|
if (n.type !== "transform" && n.type !== "array-linear") continue;
|
|
511
511
|
const params = { ...n.params ?? {} };
|
|
512
512
|
const raw = params.pivot;
|
|
@@ -532,8 +532,8 @@ var MIGRATIONS = {
|
|
|
532
532
|
* Slice's X/Y became shape-centre-anchored; old documents measured from
|
|
533
533
|
* the canvas origin, stamped in so every cut stays where it was made. */
|
|
534
534
|
10: (doc) => {
|
|
535
|
-
const
|
|
536
|
-
for (const n of
|
|
535
|
+
const graph2 = doc.graph;
|
|
536
|
+
for (const n of graph2?.nodes ?? []) {
|
|
537
537
|
const params = { ...n.params ?? {} };
|
|
538
538
|
if (n.type === "jitter") params.uniformScale ??= false;
|
|
539
539
|
else if (n.type === "slice") params.anchor ??= "canvas";
|
|
@@ -548,8 +548,8 @@ var MIGRATIONS = {
|
|
|
548
548
|
* (base only: the toggle was never exposable, so no wires ride it), and
|
|
549
549
|
* docs that leaned on the width-1 default get it stamped in. */
|
|
550
550
|
11: (doc) => {
|
|
551
|
-
const
|
|
552
|
-
for (const n of
|
|
551
|
+
const graph2 = doc.graph;
|
|
552
|
+
for (const n of graph2?.nodes ?? []) {
|
|
553
553
|
if (!TEXT_TYPES.has(n.type)) continue;
|
|
554
554
|
const params = { ...n.params ?? {} };
|
|
555
555
|
params.strokeWidth ??= 1;
|
|
@@ -575,8 +575,8 @@ var MIGRATIONS = {
|
|
|
575
575
|
* on the old corner default get it stamped in, so no lattice slides by
|
|
576
576
|
* half its span. */
|
|
577
577
|
13: (doc) => {
|
|
578
|
-
const
|
|
579
|
-
for (const n of
|
|
578
|
+
const graph2 = doc.graph;
|
|
579
|
+
for (const n of graph2?.nodes ?? []) {
|
|
580
580
|
if (n.type !== "array-grid") continue;
|
|
581
581
|
const params = { ...n.params ?? {} };
|
|
582
582
|
params.origin ??= "corner";
|
|
@@ -588,8 +588,8 @@ var MIGRATIONS = {
|
|
|
588
588
|
* Documents that leaned on the old grow-from-the-input default get 'start'
|
|
589
589
|
* stamped in, so no row slides by half its span. */
|
|
590
590
|
14: (doc) => {
|
|
591
|
-
const
|
|
592
|
-
for (const n of
|
|
591
|
+
const graph2 = doc.graph;
|
|
592
|
+
for (const n of graph2?.nodes ?? []) {
|
|
593
593
|
if (n.type !== "array-linear") continue;
|
|
594
594
|
const params = { ...n.params ?? {} };
|
|
595
595
|
params.origin ??= "start";
|
|
@@ -603,8 +603,8 @@ var MIGRATIONS = {
|
|
|
603
603
|
* #ff0000, so a fresh override is visibly an override. Old documents
|
|
604
604
|
* leaned on both defaults and get them stamped. */
|
|
605
605
|
15: (doc) => {
|
|
606
|
-
const
|
|
607
|
-
for (const n of
|
|
606
|
+
const graph2 = doc.graph;
|
|
607
|
+
for (const n of graph2?.nodes ?? []) {
|
|
608
608
|
if (n.type !== "style") continue;
|
|
609
609
|
const params = { ...n.params ?? {} };
|
|
610
610
|
params.setStroke ??= true;
|
|
@@ -617,9 +617,9 @@ var MIGRATIONS = {
|
|
|
617
617
|
* param tap used to sit. A wire drawn from that tap (`param:ink`) now has no
|
|
618
618
|
* handle; it becomes the named output (`ink`), which carries the same value. */
|
|
619
619
|
16: (doc) => {
|
|
620
|
-
const
|
|
621
|
-
const tokens = new Set((
|
|
622
|
-
for (const e of
|
|
620
|
+
const graph2 = doc.graph;
|
|
621
|
+
const tokens = new Set((graph2?.nodes ?? []).filter((n) => n.type === "style-tokens").map((n) => n.id));
|
|
622
|
+
for (const e of graph2?.edges ?? []) {
|
|
623
623
|
const port = e.fromPort;
|
|
624
624
|
if (tokens.has(e.from) && typeof port === "string" && port.startsWith("param:")) e.fromPort = port.slice(6);
|
|
625
625
|
}
|
|
@@ -631,11 +631,11 @@ var MIGRATIONS = {
|
|
|
631
631
|
* in the same order as its defaults), the first Count of them ticked to
|
|
632
632
|
* deal; wires into the swatch sockets follow their colours. */
|
|
633
633
|
17: (doc) => {
|
|
634
|
-
const
|
|
634
|
+
const graph2 = doc.graph;
|
|
635
635
|
const ROLE = ["accent", "dim", "faint", "ink", "warn", "bg"];
|
|
636
636
|
const DEAL = ["dealAccent", "dealDim", "dealFaint", "dealInk", "dealWarn", "dealBg"];
|
|
637
637
|
const palettes = /* @__PURE__ */ new Set();
|
|
638
|
-
for (const n of
|
|
638
|
+
for (const n of graph2?.nodes ?? []) {
|
|
639
639
|
if (n.type !== "palette") continue;
|
|
640
640
|
palettes.add(n.id);
|
|
641
641
|
const old = n.params ?? {};
|
|
@@ -652,7 +652,7 @@ var MIGRATIONS = {
|
|
|
652
652
|
n.type = "style-tokens";
|
|
653
653
|
n.params = params;
|
|
654
654
|
}
|
|
655
|
-
for (const e of
|
|
655
|
+
for (const e of graph2?.edges ?? []) {
|
|
656
656
|
if (!palettes.has(e.to) || typeof e.toPort !== "string") continue;
|
|
657
657
|
const m = /^color([1-6])$/.exec(e.toPort);
|
|
658
658
|
if (m) e.toPort = ROLE[Number(m[1]) - 1];
|
|
@@ -664,8 +664,8 @@ var MIGRATIONS = {
|
|
|
664
664
|
* fall to the new default and key identically, so the drift is unchanged;
|
|
665
665
|
* typed ones map to a stable small number and reroll once. */
|
|
666
666
|
18: (doc) => {
|
|
667
|
-
const
|
|
668
|
-
for (const n of
|
|
667
|
+
const graph2 = doc.graph;
|
|
668
|
+
for (const n of graph2?.nodes ?? []) {
|
|
669
669
|
if (n.type !== "noise") continue;
|
|
670
670
|
const params = { ...n.params ?? {} };
|
|
671
671
|
if (typeof params.salt === "string") {
|
|
@@ -679,9 +679,9 @@ var MIGRATIONS = {
|
|
|
679
679
|
/** v19 → v20: the last text Variations became Seeds — LFO, Random Value,
|
|
680
680
|
* Random Step and the three Switches — the same way Noise's did at v19. */
|
|
681
681
|
19: (doc) => {
|
|
682
|
-
const
|
|
682
|
+
const graph2 = doc.graph;
|
|
683
683
|
const TYPES = /* @__PURE__ */ new Set(["lfo", "random-value", "random-step", "switch", "switch-value", "switch-color"]);
|
|
684
|
-
for (const n of
|
|
684
|
+
for (const n of graph2?.nodes ?? []) {
|
|
685
685
|
if (!TYPES.has(n.type)) continue;
|
|
686
686
|
const params = { ...n.params ?? {} };
|
|
687
687
|
if (typeof params.salt === "string") {
|
|
@@ -696,8 +696,8 @@ var MIGRATIONS = {
|
|
|
696
696
|
* saved without the flag wore the theme under the old default, so it is
|
|
697
697
|
* stamped `wear: true` and keeps dressing the studio as it did. */
|
|
698
698
|
20: (doc) => {
|
|
699
|
-
const
|
|
700
|
-
for (const n of
|
|
699
|
+
const graph2 = doc.graph;
|
|
700
|
+
for (const n of graph2?.nodes ?? []) {
|
|
701
701
|
if (n.type !== "style-tokens") continue;
|
|
702
702
|
const params = { ...n.params ?? {} };
|
|
703
703
|
if (params.wear === void 0) params.wear = true;
|
|
@@ -715,10 +715,10 @@ var MIGRATIONS = {
|
|
|
715
715
|
* input shifts by one.
|
|
716
716
|
*/
|
|
717
717
|
21: (doc) => {
|
|
718
|
-
const
|
|
718
|
+
const graph2 = doc.graph;
|
|
719
719
|
const TYPES = /* @__PURE__ */ new Set(["switch", "switch-value", "switch-color"]);
|
|
720
720
|
const baseOf = (p) => typeof p === "object" && p !== null ? p.base : p;
|
|
721
|
-
for (const n of
|
|
721
|
+
for (const n of graph2?.nodes ?? []) {
|
|
722
722
|
if (!TYPES.has(n.type)) continue;
|
|
723
723
|
const params = { ...n.params ?? {} };
|
|
724
724
|
if (baseOf(params.range) === "normalized") continue;
|
|
@@ -745,8 +745,8 @@ var MIGRATIONS = {
|
|
|
745
745
|
* frame — centre-out in a centre-origin document. Old documents scattered
|
|
746
746
|
* from the top-left corner of X/Y; stamped in so nothing saved moves. */
|
|
747
747
|
22: (doc) => {
|
|
748
|
-
const
|
|
749
|
-
for (const n of
|
|
748
|
+
const graph2 = doc.graph;
|
|
749
|
+
for (const n of graph2?.nodes ?? []) {
|
|
750
750
|
if (n.type !== "scatter") continue;
|
|
751
751
|
const params = { ...n.params ?? {} };
|
|
752
752
|
params.anchor ??= "corner";
|
|
@@ -758,8 +758,8 @@ var MIGRATIONS = {
|
|
|
758
758
|
* Dot Matrix — centred on X/Y in a centre-origin document. Old documents
|
|
759
759
|
* hung the stack down and right from X/Y; stamped in so no line moves. */
|
|
760
760
|
23: (doc) => {
|
|
761
|
-
const
|
|
762
|
-
for (const n of
|
|
761
|
+
const graph2 = doc.graph;
|
|
762
|
+
for (const n of graph2?.nodes ?? []) {
|
|
763
763
|
if (n.type !== "random-code") continue;
|
|
764
764
|
const params = { ...n.params ?? {} };
|
|
765
765
|
params.stackAnchor ??= "corner";
|
|
@@ -773,9 +773,9 @@ var MIGRATIONS = {
|
|
|
773
773
|
* row at the bottom; those with Square Cells on get Top stamped so no
|
|
774
774
|
* line or copy moves. The rest take the new default when they tick it. */
|
|
775
775
|
24: (doc) => {
|
|
776
|
-
const
|
|
776
|
+
const graph2 = doc.graph;
|
|
777
777
|
const baseOf = (p) => typeof p === "object" && p !== null ? p.base : p;
|
|
778
|
-
for (const n of
|
|
778
|
+
for (const n of graph2?.nodes ?? []) {
|
|
779
779
|
if (n.type !== "grid" && n.type !== "array-grid") continue;
|
|
780
780
|
const params = { ...n.params ?? {} };
|
|
781
781
|
if (baseOf(params.square) !== true) continue;
|
|
@@ -801,7 +801,7 @@ var MIGRATIONS = {
|
|
|
801
801
|
* numbers are its own, and the drawing turns a quarter.
|
|
802
802
|
*/
|
|
803
803
|
26: (doc) => {
|
|
804
|
-
const
|
|
804
|
+
const graph2 = doc.graph;
|
|
805
805
|
const DEFAULT = {
|
|
806
806
|
circle: 0,
|
|
807
807
|
"tick-ring": 0,
|
|
@@ -811,7 +811,7 @@ var MIGRATIONS = {
|
|
|
811
811
|
sort: 0,
|
|
812
812
|
bend: void 0
|
|
813
813
|
};
|
|
814
|
-
for (const n of
|
|
814
|
+
for (const n of graph2?.nodes ?? []) {
|
|
815
815
|
const type = n.type;
|
|
816
816
|
if (!(type in DEFAULT)) continue;
|
|
817
817
|
const params = { ...n.params ?? {} };
|
|
@@ -851,10 +851,10 @@ var MIGRATIONS = {
|
|
|
851
851
|
* plays always, as before.
|
|
852
852
|
*/
|
|
853
853
|
27: (doc) => {
|
|
854
|
-
const
|
|
855
|
-
if (
|
|
856
|
-
const nodes =
|
|
857
|
-
const edges =
|
|
854
|
+
const graph2 = doc.graph;
|
|
855
|
+
if (graph2) {
|
|
856
|
+
const nodes = graph2.nodes ??= [];
|
|
857
|
+
const edges = graph2.edges ??= [];
|
|
858
858
|
const byId = new Map(nodes.map((n) => [n.id, n]));
|
|
859
859
|
for (const canvas of nodes.filter((n) => n.type === "canvas")) {
|
|
860
860
|
let k = 0;
|
|
@@ -887,7 +887,7 @@ var MIGRATIONS = {
|
|
|
887
887
|
k++;
|
|
888
888
|
}
|
|
889
889
|
}
|
|
890
|
-
assignLayers(
|
|
890
|
+
assignLayers(graph2);
|
|
891
891
|
}
|
|
892
892
|
return { ...doc, clips: Array.isArray(doc.clips) ? doc.clips : [], version: 28 };
|
|
893
893
|
},
|
|
@@ -898,8 +898,8 @@ var MIGRATIONS = {
|
|
|
898
898
|
* the SVG's `data-layer` reads the same string, so nothing moves.
|
|
899
899
|
*/
|
|
900
900
|
28: (doc) => {
|
|
901
|
-
const
|
|
902
|
-
for (const n of
|
|
901
|
+
const graph2 = doc.graph;
|
|
902
|
+
for (const n of graph2?.nodes ?? []) {
|
|
903
903
|
if (n.type !== "layer") continue;
|
|
904
904
|
const params = { ...n.params ?? {} };
|
|
905
905
|
const raw = params.name;
|
|
@@ -916,8 +916,8 @@ var MIGRATIONS = {
|
|
|
916
916
|
* far) or below moves up as one, so nothing changes places.
|
|
917
917
|
*/
|
|
918
918
|
29: (doc) => {
|
|
919
|
-
const
|
|
920
|
-
const layers = (
|
|
919
|
+
const graph2 = doc.graph;
|
|
920
|
+
const layers = (graph2?.nodes ?? []).filter((n) => n.type === "layer");
|
|
921
921
|
const orderOf = (n) => {
|
|
922
922
|
const raw = n.params?.order;
|
|
923
923
|
const base2 = typeof raw === "object" && raw !== null ? raw.base : raw;
|
|
@@ -941,9 +941,9 @@ var MIGRATIONS = {
|
|
|
941
941
|
* Ref point from now on; its output is the same.
|
|
942
942
|
*/
|
|
943
943
|
30: (doc) => {
|
|
944
|
-
const
|
|
945
|
-
const refWired = new Set((
|
|
946
|
-
for (const n of
|
|
944
|
+
const graph2 = doc.graph;
|
|
945
|
+
const refWired = new Set((graph2?.edges ?? []).filter((e) => e.toPort === "ref").map((e) => e.to));
|
|
946
|
+
for (const n of graph2?.nodes ?? []) {
|
|
947
947
|
if (n.type !== "transform" || !refWired.has(n.id)) continue;
|
|
948
948
|
const params = { ...n.params ?? {} };
|
|
949
949
|
const raw = params.pivot;
|
|
@@ -960,9 +960,9 @@ var MIGRATIONS = {
|
|
|
960
960
|
* list is its place in the layer's stack, and that is kept.
|
|
961
961
|
*/
|
|
962
962
|
31: (doc) => {
|
|
963
|
-
const
|
|
964
|
-
const layers = new Set((
|
|
965
|
-
for (const e of
|
|
963
|
+
const graph2 = doc.graph;
|
|
964
|
+
const layers = new Set((graph2?.nodes ?? []).filter((n) => n.type === "layer").map((n) => n.id));
|
|
965
|
+
for (const e of graph2?.edges ?? []) {
|
|
966
966
|
if (e.toPort === "texture" && layers.has(e.to)) e.toPort = "in";
|
|
967
967
|
}
|
|
968
968
|
return { ...doc, version: 32 };
|
|
@@ -974,8 +974,8 @@ var MIGRATIONS = {
|
|
|
974
974
|
* does not change under it.
|
|
975
975
|
*/
|
|
976
976
|
32: (doc) => {
|
|
977
|
-
const
|
|
978
|
-
for (const node of
|
|
977
|
+
const graph2 = doc.graph;
|
|
978
|
+
for (const node of graph2?.nodes ?? []) {
|
|
979
979
|
if (node.type !== "grid" && node.type !== "polar-grid") continue;
|
|
980
980
|
const params = node.params ??= {};
|
|
981
981
|
params.subJoin ??= false;
|
|
@@ -1105,6 +1105,67 @@ function orderKeys(value) {
|
|
|
1105
1105
|
function serializeProject(project) {
|
|
1106
1106
|
return JSON.stringify(orderKeys(project), null, 2) + "\n";
|
|
1107
1107
|
}
|
|
1108
|
+
function insertComposition(project, doc, mint, known = () => true) {
|
|
1109
|
+
const elementLayer = doc.graph.nodes.find((n) => n.type === "layer");
|
|
1110
|
+
if (!elementLayer) return void 0;
|
|
1111
|
+
const canvas = project.graph.nodes.find((n) => n.type === "canvas");
|
|
1112
|
+
const tokens = project.graph.nodes.find((n) => n.type === "style-tokens");
|
|
1113
|
+
const elementTokens = doc.graph.nodes.find((n) => n.type === "style-tokens");
|
|
1114
|
+
const taken = new Set(project.graph.nodes.filter((n) => n.type === "layer").map((n) => n.name?.trim()));
|
|
1115
|
+
const base2 = doc.name.trim() || elementLayer.name?.trim() || "Element";
|
|
1116
|
+
let name = base2;
|
|
1117
|
+
for (let i = 2; taken.has(name); i++) name = `${base2} ${i}`;
|
|
1118
|
+
let top = 0;
|
|
1119
|
+
for (const n of project.graph.nodes) if (n.type === "layer" && typeof n.params.order === "number") top = Math.max(top, n.params.order);
|
|
1120
|
+
const remap = /* @__PURE__ */ new Map();
|
|
1121
|
+
const nodes = doc.graph.nodes.filter((n) => n.type !== "canvas" && n.type !== "ograf" && !(n.type === "style-tokens" && tokens) && known(n)).map((n) => {
|
|
1122
|
+
const id = mint(n.type.replace(/[^a-z]/g, "") || "node");
|
|
1123
|
+
remap.set(n.id, id);
|
|
1124
|
+
const copy = { ...structuredClone(n), id };
|
|
1125
|
+
if (n.id === elementLayer.id) {
|
|
1126
|
+
copy.name = name;
|
|
1127
|
+
copy.params = { ...copy.params, order: top + 1 };
|
|
1128
|
+
}
|
|
1129
|
+
return copy;
|
|
1130
|
+
});
|
|
1131
|
+
for (const n of nodes) {
|
|
1132
|
+
if (ROOT_TYPES.has(n.type)) delete n.layer;
|
|
1133
|
+
else if (n.layer !== void 0) {
|
|
1134
|
+
const home = remap.get(n.layer);
|
|
1135
|
+
if (home) n.layer = home;
|
|
1136
|
+
else delete n.layer;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const edges = [];
|
|
1140
|
+
for (const e of doc.graph.edges) {
|
|
1141
|
+
const from = remap.get(e.from);
|
|
1142
|
+
const to = remap.get(e.to);
|
|
1143
|
+
if (from && to) edges.push({ ...e, id: mint("edge"), from, to });
|
|
1144
|
+
else if (to && tokens && elementTokens && e.from === elementTokens.id) edges.push({ ...e, id: mint("edge"), from: tokens.id, to });
|
|
1145
|
+
}
|
|
1146
|
+
const layerId = remap.get(elementLayer.id);
|
|
1147
|
+
if (canvas) edges.push({ id: mint("edge"), from: layerId, fromPort: "out", to: canvas.id, toPort: "in" });
|
|
1148
|
+
const groups = doc.groups.flatMap((g) => {
|
|
1149
|
+
const nodeIds = g.nodeIds.flatMap((id) => remap.has(id) ? [remap.get(id)] : []);
|
|
1150
|
+
if (nodeIds.length === 0) return [];
|
|
1151
|
+
return [
|
|
1152
|
+
{
|
|
1153
|
+
id: mint("group"),
|
|
1154
|
+
name: g.name,
|
|
1155
|
+
nodeIds,
|
|
1156
|
+
collapsed: g.collapsed,
|
|
1157
|
+
exposed: g.exposed.flatMap((entry) => {
|
|
1158
|
+
const dot = entry.indexOf(".");
|
|
1159
|
+
const copy = remap.get(dot < 0 ? entry : entry.slice(0, dot));
|
|
1160
|
+
return copy ? [`${copy}${dot < 0 ? "" : entry.slice(dot)}`] : [];
|
|
1161
|
+
})
|
|
1162
|
+
}
|
|
1163
|
+
];
|
|
1164
|
+
});
|
|
1165
|
+
const assets = { ...project.assets };
|
|
1166
|
+
for (const [key, record] of Object.entries(doc.assets)) if (!(key in assets)) assets[key] = structuredClone(record);
|
|
1167
|
+
return { nodes, edges, groups, assets, layerId, name };
|
|
1168
|
+
}
|
|
1108
1169
|
|
|
1109
1170
|
// ../../packages/engine/dist/index.js
|
|
1110
1171
|
var DEFAULT_STYLE = {
|
|
@@ -1390,6 +1451,16 @@ function pathPolylines(d) {
|
|
|
1390
1451
|
flush(false);
|
|
1391
1452
|
return out;
|
|
1392
1453
|
}
|
|
1454
|
+
function polylinesToPathD(lines) {
|
|
1455
|
+
const r35 = (n) => String(Math.round(n * 1e3) / 1e3);
|
|
1456
|
+
const parts = [];
|
|
1457
|
+
for (const line of lines) {
|
|
1458
|
+
if (line.points.length < 2) continue;
|
|
1459
|
+
const [first, ...rest] = line.points;
|
|
1460
|
+
parts.push(`M${r35(first.x)} ${r35(first.y)}` + rest.map((p) => `L${r35(p.x)} ${r35(p.y)}`).join("") + (line.closed ? "Z" : ""));
|
|
1461
|
+
}
|
|
1462
|
+
return parts.join("");
|
|
1463
|
+
}
|
|
1393
1464
|
var CIRCLE_SEGMENTS = 96;
|
|
1394
1465
|
function arcPoints(cx, cy, r, a0, a1) {
|
|
1395
1466
|
const sweep = Math.abs(a1 - a0);
|
|
@@ -1582,8 +1653,8 @@ function centerOf(b) {
|
|
|
1582
1653
|
return { x: b.x + b.w / 2, y: b.y + b.h / 2 };
|
|
1583
1654
|
}
|
|
1584
1655
|
function polar(cx, cy, r, deg) {
|
|
1585
|
-
const
|
|
1586
|
-
return { x: cx + r * Math.cos(
|
|
1656
|
+
const rad2 = deg * Math.PI / 180;
|
|
1657
|
+
return { x: cx + r * Math.cos(rad2), y: cy + r * Math.sin(rad2) };
|
|
1587
1658
|
}
|
|
1588
1659
|
function pointInPolygon(point, polygon) {
|
|
1589
1660
|
let inside = false;
|
|
@@ -1595,6 +1666,217 @@ function pointInPolygon(point, polygon) {
|
|
|
1595
1666
|
}
|
|
1596
1667
|
return inside;
|
|
1597
1668
|
}
|
|
1669
|
+
var NEAR_W = 0.01;
|
|
1670
|
+
function mat4Multiply(a, b) {
|
|
1671
|
+
const out = new Array(16);
|
|
1672
|
+
const A = a;
|
|
1673
|
+
const B = b;
|
|
1674
|
+
for (let r = 0; r < 4; r++) {
|
|
1675
|
+
for (let c = 0; c < 4; c++) {
|
|
1676
|
+
out[r * 4 + c] = A[r * 4] * B[c] + A[r * 4 + 1] * B[4 + c] + A[r * 4 + 2] * B[8 + c] + A[r * 4 + 3] * B[12 + c];
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return out;
|
|
1680
|
+
}
|
|
1681
|
+
function mat4Translate(x, y, z3) {
|
|
1682
|
+
return [1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z3, 0, 0, 0, 1];
|
|
1683
|
+
}
|
|
1684
|
+
function mat4Scale(sx, sy, sz) {
|
|
1685
|
+
return [sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, sz, 0, 0, 0, 0, 1];
|
|
1686
|
+
}
|
|
1687
|
+
var rad = (deg) => deg * Math.PI / 180;
|
|
1688
|
+
function mat4RotateX(deg) {
|
|
1689
|
+
const c = Math.cos(rad(deg));
|
|
1690
|
+
const s = Math.sin(rad(deg));
|
|
1691
|
+
return [1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1];
|
|
1692
|
+
}
|
|
1693
|
+
function mat4RotateY(deg) {
|
|
1694
|
+
const c = Math.cos(rad(deg));
|
|
1695
|
+
const s = Math.sin(rad(deg));
|
|
1696
|
+
return [c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, 0, 0, 0, 1];
|
|
1697
|
+
}
|
|
1698
|
+
function mat4RotateZ(deg) {
|
|
1699
|
+
const c = Math.cos(rad(deg));
|
|
1700
|
+
const s = Math.sin(rad(deg));
|
|
1701
|
+
return [c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
1702
|
+
}
|
|
1703
|
+
function isIdentityPose(pose) {
|
|
1704
|
+
return pose.x === 0 && pose.y === 0 && pose.z === 0 && pose.rx === 0 && pose.ry === 0 && pose.rz === 0 && pose.sx === 1 && pose.sy === 1;
|
|
1705
|
+
}
|
|
1706
|
+
function frameCentre(camera) {
|
|
1707
|
+
return camera.origin === "center" ? { x: 0, y: 0 } : { x: camera.width / 2, y: camera.height / 2 };
|
|
1708
|
+
}
|
|
1709
|
+
function layerModelMatrix(pose, centre) {
|
|
1710
|
+
const px = pose.pivot.x - centre.x;
|
|
1711
|
+
const py = pose.pivot.y - centre.y;
|
|
1712
|
+
let m = mat4Translate(pose.x, pose.y, pose.z);
|
|
1713
|
+
m = mat4Multiply(m, mat4Translate(px, py, 0));
|
|
1714
|
+
if (pose.rz !== 0) m = mat4Multiply(m, mat4RotateZ(pose.rz));
|
|
1715
|
+
if (pose.ry !== 0) m = mat4Multiply(m, mat4RotateY(pose.ry));
|
|
1716
|
+
if (pose.rx !== 0) m = mat4Multiply(m, mat4RotateX(pose.rx));
|
|
1717
|
+
if (pose.sx !== 1 || pose.sy !== 1) m = mat4Multiply(m, mat4Scale(pose.sx, pose.sy, 1));
|
|
1718
|
+
m = mat4Multiply(m, mat4Translate(-px, -py, 0));
|
|
1719
|
+
return m;
|
|
1720
|
+
}
|
|
1721
|
+
function planeHomography(model, perspective) {
|
|
1722
|
+
const row2 = perspective > 0 ? [model[8] / perspective, model[9] / perspective, 1 + model[11] / perspective] : [0, 0, 1];
|
|
1723
|
+
return [model[0], model[1], model[3], model[4], model[5], model[7], ...row2];
|
|
1724
|
+
}
|
|
1725
|
+
function mat3Multiply(a, b) {
|
|
1726
|
+
const out = new Array(9);
|
|
1727
|
+
const A = a;
|
|
1728
|
+
const B = b;
|
|
1729
|
+
for (let r = 0; r < 3; r++) {
|
|
1730
|
+
for (let c = 0; c < 3; c++) {
|
|
1731
|
+
out[r * 3 + c] = A[r * 3] * B[c] + A[r * 3 + 1] * B[3 + c] + A[r * 3 + 2] * B[6 + c];
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return out;
|
|
1735
|
+
}
|
|
1736
|
+
function mat3Translate(x, y) {
|
|
1737
|
+
return [1, 0, x, 0, 1, y, 0, 0, 1];
|
|
1738
|
+
}
|
|
1739
|
+
function layerHomography(pose, camera) {
|
|
1740
|
+
const c = frameCentre(camera);
|
|
1741
|
+
const hc = planeHomography(layerModelMatrix(pose, c), camera.perspective);
|
|
1742
|
+
return mat3Multiply(mat3Translate(c.x, c.y), mat3Multiply(hc, mat3Translate(-c.x, -c.y)));
|
|
1743
|
+
}
|
|
1744
|
+
function invertMat3(m) {
|
|
1745
|
+
const [a, b, c, d, e, f, g, h, i] = m;
|
|
1746
|
+
const A = e * i - f * h;
|
|
1747
|
+
const B = -(d * i - f * g);
|
|
1748
|
+
const C = d * h - e * g;
|
|
1749
|
+
const det = a * A + b * B + c * C;
|
|
1750
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return void 0;
|
|
1751
|
+
const inv = 1 / det;
|
|
1752
|
+
return [
|
|
1753
|
+
A * inv,
|
|
1754
|
+
-(b * i - c * h) * inv,
|
|
1755
|
+
(b * f - c * e) * inv,
|
|
1756
|
+
B * inv,
|
|
1757
|
+
(a * i - c * g) * inv,
|
|
1758
|
+
-(a * f - c * d) * inv,
|
|
1759
|
+
C * inv,
|
|
1760
|
+
-(a * h - b * g) * inv,
|
|
1761
|
+
(a * e - b * d) * inv
|
|
1762
|
+
];
|
|
1763
|
+
}
|
|
1764
|
+
function homTransform(h, p) {
|
|
1765
|
+
return {
|
|
1766
|
+
u: h[0] * p.x + h[1] * p.y + h[2],
|
|
1767
|
+
v: h[3] * p.x + h[4] * p.y + h[5],
|
|
1768
|
+
w: h[6] * p.x + h[7] * p.y + h[8]
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
function rigidAffineOf(h) {
|
|
1772
|
+
const k = h[8];
|
|
1773
|
+
if (!Number.isFinite(k) || Math.abs(k) < 1e-12) return void 0;
|
|
1774
|
+
const eps = 1e-9;
|
|
1775
|
+
if (Math.abs(h[6] / k) > eps || Math.abs(h[7] / k) > eps) return void 0;
|
|
1776
|
+
const a = h[0] / k;
|
|
1777
|
+
const c = h[1] / k;
|
|
1778
|
+
const e = h[2] / k;
|
|
1779
|
+
const b = h[3] / k;
|
|
1780
|
+
const d = h[4] / k;
|
|
1781
|
+
const f = h[5] / k;
|
|
1782
|
+
if (Math.abs(a * a + b * b - 1) > eps || Math.abs(c * c + d * d - 1) > eps || Math.abs(a * c + b * d) > eps) return void 0;
|
|
1783
|
+
return [a, b, c, d, e, f];
|
|
1784
|
+
}
|
|
1785
|
+
function affineAt(h, p0) {
|
|
1786
|
+
const { u, v, w } = homTransform(h, p0);
|
|
1787
|
+
if (w < NEAR_W) return void 0;
|
|
1788
|
+
const w2 = w * w;
|
|
1789
|
+
const a = (h[0] * w - u * h[6]) / w2;
|
|
1790
|
+
const b = (h[3] * w - v * h[6]) / w2;
|
|
1791
|
+
const c = (h[1] * w - u * h[7]) / w2;
|
|
1792
|
+
const d = (h[4] * w - v * h[7]) / w2;
|
|
1793
|
+
const x = u / w;
|
|
1794
|
+
const y = v / w;
|
|
1795
|
+
return [a, b, c, d, x - (a * p0.x + c * p0.y), y - (b * p0.x + d * p0.y)];
|
|
1796
|
+
}
|
|
1797
|
+
function magnificationAt(h, p) {
|
|
1798
|
+
const m = affineAt(h, p);
|
|
1799
|
+
if (!m) return void 0;
|
|
1800
|
+
const [a, b, c, d] = m;
|
|
1801
|
+
const s = a * a + b * b + c * c + d * d;
|
|
1802
|
+
const det = a * d - b * c;
|
|
1803
|
+
const disc = Math.max(0, s * s - 4 * det * det);
|
|
1804
|
+
return Math.sqrt((s + Math.sqrt(disc)) / 2);
|
|
1805
|
+
}
|
|
1806
|
+
var lerpHom = (a, b, t) => ({
|
|
1807
|
+
u: a.u + (b.u - a.u) * t,
|
|
1808
|
+
v: a.v + (b.v - a.v) * t,
|
|
1809
|
+
w: a.w + (b.w - a.w) * t
|
|
1810
|
+
});
|
|
1811
|
+
function clipPolygonNear(pts) {
|
|
1812
|
+
if (pts.length === 0) return [];
|
|
1813
|
+
const out = [];
|
|
1814
|
+
let prev = pts[pts.length - 1];
|
|
1815
|
+
let prevIn = prev.w >= NEAR_W;
|
|
1816
|
+
for (const cur of pts) {
|
|
1817
|
+
const curIn = cur.w >= NEAR_W;
|
|
1818
|
+
if (curIn !== prevIn) out.push(lerpHom(prev, cur, (NEAR_W - prev.w) / (cur.w - prev.w)));
|
|
1819
|
+
if (curIn) out.push(cur);
|
|
1820
|
+
prev = cur;
|
|
1821
|
+
prevIn = curIn;
|
|
1822
|
+
}
|
|
1823
|
+
return out;
|
|
1824
|
+
}
|
|
1825
|
+
function clipPolylineNear(pts) {
|
|
1826
|
+
const runs = [];
|
|
1827
|
+
let run = [];
|
|
1828
|
+
for (let i = 0; i < pts.length; i++) {
|
|
1829
|
+
const cur = pts[i];
|
|
1830
|
+
const curIn = cur.w >= NEAR_W;
|
|
1831
|
+
if (i > 0) {
|
|
1832
|
+
const prev = pts[i - 1];
|
|
1833
|
+
const prevIn = prev.w >= NEAR_W;
|
|
1834
|
+
if (curIn !== prevIn) {
|
|
1835
|
+
const cut3 = lerpHom(prev, cur, (NEAR_W - prev.w) / (cur.w - prev.w));
|
|
1836
|
+
if (prevIn) {
|
|
1837
|
+
run.push(cut3);
|
|
1838
|
+
runs.push(run);
|
|
1839
|
+
run = [];
|
|
1840
|
+
} else run.push(cut3);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (curIn) run.push(cur);
|
|
1844
|
+
}
|
|
1845
|
+
if (run.length > 0) runs.push(run);
|
|
1846
|
+
return runs.filter((r) => r.length >= 2);
|
|
1847
|
+
}
|
|
1848
|
+
function projectPolyline(h, points, closed) {
|
|
1849
|
+
const hom = points.map((p) => homTransform(h, p));
|
|
1850
|
+
const divide = (pts) => pts.map((p) => ({ x: p.u / p.w, y: p.v / p.w }));
|
|
1851
|
+
if (closed) {
|
|
1852
|
+
const ring = clipPolygonNear(hom);
|
|
1853
|
+
return ring.length >= 3 ? [divide(ring)] : [];
|
|
1854
|
+
}
|
|
1855
|
+
return clipPolylineNear(hom).map(divide);
|
|
1856
|
+
}
|
|
1857
|
+
function projectedQuadBounds(h, rect) {
|
|
1858
|
+
const corners = [
|
|
1859
|
+
{ x: rect.x, y: rect.y },
|
|
1860
|
+
{ x: rect.x + rect.w, y: rect.y },
|
|
1861
|
+
{ x: rect.x + rect.w, y: rect.y + rect.h },
|
|
1862
|
+
{ x: rect.x, y: rect.y + rect.h }
|
|
1863
|
+
];
|
|
1864
|
+
const ring = projectPolyline(h, corners, true)[0];
|
|
1865
|
+
return ring ? boundsOfPoints(ring) : void 0;
|
|
1866
|
+
}
|
|
1867
|
+
function planeDepth(model, p) {
|
|
1868
|
+
return model[8] * p.x + model[9] * p.y + model[11];
|
|
1869
|
+
}
|
|
1870
|
+
function planeFaces(model, p, perspective) {
|
|
1871
|
+
const nx = model[2];
|
|
1872
|
+
const ny = model[6];
|
|
1873
|
+
const nz = model[10];
|
|
1874
|
+
if (perspective <= 0) return nz > 0;
|
|
1875
|
+
const x = model[0] * p.x + model[1] * p.y + model[3];
|
|
1876
|
+
const y = model[4] * p.x + model[5] * p.y + model[7];
|
|
1877
|
+
const z3 = planeDepth(model, p);
|
|
1878
|
+
return nx * x + ny * y + nz * (z3 + perspective) > 0;
|
|
1879
|
+
}
|
|
1598
1880
|
var GRADIENT_KINDS = /* @__PURE__ */ new Set(["linear", "radial", "conic"]);
|
|
1599
1881
|
function isGradient(p) {
|
|
1600
1882
|
return typeof p === "object" && p !== null && GRADIENT_KINDS.has(p.kind) && Array.isArray(p.stops);
|
|
@@ -1650,9 +1932,9 @@ function paintBox(g, shape) {
|
|
|
1650
1932
|
function gradientGeometry(g, box) {
|
|
1651
1933
|
if (!box || g.kind === "conic") return void 0;
|
|
1652
1934
|
if (g.kind === "linear") {
|
|
1653
|
-
const
|
|
1654
|
-
const dx = Math.cos(
|
|
1655
|
-
const dy = Math.sin(
|
|
1935
|
+
const rad2 = g.angle * Math.PI / 180;
|
|
1936
|
+
const dx = Math.cos(rad2);
|
|
1937
|
+
const dy = Math.sin(rad2);
|
|
1656
1938
|
const half = (Math.abs(box.w * dx) + Math.abs(box.h * dy)) / 2;
|
|
1657
1939
|
if (!(half > 1e-9) || g.end === g.start) return void 0;
|
|
1658
1940
|
const cx2 = box.x + box.w / 2;
|
|
@@ -2288,12 +2570,14 @@ function makeCookContext(input) {
|
|
|
2288
2570
|
rasterize: input.rasterize,
|
|
2289
2571
|
video: input.video,
|
|
2290
2572
|
outline: input.outline,
|
|
2573
|
+
provided: input.provided,
|
|
2291
2574
|
assets: input.assets ?? {},
|
|
2292
2575
|
asset: input.asset,
|
|
2293
2576
|
paramDefs: defs,
|
|
2294
2577
|
params,
|
|
2295
2578
|
inputs,
|
|
2296
2579
|
wires,
|
|
2580
|
+
wiredOutputs: input.wiredOutputs ?? /* @__PURE__ */ new Set(),
|
|
2297
2581
|
inputList: (name) => multiInputs[name] ?? (inputs[name] ? [inputs[name]] : []),
|
|
2298
2582
|
scope,
|
|
2299
2583
|
num: (key, i, n, vars) => resolveNumber(params, key, fallbackNum(key), scope(i, n, vars)),
|
|
@@ -2473,12 +2757,12 @@ var CycleError = class extends Error {
|
|
|
2473
2757
|
}
|
|
2474
2758
|
cycle;
|
|
2475
2759
|
};
|
|
2476
|
-
function nodeMap(
|
|
2477
|
-
return new Map(
|
|
2760
|
+
function nodeMap(graph2) {
|
|
2761
|
+
return new Map(graph2.nodes.map((n) => [n.id, n]));
|
|
2478
2762
|
}
|
|
2479
|
-
function incomingEdges(
|
|
2480
|
-
const map = new Map(
|
|
2481
|
-
for (const e of
|
|
2763
|
+
function incomingEdges(graph2) {
|
|
2764
|
+
const map = new Map(graph2.nodes.map((n) => [n.id, []]));
|
|
2765
|
+
for (const e of graph2.edges) map.get(e.to)?.push(e);
|
|
2482
2766
|
return map;
|
|
2483
2767
|
}
|
|
2484
2768
|
function wireParam(toPort) {
|
|
@@ -2486,10 +2770,10 @@ function wireParam(toPort) {
|
|
|
2486
2770
|
}
|
|
2487
2771
|
var nodeVertex = (id) => `n|${id}`;
|
|
2488
2772
|
var tapVertex = (id, key) => `t|${id}|${key}`;
|
|
2489
|
-
function cookSteps(
|
|
2490
|
-
const byId = nodeMap(
|
|
2773
|
+
function cookSteps(graph2) {
|
|
2774
|
+
const byId = nodeMap(graph2);
|
|
2491
2775
|
const tapped = /* @__PURE__ */ new Map();
|
|
2492
|
-
for (const e of
|
|
2776
|
+
for (const e of graph2.edges) {
|
|
2493
2777
|
if (!byId.has(e.from) || !e.fromPort.startsWith("param:")) continue;
|
|
2494
2778
|
const key = e.fromPort.slice(6);
|
|
2495
2779
|
const list = tapped.get(e.from);
|
|
@@ -2498,7 +2782,7 @@ function cookSteps(graph) {
|
|
|
2498
2782
|
}
|
|
2499
2783
|
const vertices = [];
|
|
2500
2784
|
const steps = /* @__PURE__ */ new Map();
|
|
2501
|
-
for (const node of
|
|
2785
|
+
for (const node of graph2.nodes) {
|
|
2502
2786
|
for (const key of tapped.get(node.id) ?? []) {
|
|
2503
2787
|
const v2 = tapVertex(node.id, key);
|
|
2504
2788
|
vertices.push(v2);
|
|
@@ -2514,10 +2798,10 @@ function cookSteps(graph) {
|
|
|
2514
2798
|
outs.get(from).push(to);
|
|
2515
2799
|
indeg.set(to, indeg.get(to) + 1);
|
|
2516
2800
|
};
|
|
2517
|
-
for (const node of
|
|
2801
|
+
for (const node of graph2.nodes) {
|
|
2518
2802
|
for (const key of tapped.get(node.id) ?? []) link(tapVertex(node.id, key), nodeVertex(node.id));
|
|
2519
2803
|
}
|
|
2520
|
-
for (const e of
|
|
2804
|
+
for (const e of graph2.edges) {
|
|
2521
2805
|
if (!byId.has(e.from) || !byId.has(e.to)) continue;
|
|
2522
2806
|
const src = e.fromPort.startsWith("param:") ? tapVertex(e.from, e.fromPort.slice(6)) : nodeVertex(e.from);
|
|
2523
2807
|
link(src, nodeVertex(e.to));
|
|
@@ -2548,9 +2832,9 @@ function cookSteps(graph) {
|
|
|
2548
2832
|
}
|
|
2549
2833
|
return order;
|
|
2550
2834
|
}
|
|
2551
|
-
function topoSort(
|
|
2835
|
+
function topoSort(graph2) {
|
|
2552
2836
|
const order = [];
|
|
2553
|
-
for (const step of cookSteps(
|
|
2837
|
+
for (const step of cookSteps(graph2)) if (step.kind === "cook") order.push(step.node);
|
|
2554
2838
|
return order;
|
|
2555
2839
|
}
|
|
2556
2840
|
function selfWireOrder(edges) {
|
|
@@ -2584,25 +2868,25 @@ function selfWireOrder(edges) {
|
|
|
2584
2868
|
if (rank.size !== indeg.size) return null;
|
|
2585
2869
|
return [...edges].sort((a, b) => rank.get(a.fromPort.slice(6)) - rank.get(b.fromPort.slice(6)));
|
|
2586
2870
|
}
|
|
2587
|
-
function wouldCycle(
|
|
2871
|
+
function wouldCycle(graph2, from, to, fromPort, toPort) {
|
|
2588
2872
|
const probe = { id: "", from, fromPort: fromPort ?? "", to, toPort: toPort ?? "" };
|
|
2589
2873
|
try {
|
|
2590
|
-
cookSteps({ ...
|
|
2874
|
+
cookSteps({ ...graph2, edges: [...graph2.edges, probe] });
|
|
2591
2875
|
return false;
|
|
2592
2876
|
} catch (err) {
|
|
2593
2877
|
if (err instanceof CycleError) return true;
|
|
2594
2878
|
throw err;
|
|
2595
2879
|
}
|
|
2596
2880
|
}
|
|
2597
|
-
function timeDependentNodes(
|
|
2881
|
+
function timeDependentNodes(graph2, registry) {
|
|
2598
2882
|
const dependent = /* @__PURE__ */ new Set();
|
|
2599
|
-
const byId = nodeMap(
|
|
2883
|
+
const byId = nodeMap(graph2);
|
|
2600
2884
|
const enumWired = /* @__PURE__ */ new Set();
|
|
2601
|
-
for (const e of
|
|
2885
|
+
for (const e of graph2.edges) {
|
|
2602
2886
|
const def = registry.forNode(byId.get(e.to));
|
|
2603
2887
|
if (def?.params.some((p) => p.key === e.toPort && p.type === "enum")) enumWired.add(e.to);
|
|
2604
2888
|
}
|
|
2605
|
-
for (const node of
|
|
2889
|
+
for (const node of graph2.nodes) {
|
|
2606
2890
|
const def = registry.forNode(node);
|
|
2607
2891
|
const rooted = typeof def?.timeRoot === "function" ? enumWired.has(node.id) || def.timeRoot(node.params) : def?.timeRoot;
|
|
2608
2892
|
if (rooted) {
|
|
@@ -2617,7 +2901,7 @@ function timeDependentNodes(graph, registry) {
|
|
|
2617
2901
|
}
|
|
2618
2902
|
}
|
|
2619
2903
|
const outs = /* @__PURE__ */ new Map();
|
|
2620
|
-
for (const e of
|
|
2904
|
+
for (const e of graph2.edges) {
|
|
2621
2905
|
const list = outs.get(e.from);
|
|
2622
2906
|
if (list) list.push(e.to);
|
|
2623
2907
|
else outs.set(e.from, [e.to]);
|
|
@@ -2634,16 +2918,16 @@ function timeDependentNodes(graph, registry) {
|
|
|
2634
2918
|
}
|
|
2635
2919
|
return dependent;
|
|
2636
2920
|
}
|
|
2637
|
-
function validateGraph(
|
|
2921
|
+
function validateGraph(graph2, registry, clips = []) {
|
|
2638
2922
|
const issues = [];
|
|
2639
|
-
const byId = nodeMap(
|
|
2923
|
+
const byId = nodeMap(graph2);
|
|
2640
2924
|
const clipped = new Set(clips.map((c) => c.layer));
|
|
2641
|
-
for (const node of
|
|
2925
|
+
for (const node of graph2.nodes) {
|
|
2642
2926
|
if (!registry.has(node.type)) {
|
|
2643
2927
|
issues.push({ level: "error", nodeId: node.id, message: `Unknown node type "${node.type}"` });
|
|
2644
2928
|
}
|
|
2645
2929
|
}
|
|
2646
|
-
for (const e of
|
|
2930
|
+
for (const e of graph2.edges) {
|
|
2647
2931
|
const from = byId.get(e.from);
|
|
2648
2932
|
const to = byId.get(e.to);
|
|
2649
2933
|
if (!from || !to) {
|
|
@@ -2668,7 +2952,7 @@ function validateGraph(graph, registry, clips = []) {
|
|
|
2668
2952
|
}
|
|
2669
2953
|
}
|
|
2670
2954
|
}
|
|
2671
|
-
for (const e of
|
|
2955
|
+
for (const e of graph2.edges) {
|
|
2672
2956
|
const from = byId.get(e.from);
|
|
2673
2957
|
const to = byId.get(e.to);
|
|
2674
2958
|
if (!from || !to) continue;
|
|
@@ -2695,7 +2979,7 @@ function validateGraph(graph, registry, clips = []) {
|
|
|
2695
2979
|
}
|
|
2696
2980
|
}
|
|
2697
2981
|
const seen = /* @__PURE__ */ new Map();
|
|
2698
|
-
for (const e of
|
|
2982
|
+
for (const e of graph2.edges) {
|
|
2699
2983
|
const toDef = registry.forNode(byId.get(e.to));
|
|
2700
2984
|
const port = toDef?.inputs.find((i) => i.name === e.toPort);
|
|
2701
2985
|
if (!port || port.multi) continue;
|
|
@@ -2710,8 +2994,8 @@ function validateGraph(graph, registry, clips = []) {
|
|
|
2710
2994
|
}
|
|
2711
2995
|
}
|
|
2712
2996
|
let selfLoop = false;
|
|
2713
|
-
for (const node of
|
|
2714
|
-
const self =
|
|
2997
|
+
for (const node of graph2.nodes) {
|
|
2998
|
+
const self = graph2.edges.filter((e) => e.from === node.id && e.to === node.id);
|
|
2715
2999
|
if (self.length > 0 && selfWireOrder(self) === null) {
|
|
2716
3000
|
selfLoop = true;
|
|
2717
3001
|
issues.push({
|
|
@@ -2723,7 +3007,7 @@ function validateGraph(graph, registry, clips = []) {
|
|
|
2723
3007
|
}
|
|
2724
3008
|
if (!selfLoop) {
|
|
2725
3009
|
try {
|
|
2726
|
-
topoSort(
|
|
3010
|
+
topoSort(graph2);
|
|
2727
3011
|
} catch (err) {
|
|
2728
3012
|
if (err instanceof CycleError) {
|
|
2729
3013
|
issues.push({ level: "error", message: err.message });
|
|
@@ -2743,7 +3027,7 @@ function stable(value) {
|
|
|
2743
3027
|
}
|
|
2744
3028
|
var identityKeys = /* @__PURE__ */ new WeakMap();
|
|
2745
3029
|
var nextIdentityKey = 1;
|
|
2746
|
-
function
|
|
3030
|
+
function identityOf(obj) {
|
|
2747
3031
|
let key = identityKeys.get(obj);
|
|
2748
3032
|
if (key === void 0) {
|
|
2749
3033
|
key = nextIdentityKey++;
|
|
@@ -2751,6 +3035,7 @@ function identityKey(obj) {
|
|
|
2751
3035
|
}
|
|
2752
3036
|
return key;
|
|
2753
3037
|
}
|
|
3038
|
+
var identityKey = identityOf;
|
|
2754
3039
|
var textureKey = (tex) => identityKey(tex);
|
|
2755
3040
|
function hashOf(...parts) {
|
|
2756
3041
|
const [a, b, c, d] = cyrb128(parts.join(" "));
|
|
@@ -2778,7 +3063,10 @@ function evaluate(project, registry, opts = {}) {
|
|
|
2778
3063
|
const timeDeps = timeDependentNodes(project.graph, registry);
|
|
2779
3064
|
const incoming = incomingEdges(project.graph);
|
|
2780
3065
|
const hashes = /* @__PURE__ */ new Map();
|
|
2781
|
-
const
|
|
3066
|
+
const canvasNode2 = project.graph.nodes.find((n) => n.type === "canvas");
|
|
3067
|
+
const lens = canvasNode2 ? normalizeParam(canvasNode2.params.perspective).base : void 0;
|
|
3068
|
+
const perspective = typeof lens === "number" ? Math.max(0, lens) : canvasNode2 ? 1500 : void 0;
|
|
3069
|
+
const frameKey = `${project.canvas.w}x${project.canvas.h}@${project.canvas.origin}${perspective === void 0 ? "" : `/${perspective}`}`;
|
|
2782
3070
|
const clocks = envClocks(project, t, opts.audition);
|
|
2783
3071
|
const taps = /* @__PURE__ */ new Map();
|
|
2784
3072
|
const wiresOf = /* @__PURE__ */ new Map();
|
|
@@ -2804,6 +3092,10 @@ function evaluate(project, registry, opts = {}) {
|
|
|
2804
3092
|
}
|
|
2805
3093
|
const def = registry.requireNode(node);
|
|
2806
3094
|
const edges = incoming.get(node.id) ?? [];
|
|
3095
|
+
const claiming = def.outputs.filter((o) => o.claims).map((o) => o.name);
|
|
3096
|
+
const wiredOutputs = new Set(
|
|
3097
|
+
claiming.length === 0 ? [] : project.graph.edges.filter((e) => e.from === node.id && claiming.includes(e.fromPort)).map((e) => e.fromPort)
|
|
3098
|
+
);
|
|
2807
3099
|
const seed = node.seed ?? project.seed;
|
|
2808
3100
|
let wires = wiresOf.get(node.id);
|
|
2809
3101
|
if (!wires) {
|
|
@@ -2886,12 +3178,16 @@ function evaluate(project, registry, opts = {}) {
|
|
|
2886
3178
|
continue;
|
|
2887
3179
|
}
|
|
2888
3180
|
if (opts.vectors && readsPixels(def)) {
|
|
2889
|
-
const shapes = opts.vectors(node.id);
|
|
2890
|
-
const outPort = def.outputs[0];
|
|
2891
3181
|
const out = {};
|
|
2892
|
-
|
|
3182
|
+
const keys = [];
|
|
3183
|
+
for (const port of def.outputs) {
|
|
3184
|
+
if (port.type !== "shapes") continue;
|
|
3185
|
+
const shapes = opts.vectors(node.id, port.name);
|
|
3186
|
+
if (shapes) out[port.name] = shapesVal(shapes);
|
|
3187
|
+
keys.push(shapes ? String(identityKey(shapes)) : "none");
|
|
3188
|
+
}
|
|
2893
3189
|
outputs.set(node.id, withTaps(node.id, out));
|
|
2894
|
-
hashes.set(node.id, hashOf("provided", node.id,
|
|
3190
|
+
hashes.set(node.id, hashOf("provided", node.id, ...keys));
|
|
2895
3191
|
continue;
|
|
2896
3192
|
}
|
|
2897
3193
|
if (node.disabled) {
|
|
@@ -2909,6 +3205,10 @@ function evaluate(project, registry, opts = {}) {
|
|
|
2909
3205
|
);
|
|
2910
3206
|
continue;
|
|
2911
3207
|
}
|
|
3208
|
+
const providedKey = opts.textures && def.standIns ? def.standIns(inputs, multiInputs).map((key) => {
|
|
3209
|
+
const tex = opts.textures(key);
|
|
3210
|
+
return `${key}=${tex ? String(textureKey(tex)) : "none"}`;
|
|
3211
|
+
}).join(",") : "";
|
|
2912
3212
|
const nodeHash = hashOf(
|
|
2913
3213
|
node.type,
|
|
2914
3214
|
stable(node.params),
|
|
@@ -2927,7 +3227,12 @@ function evaluate(project, registry, opts = {}) {
|
|
|
2927
3227
|
timeDeps.has(node.id) ? String(tNode) : "",
|
|
2928
3228
|
// The outliner arriving mid-session must invalidate text cooked without
|
|
2929
3229
|
// it — one global salt, one full re-cook, then business as usual.
|
|
2930
|
-
opts.outliner ? "o1" : "o0"
|
|
3230
|
+
opts.outliner ? "o1" : "o0",
|
|
3231
|
+
// A claiming output wired or not reshapes the others (Contours' major).
|
|
3232
|
+
[...wiredOutputs].sort().join(","),
|
|
3233
|
+
// Pixels this cook borrows from the worker, named by identity like a
|
|
3234
|
+
// stood-in texture node's: a delivery must miss, a repeat must hit.
|
|
3235
|
+
providedKey
|
|
2931
3236
|
);
|
|
2932
3237
|
hashes.set(node.id, nodeHash);
|
|
2933
3238
|
const hit = upstreamTainted ? void 0 : cache22?.get(node.id, nodeHash);
|
|
@@ -2936,13 +3241,15 @@ function evaluate(project, registry, opts = {}) {
|
|
|
2936
3241
|
continue;
|
|
2937
3242
|
}
|
|
2938
3243
|
const ctx = makeCookContext({
|
|
3244
|
+
wiredOutputs,
|
|
2939
3245
|
nodeId: node.id,
|
|
2940
3246
|
name: node.name,
|
|
2941
3247
|
seed,
|
|
2942
3248
|
t: tNode,
|
|
2943
|
-
canvas: { w: project.canvas.w, h: project.canvas.h, origin: project.canvas.origin },
|
|
3249
|
+
canvas: { w: project.canvas.w, h: project.canvas.h, origin: project.canvas.origin, ...perspective === void 0 ? {} : { perspective } },
|
|
2944
3250
|
outline: opts.outliner,
|
|
2945
3251
|
rasterize: opts.rasterize,
|
|
3252
|
+
provided: opts.textures,
|
|
2946
3253
|
video: opts.video,
|
|
2947
3254
|
assets: project.assets,
|
|
2948
3255
|
asset: opts.asset,
|
|
@@ -3871,9 +4178,9 @@ function colorCorrectIsIdentity(o) {
|
|
|
3871
4178
|
return o.brightness === 0 && o.contrast === 0 && o.saturation === 0 && o.hue % 360 === 0;
|
|
3872
4179
|
}
|
|
3873
4180
|
function hueMatrix(degrees) {
|
|
3874
|
-
const
|
|
3875
|
-
const cos = Math.cos(
|
|
3876
|
-
const sin = Math.sin(
|
|
4181
|
+
const rad2 = degrees * Math.PI / 180;
|
|
4182
|
+
const cos = Math.cos(rad2);
|
|
4183
|
+
const sin = Math.sin(rad2);
|
|
3877
4184
|
return new Float64Array([
|
|
3878
4185
|
0.213 + cos * 0.787 - sin * 0.213,
|
|
3879
4186
|
0.715 - cos * 0.715 - sin * 0.715,
|
|
@@ -4668,6 +4975,47 @@ void main() {
|
|
|
4668
4975
|
vec4 top = B(ivec2(x0, y0)) * (1.0 - fx) + B(ivec2(x1, y0)) * fx;
|
|
4669
4976
|
vec4 bottom = B(ivec2(x0, y1)) * (1.0 - fx) + B(ivec2(x1, y1)) * fx;
|
|
4670
4977
|
o = vec4(rnd3((top * (1.0 - fy) + bottom * fy).rgb), rnd((top * (1.0 - fy) + bottom * fy).a)) / 255.0;
|
|
4978
|
+
}`,
|
|
4979
|
+
// warp.ts: each output centre back through the inverse homography onto
|
|
4980
|
+
// the bitmap, four taps mixed; taps off the bitmap are transparent black.
|
|
4981
|
+
warp: `${HEAD}
|
|
4982
|
+
uniform float u_m[9]; uniform vec3 u_place; uniform ivec2 u_out; uniform int u_w; uniform int u_h;
|
|
4983
|
+
vec4 T(int x, int y) { return (x < 0 || y < 0 || x >= u_w || y >= u_h) ? vec4(0.0) : B(ivec2(x, y)); }
|
|
4984
|
+
void main() {
|
|
4985
|
+
ivec2 p = P();
|
|
4986
|
+
float px = float(u_out.x + p.x) + 0.5;
|
|
4987
|
+
float py = float(u_out.y + p.y) + 0.5;
|
|
4988
|
+
float u = u_m[0] * px + u_m[1] * py + u_m[2];
|
|
4989
|
+
float v = u_m[3] * px + u_m[4] * py + u_m[5];
|
|
4990
|
+
float w = u_m[6] * px + u_m[7] * py + u_m[8];
|
|
4991
|
+
if (w <= 1e-9) { o = vec4(0.0); return; }
|
|
4992
|
+
float sx = (u / w - u_place.x) * u_place.z - 0.5;
|
|
4993
|
+
float sy = (v / w - u_place.y) * u_place.z - 0.5;
|
|
4994
|
+
if (sx < -1.0 || sy < -1.0 || sx > float(u_w) || sy > float(u_h)) { o = vec4(0.0); return; }
|
|
4995
|
+
int x0 = int(floor(sx)); int y0 = int(floor(sy));
|
|
4996
|
+
float fx = sx - float(x0); float fy = sy - float(y0);
|
|
4997
|
+
vec4 top = T(x0, y0) * (1.0 - fx) + T(x0 + 1, y0) * fx;
|
|
4998
|
+
vec4 bottom = T(x0, y0 + 1) * (1.0 - fx) + T(x0 + 1, y0 + 1) * fx;
|
|
4999
|
+
vec4 r = top * (1.0 - fy) + bottom * fy;
|
|
5000
|
+
o = vec4(rnd3(r.rgb), rnd(r.a)) / 255.0;
|
|
5001
|
+
}`,
|
|
5002
|
+
// dof.ts's focusSelect: the map's bytes unpremultiplied in whole numbers, the pixel kept or dropped.
|
|
5003
|
+
focusSelect: `${HEAD}
|
|
5004
|
+
uniform sampler2D u_map; uniform ivec2 u_off; uniform ivec2 u_msize; uniform int u_side; uniform ivec2 u_range;
|
|
5005
|
+
void main() {
|
|
5006
|
+
ivec2 p = P();
|
|
5007
|
+
ivec2 mp = p - u_off;
|
|
5008
|
+
bool mIn = mp.x >= 0 && mp.y >= 0 && mp.x < u_msize.x && mp.y < u_msize.y;
|
|
5009
|
+
ivec4 m = mIn ? ivec4(floor(texelFetch(u_map, mp, 0) * 255.0 + 0.5)) : ivec4(0);
|
|
5010
|
+
int level = 0;
|
|
5011
|
+
int side = 0;
|
|
5012
|
+
if (m.a > 0) {
|
|
5013
|
+
int h = m.a / 2;
|
|
5014
|
+
level = (m.r * 255 + h) / m.a;
|
|
5015
|
+
side = (m.g * 255 + h) / m.a >= 128 ? 1 : 0;
|
|
5016
|
+
}
|
|
5017
|
+
bool keep = side == u_side && level >= u_range.x && level <= u_range.y;
|
|
5018
|
+
o = keep ? S(p) : vec4(0.0);
|
|
4671
5019
|
}`,
|
|
4672
5020
|
matte: `${HEAD}
|
|
4673
5021
|
uniform sampler2D u_matte; uniform ivec2 u_off; uniform ivec2 u_msize; uniform int u_luma; uniform int u_invert;
|
|
@@ -4857,6 +5205,125 @@ function resample(tex, width, height) {
|
|
|
4857
5205
|
if (tex.at) result.at = tex.at;
|
|
4858
5206
|
return result;
|
|
4859
5207
|
}
|
|
5208
|
+
function warpTexture(src, place, hInv, out) {
|
|
5209
|
+
const W = Math.max(1, Math.round(out.w));
|
|
5210
|
+
const H = Math.max(1, Math.round(out.h));
|
|
5211
|
+
const g = gpuKernels();
|
|
5212
|
+
if (g) return g.warp(src, place, hInv, { x: out.x, y: out.y, w: W, h: H });
|
|
5213
|
+
const { width: w, height: h, data } = src;
|
|
5214
|
+
const pixels = new Uint8ClampedArray(W * H * 4);
|
|
5215
|
+
warpLoop(data, w, h, pixels, W, H, place, hInv, out.x, out.y);
|
|
5216
|
+
return { width: W, height: H, data: pixels, at: { x: out.x, y: out.y } };
|
|
5217
|
+
}
|
|
5218
|
+
function warpLoop(data, w, h, pixels, W, H, place, m, ox, oy) {
|
|
5219
|
+
const [m0, m1, m2, m3, m4, m5, m6, m7, m8] = m;
|
|
5220
|
+
const { x: px0, y: py0, scale: scale2 } = place;
|
|
5221
|
+
const tap = (x, y, k) => x < 0 || y < 0 || x >= w || y >= h ? 0 : data[(y * w + x) * 4 + k];
|
|
5222
|
+
for (let j = 0; j < H; j++) {
|
|
5223
|
+
const py = oy + j + 0.5;
|
|
5224
|
+
for (let i = 0; i < W; i++) {
|
|
5225
|
+
const px = ox + i + 0.5;
|
|
5226
|
+
const u = m0 * px + m1 * py + m2;
|
|
5227
|
+
const v = m3 * px + m4 * py + m5;
|
|
5228
|
+
const wq = m6 * px + m7 * py + m8;
|
|
5229
|
+
if (wq <= 1e-9) continue;
|
|
5230
|
+
const sx = (u / wq - px0) * scale2 - 0.5;
|
|
5231
|
+
const sy = (v / wq - py0) * scale2 - 0.5;
|
|
5232
|
+
if (sx < -1 || sy < -1 || sx > w || sy > h) continue;
|
|
5233
|
+
const x0 = Math.floor(sx);
|
|
5234
|
+
const y0 = Math.floor(sy);
|
|
5235
|
+
const fx = sx - x0;
|
|
5236
|
+
const fy = sy - y0;
|
|
5237
|
+
const o = (j * W + i) * 4;
|
|
5238
|
+
for (let k = 0; k < 4; k++) {
|
|
5239
|
+
const top = tap(x0, y0, k) * (1 - fx) + tap(x0 + 1, y0, k) * fx;
|
|
5240
|
+
const bottom = tap(x0, y0 + 1, k) * (1 - fx) + tap(x0 + 1, y0 + 1, k) * fx;
|
|
5241
|
+
pixels[o + k] = Math.round(top * (1 - fy) + bottom * fy);
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
var FOCUS_LEVEL_PX = 0.25;
|
|
5247
|
+
function focusLevel(radius) {
|
|
5248
|
+
return Math.max(0, Math.min(255, Math.round(radius / FOCUS_LEVEL_PX)));
|
|
5249
|
+
}
|
|
5250
|
+
function ordered(near, far) {
|
|
5251
|
+
const ranges = (levels, side) => {
|
|
5252
|
+
const sorted = [...new Set(levels)].sort((a, b) => a - b);
|
|
5253
|
+
return sorted.map((level, i) => ({
|
|
5254
|
+
side,
|
|
5255
|
+
lo: i === 0 ? 0 : Math.floor((sorted[i - 1] + level) / 2) + 1,
|
|
5256
|
+
hi: i === sorted.length - 1 ? 255 : Math.floor((level + sorted[i + 1]) / 2),
|
|
5257
|
+
radius: level * FOCUS_LEVEL_PX
|
|
5258
|
+
}));
|
|
5259
|
+
};
|
|
5260
|
+
return [...ranges(far, 1).reverse(), ...ranges(near, 0)];
|
|
5261
|
+
}
|
|
5262
|
+
function focusSlices(levels, max = 24) {
|
|
5263
|
+
let q = 1;
|
|
5264
|
+
for (; ; ) {
|
|
5265
|
+
const snap2 = (l) => Math.min(255, Math.round(l / q) * q);
|
|
5266
|
+
const near = [0];
|
|
5267
|
+
const far = [];
|
|
5268
|
+
for (const { level, side } of levels) {
|
|
5269
|
+
const l = snap2(Math.max(0, Math.min(255, Math.round(level))));
|
|
5270
|
+
if (side === 1 && l > 0) far.push(l);
|
|
5271
|
+
else near.push(l);
|
|
5272
|
+
}
|
|
5273
|
+
const slices = ordered(near, far);
|
|
5274
|
+
if (slices.length <= max || q >= 256) return slices;
|
|
5275
|
+
q *= 2;
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
5278
|
+
function focusSelect(tex, map, ox, oy, side, lo, hi) {
|
|
5279
|
+
const g = gpuKernels();
|
|
5280
|
+
if (g) return g.focusSelect(tex, map, ox, oy, side, lo, hi);
|
|
5281
|
+
const { width, height, data } = tex;
|
|
5282
|
+
const m = map.data;
|
|
5283
|
+
const out = like2(tex);
|
|
5284
|
+
const o = out.data;
|
|
5285
|
+
for (let y = 0; y < height; y++) {
|
|
5286
|
+
const my = y - oy;
|
|
5287
|
+
const rowIn = my >= 0 && my < map.height;
|
|
5288
|
+
for (let x = 0; x < width; x++) {
|
|
5289
|
+
const mx = x - ox;
|
|
5290
|
+
let level = 0;
|
|
5291
|
+
let on = 0;
|
|
5292
|
+
if (rowIn && mx >= 0 && mx < map.width) {
|
|
5293
|
+
const j = (my * map.width + mx) * 4;
|
|
5294
|
+
const a = m[j + 3];
|
|
5295
|
+
if (a > 0) {
|
|
5296
|
+
const half = a >> 1;
|
|
5297
|
+
level = Math.floor((m[j] * 255 + half) / a);
|
|
5298
|
+
on = Math.floor((m[j + 1] * 255 + half) / a) >= 128 ? 1 : 0;
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
if (on !== side || level < lo || level > hi) continue;
|
|
5302
|
+
const i = (y * width + x) * 4;
|
|
5303
|
+
o[i] = data[i];
|
|
5304
|
+
o[i + 1] = data[i + 1];
|
|
5305
|
+
o[i + 2] = data[i + 2];
|
|
5306
|
+
o[i + 3] = data[i + 3];
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
return out;
|
|
5310
|
+
}
|
|
5311
|
+
function depthOfField(tex, map, slices, origin = "top-left") {
|
|
5312
|
+
if (slices.length === 0) return copyTexture(tex);
|
|
5313
|
+
const pt2 = placementOf(tex, origin);
|
|
5314
|
+
const pm = placementOf(map, origin);
|
|
5315
|
+
const ox = pm.x - pt2.x;
|
|
5316
|
+
const oy = pm.y - pt2.y;
|
|
5317
|
+
const g = gpuKernels();
|
|
5318
|
+
if (g) return g.depthOfField(tex, map, ox, oy, slices.map((s) => s.radius > 0 ? { ...s, radii: boxRadii(s.radius) } : s));
|
|
5319
|
+
let acc;
|
|
5320
|
+
for (const s of slices) {
|
|
5321
|
+
const cut3 = focusSelect(tex, map, ox, oy, s.side, s.lo, s.hi);
|
|
5322
|
+
const soft = s.radius > 0 ? blur(cut3, s.radius) : cut3;
|
|
5323
|
+
acc = acc ? blendTextures(acc, soft, "normal", 1, origin) : soft;
|
|
5324
|
+
}
|
|
5325
|
+
return acc;
|
|
5326
|
+
}
|
|
4860
5327
|
var divRound = (num3, den) => Math.floor((2 * num3 + den) / (2 * den));
|
|
4861
5328
|
function sampleCells(tex, grid) {
|
|
4862
5329
|
const asked = grid.sub ?? 1;
|
|
@@ -5755,6 +6222,7 @@ var lineNode = {
|
|
|
5755
6222
|
type: "line",
|
|
5756
6223
|
label: "Line",
|
|
5757
6224
|
category: "generator",
|
|
6225
|
+
description: "A straight stroke from (X1, Y1) to (X2, Y2).",
|
|
5758
6226
|
// Both endpoints, so dragging moves the whole line.
|
|
5759
6227
|
translate: [{ x: "x1", y: "y1" }, { x: "x2", y: "y2" }],
|
|
5760
6228
|
inputs: [],
|
|
@@ -6223,8 +6691,8 @@ var videoNode = {
|
|
|
6223
6691
|
`the bytes of \u201C${record.name}\u201D are not on this machine \u2014 drop the file on the graph to restore them (the CLI reads them from the project\u2019s .assets folder)`
|
|
6224
6692
|
);
|
|
6225
6693
|
}
|
|
6226
|
-
const
|
|
6227
|
-
return { out: textureVal(
|
|
6694
|
+
const placed2 = frame.at && frame.at.x === at2.x && frame.at.y === at2.y ? frame : { ...frame, at: at2 };
|
|
6695
|
+
return { out: textureVal(placed2) };
|
|
6228
6696
|
}
|
|
6229
6697
|
};
|
|
6230
6698
|
var OPS2 = ["union", "subtract", "intersect", "exclude"];
|
|
@@ -8040,9 +8508,9 @@ function textShape(ctx, i, count, opts) {
|
|
|
8040
8508
|
const letterSpacing = grid ? Math.round(opts.letterSpacing) : opts.letterSpacing;
|
|
8041
8509
|
const onGrid = grid !== void 0 && rotation === 0;
|
|
8042
8510
|
const style = glyphStyle(ctx, i, count);
|
|
8043
|
-
const
|
|
8044
|
-
const cos = Math.cos(
|
|
8045
|
-
const sin = Math.sin(
|
|
8511
|
+
const rad2 = rotation * Math.PI / 180;
|
|
8512
|
+
const cos = Math.cos(rad2);
|
|
8513
|
+
const sin = Math.sin(rad2);
|
|
8046
8514
|
const measured = ctx.outline?.measure?.({ text, family, weight, stretch, size: fontSize, letterSpacing });
|
|
8047
8515
|
const capHeight = measured?.capHeight ?? fontSize * FALLBACK_CAP_HEIGHT;
|
|
8048
8516
|
const vAnchor = ctx.enumOf("vAnchor", ["baseline", "middle", "top"]);
|
|
@@ -8514,13 +8982,13 @@ var textOnPathNode = {
|
|
|
8514
8982
|
const gaps = closed ? runs.length : runs.length - 1;
|
|
8515
8983
|
between = gaps > 0 ? (length - words) / gaps : 0;
|
|
8516
8984
|
}
|
|
8517
|
-
const
|
|
8985
|
+
const placed2 = [];
|
|
8518
8986
|
let pen = 0;
|
|
8519
8987
|
for (const [r, { shape, run, outlined }] of runs.entries()) {
|
|
8520
8988
|
if (r > 0) pen += between;
|
|
8521
8989
|
const chars = [...shape.text];
|
|
8522
8990
|
for (const [g, glyph] of run.glyphs.entries()) {
|
|
8523
|
-
|
|
8991
|
+
placed2.push({ shape, d: glyph.d, ch: chars[g] ?? "", x: pen + glyph.x + g * spacing, advance: glyph.advance, outlined });
|
|
8524
8992
|
}
|
|
8525
8993
|
pen += runWidth(run);
|
|
8526
8994
|
}
|
|
@@ -8530,12 +8998,12 @@ var textOnPathNode = {
|
|
|
8530
8998
|
if (align === "middle") start = position - total / 2;
|
|
8531
8999
|
else if (align === "end") start = position - total;
|
|
8532
9000
|
else if (align === "justify") {
|
|
8533
|
-
const gaps = closed ?
|
|
9001
|
+
const gaps = closed ? placed2.length : placed2.length - 1;
|
|
8534
9002
|
if (gaps > 0) gap = (length - total) / gaps;
|
|
8535
9003
|
}
|
|
8536
9004
|
const out = [];
|
|
8537
9005
|
let index = 0;
|
|
8538
|
-
for (const [k, g] of
|
|
9006
|
+
for (const [k, g] of placed2.entries()) {
|
|
8539
9007
|
const mid = g.x + k * gap + g.advance / 2;
|
|
8540
9008
|
const { p, t } = along(points, closed, length, start + mid);
|
|
8541
9009
|
const angle = Math.atan2(t.y, t.x) * 180 / Math.PI;
|
|
@@ -9137,13 +9605,13 @@ var arrayLinearNode = {
|
|
|
9137
9605
|
const tx = ox + ctx.num("stepX", k, count, vars) * k;
|
|
9138
9606
|
const ty = oy + ctx.num("stepY", k, count, vars) * k;
|
|
9139
9607
|
const rotation = ctx.num("rotationStep", k, count, vars) * k;
|
|
9140
|
-
const
|
|
9608
|
+
const scale5 = ctx.num("scaleStep", k, count, vars) ** k;
|
|
9141
9609
|
const shapes = carry(k);
|
|
9142
9610
|
for (const [j, shape] of shapes.entries()) {
|
|
9143
9611
|
const index = out.length + j;
|
|
9144
9612
|
const base2 = deal ? pivotFor(ctx, pivotMode, shape, j, shapes.length) : pivots[j];
|
|
9145
9613
|
const pivot = pivotInMoveSpace(pivotMode, base2, tx, ty);
|
|
9146
|
-
const grown =
|
|
9614
|
+
const grown = scale5 !== 1 ? scaleAbout(shape, scale5, scale5, pivot) : shape;
|
|
9147
9615
|
const m = compose({ tx, ty, rotation, pivot });
|
|
9148
9616
|
out.push({
|
|
9149
9617
|
...grown,
|
|
@@ -9157,6 +9625,442 @@ var arrayLinearNode = {
|
|
|
9157
9625
|
}
|
|
9158
9626
|
};
|
|
9159
9627
|
var MODIFIERS = [transformNode, arrayLinearNode];
|
|
9628
|
+
var isBitmap = (s) => isBitmapFamily(s.fontFamily);
|
|
9629
|
+
var isVariable = (s) => resolveFace(s.fontFamily, s.fontWeight ?? 400, s.fontStretch).variation !== void 0;
|
|
9630
|
+
var needsOutlining = (s) => isBitmap(s) || isVariable(s);
|
|
9631
|
+
var every = () => true;
|
|
9632
|
+
function anyText(scene, pick) {
|
|
9633
|
+
const any = (shapes) => shapes.some((s) => s.kind === "text" ? pick(s) : s.kind === "group" && any(s.children));
|
|
9634
|
+
return scene.layers.some((l) => any(l.shapes));
|
|
9635
|
+
}
|
|
9636
|
+
function outlineWhere(shape, outliner, pick) {
|
|
9637
|
+
if (shape.kind === "group") {
|
|
9638
|
+
const children = shape.children.map((c2) => outlineWhere(c2, outliner, pick));
|
|
9639
|
+
return children.some((c2, i) => c2 !== shape.children[i]) ? { ...shape, children } : shape;
|
|
9640
|
+
}
|
|
9641
|
+
if (shape.kind !== "text" || !pick(shape)) return shape;
|
|
9642
|
+
const weight = shape.fontWeight ?? 400;
|
|
9643
|
+
const g = outliner({ text: shape.text, family: shape.fontFamily, weight, stretch: shape.fontStretch, size: shape.fontSize, letterSpacing: shape.letterSpacing });
|
|
9644
|
+
if (!g) return shape;
|
|
9645
|
+
const shift = shape.anchor === "middle" ? -g.width / 2 : shape.anchor === "end" ? -g.width : 0;
|
|
9646
|
+
const [a, b, c, d, e, f] = shape.transform;
|
|
9647
|
+
const tx = shape.x + shift;
|
|
9648
|
+
const ty = shape.y;
|
|
9649
|
+
const { x: _x, y: _y, text: _t, fontSize: _s, fontFamily: _f, fontWeight: _w, fontStretch: _st, anchor: _a, letterSpacing: _l, metrics: _m, ...rest } = shape;
|
|
9650
|
+
return {
|
|
9651
|
+
...rest,
|
|
9652
|
+
kind: "path",
|
|
9653
|
+
d: g.d,
|
|
9654
|
+
transform: [a, b, c, d, a * tx + c * ty + e, b * tx + d * ty + f],
|
|
9655
|
+
meta: { ...shape.meta, tags: [...shape.meta.tags ?? [], "glyphs"] }
|
|
9656
|
+
};
|
|
9657
|
+
}
|
|
9658
|
+
function outlineTextShape(shape, outliner, all = false) {
|
|
9659
|
+
return outlineWhere(shape, outliner, all ? every : needsOutlining);
|
|
9660
|
+
}
|
|
9661
|
+
function outlineScene(scene, outliner, pick) {
|
|
9662
|
+
if (!outliner || !anyText(scene, pick)) return scene;
|
|
9663
|
+
return {
|
|
9664
|
+
...scene,
|
|
9665
|
+
layers: scene.layers.map((l) => {
|
|
9666
|
+
const shapes = l.shapes.map((s) => outlineWhere(s, outliner, pick));
|
|
9667
|
+
return shapes.some((s, i) => s !== l.shapes[i]) ? { ...l, shapes } : l;
|
|
9668
|
+
})
|
|
9669
|
+
};
|
|
9670
|
+
}
|
|
9671
|
+
function outlineBitmapText(scene, outliner) {
|
|
9672
|
+
return outlineScene(scene, outliner, isBitmap);
|
|
9673
|
+
}
|
|
9674
|
+
var LAYER_3D_DEFAULTS = {
|
|
9675
|
+
posX: 0,
|
|
9676
|
+
posY: 0,
|
|
9677
|
+
posZ: 0,
|
|
9678
|
+
rotX: 0,
|
|
9679
|
+
rotY: 0,
|
|
9680
|
+
rotZ: 0,
|
|
9681
|
+
scaleX: 100,
|
|
9682
|
+
scaleY: 100
|
|
9683
|
+
};
|
|
9684
|
+
var PICTURE_NODE_TYPES = /* @__PURE__ */ new Set(["image", "picture", "gradient", "noise-paint"]);
|
|
9685
|
+
function layerIsPosed(node, edges) {
|
|
9686
|
+
if (node.type !== "layer") return false;
|
|
9687
|
+
for (const [key, rest] of Object.entries(LAYER_3D_DEFAULTS)) {
|
|
9688
|
+
const p = normalizeParam(node.params[key]);
|
|
9689
|
+
if (p.track || p.expr || p.wire) return true;
|
|
9690
|
+
if (p.base !== void 0 && p.base !== rest) return true;
|
|
9691
|
+
if (edges.some((e) => e.to === node.id && e.toPort === key)) return true;
|
|
9692
|
+
}
|
|
9693
|
+
return false;
|
|
9694
|
+
}
|
|
9695
|
+
function posedLayersWithPictures(project) {
|
|
9696
|
+
const { nodes, edges } = project.graph;
|
|
9697
|
+
return nodes.filter((n) => layerIsPosed(n, edges) && nodes.some((m) => m.layer === n.id && PICTURE_NODE_TYPES.has(m.type)));
|
|
9698
|
+
}
|
|
9699
|
+
function texturePlace(texture, origin) {
|
|
9700
|
+
return texture.at ?? (origin === "center" ? { x: -texture.width / 2, y: -texture.height / 2 } : { x: 0, y: 0 });
|
|
9701
|
+
}
|
|
9702
|
+
function frameRect(camera) {
|
|
9703
|
+
return camera.origin === "center" ? { x: -camera.width / 2, y: -camera.height / 2, w: camera.width, h: camera.height } : { x: 0, y: 0, w: camera.width, h: camera.height };
|
|
9704
|
+
}
|
|
9705
|
+
function layerContentBounds(layer, origin) {
|
|
9706
|
+
const shapes = boundsOfShapes(layer.shapes);
|
|
9707
|
+
if (!layer.texture) return shapes;
|
|
9708
|
+
const p = texturePlace(layer.texture, origin);
|
|
9709
|
+
const pic = { x: p.x, y: p.y, w: layer.texture.width, h: layer.texture.height };
|
|
9710
|
+
return shapes ? union(shapes, pic) : pic;
|
|
9711
|
+
}
|
|
9712
|
+
function layerPivot(mode, layer, camera, custom) {
|
|
9713
|
+
if (mode === "canvas") return { x: 0, y: 0 };
|
|
9714
|
+
if (mode === "custom") return custom;
|
|
9715
|
+
if (mode === "shape") {
|
|
9716
|
+
const b = layerContentBounds(layer, camera.origin);
|
|
9717
|
+
return b ? centerOf(b) : frameCentre(camera);
|
|
9718
|
+
}
|
|
9719
|
+
return frameCentre(camera);
|
|
9720
|
+
}
|
|
9721
|
+
function isPictureShape(shape) {
|
|
9722
|
+
if (shape.kind === "image") return true;
|
|
9723
|
+
if (shape.kind === "group") return shape.children.some(isPictureShape);
|
|
9724
|
+
return typeof shape.style.fill === "object" || typeof shape.style.stroke === "object";
|
|
9725
|
+
}
|
|
9726
|
+
function layerPieces(layer) {
|
|
9727
|
+
const pieces = [];
|
|
9728
|
+
const at2 = layer.texture ? layer.textureAt ?? 0 : layer.shapes.length;
|
|
9729
|
+
const idOf = () => pieces.length === 0 ? layer.id : `${layer.id}/${pieces.length}`;
|
|
9730
|
+
const last = () => pieces[pieces.length - 1];
|
|
9731
|
+
const pushShape = (shape) => {
|
|
9732
|
+
const kind = isPictureShape(shape) ? "picture" : "vector";
|
|
9733
|
+
const tail = last();
|
|
9734
|
+
if (tail && tail.kind === kind) tail.shapes.push(shape);
|
|
9735
|
+
else pieces.push({ kind, id: idOf(), shapes: [shape] });
|
|
9736
|
+
};
|
|
9737
|
+
for (let i = 0; i < layer.shapes.length; i++) {
|
|
9738
|
+
if (i === at2 && layer.texture) {
|
|
9739
|
+
const tail = last();
|
|
9740
|
+
if (tail && tail.kind === "picture") {
|
|
9741
|
+
tail.texture = layer.texture;
|
|
9742
|
+
tail.textureAt = tail.shapes.length;
|
|
9743
|
+
} else pieces.push({ kind: "picture", id: idOf(), shapes: [], texture: layer.texture });
|
|
9744
|
+
}
|
|
9745
|
+
pushShape(layer.shapes[i]);
|
|
9746
|
+
}
|
|
9747
|
+
if (layer.texture && at2 >= layer.shapes.length) {
|
|
9748
|
+
const tail = last();
|
|
9749
|
+
if (tail && tail.kind === "picture") {
|
|
9750
|
+
tail.texture = layer.texture;
|
|
9751
|
+
tail.textureAt = tail.shapes.length;
|
|
9752
|
+
} else pieces.push({ kind: "picture", id: idOf(), shapes: [], texture: layer.texture });
|
|
9753
|
+
}
|
|
9754
|
+
return pieces;
|
|
9755
|
+
}
|
|
9756
|
+
function placeOnTangent(shape, h) {
|
|
9757
|
+
const b = shapeBounds(shape);
|
|
9758
|
+
const a = affineAt(h, b ? centerOf(b) : { x: 0, y: 0 });
|
|
9759
|
+
return a ? { ...shape, transform: multiply(a, shape.transform) } : void 0;
|
|
9760
|
+
}
|
|
9761
|
+
function projectShape(shape, parent, h, outliner) {
|
|
9762
|
+
const world = multiply(parent, shape.transform);
|
|
9763
|
+
switch (shape.kind) {
|
|
9764
|
+
case "group": {
|
|
9765
|
+
const children = shape.children.map((c) => projectShape(c, world, h, outliner)).filter((c) => c !== void 0);
|
|
9766
|
+
return { ...shape, transform: IDENTITY, children };
|
|
9767
|
+
}
|
|
9768
|
+
case "text": {
|
|
9769
|
+
if (outliner) {
|
|
9770
|
+
const outlined = outlineTextShape(shape, outliner, true);
|
|
9771
|
+
if (outlined.kind === "path") return projectShape(outlined, parent, h, outliner);
|
|
9772
|
+
}
|
|
9773
|
+
const a = affineAt(h, apply(world, { x: shape.x, y: shape.y }));
|
|
9774
|
+
return a ? { ...shape, transform: multiply(a, world) } : void 0;
|
|
9775
|
+
}
|
|
9776
|
+
case "image": {
|
|
9777
|
+
const a = affineAt(h, apply(world, { x: shape.x + shape.w / 2, y: shape.y + shape.h / 2 }));
|
|
9778
|
+
return a ? { ...shape, transform: multiply(a, world) } : void 0;
|
|
9779
|
+
}
|
|
9780
|
+
default: {
|
|
9781
|
+
const runs = flatten({ ...shape, transform: world }).flatMap(
|
|
9782
|
+
(line) => projectPolyline(h, line.points, line.closed).map((points) => ({ points, closed: line.closed }))
|
|
9783
|
+
);
|
|
9784
|
+
if (runs.length === 0) return void 0;
|
|
9785
|
+
return { id: shape.id, kind: "path", d: polylinesToPathD(runs), transform: IDENTITY, style: shape.style, meta: shape.meta };
|
|
9786
|
+
}
|
|
9787
|
+
}
|
|
9788
|
+
}
|
|
9789
|
+
function projectShapes(shapes, h, outliner) {
|
|
9790
|
+
const rigid = rigidAffineOf(h);
|
|
9791
|
+
if (rigid) return shapes.map((s) => ({ ...s, transform: multiply(rigid, s.transform) }));
|
|
9792
|
+
return shapes.map((s) => projectShape(s, IDENTITY, h, outliner)).filter((s) => s !== void 0);
|
|
9793
|
+
}
|
|
9794
|
+
var PictureCache = class {
|
|
9795
|
+
constructor(limit) {
|
|
9796
|
+
this.limit = limit;
|
|
9797
|
+
}
|
|
9798
|
+
limit;
|
|
9799
|
+
map = /* @__PURE__ */ new Map();
|
|
9800
|
+
get(key) {
|
|
9801
|
+
const v = this.map.get(key);
|
|
9802
|
+
if (v) {
|
|
9803
|
+
this.map.delete(key);
|
|
9804
|
+
this.map.set(key, v);
|
|
9805
|
+
}
|
|
9806
|
+
return v;
|
|
9807
|
+
}
|
|
9808
|
+
set(key, v) {
|
|
9809
|
+
gpuKernels()?.retain(v.tex);
|
|
9810
|
+
this.map.set(key, v);
|
|
9811
|
+
while (this.map.size > this.limit) {
|
|
9812
|
+
const first = this.map.entries().next().value;
|
|
9813
|
+
if (!first) break;
|
|
9814
|
+
this.map.delete(first[0]);
|
|
9815
|
+
gpuKernels()?.release(first[1].tex);
|
|
9816
|
+
}
|
|
9817
|
+
return v;
|
|
9818
|
+
}
|
|
9819
|
+
};
|
|
9820
|
+
var flats = new PictureCache(64);
|
|
9821
|
+
var warps = new PictureCache(64);
|
|
9822
|
+
var PAD2 = 2;
|
|
9823
|
+
var SCALE_STEP = 4;
|
|
9824
|
+
var MAX_SCALE = 2;
|
|
9825
|
+
function flatPicture(piece, box, s, camera, ctx) {
|
|
9826
|
+
const key = `${piece.shapes.map((sh) => identityOf(sh)).join(",")}|${piece.texture ? identityOf(piece.texture) : "-"}|${piece.textureAt ?? "-"}|${s}|${box.x},${box.y},${box.w},${box.h}`;
|
|
9827
|
+
const hit = flats.get(key);
|
|
9828
|
+
if (hit) return hit;
|
|
9829
|
+
const into = s === 1 ? translate(-box.x, -box.y) : multiply(scale(s), translate(-box.x, -box.y));
|
|
9830
|
+
const shapes = piece.shapes.map((sh) => ({ ...sh, transform: multiply(into, sh.transform) }));
|
|
9831
|
+
const width = Math.max(1, Math.ceil(box.w * s));
|
|
9832
|
+
const height = Math.max(1, Math.ceil(box.h * s));
|
|
9833
|
+
let texture;
|
|
9834
|
+
if (piece.texture) {
|
|
9835
|
+
const p = texturePlace(piece.texture, camera.origin);
|
|
9836
|
+
const at2 = { x: (p.x - box.x) * s, y: (p.y - box.y) * s };
|
|
9837
|
+
const sized = s === 1 ? piece.texture : resample(piece.texture, piece.texture.width * s, piece.texture.height * s);
|
|
9838
|
+
texture = placed(sized, at2);
|
|
9839
|
+
}
|
|
9840
|
+
const flat = { id: piece.id, name: "posed", opacity: 1, visible: true, order: 0, shapes };
|
|
9841
|
+
if (texture) {
|
|
9842
|
+
flat.texture = texture;
|
|
9843
|
+
if (piece.textureAt !== void 0) flat.textureAt = piece.textureAt;
|
|
9844
|
+
}
|
|
9845
|
+
const tex = ctx.rasterize({ width, height, background: "none", origin: "top-left", layers: [flat] }, { x: box.x, y: box.y });
|
|
9846
|
+
return flats.set(key, { tex, x: box.x, y: box.y, scale: s });
|
|
9847
|
+
}
|
|
9848
|
+
function placed(tex, at2) {
|
|
9849
|
+
const g = gpuKernels();
|
|
9850
|
+
if (g?.resident(tex)) return g.copy(tex, at2);
|
|
9851
|
+
return { ...tex, at: at2 };
|
|
9852
|
+
}
|
|
9853
|
+
function snap(b) {
|
|
9854
|
+
const x = Math.floor(b.x);
|
|
9855
|
+
const y = Math.floor(b.y);
|
|
9856
|
+
return { x, y, w: Math.ceil(b.x + b.w) - x, h: Math.ceil(b.y + b.h) - y };
|
|
9857
|
+
}
|
|
9858
|
+
function intersect(a, b) {
|
|
9859
|
+
const x = Math.max(a.x, b.x);
|
|
9860
|
+
const y = Math.max(a.y, b.y);
|
|
9861
|
+
const r = Math.min(a.x + a.w, b.x + b.w);
|
|
9862
|
+
const d = Math.min(a.y + a.h, b.y + b.h);
|
|
9863
|
+
return r > x && d > y ? { x, y, w: r - x, h: d - y } : void 0;
|
|
9864
|
+
}
|
|
9865
|
+
function picturePiece(piece, base2, h, pose, camera, ctx, crisp) {
|
|
9866
|
+
const plane = { h };
|
|
9867
|
+
const served = ctx.provided?.(piece.id);
|
|
9868
|
+
if (served) return { ...base2, id: piece.id, shapes: [], texture: served, plane };
|
|
9869
|
+
if (!ctx.rasterize) {
|
|
9870
|
+
const shapes = piece.shapes.map((s2) => placeOnTangent(s2, h)).filter((s2) => s2 !== void 0);
|
|
9871
|
+
const out2 = { ...base2, id: piece.id, shapes, plane };
|
|
9872
|
+
if (piece.texture) {
|
|
9873
|
+
out2.texture = piece.texture;
|
|
9874
|
+
if (piece.textureAt !== void 0) out2.textureAt = piece.textureAt;
|
|
9875
|
+
}
|
|
9876
|
+
return out2;
|
|
9877
|
+
}
|
|
9878
|
+
const empty = { ...base2, id: piece.id, shapes: [], plane };
|
|
9879
|
+
const content = layerContentBounds(piece, camera.origin);
|
|
9880
|
+
if (!content) return empty;
|
|
9881
|
+
const frame = frameRect(camera);
|
|
9882
|
+
const reach = { x: frame.x - frame.w, y: frame.y - frame.h, w: frame.w * 3, h: frame.h * 3 };
|
|
9883
|
+
const padded = { x: content.x - PAD2, y: content.y - PAD2, w: content.w + 2 * PAD2, h: content.h + 2 * PAD2 };
|
|
9884
|
+
const within = intersect(padded, reach);
|
|
9885
|
+
if (!within) return empty;
|
|
9886
|
+
const box = snap(within);
|
|
9887
|
+
const inv = invertMat3(h);
|
|
9888
|
+
const out = inv && projectedQuadBounds(h, box);
|
|
9889
|
+
const target = out && intersect(out, frame);
|
|
9890
|
+
if (!inv || !target) return empty;
|
|
9891
|
+
const rect = snap(target);
|
|
9892
|
+
const s = crisp ? 1 : Math.min(MAX_SCALE, Math.max(1, Math.ceil((magnificationAt(h, pose.pivot) ?? 1) * SCALE_STEP) / SCALE_STEP));
|
|
9893
|
+
const flat = flatPicture(piece, box, s, camera, ctx);
|
|
9894
|
+
const key = `${identityOf(flat.tex)}|${h.join(",")}|${rect.x},${rect.y},${rect.w},${rect.h}`;
|
|
9895
|
+
const hit = warps.get(key);
|
|
9896
|
+
const texture = hit ? hit.tex : warps.set(key, { tex: warpTexture(flat.tex, { x: flat.x, y: flat.y, scale: flat.scale }, inv, rect) }).tex;
|
|
9897
|
+
return { ...base2, id: piece.id, shapes: [], texture, plane };
|
|
9898
|
+
}
|
|
9899
|
+
function projectLayer(layer, camera, ctx, crisp = false) {
|
|
9900
|
+
const pose = layer.pose;
|
|
9901
|
+
if (!pose || !layer.visible) return [layer];
|
|
9902
|
+
const h = layerHomography(pose, camera);
|
|
9903
|
+
const { pose: _pose, plane: _plane, shapes: _shapes, texture: _texture, textureAt: _at, id: _id, ...base2 } = layer;
|
|
9904
|
+
return layerPieces(layer).map(
|
|
9905
|
+
(piece) => piece.kind === "vector" ? { ...base2, id: piece.id, shapes: projectShapes(piece.shapes, h, ctx.outline), plane: { h } } : picturePiece(piece, base2, h, pose, camera, ctx, crisp)
|
|
9906
|
+
);
|
|
9907
|
+
}
|
|
9908
|
+
var ownCamera = (p) => normalizeParam(p.camera).base === "own";
|
|
9909
|
+
var onePlane = (p) => (normalizeParam(p.plane).base ?? "stream") !== "shape";
|
|
9910
|
+
var pivotIsPreset2 = (p) => (normalizeParam(p.pivot).base ?? "shape") !== "custom";
|
|
9911
|
+
var PIVOTS = ["shape", "center", "canvas", "custom"];
|
|
9912
|
+
var transform3dNode = {
|
|
9913
|
+
type: "transform3d",
|
|
9914
|
+
label: "Transform 3D",
|
|
9915
|
+
category: "modifier",
|
|
9916
|
+
description: "Poses the stream as a plane under the camera: move in X, Y and Z, turn about X, Y and Z, scale \u2014 one plane for the stream, or one per shape.",
|
|
9917
|
+
inputs: [{ name: "in", type: "shapes" }],
|
|
9918
|
+
outputs: [{ name: "out", type: "shapes" }],
|
|
9919
|
+
params: [
|
|
9920
|
+
{
|
|
9921
|
+
key: "plane",
|
|
9922
|
+
label: "Plane",
|
|
9923
|
+
type: "enum",
|
|
9924
|
+
default: "stream",
|
|
9925
|
+
options: [
|
|
9926
|
+
{ value: "stream", label: "One for the stream" },
|
|
9927
|
+
{ value: "shape", label: "One per shape" }
|
|
9928
|
+
],
|
|
9929
|
+
description: "One plane carrying every shape, or each shape on a plane of its own \u2014 then every value below resolves per shape, so a jitter or a stagger turns them one by one."
|
|
9930
|
+
},
|
|
9931
|
+
{
|
|
9932
|
+
key: "order",
|
|
9933
|
+
label: "Order",
|
|
9934
|
+
type: "enum",
|
|
9935
|
+
default: "stack",
|
|
9936
|
+
options: [
|
|
9937
|
+
{ value: "stack", label: "As they came" },
|
|
9938
|
+
{ value: "depth", label: "By depth" }
|
|
9939
|
+
],
|
|
9940
|
+
description: "One per shape only: keep the stream\u2019s order, or draw the nearer plane over the farther \u2014 a carousel\u2019s front cards over its back ones. Planes still cannot cut through each other.",
|
|
9941
|
+
dimWhen: onePlane
|
|
9942
|
+
},
|
|
9943
|
+
{
|
|
9944
|
+
key: "hideBack",
|
|
9945
|
+
label: "Hide Back",
|
|
9946
|
+
type: "bool",
|
|
9947
|
+
default: false,
|
|
9948
|
+
inline: true,
|
|
9949
|
+
description: "Drop a plane turned past edge-on, showing its other side \u2014 a card flipped over, a carousel\u2019s far cards."
|
|
9950
|
+
},
|
|
9951
|
+
{ key: "posX", label: "Pos. X", type: "number", default: 0, step: 1, unit: "px", description: "Moves the plane across the frame." },
|
|
9952
|
+
{ key: "posY", label: "Pos. Y", type: "number", default: 0, step: 1, unit: "px", description: "Moves the plane down the frame." },
|
|
9953
|
+
{
|
|
9954
|
+
key: "posZ",
|
|
9955
|
+
label: "Pos. Z",
|
|
9956
|
+
type: "number",
|
|
9957
|
+
default: 0,
|
|
9958
|
+
step: 1,
|
|
9959
|
+
unit: "px",
|
|
9960
|
+
description: "Depth: positive moves the plane away from the camera and it draws smaller; negative brings it nearer."
|
|
9961
|
+
},
|
|
9962
|
+
{ key: "rotX", label: "Rot. X", type: "number", default: 0, step: 1, unit: "deg", description: "Tilts the plane about a horizontal line through the pivot; positive leans the top away." },
|
|
9963
|
+
{ key: "rotY", label: "Rot. Y", type: "number", default: 0, step: 1, unit: "deg", description: "Turns the plane about a vertical line through the pivot; positive turns the right edge away." },
|
|
9964
|
+
{ key: "rotZ", label: "Rot. Z", type: "number", default: 0, step: 1, unit: "deg", description: "Spins the plane in its own surface, clockwise on screen." },
|
|
9965
|
+
{ key: "scaleX", label: "Scale X", type: "number", default: 100, step: 1, unit: "%" },
|
|
9966
|
+
{ key: "scaleY", label: "Scale Y", type: "number", default: 100, step: 1, unit: "%" },
|
|
9967
|
+
{
|
|
9968
|
+
key: "pivot",
|
|
9969
|
+
label: "Pivot",
|
|
9970
|
+
type: "enum",
|
|
9971
|
+
default: "shape",
|
|
9972
|
+
options: [
|
|
9973
|
+
{ value: "shape", label: "Shape centre" },
|
|
9974
|
+
{ value: "center", label: "Frame centre" },
|
|
9975
|
+
{ value: "canvas", label: "Canvas origin" },
|
|
9976
|
+
{ value: "custom", label: "Custom point" }
|
|
9977
|
+
],
|
|
9978
|
+
description: "What the rotations and scale turn about. Shape centre is the stream\u2019s centre for one plane, each shape\u2019s own for one per shape; Frame centre is where the camera looks."
|
|
9979
|
+
},
|
|
9980
|
+
{ key: "pivotX", label: "Pivot X", type: "number", default: 0, step: 0.5, unit: "px", dimWhen: pivotIsPreset2 },
|
|
9981
|
+
{ key: "pivotY", label: "Pivot Y", type: "number", default: 0, step: 0.5, unit: "px", dimWhen: pivotIsPreset2 },
|
|
9982
|
+
{
|
|
9983
|
+
key: "camera",
|
|
9984
|
+
label: "Camera",
|
|
9985
|
+
type: "enum",
|
|
9986
|
+
default: "canvas",
|
|
9987
|
+
options: [
|
|
9988
|
+
{ value: "canvas", label: "The Canvas\u2019s" },
|
|
9989
|
+
{ value: "own", label: "Own perspective" }
|
|
9990
|
+
],
|
|
9991
|
+
description: "Follow the Canvas\u2019s Perspective \u2014 one camera for the document \u2014 or give this plane a lens of its own."
|
|
9992
|
+
},
|
|
9993
|
+
{
|
|
9994
|
+
key: "perspective",
|
|
9995
|
+
label: "Perspective",
|
|
9996
|
+
type: "number",
|
|
9997
|
+
default: 1500,
|
|
9998
|
+
min: 0,
|
|
9999
|
+
step: 10,
|
|
10000
|
+
unit: "px",
|
|
10001
|
+
dimWhen: (p) => !ownCamera(p),
|
|
10002
|
+
description: "Own perspective only: the eye\u2019s distance from the frame; smaller is a wider lens, 0 is orthographic."
|
|
10003
|
+
}
|
|
10004
|
+
],
|
|
10005
|
+
cook(ctx) {
|
|
10006
|
+
const shapes = ctx.shapes("in");
|
|
10007
|
+
if (shapes.length === 0) return { out: shapesVal(shapes) };
|
|
10008
|
+
const perspective = ctx.enumOf("camera", ["canvas", "own"]) === "own" ? Math.max(0, ctx.num("perspective")) : ctx.canvas.perspective ?? 1500;
|
|
10009
|
+
const camera = { perspective, width: ctx.canvas.w, height: ctx.canvas.h, origin: ctx.canvas.origin };
|
|
10010
|
+
const mode = ctx.enumOf("pivot", PIVOTS);
|
|
10011
|
+
const n = shapes.length;
|
|
10012
|
+
const pivotOf2 = (i, content) => {
|
|
10013
|
+
if (mode === "canvas") return { x: 0, y: 0 };
|
|
10014
|
+
if (mode === "custom") return { x: ctx.num("pivotX", i, n), y: ctx.num("pivotY", i, n) };
|
|
10015
|
+
if (mode === "shape" && content) return centerOf(content);
|
|
10016
|
+
return frameCentre(camera);
|
|
10017
|
+
};
|
|
10018
|
+
const poseAt = (i, count, content) => ({
|
|
10019
|
+
x: ctx.num("posX", i, count),
|
|
10020
|
+
y: ctx.num("posY", i, count),
|
|
10021
|
+
z: ctx.num("posZ", i, count),
|
|
10022
|
+
rx: ctx.num("rotX", i, count),
|
|
10023
|
+
ry: ctx.num("rotY", i, count),
|
|
10024
|
+
rz: ctx.num("rotZ", i, count),
|
|
10025
|
+
sx: ctx.num("scaleX", i, count) / 100,
|
|
10026
|
+
sy: ctx.num("scaleY", i, count) / 100,
|
|
10027
|
+
pivot: pivotOf2(i, content)
|
|
10028
|
+
});
|
|
10029
|
+
const hideBack = ctx.bool("hideBack");
|
|
10030
|
+
const centre = frameCentre(camera);
|
|
10031
|
+
const judged = (pose, content) => {
|
|
10032
|
+
const c = content ? centerOf(content) : pose.pivot;
|
|
10033
|
+
const p = { x: c.x - centre.x, y: c.y - centre.y };
|
|
10034
|
+
const model = layerModelMatrix(pose, centre);
|
|
10035
|
+
return { model, depth: planeDepth(model, p), faces: planeFaces(model, p, camera.perspective) };
|
|
10036
|
+
};
|
|
10037
|
+
const stamped = (projected, sources, model) => projected.map((shape, i) => {
|
|
10038
|
+
const b = shapeBounds(sources[i] ?? shape);
|
|
10039
|
+
const c = b ? centerOf(b) : centre;
|
|
10040
|
+
const depth = planeDepth(model, { x: c.x - centre.x, y: c.y - centre.y });
|
|
10041
|
+
return depth === 0 && shape.meta.depth === void 0 ? shape : { ...shape, meta: { ...shape.meta, depth } };
|
|
10042
|
+
});
|
|
10043
|
+
if (ctx.enumOf("plane", ["stream", "shape"]) === "stream") {
|
|
10044
|
+
const content = boundsOfShapes(shapes);
|
|
10045
|
+
const pose = poseAt(0, 1, content);
|
|
10046
|
+
if (isIdentityPose(pose)) return { out: shapesVal(shapes) };
|
|
10047
|
+
const { model, faces } = judged(pose, content);
|
|
10048
|
+
if (hideBack && !faces) return { out: shapesVal([]) };
|
|
10049
|
+
return { out: shapesVal(stamped(projectShapes(shapes, layerHomography(pose, camera), ctx.outline), shapes, model)) };
|
|
10050
|
+
}
|
|
10051
|
+
const byDepth = ctx.enumOf("order", ["stack", "depth"]) === "depth";
|
|
10052
|
+
const posed = [];
|
|
10053
|
+
shapes.forEach((shape, i) => {
|
|
10054
|
+
const content = shapeBounds(shape);
|
|
10055
|
+
const pose = poseAt(i, n, content);
|
|
10056
|
+
const { model, depth, faces } = judged(pose, content);
|
|
10057
|
+
if (hideBack && !faces) return;
|
|
10058
|
+
posed.push({ depth, shapes: isIdentityPose(pose) ? [shape] : stamped(projectShapes([shape], layerHomography(pose, camera), ctx.outline), [shape], model) });
|
|
10059
|
+
});
|
|
10060
|
+
if (byDepth) posed.sort((a, b) => b.depth - a.depth);
|
|
10061
|
+
return { out: shapesVal(posed.flatMap((p) => p.shapes)) };
|
|
10062
|
+
}
|
|
10063
|
+
};
|
|
9160
10064
|
var CIRCLE_SEGMENTS2 = 96;
|
|
9161
10065
|
var CORNER_STYLES = [
|
|
9162
10066
|
{ value: "round", label: "Round" },
|
|
@@ -10050,13 +10954,13 @@ var arrayRadialNode = {
|
|
|
10050
10954
|
const angle = start + sweep * k / divisor;
|
|
10051
10955
|
const vars = { k, angle };
|
|
10052
10956
|
const radius = ctx.num("radius", k, count, vars);
|
|
10053
|
-
const
|
|
10957
|
+
const rad2 = angle * Math.PI / 180;
|
|
10054
10958
|
let m;
|
|
10055
10959
|
if (mode === "rotate") {
|
|
10056
10960
|
m = multiply(multiply(translate(cx, cy), rotate(angle)), translate(-cx, -cy));
|
|
10057
10961
|
} else {
|
|
10058
|
-
const px = cx + radius * Math.cos(
|
|
10059
|
-
const py = cy + radius * Math.sin(
|
|
10962
|
+
const px = cx + radius * Math.cos(rad2);
|
|
10963
|
+
const py = cy + radius * Math.sin(rad2);
|
|
10060
10964
|
m = mode === "orbit" ? multiply(translate(px, py), rotate(angle)) : translate(px, py);
|
|
10061
10965
|
}
|
|
10062
10966
|
for (const [j, shape] of carry(k).entries()) {
|
|
@@ -10579,7 +11483,7 @@ var jitterNode = {
|
|
|
10579
11483
|
const posYParam = ctx.num("positionY");
|
|
10580
11484
|
const posY = posYParam < 0 ? posX : posYParam;
|
|
10581
11485
|
const rot = ctx.num("rotation");
|
|
10582
|
-
const
|
|
11486
|
+
const scale5 = ctx.num("scale");
|
|
10583
11487
|
const axis2 = ctx.enumOf("scaleAxis", ["both", "x", "y"]);
|
|
10584
11488
|
const uniform = ctx.bool("uniformScale");
|
|
10585
11489
|
const opacity = ctx.num("opacity");
|
|
@@ -10597,8 +11501,8 @@ var jitterNode = {
|
|
|
10597
11501
|
const tx = posX === 0 ? 0 : signed() * posX;
|
|
10598
11502
|
const ty = posY === 0 ? 0 : signed() * posY;
|
|
10599
11503
|
const rotation = rot === 0 ? 0 : signed() * rot;
|
|
10600
|
-
const first =
|
|
10601
|
-
const second =
|
|
11504
|
+
const first = scale5 === 0 ? 1 : 1 + signed() * scale5;
|
|
11505
|
+
const second = scale5 === 0 ? 1 : uniform || axis2 !== "both" ? first : 1 + signed() * scale5;
|
|
10602
11506
|
const sx = axis2 === "y" ? 1 : first;
|
|
10603
11507
|
const sy = axis2 === "x" ? 1 : axis2 === "both" ? second : first;
|
|
10604
11508
|
const grown = sx !== 1 || sy !== 1 ? scaleAbout(shape, sx, sy, pivotOf(shape)) : shape;
|
|
@@ -10713,8 +11617,8 @@ function drawnFill(fill, local2) {
|
|
|
10713
11617
|
const rgba = parseHex(fill);
|
|
10714
11618
|
if (!rgba) return "none";
|
|
10715
11619
|
const a = Math.round(rgba[3] * local2 * local2 * 255);
|
|
10716
|
-
const
|
|
10717
|
-
return `#${
|
|
11620
|
+
const hex242 = (n) => n.toString(16).padStart(2, "0");
|
|
11621
|
+
return `#${hex242(rgba[0])}${hex242(rgba[1])}${hex242(rgba[2])}${hex242(a)}`;
|
|
10718
11622
|
}
|
|
10719
11623
|
function drawShape(ctx, shape, local2, emitted) {
|
|
10720
11624
|
const lines = flatten(shape);
|
|
@@ -10737,8 +11641,8 @@ function drawShape(ctx, shape, local2, emitted) {
|
|
|
10737
11641
|
return out.length > 0 ? out : [fadeShape(shape, local2)];
|
|
10738
11642
|
}
|
|
10739
11643
|
function wipeDirection(angleDeg) {
|
|
10740
|
-
const
|
|
10741
|
-
return { x: Math.cos(
|
|
11644
|
+
const rad2 = angleDeg * Math.PI / 180;
|
|
11645
|
+
return { x: Math.cos(rad2), y: Math.sin(rad2) };
|
|
10742
11646
|
}
|
|
10743
11647
|
function wipeExtent(shapes, dir) {
|
|
10744
11648
|
const dot = (x, y) => x * dir.x + y * dir.y;
|
|
@@ -11084,52 +11988,6 @@ function parseIndexList(text) {
|
|
|
11084
11988
|
}
|
|
11085
11989
|
return [...out].sort((m, n) => m - n);
|
|
11086
11990
|
}
|
|
11087
|
-
var isBitmap = (s) => isBitmapFamily(s.fontFamily);
|
|
11088
|
-
var isVariable = (s) => resolveFace(s.fontFamily, s.fontWeight ?? 400, s.fontStretch).variation !== void 0;
|
|
11089
|
-
var needsOutlining = (s) => isBitmap(s) || isVariable(s);
|
|
11090
|
-
var every = () => true;
|
|
11091
|
-
function anyText(scene, pick) {
|
|
11092
|
-
const any = (shapes) => shapes.some((s) => s.kind === "text" ? pick(s) : s.kind === "group" && any(s.children));
|
|
11093
|
-
return scene.layers.some((l) => any(l.shapes));
|
|
11094
|
-
}
|
|
11095
|
-
function outlineWhere(shape, outliner, pick) {
|
|
11096
|
-
if (shape.kind === "group") {
|
|
11097
|
-
const children = shape.children.map((c2) => outlineWhere(c2, outliner, pick));
|
|
11098
|
-
return children.some((c2, i) => c2 !== shape.children[i]) ? { ...shape, children } : shape;
|
|
11099
|
-
}
|
|
11100
|
-
if (shape.kind !== "text" || !pick(shape)) return shape;
|
|
11101
|
-
const weight = shape.fontWeight ?? 400;
|
|
11102
|
-
const g = outliner({ text: shape.text, family: shape.fontFamily, weight, stretch: shape.fontStretch, size: shape.fontSize, letterSpacing: shape.letterSpacing });
|
|
11103
|
-
if (!g) return shape;
|
|
11104
|
-
const shift = shape.anchor === "middle" ? -g.width / 2 : shape.anchor === "end" ? -g.width : 0;
|
|
11105
|
-
const [a, b, c, d, e, f] = shape.transform;
|
|
11106
|
-
const tx = shape.x + shift;
|
|
11107
|
-
const ty = shape.y;
|
|
11108
|
-
const { x: _x, y: _y, text: _t, fontSize: _s, fontFamily: _f, fontWeight: _w, fontStretch: _st, anchor: _a, letterSpacing: _l, metrics: _m, ...rest } = shape;
|
|
11109
|
-
return {
|
|
11110
|
-
...rest,
|
|
11111
|
-
kind: "path",
|
|
11112
|
-
d: g.d,
|
|
11113
|
-
transform: [a, b, c, d, a * tx + c * ty + e, b * tx + d * ty + f],
|
|
11114
|
-
meta: { ...shape.meta, tags: [...shape.meta.tags ?? [], "glyphs"] }
|
|
11115
|
-
};
|
|
11116
|
-
}
|
|
11117
|
-
function outlineTextShape(shape, outliner, all = false) {
|
|
11118
|
-
return outlineWhere(shape, outliner, all ? every : needsOutlining);
|
|
11119
|
-
}
|
|
11120
|
-
function outlineScene(scene, outliner, pick) {
|
|
11121
|
-
if (!outliner || !anyText(scene, pick)) return scene;
|
|
11122
|
-
return {
|
|
11123
|
-
...scene,
|
|
11124
|
-
layers: scene.layers.map((l) => {
|
|
11125
|
-
const shapes = l.shapes.map((s) => outlineWhere(s, outliner, pick));
|
|
11126
|
-
return shapes.some((s, i) => s !== l.shapes[i]) ? { ...l, shapes } : l;
|
|
11127
|
-
})
|
|
11128
|
-
};
|
|
11129
|
-
}
|
|
11130
|
-
function outlineBitmapText(scene, outliner) {
|
|
11131
|
-
return outlineScene(scene, outliner, isBitmap);
|
|
11132
|
-
}
|
|
11133
11991
|
var selectModeIsNot = (mode) => (params) => (normalizeParam(params.mode).base ?? "every-nth") !== mode;
|
|
11134
11992
|
var selectNode = {
|
|
11135
11993
|
type: "select",
|
|
@@ -11348,8 +12206,8 @@ var alignNode = {
|
|
|
11348
12206
|
const alignX = ctx.bool("alignX");
|
|
11349
12207
|
const alignY = ctx.bool("alignY");
|
|
11350
12208
|
if (!alignX && !alignY) return { out: shapesVal(withRef(input)) };
|
|
11351
|
-
const
|
|
11352
|
-
const step =
|
|
12209
|
+
const snap2 = ctx.int("snap");
|
|
12210
|
+
const step = snap2 > 0 ? 90 / 2 ** (snap2 - 1) : 0;
|
|
11353
12211
|
const side = ctx.enumOf("side", ["inside", "on-edge", "outside"]);
|
|
11354
12212
|
const over = side === "inside" ? 0 : side === "on-edge" ? 0.5 : 1;
|
|
11355
12213
|
const asOne = ctx.bool("asOne");
|
|
@@ -11366,9 +12224,9 @@ var alignNode = {
|
|
|
11366
12224
|
if (!b) return shape;
|
|
11367
12225
|
const raw = ctx.num("angle", i, n);
|
|
11368
12226
|
const deg = step > 0 ? Math.round(raw / step) * step : raw;
|
|
11369
|
-
const
|
|
11370
|
-
const cx = Math.cos(
|
|
11371
|
-
const cy = Math.sin(
|
|
12227
|
+
const rad2 = deg * Math.PI / 180;
|
|
12228
|
+
const cx = Math.cos(rad2);
|
|
12229
|
+
const cy = Math.sin(rad2);
|
|
11372
12230
|
const m = Math.max(Math.abs(cx), Math.abs(cy)) || 1;
|
|
11373
12231
|
const reach = Math.max(0, Math.min(1, ctx.num("reach", i, n)));
|
|
11374
12232
|
const ax = (1 + cx / m * reach) / 2;
|
|
@@ -11578,7 +12436,7 @@ var sortNode = {
|
|
|
11578
12436
|
cook(ctx) {
|
|
11579
12437
|
const input = ctx.shapes("in");
|
|
11580
12438
|
const by = ctx.enumOf("by", SORT_MODES);
|
|
11581
|
-
let
|
|
12439
|
+
let ordered2;
|
|
11582
12440
|
if (by === "shuffle") {
|
|
11583
12441
|
const clock = ctx.inputs["reseed"];
|
|
11584
12442
|
let reseed = 0;
|
|
@@ -11588,39 +12446,39 @@ var sortNode = {
|
|
|
11588
12446
|
}
|
|
11589
12447
|
const salt = ctx.int("salt");
|
|
11590
12448
|
const slot = new Map(input.map((s) => [s.id, ctx.rand("shuffle", salt, reseed, s.id).next()]));
|
|
11591
|
-
|
|
12449
|
+
ordered2 = [...input].sort((a, b) => slot.get(a.id) - slot.get(b.id) || a.id.localeCompare(b.id));
|
|
11592
12450
|
} else if (by === "source") {
|
|
11593
12451
|
const emitters = [];
|
|
11594
12452
|
for (const s of input) if (!emitters.includes(s.meta.sourceNodeId)) emitters.push(s.meta.sourceNodeId);
|
|
11595
|
-
|
|
12453
|
+
ordered2 = [...input].sort((a, b) => {
|
|
11596
12454
|
const e = emitters.indexOf(a.meta.sourceNodeId) - emitters.indexOf(b.meta.sourceNodeId);
|
|
11597
12455
|
return e !== 0 ? e : sourceIndex(a) - sourceIndex(b) || a.id.localeCompare(b.id);
|
|
11598
12456
|
});
|
|
11599
12457
|
} else {
|
|
11600
|
-
const
|
|
12458
|
+
const placed2 = [];
|
|
11601
12459
|
const unplaced = [];
|
|
11602
12460
|
for (const shape of input) {
|
|
11603
12461
|
const b = shapeBounds(shape);
|
|
11604
12462
|
if (b) {
|
|
11605
12463
|
const c = centerOf(b);
|
|
11606
|
-
|
|
12464
|
+
placed2.push({ shape, cx: c.x, cy: c.y, h: b.h });
|
|
11607
12465
|
} else unplaced.push(shape);
|
|
11608
12466
|
}
|
|
11609
12467
|
if (by === "x") {
|
|
11610
|
-
|
|
12468
|
+
placed2.sort((a, b) => a.cx - b.cx || a.cy - b.cy);
|
|
11611
12469
|
} else if (by === "y") {
|
|
11612
|
-
|
|
12470
|
+
placed2.sort((a, b) => a.cy - b.cy || a.cx - b.cx);
|
|
11613
12471
|
} else if (by === "reading") {
|
|
11614
|
-
const tol = ctx.num("rowTolerance") * median(
|
|
11615
|
-
|
|
12472
|
+
const tol = ctx.num("rowTolerance") * median(placed2.map((p) => p.h));
|
|
12473
|
+
placed2.sort((a, b) => a.cy - b.cy);
|
|
11616
12474
|
const rows = [];
|
|
11617
|
-
for (const p of
|
|
12475
|
+
for (const p of placed2) {
|
|
11618
12476
|
const row = rows[rows.length - 1];
|
|
11619
12477
|
if (row && p.cy - row[0].cy <= tol) row.push(p);
|
|
11620
12478
|
else rows.push([p]);
|
|
11621
12479
|
}
|
|
11622
12480
|
for (const row of rows) row.sort((a, b) => a.cx - b.cx);
|
|
11623
|
-
|
|
12481
|
+
placed2.splice(0, placed2.length, ...rows.flat());
|
|
11624
12482
|
} else {
|
|
11625
12483
|
const bounds = boundsOfShapes(input);
|
|
11626
12484
|
const c = bounds ? centerOf(bounds) : { x: 0, y: 0 };
|
|
@@ -11630,12 +12488,12 @@ var sortNode = {
|
|
|
11630
12488
|
const k = ((deg - start) % 360 + 360) % 360;
|
|
11631
12489
|
return k > 360 - 1e-6 ? 0 : k;
|
|
11632
12490
|
};
|
|
11633
|
-
|
|
12491
|
+
placed2.sort((a, b) => keyOf(a) - keyOf(b) || Math.hypot(a.cx - c.x, a.cy - c.y) - Math.hypot(b.cx - c.x, b.cy - c.y));
|
|
11634
12492
|
}
|
|
11635
|
-
|
|
12493
|
+
ordered2 = [...placed2.map((p) => p.shape), ...unplaced];
|
|
11636
12494
|
}
|
|
11637
|
-
if (ctx.bool("reverse"))
|
|
11638
|
-
return { out: shapesVal(
|
|
12495
|
+
if (ctx.bool("reverse")) ordered2.reverse();
|
|
12496
|
+
return { out: shapesVal(ordered2.map((s, i) => renumbered(s, i))) };
|
|
11639
12497
|
}
|
|
11640
12498
|
};
|
|
11641
12499
|
var groupNode = {
|
|
@@ -11889,9 +12747,9 @@ var randomValueNode = {
|
|
|
11889
12747
|
cook(ctx) {
|
|
11890
12748
|
const min = ctx.num("min");
|
|
11891
12749
|
const max = ctx.num("max");
|
|
11892
|
-
const
|
|
12750
|
+
const snap2 = ctx.num("step");
|
|
11893
12751
|
let value = ctx.rand("value", seedSalt(ctx.int("salt"))).range(Math.min(min, max), Math.max(min, max));
|
|
11894
|
-
if (
|
|
12752
|
+
if (snap2 > 0) value = Math.round(value / snap2) * snap2;
|
|
11895
12753
|
return { out: numVal(value) };
|
|
11896
12754
|
}
|
|
11897
12755
|
};
|
|
@@ -12019,9 +12877,9 @@ var randomStepNode = {
|
|
|
12019
12877
|
const index = interval > 0 ? Math.floor(ctx.t / interval) : 0;
|
|
12020
12878
|
const min = ctx.num("min");
|
|
12021
12879
|
const max = ctx.num("max");
|
|
12022
|
-
const
|
|
12880
|
+
const snap2 = ctx.num("step");
|
|
12023
12881
|
let value = ctx.rand("value", seedSalt(ctx.int("salt")), index).range(Math.min(min, max), Math.max(min, max));
|
|
12024
|
-
if (
|
|
12882
|
+
if (snap2 > 0) value = Math.round(value / snap2) * snap2;
|
|
12025
12883
|
return { out: numVal(value) };
|
|
12026
12884
|
}
|
|
12027
12885
|
};
|
|
@@ -12416,6 +13274,7 @@ var mathNode = {
|
|
|
12416
13274
|
type: "math",
|
|
12417
13275
|
label: "Math",
|
|
12418
13276
|
category: "driver",
|
|
13277
|
+
description: "One operation on two numbers, A and B: wire a driver into A and set B to scale, offset, clamp or wrap it \u2014 Time \xD7 45 is a rotation in degrees per second.",
|
|
12419
13278
|
inputs: [],
|
|
12420
13279
|
outputs: [{ name: "out", type: "number" }],
|
|
12421
13280
|
params: [
|
|
@@ -12980,6 +13839,7 @@ var blurNode = {
|
|
|
12980
13839
|
type: "blur",
|
|
12981
13840
|
label: "Blur",
|
|
12982
13841
|
category: "post",
|
|
13842
|
+
description: "A Gaussian blur of the picture by Radius, the same bytes everywhere. For a blur that follows depth, see Depth of Field.",
|
|
12983
13843
|
inputs: [{ name: "in", type: "texture" }],
|
|
12984
13844
|
outputs: [{ name: "out", type: "texture" }],
|
|
12985
13845
|
params: [{ key: "radius", label: "Radius", type: "number", default: 4, min: 0, max: 64, step: 0.5, unit: "px" }],
|
|
@@ -13129,9 +13989,9 @@ var chromaticNode = {
|
|
|
13129
13989
|
const tex = ctx.texture();
|
|
13130
13990
|
if (!tex) return QUIET3;
|
|
13131
13991
|
const offset = ctx.num("offset");
|
|
13132
|
-
const
|
|
13992
|
+
const rad2 = ctx.num("angle") * Math.PI / 180;
|
|
13133
13993
|
return {
|
|
13134
|
-
out: textureVal(chromaticAberration(tex, offset * Math.cos(
|
|
13994
|
+
out: textureVal(chromaticAberration(tex, offset * Math.cos(rad2), offset * Math.sin(rad2)))
|
|
13135
13995
|
};
|
|
13136
13996
|
}
|
|
13137
13997
|
};
|
|
@@ -13139,6 +13999,7 @@ var scanlinesNode = {
|
|
|
13139
13999
|
type: "scanlines",
|
|
13140
14000
|
label: "Scanlines",
|
|
13141
14001
|
category: "post",
|
|
14002
|
+
description: "The CRT look: every few rows (or columns) darkened by Strength, rolling with Roll.",
|
|
13142
14003
|
inputs: [{ name: "in", type: "texture" }],
|
|
13143
14004
|
outputs: [{ name: "out", type: "texture" }],
|
|
13144
14005
|
// `speed` rolls the lines with t.
|
|
@@ -13533,9 +14394,130 @@ var matteNode = {
|
|
|
13533
14394
|
return { out: textureVal(matte(tex, m, { mode: ctx.enumOf("mode", ["alpha", "luma"]), invert: ctx.bool("invert") }, ctx.canvas.origin ?? "top-left")) };
|
|
13534
14395
|
}
|
|
13535
14396
|
};
|
|
14397
|
+
function silhouette(shape, colour) {
|
|
14398
|
+
const s = shape.style;
|
|
14399
|
+
const style = {
|
|
14400
|
+
...s,
|
|
14401
|
+
fill: s.fill === void 0 || s.fill === "none" ? "none" : colour,
|
|
14402
|
+
stroke: s.stroke === void 0 || s.stroke === "none" || (s.strokeWidth ?? 1) <= 0 ? "none" : colour,
|
|
14403
|
+
opacity: 1
|
|
14404
|
+
};
|
|
14405
|
+
switch (shape.kind) {
|
|
14406
|
+
case "group":
|
|
14407
|
+
return { ...shape, style, children: shape.children.map((c) => silhouette(c, colour)) };
|
|
14408
|
+
case "image":
|
|
14409
|
+
return { id: shape.id, kind: "rect", x: shape.x, y: shape.y, w: shape.w, h: shape.h, rx: 0, transform: shape.transform, style: { ...style, fill: colour, stroke: "none" }, meta: shape.meta };
|
|
14410
|
+
default:
|
|
14411
|
+
return { ...shape, style };
|
|
14412
|
+
}
|
|
14413
|
+
}
|
|
14414
|
+
var hex222 = (v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, "0");
|
|
14415
|
+
function farthestFirst(items) {
|
|
14416
|
+
return [...items].sort((a, b) => b.depth - a.depth);
|
|
14417
|
+
}
|
|
14418
|
+
var depthOfFieldNode = {
|
|
14419
|
+
type: "depth-of-field",
|
|
14420
|
+
label: "Depth of Field",
|
|
14421
|
+
category: "post",
|
|
14422
|
+
description: "A lens with a focal plane: what sits at Focus stays sharp, and the picture blurs the farther a shape is from it, nearer or beyond. Wire the picture into In and the same shapes that made it into Depth \u2014 they remember how deep Transform 3D put them.",
|
|
14423
|
+
keywords: ["dof", "focus", "defocus", "bokeh", "lens", "rack focus", "depth", "3d", "blur"],
|
|
14424
|
+
quickKey: "focus",
|
|
14425
|
+
inputs: [
|
|
14426
|
+
{ name: "in", type: "texture", label: "In" },
|
|
14427
|
+
{ name: "depth", type: "shapes", label: "Depth" }
|
|
14428
|
+
],
|
|
14429
|
+
outputs: [{ name: "out", type: "texture" }],
|
|
14430
|
+
params: [
|
|
14431
|
+
{ key: "focus", label: "Focus", type: "number", default: 0, step: 10, unit: "px", description: "The depth that is sharp, in Pos. Z\u2019s terms: 0 is the frame itself, positive is away from the camera. Wire a Seq to pull focus." },
|
|
14432
|
+
{ key: "range", label: "Range", type: "number", default: 200, min: 0, step: 10, unit: "px", description: "The depth kept sharp around Focus, half of it either side." },
|
|
14433
|
+
{ key: "falloff", label: "Falloff", type: "number", default: 800, min: 0, step: 10, unit: "px", description: "How much farther out of the Range the blur takes to reach its full Blur; 0 snaps to it at once." },
|
|
14434
|
+
{ key: "blur", label: "Blur", type: "number", default: 12, min: 0, max: 64, step: 0.5, unit: "px", description: "The blur at the far end of the Falloff, like the Blur node\u2019s Radius." }
|
|
14435
|
+
],
|
|
14436
|
+
cook(ctx) {
|
|
14437
|
+
const tex = ctx.texture("in");
|
|
14438
|
+
if (!tex) return QUIET3;
|
|
14439
|
+
const shapes = ctx.shapes("depth");
|
|
14440
|
+
const most = ctx.num("blur");
|
|
14441
|
+
if (shapes.length === 0 || most <= 0 || ctx.canvas.w <= 0 || ctx.canvas.h <= 0) return { out: textureVal(tex) };
|
|
14442
|
+
if (!ctx.rasterize) {
|
|
14443
|
+
throw new Error("no rasteriser available yet \u2014 the preview loads one on demand; exports always have one");
|
|
14444
|
+
}
|
|
14445
|
+
const focus = ctx.num("focus");
|
|
14446
|
+
const range = Math.max(0, ctx.num("range"));
|
|
14447
|
+
const falloff = Math.max(0, ctx.num("falloff"));
|
|
14448
|
+
const judged = shapes.map((shape) => {
|
|
14449
|
+
const depth = shape.meta.depth ?? 0;
|
|
14450
|
+
const away = Math.abs(depth - focus) - range / 2;
|
|
14451
|
+
const k = away <= 0 ? 0 : falloff > 0 ? Math.min(1, away / falloff) : 1;
|
|
14452
|
+
const level = focusLevel(most * k);
|
|
14453
|
+
return { shape, depth, level, side: level > 0 && depth > focus ? 1 : 0 };
|
|
14454
|
+
});
|
|
14455
|
+
if (judged.every((j) => j.level === 0)) return { out: textureVal(tex) };
|
|
14456
|
+
const map = ctx.rasterize({
|
|
14457
|
+
width: ctx.canvas.w,
|
|
14458
|
+
height: ctx.canvas.h,
|
|
14459
|
+
background: "none",
|
|
14460
|
+
origin: ctx.canvas.origin,
|
|
14461
|
+
layers: [
|
|
14462
|
+
{
|
|
14463
|
+
id: ctx.nodeId,
|
|
14464
|
+
name: "focus",
|
|
14465
|
+
opacity: 1,
|
|
14466
|
+
visible: true,
|
|
14467
|
+
order: 0,
|
|
14468
|
+
shapes: farthestFirst(judged).map((j) => silhouette(j.shape, `#${hex222(j.level)}${j.side ? "ff" : "00"}00`))
|
|
14469
|
+
}
|
|
14470
|
+
]
|
|
14471
|
+
});
|
|
14472
|
+
const slices = focusSlices(judged.map((j) => ({ level: j.level, side: j.side })));
|
|
14473
|
+
return { out: textureVal(depthOfField(tex, map, slices, ctx.canvas.origin ?? "top-left")) };
|
|
14474
|
+
}
|
|
14475
|
+
};
|
|
14476
|
+
var depthMapNode = {
|
|
14477
|
+
type: "depth-map",
|
|
14478
|
+
label: "Depth Map",
|
|
14479
|
+
category: "post",
|
|
14480
|
+
description: "The shapes drawn as their depth: a grey per shape from black at Near to white at Far, the nearer shape over the farther. What Depth of Field sees \u2014 and a depth fog, through a Blend.",
|
|
14481
|
+
keywords: ["depth", "z", "map", "zdepth", "fog", "3d", "grey"],
|
|
14482
|
+
inputs: [{ name: "in", type: "shapes" }],
|
|
14483
|
+
outputs: [{ name: "out", type: "texture" }],
|
|
14484
|
+
params: [
|
|
14485
|
+
{ key: "near", label: "Near", type: "number", default: -1e3, step: 10, unit: "px", description: "The depth drawn black, in Pos. Z\u2019s terms; anything nearer is black too." },
|
|
14486
|
+
{ key: "far", label: "Far", type: "number", default: 1e3, step: 10, unit: "px", description: "The depth drawn white; anything farther is white too. A shape no 3D node has posed sits at 0." }
|
|
14487
|
+
],
|
|
14488
|
+
cook(ctx) {
|
|
14489
|
+
if (!ctx.rasterize) {
|
|
14490
|
+
throw new Error("no rasteriser available yet \u2014 the preview loads one on demand; exports always have one");
|
|
14491
|
+
}
|
|
14492
|
+
if (ctx.canvas.w <= 0 || ctx.canvas.h <= 0) throw new Error("has no size to draw at \u2014 give the project a canvas");
|
|
14493
|
+
const near = ctx.num("near");
|
|
14494
|
+
const span = ctx.num("far") - near;
|
|
14495
|
+
const judged = ctx.shapes("in").map((shape) => ({ shape, depth: shape.meta.depth ?? 0 }));
|
|
14496
|
+
const grey = (depth) => hex222(span === 0 ? 128 : Math.max(0, Math.min(1, (depth - near) / span)) * 255);
|
|
14497
|
+
const texture = ctx.rasterize({
|
|
14498
|
+
width: ctx.canvas.w,
|
|
14499
|
+
height: ctx.canvas.h,
|
|
14500
|
+
background: "none",
|
|
14501
|
+
origin: ctx.canvas.origin,
|
|
14502
|
+
layers: [
|
|
14503
|
+
{
|
|
14504
|
+
id: ctx.nodeId,
|
|
14505
|
+
name: "depth",
|
|
14506
|
+
opacity: 1,
|
|
14507
|
+
visible: true,
|
|
14508
|
+
order: 0,
|
|
14509
|
+
shapes: farthestFirst(judged).map((j) => silhouette(j.shape, `#${grey(j.depth).repeat(3)}`))
|
|
14510
|
+
}
|
|
14511
|
+
]
|
|
14512
|
+
});
|
|
14513
|
+
return { out: textureVal(texture) };
|
|
14514
|
+
}
|
|
14515
|
+
};
|
|
13536
14516
|
var POST_NODES = [
|
|
13537
14517
|
rasterizeNode,
|
|
13538
14518
|
blurNode,
|
|
14519
|
+
depthOfFieldNode,
|
|
14520
|
+
depthMapNode,
|
|
13539
14521
|
glitchNode,
|
|
13540
14522
|
chromaticNode,
|
|
13541
14523
|
scanlinesNode,
|
|
@@ -13570,13 +14552,13 @@ var familyOf = (p) => resolveFamily(String(normalizeParam(p.fontFamily).base ??
|
|
|
13570
14552
|
var FALLBACK_CAP_HEIGHT2 = 0.7;
|
|
13571
14553
|
var FALLBACK_BLOCK = { top: 0.95, bottom: -0.35 };
|
|
13572
14554
|
var styleParam2 = (key) => STYLE_PARAMS.find((p) => p.key === key);
|
|
13573
|
-
var
|
|
14555
|
+
var hex23 = (n) => n.toString(16).padStart(2, "0");
|
|
13574
14556
|
function hexOf(rgba, k) {
|
|
13575
14557
|
const a = rgba[k * 4 + 3];
|
|
13576
14558
|
if (a === 0) return void 0;
|
|
13577
14559
|
const straight = (v) => Math.min(255, Math.round(v * 255 / a));
|
|
13578
|
-
const rgb = `#${
|
|
13579
|
-
return a === 255 ? rgb : `${rgb}${
|
|
14560
|
+
const rgb = `#${hex23(straight(rgba[k * 4]))}${hex23(straight(rgba[k * 4 + 1]))}${hex23(straight(rgba[k * 4 + 2]))}`;
|
|
14561
|
+
return a === 255 ? rgb : `${rgb}${hex23(a)}`;
|
|
13580
14562
|
}
|
|
13581
14563
|
function pathYBounds(d) {
|
|
13582
14564
|
const nums = d.match(/-?\d*\.?\d+(?:e-?\d+)?/g);
|
|
@@ -13992,9 +14974,9 @@ var halftoneNode = {
|
|
|
13992
14974
|
const ty = tex.at?.y ?? (ctx.canvas.origin === "center" ? -tex.height / 2 : 0);
|
|
13993
14975
|
const cx = tex.width / 2;
|
|
13994
14976
|
const cy = tex.height / 2;
|
|
13995
|
-
const
|
|
13996
|
-
const ex = Math.cos(
|
|
13997
|
-
const ey = Math.sin(
|
|
14977
|
+
const rad2 = angle * Math.PI / 180;
|
|
14978
|
+
const ex = Math.cos(rad2);
|
|
14979
|
+
const ey = Math.sin(rad2);
|
|
13998
14980
|
const reach = Math.ceil(Math.hypot(cx, cy) / cell);
|
|
13999
14981
|
const shapes = [];
|
|
14000
14982
|
for (let j = -reach; j <= reach; j++) {
|
|
@@ -14195,10 +15177,15 @@ var contoursNode = {
|
|
|
14195
15177
|
type: "contours",
|
|
14196
15178
|
label: "Contours",
|
|
14197
15179
|
category: "post",
|
|
14198
|
-
description: "Pixels back into lines: the picture read as a height map and traced where it crosses evenly spaced levels \u2014 the lines of a topographic map, every Nth one a major line. Wire Noise Paint \u2192 Rect \u2192 Rasterize in for terrain; animate Offset to make the lines flow.",
|
|
15180
|
+
description: "Pixels back into lines: the picture read as a height map and traced where it crosses evenly spaced levels \u2014 the lines of a topographic map, every Nth one a major line (wire the Major output to give the majors a chain of their own). Wire Noise Paint \u2192 Rect \u2192 Rasterize in for terrain; animate Offset to make the lines flow.",
|
|
14199
15181
|
keywords: ["contours", "contour", "topographic", "topo", "isolines", "iso", "height", "map", "terrain", "marching squares", "lines", "trace"],
|
|
14200
15182
|
inputs: [{ name: "in", type: "texture", label: "In" }],
|
|
14201
|
-
outputs: [
|
|
15183
|
+
outputs: [
|
|
15184
|
+
{ name: "out", type: "shapes" },
|
|
15185
|
+
// Wired, the majors leave `out` for this socket, so each set can take
|
|
15186
|
+
// its own chain (Outline Stroke, a Glow) without a Select.
|
|
15187
|
+
{ name: "major", type: "shapes", label: "Major", claims: true }
|
|
15188
|
+
],
|
|
14202
15189
|
params: [
|
|
14203
15190
|
{ key: "lines", label: "Lines", type: "int", default: 12, min: 1, max: 128, description: "How many levels between black and white: the lines\u2019 spacing in height." },
|
|
14204
15191
|
{ key: "offset", label: "Offset", type: "number", default: 0.5, step: 0.01, description: "Slides every level up the height map, in line spacings: 1 moves each line to where the next one was. Animate it for lines that flow; the majors travel with their lines." },
|
|
@@ -14220,6 +15207,7 @@ var contoursNode = {
|
|
|
14220
15207
|
{ key: "width", label: "Width", type: "number", default: 1, min: 0, step: 0.1, unit: "px", description: "A minor line\u2019s width.", group: "Lines" },
|
|
14221
15208
|
{ key: "majorEvery", label: "Major Every", type: "int", default: 5, min: 0, max: 32, description: "Every Nth level is a major line, drawn at Major Width and tagged major. 0: no majors.", group: "Lines" },
|
|
14222
15209
|
{ key: "majorWidth", label: "Major Width", type: "number", default: 3, min: 0, step: 0.1, unit: "px", group: "Lines" },
|
|
15210
|
+
{ key: "majorStroke", label: "Major Stroke", type: "color", default: "none", description: "The major lines\u2019 colour. None: the same as Stroke.", group: "Lines" },
|
|
14223
15211
|
{ key: "opacity", label: "Opacity", type: "number", default: 1, min: 0, max: 1, step: 0.01, group: "Lines" }
|
|
14224
15212
|
],
|
|
14225
15213
|
cook(ctx) {
|
|
@@ -14234,6 +15222,8 @@ var contoursNode = {
|
|
|
14234
15222
|
const width = Math.max(0, ctx.num("width"));
|
|
14235
15223
|
const every2 = Math.max(0, ctx.int("majorEvery"));
|
|
14236
15224
|
const majorWidth = Math.max(0, ctx.num("majorWidth"));
|
|
15225
|
+
const majorColor = ctx.color("majorStroke");
|
|
15226
|
+
const majorStroke = majorColor && majorColor !== "none" ? majorColor : stroke;
|
|
14237
15227
|
const opacity = ctx.num("opacity");
|
|
14238
15228
|
const tx = tex.at?.x ?? (ctx.canvas.origin === "center" ? -tex.width / 2 : 0);
|
|
14239
15229
|
const ty = tex.at?.y ?? (ctx.canvas.origin === "center" ? -tex.height / 2 : 0);
|
|
@@ -14243,6 +15233,8 @@ var contoursNode = {
|
|
|
14243
15233
|
const base2 = Math.floor(offset);
|
|
14244
15234
|
const frac = offset - base2;
|
|
14245
15235
|
const shapes = [];
|
|
15236
|
+
const majors = [];
|
|
15237
|
+
const split = ctx.wiredOutputs.has("major");
|
|
14246
15238
|
for (let k = 0; k < count; k++) {
|
|
14247
15239
|
const level = (k + frac) / count;
|
|
14248
15240
|
if (level <= 0 || level >= 1) continue;
|
|
@@ -14258,18 +15250,19 @@ var contoursNode = {
|
|
|
14258
15250
|
d += `${p === 0 ? "M" : " L"} ${num22(sx(gx))} ${num22(sy(gy))}`;
|
|
14259
15251
|
}
|
|
14260
15252
|
if (line.closed) d += " Z";
|
|
14261
|
-
const
|
|
14262
|
-
|
|
15253
|
+
const into = split && major ? majors : shapes;
|
|
15254
|
+
const index = into.length;
|
|
15255
|
+
into.push({
|
|
14263
15256
|
kind: "path",
|
|
14264
15257
|
d,
|
|
14265
|
-
id: ctx.shapeId(index),
|
|
15258
|
+
id: ctx.shapeId(index, into === majors ? "major" : void 0),
|
|
14266
15259
|
transform: IDENTITY,
|
|
14267
|
-
style: { fill: "none", stroke, strokeWidth: major ? majorWidth : width, opacity },
|
|
15260
|
+
style: { fill: "none", stroke: major ? majorStroke : stroke, strokeWidth: major ? majorWidth : width, opacity },
|
|
14268
15261
|
meta: ctx.meta(index, ["contour", major ? "major" : "minor", `level:${g}`])
|
|
14269
15262
|
});
|
|
14270
15263
|
}
|
|
14271
15264
|
}
|
|
14272
|
-
return { out: shapesVal(shapes) };
|
|
15265
|
+
return { out: shapesVal(shapes), major: shapesVal(split ? majors : shapes.filter((s2) => s2.meta.tags?.includes("major"))) };
|
|
14273
15266
|
}
|
|
14274
15267
|
};
|
|
14275
15268
|
var BORDERS = {
|
|
@@ -14450,10 +15443,66 @@ var textBoxNode = {
|
|
|
14450
15443
|
}
|
|
14451
15444
|
};
|
|
14452
15445
|
var notMono = (p) => normalizeParam(p.display).base !== "mono";
|
|
15446
|
+
var pivotIsPreset3 = (p) => (normalizeParam(p.pivot).base ?? "center") !== "custom";
|
|
15447
|
+
var LAYER_3D_PARAMS = [
|
|
15448
|
+
{ key: "posX", label: "Pos. X", type: "number", default: 0, step: 1, unit: "px", group: "3D", description: "Moves the layer\u2019s plane across the frame." },
|
|
15449
|
+
{ key: "posY", label: "Pos. Y", type: "number", default: 0, step: 1, unit: "px", group: "3D", description: "Moves the layer\u2019s plane down the frame." },
|
|
15450
|
+
{
|
|
15451
|
+
key: "posZ",
|
|
15452
|
+
label: "Pos. Z",
|
|
15453
|
+
type: "number",
|
|
15454
|
+
default: 0,
|
|
15455
|
+
step: 1,
|
|
15456
|
+
unit: "px",
|
|
15457
|
+
group: "3D",
|
|
15458
|
+
description: "Depth: positive moves the plane away from the camera and it draws smaller; negative brings it nearer."
|
|
15459
|
+
},
|
|
15460
|
+
{
|
|
15461
|
+
key: "rotX",
|
|
15462
|
+
label: "Rot. X",
|
|
15463
|
+
type: "number",
|
|
15464
|
+
default: 0,
|
|
15465
|
+
step: 1,
|
|
15466
|
+
unit: "deg",
|
|
15467
|
+
group: "3D",
|
|
15468
|
+
description: "Tilts the plane about a horizontal line through the pivot; positive leans the top away."
|
|
15469
|
+
},
|
|
15470
|
+
{
|
|
15471
|
+
key: "rotY",
|
|
15472
|
+
label: "Rot. Y",
|
|
15473
|
+
type: "number",
|
|
15474
|
+
default: 0,
|
|
15475
|
+
step: 1,
|
|
15476
|
+
unit: "deg",
|
|
15477
|
+
group: "3D",
|
|
15478
|
+
description: "Turns the plane about a vertical line through the pivot; positive turns the right edge away."
|
|
15479
|
+
},
|
|
15480
|
+
{ key: "rotZ", label: "Rot. Z", type: "number", default: 0, step: 1, unit: "deg", group: "3D", description: "Spins the plane in its own surface, clockwise on screen." },
|
|
15481
|
+
{ key: "scaleX", label: "Scale X", type: "number", default: 100, step: 1, unit: "%", group: "3D" },
|
|
15482
|
+
{ key: "scaleY", label: "Scale Y", type: "number", default: 100, step: 1, unit: "%", group: "3D" },
|
|
15483
|
+
{
|
|
15484
|
+
key: "pivot",
|
|
15485
|
+
label: "Pivot",
|
|
15486
|
+
type: "enum",
|
|
15487
|
+
default: "center",
|
|
15488
|
+
group: "3D",
|
|
15489
|
+
options: [
|
|
15490
|
+
{ value: "center", label: "Frame centre" },
|
|
15491
|
+
{ value: "canvas", label: "Canvas origin" },
|
|
15492
|
+
{ value: "shape", label: "Content centre" },
|
|
15493
|
+
{ value: "custom", label: "Custom point" }
|
|
15494
|
+
],
|
|
15495
|
+
description: "What the rotations and scale turn about: where the camera looks, the document\u2019s (0,0), the centre of what the layer holds, or a point of your own."
|
|
15496
|
+
},
|
|
15497
|
+
{ key: "pivotX", label: "Pivot X", type: "number", default: 0, step: 0.5, unit: "px", group: "3D", dimWhen: pivotIsPreset3 },
|
|
15498
|
+
{ key: "pivotY", label: "Pivot Y", type: "number", default: 0, step: 0.5, unit: "px", group: "3D", dimWhen: pivotIsPreset3 }
|
|
15499
|
+
];
|
|
15500
|
+
var gathered = /* @__PURE__ */ new Map();
|
|
14453
15501
|
var layerNode = {
|
|
14454
15502
|
type: "layer",
|
|
14455
15503
|
label: "Layer",
|
|
14456
15504
|
category: "output",
|
|
15505
|
+
description: 'A composition of its own with its own page and clock: wire shapes (and one post chain) into "in", the Layer into the Canvas. Order stacks layers; the 3D group poses the whole layer as a plane under the camera.',
|
|
14457
15506
|
inputs: [
|
|
14458
15507
|
// Multi: a layer gathers streams directly — no obligatory Merge in front.
|
|
14459
15508
|
// The one socket takes a texture too: the way pixels re-enter the scene.
|
|
@@ -14496,7 +15545,8 @@ var layerNode = {
|
|
|
14496
15545
|
exposable: false,
|
|
14497
15546
|
inspectorOnly: true,
|
|
14498
15547
|
description: "Wrap the layer\u2019s clock at its Duration instead of switching off."
|
|
14499
|
-
}
|
|
15548
|
+
},
|
|
15549
|
+
...LAYER_3D_PARAMS
|
|
14500
15550
|
],
|
|
14501
15551
|
cook(ctx) {
|
|
14502
15552
|
const layer = {
|
|
@@ -14507,13 +15557,41 @@ var layerNode = {
|
|
|
14507
15557
|
order: ctx.int("order"),
|
|
14508
15558
|
shapes: []
|
|
14509
15559
|
};
|
|
14510
|
-
|
|
14511
|
-
|
|
15560
|
+
const inputs = ctx.inputList("in");
|
|
15561
|
+
const shapes = [];
|
|
15562
|
+
for (const v of inputs) {
|
|
15563
|
+
if (v.t === "shapes") shapes.push(...v.v);
|
|
14512
15564
|
else if (v.t === "texture" && !layer.texture) {
|
|
14513
15565
|
layer.texture = v.v;
|
|
14514
|
-
if (
|
|
15566
|
+
if (shapes.length > 0) layer.textureAt = shapes.length;
|
|
14515
15567
|
}
|
|
14516
15568
|
}
|
|
15569
|
+
const key = inputs.map((v) => v.t === "shapes" ? identityOf(v.v) : "-").join(",");
|
|
15570
|
+
const kept = gathered.get(ctx.nodeId);
|
|
15571
|
+
if (kept && kept.key === key) layer.shapes = kept.shapes;
|
|
15572
|
+
else {
|
|
15573
|
+
layer.shapes = shapes;
|
|
15574
|
+
gathered.set(ctx.nodeId, { key, shapes });
|
|
15575
|
+
}
|
|
15576
|
+
const camera = { perspective: 0, width: ctx.canvas.w, height: ctx.canvas.h, origin: ctx.canvas.origin };
|
|
15577
|
+
const pose = {
|
|
15578
|
+
x: ctx.num("posX"),
|
|
15579
|
+
y: ctx.num("posY"),
|
|
15580
|
+
z: ctx.num("posZ"),
|
|
15581
|
+
rx: ctx.num("rotX"),
|
|
15582
|
+
ry: ctx.num("rotY"),
|
|
15583
|
+
rz: ctx.num("rotZ"),
|
|
15584
|
+
sx: ctx.num("scaleX") / 100,
|
|
15585
|
+
sy: ctx.num("scaleY") / 100,
|
|
15586
|
+
pivot: { x: 0, y: 0 }
|
|
15587
|
+
};
|
|
15588
|
+
if (!isIdentityPose(pose)) {
|
|
15589
|
+
pose.pivot = layerPivot(ctx.enumOf("pivot", ["center", "canvas", "shape", "custom"]), layer, camera, {
|
|
15590
|
+
x: ctx.num("pivotX"),
|
|
15591
|
+
y: ctx.num("pivotY")
|
|
15592
|
+
});
|
|
15593
|
+
layer.pose = pose;
|
|
15594
|
+
}
|
|
14517
15595
|
return { out: layersVal([layer]) };
|
|
14518
15596
|
}
|
|
14519
15597
|
};
|
|
@@ -14545,6 +15623,18 @@ var canvasNode = {
|
|
|
14545
15623
|
],
|
|
14546
15624
|
description: "Centre puts (0,0) mid-frame \u2014 HUD maths without the offsets."
|
|
14547
15625
|
},
|
|
15626
|
+
// The fixed camera, for layers posed in 3D: it looks at the frame
|
|
15627
|
+
// centre from this far in front. CSS's `perspective`, AE's zoom.
|
|
15628
|
+
{
|
|
15629
|
+
key: "perspective",
|
|
15630
|
+
label: "Perspective",
|
|
15631
|
+
type: "number",
|
|
15632
|
+
default: 1500,
|
|
15633
|
+
min: 0,
|
|
15634
|
+
step: 10,
|
|
15635
|
+
unit: "px",
|
|
15636
|
+
description: "The camera\u2019s distance from the frame, for layers posed in 3D: smaller is a wider lens and stronger foreshortening; 0 is orthographic, no foreshortening at all."
|
|
15637
|
+
},
|
|
14548
15638
|
// How the pixels are made. A small LCD has no anti-aliasing and, as
|
|
14549
15639
|
// often as not, one colour beside its background; the preview and every
|
|
14550
15640
|
// export read this, so the artboard shows what ships.
|
|
@@ -14573,6 +15663,12 @@ var canvasNode = {
|
|
|
14573
15663
|
dimWhen: notMono
|
|
14574
15664
|
}
|
|
14575
15665
|
],
|
|
15666
|
+
// The pictures the raster worker makes of posed layers' picture runs,
|
|
15667
|
+
// under the runs' ids: served on the main thread, where the cook below
|
|
15668
|
+
// has no rasteriser (it projects the vectors itself).
|
|
15669
|
+
standIns: (_inputs, multi) => (multi.in ?? []).flatMap(
|
|
15670
|
+
(v) => v.t === "layers" ? v.v.filter((l) => l.pose && l.visible).flatMap((l) => layerPieces(l).filter((p) => p.kind === "picture").map((p) => p.id)) : []
|
|
15671
|
+
),
|
|
14576
15672
|
cook(ctx) {
|
|
14577
15673
|
const layers = [];
|
|
14578
15674
|
for (const value of ctx.inputList("in")) {
|
|
@@ -14601,14 +15697,19 @@ var canvasNode = {
|
|
|
14601
15697
|
const sorted = layers.map((l, i) => ({ l, i })).sort((a, b) => a.l.order - b.l.order || a.i - b.i).map(({ l }) => l);
|
|
14602
15698
|
const mode = ctx.enumOf("display", ["vector", "pixel", "mono"]);
|
|
14603
15699
|
const display = mode === "vector" ? void 0 : { mode, ink: ctx.color("ink"), threshold: Math.min(1, Math.max(0, ctx.num("threshold"))) };
|
|
15700
|
+
const width = ctx.int("width");
|
|
15701
|
+
const height = ctx.int("height");
|
|
15702
|
+
const origin = ctx.enumOf("origin", ["top-left", "center"]);
|
|
15703
|
+
const camera = { perspective: Math.max(0, ctx.num("perspective")), width, height, origin };
|
|
15704
|
+
const posed = sorted.flatMap((l) => l.pose ? projectLayer(l, camera, ctx, display !== void 0) : [l]);
|
|
14604
15705
|
return {
|
|
14605
15706
|
out: sceneVal({
|
|
14606
|
-
width
|
|
14607
|
-
height
|
|
15707
|
+
width,
|
|
15708
|
+
height,
|
|
14608
15709
|
background: ctx.color("bg"),
|
|
14609
|
-
origin
|
|
15710
|
+
origin,
|
|
14610
15711
|
...display ? { display } : {},
|
|
14611
|
-
layers:
|
|
15712
|
+
layers: posed
|
|
14612
15713
|
})
|
|
14613
15714
|
};
|
|
14614
15715
|
}
|
|
@@ -14765,6 +15866,888 @@ var ografNode = {
|
|
|
14765
15866
|
return out;
|
|
14766
15867
|
}
|
|
14767
15868
|
};
|
|
15869
|
+
function graph() {
|
|
15870
|
+
const nodes = [];
|
|
15871
|
+
const edges = [];
|
|
15872
|
+
const node = (id, type, col, row, params = {}, name) => {
|
|
15873
|
+
const full = type === "text" ? { vAnchor: "baseline", ...params } : params;
|
|
15874
|
+
nodes.push({ id, type, params: full, position: { x: col * 260, y: row * 200 }, ...name ? { name } : {} });
|
|
15875
|
+
return id;
|
|
15876
|
+
};
|
|
15877
|
+
const wire = (from, to, toPort, fromPort = "out") => {
|
|
15878
|
+
edges.push({ id: `e${edges.length + 1}`, from, fromPort, to, toPort });
|
|
15879
|
+
};
|
|
15880
|
+
return { nodes, edges, node, wire };
|
|
15881
|
+
}
|
|
15882
|
+
function glitchRelay() {
|
|
15883
|
+
const { nodes, edges, node, wire } = graph();
|
|
15884
|
+
node("clock", "time", 0, 0);
|
|
15885
|
+
node("degrees", "math", 1, 0, { op: "multiply", a: 0, b: 90 });
|
|
15886
|
+
node("ring", "tick-ring", 0, 1, {
|
|
15887
|
+
cx: 0,
|
|
15888
|
+
cy: 0,
|
|
15889
|
+
radius: 210,
|
|
15890
|
+
count: 60,
|
|
15891
|
+
length: 8,
|
|
15892
|
+
majorEvery: 5,
|
|
15893
|
+
majorLength: 18,
|
|
15894
|
+
majorWidth: 2,
|
|
15895
|
+
align: "inward",
|
|
15896
|
+
stroke: "#7fe8d0",
|
|
15897
|
+
strokeWidth: 1
|
|
15898
|
+
});
|
|
15899
|
+
node("spin", "transform", 1, 1, { pivot: "custom", pivotX: 0, pivotY: 0 });
|
|
15900
|
+
node("hub", "crosshair", 1, 2, { cx: 0, cy: 0, size: 26, gap: 7, stroke: "#5d7285" });
|
|
15901
|
+
node("dialMerge", "merge", 2, 1);
|
|
15902
|
+
node("dial", "layer", 3, 1, { order: 1 }, "Dial");
|
|
15903
|
+
wire("clock", "degrees", "a");
|
|
15904
|
+
wire("degrees", "spin", "rotation");
|
|
15905
|
+
wire("ring", "spin", "in");
|
|
15906
|
+
wire("spin", "dialMerge", "in");
|
|
15907
|
+
wire("hub", "dialMerge", "in");
|
|
15908
|
+
wire("dialMerge", "dial", "in");
|
|
15909
|
+
node("title", "text", 0, 4, { text: "SYS//RELAY 07", x: -90, y: -68, fontSize: 13, fill: "#dbe7f0", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
15910
|
+
node("code", "random-code", 0, 3, { count: 9, x: -90, y: -50, stepY: 14, fontSize: 11, fill: "#9fd4e8" });
|
|
15911
|
+
node("readMerge", "merge", 1, 3);
|
|
15912
|
+
node("pixels", "rasterize", 2, 3);
|
|
15913
|
+
node("tear", "glitch", 3, 3, { amount: 16, bands: 30, probability: 0.35, rate: 10, channelShift: 4 });
|
|
15914
|
+
node("fringe", "chromatic", 4, 3, { offset: 1.5 });
|
|
15915
|
+
node("crt", "scanlines", 5, 3, { spacing: 3, thickness: 1, strength: 0.3, speed: 24 });
|
|
15916
|
+
node("readout", "layer", 6, 3, { order: 2 }, "Readout");
|
|
15917
|
+
wire("code", "readMerge", "in");
|
|
15918
|
+
wire("title", "readMerge", "in");
|
|
15919
|
+
wire("readMerge", "pixels", "in");
|
|
15920
|
+
wire("pixels", "tear", "in");
|
|
15921
|
+
wire("tear", "fringe", "in");
|
|
15922
|
+
wire("fringe", "crt", "in");
|
|
15923
|
+
wire("crt", "readout", "in");
|
|
15924
|
+
node("out", "canvas", 7, 1, { width: 600, height: 600, bg: "#04070a", origin: "center" });
|
|
15925
|
+
wire("dial", "out", "in");
|
|
15926
|
+
wire("readout", "out", "in");
|
|
15927
|
+
return Project.parse({
|
|
15928
|
+
name: "glitch relay",
|
|
15929
|
+
seed: "relay-07",
|
|
15930
|
+
canvas: { w: 600, h: 600, bg: "#04070a", origin: "center" },
|
|
15931
|
+
timeline: { fps: 30, duration: 2 },
|
|
15932
|
+
graph: assignLayers({ nodes, edges })
|
|
15933
|
+
});
|
|
15934
|
+
}
|
|
15935
|
+
function warningStrip() {
|
|
15936
|
+
const { nodes, edges, node, wire } = graph();
|
|
15937
|
+
node("clock", "time", 0, 0);
|
|
15938
|
+
node("crawl", "math", 1, 0, { op: "multiply", a: 0, b: 40 });
|
|
15939
|
+
wire("clock", "crawl", "a");
|
|
15940
|
+
node("hazard", "stripes", 1, 1, { x: 0, y: -60, w: 420, h: 28, stripe: 14, gap: 14, lean: 30, fill: "#ffb547" });
|
|
15941
|
+
node("frame", "rect", 1, 2, { x: 0, y: -60, w: 420, h: 28, stroke: "#ffb547", strokeWidth: 1 });
|
|
15942
|
+
node("chevrons", "stripes", 1, 3, { x: 0, y: 40, w: 420, h: 40, pattern: "chevron", stripe: 18, gap: 22, point: 90, fill: "#7fe8d0" });
|
|
15943
|
+
node("fade", "stagger", 2, 3, { property: "opacity", from: 0.25, to: 1 });
|
|
15944
|
+
wire("crawl", "hazard", "offset");
|
|
15945
|
+
wire("crawl", "chevrons", "offset");
|
|
15946
|
+
wire("chevrons", "fade", "in");
|
|
15947
|
+
node("label", "text", 1, 4, { text: "CAUTION // LIVE FEED", x: -210, y: 110, fontSize: 12, fill: "#dbe7f0", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
15948
|
+
node("all", "merge", 3, 2);
|
|
15949
|
+
wire("hazard", "all", "in");
|
|
15950
|
+
wire("frame", "all", "in");
|
|
15951
|
+
wire("fade", "all", "in");
|
|
15952
|
+
wire("label", "all", "in");
|
|
15953
|
+
node("strip", "layer", 4, 2, { order: 1 }, "Strip");
|
|
15954
|
+
wire("all", "strip", "in");
|
|
15955
|
+
node("out", "canvas", 5, 2, { width: 600, height: 300, bg: "#04070a", origin: "center" });
|
|
15956
|
+
wire("strip", "out", "in");
|
|
15957
|
+
return Project.parse({
|
|
15958
|
+
name: "warning strip",
|
|
15959
|
+
seed: "caution-01",
|
|
15960
|
+
canvas: { w: 600, h: 300, bg: "#04070a", origin: "center" },
|
|
15961
|
+
timeline: { fps: 30, duration: 4 },
|
|
15962
|
+
graph: assignLayers({ nodes, edges })
|
|
15963
|
+
});
|
|
15964
|
+
}
|
|
15965
|
+
function panelSplit() {
|
|
15966
|
+
const { nodes, edges, node, wire } = graph();
|
|
15967
|
+
node("frameA", "rect", 0, 0, { x: -220, y: -260, w: 440, h: 230, anchor: "corner", stroke: "#8fd4e8", strokeWidth: 1 });
|
|
15968
|
+
node("header", "subdivide", 1, 0, { axis: "h", count: 2, ratio: 38 });
|
|
15969
|
+
node("lower", "select", 2, 0, { mode: "range", from: 1, to: 1 });
|
|
15970
|
+
node("columns", "subdivide", 3, 0, { axis: "v", count: 3, gap: 8 });
|
|
15971
|
+
node("recipe", "merge", 4, 0);
|
|
15972
|
+
wire("frameA", "header", "in");
|
|
15973
|
+
wire("header", "lower", "in");
|
|
15974
|
+
wire("lower", "columns", "in");
|
|
15975
|
+
wire("lower", "recipe", "in", "rest");
|
|
15976
|
+
wire("columns", "recipe", "in");
|
|
15977
|
+
node("frameB", "rect", 0, 2, { x: -220, y: 30, w: 440, h: 230, anchor: "corner", stroke: "#e8f4ff", strokeWidth: 1 });
|
|
15978
|
+
node("breath", "lfo", 0, 3, { rate: 0.25, amplitude: 12, center: 50 });
|
|
15979
|
+
node("mosaic", "subdivide", 1, 2, { mode: "recursive", depth: 4, rule: "alternate", variation: 0.4, stop: 0.25, gap: 10, salt: 3 });
|
|
15980
|
+
node("fade", "stagger", 2, 2, { property: "opacity", from: 0.3, to: 1 });
|
|
15981
|
+
wire("frameB", "mosaic", "in");
|
|
15982
|
+
wire("breath", "mosaic", "ratio");
|
|
15983
|
+
wire("mosaic", "fade", "in");
|
|
15984
|
+
node("all", "merge", 5, 1);
|
|
15985
|
+
wire("recipe", "all", "in");
|
|
15986
|
+
wire("fade", "all", "in");
|
|
15987
|
+
wire("mosaic", "all", "in", "lines");
|
|
15988
|
+
node("panels", "layer", 6, 1, { order: 1 }, "Panels");
|
|
15989
|
+
wire("all", "panels", "in");
|
|
15990
|
+
node("out", "canvas", 7, 1, { width: 600, height: 600, bg: "#04070a", origin: "center" });
|
|
15991
|
+
wire("panels", "out", "in");
|
|
15992
|
+
return Project.parse({
|
|
15993
|
+
name: "panel split",
|
|
15994
|
+
seed: "panels-01",
|
|
15995
|
+
canvas: { w: 600, h: 600, bg: "#04070a", origin: "center" },
|
|
15996
|
+
timeline: { fps: 30, duration: 4 },
|
|
15997
|
+
graph: assignLayers({ nodes, edges })
|
|
15998
|
+
});
|
|
15999
|
+
}
|
|
16000
|
+
function subdividerStudy() {
|
|
16001
|
+
const { nodes, edges, node, wire } = graph();
|
|
16002
|
+
const ink = "#aab4bf";
|
|
16003
|
+
const frame = (id, col, row, x, y, w, h) => node(id, "rect", col, row, { x, y, w, h, anchor: "corner", stroke: ink, strokeWidth: 1 });
|
|
16004
|
+
frame("f1", 0, 0, 40, 40, 300, 160);
|
|
16005
|
+
node("f1cut", "subdivide", 1, 0, { axis: "h", count: 2, ratio: 48 });
|
|
16006
|
+
node("f1low", "select", 2, 0, { mode: "range", from: 1, to: 1 });
|
|
16007
|
+
node("f1cols", "subdivide", 3, 0, { axis: "v", count: 3, ratios: "30 42 28" });
|
|
16008
|
+
node("f1all", "merge", 4, 0);
|
|
16009
|
+
wire("f1", "f1cut", "in");
|
|
16010
|
+
wire("f1cut", "f1low", "in");
|
|
16011
|
+
wire("f1low", "f1cols", "in");
|
|
16012
|
+
wire("f1low", "f1all", "in", "rest");
|
|
16013
|
+
wire("f1cols", "f1all", "in");
|
|
16014
|
+
frame("f2", 0, 1, 400, 40, 300, 160);
|
|
16015
|
+
node("f2cut", "subdivide", 1, 1, { axis: "h", count: 2, ratio: 70 });
|
|
16016
|
+
node("f2low", "select", 2, 1, { mode: "range", from: 1, to: 1 });
|
|
16017
|
+
node("f2cols", "subdivide", 3, 1, { axis: "v", count: 3, ratios: "30 40 30" });
|
|
16018
|
+
node("f2all", "merge", 4, 1);
|
|
16019
|
+
wire("f2", "f2cut", "in");
|
|
16020
|
+
wire("f2cut", "f2low", "in");
|
|
16021
|
+
wire("f2low", "f2cols", "in");
|
|
16022
|
+
wire("f2low", "f2all", "in", "rest");
|
|
16023
|
+
wire("f2cols", "f2all", "in");
|
|
16024
|
+
frame("f3", 0, 2, 40, 240, 300, 160);
|
|
16025
|
+
node("f3side", "subdivide", 1, 2, { axis: "v", count: 2, ratio: 19 });
|
|
16026
|
+
node("f3foot", "subdivide", 2, 2, { axis: "h", count: 2, ratio: 71 });
|
|
16027
|
+
wire("f3", "f3side", "in");
|
|
16028
|
+
wire("f3side", "f3foot", "in");
|
|
16029
|
+
frame("f4", 0, 3, 400, 240, 150, 160);
|
|
16030
|
+
node("f4cut", "subdivide", 1, 3, { axis: "v", count: 2, ratio: 50 });
|
|
16031
|
+
node("f4right", "select", 2, 3, { mode: "range", from: 1, to: 1 });
|
|
16032
|
+
node("f4strip", "subdivide", 3, 3, { axis: "h", count: 2, ratio: 82 });
|
|
16033
|
+
node("f4all", "merge", 4, 3);
|
|
16034
|
+
wire("f4", "f4cut", "in");
|
|
16035
|
+
wire("f4cut", "f4right", "in");
|
|
16036
|
+
wire("f4right", "f4strip", "in");
|
|
16037
|
+
wire("f4right", "f4all", "in", "rest");
|
|
16038
|
+
wire("f4strip", "f4all", "in");
|
|
16039
|
+
node("sheet", "merge", 5, 1);
|
|
16040
|
+
wire("f1all", "sheet", "in");
|
|
16041
|
+
wire("f2all", "sheet", "in");
|
|
16042
|
+
wire("f3foot", "sheet", "in");
|
|
16043
|
+
wire("f4all", "sheet", "in");
|
|
16044
|
+
node("panels", "layer", 6, 1, { order: 1 }, "Panels");
|
|
16045
|
+
wire("sheet", "panels", "in");
|
|
16046
|
+
node("out", "canvas", 7, 1, { width: 740, height: 440, bg: "#21252b", origin: "top-left" });
|
|
16047
|
+
wire("panels", "out", "in");
|
|
16048
|
+
return Project.parse({
|
|
16049
|
+
name: "subdivider study",
|
|
16050
|
+
seed: "sheet-01",
|
|
16051
|
+
canvas: { w: 740, h: 440, bg: "#21252b", origin: "top-left" },
|
|
16052
|
+
timeline: { fps: 30, duration: 2 },
|
|
16053
|
+
graph: assignLayers({ nodes, edges })
|
|
16054
|
+
});
|
|
16055
|
+
}
|
|
16056
|
+
function litPanels() {
|
|
16057
|
+
const { nodes, edges, node, wire } = graph();
|
|
16058
|
+
node("tokens", "style-tokens", 2, 2, {
|
|
16059
|
+
wear: true,
|
|
16060
|
+
ink: "#d8e6ee",
|
|
16061
|
+
dim: "#4e6a7a",
|
|
16062
|
+
faint: "#22323d",
|
|
16063
|
+
accent: "#5fd3ff",
|
|
16064
|
+
warn: "#ff7a45",
|
|
16065
|
+
bg: "#05090d",
|
|
16066
|
+
stroke: 1,
|
|
16067
|
+
bold: 2,
|
|
16068
|
+
mode: "random",
|
|
16069
|
+
dealInk: true,
|
|
16070
|
+
dealWarn: true,
|
|
16071
|
+
skip: 0.55,
|
|
16072
|
+
salt: 5
|
|
16073
|
+
});
|
|
16074
|
+
node("frame", "rect", 0, 2, { x: -260, y: -160, w: 520, h: 320, anchor: "corner", stroke: "#4e6a7a", strokeWidth: 1 });
|
|
16075
|
+
node("mosaic", "subdivide", 1, 2, { mode: "recursive", depth: 5, rule: "alternate", variation: 0.45, stop: 0.3, gap: 6, minSize: 18, salt: 4 });
|
|
16076
|
+
wire("frame", "mosaic", "in");
|
|
16077
|
+
wire("mosaic", "tokens", "in");
|
|
16078
|
+
node("rules", "style", 2, 3, { setStroke: true, setWidth: true, strokeWidth: 1 });
|
|
16079
|
+
wire("mosaic", "rules", "in", "lines");
|
|
16080
|
+
wire("tokens", "rules", "stroke", "faint");
|
|
16081
|
+
node("fade", "stagger", 3, 2, { property: "opacity", from: 0.4, to: 1 });
|
|
16082
|
+
wire("tokens", "fade", "in");
|
|
16083
|
+
node("label", "text", 2, 4, { text: "SECTOR MAP // 04", x: -260, y: 190, fontSize: 12, fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
16084
|
+
wire("tokens", "label", "fill", "ink");
|
|
16085
|
+
node("all", "merge", 4, 2);
|
|
16086
|
+
wire("fade", "all", "in");
|
|
16087
|
+
wire("rules", "all", "in");
|
|
16088
|
+
wire("label", "all", "in");
|
|
16089
|
+
node("board", "layer", 5, 2, { order: 1 }, "Board");
|
|
16090
|
+
wire("all", "board", "in");
|
|
16091
|
+
node("out", "canvas", 6, 2, { width: 640, height: 440, origin: "center" });
|
|
16092
|
+
wire("tokens", "out", "bg", "bg");
|
|
16093
|
+
wire("board", "out", "in");
|
|
16094
|
+
return Project.parse({
|
|
16095
|
+
name: "lit panels",
|
|
16096
|
+
seed: "sector-04",
|
|
16097
|
+
canvas: { w: 640, h: 440, bg: "#05090d", origin: "center" },
|
|
16098
|
+
timeline: { fps: 30, duration: 3 },
|
|
16099
|
+
graph: assignLayers({ nodes, edges })
|
|
16100
|
+
});
|
|
16101
|
+
}
|
|
16102
|
+
function kiosk() {
|
|
16103
|
+
const { nodes, edges, node, wire } = graph();
|
|
16104
|
+
node("tokens", "style-tokens", 2, 2, {
|
|
16105
|
+
wear: true,
|
|
16106
|
+
ink: "#f3ead3",
|
|
16107
|
+
dim: "#1e2a5a",
|
|
16108
|
+
faint: "#8a1e22",
|
|
16109
|
+
accent: "#2eb1c4",
|
|
16110
|
+
warn: "#f0a030",
|
|
16111
|
+
bg: "#d7262c",
|
|
16112
|
+
stroke: 1,
|
|
16113
|
+
bold: 2,
|
|
16114
|
+
mode: "cycle",
|
|
16115
|
+
dealInk: true,
|
|
16116
|
+
dealDim: true,
|
|
16117
|
+
dealFaint: false,
|
|
16118
|
+
dealAccent: true,
|
|
16119
|
+
dealWarn: true
|
|
16120
|
+
});
|
|
16121
|
+
node("word", "text", 0, 2, { text: "kiosk", fontFamily: "Anton SC", fontSize: 190, x: 0, y: 70, anchor: "middle", fill: "#f3ead3", outlines: true, letterSpacing: -4 });
|
|
16122
|
+
node("bands", "offset", 1, 2, { distance: 11, count: 6, join: "round", keepStyle: true });
|
|
16123
|
+
wire("word", "bands", "in");
|
|
16124
|
+
wire("bands", "tokens", "in");
|
|
16125
|
+
node("all", "merge", 3, 2);
|
|
16126
|
+
wire("tokens", "all", "in");
|
|
16127
|
+
wire("bands", "all", "in", "source");
|
|
16128
|
+
node("poster", "layer", 4, 2, { order: 1 }, "Poster");
|
|
16129
|
+
wire("all", "poster", "in");
|
|
16130
|
+
node("out", "canvas", 5, 2, { width: 640, height: 480, origin: "center" });
|
|
16131
|
+
wire("tokens", "out", "bg", "bg");
|
|
16132
|
+
wire("poster", "out", "in");
|
|
16133
|
+
return Project.parse({
|
|
16134
|
+
name: "kiosk",
|
|
16135
|
+
seed: "kiosk-01",
|
|
16136
|
+
canvas: { w: 640, h: 480, bg: "#d7262c", origin: "center" },
|
|
16137
|
+
timeline: { fps: 30, duration: 2 },
|
|
16138
|
+
graph: assignLayers({ nodes, edges })
|
|
16139
|
+
});
|
|
16140
|
+
}
|
|
16141
|
+
function ionDial() {
|
|
16142
|
+
const { nodes, edges, node, wire } = graph();
|
|
16143
|
+
node("tokens", "style-tokens", 0, 0, {
|
|
16144
|
+
wear: true,
|
|
16145
|
+
ink: "#e8dcc8",
|
|
16146
|
+
dim: "#8a7a5c",
|
|
16147
|
+
faint: "#453a24",
|
|
16148
|
+
accent: "#ffb547",
|
|
16149
|
+
warn: "#ff5b2e",
|
|
16150
|
+
bg: "#0a0703",
|
|
16151
|
+
stroke: 1,
|
|
16152
|
+
bold: 2.5
|
|
16153
|
+
});
|
|
16154
|
+
node("ring", "tick-ring", 1, 1, {
|
|
16155
|
+
cx: 0,
|
|
16156
|
+
cy: 0,
|
|
16157
|
+
radius: 215,
|
|
16158
|
+
count: 72,
|
|
16159
|
+
length: 7,
|
|
16160
|
+
majorEvery: 6,
|
|
16161
|
+
majorLength: 15,
|
|
16162
|
+
majorWidth: 1.8,
|
|
16163
|
+
align: "inward"
|
|
16164
|
+
});
|
|
16165
|
+
node("gauge", "circle", 1, 2, { cx: 0, cy: 0, r: 182, start: 220, sweep: 280 });
|
|
16166
|
+
node("redline", "circle", 1, 3, { cx: 0, cy: 0, r: 182, start: 140, sweep: 28 });
|
|
16167
|
+
node("labels", "radial-labels", 2, 1, {
|
|
16168
|
+
cx: 0,
|
|
16169
|
+
cy: 0,
|
|
16170
|
+
radius: 154,
|
|
16171
|
+
count: 8,
|
|
16172
|
+
start: 220,
|
|
16173
|
+
sweep: 280,
|
|
16174
|
+
from: 0,
|
|
16175
|
+
to: 700,
|
|
16176
|
+
fontSize: 11
|
|
16177
|
+
});
|
|
16178
|
+
node("readout", "text", 2, 2, { text: "PWR // BUS A", x: 0, y: 72, fontSize: 13, anchor: "middle", fontFamily: "IBM Plex Mono" });
|
|
16179
|
+
node("hub", "crosshair", 2, 3, { cx: 0, cy: 0, size: 24, gap: 8 });
|
|
16180
|
+
node("merged", "merge", 3, 1);
|
|
16181
|
+
node("main", "layer", 4, 1, {}, "Dial");
|
|
16182
|
+
node("out", "canvas", 5, 1, { width: 600, height: 600, origin: "center" });
|
|
16183
|
+
for (const id of ["ring", "gauge", "redline", "labels", "readout", "hub"]) wire(id, "merged", "in");
|
|
16184
|
+
wire("merged", "main", "in");
|
|
16185
|
+
wire("main", "out", "in");
|
|
16186
|
+
wire("tokens", "ring", "stroke", "dim");
|
|
16187
|
+
wire("tokens", "gauge", "stroke", "accent");
|
|
16188
|
+
wire("tokens", "gauge", "strokeWidth", "bold");
|
|
16189
|
+
wire("tokens", "redline", "stroke", "warn");
|
|
16190
|
+
wire("tokens", "redline", "strokeWidth", "bold");
|
|
16191
|
+
wire("tokens", "labels", "fill", "ink");
|
|
16192
|
+
wire("tokens", "readout", "fill", "ink");
|
|
16193
|
+
wire("tokens", "hub", "stroke", "faint");
|
|
16194
|
+
wire("tokens", "out", "bg", "bg");
|
|
16195
|
+
return Project.parse({
|
|
16196
|
+
name: "ion dial",
|
|
16197
|
+
seed: "ion-9",
|
|
16198
|
+
canvas: { w: 600, h: 600, bg: "#0a0703", origin: "center" },
|
|
16199
|
+
graph: assignLayers({ nodes, edges })
|
|
16200
|
+
});
|
|
16201
|
+
}
|
|
16202
|
+
function radar() {
|
|
16203
|
+
const { nodes, edges, node, wire } = graph();
|
|
16204
|
+
node("scope", "polar-grid", 0, 1, {
|
|
16205
|
+
cx: 0,
|
|
16206
|
+
cy: 0,
|
|
16207
|
+
radius: 224,
|
|
16208
|
+
innerRadius: 28,
|
|
16209
|
+
rings: 4,
|
|
16210
|
+
spokes: 12,
|
|
16211
|
+
subdivisions: 2,
|
|
16212
|
+
stroke: "#2c3e4a",
|
|
16213
|
+
strokeWidth: 1,
|
|
16214
|
+
subOpacity: 0.4
|
|
16215
|
+
});
|
|
16216
|
+
node("hub", "crosshair", 0, 2, { cx: 0, cy: 0, size: 20, gap: 6, stroke: "#5d7285" });
|
|
16217
|
+
node("edge", "line", 0, 3, { x1: 28, y1: 0, x2: 224, y2: 0, stroke: "#7fe8d0", strokeWidth: 1.6 });
|
|
16218
|
+
node("trail", "circle", 0, 4, { cx: 0, cy: 0, r: 222, start: 64, sweep: 26, stroke: "#7fe8d0", strokeWidth: 2, opacity: 0.5 });
|
|
16219
|
+
node("clock", "time", 1, 0);
|
|
16220
|
+
node("rate", "math", 2, 0, { op: "multiply", a: 0, b: 120 });
|
|
16221
|
+
node("sweepMerge", "merge", 1, 3);
|
|
16222
|
+
node("spin", "transform", 2, 3, { pivot: "custom", pivotX: 0, pivotY: 0 });
|
|
16223
|
+
node("blip", "circle", 1, 5, { cx: 0, cy: 0, r: 2.5, fill: "#7fe8d0", stroke: "none" });
|
|
16224
|
+
node("field", "scatter", 2, 5, { mode: "disc", count: 14, x: 0, y: 0, radius: 205 });
|
|
16225
|
+
node("scopeMerge", "merge", 1, 1);
|
|
16226
|
+
node("contactMerge", "merge", 3, 3);
|
|
16227
|
+
node("scopeLayer", "layer", 3, 1, { order: 1 }, "Scope");
|
|
16228
|
+
node("sweepLayer", "layer", 4, 3, { order: 2 }, "Sweep");
|
|
16229
|
+
node("out", "canvas", 5, 1, { width: 600, height: 600, bg: "#04070a", origin: "center" });
|
|
16230
|
+
wire("clock", "rate", "a");
|
|
16231
|
+
wire("rate", "spin", "rotation");
|
|
16232
|
+
wire("edge", "sweepMerge", "in");
|
|
16233
|
+
wire("trail", "sweepMerge", "in");
|
|
16234
|
+
wire("sweepMerge", "spin", "in");
|
|
16235
|
+
wire("blip", "field", "in");
|
|
16236
|
+
wire("scope", "scopeMerge", "in");
|
|
16237
|
+
wire("hub", "scopeMerge", "in");
|
|
16238
|
+
wire("scopeMerge", "scopeLayer", "in");
|
|
16239
|
+
wire("spin", "contactMerge", "in");
|
|
16240
|
+
wire("field", "contactMerge", "in");
|
|
16241
|
+
wire("contactMerge", "sweepLayer", "in");
|
|
16242
|
+
wire("scopeLayer", "out", "in");
|
|
16243
|
+
wire("sweepLayer", "out", "in");
|
|
16244
|
+
return Project.parse({
|
|
16245
|
+
name: "radar",
|
|
16246
|
+
seed: "contact-4",
|
|
16247
|
+
canvas: { w: 600, h: 600, bg: "#04070a", origin: "center" },
|
|
16248
|
+
// 120°/s over 3s = one full turn: the loop seam is invisible.
|
|
16249
|
+
timeline: { fps: 30, duration: 3 },
|
|
16250
|
+
graph: assignLayers({ nodes, edges })
|
|
16251
|
+
});
|
|
16252
|
+
}
|
|
16253
|
+
function telemetry() {
|
|
16254
|
+
const { nodes, edges, node, wire } = graph();
|
|
16255
|
+
node("paper", "grid", 0, 1, {
|
|
16256
|
+
x: 0,
|
|
16257
|
+
y: 0,
|
|
16258
|
+
w: 520,
|
|
16259
|
+
h: 520,
|
|
16260
|
+
cols: 6,
|
|
16261
|
+
rows: 6,
|
|
16262
|
+
subdivisions: 4,
|
|
16263
|
+
stroke: "#22303c",
|
|
16264
|
+
strokeWidth: 1,
|
|
16265
|
+
subOpacity: 0.35
|
|
16266
|
+
});
|
|
16267
|
+
node("title", "text", 0, 2, { text: "TELEMETRY // LINK 02", x: -252, y: -232, fontSize: 13, fill: "#dbe7f0", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
16268
|
+
node("code", "random-code", 0, 3, { count: 11, x: -252, y: -200, stepY: 15, fontSize: 10, fill: "#5d7285" });
|
|
16269
|
+
node("breath", "lfo", 1, 0, { shape: "sine", rate: 0.33, amplitude: 0.35, center: 0.55 });
|
|
16270
|
+
node("rx", "bar-meter", 2, 1, { x: 124, y: -197, count: 26, segW: 5, segH: 13, gap: 3, fillColor: "#7fe8d0", emptyColor: "#22303c" });
|
|
16271
|
+
node("tx", "bar-meter", 2, 2, { x: 124, y: -169, count: 26, value: 0.3, segW: 5, segH: 13, gap: 3, fillColor: "#9fd4e8", emptyColor: "#22303c" });
|
|
16272
|
+
node("rxLabel", "text", 1, 1, { text: "RX", x: -4, y: -193, fontSize: 10, fill: "#8a9aa8", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
16273
|
+
node("txLabel", "text", 1, 2, { text: "TX", x: -4, y: -165, fontSize: 10, fill: "#8a9aa8", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
16274
|
+
node("ruler", "tick-row", 0, 4, {
|
|
16275
|
+
x: 0,
|
|
16276
|
+
y: 236,
|
|
16277
|
+
span: 504,
|
|
16278
|
+
count: 43,
|
|
16279
|
+
length: 6,
|
|
16280
|
+
majorEvery: 6,
|
|
16281
|
+
majorLength: 12,
|
|
16282
|
+
majorWidth: 1.5,
|
|
16283
|
+
stroke: "#5d7285"
|
|
16284
|
+
});
|
|
16285
|
+
node("merged", "merge", 3, 1);
|
|
16286
|
+
node("main", "layer", 4, 1, {}, "Panel");
|
|
16287
|
+
node("out", "canvas", 5, 1, { width: 600, height: 600, bg: "#04070a", origin: "center" });
|
|
16288
|
+
wire("breath", "rx", "value");
|
|
16289
|
+
for (const id of ["paper", "title", "code", "rx", "tx", "rxLabel", "txLabel", "ruler"]) wire(id, "merged", "in");
|
|
16290
|
+
wire("merged", "main", "in");
|
|
16291
|
+
wire("main", "out", "in");
|
|
16292
|
+
return Project.parse({
|
|
16293
|
+
name: "telemetry",
|
|
16294
|
+
seed: "link-02",
|
|
16295
|
+
canvas: { w: 600, h: 600, bg: "#04070a", origin: "center" },
|
|
16296
|
+
timeline: { fps: 30, duration: 3 },
|
|
16297
|
+
graph: assignLayers({ nodes, edges })
|
|
16298
|
+
});
|
|
16299
|
+
}
|
|
16300
|
+
function bootSequence() {
|
|
16301
|
+
const { nodes, edges, node, wire } = graph();
|
|
16302
|
+
node("boot", "trigger", 0, 0, {
|
|
16303
|
+
value: { base: 1, track: { keyframes: [{ t: 0.2, value: 1, easing: "step" }] } }
|
|
16304
|
+
});
|
|
16305
|
+
node("frameSeq", "clip", 1, 0, {
|
|
16306
|
+
keys: { base: 0, track: { keyframes: [
|
|
16307
|
+
{ t: 0, value: 0, easing: "ease-in-out" },
|
|
16308
|
+
{ t: 1.4, value: 1, easing: "ease-in-out" }
|
|
16309
|
+
] } },
|
|
16310
|
+
duration: 1.4
|
|
16311
|
+
});
|
|
16312
|
+
node("textSeq", "clip", 2, 0, {
|
|
16313
|
+
keys: { base: 0, track: { keyframes: [
|
|
16314
|
+
{ t: 0, value: 0, easing: "linear" },
|
|
16315
|
+
{ t: 0.5, value: 0, easing: "linear" },
|
|
16316
|
+
{ t: 2.2, value: 1, easing: "linear" }
|
|
16317
|
+
] } },
|
|
16318
|
+
duration: 2.2
|
|
16319
|
+
});
|
|
16320
|
+
node("meterSeq", "clip", 3, 0, {
|
|
16321
|
+
keys: { base: 0, track: { keyframes: [
|
|
16322
|
+
{ t: 0, value: 0, easing: "linear" },
|
|
16323
|
+
{ t: 0.9, value: 0, easing: "ease-in-out" },
|
|
16324
|
+
{ t: 2.4, value: 1, easing: "ease-in-out" }
|
|
16325
|
+
] } },
|
|
16326
|
+
duration: 2.4
|
|
16327
|
+
});
|
|
16328
|
+
wire("boot", "frameSeq", "play");
|
|
16329
|
+
wire("boot", "textSeq", "play");
|
|
16330
|
+
wire("boot", "meterSeq", "play");
|
|
16331
|
+
node("brackets", "corner-brackets", 0, 1, { x: 0, y: 0, w: 470, h: 470, armX: 34, armY: 34, stroke: "#7fe8d0", strokeWidth: 1.5 });
|
|
16332
|
+
node("ring", "tick-ring", 0, 2, {
|
|
16333
|
+
cx: 0,
|
|
16334
|
+
cy: 0,
|
|
16335
|
+
radius: 200,
|
|
16336
|
+
count: 72,
|
|
16337
|
+
length: 6,
|
|
16338
|
+
majorEvery: 6,
|
|
16339
|
+
majorLength: 13,
|
|
16340
|
+
majorWidth: 1.5,
|
|
16341
|
+
align: "inward",
|
|
16342
|
+
stroke: "#5d7285"
|
|
16343
|
+
});
|
|
16344
|
+
node("hub", "crosshair", 0, 3, { cx: 0, cy: 0, size: 22, gap: 7, stroke: "#5d7285" });
|
|
16345
|
+
node("frameMerge", "merge", 1, 2);
|
|
16346
|
+
node("frameReveal", "reveal", 2, 2, { mode: "draw", stagger: 0.5 });
|
|
16347
|
+
node("frameLayer", "layer", 4, 2, { order: 1 }, "Frame");
|
|
16348
|
+
for (const id of ["brackets", "ring", "hub"]) wire(id, "frameMerge", "in");
|
|
16349
|
+
wire("frameMerge", "frameReveal", "in");
|
|
16350
|
+
wire("frameReveal", "frameLayer", "in");
|
|
16351
|
+
wire("frameSeq", "frameReveal", "progress");
|
|
16352
|
+
node("title", "text", 0, 4, { text: "BOOT SEQUENCE 09", x: -205, y: -205, fontSize: 14, letterSpacing: 2, fill: "#dbe7f0", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
16353
|
+
node("sub", "text", 0, 5, { text: "CORE SYSTEMS // ONLINE", x: -205, y: -184, fontSize: 10, fill: "#8a9aa8", fontFamily: "IBM Plex Mono", anchor: "start" });
|
|
16354
|
+
node("code", "random-code", 1, 4, { count: 8, x: -205, y: 128, stepY: 14, fontSize: 10, fill: "#9fd4e8" });
|
|
16355
|
+
node("meter", "bar-meter", 1, 5, { x: 60, y: -210, count: 22, value: 0.72, segW: 5, segH: 12, gap: 3, fillColor: "#7fe8d0", emptyColor: "#22303c" });
|
|
16356
|
+
node("codeReveal", "reveal", 2, 4, { mode: "wipe", angle: 90, stagger: 0.75 });
|
|
16357
|
+
node("meterReveal", "reveal", 2, 5, { mode: "wipe", angle: 0, stagger: 0.85 });
|
|
16358
|
+
node("readMerge", "merge", 3, 4);
|
|
16359
|
+
node("readLayer", "layer", 4, 4, { order: 2 }, "Readout");
|
|
16360
|
+
wire("textSeq", "title", "reveal");
|
|
16361
|
+
wire("textSeq", "sub", "reveal");
|
|
16362
|
+
wire("code", "codeReveal", "in");
|
|
16363
|
+
wire("meter", "meterReveal", "in");
|
|
16364
|
+
wire("meterSeq", "codeReveal", "progress");
|
|
16365
|
+
wire("meterSeq", "meterReveal", "progress");
|
|
16366
|
+
for (const id of ["title", "sub", "codeReveal", "meterReveal"]) wire(id, "readMerge", "in");
|
|
16367
|
+
wire("readMerge", "readLayer", "in");
|
|
16368
|
+
node("out", "canvas", 5, 3, { width: 600, height: 600, bg: "#04070a", origin: "center" });
|
|
16369
|
+
wire("frameLayer", "out", "in");
|
|
16370
|
+
wire("readLayer", "out", "in");
|
|
16371
|
+
return Project.parse({
|
|
16372
|
+
name: "boot sequence",
|
|
16373
|
+
seed: "boot-09",
|
|
16374
|
+
canvas: { w: 600, h: 600, bg: "#04070a", origin: "center" },
|
|
16375
|
+
// The boot takes ~2.6s; the rest of the loop is the finished HUD holding.
|
|
16376
|
+
timeline: { fps: 30, duration: 4 },
|
|
16377
|
+
graph: assignLayers({ nodes, edges })
|
|
16378
|
+
});
|
|
16379
|
+
}
|
|
16380
|
+
function speedDial() {
|
|
16381
|
+
const { nodes, edges, node, wire } = graph();
|
|
16382
|
+
node("level", "value", 0, 0, { value: 25 });
|
|
16383
|
+
node("levelNext", "math", 1, 0, { op: "add", a: 0, b: 1 });
|
|
16384
|
+
node("outerTicks", "tick-ring", 0, 1, { align: "inward", count: 120, cx: 400, cy: 400, length: 5, majorEvery: 10, majorLength: 11, majorWidth: 1.4, radius: 306, stroke: "#5d7285", strokeWidth: 0.9 });
|
|
16385
|
+
node("outerLabels", "radial-labels", 0, 2, { from: 0, to: 8, anchor: "middle", count: 9, cx: 400, cy: 400, fill: "#dbe7f0", fontSize: 13, radius: 274, start: 220, sweep: 280 });
|
|
16386
|
+
node("outerRing", "circle", 0, 3, { cx: 400, cy: 400, r: 330, stroke: "#9fd4e8", strokeWidth: 1.2, sweep: 360 });
|
|
16387
|
+
node("outerArcs", "partial-outline", 1, 1, { from: 0.02, to: 0.46, offset: 0.13, segments: 2 });
|
|
16388
|
+
node("redRing", "circle", 0, 4, { cx: 400, cy: 400, r: 306, stroke: "#ff5b2e", strokeWidth: 2.4 });
|
|
16389
|
+
node("redArc", "partial-outline", 1, 2, { from: 0, to: 0.115, offset: 0.055, segments: 1 });
|
|
16390
|
+
node("redTick", "tick-ring", 0, 5, { align: "outward", count: 1, cx: 400, cy: 400, length: 22, majorEvery: 0, radius: 296, start: 20, stroke: "#ff5b2e", strokeWidth: 2.4, sweep: 0 });
|
|
16391
|
+
node("innerRing", "circle", 0, 6, { cx: 400, cy: 400, r: 232, stroke: "#ffffff", strokeWidth: 3.2 });
|
|
16392
|
+
node("midRing", "circle", 0, 7, { cx: 400, cy: 400, r: 248, stroke: "#33424f", strokeWidth: 1 });
|
|
16393
|
+
node("gaugeBar", "rect", 0, 8, { w: 22, h: 3.6, x: -11, y: -1.8, fill: "#5d7285", rx: 0, stroke: "none" });
|
|
16394
|
+
node("gaugeRing", "array-radial", 1, 3, { count: 44, cx: 400, cy: 400, mode: "orbit", radius: 202, start: 208, sweep: 304 });
|
|
16395
|
+
node("gaugeLit", "select", 2, 1, { from: 0, to: 25, mode: "range" });
|
|
16396
|
+
node("gaugeLitStyle", "style", 3, 1, { fill: "#dbe7f0", setFill: true, setStroke: false });
|
|
16397
|
+
node("gaugeMid", "select", 2, 2, { from: 26, to: 37, mode: "range" });
|
|
16398
|
+
node("gaugeMidStyle", "style", 3, 2, { fill: "#5d7285", setFill: true, setStroke: false });
|
|
16399
|
+
node("gaugeHot", "select", 2, 3, { from: 38, to: 43, mode: "range" });
|
|
16400
|
+
node("gaugeHotStyle", "style", 3, 3, { fill: "#ff5b2e", setFill: true, setStroke: false });
|
|
16401
|
+
node("innerTicks", "tick-ring", 0, 9, { align: "inward", count: 60, cx: 400, cy: 400, length: 4, majorEvery: 5, majorLength: 8, radius: 178, start: 210, stroke: "#33424f", strokeWidth: 0.8, sweep: 300 });
|
|
16402
|
+
node("centreFrame", "corner-brackets", 0, 10, { w: 300, h: 184, x: 250, y: 308, armX: 34, armY: 26, stroke: "#5d7285", strokeWidth: 1 });
|
|
16403
|
+
node("hairline", "line", 0, 11, { stroke: "#33424f", strokeWidth: 1, x1: 268, x2: 532, y1: 414, y2: 414 });
|
|
16404
|
+
node("marker", "polygon", 0, 12, { cx: 400, cy: 550, radius: 13, rotation: 90, sides: 3, stroke: "#5d7285", strokeWidth: 1.4 });
|
|
16405
|
+
node("speed", "text", 0, 13, { x: 400, y: 378, anchor: "middle", fill: "#ffffff", fontFamily: "Rajdhani", fontSize: 96, letterSpacing: 6, text: "64", fontWeight: 600 });
|
|
16406
|
+
node("unit", "text", 0, 14, { x: 400, y: 402, anchor: "middle", fill: "#5d7285", fontFamily: "IBM Plex Mono", fontSize: 15, letterSpacing: 4, text: "MPH" });
|
|
16407
|
+
node("gear", "text", 0, 15, { x: 400, y: 492, anchor: "middle", fill: "#ffffff", fontFamily: "Rajdhani", fontSize: 62, text: "3", fontWeight: 600 });
|
|
16408
|
+
node("corners", "text", 0, 16, { x: 272, y: 338, anchor: "middle", fill: "#dbe7f0", fontFamily: "IBM Plex Mono", fontSize: 13, text: "F" });
|
|
16409
|
+
node("cornersH", "text", 0, 17, { x: 528, y: 338, anchor: "middle", fill: "#ff5b2e", fontFamily: "IBM Plex Mono", fontSize: 13, text: "H" });
|
|
16410
|
+
node("cornersE", "text", 0, 18, { x: 272, y: 474, anchor: "middle", fill: "#ff5b2e", fontFamily: "IBM Plex Mono", fontSize: 13, text: "E" });
|
|
16411
|
+
node("cornersC", "text", 0, 19, { x: 528, y: 474, anchor: "middle", fill: "#dbe7f0", fontFamily: "IBM Plex Mono", fontSize: 13, text: "C" });
|
|
16412
|
+
node("codes", "random-code", 0, 20, { x: 96, y: 690, count: 3, fill: "#33424f", fontSize: 11, letterSpacing: 1, pattern: "AA#-####", stepY: 17 });
|
|
16413
|
+
node("bgLayerMerge", "merge", 1, 4);
|
|
16414
|
+
node("bgLayer", "layer", 2, 4, { order: 1 }, "bgLayer");
|
|
16415
|
+
node("gaugeLayerMerge", "merge", 4, 1);
|
|
16416
|
+
node("gaugeLayer", "layer", 5, 1, { order: 2 }, "gaugeLayer");
|
|
16417
|
+
node("ringLayerMerge", "merge", 2, 5);
|
|
16418
|
+
node("ringLayer", "layer", 3, 4, { order: 3 }, "ringLayer");
|
|
16419
|
+
node("textLayerMerge", "merge", 1, 5);
|
|
16420
|
+
node("textLayer", "layer", 2, 6, { order: 4 }, "textLayer");
|
|
16421
|
+
node("out", "canvas", 6, 1, { bg: "#04070a", width: 800, height: 800, origin: "top-left" });
|
|
16422
|
+
wire("outerRing", "outerArcs", "in");
|
|
16423
|
+
wire("redRing", "redArc", "in");
|
|
16424
|
+
wire("gaugeBar", "gaugeRing", "in");
|
|
16425
|
+
wire("gaugeRing", "gaugeLit", "in");
|
|
16426
|
+
wire("gaugeLit", "gaugeLitStyle", "in");
|
|
16427
|
+
wire("gaugeRing", "gaugeMid", "in");
|
|
16428
|
+
wire("gaugeMid", "gaugeMidStyle", "in");
|
|
16429
|
+
wire("gaugeRing", "gaugeHot", "in");
|
|
16430
|
+
wire("gaugeHot", "gaugeHotStyle", "in");
|
|
16431
|
+
wire("outerTicks", "bgLayerMerge", "in");
|
|
16432
|
+
wire("outerLabels", "bgLayerMerge", "in");
|
|
16433
|
+
wire("innerTicks", "bgLayerMerge", "in");
|
|
16434
|
+
wire("midRing", "bgLayerMerge", "in");
|
|
16435
|
+
wire("bgLayerMerge", "bgLayer", "in");
|
|
16436
|
+
wire("gaugeLitStyle", "gaugeLayerMerge", "in");
|
|
16437
|
+
wire("gaugeMidStyle", "gaugeLayerMerge", "in");
|
|
16438
|
+
wire("gaugeHotStyle", "gaugeLayerMerge", "in");
|
|
16439
|
+
wire("gaugeLayerMerge", "gaugeLayer", "in");
|
|
16440
|
+
wire("outerArcs", "ringLayerMerge", "in");
|
|
16441
|
+
wire("redArc", "ringLayerMerge", "in");
|
|
16442
|
+
wire("redTick", "ringLayerMerge", "in");
|
|
16443
|
+
wire("innerRing", "ringLayerMerge", "in");
|
|
16444
|
+
wire("ringLayerMerge", "ringLayer", "in");
|
|
16445
|
+
wire("centreFrame", "textLayerMerge", "in");
|
|
16446
|
+
wire("hairline", "textLayerMerge", "in");
|
|
16447
|
+
wire("speed", "textLayerMerge", "in");
|
|
16448
|
+
wire("unit", "textLayerMerge", "in");
|
|
16449
|
+
wire("gear", "textLayerMerge", "in");
|
|
16450
|
+
wire("corners", "textLayerMerge", "in");
|
|
16451
|
+
wire("cornersH", "textLayerMerge", "in");
|
|
16452
|
+
wire("cornersE", "textLayerMerge", "in");
|
|
16453
|
+
wire("cornersC", "textLayerMerge", "in");
|
|
16454
|
+
wire("codes", "textLayerMerge", "in");
|
|
16455
|
+
wire("marker", "textLayerMerge", "in");
|
|
16456
|
+
wire("textLayerMerge", "textLayer", "in");
|
|
16457
|
+
wire("bgLayer", "out", "in");
|
|
16458
|
+
wire("gaugeLayer", "out", "in");
|
|
16459
|
+
wire("ringLayer", "out", "in");
|
|
16460
|
+
wire("textLayer", "out", "in");
|
|
16461
|
+
wire("level", "gaugeLit", "to");
|
|
16462
|
+
wire("level", "levelNext", "a");
|
|
16463
|
+
wire("levelNext", "gaugeMid", "from");
|
|
16464
|
+
return Project.parse({
|
|
16465
|
+
name: "speed dial",
|
|
16466
|
+
seed: "gmunk",
|
|
16467
|
+
canvas: { w: 800, h: 800, bg: "#04070a", origin: "top-left" },
|
|
16468
|
+
timeline: { fps: 30, duration: 4, loop: true },
|
|
16469
|
+
graph: assignLayers({ nodes, edges })
|
|
16470
|
+
});
|
|
16471
|
+
}
|
|
16472
|
+
function cornersStudy() {
|
|
16473
|
+
const { nodes, edges, node, wire } = graph();
|
|
16474
|
+
node("panel", "rect", 0, 0, { w: 300, h: 180, x: 60, y: 80, anchor: "corner", fill: "none", stroke: "#7fe8d0", strokeWidth: 1.5 }, "Panel");
|
|
16475
|
+
node("tab", "rect", 0, 1, { w: 120, h: 70, x: 260, y: 60, anchor: "corner", fill: "none", stroke: "#7fe8d0", strokeWidth: 1.5 }, "Tab cutout");
|
|
16476
|
+
node("panelCut", "boolean", 1, 0, { op: "subtract" });
|
|
16477
|
+
node("panelIn", "corners", 2, 0, { size: 12, style: "round", which: "concave" }, "Inner: round 12");
|
|
16478
|
+
node("panelOut", "corners", 3, 0, { size: 16, style: "chamfer", which: "convex" }, "Outer: chamfer 16");
|
|
16479
|
+
node("panelGhost", "style", 2, 1, { opacity: 0.6, setOpacity: true, setStroke: true, setWidth: true, stroke: "#3a5a55", strokeWidth: 1 }, "Raw cut (ghost, in place)");
|
|
16480
|
+
node("L", "svg", 0, 2, { x: 540, y: 170, fill: "none", size: 180, stroke: "#7fe8d0", strokeWidth: 1.5, svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160"><path d="M0 0H160V60H60V160H0Z"/></svg>' }, "L profile");
|
|
16481
|
+
node("Lin", "corners", 1, 2, { size: 10, style: "round", which: "concave" }, "Inner: round 10");
|
|
16482
|
+
node("Lout", "corners", 2, 2, { size: 26, style: "round", which: "convex" }, "Outer: round 26");
|
|
16483
|
+
node("starA", "svg", 0, 3, { x: 820, y: 170, fill: "none", size: 180, stroke: "#7fe8d0", strokeWidth: 1.5, svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><path d="M100.00 10.00L122.34 69.26L185.60 72.19L136.14 111.74L152.90 172.81L100.00 138.00L47.10 172.81L63.86 111.74L14.40 72.19L77.66 69.26Z"/></svg>' }, "Star");
|
|
16484
|
+
node("starValleys", "corners", 1, 3, { size: 9, style: "round", which: "concave" }, "Valleys: round 9");
|
|
16485
|
+
node("starB", "svg", 0, 4, { x: 1040, y: 170, fill: "none", size: 180, stroke: "#7fe8d0", strokeWidth: 1.5, svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"><path d="M100.00 10.00L122.34 69.26L185.60 72.19L136.14 111.74L152.90 172.81L100.00 138.00L47.10 172.81L63.86 111.74L14.40 72.19L77.66 69.26Z"/></svg>' }, "Star");
|
|
16486
|
+
node("starTips", "corners", 1, 4, { size: 9, style: "round", which: "convex" }, "Tips: round 9");
|
|
16487
|
+
node("bar", "rect", 0, 5, { w: 26, h: 90, x: 80, y: 380, anchor: "corner", fill: "none", stroke: "#7fe8d0", strokeWidth: 1.5 }, "Bar");
|
|
16488
|
+
node("bars", "array-linear", 1, 5, { count: 8, origin: "start", stepX: 40, stepY: 0 });
|
|
16489
|
+
node("base", "rect", 0, 6, { w: 306, h: 40, x: 80, y: 450, anchor: "corner", fill: "none", stroke: "#7fe8d0", strokeWidth: 1.5 }, "Base");
|
|
16490
|
+
node("comb", "boolean", 2, 4, { op: "union" });
|
|
16491
|
+
node("combIn", "corners", 3, 4, { size: 6, style: "chamfer", which: "concave" }, "Gaps: chamfer 6");
|
|
16492
|
+
node("comb2", "boolean", 2, 5, { op: "union" });
|
|
16493
|
+
node("shift2", "transform", 3, 5, { tx: 420, ty: 0 });
|
|
16494
|
+
node("combNotch", "corners", 4, 6, { size: 6, style: "notch", which: "concave" }, "Gaps: notch 6");
|
|
16495
|
+
node("t1", "text", 4, 0, { x: 60, y: 62, anchor: "start", fill: "#7fe8d0", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "NOTCHED PANEL \xB7 in round 12 / out chamfer 16" });
|
|
16496
|
+
node("main", "layer", 5, 2, {}, "Corners");
|
|
16497
|
+
node("out", "canvas", 6, 2, { bg: "#05070a", width: 1200, height: 700, origin: "top-left" });
|
|
16498
|
+
node("t2", "text", 4, 2, { x: 450, y: 62, anchor: "start", fill: "#7fe8d0", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "L PROFILE \xB7 in round 10 / out round 26" });
|
|
16499
|
+
node("t3", "text", 4, 3, { x: 730, y: 62, anchor: "start", fill: "#7fe8d0", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "STAR \xB7 valleys rounded" });
|
|
16500
|
+
node("t3b", "text", 4, 4, { x: 960, y: 62, anchor: "start", fill: "#7fe8d0", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "STAR \xB7 tips rounded" });
|
|
16501
|
+
node("t4", "text", 4, 5, { x: 80, y: 360, anchor: "start", fill: "#7fe8d0", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "COMB \xB7 concave chamfer 6" });
|
|
16502
|
+
node("t4b", "text", 4, 7, { x: 500, y: 360, anchor: "start", fill: "#7fe8d0", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "COMB \xB7 concave notch 6" });
|
|
16503
|
+
node("t1b", "text", 4, 1, { x: 60, y: 285, anchor: "start", fill: "#3a5a55", fontSize: 11, letterSpacing: 1.5, stroke: "none", text: "dim outline: the raw Boolean cut, before Corners" });
|
|
16504
|
+
wire("panel", "panelCut", "in");
|
|
16505
|
+
wire("tab", "panelCut", "with");
|
|
16506
|
+
wire("panelCut", "panelIn", "in");
|
|
16507
|
+
wire("panelIn", "panelOut", "in");
|
|
16508
|
+
wire("panelCut", "panelGhost", "in");
|
|
16509
|
+
wire("L", "Lin", "in");
|
|
16510
|
+
wire("Lin", "Lout", "in");
|
|
16511
|
+
wire("starA", "starValleys", "in");
|
|
16512
|
+
wire("starB", "starTips", "in");
|
|
16513
|
+
wire("bar", "bars", "in");
|
|
16514
|
+
wire("bars", "comb", "in");
|
|
16515
|
+
wire("base", "comb", "with");
|
|
16516
|
+
wire("comb", "combIn", "in");
|
|
16517
|
+
wire("bars", "comb2", "in");
|
|
16518
|
+
wire("base", "comb2", "with");
|
|
16519
|
+
wire("comb2", "shift2", "in");
|
|
16520
|
+
wire("shift2", "combNotch", "in");
|
|
16521
|
+
wire("panelOut", "main", "in");
|
|
16522
|
+
wire("panelGhost", "main", "in");
|
|
16523
|
+
wire("Lout", "main", "in");
|
|
16524
|
+
wire("starValleys", "main", "in");
|
|
16525
|
+
wire("starTips", "main", "in");
|
|
16526
|
+
wire("combIn", "main", "in");
|
|
16527
|
+
wire("combNotch", "main", "in");
|
|
16528
|
+
wire("t1", "main", "in");
|
|
16529
|
+
wire("main", "out", "in");
|
|
16530
|
+
wire("t2", "main", "in");
|
|
16531
|
+
wire("t3", "main", "in");
|
|
16532
|
+
wire("t3b", "main", "in");
|
|
16533
|
+
wire("t4", "main", "in");
|
|
16534
|
+
wire("t4b", "main", "in");
|
|
16535
|
+
wire("t1b", "main", "in");
|
|
16536
|
+
return Project.parse({
|
|
16537
|
+
name: "corners study",
|
|
16538
|
+
seed: "corners",
|
|
16539
|
+
canvas: { w: 1200, h: 700, bg: "#05070a", origin: "top-left" },
|
|
16540
|
+
timeline: { fps: 30, duration: 4, loop: false },
|
|
16541
|
+
graph: assignLayers({ nodes, edges })
|
|
16542
|
+
});
|
|
16543
|
+
}
|
|
16544
|
+
function varyRow() {
|
|
16545
|
+
const { nodes, edges, node, wire } = graph();
|
|
16546
|
+
const teal = "#7fe8d0";
|
|
16547
|
+
const dim = "#3a5a55";
|
|
16548
|
+
const bar = (y, w = 3, fill = teal) => ({ w, h: 80, x: 60, y, anchor: "corner", fill, stroke: "none" });
|
|
16549
|
+
const row = { count: 60, origin: "start", stepX: 18, stepY: 0 };
|
|
16550
|
+
const label = (y, text, fill = teal) => ({ x: 60, y, anchor: "start", fill, fontSize: 11, letterSpacing: 1.5, stroke: "none", text });
|
|
16551
|
+
node("bar1", "rect", 0, 0, bar(70), "Bar 3\xD780");
|
|
16552
|
+
node("row1", "array-linear", 1, 0, row);
|
|
16553
|
+
node("jit1", "jitter", 2, 0, { position: 0, scale: 0.7, scaleAxis: "x" }, "Jitter \xB7 Scale Axis X");
|
|
16554
|
+
node("t1", "text", 3, 0, label(58, "JITTER \xB7 Scale 0.7, Scale Axis X only \u2014 widths scatter, heights hold"));
|
|
16555
|
+
wire("bar1", "row1", "in");
|
|
16556
|
+
wire("row1", "jit1", "in");
|
|
16557
|
+
node("bar2", "rect", 0, 1, bar(195), "Bar 3\xD780");
|
|
16558
|
+
node("row2", "array-linear", 1, 1, row);
|
|
16559
|
+
node("tf2", "transform", 2, 1, { sx: { base: 1, jitter: { amount: 0.6, apply: "mul", mode: "frozen" } } }, "Transform \xB7 Scale X jittered");
|
|
16560
|
+
node("t2", "text", 3, 1, label(183, "TRANSFORM \xB7 right-click Scale X, Add jitter, \xD70.6 \u2014 works on any param of any node after the array"));
|
|
16561
|
+
wire("bar2", "row2", "in");
|
|
16562
|
+
wire("row2", "tf2", "in");
|
|
16563
|
+
node("bar3", "rect", 0, 2, bar(320), "Bar 3\xD780");
|
|
16564
|
+
node("row3", "array-linear", 1, 2, row);
|
|
16565
|
+
node("st3", "stagger", 2, 2, { property: "scale", axis: "x", from: 0.4, to: { base: 2.6, jitter: { amount: 1, mode: "frozen" } } }, "Stagger \xB7 Scale X, To jittered");
|
|
16566
|
+
node("t3", "text", 3, 2, label(308, "STAGGER \xB7 Scale X ramp 0.4 to 2.6, with a jitter on To \u2014 ramp plus noise, louder along the row"));
|
|
16567
|
+
wire("bar3", "row3", "in");
|
|
16568
|
+
wire("row3", "st3", "in");
|
|
16569
|
+
node("bar4a", "rect", 0, 3, bar(445, 2), "Bar 2\xD780");
|
|
16570
|
+
node("bar4b", "rect", 0, 4, bar(445, 5), "Bar 5\xD780");
|
|
16571
|
+
node("bar4c", "rect", 0, 5, bar(445, 10), "Bar 10\xD780");
|
|
16572
|
+
node("mg4", "merge", 1, 4);
|
|
16573
|
+
node("row4", "array-linear", 2, 4, { ...row, deal: true }, "Array Linear \xB7 Deal");
|
|
16574
|
+
node("t4", "text", 3, 4, label(433, "DEAL \xB7 three bars 2 / 5 / 10 merged, Array Linear with Deal on \u2014 cycles them, no randomness"));
|
|
16575
|
+
wire("bar4a", "mg4", "in");
|
|
16576
|
+
wire("bar4b", "mg4", "in");
|
|
16577
|
+
wire("bar4c", "mg4", "in");
|
|
16578
|
+
wire("mg4", "row4", "in");
|
|
16579
|
+
node("bar5", "rect", 0, 6, { ...bar(570, 3, dim), w: { base: 3, jitter: { amount: 0.6, apply: "mul", mode: "frozen" } } }, "Bar \xB7 Width jittered");
|
|
16580
|
+
node("row5", "array-linear", 1, 6, row);
|
|
16581
|
+
node("t5", "text", 3, 6, label(558, "RECT WIDTH JITTERED, THEN ARRAYED \xB7 one shape, one roll: sixty identical bars. Vary after the array, not before.", dim));
|
|
16582
|
+
wire("bar5", "row5", "in");
|
|
16583
|
+
node("main", "layer", 4, 3, {}, "Rows");
|
|
16584
|
+
node("out", "canvas", 5, 3, { bg: "#05070a", width: 1200, height: 700, origin: "top-left" });
|
|
16585
|
+
for (const id of ["jit1", "tf2", "st3", "row4", "row5", "t1", "t2", "t3", "t4", "t5"]) wire(id, "main", "in");
|
|
16586
|
+
wire("main", "out", "in");
|
|
16587
|
+
return Project.parse({
|
|
16588
|
+
name: "vary a row",
|
|
16589
|
+
seed: "vary",
|
|
16590
|
+
canvas: { w: 1200, h: 700, bg: "#05070a", origin: "top-left" },
|
|
16591
|
+
timeline: { fps: 30, duration: 4, loop: false },
|
|
16592
|
+
graph: assignLayers({ nodes, edges })
|
|
16593
|
+
});
|
|
16594
|
+
}
|
|
16595
|
+
function variableFontStudy() {
|
|
16596
|
+
const { nodes, edges, node, wire } = graph();
|
|
16597
|
+
const face = { fontFamily: "Orbitron", fontSize: 64, anchor: "start", fill: "#dbe7f0" };
|
|
16598
|
+
const caption = { fontFamily: "IBM Plex Mono", fontSize: 11, anchor: "start", fill: "#5d7285", letterSpacing: 0.5 };
|
|
16599
|
+
const X = 48;
|
|
16600
|
+
node("breath", "lfo", 0, 0, { shape: "sine", rate: 0.5, center: 650, amplitude: 250 });
|
|
16601
|
+
node("breathe", "text", 1, 0, { ...face, text: "BREATHE", x: X, y: 96, fontWeight: "650" });
|
|
16602
|
+
node("breatheCap", "text", 1, 1, { ...caption, text: "LFO \u2192 WEIGHT \xB7 SINE 0.5 HZ, CENTRE 650, AMPLITUDE 250", x: X, y: 124 });
|
|
16603
|
+
wire("breath", "breathe", "fontWeight");
|
|
16604
|
+
node("clock", "time", 0, 2);
|
|
16605
|
+
node("rise", "ramp", 1, 2, { inMin: 0, inMax: 2.5, outMin: 400, outMax: 900, clamp: true, ease: "smooth" });
|
|
16606
|
+
node("rising", "text", 2, 2, { ...face, text: "MATERIALISE", x: X, y: 218, fontWeight: "400" });
|
|
16607
|
+
node("risingCap", "text", 2, 3, { ...caption, text: "TIME \u2192 RAMP \u2192 WEIGHT \xB7 400 AT 0 S, 900 BY 2.5 S, EASED", x: X, y: 246 });
|
|
16608
|
+
wire("clock", "rise", "value");
|
|
16609
|
+
wire("rise", "rising", "fontWeight");
|
|
16610
|
+
node("wave", "text", 0, 4, { ...face, text: "WAVEFORM", x: X, y: 340, fontWeight: "650", letterSpacing: 6 });
|
|
16611
|
+
node("waveGlyphs", "split-glyphs", 1, 4);
|
|
16612
|
+
node("waveClock", "time", 1, 5);
|
|
16613
|
+
node("wavePhase", "math", 2, 5, { op: "multiply", a: 0, b: 0.5 });
|
|
16614
|
+
node("waveStagger", "stagger", 2, 4, { property: "weight", from: 400, to: 900, curve: "wave", cycles: 1 });
|
|
16615
|
+
node("waveCap", "text", 3, 5, { ...caption, text: "SPLIT GLYPHS \u2192 STAGGER \xB7 FONT WEIGHT 400\u2013900, CURVE WAVE, 1 CYCLE \xB7 TIME \xD7 0.5 \u2192 PHASE", x: X, y: 368 });
|
|
16616
|
+
wire("wave", "waveGlyphs", "in");
|
|
16617
|
+
wire("waveGlyphs", "waveStagger", "in");
|
|
16618
|
+
wire("waveClock", "wavePhase", "a");
|
|
16619
|
+
wire("wavePhase", "waveStagger", "phase");
|
|
16620
|
+
node("scan", "text", 0, 6, { ...face, text: "SCANLINE", x: X, y: 462, fontWeight: "400", letterSpacing: 6 });
|
|
16621
|
+
node("scanGlyphs", "split-glyphs", 1, 6);
|
|
16622
|
+
node("scanSweep", "lfo", 1, 7, { shape: "saw", rate: 0.5, unipolar: true, center: -0.2, amplitude: 1.4 });
|
|
16623
|
+
node("scanStagger", "stagger", 2, 6, { property: "weight", from: 400, to: 900, curve: "peak", width: 0.25 });
|
|
16624
|
+
node("scanCap", "text", 3, 7, { ...caption, text: "SPLIT GLYPHS \u2192 STAGGER \xB7 FONT WEIGHT 400\u2013900, CURVE PEAK, WIDTH 0.25 \xB7 SAW LFO \u22120.2\u20261.2 \u2192 POSITION", x: X, y: 490 });
|
|
16625
|
+
wire("scan", "scanGlyphs", "in");
|
|
16626
|
+
wire("scanGlyphs", "scanStagger", "in");
|
|
16627
|
+
wire("scanSweep", "scanStagger", "position");
|
|
16628
|
+
node("boil", "text", 0, 8, { ...face, text: "DECODE 0x7F3A", x: X, y: 584, fontWeight: "650", letterSpacing: 6 });
|
|
16629
|
+
node("boilGlyphs", "split-glyphs", 1, 8);
|
|
16630
|
+
node("boilStyle", "style", 2, 8, {
|
|
16631
|
+
setWeight: true,
|
|
16632
|
+
weight: { base: 650, jitter: { amount: 250, apply: "add", distribution: "uniform", mode: "animated", noiseFreq: 3 } }
|
|
16633
|
+
});
|
|
16634
|
+
node("boilCap", "text", 2, 9, { ...caption, text: "SPLIT GLYPHS \u2192 STYLE \xB7 SET WEIGHT 650, RIGHT-CLICK JITTER \xB1250, ANIMATED, 3 HZ", x: X, y: 612 });
|
|
16635
|
+
wire("boil", "boilGlyphs", "in");
|
|
16636
|
+
wire("boilGlyphs", "boilStyle", "in");
|
|
16637
|
+
node("squash", "lfo", 0, 10, { shape: "triangle", rate: 0.25, center: 87.5, amplitude: 12.5 });
|
|
16638
|
+
node("squeeze", "text", 1, 10, { ...face, fontFamily: "Tektur", text: "SQUEEZE TO FIT", x: X, y: 706, fontWeight: "600" });
|
|
16639
|
+
node("squeezeCap", "text", 1, 11, { ...caption, text: "TEKTUR \xB7 LFO \u2192 STRETCH \xB7 TRIANGLE 0.25 HZ, 75\u2013100 % \xB7 WEIGHT HOLDS AT 600", x: X, y: 734 });
|
|
16640
|
+
wire("squash", "squeeze", "fontStretch");
|
|
16641
|
+
node("title", "text", 3, 0, { ...caption, text: "VARIABLE FONT STUDY \xB7 ORBITRON 400\u2013900 \xB7 TEKTUR 400\u2013900 \xD7 75\u2013100 % \xB7 ONE FILE, ANY WEIGHT", x: X, y: 36, fill: "#7fe8d0" });
|
|
16642
|
+
node("sheet", "merge", 3, 4);
|
|
16643
|
+
node("sheetLayer", "layer", 4, 4, { order: 1 }, "Study");
|
|
16644
|
+
for (const id of ["title", "breathe", "breatheCap", "rising", "risingCap", "waveStagger", "waveCap", "scanStagger", "scanCap", "boilStyle", "boilCap", "squeeze", "squeezeCap"]) {
|
|
16645
|
+
wire(id, "sheet", "in");
|
|
16646
|
+
}
|
|
16647
|
+
wire("sheet", "sheetLayer", "in");
|
|
16648
|
+
node("out", "canvas", 5, 4, { width: 960, height: 760, bg: "#05070a", origin: "top-left" });
|
|
16649
|
+
wire("sheetLayer", "out", "in");
|
|
16650
|
+
return Project.parse({
|
|
16651
|
+
name: "variable font study",
|
|
16652
|
+
seed: "wght",
|
|
16653
|
+
canvas: { w: 960, h: 760, bg: "#05070a", origin: "top-left" },
|
|
16654
|
+
// Every driver repeats in 2 s or 4 s, so the loop closes on itself.
|
|
16655
|
+
timeline: { fps: 30, duration: 4 },
|
|
16656
|
+
graph: assignLayers({ nodes, edges })
|
|
16657
|
+
});
|
|
16658
|
+
}
|
|
16659
|
+
function depthStack() {
|
|
16660
|
+
const { nodes, edges, node, wire } = graph();
|
|
16661
|
+
const teal = "#7fe8d0";
|
|
16662
|
+
node("frame", "rect", 0, 0, { x: 0, y: 0, w: 380, h: 220, rx: 8, stroke: teal, strokeWidth: 1.5, fill: "#0b161c" }, "Panel");
|
|
16663
|
+
node("cross", "crosshair", 0, 1, { cx: -120, cy: 0, size: 40, gap: 8, stroke: teal, strokeWidth: 1 });
|
|
16664
|
+
node("label", "text", 0, 2, { text: "SECTOR 7 \xB7 ONLINE", x: 40, y: 0, vAnchor: "middle", anchor: "middle", fontSize: 18, letterSpacing: 2, fill: teal, stroke: "none" });
|
|
16665
|
+
node("panel", "merge", 1, 1, { group: true }, "Merge \xB7 Group");
|
|
16666
|
+
node("copies", "array-linear", 2, 1, { count: 5, stepX: 0, stepY: 0 }, "Array \xB7 in place");
|
|
16667
|
+
node("stack", "transform3d", 3, 1, {
|
|
16668
|
+
plane: "shape",
|
|
16669
|
+
order: "depth",
|
|
16670
|
+
// A wide fan: the slide must outrun the shrink, or the far panels
|
|
16671
|
+
// land behind the near one's fill.
|
|
16672
|
+
posX: { base: -380, stagger: { amount: 1100 } },
|
|
16673
|
+
posY: { base: 200, stagger: { amount: -700 } },
|
|
16674
|
+
posZ: { base: 0, stagger: { amount: 2200 } },
|
|
16675
|
+
rotY: -18
|
|
16676
|
+
}, "Transform 3D \xB7 one per shape, Stagger on Z");
|
|
16677
|
+
node("main", "layer", 4, 1, {}, "Stack");
|
|
16678
|
+
node("out", "canvas", 5, 1, { bg: "#05070a", width: 1200, height: 700, origin: "center", perspective: 900 });
|
|
16679
|
+
wire("frame", "panel", "in");
|
|
16680
|
+
wire("cross", "panel", "in");
|
|
16681
|
+
wire("label", "panel", "in");
|
|
16682
|
+
wire("panel", "copies", "in");
|
|
16683
|
+
wire("copies", "stack", "in");
|
|
16684
|
+
wire("stack", "main", "in");
|
|
16685
|
+
wire("main", "out", "in");
|
|
16686
|
+
return Project.parse({
|
|
16687
|
+
name: "depth stack",
|
|
16688
|
+
seed: "stack",
|
|
16689
|
+
canvas: { w: 1200, h: 700, bg: "#05070a", origin: "center" },
|
|
16690
|
+
timeline: { fps: 30, duration: 4, loop: false },
|
|
16691
|
+
graph: assignLayers({ nodes, edges })
|
|
16692
|
+
});
|
|
16693
|
+
}
|
|
16694
|
+
function cardCarousel() {
|
|
16695
|
+
const { nodes, edges, node, wire } = graph();
|
|
16696
|
+
const teal = "#7fe8d0";
|
|
16697
|
+
const dim = "#3a5a55";
|
|
16698
|
+
node("card", "rect", 0, 0, { x: 0, y: 0, w: 160, h: 230, rx: 10, stroke: teal, strokeWidth: 1.5, fill: "#0b161c" }, "Card");
|
|
16699
|
+
node("face", "rect", 0, 1, { x: 0, y: -40, w: 120, h: 110, rx: 4, stroke: dim, strokeWidth: 1, fill: "none" });
|
|
16700
|
+
node("bar", "rect", 0, 2, { x: 0, y: 70, w: 120, h: 10, stroke: "none", fill: teal });
|
|
16701
|
+
node("one", "merge", 1, 1, { group: true }, "Merge \xB7 Group");
|
|
16702
|
+
node("eight", "array-linear", 2, 1, { count: 8, stepX: 0, stepY: 0 }, "Array \xB7 in place");
|
|
16703
|
+
node("clock", "time", 2, 0);
|
|
16704
|
+
node("spin", "math", 3, 0, { op: "multiply", a: 0, b: 30 }, "degrees per second");
|
|
16705
|
+
node("drum", "transform3d", 3, 1, {
|
|
16706
|
+
plane: "shape",
|
|
16707
|
+
order: "depth",
|
|
16708
|
+
hideBack: true,
|
|
16709
|
+
pivot: "shape",
|
|
16710
|
+
rotY: { base: 0, expr: "v + i / n * 360" },
|
|
16711
|
+
posX: { base: 0, expr: "sin(rad(v + i / n * 360)) * 340" },
|
|
16712
|
+
posZ: { base: 0, expr: "(1 - cos(rad(v + i / n * 360))) * 340 + 200" }
|
|
16713
|
+
}, "Transform 3D \xB7 one per shape, on the rim");
|
|
16714
|
+
node("main", "layer", 4, 1, {}, "Carousel");
|
|
16715
|
+
node("out", "canvas", 5, 1, { bg: "#05070a", width: 1200, height: 700, origin: "center", perspective: 1100 });
|
|
16716
|
+
wire("card", "one", "in");
|
|
16717
|
+
wire("face", "one", "in");
|
|
16718
|
+
wire("bar", "one", "in");
|
|
16719
|
+
wire("one", "eight", "in");
|
|
16720
|
+
wire("eight", "drum", "in");
|
|
16721
|
+
wire("clock", "spin", "a");
|
|
16722
|
+
wire("spin", "drum", "rotY");
|
|
16723
|
+
wire("drum", "main", "in");
|
|
16724
|
+
wire("main", "out", "in");
|
|
16725
|
+
return Project.parse({
|
|
16726
|
+
name: "card carousel",
|
|
16727
|
+
seed: "drum",
|
|
16728
|
+
canvas: { w: 1200, h: 700, bg: "#05070a", origin: "center" },
|
|
16729
|
+
timeline: { fps: 30, duration: 12, loop: true },
|
|
16730
|
+
graph: assignLayers({ nodes, edges })
|
|
16731
|
+
});
|
|
16732
|
+
}
|
|
16733
|
+
var PRESETS = [
|
|
16734
|
+
{ name: "boot-sequence", label: "Boot Sequence", description: "The power-on: a Trigger starts three Seqs \u2014 the frame draws itself, the title scrambles in, meters wipe across.", build: bootSequence },
|
|
16735
|
+
{ name: "glitch-relay", label: "Glitch Relay", description: "The post chain: a spinning dial, its readout rasterized and torn.", build: glitchRelay },
|
|
16736
|
+
{ name: "warning-strip", label: "Warning Strip", description: "Stripes: a crawling hazard bar and streaming chevrons, Time wired into Offset.", build: warningStrip },
|
|
16737
|
+
{ name: "panel-split", label: "Panel Split", description: "Subdivide: a header-over-three-cells recipe via Select, and a seeded mosaic breathing on an LFO.", build: panelSplit },
|
|
16738
|
+
{ name: "subdivider-study", label: "Subdivider Study", description: "Four panel layouts from the reference sheet, each one Subdivide \u2192 Select \u2192 Subdivide \u2192 Merge, Split mode only.", build: subdividerStudy },
|
|
16739
|
+
{ name: "lit-panels", label: "Lit Panels", description: "Style Tokens painting a Subdivide: a seeded mosaic dealt the palette, most cells left hollow. Switch the Preset, the board redeals.", build: litPanels },
|
|
16740
|
+
{ name: "kiosk", label: "Kiosk", description: "Offset in six bands around a traced word, painted by Style Tokens in Cycle mode \u2014 the retro multi-stroke poster.", build: kiosk },
|
|
16741
|
+
{ name: "ion-dial", label: "Ion Dial", description: "Style Tokens driving every stroke \u2014 recolour the palette, the HUD and the UI follow.", build: ionDial },
|
|
16742
|
+
{ name: "radar", label: "Radar", description: "Polar grid, a Time-driven sweep, seeded contacts. Loops in 3s.", build: radar },
|
|
16743
|
+
{ name: "telemetry", label: "Telemetry", description: "Grid paper, code column, an LFO breathing through a bar meter.", build: telemetry },
|
|
16744
|
+
{ name: "speed-dial", label: "Speed Dial", description: "The Oblivion instrument: tick rings, partial arcs, a three-band gauge one Value fills, a bracketed readout.", build: speedDial },
|
|
16745
|
+
{ name: "vary-row", label: "Vary a Row", description: "Five ways to give a row of equal bars different widths: Jitter with Scale Axis X, a right-click jitter on Transform Scale X, a Stagger ramp plus noise, Deal cycling three bars \u2014 and why jittering the Rect itself gives sixty identical copies.", build: varyRow },
|
|
16746
|
+
{ name: "corners-study", label: "Corners Study", description: "Concave vs convex: a notched panel, an L profile, two stars and a comb, each finished by the Corners node.", build: cornersStudy },
|
|
16747
|
+
{ name: "depth-stack", label: "Depth Stack", description: "Panels receding into depth: one panel arrayed in place, Transform 3D with a plane per shape and a Stagger on Pos. Z \u2014 the move an array\u2019s Step cannot make \u2014 drawn nearest-on-top.", build: depthStack },
|
|
16748
|
+
{ name: "card-carousel", label: "Card Carousel", description: "Eight cards on a spinning drum: Transform 3D with a plane per shape, Pos. X and Z from sin and cos of each card\u2019s share of the circle, Time into Rot. Y, Hide Back and By depth doing the rest.", build: cardCarousel },
|
|
16749
|
+
{ name: "variable-font-study", label: "Variable Font Study", description: "Weight and width as moving quantities, on the bundled variable faces, with nodes alone: an LFO breathing a word, a Ramp materialising one, Stagger\u2019s Wave and Peak curves rolling a wave and a scan along split letters, a jitter boiling them \u2014 and Tektur squeezed along its width axis.", build: variableFontStudy }
|
|
16750
|
+
];
|
|
14768
16751
|
var ALL_NODES = [
|
|
14769
16752
|
...GENERATORS,
|
|
14770
16753
|
stripesNode,
|
|
@@ -14777,6 +16760,7 @@ var ALL_NODES = [
|
|
|
14777
16760
|
splitGlyphsNode,
|
|
14778
16761
|
textEffectNode,
|
|
14779
16762
|
...MODIFIERS,
|
|
16763
|
+
transform3dNode,
|
|
14780
16764
|
cornersNode,
|
|
14781
16765
|
bendNode,
|
|
14782
16766
|
booleanNode,
|
|
@@ -14964,8 +16948,8 @@ function styleAttrs(fill, stroke, style, n, split = true) {
|
|
|
14964
16948
|
return out;
|
|
14965
16949
|
}
|
|
14966
16950
|
function polar2(cx, cy, r, deg) {
|
|
14967
|
-
const
|
|
14968
|
-
return [cx + r * Math.cos(
|
|
16951
|
+
const rad2 = deg * Math.PI / 180;
|
|
16952
|
+
return [cx + r * Math.cos(rad2), cy + r * Math.sin(rad2)];
|
|
14969
16953
|
}
|
|
14970
16954
|
function arcPath(cx, cy, r, a0, a1, n) {
|
|
14971
16955
|
const sweep = a1 - a0;
|
|
@@ -15077,16 +17061,16 @@ function renderSvg(scene, options = {}) {
|
|
|
15077
17061
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${n(view.w)}" height="${n(view.h)}" viewBox="${n(view.x)} ${n(view.y)} ${n(view.w)} ${n(view.h)}">${nl}${defs}${bg}${body}</svg>${nl}`;
|
|
15078
17062
|
}
|
|
15079
17063
|
|
|
15080
|
-
// ../../packages/ograf/dist/chunk-
|
|
17064
|
+
// ../../packages/ograf/dist/chunk-2F3KOQL2.js
|
|
15081
17065
|
function ografNodeOf(project) {
|
|
15082
17066
|
return project.graph.nodes.find((n) => n.type === OGRAF_TYPE);
|
|
15083
17067
|
}
|
|
15084
17068
|
var COLOR_PATTERN = { rgb: "^#[0-9a-f]{6}$", rgba: "^#[0-9a-f]{8}$" };
|
|
15085
|
-
var
|
|
17069
|
+
var hex24 = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, "0");
|
|
15086
17070
|
function canonicalColor(value, alpha) {
|
|
15087
17071
|
const c = parseHex(value);
|
|
15088
17072
|
if (!c) return void 0;
|
|
15089
|
-
return `#${
|
|
17073
|
+
return `#${hex24(c[0])}${hex24(c[1])}${hex24(c[2])}${alpha ? hex24(c[3] * 255) : ""}`;
|
|
15090
17074
|
}
|
|
15091
17075
|
var clamp3 = (v, min, max) => Math.min(max ?? Infinity, Math.max(min ?? -Infinity, v));
|
|
15092
17076
|
function controlValue(row, node) {
|
|
@@ -15956,6 +17940,11 @@ function buildTemplate(input) {
|
|
|
15956
17940
|
`Post effects stay in the studio: ${postRemoved} post ${postRemoved === 1 ? "node is" : "nodes are"} left out, and the shapes they worked on come through as plain vectors. Add the look in Resolve.`
|
|
15957
17941
|
);
|
|
15958
17942
|
}
|
|
17943
|
+
for (const node of posedLayersWithPictures(project)) {
|
|
17944
|
+
warnings.push(
|
|
17945
|
+
`Layer "${node.name?.trim() || node.id}" is posed in 3D and holds a picture or a paint: a template has no rasteriser, so Resolve places those flat on the plane instead of warping them. Its shapes project as they do in the studio.`
|
|
17946
|
+
);
|
|
17947
|
+
}
|
|
15959
17948
|
const assets = {};
|
|
15960
17949
|
for (const node of project.graph.nodes) {
|
|
15961
17950
|
if (node.type !== "image") continue;
|
|
@@ -16040,6 +18029,7 @@ export {
|
|
|
16040
18029
|
adoptions,
|
|
16041
18030
|
loadProject,
|
|
16042
18031
|
serializeProject,
|
|
18032
|
+
insertComposition,
|
|
16043
18033
|
isExposable,
|
|
16044
18034
|
layerDuration,
|
|
16045
18035
|
layerLoop,
|
|
@@ -16055,10 +18045,6 @@ export {
|
|
|
16055
18045
|
monochrome,
|
|
16056
18046
|
yuvToRgb,
|
|
16057
18047
|
premultiplyRgba,
|
|
16058
|
-
parseBdf,
|
|
16059
|
-
bdfOutline,
|
|
16060
|
-
bdfGlyphs,
|
|
16061
|
-
bdfMeasure,
|
|
16062
18048
|
FONT_FACES,
|
|
16063
18049
|
DEFAULT_FONT_FAMILY,
|
|
16064
18050
|
localFontBytes,
|
|
@@ -16067,8 +18053,13 @@ export {
|
|
|
16067
18053
|
facesFor,
|
|
16068
18054
|
fileName,
|
|
16069
18055
|
fontFaceCss,
|
|
18056
|
+
parseBdf,
|
|
18057
|
+
bdfOutline,
|
|
18058
|
+
bdfGlyphs,
|
|
18059
|
+
bdfMeasure,
|
|
16070
18060
|
outlineBitmapText,
|
|
16071
18061
|
usableRows,
|
|
18062
|
+
PRESETS,
|
|
16072
18063
|
createRegistry,
|
|
16073
18064
|
renderSvg,
|
|
16074
18065
|
ografNodeOf,
|