use std::marker::PhantomData;
use qtty::{LengthUnit, Meter, Quantity};
use super::internal::{
classify_eccentricity_unchecked, validate_eccentricity, validate_positive_length,
};
use super::{
sealed, ConicKind, ConicShape, ConicValidationError, Elliptic, Hyperbolic, KindMarker,
Parabolic, SemiMajorAxisParam, TypedSemiMajorAxisParam,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PeriapsisParam<U: LengthUnit = Meter> {
periapsis_distance: Quantity<U>,
eccentricity: f64,
}
impl<U: LengthUnit> sealed::ConicShapeSealed for PeriapsisParam<U> {}
impl<U: LengthUnit> ConicShape for PeriapsisParam<U> {
fn shape_name() -> &'static str {
"periapsis_distance"
}
fn eccentricity(&self) -> f64 {
self.eccentricity
}
fn kind(&self) -> ConicKind {
classify_eccentricity_unchecked(self.eccentricity)
}
}
impl<U: LengthUnit> PeriapsisParam<U> {
pub fn try_new(
periapsis_distance: Quantity<U>,
eccentricity: f64,
) -> Result<Self, ConicValidationError> {
validate_positive_length(
periapsis_distance,
ConicValidationError::InvalidPeriapsisDistance,
)?;
validate_eccentricity(eccentricity)?;
Ok(Self {
periapsis_distance,
eccentricity,
})
}
pub const fn new_unchecked(periapsis_distance: Quantity<U>, eccentricity: f64) -> Self {
Self {
periapsis_distance,
eccentricity,
}
}
#[inline]
pub const fn periapsis_distance(&self) -> Quantity<U> {
self.periapsis_distance
}
#[inline]
pub const fn eccentricity(&self) -> f64 {
self.eccentricity
}
pub fn to_semi_major_axis(&self) -> Option<SemiMajorAxisParam<U>> {
let e = self.eccentricity;
if e == 1.0 {
return None;
}
let a = self.periapsis_distance.value() / (1.0 - e).abs();
if !a.is_finite() || a <= 0.0 {
return None;
}
Some(SemiMajorAxisParam::new_unchecked(Quantity::new(a), e))
}
pub fn classify(self) -> ClassifiedPeriapsisParam<U> {
match self.kind() {
ConicKind::Elliptic => {
ClassifiedPeriapsisParam::Elliptic(TypedPeriapsisParam::from_inner(self))
}
ConicKind::Parabolic => {
ClassifiedPeriapsisParam::Parabolic(TypedPeriapsisParam::from_inner(self))
}
ConicKind::Hyperbolic => {
ClassifiedPeriapsisParam::Hyperbolic(TypedPeriapsisParam::from_inner(self))
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClassifiedPeriapsisParam<U: LengthUnit = Meter> {
Elliptic(TypedPeriapsisParam<U, Elliptic>),
Parabolic(TypedPeriapsisParam<U, Parabolic>),
Hyperbolic(TypedPeriapsisParam<U, Hyperbolic>),
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TypedPeriapsisParam<U: LengthUnit, K: KindMarker> {
inner: PeriapsisParam<U>,
_kind: PhantomData<K>,
}
impl<U: LengthUnit, K: KindMarker> sealed::ConicShapeSealed for TypedPeriapsisParam<U, K> {}
impl<U: LengthUnit, K: KindMarker> ConicShape for TypedPeriapsisParam<U, K> {
fn shape_name() -> &'static str {
"periapsis_distance"
}
fn eccentricity(&self) -> f64 {
self.inner.eccentricity()
}
fn kind(&self) -> ConicKind {
K::conic_kind()
}
}
impl<U: LengthUnit, K: KindMarker> TypedPeriapsisParam<U, K> {
pub(super) const fn from_inner(inner: PeriapsisParam<U>) -> Self {
Self {
inner,
_kind: PhantomData,
}
}
#[inline]
pub const fn periapsis_distance(&self) -> Quantity<U> {
self.inner.periapsis_distance()
}
#[inline]
pub const fn eccentricity(&self) -> f64 {
self.inner.eccentricity()
}
#[inline]
pub const fn into_inner(self) -> PeriapsisParam<U> {
self.inner
}
}
impl<U: LengthUnit> TypedPeriapsisParam<U, Elliptic> {
pub fn to_semi_major_axis(&self) -> Option<TypedSemiMajorAxisParam<U, Elliptic>> {
self.inner
.to_semi_major_axis()
.map(TypedSemiMajorAxisParam::from_inner)
}
}
impl<U: LengthUnit> TypedPeriapsisParam<U, Hyperbolic> {
pub fn to_semi_major_axis(&self) -> Option<TypedSemiMajorAxisParam<U, Hyperbolic>> {
self.inner
.to_semi_major_axis()
.map(TypedSemiMajorAxisParam::from_inner)
}
}
#[cfg(feature = "serde")]
mod periapsis_serde {
use super::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize)]
#[serde(bound = "")]
struct PeriapsisParamProxy<U: LengthUnit> {
periapsis_distance: Quantity<U>,
eccentricity: f64,
}
impl<U: LengthUnit> Serialize for PeriapsisParam<U> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
PeriapsisParamProxy {
periapsis_distance: self.periapsis_distance,
eccentricity: self.eccentricity,
}
.serialize(s)
}
}
impl<'de, U: LengthUnit> Deserialize<'de> for PeriapsisParam<U> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let proxy = PeriapsisParamProxy::<U>::deserialize(d)?;
PeriapsisParam::try_new(proxy.periapsis_distance, proxy.eccentricity)
.map_err(serde::de::Error::custom)
}
}
impl<U: LengthUnit, K: KindMarker> Serialize for TypedPeriapsisParam<U, K>
where
PeriapsisParam<U>: Serialize,
{
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
self.inner.serialize(s)
}
}
impl<'de, U: LengthUnit, K: KindMarker> Deserialize<'de> for TypedPeriapsisParam<U, K>
where
PeriapsisParam<U>: Deserialize<'de>,
{
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let inner = PeriapsisParam::<U>::deserialize(d)?;
if inner.kind() != K::conic_kind() {
return Err(serde::de::Error::custom(
"eccentricity does not match the expected conic kind",
));
}
Ok(Self {
inner,
_kind: PhantomData,
})
}
}
}