feat(B05): 3D 뷰어 원근 카메라 복귀

사용자 지시(2026-09-04) — 「이전 방식이 좋았음」. 2026-08-25 `f6296fa7` 에서
직교로 바꿨던 것을 시야각 45° 원근으로 되돌림.

- 직교 전용 모듈 `B05_Profile_UI_Viewer_Camera.ts` 삭제, 뷰어 4자리 복원
  (카메라 생성 · 리사이즈 종횡비 · 화면맞춤 · 투영행렬).
- 근평면 상한 0.4m 신설 — 휠 확대가 커서 아래 지점 0.5m 앞에서 멈추는데
  근평면을 맞춤거리의 1/1000 로만 두면 긴 노선에서 그 0.5m 를 넘어 지형이 잘림
  (용화_LAS 맞춤거리 1,208m → 상한 없으면 근평면 1.21m).

자체검증(공용 브라우저 5173, 용화_LAS) — 네 방향 모두 원근 45°, 확대 한계
0.516m > 근평면 0.4m 로 잘림 없음, 구조물 클릭 선택·해제 그대로.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 18:26:26 +09:00
co-authored by Claude Opus 5
parent 92441eb8c9
commit 6ff86cdd20
2 changed files with 11 additions and 49 deletions
+11 -7
View File
@@ -14,7 +14,6 @@ import {
type RouteMarkers,
type SectionStationMarker,
} from "./B05_Profile_UI_Markers";
import { createOrthoCameraRig } from "./B05_Profile_UI_Viewer_Camera";
import { bindMarkerPointerControls } from "./B05_Profile_UI_Viewer_Marker_Input";
import type { CorridorBuildResult } from "./B05_Profile_UI_Corridor_Build";
import {
@@ -165,8 +164,10 @@ export function createRouteViewer(): RouteViewer {
});
systemDarkTheme.addEventListener("change", updateSceneBackground);
updateSceneBackground();
const cameraRig = createOrthoCameraRig();
const camera = cameraRig.camera;
// 원근 카메라(시야각 45°) — 2026-09-04 사용자 지시로 직교에서 되돌렸다. 직교가
// 필요했던 탑뷰 구조물 투영 윤곽선은 2026-09-02에 숨김 처리되어 화면에 없다.
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100000);
camera.position.set(100, 120, 100);
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
const controls = new OrbitControls(camera, canvas);
@@ -275,7 +276,8 @@ export function createRouteViewer(): RouteViewer {
const width = Math.max(1, root.clientWidth);
const height = Math.max(1, root.clientHeight);
renderer.setSize(width, height, false);
cameraRig.setAspect(width / height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}
const resizeObserver = new ResizeObserver(resize);
resizeObserver.observe(root);
@@ -291,10 +293,12 @@ export function createRouteViewer(): RouteViewer {
} as const;
const [x, y, z] = positions[view];
camera.position.set(target.x + x, target.y + y, target.z + z);
camera.near = Math.max(0.1, distance / 1000);
// 근평면 상한 0.4m — 휠 확대는 커서 아래 지점 0.5m 앞에서 멈춘다(커서 피벗 유틸).
// 거리에만 비례시키면 긴 노선(맞춤 거리 1km 이상)에서 근평면이 그 0.5m를 넘어
// 최대 확대 시 지형이 잘린다(2026-09-04 원근 복귀 실측: 400m 노선 여유 13mm).
camera.near = Math.max(0.1, Math.min(0.4, distance / 1000));
camera.far = distance * 10;
// 원근 45°(반각 tan ≈ 0.414)와 비슷한 화면 배율 — 뷰 전환 시 크기감이 유지된다.
cameraRig.setHalfHeight(distance * 0.42);
camera.updateProjectionMatrix();
controls.update();
}
@@ -1,42 +0,0 @@
/* =============================================================================
* B05_Profile_UI_Viewer_Camera.ts
* B05 뷰어의 **직교(원근 없음) 카메라**(2026-08-25 사용자 확정) — 탑뷰에서 구조물·
* 절단 경계가 원근으로 일그러지지 않는다. 화면 배율은 camera.zoom이 지고(커서 피벗
* 유틸이 조작), 절두체 반높이는 fit()이 정한다. Viewer 700줄 제한으로 분리.
* ========================================================================== */
import * as THREE from "three";
export interface OrthoCameraRig {
camera: THREE.OrthographicCamera;
/** 뷰포트 종횡비 반영(리사이즈 시). */
setAspect(aspect: number): void;
/** 절두체 반높이(월드 m) 지정 — fit()이 화면 배율을 잡을 때 쓴다. zoom은 1로 되돌린다. */
setHalfHeight(halfHeight: number): void;
}
export function createOrthoCameraRig(): OrthoCameraRig {
const camera = new THREE.OrthographicCamera(-100, 100, 100, -100, 0.1, 100000);
camera.position.set(100, 120, 100);
let halfHeight = 100;
let aspect = 1;
const apply = (): void => {
camera.left = -halfHeight * aspect;
camera.right = halfHeight * aspect;
camera.top = halfHeight;
camera.bottom = -halfHeight;
camera.updateProjectionMatrix();
};
return {
camera,
setAspect(value: number): void {
aspect = value;
apply();
},
setHalfHeight(value: number): void {
halfHeight = value;
camera.zoom = 1;
apply();
},
};
}