Files

38 lines
2.5 KiB
JavaScript

// 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 };
}