diff --git a/B05_Profile/B05_Profile_UI_Corridor.ts b/B05_Profile/B05_Profile_UI_Corridor.ts index 42b919c1..7cd2dc5d 100644 --- a/B05_Profile/B05_Profile_UI_Corridor.ts +++ b/B05_Profile/B05_Profile_UI_Corridor.ts @@ -84,7 +84,7 @@ function fnv1a(text: string): string { * (2026-08-23 사용자 보고: "원복이 안 된 것 같다가 갑자기 반영됨"). 판번호를 해시에 * 섞어 두면 배포와 동시에 저장본이 만료된다. */ -const BUILD_VERSION = 37; // 날개벽 바깥 세로선을 구체 측면에 붙임(2026-08-26). +const BUILD_VERSION = 38; // BOX암거 비탈 커브를 박스 방향 단일축으로(2026-08-26). /** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */ export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string { diff --git a/B05_Profile/B05_Profile_UI_Corridor_Plan.ts b/B05_Profile/B05_Profile_UI_Corridor_Plan.ts index f261e517..336debfc 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Plan.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Plan.ts @@ -19,7 +19,11 @@ import type { CrossSection } from "../B06_Section/B06_Section_Api_Fetch"; import type { CorridorStructure, RouteFrame } from "./B05_Profile_UI_Corridor_Structures"; import { structureSilhouettes } from "./B05_Profile_UI_Corridor_Station_Structure"; import { assembleStripLoops } from "./B05_Profile_UI_Corridor_Carve"; -import { structureFootprintLoops, wingBoxLoops } from "./B05_Profile_UI_Corridor_Plan_Footprint"; +import { + castToToe, + structureFootprintLoops, + wingBoxLoops, +} from "./B05_Profile_UI_Corridor_Plan_Footprint"; import { mixedGround, trimSlopeToGround } from "./B05_Profile_UI_Corridor_Station"; import { groundInterpolator } from "../B06_Section/B06_Section_UI_Cross_Culvert_Solve"; import type { PXY } from "./B05_Profile_UI_Corridor_Union"; @@ -187,7 +191,19 @@ export function buildPlanCurves( leftY: section.frame.left_xy[1], designZ: null, }; - const frameOf = (chainageM: number): RouteFrame => frameAt(chainageM) ?? fallback; + const routeFrameOf = (chainageM: number): RouteFrame => frameAt(chainageM) ?? fallback; + /** + * BOX암거는 비탈 커브도 **박스 방향 하나**로 긋는다(2026-08-26 사용자: 날개벽처럼 + * 단일방향). 행 자리는 노선 위 그대로 두고 **가로 방향만** 기준 측점 좌향으로 + * 고정한다 — 곡선에서 행마다 방향이 틀어져 커브가 뒤틀리던 걸 막는다. + * 세월교·배수관은 종전대로 그 행의 노선 프레임을 쓴다. + */ + const boxAxis = section.box ? section.frame.left_xy : null; + const frameOf = (chainageM: number): RouteFrame => { + const frame = routeFrameOf(chainageM); + if (!boxAxis) return frame; + return { ...frame, leftX: boxAxis[0], leftY: boxAxis[1] }; + }; const inletSide: Side = (section.uphill_side ?? "left") === "left" ? "left" : "right"; const roleOf = (side: Side): PlanCurve["role"] => (side === inletSide ? "inlet" : "outlet"); // 노견 끝 편거리는 소유 측점 값을 구간 내내 쓴다 — 구간이 수 m라 확폭 변화는 무시. @@ -266,11 +282,26 @@ export function buildPlanCurves( push("slope-deformed", side, stripLoops(deformedRows, planZ)); // 원본 성토·절토선 — [노견 끝 → 코리도 바깥 윤곽(catch)]. 구조물이 없다고 본 면이다. const rows: Array<[PXY, PXY] | null> = []; + // BOX암거는 가로 방향이 고정이라 그 행의 윤곽 점을 그대로 쓰면 방향이 틀어진다. + // 대신 고정 방향으로 쏜 선이 성토 끝선과 만나는 자리를 바깥 끝으로 삼는다 — + // 끝은 여전히 실제 성토 끝선 위다(2026-08-26 사용자). + const boxToe = boxAxis ? nearToe(outline, side, section.chainage_m) : null; outline.chainages.forEach((chainageM, index) => { if (chainageM < from - 1e-9 || chainageM > to + 1e-9) return; - const [ox, oy] = side === "left" ? outline.left[index] : outline.right[index]; - const innerXY = at(frameOf(chainageM), roadEdge); - const outerXY = { x: ox, y: oy }; + const frame = frameOf(chainageM); + const innerXY = at(frame, roadEdge); + let outerXY: PXY; + if (boxToe) { + const hit = castToToe(frame.cx, frame.cy, frame.leftX * sign, frame.leftY * sign, boxToe); + if (!hit) { + rows.push(null); + return; + } + outerXY = { x: hit.x, y: hit.y }; + } else { + const [ox, oy] = side === "left" ? outline.left[index] : outline.right[index]; + outerXY = { x: ox, y: oy }; + } rows.push(apart(innerXY, outerXY) ? [innerXY, outerXY] : null); }); push("slope-original", side, stripLoops(rows, planZ)); diff --git a/B05_Profile/B05_Profile_UI_Corridor_Plan_Footprint.ts b/B05_Profile/B05_Profile_UI_Corridor_Plan_Footprint.ts index 3b2ecd6e..f7370089 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Plan_Footprint.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Plan_Footprint.ts @@ -143,7 +143,7 @@ export function structureFootprintLoops( } /** 광선이 성토 끝선(폴리라인)과 만난 자리 — 교점과 그 앞 마디 번호. */ -interface ToeHit { +export interface ToeHit { x: number; y: number; segment: number; @@ -163,7 +163,7 @@ const RAY_LIMIT_M = 15; * 쏘면 영영 안 만나 박스가 통째로 빠진다. 안쪽에서 만나도 "성토 끝선과 만나게"라는 * 규칙은 그대로 성립한다. */ -function castToToe( +export function castToToe( ox: number, oy: number, dx: number,