mod bodies;
mod frame_attach;
mod mass_tree;
mod sources;
mod step;
pub(crate) mod types;
mod validate;
pub use astrodyn::{DetachedSubtreeState, GroundFacet, SphericalTerrain, Terrain};
pub use types::{ContactPairConfig, FrameAttachState, GroundContactPairConfig, VehicleOutput};
#[cfg(feature = "phase_timing")]
pub use step::timings::PhaseTimings;
use std::collections::HashMap;
use glam::DMat3;
use astrodyn::atmosphere::AtmosphereConfig;
use astrodyn::{FrameId, FrameTree, MassBodyId, RefFrameKind, SimulationTime, SourceFrameIds};
use crate::simulation::types::{GravityData, SimBody};
#[derive(Debug, Clone, Copy)]
pub(crate) struct EarthRnpCache {
pub(crate) simtime_at_refresh: f64,
pub(crate) np: DMat3,
pub(crate) polar_t: Option<DMat3>,
pub(crate) equa_of_equi: f64,
}
pub struct Simulation {
pub time: SimulationTime,
bodies: Vec<SimBody>,
frame_tree: FrameTree,
pub root_frame_id: FrameId,
source_frame_ids: Vec<SourceFrameIds>,
gravity_data: Vec<GravityData>,
source_ephem_bodies: Vec<Option<(astrodyn::EphemerisBody, astrodyn::EphemerisBody)>>,
pub atmosphere: Option<AtmosphereConfig>,
pub atmosphere_planet_source: Option<usize>,
pub sun_source: Option<usize>,
pub moon_source: Option<usize>,
pub polar_motion: Option<(f64, f64)>,
pub dt: f64,
pub ephemeris: Option<astrodyn::Ephemeris>,
pub mass_tree: Option<astrodyn::MassTree>,
pub detached_subtrees: HashMap<MassBodyId, DetachedSubtreeState>,
contact_pairs: Vec<ContactPairConfig>,
ground_contact_pairs: Vec<GroundContactPairConfig>,
ground_contact_planet_source: Option<usize>,
coupled_integ_scratch: astrodyn::integration::CoupledIntegScratch,
pub(crate) has_stepped: bool,
pub(crate) earth_rnp_refresh_cadence_s: f64,
pub(crate) earth_rnp_cache: Option<EarthRnpCache>,
#[cfg(feature = "phase_timing")]
pub(crate) phase_timings: crate::simulation::step::timings::PhaseTimings,
}
impl Simulation {
pub fn new(time: SimulationTime, dt: f64) -> Self {
let mut frame_tree = FrameTree::new();
let root_frame_id = frame_tree.add_root("Earth.inertial".into(), RefFrameKind::Inertial);
Self {
time,
bodies: Vec::new(),
frame_tree,
root_frame_id,
source_frame_ids: Vec::new(),
gravity_data: Vec::new(),
source_ephem_bodies: Vec::new(),
atmosphere: None,
atmosphere_planet_source: None,
sun_source: None,
moon_source: None,
polar_motion: None,
dt,
ephemeris: None,
mass_tree: None,
detached_subtrees: HashMap::new(),
contact_pairs: Vec::new(),
ground_contact_pairs: Vec::new(),
ground_contact_planet_source: None,
coupled_integ_scratch: astrodyn::integration::CoupledIntegScratch::new(),
has_stepped: false,
earth_rnp_refresh_cadence_s: 0.0,
earth_rnp_cache: None,
#[cfg(feature = "phase_timing")]
phase_timings: crate::simulation::step::timings::PhaseTimings::default(),
}
}
#[cfg(feature = "phase_timing")]
pub fn phase_timings(&self) -> &crate::simulation::step::timings::PhaseTimings {
&self.phase_timings
}
#[cfg(feature = "phase_timing")]
pub fn reset_phase_timings(&mut self) {
self.phase_timings = crate::simulation::step::timings::PhaseTimings::default();
}
#[cfg(feature = "phase_timing")]
pub fn phase_timings_summary(&self) -> String {
self.phase_timings.summary()
}
pub fn set_earth_rnp_refresh_cadence(&mut self, cadence_s: f64) -> &mut Self {
assert!(
cadence_s.is_finite() && cadence_s >= 0.0,
"set_earth_rnp_refresh_cadence: cadence must be finite and >= 0, got {cadence_s}. \
Use 0.0 for per-step refresh (default) or a positive value to reuse the cached \
matrix across that many simulated seconds."
);
self.earth_rnp_refresh_cadence_s = cadence_s;
self.earth_rnp_cache = None;
self
}
pub fn earth_rnp_refresh_cadence(&self) -> f64 {
self.earth_rnp_refresh_cadence_s
}
pub fn num_bodies(&self) -> usize {
self.bodies.len()
}
pub fn set_dt(&mut self, dt: f64) {
assert!(
dt.is_finite() && dt > 0.0,
"dt must be finite and > 0, got {dt}"
);
self.dt = dt;
}
pub fn elapsed(&self) -> f64 {
self.time.simtime
}
}