From 474dfbe723aabe90e10252896fea93232c4986e1 Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 16 Jul 2026 17:55:42 +0800 Subject: [PATCH] feat(planets): OrbitParams component + orbit_position geometry --- src/scene/planets.rs | 111 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/scene/planets.rs b/src/scene/planets.rs index a0b10d8..1417c98 100644 --- a/src/scene/planets.rs +++ b/src/scene/planets.rs @@ -1,3 +1,5 @@ +use std::f32::consts::{PI, TAU}; + use bevy::prelude::*; use bevy::render::storage::ShaderBuffer; @@ -12,6 +14,59 @@ pub struct Planet { pub emissive: bool, } +/// 轨道根数 (不变量, 启动时随机生成, 运行时不变除非 UI 改种子重生). +#[derive(Component, Clone, Copy)] +pub struct OrbitParams { + /// k, 乘到 kerr_isco(χ) 上得实际轨道半径. + pub radius_factor: f32, + /// 轨道面法向与 Y 轴(自旋轴)的夹角 (rad). + pub inclination: f32, + /// 升交点经度 (rad), 决定轨道面在方位上的初始取向. + pub longitude_of_node: f32, + /// 轨道内初始相位 (rad). + pub phase: f32, +} + +/// 由轨道根数 + 当前 (模拟)时间 + 自旋, 计算行星世界空间位置. +/// 纯函数: 无 Bevy 依赖, 可独立测试. +/// +/// 物理: +/// - r = k · kerr_isco(χ) +/// - Ω_φ = kerr_orbital_frequency(r, χ) (轨道角速度) +/// - Ω_LT = kerr_nodal_precession(r, χ) (轨道面绕 Y 轴的进动率) +/// 轨道面基 (u, v) 由 inclination + longitude_of_node 构造, 然后绕 Y 轴 +/// 整体旋转 Ω_LT·t (Lense-Thirring 进动). +pub fn orbit_position(orbit: &OrbitParams, t: f32, chi: f32) -> Vec3 { + let r = orbit.radius_factor * crate::physics::kerr_isco(chi); + let omega_phi = crate::physics::kerr_orbital_frequency(r, chi); + let omega_lt = crate::physics::kerr_nodal_precession(r, chi); + + // 1. 轨道面法向 (Y 轴为极轴的球坐标) + let inc = orbit.inclination; + let lon = orbit.longitude_of_node; + let sin_inc = inc.sin(); + let n = Vec3::new( + sin_inc * lon.cos(), + inc.cos(), + sin_inc * lon.sin(), + ); + // 2. 轨道面内正交基: u 沿升节点方向, v = n × u + // u 在 XZ 平面 (垂直于 Y 轴), 指向升节点 + let u = Vec3::new(-lon.sin(), 0.0, lon.cos()); + let v = n.cross(u); + + // 3. 进动: (u, v) 绕 Y 轴整体旋转 Ω_LT·t + let pa = omega_lt * t; + let cp = pa.cos(); + let sp = pa.sin(); + let u_p = Vec3::new(u.x * cp + u.z * sp, u.y, -u.x * sp + u.z * cp); + let v_p = Vec3::new(v.x * cp + v.z * sp, v.y, -v.x * sp + v.z * cp); + + // 4. 行星在进动后的轨道面内的位置 + let theta = orbit.phase + omega_phi * t; + r * (theta.cos() * u_p + theta.sin() * v_p) +} + /// Collects all Planet components, writes them into the shared MAX_PLANETS-sized /// `ShaderBuffer` that the material already binds, and updates `planet_count`. /// @@ -71,3 +126,59 @@ pub fn spawn_default_planet(mut commands: Commands) { emissive: false, }); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn orbit_position_radius_is_preserved() { + // 不管时间/相位, 行星到原点距离应恒等于 r = k·isco(χ) + let orbit = OrbitParams { + radius_factor: 2.5, + inclination: 0.7, + longitude_of_node: 1.3, + phase: 0.5, + }; + let chi = 0.8; + let expected_r = 2.5 * crate::physics::kerr_isco(chi); + for t in [0.0_f32, 1.0, 5.5, 100.0] { + let pos = orbit_position(&orbit, t, chi); + let dist = pos.length(); + assert!( + (dist - expected_r).abs() < 1e-4, + "t={}: dist {} != r {}", + t, dist, expected_r + ); + } + } + + #[test] + fn orbit_position_zero_spin_keeps_equatorial_plane() { + // χ=0: 无进动, 倾角 0 (赤道面) 的行星应严格在 y=0 平面 + let orbit = OrbitParams { + radius_factor: 3.0, + inclination: 0.0, + longitude_of_node: 0.0, + phase: 0.0, + }; + for t in [0.0_f32, 1.0, 10.0] { + let pos = orbit_position(&orbit, t, 0.0); + assert!(pos.y.abs() < 1e-5, "χ=0 equatorial orbit should stay in y=0 plane at t={}", t); + } + } + + #[test] + fn orbit_position_advances_with_time() { + // 不同时间应给不同位置 (除非极端巧合) + let orbit = OrbitParams { + radius_factor: 3.0, + inclination: 0.5, + longitude_of_node: 0.0, + phase: 0.0, + }; + let p0 = orbit_position(&orbit, 0.0, 0.5); + let p1 = orbit_position(&orbit, 1.0, 0.5); + assert!((p0 - p1).length() > 0.01, "planet should move over time"); + } +}