use crate::coordinate_systems::{Cartesian, Radians};
use crate::utils::vector::{slerp, triple_product};
pub type SphericalPolygon = Vec<Cartesian>;
#[derive(Debug)]
pub struct SphericalPolygonShape {
vertices: SphericalPolygon,
area: Option<Radians>,
}
impl SphericalPolygonShape {
pub fn new(vertices: SphericalPolygon) -> Self {
Self {
vertices,
area: None,
}
}
pub fn get_boundary(&self, n_segments: usize, closed_ring: bool) -> SphericalPolygon {
let mut points = Vec::new();
let n = self.vertices.len();
for s in 0..(n * n_segments) {
let t = s as f64 / n_segments as f64;
points.push(self.slerp(t));
}
if closed_ring && !points.is_empty() {
points.push(points[0]);
}
points
}
pub fn slerp(&self, t: f64) -> Cartesian {
let n = self.vertices.len();
let f = t % 1.0;
let i = (t % n as f64) as usize;
let j = (i + 1) % n;
slerp(self.vertices[i], self.vertices[j], f)
}
pub fn get_transformed_vertices(&self, t: f64) -> (Cartesian, Cartesian, Cartesian) {
let n = self.vertices.len();
let i = (t % n as f64) as usize;
let j = (i + 1) % n;
let k = (i + n - 1) % n;
let v = self.vertices[i];
let va = subtract(self.vertices[j], v);
let vb = subtract(self.vertices[k], v);
(v, va, vb)
}
pub fn contains_point(&self, point: Cartesian) -> f64 {
let n = self.vertices.len();
let mut theta_delta_min = f64::INFINITY;
for i in 0..n {
let (v, va, vb) = self.get_transformed_vertices(i as f64);
let vp = subtract(point, v);
let vp = normalize(vp);
let va = normalize(va);
let vb = normalize(vb);
let cross_ap = cross(va, vp);
let cross_pb = cross(vp, vb);
let sin_ap = dot(v, cross_ap);
let sin_pb = dot(v, cross_pb);
theta_delta_min = theta_delta_min.min(sin_ap).min(sin_pb);
}
theta_delta_min
}
fn get_triangle_area(&self, v1: Cartesian, v2: Cartesian, v3: Cartesian) -> Radians {
let mid_a = normalize(lerp(v2, v3, 0.5));
let mid_b = normalize(lerp(v3, v1, 0.5));
let mid_c = normalize(lerp(v1, v2, 0.5));
let s = triple_product(mid_a, mid_b, mid_c);
let clamped = s.clamp(-1.0, 1.0);
let area = if clamped.abs() < 1e-8 {
2.0 * clamped
} else {
clamped.asin() * 2.0
};
Radians::new_unchecked(area)
}
pub fn get_area(&mut self) -> Radians {
if let Some(area) = self.area {
return area;
}
let area = self.compute_area();
self.area = Some(area);
area
}
fn compute_area(&self) -> Radians {
if self.vertices.len() < 3 {
return Radians::new_unchecked(0.0);
}
if self.vertices.len() == 3 {
return self.get_triangle_area(self.vertices[0], self.vertices[1], self.vertices[2]);
}
let mut center = Cartesian::new(0.0, 0.0, 0.0);
for vertex in &self.vertices {
center = add(center, *vertex);
}
center = normalize(center);
let mut area = 0.0;
for i in 0..self.vertices.len() {
let v1 = self.vertices[i];
let v2 = self.vertices[(i + 1) % self.vertices.len()];
let tri_area = self.get_triangle_area(center, v1, v2);
if !tri_area.get().is_nan() {
area += tri_area.get();
}
}
Radians::new_unchecked(area)
}
#[allow(dead_code)]
fn is_winding_correct(&mut self) -> bool {
let area = self.get_area();
area.get() > 0.0
}
}
fn dot(a: Cartesian, b: Cartesian) -> f64 {
a.x() * b.x() + a.y() * b.y() + a.z() * b.z()
}
fn cross(a: Cartesian, b: Cartesian) -> Cartesian {
Cartesian::new(
a.y() * b.z() - a.z() * b.y(),
a.z() * b.x() - a.x() * b.z(),
a.x() * b.y() - a.y() * b.x(),
)
}
fn length(v: Cartesian) -> f64 {
(v.x() * v.x() + v.y() * v.y() + v.z() * v.z()).sqrt()
}
fn normalize(v: Cartesian) -> Cartesian {
let len = length(v);
if len == 0.0 {
return v;
}
Cartesian::new(v.x() / len, v.y() / len, v.z() / len)
}
fn lerp(a: Cartesian, b: Cartesian, t: f64) -> Cartesian {
Cartesian::new(
a.x() + t * (b.x() - a.x()),
a.y() + t * (b.y() - a.y()),
a.z() + t * (b.z() - a.z()),
)
}
fn subtract(a: Cartesian, b: Cartesian) -> Cartesian {
Cartesian::new(a.x() - b.x(), a.y() - b.y(), a.z() - b.z())
}
fn add(a: Cartesian, b: Cartesian) -> Cartesian {
Cartesian::new(a.x() + b.x(), a.y() + b.y(), a.z() + b.z())
}