use glam::{DMat3, DVec3};
pub use astrodyn_dynamics::body_init::LvlhAngularVelocityFrame;
use astrodyn_dynamics::body_init::{
init_from_lvlh, init_from_mean_anomaly, init_from_ned, init_from_orbital_elements,
init_from_time_periapsis, init_rot_from_lvlh,
};
use astrodyn_dynamics::{MassProperties, RotationalState, TranslationalState};
use astrodyn_math::{GeodeticState, JeodQuat, OrbitalElements};
use astrodyn_quantities::frame::SelfPlanet;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrbitalElementSet {
SmaEccIncAscnodeArgperTanom,
SmaEccIncAscnodeArgperManom,
SmaEccIncAscnodeArgperTimeperi,
}
#[derive(Debug, Clone)]
pub enum BodyAction {
InitMass {
mass: MassProperties,
},
InitTrans {
state: TranslationalState,
},
InitRot {
quaternion: JeodQuat,
ang_vel_body: DVec3,
},
InitLvlhRot {
q_lvlh_body: JeodQuat,
ang_vel_lvlh_to_body: DVec3,
ang_vel_frame: LvlhAngularVelocityFrame,
reference_position: DVec3,
reference_velocity: DVec3,
},
InitTransOrbital {
set: OrbitalElementSet,
elements: OrbitalElements<SelfPlanet>,
time_periapsis: f64,
mu: f64,
},
InitTransLvlh {
lvlh_position: DVec3,
lvlh_velocity: DVec3,
reference_position: DVec3,
reference_velocity: DVec3,
},
InitTransNed {
geodetic: GeodeticState,
ned_velocity: DVec3,
r_equatorial: f64,
r_polar: f64,
t_eci_pcpf: DMat3,
omega_planet: DVec3,
},
}
impl BodyAction {
#[inline]
pub fn is_ready(&self) -> bool {
true
}
pub fn apply_translational(&self) -> Option<TranslationalState> {
match self {
BodyAction::InitTrans { state } => Some(*state),
BodyAction::InitTransOrbital {
set,
elements,
time_periapsis,
mu,
} => Some(match set {
OrbitalElementSet::SmaEccIncAscnodeArgperTanom => init_from_orbital_elements(
elements.semi_major_axis,
elements.e_mag,
elements.inclination,
elements.long_asc_node,
elements.arg_periapsis,
elements.true_anom,
*mu,
),
OrbitalElementSet::SmaEccIncAscnodeArgperManom => init_from_mean_anomaly(
elements.semi_major_axis,
elements.e_mag,
elements.inclination,
elements.long_asc_node,
elements.arg_periapsis,
elements.mean_anom,
*mu,
),
OrbitalElementSet::SmaEccIncAscnodeArgperTimeperi => init_from_time_periapsis(
elements.semi_major_axis,
elements.e_mag,
elements.inclination,
elements.long_asc_node,
elements.arg_periapsis,
*time_periapsis,
*mu,
),
}),
BodyAction::InitTransLvlh {
lvlh_position,
lvlh_velocity,
reference_position,
reference_velocity,
} => Some(init_from_lvlh(
*lvlh_position,
*lvlh_velocity,
*reference_position,
*reference_velocity,
)),
BodyAction::InitTransNed {
geodetic,
ned_velocity,
r_equatorial,
r_polar,
t_eci_pcpf,
omega_planet,
} => Some(init_from_ned(
geodetic,
*ned_velocity,
*r_equatorial,
*r_polar,
t_eci_pcpf,
*omega_planet,
)),
BodyAction::InitMass { .. }
| BodyAction::InitRot { .. }
| BodyAction::InitLvlhRot { .. } => None,
}
}
pub fn apply_rotational(&self) -> Option<RotationalState> {
match self {
BodyAction::InitRot {
quaternion,
ang_vel_body,
} => Some(RotationalState {
quaternion: *quaternion,
ang_vel_body: *ang_vel_body,
}),
BodyAction::InitLvlhRot {
q_lvlh_body,
ang_vel_lvlh_to_body,
ang_vel_frame,
reference_position,
reference_velocity,
} => Some(init_rot_from_lvlh(
*q_lvlh_body,
*ang_vel_lvlh_to_body,
*ang_vel_frame,
*reference_position,
*reference_velocity,
)),
BodyAction::InitMass { .. }
| BodyAction::InitTrans { .. }
| BodyAction::InitTransOrbital { .. }
| BodyAction::InitTransLvlh { .. }
| BodyAction::InitTransNed { .. } => None,
}
}
pub fn apply_mass(&self) -> Option<MassProperties> {
match self {
BodyAction::InitMass { mass } => Some(*mass),
BodyAction::InitTrans { .. }
| BodyAction::InitRot { .. }
| BodyAction::InitLvlhRot { .. }
| BodyAction::InitTransOrbital { .. }
| BodyAction::InitTransLvlh { .. }
| BodyAction::InitTransNed { .. } => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_mass_is_ready_and_returns_mass() {
let mp = MassProperties::new(100.0);
let action = BodyAction::InitMass { mass: mp };
assert!(action.is_ready());
let out = action.apply_mass().expect("mass present");
assert_eq!(out.mass, mp.mass);
assert!(action.apply_translational().is_none());
assert!(action.apply_rotational().is_none());
}
#[test]
fn init_trans_returns_state() {
let state = TranslationalState {
position: DVec3::new(7_000_000.0, 0.0, 0.0),
velocity: DVec3::new(0.0, 7500.0, 0.0),
};
let action = BodyAction::InitTrans { state };
assert!(action.is_ready());
let out = action.apply_translational().expect("trans present");
assert_eq!(out, state);
assert!(action.apply_mass().is_none());
assert!(action.apply_rotational().is_none());
}
#[test]
fn init_rot_returns_state() {
let action = BodyAction::InitRot {
quaternion: JeodQuat::identity(),
ang_vel_body: DVec3::ZERO,
};
let out = action.apply_rotational().expect("rot present");
assert_eq!(out.quaternion, JeodQuat::identity());
assert_eq!(out.ang_vel_body, DVec3::ZERO);
}
#[test]
fn init_lvlh_rot_dispatches_to_kernel() {
const EARTH_MU: f64 = 3.986_004_415e14;
let r = 6_778_137.0;
let v = (EARTH_MU / r).sqrt();
let action = BodyAction::InitLvlhRot {
q_lvlh_body: JeodQuat::identity(),
ang_vel_lvlh_to_body: DVec3::ZERO,
ang_vel_frame: LvlhAngularVelocityFrame::Body,
reference_position: DVec3::new(r, 0.0, 0.0),
reference_velocity: DVec3::new(0.0, v, 0.0),
};
assert!(action.is_ready());
let out = action.apply_rotational().expect("rot present");
assert_ne!(out.quaternion, JeodQuat::identity());
assert!(out.ang_vel_body.length() > 0.0);
assert!(action.apply_translational().is_none());
assert!(action.apply_mass().is_none());
}
#[test]
fn init_trans_orbital_true_anom_round_trip() {
const MU: f64 = 3.986_004_415e14;
let mut elements = OrbitalElements::default();
elements.semi_major_axis = 7.0e6;
elements.e_mag = 0.001;
elements.inclination = 51.6_f64.to_radians();
elements.long_asc_node = 0.0;
elements.arg_periapsis = 0.0;
elements.true_anom = 0.5;
let a = elements.semi_major_axis;
let e = elements.e_mag;
let action = BodyAction::InitTransOrbital {
set: OrbitalElementSet::SmaEccIncAscnodeArgperTanom,
elements,
time_periapsis: 0.0,
mu: MU,
};
let trans = action.apply_translational().expect("trans present");
let r = trans.position.length();
let r_p = a * (1.0 - e);
let r_a = a * (1.0 + e);
assert!(r >= r_p - 1e-6);
assert!(r <= r_a + 1e-6);
}
}