| 1 | //! An iterator over all line intersections with a given scanline. |
| 2 | |
| 3 | use 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))] |
| 16 | pub 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 | |
| 24 | const EMPTY: &[Point; 3] = &[Point::zero(); 3]; |
| 25 | |
| 26 | impl<'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 | /// ``` |
| 106 | impl<'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 | |