1 | //! Line intersection parameters. |
2 | |
3 | use crate::{ |
4 | geometry::{Point, PointExt}, |
5 | primitives::{ |
6 | common::{LineSide, LinearEquation}, |
7 | Line, |
8 | }, |
9 | }; |
10 | |
11 | /// Intersection test result. |
12 | #[derive (Copy, Clone, Debug, PartialEq)] |
13 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
14 | pub enum Intersection { |
15 | /// Intersection at point |
16 | Point { |
17 | /// Intersection point. |
18 | point: Point, |
19 | |
20 | /// The "outer" side of the intersection, i.e. the side that has the joint's reflex angle. |
21 | /// |
22 | /// For example: |
23 | /// |
24 | /// ```text |
25 | /// # Left outer side: |
26 | /// |
27 | /// ⎯ |
28 | /// ╱ |
29 | /// |
30 | /// # Right outer side: |
31 | /// │ |
32 | /// ╱ |
33 | /// ``` |
34 | /// |
35 | /// This is used to find the outside edge of a corner. |
36 | outer_side: LineSide, |
37 | }, |
38 | |
39 | /// No intersection: lines are colinear or parallel. |
40 | Colinear, |
41 | } |
42 | /// Line intersection parameters. |
43 | #[derive (Debug, Copy, Clone, PartialEq)] |
44 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
45 | pub struct IntersectionParams<'a> { |
46 | line1: &'a Line, |
47 | line2: &'a Line, |
48 | le1: LinearEquation, |
49 | le2: LinearEquation, |
50 | |
51 | /// Determinant, used to solve linear equations using Cramer's rule. |
52 | denominator: i32, |
53 | } |
54 | |
55 | impl<'a> IntersectionParams<'a> { |
56 | pub fn from_lines(line1: &'a Line, line2: &'a Line) -> Self { |
57 | let le1 = LinearEquation::from_line(line1); |
58 | let le2 = LinearEquation::from_line(line2); |
59 | let denominator = le1.normal_vector.determinant(le2.normal_vector); |
60 | |
61 | Self { |
62 | line1, |
63 | line2, |
64 | le1, |
65 | le2, |
66 | denominator, |
67 | } |
68 | } |
69 | |
70 | /// Check whether two almost-colinear lines are intersecting in the wrong place due to numerical |
71 | /// inaccuracies. |
72 | pub fn nearly_colinear_has_error(&self) -> bool { |
73 | self.denominator.pow(2) < self.line1.delta().dot_product(self.line2.delta()).abs() |
74 | } |
75 | |
76 | /// Compute the intersection point. |
77 | pub fn intersection(&self) -> Intersection { |
78 | let Self { |
79 | denominator, |
80 | le1: line1, |
81 | le2: line2, |
82 | .. |
83 | } = *self; |
84 | |
85 | // The system of linear equations has no solutions if the determinant is zero. In this case, |
86 | // the lines must be colinear. |
87 | if denominator == 0 { |
88 | return Intersection::Colinear; |
89 | } |
90 | |
91 | let outer_side = if denominator < 0 { |
92 | LineSide::Left |
93 | } else { |
94 | LineSide::Right |
95 | }; |
96 | |
97 | // If we got here, line segments intersect. Compute intersection point using method similar |
98 | // to that described here: http://paulbourke.net/geometry/pointlineplane/#i2l |
99 | |
100 | // The denominator/2 is to get rounding instead of truncating. |
101 | let offset = denominator.abs() / 2; |
102 | |
103 | let origin_distances = Point::new(line1.origin_distance, line2.origin_distance); |
104 | |
105 | let numerator = |
106 | origin_distances.determinant(Point::new(line1.normal_vector.y, line2.normal_vector.y)); |
107 | let x_numerator = if numerator < 0 { |
108 | numerator - offset |
109 | } else { |
110 | numerator + offset |
111 | }; |
112 | |
113 | let numerator = |
114 | Point::new(line1.normal_vector.x, line2.normal_vector.x).determinant(origin_distances); |
115 | let y_numerator = if numerator < 0 { |
116 | numerator - offset |
117 | } else { |
118 | numerator + offset |
119 | }; |
120 | |
121 | Intersection::Point { |
122 | point: Point::new(x_numerator, y_numerator) / denominator, |
123 | outer_side, |
124 | } |
125 | } |
126 | } |
127 | |
128 | #[cfg (test)] |
129 | mod tests { |
130 | use super::*; |
131 | |
132 | #[test ] |
133 | fn point_left() { |
134 | let line1 = Line::new(Point::new(50, 0), Point::new(20, 0)); |
135 | let line2 = Line::new(Point::new(0, 20), Point::new(0, 50)); |
136 | |
137 | let params = IntersectionParams::from_lines(&line1, &line2); |
138 | assert_eq!( |
139 | params.intersection(), |
140 | Intersection::Point { |
141 | point: Point::zero(), |
142 | outer_side: LineSide::Left, |
143 | } |
144 | ); |
145 | } |
146 | |
147 | #[test ] |
148 | fn point_right() { |
149 | let line1 = Line::new(Point::new(0, 50), Point::new(0, 20)); |
150 | let line2 = Line::new(Point::new(20, 0), Point::new(50, 0)); |
151 | |
152 | let params = IntersectionParams::from_lines(&line1, &line2); |
153 | assert_eq!( |
154 | params.intersection(), |
155 | Intersection::Point { |
156 | point: Point::zero(), |
157 | outer_side: LineSide::Right, |
158 | } |
159 | ); |
160 | } |
161 | |
162 | #[test ] |
163 | fn colinear() { |
164 | let line1 = Line::new(Point::new(0, 50), Point::new(0, 20)); |
165 | let line2 = Line::new(Point::new(10, 20), Point::new(10, 50)); |
166 | |
167 | let params = IntersectionParams::from_lines(&line1, &line2); |
168 | assert_eq!(params.intersection(), Intersection::Colinear); |
169 | } |
170 | } |
171 | |