/ src / segments.rs
segments.rs
  1  use std::iter::Peekable;
  2  
  3  use kurbo::BezPath;
  4  
  5  use crate::{
  6      geom::{monotonic_pieces, Point, Segment},
  7      num::CheapOrderedFloat,
  8  };
  9  
 10  /// An index into our segment arena.
 11  ///
 12  /// Throughout this library, we assign identities to segments, so that we may
 13  /// consider segments as different even if they have the same start- and end-points.
 14  ///
 15  /// This index is used to identify a segment, whose data can be retrieved by looking
 16  /// it up in [`Segments`]. (Of course, this index-as-identifier breaks down if there are
 17  /// multiple `Segments` in flight. Just be careful not to mix them up.)
 18  #[cfg_attr(test, derive(serde::Serialize))]
 19  #[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
 20  pub struct SegIdx(pub(crate) usize);
 21  
 22  /// A vector indexed by `SegIdx`.
 23  #[cfg_attr(test, derive(serde::Serialize))]
 24  #[derive(Clone, Hash, PartialEq, Eq)]
 25  #[cfg_attr(test, serde(transparent))]
 26  pub struct SegVec<T> {
 27      inner: Vec<T>,
 28  }
 29  
 30  impl_typed_vec!(SegVec, SegIdx, "s");
 31  
 32  /// An arena of segments, each of which is a cubic Bézier.
 33  ///
 34  /// Segments are indexed by [`SegIdx`] and can be retrieved by indexing (i.e. with square brackets).
 35  #[derive(Clone, Default)]
 36  pub struct Segments {
 37      segs: SegVec<Segment>,
 38      contour_prev: SegVec<Option<SegIdx>>,
 39      contour_next: SegVec<Option<SegIdx>>,
 40      /// For each segment, stores true if the sweep-line order (small y to big y)
 41      /// is the same as the orientation in its original contour.
 42      orientation: SegVec<bool>,
 43  
 44      /// All the entrance heights, of segments, ordered by height.
 45      /// This includes horizontal segments.
 46      enter: Vec<(f64, SegIdx)>,
 47      /// All the exit heights of segments, ordered by height.
 48      /// This does not include horizontal segments.
 49      exit: Vec<(f64, SegIdx)>,
 50  }
 51  
 52  /// Certain operations expect closed paths, and return this as an error when a path is not closed.
 53  #[derive(Clone, Copy, Debug, PartialEq)]
 54  pub struct NonClosedPath {
 55      /// The point at the start of the non-closed path (always generated by a [`kurbo::PathEl::MoveTo`]).
 56      pub start_point: kurbo::Point,
 57      /// The point at the end of the non-closed path.
 58      pub end_point: kurbo::Point,
 59  }
 60  
 61  impl std::fmt::Display for NonClosedPath {
 62      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 63          write!(
 64              f,
 65              "non-closed path starting at {} and ending at {}",
 66              self.start_point, self.end_point,
 67          )
 68      }
 69  }
 70  
 71  impl std::error::Error for NonClosedPath {}
 72  
 73  struct SegmentEntryFormatter<'a> {
 74      idx: SegIdx,
 75      seg: &'a Segment,
 76      prev: Option<SegIdx>,
 77      next: Option<SegIdx>,
 78      oriented: bool,
 79  }
 80  
 81  impl std::fmt::Debug for SegmentEntryFormatter<'_> {
 82      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 83          let seg_idx = self.idx;
 84          let seg = self.seg;
 85          let prefix = if self.oriented {
 86              self.prev.map(|i| format!("{i:?} -> ")).unwrap_or_default()
 87          } else {
 88              self.next.map(|i| format!("{i:?} <- ")).unwrap_or_default()
 89          };
 90          let suffix = if self.oriented {
 91              self.next.map(|i| format!(" -> {i:?}")).unwrap_or_default()
 92          } else {
 93              self.prev.map(|i| format!(" <- {i:?}")).unwrap_or_default()
 94          };
 95          write!(f, "{seg_idx:?}: {prefix}{seg:?}{suffix}")
 96      }
 97  }
 98  
 99  impl std::fmt::Debug for Segments {
100      fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101          let mut list = f.debug_list();
102          for (idx, seg) in self.segs.iter() {
103              list.entry(&SegmentEntryFormatter {
104                  idx,
105                  seg,
106                  prev: self.contour_prev[idx],
107                  next: self.contour_next[idx],
108                  oriented: self.orientation[idx],
109              });
110          }
111          list.finish()
112      }
113  }
114  
115  fn cyclic_pairs<T>(xs: &[T]) -> impl Iterator<Item = (&T, &T)> {
116      pairs(xs).chain(xs.last().zip(xs.first()))
117  }
118  
119  fn pairs<T>(xs: &[T]) -> impl Iterator<Item = (&T, &T)> {
120      xs.windows(2).map(|pair| (&pair[0], &pair[1]))
121  }
122  
123  struct SubpathIter<'a, I: Iterator> {
124      inner: &'a mut Peekable<I>,
125      started: bool,
126  }
127  
128  impl<I> Iterator for SubpathIter<'_, I>
129  where
130      I: Iterator<Item = kurbo::PathEl>,
131  {
132      type Item = kurbo::PathEl;
133  
134      fn next(&mut self) -> Option<Self::Item> {
135          let ret = self.inner.peek()?;
136          if matches!(ret, kurbo::PathEl::MoveTo(_)) && self.started {
137              None
138          } else {
139              self.started = true;
140              self.inner.next()
141          }
142      }
143  }
144  
145  struct Subpaths<I: Iterator> {
146      inner: Peekable<I>,
147  }
148  
149  impl<I> Subpaths<I>
150  where
151      I: Iterator<Item = kurbo::PathEl>,
152  {
153      fn next(&mut self) -> Option<SubpathIter<'_, I>> {
154          if self.inner.peek().is_none() {
155              None
156          } else {
157              Some(SubpathIter {
158                  inner: &mut self.inner,
159                  started: false,
160              })
161          }
162      }
163  }
164  
165  impl Segments {
166      /// The number of segments in this arena.
167      #[allow(clippy::len_without_is_empty)]
168      pub fn len(&self) -> usize {
169          self.segs.len()
170      }
171  
172      /// Iterate over all indices that can be used to index into this arena.
173      pub fn indices(&self) -> impl Iterator<Item = SegIdx> {
174          (0..self.segs.len()).map(SegIdx)
175      }
176  
177      /// Iterate over all segments in this arena.
178      pub fn segments(&self) -> impl Iterator<Item = &Segment> {
179          self.segs.iter().map(|(_, s)| s)
180      }
181  
182      /// Returns the starting point of the segment at `idx`, relative to the segment's original orientation.
183      ///
184      /// The segment itself is stored in sweep-line order (i.e. its starting
185      /// point has the smaller y coordinate) regardless of the original
186      /// orientation of the segment. Use this method to retrieve the segment's
187      /// original start point.
188      pub fn oriented_start(&self, idx: SegIdx) -> Point {
189          if self.orientation[idx] {
190              self[idx].start()
191          } else {
192              self[idx].end()
193          }
194      }
195  
196      /// Returns the ending point of the segment at `idx`, relative to the segment's original orientation.
197      ///
198      /// The segment itself is stored in sweep-line order (i.e. its starting
199      /// point has the smaller y coordinate) regardless of the original
200      /// orientation of the segment. Use this method to retrieve the segment's
201      /// original end point.
202      pub fn oriented_end(&self, idx: SegIdx) -> Point {
203          if self.orientation[idx] {
204              self[idx].end()
205          } else {
206              self[idx].start()
207          }
208      }
209  
210      /// Returns the index of the segment following `idx`.
211      ///
212      /// If `idx` is part of a non-closed path and it is the last segment,
213      /// this returns `None`. If `idx` is part of a closed path, this will
214      /// always return `Some`, and you might need to be careful to avoid looping
215      /// infinitely.
216      pub fn contour_next(&self, idx: SegIdx) -> Option<SegIdx> {
217          self.contour_next[idx]
218      }
219  
220      /// Returns the index of the segment preceding `idx`.
221      ///
222      /// If `idx` is part of a non-closed path and it is the first segment,
223      /// this returns `None`. If `idx` is part of a closed path, this will
224      /// always return `Some`, and you might need to be careful to avoid looping
225      /// infinitely.
226      pub fn contour_prev(&self, idx: SegIdx) -> Option<SegIdx> {
227          self.contour_prev[idx]
228      }
229  
230      /// Does the sweep-line orientation of `idx` agree with its original orientation?
231      pub fn positively_oriented(&self, idx: SegIdx) -> bool {
232          self.orientation[idx]
233      }
234  
235      /// Add a (non-closed) polyline to this arena.
236      pub fn add_points<P: Into<Point>>(&mut self, ps: impl IntoIterator<Item = P>) {
237          let old_len = self.segs.len();
238  
239          let ps: Vec<_> = ps.into_iter().map(|p| p.into()).collect();
240          if ps.len() <= 1 {
241              return;
242          }
243  
244          for (p, q) in pairs(&ps) {
245              let (a, b, orient) = if p < q { (p, q, true) } else { (q, p, false) };
246              self.segs.push(Segment::straight(*a, *b));
247              self.orientation.push(orient);
248              self.contour_prev
249                  .push(Some(SegIdx(self.segs.len().saturating_sub(2))));
250              self.contour_next.push(Some(SegIdx(self.segs.len())));
251          }
252  
253          if old_len < self.segs.len() {
254              self.contour_prev[SegIdx(old_len)] = None;
255              // unwrap: contour_next has the same length as `segs`, which is
256              // non-empty because we checked its length
257              *self.contour_next.inner.last_mut().unwrap() = None;
258          }
259  
260          self.update_enter_exit(old_len);
261      }
262  
263      /// Add a collection of closed polylines to this arena.
264      ///
265      /// This can be much faster than calling `add_cycles` repeatedly.
266      pub fn add_closed_polylines<P: Into<Point>>(
267          &mut self,
268          ps: impl IntoIterator<Item = impl IntoIterator<Item = P>>,
269      ) {
270          let old_len = self.segs.len();
271          for p in ps {
272              self.add_polyline_without_updating_enter_exit(p);
273          }
274          self.update_enter_exit(old_len);
275      }
276  
277      /// Add a closed polyline to this arena.
278      pub fn add_closed_polyline<P: Into<Point>>(&mut self, ps: impl IntoIterator<Item = P>) {
279          let old_len = self.segs.len();
280          self.add_polyline_without_updating_enter_exit(ps);
281          self.update_enter_exit(old_len);
282      }
283  
284      /// Add a Bézier path to this arena.
285      ///
286      /// The path can contain multiple subpaths, and each of them must be closed.
287      pub fn add_bez_path(&mut self, p: &BezPath) -> Result<(), NonClosedPath> {
288          let old_len = self.segs.len();
289          self.add_path_without_updating_enter_exit(p, true)?;
290          self.update_enter_exit(old_len);
291          Ok(())
292      }
293  
294      /// Add a Bézier path to this arena.
295      ///
296      /// The path can contain multiple subpaths, and each of them may or may not be closed.
297      pub fn add_non_closed_bez_path(&mut self, p: &BezPath) -> Result<(), NonClosedPath> {
298          let old_len = self.segs.len();
299          self.add_path_without_updating_enter_exit(p, false)?;
300          self.update_enter_exit(old_len);
301          Ok(())
302      }
303  
304      pub(crate) fn add_path_without_updating_enter_exit(
305          &mut self,
306          p: &BezPath,
307          must_be_closed: bool,
308      ) -> Result<(), NonClosedPath> {
309          let mut subpaths = Subpaths {
310              inner: p.iter().peekable(),
311          };
312          while let Some(subpath) = subpaths.next() {
313              let old_len = self.segs.len();
314              for seg in kurbo::segments(subpath) {
315                  match seg {
316                      kurbo::PathSeg::Line(ell) => {
317                          let p0: Point = ell.p0.into();
318                          let p1: Point = ell.p1.into();
319                          let (p0, p1, orient) = if p0 <= p1 {
320                              (p0, p1, true)
321                          } else {
322                              (p1, p0, false)
323                          };
324                          if p0 != p1 {
325                              self.segs.push(Segment::straight(p0, p1));
326                              self.orientation.push(orient);
327                              self.contour_prev
328                                  .push(Some(SegIdx(self.segs.len().saturating_sub(2))));
329                              self.contour_next.push(Some(SegIdx(self.segs.len())));
330                          }
331                      }
332                      _ => {
333                          let cubic = to_cubic(seg);
334                          let cubics = monotonic_pieces(cubic);
335                          for c in cubics {
336                              let (p0, p1, p2, p3, orient) = if (c.p0.y, c.p0.x) <= (c.p3.y, c.p3.x) {
337                                  (c.p0, c.p1, c.p2, c.p3, true)
338                              } else {
339                                  (c.p3, c.p2, c.p1, c.p0, false)
340                              };
341                              self.segs.push(Segment::monotonic_cubic(
342                                  p0.into(),
343                                  p1.into(),
344                                  p2.into(),
345                                  p3.into(),
346                              ));
347                              self.orientation.push(orient);
348                              self.contour_prev
349                                  .push(Some(SegIdx(self.segs.len().saturating_sub(2))));
350                              self.contour_next.push(Some(SegIdx(self.segs.len())));
351                          }
352                      }
353                  }
354              }
355              if old_len < self.segs.len() {
356                  let start_idx = SegIdx(old_len);
357                  let end_idx = SegIdx(self.segs.len() - 1);
358  
359                  // unwrap: contour_next has the same length as `segs`, which is
360                  // non-empty because we checked its length
361                  let start_point = self.oriented_start(start_idx).to_kurbo();
362                  let end_point = self.oriented_end(end_idx).to_kurbo();
363                  if start_point == end_point {
364                      self.contour_prev[start_idx] = Some(end_idx);
365                      self.contour_next[end_idx] = Some(start_idx);
366                  } else if must_be_closed {
367                      return Err(NonClosedPath {
368                          start_point,
369                          end_point,
370                      });
371                  }
372              }
373          }
374          Ok(())
375      }
376  
377      fn add_polyline_without_updating_enter_exit<P: Into<Point>>(
378          &mut self,
379          ps: impl IntoIterator<Item = P>,
380      ) {
381          let old_len = self.segs.len();
382  
383          let ps: Vec<_> = ps.into_iter().map(|p| p.into()).collect();
384          if ps.len() <= 1 {
385              return;
386          }
387  
388          for (p, q) in cyclic_pairs(&ps) {
389              let (a, b, orient) = if p < q { (p, q, true) } else { (q, p, false) };
390              self.segs.push(Segment::straight(*a, *b));
391              self.orientation.push(orient);
392              self.contour_prev
393                  .push(Some(SegIdx(self.segs.len().saturating_sub(2))));
394              self.contour_next.push(Some(SegIdx(self.segs.len())));
395          }
396  
397          if old_len < self.segs.len() {
398              self.contour_prev[SegIdx(old_len)] = Some(SegIdx(self.segs.len() - 1));
399              // unwrap: contour_next has the same length as `segs`, which is
400              // non-empty because we checked its length
401              *self.contour_next.inner.last_mut().unwrap() = Some(SegIdx(old_len));
402          }
403      }
404  
405      /// Construct a segment arena from a single closed polyline.
406      pub fn from_closed_polyline<P: Into<Point>>(ps: impl IntoIterator<Item = P>) -> Self {
407          let mut ret = Self::default();
408          ret.add_closed_polyline(ps);
409          ret
410      }
411  
412      pub(crate) fn update_enter_exit(&mut self, old_len: usize) {
413          for idx in old_len..self.len() {
414              let seg_idx = SegIdx(idx);
415              let seg = &self.segs[seg_idx];
416  
417              self.enter.push((seg.start().y, seg_idx));
418              if !seg.is_horizontal() {
419                  self.exit.push((seg.end().y, seg_idx));
420              }
421          }
422  
423          // We sort the enter segments by y position, and then by horizontal
424          // start position so that they're fairly likely to get inserted in the
425          // sweep-line in order (which makes the indexing fix-ups faster).
426          self.enter.sort_by(|(y1, seg1), (y2, seg2)| {
427              CheapOrderedFloat::from(*y1)
428                  .cmp(&CheapOrderedFloat::from(*y2))
429                  .then_with(|| {
430                      CheapOrderedFloat::from(self.segs[*seg1].at_y(*y1))
431                          .cmp(&CheapOrderedFloat::from(self.segs[*seg2].at_y(*y1)))
432                  })
433          });
434          self.exit.sort_by(|(y1, _), (y2, _)| {
435              CheapOrderedFloat::from(*y1).cmp(&CheapOrderedFloat::from(*y2))
436          });
437      }
438  
439      /// All the entrance heights of segments, ordered by height.
440      ///
441      /// Includes horizontal segments.
442      pub fn entrances(&self) -> &[(f64, SegIdx)] {
443          &self.enter
444      }
445  
446      /// All the exit heights of segments, ordered by height.
447      ///
448      /// Does not include horizontal segments.
449      pub fn exits(&self) -> &[(f64, SegIdx)] {
450          &self.exit
451      }
452  
453      /// Checks that we satisfy our internal invariants. For testing only.
454      pub fn check_invariants(&self) {
455          for (idx, seg) in self.segs.iter() {
456              assert!(seg.start() <= seg.end());
457              if let Some(next_idx) = self.contour_next(idx) {
458                  assert_eq!(self.oriented_end(idx), self.oriented_start(next_idx));
459                  assert_eq!(self.contour_prev(next_idx), Some(idx));
460              }
461          }
462      }
463  }
464  
465  // kurbo's PathSeg::to_cubic turns lines into degenerate cubics, which are numerically
466  // annoying (e.g. they confuse the monotonicity checker). So we roll our own version,
467  // but eventually we should just handle lines and quadratics without converting them.
468  fn to_cubic(seg: kurbo::PathSeg) -> kurbo::CubicBez {
469      match seg {
470          kurbo::PathSeg::Line(kurbo::Line { p0, p1 }) => kurbo::CubicBez {
471              p0,
472              p1: p0 + (p1 - p0) * (1.0 / 3.0),
473              p2: p0 + (p1 - p0) * (2.0 / 3.0),
474              p3: p1,
475          },
476          kurbo::PathSeg::Quad(_) => seg.to_cubic(),
477          kurbo::PathSeg::Cubic(c) => c,
478      }
479  }
480  
481  impl std::ops::Index<SegIdx> for Segments {
482      type Output = Segment;
483  
484      fn index(&self, index: SegIdx) -> &Self::Output {
485          &self.segs[index]
486      }
487  }