use crate::coordinate_systems::{Barycentric, Cartesian, Face, FaceTriangle, SphericalTriangle};
use crate::core::coordinate_transforms::{barycentric_to_face, face_to_barycentric};
use crate::geometry::spherical_triangle::SphericalTriangleShape;
use crate::utils::vector::{quadruple_product, slerp, vector_difference};
pub struct PolyhedralProjection;
impl PolyhedralProjection {
pub fn new() -> Self {
Self
}
pub fn forward(
&self,
v: Cartesian,
spherical_triangle: SphericalTriangle,
face_triangle: FaceTriangle,
) -> Face {
let a = spherical_triangle.a;
let b = spherical_triangle.b;
let c = spherical_triangle.c;
let mut triangle_shape = SphericalTriangleShape::new(vec![a, b, c])
.expect("Failed to create spherical triangle");
let z = normalize(subtract(v, a));
let p = normalize(quadruple_product(a, z, b, c));
let h = vector_difference(a, v) / vector_difference(a, p);
let area_abc = triangle_shape.get_area().get();
let scaled_area = h / area_abc;
let b_coords = Barycentric::new(
1.0 - h,
scaled_area
* SphericalTriangleShape::new(vec![a, p, c])
.expect("Failed to create spherical triangle")
.get_area()
.get(),
scaled_area
* SphericalTriangleShape::new(vec![a, b, p])
.expect("Failed to create spherical triangle")
.get_area()
.get(),
);
barycentric_to_face(b_coords, face_triangle)
}
pub fn inverse(
&self,
face_point: Face,
face_triangle: FaceTriangle,
spherical_triangle: SphericalTriangle,
) -> Cartesian {
let a = spherical_triangle.a;
let b = spherical_triangle.b;
let c = spherical_triangle.c;
let mut triangle_shape = SphericalTriangleShape::new(vec![a, b, c])
.expect("Failed to create spherical triangle");
let b_coords = face_to_barycentric(face_point, face_triangle);
let threshold = 1.0 - 1e-14;
if b_coords.u > threshold {
return a;
}
if b_coords.v > threshold {
return b;
}
if b_coords.w > threshold {
return c;
}
let c1 = cross(b, c);
let area_abc = triangle_shape.get_area().get();
let h = 1.0 - b_coords.u;
let r = b_coords.w / h;
let alpha = r * area_abc;
let s = alpha.sin();
let half_c = (alpha / 2.0).sin();
let cc = 2.0 * half_c * half_c;
let c01 = dot(a, b);
let c12 = dot(b, c);
let c20 = dot(c, a);
let s12 = length(c1);
let v = dot(a, c1); let f = s * v + cc * (c01 * c12 - c20);
let g = cc * s12 * (1.0 + c01);
let q = (2.0 / c12.acos()) * g.atan2(f);
let p = slerp(b, c, q);
let k = vector_difference(a, p);
let t = self.safe_acos(h * k) / self.safe_acos(k);
slerp(a, p, t)
}
fn safe_acos(&self, x: f64) -> f64 {
if x < 1e-3 {
2.0 * x + x * x * x / 3.0
} else {
(1.0 - 2.0 * x * x).acos()
}
}
}
impl Default for PolyhedralProjection {
fn default() -> Self {
Self::new()
}
}
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 subtract(a: Cartesian, b: Cartesian) -> Cartesian {
Cartesian::new(a.x() - b.x(), a.y() - b.y(), a.z() - b.z())
}