1 | use crate::{ |
2 | draw_target::DrawTarget, |
3 | geometry::{Point, Size}, |
4 | primitives::{Line, PointsIter, Rectangle}, |
5 | }; |
6 | use core::ops::Range; |
7 | |
8 | /// Scanline. |
9 | #[derive (Debug, Clone, Eq, PartialEq, Hash)] |
10 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
11 | pub struct Scanline { |
12 | pub y: i32, |
13 | pub x: Range<i32>, |
14 | } |
15 | |
16 | impl Scanline { |
17 | /// Creates a new scanline. |
18 | pub const fn new(y: i32, x: Range<i32>) -> Self { |
19 | Self { y, x } |
20 | } |
21 | |
22 | /// Creates a new empty scanline. |
23 | pub const fn new_empty(y: i32) -> Self { |
24 | Self::new(y, 0..0) |
25 | } |
26 | |
27 | /// Returns `true` if the x range of the scanline is empty. |
28 | pub fn is_empty(&self) -> bool { |
29 | self.x.is_empty() |
30 | } |
31 | |
32 | /// Extends the scanline to include the given x coordinate. |
33 | fn extend(&mut self, x: i32) { |
34 | if self.is_empty() { |
35 | self.x = x..x + 1; |
36 | } else if x < self.x.start { |
37 | self.x.start = x; |
38 | } else if x >= self.x.end { |
39 | self.x.end = x + 1; |
40 | } |
41 | } |
42 | |
43 | /// Intersect a horizontal scan line with the Bresenham representation of this line segment. |
44 | /// |
45 | /// Intersection lines produced by this function are sorted so that the start always lies to the |
46 | /// left of the end. |
47 | pub fn bresenham_intersection(&mut self, line: &Line) { |
48 | // Check if the scanline is in the y range of the line. |
49 | let y_range = if line.start.y <= line.end.y { |
50 | line.start.y..=line.end.y |
51 | } else { |
52 | line.end.y..=line.start.y |
53 | }; |
54 | |
55 | if !y_range.contains(&self.y) { |
56 | return; |
57 | } |
58 | |
59 | let y = self.y; |
60 | |
61 | line.points() |
62 | .skip_while(|p| p.y != y) |
63 | .take_while(|p| p.y == y) |
64 | .for_each(|p| { |
65 | self.extend(p.x); |
66 | }); |
67 | } |
68 | |
69 | /// Check for lines that are adjacent or overlapping. |
70 | /// |
71 | /// This assumes that both lines have the same y coordinate. |
72 | fn touches(&self, other: &Scanline) -> bool { |
73 | debug_assert_eq!( |
74 | self.y, other.y, |
75 | "try_extend must be called with scanlines with equal y coordinate" |
76 | ); |
77 | |
78 | if self.is_empty() || other.is_empty() { |
79 | return false; |
80 | } |
81 | |
82 | let range = self.x.start - 1..=self.x.end; |
83 | |
84 | range.contains(&(other.x.start)) || range.contains(&(other.x.end - 1)) || { |
85 | // PERF: If the other conditions short circuit, this won't be computed. |
86 | let range = other.x.start - 1..=other.x.end; |
87 | |
88 | range.contains(&(self.x.start)) || range.contains(&(self.x.end - 1)) |
89 | } |
90 | } |
91 | |
92 | /// Tries to extend this line by another line. |
93 | /// |
94 | /// The line is only extended when the other line overlaps this line or is directly adjacent |
95 | /// to this line. |
96 | /// |
97 | /// Returns `true` if the line was extended and `false` otherwise. |
98 | pub fn try_extend(&mut self, other: &Scanline) -> bool { |
99 | debug_assert_eq!( |
100 | self.y, other.y, |
101 | "try_extend must be called with scanlines with equal y coordinate" |
102 | ); |
103 | |
104 | if self.touches(other) { |
105 | self.x.start = self.x.start.min(other.x.start); |
106 | self.x.end = self.x.end.max(other.x.end); |
107 | |
108 | true |
109 | } else { |
110 | false |
111 | } |
112 | } |
113 | |
114 | /// Converts the scanline into a 1px high rectangle. |
115 | pub fn to_rectangle(&self) -> Rectangle { |
116 | let width = if !self.is_empty() { |
117 | (self.x.end - self.x.start) as u32 |
118 | } else { |
119 | 0 |
120 | }; |
121 | |
122 | Rectangle::new(Point::new(self.x.start, self.y), Size::new(width, 1)) |
123 | } |
124 | |
125 | /// Returns a clone of the scanline if it isn't empty. |
126 | /// |
127 | /// This method is used similar to `Option::take`, but was renamed to `try_take` because |
128 | /// of a naming conflict with `Iterator::take`. |
129 | pub fn try_take(&mut self) -> Option<Self> { |
130 | if !self.is_empty() { |
131 | let ret = self.clone(); |
132 | self.x = 0..0; |
133 | Some(ret) |
134 | } else { |
135 | None |
136 | } |
137 | } |
138 | |
139 | /// Draws the scanline. |
140 | pub fn draw<T>(&self, target: &mut T, color: T::Color) -> Result<(), T::Error> |
141 | where |
142 | T: DrawTarget, |
143 | { |
144 | if self.is_empty() { |
145 | return Ok(()); |
146 | } |
147 | |
148 | let width = (self.x.end - self.x.start) as u32; |
149 | |
150 | target.fill_solid( |
151 | &Rectangle::new(Point::new(self.x.start, self.y), Size::new(width, 1)), |
152 | color, |
153 | ) |
154 | } |
155 | } |
156 | |
157 | impl Iterator for Scanline { |
158 | type Item = Point; |
159 | |
160 | fn next(&mut self) -> Option<Self::Item> { |
161 | self.x.next().map(|x: i32| Point::new(x, self.y)) |
162 | } |
163 | } |
164 | |
165 | #[cfg (test)] |
166 | mod tests { |
167 | use super::*; |
168 | |
169 | fn run_touches_test(s1: i32, e1: i32, s2: i32, e2: i32, expected: bool, ident: &str) { |
170 | let mut l1 = Scanline::new_empty(0); |
171 | l1.extend(s1); |
172 | l1.extend(e1); |
173 | |
174 | let mut l2 = Scanline::new_empty(0); |
175 | l2.extend(s2); |
176 | l2.extend(e2); |
177 | |
178 | assert_eq!(l1.touches(&l2), expected, "{}" , ident); |
179 | } |
180 | |
181 | #[test ] |
182 | fn check_touches() { |
183 | run_touches_test(30, 40, 5, 15, false, "Reversed" ); |
184 | run_touches_test(0, 6, 5, 10, true, "Contained" ); |
185 | run_touches_test(11, 13, 11, 14, true, "Contained 2" ); |
186 | run_touches_test(10, 15, 25, 35, false, "Separated" ); |
187 | run_touches_test(10, 10, 10, 10, true, "Zero size" ); |
188 | run_touches_test(10, 20, 10, 20, true, "Equal" ); |
189 | run_touches_test(10, 20, 20, 10, true, "Equal reversed" ); |
190 | run_touches_test(79, 82, 82, 92, true, "Overlapping lines 1" ); |
191 | run_touches_test(82, 92, 79, 82, true, "Overlapping lines 1, reversed" ); |
192 | run_touches_test(80, 83, 83, 94, true, "Overlapping lines 2" ); |
193 | run_touches_test(83, 94, 80, 83, true, "Overlapping lines 2, reversed" ); |
194 | run_touches_test(83, 94, 94, 100, true, "Adjacent" ); |
195 | } |
196 | |
197 | #[test ] |
198 | fn issue_489_filled_triangle_bug() { |
199 | let mut l1 = Scanline { y: 5, x: 18..20 }; |
200 | let l2 = Scanline { y: 5, x: 11..26 }; |
201 | |
202 | assert_eq!(l1.touches(&l2), true, "l1 touches l2" ); |
203 | |
204 | let result = l1.try_extend(&l2); |
205 | |
206 | assert_eq!(result, true); |
207 | assert_eq!(l1, Scanline { y: 5, x: 11..26 }); |
208 | } |
209 | } |
210 | |