From fdcd07539693fec3cfde0e3110d23c76b12d29ca Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 6 Sep 2026 11:32:29 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05):=203D=20=EB=85=B8=EC=84=A0=20=EC=84=A0?= =?UTF-8?q?=EC=9D=B4=20=EB=8A=A5=EC=84=A0=EC=97=90=EC=84=9C=20=EB=81=8A?= =?UTF-8?q?=EA=B2=A8=20=EB=B3=B4=EC=9D=B4=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=E2=80=94=202m=20=EA=B0=84=EA=B2=A9=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=A7=80=ED=98=95=EC=97=90=20=EB=B6=99=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 원인은 선의 해상도 (2026-09-06 실측). 노선 정점(간격 중앙값 10.0m)을 곧바로 이어 지형 위 0.35m 로 띄웠는데, 볼록한 능선에서는 그 10m 직선이 지면을 뚫고 들어가 선이 땅속에 잠겼다 — 화면에서는 161+0.0~163+0.0 구간이 통째로 사라졌다. 정점 사이를 2m 간격으로 나눠 각 점의 지형고를 찍는다. 원래 정점은 모두 남기므로 평면 형상은 불변. 끼운 점과 원래 정점이 같은 지형면에서 높이를 받도록 지형고를 먼저 보고, 없을 때만 정점 z 를 쓴다. 곡선 경고 구간은 원래 정점 기준 인덱스라 조밀화 뒤 자리로 옮겨 자른다. Co-Authored-By: Claude Opus 5 (1M context) --- B05_Profile/B05_Profile_UI_Markers.ts | 53 +++++++++++++++++++++------ 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Markers.ts b/B05_Profile/B05_Profile_UI_Markers.ts index 7e304fa5..f115c28c 100644 --- a/B05_Profile/B05_Profile_UI_Markers.ts +++ b/B05_Profile/B05_Profile_UI_Markers.ts @@ -95,6 +95,14 @@ export function sceneToModel(point: THREE.Vector3, bounds: ModelBounds) { return { x: point.x + cx, y: -point.z + cy, z: point.y + cz }; } +/** 노선 선을 지형에 붙일 때 정점 사이를 나누는 간격(m) — 2m 면 20m 측점 사이가 10토막이라 + * 능선을 가로질러도 선이 지면 아래로 잠기지 않는다(2026-09-06). */ +const DRAPE_STEP_M = 2; +/** 한 구간을 나누는 최대 토막 수 — 정점이 아주 멀리 떨어진 자료에서 점이 폭주하지 않게 한다. */ +const MAX_DRAPE_STEPS = 64; +/** 노선 선을 지형 위로 띄우는 높이(m) — 지형과 겹쳐 깜빡이지 않을 만큼만. */ +const ROUTE_LINE_LIFT_M = 0.35; + export function createRouteMarkers( scene: THREE.Scene, getBounds: () => ModelBounds | null, @@ -254,17 +262,40 @@ export function createRouteMarkers( disposeGroup(routeGroup); const bounds = getBounds(); if (!bounds || polyline.length < 2) return; + // 정점 사이를 잘게 나눠 **각 점의 지형고를 찍어** 선을 지면에 붙인다(2026-09-06). + // 원래는 정점(간격 중앙값 10m)을 곧바로 이었는데, 볼록한 능선에서는 그 10m 직선이 + // 지면을 뚫고 들어가 선이 땅속에 잠겼다 — 화면에는 폴리라인이 끊긴 것처럼 보였다 + // (실측: 161+0.0~163+0.0 구간이 통째로 사라짐). 원래 정점은 모두 남기므로 평면 + // 형상은 바뀌지 않는다. + const dense: Array<{ x: number; y: number; z?: number }> = []; + /** 원래 정점 i 가 조밀화 뒤 몇 번째인지 — 경고 구간이 인덱스로 자르므로 필요하다. */ + const denseIndex: number[] = []; + polyline.forEach((point, index) => { + if (index > 0) { + const previous = polyline[index - 1]; + const span = Math.hypot(point.x - previous.x, point.y - previous.y); + const steps = Math.min(MAX_DRAPE_STEPS, Math.ceil(span / DRAPE_STEP_M)); + for (let step = 1; step < steps; step += 1) { + const ratio = step / steps; + dense.push({ + x: previous.x + (point.x - previous.x) * ratio, + y: previous.y + (point.y - previous.y) * ratio, + }); + } + } + denseIndex[index] = dense.length; + dense.push(point); + }); // 표고 없는 점을 0으로 삼키면 마커와 같은 함정에 빠진다(지형 한참 아래 평면에 눕는다). - // 지형 표면에서 찾아 채우고, 그래도 모르면 직전 점 높이를 이어 쓴다. 경고 구간이 - // 인덱스로 이 배열을 다시 자르므로 점 개수는 그대로 두어야 한다. + // 지형 표면에서 찾아 채우고, 그래도 모르면 직전 점 높이를 이어 쓴다. 끼워 넣은 점과 + // 원래 정점이 **같은 지형면**에서 높이를 받아야 선이 매끄러우므로 지형고를 먼저 본다. let lastZ: number | null = null; - const linePoints = polyline.map((point) => { - const resolved = Number.isFinite(point.z) - ? (point.z as number) - : (getTerrainZ?.(point.x, point.y) ?? lastZ); + const linePoints = dense.map((point) => { + const sampled = getTerrainZ?.(point.x, point.y) ?? null; + const resolved = sampled ?? (Number.isFinite(point.z) ? (point.z as number) : lastZ); if (resolved !== null) lastZ = resolved; return modelToScene({ x: point.x, y: point.y, z: resolved ?? bounds.z[0] }, bounds).add( - new THREE.Vector3(0, 0.35, 0), + new THREE.Vector3(0, ROUTE_LINE_LIFT_M, 0), ); }); routeGroup.add( @@ -274,10 +305,10 @@ export function createRouteMarkers( ), ); warnings.forEach((warning) => { - const segment = linePoints.slice( - Math.max(0, warning.polyline_start_index), - warning.polyline_end_index + 1, - ); + // 경고 구간의 인덱스는 **원래 정점 기준**이라 조밀화 뒤 자리로 옮겨 자른다. + const from = denseIndex[Math.max(0, warning.polyline_start_index)] ?? 0; + const to = denseIndex[Math.min(warning.polyline_end_index, polyline.length - 1)]; + const segment = linePoints.slice(from, (to ?? linePoints.length - 1) + 1); if (segment.length > 1) { routeGroup.add( new THREE.Line(