/* ============================================================================= * B06_Section_UI_Cross_Culvert_Fit.ts * 관 길이 **m 단위 맞춤** — 배수관 기하(`_Cross_Culvert_Geom.ts`)에서 700줄 제한으로 분리. * * · 보통(2026-08-21 사용자 ①) — 긴 변 기준 올림, 모자란 만큼 유출 벽 자리를 관 축 방향 바깥으로 * 민다(벽 두께 0.45 · 높이 고정 · 벽 상단은 1:1.2 성토선을 탐). * · 노견을 넓힌 쪽(139-6 설계선 쉬프트) — 사면 우선: 벽을 관 축을 따라 옮기되 벽 상단이 그 쪽 * 성토선(넓힌 노견에서 같은 물매)에 닿도록 **벽 높이를 0.1 m 눈금으로 역산**. 눈금 탓에 남는 * 차는 표기 올림. * ========================================================================== */ import type { OffsetPoint } from "./B06_Section_UI_Cross_Culvert_Types"; import { FILL_SLOPE_MAX_LENGTH_M, FILL_SLOPE_RATIO_MAX, FILL_SLOPE_RATIO_MIN, REVET_EMBED_DEPTH_M, REVET_HEIGHT_STEP_M, WALL_MIN_PURE_HEIGHT_M, } from "./B06_Section_UI_Cross_Culvert_Const"; export interface OutletFitInput { /** 유출 쪽 노견을 넓혔나(139-6) — 높이 역산 갈래. */ shifted: boolean; edge: { offset_m: number; elevation_m: number }; outward: number; axis: () => OffsetPoint; thickness: number; anchor: OffsetPoint; /** 계산용 높이(순수 − 근입). */ height: number; /** 보통 갈래의 invert 선(높이 고정 · 성토선 평행). */ lineInvertAt: (offset: number) => number; slopeAt: ( offset: number, thickness: number, height: number, ) => { ratio: number; lengthM: number }; cutLength: () => number; /** 벽을 새 자리 · 높이로 다시 세우고 관 축 길이 `target` 으로 겨눔 — 벽이 안 서면 false. */ rebuild: (anchor: OffsetPoint, height: number, target: number) => boolean; } /** 맞춤 뒤 벽 자리 · 높이. */ export function fitOutletPipeLength(c: OutletFitInput): { anchor: OffsetPoint; height: number } { const target = Math.ceil(c.cutLength() - 1e-6); let anchor = c.anchor; let height = c.height; const ratio0 = c.slopeAt(anchor.offset, c.thickness, height).ratio; for (let pass = 0; pass < 8; pass += 1) { const gap = target - c.cutLength(); if (Math.abs(gap) < 0.005) break; const axis = c.axis(); const moved = anchor.offset + axis.offset * gap; // 노견 안쪽으로는 옮기지 않는다 — 길이 맞춤보다 도면 성립이 먼저. if ((moved - c.edge.offset_m) * c.outward < 0) break; if (c.shifted) { // 사면 우선 — invert 는 관 축을 따라 · 벽 상단 = 같은 물매 성토선 → 높이 역산(0.1 m 눈금) if (!Number.isFinite(ratio0)) break; const invert = anchor.elevation + axis.elevation * gap; const run = Math.abs(moved + c.outward * (c.thickness / 2) - c.edge.offset_m); const pure = c.edge.elevation_m - run / ratio0 - invert + REVET_EMBED_DEPTH_M; const stepped = Math.round(pure / REVET_HEIGHT_STEP_M) * REVET_HEIGHT_STEP_M; if (stepped < WALL_MIN_PURE_HEIGHT_M - 1e-9) break; const next = stepped - REVET_EMBED_DEPTH_M; if (Math.abs(next - height) < 1e-9 && Math.abs(moved - anchor.offset) < 0.01) break; anchor = { offset: moved, elevation: invert }; height = next; if (!c.rebuild(anchor, height, target)) break; continue; } const movedSlope = c.slopeAt(moved, c.thickness, height); // 사면 역전(벽 상단이 노견 위)으로는 옮기지 않는다 · 성토고 0(성토선 0점)까지는 허용(2026-08-23). if (c.edge.elevation_m - (c.lineInvertAt(moved) + height) < 0) break; // 자동 자리만 옮긴다(수동 벽은 이 루프에 아예 안 들어온다). 폭은 0.45 고정 — // 폭으로 흡수하면 미는 동안 벽 단면이 변한다(2026-08-21 사용자 지적). if ( movedSlope.ratio >= FILL_SLOPE_RATIO_MIN - 1e-6 && movedSlope.ratio <= FILL_SLOPE_RATIO_MAX && movedSlope.lengthM <= FILL_SLOPE_MAX_LENGTH_M + 1e-6 ) { anchor = { offset: moved, elevation: c.lineInvertAt(moved) }; if (!c.rebuild(anchor, height, target)) break; continue; } // 물매·사면 5m를 벗어나면 멈춘다. 폭으로 흡수하지 않는다(좌·우 단면이 달라진다). break; } return { anchor, height }; }