/* ============================================================================= * b_polygon_clip.ts * 선분 · 점열을 폴리곤 경계에서 자르는 공용 조각 — B07 횡단 오버레이(안쪽 조각)와 * B06 설계선 고름(벽 · 본체 밖 조각 · 137-2)이 같이 쓴다. 좌표계는 부르는 쪽 것 그대로. * ========================================================================== */ export type XY = [number, number]; /** 점이 폴리곤 안인가 — 오목한 벽 단면도 되도록 광선 교차 홀짝으로 본다. */ export function pointInPolygon(point: XY, polygon: XY[]): boolean { let inside = false; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i, i += 1) { const [xi, yi] = polygon[i]; const [xj, yj] = polygon[j]; const straddles = yi > point[1] !== yj > point[1]; if (straddles && point[0] < ((xj - xi) * (point[1] - yi)) / (yj - yi) + xi) inside = !inside; } return inside; } /** 선분을 폴리곤 경계에서 잘라 **안쪽 조각들**(`keep = "outside"` 면 바깥 조각들)만 돌려준다. */ export function clipSegment( start: XY, end: XY, polygon: XY[], keep: "inside" | "outside" = "inside", ): XY[][] { const [x0, y0] = start; const [x1, y1] = end; const dx = x1 - x0; const dy = y1 - y0; const cuts = [0, 1]; for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i, i += 1) { const [ex, ey] = polygon[j]; const [fx, fy] = polygon[i]; const denominator = dx * (fy - ey) - dy * (fx - ex); if (Math.abs(denominator) < 1e-12) continue; const t = ((ex - x0) * (fy - ey) - (ey - y0) * (fx - ex)) / denominator; const u = ((ex - x0) * dy - (ey - y0) * dx) / denominator; if (t > 0 && t < 1 && u >= 0 && u <= 1) cuts.push(t); } cuts.sort((a, b) => a - b); const runs: XY[][] = []; for (let index = 0; index + 1 < cuts.length; index += 1) { const from = cuts[index]; const to = cuts[index + 1]; if (to - from < 1e-9) continue; const mid = (from + to) / 2; if (pointInPolygon([x0 + dx * mid, y0 + dy * mid], polygon) !== (keep === "inside")) continue; runs.push([ [x0 + dx * from, y0 + dy * from], [x0 + dx * to, y0 + dy * to], ]); } return runs; } /** 점열을 폴리곤 안으로 자른다 — 조각마다 한 줄. */ export function clipPointsToPolygon(points: XY[], polygon: XY[]): XY[][] { const runs: XY[][] = []; for (let index = 0; index + 1 < points.length; index += 1) { runs.push(...clipSegment(points[index], points[index + 1], polygon)); } return runs; }