diff --git a/src/components/map/ThreeMap.vue b/src/components/map/ThreeMap.vue index 4b92f95..583f3fa 100644 --- a/src/components/map/ThreeMap.vue +++ b/src/components/map/ThreeMap.vue @@ -66,6 +66,12 @@ import type { type ViewMode = 'overview' | 'floor' | 'multi' type CameraPreset = 'top' | 'oblique' type TouchGestureMode = 'orbit' | 'pan' +interface CameraFitOptions { + distanceFactor?: number + targetOffsetRatio?: THREE.Vector3 + screenOffsetRatio?: THREE.Vector2 + up?: THREE.Vector3 +} type FloorIndexItem = GuideModelFloorAsset @@ -1630,7 +1636,12 @@ const disableAutoSwitchTemporarily = (durationMs: number) => { }, durationMs) } -const setCameraView = (center: THREE.Vector3, maxDim: number, direction: THREE.Vector3, distanceFactor = 0.58) => { +const setCameraView = ( + center: THREE.Vector3, + maxDim: number, + direction: THREE.Vector3, + options: CameraFitOptions = {} +) => { if (!camera || !controls) return isProgrammaticCameraChange = true @@ -1639,15 +1650,37 @@ const setCameraView = (center: THREE.Vector3, maxDim: number, direction: THREE.V } const fov = camera.fov * (Math.PI / 180) + const distanceFactor = options.distanceFactor ?? 0.58 + const targetOffset = options.targetOffsetRatio + ? new THREE.Vector3( + options.targetOffsetRatio.x * maxDim, + options.targetOffsetRatio.y * maxDim, + options.targetOffsetRatio.z * maxDim + ) + : new THREE.Vector3() + const targetCenter = center.clone().add(targetOffset) const distance = Math.abs(maxDim / Math.sin(fov / 2)) * distanceFactor try { camera.near = Math.max(0.1, maxDim / 1000) camera.far = Math.max(10000, maxDim * 20) - camera.position.copy(center).add(direction.normalize().multiplyScalar(distance)) + camera.up.copy(options.up || new THREE.Vector3(0, 1, 0)) + camera.position.copy(targetCenter).add(direction.normalize().multiplyScalar(distance)) camera.updateProjectionMatrix() - controls.target.copy(center) + controls.target.copy(targetCenter) + if (options.screenOffsetRatio) { + const viewDirection = controls.target.clone().sub(camera.position).normalize() + const cameraRight = new THREE.Vector3().crossVectors(viewDirection, camera.up).normalize() + const viewHeight = 2 * distance * Math.tan(fov / 2) + const viewWidth = viewHeight * camera.aspect + const screenOffset = cameraRight + .multiplyScalar(options.screenOffsetRatio.x * viewWidth) + .add(camera.up.clone().multiplyScalar(options.screenOffsetRatio.y * viewHeight)) + + camera.position.add(screenOffset) + controls.target.add(screenOffset) + } controls.minDistance = Math.max(2, maxDim * 0.08) controls.maxDistance = Math.max(20, maxDim * 8) controls.update() @@ -1668,9 +1701,18 @@ const fitCameraToObject = (object: THREE.Object3D, preset: CameraPreset = 'obliq const maxDim = Math.max(size.x, size.y, size.z, 1) const direction = preset === 'top' ? new THREE.Vector3(0.02, 1, 0.02) - : new THREE.Vector3(0.85, 0.62, 1) + : activeView.value === 'overview' + ? new THREE.Vector3(-0.16, 1, 0.18) + : new THREE.Vector3(0.85, 0.62, 1) + const overviewFitOptions: CameraFitOptions = activeView.value === 'overview' + ? { + distanceFactor: 1.1, + screenOffsetRatio: new THREE.Vector2(-0.085, 0.065), + up: new THREE.Vector3(-0.82, 0, -0.57) + } + : {} - setCameraView(center, maxDim, direction) + setCameraView(center, maxDim, direction, overviewFitOptions) } const resetCamera = () => {