# Cosmo 相机聚焦算法文档 本文档详细说明了Cosmo项目中两种视图模式下的相机聚焦算法实现。 --- ## 目录 - [1. 概述](#1-概述) - [2. 太阳系模式(Solar System Mode)](#2-太阳系模式solar-system-mode) - [3. 银河系模式(Galaxy Mode)](#3-银河系模式galaxy-mode) - [4. 算法对比](#4-算法对比) - [5. 关键参数调优建议](#5-关键参数调优建议) --- ## 1. 概述 Cosmo项目包含两种视图模式,每种模式都有独特的相机聚焦算法: - **太阳系模式**:用于观察太阳系内的天体(行星、卫星、探测器等) - **银河系模式**:用于观察恒星际空间中的恒星系统和系外行星 两种模式的聚焦算法设计理念不同,以适应各自的尺度和用户体验需求。 --- ## 2. 太阳系模式(Solar System Mode) ### 2.1 实现位置 **文件**: `/frontend/src/components/CameraController.tsx` **组件**: `CameraController` ### 2.2 算法原理 太阳系模式采用**固定偏移量**的聚焦策略,相机位置相对于目标天体有固定的空间偏移。 ### 2.3 核心算法 ```typescript // 1. 获取目标天体的渲染位置 const renderPos = calculateRenderPosition(focusTarget, allBodies); const currentTargetPos = new Vector3(renderPos.x, renderPos.z, renderPos.y); // 2. 计算目标到原点的距离 const pos = focusTarget.positions[0]; const distance = Math.sqrt(pos.x ** 2 + pos.y ** 2 + pos.z ** 2); // 3. 根据天体类型确定偏移量 let offset: number; let heightMultiplier = 1; let sideMultiplier = 1; if (focusTarget.type === 'planet') { offset = 4; heightMultiplier = 1.5; sideMultiplier = 1; } else if (focusTarget.type === 'probe') { if (parentInfo) { // 探测器在行星附近 offset = 3; heightMultiplier = 0.8; sideMultiplier = 1.2; } else if (distance < 10) { // 近距离探测器 offset = 5; heightMultiplier = 0.6; sideMultiplier = 1.5; } else if (distance > 50) { // 远距离探测器 offset = 4; heightMultiplier = 0.8; sideMultiplier = 1; } else { // 中距离探测器 offset = 6; heightMultiplier = 0.8; sideMultiplier = 1.2; } } else { // 其他天体类型 offset = 10; heightMultiplier = 1; sideMultiplier = 1; } // 4. 计算相机目标位置(简单的坐标偏移) targetPosition.current.set( currentTargetPos.x + (offset * sideMultiplier), currentTargetPos.y + (offset * heightMultiplier), currentTargetPos.z + offset ); ``` ### 2.4 动画实现 使用帧动画进行平滑过渡: ```typescript // 使用 easing 函数实现平滑动画 const eased = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; // 线性插值相机位置 camera.position.lerpVectors(startPosition.current, targetPosition.current, eased); // 相机始终看向目标 camera.lookAt(renderPos.x, renderPos.z, renderPos.y); ``` ### 2.5 特点 - ✅ **固定偏移量**:相机距离目标的偏移量是预设的常量 - ✅ **类型感知**:不同类型的天体使用不同的偏移参数 - ✅ **上下文感知**:探测器根据其位置(近行星、近太阳、远距离)调整相机距离 - ✅ **简单直观**:适合太阳系内的小尺度观察 - ✅ **动画平滑**:使用ease-in-out缓动函数 ### 2.6 偏移量参数表 | 天体类型 | offset | heightMultiplier | sideMultiplier | 相机高度 | 相机侧向距离 | 相机深度距离 | |---------|--------|------------------|----------------|----------|-------------|-------------| | 行星 (planet) | 4 | 1.5 | 1 | 6 | 4 | 4 | | 探测器-近行星 (probe near planet) | 3 | 0.8 | 1.2 | 2.4 | 3.6 | 3 | | 探测器-近距离 (probe < 10 AU) | 5 | 0.6 | 1.5 | 3 | 7.5 | 5 | | 探测器-远距离 (probe > 50 AU) | 4 | 0.8 | 1 | 3.2 | 4 | 4 | | 探测器-中距离 (probe 10-50 AU) | 6 | 0.8 | 1.2 | 4.8 | 7.2 | 6 | | 其他天体 | 10 | 1 | 1 | 10 | 10 | 10 | **计算公式**: ``` 相机X = 目标X + offset × sideMultiplier 相机Y = 目标Y + offset × heightMultiplier 相机Z = 目标Z + offset ``` --- ## 3. 银河系模式(Galaxy Mode) ### 3.1 实现位置 **文件**: `/frontend/src/components/GalaxyScene.tsx` **组件**: `CameraAnimator` ### 3.2 算法原理 银河系模式采用**向量方向聚焦**策略,相机沿着"太阳→目标恒星"的方向向量定位,确保: 1. 目标恒星始终在屏幕正前方 2. 相机在目标的远端(远离太阳的一侧) 3. 距离根据目标的远近动态调整 ### 3.3 核心算法 ```typescript // 1. 计算目标恒星到太阳的距离 const targetDistanceFromSun = Math.sqrt(x * x + y * y + z * z); // 2. 动态计算相机拉远距离 const basePullBack = 150; const pullBackDistance = targetDistanceFromSun < 500 ? basePullBack : basePullBack + (targetDistanceFromSun - 500) * 0.08; // 3. 计算方向向量(从太阳指向目标恒星,已归一化) const dirX = x / targetDistanceFromSun; const dirY = y / targetDistanceFromSun; const dirZ = z / targetDistanceFromSun; // 4. 计算相机位置:目标位置 + 方向向量 × 拉远距离 // 相机在目标的"后方"(远离太阳的一侧) const cameraX = x + dirX * pullBackDistance; const cameraY = y + dirY * pullBackDistance + 30; // 额外的垂直偏移 const cameraZ = z + dirZ * pullBackDistance; ``` ### 3.4 图解说明 ``` 相机位置 ↓ 太阳 (0,0,0) ----方向向量----> 目标恒星 ------拉远距离-----> 📷 Origin Target Star (x,y,z) (x+dirX×d, y+dirY×d+30, z+dirZ×d) 相机看向目标 ← ``` **关键点**: - 相机位置 = `目标位置 + 方向向量 × pullBackDistance` - **不是**:`目标位置 - 方向向量 × pullBackDistance`(这会把相机放在太阳和目标之间,导致聚焦错误) ### 3.5 动画实现 ```typescript // 使用 easeInOutCubic 缓动函数 const eased = progress < 0.5 ? 4 * progress * progress * progress : 1 - Math.pow(-2 * progress + 2, 3) / 2; // 插值相机位置 camera.position.x = startPos.x + (cameraX - startPos.x) * eased; camera.position.y = startPos.y + (cameraY - startPos.y) * eased; camera.position.z = startPos.z + (cameraZ - startPos.z) * eased; // 插值 OrbitControls 的目标点 controls.target.x = startTarget.x + (x - startTarget.x) * eased; controls.target.y = startTarget.y + (y - startTarget.y) * eased; controls.target.z = startTarget.z + (z - startTarget.z) * eased; controls.update(); ``` ### 3.6 特点 - ✅ **方向向量驱动**:基于太阳→目标的方向计算相机位置 - ✅ **动态距离**:根据目标距离自动调整拉远距离 - ✅ **正确定位**:相机在目标的远端,确保目标在屏幕正前方 - ✅ **尺度感知**:近距离恒星和远距离恒星有明显的视觉差异 - ✅ **平滑过渡**:同时插值相机位置和OrbitControls的target ### 3.7 距离计算公式 | 目标距离 (AU单位) | 拉远距离计算 | 示例 | |------------------|------------|------| | < 500 | 150 (固定) | 比邻星系统 (~130 AU):拉远150单位 | | ≥ 500 | 150 + (distance - 500) × 0.08 | 距离2000 AU的系统:拉远150 + 1500×0.08 = 270单位 | | ≥ 500 | 150 + (distance - 500) × 0.08 | 距离5000 AU的系统:拉远150 + 4500×0.08 = 510单位 | **公式**: ``` pullBackDistance = distance < 500 ? 150 : 150 + (distance - 500) × 0.08 ``` ### 3.8 坐标系说明 在银河系模式中,使用的坐标系统: - **X轴**:指向银道坐标系的X方向 - **Y轴**:垂直于银道平面(向上为正) - **Z轴**:指向银道坐标系的Z方向 - **原点**:太阳系(Solar System) - **单位**:秒差距(Parsec)× 100(渲染缩放) **坐标转换**: ```typescript // 数据库中的坐标(秒差距) const db_x = star.position_x; // 单位: pc const db_y = star.position_y; // 单位: pc const db_z = star.position_z; // 单位: pc // 渲染坐标(Three.js场景坐标,SCALE=100) const render_x = db_x * 100; const render_y = db_y * 100; const render_z = db_z * 100; ``` --- ## 4. 算法对比 | 特性 | 太阳系模式 | 银河系模式 | |------|----------|----------| | **聚焦策略** | 固定偏移量 | 方向向量 + 动态距离 | | **相机定位方式** | 目标 + 常量偏移 | 目标 + 方向 × 动态距离 | | **尺度范围** | 0.1 - 100 AU | 100 - 5000+ AU (pc级别) | | **距离感** | 偏移量固定,距离感弱 | 动态调整,距离感强 | | **类型感知** | 强(根据天体类型调整) | 无(所有恒星系统相同策略) | | **计算复杂度** | 低(简单加法) | 中(向量计算 + 归一化) | | **缓动函数** | ease-in-out (quadratic) | easeInOutCubic | | **动画时长** | 由帧率和速度参数决定 | 固定2.5秒 | | **适用场景** | 小尺度、多类型天体 | 大尺度、单一类型(恒星系统) | --- ## 5. 关键参数调优建议 ### 5.1 太阳系模式 如果需要调整相机距离: ```typescript // 在 CameraController.tsx 中修改 offset 值 if (focusTarget.type === 'planet') { offset = 4; // 增大此值会让相机离行星更远 heightMultiplier = 1.5; // 增大此值会增加相机的高度 sideMultiplier = 1; // 增大此值会增加相机的侧向距离 } ``` **建议**: - 小天体(卫星、小行星):offset 2-5 - 中等天体(行星):offset 4-8 - 大天体(木星、土星):offset 8-15 - 探测器:offset 3-6 ### 5.2 银河系模式 如果需要调整相机距离: ```typescript // 在 GalaxyScene.tsx 中修改 basePullBack 和系数 const basePullBack = 150; // 基础拉远距离(单位:AU × 100) const pullBackDistance = targetDistanceFromSun < 500 ? basePullBack : basePullBack + (targetDistanceFromSun - 500) * 0.08; // 0.08 是距离系数 ``` **建议**: - 近距离恒星(< 500单位):basePullBack = 120-180 - 距离系数:0.05-0.12(越大,远距离恒星拉得越远) - 垂直偏移:20-50(增加俯视角度) ### 5.3 动画速度调整 **太阳系模式**: ```typescript animationProgress.current += delta * 0.8; // 增大此值会加快动画 ``` **银河系模式**: ```typescript const duration = 2500; // 增大此值会减慢动画(单位:毫秒) ``` --- ## 6. 常见问题与解决方案 ### Q1: 银河系模式聚焦后看不到目标恒星? **原因**:相机距离目标太近,或者相机定位在目标和太阳之间。 **检查**: ```typescript // 确保使用的是加法,不是减法 const cameraX = x + dirX * pullBackDistance; // ✅ 正确 const cameraX = x - dirX * pullBackDistance; // ❌ 错误,会把相机放在中间 ``` ### Q2: 太阳系模式下相机离探测器太远? **解决**: ```typescript // 减小探测器的 offset 值 if (focusTarget.type === 'probe') { offset = 3; // 从 6 减小到 3 } ``` ### Q3: 银河系模式下远距离恒星聚焦后太小? **解决**: ```typescript // 减小距离系数,让远距离恒星的相机不要拉得太远 const pullBackDistance = targetDistanceFromSun < 500 ? basePullBack : basePullBack + (targetDistanceFromSun - 500) * 0.05; // 从0.08降到0.05 ``` ### Q4: 动画过渡不够平滑? **解决**: ```typescript // 太阳系模式:减小动画速度 animationProgress.current += delta * 0.5; // 从0.8降到0.5 // 银河系模式:增加动画时长 const duration = 3500; // 从2500增加到3500ms ``` --- ## 7. 版本历史 | 版本 | 日期 | 修改内容 | |-----|------|---------| | 1.0 | 2025-12-06 | 初始版本,记录太阳系模式和银河系模式的聚焦算法 | --- ## 8. 参考资料 - Three.js 文档: https://threejs.org/docs/ - React Three Fiber: https://docs.pmnd.rs/react-three-fiber/ - OrbitControls: https://threejs.org/docs/#examples/en/controls/OrbitControls - Easing Functions: https://easings.net/ --- **文档维护**: Cosmo Development Team **最后更新**: 2025-12-06