diff --git a/assets/initial_state.ron b/assets/initial_state.ron index 81d594c..e725ad6 100644 --- a/assets/initial_state.ron +++ b/assets/initial_state.ron @@ -68,13 +68,6 @@ position: (2.987992735576156e+01, -6.341879950443392e-01, -6.754997950415415e-01), velocity: (3.941595250081164e-05, 3.160389775728832e-03, -6.636996572427530e-05), ), - ( - name: "Pluto", - mass: 1.307e22, - radius: 1188.3, - position: (1.822881632666475e+01, -3.000801293901950e+01, -2.059815785829023e+00), - velocity: (2.758977855246929e-03, 9.457264850164455e-04, -8.880815395819214e-04), - ), ( name: "Moon", mass: 7.349e22, diff --git a/assets/jpl_horizon/bodies/Pluto.txt b/assets/jpl_horizon/bodies/Pluto.txt deleted file mode 100644 index 95e23b6..0000000 --- a/assets/jpl_horizon/bodies/Pluto.txt +++ /dev/null @@ -1,124 +0,0 @@ -API VERSION: 1.2 -API SOURCE: NASA/JPL Horizons API - -******************************************************************************* - Revised: Apr 03, 2024 134340 Pluto 999 - - Pre-computed solution PLU060/DE440. Fit to post New Horizons encounter and - Gaia data through 2023. For discussion, see ... - - M. Brozovic, R. A. Jacobson (2024) "Post-New Horizons orbits and masses - for the satellites of Pluto". AJ (in press) - - PHYSICAL DATA (updated 2021-Jun-07; Mc= Charon mass, radius is IAU 2015): - Mass x10^22 (kg) = 1.307+-0.018 Volume, 10^10 km^3 = 0.697 - GM (planet) km^3/s^2 = 869.326 Density (R=1195 km) = 1.86 g/cm^3 - GM 1-sigma, km^3/s^2 = 0.4 Surface gravity = 0.611 m/s^2 - Vol. mean radius (km) = 1188.3+-1.6 Mass ratio (Mc/Mp) = 0.122 - Sidereal rot. period = 153.29335198 h Sid. rot. rat, rad/s = 0.0000113856 - Mean solar day, h = 153.2820 Mean orbit velocity = 4.67 km/s - Sidereal orbit period = 249.58932 yr Escape speed, km/s = 1.21 - Perihelion Aphelion Mean - Solar Constant (W/m^2) 1.56 0.56 0.88 - Maximum Planetary IR (W/m^2) 0.8 0.3 0.5 - Minimum Planetary IR (W/m^2) 0.8 0.3 0.5 -******************************************************************************* - - -******************************************************************************* -Ephemeris / API_USER Thu Aug 14 15:51:59 2025 Pasadena, USA / Horizons -******************************************************************************* -Target body name: Pluto (999) {source: plu060_merged} -Center body name: Sun (10) {source: plu060_merged} -Center-site name: BODY CENTER -******************************************************************************* -Start time : A.D. 2025-Jan-01 00:00:00.0000 TDB -Stop time : A.D. 2025-Jan-01 00:01:00.0000 TDB -Step-size : 1 minutes -******************************************************************************* -Center geodetic : 0.0, 0.0, 0.0 {E-lon(deg),Lat(deg),Alt(km)} -Center cylindric: 0.0, 0.0, 0.0 {E-lon(deg),Dxy(km),Dz(km)} -Center radii : 695700.0, 695700.0, 695700.0 km {Equator_a, b, pole_c} -Output units : AU-D -Calendar mode : Mixed Julian/Gregorian -Output type : GEOMETRIC cartesian states -Output format : 2 (position and velocity) -Reference frame : Ecliptic of J2000.0 -******************************************************************************* -JDTDB - X Y Z - VX VY VZ -******************************************************************************* -$$SOE -2460676.500000000 = A.D. 2025-Jan-01 00:00:00.0000 TDB - X = 1.822881632666475E+01 Y =-3.000801293901950E+01 Z =-2.059815785829023E+00 - VX= 2.758977855246929E-03 VY= 9.457264850164455E-04 VZ=-8.880815395819214E-04 -2460676.500694444 = A.D. 2025-Jan-01 00:01:00.0000 TDB - X = 1.822881824262399E+01 Y =-3.000801228226284E+01 Z =-2.059816402553139E+00 - VX= 2.758984750779286E-03 VY= 9.457327203036255E-04 VZ=-8.880839212592113E-04 -$$EOE -******************************************************************************* - -TIME - - Barycentric Dynamical Time ("TDB" or T_eph) output was requested. This -continuous coordinate time is equivalent to the relativistic proper time -of a clock at rest in a reference frame co-moving with the solar system -barycenter but outside the system's gravity well. It is the independent -variable in the solar system relativistic equations of motion. - - TDB runs at a uniform rate of one SI second per second and is independent -of irregularities in Earth's rotation. - -CALENDAR SYSTEM - - Mixed calendar mode was active such that calendar dates after AD 1582-Oct-15 -(if any) are in the modern Gregorian system. Dates prior to 1582-Oct-5 (if any) -are in the Julian calendar system, which is automatically extended for dates -prior to its adoption on 45-Jan-1 BC. The Julian calendar is useful for -matching historical dates. The Gregorian calendar more accurately corresponds -to the Earth's orbital motion and seasons. A "Gregorian-only" calendar mode is -available if such physical events are the primary interest. - -REFERENCE FRAME AND COORDINATES - - Ecliptic at the standard reference epoch - - Reference epoch: J2000.0 - X-Y plane: adopted Earth orbital plane at the reference epoch - Note: IAU76 obliquity of 84381.448 arcseconds wrt ICRF X-Y plane - X-axis : ICRF - Z-axis : perpendicular to the X-Y plane in the directional (+ or -) sense - of Earth's north pole at the reference epoch. - - Symbol meaning [1 au= 149597870.700 km, 1 day= 86400.0 s]: - - JDTDB Julian Day Number, Barycentric Dynamical Time - X X-component of position vector (au) - Y Y-component of position vector (au) - Z Z-component of position vector (au) - VX X-component of velocity vector (au/day) - VY Y-component of velocity vector (au/day) - VZ Z-component of velocity vector (au/day) - -ABERRATIONS AND CORRECTIONS - - Geometric state vectors have NO corrections or aberrations applied. - -Computations by ... - - Solar System Dynamics Group, Horizons On-Line Ephemeris System - 4800 Oak Grove Drive, Jet Propulsion Laboratory - Pasadena, CA 91109 USA - - General site: https://ssd.jpl.nasa.gov/ - Mailing list: https://ssd.jpl.nasa.gov/email_list.html - System news : https://ssd.jpl.nasa.gov/horizons/news.html - User Guide : https://ssd.jpl.nasa.gov/horizons/manual.html - Connect : browser https://ssd.jpl.nasa.gov/horizons/app.html#/x - API https://ssd-api.jpl.nasa.gov/doc/horizons.html - command-line telnet ssd.jpl.nasa.gov 6775 - e-mail/batch https://ssd.jpl.nasa.gov/ftp/ssd/horizons_batch.txt - scripts https://ssd.jpl.nasa.gov/ftp/ssd/SCRIPTS - Author : Jon.D.Giorgini@jpl.nasa.gov -******************************************************************************* diff --git a/src/main.rs b/src/main.rs index 0dcdcfc..fc88ba3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ pub struct PositionAu(pub DVec3); pub struct VelocityAuPerDay(pub DVec3); #[derive(Clone, Copy, Debug, PartialEq)] -pub struct MassKg(pub f64); +pub struct MassSolarMass(pub f64); #[derive(Clone, Debug, PartialEq)] pub struct ObjectName(pub String); @@ -55,7 +55,7 @@ struct Position(PositionAu); struct Velocity(VelocityAuPerDay); #[derive(Component)] -struct Mass(MassKg); +struct Mass(MassSolarMass); #[derive(Component)] struct Radius(DistanceAu); @@ -83,6 +83,10 @@ struct ObjectLabel { target_entity: Entity, } +// Component to control label visibility +#[derive(Component)] +struct LabelVisible(bool); + // Component to mark objects that can be focused on with proper zoom levels #[derive(Component)] struct Trackable {} @@ -109,10 +113,11 @@ impl Plugin for SolarRenderingPlugin { sync_radius_to_mesh, sync_position_to_transform, sync_name_labels, + manage_label_overlaps.after(sync_position_to_transform).after(sync_name_labels), camera_follow_system.after(sync_position_to_transform), ), ) - .add_systems(PostUpdate, update_label_positions) + .add_systems(PostUpdate, update_label_positions.after(manage_label_overlaps)) .add_systems( Update, ( @@ -251,13 +256,14 @@ fn sync_name_labels( ObjectLabel { target_entity: entity, }, + LabelVisible(true), // Start visible by default )); } } } -fn update_label_positions( - mut label_query: Query<(&mut Node, &ObjectLabel, &mut Text), With>, +fn manage_label_overlaps( + mut label_query: Query<(&ObjectLabel, &mut LabelVisible)>, objects_query: Query<(&GlobalTransform, &Name, &Radius)>, camera_query: Query<(&Camera, &GlobalTransform)>, ) { @@ -265,7 +271,78 @@ fn update_label_positions( return; }; - for (mut node, label, mut text) in label_query.iter_mut() { + // Collect all on-screen objects with their screen positions and radii + let mut visible_objects: Vec<(Entity, Vec2, f64, String)> = Vec::new(); + + for (label, _) in label_query.iter() { + if let Ok((global_transform, name, radius)) = objects_query.get(label.target_entity) { + let world_pos = global_transform.translation(); + if let Ok(screen_pos) = camera.world_to_viewport(camera_transform, world_pos) { + visible_objects.push(( + label.target_entity, + screen_pos, + radius.0.0, // radius in AU + name.0.0.clone(), + )); + } + } + } + + // Reset all labels to visible first + for (_, mut visible) in label_query.iter_mut() { + visible.0 = true; + } + + // Check for overlaps and hide smaller objects + const OVERLAP_THRESHOLD: f32 = 50.0; // pixels + + for i in 0..visible_objects.len() { + for j in (i + 1)..visible_objects.len() { + let (entity_a, pos_a, radius_a, name_a) = &visible_objects[i]; + let (entity_b, pos_b, radius_b, name_b) = &visible_objects[j]; + + let distance = pos_a.distance(*pos_b); + if distance < OVERLAP_THRESHOLD { + // Determine which label to hide based on radius (larger wins) + let entity_to_hide = if radius_a > radius_b { + *entity_b + } else if radius_b > radius_a { + *entity_a + } else { + // If radii are equal, prefer alphabetically first name + if name_a < name_b { *entity_b } else { *entity_a } + }; + + // Hide the smaller object's label + for (label, mut visible) in label_query.iter_mut() { + if label.target_entity == entity_to_hide { + visible.0 = false; + // Debug: which label got hidden + debug!("Hiding label due to overlap (distance: {:.1}px)", distance); + break; + } + } + } + } + } +} + +fn update_label_positions( + mut label_query: Query<(&mut Node, &ObjectLabel, &mut Text, &LabelVisible), With>, + objects_query: Query<(&GlobalTransform, &Name, &Radius)>, + camera_query: Query<(&Camera, &GlobalTransform)>, +) { + let Ok((camera, camera_transform)) = camera_query.single() else { + return; + }; + + for (mut node, label, mut text, visible) in label_query.iter_mut() { + if !visible.0 { + // Label is marked as invisible due to overlap, hide it + node.display = Display::None; + continue; + } + if let Ok((global_transform, name, _radius)) = objects_query.get(label.target_entity) { let world_pos = global_transform.translation(); if let Ok(screen_pos) = camera.world_to_viewport(camera_transform, world_pos) { @@ -275,17 +352,17 @@ fn update_label_positions( // Position the label on screen, offset slightly to avoid overlapping the object node.left = Val::Px(screen_pos.x + scaled_offset); node.top = Val::Px(screen_pos.y - scaled_offset); + node.display = Display::Flex; // Make sure it's visible // Update text in case name changed text.0 = name.0.0.clone(); } else { - // Object is off-screen, hide label by moving it off-screen - node.left = Val::Px(-1000.0); - node.top = Val::Px(-1000.0); + // Object is off-screen, hide label + node.display = Display::None; } } else { - // Target entity no longer exists, remove label - // Note: In a more complex system, you might want to handle this in a separate cleanup system + // Target entity no longer exists, hide label + node.display = Display::None; } } } @@ -445,6 +522,9 @@ fn setup_solar_system(mut commands: Commands) { for body_data in initial_state.bodies { // Convert radius from km to AU let radius_au = body_data.radius / 149597870.691; // km to AU conversion + + // Convert mass from kg to solar masses + let mass_solar = body_data.mass / SOLAR_MASS_KG; // Create base bundle let mut entity_commands = commands.spawn(( @@ -460,7 +540,7 @@ fn setup_solar_system(mut commands: Commands) { body_data.velocity.1, body_data.velocity.2, ))), - mass: Mass(MassKg(body_data.mass)), + mass: Mass(MassSolarMass(mass_solar)), radius: Radius(DistanceAu(radius_au)), }, Trackable {}, @@ -485,9 +565,13 @@ fn setup_solar_system(mut commands: Commands) { const G_SI: f64 = 6.67430e-11; // m^3 / (kg s^2) const AU_TO_M: f64 = 149_597_870_691.0; // m const DAY_TO_S: f64 = 86_400.0; // s +const SOLAR_MASS_KG: f64 = 1.98841e30; // kg -// G in AU^3 / (kg day^2) -const G_AU: f64 = G_SI * (DAY_TO_S * DAY_TO_S) / (AU_TO_M * AU_TO_M * AU_TO_M); +// G in AU^3 / (solar_mass day^2) +// G_SI has units m^3 / (kg s^2) +// We want AU^3 / (solar_mass day^2) +// G_AU = G_SI * (day^2 / s^2) * (kg / solar_mass) * (m^3 / AU^3) +const G_AU: f64 = G_SI * (DAY_TO_S * DAY_TO_S) / (SOLAR_MASS_KG * AU_TO_M * AU_TO_M * AU_TO_M); const STEPS: usize = 100; // If FixedUpdate is 64 Hz and you want 1 day per game second: DT = 1/(64*STEPS) day/step