SPB Git

spb/phi-conseil

Public
2.9 KB · 82 lines typescript
1/**2 * Géométrie de la spirale de Fibonacci (pure, sans React) — partagée par le3 * composant <GoldenSpiral /> et le script de génération des icônes.4 */5type Square = { x: number; y: number; s: number };6type Dir = "right" | "down" | "left" | "up";7const DIRS: Dir[] = ["right", "down", "left", "up"];89export function buildSpiral(levels = 8) {10  const fib = [1, 1];11  while (fib.length < levels) fib.push(fib[fib.length - 1] + fib[fib.length - 2]);1213  const squares: Square[] = [{ x: 0, y: 0, s: 1 }];14  const dirs: Dir[] = ["right"]; // direction "virtuelle" du premier carré15  let bbox = { x: 0, y: 0, w: 1, h: 1 };1617  for (let i = 1; i < fib.length; i++) {18    const s = fib[i];19    const d = DIRS[(i - 1) % 4];20    let sq: Square;21    if (d === "right") sq = { x: bbox.x + bbox.w, y: bbox.y, s };22    else if (d === "down") sq = { x: bbox.x, y: bbox.y + bbox.h, s };23    else if (d === "left") sq = { x: bbox.x - s, y: bbox.y, s };24    else sq = { x: bbox.x, y: bbox.y - s, s };25    squares.push(sq);26    dirs.push(d);27    const x0 = Math.min(bbox.x, sq.x);28    const y0 = Math.min(bbox.y, sq.y);29    const x1 = Math.max(bbox.x + bbox.w, sq.x + sq.s);30    const y1 = Math.max(bbox.y + bbox.h, sq.y + sq.s);31    bbox = { x: x0, y: y0, w: x1 - x0, h: y1 - y0 };32  }3334  // Centre de l'arc d'un carré = coin situé du côté du carré précédent35  // (opposé à sa direction) et du côté de la direction suivante.36  const opposite: Record<Dir, Dir> = {37    right: "left",38    left: "right",39    up: "down",40    down: "up",41  };42  const corner = (sq: Square, a: Dir, b: Dir) => {43    const x = a === "left" || b === "left" ? sq.x : sq.x + sq.s;44    const y = a === "up" || b === "up" ? sq.y : sq.y + sq.s;45    return { x, y };46  };4748  const centers = squares.map((sq, i) => {49    if (i === 0) return { x: sq.x, y: sq.y }; // coin haut-gauche du 1er carré50    const next = i + 1 < dirs.length ? dirs[i + 1] : DIRS[i % 4];51    return corner(sq, opposite[dirs[i]], next);52  });5354  let d = "";55  let prev: { x: number; y: number } | null = null;56  squares.forEach((sq, i) => {57    const c = centers[i];58    // Les deux extrémités possibles de l'arc : à distance s sur chaque axe.59    const dx = c.x === sq.x ? sq.x + sq.s : sq.x - 0; // extrémité horizontale60    const dy = c.y === sq.y ? sq.y + sq.s : sq.y - 0;61    const pH = { x: c.x === sq.x ? dx : sq.x, y: c.y };62    const pV = { x: c.x, y: c.y === sq.y ? dy : sq.y };63    let from = pV;64    let to = pH;65    if (prev) {66      const same = (p: { x: number; y: number }) =>67        Math.abs(p.x - prev!.x) < 1e-9 && Math.abs(p.y - prev!.y) < 1e-9;68      if (same(pH)) {69        from = pH;70        to = pV;71      }72    }73    const cross = (from.x - c.x) * (to.y - c.y) - (from.y - c.y) * (to.x - c.x);74    const sweep = cross > 0 ? 1 : 0;75    if (!prev) d += `M ${from.x} ${from.y} `;76    d += `A ${sq.s} ${sq.s} 0 0 ${sweep} ${to.x} ${to.y} `;77    prev = to;78  });7980  return { squares, path: d.trim(), bbox };81}82
Files · 986334f6f6688676835eaf88399856804855ca6d
Menu

Theme