Files
Aislo/B06_Section/B06_Section_UI_Cross_Structure_Cut.ts
T

108 lines
4.6 KiB
TypeScript

/* =============================================================================
* 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<CrossDesign> | 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<T extends { points: OffsetPoint[] }>(
slope: T,
wall: { role: string; outward: number; points: OffsetPoint[] },
design: Partial<CrossDesign> | 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;
}