jagua_rs/geometry/primitives/
edge.rs1use crate::geometry::Transformation;
2use crate::geometry::geo_traits::{CollidesWith, DistanceTo, Transformable, TransformableFrom};
3use crate::geometry::primitives::Point;
4use crate::geometry::primitives::Rect;
5use anyhow::Result;
6use anyhow::ensure;
7
8#[derive(Clone, Debug, PartialEq, Copy)]
10pub struct Edge {
11 pub start: Point,
12 pub end: Point,
13}
14
15impl Edge {
16 pub fn try_new(start: Point, end: Point) -> Result<Self> {
17 ensure!(start != end, "degenerate edge, {start:?} == {end:?}");
18 Ok(Edge { start, end })
19 }
20
21 pub fn extend_at_front(mut self, d: f32) -> Self {
22 let (dx, dy) = (self.end.0 - self.start.0, self.end.1 - self.start.1);
24 let l = self.length();
25 self.start.0 -= dx * (d / l);
26 self.start.1 -= dy * (d / l);
27 self
28 }
29
30 pub fn extend_at_back(mut self, d: f32) -> Self {
31 let (dx, dy) = (self.end.0 - self.start.0, self.end.1 - self.start.1);
33 let l = self.length();
34 self.end.0 += dx * (d / l);
35 self.end.1 += dy * (d / l);
36 self
37 }
38
39 pub fn scale(mut self, factor: f32) -> Self {
40 let (dx, dy) = (self.end.0 - self.start.0, self.end.1 - self.start.1);
41 self.start.0 -= dx * (factor - 1.0) / 2.0;
42 self.start.1 -= dy * (factor - 1.0) / 2.0;
43 self.end.0 += dx * (factor - 1.0) / 2.0;
44 self.end.1 += dy * (factor - 1.0) / 2.0;
45 self
46 }
47
48 pub fn reverse(mut self) -> Self {
49 std::mem::swap(&mut self.start, &mut self.end);
50 self
51 }
52
53 pub fn collides_at(&self, other: &Edge) -> Option<Point> {
54 match edge_intersection(self, other, true) {
55 Intersection::No => None,
56 Intersection::Yes(point) => Some(
57 point.expect("Intersection::Yes, but returned no point when this was requested"),
58 ),
59 }
60 }
61
62 pub fn closest_point_on_edge(&self, point: &Point) -> Point {
64 let Point(x1, y1) = self.start;
66 let Point(x2, y2) = self.end;
67 let Point(x, y) = point;
68
69 let a = x - x1;
70 let b = y - y1;
71 let c = x2 - x1;
72 let d = y2 - y1;
73
74 let dot = a * c + b * d;
75 let len_sq = c * c + d * d;
76 let mut param = -1.0;
77 if len_sq != 0.0 {
78 param = dot / len_sq;
79 }
80 let (xx, yy) = match param {
81 p if p < 0.0 => (x1, y1), p if p > 1.0 => (x2, y2), _ => (x1 + param * c, y1 + param * d), };
85
86 Point(xx, yy)
87 }
88
89 pub fn x_min(&self) -> f32 {
90 f32::min(self.start.0, self.end.0)
91 }
92
93 pub fn y_min(&self) -> f32 {
94 f32::min(self.start.1, self.end.1)
95 }
96
97 pub fn x_max(&self) -> f32 {
98 f32::max(self.start.0, self.end.0)
99 }
100
101 pub fn y_max(&self) -> f32 {
102 f32::max(self.start.1, self.end.1)
103 }
104
105 pub fn length(&self) -> f32 {
106 self.start.distance_to(&self.end)
107 }
108
109 pub fn centroid(&self) -> Point {
110 Point(
111 (self.start.0 + self.end.0) / 2.0,
112 (self.start.1 + self.end.1) / 2.0,
113 )
114 }
115
116 pub fn bbox(&self) -> Rect {
117 Rect {
118 x_min: self.x_min(),
119 y_min: self.y_min(),
120 x_max: self.x_max(),
121 y_max: self.y_max(),
122 }
123 }
124}
125
126impl Transformable for Edge {
127 fn transform(&mut self, t: &Transformation) -> &mut Self {
128 let Edge { start, end } = self;
129 start.transform(t);
130 end.transform(t);
131
132 self
133 }
134}
135
136impl TransformableFrom for Edge {
137 fn transform_from(&mut self, reference: &Self, t: &Transformation) -> &mut Self {
138 let Edge { start, end } = self;
139 start.transform_from(&reference.start, t);
140 end.transform_from(&reference.end, t);
141
142 self
143 }
144}
145
146impl DistanceTo<Point> for Edge {
147 #[inline(always)]
148 fn distance_to(&self, point: &Point) -> f32 {
149 f32::sqrt(self.sq_distance_to(point))
150 }
151
152 #[inline(always)]
153 fn sq_distance_to(&self, point: &Point) -> f32 {
154 let Point(x, y) = point;
155 let Point(xx, yy) = self.closest_point_on_edge(point);
156
157 let (dx, dy) = (x - xx, y - yy);
158 dx.powi(2) + dy.powi(2)
159 }
160}
161
162impl CollidesWith<Edge> for Edge {
163 #[inline(always)]
164 fn collides_with(&self, other: &Edge) -> bool {
165 match edge_intersection(self, other, false) {
166 Intersection::No => false,
167 Intersection::Yes(_) => true,
168 }
169 }
170}
171
172impl CollidesWith<Rect> for Edge {
173 #[inline(always)]
174 fn collides_with(&self, other: &Rect) -> bool {
175 other.collides_with(self)
176 }
177}
178
179#[inline(always)]
180fn edge_intersection(e1: &Edge, e2: &Edge, calc_loc: bool) -> Intersection {
181 let Point(x1, y1) = e1.start;
182 let Point(x2, y2) = e1.end;
183 let Point(x3, y3) = e2.start;
184 let Point(x4, y4) = e2.end;
185
186 {
188 let x_min_e1 = x1.min(x2);
189 let x_max_e1 = x1.max(x2);
190 let y_min_e1 = y1.min(y2);
191 let y_max_e1 = y1.max(y2);
192
193 let x_min_e2 = x3.min(x4);
194 let x_max_e2 = x3.max(x4);
195 let y_min_e2 = y3.min(y4);
196 let y_max_e2 = y3.max(y4);
197
198 let x_axis_no_overlap = x_min_e1.max(x_min_e2) > x_max_e1.min(x_max_e2);
199 let y_axis_no_overlap = y_min_e1.max(y_min_e2) > y_max_e1.min(y_max_e2);
200
201 if x_axis_no_overlap || y_axis_no_overlap {
202 return Intersection::No;
203 }
204 }
205
206 let t_nom = (x2 - x4) * (y4 - y3) - (y2 - y4) * (x4 - x3);
208 let t_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
209 let u_nom = (x2 - x4) * (y2 - y1) - (y2 - y4) * (x2 - x1);
210 let u_denom = (x2 - x1) * (y4 - y3) - (y2 - y1) * (x4 - x3);
211
212 if t_denom == 0.0 || u_denom == 0.0 {
213 return Intersection::No;
215 }
216
217 let t = t_nom / t_denom;
218 let u = u_nom / u_denom;
219 if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
220 let loc = match calc_loc {
222 false => None,
223 true => Some(Point(x2 + t * (x1 - x2), y2 + t * (y1 - y2))),
224 };
225 return Intersection::Yes(loc);
226 }
227 Intersection::No
228}
229
230enum Intersection {
231 Yes(Option<Point>),
232 No,
233}