diff --git a/src/main.rs b/src/main.rs index 8a79529..c055832 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,15 @@ use bevy::{ core_pipeline::{bloom::Bloom, tonemapping::Tonemapping}, - log::tracing_subscriber::field::debug, math::DVec3, prelude::*, - window::{WindowMode, WindowResolution}, + window::WindowMode, }; use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin}; use bevy_panorbit_camera::{PanOrbitCamera, PanOrbitCameraPlugin}; // Scaling factor to convert AU to game units -// This makes the solar system larger so objects are visible at minimum camera zoom +// Neptune is approximately 30.1 astronomical units (AU) from the Sun +// Point light effective range is about 10 game units, so 10 game units = ~30 AU const AU_TO_GAME_UNITS: f64 = 0.3; // Unit wrapper types - all distances in AU @@ -160,7 +160,7 @@ fn setup_rendering(mut commands: Commands) { ..default() }), Tonemapping::TonyMcMapface, - Transform::from_translation(Vec3::new(2.0, 1.5, 2.0)), + Transform::from_translation(Vec3::new(0., 0., 10.0)), Bloom::NATURAL, PanOrbitCamera { pan_sensitivity: 0.0, // Disable panning by setting sensitivity to 0 @@ -233,7 +233,7 @@ fn update_label_positions( }; for (mut node, label, mut text) in label_query.iter_mut() { - if let Ok((position, name, radius)) = objects_query.get(label.target_entity) { + if let Ok((position, name, _radius)) = objects_query.get(label.target_entity) { let world_pos = position.0.0 * AU_TO_GAME_UNITS; if let Ok(screen_pos) = camera.world_to_viewport(camera_transform, world_pos.as_vec3()) @@ -341,6 +341,74 @@ fn setup_solar_system(mut commands: Commands) { )); } +// High precision constants +const G_SI: f64 = 6.67430e-11; // Gravitational constant in m³/kg/s² (2018 CODATA value) +const AU_TO_M: f64 = 149597870691.0; // AU to meters (IAU 2012 definition, exact) +const DAY_TO_S: f64 = 86400.0; // Day to seconds (exact) + +// Pre-calculated G in AU³/kg/day² units for optimization +// G_AU = G_SI * (day²/s²) / (AU³/m³) +// G_AU = G_SI * (DAY_TO_S²) / (AU_TO_M³) +const G_AU: f64 = G_SI * (DAY_TO_S * DAY_TO_S) / (AU_TO_M * AU_TO_M * AU_TO_M); + +const STEPS: usize = 100; +// Time step calculation: +// FixedUpdate runs at 64fps, 1 game second = 7 days, 100 steps per FixedUpdate +// DT = (7 days / 64 fps) / 100 steps = 7 / (64 * 100) = 0.00109375 days per step +const DT: f64 = 7.0 / (64.0 * STEPS as f64); // Time step in days + +fn n_body(mut query: Query<(&Mass, &mut Position, &mut Velocity)>) { + // Collect all bodies data (mass, position, velocity) + let mut bodies: Vec<(f64, DVec3, DVec3)> = query + .iter() + .map(|(mass, pos, vel)| (mass.0.0, pos.0.0, vel.0.0)) + .collect(); + + // Perform integration steps + for _ in 0..STEPS { + // Calculate forces for all bodies + let mut forces: Vec = vec![DVec3::ZERO; bodies.len()]; + + for i in 0..bodies.len() { + for j in 0..bodies.len() { + if i != j { + let r_vec = bodies[j].1 - bodies[i].1; // Position difference in AU + let r_mag_au = r_vec.length(); + + if r_mag_au > 1e-10 { + // Avoid division by zero + // Calculate force directly in AU/day² units + let force_magnitude = + G_AU * bodies[i].0 * bodies[j].0 / (r_mag_au * r_mag_au); + let acceleration = force_magnitude / bodies[i].0; // F/m = a + let force_vec = r_vec.normalize() * acceleration; + + forces[i] += force_vec; + } + } + } + } + + // Update velocities and positions using Verlet integration + for i in 0..bodies.len() { + // Update velocity: v += a * dt + bodies[i].2 += forces[i] * DT; + + // Update position: x += v * dt + let velocity = bodies[i].2; + bodies[i].1 += velocity * DT; + } + } + + // Write back updated positions and velocities + for (i, (_mass, mut pos, mut vel)) in query.iter_mut().enumerate() { + if let Some(body) = bodies.get(i) { + pos.0.0 = body.1; + vel.0.0 = body.2; + } + } +} + fn main() { App::new() .add_plugins(DefaultPlugins.set(WindowPlugin { @@ -356,5 +424,6 @@ fn main() { .add_plugins(WorldInspectorPlugin::new()) .add_plugins(SolarRenderingPlugin) .add_systems(Startup, setup_solar_system) + .add_systems(FixedUpdate, n_body) .run(); }