1 | //! Triangle scanline intersections iterator. |
2 | |
3 | use crate::{ |
4 | geometry::Point, |
5 | primitives::{ |
6 | common::{LineJoin, PointType, Scanline, StrokeOffset, ThickSegment}, |
7 | Triangle, |
8 | }, |
9 | }; |
10 | |
11 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
12 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
13 | struct LineConfig { |
14 | first: Scanline, |
15 | second: Scanline, |
16 | internal: Scanline, |
17 | internal_type: PointType, |
18 | } |
19 | |
20 | /// Triangle scanline intersections iterator. |
21 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
22 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
23 | pub struct ScanlineIntersections { |
24 | lines: LineConfig, |
25 | triangle: Triangle, |
26 | stroke_width: u32, |
27 | stroke_offset: StrokeOffset, |
28 | has_fill: bool, |
29 | is_collapsed: bool, |
30 | } |
31 | |
32 | impl ScanlineIntersections { |
33 | /// Create a new thick segments iterator. |
34 | pub fn new( |
35 | triangle: &Triangle, |
36 | stroke_width: u32, |
37 | stroke_offset: StrokeOffset, |
38 | has_fill: bool, |
39 | scanline_y: i32, |
40 | ) -> Self { |
41 | // Special case: If thick strokes completely fill the triangle interior and the stroke is |
42 | // inside the triangle, the normal triangle shape can be used to detect the intersection, |
43 | // with the line type being marked as Border so, when rendered, the correct color is used. |
44 | let is_collapsed = triangle.is_collapsed(stroke_width, stroke_offset) |
45 | && stroke_offset == StrokeOffset::Right; |
46 | |
47 | let mut self_ = Self { |
48 | has_fill, |
49 | triangle: *triangle, |
50 | stroke_offset, |
51 | stroke_width, |
52 | is_collapsed, |
53 | ..Self::empty() |
54 | }; |
55 | |
56 | self_.reset_with_new_scanline(scanline_y); |
57 | |
58 | self_ |
59 | } |
60 | |
61 | /// Empty. |
62 | pub const fn empty() -> Self { |
63 | Self { |
64 | lines: LineConfig { |
65 | first: Scanline::new_empty(0), |
66 | second: Scanline::new_empty(0), |
67 | internal: Scanline::new_empty(0), |
68 | internal_type: PointType::Fill, |
69 | }, |
70 | has_fill: false, |
71 | triangle: Triangle::new(Point::zero(), Point::zero(), Point::zero()), |
72 | stroke_width: 0, |
73 | stroke_offset: StrokeOffset::None, |
74 | is_collapsed: false, |
75 | } |
76 | } |
77 | |
78 | /// Reset with a new scanline. |
79 | pub fn reset_with_new_scanline(&mut self, scanline_y: i32) { |
80 | if let Some(lines) = self.generate_lines(scanline_y) { |
81 | self.lines = lines |
82 | } |
83 | } |
84 | |
85 | fn edge_intersections(&self, scanline_y: i32) -> impl Iterator<Item = Scanline> + '_ { |
86 | let mut idx = 0; |
87 | let mut left = Scanline::new_empty(scanline_y); |
88 | let mut right = Scanline::new_empty(scanline_y); |
89 | |
90 | core::iter::from_fn(move || { |
91 | if self.stroke_width == 0 { |
92 | return None; |
93 | } |
94 | |
95 | while idx < 3 { |
96 | let start = LineJoin::from_points( |
97 | self.triangle.vertices[idx % 3], |
98 | self.triangle.vertices[(idx + 1) % 3], |
99 | self.triangle.vertices[(idx + 2) % 3], |
100 | self.stroke_width, |
101 | self.stroke_offset, |
102 | ); |
103 | let end = LineJoin::from_points( |
104 | self.triangle.vertices[(idx + 1) % 3], |
105 | self.triangle.vertices[(idx + 2) % 3], |
106 | self.triangle.vertices[(idx + 3) % 3], |
107 | self.stroke_width, |
108 | self.stroke_offset, |
109 | ); |
110 | |
111 | idx += 1; |
112 | |
113 | let scanline = ThickSegment::new(start, end).intersection(scanline_y); |
114 | |
115 | if !left.is_empty() { |
116 | if left.try_extend(&scanline) { |
117 | continue; |
118 | } |
119 | } else { |
120 | left = scanline; |
121 | continue; |
122 | } |
123 | |
124 | if !right.is_empty() { |
125 | right.try_extend(&scanline); |
126 | } else { |
127 | right = scanline; |
128 | } |
129 | } |
130 | |
131 | // Merge any overlap between final left/right results |
132 | if left.try_extend(&right) { |
133 | right = Scanline::new_empty(scanline_y); |
134 | } |
135 | |
136 | left.try_take().or_else(|| right.try_take()) |
137 | }) |
138 | } |
139 | |
140 | fn generate_lines(&self, scanline_y: i32) -> Option<LineConfig> { |
141 | let mut edge_intersections = self.edge_intersections(scanline_y); |
142 | |
143 | if self.is_collapsed { |
144 | Some(LineConfig { |
145 | internal: self.triangle.scanline_intersection(scanline_y), |
146 | internal_type: PointType::Stroke, |
147 | first: Scanline::new_empty(0), |
148 | second: Scanline::new_empty(0), |
149 | }) |
150 | } else { |
151 | let first = edge_intersections.next(); |
152 | |
153 | // For scanlines that are parallel with and are inside one edge, this should be None. |
154 | let second = edge_intersections.next(); |
155 | |
156 | // If there are two intersections, this must mean we've traversed across the center of the |
157 | // triangle (assuming the edge line merging logic is correct). In this case, we need a |
158 | // scanline between the two edge intersections. |
159 | let internal = if self.has_fill { |
160 | match (&first, &second) { |
161 | // Triangle stroke is non-zero, so the fill line is between the insides of each |
162 | // stroke. |
163 | (Some(first), Some(second)) => { |
164 | let start_x = first.x.end.min(second.x.end); |
165 | let end_x = first.x.start.max(second.x.start); |
166 | |
167 | Scanline { |
168 | x: start_x..end_x, |
169 | y: scanline_y, |
170 | } |
171 | } |
172 | // Triangles with no stroke intersections and a fill color. |
173 | (None, None) => self.triangle.scanline_intersection(scanline_y), |
174 | // Because a triangle is a closed shape, a single intersection here likely means |
175 | // we're inside one of the borders, so no fill should be returned for this |
176 | // scanline. |
177 | _ => Scanline::new_empty(scanline_y), |
178 | } |
179 | } else { |
180 | Scanline::new_empty(scanline_y) |
181 | }; |
182 | |
183 | Some(LineConfig { |
184 | first: first.unwrap_or(Scanline::new_empty(scanline_y)), |
185 | second: second.unwrap_or(Scanline::new_empty(scanline_y)), |
186 | internal, |
187 | internal_type: PointType::Fill, |
188 | }) |
189 | } |
190 | } |
191 | } |
192 | |
193 | impl Iterator for ScanlineIntersections { |
194 | type Item = (Scanline, PointType); |
195 | |
196 | fn next(&mut self) -> Option<Self::Item> { |
197 | #[allow (clippy::manual_map)] |
198 | if let Some(internal: Scanline) = self.lines.internal.try_take() { |
199 | Some((internal, self.lines.internal_type)) |
200 | } else if let Some(first: Scanline) = self.lines.first.try_take() { |
201 | Some((first, PointType::Stroke)) |
202 | } else if let Some(second: Scanline) = self.lines.second.try_take() { |
203 | Some((second, PointType::Stroke)) |
204 | } else { |
205 | None |
206 | } |
207 | } |
208 | } |
209 | |