Files
Aislo/B05_Profile/logic/B05_Profile_RouteEdit_StructureMarks.ts
T
eomsangdonandClaude Opus 5.5 c3efa6dd7e feat(B05 평면 구조물 마커): 구조물 측점 마커 · 고름 정보 창 · 20 m 법선 · 끌기 · 방향키 1 m (399)
- 마커 = 종단 알약과 같은 목록(laneStructures) · 노선 위 알약 꼴(종류 색) · 스냅 · 간격과 무관
- 고름 = 알약 고름과 같은 길(onStructureSelect · 공용 고름) · 알약 벌룬 그대로 · 좌우 20 m 법선
- 끌기 · 방향키 = 노선 따라 정수 m 1 m 씩 · 시점 · 종점 밖 안 감 · 놓을 때 알약 끌기와 같은 길로 한 번
- 집기 차례 = 교각점 · 곡선 손잡이 다음 · 옛 마름모 지움
- 시험 test_399_plan_structure_marks · test_391 마름모 줄 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
2026-10-17 21:43:51 +09:00

48 lines
2.6 KiB
TypeScript

/* =============================================================================
* B05_Profile/logic/B05_Profile_RouteEdit_StructureMarks.ts
* 평면 구조물 측점 마커(399 · 요구 250-2) — 노선 따라 정수 m 자리 · 방향키 부호 · 고른 마커.
*
* · 끌기 · 방향키 = 노선 위 정수 m(시점 0 ~ 종점 내림 · 밖은 끝에 붙음) · 방향키 한 번 1 m(Shift 없음).
* · 방향키 부호 = 누른 화면 방향이 노선 앞쪽과 같은 쪽이면 + · 거의 직각이면 → · ↑ = +.
* · 고른 마커 = 공용 고름 구조물 id 와 같은 것 · id 없으면(관 고름) 그 누가거리 가까운 것.
* 파이썬 짝 아님. 순수 계산 — 화면 · fetch · sessionStorage 없음. 부르는 쪽은 `_UI_RouteEdit_StructureMarks`.
* ========================================================================== */
import type { ArrowKey } from "./B05_Profile_RouteEdit_KeyNudge";
import type { Vertex } from "./B05_Profile_UI_RouteEdit_Curve";
/** 노선 위 정수 m 자리 — 0 ~ 종점(내림) 밖은 끝에 붙임. */
export function markChainage(rawM: number, totalM: number): number {
return Math.min(Math.max(Math.round(rawM), 0), Math.max(0, Math.floor(totalM)));
}
/** 방향키 한 번 — 지금 자리(정수로) ± 1 m. */
export const markStep = (chainageM: number, sign: 1 | -1, totalM: number): number =>
markChainage(Math.round(chainageM) + sign, totalM);
/** 방향키 부호 — `keyDir`(누른 화면 방향의 도면 벡터) · `tangent`(노선 앞쪽) 같은 쪽이면 +1. */
export function arrowSign(key: ArrowKey, keyDir: Vertex, tangent: Vertex): 1 | -1 {
const dot = keyDir[0] * tangent[0] + keyDir[1] * tangent[1];
const size = Math.hypot(keyDir[0], keyDir[1]) * Math.hypot(tangent[0], tangent[1]);
if (Math.abs(dot) > size * 0.2) return dot > 0 ? 1 : -1;
return key === "ArrowRight" || key === "ArrowUp" ? 1 : -1;
}
/** 고른 마커 id — 고름 구조물 id 와 같은 것 · id 없으면 그 누가거리 `tolM` 안 가장 가까운 것 · 없으면 null. */
export function pickedMarkId(
marks: ReadonlyArray<{ id: string; chainageM: number }>,
selection: { chainageM: number | null; structureId: string | null } | null,
tolM: number,
): string | null {
if (!selection || selection.chainageM === null) return null;
if (selection.structureId)
return marks.some((mark) => mark.id === selection.structureId) ? selection.structureId : null;
let best: string | null = null;
let bestGap = tolM;
for (const mark of marks) {
const gap = Math.abs(mark.chainageM - selection.chainageM);
if (gap < bestGap) [best, bestGap] = [mark.id, gap];
}
return best;
}