use crate::math::Vector3;
use core::fmt;
use der::{Decode, Encode, Reader, Writer};
use serde_derive::{Deserialize, Serialize};
#[cfg(feature = "metaload")]
use serde_dhall::StaticType;
#[cfg(feature = "python")]
use pyo3::exceptions::PyTypeError;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::pyclass::CompareOp;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "metaload", derive(StaticType))]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "python", pyo3(module = "anise.astro"))]
pub struct Ellipsoid {
pub semi_major_equatorial_radius_km: f64,
pub semi_minor_equatorial_radius_km: f64,
pub polar_radius_km: f64,
}
impl Ellipsoid {
pub fn from_sphere(radius_km: f64) -> Self {
Self {
semi_major_equatorial_radius_km: radius_km,
semi_minor_equatorial_radius_km: radius_km,
polar_radius_km: radius_km,
}
}
pub fn from_spheroid(equatorial_radius_km: f64, polar_radius_km: f64) -> Self {
Self {
semi_major_equatorial_radius_km: equatorial_radius_km,
semi_minor_equatorial_radius_km: equatorial_radius_km,
polar_radius_km,
}
}
pub fn intersect(&self, view_point: Vector3, view_direction: Vector3) -> Option<Vector3> {
let a = self.semi_major_equatorial_radius_km;
let b = self.semi_minor_equatorial_radius_km;
let c = self.polar_radius_km;
let origin = Vector3::new(view_point.x / a, view_point.y / b, view_point.z / c);
let direction = Vector3::new(
view_direction.x / a,
view_direction.y / b,
view_direction.z / c,
);
let a_coeff = direction.dot(&direction);
let b_coeff = 2.0 * origin.dot(&direction);
let c_coeff = origin.dot(&origin) - 1.0;
let discriminant = b_coeff * b_coeff - 4.0 * a_coeff * c_coeff;
if discriminant < 0.0 {
return None; }
let sqrt_disc = discriminant.sqrt();
let t1 = (-b_coeff - sqrt_disc) / (2.0 * a_coeff);
let t2 = (-b_coeff + sqrt_disc) / (2.0 * a_coeff);
let t = if t1 > 1e-9 {
t1
} else if t2 > 1e-9 {
t2
} else {
return None; };
Some(view_point + view_direction * t)
}
pub fn surface_normal(&self, surface_point: Vector3) -> Vector3 {
Vector3::new(
surface_point.x / self.semi_major_equatorial_radius_km.powi(2),
surface_point.y / self.semi_minor_equatorial_radius_km.powi(2),
surface_point.z / self.polar_radius_km.powi(2),
)
.normalize()
}
pub fn emission_angle_deg(&self, surface_point: Vector3, observer_pos_body: Vector3) -> f64 {
let normal = self.surface_normal(surface_point);
let vec_to_observer = (observer_pos_body - surface_point).normalize();
normal
.dot(&vec_to_observer)
.clamp(-1.0, 1.0)
.acos()
.to_degrees()
}
pub fn solar_incidence_angle_deg(&self, surface_point: Vector3, sun_pos_body: Vector3) -> f64 {
let normal = self.surface_normal(surface_point);
let vec_to_sun = (sun_pos_body - surface_point).normalize();
normal.dot(&vec_to_sun).clamp(-1.0, 1.0).acos().to_degrees()
}
}
#[cfg_attr(feature = "python", pymethods)]
#[cfg(feature = "python")]
impl Ellipsoid {
#[new]
#[pyo3(signature=(semi_major_equatorial_radius_km, polar_radius_km=None, semi_minor_equatorial_radius_km=None))]
fn py_new(
semi_major_equatorial_radius_km: f64,
polar_radius_km: Option<f64>,
semi_minor_equatorial_radius_km: Option<f64>,
) -> Self {
match polar_radius_km {
Some(polar_radius_km) => match semi_minor_equatorial_radius_km {
Some(semi_minor_equatorial_radius_km) => Self {
semi_major_equatorial_radius_km,
semi_minor_equatorial_radius_km,
polar_radius_km,
},
None => Self::from_spheroid(semi_major_equatorial_radius_km, polar_radius_km),
},
None => Self::from_sphere(semi_major_equatorial_radius_km),
}
}
fn __str__(&self) -> String {
format!("{self}")
}
fn __repr__(&self) -> String {
format!("{self} (@{self:p})")
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> Result<bool, PyErr> {
match op {
CompareOp::Eq => Ok(self == other),
CompareOp::Ne => Ok(self != other),
_ => Err(PyErr::new::<PyTypeError, _>(format!(
"{op:?} not available"
))),
}
}
fn __getnewargs__(&self) -> Result<(f64, Option<f64>, Option<f64>), PyErr> {
Ok((
self.semi_major_equatorial_radius_km,
Some(self.polar_radius_km),
Some(self.semi_minor_equatorial_radius_km),
))
}
#[getter]
fn get_semi_major_equatorial_radius_km(&self) -> PyResult<f64> {
Ok(self.semi_major_equatorial_radius_km)
}
#[setter]
fn set_semi_major_equatorial_radius_km(
&mut self,
semi_major_equatorial_radius_km: f64,
) -> PyResult<()> {
self.semi_major_equatorial_radius_km = semi_major_equatorial_radius_km;
Ok(())
}
#[getter]
fn get_polar_radius_km(&self) -> PyResult<f64> {
Ok(self.polar_radius_km)
}
#[setter]
fn set_polar_radius_km(&mut self, polar_radius_km: f64) -> PyResult<()> {
self.polar_radius_km = polar_radius_km;
Ok(())
}
#[getter]
fn get_semi_minor_equatorial_radius_km(&self) -> PyResult<f64> {
Ok(self.semi_minor_equatorial_radius_km)
}
#[setter]
fn set_semi_minor_equatorial_radius_km(
&mut self,
semi_minor_equatorial_radius_km: f64,
) -> PyResult<()> {
self.semi_minor_equatorial_radius_km = semi_minor_equatorial_radius_km;
Ok(())
}
}
#[cfg_attr(feature = "python", pymethods)]
impl Ellipsoid {
pub fn mean_equatorial_radius_km(&self) -> f64 {
(self.semi_major_equatorial_radius_km + self.semi_minor_equatorial_radius_km) / 2.0
}
pub fn is_sphere(&self) -> bool {
self.is_spheroid()
&& (self.polar_radius_km - self.semi_minor_equatorial_radius_km).abs() < f64::EPSILON
}
pub fn is_spheroid(&self) -> bool {
(self.semi_major_equatorial_radius_km - self.semi_minor_equatorial_radius_km).abs()
< f64::EPSILON
}
pub fn flattening(&self) -> f64 {
(self.mean_equatorial_radius_km() - self.polar_radius_km) / self.mean_equatorial_radius_km()
}
}
impl fmt::Display for Ellipsoid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
if self.is_sphere() {
write!(f, "radius = {} km", self.semi_major_equatorial_radius_km)
} else if self.is_spheroid() {
write!(
f,
"eq. radius = {} km, polar radius = {} km, f = {}",
self.semi_major_equatorial_radius_km,
self.polar_radius_km,
self.flattening()
)
} else {
write!(
f,
"major radius = {} km, minor radius = {} km, polar radius = {} km, f = {}",
self.semi_major_equatorial_radius_km,
self.semi_minor_equatorial_radius_km,
self.polar_radius_km,
self.flattening()
)
}
}
}
impl Encode for Ellipsoid {
fn encoded_len(&self) -> der::Result<der::Length> {
self.semi_major_equatorial_radius_km.encoded_len()?
+ self.semi_minor_equatorial_radius_km.encoded_len()?
+ self.polar_radius_km.encoded_len()?
}
fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> {
self.semi_major_equatorial_radius_km.encode(encoder)?;
self.semi_minor_equatorial_radius_km.encode(encoder)?;
self.polar_radius_km.encode(encoder)
}
}
impl<'a> Decode<'a> for Ellipsoid {
fn decode<R: Reader<'a>>(decoder: &mut R) -> der::Result<Self> {
Ok(Self {
semi_major_equatorial_radius_km: decoder.decode()?,
semi_minor_equatorial_radius_km: decoder.decode()?,
polar_radius_km: decoder.decode()?,
})
}
}