// Shared geometry used by drawing AND swept collision detection. No host dependencies. export const PORTS = [0,1,2,3,4,5].map(d => ({x:Math.cos(-Math.PI/2+d*Math.PI/3)*Math.sqrt(3)/2,y:Math.sin(-Math.PI/2+d*Math.PI/3)*Math.sqrt(3)/2})); export function position(q,r,size=1){return{x:1.5*q*size,y:Math.sqrt(3)*(r+q/2)*size};} export function hexPoints(x,y,r){return Array.from({length:6},(_,i)=>({x:x+Math.cos(i*Math.PI/3)*r,y:y+Math.sin(i*Math.PI/3)*r}));} export function trackPoints(x,y,r,a,b){ const p=PORTS[a],q=PORTS[b],points=[]; for(let i=0;i<=20;i++){const t=i/20,u=1-t;points.push({x:x+r*(u*u*u*p.x+3*u*u*t*p.x*.28+3*u*t*t*q.x*.28+t*t*t*q.x),y:y+r*(u*u*u*p.y+3*u*u*t*p.y*.28+3*u*t*t*q.y*.28+t*t*t*q.y)});} return points; } export function pathPoints(route){ const points=[]; for(const s of route.steps){const c=position(s.q,s.r),p=PORTS[s.exit];if(s.enter===null)points.push(c,{x:c.x+p.x,y:c.y+p.y});else points.push(...trackPoints(c.x,c.y,1,s.enter,s.exit));} if(route.end)points.push(position(route.end.q,route.end.r)); return points; } // Collider is the solid top face, not the decorative drop shadow or extruded side. export const COLLIDER_RADIUS = 0.88; const NORMALS=[{x:0,y:1},{x:Math.sqrt(3)/2,y:.5},{x:Math.sqrt(3)/2,y:-.5}]; // Exact swept intersection for each rendered line segment against a Minkowski hexagon. // This avoids tunneling between animation frames, including grazing a neighboring tile. export function segmentHexHit(a,b,center,radius=COLLIDER_RADIUS*2){ const limit=radius*Math.sqrt(3)/2; let enter=0,exit=1; for(const n of NORMALS){ const value=(a.x-center.x)*n.x+(a.y-center.y)*n.y,delta=(b.x-a.x)*n.x+(b.y-a.y)*n.y; if(Math.abs(delta)<1e-10){if(Math.abs(value)>limit)return null;continue;} let lo=(-limit-value)/delta,hi=(limit-value)/delta;if(lo>hi)[lo,hi]=[hi,lo]; enter=Math.max(enter,lo);exit=Math.min(exit,hi);if(enter>exit)return null; } return enter<=1&&exit>=0?Math.max(0,enter):null; } export function sweepPath(points,obstacles){ for(let i=1;iMath.max(a.x,b.x)+1.8||o.yMath.max(a.y,b.y)+1.8)continue; const t=segmentHexHit(a,b,o); if(t!==null&&(!first||t