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
107impl Shape for Triangle {
108 fn from_points(points: &[Coord]) -> Self
109 where
110 Self: Sized,
111 {
112 Triangle::new(points[0], points[1], points[2])
113 }
114
115 fn rebuild(&self, points: &[Coord]) -> Self
116 where
117 Self: Sized,
118 {
119 Triangle::from_points(points)
120 }
121
122 fn contains(&self, point: Coord) -> bool {
123 let p1 = coord!(
124 self.points[1].x - self.points[0].x,
125 self.points[1].y - self.points[0].y,
126 );
127 let p2 = coord!(
128 self.points[2].x - self.points[0].x,
129 self.points[2].y - self.points[0].y,
130 );
131 let q = coord!(point.x - self.points[0].x, point.y - self.points[0].y);
132
133 let s = q.cross_product(p2) as f32 / p1.cross_product(p2) as f32;
134 let t = p1.cross_product(q) as f32 / p1.cross_product(p2) as f32;
135
136 s >= 0.0 && t >= 0.0 && (s + t) <= 1.0
137 }
138
139 fn points(&self) -> Vec<Coord> {
140 self.points.to_vec()
141 }
142
143 #[inline]
144 fn center(&self) -> Coord {
145 self.center
146 }
147
148 fn outline_pixels(&self) -> Vec<Coord> {
149 self.as_lines()
150 .iter()
151 .flat_map(|line| line.outline_pixels())
152 .collect()
153 }
154
155 fn filled_pixels(&self) -> Vec<Coord> {
156 let mut output = new_hash_set();
157 let mut sorted_points = self.points.to_vec();
158 sorted_points.sort_by_key(|c| c.y);
159 let points = [
160 (sorted_points[0].x as f32, sorted_points[0].y as f32),
161 (sorted_points[1].x as f32, sorted_points[1].y as f32),
162 (sorted_points[2].x as f32, sorted_points[2].y as f32),
163 ];
164 if points[1].1 == points[2].1 {
165 draw_flat_bottom(&mut output, points);
166 } else if points[0].1 == points[1].1 {
167 draw_flat_top(&mut output, points);
168 } else {
169 let p = (
170 points[0].0
171 + ((points[1].1 - points[0].1) / (points[2].1 - points[0].1))
172 * (points[2].0 - points[0].0),
173 points[1].1,
174 );
175 draw_flat_bottom(&mut output, [points[0], points[1], p]);
176 draw_flat_top(&mut output, [points[1], p, points[2]]);
177 }
178 output.into_iter().collect()
179 }
180
181 fn to_shape_box(&self) -> ShapeBox {
182 ShapeBox::Triangle(self.clone())
183 }
184}
185
186#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
187#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
188pub enum AnglePosition {
189 TopLeft,
190 TopRight,
191 BottomRight,
192 BottomLeft,
193 Top,
194 Right,
195 Bottom,
196 Left,
197}
198
199#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
200#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
201pub enum FlatSide {
202 Top,
203 Bottom,
204 Left,
205 Right,
206}
207
208impl Triangle {
209 #[must_use]
210 pub fn as_rect(&self) -> Rect {
211 Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
212 }
213
214 #[must_use]
215 pub fn as_lines(&self) -> [Line; 3] {
216 let points = self.points();
217 [
218 Line::new(points[0], points[1]),
219 Line::new(points[1], points[2]),
220 Line::new(points[2], points[0]),
221 ]
222 }
223
224 #[must_use]
226 pub fn right_angle<P: Into<Coord>>(
227 angle_coord: P,
228 size: usize,
229 angle_position: AnglePosition,
230 ) -> Triangle {
231 let size = size as isize;
232 let point = angle_coord.into();
233 let left = point.x - size;
234 let right = point.x + size;
235 let top = point.y - size;
236 let bottom = point.y + size;
237 let half_size = size / 2;
238 match angle_position {
239 AnglePosition::TopLeft => Triangle::new(point, (right, point.y), (point.x, bottom)),
240 AnglePosition::BottomRight => Triangle::new(point, (left, point.y), (point.x, top)),
241 AnglePosition::BottomLeft => Triangle::new(point, (right, point.y), (point.x, top)),
242 AnglePosition::TopRight => Triangle::new(point, (left, point.y), (point.x, bottom)),
243 AnglePosition::Top => Triangle::new(
244 point,
245 point + (-half_size, half_size),
246 point + (half_size, half_size),
247 ),
248 AnglePosition::Right => Triangle::new(
249 point,
250 point - (half_size, half_size),
251 point + (-half_size, half_size),
252 ),
253 AnglePosition::Bottom => Triangle::new(
254 point,
255 point - (half_size, half_size),
256 point + (half_size, -half_size),
257 ),
258 AnglePosition::Left => Triangle::new(
259 point,
260 point + (half_size, -half_size),
261 point + (half_size, half_size),
262 ),
263 }
264 }
265
266 #[must_use]
269 pub fn equilateral<P: Into<Coord>>(center: P, size: usize, flat_side: FlatSide) -> Triangle {
270 let point = center.into();
271 let dist = (size / 2) as isize;
272 let left = point.x - dist;
273 let right = point.x + dist;
274 let top = point.y - dist;
275 let bottom = point.y + dist;
276 match flat_side {
277 FlatSide::Top => Triangle::new((left, top), (right, top), (point.x, bottom)),
278 FlatSide::Bottom => Triangle::new((left, bottom), (right, bottom), (point.x, top)),
279 FlatSide::Left => Triangle::new((left, top), (left, bottom), (right, point.y)),
280 FlatSide::Right => Triangle::new((right, top), (right, bottom), (left, point.y)),
281 }
282 }
283}
284
285fn draw_flat_bottom(output: &mut FnvHashSet<Coord>, points: [(f32, f32); 3]) {
286 let slope1 = (points[1].0 - points[0].0) / (points[1].1 - points[0].1);
287 let slope2 = (points[2].0 - points[0].0) / (points[2].1 - points[0].1);
288 let mut x1 = points[0].0;
289 let mut x2 = points[0].0;
290 for y in (points[0].1 as usize)..(points[1].1 as usize) {
291 let start = x1.min(x2) as isize;
292 let end = x1.max(x2) as isize + 1;
293 let line_points = Line::new((start, y as isize), (end, y as isize)).outline_pixels();
294 for point in line_points {
295 output.insert(point);
296 }
297 x1 += slope1;
298 x2 += slope2;
299 }
300}
301
302fn draw_flat_top(output: &mut FnvHashSet<Coord>, points: [(f32, f32); 3]) {
303 let slope1 = (points[2].0 - points[0].0) / (points[2].1 - points[0].1);
304 let slope2 = (points[2].0 - points[1].0) / (points[2].1 - points[1].1);
305 let mut x1 = points[2].0;
306 let mut x2 = points[2].0;
307 for y in ((points[0].1 as usize)..(points[2].1 as usize)).rev() {
308 let start = x1.min(x2) as usize;
309 let end = x1.max(x2) as usize + 1;
310 let line_points = Line::new((start, y), (end, y)).outline_pixels();
311 for point in line_points {
312 output.insert(point);
313 }
314 x1 -= slope1;
315 x2 -= slope2;
316 }
317}
318
319#[cfg(test)]
320mod test {
321 use crate::triangle::{AnglePosition, FlatSide, Triangle};
322 use crate::Shape;
323
324 #[test]
325 fn right_angle_triangles() {
326 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::TopLeft);
327 assert_eq!(
328 triangle.points[0],
329 (100, 100).into(),
330 "topleft - main angle"
331 );
332 assert_eq!(triangle.points[1], (200, 100).into(), "topleft - same y");
333 assert_eq!(triangle.points[2], (100, 200).into(), "topleft - same x");
334
335 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::BottomRight);
336 assert_eq!(
337 triangle.points[0],
338 (100, 100).into(),
339 "bottomright - main angle"
340 );
341 assert_eq!(triangle.points[1], (0, 100).into(), "bottomright - same y");
342 assert_eq!(triangle.points[2], (100, 0).into(), "bottomright - same x");
343
344 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::TopRight);
345 assert_eq!(
346 triangle.points[0],
347 (100, 100).into(),
348 "topright - main angle"
349 );
350 assert_eq!(triangle.points[1], (0, 100).into(), "topright - same y");
351 assert_eq!(triangle.points[2], (100, 200).into(), "topright - same x");
352
353 let triangle = Triangle::right_angle((100, 100), 100, AnglePosition::BottomLeft);
354 assert_eq!(
355 triangle.points[0],
356 (100, 100).into(),
357 "bottomleft - main angle"
358 );
359 assert_eq!(triangle.points[1], (200, 100).into(), "bottomleft - same y");
360 assert_eq!(triangle.points[2], (100, 0).into(), "bottomleft - same x");
361 }
362
363 #[test]
364 fn check_moving() {
365 let triangle = Triangle::equilateral((50, 50), 10, FlatSide::Left);
366 assert_eq!(triangle.center, coord!(50, 50));
367 assert_eq!(triangle.points[0], coord!(45, 45));
368 let moved = triangle.move_to(coord!(30, 30));
369 assert_eq!(moved.points[0], coord!(30, 30));
370
371 let moved = triangle.move_center_to(coord!(130, 230));
372 assert_eq!(moved.center, coord!(130, 230));
373 assert_eq!(moved.points[0], coord!(125, 225));
374 }
375}