feat(B06): 관 접속 절·성토선 3도 물매 + 교차점 보간 + 절성토 교차 기준 자동 줌아웃

- 관 유도 접속선을 0도 → **3도**로: 유입측은 계류 쪽 오름(물이 관으로),
  유출측 매몰 절토선은 계류 쪽 내림(물이 밖으로). 집수정 I형 되메움선도 동일
- 모든 유도선·성토부선 끝점을 격자점이 아니라 **선형 보간 교차점**에 붙여
  선이 원지반을 미세하게 벗어나던 문제 해소(2026-08-22 사용자 지적)
- 카드 자동 줌아웃: **구조물 없는 상태**의 설계 절·성토선이 원지반과 만나는
  지점(+0.5m)이 표시 반폭 밖이면 그 카드만 반폭을 넓혀 그린다. 보유 샘플
  범위를 넘는 경우는 기존 전역 반폭 재생성 경로(applyPanelToAll)가 담당

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-22 15:44:10 +09:00
co-authored by Claude Opus 5
parent de78feabde
commit cd855e32d3
7 changed files with 154 additions and 38 deletions
@@ -9,6 +9,11 @@ import { computeCulvertLayout } from "./B06_Section_UI_Cross_Culvert";
import type { CulvertLayout, InletStructureChoice, RevetKey } from "./B06_Section_UI_Cross_Culvert";
import { materialLabel, materialLimit } from "./B06_Section_UI_Cross_Culvert_Const";
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
import {
designInterpolator,
groundInterpolator,
slopeToeOffset,
} from "./B06_Section_UI_Cross_Culvert_Solve";
/**
* 기슭막이 4축 조작 제어(2026-08-22 확정) — 벽을 눌러 고르고 조정창에서
* 좌우(x)·상하(d, 성토선 대각)·높이(h)·재질(m)을 만진다. 고른 벽은 여기 담아 두어
@@ -70,15 +75,7 @@ export function computeCardCulvert(
const layout = computeCulvertLayout(
section,
sourceSamples,
revetOffset
? {
inlet: revetOffset.adjustFor(section, "inlet"),
outlet: revetOffset.adjustFor(section, "outlet"),
extras: Array.from({ length: extraWalls?.countFor(section) ?? 0 }, (_, i) =>
revetOffset.adjustFor(section, `extra${i}`),
),
}
: undefined,
adjustsInput(section, revetOffset, extraWalls),
inletStructure?.valueFor(section),
equalizeExtras,
);
@@ -147,3 +144,41 @@ export function computeCardCulvert(
}
return layout;
}
function adjustsInput(
section: CrossSection,
revetOffset?: RevetOffsetControl,
extraWalls?: ExtraWallControl,
): { inlet: WallAdjust; outlet: WallAdjust; extras: WallAdjust[] } | undefined {
if (!revetOffset) return undefined;
return {
inlet: revetOffset.adjustFor(section, "inlet"),
outlet: revetOffset.adjustFor(section, "outlet"),
extras: Array.from({ length: extraWalls?.countFor(section) ?? 0 }, (_, i) =>
revetOffset.adjustFor(section, `extra${i}`),
),
};
}
/**
* **구조물 없는 상태**의 설계 절·성토선이 원지반과 만나는 지점이 들어오는 데
* 필요한 표시 반폭(m, 여유 0.5 포함) — 2026-08-22 사용자 확정 줌 기준.
* 구조물(기슭막이·집수정)이 어디로 가든 화면 기준은 지형·설계선이 정한다.
*/
export function culvertRequiredHalfWidth(section: CrossSection): number | null {
const design = section.design;
if (!design) return null;
const groundAt = groundInterpolator(section.samples);
const designAt = designInterpolator(design.design_line);
const edges = design.road_edges;
if (!groundAt || !designAt || !edges) return null;
const offsets = section.samples.map((sample) => sample.offset_m ?? 0);
const limits = { left: Math.max(...offsets), right: Math.min(...offsets) };
let extent = 0;
for (const side of ["left", "right"] as const) {
const edge = side === "left" ? edges.left : edges.right;
const toe = slopeToeOffset(designAt, groundAt, edge.offset_m, limits[side]);
extent = Math.max(extent, Math.abs(toe));
}
return extent > 0 ? extent + 0.5 : null;
}