3D 구조물과 성토면의 관계를 "잘라내기"에서 "투영 표기"로 바꾸고, 세월교·BOX암거 조정창을 기슭막이 템플릿으로 통일했다. B05 코리도 - 구조물 탑뷰 투영 산출(_Corridor_Carve.ts 신규): 구조물 솔리드 링 사이 실좌표 사각형 + 변형 성토면 영역. 노선 파라미터가 아니라 행 횡단선과 실좌표로 교차시켜 곡선 구간·날개벽 벌어짐이 그대로 잡힌다. - 사각형 합집합 경계(_Corridor_Union.ts 신규): 변을 교점에서 토막 내고 내부·공유 토막을 버려 최외곽 윤곽만 남긴다(측정 133 -> 13 루프). - 표기 모드(CARVE_APPLY=false): 서피스를 자르지 않고 ① 성토면 위 절단 투영선 ② 원지반 최고 표고 위 Z평면에 구조물 윤곽(노랑)과 유입·유출 성토부 윤곽 (하늘·주황)을 따로 그린다. 구조물 커브는 성토면 없다고 보고, 성토부 커브는 구조물 없다고 보고 만든다. - 구조물 실루엣 공용화(_Corridor_Station_Structure.ts 신규): 기슭막이·BOX암거· 세월교 실루엣을 한 자료형으로. 성토면 강제 변형은 스위치로 끄고 (STRUCTURE_SILHOUETTE_OVERRIDE), 노견 확장만 상시 적용(shoulderSpans). - 평면 영역 분리(_Corridor_Region.ts), 절단기 일반화(clipGeometry가 영역 배열· 전체 정점 속성 보간 — 리본 UV 유지). 지형은 코리도 스트립만 도려낸다. - BOX암거 3D를 측점 프레임 직선 압출로(노선 추종 금지), 날개벽 계산 공용화 (_Corridor_Structures_Wing.ts 신규) 후 세월교에도 날개벽 4매 추가. - 세월교 보어를 관 련수만큼 뚫고, 보어 밴드를 단면 표고로 클램프해 구멍 찌그러짐 제거. 날개벽 뿌리를 벽 외측면 모서리로 교정. - 직교 카메라(_Viewer_Camera.ts 신규) — 원근 왜곡 없이 탑뷰 판독. 커서 피벗 줌이 직교에서 camera.zoom을 함께 조인다. B06 횡단 - 조정창 공용 부품(_Cross_Panel_Base.ts 신규): 머리줄·2행 항목·D-pad·키보드 방향키. 세월교·BOX 조정창을 같은 템플릿으로 옮기고 십자 세트를 창 맨 아래로. - 세월교 조정창에 날개벽 항목 추가(설치·짧은쪽 높이·길이·각도) — 정본 pipe_points 되쓰기, 바닥판 연장은 길이×cos각으로 즉시 재계산. - 세월교 좌우 이동을 집수정 규칙으로: 노견 수평 확장 + 상하 이동분만 성토선. BUILD_VERSION 19 -> 31(저장 코리도 캐시 무효화). typecheck·prettier 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
3.0 KiB
TypeScript
70 lines
3.0 KiB
TypeScript
/* =============================================================================
|
|
* B06_Section_UI_Cross_View_Ford.ts
|
|
* 횡단 카드 ↔ **세월교 조정창** 배선 — 카드 렌더러(`_UI_Cross_View.ts`)에서 700줄
|
|
* 제한으로 분리했다(2026-08-25). 조정창이 부르는 동작을 제어기로 넘기는 얇은 층이고,
|
|
* 기하나 그리기는 하지 않는다.
|
|
* ========================================================================== */
|
|
|
|
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
|
import type { FordPanelDeps, FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
|
|
import type { FordLayout, FordWallRole } from "./B06_Section_UI_Cross_Ford";
|
|
|
|
/** 조정창 배선에 필요한 카드 상태. 계산 결과는 그릴 때마다 바뀌므로 함수로 받는다. */
|
|
export interface FordPanelContext {
|
|
section: CrossSection;
|
|
ford: FordControl;
|
|
/** 마지막 계산의 측벽 높이(m) — 높이 조작의 시작값. */
|
|
heightFor: (role: FordWallRole) => number;
|
|
/** 마지막 계산의 바닥판 길이(m). */
|
|
slabLengthM: () => number;
|
|
close: () => void;
|
|
}
|
|
|
|
/** 화면 좌(◀) 방향을 벽 기준 바깥 부호로 환산한다. 유입 = 상단측. */
|
|
function outwardOf(section: CrossSection, role: FordWallRole): number {
|
|
return ((section.uphill_side ?? "left") === "left") === (role === "inlet") ? 1 : -1;
|
|
}
|
|
|
|
export function fordPanelDeps(context: FordPanelContext): FordPanelDeps {
|
|
const { section, ford } = context;
|
|
const chainage = section.chainage_m;
|
|
return {
|
|
adjustFor: (role) => ford.adjustFor(chainage)[role],
|
|
heightFor: context.heightFor,
|
|
nudgeHeight: (role, deltaM) =>
|
|
ford.update(chainage, role, { heightM: context.heightFor(role) + deltaM }),
|
|
nudge: (role, screenDeltaM) =>
|
|
ford.update(chainage, role, {
|
|
lateralM: ford.adjustFor(chainage)[role].lateralM + screenDeltaM * outwardOf(section, role),
|
|
}),
|
|
nudgeSlope: (role, deltaM) =>
|
|
ford.update(chainage, role, {
|
|
slopeM: ford.adjustFor(chainage)[role].slopeM + deltaM,
|
|
}),
|
|
reset: (role) => ford.reset(chainage, role),
|
|
pipeDiameterMm: () => Math.round((section.ford?.diameter_m ?? 1) * 1000),
|
|
setPipeDiameterMm: (value) => ford.setPipe(chainage, { pipe_diameter_mm: value }),
|
|
pipeCount: () => section.ford?.pipe_count ?? 1,
|
|
setPipeCount: (value) => ford.setPipe(chainage, { pipe_count: value }),
|
|
wingFor: (role) => {
|
|
const spec = section.ford;
|
|
if (!spec) return null;
|
|
return role === "inlet" ? spec.wing_in : spec.wing_out;
|
|
},
|
|
setWing: (role, patch) => ford.setWing(chainage, role, patch),
|
|
slabLengthM: context.slabLengthM,
|
|
close: context.close,
|
|
};
|
|
}
|
|
|
|
/** 조정창이 쓸 세월교 카드 상태 — 마지막 기하 결과에서 뽑는다(카드 렌더러 700줄 분리). */
|
|
export function fordCardState(layout: FordLayout): {
|
|
slabLengthM: number;
|
|
heights: Map<FordWallRole, number>;
|
|
} {
|
|
return {
|
|
slabLengthM: layout.slabLengthM,
|
|
heights: new Map(layout.sides.map((side) => [side.role, side.heightM])),
|
|
};
|
|
}
|