use astrodyn_quantities::aliases::{Position, Velocity};
use astrodyn_quantities::dims::GravParam;
use astrodyn_quantities::frame::{Planet, PlanetInertial};
use glam::DVec3;
#[derive(Debug, Clone, Copy)]
pub struct PeriapsisEvent {
pub time: f64,
pub long_perihelion: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct PeriapsisDetector {
prev_rdot: f64,
initialized: bool,
}
impl Default for PeriapsisDetector {
fn default() -> Self {
Self::new()
}
}
impl PeriapsisDetector {
pub fn new() -> Self {
Self {
prev_rdot: 0.0,
initialized: false,
}
}
pub fn reset(&mut self) {
self.initialized = false;
self.prev_rdot = 0.0;
}
pub fn observe(&mut self, pos: DVec3, vel: DVec3) -> bool {
let r_dot = pos.dot(vel) / pos.length();
let crossed = self.initialized && self.prev_rdot < 0.0 && r_dot >= 0.0;
self.prev_rdot = r_dot;
self.initialized = true;
crossed
}
}
pub fn detect_periapsis_passages<P, I>(samples: I, mu: GravParam<P>) -> Vec<PeriapsisEvent>
where
P: Planet,
I: IntoIterator<
Item = (
f64,
Position<PlanetInertial<P>>,
Velocity<PlanetInertial<P>>,
),
>,
{
use astrodyn_math::OrbitalElements;
let mut det = PeriapsisDetector::new();
let mut events = Vec::new();
for (t, r, v) in samples {
let r_raw = r.raw_si();
let v_raw = v.raw_si();
if det.observe(r_raw, v_raw) {
let oe = OrbitalElements::<P>::from_cartesian_typed(mu, r, v).unwrap_or_else(|e| {
panic!(
"periapsis_detection: from_cartesian_typed failed at t={t}, \
position={r_raw:?}, velocity={v_raw:?}: {e:?}"
)
});
events.push(PeriapsisEvent {
time: t,
long_perihelion: oe.arg_periapsis + oe.long_asc_node,
});
}
}
events
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detector_emits_on_negative_to_positive_crossing() {
let mut det = PeriapsisDetector::new();
let crossed_1 = det.observe(DVec3::X, -DVec3::X);
assert!(!crossed_1, "first sample seeds prev_rdot, no event");
let crossed_2 = det.observe(DVec3::X, DVec3::X);
assert!(crossed_2);
}
#[test]
fn detector_quiet_when_monotonic() {
let mut det = PeriapsisDetector::new();
det.observe(DVec3::X, DVec3::X); let crossed = det.observe(DVec3::X, DVec3::X);
assert!(!crossed);
}
}