/* ============================================================================= * B06_Section_UI_Cross_Box.ts * BOX암거 측점 횡단 카드의 구체(상판·저판·내공) **오버레이 그리기**. * * 기하 계산은 `B06_Section_UI_Cross_Box_Geom.ts`가 맡는다(700줄 제한 분리). * 여기서는 계산 결과(`BoxLayout`)를 SVG 도형으로 옮기기만 한다 — 치수 결정 금지. * * 그리는 순서 = 시공 순서: ① 저판 → ② 상판 → ③ 내공 윤곽 → ④ 성토선 → 라벨. * 콘크리트 색은 세월교 바닥판 클래스를 그대로 쓴다. * ========================================================================== */ import type { BoxLayout } from "./B06_Section_UI_Cross_Box_Geom"; import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; export { computeBoxLayout } from "./B06_Section_UI_Cross_Box_Geom"; export type { BoxLayout } from "./B06_Section_UI_Cross_Box_Geom"; const SVG_NS = "http://www.w3.org/2000/svg"; /** BOX암거 구체 오버레이 — computeBoxLayout 결과를 그린다. */ export function appendBoxOverlay( layer: SVGElement, layout: BoxLayout, x: (offset: number) => number, toDisplayY: (elevation: number) => number, ): void { const { box } = layout; const toXY = (point: OffsetPoint): [number, number] => [ x(point.offset), toDisplayY(point.elevation), ]; const polygon = ( points: OffsetPoint[], className: string, tooltip: string, ): SVGPolygonElement => { const shape = document.createElementNS(SVG_NS, "polygon"); shape.setAttribute("points", points.map((point) => toXY(point).join(",")).join(" ")); shape.setAttribute("class", className); const title = document.createElementNS(SVG_NS, "title"); title.textContent = tooltip; shape.append(title); return shape; }; const wingText = (wing: { installed: boolean; length_m: number | null; angle_deg: number | null; }): string => (wing.installed ? `${wing.length_m ?? 0}m·${wing.angle_deg ?? 0}°` : "없음"); // ① 저판 — 날개벽 투영만큼 계류 상·하류로 길어진다. layer.append( polygon( layout.bottomSlab, "b06-chart__ford-slab", `BOX암거 저판 — 길이 ${layout.slabLengthM.toFixed(2)}m · 두께 ${box.slab_thickness_m.toFixed(2)}m` + ` (날개벽 유입 ${wingText(box.wing_in)} / 유출 ${wingText(box.wing_out)} 투영 연장 반영)` + (layout.bedGapM > 0.05 ? ` · 계류 하상보다 ${layout.bedGapM.toFixed(2)}m 높음` : layout.bedGapM < -0.05 ? ` · 계류 하상보다 ${Math.abs(layout.bedGapM).toFixed(2)}m 낮음(터파기)` : ""), ), ); // ② 상판 — 윗면이 노면 아래 복토 두께만큼 내려온다. layer.append( polygon( layout.topSlab, "b06-chart__ford-slab", `BOX암거 상판 — 두께 ${box.top_thickness_m.toFixed(2)}m · 윗면이 노면 −${box.cover_m.toFixed(2)}m(복토)`, ), ); // ③ 내공(유로) — 면은 비우고 윤곽만 점선으로 두른다. const barrel = polygon( layout.barrel, "b06-chart__box-barrel", `BOX암거 내공(유로) ${box.inner_width_m.toFixed(1)}×${box.inner_height_m.toFixed(1)}m` + ` · 측벽 두께 ${box.wall_thickness_m.toFixed(2)}m`, ); layer.append(barrel); // ④ 성토선 — 노견에서 (연장 후) 박스 최상단 모서리를 지나 원지반까지, 물매 1:1.2. for (const line of layout.fillLines) { if (line.length < 2) continue; const polyline = document.createElementNS(SVG_NS, "polyline"); polyline.setAttribute("points", line.map((point) => toXY(point).join(",")).join(" ")); polyline.setAttribute("class", "b06-chart__design-cross"); const title = document.createElementNS(SVG_NS, "title"); const height = line[0].elevation - line[line.length - 1].elevation; title.textContent = `BOX암거 성토선 — 물매 1:1.2 · 성토고 ${height.toFixed(2)}m` + " (박스 최상단 모서리를 지난다)"; polyline.append(title); layer.append(polyline); } const label = document.createElementNS(SVG_NS, "text"); const [labelX, labelY] = toXY(layout.label.at); label.setAttribute("x", String(labelX)); label.setAttribute("y", String(labelY)); label.setAttribute("text-anchor", "middle"); label.setAttribute("class", "b06-chart__culvert-label"); label.textContent = layout.label.text; layer.append(label); }