@motionscript/layout-grid 0.0.0-stage → 0.1.0-alpha.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/CHANGELOG.md +15 -0
- package/LICENSE +201 -0
- package/dist/browser/chunks/chunk-LEUMKC3A.js +2 -0
- package/dist/browser/chunks/chunk-LEUMKC3A.js.map +7 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/kit.js +2 -0
- package/dist/browser/kit.js.map +7 -0
- package/dist/browser/manifest.json +12 -0
- package/dist/engine.d.ts +13 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +13 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/index.d.ts +7 -0
- package/dist/kit/index.d.ts.map +1 -0
- package/dist/kit/index.js +7 -0
- package/dist/kit/index.js.map +1 -0
- package/dist/kit/item-effects.d.ts +37 -0
- package/dist/kit/item-effects.d.ts.map +1 -0
- package/dist/kit/item-effects.js +76 -0
- package/dist/kit/item-effects.js.map +1 -0
- package/dist/kit/layout-grid.d.ts +243 -0
- package/dist/kit/layout-grid.d.ts.map +1 -0
- package/dist/kit/layout-grid.js +542 -0
- package/dist/kit/layout-grid.js.map +1 -0
- package/dist/kit/order.d.ts +13 -0
- package/dist/kit/order.d.ts.map +1 -0
- package/dist/kit/order.js +43 -0
- package/dist/kit/order.js.map +1 -0
- package/dist/kit/random.d.ts +7 -0
- package/dist/kit/random.d.ts.map +1 -0
- package/dist/kit/random.js +29 -0
- package/dist/kit/random.js.map +1 -0
- package/dist/kit/slots/index.d.ts +22 -0
- package/dist/kit/slots/index.d.ts.map +1 -0
- package/dist/kit/slots/index.js +28 -0
- package/dist/kit/slots/index.js.map +1 -0
- package/dist/kit/slots/path-templates.d.ts +15 -0
- package/dist/kit/slots/path-templates.d.ts.map +1 -0
- package/dist/kit/slots/path-templates.js +113 -0
- package/dist/kit/slots/path-templates.js.map +1 -0
- package/dist/kit/slots/path.d.ts +45 -0
- package/dist/kit/slots/path.d.ts.map +1 -0
- package/dist/kit/slots/path.js +87 -0
- package/dist/kit/slots/path.js.map +1 -0
- package/dist/kit/slots/radial.d.ts +30 -0
- package/dist/kit/slots/radial.d.ts.map +1 -0
- package/dist/kit/slots/radial.js +45 -0
- package/dist/kit/slots/radial.js.map +1 -0
- package/dist/kit/slots/rectangular.d.ts +42 -0
- package/dist/kit/slots/rectangular.d.ts.map +1 -0
- package/dist/kit/slots/rectangular.js +79 -0
- package/dist/kit/slots/rectangular.js.map +1 -0
- package/dist/kit/slots/slot.d.ts +62 -0
- package/dist/kit/slots/slot.d.ts.map +1 -0
- package/dist/kit/slots/slot.js +41 -0
- package/dist/kit/slots/slot.js.map +1 -0
- package/dist/kit/slots/sphere.d.ts +36 -0
- package/dist/kit/slots/sphere.d.ts.map +1 -0
- package/dist/kit/slots/sphere.js +68 -0
- package/dist/kit/slots/sphere.js.map +1 -0
- package/dist/kit/stagger.d.ts +24 -0
- package/dist/kit/stagger.d.ts.map +1 -0
- package/dist/kit/stagger.js +90 -0
- package/dist/kit/stagger.js.map +1 -0
- package/dist/nodes.d.ts +15 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +15 -0
- package/dist/nodes.js.map +1 -0
- package/dist/path-grid/index.d.ts +2 -0
- package/dist/path-grid/index.d.ts.map +1 -0
- package/dist/path-grid/index.js +2 -0
- package/dist/path-grid/index.js.map +1 -0
- package/dist/path-grid/path-grid.d.ts +82 -0
- package/dist/path-grid/path-grid.d.ts.map +1 -0
- package/dist/path-grid/path-grid.js +192 -0
- package/dist/path-grid/path-grid.js.map +1 -0
- package/dist/radial-grid/index.d.ts +2 -0
- package/dist/radial-grid/index.d.ts.map +1 -0
- package/dist/radial-grid/index.js +2 -0
- package/dist/radial-grid/index.js.map +1 -0
- package/dist/radial-grid/radial-grid.d.ts +51 -0
- package/dist/radial-grid/radial-grid.d.ts.map +1 -0
- package/dist/radial-grid/radial-grid.js +95 -0
- package/dist/radial-grid/radial-grid.js.map +1 -0
- package/dist/rectangular-grid/index.d.ts +2 -0
- package/dist/rectangular-grid/index.d.ts.map +1 -0
- package/dist/rectangular-grid/index.js +2 -0
- package/dist/rectangular-grid/index.js.map +1 -0
- package/dist/rectangular-grid/rectangular-grid.d.ts +79 -0
- package/dist/rectangular-grid/rectangular-grid.d.ts.map +1 -0
- package/dist/rectangular-grid/rectangular-grid.js +156 -0
- package/dist/rectangular-grid/rectangular-grid.js.map +1 -0
- package/dist/sphere-grid/index.d.ts +2 -0
- package/dist/sphere-grid/index.d.ts.map +1 -0
- package/dist/sphere-grid/index.js +2 -0
- package/dist/sphere-grid/index.js.map +1 -0
- package/dist/sphere-grid/sphere-grid.d.ts +68 -0
- package/dist/sphere-grid/sphere-grid.d.ts.map +1 -0
- package/dist/sphere-grid/sphere-grid.js +149 -0
- package/dist/sphere-grid/sphere-grid.js.map +1 -0
- package/package.json +70 -3
- package/registry.json +32 -0
- package/src/engine.ts +12 -0
- package/src/index.ts +6 -0
- package/src/kit/index.ts +6 -0
- package/src/kit/item-effects.ts +114 -0
- package/src/kit/layout-grid.ts +605 -0
- package/src/kit/order.ts +41 -0
- package/src/kit/random.ts +29 -0
- package/src/kit/slots/index.ts +39 -0
- package/src/kit/slots/path-templates.ts +120 -0
- package/src/kit/slots/path.ts +120 -0
- package/src/kit/slots/radial.ts +62 -0
- package/src/kit/slots/rectangular.ts +108 -0
- package/src/kit/slots/slot.ts +82 -0
- package/src/kit/slots/sphere.ts +94 -0
- package/src/kit/stagger.ts +96 -0
- package/src/nodes.ts +15 -0
- package/src/path-grid/index.ts +1 -0
- package/src/path-grid/path-grid.ts +213 -0
- package/src/radial-grid/index.ts +1 -0
- package/src/radial-grid/radial-grid.ts +90 -0
- package/src/rectangular-grid/index.ts +1 -0
- package/src/rectangular-grid/rectangular-grid.ts +160 -0
- package/src/sphere-grid/index.ts +1 -0
- package/src/sphere-grid/sphere-grid.ts +143 -0
- package/README.md +0 -4
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A grid's `order` lists items in slot order: slot `k` holds item `order[k]`.
|
|
3
|
+
* These keep it a permutation whatever it was written as — a stale entry past
|
|
4
|
+
* the count, a repeat, or nothing at all — so a child added or removed never
|
|
5
|
+
* leaves a slot empty or two items in one.
|
|
6
|
+
*/
|
|
7
|
+
/** `order` repaired into a permutation of `0..count-1`: its valid entries first, then the rest in item order. */
|
|
8
|
+
export declare function normalizeOrder(order: readonly number[] | undefined, count: number): number[];
|
|
9
|
+
/** For each item, the slot it sits in. */
|
|
10
|
+
export declare function slotOfItem(order: readonly number[]): number[];
|
|
11
|
+
/** An order written as `"2, 0, 1"` or as numbers. */
|
|
12
|
+
export declare function parseOrder(value: unknown): number[];
|
|
13
|
+
//# sourceMappingURL=order.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.d.ts","sourceRoot":"","sources":["../../src/kit/order.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iHAAiH;AACjH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAY5F;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAM7D;AAED,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAQnD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A grid's `order` lists items in slot order: slot `k` holds item `order[k]`.
|
|
3
|
+
* These keep it a permutation whatever it was written as — a stale entry past
|
|
4
|
+
* the count, a repeat, or nothing at all — so a child added or removed never
|
|
5
|
+
* leaves a slot empty or two items in one.
|
|
6
|
+
*/
|
|
7
|
+
/** `order` repaired into a permutation of `0..count-1`: its valid entries first, then the rest in item order. */
|
|
8
|
+
export function normalizeOrder(order, count) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const taken = new Array(count).fill(false);
|
|
11
|
+
for (const entry of order ?? []) {
|
|
12
|
+
const item = Math.round(entry);
|
|
13
|
+
if (item >= 0 && item < count && !taken[item]) {
|
|
14
|
+
taken[item] = true;
|
|
15
|
+
out.push(item);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (let item = 0; item < count; item++)
|
|
19
|
+
if (!taken[item])
|
|
20
|
+
out.push(item);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** For each item, the slot it sits in. */
|
|
24
|
+
export function slotOfItem(order) {
|
|
25
|
+
const slots = new Array(order.length);
|
|
26
|
+
order.forEach((item, slot) => {
|
|
27
|
+
slots[item] = slot;
|
|
28
|
+
});
|
|
29
|
+
return slots;
|
|
30
|
+
}
|
|
31
|
+
/** An order written as `"2, 0, 1"` or as numbers. */
|
|
32
|
+
export function parseOrder(value) {
|
|
33
|
+
if (Array.isArray(value))
|
|
34
|
+
return value.map(Number).filter(Number.isFinite);
|
|
35
|
+
if (typeof value !== "string")
|
|
36
|
+
return [];
|
|
37
|
+
return value
|
|
38
|
+
.split(/[\s,]+/)
|
|
39
|
+
.filter((part) => part !== "")
|
|
40
|
+
.map(Number)
|
|
41
|
+
.filter(Number.isFinite);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","sourceRoot":"","sources":["../../src/kit/order.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iHAAiH;AACjH,MAAM,UAAU,cAAc,CAAC,KAAoC,EAAE,KAAa;IAChF,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAU,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnD,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QAC9B,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;YAClB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAChB,CAAC;IACH,CAAC;IACD,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,IAAI,EAAE;QAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACzE,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,KAAwB;IACjD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAS,KAAK,CAAC,MAAM,CAAC,CAAA;IAC7C,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QAC3B,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpB,CAAC,CAAC,CAAA;IACF,OAAO,KAAK,CAAA;AACd,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;IAC1E,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAA;IACxC,OAAO,KAAK;SACT,KAAK,CAAC,QAAQ,CAAC;SACf,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,GAAG,CAAC,MAAM,CAAC;SACX,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;AAC5B,CAAC","sourcesContent":["/**\n * A grid's `order` lists items in slot order: slot `k` holds item `order[k]`.\n * These keep it a permutation whatever it was written as — a stale entry past\n * the count, a repeat, or nothing at all — so a child added or removed never\n * leaves a slot empty or two items in one.\n */\n\n/** `order` repaired into a permutation of `0..count-1`: its valid entries first, then the rest in item order. */\nexport function normalizeOrder(order: readonly number[] | undefined, count: number): number[] {\n const out: number[] = []\n const taken = new Array<boolean>(count).fill(false)\n for (const entry of order ?? []) {\n const item = Math.round(entry)\n if (item >= 0 && item < count && !taken[item]) {\n taken[item] = true\n out.push(item)\n }\n }\n for (let item = 0; item < count; item++) if (!taken[item]) out.push(item)\n return out\n}\n\n/** For each item, the slot it sits in. */\nexport function slotOfItem(order: readonly number[]): number[] {\n const slots = new Array<number>(order.length)\n order.forEach((item, slot) => {\n slots[item] = slot\n })\n return slots\n}\n\n/** An order written as `\"2, 0, 1\"` or as numbers. */\nexport function parseOrder(value: unknown): number[] {\n if (Array.isArray(value)) return value.map(Number).filter(Number.isFinite)\n if (typeof value !== \"string\") return []\n return value\n .split(/[\\s,]+/)\n .filter((part) => part !== \"\")\n .map(Number)\n .filter(Number.isFinite)\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** A seeded generator in `[0, 1)` — mulberry32, so a shuffle is the same on every frame and every machine. */
|
|
2
|
+
export declare function seededRandom(seed: number): () => number;
|
|
3
|
+
/** `values` in a seeded random order. */
|
|
4
|
+
export declare function shuffled<T>(values: readonly T[], seed: number): T[];
|
|
5
|
+
/** A stable number for a string — a node's id, so its "random" stagger never changes between builds. */
|
|
6
|
+
export declare function hashSeed(text: string): number;
|
|
7
|
+
//# sourceMappingURL=random.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"random.d.ts","sourceRoot":"","sources":["../../src/kit/random.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CASvD;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,EAAE,CAQnE;AAED,wGAAwG;AACxG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI7C"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** A seeded generator in `[0, 1)` — mulberry32, so a shuffle is the same on every frame and every machine. */
|
|
2
|
+
export function seededRandom(seed) {
|
|
3
|
+
let a = Math.floor(seed) >>> 0;
|
|
4
|
+
return () => {
|
|
5
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
6
|
+
let t = a;
|
|
7
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
8
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
9
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** `values` in a seeded random order. */
|
|
13
|
+
export function shuffled(values, seed) {
|
|
14
|
+
const out = [...values];
|
|
15
|
+
const random = seededRandom(seed);
|
|
16
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
17
|
+
const j = Math.floor(random() * (i + 1));
|
|
18
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
/** A stable number for a string — a node's id, so its "random" stagger never changes between builds. */
|
|
23
|
+
export function hashSeed(text) {
|
|
24
|
+
let h = 2166136261;
|
|
25
|
+
for (let i = 0; i < text.length; i++)
|
|
26
|
+
h = Math.imul(h ^ text.charCodeAt(i), 16777619);
|
|
27
|
+
return h >>> 0;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=random.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"random.js","sourceRoot":"","sources":["../../src/kit/random.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAC9G,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC9B,OAAO,GAAG,EAAE;QACV,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,CAAC,GAAG,CAAC,CAAA;QACT,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QACpC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAA;QACzC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAA;IAC9C,CAAC,CAAA;AACH,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,QAAQ,CAAI,MAAoB,EAAE,IAAY;IAC5D,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAA;IACvB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACjC,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CACvC;QAAA,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,IAAI,CAAC,GAAG,UAAU,CAAA;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;IACrF,OAAO,CAAC,KAAK,CAAC,CAAA;AAChB,CAAC","sourcesContent":["/** A seeded generator in `[0, 1)` — mulberry32, so a shuffle is the same on every frame and every machine. */\nexport function seededRandom(seed: number): () => number {\n let a = Math.floor(seed) >>> 0\n return () => {\n a = (a + 0x6d2b79f5) >>> 0\n let t = a\n t = Math.imul(t ^ (t >>> 15), t | 1)\n t ^= t + Math.imul(t ^ (t >>> 7), t | 61)\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296\n }\n}\n\n/** `values` in a seeded random order. */\nexport function shuffled<T>(values: readonly T[], seed: number): T[] {\n const out = [...values]\n const random = seededRandom(seed)\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1))\n ;[out[i], out[j]] = [out[j], out[i]]\n }\n return out\n}\n\n/** A stable number for a string — a node's id, so its \"random\" stagger never changes between builds. */\nexport function hashSeed(text: string): number {\n let h = 2166136261\n for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619)\n return h >>> 0\n}\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PathArrangement } from "./path.js";
|
|
2
|
+
import { type RadialArrangement } from "./radial.js";
|
|
3
|
+
import { type RectangularArrangement } from "./rectangular.js";
|
|
4
|
+
import type { Slot, SlotInput } from "./slot.js";
|
|
5
|
+
import { type SphereArrangement } from "./sphere.js";
|
|
6
|
+
export * from "./slot.js";
|
|
7
|
+
export * from "./rectangular.js";
|
|
8
|
+
export * from "./radial.js";
|
|
9
|
+
export * from "./path.js";
|
|
10
|
+
export * from "./path-templates.js";
|
|
11
|
+
export * from "./sphere.js";
|
|
12
|
+
/**
|
|
13
|
+
* Everything an arrangement needs to place items, as plain data — so a
|
|
14
|
+
* transition can keep the one it is leaving, and a grid can borrow another's.
|
|
15
|
+
*/
|
|
16
|
+
export type Arrangement = RectangularArrangement | RadialArrangement | PathArrangement | SphereArrangement;
|
|
17
|
+
export type ArrangementKind = Arrangement["kind"];
|
|
18
|
+
/** Where each of `input.count` items sits under `arrangement`, in slot order. */
|
|
19
|
+
export declare function slotsOf(arrangement: Arrangement, input: SlotInput): Slot[];
|
|
20
|
+
/** The camera distance an arrangement is seen through, or `0` when it is flat. */
|
|
21
|
+
export declare function focalLengthOf(arrangement: Arrangement): number;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAA;AACxD,OAAO,EAAe,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC9D,OAAO,EAAoB,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AAC7E,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAe,KAAK,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAE9D,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA;AAChC,cAAc,UAAU,CAAA;AAExB;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,sBAAsB,GAAG,iBAAiB,GAAG,eAAe,GAAG,iBAAiB,CAAA;AAE1G,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,CAAC,CAAA;AAEjD,iFAAiF;AACjF,wBAAgB,OAAO,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CAW1E;AAED,kFAAkF;AAClF,wBAAgB,aAAa,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { pathSlots } from "./path.js";
|
|
2
|
+
import { radialSlots } from "./radial.js";
|
|
3
|
+
import { rectangularSlots } from "./rectangular.js";
|
|
4
|
+
import { sphereSlots } from "./sphere.js";
|
|
5
|
+
export * from "./slot.js";
|
|
6
|
+
export * from "./rectangular.js";
|
|
7
|
+
export * from "./radial.js";
|
|
8
|
+
export * from "./path.js";
|
|
9
|
+
export * from "./path-templates.js";
|
|
10
|
+
export * from "./sphere.js";
|
|
11
|
+
/** Where each of `input.count` items sits under `arrangement`, in slot order. */
|
|
12
|
+
export function slotsOf(arrangement, input) {
|
|
13
|
+
switch (arrangement.kind) {
|
|
14
|
+
case "rectangular":
|
|
15
|
+
return rectangularSlots(arrangement, input);
|
|
16
|
+
case "radial":
|
|
17
|
+
return radialSlots(arrangement, input);
|
|
18
|
+
case "path":
|
|
19
|
+
return pathSlots(arrangement, input);
|
|
20
|
+
case "sphere":
|
|
21
|
+
return sphereSlots(arrangement, input);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** The camera distance an arrangement is seen through, or `0` when it is flat. */
|
|
25
|
+
export function focalLengthOf(arrangement) {
|
|
26
|
+
return arrangement.kind === "sphere" ? Math.max(1, arrangement.focalLength) : 0;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/kit/slots/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAwB,MAAM,QAAQ,CAAA;AACxD,OAAO,EAAE,WAAW,EAA0B,MAAM,UAAU,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAA+B,MAAM,eAAe,CAAA;AAE7E,OAAO,EAAE,WAAW,EAA0B,MAAM,UAAU,CAAA;AAE9D,cAAc,QAAQ,CAAA;AACtB,cAAc,eAAe,CAAA;AAC7B,cAAc,UAAU,CAAA;AACxB,cAAc,QAAQ,CAAA;AACtB,cAAc,kBAAkB,CAAA;AAChC,cAAc,UAAU,CAAA;AAUxB,iFAAiF;AACjF,MAAM,UAAU,OAAO,CAAC,WAAwB,EAAE,KAAgB;IAChE,QAAQ,WAAW,CAAC,IAAI,EAAE,CAAC;QACzB,KAAK,aAAa;YAChB,OAAO,gBAAgB,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QAC7C,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QACxC,KAAK,MAAM;YACT,OAAO,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QACtC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;IAC1C,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,aAAa,CAAC,WAAwB;IACpD,OAAO,WAAW,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACjF,CAAC","sourcesContent":["import { pathSlots, type PathArrangement } from \"./path\"\nimport { radialSlots, type RadialArrangement } from \"./radial\"\nimport { rectangularSlots, type RectangularArrangement } from \"./rectangular\"\nimport type { Slot, SlotInput } from \"./slot\"\nimport { sphereSlots, type SphereArrangement } from \"./sphere\"\n\nexport * from \"./slot\"\nexport * from \"./rectangular\"\nexport * from \"./radial\"\nexport * from \"./path\"\nexport * from \"./path-templates\"\nexport * from \"./sphere\"\n\n/**\n * Everything an arrangement needs to place items, as plain data — so a\n * transition can keep the one it is leaving, and a grid can borrow another's.\n */\nexport type Arrangement = RectangularArrangement | RadialArrangement | PathArrangement | SphereArrangement\n\nexport type ArrangementKind = Arrangement[\"kind\"]\n\n/** Where each of `input.count` items sits under `arrangement`, in slot order. */\nexport function slotsOf(arrangement: Arrangement, input: SlotInput): Slot[] {\n switch (arrangement.kind) {\n case \"rectangular\":\n return rectangularSlots(arrangement, input)\n case \"radial\":\n return radialSlots(arrangement, input)\n case \"path\":\n return pathSlots(arrangement, input)\n case \"sphere\":\n return sphereSlots(arrangement, input)\n }\n}\n\n/** The camera distance an arrangement is seen through, or `0` when it is flat. */\nexport function focalLengthOf(arrangement: Arrangement): number {\n return arrangement.kind === \"sphere\" ? Math.max(1, arrangement.focalLength) : 0\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PathCommand, Size2D } from "@motionscript/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The shapes a path grid can follow without a path of its own, each drawn to
|
|
4
|
+
* fill the grid's box. Centred and y-up, like every path in the library; the
|
|
5
|
+
* closed ones run clockwise, so an item flowing along any of them travels the
|
|
6
|
+
* same way round.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PATH_TEMPLATES: readonly ["circle", "square", "star", "heart", "infinity", "wave", "spiral", "arc", "line"];
|
|
9
|
+
export type PathTemplate = (typeof PATH_TEMPLATES)[number];
|
|
10
|
+
/**
|
|
11
|
+
* `template` drawn to fill `box`. The same template at the same size is the
|
|
12
|
+
* same array, which is what lets the path's measurement be kept between frames.
|
|
13
|
+
*/
|
|
14
|
+
export declare function templatePath(template: PathTemplate, box: Size2D): PathCommand[];
|
|
15
|
+
//# sourceMappingURL=path-templates.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-templates.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/path-templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,cAAc,6FAUjB,CAAA;AACV,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAQ1D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAQ/E"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes a path grid can follow without a path of its own, each drawn to
|
|
3
|
+
* fill the grid's box. Centred and y-up, like every path in the library; the
|
|
4
|
+
* closed ones run clockwise, so an item flowing along any of them travels the
|
|
5
|
+
* same way round.
|
|
6
|
+
*/
|
|
7
|
+
export const PATH_TEMPLATES = [
|
|
8
|
+
"circle",
|
|
9
|
+
"square",
|
|
10
|
+
"star",
|
|
11
|
+
"heart",
|
|
12
|
+
"infinity",
|
|
13
|
+
"wave",
|
|
14
|
+
"spiral",
|
|
15
|
+
"arc",
|
|
16
|
+
"line",
|
|
17
|
+
];
|
|
18
|
+
/** What a template is drawn at when the grid has no box to fill. */
|
|
19
|
+
const FALLBACK = 400;
|
|
20
|
+
const DRAWN = new Map();
|
|
21
|
+
const DRAWN_CACHE = 64;
|
|
22
|
+
/**
|
|
23
|
+
* `template` drawn to fill `box`. The same template at the same size is the
|
|
24
|
+
* same array, which is what lets the path's measurement be kept between frames.
|
|
25
|
+
*/
|
|
26
|
+
export function templatePath(template, box) {
|
|
27
|
+
const key = `${template}:${Math.round(box.width * 100)}:${Math.round(box.height * 100)}`;
|
|
28
|
+
const hit = DRAWN.get(key);
|
|
29
|
+
if (hit)
|
|
30
|
+
return hit;
|
|
31
|
+
const path = drawTemplate(template, box);
|
|
32
|
+
if (DRAWN.size >= DRAWN_CACHE)
|
|
33
|
+
DRAWN.delete(DRAWN.keys().next().value);
|
|
34
|
+
DRAWN.set(key, path);
|
|
35
|
+
return path;
|
|
36
|
+
}
|
|
37
|
+
function drawTemplate(template, box) {
|
|
38
|
+
const w = box.width > 0 ? box.width : FALLBACK;
|
|
39
|
+
const h = box.height > 0 ? box.height : FALLBACK;
|
|
40
|
+
const rx = w / 2;
|
|
41
|
+
const ry = h / 2;
|
|
42
|
+
switch (template) {
|
|
43
|
+
case "square":
|
|
44
|
+
return closed([
|
|
45
|
+
[-rx, ry], [rx, ry], [rx, -ry], [-rx, -ry],
|
|
46
|
+
]);
|
|
47
|
+
case "star": {
|
|
48
|
+
const points = [];
|
|
49
|
+
for (let i = 0; i < 10; i++) {
|
|
50
|
+
const angle = (i * Math.PI) / 5;
|
|
51
|
+
const r = i % 2 === 0 ? 1 : 0.45;
|
|
52
|
+
points.push([Math.sin(angle) * rx * r, Math.cos(angle) * ry * r]);
|
|
53
|
+
}
|
|
54
|
+
return closed(points);
|
|
55
|
+
}
|
|
56
|
+
case "heart":
|
|
57
|
+
return closed(sample(96, (t) => {
|
|
58
|
+
// The classic heart curve, from its top notch, clockwise.
|
|
59
|
+
const a = t * 2 * Math.PI;
|
|
60
|
+
const x = 16 * Math.sin(a) ** 3;
|
|
61
|
+
const y = 13 * Math.cos(a) - 5 * Math.cos(2 * a) - 2 * Math.cos(3 * a) - Math.cos(4 * a);
|
|
62
|
+
return [(x / 17) * rx, ((y + 2.5) / 15.5) * ry];
|
|
63
|
+
}).slice(0, -1));
|
|
64
|
+
case "infinity":
|
|
65
|
+
return closed(sample(96, (t) => {
|
|
66
|
+
const a = t * 2 * Math.PI;
|
|
67
|
+
const d = 1 + Math.sin(a) ** 2;
|
|
68
|
+
return [(Math.cos(a) / d) * rx, ((Math.sin(a) * Math.cos(a)) / d) * ry * 2];
|
|
69
|
+
}).slice(0, -1));
|
|
70
|
+
case "wave":
|
|
71
|
+
return open(sample(96, (t) => [(-1 + 2 * t) * rx, Math.sin(t * 4 * Math.PI) * ry * 0.6]));
|
|
72
|
+
case "spiral":
|
|
73
|
+
return open(sample(160, (t) => {
|
|
74
|
+
const a = t * 3 * 2 * Math.PI;
|
|
75
|
+
const r = 0.08 + 0.92 * t;
|
|
76
|
+
return [Math.sin(a) * rx * r, Math.cos(a) * ry * r];
|
|
77
|
+
}));
|
|
78
|
+
case "arc":
|
|
79
|
+
return open(sample(64, (t) => {
|
|
80
|
+
const a = -Math.PI / 2 + t * Math.PI;
|
|
81
|
+
return [Math.sin(a) * rx, -ry + Math.cos(a) * h];
|
|
82
|
+
}));
|
|
83
|
+
case "line":
|
|
84
|
+
return [{ type: "M", x: -rx, y: 0 }, { type: "L", x: rx, y: 0 }];
|
|
85
|
+
default:
|
|
86
|
+
return ellipse(rx, ry);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Four quarter-arcs from the top, clockwise. */
|
|
90
|
+
function ellipse(rx, ry) {
|
|
91
|
+
const k = 0.5522847498;
|
|
92
|
+
return [
|
|
93
|
+
{ type: "M", x: 0, y: ry },
|
|
94
|
+
{ type: "C", x1: k * rx, y1: ry, x2: rx, y2: k * ry, x: rx, y: 0 },
|
|
95
|
+
{ type: "C", x1: rx, y1: -k * ry, x2: k * rx, y2: -ry, x: 0, y: -ry },
|
|
96
|
+
{ type: "C", x1: -k * rx, y1: -ry, x2: -rx, y2: -k * ry, x: -rx, y: 0 },
|
|
97
|
+
{ type: "C", x1: -rx, y1: k * ry, x2: -k * rx, y2: ry, x: 0, y: ry },
|
|
98
|
+
{ type: "Z" },
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
function sample(steps, at) {
|
|
102
|
+
const points = [];
|
|
103
|
+
for (let i = 0; i <= steps; i++)
|
|
104
|
+
points.push(at(i / steps));
|
|
105
|
+
return points;
|
|
106
|
+
}
|
|
107
|
+
function open(points) {
|
|
108
|
+
return points.map(([x, y], i) => (i === 0 ? { type: "M", x, y } : { type: "L", x, y }));
|
|
109
|
+
}
|
|
110
|
+
function closed(points) {
|
|
111
|
+
return [...open(points), { type: "Z" }];
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=path-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-templates.js","sourceRoot":"","sources":["../../../src/kit/slots/path-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PathCommand, Size2D } from \"@motionscript/sdk\"\n\n/**\n * The shapes a path grid can follow without a path of its own, each drawn to\n * fill the grid's box. Centred and y-up, like every path in the library; the\n * closed ones run clockwise, so an item flowing along any of them travels the\n * same way round.\n */\nexport const PATH_TEMPLATES = [\n \"circle\",\n \"square\",\n \"star\",\n \"heart\",\n \"infinity\",\n \"wave\",\n \"spiral\",\n \"arc\",\n \"line\",\n] as const\nexport type PathTemplate = (typeof PATH_TEMPLATES)[number]\n\n/** What a template is drawn at when the grid has no box to fill. */\nconst FALLBACK = 400\n\nconst DRAWN = new Map<string, PathCommand[]>()\nconst DRAWN_CACHE = 64\n\n/**\n * `template` drawn to fill `box`. The same template at the same size is the\n * same array, which is what lets the path's measurement be kept between frames.\n */\nexport function templatePath(template: PathTemplate, box: Size2D): PathCommand[] {\n const key = `${template}:${Math.round(box.width * 100)}:${Math.round(box.height * 100)}`\n const hit = DRAWN.get(key)\n if (hit) return hit\n const path = drawTemplate(template, box)\n if (DRAWN.size >= DRAWN_CACHE) DRAWN.delete(DRAWN.keys().next().value!)\n DRAWN.set(key, path)\n return path\n}\n\nfunction drawTemplate(template: PathTemplate, box: Size2D): PathCommand[] {\n const w = box.width > 0 ? box.width : FALLBACK\n const h = box.height > 0 ? box.height : FALLBACK\n const rx = w / 2\n const ry = h / 2\n switch (template) {\n case \"square\":\n return closed([\n [-rx, ry], [rx, ry], [rx, -ry], [-rx, -ry],\n ])\n case \"star\": {\n const points: [number, number][] = []\n for (let i = 0; i < 10; i++) {\n const angle = (i * Math.PI) / 5\n const r = i % 2 === 0 ? 1 : 0.45\n points.push([Math.sin(angle) * rx * r, Math.cos(angle) * ry * r])\n }\n return closed(points)\n }\n case \"heart\":\n return closed(sample(96, (t) => {\n // The classic heart curve, from its top notch, clockwise.\n const a = t * 2 * Math.PI\n const x = 16 * Math.sin(a) ** 3\n const y = 13 * Math.cos(a) - 5 * Math.cos(2 * a) - 2 * Math.cos(3 * a) - Math.cos(4 * a)\n return [(x / 17) * rx, ((y + 2.5) / 15.5) * ry]\n }).slice(0, -1))\n case \"infinity\":\n return closed(sample(96, (t) => {\n const a = t * 2 * Math.PI\n const d = 1 + Math.sin(a) ** 2\n return [(Math.cos(a) / d) * rx, ((Math.sin(a) * Math.cos(a)) / d) * ry * 2]\n }).slice(0, -1))\n case \"wave\":\n return open(sample(96, (t) => [(-1 + 2 * t) * rx, Math.sin(t * 4 * Math.PI) * ry * 0.6]))\n case \"spiral\":\n return open(sample(160, (t) => {\n const a = t * 3 * 2 * Math.PI\n const r = 0.08 + 0.92 * t\n return [Math.sin(a) * rx * r, Math.cos(a) * ry * r]\n }))\n case \"arc\":\n return open(sample(64, (t) => {\n const a = -Math.PI / 2 + t * Math.PI\n return [Math.sin(a) * rx, -ry + Math.cos(a) * h]\n }))\n case \"line\":\n return [{ type: \"M\", x: -rx, y: 0 }, { type: \"L\", x: rx, y: 0 }]\n default:\n return ellipse(rx, ry)\n }\n}\n\n/** Four quarter-arcs from the top, clockwise. */\nfunction ellipse(rx: number, ry: number): PathCommand[] {\n const k = 0.5522847498\n return [\n { type: \"M\", x: 0, y: ry },\n { type: \"C\", x1: k * rx, y1: ry, x2: rx, y2: k * ry, x: rx, y: 0 },\n { type: \"C\", x1: rx, y1: -k * ry, x2: k * rx, y2: -ry, x: 0, y: -ry },\n { type: \"C\", x1: -k * rx, y1: -ry, x2: -rx, y2: -k * ry, x: -rx, y: 0 },\n { type: \"C\", x1: -rx, y1: k * ry, x2: -k * rx, y2: ry, x: 0, y: ry },\n { type: \"Z\" },\n ]\n}\n\nfunction sample(steps: number, at: (t: number) => [number, number]): [number, number][] {\n const points: [number, number][] = []\n for (let i = 0; i <= steps; i++) points.push(at(i / steps))\n return points\n}\n\nfunction open(points: [number, number][]): PathCommand[] {\n return points.map(([x, y], i) => (i === 0 ? { type: \"M\", x, y } : { type: \"L\", x, y }))\n}\n\nfunction closed(points: [number, number][]): PathCommand[] {\n return [...open(points), { type: \"Z\" }]\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type PathData } from "@motionscript/sdk";
|
|
2
|
+
import { type Slot, type SlotInput } from "./slot.js";
|
|
3
|
+
/** `distribute` spreads the items over the path; `fixed` sets them a distance apart. */
|
|
4
|
+
export declare const PATH_SPACINGS: readonly ["distribute", "fixed"];
|
|
5
|
+
export type PathSpacing = (typeof PATH_SPACINGS)[number];
|
|
6
|
+
/** How path coordinates land in the grid's frame: `origin` of the path goes to `(x, y)`, turned and scaled. */
|
|
7
|
+
export interface PathPlacement {
|
|
8
|
+
originX: number;
|
|
9
|
+
originY: number;
|
|
10
|
+
/** The path is y-down, as SVG and the editor write one. */
|
|
11
|
+
flipY?: boolean;
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
/** Degrees clockwise. */
|
|
15
|
+
rotation: number;
|
|
16
|
+
scale: number;
|
|
17
|
+
}
|
|
18
|
+
export interface PathArrangement {
|
|
19
|
+
kind: "path";
|
|
20
|
+
/** y-up, like every path in the library, unless its placement says otherwise. */
|
|
21
|
+
path: PathData;
|
|
22
|
+
placement: PathPlacement;
|
|
23
|
+
spacing: PathSpacing;
|
|
24
|
+
/** Pixels between items when `spacing` is `fixed`. */
|
|
25
|
+
spacingDistance: number;
|
|
26
|
+
/** Share of the path the items are moved along; wraps on a closed path. */
|
|
27
|
+
offset: number;
|
|
28
|
+
/** Share of the path per second the items travel on their own. */
|
|
29
|
+
flowSpeed: number;
|
|
30
|
+
trimStart: number;
|
|
31
|
+
trimEnd: number;
|
|
32
|
+
/** Turn each item to follow the path's direction. */
|
|
33
|
+
alignToPath: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Items at even arc-length steps — by distance along the curve rather than the
|
|
37
|
+
* path's own parameter, which bunches them up wherever a curve is tight.
|
|
38
|
+
*
|
|
39
|
+
* On a closed path used whole, `n` items take `n` equal steps and `offset`
|
|
40
|
+
* wraps round, so an animated offset is a loop. Anywhere else the ends are
|
|
41
|
+
* ends: `n` items take `n - 1` steps so both get one, and an item moved past
|
|
42
|
+
* either is hidden rather than piled on it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function pathSlots(arrangement: PathArrangement, input: SlotInput): Slot[];
|
|
45
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,QAAQ,EAAoB,MAAM,mBAAmB,CAAA;AAE5E,OAAO,EAAQ,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExD,wFAAwF;AACxF,eAAO,MAAM,aAAa,kCAAmC,CAAA;AAC7D,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAA;AAExD,+GAA+G;AAC/G,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,iFAAiF;IACjF,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,EAAE,aAAa,CAAA;IACxB,OAAO,EAAE,WAAW,CAAA;IACpB,sDAAsD;IACtD,eAAe,EAAE,MAAM,CAAA;IACvB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,WAAW,EAAE,OAAO,CAAA;CACrB;AAmBD;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,WAAW,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CAkDhF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { pathOps } from "@motionscript/sdk";
|
|
2
|
+
import { slot } from "./slot.js";
|
|
3
|
+
/** `distribute` spreads the items over the path; `fixed` sets them a distance apart. */
|
|
4
|
+
export const PATH_SPACINGS = ["distribute", "fixed"];
|
|
5
|
+
const SAMPLERS = new Map();
|
|
6
|
+
const SAMPLER_CACHE = 32;
|
|
7
|
+
/** A sampler for `path`, kept while the same value keeps arriving — a path is re-read every frame. */
|
|
8
|
+
function measured(path) {
|
|
9
|
+
const hit = SAMPLERS.get(path);
|
|
10
|
+
if (hit)
|
|
11
|
+
return hit;
|
|
12
|
+
const contours = pathOps.contours(path);
|
|
13
|
+
const entry = {
|
|
14
|
+
sampler: pathOps.sampler(path),
|
|
15
|
+
closed: contours.length === 1 && contours[0].closed,
|
|
16
|
+
};
|
|
17
|
+
if (SAMPLERS.size >= SAMPLER_CACHE)
|
|
18
|
+
SAMPLERS.delete(SAMPLERS.keys().next().value);
|
|
19
|
+
SAMPLERS.set(path, entry);
|
|
20
|
+
return entry;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Items at even arc-length steps — by distance along the curve rather than the
|
|
24
|
+
* path's own parameter, which bunches them up wherever a curve is tight.
|
|
25
|
+
*
|
|
26
|
+
* On a closed path used whole, `n` items take `n` equal steps and `offset`
|
|
27
|
+
* wraps round, so an animated offset is a loop. Anywhere else the ends are
|
|
28
|
+
* ends: `n` items take `n - 1` steps so both get one, and an item moved past
|
|
29
|
+
* either is hidden rather than piled on it.
|
|
30
|
+
*/
|
|
31
|
+
export function pathSlots(arrangement, input) {
|
|
32
|
+
const { count } = input;
|
|
33
|
+
if (count === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const { sampler, closed } = measured(arrangement.path);
|
|
36
|
+
const length = sampler.length;
|
|
37
|
+
const a = clamp01(Math.min(arrangement.trimStart, arrangement.trimEnd));
|
|
38
|
+
const b = clamp01(Math.max(arrangement.trimStart, arrangement.trimEnd));
|
|
39
|
+
const start = a * length;
|
|
40
|
+
const span = (b - a) * length;
|
|
41
|
+
const loops = closed && a === 0 && b === 1;
|
|
42
|
+
const offset = arrangement.offset + arrangement.flowSpeed * input.time;
|
|
43
|
+
const place = arrangement.placement;
|
|
44
|
+
const rad = (place.rotation * Math.PI) / 180;
|
|
45
|
+
const cos = Math.cos(rad);
|
|
46
|
+
const sin = Math.sin(rad);
|
|
47
|
+
const slots = [];
|
|
48
|
+
for (let k = 0; k < count; k++) {
|
|
49
|
+
let u;
|
|
50
|
+
if (arrangement.spacing === "fixed" && span > 0) {
|
|
51
|
+
u = offset + (k * arrangement.spacingDistance) / span;
|
|
52
|
+
}
|
|
53
|
+
else if (loops) {
|
|
54
|
+
u = offset + k / count;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
u = offset + (count === 1 ? 0.5 : k / (count - 1));
|
|
58
|
+
}
|
|
59
|
+
let visible = true;
|
|
60
|
+
if (loops)
|
|
61
|
+
u = u - Math.floor(u);
|
|
62
|
+
else if (u < -1e-9 || u > 1 + 1e-9)
|
|
63
|
+
visible = false;
|
|
64
|
+
const at = sampler.frameAt(start + clamp01(u) * span);
|
|
65
|
+
if (!at) {
|
|
66
|
+
const s = slot(0, 0, { kind: "path", t: clamp01(u) });
|
|
67
|
+
s.opacity = 0;
|
|
68
|
+
slots.push(s);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// Into the grid's frame: about the path's origin, turned clockwise (y-up).
|
|
72
|
+
const flip = place.flipY ? -1 : 1;
|
|
73
|
+
const px = (at.x - place.originX) * place.scale;
|
|
74
|
+
const py = (at.y - place.originY) * place.scale * flip;
|
|
75
|
+
const s = slot(place.x + px * cos + py * sin, place.y - px * sin + py * cos, { kind: "path", t: clamp01(u) });
|
|
76
|
+
if (arrangement.alignToPath)
|
|
77
|
+
s.rotation = (-Math.atan2(at.ty * flip, at.tx) * 180) / Math.PI + place.rotation;
|
|
78
|
+
if (!visible)
|
|
79
|
+
s.opacity = 0;
|
|
80
|
+
slots.push(s);
|
|
81
|
+
}
|
|
82
|
+
return slots;
|
|
83
|
+
}
|
|
84
|
+
function clamp01(v) {
|
|
85
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../../src/kit/slots/path.ts"],"names":[],"mappings":"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","sourcesContent":["import { pathOps, type PathData, type PathSampler } from \"@motionscript/sdk\"\n\nimport { slot, type Slot, type SlotInput } from \"./slot\"\n\n/** `distribute` spreads the items over the path; `fixed` sets them a distance apart. */\nexport const PATH_SPACINGS = [\"distribute\", \"fixed\"] as const\nexport type PathSpacing = (typeof PATH_SPACINGS)[number]\n\n/** How path coordinates land in the grid's frame: `origin` of the path goes to `(x, y)`, turned and scaled. */\nexport interface PathPlacement {\n originX: number\n originY: number\n /** The path is y-down, as SVG and the editor write one. */\n flipY?: boolean\n x: number\n y: number\n /** Degrees clockwise. */\n rotation: number\n scale: number\n}\n\nexport interface PathArrangement {\n kind: \"path\"\n /** y-up, like every path in the library, unless its placement says otherwise. */\n path: PathData\n placement: PathPlacement\n spacing: PathSpacing\n /** Pixels between items when `spacing` is `fixed`. */\n spacingDistance: number\n /** Share of the path the items are moved along; wraps on a closed path. */\n offset: number\n /** Share of the path per second the items travel on their own. */\n flowSpeed: number\n trimStart: number\n trimEnd: number\n /** Turn each item to follow the path's direction. */\n alignToPath: boolean\n}\n\nconst SAMPLERS = new Map<PathData, { sampler: PathSampler; closed: boolean }>()\nconst SAMPLER_CACHE = 32\n\n/** A sampler for `path`, kept while the same value keeps arriving — a path is re-read every frame. */\nfunction measured(path: PathData): { sampler: PathSampler; closed: boolean } {\n const hit = SAMPLERS.get(path)\n if (hit) return hit\n const contours = pathOps.contours(path)\n const entry = {\n sampler: pathOps.sampler(path),\n closed: contours.length === 1 && contours[0].closed,\n }\n if (SAMPLERS.size >= SAMPLER_CACHE) SAMPLERS.delete(SAMPLERS.keys().next().value!)\n SAMPLERS.set(path, entry)\n return entry\n}\n\n/**\n * Items at even arc-length steps — by distance along the curve rather than the\n * path's own parameter, which bunches them up wherever a curve is tight.\n *\n * On a closed path used whole, `n` items take `n` equal steps and `offset`\n * wraps round, so an animated offset is a loop. Anywhere else the ends are\n * ends: `n` items take `n - 1` steps so both get one, and an item moved past\n * either is hidden rather than piled on it.\n */\nexport function pathSlots(arrangement: PathArrangement, input: SlotInput): Slot[] {\n const { count } = input\n if (count === 0) return []\n const { sampler, closed } = measured(arrangement.path)\n const length = sampler.length\n const a = clamp01(Math.min(arrangement.trimStart, arrangement.trimEnd))\n const b = clamp01(Math.max(arrangement.trimStart, arrangement.trimEnd))\n const start = a * length\n const span = (b - a) * length\n const loops = closed && a === 0 && b === 1\n const offset = arrangement.offset + arrangement.flowSpeed * input.time\n const place = arrangement.placement\n const rad = (place.rotation * Math.PI) / 180\n const cos = Math.cos(rad)\n const sin = Math.sin(rad)\n\n const slots: Slot[] = []\n for (let k = 0; k < count; k++) {\n let u: number\n if (arrangement.spacing === \"fixed\" && span > 0) {\n u = offset + (k * arrangement.spacingDistance) / span\n } else if (loops) {\n u = offset + k / count\n } else {\n u = offset + (count === 1 ? 0.5 : k / (count - 1))\n }\n let visible = true\n if (loops) u = u - Math.floor(u)\n else if (u < -1e-9 || u > 1 + 1e-9) visible = false\n const at = sampler.frameAt(start + clamp01(u) * span)\n if (!at) {\n const s = slot(0, 0, { kind: \"path\", t: clamp01(u) })\n s.opacity = 0\n slots.push(s)\n continue\n }\n // Into the grid's frame: about the path's origin, turned clockwise (y-up).\n const flip = place.flipY ? -1 : 1\n const px = (at.x - place.originX) * place.scale\n const py = (at.y - place.originY) * place.scale * flip\n const s = slot(\n place.x + px * cos + py * sin,\n place.y - px * sin + py * cos,\n { kind: \"path\", t: clamp01(u) },\n )\n if (arrangement.alignToPath) s.rotation = (-Math.atan2(at.ty * flip, at.tx) * 180) / Math.PI + place.rotation\n if (!visible) s.opacity = 0\n slots.push(s)\n }\n return slots\n}\n\nfunction clamp01(v: number): number {\n return v < 0 ? 0 : v > 1 ? 1 : v\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type Slot, type SlotInput } from "./slot.js";
|
|
2
|
+
export declare const RADIAL_DIRECTIONS: readonly ["clockwise", "counterclockwise"];
|
|
3
|
+
export type RadialDirection = (typeof RADIAL_DIRECTIONS)[number];
|
|
4
|
+
export interface RadialArrangement {
|
|
5
|
+
kind: "radial";
|
|
6
|
+
radius: number;
|
|
7
|
+
/** Where the first item sits, degrees clockwise from twelve o'clock. */
|
|
8
|
+
startAngle: number;
|
|
9
|
+
/** How much of the circle the items span, degrees. */
|
|
10
|
+
sweep: number;
|
|
11
|
+
direction: RadialDirection;
|
|
12
|
+
/** Turn each item so its top faces away from the centre. */
|
|
13
|
+
rotateItems: boolean;
|
|
14
|
+
/** Degrees per second the ring turns on its own. */
|
|
15
|
+
spinSpeed: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The angle of each of `count` slots, clockwise from twelve o'clock. A full
|
|
19
|
+
* circle divides by the count, so the last item does not land on the first; an
|
|
20
|
+
* arc divides by one less, so both ends get an item. In between the divisor
|
|
21
|
+
* slides from one to the other as the arc closes — steeply, so an arc is an arc
|
|
22
|
+
* until it is nearly whole, and a sweep animated to 360 lands without a jump.
|
|
23
|
+
*/
|
|
24
|
+
export declare function radialAngles(arrangement: RadialArrangement, count: number, time: number): number[];
|
|
25
|
+
export declare function radialSlots(arrangement: RadialArrangement, input: SlotInput): Slot[];
|
|
26
|
+
/** The change to `startAngle` that brings slot `index` to twelve o'clock by the shorter way round. */
|
|
27
|
+
export declare function radialTurnToTop(arrangement: RadialArrangement, count: number, index: number): number;
|
|
28
|
+
/** `degrees` folded into `(-180, 180]`. */
|
|
29
|
+
export declare function wrapDegrees(degrees: number): number;
|
|
30
|
+
//# sourceMappingURL=radial.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radial.d.ts","sourceRoot":"","sources":["../../../src/kit/slots/radial.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExD,eAAO,MAAM,iBAAiB,4CAA6C,CAAA;AAC3E,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAA;IAClB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,eAAe,CAAA;IAC1B,4DAA4D;IAC5D,WAAW,EAAE,OAAO,CAAA;IACpB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CASlG;AAED,wBAAgB,WAAW,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,EAAE,CAYpF;AAED,sGAAsG;AACtG,wBAAgB,eAAe,CAAC,WAAW,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGpG;AAED,2CAA2C;AAC3C,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGnD"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { slot } from "./slot.js";
|
|
2
|
+
export const RADIAL_DIRECTIONS = ["clockwise", "counterclockwise"];
|
|
3
|
+
/**
|
|
4
|
+
* The angle of each of `count` slots, clockwise from twelve o'clock. A full
|
|
5
|
+
* circle divides by the count, so the last item does not land on the first; an
|
|
6
|
+
* arc divides by one less, so both ends get an item. In between the divisor
|
|
7
|
+
* slides from one to the other as the arc closes — steeply, so an arc is an arc
|
|
8
|
+
* until it is nearly whole, and a sweep animated to 360 lands without a jump.
|
|
9
|
+
*/
|
|
10
|
+
export function radialAngles(arrangement, count, time) {
|
|
11
|
+
const sweep = arrangement.sweep;
|
|
12
|
+
const closing = Math.min(1, Math.abs(sweep) / 360) ** 8;
|
|
13
|
+
const step = count <= 1 ? 0 : sweep / (count - 1 + closing);
|
|
14
|
+
const sign = arrangement.direction === "counterclockwise" ? -1 : 1;
|
|
15
|
+
const start = arrangement.startAngle + arrangement.spinSpeed * time;
|
|
16
|
+
const angles = [];
|
|
17
|
+
for (let k = 0; k < count; k++)
|
|
18
|
+
angles.push(start + sign * k * step);
|
|
19
|
+
return angles;
|
|
20
|
+
}
|
|
21
|
+
export function radialSlots(arrangement, input) {
|
|
22
|
+
const angles = radialAngles(arrangement, input.count, input.time);
|
|
23
|
+
const start = angles[0] ?? 0;
|
|
24
|
+
return angles.map((angle) => {
|
|
25
|
+
const rad = (angle * Math.PI) / 180;
|
|
26
|
+
const s = slot(arrangement.radius * Math.sin(rad), arrangement.radius * Math.cos(rad), {
|
|
27
|
+
kind: "radial",
|
|
28
|
+
angle: Math.abs(angle - start),
|
|
29
|
+
});
|
|
30
|
+
if (arrangement.rotateItems)
|
|
31
|
+
s.rotation = angle;
|
|
32
|
+
return s;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/** The change to `startAngle` that brings slot `index` to twelve o'clock by the shorter way round. */
|
|
36
|
+
export function radialTurnToTop(arrangement, count, index) {
|
|
37
|
+
const angle = radialAngles({ ...arrangement, spinSpeed: 0 }, count, 0)[index] ?? arrangement.startAngle;
|
|
38
|
+
return -wrapDegrees(angle);
|
|
39
|
+
}
|
|
40
|
+
/** `degrees` folded into `(-180, 180]`. */
|
|
41
|
+
export function wrapDegrees(degrees) {
|
|
42
|
+
const wrapped = ((degrees % 360) + 360) % 360;
|
|
43
|
+
return wrapped > 180 ? wrapped - 360 : wrapped;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=radial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radial.js","sourceRoot":"","sources":["../../../src/kit/slots/radial.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAA6B,MAAM,QAAQ,CAAA;AAExD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,kBAAkB,CAAU,CAAA;AAiB3E;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,WAA8B,EAAE,KAAa,EAAE,IAAY;IACtF,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAA;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAA;IACvD,MAAM,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,CAAA;IAC3D,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,KAAK,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAClE,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,GAAG,WAAW,CAAC,SAAS,GAAG,IAAI,CAAA;IACnE,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;QAAE,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IACpE,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,WAA8B,EAAE,KAAgB;IAC1E,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;IACjE,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAC5B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,MAAM,GAAG,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,GAAG,GAAG,CAAA;QACnC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YACrF,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;SAC/B,CAAC,CAAA;QACF,IAAI,WAAW,CAAC,WAAW;YAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAA;QAC/C,OAAO,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,eAAe,CAAC,WAA8B,EAAE,KAAa,EAAE,KAAa;IAC1F,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,GAAG,WAAW,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,UAAU,CAAA;IACvG,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;AAC5B,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAA;IAC7C,OAAO,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAA;AAChD,CAAC","sourcesContent":["import { slot, type Slot, type SlotInput } from \"./slot\"\n\nexport const RADIAL_DIRECTIONS = [\"clockwise\", \"counterclockwise\"] as const\nexport type RadialDirection = (typeof RADIAL_DIRECTIONS)[number]\n\nexport interface RadialArrangement {\n kind: \"radial\"\n radius: number\n /** Where the first item sits, degrees clockwise from twelve o'clock. */\n startAngle: number\n /** How much of the circle the items span, degrees. */\n sweep: number\n direction: RadialDirection\n /** Turn each item so its top faces away from the centre. */\n rotateItems: boolean\n /** Degrees per second the ring turns on its own. */\n spinSpeed: number\n}\n\n/**\n * The angle of each of `count` slots, clockwise from twelve o'clock. A full\n * circle divides by the count, so the last item does not land on the first; an\n * arc divides by one less, so both ends get an item. In between the divisor\n * slides from one to the other as the arc closes — steeply, so an arc is an arc\n * until it is nearly whole, and a sweep animated to 360 lands without a jump.\n */\nexport function radialAngles(arrangement: RadialArrangement, count: number, time: number): number[] {\n const sweep = arrangement.sweep\n const closing = Math.min(1, Math.abs(sweep) / 360) ** 8\n const step = count <= 1 ? 0 : sweep / (count - 1 + closing)\n const sign = arrangement.direction === \"counterclockwise\" ? -1 : 1\n const start = arrangement.startAngle + arrangement.spinSpeed * time\n const angles: number[] = []\n for (let k = 0; k < count; k++) angles.push(start + sign * k * step)\n return angles\n}\n\nexport function radialSlots(arrangement: RadialArrangement, input: SlotInput): Slot[] {\n const angles = radialAngles(arrangement, input.count, input.time)\n const start = angles[0] ?? 0\n return angles.map((angle) => {\n const rad = (angle * Math.PI) / 180\n const s = slot(arrangement.radius * Math.sin(rad), arrangement.radius * Math.cos(rad), {\n kind: \"radial\",\n angle: Math.abs(angle - start),\n })\n if (arrangement.rotateItems) s.rotation = angle\n return s\n })\n}\n\n/** The change to `startAngle` that brings slot `index` to twelve o'clock by the shorter way round. */\nexport function radialTurnToTop(arrangement: RadialArrangement, count: number, index: number): number {\n const angle = radialAngles({ ...arrangement, spinSpeed: 0 }, count, 0)[index] ?? arrangement.startAngle\n return -wrapDegrees(angle)\n}\n\n/** `degrees` folded into `(-180, 180]`. */\nexport function wrapDegrees(degrees: number): number {\n const wrapped = ((degrees % 360) + 360) % 360\n return wrapped > 180 ? wrapped - 360 : wrapped\n}\n"]}
|