use crate::mass_body::MassPointState;
use crate::propagation::propagate_forward;
use astrodyn_frames::RefFrameState;
#[derive(Debug, Clone, Copy)]
pub struct FrameAttachInputs {
pub parent_frame: RefFrameState,
pub attach_offset: MassPointState,
pub composite_offset: MassPointState,
}
pub fn derive_frame_attached_state(input: FrameAttachInputs) -> RefFrameState {
let structure_state = propagate_forward(&input.parent_frame, &input.attach_offset);
propagate_forward(&structure_state, &input.composite_offset)
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_frames::{RefFrameRot, RefFrameTrans};
use astrodyn_math::JeodQuat;
use glam::DVec3;
#[test]
fn identity_offset_returns_parent_state() {
let parent = RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(7e6, 0.0, 0.0),
velocity: DVec3::new(0.0, 7600.0, 0.0),
},
rot: RefFrameRot {
q_parent_this: JeodQuat::identity(),
t_parent_this: JeodQuat::identity().left_quat_to_transformation(),
ang_vel_this: DVec3::new(0.0, 0.0, 7.292115e-5),
},
};
let offset = MassPointState::default();
let derived = derive_frame_attached_state(FrameAttachInputs {
parent_frame: parent,
attach_offset: offset,
composite_offset: MassPointState::default(),
});
assert!((derived.trans.position - parent.trans.position).length() < 1e-9);
assert!((derived.trans.velocity - parent.trans.velocity).length() < 1e-9);
assert!((derived.rot.ang_vel_this - parent.rot.ang_vel_this).length() < 1e-9);
}
#[test]
fn pfix_attached_body_picks_up_omega_cross_r_velocity() {
let omega_earth = 7.292115e-5_f64;
let parent = RefFrameState {
trans: RefFrameTrans {
position: DVec3::ZERO,
velocity: DVec3::ZERO,
},
rot: RefFrameRot {
q_parent_this: JeodQuat::identity(),
t_parent_this: JeodQuat::identity().left_quat_to_transformation(),
ang_vel_this: DVec3::new(0.0, 0.0, omega_earth),
},
};
let r = 6.371e6;
let offset = MassPointState {
position: DVec3::new(r, 0.0, 0.0),
t_parent_this: JeodQuat::identity().left_quat_to_transformation(),
};
let derived = derive_frame_attached_state(FrameAttachInputs {
parent_frame: parent,
attach_offset: offset,
composite_offset: MassPointState::default(),
});
assert!((derived.trans.position - DVec3::new(r, 0.0, 0.0)).length() < 1e-9);
let expected_v = DVec3::new(0.0, omega_earth * r, 0.0);
assert!(
(derived.trans.velocity - expected_v).length() < 1e-6,
"got {:?} expected {:?}",
derived.trans.velocity,
expected_v
);
assert!((derived.rot.ang_vel_this - parent.rot.ang_vel_this).length() < 1e-12);
}
#[test]
fn composite_offset_applies_struct_to_com_correction() {
let parent = RefFrameState::default();
let attach_offset = MassPointState {
position: DVec3::new(1.0e6, 0.0, 0.0),
t_parent_this: JeodQuat::identity().left_quat_to_transformation(),
};
let composite_offset = MassPointState {
position: DVec3::new(-3.0, -1.5, 4.0),
t_parent_this: JeodQuat::identity().left_quat_to_transformation(),
};
let derived = derive_frame_attached_state(FrameAttachInputs {
parent_frame: parent,
attach_offset,
composite_offset,
});
let expected = attach_offset.position + composite_offset.position;
assert!(
(derived.trans.position - expected).length() < 1e-12,
"expected {expected:?}, got {:?}",
derived.trans.position
);
let no_correction = derive_frame_attached_state(FrameAttachInputs {
parent_frame: parent,
attach_offset,
composite_offset: MassPointState::default(),
});
let delta = (derived.trans.position - no_correction.trans.position).length();
let expected_delta = composite_offset.position.length();
assert!(
(delta - expected_delta).abs() < 1e-12,
"CoM correction magnitude should be {expected_delta} m, got {delta}"
);
}
#[test]
fn forward_propagate_round_trips_via_reverse() {
use crate::propagation::propagate_reverse;
let q_parent =
JeodQuat::left_quat_from_eigen_rotation(0.5, DVec3::new(0.2, 0.7, 1.0).normalize());
let parent = RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(7e6, 1e5, -3e4),
velocity: DVec3::new(7300.0, -50.0, 13.0),
},
rot: RefFrameRot {
q_parent_this: q_parent,
t_parent_this: q_parent.left_quat_to_transformation(),
ang_vel_this: DVec3::new(1e-4, -2e-4, 3e-4),
},
};
let q_offset =
JeodQuat::left_quat_from_eigen_rotation(0.3, DVec3::new(1.0, -0.4, 0.2).normalize());
let offset = MassPointState {
position: DVec3::new(2.5, -1.0, 0.5),
t_parent_this: q_offset.left_quat_to_transformation(),
};
let derived = derive_frame_attached_state(FrameAttachInputs {
parent_frame: parent,
attach_offset: offset,
composite_offset: MassPointState::default(),
});
let recovered = propagate_reverse(&derived, &offset);
assert!((recovered.trans.position - parent.trans.position).length() < 1e-6);
assert!((recovered.trans.velocity - parent.trans.velocity).length() < 1e-6);
assert!((recovered.rot.ang_vel_this - parent.rot.ang_vel_this).length() < 1e-9);
}
}