/** Shared geometry helpers used by both collision.ts and interactions.ts */ type Vec2 = [number, number]; /** Compute 4 corners of an oriented box anchored at a point along a direction. * anchorSide='right': box extends backward from anchor (left box, right-center at anchor). * anchorSide='left': box extends forward from anchor (right box, left-center at anchor). */ export function orientedBoxCorners( anchorX: number, anchorY: number, dirX: number, dirY: number, boxW: number, boxH: number, anchorSide: 'left' | 'right' ): Vec2[] { const len = Math.sqrt(dirX * dirX + dirY * dirY); if (len === 0) return [[anchorX, anchorY]]; const ux = dirX / len, uy = dirY / len; const nx = -uy, ny = ux; const hh = boxH / 2; if (anchorSide === 'right') { return [ [anchorX - ux * boxW - nx * hh, anchorY - uy * boxW - ny * hh], [anchorX - nx * hh, anchorY - ny * hh], [anchorX + nx * hh, anchorY + ny * hh], [anchorX - ux * boxW + nx * hh, anchorY - uy * boxW + ny * hh], ]; } else { return [ [anchorX - nx * hh, anchorY - ny * hh], [anchorX + ux * boxW - nx * hh, anchorY + uy * boxW - ny * hh], [anchorX + ux * boxW + nx * hh, anchorY + uy * boxW + ny * hh], [anchorX + nx * hh, anchorY + ny * hh], ]; } } /** Check if mouse has moved past a drag threshold */ export function pastDragThreshold( posX: number, posY: number, startX: number, startY: number, threshold: number ): boolean { const dx = posX - startX; const dy = posY - startY; return dx * dx + dy * dy >= threshold * threshold; } /** Create a world-transform pair (toLocal / toWorld) for a rotated curve symbol. * Arc center is at (acx, acy), symbol center at (scx, scy). */ export function createCurveTransforms( acx: number, acy: number, scx: number, scy: number, curveRotRad: number ) { const invCos = Math.cos(-curveRotRad), invSin = Math.sin(-curveRotRad); const fwdCos = Math.cos(curveRotRad), fwdSin = Math.sin(curveRotRad); /** World → arc-local (Y up) */ function toLocal(wx: number, wy: number): Vec2 { let lx = wx, ly = wy; if (curveRotRad) { const dx = wx - scx, dy = wy - scy; lx = scx + dx * invCos - dy * invSin; ly = scy + dx * invSin + dy * invCos; } return [lx - acx, acy - ly]; } /** Unrotated-local → world */ function toWorld(localX: number, localY: number): Vec2 { if (!curveRotRad) return [localX, localY]; const dx = localX - scx, dy = localY - scy; return [scx + dx * fwdCos - dy * fwdSin, scy + dx * fwdSin + dy * fwdCos]; } return { toLocal, toWorld }; }