1//! An iterator over all line intersections with a given scanline.
2
3use crate::{
4 geometry::Point,
5 primitives::common::{LineJoin, Scanline, StrokeOffset, ThickSegment},
6};
7
8/// Scanline intersections iterator.
9///
10/// This iterator returns multiple `Line`s corresponding to the filled in areas of a polyline
11/// defined by the `points` parameter.
12///
13/// The result is one line of a filled polygon.
14#[derive(Clone, Debug)]
15#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
16pub struct ScanlineIntersections<'a> {
17 points: &'a [Point],
18 remaining_points: &'a [Point],
19 next_start_join: Option<LineJoin>,
20 width: u32,
21 scanline: Scanline,
22}
23
24const EMPTY: &[Point; 3] = &[Point::zero(); 3];
25
26impl<'a> ScanlineIntersections<'a> {
27 /// New
28 pub fn new(points: &'a [Point], width: u32, scanline_y: i32) -> Self {
29 let next_start_join = match points {
30 [first, second, ..] => {
31 Some(LineJoin::start(*first, *second, width, StrokeOffset::None))
32 }
33 _ => None,
34 };
35
36 Self {
37 next_start_join,
38 width,
39 points,
40 remaining_points: points,
41 scanline: Scanline::new_empty(scanline_y),
42 }
43 }
44
45 /// Empty scanline iterator.
46 pub(in crate::primitives) const fn empty() -> Self {
47 Self {
48 next_start_join: None,
49 width: 0,
50 points: EMPTY,
51 remaining_points: EMPTY,
52 scanline: Scanline::new_empty(0),
53 }
54 }
55
56 /// Reset scanline iterator with a new scanline.
57 pub(in crate::primitives) fn reset_with_new_scanline(&mut self, scanline_y: i32) {
58 *self = Self::new(self.points, self.width, scanline_y);
59 }
60
61 fn next_segment(&mut self) -> Option<ThickSegment> {
62 let start_join = self.next_start_join?;
63
64 let end_join = match self.remaining_points {
65 [start, mid, end, ..] => {
66 LineJoin::from_points(*start, *mid, *end, self.width, StrokeOffset::None)
67 }
68 [start, end] => LineJoin::end(*start, *end, self.width, StrokeOffset::None),
69 _ => return None,
70 };
71
72 self.remaining_points = self.remaining_points.get(1..)?;
73
74 let segment = ThickSegment::new(start_join, end_join);
75
76 self.next_start_join = Some(end_join);
77
78 Some(segment)
79 }
80}
81
82/// This iterator loops through all scanline intersections for all segments. If two intersections
83/// are adjacent or overlapping, an accumulator line is extended. This repeats until the next
84/// intersection does not touch the current accumulator. At this point, the accumulated line
85/// segment is returned, and is reset to the next segment.
86///
87/// This process reduces the number of draw calls for adjacent scanlines, whilst preventing overdraw
88/// from overlapping scanline segments.
89///
90/// ```text
91/// # Adjacent - merge
92/// A---AB+++B
93/// ⇓
94/// A--------A
95///
96/// # Overlapping - merge
97/// A---B+++A+++B
98/// ⇓
99/// A-----------A
100///
101/// # Separate - leave alone
102/// A---A B---B
103/// ⇓
104/// A---A B---B
105/// ```
106impl<'a> Iterator for ScanlineIntersections<'a> {
107 type Item = Scanline;
108
109 fn next(&mut self) -> Option<Self::Item> {
110 while let Some(segment: ThickSegment) = self.next_segment() {
111 let next_scanline: Scanline = segment.intersection(self.scanline.y);
112
113 if !self.scanline.try_extend(&next_scanline) {
114 let ret: Scanline = self.scanline.clone();
115 self.scanline = next_scanline;
116
117 return Some(ret);
118 }
119 }
120
121 // No more segments - return the final accumulated line.
122 self.scanline.try_take()
123 }
124}
125