use super::StateParameter;
use crate::celestia::{Cosm, Frame, Orbit};
use crate::dimensions::allocator::Allocator;
use crate::dimensions::DefaultAllocator;
use crate::time::{Duration, TimeUnit};
use crate::utils::between_pm_x;
use crate::{SpacecraftState, State};
use std::fmt;
use std::sync::Arc;
fn angled_value(cur_angle: f64, desired_angle: f64) -> f64 {
if between_pm_x(cur_angle, desired_angle) > 0.0 {
cur_angle - desired_angle
} else {
cur_angle + 2.0 * desired_angle
}
}
pub trait EventEvaluator<S: State>: fmt::Display + Send + Sync
where
DefaultAllocator: Allocator<f64, S::Size>,
{
fn eval_crossing(&self, prev_state: &S, next_state: &S) -> bool {
self.eval(prev_state) * self.eval(next_state) < 0.0
}
fn eval(&self, state: &S) -> f64;
fn epoch_precision(&self) -> Duration;
fn value_precision(&self) -> f64;
}
#[derive(Clone, Debug)]
pub struct Event {
pub parameter: StateParameter,
pub desired_value: f64,
pub epoch_precision: TimeUnit,
pub value_precision: f64,
pub in_frame: Option<(Frame, Arc<Cosm>)>,
}
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.parameter)?;
if let Some((frame, _)) = self.in_frame {
write!(f, "in frame {}", frame)?;
}
fmt::Result::Ok(())
}
}
impl Event {
pub fn new(parameter: StateParameter, desired_value: f64) -> Self {
Self {
parameter,
desired_value,
epoch_precision: TimeUnit::Millisecond,
value_precision: parameter.default_event_precision(),
in_frame: None,
}
}
pub fn periapsis() -> Self {
Self::new(StateParameter::Periapsis, 0.0)
}
pub fn apoapsis() -> Self {
Self::new(StateParameter::Apoapsis, 180.0)
}
pub fn in_frame(
parameter: StateParameter,
desired_value: f64,
target_frame: Frame,
cosm: Arc<Cosm>,
) -> Self {
warn!("Searching for an event in another frame is slow: you should instead convert the trajectory into that other frame");
Self {
parameter,
desired_value,
epoch_precision: TimeUnit::Millisecond,
value_precision: 1e-3,
in_frame: Some((target_frame, cosm)),
}
}
}
impl EventEvaluator<Orbit> for Event {
#[allow(clippy::identity_op)]
fn epoch_precision(&self) -> Duration {
1 * self.epoch_precision
}
fn value_precision(&self) -> f64 {
self.value_precision
}
fn eval(&self, state: &Orbit) -> f64 {
let state = if let Some((frame, cosm)) = &self.in_frame {
if state.frame == *frame {
*state
} else {
cosm.frame_chg(state, *frame)
}
} else {
*state
};
match self.parameter {
StateParameter::AoL => angled_value(state.aol(), self.desired_value),
StateParameter::AoP => angled_value(state.aop(), self.desired_value),
StateParameter::Apoapsis => angled_value(state.ta(), 180.0),
StateParameter::Declination => angled_value(state.declination(), self.desired_value),
StateParameter::ApoapsisRadius => state.apoapsis() - self.desired_value,
StateParameter::EccentricAnomaly => angled_value(state.ea(), self.desired_value),
StateParameter::Eccentricity => state.ecc() - self.desired_value,
StateParameter::Energy => state.energy() - self.desired_value,
StateParameter::GeodeticHeight => state.geodetic_height() - self.desired_value,
StateParameter::GeodeticLatitude => state.geodetic_latitude() - self.desired_value,
StateParameter::GeodeticLongitude => state.geodetic_longitude() - self.desired_value,
StateParameter::Hmag => state.hmag() - self.desired_value,
StateParameter::HX => state.hx() - self.desired_value,
StateParameter::HY => state.hy() - self.desired_value,
StateParameter::HZ => state.hz() - self.desired_value,
StateParameter::Inclination => angled_value(state.inc(), self.desired_value),
StateParameter::MeanAnomaly => angled_value(state.ma(), self.desired_value),
StateParameter::Periapsis => between_pm_x(state.ta(), 180.0),
StateParameter::PeriapsisRadius => state.periapsis() - self.desired_value,
StateParameter::Period => state.period().in_seconds() - self.desired_value,
StateParameter::RightAscension => {
angled_value(state.right_ascension(), self.desired_value)
}
StateParameter::RAAN => angled_value(state.raan(), self.desired_value),
StateParameter::Rmag => state.rmag() - self.desired_value,
StateParameter::SemiParameter => state.semi_parameter() - self.desired_value,
StateParameter::SemiMinorAxis => state.semi_minor_axis() - self.desired_value,
StateParameter::SMA => state.sma() - self.desired_value,
StateParameter::TrueAnomaly => angled_value(state.ta(), self.desired_value),
StateParameter::TrueLongitude => angled_value(state.tlong(), self.desired_value),
StateParameter::Vmag => state.vmag() - self.desired_value,
StateParameter::X => state.x - self.desired_value,
StateParameter::Y => state.y - self.desired_value,
StateParameter::Z => state.z - self.desired_value,
StateParameter::VX => state.vx - self.desired_value,
StateParameter::VY => state.vy - self.desired_value,
StateParameter::VZ => state.vz - self.desired_value,
_ => unimplemented!(),
}
}
}
impl EventEvaluator<SpacecraftState> for Event {
fn eval(&self, state: &SpacecraftState) -> f64 {
match self.parameter {
StateParameter::FuelMass => state.fuel_mass_kg - self.desired_value,
_ => self.eval(&state.orbit),
}
}
#[allow(clippy::identity_op)]
fn epoch_precision(&self) -> Duration {
1 * self.epoch_precision
}
fn value_precision(&self) -> f64 {
self.value_precision
}
}
#[derive(Clone, Debug)]
pub struct ElevationEvent {}