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:
@@ -14,7 +14,6 @@ import {
|
|||||||
type RouteMarkers,
|
type RouteMarkers,
|
||||||
type SectionStationMarker,
|
type SectionStationMarker,
|
||||||
} from "./B05_Profile_UI_Markers";
|
} from "./B05_Profile_UI_Markers";
|
||||||
import { createOrthoCameraRig } from "./B05_Profile_UI_Viewer_Camera";
|
|
||||||
import { bindMarkerPointerControls } from "./B05_Profile_UI_Viewer_Marker_Input";
|
import { bindMarkerPointerControls } from "./B05_Profile_UI_Viewer_Marker_Input";
|
||||||
import type { CorridorBuildResult } from "./B05_Profile_UI_Corridor_Build";
|
import type { CorridorBuildResult } from "./B05_Profile_UI_Corridor_Build";
|
||||||
import {
|
import {
|
||||||
@@ -165,8 +164,10 @@ export function createRouteViewer(): RouteViewer {
|
|||||||
});
|
});
|
||||||
systemDarkTheme.addEventListener("change", updateSceneBackground);
|
systemDarkTheme.addEventListener("change", updateSceneBackground);
|
||||||
updateSceneBackground();
|
updateSceneBackground();
|
||||||
const cameraRig = createOrthoCameraRig();
|
// 원근 카메라(시야각 45°) — 2026-09-04 사용자 지시로 직교에서 되돌렸다. 직교가
|
||||||
const camera = cameraRig.camera;
|
// 필요했던 탑뷰 구조물 투영 윤곽선은 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 });
|
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
|
||||||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||||||
const controls = new OrbitControls(camera, canvas);
|
const controls = new OrbitControls(camera, canvas);
|
||||||
@@ -275,7 +276,8 @@ export function createRouteViewer(): RouteViewer {
|
|||||||
const width = Math.max(1, root.clientWidth);
|
const width = Math.max(1, root.clientWidth);
|
||||||
const height = Math.max(1, root.clientHeight);
|
const height = Math.max(1, root.clientHeight);
|
||||||
renderer.setSize(width, height, false);
|
renderer.setSize(width, height, false);
|
||||||
cameraRig.setAspect(width / height);
|
camera.aspect = width / height;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
}
|
}
|
||||||
const resizeObserver = new ResizeObserver(resize);
|
const resizeObserver = new ResizeObserver(resize);
|
||||||
resizeObserver.observe(root);
|
resizeObserver.observe(root);
|
||||||
@@ -291,10 +293,12 @@ export function createRouteViewer(): RouteViewer {
|
|||||||
} as const;
|
} as const;
|
||||||
const [x, y, z] = positions[view];
|
const [x, y, z] = positions[view];
|
||||||
camera.position.set(target.x + x, target.y + y, target.z + z);
|
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;
|
camera.far = distance * 10;
|
||||||
// 원근 45°(반각 tan ≈ 0.414)와 비슷한 화면 배율 — 뷰 전환 시 크기감이 유지된다.
|
camera.updateProjectionMatrix();
|
||||||
cameraRig.setHalfHeight(distance * 0.42);
|
|
||||||
controls.update();
|
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();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user