feat(B05): BOX암거 비탈 투영 커브를 박스 방향 단일축으로 긋는다
원본·변형 성토면 투영 커브는 행마다 그 자리 노선 프레임을 써서, 곡선 구간이면 가로 방향이 행마다 틀어져 커브가 뒤틀린다. BOX암거는 날개벽처럼 박스 방향 하나로 그어야 한다(사용자). BOX암거 세트만(`section.box`) 행 자리는 노선 위 그대로 두고 가로 방향만 기준 측점 좌향으로 고정한다. 세월교·배수관은 종전대로 그 행의 노선 프레임을 쓴다. 원본 커브의 바깥 끝은 그 행의 코리도 윤곽 점을 그대로 썼는데 가로 방향이 고정되면 어긋나므로, 고정 방향으로 쏜 선이 성토 끝선과 만나는 자리를 바깥 끝으로 삼는다(`castToToe`를 공개로 돌려 재사용). 끝은 여전히 실제 성토 끝선 위다. `BUILD_VERSION` 37 → 38. 실측(한 커브 안 행별 가로 방향 분산, ±180 감김 보정): BOX 200.92는 원본·변형 × 좌·우 4개 모두 0.00°. 배수관 84.3 11.42~50.80°, 세월교 149.73 22.84~23.02°, 배수관 275.71 2.28~8.95°로 그대로다. 행 손실 없음 — BOX 변형 6행·원본 13행. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user