1use core::cmp::{max, min, Ordering};
4
5use 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
14mod points;
15mod scanline_intersections;
16mod scanline_iterator;
17mod styled;
18
19pub use points::Points;
20pub use styled::StyledPixelsIterator;
21
22#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
68#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
69pub struct Triangle {
70 pub vertices: [Point; 3],
72}
73
74impl Primitive for Triangle {}
75
76impl PointsIter for Triangle {
77 type Iter = Points;
78
79 fn points(&self) -> Self::Iter {
80 Points::new(self)
81 }
82}
83
84impl ContainsPoint for Triangle {
85 fn contains(&self, point: Point) -> bool {
86 if !self.bounding_box().contains(point) {
88 return false;
89 }
90
91 let p = point;
92 let [p1, p2, p3] = self.vertices;
93
94 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 let a = self.area_doubled();
105
106 if a == 0 {
108 return false;
109 }
110
111 if a < 0 {
114 s <= 0 && s + t >= a
115 } else {
116 s >= 0 && s + t <= a
117 }
118 }
119 };
120
121 if is_inside {
123 return true;
124 }
125
126 let [p1, p2, p3] = self.sorted_yx().vertices;
129
130 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
141impl Dimensions for Triangle {
142 fn bounding_box(&self) -> Rectangle {
143 let [p1, p2, p3] = self.vertices;
144
145 let x_min = min(min(p1.x, p2.x), p3.x);
146 let y_min = min(min(p1.y, p2.y), p3.y);
147
148 let x_max = max(max(p1.x, p2.x), p3.x);
149 let y_max = max(max(p1.y, p2.y), p3.y);
150
151 Rectangle::with_corners(Point::new(x_min, y_min), Point::new(x_max, y_max))
152 }
153}
154
155impl Triangle {
156 pub const fn new(vertex1: Point, vertex2: Point, vertex3: Point) -> Self {
158 Triangle {
159 vertices: [vertex1, vertex2, vertex3],
160 }
161 }
162
163 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 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 pub(in crate::primitives::triangle) fn sorted_clockwise(&self) -> Self {
189 match self.area_doubled().cmp(&0) {
190 Ordering::Less => Self::new(self.vertices[1], self.vertices[0], self.vertices[2]),
192 Ordering::Greater => *self,
194 Ordering::Equal => self.sorted_yx(),
196 }
197 }
198
199 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 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 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 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 if join.is_degenerate() {
259 return true;
260 }
261
262 let inner_point = join.first_edge_end.right;
267
268 let opposite = {
270 let start = self.vertices[(i + 1) % 3];
271 let end = self.vertices[(i + 2) % 3];
272
273 Line::new(start, end).extents(stroke_width, stroke_offset).1
275 };
276
277 LinearEquation::from_line(&opposite).check_side(inner_point, LineSide::Left)
280 })
281 }
282}
283
284impl Transform for Triangle {
285 fn translate(&self, by: Point) -> Self {
300 let mut triangle = *self;
301 triangle.translate_mut(by);
302 triangle
303 }
304
305 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
325const fn sort_two_yx(p1: Point, p2: Point) -> (Point, Point) {
326 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)]
336mod 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}