use super::{Cosm, Frame, LTCorr, State};
use std::cmp::{Eq, Ord, Ordering, PartialOrd};
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum EclipseState {
Umbra,
Penumbra(f64),
Visibilis,
}
impl Eq for EclipseState {}
impl Ord for EclipseState {
fn cmp(&self, other: &Self) -> Ordering {
match *self {
EclipseState::Umbra => {
if *other == EclipseState::Umbra {
Ordering::Equal
} else {
Ordering::Greater
}
}
EclipseState::Visibilis => {
if *other == EclipseState::Visibilis {
Ordering::Equal
} else {
Ordering::Less
}
}
EclipseState::Penumbra(s) => match *other {
EclipseState::Penumbra(o) => {
if s > o {
Ordering::Greater
} else {
Ordering::Less
}
}
EclipseState::Visibilis => Ordering::Greater,
EclipseState::Umbra => Ordering::Less,
},
}
}
}
impl PartialOrd for EclipseState {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for EclipseState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Self::Umbra => write!(f, "Umbra"),
Self::Visibilis => write!(f, "Visibilis"),
Self::Penumbra(v) => write!(f, "Penumbra {}%", v * 100.0),
}
}
}
#[derive(Clone)]
pub struct EclipseLocator<'a> {
pub light_source: Frame,
pub shadow_bodies: Vec<Frame>,
pub cosm: &'a Cosm,
pub correction: LTCorr,
}
impl<'a> EclipseLocator<'a> {
pub fn compute(&self, observer: &State) -> EclipseState {
let mut state = EclipseState::Visibilis;
for eclipsing_body in &self.shadow_bodies {
let this_state = eclipse_state(
observer,
self.light_source,
*eclipsing_body,
self.cosm,
self.correction,
);
if this_state > state {
state = this_state;
}
}
state
}
}
pub fn eclipse_state(
observer: &State,
light_source: Frame,
eclipsing_body: Frame,
cosm: &Cosm,
correction: LTCorr,
) -> EclipseState {
assert!(light_source.is_geoid() || light_source.is_celestial());
assert!(eclipsing_body.is_geoid());
if light_source.equatorial_radius() < std::f64::EPSILON {
let observed = cosm.celestial_state(
light_source.exb_id(),
observer.dt,
observer.frame,
correction,
);
return line_of_sight(observer, &observed, eclipsing_body, &cosm);
}
let r_eb = -cosm.frame_chg(observer, eclipsing_body).radius();
let r_eb_unit = r_eb / r_eb.norm();
let r_eb_ls = cosm
.celestial_state(
light_source.exb_id(),
observer.dt,
eclipsing_body,
correction,
)
.radius();
let r_eb_ls_unit = r_eb_ls / r_eb_ls.norm();
let beta2 = r_eb_unit.dot(&r_eb_ls_unit).acos();
if beta2 <= std::f64::consts::FRAC_PI_2 {
return EclipseState::Visibilis;
}
let r_ls = r_eb + r_eb_ls;
let r_ls_unit = r_ls / r_ls.norm();
let cos_beta3 = r_ls_unit.dot(&r_eb_unit);
let r_ls_p = (r_eb.norm() / cos_beta3) * r_ls_unit;
let beta1 = (-r_ls_unit).dot(&-r_eb_ls_unit).acos();
let gamma = std::f64::consts::FRAC_PI_2 - beta2 - beta1;
let pseudo_ls_radius = r_ls_p.norm() * light_source.equatorial_radius() / r_ls.norm();
let ls_radius = pseudo_ls_radius / gamma.cos();
let r_plane_ls = r_ls_p - r_eb;
let d_plane_ls = r_plane_ls.norm();
let eb_radius = eclipsing_body.equatorial_radius();
if d_plane_ls - ls_radius > eb_radius {
return EclipseState::Visibilis;
} else if eb_radius > d_plane_ls + ls_radius {
return EclipseState::Umbra;
}
let d1 = (d_plane_ls.powi(2) - ls_radius.powi(2) + eb_radius.powi(2)) / (2.0 * d_plane_ls);
let d2 = (d_plane_ls.powi(2) + ls_radius.powi(2) - eb_radius.powi(2)) / (2.0 * d_plane_ls);
let shadow_area = circ_seg_area(eb_radius, d1) + circ_seg_area(ls_radius, d2);
if shadow_area.is_nan() {
return EclipseState::Umbra;
}
let nominal_area = std::f64::consts::PI * ls_radius.powi(2);
EclipseState::Penumbra((nominal_area - shadow_area) / nominal_area)
}
fn circ_seg_area(r: f64, d: f64) -> f64 {
r.powi(2) * (d / r).acos() - d * (r.powi(2) - d.powi(2)).sqrt()
}
pub fn line_of_sight(
observer: &State,
observed: &State,
eclipsing_body: Frame,
cosm: &Cosm,
) -> EclipseState {
if observer == observed {
return EclipseState::Visibilis;
}
let observed_fok = &cosm.frame_chg(observed, eclipsing_body);
let observer_fok = &cosm.frame_chg(observer, eclipsing_body);
let mut l = observed_fok.radius() - observer_fok.radius();
l /= l.norm();
let omc = observer_fok.radius();
let r = &eclipsing_body.equatorial_radius();
let discriminant_sq = (l.dot(&omc)).powi(2) - l.dot(&l) * (omc.dot(&omc) - (r.powi(2)));
if discriminant_sq < -1e-12 {
EclipseState::Visibilis
} else {
let intersect_dist = -(l.dot(&omc)) + discriminant_sq.sqrt();
let intersect_pt = observer_fok.radius() + intersect_dist * l;
let dist_ori_inters = observer_fok.distance_to_point(&intersect_pt);
let dist_oth_inters = observed_fok.distance_to_point(&intersect_pt);
let dist_ori_oth = observer_fok.distance_to(&observed_fok);
if (dist_ori_inters + dist_oth_inters - dist_ori_oth).abs() < 1e-15 {
if discriminant_sq > 1e-12 {
EclipseState::Umbra
} else {
EclipseState::Penumbra(0.5)
}
} else {
EclipseState::Visibilis
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hifitime::Epoch;
#[test]
fn los_trivial() {
}
#[test]
fn los_earth_eclipse() {
let cosm = Cosm::from_xb("./de438s");
let eme2k = cosm.frame("EME2000");
let dt = Epoch::from_gregorian_tai_at_midnight(2020, 1, 1);
let sma = eme2k.equatorial_radius() + 300.0;
let sc1 = State::keplerian(sma, 0.001, 0.1, 90.0, 75.0, 0.0, dt, eme2k);
let sc2 = State::keplerian(sma + 1.0, 0.001, 0.1, 90.0, 75.0, 0.0, dt, eme2k);
let sc3 = State::keplerian(sma, 0.001, 0.1, 90.0, 75.0, 180.0, dt, eme2k);
assert_eq!(line_of_sight(&sc1, &sc3, eme2k, &cosm), EclipseState::Umbra);
assert_eq!(
line_of_sight(&sc1, &sc2, eme2k, &cosm),
EclipseState::Visibilis
);
}
#[test]
fn eclipse_sun_eclipse() {
let cosm = Cosm::from_xb("./de438s");
let sun = cosm.frame("Sun J2000");
let eme2k = cosm.frame("EME2000");
let dt = Epoch::from_gregorian_tai_at_midnight(2020, 1, 1);
let sma = eme2k.equatorial_radius() + 300.0;
let sc1 = State::keplerian(sma, 0.001, 0.1, 90.0, 75.0, 25.0, dt, eme2k);
let sc2 = State::keplerian(sma, 0.001, 0.1, 90.0, 75.0, 115.0, dt, eme2k);
let sc3 = State::keplerian(sma, 0.001, 0.1, 90.0, 75.0, 77.2, dt, eme2k);
let correction = LTCorr::None;
assert_eq!(
eclipse_state(&sc1, sun, eme2k, &cosm, correction),
EclipseState::Visibilis
);
assert_eq!(
eclipse_state(&sc2, sun, eme2k, &cosm, correction),
EclipseState::Umbra
);
match eclipse_state(&sc3, sun, eme2k, &cosm, correction) {
EclipseState::Penumbra(val) => assert!(val > 0.9),
_ => panic!("should be in penumbra"),
};
}
}