feat(B05): 3D 화면 검증용 카메라 이동 창구 추가

- `__corridorScene.lookAt(x, y, z, distance, view)` — 모델 좌표 한 점으로 카메라를
  바로 보냄. 측점 확인을 마우스 휠·드래그로 하면 한 번에 20~30초가 걸리고
  우클릭이 구조물 메뉴를 여는 문제 해소.
- `fit()` 의 카메라 배치를 `placeCamera()` 로 공용화 — 뷰 4종(iso/top/front/side)
  위치·근평면·직교 반높이 계산이 한 곳.
This commit is contained in:
2026-09-02 16:10:29 +09:00
parent cb06e40f9c
commit fc59cef976
+19 -7
View File
@@ -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<void> {
if (!current || !bounds) return;
current.interval = interval;