diff --git a/B05_Profile/B05_Profile_UI_Corridor.ts b/B05_Profile/B05_Profile_UI_Corridor.ts index 6fad466f..0541eadc 100644 --- a/B05_Profile/B05_Profile_UI_Corridor.ts +++ b/B05_Profile/B05_Profile_UI_Corridor.ts @@ -218,7 +218,11 @@ function fnv1a(text: string): string { // 걸려 있어 BOX 패치는 한 번도 안 잘리고 영역 밖으로 넘쳤다 — 실측에서 BOX 패치 // 리본 두 벌(198.4~203.5m)에 셀 마스크가 없었다. BOX도 `wing-box` 커브로 절취 // 영역을 내므로 같은 반대 규칙이 성립한다. -const BUILD_VERSION = 89; +// 90 = 잘린 자리 윤곽(`cut-merged`)의 **시작 모서리 유실**을 고친다 — `dropCollinear`가 +// 고리의 닫힘 중복점(머리=꼬리)을 순환 이웃으로 셈해 머리 모서리를 지우고, 그 양옆 +// 일직선 점까지 빠져 첫 행이 대각선으로 잘렸다(실측: 8고리 전부, 63셀 구멍 — +// 우 197.6~198.2m BOX 앞 쐐기 33셀이 가장 큼). 되살릴 영역이 이 커브라 저장본을 만료한다. +const BUILD_VERSION = 90; /** 종횡단 정본에서 코리도에 영향을 주는 입력만 요약해 해시 — 갱신 감지 기준. */ export function corridorHash(detail: SectionDetailResponse, routePoints: RoutePoint[]): string { diff --git a/B05_Profile/B05_Profile_UI_Corridor_Cut.ts b/B05_Profile/B05_Profile_UI_Corridor_Cut.ts index 07f4fc5d..42b90c52 100644 --- a/B05_Profile/B05_Profile_UI_Corridor_Cut.ts +++ b/B05_Profile/B05_Profile_UI_Corridor_Cut.ts @@ -323,12 +323,24 @@ const COLLINEAR_TOLERANCE_M = 0.001; function dropCollinear( loop: ReadonlyArray<[number, number, number]>, ): Array<[number, number, number]> { - if (loop.length < 3) return [...loop]; + // 추적 고리는 머리 점이 꼬리에 한 번 더 실려 온다(닫힘 표시). **순환으로 다듬기 전에 + // 뗀다** — 그대로 두면 머리 점의 이웃이 자기 자신이 되어 `away = 0`으로 지워지고, 그 + // 모서리 양옆의 일직선 점들까지 함께 빠져 **시작 모서리가 대각선으로 잘려 나갔다** + // (2026-09-02 실측: 고리마다 첫 행 쐐기 63셀이 되살릴 영역에서 빠져 구멍이 됐다). + const closed = + loop.length > 1 && + loop[0][0] === loop[loop.length - 1][0] && + loop[0][1] === loop[loop.length - 1][1]; + const ring = closed ? loop.slice(0, -1) : loop; + if (ring.length < 3) return [...loop]; const kept: Array<[number, number, number]> = []; - for (let i = 0; i < loop.length; i += 1) { - const prev = loop[(i - 1 + loop.length) % loop.length]; - const at = loop[i]; - const next = loop[(i + 1) % loop.length]; + for (let i = 0; i < ring.length; i += 1) { + // 앞점은 **마지막으로 남긴 점**이다 — 바로 앞 원점으로 재면 완만한 곡선에서 점마다 + // 1mm 미만이라 전부 빠지고, 남은 긴 현이 곡선 안쪽으로 2cm 파고든다(2026-09-02 실측: + // 직선부 노견 열의 셀 중심이 현 위에 놓였다). `_Region.ts` `simplifyLoop`과 같은 규칙. + const prev = kept[kept.length - 1] ?? ring[ring.length - 1]; + const at = ring[i]; + const next = ring[(i + 1) % ring.length]; const dx = next[0] - prev[0]; const dy = next[1] - prev[1]; const span = Math.hypot(dx, dy); @@ -339,6 +351,8 @@ function dropCollinear( } kept.push(at); } + // 닫힘 표시는 그대로 돌려준다 — 스케치가 `Line`으로 그려져 꼬리 점이 있어야 닫힌다. + if (closed && kept.length) kept.push(kept[0]); return kept; }