use astrodyn_frames::{FrameId, FrameTree};
pub use astrodyn_frames::{
compute_relative_state_typed, frame_origin, frame_origin_typed, sync_pfix_rotation,
};
use crate::vehicle_config::{FrameSwitchConfig, SwitchSense};
use crate::GravityControls;
use crate::TranslationalState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FrameSwitchTargetMissing<SourceId = usize> {
pub body_idx: usize,
pub target_source: SourceId,
pub num_sources: usize,
}
#[allow(clippy::too_many_arguments)]
pub fn evaluate_and_apply_frame_switch<SourceId, F>(
frame_tree: &mut FrameTree,
root_frame_id: FrameId,
body_frame_id: FrameId,
integ_frame_id: &mut FrameId,
trans: &mut TranslationalState,
frame_switches: &mut [FrameSwitchConfig<SourceId>],
gravity_controls: &mut GravityControls<SourceId>,
resolve_source: F,
num_sources: usize,
body_idx: usize,
) -> Result<bool, FrameSwitchTargetMissing<SourceId>>
where
SourceId: Clone + PartialEq,
F: Fn(&SourceId) -> Option<FrameId>,
{
if frame_switches.is_empty() {
return Ok(false);
}
let mut switch_idx = None;
for (idx, sw) in frame_switches.iter().enumerate() {
if !sw.active {
continue;
}
let target_fid =
resolve_source(&sw.target_source).ok_or_else(|| FrameSwitchTargetMissing {
body_idx,
target_source: sw.target_source.clone(),
num_sources,
})?;
let (target_origin, _) = frame_origin(frame_tree, root_frame_id, target_fid);
let (current_origin, _) = frame_origin(frame_tree, root_frame_id, *integ_frame_id);
let body_pos_eci = trans.position + current_origin;
let threshold_sq = sw.switch_distance * sw.switch_distance;
let triggered = match sw.switch_sense {
SwitchSense::OnApproach => {
(body_pos_eci - target_origin).length_squared() < threshold_sq
}
SwitchSense::OnDeparture => trans.position.length_squared() > threshold_sq,
};
if triggered {
switch_idx = Some(idx);
break;
}
}
let Some(idx) = switch_idx else {
return Ok(false);
};
let target_source = frame_switches[idx].target_source.clone();
frame_switches[idx].active = false;
let new_integ_fid = resolve_source(&target_source).expect(
"evaluate_and_apply_frame_switch: target source resolved during evaluation \
but failed during application — caller-side mutation between lookups",
);
frame_tree.reparent(body_frame_id, new_integ_fid);
let new_state = frame_tree.get(body_frame_id).state;
trans.position = new_state.trans.position;
trans.velocity = new_state.trans.velocity;
*integ_frame_id = new_integ_fid;
for ctrl in &mut gravity_controls.controls {
ctrl.differential = ctrl.source_name != target_source;
}
Ok(true)
}