/* ============================================================================= * B05_Profile_UI_Profile_Pivot.ts * 종단 ▲▼ 축 찾기(355-1 · 368-1 · 요구 235-15 · 240-20) — 누른 점에서 좌우로 같은 * 기울기 마디를 건너 다음 꺾임(R 있는 점 · 없으면 시점 · 종점)을 축으로 삼는다. 사이 * 측점은 새 직선(축 → 누른 점 → 축) 위로 따라 돈다(`adjustStation`). 직선 안 점 · * 꼭짓점 아닌 자리(추가 측점)도 같은 규칙 — 누른 점이 새 꺾임(기본 L 곡선)이 된다. * * 처음 계획선은 마디마다 꺾여 축 = 누른 점 ±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); // 꼭짓점 아닌 자리 = 그 점을 품은 마디 하나가 좌우 마디 const right = index >= 0 ? index : pvi.findIndex((node) => node.chainage_m > chainageM); if (right <= 0 || (index >= 0 && index >= pvi.length - 1)) return null; const [leftSeg, rightSeg] = index >= 0 ? [index - 1, index] : [right - 1, right - 1]; 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; let from = leftSeg; while (from > 0 && same(from - 1, leftSeg)) from -= 1; let to = rightSeg + 1; while (to < pvi.length - 1 && same(to, rightSeg)) 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), }; }