/* ============================================================================= * B06_Section_UI_Cross_Structure_Cut.ts * 구조물 끝 → 원지반 **절토선 점열**(91-4) — 본선 절토 사면과 같은 복합경사. * * 꼭짓점은 `common_util_cross_berm.cutProfilePoints` 그대로(암 → 토사 무릎 · 소단) · 경계 = 원지반 + * `rock_boundary_offset_m` · 켬 = 암 프리셋 + `two_stage_slope` · 소단 = `design.berm` · 소단 셈은 * 구조물 끝에서 새로 · 원지반 교점에서 자름. * 쓰는 곳 = 흙막이 수평공간 뒤 · 박스 끝 · 세월교 측벽 끝 · ㄴ·ㄷ 집수정 끝 · 절토 쪽 배관 유출 벽 뒤. * ========================================================================== */ import { bermSpec, cutProfilePoints } from "../common_util/common_util_cross_berm"; import type { BermSpec } from "../common_util/common_util_cross_berm"; import type { CrossDesign } from "./B06_Section_Api_Fetch"; import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; /** 절토 제원 — 암(또는 단일) 경사 · 토사 경사 · 암 경계 오프셋(없으면 경사 하나) · 소단. */ export interface StructureCutSpec { cutRatio: number; soilRatio: number; rockOffsetM: number | null; berm: BermSpec | null; } /** 측점 설계값에서 절토 제원 — 본선 `computeCrossDesign` 과 같은 켬 조건. */ export function structureCutSpec(design?: Partial | null): StructureCutSpec { const cutRatio = Math.max(design?.cut_slope_ratio ?? 1.0, 0.1); const offset = design?.rock_boundary_offset_m; const twoStage = design?.geometry_preset === "rock" && design.two_stage_slope === true && offset != null; const berm = design?.berm; return { cutRatio, soilRatio: Math.max(design?.soil_cut_slope_ratio ?? cutRatio, 0.1), rockOffsetM: twoStage ? offset : null, berm: berm ? bermSpec(berm.width_m, berm.interval_m, berm.slope_deg) : null, }; } /** 원지반 교점 찾는 걸음(m). */ const STEP_M = 0.05; /** * `from`(원지반 아래) → 바깥(`outward`)으로 절토 꼭짓점 · 마지막 점 = 원지반 교점. * `reachM` 안에서 못 만나면 null. */ export function structureCutPoints( from: OffsetPoint, outward: number, spec: StructureCutSpec, groundAt: (offset: number) => number, reachM = 50, ): OffsetPoint[] | null { const at = (dist: number): number => from.offset + outward * dist; const rockOffsetM = spec.rockOffsetM; const boundary = rockOffsetM === null ? null : (dist: number) => groundAt(at(dist)) + rockOffsetM; const profile = cutProfilePoints( 0, from.elevation, spec.cutRatio, spec.soilRatio, boundary, spec.berm, Math.max(reachM, STEP_M), // 소단이 있으면 경계를 오갈 때마다 꺾음 — 본선 `cutPoints` 와 같음. spec.berm !== null, ); const points: OffsetPoint[] = [from]; for (let i = 1; i < profile.length; i += 1) { const [d0, z0] = profile[i - 1]; const [d1, z1] = profile[i]; const steps = Math.max(1, Math.ceil((d1 - d0) / STEP_M)); let previousGap = groundAt(at(d0)) - z0; for (let k = 1; k <= steps; k += 1) { const t = k / steps; const gap = groundAt(at(d0 + (d1 - d0) * t)) - (z0 + (z1 - z0) * t); if (gap <= 0) { // 걸음 사이 교점 보간 — 선이 지반을 넘지 않게 const share = previousGap > 1e-12 ? previousGap / (previousGap - gap) : 1; const tc = (k - 1 + share) / steps; points.push({ offset: at(d0 + (d1 - d0) * tc), elevation: z0 + (z1 - z0) * tc }); return points; } previousGap = gap; } points.push({ offset: at(d1), elevation: z1 }); } return null; } /** * 배관 유출 벽이 절토 쪽(벽 바깥 윗모서리가 원지반 아래)이면 그 모서리부터 절토선을 사면 점열 뒤에 * 이음 — 트림이 노견부터 설계선을 지워 절토사면이 사라지던 자리(91-4). 관 숨김(세월교 · 박스 흙막이 · * 독립 기슭막이)은 `enabled` 거짓. */ export function withBackCut( slope: T, wall: { role: string; outward: number; points: OffsetPoint[] }, design: Partial | null | undefined, groundAt: (offset: number) => number, enabled: boolean, ): T { const corner = wall.points[2]; if (!enabled || wall.role !== "outlet" || !corner) return slope; if (groundAt(corner.offset) <= corner.elevation + 0.05) return slope; const cut = structureCutPoints(corner, wall.outward, structureCutSpec(design), groundAt); return cut ? { ...slope, points: [...slope.points, ...cut] } : slope; }