use astrodyn_dynamics::mass::MassPropertiesTyped;
use astrodyn_dynamics::rotational::RotationalStateTyped;
use astrodyn_dynamics::state::TranslationalStateTyped;
use astrodyn_dynamics::{MassProperties, RotationalState, TranslationalState};
use astrodyn_quantities::aliases::{AngularVelocity, InertiaTensor, Position, Velocity};
use astrodyn_quantities::body_attitude::BodyAttitude;
use astrodyn_quantities::frame::{
BodyFrame, Frame, Planet, PlanetInertial, RootInertial, SelfRef, StructuralFrame, Vehicle,
};
use uom::si::f64::Mass;
use uom::si::mass::kilogram;
#[inline]
pub fn mass_typed_to_raw<V: Vehicle>(m: &MassPropertiesTyped<V>) -> MassProperties {
MassProperties {
mass: m.mass.get::<kilogram>(),
inverse_mass: m.inverse_mass,
inertia: m.inertia.as_dmat3(),
inverse_inertia: m.inverse_inertia,
position: m.center_of_mass.raw_si(),
t_parent_this: m.t_parent_this,
dirty: m.dirty,
}
}
#[inline]
pub fn mass_raw_to_typed<V: Vehicle>(mp: &MassProperties) -> MassPropertiesTyped<V> {
MassPropertiesTyped::<V>::with_inertia(
Mass::new::<kilogram>(mp.mass),
InertiaTensor::<BodyFrame<V>>::from_dmat3_unchecked(mp.inertia), Position::<StructuralFrame<V>>::from_raw_si(mp.position), )
.with_t_parent_this(mp.t_parent_this)
}
#[inline]
pub fn rot_typed_to_raw<V: Vehicle>(s: &RotationalStateTyped<V>) -> RotationalState {
RotationalState {
quaternion: s.q_inertial_body.to_jeod_quat(),
ang_vel_body: s.ang_vel_body.raw_si(),
}
}
#[inline]
pub fn rot_raw_to_typed<V: Vehicle>(s: &RotationalState) -> RotationalStateTyped<V> {
RotationalStateTyped::<V>::new(
BodyAttitude::from_jeod_quat(s.quaternion),
AngularVelocity::<BodyFrame<V>>::from_raw_si(s.ang_vel_body), )
}
#[inline]
pub fn trans_typed_to_raw<F: Frame>(s: &TranslationalStateTyped<F>) -> TranslationalState {
TranslationalState {
position: s.position.raw_si(),
velocity: s.velocity.raw_si(),
}
}
#[inline]
pub fn trans_raw_to_typed<F: Frame>(s: &TranslationalState) -> TranslationalStateTyped<F> {
TranslationalStateTyped::<F> {
position: Position::<F>::from_raw_si(s.position), velocity: Velocity::<F>::from_raw_si(s.velocity), }
}
#[inline]
pub fn trans_raw_to_root(s: &TranslationalState) -> TranslationalStateTyped<RootInertial> {
trans_raw_to_typed::<RootInertial>(s)
}
#[inline]
pub fn trans_raw_to_planet<P: Planet>(
s: &TranslationalState,
) -> TranslationalStateTyped<PlanetInertial<P>> {
trans_raw_to_typed::<PlanetInertial<P>>(s)
}
#[inline]
pub fn rot_raw_to_self_ref(s: &RotationalState) -> RotationalStateTyped<SelfRef> {
rot_raw_to_typed::<SelfRef>(s)
}
#[inline]
pub fn mass_raw_to_self_ref(mp: &MassProperties) -> MassPropertiesTyped<SelfRef> {
mass_raw_to_typed::<SelfRef>(mp)
}
#[cfg(test)]
#[allow(
clippy::float_cmp,
reason = "typed-bridge tests assert bit-exact identity at the typed-vs-raw boundary"
)]
mod tests {
use super::*;
use glam::DMat3;
fn dmat3_byte_eq(a: DMat3, b: DMat3) -> bool {
a.to_cols_array()
.iter()
.zip(b.to_cols_array().iter())
.all(|(x, y)| x.to_bits() == y.to_bits())
}
#[test]
fn point_mass_inverse_inertia_matches_across_construction_paths() {
use glam::DVec3;
let a = MassPropertiesTyped::<SelfRef>::new(Mass::new::<kilogram>(424.0));
let b = mass_raw_to_self_ref(&MassProperties::new(424.0));
assert!(
dmat3_byte_eq(a.inverse_inertia, b.inverse_inertia),
"inverse_inertia diverges between construction paths: \
a={:?} b={:?}",
a.inverse_inertia,
b.inverse_inertia,
);
assert_eq!(a.inverse_mass.to_bits(), b.inverse_mass.to_bits());
assert_eq!(a.mass, b.mass);
assert!(
dmat3_byte_eq(a.inertia.as_dmat3(), b.inertia.as_dmat3()),
"inertia diverges between construction paths: a={:?} b={:?}",
a.inertia.as_dmat3(),
b.inertia.as_dmat3(),
);
assert_eq!(a.center_of_mass.raw_si(), b.center_of_mass.raw_si());
assert!(
dmat3_byte_eq(a.t_parent_this, b.t_parent_this),
"t_parent_this diverges between construction paths: \
a={:?} b={:?}",
a.t_parent_this,
b.t_parent_this,
);
assert_eq!(a.dirty, b.dirty);
assert!(!a.dirty);
assert_eq!(
MassPropertiesTyped::<SelfRef>::to_untyped(&a),
MassPropertiesTyped::<SelfRef>::to_untyped(&b),
);
let mass_kg = 424.0_f64;
let canonical_inertia = DMat3::IDENTITY * mass_kg;
let raw_with_stale_cache = MassProperties {
mass: mass_kg,
inverse_mass: 999.0,
inertia: canonical_inertia,
inverse_inertia: DMat3::from_diagonal(DVec3::new(999.0, 999.0, 999.0)),
position: DVec3::ZERO,
t_parent_this: DMat3::IDENTITY,
dirty: true,
};
let c = mass_raw_to_self_ref(&raw_with_stale_cache);
assert_eq!(
c.inverse_mass.to_bits(),
a.inverse_mass.to_bits(),
"raw→typed bridge must recompute inverse_mass from mass; \
stale raw cache (999.0) leaked through into the typed \
sibling instead of being replaced by 1/mass"
);
assert!(
dmat3_byte_eq(c.inverse_inertia, a.inverse_inertia),
"raw→typed bridge must recompute inverse_inertia from \
inertia; stale raw cache leaked through into the typed \
sibling. expected={:?} got={:?}",
a.inverse_inertia,
c.inverse_inertia,
);
assert!(
!c.dirty,
"raw→typed bridge must canonicalise dirty to false \
regardless of the raw input's flag; got dirty=true"
);
}
#[test]
fn non_default_mass_props_round_trip_across_construction_paths() {
use glam::DVec3;
let mass = 424.0_f64;
let diag = DMat3::from_diagonal(DVec3::new(100.0, 200.0, 300.0));
let rot = DMat3::from_axis_angle(DVec3::new(1.0, 2.0, 3.0).normalize(), 0.5_f64);
let inertia = rot.transpose() * diag * rot;
let com = DVec3::new(0.1, -0.2, 0.3);
let t_parent_this = DMat3::from_axis_angle(DVec3::Z, std::f64::consts::PI);
let raw = MassProperties {
mass,
inverse_mass: 1.0 / mass,
inertia,
inverse_inertia: inertia.inverse(),
position: com,
t_parent_this,
dirty: false,
};
let typed = mass_raw_to_self_ref(&raw);
assert_eq!(typed.mass.get::<kilogram>(), raw.mass);
assert_eq!(typed.inverse_mass.to_bits(), raw.inverse_mass.to_bits());
assert!(
dmat3_byte_eq(typed.inertia.as_dmat3(), raw.inertia),
"inertia diverges from raw input: typed={:?} raw={:?}",
typed.inertia.as_dmat3(),
raw.inertia,
);
assert!(
dmat3_byte_eq(typed.inverse_inertia, raw.inverse_inertia),
"inverse_inertia diverges from raw input: typed={:?} raw={:?}",
typed.inverse_inertia,
raw.inverse_inertia,
);
assert_eq!(typed.center_of_mass.raw_si(), raw.position);
assert!(
dmat3_byte_eq(typed.t_parent_this, raw.t_parent_this),
"t_parent_this diverges from raw input: typed={:?} raw={:?}",
typed.t_parent_this,
raw.t_parent_this,
);
assert_eq!(typed.dirty, raw.dirty);
assert_ne!(raw.position, DVec3::ZERO);
assert_ne!(raw.t_parent_this, DMat3::IDENTITY);
assert_eq!(MassPropertiesTyped::<SelfRef>::to_untyped(&typed), raw);
}
#[test]
fn raw_to_typed_canonicalises_dirty_flag() {
use glam::DVec3;
let mass = 424.0_f64;
let inertia = DMat3::from_diagonal(DVec3::new(100.0, 200.0, 300.0));
let raw_dirty = MassProperties {
mass,
inverse_mass: 1.0 / mass,
inertia,
inverse_inertia: inertia.inverse(),
position: DVec3::new(0.1, -0.2, 0.3),
t_parent_this: DMat3::IDENTITY,
dirty: true,
};
let typed = mass_raw_to_self_ref(&raw_dirty);
assert!(
!typed.dirty,
"raw→typed bridge must canonicalise dirty to false \
(with_inertia re-derives the cache); got dirty=true"
);
assert_eq!(
typed.inverse_mass.to_bits(),
raw_dirty.inverse_mass.to_bits()
);
assert!(dmat3_byte_eq(
typed.inverse_inertia,
raw_dirty.inverse_inertia
));
}
}