use crate::mass_body::MassPointState;
use astrodyn_frames::{RefFrameRot, RefFrameState, RefFrameTrans};
use astrodyn_math::JeodQuat;
pub fn propagate_forward(source: &RefFrameState, rel: &MassPointState) -> RefFrameState {
let t_derived = rel.t_parent_this * source.rot.t_parent_this;
let q_derived = JeodQuat::left_quat_from_transformation(&t_derived);
let ang_vel_derived = rel.t_parent_this * source.rot.ang_vel_this;
let pos_derived = source.trans.position + source.rot.t_parent_this.transpose() * rel.position;
let omega_cross_r = source.rot.ang_vel_this.cross(rel.position);
let vel_derived = source.trans.velocity + source.rot.t_parent_this.transpose() * omega_cross_r;
RefFrameState {
trans: RefFrameTrans {
position: pos_derived,
velocity: vel_derived,
},
rot: RefFrameRot {
q_parent_this: q_derived,
t_parent_this: t_derived,
ang_vel_this: ang_vel_derived,
},
}
}
pub fn propagate_reverse(derived: &RefFrameState, rel: &MassPointState) -> RefFrameState {
let t_source = rel.t_parent_this.transpose() * derived.rot.t_parent_this;
let q_source = JeodQuat::left_quat_from_transformation(&t_source);
let ang_vel_source = rel.t_parent_this.transpose() * derived.rot.ang_vel_this;
let pos_source = derived.trans.position - t_source.transpose() * rel.position;
let omega_cross_r = ang_vel_source.cross(rel.position);
let vel_source = derived.trans.velocity - t_source.transpose() * omega_cross_r;
RefFrameState {
trans: RefFrameTrans {
position: pos_source,
velocity: vel_source,
},
rot: RefFrameRot {
q_parent_this: q_source,
t_parent_this: t_source,
ang_vel_this: ang_vel_source,
},
}
}
pub fn propagate_body_frames(
structure: &RefFrameState,
composite_offset: &MassPointState,
core_offset: &MassPointState,
) -> (RefFrameState, RefFrameState) {
let composite = propagate_forward(structure, composite_offset);
let core = propagate_forward(structure, core_offset);
(composite, core)
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_frames::RefFrameRot;
use astrodyn_math::test_utils::{approx_eq_f64, approx_eq_mat3, approx_eq_vec3};
use glam::{DMat3, DVec3};
use std::f64::consts::FRAC_PI_4;
const TOL: f64 = 1e-14;
fn make_state(
angle: f64,
axis: DVec3,
pos: DVec3,
vel: DVec3,
ang_vel: DVec3,
) -> RefFrameState {
let q = JeodQuat::left_quat_from_eigen_rotation(angle, axis);
let t = q.left_quat_to_transformation();
RefFrameState {
trans: RefFrameTrans {
position: pos,
velocity: vel,
},
rot: RefFrameRot {
q_parent_this: q,
t_parent_this: t,
ang_vel_this: ang_vel,
},
}
}
#[test]
fn forward_identity_offset() {
let source = make_state(
0.5,
DVec3::Z,
DVec3::new(1e7, 0.0, 0.0),
DVec3::new(7000.0, 0.0, 0.0),
DVec3::new(0.0, 0.0, 0.001),
);
let rel = MassPointState::default();
let derived = propagate_forward(&source, &rel);
assert!(
approx_eq_vec3(derived.trans.position, source.trans.position, TOL),
"identity offset: position"
);
assert!(
approx_eq_vec3(derived.trans.velocity, source.trans.velocity, TOL),
"identity offset: velocity"
);
assert!(
approx_eq_mat3(&derived.rot.t_parent_this, &source.rot.t_parent_this, TOL),
"identity offset: T"
);
assert!(
approx_eq_vec3(derived.rot.ang_vel_this, source.rot.ang_vel_this, TOL),
"identity offset: ang_vel"
);
}
#[test]
fn forward_translation_offset() {
let source = RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(1e7, 0.0, 0.0),
velocity: DVec3::new(7000.0, 0.0, 0.0),
},
rot: RefFrameRot::default(),
};
let rel = MassPointState {
position: DVec3::new(2.0, 0.0, 0.0),
t_parent_this: DMat3::IDENTITY,
};
let derived = propagate_forward(&source, &rel);
let expected_pos = DVec3::new(1e7 + 2.0, 0.0, 0.0);
assert!(
approx_eq_vec3(derived.trans.position, expected_pos, TOL),
"translation offset: position, expected {:?} got {:?}",
expected_pos,
derived.trans.position
);
assert!(
approx_eq_vec3(derived.trans.velocity, source.trans.velocity, TOL),
"translation offset: velocity"
);
}
#[test]
fn forward_reverse_round_trip() {
let source = make_state(
0.7,
DVec3::new(1.0, 1.0, 1.0).normalize(),
DVec3::new(6.778e6, 0.0, 0.0),
DVec3::new(0.0, 7672.0, 0.0),
DVec3::new(0.001, -0.002, 0.0011),
);
let rel = MassPointState {
position: DVec3::new(0.5, -0.3, 0.1),
t_parent_this: {
let q = JeodQuat::left_quat_from_eigen_rotation(0.05, DVec3::Y);
q.left_quat_to_transformation()
},
};
let derived = propagate_forward(&source, &rel);
let recovered = propagate_reverse(&derived, &rel);
assert!(
approx_eq_vec3(recovered.trans.position, source.trans.position, 1e-8),
"round-trip position: expected {:?}, got {:?}",
source.trans.position,
recovered.trans.position
);
assert!(
approx_eq_vec3(recovered.trans.velocity, source.trans.velocity, 1e-8),
"round-trip velocity: expected {:?}, got {:?}",
source.trans.velocity,
recovered.trans.velocity
);
assert!(
approx_eq_mat3(
&recovered.rot.t_parent_this,
&source.rot.t_parent_this,
1e-12
),
"round-trip T"
);
assert!(
approx_eq_vec3(recovered.rot.ang_vel_this, source.rot.ang_vel_this, 1e-12),
"round-trip ang_vel"
);
}
#[test]
fn propagate_body_frames_iss() {
let structure = make_state(
FRAC_PI_4,
DVec3::Z,
DVec3::new(6.778e6, 0.0, 0.0),
DVec3::new(0.0, 7672.0, 0.0),
DVec3::new(0.0, 0.0, 0.0011), );
let composite_offset = MassPointState {
position: DVec3::new(1.0, 0.5, 0.0),
t_parent_this: DMat3::IDENTITY,
};
let core_offset = MassPointState {
position: DVec3::new(0.8, 0.4, 0.1),
t_parent_this: DMat3::IDENTITY,
};
let (composite, core) = propagate_body_frames(&structure, &composite_offset, &core_offset);
let t_t = structure.rot.t_parent_this.transpose();
let expected_comp_pos = structure.trans.position + t_t * composite_offset.position;
assert!(
approx_eq_vec3(composite.trans.position, expected_comp_pos, 1e-8),
"body frames: composite position, expected {:?} got {:?}",
expected_comp_pos,
composite.trans.position
);
let expected_core_pos = structure.trans.position + t_t * core_offset.position;
assert!(
approx_eq_vec3(core.trans.position, expected_core_pos, 1e-8),
"body frames: core position, expected {:?} got {:?}",
expected_core_pos,
core.trans.position
);
let diff = (composite.trans.position - core.trans.position).length();
assert!(
diff > 0.01,
"composite and core positions should differ, diff = {}",
diff
);
}
#[test]
fn rotation_consistency() {
let source = make_state(
1.2,
DVec3::new(1.0, 2.0, 3.0).normalize(),
DVec3::new(5e6, -3e6, 1e6),
DVec3::new(500.0, -300.0, 100.0),
DVec3::new(0.05, -0.02, 0.01),
);
let rel = MassPointState {
position: DVec3::new(1.5, -0.8, 0.3),
t_parent_this: {
let q = JeodQuat::left_quat_from_eigen_rotation(0.3, DVec3::X);
q.left_quat_to_transformation()
},
};
let derived = propagate_forward(&source, &rel);
let product = derived.rot.t_parent_this * derived.rot.t_parent_this.transpose();
assert!(
approx_eq_mat3(&product, &DMat3::IDENTITY, 1e-12),
"T * T^T should be identity, got {:?}",
product
);
let det = derived.rot.t_parent_this.determinant();
assert!(
approx_eq_f64(det, 1.0, 1e-12),
"det(T) should be 1, got {}",
det
);
}
}