Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_Hover.ts
T
eomsangdonandClaude Opus 5.5 a0a29fd002 feat(B05 평면): 맴돔 = 고름 20 m 법선 · 고른 곡선 3점 · 곡선 없는 교각점 삼각형 · 잠금 황금 (391)
- 391-1 측점 맴돔 · 고름 = 한 함수(drawStationNormal) · 좌우 20 m 고정 하늘색 · 짧은 12 px 선 · 절성토 색 지움 · 구조물 마름모 그대로
- 391-2 고른 곡선 = 굵은 호 + 교각점→BC · EC 접선 + BC · MC · EC 세 점(새 파일 RouteEdit_Curve_Pick · MC = curveMidVertex) · 곡선 없는 교각점 = 같은 크기 삼각형
- 391-3 잠금 = 황금(#ca8a04 · 어두움 #facc15) · 굵기 4 + 흰 테
- 시험 test_391_routeedit_plan_draw 새로 · test_381_3 맴돔 배선 고쳐 씀

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

91 lines
3.5 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_Hover.ts
* 노선 편집 평면 **맴돔 밝힘**(요구 241-14 · PLAN 381-3).
*
* 맴도는 자리는 `_Pointer` 가 `_Input.pickAtScreen`(집기와 같은 셈)으로 적고, 여기서 이길 쪽만
* 그린다 — 노드 = 바깥 고리 · 횡단 눈금 = 고른 측점과 같은 좌우 20 m 법선(`drawStationNormal` ·
* 391-1). `_Render` 가 노드 그린 뒤 부름.
* ========================================================================== */
import { pickHover } from "./B05_Profile_UI_RouteEdit_Input";
import type { RouteEditScene } from "./B05_Profile_UI_RouteEdit_Render";
import { stationFrameAt } from "./logic/B05_Profile_UI_RouteEdit_Frame";
import type { Vertex } from "./logic/B05_Profile_UI_RouteEdit_Curve";
/** 측점 법선 색 — 노선(주황)·재기(초록)·등고선(연보라)과 겹치지 않는 하늘색. */
const STATION_NORMAL_COLOR = "#0ea5e9";
/** 법선 좌우 길이(m) — 서버 `SECTION_CROSS_HALF_WIDTH_M` 과 같은 값 · 고정(391-1). */
const STATION_NORMAL_HALF_M = 20;
/** 측점 법선(391-1) — 맴돔 · 고름 한 그림: 측점에서 노선에 직각 좌우 20 m · 끝 점 · 측점 고리.
* 방향은 지금 그려진 노선으로 매 프레임 다시 짚음(`stationFrameAt`) — 끄는 동안 바로 따라감. */
export function drawStationNormal(
context: CanvasRenderingContext2D,
scene: RouteEditScene,
line: ReadonlyArray<Vertex>,
chainageM: number,
): void {
const frame = stationFrameAt(line, chainageM);
if (!frame) return;
const [ox, oy] = frame.origin;
const [lx, ly] = frame.left;
const half = STATION_NORMAL_HALF_M;
const [cx, cy] = scene.toScreen(frame.origin);
const [ax, ay] = scene.toScreen([ox + lx * half, oy + ly * half]);
const [bx, by] = scene.toScreen([ox - lx * half, oy - ly * half]);
context.save();
context.setLineDash([]);
context.strokeStyle = STATION_NORMAL_COLOR;
context.fillStyle = STATION_NORMAL_COLOR;
context.lineWidth = 2.4;
context.beginPath();
context.moveTo(ax, ay);
context.lineTo(bx, by);
context.stroke();
for (const [x, y] of [
[ax, ay],
[bx, by],
]) {
context.beginPath();
context.arc(x, y, 3, 0, Math.PI * 2);
context.fill();
}
context.lineWidth = 2;
context.beginPath();
context.arc(cx, cy, 6, 0, Math.PI * 2);
context.stroke();
context.restore();
}
/** 맴도는 자리 밝힘(381-3) — 누르면 집힐 쪽만: 노드 = 바깥 고리 · 횡단 눈금 = 측점 법선. */
export function drawPickHover(
context: CanvasRenderingContext2D,
scene: RouteEditScene,
line: ReadonlyArray<Vertex>,
style: CSSStyleDeclaration,
nodeR: number,
): void {
const hover = pickHover(context.canvas);
if (!hover) return;
context.save();
context.setLineDash([]);
if ("node" in hover) {
const vertex = scene.planned[hover.node];
if (vertex) {
const [x, y] = scene.toScreen(vertex);
const radius = (hover.node === scene.picked ? nodeR + 2 : nodeR) + 4;
context.beginPath();
context.arc(x, y, radius, 0, Math.PI * 2);
context.strokeStyle = style.getPropertyValue("--map-halo") || "rgba(255,255,255,0.9)";
context.lineWidth = 4;
context.stroke();
context.strokeStyle = style.getPropertyValue("--map-route") || "#f97316";
context.lineWidth = 2;
context.stroke();
}
} else {
drawStationNormal(context, scene, line, hover.chainage);
}
context.restore();
}