- `B05_Profile_UI_RouteEdit_Curve.ts` 신설(68줄) — 손잡이 끌기 역셈 (`dragHandleTo`)·직선 교점(`intersect`)·내각(`innerAngleDeg`) 순수 기하만. - 모달은 740 → 692줄. 화면 배선·동작 불변, 셈 식 그대로 옮김. - 서버 정셈과 짝임을 새 파일 머리에 명시. 거울 시험은 새 자리를 보게 갱신. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
69 lines
3.2 KiB
TypeScript
69 lines
3.2 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_Curve.ts
|
|
* 곡선 손잡이 셈 — 편집 모달(`B05_Profile_UI_RouteEdit.ts`)에서 떼어냄
|
|
* (700줄 제한, 2026-09-07). 화면·DOM 을 안 만지는 순수 기하만 둔다.
|
|
*
|
|
* ⚠ 이 셈은 서버(`common_util/common_util_route_polyline.py`)의 **반대 방향**이다 —
|
|
* 서버는 교각점·R 에서 접선점을 내고, 여기서는 끈 접선점에서 교각점·R 을 낸다.
|
|
* 두 벌이 아니라 **짝**이며, 왕복이 제자리인지는
|
|
* `tmp/tests/test_route_polyline_handle_drag.py` 가 지킨다.
|
|
* ========================================================================== */
|
|
|
|
export type Vertex = [number, number];
|
|
|
|
/** 두 **직선**(선분 아님)이 만나는 자리. 나란하면 null. */
|
|
export function intersect(a1: Vertex, a2: Vertex, b1: Vertex, b2: Vertex): Vertex | null {
|
|
const dx1 = a2[0] - a1[0];
|
|
const dy1 = a2[1] - a1[1];
|
|
const dx2 = b2[0] - b1[0];
|
|
const dy2 = b2[1] - b1[1];
|
|
const denominator = dx1 * dy2 - dy1 * dx2;
|
|
if (Math.abs(denominator) <= 1e-12) return null;
|
|
const t = ((b1[0] - a1[0]) * dy2 - (b1[1] - a1[1]) * dx2) / denominator;
|
|
return [a1[0] + dx1 * t, a1[1] + dy1 * t];
|
|
}
|
|
|
|
/** 세 점이 이루는 내각(도). 일직선이면 180. */
|
|
export function innerAngleDeg(before: Vertex, at: Vertex, after: Vertex): number {
|
|
const ax = before[0] - at[0];
|
|
const ay = before[1] - at[1];
|
|
const bx = after[0] - at[0];
|
|
const by = after[1] - at[1];
|
|
const la = Math.hypot(ax, ay);
|
|
const lb = Math.hypot(bx, by);
|
|
if (la <= 0 || lb <= 0) return 180;
|
|
const cosine = Math.max(-1, Math.min(1, (ax * bx + ay * by) / (la * lb)));
|
|
return (Math.acos(cosine) * 180) / Math.PI;
|
|
}
|
|
|
|
/** 끈 접선점으로 **새 교각점과 새 반지름**을 구한다 — 「직선의 각도와 반지름 값 변경」.
|
|
*
|
|
* 사용자 확정(2026-09-07) — 곡선 시작·끝점을 옮기면 그쪽 **직선 각도**와 **반지름**이 함께
|
|
* 바뀐다(반대로 반지름만 바꿀 때는 직선이 고정이다).
|
|
*
|
|
* 셈은 이렇다. 끈 것이 시작점이면 들어오는 직선은 **앞 노드 → 끈 자리**로 돌아간다.
|
|
* 나가는 직선은 그대로이므로 **두 직선이 만나는 자리**가 새 교각점이고, 접선 길이
|
|
* T = |새 교각점 − 끈 자리| 에서 R = T / tan(교각/2) 가 나온다.
|
|
*/
|
|
export function dragHandleTo(
|
|
before: Vertex,
|
|
oldApex: Vertex,
|
|
after: Vertex,
|
|
which: "start" | "end",
|
|
to: Vertex,
|
|
): { apex: Vertex; radius: number } | null {
|
|
// 끈 쪽 직선만 돌아간다 — 반대쪽 직선은 옛 교각점을 지나는 그대로다.
|
|
const apex =
|
|
which === "start"
|
|
? intersect(before, to, oldApex, after) // 들어오는 직선이 끈 자리를 지나게 돌린다
|
|
: intersect(before, oldApex, to, after); // 나가는 직선을 돌린다
|
|
if (!apex) return null;
|
|
const inner = innerAngleDeg(before, apex, after);
|
|
const halfTan = Math.tan(((180 - inner) * Math.PI) / 360);
|
|
if (!(halfTan > 1e-9)) return null;
|
|
const tangent = Math.hypot(apex[0] - to[0], apex[1] - to[1]);
|
|
const radius = tangent / halfTan;
|
|
if (!(radius > 0) || !Number.isFinite(radius)) return null;
|
|
return { apex, radius };
|
|
}
|