fix(454 B05 방향키 측점 걸음): 곡선 맞춤 후보 측점 둘 · 뒤 곡선 앞보기 (433-21 ②)
- 곡선마다 내림 · 올림 측점 후보 · 뒤 곡선이 측점 위거나 맞춰지는 쪽 고름 - 무작위 노선 30 개 20 m 걸음 측점 밖 막힘 141 → 25 · 놓은 뒤 측점 밖 43 → 0 - 시험 test_454_arrow_station_step 추가 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* 추천 노선 맞춤(`B05_Profile_Engine_RouteInitial_Station.py` `_try` · `_solve`)을 줄인 꼴:
|
||||
* · `from`(`fitStart` = 고친 자리 앞)부터 끝까지 차례로 — 고정 아닌 교각점(IP)을 들어오는 접선 따라 ±간격 안에서
|
||||
* 밀어 MC(BC · EC 누가거리 가운데)를 가까운 일반측점(앞 MC 측점 + 간격 이상 · No.1 이상)에 둠.
|
||||
* 밀어 MC(BC · EC 누가거리 가운데)를 일반측점(앞 MC 측점 + 간격 이상 · No.1 이상)에 둠 — 내림 · 올림 후보 중 뒤 곡선도 서는 쪽(454).
|
||||
* · 곡선 하한을 새로 깨거나(`shortfallCrossed`) 앞 곡선 MC 를 흔들면 버림 → 그 곡선은 그대로.
|
||||
* · 반지름 · 켬끔은 안 바꿈 — IP 자리만. 합치기 · 곡선 끄기 · 되돌아가기 없음.
|
||||
* · 간격 = 스냅 간격(390-2 · 0.1 · 1 · 10 m · 측점 No.) · 허용 오차도 간격 따라(`mcTolM`).
|
||||
@@ -139,7 +139,9 @@ function solve(
|
||||
return null;
|
||||
}
|
||||
|
||||
/** `from` 꺾임점부터 끝까지 곡선 MC 를 일반측점에 — 새 IP 목록(못 놓은 곡선 IP 는 그대로). */
|
||||
/** `from` 꺾임점부터 끝까지 곡선 MC 를 일반측점에 — 새 IP 목록(못 놓은 곡선 IP 는 그대로).
|
||||
* 곡선마다 후보 측점 둘(내림 · 올림 · 가까운 차례) · 뒤 곡선 하나를 미리 맞춰 보고 둘 다 서는 쪽(433-21 ②)
|
||||
* — 가까운 쪽만 고르면 뒤 곡선이 「앞 측점 + 1」에 몰려 R 이 눌리고 앞 MC 가 흔들려 버려짐. */
|
||||
export function fitCurveStations(
|
||||
cols: Columns & Pick<NodeColumns, "planned">,
|
||||
limits: FitLimits,
|
||||
@@ -150,28 +152,64 @@ export function fitCurveStations(
|
||||
if (!(interval > 0) || planned.length < 3) return planned;
|
||||
let built = build(cols, planned, limits);
|
||||
const floor = curveShortfalls(built.nodes, limits.limitRadiusM, limits.limitArcM);
|
||||
const tol = mcTolM(interval);
|
||||
type Fit = { planned: Vertex[]; built: EditedPolyline };
|
||||
/** 곡선을 놓을 측점 — 내림 · 올림 중 앞 측점 + 1 이상 · 가까운 차례. */
|
||||
const near = (mc: number, last: number): number[] => {
|
||||
const lo = Math.floor(mc / interval);
|
||||
return [lo, lo + 1]
|
||||
.filter((n) => n > last)
|
||||
.sort((a, b) => Math.abs(a * interval - mc) - Math.abs(b * interval - mc));
|
||||
};
|
||||
const onAt = (mc: number, last: number) =>
|
||||
near(mc, last).find((n) => Math.abs(mc - n * interval) <= tol);
|
||||
/** 곡선 `k` 를 측점 `n` 에 — 하한을 새로 깨거나 앞 곡선(`previous`) MC 를 흔들면 null. */
|
||||
const attempt = (at: Fit, k: number, mc: number, n: number, previous: number): Fit | null => {
|
||||
const fitted = solve(cols, limits, at.planned, k, n * interval, mc);
|
||||
if (
|
||||
!fitted ||
|
||||
shortfallCrossed(
|
||||
floor,
|
||||
curveShortfalls(fitted.built.nodes, limits.limitRadiusM, limits.limitArcM),
|
||||
)
|
||||
)
|
||||
return null;
|
||||
if (previous < 0) return fitted;
|
||||
const moved = Math.abs(
|
||||
(mcOf(fitted.built, previous) ?? Infinity) - (mcOf(at.built, previous) ?? 0),
|
||||
);
|
||||
return moved <= tol / 5 ? fitted : null;
|
||||
};
|
||||
/** `k` 뒤 첫 곡선이 측점 위이거나 맞춰지나(앞보기 한 걸음). */
|
||||
const nextSettles = (at: Fit, k: number, last: number): boolean => {
|
||||
for (let j = k + 1; j < at.planned.length - 1; j += 1) {
|
||||
const mc = mcOf(at.built, j);
|
||||
if (mc === null) continue;
|
||||
if (onAt(mc, last) !== undefined) return true;
|
||||
return !cols.apexLock[j] && near(mc, last).some((n) => attempt(at, j, mc, n, k) !== null);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
let lastStation = 0; // 앞 곡선이 놓인 측점 번호(없으면 0 = 시점)
|
||||
let previous = -1; // 앞 곡선 꺾임점
|
||||
for (let k = 1; k < planned.length - 1; k += 1) {
|
||||
const mc = mcOf(built, k);
|
||||
if (mc === null) continue;
|
||||
const near = Math.max(lastStation + 1, Math.round(mc / interval));
|
||||
const onStation = Math.abs(mc - near * interval) <= mcTolM(interval);
|
||||
const fitted =
|
||||
onStation || k < from || cols.apexLock[k] || Math.abs(near * interval - mc) > interval / 2
|
||||
? null
|
||||
: solve(cols, limits, planned, k, near * interval, mc);
|
||||
const keep =
|
||||
fitted &&
|
||||
!shortfallCrossed(
|
||||
floor,
|
||||
curveShortfalls(fitted.built.nodes, limits.limitRadiusM, limits.limitArcM),
|
||||
) &&
|
||||
(previous < 0 ||
|
||||
Math.abs((mcOf(fitted.built, previous) ?? Infinity) - (mcOf(built, previous) ?? 0)) <=
|
||||
mcTolM(interval) / 5);
|
||||
if (fitted && keep) ({ planned, built } = fitted);
|
||||
if (onStation || keep) lastStation = near;
|
||||
const on = onAt(mc, lastStation);
|
||||
if (on !== undefined) lastStation = on;
|
||||
else if (k >= from && !cols.apexLock[k]) {
|
||||
let pick: { fit: Fit; n: number } | null = null;
|
||||
for (const n of near(mc, lastStation)) {
|
||||
const fit = attempt({ planned, built }, k, mc, n, previous);
|
||||
if (!fit) continue;
|
||||
pick ??= { fit, n };
|
||||
if (nextSettles(fit, k, n)) {
|
||||
pick = { fit, n };
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pick) (({ planned, built } = pick.fit), (lastStation = pick.n));
|
||||
}
|
||||
previous = k;
|
||||
}
|
||||
return planned;
|
||||
|
||||
Reference in New Issue
Block a user