use nalgebra::Vector3;
use std::f64::consts::PI;
pub fn rodrigues_rotation(vector: &Vector3<f64>, axis: &Vector3<f64>, angle: f64) -> Vector3<f64> {
let k = axis.normalize();
let cos_a = angle.cos();
let sin_a = angle.sin();
vector * cos_a + k.cross(vector) * sin_a + k * k.dot(vector) * (1.0 - cos_a)
}
pub fn great_circle_arcs_intersect(
a1: &Vector3<f64>,
a2: &Vector3<f64>,
b1: &Vector3<f64>,
b2: &Vector3<f64>,
) -> bool {
let n1 = a1.cross(a2);
let n2 = b1.cross(b2);
let cross = n1.cross(&n2);
if cross.norm() < 1e-15 {
return false;
}
let candidate = cross.normalize();
let within_a = |p: &Vector3<f64>| -> bool {
let t1 = a1.cross(p).dot(&n1);
let t2 = a2.cross(p).dot(&n1);
t1 >= -1e-12 && t2 <= 1e-12
};
let within_b = |p: &Vector3<f64>| -> bool {
let t1 = b1.cross(p).dot(&n2);
let t2 = b2.cross(p).dot(&n2);
t1 >= -1e-12 && t2 <= 1e-12
};
let antipode = -candidate;
(within_a(&candidate) && within_b(&candidate)) || (within_a(&antipode) && within_b(&antipode))
}
pub fn great_circle_arc_intersection(
a1: &Vector3<f64>,
a2: &Vector3<f64>,
b1: &Vector3<f64>,
b2: &Vector3<f64>,
hemisphere_hint: &Vector3<f64>,
) -> Option<Vector3<f64>> {
let n1 = a1.cross(a2);
let n2 = b1.cross(b2);
let cross = n1.cross(&n2);
if cross.norm() < 1e-15 {
return None;
}
let mut n3 = cross.normalize();
if hemisphere_hint.dot(&n3) < 0.0 {
n3 = -n3;
}
let within_a = {
let t1 = a1.cross(&n3).dot(&n1);
let t2 = a2.cross(&n3).dot(&n1);
t1 >= -1e-10 && t2 <= 1e-10
};
let within_b = {
let t1 = b1.cross(&n3).dot(&n2);
let t2 = b2.cross(&n3).dot(&n2);
t1 >= -1e-10 && t2 <= 1e-10
};
if within_a && within_b { Some(n3) } else { None }
}
pub fn great_circle_arc_polygon_intersections(
arc_start: &Vector3<f64>,
arc_end: &Vector3<f64>,
polygon_vertices: &[Vector3<f64>],
hemisphere_hint: &Vector3<f64>,
) -> Vec<Vector3<f64>> {
let mut intersections = Vec::new();
if polygon_vertices.len() < 2 {
return intersections;
}
let a1 = arc_start.normalize();
let a2 = arc_end.normalize();
for i in 0..polygon_vertices.len() - 1 {
let b1 = polygon_vertices[i].normalize();
let b2 = polygon_vertices[i + 1].normalize();
if let Some(pt) = great_circle_arc_intersection(&a1, &a2, &b1, &b2, hemisphere_hint) {
intersections.push(pt);
}
}
intersections
}
pub fn cross_track_projection(
point: &Vector3<f64>,
arc_start: &Vector3<f64>,
arc_end: &Vector3<f64>,
) -> (Vector3<f64>, f64) {
let p = point.normalize();
let a = arc_start.normalize();
let b = arc_end.normalize();
let n = a.cross(&b);
if n.norm() < 1e-15 {
return (a, p.dot(&a).clamp(-1.0, 1.0).acos());
}
let d = n.cross(&p).cross(&n);
if d.norm() < 1e-15 {
return (a, PI / 2.0);
}
let d = d.normalize();
let ang = p.dot(&d).clamp(-1.0, 1.0).acos();
(d, ang)
}
pub fn polygon_circumscription_angle(vertices: &[Vector3<f64>]) -> f64 {
let n = vertices.len();
if n < 2 {
return 0.0;
}
let mut max_angle = 0.0_f64;
for (i, vi) in vertices.iter().enumerate() {
let vi = vi.normalize();
for vj in &vertices[(i + 1)..] {
let vj = vj.normalize();
let angle = vi.dot(&vj).clamp(-1.0, 1.0).acos();
max_angle = max_angle.max(angle);
}
}
(3.0_f64).sqrt() * 2.0 / 3.0 * max_angle
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use std::f64::consts::FRAC_PI_2;
#[test]
fn test_rodrigues_rotation_90_degrees() {
let result = rodrigues_rotation(
&Vector3::new(1.0, 0.0, 0.0),
&Vector3::new(0.0, 0.0, 1.0),
FRAC_PI_2,
);
assert_abs_diff_eq!(result.x, 0.0, epsilon = 1e-12);
assert_abs_diff_eq!(result.y, 1.0, epsilon = 1e-12);
assert_abs_diff_eq!(result.z, 0.0, epsilon = 1e-12);
}
#[test]
fn test_rodrigues_rotation_identity() {
let v = Vector3::new(0.5, 0.3, 0.8);
let result = rodrigues_rotation(&v, &Vector3::new(0.0, 0.0, 1.0), 0.0);
assert_abs_diff_eq!(result.x, v.x, epsilon = 1e-12);
assert_abs_diff_eq!(result.y, v.y, epsilon = 1e-12);
assert_abs_diff_eq!(result.z, v.z, epsilon = 1e-12);
}
#[test]
fn test_rodrigues_rotation_180_degrees() {
let result = rodrigues_rotation(
&Vector3::new(1.0, 0.0, 0.0),
&Vector3::new(0.0, 0.0, 1.0),
PI,
);
assert_abs_diff_eq!(result.x, -1.0, epsilon = 1e-12);
assert_abs_diff_eq!(result.y, 0.0, epsilon = 1e-12);
assert_abs_diff_eq!(result.z, 0.0, epsilon = 1e-12);
}
#[test]
fn test_great_circle_arcs_intersect_orthogonal() {
let a1 = Vector3::new(1.0, 0.0, 0.0);
let a2 = Vector3::new(0.0, 1.0, 0.0);
let b1 = Vector3::new(0.5, 0.5, -0.707).normalize();
let b2 = Vector3::new(0.5, 0.5, 0.707).normalize();
assert!(great_circle_arcs_intersect(&a1, &a2, &b1, &b2));
}
#[test]
fn test_great_circle_arcs_no_intersect() {
let a1 = Vector3::new(1.0, 0.0, 0.0);
let a2 = Vector3::new(0.0, 1.0, 0.0);
let b1 = Vector3::new(-1.0, 0.0, 0.0);
let b2 = Vector3::new(0.0, -1.0, 0.0);
assert!(!great_circle_arcs_intersect(&a1, &a2, &b1, &b2));
}
#[test]
fn test_great_circle_arc_intersection_at_known_point() {
let a1 = Vector3::new(1.0, 0.0, 0.0);
let a2 = Vector3::new(0.0, 1.0, 0.0);
let midpoint = Vector3::new(1.0, 1.0, 0.0).normalize();
let b1 = Vector3::new(midpoint.x, midpoint.y, -0.5).normalize();
let b2 = Vector3::new(midpoint.x, midpoint.y, 0.5).normalize();
let hint = Vector3::new(1.0, 1.0, 0.0);
let result = great_circle_arc_intersection(&a1, &a2, &b1, &b2, &hint);
assert!(result.is_some());
let pt = result.unwrap();
assert_abs_diff_eq!(pt.x, midpoint.x, epsilon = 1e-10);
assert_abs_diff_eq!(pt.y, midpoint.y, epsilon = 1e-10);
assert_abs_diff_eq!(pt.z, 0.0, epsilon = 1e-10);
}
#[test]
fn test_great_circle_arc_intersection_parallel() {
let a1 = Vector3::new(1.0, 0.0, 0.0);
let a2 = Vector3::new(0.0, 1.0, 0.0);
let b1 = Vector3::new(-1.0, 0.0, 0.0);
let b2 = Vector3::new(0.0, -1.0, 0.0);
let hint = Vector3::new(0.0, 0.0, 1.0);
let result = great_circle_arc_intersection(&a1, &a2, &b1, &b2, &hint);
assert!(result.is_none());
}
#[test]
fn test_cross_track_projection_on_arc() {
let p = Vector3::new(1.0, 1.0, 0.0).normalize();
let a = Vector3::new(1.0, 0.0, 0.0);
let b = Vector3::new(0.0, 1.0, 0.0);
let (proj, dist) = cross_track_projection(&p, &a, &b);
assert_abs_diff_eq!(dist, 0.0, epsilon = 1e-10);
assert_abs_diff_eq!(proj.x, p.x, epsilon = 1e-10);
assert_abs_diff_eq!(proj.y, p.y, epsilon = 1e-10);
}
#[test]
fn test_cross_track_projection_off_arc() {
let p = Vector3::new(0.0, 0.0, 1.0);
let a = Vector3::new(1.0, 0.0, 0.0);
let b = Vector3::new(0.0, 1.0, 0.0);
let (_proj, dist) = cross_track_projection(&p, &a, &b);
assert_abs_diff_eq!(dist, FRAC_PI_2, epsilon = 1e-10);
}
#[test]
fn test_polygon_circumscription_angle() {
let v1 = Vector3::new(1.0, 0.0, 0.0);
let v2 = Vector3::new((2.0 * PI / 3.0).cos(), (2.0 * PI / 3.0).sin(), 0.0);
let v3 = Vector3::new((4.0 * PI / 3.0).cos(), (4.0 * PI / 3.0).sin(), 0.0);
let angle = polygon_circumscription_angle(&[v1, v2, v3]);
let expected = (3.0_f64).sqrt() * 2.0 / 3.0 * (2.0 * PI / 3.0);
assert_abs_diff_eq!(angle, expected, epsilon = 1e-10);
}
#[test]
fn test_polygon_circumscription_single_point() {
let v1 = Vector3::new(1.0, 0.0, 0.0);
assert_abs_diff_eq!(polygon_circumscription_angle(&[v1]), 0.0, epsilon = 1e-12);
}
#[test]
fn test_great_circle_arc_polygon_intersections() {
let v1 = Vector3::new(1.0, 0.0, 0.1).normalize();
let v2 = Vector3::new(1.0, 0.0, -0.1).normalize();
let v3 = Vector3::new(0.9, 0.1, -0.1).normalize();
let v4 = Vector3::new(0.9, 0.1, 0.1).normalize();
let polygon = vec![v1, v2, v3, v4, v1];
let arc_start = Vector3::new(1.0, 0.0, 0.5).normalize();
let arc_end = Vector3::new(1.0, 0.0, -0.5).normalize();
let hint = Vector3::new(1.0, 0.0, 0.0);
let intersections =
great_circle_arc_polygon_intersections(&arc_start, &arc_end, &polygon, &hint);
assert_eq!(intersections.len(), 2);
}
}