diff --git a/B05_Profile/B05_Profile_UI_Viewer.ts b/B05_Profile/B05_Profile_UI_Viewer.ts index 6487cd91..9c67a27c 100644 --- a/B05_Profile/B05_Profile_UI_Viewer.ts +++ b/B05_Profile/B05_Profile_UI_Viewer.ts @@ -26,6 +26,8 @@ import { TerrainHeightIndex } from "./B05_Profile_UI_Corridor_Terrain"; import { buildPatchSkirts } from "./B05_Profile_UI_Corridor_Skirt"; import { BAND_MARGIN_M, TerrainBandSplit, type SceneBox } from "./B05_Profile_UI_Corridor_Split"; +type ViewKind = "iso" | "top" | "front" | "side"; + const LIGHT_VIEWER_BACKGROUND = 0xf5f7fa; const DARK_VIEWER_BACKGROUND = 0x251f38; @@ -207,6 +209,13 @@ export function createRouteViewer(): RouteViewer { toScene: (x: number, y: number, z: number) => bounds ? modelToScene({ x, y, z }, bounds) : null, topZ: () => (bounds ? bounds.z[1] + 100 : null), + // 카메라를 모델 좌표 한 점으로 바로 보낸다 — 측점 확인을 마우스 휠·드래그로 하면 + // 한 번에 20~30초가 걸리고 우클릭이 구조물 메뉴를 연다(2026-09-02). 화면 검증 전용. + lookAt: (x: number, y: number, z: number, distance: number, view: ViewKind = "top") => { + if (!bounds) return false; + placeCamera(modelToScene({ x, y, z }, bounds), distance, view); + return true; + }, }; const markers = createRouteMarkers(scene, () => bounds, terrainElevation); // 회전·줌 중심을 커서 아래 지형 지점으로 (B04 뷰어들과 공용 유틸). @@ -234,12 +243,8 @@ export function createRouteViewer(): RouteViewer { const resizeObserver = new ResizeObserver(resize); resizeObserver.observe(root); - function fit(view: "iso" | "top" | "front" | "side" = "top"): void { - if (!bounds) return; - const width = bounds.x[1] - bounds.x[0]; - const depth = bounds.y[1] - bounds.y[0]; - const distance = Math.max(width, depth, 20) * 1.35; - controls.target.set(0, 0, 0); + function placeCamera(target: THREE.Vector3, distance: number, view: ViewKind): void { + controls.target.copy(target); const positions = { iso: [distance, distance, distance], // 정확히 수직이면 lookAt이 화면 방향을 못 정해 첫 드래그에 화면이 뒤집힌다. @@ -248,7 +253,7 @@ export function createRouteViewer(): RouteViewer { side: [distance, distance * 0.25, 0], } as const; const [x, y, z] = positions[view]; - camera.position.set(x, y, z); + camera.position.set(target.x + x, target.y + y, target.z + z); camera.near = Math.max(0.1, distance / 1000); camera.far = distance * 10; // 원근 45°(반각 tan ≈ 0.414)와 비슷한 화면 배율 — 뷰 전환 시 크기감이 유지된다. @@ -256,6 +261,13 @@ export function createRouteViewer(): RouteViewer { controls.update(); } + function fit(view: ViewKind = "top"): void { + if (!bounds) return; + const width = bounds.x[1] - bounds.x[0]; + const depth = bounds.y[1] - bounds.y[0]; + placeCamera(new THREE.Vector3(0, 0, 0), Math.max(width, depth, 20) * 1.35, view); + } + async function reloadContours(interval: number): Promise { if (!current || !bounds) return; current.interval = interval;