cosmo/VISUALIZATION_ISSUES.md

14 KiB
Raw Blame History

可视化问题分析与解决方案

问题 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 AU38万公里
  • 但在视觉上被推到距离地球表面 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 上明确标注真实距离。

实现

  1. 在天体详情卡片中显示真实距离
// 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>
)}
  1. 添加真实轨道线(虚线)
// 在 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 实现。

实施步骤

  1. 创建 orbits 表(已在 ORBIT_OPTIMIZATION.md 中定义)

  2. 后端管理接口生成轨道

# 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"}
  1. 前端从 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;
}

轨道要素来源

优点

  • 不需要网络请求
  • 瞬时生成
  • 数学上准确
  • 可以显示任意时间的轨道

缺点

  • ⚠️ 实现复杂(轨道力学)
  • ⚠️ 不考虑摄动(其他行星引力影响)
  • ⚠️ 需要获取和验证轨道要素

方案 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数学模拟 作为临时解决方案。


实施步骤(建议顺序)

第一阶段:修复探测器偏移问题

  1. 修改 scaleDistance.ts,添加超近距离缩放
  2. 简化 renderPosition.ts,移除偏移逻辑
  3. 测试月球、火星探测器等的显示效果
  4. 微调缩放参数

第二阶段:优化矮行星轨道

  1. 创建 orbits 表(数据库迁移)
  2. 实现后端轨道生成 API
  3. 在管理后台添加"生成轨道"按钮
  4. 修改 DwarfPlanetOrbits.tsx 从 API 读取
  5. 首次生成所有矮行星轨道数据

后续优化建议

  1. 添加缩放级别指示器

    • 显示当前视图的缩放比例
    • "内太阳系视图0-2 AU放大 3x"
  2. 添加距离标尺

    • 在场景中显示距离参考线
    • "1 AU = X 屏幕单位"
  3. 轨道数据自动更新

    • 定期(每月)重新生成轨道数据
    • 保持数据时效性

文档版本: v1.0 创建时间: 2025-11-29 相关文件:

  • frontend/src/utils/scaleDistance.ts
  • frontend/src/utils/renderPosition.ts
  • frontend/src/components/DwarfPlanetOrbits.tsx
  • ORBIT_OPTIMIZATION.md