| 1 | /** |
| 2 | * The geometry and timing behind g1t's artwork (components/art.tsx). Pure, |
| 3 | * so it is tested on its own. |
| 4 | */ |
| 5 | |
| 6 | /** Isometric projection: x runs down-right, y down-left, z up. */ |
| 7 | const COS = Math.cos(Math.PI / 6); |
| 8 | const SIN = 0.5; |
| 9 | |
| 10 | export type Point = [number, number]; |
| 11 | |
| 12 | /** A point on the floor (or at height `z`), as it is drawn on the page. */ |
| 13 | export function iso(x: number, y: number, z = 0): Point { |
| 14 | return [(x - y) * COS, (x + y) * SIN - z]; |
| 15 | } |
| 16 | |
| 17 | /** Points for a `<polygon>` or `<polyline>`, to one decimal place. */ |
| 18 | export function pts(points: Point[]): string { |
| 19 | return points.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(" "); |
| 20 | } |
| 21 | |
| 22 | /** A path's `d` through floor points at height `z`. */ |
| 23 | export function isoPath(points: Point[], z = 0): string { |
| 24 | return points |
| 25 | .map(([x, y], index) => { |
| 26 | const [px, py] = iso(x, y, z); |
| 27 | return `${index === 0 ? "M" : "L"}${px.toFixed(1)} ${py.toFixed(1)}`; |
| 28 | }) |
| 29 | .join(" "); |
| 30 | } |
| 31 | |
| 32 | /** |
| 33 | * When each of `count` things starts within one loop of `period` seconds, |
| 34 | * spread evenly from `from` and never past the loop, as CSS delays. |
| 35 | */ |
| 36 | export function stagger(count: number, period: number, from = 0): string[] { |
| 37 | if (count <= 0) return []; |
| 38 | const step = (period - from) / count; |
| 39 | return Array.from({ length: count }, (_, index) => `${round(from + index * step)}s`); |
| 40 | } |
| 41 | |
| 42 | /** The CSS custom properties an animated piece reads: its loop and its start. */ |
| 43 | export function timing(dur: number, delay: number | string = 0): Record<string, string> { |
| 44 | return { "--dur": `${round(dur)}s`, "--delay": typeof delay === "number" ? `${round(delay)}s` : delay }; |
| 45 | } |
| 46 | |
| 47 | function round(value: number): number { |
| 48 | return Math.round(value * 100) / 100; |
| 49 | } |