1use crate::new_hash_set;
2use crate::prelude::*;
3use crate::shape_box::ShapeBox;
4use fnv::FnvHashSet;
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
10pub enum TriangleAngleType {
11 Acute,
12 Right,
13 Obtuse,
14 Equiangular,
15 Other,
16}
17
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19#[derive(Debug, Clone, Eq, PartialEq, Hash, Copy)]
20pub enum TriangleSideType {
21 Isosceles,
22 Scalene,
23 Equilateral,
24}
25
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27#[derive(Debug, Clone, Eq, PartialEq, Hash)]
28pub struct Triangle {
29 points: [Coord; 3],
30 angles: [isize; 3],
31 angle_type: TriangleAngleType,
32 side_type: TriangleSideType,
33 center: Coord,
34}
35
36impl IntersectsContains for Triangle {}
37
38impl Triangle {
39 #[must_use]
40 pub fn new<P1: Into<Coord>, P2: Into<Coord>, P3: Into<Coord>>(
41 point1: P1,
42 point2: P2,
43 point3: P3,
44 ) -> Self {
45 let points = [point1.into(), point2.into(), point3.into()];
46 let angles = [
47 points[0].angle_to(points[1]),
48 points[1].angle_to(points[2]),
49 points[2].angle_to(points[0]),
50 ];
51 let angle_type = if angles.iter().any(|a| a.abs() == 90) {
52 TriangleAngleType::Right
53 } else if angles.iter().all(|a| a.abs() < 90) {
54 TriangleAngleType::Acute
55 } else if angles[0].abs() == angles[1].abs() && angles[1].abs() == angles[2].abs() {
56 TriangleAngleType::Equiangular
57 } else if angles.iter().any(|a| a.abs() > 90) {
58 TriangleAngleType::Obtuse
59 } else {
60 TriangleAngleType::Other
61 };
62 let side_type = if points[0].distance(points[1]) == points[1].distance(points[2])
63 && points[2].distance(points[0]) == points[1].distance(points[2])
64 {
65 TriangleSideType::Equilateral
66 } else if points[0].distance(points[1]) == points[1].distance(points[2])
67 || points[2].distance(points[0]) == points[1].distance(points[2])
68 || points[2].distance(points[0]) == points[0].distance(points[1])
69 {
70 TriangleSideType::Isosceles
71 } else {
72 TriangleSideType::Scalene
73 };
74 let mut triangle = Self {
75 points,
76 angles,
77 angle_type,
78 side_type,
79 center: coord!(0, 0),
80 };
81 triangle.center = coord!(triangle.left(), triangle.top())
82 .mid_point(coord!(triangle.right(), triangle.bottom()));
83 triangle
84 }
85}
86
87impl Triangle {
88 #[inline]
89 #[must_use]
90 pub fn angles(&self) -> [isize; 3] {
91 self.angles
92 }
93
94 #[inline]
95 #[must_use]
96 pub fn angle_type(&self) -> &TriangleAngleType {
97 &self.angle_type
98 }
99
100 #[inline]
101 #[must_use]
102 pub fn side_type(&self) -> &TriangleSideType {
103 &self.side_type
104 }
105
106 pub fn centroid(&self) -> Coord {
109 (self.points[0] + self.points[1] + self.points[2]) / 3
110 }
111}
112
113impl Shape for Triangle {
114 fn from_points(points: &[Coord]) -> Self
115 where
116 Self: Sized,
117 {
118 Triangle::new(points[0], points[1], points[2])
119 }
120
121 fn rebuild(&self, points: &[Coord]) -> Self
122 where
123 Self: Sized,
124 {
125 Triangle::from_points(points)
126 }
127
128 fn contains(&self, point: Coord) -> bool {
129 let p1 = coord!(
130 self.points[1].x - self.points[0].x,
131 self.points[1].y - self.points[0].y,
132 );
133 let p2 = coord!(
134 self.points[2].x - self.points[0].x,
135 self.points[2].y - self.points[0].y,
136 );
137 let q = coord!(point.x - self.points[0].x, point.y - self.points[0].y);
138
139 let s = q.cross_product(p2) as f32 / p1.cross_product(p2) as f32;
140 let t = p1.cross_product(q) as f32 / p1.cross_product(p2) as f32;
141
142 s >= 0.0 && t >= 0.0 && (s + t) <= 1.0
143 }
144
145 fn points(&self) -> Vec<Coord> {
146 self.points.to_vec()
147 }
148
149 #[inline]
150 fn center(&self) -> Coord {
151 self.center
152 }
153
154 fn outline_pixels(&self) -> Vec<Coord> {
155 self.as_lines()
156 .iter()
157 .flat_map(|line| line.outline_pixels())
158 .collect()
159 }
160
161 fn filled_pixels(&self) -> Vec<Coord> {
162 let mut output = new_hash_set();
163 let mut sorted_points = self.points.to_vec();
164 sorted_points.sort_by_key(|c| c.y);
165 let points = [
166 (sorted_points[0].x as f32, sorted_points[0].y as f32),
167 (sorted_points[1].x as f32, sorted_points[1].y as f32),
168 (sorted_points[2].x as f32, sorted_points[2].y as f32),
169 ];
170 if points[1].1 == points[2].1 {
171 draw_flat_bottom(&mut output, points);
172 } else if points[0].1 == points[1].1 {
173 draw_flat_top(&mut output, points);
174 } else {
175 let p = (
176 points[0].0
177 + ((points[1].1 - points[0].1) / (points[2].1 - points[0].1))
178 * (points[2].0 - points[0].0),
179 points[1].1,
180 );
181 draw_flat_bottom(&mut output, [points[0], points[1], p]);
182 draw_flat_top(&mut output, [points[1], p, points[2]]);
183 }
184 output.into_iter().collect()
185 }
186
187 fn to_shape_box(&self) -> ShapeBox {
188 ShapeBox::Triangle(self.clone())
189 }
190}
191
192#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
193#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
194pub enum AnglePosition {
195 TopLeft,
196 TopRight,
197 BottomRight,
198 BottomLeft,
199 Top,
200 Right,
201 Bottom,
202 Left,
203}
204
205#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
206#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
207pub enum FlatSide {
208 Top,
209 Bottom,
210 Left,
211 Right,
212}
213
214impl Triangle {
215 #[must_use]
216 pub fn as_rect(&self) -> Rect {
217 Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
218 }
219
220 #[must_use]
221 pub fn as_lines(&self) -> [Line; 3] {
222 let points = self.points();
223 [
224 Line::new(points[0], points[1]),
225 Line::new(points[1], points[2]),
226 Line::new(points[2], points[0]),
227 ]
228 }
229
230 #[must_use]
232 pub fn right_angle<P: Into<Coord>>(
233 angle_coord: P,
234 size: usize,
235 angle_position: AnglePosition,
236 ) -> Triangle {
237 let size = size as isize;
238 let point = angle_coord.into();
239 let left = point.x - size;
240 let right = point.x + size;
241 let top = point.y - size;
242 let bottom = point.y + size;
243 let half_size = size / 2;
244 match angle_position {
245 AnglePosition::TopLeft => Triangle::new(point, (right, point.y), (point.x, bottom)),
246 AnglePosition::BottomRight => Triangle::new(point, (left, point.y), (point.x, top)),
247 AnglePosition::BottomLeft => Triangle::new(point, (right, point.y), (point.x, top)),
248 AnglePosition::TopRight => Triangle::new(point, (left, point.y), (point.x, bottom)),
249 AnglePosition::Top => Triangle::new(
250 point,
251 point + (-half_size, half_size),
252 point + (half_size, half_size),
253 ),
254 AnglePosition::Right => Triangle::new(
255 point,
256 point - (half_size, half_size),
257 point + (-half_size, half_size),
258 ),
259 AnglePosition::Bottom => Triangle::new(
260 point,
261 point - (half_size, half_size),
262 point + (half_size, -half_size),
263 ),
264 AnglePosition::Left => Triangle::new(
265 point,
266 point + (half_size, -half_size),
267 point + (half_size, half_size),
268 ),
269 }
270 }
271
272 #[must_use]
275 pub fn equilateral<P: Into<Coord>>(center: P, size: usize, flat_side: FlatSide) -> Triangle {
276 let point = center.into();
277 let dist = (size / 2) as isize;
278 let left = point.x - dist;
279 let right = point.x + dist;
280 let top = point.y - dist;
281 let bottom = point.y + dist;
282 match flat_side {
283 FlatSide::Top => Triangle::new((left, top), (right, top), (point.x, bottom)),
284 FlatSide::Bottom => Triangle::new((left, bottom), (right, bottom), (point.x, top)),
285 FlatSide::Left => Triangle::new((left, top), (left, bottom), (right, point.y)),
286 FlatSide::Right => Triangle::new((right, top), (right, bottom), (left, point.y)),
287 }
288 }
289}
290
291fn draw_flat_bottom(output: &mut FnvHashSet<Coord>, points: [(f32, f32); 3]) {
292 let slope1 = (points[1].0 - points[0].0) / (points[1].1 - points[0].1);
293 let slope2 = (points[2].0 - points[0].0) / (points[2].1 - points[0].1);
294 let mut x1 = points[0].0;
295 let mut x2 = points[0].0;
296 for y in (points[0].1 as usize)..(points[1].1 as usize) {
297 let start = x1.min(x2) as isize;
298 let end = x1.max(x2) as isize + 1;
299 let line_points = Line::new((start, y as isize), (end, y as isize)).outline_pixels();
300 for point in line_points {
301 output.insert(point);
302 }
303 x1 += slope1;
304 x2 += slope2;
305 }
306}
307
308fn draw_flat_top(output: &mut FnvHashSet<Coord>, points: [(f32, f32); 3]) {
309 let slope1 = (points[2].0 - points[0].0) / (points[2].1 - points[0].1);
310 let slope2 = (points[2].0 - points[1].0) / (points[2].1 - points[1].1);
311 let mut x1 = points[2].0;
312 let mut x2 = points[2].0;
313 for y in ((points[0].1 as usize)..(points[2].1 as usize)).rev() {
314 let start = x1.min(x2) as usize;
315 let end = x1.max(x2) as usize + 1;
316 let line_points = Line::new((start, y), (end, y)).outline_pixels();
317 for point in line_points {
318 output.insert(point);
319 }
320 x1 -= slope1;
321 x2 -= slope2;
322 }
323}
324
325#[cfg(test)]
326mod test {
327 use crate::triangle::{AnglePosition, FlatSide, Triangle};
328 use crate::Shape;
329
330 #[test]
331 fn right_angle_triangles() {
332 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::TopLeft);
333 assert_eq!(
334 triangle.points[0],
335 (100, 100).into(),
336 "topleft - main angle"
337 );
338 assert_eq!(triangle.points[1], (200, 100).into(), "topleft - same y");
339 assert_eq!(triangle.points[2], (100, 200).into(), "topleft - same x");
340
341 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::BottomRight);
342 assert_eq!(
343 triangle.points[0],
344 (100, 100).into(),
345 "bottomright - main angle"
346 );
347 assert_eq!(triangle.points[1], (0, 100).into(), "bottomright - same y");
348 assert_eq!(triangle.points[2], (100, 0).into(), "bottomright - same x");
349
350 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::TopRight);
351 assert_eq!(
352 triangle.points[0],
353 (100, 100).into(),
354 "topright - main angle"
355 );
356 assert_eq!(triangle.points[1], (0, 100).into(), "topright - same y");
357 assert_eq!(triangle.points[2], (100, 200).into(), "topright - same x");
358
359 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::BottomLeft);
360 assert_eq!(
361 triangle.points[0],
362 (100, 100).into(),
363 "bottomleft - main angle"
364 );
365 assert_eq!(triangle.points[1], (200, 100).into(), "bottomleft - same y");
366 assert_eq!(triangle.points[2], (100, 0).into(), "bottomleft - same x");
367 }
368
369 #[test]
370 fn check_moving() {
371 let triangle = Triangle::equilateral((50, 50), 10, FlatSide::Left);
372 assert_eq!(triangle.center, coord!(50, 50));
373 assert_eq!(triangle.points[0], coord!(45, 45));
374 let moved = triangle.move_to(coord!(30, 30));
375 assert_eq!(moved.points[0], coord!(30, 30));
376
377 let moved = triangle.move_center_to(coord!(130, 230));
378 assert_eq!(moved.center, coord!(130, 230));
379 assert_eq!(moved.points[0], coord!(125, 225));
380 }
381}