chemrust_nasl/geometry/intersections/
sphere_sphere.rs

1
2use nalgebra::{Point3, UnitVector3};
3
4use crate::geometry::primitives::{Circle3d, Sphere};
5
6use super::{approx_cmp_f64, FloatOrdering, Intersect};
7
8#[derive(Debug)]
9/// Relationship between two spheres
10enum SphereSphereRelationship<'a> {
11    /// d > r1 + r2
12    TooFarAway,
13    /// d = 0, r1 = r2
14    Overlaps,
15    /// d + r_smaller < r_larger
16    InsideOutOfReach,
17    /// d + r_smaller = r_larger, returns larger and direction from smaller center to larger center
18    InsideCut(&'a Sphere, UnitVector3<f64>),
19    /// d = r1 + r2
20    OutsideCut,
21    /// r_large - r_smaller < d < r1 + r2
22    Intersect,
23}
24
25impl<'a> SphereSphereRelationship<'a> {
26    /// returns (larger, smaller)
27    /// if the two spheres are considered to be identical, returns the first as the larger
28    fn cmp_sphere(s1: &'a Sphere, s2: &'a Sphere) -> (&'a Sphere, &'a Sphere) {
29        if s1.radius() - s2.radius() > 5.0 * f64::EPSILON {
30            (s1, s2)
31        } else if s1.radius() - s2.radius() < -5.0 * f64::EPSILON {
32            (s2, s1)
33        } else {
34            (s1, s2)
35        }
36    }
37    /// Determine the relationship
38    fn determine(s1: &'a Sphere, s2: &'a Sphere) -> Self {
39        let d = s2.center() - s1.center();
40        let d_norm2 = d.norm_squared();
41        let r1_plus_r2_2 = (s1.radius() + s2.radius()).powi(2);
42        let larger_r = f64::max(s1.radius(), s2.radius());
43        let smaller_r = f64::min(s1.radius(), s2.radius());
44        let r1_diff_r2 = (larger_r - smaller_r).powi(2);
45        // edge cases
46        match approx_cmp_f64(r1_plus_r2_2, d_norm2) {
47            // 1. two spheres too far away (r1 + r2) < d
48            FloatOrdering::Less => Self::TooFarAway,
49            // 2. Two spheres touch from outside
50            // d = r1 + r2
51            FloatOrdering::Equal => Self::OutsideCut,
52            // Check r1-r2
53            FloatOrdering::Greater => {
54                match approx_cmp_f64(r1_diff_r2, d_norm2) {
55                    // 3. general intersect
56                    // r1 - r2 < d < r1 + r2
57                    FloatOrdering::Less => Self::Intersect,
58                    FloatOrdering::Equal => {
59                        // 4. Overlaps
60                        // d = 0, r1-r2 = 0
61                        if let FloatOrdering::Equal = approx_cmp_f64(r1_diff_r2, 0.0) {
62                            Self::Overlaps
63                        } else {
64                            // 5. One touches the outer from inside
65                            // d + r_small = r_large
66                            let (larger, smaller) = Self::cmp_sphere(s1, s2);
67                            let direction =
68                                UnitVector3::new_normalize(smaller.center() - larger.center());
69                            Self::InsideCut(larger, direction)
70                        }
71                    }
72                    // 6. one sphere is inside another, and the inner one
73                    // doesn't touch the outer.
74                    // d + r_small < r_large
75                    FloatOrdering::Greater => Self::InsideOutOfReach,
76                }
77            }
78        }
79    }
80}
81
82#[derive(Debug)]
83/// Result of Sphere-Sphere Intersection
84pub enum SphereSphereResult {
85    Empty,
86    Point(Point3<f64>),
87    Circle(Circle3d),
88    Overlap(Sphere),
89}
90
91impl SphereSphereResult {
92    /// Trivial math deduction
93    pub fn circle_result(s1: &Sphere, s2: &Sphere) -> Self {
94        let d = s2.center() - s1.center();
95        // d1 = d/2 + (r1^2 - r2^2)/2d
96        let d1 = 0.5 * d.norm() + 0.5 * (s1.radius().powi(2) - s2.radius().powi(2)) / d.norm();
97        // h^2 = r1^2 - d1^2 = (r1+d1)(r1-d1)
98        let h = ((s1.radius() + d1) * (s1.radius() - d1)).sqrt();
99        let norm = UnitVector3::new_normalize(d);
100        let center = s1.center() + norm.scale(d1);
101        let circle = Circle3d::new(center, h, norm);
102        SphereSphereResult::Circle(circle)
103    }
104}
105
106impl Intersect for Sphere {
107    type Output = SphereSphereResult;
108    fn intersect(&self, rhs: &Self) -> Self::Output {
109        let sphere_intersect_cases = SphereSphereRelationship::determine(self, rhs);
110        match sphere_intersect_cases {
111            SphereSphereRelationship::Intersect => SphereSphereResult::circle_result(self, rhs),
112            SphereSphereRelationship::OutsideCut => {
113                // direction d is from self pointing to rhs
114                let d = rhs.center() - self.center();
115                // get point from center of self and direction d
116                SphereSphereResult::Point(self.point_at_surface(&UnitVector3::new_normalize(d)))
117            }
118            SphereSphereRelationship::TooFarAway => SphereSphereResult::Empty,
119            SphereSphereRelationship::Overlaps => SphereSphereResult::Overlap(*self),
120            SphereSphereRelationship::InsideOutOfReach => SphereSphereResult::Empty,
121            SphereSphereRelationship::InsideCut(larger, direction) => {
122                SphereSphereResult::Point(larger.point_at_surface(&direction))
123            }
124        }
125    }
126}
127
128#[cfg(test)]
129mod test {
130
131    
132
133    use nalgebra::Point3;
134
135    use crate::geometry::{
136        intersections::{sphere_sphere::SphereSphereRelationship, Intersect},
137        primitives::Sphere,
138    };
139
140    #[test]
141    fn sphere_sphere() {
142        let s1 = Sphere::new(Point3::origin(), 2.0); // at origin, r = 2
143        let s2 = Sphere::new(Point3::new(1.0, 1.0, 0.0), 2.0); // normal intersects with s1
144        let s3 = Sphere::new(Point3::new(1.0, 0.0, 0.0), 1.0); // inside touches s1 at (2.0,0.0,0.0)
145        let s4 = Sphere::new(Point3::origin(), 2.0 + f64::EPSILON); // overlaps with s1
146        let s5 = Sphere::new(Point3::new(0.0, 4.0, 0.0), 2.0 + f64::EPSILON); // outside touches s1 (2.0,0.0,0.0)
147        let s6 = Sphere::new(Point3::new(4.0, 0.0, 0.0), 1.999 + f64::EPSILON); // outside empty with s1
148        let s7 = Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0); // inside empty with s1
149        let s8 = Sphere::new(Point3::new(2.0, 2.0, 2.0), 3.0);
150        let pairs = [s2, s3, s4, s5, s6, s7, s8];
151        pairs.iter().enumerate().for_each(|(id, s)| {
152            println!("\nCase {} result: {:?}", id, s1.intersect(s));
153            println!(
154                "Relationship: {:?}\n",
155                SphereSphereRelationship::determine(&s1, s)
156            )
157        });
158    }
159}