Files
Aislo/B05_Profile/logic/B05_Profile_UI_Profile_Pivot.ts
T
eomsangdonandClaude Opus 5.5 1ac1a7d936 feat(B05): 종단 ▲▼ 꺾임 누름 시 이웃 꺾임 축으로 사이 직선 돌림 (355-1)
- adjustStation — 누른 점이 꺾임이면 pivotAxes 로 좌우 다음 꺾임(없으면 시점 · 종점)을 축으로 사이 측점을 새 직선 위로 · 사이 R 키 지움 · 직선 안쪽 · 처음 계획선은 옛 값 그대로
- 새 logic/B05_Profile_UI_Profile_Pivot.ts — 타입만 import(순환 없음)
- 시험 test_355_1 · helper_355_1 세 경우 · test_213_1/2 tsc 산출 ESM import 에 .js 붙임

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-16 20:58:31 +09:00

52 lines
2.3 KiB
TypeScript

/* =============================================================================
* 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),
};
}