fix(B05 평면 측점 스냅): 끌기 · 방향키 하한 검사 = 맞추기 전 노선 — 10 m · 측점 No. 미리보기가 IP 를 밀어 헛걸리던 것 (390-5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
2026-10-17 20:11:30 +09:00
co-authored by Claude Opus 5.5
parent e3b8123f8b
commit a8a8e4faba
4 changed files with 39 additions and 9 deletions
@@ -101,8 +101,9 @@ export interface RouteEditPointerParams {
commit: () => void;
/** 스냅 간격(m · 390-5) — 스냅 켬이면 방향키 한 걸음. */
snapStepM: () => number;
/** 끄는 중 · 방향키 걸음 미리보기(390-5) — 스냅 켬이면 IP `index` 자리 그대로 MC 를 간격 배수에. */
fitDrag: (index: number) => void;
/** 끄는 중 · 방향키 걸음 미리보기(390-5) — 스냅 켬이면 IP `index` 자리 그대로 MC 를 간격 배수에 ·
* 맞췄으면 true(끔이면 false · 아무 일 없음). */
fitDrag: (index: number) => boolean;
}
export interface RouteEditPointer {
@@ -188,6 +189,8 @@ export function bindRouteEditPointer(params: RouteEditPointerParams): RouteEditP
/** 이번 끌기에서 **실제로 움직였나** — 그냥 눌러 고르기만 한 것은 되돌릴 걸음이 아니다
* (2026-09-07 실화면: 노드를 클릭만 해도 [되돌리기]가 켜졌다). */
let dragMoved = false;
/** 스냅 미리보기 전 자리(390-5) — 하한 검사는 맞춘 선이 아닌 이것으로(맞춤이 IP 를 ±간격/2 밂). */
let dragRaw: Vertex[] | null = null;
canvas.addEventListener("pointerdown", (event) => {
if (event.button !== 0) return;
@@ -208,6 +211,7 @@ export function bindRouteEditPointer(params: RouteEditPointerParams): RouteEditP
const hitNode = pick && "node" in pick ? pick.node : -1;
// 교각점을 고정한 자리는 **고르기만** 되고 안 끌린다(2026-09-12 사용자 지시 ①).
dragNode = hitNode >= 0 && state.apexLock[hitNode] !== true ? hitNode : -1;
dragRaw = null;
dragHandle = hitNode >= 0 ? null : handleAt(px, py);
dragMoved = false;
if (hitNode >= 0) {
@@ -274,6 +278,12 @@ export function bindRouteEditPointer(params: RouteEditPointerParams): RouteEditP
canvas.setPointerCapture(event.pointerId);
});
/** 스냅 미리보기 — 맞추기 전 자리를 쥔 뒤 맞춤(끔이면 쥐지 않음). */
const fitPreview = (planned: Vertex[]): void => {
const raw = planned.map((vertex): Vertex => [vertex[0], vertex[1]]);
dragRaw = params.fitDrag(dragNode) ? raw : null;
};
canvas.addEventListener("pointermove", (event) => {
const rect = canvas.getBoundingClientRect();
const [px, py] = params.unrotate(event.clientX - rect.left, event.clientY - rect.top);
@@ -303,6 +313,10 @@ export function bindRouteEditPointer(params: RouteEditPointerParams): RouteEditP
return;
}
if (dragNode >= 0) {
if (dragRaw) {
dragRaw.forEach((vertex, index) => (state.planned[index] = vertex));
params.markEdited();
}
// 옮기기 **전**에 하한을 지키던 자리 — 이미 밑돌던 자리는 그대로 고칠 수 있어야 하므로
// **지키던 자리가 넘어가는 것만** 막는다(계획서 0-9 ④).
const before = curveShortfalls(state.nodeInfo, state.limitRadiusM, state.limitArcM);
@@ -320,13 +334,14 @@ export function bindRouteEditPointer(params: RouteEditPointerParams): RouteEditP
// 접선 자리가 모자라 R 이 하한 아래로 눌리는 자리다 — 그 걸음만 되돌린다.
state.planned[dragNode] = previous;
params.markEdited();
fitPreview(state.planned);
status.textContent =
`${params.routeHead()} — 하한에 걸려 더 못 옮깁니다` +
`(곡선반지름 ${state.limitRadiusM}m${state.limitArcM > 0 ? ` · 곡선 길이 ${state.limitArcM}m` : ""}).`;
params.draw();
return;
}
params.fitDrag(dragNode); // 스냅 켬 = 끄는 동안 MC 를 간격 배수에(390-5 · 놓을 때 정본)
fitPreview(state.planned); // 스냅 켬 = 끄는 동안 MC 를 간격 배수에(390-5 · 놓을 때 정본)
// 끄는 동안에도 상태줄이 살아 있어야 한다 — 예전에는 여기서 아무 말이 없어
// 「곡선이 사라졌다」는 인상만 남았다(2026-09-07 사용자 지적 ②).
status.textContent = `${params.routeHead()} — 옮기는 중. ${params.curveHint()}`;
@@ -370,6 +385,7 @@ export function bindRouteEditPointer(params: RouteEditPointerParams): RouteEditP
dragNode = -1;
dragHandle = null;
dragMoved = false;
dragRaw = null;
// 끄는 동안 숨겨 둔 기울기 라벨 — 놓은 노선 높이를 한 번 묻게 다시 그린다(계획서 18 ①).
if (moved) {
params.slopeRefresh();