use crate::mass::MassProperties;
use astrodyn_frames::{RefFrameRot, RefFrameState, RefFrameTrans};
use glam::{DMat3, DVec3};
#[derive(Debug, Clone, Copy)]
pub struct AttachCombineInputs {
pub parent_composite: RefFrameState,
pub parent_mass: MassProperties,
pub parent_t_inertial_struct: DMat3,
pub child_composite: RefFrameState,
pub child_mass: MassProperties,
pub combined_mass: MassProperties,
pub orig_parent_cm_struct: DVec3,
}
#[derive(Debug, Clone, Copy)]
pub struct AttachCombineOutputs {
pub composite_state: RefFrameState,
}
pub fn combine_states_at_attach(input: AttachCombineInputs) -> AttachCombineOutputs {
let parent = &input.parent_composite;
let child = &input.child_composite;
let m_p = input.parent_mass.mass;
let m_c = input.child_mass.mass;
let inv_m_t = input.combined_mass.inverse_mass;
let inv_i_t = input.combined_mass.inverse_inertia;
let v_p = parent.trans.velocity;
let v_c = child.trans.velocity;
let w_p = parent.rot.ang_vel_this;
let w_c = child.rot.ang_vel_this;
let v_t = (v_p * m_p + v_c * m_c) * inv_m_t;
let cm_delta_struct = input.combined_mass.position - input.orig_parent_cm_struct;
let cm_delta_inertial = input.parent_t_inertial_struct.transpose() * cm_delta_struct;
let new_composite_position = parent.trans.position + cm_delta_inertial;
let l_p_pbdy = input.parent_mass.inertia * w_p;
let l_c_cbdy = input.child_mass.inertia * w_c;
let l_c_inertial = child.rot.t_parent_this.transpose() * l_c_cbdy;
let r_c_rel = child.trans.position - new_composite_position;
let v_c_rel = v_c - v_t;
let v_p_rel = v_p - v_t;
let l_c_vel = r_c_rel.cross(v_c_rel * m_c);
let l_p_vel = (v_p_rel * m_p).cross(cm_delta_inertial);
let l_inertial = l_c_vel + l_p_vel + l_c_inertial;
let l_in_pbody = parent.rot.t_parent_this * l_inertial;
let l_total_pbody = l_in_pbody + l_p_pbdy;
let ang_vel_this = inv_i_t * l_total_pbody;
AttachCombineOutputs {
composite_state: RefFrameState {
trans: RefFrameTrans {
position: new_composite_position,
velocity: v_t,
},
rot: RefFrameRot {
q_parent_this: parent.rot.q_parent_this,
t_parent_this: parent.rot.t_parent_this,
ang_vel_this,
},
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use astrodyn_math::JeodQuat;
#[test]
fn no_relative_motion_keeps_state() {
let parent_mass = MassProperties::with_inertia(
100.0,
DMat3::from_diagonal(DVec3::new(50.0, 50.0, 50.0)),
DVec3::ZERO,
);
let child_mass = MassProperties::with_inertia(
50.0,
DMat3::from_diagonal(DVec3::new(20.0, 20.0, 20.0)),
DVec3::ZERO,
);
let combined_mass = MassProperties::with_inertia(
150.0,
DMat3::from_diagonal(DVec3::new(70.0, 70.0, 70.0)),
DVec3::ZERO, );
let v = DVec3::new(0.0, 7600.0, 0.0);
let w = DVec3::new(0.0, -1.13e-3, 0.0);
let parent = RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(7e6, 0.0, 0.0),
velocity: v,
},
rot: RefFrameRot {
ang_vel_this: w,
..Default::default()
},
};
let child = RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(7e6, 0.0, 0.0),
velocity: v,
},
rot: RefFrameRot {
ang_vel_this: w,
..Default::default()
},
};
let out = combine_states_at_attach(AttachCombineInputs {
parent_composite: parent,
parent_mass,
parent_t_inertial_struct: DMat3::IDENTITY,
child_composite: child,
child_mass,
combined_mass,
orig_parent_cm_struct: DVec3::ZERO,
});
assert!((out.composite_state.trans.velocity - v).length() < 1e-9);
assert!((out.composite_state.rot.ang_vel_this - w).length() < 1e-9);
}
#[test]
fn linear_momentum_conserved_across_combine() {
let parent_mass = MassProperties::with_inertia(
420.0,
DMat3::from_diagonal(DVec3::new(150.0, 200.0, 250.0)),
DVec3::ZERO,
);
let child_mass = MassProperties::with_inertia(
80.0,
DMat3::from_diagonal(DVec3::new(40.0, 50.0, 60.0)),
DVec3::ZERO,
);
let combined_mass = MassProperties::with_inertia(
500.0,
DMat3::from_diagonal(DVec3::new(190.0, 250.0, 310.0)),
DVec3::new(0.48, 0.0, 0.0),
);
let q = JeodQuat::identity();
let v_p = DVec3::new(7000.0, -50.0, 13.0);
let v_c = DVec3::new(7100.0, 25.0, -7.0);
let out = combine_states_at_attach(AttachCombineInputs {
parent_composite: RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(7e6, 0.0, 0.0),
velocity: v_p,
},
rot: RefFrameRot {
q_parent_this: q,
t_parent_this: q.left_quat_to_transformation(),
ang_vel_this: DVec3::new(0.0, 0.0, 1e-3),
},
},
parent_mass,
parent_t_inertial_struct: DMat3::IDENTITY,
child_composite: RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(7e6 + 3.0, 0.0, 0.0),
velocity: v_c,
},
rot: RefFrameRot {
q_parent_this: q,
t_parent_this: q.left_quat_to_transformation(),
ang_vel_this: DVec3::new(0.0, 0.0, -2e-3),
},
},
child_mass,
combined_mass,
orig_parent_cm_struct: DVec3::ZERO,
});
let p_pre = parent_mass.mass * v_p + child_mass.mass * v_c;
let p_post = combined_mass.mass * out.composite_state.trans.velocity;
assert!(
(p_post - p_pre).length() < 1e-9,
"linear momentum violation: pre={p_pre:?} post={p_post:?}"
);
}
#[test]
fn parent_position_round_trips_under_combine_then_inverse_split() {
let parent_mass = MassProperties::with_inertia(
420.0,
DMat3::from_diagonal(DVec3::new(150.0, 200.0, 250.0)),
DVec3::ZERO,
);
let child_mass = MassProperties::with_inertia(
80.0,
DMat3::from_diagonal(DVec3::new(40.0, 50.0, 60.0)),
DVec3::ZERO,
);
let combined_mass = MassProperties::with_inertia(
500.0,
DMat3::from_diagonal(DVec3::new(190.0, 250.0, 310.0)),
DVec3::new(0.48, 0.0, 0.0),
);
let parent_pre_position = DVec3::new(6.7e6, 1.0e5, -3.0e4);
let parent_pre_velocity = DVec3::new(7300.0, -50.0, 13.0);
let q = JeodQuat::identity();
let out = combine_states_at_attach(AttachCombineInputs {
parent_composite: RefFrameState {
trans: RefFrameTrans {
position: parent_pre_position,
velocity: parent_pre_velocity,
},
rot: RefFrameRot {
q_parent_this: q,
t_parent_this: q.left_quat_to_transformation(),
ang_vel_this: DVec3::ZERO,
},
},
parent_mass,
parent_t_inertial_struct: DMat3::IDENTITY,
child_composite: RefFrameState {
trans: RefFrameTrans {
position: parent_pre_position + DVec3::new(3.0, 0.0, 0.0),
velocity: parent_pre_velocity,
},
rot: RefFrameRot {
q_parent_this: q,
t_parent_this: q.left_quat_to_transformation(),
ang_vel_this: DVec3::ZERO,
},
},
child_mass,
combined_mass,
orig_parent_cm_struct: DVec3::ZERO,
});
let cm_delta_inverse_struct = parent_mass.position - combined_mass.position;
let parent_post_detach = out.composite_state.trans.position + cm_delta_inverse_struct;
assert!(
(parent_post_detach - parent_pre_position).length() < 1e-9,
"round-trip violation: pre={parent_pre_position:?} post={parent_post_detach:?}"
);
}
#[test]
fn relative_velocity_at_offset_induces_spin() {
let parent_mass = MassProperties::with_inertia(
1000.0,
DMat3::from_diagonal(DVec3::new(500.0, 500.0, 500.0)),
DVec3::ZERO,
);
let child_mass = MassProperties::with_inertia(
1000.0,
DMat3::from_diagonal(DVec3::new(500.0, 500.0, 500.0)),
DVec3::ZERO,
);
let combined_mass = MassProperties::with_inertia(
2000.0,
DMat3::from_diagonal(DVec3::new(2000.0, 2000.0, 2000.0)),
DVec3::new(1.0, 0.0, 0.0), );
let parent = RefFrameState {
trans: RefFrameTrans {
position: DVec3::ZERO,
velocity: DVec3::ZERO,
},
..Default::default()
};
let child = RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(2.0, 0.0, 0.0),
velocity: DVec3::new(0.0, 1.0, 0.0),
},
..Default::default()
};
let out = combine_states_at_attach(AttachCombineInputs {
parent_composite: parent,
parent_mass,
parent_t_inertial_struct: DMat3::IDENTITY,
child_composite: child,
child_mass,
combined_mass,
orig_parent_cm_struct: DVec3::ZERO,
});
let expected_v = DVec3::new(0.0, 0.5, 0.0);
let expected_w = DVec3::new(0.0, 0.0, 0.5);
assert!(
(out.composite_state.trans.velocity - expected_v).length() < 1e-9,
"got {:?}",
out.composite_state.trans.velocity
);
assert!(
(out.composite_state.rot.ang_vel_this - expected_w).length() < 1e-9,
"got {:?}",
out.composite_state.rot.ang_vel_this
);
}
}