fix(B04,B05): 탑뷰를 극점에서 2° 띄워 첫 드래그 화면 뒤집힘 제거
앞선 두 커밋(c3f643b,7d50ded)으로도 증상이 남아 헤드리스 크롬으로 실제 앱에 로그인해 B05 3D를 끌어 보며 측정했다. 원인은 회전 계산이 아니라 **탑뷰 카메라 자리**였다. 카메라를 정확히 수직으로 세우면(기존 top = [0, distance, 0.001], 극각 1e-6도) lookAt이 화면의 가로·세로 방향을 정하지 못한다. 그 자리에서 벗어나는 순간 화면 축이 180° 돌아간다. 실측(2px 드래그 6걸음): before 극각 0.00° -> 0.86°, 1걸음째 화면축 반전, 이후 같은 방향 드래그 먹통 after 극각 2.00° -> 1.15°, 반전 0회, 반대 방향 20걸음도 반전 0회(1.15° -> 130°) - TOP_VIEW_TILT(2°) 신설 — 화면상 위에서 내려다보는 그림과 구분되지 않으면서 여유각 (POLAR_EPSILON, 약 1.15°)보다 바깥이라 첫 드래그부터 회전이 이어진다. - B05 노선 뷰어, B04 지형·포인트클라우드 뷰어의 탑뷰 세 곳에 모두 적용. typecheck·prettier 통과, 정적 번들 재빌드. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,18 @@ export function niceScaleDistance(roughMeters: number): number {
|
||||
|
||||
/** 극점을 넘어 화면이 뒤집히지 않도록 남기는 여유각(rad). */
|
||||
const POLAR_EPSILON = 0.02;
|
||||
|
||||
/**
|
||||
* 탑뷰를 극점에서 살짝 띄우는 기울기(수평/수직 비).
|
||||
*
|
||||
* 카메라를 정확히 수직으로 세우면 `lookAt`이 화면의 가로·세로 방향을 정할 수 없다(시선과
|
||||
* up이 평행). 그 자리에서 조금만 끌어도 화면 축이 한 번에 180° 돌아간다 — 헤드리스 크롬으로
|
||||
* 실측했다: 2px 드래그 한 걸음에 화면축 반전, 그 뒤 같은 방향 드래그는 먹통(2026-08-08).
|
||||
*
|
||||
* 2°는 화면에서 위에서 내려다보는 그림과 사실상 구분되지 않으면서, 여유각(`POLAR_EPSILON`
|
||||
* ≈ 1.15°)보다 바깥이라 첫 드래그부터 회전이 자연스럽게 이어진다.
|
||||
*/
|
||||
export const TOP_VIEW_TILT = Math.tan((2 * Math.PI) / 180);
|
||||
/** 포인트클라우드 클릭 허용 반경 — 카메라 거리에 비례(멀수록 점이 성기게 보인다). */
|
||||
const POINT_PICK_RATIO = 0.01;
|
||||
/** 휠 한 칸당 배율. 휠을 위로 올리면 이 값의 역수만큼 멀어진다(2026-08-01 사용자 지시). */
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
bindCursorPivotControls,
|
||||
bindSurfaceViewerTheme,
|
||||
getTopFitDistance,
|
||||
TOP_VIEW_TILT,
|
||||
niceScaleDistance,
|
||||
SURFACE_CAMERA_FOV,
|
||||
targetPlaneMetersPerPixel,
|
||||
@@ -305,7 +306,8 @@ export function createSurfaceTerrainViewer(): SurfaceTerrainViewer {
|
||||
const aspect = viewerArea.clientWidth / Math.max(viewerArea.clientHeight, 1);
|
||||
const distance = referenceBounds ? getTopFitDistance(referenceBounds, aspect) : span * 1.2;
|
||||
controls.target.set(0, 0, 0);
|
||||
camera.position.set(0, distance, 0.001);
|
||||
// 정확히 수직이면 lookAt이 화면 방향을 못 정해 첫 드래그에 화면이 뒤집힌다.
|
||||
camera.position.set(0, distance, distance * TOP_VIEW_TILT);
|
||||
camera.near = Math.max(distance / 10000, 0.01);
|
||||
camera.far = distance * 100;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
bindSurfaceViewerTheme,
|
||||
getReferenceCenter,
|
||||
getTopFitDistance,
|
||||
TOP_VIEW_TILT,
|
||||
niceScaleDistance,
|
||||
SURFACE_CAMERA_FOV,
|
||||
targetPlaneMetersPerPixel,
|
||||
@@ -195,7 +196,8 @@ export function createSurfacePointCloudViewer(): SurfacePointCloudViewer {
|
||||
function setCameraView(view: CameraView): void {
|
||||
const directions: Record<CameraView, [number, number, number]> = {
|
||||
iso: [120, 95, 135],
|
||||
top: [0, 1, 0.00001],
|
||||
// 정확히 수직이면 lookAt이 화면 방향을 못 정해 첫 드래그에 화면이 뒤집힌다.
|
||||
top: [0, 1, TOP_VIEW_TILT],
|
||||
front: [0, 60, 240],
|
||||
side: [240, 60, 0],
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
||||
import { PLYLoader } from "three/examples/jsm/loaders/PLYLoader.js";
|
||||
import { API_BASE_URL } from "@config/config_frontend";
|
||||
import { fetchCachedBytes, fetchCachedJson } from "../A00_Common/b_asset_cache";
|
||||
import { bindCursorPivotControls } from "../B04_PreProcess/B04_PreProcess_UI_Camera";
|
||||
import { bindCursorPivotControls, TOP_VIEW_TILT } from "../B04_PreProcess/B04_PreProcess_UI_Camera";
|
||||
import {
|
||||
createRouteMarkers,
|
||||
modelToScene,
|
||||
@@ -165,7 +165,8 @@ export function createRouteViewer(): RouteViewer {
|
||||
controls.target.set(0, 0, 0);
|
||||
const positions = {
|
||||
iso: [distance, distance, distance],
|
||||
top: [0, distance, 0.001],
|
||||
// 정확히 수직이면 lookAt이 화면 방향을 못 정해 첫 드래그에 화면이 뒤집힌다.
|
||||
top: [0, distance, distance * TOP_VIEW_TILT],
|
||||
front: [0, distance * 0.25, distance],
|
||||
side: [distance, distance * 0.25, 0],
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user