From 4c9097f82f3be236d56917daa4aea311170e20d6 Mon Sep 17 00:00:00 2001 From: xfy Date: Fri, 17 Jul 2026 09:45:56 +0800 Subject: [PATCH] fix(planets): bind orbit radius to disk_outer so planets sit outside the disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Planets were landing on/in the accretion disk. Root cause: orbit radius was r = radius_factor · kerr_isco(χ), keyed off the disk's INNER edge. The disk extends from ISCO out to disk_outer (default 25), so for every spin the planets' radii fell inside the disk's radial span: χ=0: planets r ∈ [4.5, 17.25], disk [3, 25] — all inside χ=1: planets r ∈ [0.75, 2.88], disk [0.5, 25] — all inside, near hole Re-keyed to the disk OUTER edge: r = radius_factor · disk_outer. With the factor now meaning "multiple of disk_outer" (default 1.3, slider 1.1..=2.0, per-planet jitter ±0.25 floored at 1.05), planets stay at r ≈ 32.5 — reliably outside the disk regardless of spin, while still close enough for lensing to curve their images. Changes: - orbit_position: new disk_outer param, r = radius_factor · disk_outer - orbit_system: passes params.disk_outer - spawn_planet_system: jitter range tightened to ±0.25 around the factor, floor 1.05 (was ±0.75 around an ISCO-keyed k, floor 1.0) - params default planet_radius_factor 2.5 → 1.3 (new semantics) - UI: slider 1.5..=5.0 → 1.1..=2.0, label "Radius (× disk outer)", readout shows orbit r and disk_outer instead of ISCO - tests updated for the new signature and r = factor · disk_outer invariant --- src/params.rs | 7 ++++--- src/scene/planets.rs | 42 ++++++++++++++++++++++++------------------ src/ui.rs | 5 ++--- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/params.rs b/src/params.rs index 78267df..bfa21b6 100644 --- a/src/params.rs +++ b/src/params.rs @@ -212,11 +212,12 @@ impl Default for BlackHoleParams { // at 2× by default — enough to soften the rings — with High (4×) // available in the UI for a fully smooth Gargantua look. aa_quality: if cfg!(target_arch = "wasm32") { AaQuality::Off } else { AaQuality::Low }, - // Planets: 6 颗, k=2.5 (r ∈ [1.25, 7.5], 横跨强场区), - // 种子 42, time_scale 50× (Ω_LT 在 r=8 转一圈 ~25 min, 放大才可见). + // Planets: 6 颗. 半径基准是 disk_outer (默认 25), factor 1.3 → + // r ≈ 32.5, 稳定在盘外 (盘范围 [isco, 25]). 早期用 ISCO 作基准会让 + // 行星落进盘的径向范围. 种子 42, time_scale 50× (进动慢, 需放大). planets_enabled: true, planet_count_target: 6, - planet_radius_factor: 2.5, + planet_radius_factor: 1.3, planet_seed: 42, planet_time_scale: 50.0, } diff --git a/src/scene/planets.rs b/src/scene/planets.rs index f56058d..6b45d91 100644 --- a/src/scene/planets.rs +++ b/src/scene/planets.rs @@ -33,17 +33,21 @@ pub struct OrbitParams { #[derive(Resource, Default)] pub struct PlanetSystemDirty(pub bool); -/// 由轨道根数 + 当前 (模拟)时间 + 自旋, 计算行星世界空间位置. +/// 由轨道根数 + 当前 (模拟)时间 + 自旋 + 盘外半径, 计算行星世界空间位置. /// 纯函数: 无 Bevy 依赖, 可独立测试. /// /// 物理: -/// - r = k · kerr_isco(χ) +/// - r = radius_factor · disk_outer (绑定盘外缘, 保证行星在盘外) /// - Ω_φ = 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); +/// +/// 注: 半径基准是 disk_outer 而非 kerr_isco(χ). 早期版本用 ISCO 作基准, +/// 但盘从 ISCO 向外延伸到 disk_outer (默认 25), 用 ISCO 会让行星落在 +/// 盘的径向范围内 (与盘重叠/被盘淹没). disk_outer 保证行星永远在盘外. +pub fn orbit_position(orbit: &OrbitParams, t: f32, chi: f32, disk_outer: f32) -> Vec3 { + let r = orbit.radius_factor * disk_outer; let omega_phi = crate::physics::kerr_orbital_frequency(r, chi); let omega_lt = crate::physics::kerr_nodal_precession(r, chi); @@ -86,7 +90,7 @@ pub fn orbit_system( // time_scale 放大模拟时间, 让慢进动在合理时间内可见 (Ω_LT 在 r=8 转一圈 ~25 min) let t = time.elapsed_secs() * params.planet_time_scale; for (orbit, mut planet) in &mut query { - planet.center = orbit_position(orbit, t, params.spin); + planet.center = orbit_position(orbit, t, params.spin, params.disk_outer); } } @@ -172,10 +176,11 @@ pub fn spawn_planet_system( let inclination = rng.gen_range(0.0..PI); let longitude = rng.gen_range(0.0..TAU); let phase = rng.gen_range(0.0..TAU); - // 半径因子以 UI 的 planet_radius_factor 为中心 ±0.75 散布, 既让滑条 - // 真正控制轨道尺度, 又保留 per-planet 半径多样性 (避免全同半径). - // 滑条范围 1.5..=5.0, 散布后实际 k ∈ [0.75, 5.75], 钳到 ISCO 外. - let radius_factor = (params.planet_radius_factor + rng.gen_range(-0.75..0.75)).max(1.0); + // 半径因子 = planet_radius_factor ± 0.25 (per-planet 散布). + // orbit_position 用 r = radius_factor · disk_outer, 所以这个因子是 + // "盘外缘的倍数": 1.0 = 紧贴盘外, 1.5 = 盘外 50%. 默认滑条 1.3 让行星 + // 稳定在盘外, 散布保留 per-planet 半径多样性. 钳到 1.05 以上保证始终盘外. + let radius_factor = (params.planet_radius_factor + rng.gen_range(-0.25..0.25)).max(1.05); // 颜色: 暖色行星 (橙/红/黄系), 避开蓝色 (易与背景星混淆) let hue = rng.gen_range(0.02..0.13); let color = hsv_to_rgb(hue, rng.gen_range(0.5..0.9), rng.gen_range(0.7..1.0)); @@ -219,17 +224,18 @@ mod tests { #[test] fn orbit_position_radius_is_preserved() { - // 不管时间/相位, 行星到原点距离应恒等于 r = k·isco(χ) + // 不管时间/相位, 行星到原点距离应恒等于 r = radius_factor · disk_outer let orbit = OrbitParams { - radius_factor: 2.5, + radius_factor: 1.3, 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); + let disk_outer = 25.0; + let expected_r = 1.3 * disk_outer; for t in [0.0_f32, 1.0, 5.5, 100.0] { - let pos = orbit_position(&orbit, t, chi); + let pos = orbit_position(&orbit, t, chi, disk_outer); let dist = pos.length(); assert!( (dist - expected_r).abs() < 1e-4, @@ -243,13 +249,13 @@ mod tests { fn orbit_position_zero_spin_keeps_equatorial_plane() { // χ=0: 无进动, 倾角 0 (赤道面) 的行星应严格在 y=0 平面 let orbit = OrbitParams { - radius_factor: 3.0, + radius_factor: 1.3, 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); + let pos = orbit_position(&orbit, t, 0.0, 25.0); assert!(pos.y.abs() < 1e-5, "χ=0 equatorial orbit should stay in y=0 plane at t={}", t); } } @@ -258,13 +264,13 @@ mod tests { fn orbit_position_advances_with_time() { // 不同时间应给不同位置 (除非极端巧合) let orbit = OrbitParams { - radius_factor: 3.0, + radius_factor: 1.3, 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); + let p0 = orbit_position(&orbit, 0.0, 0.5, 25.0); + let p1 = orbit_position(&orbit, 1.0, 0.5, 25.0); assert!((p0 - p1).length() > 0.01, "planet should move over time"); } } diff --git a/src/ui.rs b/src/ui.rs index d5ea050..83cf5f7 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -69,9 +69,8 @@ pub fn ui_system( ); ui.checkbox(&mut params.planets_enabled, "Enable"); ui.add(egui::Slider::new(&mut params.planet_count_target, 0..=8).text("Count")); - ui.add(egui::Slider::new(&mut params.planet_radius_factor, 1.5..=5.0).text("Radius factor k")); - let isco = crate::physics::kerr_isco(params.spin); - ui.label(format!("ISCO: {:.3} → r = {:.3}", isco, params.planet_radius_factor * isco)); + ui.add(egui::Slider::new(&mut params.planet_radius_factor, 1.1..=2.0).text("Radius (× disk outer)")); + ui.label(format!("Orbit r = {:.2} (disk outer: {:.1})", params.planet_radius_factor * params.disk_outer, params.disk_outer)); ui.add(egui::Slider::new(&mut params.planet_seed, 0..=1000).text("Seed")); ui.add(egui::Slider::new(&mut params.planet_time_scale, 1.0..=200.0).text("Time scale")); let curr = (