/* ============================================================================= * B05_Profile_Footprint_Strokes.ts * 평면 발자국(가게 키 `footprint` · 계약 `PlanFootprint`) → 그릴 선 갈래(PLAN 346-2). * * 도로 끝 좌 · 우 = `road` · 비탈 끝 = 점마다 깎기 여부(`slopeLeftCut` · `slopeRightCut` — * footprint 단계가 붙임)로 `cut` · `fill` 조각 · 깎기 여부가 없으면 `slope` 한 줄. * 조각이 바뀌는 점은 앞뒤 조각이 함께 가짐(선이 끊겨 보이지 않게 · `splitRuns` 와 같은 규칙). * ========================================================================== */ import type { PlanFootprint, Vertex } from "../../A00_Common/design/logic/design_pipeline_types"; export type FootprintStrokeKind = "road" | "cut" | "fill" | "slope"; export interface FootprintStroke { kind: FootprintStrokeKind; points: Vertex[]; } /** footprint 단계 출력 — 계약 칸 + 비탈 끝 점마다 깎기 여부(없을 수 있음). */ export type FootprintValue = PlanFootprint & { slopeLeftCut?: ReadonlyArray; slopeRightCut?: ReadonlyArray; }; function slopeRuns(points: Vertex[], cut: ReadonlyArray | undefined): FootprintStroke[] { if (points.length < 2) return []; if (!cut || cut.length !== points.length) return [{ kind: "slope", points }]; const runs: FootprintStroke[] = []; let current: FootprintStroke | null = null; points.forEach((point, index) => { const kind = cut[index] ? "cut" : "fill"; if (current?.kind !== kind) { current?.points.push(point); current = { kind, points: [point] }; runs.push(current); } else { current.points.push(point); } }); return runs.filter((run) => run.points.length >= 2); } /** 그릴 선 — 도로 끝 둘 · 비탈 끝 조각들(2점 미만은 뺌). */ export function footprintStrokes(footprint: FootprintValue | null): FootprintStroke[] { if (!footprint) return []; const road = [footprint.roadLeft, footprint.roadRight] .filter((points) => points?.length >= 2) .map((points): FootprintStroke => ({ kind: "road", points })); return [ ...road, ...slopeRuns(footprint.slopeLeft ?? [], footprint.slopeLeftCut), ...slopeRuns(footprint.slopeRight ?? [], footprint.slopeRightCut), ]; }