feat(planets): spawn_planet_system with deterministic ChaCha8Rng seeding

This commit is contained in:
xfy 2026-07-16 18:07:53 +08:00
parent 78a222e685
commit ec69698530
2 changed files with 82 additions and 9 deletions

View File

@ -96,6 +96,7 @@ impl Plugin for BlackHolePlugin {
app.init_resource::<crate::camera::OrbitCamera>() app.init_resource::<crate::camera::OrbitCamera>()
.init_resource::<crate::camera::WantsPointer>() .init_resource::<crate::camera::WantsPointer>()
.init_resource::<crate::params::BlackHoleParams>() .init_resource::<crate::params::BlackHoleParams>()
.init_resource::<crate::scene::planets::PlanetSystemDirty>()
.add_plugins(Material2dPlugin::<BlackHoleMaterial>::default()) .add_plugins(Material2dPlugin::<BlackHoleMaterial>::default())
.add_plugins(Material2dPlugin::<crate::render::material::CompositeMaterial>::default()) .add_plugins(Material2dPlugin::<crate::render::material::CompositeMaterial>::default())
.add_plugins(Material2dPlugin::<crate::render::material::BrightPassMaterial>::default()) .add_plugins(Material2dPlugin::<crate::render::material::BrightPassMaterial>::default())
@ -110,7 +111,7 @@ impl Plugin for BlackHolePlugin {
// Runs in PreStartup, before setup_primary_egui_context_system. // Runs in PreStartup, before setup_primary_egui_context_system.
.add_systems(bevy::prelude::PreStartup, disable_egui_auto_context) .add_systems(bevy::prelude::PreStartup, disable_egui_auto_context)
.add_systems(Startup, spawn_fullscreen_quad) .add_systems(Startup, spawn_fullscreen_quad)
.add_systems(Startup, crate::scene::planets::spawn_default_planet) .add_systems(Startup, crate::scene::planets::spawn_planet_system)
.add_systems( .add_systems(
Update, Update,
( (
@ -120,6 +121,12 @@ impl Plugin for BlackHolePlugin {
nudge_camera, nudge_camera,
), ),
) )
.add_systems(Update, crate::scene::planets::spawn_planet_system)
.add_systems(
Update,
crate::scene::planets::orbit_system
.before(crate::scene::planets::upload_planets),
)
.add_systems(Update, crate::scene::planets::upload_planets) .add_systems(Update, crate::scene::planets::upload_planets)
.add_systems(Update, rebuild_bloom) .add_systems(Update, rebuild_bloom)
// bevy_egui 0.41 requires UI systems to run inside the egui context // bevy_egui 0.41 requires UI systems to run inside the egui context

View File

@ -2,6 +2,8 @@ use std::f32::consts::{PI, TAU};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::storage::ShaderBuffer; use bevy::render::storage::ShaderBuffer;
use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use crate::render::material::{SphereData, MAX_PLANETS}; use crate::render::material::{SphereData, MAX_PLANETS};
@ -27,6 +29,10 @@ pub struct OrbitParams {
pub phase: f32, pub phase: f32,
} }
/// UI 改了种子/count/k 时置位, spawn_planet_system 检测到就重生整个行星系统.
#[derive(Resource, Default)]
pub struct PlanetSystemDirty(pub bool);
/// 由轨道根数 + 当前 (模拟)时间 + 自旋, 计算行星世界空间位置. /// 由轨道根数 + 当前 (模拟)时间 + 自旋, 计算行星世界空间位置.
/// 纯函数: 无 Bevy 依赖, 可独立测试. /// 纯函数: 无 Bevy 依赖, 可独立测试.
/// ///
@ -134,14 +140,74 @@ pub fn upload_planets(
} }
} }
/// Spawns a default test planet behind/above the hole so lensing is visible. /// (重)生成行星系统. 检测 PlanetSystemDirty: 若置位, 先 despawn 所有现有
pub fn spawn_default_planet(mut commands: Commands) { /// (Planet, OrbitParams), 再用 ChaCha8Rng + params.planet_seed 重新随机生成.
commands.spawn(Planet { /// 确定性种子 → 同种子给同布局, 方便调试/截图/测试.
center: Vec3::new(0.0, 2.0, -25.0), ///
radius: 2.0, /// 同时注册在 Startup 和 Update: Startup 首帧靠 "existing 为空" 门控首次生成;
color: Vec3::new(0.3, 0.5, 1.0), /// Update 路径靠 dirty flag 门控重生. 不会每帧重建.
emissive: false, pub fn spawn_planet_system(
}); mut commands: Commands,
params: Res<crate::params::BlackHoleParams>,
mut dirty: ResMut<PlanetSystemDirty>,
existing: Query<Entity, With<Planet>>,
) {
// 只在 dirty, 或现有行星为零 (首帧/Startup) 时重生.
if !dirty.0 && !existing.is_empty() {
return;
}
// despawn 现有行星
for entity in &existing {
commands.entity(entity).despawn();
}
dirty.0 = false;
if !params.planets_enabled {
return;
}
let mut rng = ChaCha8Rng::seed_from_u64(params.planet_seed as u64);
let max = crate::render::material::MAX_PLANETS as u32;
for _ in 0..params.planet_count_target.min(max) {
let inclination = rng.gen_range(0.0..PI);
let longitude = rng.gen_range(0.0..TAU);
let phase = rng.gen_range(0.0..TAU);
let radius_factor = rng.gen_range(2.0..4.0);
// 颜色: 暖色行星 (橙/红/黄系), 避开蓝色 (易与背景星混淆)
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));
commands.spawn((
OrbitParams {
radius_factor,
inclination,
longitude_of_node: longitude,
phase,
},
Planet {
center: Vec3::ZERO, // 首帧由 orbit_system 填
radius: rng.gen_range(0.8..1.6),
color,
emissive: false,
},
));
}
}
/// HSV → RGB (h,s,v ∈ [0,1]). 行星颜色用.
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> Vec3 {
let i = (h * 6.0).floor() as i32 % 6;
let f = h * 6.0 - (h * 6.0).floor();
let p = v * (1.0 - s);
let q = v * (1.0 - f * s);
let t = v * (1.0 - (1.0 - f) * s);
match i {
0 => Vec3::new(v, t, p),
1 => Vec3::new(q, v, p),
2 => Vec3::new(p, v, t),
3 => Vec3::new(p, q, v),
4 => Vec3::new(t, p, v),
_ => Vec3::new(v, p, q),
}
} }
#[cfg(test)] #[cfg(test)]