14 KiB
14 KiB
可视化问题分析与解决方案
问题 1: 探测器/月球的视觉偏移导致距离失真
🚨 问题描述
当前实现中,为了避免探测器和月球与行星重合,使用了 renderPosition.ts 中的智能偏移逻辑:
// renderPosition.ts:116-127
if (realDistance < 0.01) {
// 月球或表面探测器 - 固定偏移
visualOffset = planetVisualRadius + 2.0; // 距离表面 2 个单位
} else if (realDistance < 0.05) {
visualOffset = planetVisualRadius + 3.0;
} else {
visualOffset = planetVisualRadius + 4.0;
}
问题:
- ❌ 月球真实距离地球 0.0026 AU(38万公里)
- ❌ 但在视觉上被推到距离地球表面 2 个缩放单位
- ❌ 用户无法判断月球与太阳的真实距离关系
- ❌ 探测器的真实轨道距离完全失真
影响范围:
- 月球(Earth's Moon)
- 火星探测器(Perseverance, Curiosity)
- 木星探测器(Juno)
- 土星探测器(Cassini)
问题 2: 矮行星轨道数据加载量过大
🚨 问题描述
当前 DwarfPlanetOrbits.tsx 的实现:
// DwarfPlanetOrbits.tsx:61-72
const startDate = new Date('2020-01-01');
const endDate = new Date('2030-01-01');
const response = await fetch(
`http://localhost:8000/api/celestial/positions?` +
`body_ids=${bodyIds}&` +
`start_time=${startDate.toISOString()}&` +
`end_time=${endDate.toISOString()}&` +
`step=30d` // 10 年 = 120 个点/天体
);
问题:
- ❌ 矮行星公转周期:冥王星 248 年,阋神星 557 年
- ❌ 10 年数据只能显示公转轨道的 4-18%
- ❌ 需要请求完整轨道周期数据(248-557 年)
- ❌ 数据量巨大:冥王星完整轨道 = 248 年 × 12 月 = 2976 个点
- ❌ 首次加载会触发大量 NASA API 调用
矮行星轨道周期:
| 天体 | 公转周期 | 完整轨道点数(30天/点) | 数据量 |
|---|---|---|---|
| 冥王星 | 248 年 | 2,976 点 | 71 KB |
| 阋神星 | 557 年 | 6,684 点 | 160 KB |
| 妊神星 | 285 年 | 3,420 点 | 82 KB |
| 鸟神星 | 309 年 | 3,708 点 | 89 KB |
| 谷神星 | 4.6 年 | 55 点 | 1.3 KB |
总数据量:~403 KB(单次请求)
解决方案对比
方案 1: 视觉偏移 + 真实距离提示 ⭐⭐⭐
策略:保持当前视觉偏移,但在 UI 上明确标注真实距离。
实现:
- 在天体详情卡片中显示真实距离:
// ProbeList.tsx 或 CelestialBody 详情
{hasOffset && (
<div className="text-yellow-400 text-xs">
<span>⚠️ 视觉位置已调整便于观察</span>
<span>真实距离: {realDistance.toFixed(4)} AU</span>
<span>约 {(realDistance * 149597870.7).toFixed(0)} 千米</span>
</div>
)}
- 添加真实轨道线(虚线):
// 在 Probe.tsx 中添加真实轨道路径
{hasOffset && (
<Line
points={[
new Vector3(realPos.x, realPos.y, realPos.z), // 真实位置
new Vector3(visualPos.x, visualPos.y, visualPos.z) // 视觉位置
]}
color="yellow"
lineWidth={1}
dashed
dashSize={0.1}
gapSize={0.05}
/>
)}
优点:
- ✅ 保持当前的视觉清晰度
- ✅ 通过文字和虚线提示真实位置
- ✅ 实现简单,改动小
缺点:
- ⚠️ 用户仍然无法直观看到真实距离
- ⚠️ 需要额外的 UI 提示
方案 2: 移除视觉偏移,优化缩放算法 ⭐⭐⭐⭐⭐(推荐)
策略:改进距离缩放算法,让近距离天体也能清晰显示,无需偏移。
核心思路:
- 在 极近距离(< 0.01 AU)使用对数缩放
- 让月球和探测器保持真实方向,但有足够的视觉间隔
实现:
// scaleDistance.ts - 新增超近距离缩放
export function scaleDistance(distanceInAU: number): number {
// Ultra-close region (< 0.001 AU): extreme expansion for moons/probes
if (distanceInAU < 0.001) {
// 对数缩放:0.0001 AU → 0.1, 0.001 AU → 0.5
return 0.1 + Math.log10(distanceInAU + 0.0001) * 0.4;
}
// Very close region (0.001-0.01 AU): strong expansion
if (distanceInAU < 0.01) {
// 0.001 AU → 0.5, 0.01 AU → 1.5
return 0.5 + (distanceInAU - 0.001) * 100;
}
// Close region (0.01-0.1 AU): moderate expansion
if (distanceInAU < 0.1) {
return 1.5 + (distanceInAU - 0.01) * 20;
}
// Inner solar system (0.1-2 AU): expand by 3x
if (distanceInAU < 2) {
return 3.3 + (distanceInAU - 0.1) * 3;
}
// Middle region (2-10 AU): normal scale
if (distanceInAU < 10) {
return 9 + (distanceInAU - 2) * 1.5;
}
// Outer solar system (10-50 AU): compressed
if (distanceInAU < 50) {
return 21 + (distanceInAU - 10) * 0.5;
}
// Very far (> 50 AU): heavily compressed
return 41 + (distanceInAU - 50) * 0.2;
}
修改 renderPosition.ts:
// 移除视觉偏移逻辑,直接使用缩放位置
export function calculateRenderPosition(
body: CelestialBody,
allBodies: CelestialBody[]
): { x: number; y: number; z: number } {
const pos = body.positions[0];
if (!pos) {
return { x: 0, y: 0, z: 0 };
}
// 直接使用改进的缩放算法,无需偏移
const scaled = scalePosition(pos.x, pos.y, pos.z);
return { x: scaled.x, y: scaled.y, z: scaled.z };
}
效果对比:
| 天体 | 真实距离 | 旧缩放 | 新缩放 | 改进 |
|---|---|---|---|---|
| 月球 | 0.0026 AU | 0.0078 | 0.76 | 98倍 |
| 火星探测器 | ~1.5 AU | 4.5 | 7.5 | 更清晰 |
| 地球 | 1.0 AU | 3.0 | 5.7 | 更合理 |
优点:
- ✅ 保持真实的空间关系
- ✅ 月球和探测器仍然可见(足够大)
- ✅ 用户可以直观理解距离
- ✅ 无需 UI 提示
缺点:
- ⚠️ 需要调整缩放参数,可能需要多次调试
方案 3: 双模式切换(真实模式 vs 演示模式)⭐⭐⭐⭐
策略:提供两种显示模式,用户可以切换。
实现:
// App.tsx
const [visualMode, setVisualMode] = useState<'realistic' | 'demo'>('demo');
// Header.tsx 添加切换按钮
<button onClick={() => setVisualMode(mode === 'realistic' ? 'demo' : 'realistic')}>
{visualMode === 'realistic' ? '真实距离' : '演示模式'}
</button>
// renderPosition.ts
export function calculateRenderPosition(
body: CelestialBody,
allBodies: CelestialBody[],
mode: 'realistic' | 'demo'
): Position {
if (mode === 'realistic') {
// 使用改进的缩放,无偏移
return scalePosition(pos.x, pos.y, pos.z);
} else {
// 使用视觉偏移
return calculateDemoPosition(body, allBodies);
}
}
优点:
- ✅ 灵活性最高
- ✅ 满足不同用户需求
- ✅ 教育价值高
缺点:
- ⚠️ 实现复杂度较高
- ⚠️ 需要维护两套逻辑
矮行星轨道问题解决方案
方案 A: 预计算并存储完整轨道数据 ⭐⭐⭐⭐⭐(推荐)
策略:按照 ORBIT_OPTIMIZATION.md 的方案 1A 实现。
实施步骤:
-
创建 orbits 表(已在 ORBIT_OPTIMIZATION.md 中定义)
-
后端管理接口生成轨道:
# app/api/routes.py
@router.post("/admin/orbits/generate")
async def generate_dwarf_planet_orbits(db: AsyncSession = Depends(get_db)):
"""为矮行星生成完整轨道数据"""
dwarf_planets = await celestial_body_service.get_bodies_by_type(db, "dwarf_planet")
orbital_periods = {
"999": 248, # 冥王星
"136199": 557, # 阋神星
"136108": 285, # 妊神星
"136472": 309, # 鸟神星
"1": 4.6, # 谷神星
}
for planet in dwarf_planets:
period_years = orbital_periods.get(planet.id, 250)
# 计算采样点数:完整周期,每 30 天一个点
num_points = min(int(period_years * 365 / 30), 1000) # 最多 1000 点
# 查询 NASA Horizons
start = datetime.utcnow()
end = start + timedelta(days=period_years * 365)
step_days = int(period_years * 365 / num_points)
positions = await horizons_service.get_body_positions(
planet.id,
start,
end,
f"{step_days}d"
)
# 保存到 orbits 表
await orbit_service.save_orbit(
planet.id,
[{"x": p.x, "y": p.y, "z": p.z} for p in positions],
num_points,
period_years * 365
)
return {"message": f"Generated {len(dwarf_planets)} orbits"}
- 前端从 API 读取:
// DwarfPlanetOrbits.tsx - 简化版
useEffect(() => {
const fetchOrbits = async () => {
// 直接从后端读取预存的轨道数据
const response = await fetch('http://localhost:8000/api/celestial/orbits?body_type=dwarf_planet');
const data = await response.json();
const orbitData = data.orbits.map((orbit: any) => ({
bodyId: orbit.body_id,
points: orbit.points.map((p: any) => {
const scaled = scalePosition(p.x, p.y, p.z);
return new THREE.Vector3(scaled.x, scaled.z, scaled.y);
}),
color: orbit.color || getDefaultColor(orbit.body_name)
}));
setOrbits(orbitData);
};
fetchOrbits();
}, []);
优点:
- ✅ 完整准确的轨道
- ✅ 前端加载快(<1秒)
- ✅ 无需实时 NASA API 调用
- ✅ 数据量可接受(总共 ~400 KB)
缺点:
- ⚠️ 需要数据库迁移
- ⚠️ 首次生成需要时间(一次性)
方案 B: 使用数学模拟轨道 ⭐⭐⭐⭐
策略:基于轨道六要素(orbital elements)数学计算。
实现:
// EllipticalOrbit.tsx
interface OrbitalElements {
a: number; // 半长轴 (AU)
e: number; // 离心率
i: number; // 轨道倾角 (度)
omega: number; // 升交点黄经 (度)
w: number; // 近日点幅角 (度)
M0: number; // 平近点角 (度)
period: number; // 轨道周期 (天)
}
// 冥王星轨道要素(来自 NASA JPL)
const PLUTO_ELEMENTS: OrbitalElements = {
a: 39.48,
e: 0.2488,
i: 17.16,
omega: 110.30,
w: 113.77,
M0: 14.53,
period: 90560
};
function generateOrbitPoints(elements: OrbitalElements, numPoints = 360): Vector3[] {
const points: Vector3[] = [];
for (let i = 0; i <= numPoints; i++) {
const M = (i / numPoints) * 2 * Math.PI; // 平近点角
// 求解开普勒方程得到偏近点角 E
let E = M;
for (let j = 0; j < 10; j++) {
E = M + elements.e * Math.sin(E);
}
// 计算真近点角
const v = 2 * Math.atan(
Math.sqrt((1 + elements.e) / (1 - elements.e)) * Math.tan(E / 2)
);
// 计算轨道平面坐标
const r = elements.a * (1 - elements.e * Math.cos(E));
const x_orb = r * Math.cos(v);
const y_orb = r * Math.sin(v);
// 旋转到黄道坐标系
const point = rotateToEcliptic(x_orb, y_orb, 0, elements);
const scaled = scalePosition(point.x, point.y, point.z);
points.push(new Vector3(scaled.x, scaled.z, scaled.y));
}
return points;
}
轨道要素来源:
- NASA JPL Small-Body Database: https://ssd.jpl.nasa.gov/sbdb.cgi
- 可以从后端
celestial_bodies表的orbital_elements字段读取
优点:
- ✅ 不需要网络请求
- ✅ 瞬时生成
- ✅ 数学上准确
- ✅ 可以显示任意时间的轨道
缺点:
- ⚠️ 实现复杂(轨道力学)
- ⚠️ 不考虑摄动(其他行星引力影响)
- ⚠️ 需要获取和验证轨道要素
方案 C: 混合方案 - 谷神星用真实数据,其他用模拟 ⭐⭐⭐
策略:
- 谷神星(4.6 年周期):使用真实数据(55 点,1.3 KB)
- 冥王星、阋神星等(>200 年周期):使用数学模拟
理由:
- 谷神星周期短,数据量小
- 长周期矮行星用数学模拟足够准确
实现:
// DwarfPlanetOrbits.tsx
const fetchOrbits = async () => {
// 只请求谷神星(Ceres)的真实数据
const ceresOrbit = await fetchRealOrbit('1', 5); // 5 年数据
// 其他矮行星用数学模拟
const plutoOrbit = generateOrbitPoints(PLUTO_ELEMENTS);
const erisOrbit = generateOrbitPoints(ERIS_ELEMENTS);
// ...
};
优点:
- ✅ 平衡准确性和性能
- ✅ 减少数据加载
- ✅ 关键天体(谷神星)使用真实数据
缺点:
- ⚠️ 需要维护两套逻辑
推荐实施方案
问题 1(探测器偏移):方案 2 - 优化缩放算法
- 实施优先级:高
- 预计工时:2-3 小时
- 风险:低(可以逐步调整参数)
问题 2(矮行星轨道):方案 A - 预计算轨道数据
- 实施优先级:中
- 预计工时:4-6 小时(包括数据库迁移)
- 风险:低(数据生成是一次性的)
备选方案:如果时间紧张,可以先用 方案 B(数学模拟) 作为临时解决方案。
实施步骤(建议顺序)
第一阶段:修复探测器偏移问题
- 修改
scaleDistance.ts,添加超近距离缩放 - 简化
renderPosition.ts,移除偏移逻辑 - 测试月球、火星探测器等的显示效果
- 微调缩放参数
第二阶段:优化矮行星轨道
- 创建
orbits表(数据库迁移) - 实现后端轨道生成 API
- 在管理后台添加"生成轨道"按钮
- 修改
DwarfPlanetOrbits.tsx从 API 读取 - 首次生成所有矮行星轨道数据
后续优化建议
-
添加缩放级别指示器:
- 显示当前视图的缩放比例
- "内太阳系视图(0-2 AU,放大 3x)"
-
添加距离标尺:
- 在场景中显示距离参考线
- "1 AU = X 屏幕单位"
-
轨道数据自动更新:
- 定期(每月)重新生成轨道数据
- 保持数据时效性
文档版本: v1.0 创建时间: 2025-11-29 相关文件:
frontend/src/utils/scaleDistance.tsfrontend/src/utils/renderPosition.tsfrontend/src/components/DwarfPlanetOrbits.tsxORBIT_OPTIMIZATION.md