use astrodyn_quantities::frame_descriptor::FrameUid;
use glam::{DMat3, DVec3};
use crate::atmosphere::AtmosphereConfig;
use crate::sources::GravitySourceEntry;
use crate::vehicle_config::VehicleConfig;
use crate::SimulationTime;
use astrodyn_ephemeris::{Ephemeris, EphemerisBody};
#[derive(Debug, Clone)]
pub struct MassTreeAttachment {
pub child_idx: usize,
pub parent_idx: usize,
pub offset: DVec3,
pub t_parent_child: DMat3,
}
pub struct SimulationBuilder {
pub time: SimulationTime,
pub dt: f64,
pub atmosphere: Option<AtmosphereConfig>,
pub atmosphere_planet_source: Option<usize>,
pub ephemeris: Option<Ephemeris>,
pub polar_motion: Option<(f64, f64)>,
pub sun_source: Option<usize>,
pub moon_source: Option<usize>,
pub sources: Vec<(String, GravitySourceEntry)>,
pub source_uids: Vec<Option<(FrameUid, FrameUid)>>,
pub source_ephem_bodies: Vec<Option<(EphemerisBody, EphemerisBody)>>,
pub bodies: Vec<VehicleConfig>,
pub mass_tree_names: Vec<Option<String>>,
pub mass_tree_attachments: Vec<MassTreeAttachment>,
}
impl SimulationBuilder {
pub fn new(time: SimulationTime, dt: f64) -> Self {
Self {
time,
dt,
atmosphere: None,
atmosphere_planet_source: None,
ephemeris: None,
polar_motion: None,
sun_source: None,
moon_source: None,
sources: Vec::new(),
source_uids: Vec::new(),
source_ephem_bodies: Vec::new(),
bodies: Vec::new(),
mass_tree_names: Vec::new(),
mass_tree_attachments: Vec::new(),
}
}
pub fn atmosphere(mut self, config: AtmosphereConfig, planet_source: usize) -> Self {
self.atmosphere = Some(config);
self.atmosphere_planet_source = Some(planet_source);
self
}
pub fn atmosphere_from_planet(
mut self,
model: crate::AtmosphereModel,
planet: &crate::PlanetConfig,
planet_source: usize,
) -> Self {
self.atmosphere = Some(AtmosphereConfig::from_planet(model, planet));
self.atmosphere_planet_source = Some(planet_source);
self
}
pub fn ephemeris(mut self, eph: Ephemeris) -> Self {
self.ephemeris = Some(eph);
self
}
pub fn polar_motion(mut self, xp: f64, yp: f64) -> Self {
self.polar_motion = Some((xp, yp));
self
}
pub fn sun(mut self, idx: usize) -> Self {
self.sun_source = Some(idx);
self
}
pub fn moon(mut self, idx: usize) -> Self {
self.moon_source = Some(idx);
self
}
pub fn add_source(&mut self, name: impl Into<String>, entry: GravitySourceEntry) -> usize {
let idx = self.sources.len();
self.source_uids.push(None);
self.sources.push((name.into(), entry));
self.source_ephem_bodies.push(None);
idx
}
pub fn add_source_typed<P: astrodyn_quantities::frame::Planet>(
&mut self,
name: impl Into<String>,
entry: GravitySourceEntry,
) -> usize {
let idx = self.sources.len();
self.source_uids.push(Some((
FrameUid::of::<astrodyn_quantities::frame::PlanetInertial<P>>(),
FrameUid::of::<astrodyn_quantities::frame::PlanetFixed<P>>(),
)));
self.sources.push((name.into(), entry));
self.source_ephem_bodies.push(None);
idx
}
pub fn set_source_ephemeris(
&mut self,
idx: usize,
target: EphemerisBody,
observer: EphemerisBody,
) -> &mut Self {
assert!(
idx < self.source_ephem_bodies.len(),
"set_source_ephemeris: source index {idx} out of range \
({} sources added)",
self.source_ephem_bodies.len()
);
self.source_ephem_bodies[idx] = Some((target, observer));
self
}
pub fn add_third_body_with_ephemeris(
&mut self,
name: impl Into<String>,
planet: &crate::PlanetConfig,
target: EphemerisBody,
observer: EphemerisBody,
) -> usize {
let entry = crate::sources::GravitySourceEntry::third_body(
planet,
astrodyn_quantities::aliases::Position::<astrodyn_quantities::frame::RootInertial>::zero(),
);
let idx = self.add_source(name, entry);
self.set_source_ephemeris(idx, target, observer);
idx
}
pub fn add_body(&mut self, config: VehicleConfig) -> usize {
let idx = self.bodies.len();
self.bodies.push(config);
self.mass_tree_names.push(None);
idx
}
pub fn register_in_mass_tree(&mut self, body_idx: usize, name: impl Into<String>) -> &mut Self {
assert!(
body_idx < self.bodies.len(),
"register_in_mass_tree: body index {body_idx} out of range \
({} bodies added)",
self.bodies.len()
);
assert!(
self.bodies[body_idx].mass.is_some(),
"register_in_mass_tree: body {body_idx} has no mass properties"
);
self.mass_tree_names[body_idx] = Some(name.into());
self
}
pub fn attach_bodies(
&mut self,
child_idx: usize,
parent_idx: usize,
offset: DVec3,
t_parent_child: DMat3,
) -> &mut Self {
assert!(
child_idx < self.bodies.len(),
"attach_bodies: child body index {child_idx} out of range \
({} bodies added)",
self.bodies.len()
);
assert!(
parent_idx < self.bodies.len(),
"attach_bodies: parent body index {parent_idx} out of range \
({} bodies added)",
self.bodies.len()
);
assert!(
self.mass_tree_names[child_idx].is_some(),
"attach_bodies: child body {child_idx} not registered in mass tree"
);
assert!(
self.mass_tree_names[parent_idx].is_some(),
"attach_bodies: parent body {parent_idx} not registered in mass tree"
);
self.mass_tree_attachments.push(MassTreeAttachment {
child_idx,
parent_idx,
offset,
t_parent_child,
});
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{GravityModel, GravitySource};
mod tags {
crate::define_planet!(BuilderTestPlanet);
}
fn entry() -> GravitySourceEntry {
GravitySourceEntry {
source: GravitySource {
mu: 0.0,
model: GravityModel::PointMass,
},
position: astrodyn_quantities::aliases::Position::zero(),
velocity: astrodyn_quantities::aliases::Velocity::zero(),
t_inertial_pfix: None,
delta_c20: 0.0,
rotation_model: crate::RotationModel::None,
tidal_config: None,
planet_omega: 0.0,
central: true,
marker_only: false,
}
}
#[test]
fn source_registration_keeps_parallel_vecs_in_sync() {
let time = crate::SimulationTime::at_j2000(crate::default_leap_second_table());
let mut sb = SimulationBuilder::new(time, 1.0);
sb.add_source("Earth", entry());
sb.add_source_typed::<tags::BuilderTestPlanet>("BuilderTestPlanet", entry());
assert_eq!(sb.sources.len(), 2);
assert_eq!(sb.source_uids.len(), 2);
assert_eq!(sb.source_ephem_bodies.len(), 2);
assert!(
sb.source_uids[0].is_none(),
"name-dispatch path carries no uid"
);
assert!(
sb.source_uids[1].is_some(),
"typed path carries minted uids"
);
}
}