chemrust_nasl/geometry/intersections/
circle_circle.rs1use nalgebra::{Point3, UnitVector3};
2
3use crate::geometry::{
4 intersections::{approx_cmp_f64, FloatOrdering},
5 primitives::{Circle3d, Line},
6};
7
8use super::{approx_eq_point_f64, plane_plane::PlanePlaneIntersection, FloatEq, Intersect};
9
10#[derive(Debug, Clone, Copy)]
11enum CircleCircleRelationship {
12 Coplanar,
13 ParallelPlane,
14 PlaneIntersect(Line),
15}
16
17impl CircleCircleRelationship {
18 fn determine(c1: &Circle3d, c2: &Circle3d) -> Self {
19 let p1 = c1.plane_of_circle();
20 let p2 = c2.plane_of_circle();
21 match p1.intersect(&p2) {
22 PlanePlaneIntersection::Same => Self::Coplanar,
23 PlanePlaneIntersection::Parallel => Self::ParallelPlane,
24 PlanePlaneIntersection::Intersect(line) => Self::PlaneIntersect(line),
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy)]
30pub enum CircleCircleIntersection {
31 Invalid,
32 Empty,
33 Single(Point3<f64>),
34 Double(Point3<f64>, Point3<f64>),
35 Overlap(Circle3d),
36 Contains(Circle3d, Circle3d),
38}
39
40#[derive(Debug, Clone, Copy)]
41pub struct CircleCoplanarLine(Line);
42
43#[derive(Debug, Clone, Copy)]
44pub enum CircleLineIntersection {
45 Empty,
46 Single(Point3<f64>),
47 Double(Point3<f64>, Point3<f64>),
48}
49
50impl Intersect for Circle3d {
51 type Output = CircleCircleIntersection;
52
53 fn intersect(&self, rhs: &Self) -> Self::Output {
54 let case = CircleCircleRelationship::determine(self, rhs);
55 match case {
56 CircleCircleRelationship::ParallelPlane => CircleCircleIntersection::Empty,
57 CircleCircleRelationship::Coplanar => coplanar_circle_circle_intersect(self, rhs),
58 CircleCircleRelationship::PlaneIntersect(line) => {
59 noncoplanar_circle_circle_intersect(self, rhs, &line)
60 }
61 }
62 }
63}
64
65impl Intersect<CircleCoplanarLine> for Circle3d {
66 type Output = CircleLineIntersection;
67
68 fn intersect(&self, rhs: &CircleCoplanarLine) -> Self::Output {
69 let line = rhs.0;
70 let distance_to_line = line.point_to_line_distance(&self.center());
71 match approx_cmp_f64(distance_to_line, self.radius()) {
72 FloatOrdering::Less => {
73 let line_origin_to_p = self.center() - line.origin();
74 let angle = line.direction().angle(&line_origin_to_p);
75 let h = (self.radius().powi(2) - distance_to_line.powi(2)).sqrt();
76 let t1 = line_origin_to_p.norm() * angle.cos() + h;
77 let t2 = line_origin_to_p.norm() * angle.cos() - h;
78 let p1 = line.point(t1);
79 let p2 = line.point(t2);
80 CircleLineIntersection::Double(p1, p2)
81 }
82 FloatOrdering::Equal => CircleLineIntersection::Single(line.origin()),
83 FloatOrdering::Greater => CircleLineIntersection::Empty,
84 }
85 }
86}
87
88fn noncoplanar_circle_circle_intersect(
89 c1: &Circle3d,
90 c2: &Circle3d,
91 intersect_line: &Line,
92) -> CircleCircleIntersection {
93 let coplanar_line = CircleCoplanarLine(*intersect_line);
94 let c1_line_result: CircleLineIntersection = c1.intersect(&coplanar_line);
95 let c2_line_result: CircleLineIntersection = c2.intersect(&coplanar_line);
96 match (c1_line_result, c2_line_result) {
97 (CircleLineIntersection::Empty, _) => CircleCircleIntersection::Empty,
98 (_, CircleLineIntersection::Empty) => CircleCircleIntersection::Empty,
99 (CircleLineIntersection::Single(p1), CircleLineIntersection::Single(p2)) => {
100 match approx_eq_point_f64(p1, p2) {
101 FloatEq::Eq => CircleCircleIntersection::Single(p1),
102 FloatEq::NotEq => CircleCircleIntersection::Empty,
103 }
104 }
105 (CircleLineIntersection::Single(p1), CircleLineIntersection::Double(p2, p3)) => {
106 if let FloatEq::Eq = approx_eq_point_f64(p1, p2) {
107 CircleCircleIntersection::Single(p1)
108 } else if let FloatEq::Eq = approx_eq_point_f64(p1, p3) {
109 CircleCircleIntersection::Single(p1)
110 } else {
111 CircleCircleIntersection::Empty
112 }
113 }
114 (CircleLineIntersection::Double(p2, p3), CircleLineIntersection::Single(p1)) => {
115 if let FloatEq::Eq = approx_eq_point_f64(p1, p2) {
116 CircleCircleIntersection::Single(p1)
117 } else if let FloatEq::Eq = approx_eq_point_f64(p1, p3) {
118 CircleCircleIntersection::Single(p1)
119 } else {
120 CircleCircleIntersection::Empty
121 }
122 }
123 (CircleLineIntersection::Double(p1, p2), CircleLineIntersection::Double(p3, p4)) => {
124 let case_1 = (approx_eq_point_f64(p1, p3), approx_eq_point_f64(p2, p4));
126 match case_1 {
128 (FloatEq::NotEq, FloatEq::Eq) => CircleCircleIntersection::Single(p2),
130 (FloatEq::Eq, FloatEq::NotEq) => CircleCircleIntersection::Single(p1),
132 (FloatEq::Eq, FloatEq::Eq) => CircleCircleIntersection::Double(p1, p2),
134 (FloatEq::NotEq, FloatEq::NotEq) => {
136 let case_2 = (approx_eq_point_f64(p1, p4), approx_eq_point_f64(p2, p3));
138 match case_2 {
139 (FloatEq::NotEq, FloatEq::Eq) => CircleCircleIntersection::Single(p2),
141 (FloatEq::Eq, FloatEq::NotEq) => CircleCircleIntersection::Single(p1),
142 (FloatEq::Eq, FloatEq::Eq) => CircleCircleIntersection::Double(p1, p2),
143 (FloatEq::NotEq, FloatEq::NotEq) => CircleCircleIntersection::Empty,
146 }
147 }
148 }
149 }
150 }
151}
152
153fn cmp_radius_circle<'a>(c1: &'a Circle3d, c2: &'a Circle3d) -> (&'a Circle3d, &'a Circle3d) {
156 match approx_cmp_f64(c1.radius(), c2.radius()) {
157 FloatOrdering::Less => (c2, c1),
158 _ => (c1, c2),
159 }
160}
161
162pub(crate) fn coplanar_circle_circle_intersect(
164 c1: &Circle3d,
165 c2: &Circle3d,
166) -> CircleCircleIntersection {
167 let c1c2 = c2.center() - c1.center();
168 let c1c2_norm_squared = c1c2.norm_squared();
169 let r1r2_sum_squared = (c1.radius() + c2.radius()).powi(2);
180 match approx_cmp_f64(c1c2_norm_squared, r1r2_sum_squared) {
181 FloatOrdering::Equal => {
183 let direction = UnitVector3::new_normalize(c1c2);
184 let p = c1.center() + direction.scale(c1.radius());
185 CircleCircleIntersection::Single(p)
186 }
187 FloatOrdering::Greater => CircleCircleIntersection::Empty,
189 FloatOrdering::Less => {
191 let r1r2_diff_squared = (c1.radius() - c2.radius()).powi(2);
193 match approx_cmp_f64(c1c2_norm_squared, r1r2_diff_squared) {
194 FloatOrdering::Less => {
196 match approx_cmp_f64(c1.radius(), c2.radius()) {
197 FloatOrdering::Equal => CircleCircleIntersection::Invalid,
201 FloatOrdering::Less => CircleCircleIntersection::Contains(*c1, *c2),
202 FloatOrdering::Greater => CircleCircleIntersection::Contains(*c2, *c1),
203 }
204 }
205 FloatOrdering::Equal => {
208 if let FloatOrdering::Equal = approx_cmp_f64(r1r2_diff_squared, 0.0) {
209 CircleCircleIntersection::Overlap(*c1)
210 } else {
211 let (larger_c, smaller_c) = cmp_radius_circle(c1, c2);
212 let direction =
213 UnitVector3::new_normalize(smaller_c.center() - larger_c.center());
214 let p = larger_c.center() + direction.scale(larger_c.radius());
215 CircleCircleIntersection::Single(p)
216 }
217 }
218 FloatOrdering::Greater => {
220 let c1c2_normalized = UnitVector3::new_normalize(c1c2);
221 let c1c2_perpendicular =
222 UnitVector3::new_normalize(c1.n().cross(&c1c2_normalized));
223 let h = (c1c2_norm_squared + c1.radius().powi(2) - c2.radius().powi(2))
226 / (2.0 * c1c2.norm());
227 let dy = (c1.radius().powi(2) - h.powi(2)).sqrt();
230 let p_dx = c1.center() + c1c2_normalized.scale(h);
231 let p1 = p_dx + c1c2_perpendicular.scale(dy);
232 let p2 = p_dx - c1c2_perpendicular.scale(dy);
233 CircleCircleIntersection::Double(p1, p2)
234 }
235 }
236 }
237 }
238 }
240
241#[cfg(test)]
242mod test {
244 use nalgebra::{Point3, UnitVector3, Vector3};
245
246 use crate::geometry::{intersections::Intersect, primitives::Circle3d};
247
248 #[test]
249 fn circle_circle() {
250 let c1 = Circle3d::new(
251 Point3::new(0.5833333333333333, 0.5833333333333333, 0.5833333333333333),
252 1.726026264767,
253 UnitVector3::new_normalize(Vector3::new(1.0, 1.0, 1.0)),
254 );
255 let c2 = Circle3d::new(
256 Point3::new(0.5, 0.5, 0.0),
257 1.8708286933869707,
258 UnitVector3::new_normalize(Vector3::new(1.0, 1.0, 0.0)),
259 );
260 let res = c1.intersect(&c2);
261 println!("{:?}", res);
262 }
263}