feat(B05): 세월교 날개 투영 커브를 노선 흐름에 맞춰 긋는다

성토끝 박스 옆선이 날개 4장 전부 기준 측점의 고정 좌향을 쓰고 있었다. 세월교는
날개가 저마다 다른 자리의 노선 프레임에 붙는데 커브만 한 방향이라 흐름과 어긋난다.
박스암거는 그 고정 방향이 맞으므로 유지한다(사용자).

방향을 구조물에 실어 보낸다 — `CorridorStructure.wingAxis`(단위벡터). 세월교
날개를 만들 때 그 자리 노선 프레임의 좌향을 담고, `wingBoxLoops`가
`structure.wingAxis ?? 측점 좌향`으로 고른다. Plan 쪽에 새 배관이 없다.
`wing` 표식은 `true` → `"station" | "route"`로 바꿔 어느 규칙으로 만든 날개인지
남긴다(문자열이라 기존 truthy 검사는 그대로 통한다). `BUILD_VERSION` 35 → 36.

실측: 149.73 날개 4장 모두 route, 실어 보낸 축이 기준 측점 좌향과 −20.45°/+2.57°
어긋난다. 박스 옆선 각도가 그 값과 정확히 일치(좌 −20.45°·2.57°, 우 159.55°·
−177.43° = 같은 축 반대 방향). 200.92는 4장 모두 station, 옆선 0°·−180°로 무변화.
박스 4/4·4/4, 에이프런 4장·0장, 기준 직선 이탈 0.0000m.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 18:13:29 +09:00
co-authored by Claude Opus 5
parent 24bc9480a5
commit a8793819af
4 changed files with 23 additions and 6 deletions
+1 -1
View File
@@ -84,7 +84,7 @@ function fnv1a(text: string): string {
* (2026-08-23 사용자 보고: "원복이 안 된 것 같다가 갑자기 반영됨"). 판번호를 해시에
* 섞어 두면 배포와 동시에 저장본이 만료된다.
*/
const BUILD_VERSION = 35; // 세월교 날개벽 벌어짐 방향 정정(2026-08-26).
const BUILD_VERSION = 36; // 세월교 날개 투영 커브를 노선 흐름에 맞춤(2026-08-26).
/** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */
export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string {
@@ -209,14 +209,17 @@ export function wingBoxLoops(
): Array<Array<[number, number, number]>> {
if (toe.length < 2) return [];
const sign = side === "left" ? 1 : -1;
const dx = axis.leftX * sign;
const dy = axis.leftY * sign;
const loops: Array<Array<[number, number, number]>> = [];
for (const structure of set) {
const rings = structure.rings;
// 날개벽(revet)만 — 세월교 바닥 에이프런(basin)도 `wing` 표식을 달지만 박스는 없다.
if (!structure.wing || structure.kind !== "revet" || !rings || rings.length < 2) continue;
if (wingSide(rings, axis) !== side) continue;
// 옆선 방향 — 세월교는 그 날개가 붙은 자리의 노선 좌향(구조물이 실어 보낸다),
// BOX암거는 기준 측점의 좌향 그대로다(2026-08-26 사용자: "박스 암거는 유지").
const [ax, ay] = structure.wingAxis ?? [axis.leftX, axis.leftY];
const dx = ax * sign;
const dy = ay * sign;
const start = rings[0];
const end = rings[rings.length - 1];
const startHit = castToToe(start.cx, start.cy, dx, dy, toe);
@@ -77,8 +77,18 @@ export interface CorridorStructure {
* 날개벽 표식 — 링이 **노선이 아니라 날개 축**을 탄다(2026-08-26). 그래서 링
* 폴리곤의 편거리는 노선 편거리가 아니라 **패널 두께**다. 탑뷰 투영에서 도로부
* 클립을 적용하면 안 되고(링이 이미 노견 밖), 성토끝 박스를 하나 더 그린다.
*
* `"station"` = 기준 측점 프레임에서 직선으로 뻗은 날개(BOX암거).
* `"route"` = 측벽 끝단부 자리의 노선 프레임을 탄 날개(세월교).
*/
wing?: true;
wing?: "station" | "route";
/**
* 성토끝 박스 옆선을 그을 **바깥 방향**(단위벡터, 좌향 기준) — 세월교 전용.
* 없으면 기준 측점의 좌향을 쓴다(BOX암거 현행 — 2026-08-26 사용자 "박스 암거는
* 유지"). 세월교는 날개마다 그 자리 노선 프레임의 좌향이 들어와 커브가 노선
* 흐름을 탄다.
*/
wingAxis?: [number, number];
/** 배관 전용 — 관 하단 시·끝점(offset/표고)과 관경(m), 관벽 두께(m·중공 튜브). */
pipe?: {
start: [number, number];
@@ -143,7 +143,11 @@ export function buildWingSolids(
[-thicknessM / 2, anchor.bottomElevation],
]);
}
solids.push({ chainage_m: chainageM, kind, polygons, rings, wing: true });
// 노선 프레임을 탄 날개(세월교)는 그 프레임 좌향을 투영 축으로 실어 보낸다 —
// 성토끝 박스 옆선이 노선 흐름을 타게 된다(2026-08-26 사용자). BOX는 안 싣는다.
const wingMode = end ? "route" : "station";
const wingAxis: [number, number] | undefined = end ? [base.leftX, base.leftY] : undefined;
solids.push({ chainage_m: chainageM, kind, polygons, rings, wing: wingMode, wingAxis });
const apron = anchor.apron;
if (!apron) continue;
// 바닥판 종방향 끝의 계류측 모서리까지의 폭 — 뿌리에서 그만큼 벌어져 있다.
@@ -175,7 +179,7 @@ export function buildWingSolids(
kind: "basin",
polygons: apronPolygons,
rings: apronRings,
wing: true,
wing: wingMode,
});
}
}