393 lines
12 KiB
Markdown
393 lines
12 KiB
Markdown
# 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
|