1 | //! The triangle primitive. |
2 | |
3 | use core::cmp::{max, min, Ordering}; |
4 | |
5 | use crate::{ |
6 | geometry::{Dimensions, Point}, |
7 | primitives::{ |
8 | common::{LineJoin, LineSide, LinearEquation, Scanline, StrokeOffset}, |
9 | ContainsPoint, Line, PointsIter, Primitive, Rectangle, |
10 | }, |
11 | transform::Transform, |
12 | }; |
13 | |
14 | mod points; |
15 | mod scanline_intersections; |
16 | mod scanline_iterator; |
17 | mod styled; |
18 | |
19 | pub use points::Points; |
20 | pub use styled::StyledPixelsIterator; |
21 | |
22 | /// Triangle primitive |
23 | /// |
24 | /// # Examples |
25 | /// |
26 | /// ## Create some triangles with different styles |
27 | /// |
28 | /// ```rust |
29 | /// use embedded_graphics::{ |
30 | /// pixelcolor::Rgb565, prelude::*, primitives::{Triangle, PrimitiveStyle}, |
31 | /// }; |
32 | /// # use embedded_graphics::mock_display::MockDisplay; |
33 | /// # let mut display = MockDisplay::default(); |
34 | /// # display.set_allow_overdraw(true); |
35 | /// |
36 | /// // Triangle with red 1 px wide stroke |
37 | /// Triangle::new(Point::new(40, 20), Point::new(50, 25), Point::new(60, 60)) |
38 | /// .into_styled(PrimitiveStyle::with_stroke(Rgb565::RED, 1)) |
39 | /// .draw(&mut display)?; |
40 | /// |
41 | /// // Triangle with translation applied |
42 | /// Triangle::new(Point::new(40, 20), Point::new(50, 25), Point::new(60, 60)) |
43 | /// .translate(Point::new(-10, -20)) |
44 | /// .into_styled(PrimitiveStyle::with_stroke(Rgb565::GREEN, 1)) |
45 | /// .draw(&mut display)?; |
46 | /// # Ok::<(), core::convert::Infallible>(()) |
47 | /// ``` |
48 | /// |
49 | /// ## Create a triangle from a slice |
50 | /// |
51 | /// A triangle can be created from a `&[Point]` slice. If the slice is not exactly 3 elements long, |
52 | /// the [`from_slice`] method will panic. |
53 | /// |
54 | /// ```rust |
55 | /// use embedded_graphics::{geometry::Point, primitives::Triangle}; |
56 | /// |
57 | /// let p1 = Point::new(5, 10); |
58 | /// let p2 = Point::new(15, 25); |
59 | /// let p3 = Point::new(5, 25); |
60 | /// |
61 | /// let tri = Triangle::from_slice(&[p1, p2, p3]); |
62 | /// # |
63 | /// # assert_eq!(tri, Triangle::new(p1, p2, p3)); |
64 | /// ``` |
65 | /// |
66 | /// [`from_slice`]: Triangle::from_slice() |
67 | #[derive (Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] |
68 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
69 | pub struct Triangle { |
70 | /// The vertices of the triangle. |
71 | pub vertices: [Point; 3], |
72 | } |
73 | |
74 | impl Primitive for Triangle {} |
75 | |
76 | impl PointsIter for Triangle { |
77 | type Iter = Points; |
78 | |
79 | fn points(&self) -> Self::Iter { |
80 | Points::new(self) |
81 | } |
82 | } |
83 | |
84 | impl ContainsPoint for Triangle { |
85 | fn contains(&self, point: Point) -> bool { |
86 | // Skip expensive calculations below if point is outside the bounding box |
87 | if !self.bounding_box().contains(point) { |
88 | return false; |
89 | } |
90 | |
91 | let p = point; |
92 | let [p1, p2, p3] = self.vertices; |
93 | |
94 | // Check if point is inside triangle using https://stackoverflow.com/a/20861130/383609. |
95 | // Works for any point ordering. |
96 | let is_inside = { |
97 | let s = p1.y * p3.x - p1.x * p3.y + (p3.y - p1.y) * p.x + (p1.x - p3.x) * p.y; |
98 | let t = p1.x * p2.y - p1.y * p2.x + (p1.y - p2.y) * p.x + (p2.x - p1.x) * p.y; |
99 | |
100 | if (s < 0) != (t < 0) { |
101 | false |
102 | } else { |
103 | // Determinant |
104 | let a = self.area_doubled(); |
105 | |
106 | // If determinant is zero, triangle is colinear and can never contain a point. |
107 | if a == 0 { |
108 | return false; |
109 | } |
110 | |
111 | // This check allows this algorithm to work with clockwise or counterclockwise |
112 | // triangles. |
113 | if a < 0 { |
114 | s <= 0 && s + t >= a |
115 | } else { |
116 | s >= 0 && s + t <= a |
117 | } |
118 | } |
119 | }; |
120 | |
121 | // Skip expensive point-on-line check below if point is definitely inside triangle |
122 | if is_inside { |
123 | return true; |
124 | } |
125 | |
126 | // Sort points into same order as `ScanlineIterator` so this check produces the same results |
127 | // as a rendered triangle would. |
128 | let [p1, p2, p3] = self.sorted_yx().vertices; |
129 | |
130 | // Special case: due to the Bresenham algorithm being used to render triangles, some pixel |
131 | // centers on a Styled<Triangle> lie outside the mathematical triangle. This check |
132 | // inefficiently checks to see if the point lies on any of the border edges. |
133 | Line::new(p1, p2) |
134 | .points() |
135 | .chain(Line::new(p1, p3).points()) |
136 | .chain(Line::new(p2, p3).points()) |
137 | .any(|line_point| line_point == p) |
138 | } |
139 | } |
140 | |
141 | impl Dimensions for Triangle { |
142 | fn bounding_box(&self) -> Rectangle { |
143 | let [p1: Point, p2: Point, p3: Point] = self.vertices; |
144 | |
145 | let x_min: i32 = min(v1:min(p1.x, p2.x), v2:p3.x); |
146 | let y_min: i32 = min(v1:min(p1.y, p2.y), v2:p3.y); |
147 | |
148 | let x_max: i32 = max(v1:max(p1.x, p2.x), v2:p3.x); |
149 | let y_max: i32 = max(v1:max(p1.y, p2.y), v2:p3.y); |
150 | |
151 | Rectangle::with_corners(corner_1:Point::new(x_min, y_min), corner_2:Point::new(x_max, y_max)) |
152 | } |
153 | } |
154 | |
155 | impl Triangle { |
156 | /// Create a new triangle with the given vertices. |
157 | pub const fn new(vertex1: Point, vertex2: Point, vertex3: Point) -> Self { |
158 | Triangle { |
159 | vertices: [vertex1, vertex2, vertex3], |
160 | } |
161 | } |
162 | |
163 | /// Creates a new triangle from a [`Point`] slice. |
164 | /// |
165 | /// # Panics |
166 | /// |
167 | /// This method will panic if the given slice is not exactly 3 items long. |
168 | /// |
169 | /// [`Point`]: super::super::geometry::Point |
170 | pub fn from_slice(vertices: &[Point]) -> Self { |
171 | match vertices { |
172 | [p1, p2, p3] => Self::new(*p1, *p2, *p3), |
173 | vertices => panic!("source slice length ( {}) must equal 3" , vertices.len()), |
174 | } |
175 | } |
176 | |
177 | /// Return the area of the triangle, doubled. |
178 | /// |
179 | /// This method can be used to determine if the triangle is colinear by checking if the returned |
180 | /// value is equal to zero. |
181 | pub(in crate::primitives) const fn area_doubled(&self) -> i32 { |
182 | let [p1, p2, p3] = self.vertices; |
183 | |
184 | -p2.y * p3.x + p1.y * (p3.x - p2.x) + p1.x * (p2.y - p3.y) + p2.x * p3.y |
185 | } |
186 | |
187 | /// Create a new triangle with points sorted in a clockwise direction. |
188 | pub(in crate::primitives::triangle) fn sorted_clockwise(&self) -> Self { |
189 | match self.area_doubled().cmp(&0) { |
190 | // Triangle is wound CCW. Swap two points to make it CW. |
191 | Ordering::Less => Self::new(self.vertices[1], self.vertices[0], self.vertices[2]), |
192 | // Triangle is already CW, do nothing. |
193 | Ordering::Greater => *self, |
194 | // Triangle is colinear. Sort points so they lie sequentially along the line. |
195 | Ordering::Equal => self.sorted_yx(), |
196 | } |
197 | } |
198 | |
199 | /// Sort the 3 vertices of the triangle in order of increasing Y value. |
200 | /// |
201 | /// If two points have the same Y value, the one with the lesser X value is put before. |
202 | const fn sorted_yx(&self) -> Self { |
203 | let [p1, p2, p3] = self.vertices; |
204 | |
205 | let (y1, y2) = sort_two_yx(p1, p2); |
206 | let (y1, y3) = sort_two_yx(p3, y1); |
207 | let (y2, y3) = sort_two_yx(y3, y2); |
208 | |
209 | Self::new(y1, y2, y3) |
210 | } |
211 | |
212 | pub(in crate::primitives::triangle) fn scanline_intersection( |
213 | &self, |
214 | scanline_y: i32, |
215 | ) -> Scanline { |
216 | let [p1, p2, p3] = self.sorted_yx().vertices; |
217 | |
218 | let mut scanline = Scanline::new_empty(scanline_y); |
219 | |
220 | // Triangle is colinear. We can get away with only intersecting the single line. |
221 | if self.area_doubled() == 0 { |
222 | scanline.bresenham_intersection(&Line::new(p1, p3)); |
223 | |
224 | return scanline; |
225 | } |
226 | |
227 | scanline.bresenham_intersection(&Line::new(p1, p2)); |
228 | scanline.bresenham_intersection(&Line::new(p1, p3)); |
229 | scanline.bresenham_intersection(&Line::new(p2, p3)); |
230 | |
231 | scanline |
232 | } |
233 | |
234 | /// Generate a line join for each corner of the triangle. |
235 | fn joins(&self, stroke_width: u32, stroke_offset: StrokeOffset) -> [LineJoin; 3] { |
236 | let [p1, p2, p3] = self.vertices; |
237 | |
238 | [ |
239 | LineJoin::from_points(p3, p1, p2, stroke_width, stroke_offset), |
240 | LineJoin::from_points(p1, p2, p3, stroke_width, stroke_offset), |
241 | LineJoin::from_points(p2, p3, p1, stroke_width, stroke_offset), |
242 | ] |
243 | } |
244 | |
245 | /// Compute whether a triangle with thick stroke has a hole in its center or is completely |
246 | /// filled by stroke. |
247 | // PERF: This doesn't need to compute the entire join, much like how `thick_stroke_inset` |
248 | // doesn't |
249 | pub(in crate::primitives::triangle) fn is_collapsed( |
250 | &self, |
251 | stroke_width: u32, |
252 | stroke_offset: StrokeOffset, |
253 | ) -> bool { |
254 | let joins = self.joins(stroke_width, stroke_offset); |
255 | |
256 | joins.iter().enumerate().any(|(i, join)| { |
257 | // Quick check: if the join is degenerate, no hole can occur. |
258 | if join.is_degenerate() { |
259 | return true; |
260 | } |
261 | |
262 | // Compute inner-most points of each join. The triangle is sorted clockwise, so that's |
263 | // the right-side point. The `first_edge_end` and `second_edge_start` points are always |
264 | // the same in this case, as this is the "pinched" side of the join, so we'll |
265 | // arbitrarily pick `first_edge_end`. |
266 | let inner_point = join.first_edge_end.right; |
267 | |
268 | // Find opposite edge to the given point. |
269 | let opposite = { |
270 | let start = self.vertices[(i + 1) % 3]; |
271 | let end = self.vertices[(i + 2) % 3]; |
272 | |
273 | // Get right side extent (triangle is sorted clockwise, remember) |
274 | Line::new(start, end).extents(stroke_width, stroke_offset).1 |
275 | }; |
276 | |
277 | // If the inner point is to the left of the opposite side line, the triangle edges self- |
278 | // intersect, so the triangle is collapsed. |
279 | LinearEquation::from_line(&opposite).check_side(inner_point, LineSide::Left) |
280 | }) |
281 | } |
282 | } |
283 | |
284 | impl Transform for Triangle { |
285 | /// Translate the triangle from its current position to a new position by (x, y) pixels, |
286 | /// returning a new `Triangle`. For a mutating transform, see `translate_mut`. |
287 | /// |
288 | /// ``` |
289 | /// # use embedded_graphics::primitives::Triangle; |
290 | /// # use embedded_graphics::prelude::*; |
291 | /// let tri = Triangle::new(Point::new(5, 10), Point::new(15, 20), Point::new(8, 15)); |
292 | /// let moved = tri.translate(Point::new(10, 10)); |
293 | /// |
294 | /// assert_eq!( |
295 | /// moved, |
296 | /// Triangle::new(Point::new(15, 20), Point::new(25, 30), Point::new(18, 25)) |
297 | /// ); |
298 | /// ``` |
299 | fn translate(&self, by: Point) -> Self { |
300 | let mut triangle = *self; |
301 | triangle.translate_mut(by); |
302 | triangle |
303 | } |
304 | |
305 | /// Translate the triangle from its current position to a new position by (x, y) pixels. |
306 | /// |
307 | /// ``` |
308 | /// # use embedded_graphics::primitives::Triangle; |
309 | /// # use embedded_graphics::prelude::*; |
310 | /// let mut tri = Triangle::new(Point::new(5, 10), Point::new(15, 20), Point::new(10, 15)); |
311 | /// tri.translate_mut(Point::new(10, 10)); |
312 | /// |
313 | /// assert_eq!( |
314 | /// tri, |
315 | /// Triangle::new(Point::new(15, 20), Point::new(25, 30), Point::new(20, 25)) |
316 | /// ) |
317 | /// ``` |
318 | fn translate_mut(&mut self, by: Point) -> &mut Self { |
319 | self.vertices.iter_mut().for_each(|v| *v += by); |
320 | |
321 | self |
322 | } |
323 | } |
324 | |
325 | const fn sort_two_yx(p1: Point, p2: Point) -> (Point, Point) { |
326 | // If p1.y is less than p2.y, return it first. Otherwise, if they have the same Y coordinate, |
327 | // the first point becomes the one with the lesser X coordinate. |
328 | if p1.y < p2.y || (p1.y == p2.y && p1.x < p2.x) { |
329 | (p1, p2) |
330 | } else { |
331 | (p2, p1) |
332 | } |
333 | } |
334 | |
335 | #[cfg (test)] |
336 | mod tests { |
337 | use super::*; |
338 | use crate::{geometry::Size, mock_display::MockDisplay, pixelcolor::BinaryColor}; |
339 | |
340 | #[test ] |
341 | fn dimensions() { |
342 | let tri = Triangle::new(Point::new(5, 10), Point::new(15, 25), Point::new(5, 25)); |
343 | let moved = tri.translate(Point::new(-10, -11)); |
344 | |
345 | assert_eq!(tri.bounding_box().size, Size::new(11, 16)); |
346 | |
347 | assert_eq!( |
348 | moved, |
349 | Triangle::new(Point::new(-5, -1), Point::new(5, 14), Point::new(-5, 14)) |
350 | ); |
351 | assert_eq!(moved.bounding_box().size, Size::new(11, 16)); |
352 | } |
353 | |
354 | #[test ] |
355 | fn it_can_be_translated() { |
356 | let tri = Triangle::new(Point::new(5, 10), Point::new(15, 20), Point::new(10, 15)); |
357 | let moved = tri.translate(Point::new(5, 10)); |
358 | |
359 | assert_eq!( |
360 | moved, |
361 | Triangle::new(Point::new(10, 20), Point::new(20, 30), Point::new(15, 25)) |
362 | ); |
363 | } |
364 | |
365 | #[test ] |
366 | fn contains() { |
367 | let triangles = [ |
368 | Triangle::new(Point::new(0, 0), Point::new(63, 10), Point::new(15, 63)), |
369 | Triangle::new(Point::new(5, 0), Point::new(30, 63), Point::new(63, 0)), |
370 | Triangle::new(Point::new(0, 0), Point::new(0, 63), Point::new(63, 30)), |
371 | Triangle::new(Point::new(22, 0), Point::new(0, 63), Point::new(63, 63)), |
372 | Triangle::new(Point::new(0, 22), Point::new(63, 0), Point::new(63, 63)), |
373 | ]; |
374 | |
375 | for triangle in triangles.iter() { |
376 | let expected = MockDisplay::from_points(triangle.points(), BinaryColor::On); |
377 | |
378 | for point in Rectangle::new(Point::new(-5, -5), Size::new(70, 70)).points() { |
379 | let should_contain = |
380 | expected.bounding_box().contains(point) && expected.get_pixel(point).is_some(); |
381 | |
382 | assert_eq!( |
383 | triangle.contains(point), |
384 | should_contain, |
385 | "{:?}, {:?}" , |
386 | point, |
387 | triangle |
388 | ); |
389 | } |
390 | } |
391 | } |
392 | |
393 | #[test ] |
394 | fn colinear_never_contains() { |
395 | let triangles = [ |
396 | Triangle::new(Point::new(5, 10), Point::new(15, 20), Point::new(10, 15)), |
397 | Triangle::new(Point::new(2, 2), Point::new(2, 4), Point::new(2, 4)), |
398 | Triangle::new(Point::new(2, 2), Point::new(4, 2), Point::new(4, 2)), |
399 | ]; |
400 | |
401 | for triangle in triangles.iter() { |
402 | for point in Rectangle::new(Point::new(-5, -5), Size::new(70, 70)).points() { |
403 | assert_eq!(triangle.contains(point), false); |
404 | } |
405 | } |
406 | } |
407 | |
408 | #[test ] |
409 | #[should_panic (expected = "source slice length (2) must equal 3" )] |
410 | fn slice_panic_too_short() { |
411 | let points = [Point::zero(), Point::zero()]; |
412 | |
413 | Triangle::from_slice(&points); |
414 | } |
415 | |
416 | #[test ] |
417 | #[should_panic (expected = "source slice length (4) must equal 3" )] |
418 | fn slice_panic_too_long() { |
419 | let points = [Point::zero(), Point::zero(), Point::zero(), Point::zero()]; |
420 | |
421 | Triangle::from_slice(&points); |
422 | } |
423 | |
424 | #[test ] |
425 | fn slice_just_right() { |
426 | let points = [ |
427 | Point::new_equal(1), |
428 | Point::new_equal(2), |
429 | Point::new_equal(3), |
430 | ]; |
431 | |
432 | assert_eq!( |
433 | Triangle::from_slice(&points), |
434 | Triangle::new( |
435 | Point::new_equal(1), |
436 | Point::new_equal(2), |
437 | Point::new_equal(3) |
438 | ) |
439 | ); |
440 | } |
441 | |
442 | #[test ] |
443 | fn check_collapsed() { |
444 | let triangle = Triangle::new(Point::new(10, 10), Point::new(30, 20), Point::new(20, 25)); |
445 | |
446 | assert_eq!(triangle.is_collapsed(20, StrokeOffset::None), true); |
447 | } |
448 | } |
449 | |