/* ============================================================================= * B05_Profile_UI_Profile_Pivot.ts * 종단 ▲▼ 축 찾기(355-1 · 요구 235-15) — 누른 점이 꺾임(R 있는 점)이면 좌우로 같은 * 기울기 마디를 건너 다음 꺾임(없으면 시점 · 종점)을 축으로 삼는다. 사이 측점은 * 새 직선(축 → 누른 점 → 축) 위로 따라 돈다(`adjustStation`). * * 처음 계획선은 마디마다 꺾여 축 = 누른 점 ±1 → 사이 측점 없음(옛 동작 그대로). * `B05_Profile_UI_Profile_Alignment` 를 타입으로만 읽는다(순환 import 없음). * ========================================================================== */ import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment"; /** 같은 직선으로 볼 기울기 차이 — `B05_Profile_UI_Profile_Straighten` 과 같은 값. */ const COLLINEAR_EPSILON = 1e-6; const SAME_STATION_M = 1e-6; export interface PivotAxes { fromM: number; fromZ: number; toM: number; toZ: number; /** 두 축 사이(끝 · 누른 점 제외) 변화점 chainage. */ inner: number[]; } /** 누른 점이 꺾임이면 두 축과 사이 변화점 — 직선 안쪽 · 시종점 · 변화점 아님이면 null. */ export function pivotAxes(alignment: ProfileAlignment, chainageM: number): PivotAxes | null { const { pvi, segments } = alignment; const index = pvi.findIndex((node) => Math.abs(node.chainage_m - chainageM) < SAME_STATION_M); if (index <= 0 || index >= pvi.length - 1) return null; const grade = (at: number): number => { const segment = segments[at]; return segment && segment.length_m > 1e-12 ? segment.height_m / segment.length_m : 0; }; const same = (a: number, b: number): boolean => Math.abs(grade(a) - grade(b)) < COLLINEAR_EPSILON; if (same(index - 1, index)) return null; let from = index - 1; while (from > 0 && same(from - 1, index - 1)) from -= 1; let to = index + 1; while (to < pvi.length - 1 && same(to, index)) to += 1; return { fromM: pvi[from].chainage_m, fromZ: pvi[from].elevation_m, toM: pvi[to].chainage_m, toZ: pvi[to].elevation_m, inner: pvi .slice(from + 1, to) .map((node) => node.chainage_m) .filter((chainage) => Math.abs(chainage - chainageM) >= SAME_STATION_M), }; }