// Clockwise, rounded polygon lane; normalized parameters measure distance, not polar angle. const TAU = Math.PI * 2; export function createPerimeter(kind, cx, cy, size, radius = 3) { const vertices = kind === 'square' ? [[1, -1], [1, 1], [-1, 1], [-1, -1]].map(([x, y]) => ({ x: cx + x * ((radius + .5) * size + 32), y: cy + y * ((radius + .5) * size + 32) })) : Array.from({ length: 6 }, (_, i) => { const a = -Math.PI / 6 + i * TAU / 6; return { x: cx + Math.cos(a) * size * (radius + 1.12), y: cy + Math.sin(a) * size * (radius + 1.12) }; }); const points = [], inset = 15; for (let i = 0; i < vertices.length; i++) { const p = vertices[i], before = vertices[(i + vertices.length - 1) % vertices.length], after = vertices[(i + 1) % vertices.length]; const toward = q => { const d = Math.hypot(q.x - p.x, q.y - p.y); return { x: p.x + (q.x - p.x) * inset / d, y: p.y + (q.y - p.y) * inset / d }; }; const a = toward(before), b = toward(after); for (let j = 0; j <= 16; j++) { const t = j / 16, u = 1 - t; points.push({ x: u * u * a.x + 2 * u * t * p.x + t * t * b.x, y: u * u * a.y + 2 * u * t * p.y + t * t * b.y }); } } points.push(points[0]); const lengths = [0]; for (let i = 1; i < points.length; i++) lengths.push(lengths[i - 1] + Math.hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y)); const length = lengths.at(-1), unit = length / TAU; function at(parameter) { const distance = ((parameter % TAU + TAU) % TAU) * unit; let i = lengths.findIndex((v, j) => j > 0 && v >= distance); if (i < 1) i = 1; const a = points[i - 1], b = points[i], t = (distance - lengths[i - 1]) / (lengths[i] - lengths[i - 1]); return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t, angle: Math.atan2(b.y - a.y, b.x - a.x) }; } function intersect(origin, direction) { let nearest = null; for (let i = 1; i < points.length; i++) { const a = points[i - 1], b = points[i], sx = b.x - a.x, sy = b.y - a.y; const cross = direction.x * sy - direction.y * sx; if (Math.abs(cross) < 1e-9) continue; const dx = a.x - origin.x, dy = a.y - origin.y; const ray = (dx * sy - dy * sx) / cross, t = (dx * direction.y - dy * direction.x) / cross; if (ray >= 0 && t >= 0 && t <= 1 && (!nearest || ray < nearest.distance)) nearest = { distance: ray, parameter: (lengths[i - 1] + t * (lengths[i] - lengths[i - 1])) / unit }; } if (!nearest) throw new Error('Exit ray must intersect perimeter'); return nearest; } return { points, length, unit, at, intersect }; }