/ src / order.rs
order.rs
  1  //! Curve order comparisons, with caching.
  2  
  3  use rustc_hash::FxHashMap;
  4  
  5  use crate::{
  6      curve::{self, CurveOrder},
  7      SegIdx, Segments,
  8  };
  9  
 10  /// A cache for curve comparisons, so that each pair of curves needs to be compared at most once.
 11  #[derive(Clone, Debug)]
 12  pub struct ComparisonCache {
 13      inner: FxHashMap<(SegIdx, SegIdx), (CurveOrder, CurveOrder)>,
 14      accuracy: f64,
 15      tolerance: f64,
 16      y_slop: f64,
 17  }
 18  
 19  impl ComparisonCache {
 20      /// Creates a new comparison cache.
 21      ///
 22      /// `tolerance` tells us how close two curves can be to be declared "ish", and
 23      /// `accuracy` tells us how closely we need to evaluate the tolerance. For
 24      /// example, if `accuracy` is `tolerance / 2.0` then we'll guarantee (up to
 25      /// some floating-point error) that if the two curves are further than
 26      /// `1.5 * tolerance` apart then we'll give them a strict order, and if they're
 27      /// less than `tolerance / 2.0` apart then we'll give then an "ish" order.
 28      pub fn new(tolerance: f64, accuracy: f64) -> Self {
 29          ComparisonCache {
 30              inner: FxHashMap::default(),
 31              accuracy,
 32              tolerance,
 33              y_slop: tolerance,
 34          }
 35      }
 36  
 37      /// Creates a new comparison cache.
 38      ///
 39      /// `tolerance` tells us how close two curves can be to be declared "ish", and
 40      /// `accuracy` tells us how closely we need to evaluate the tolerance. For
 41      /// example, if `accuracy` is `tolerance / 2.0` then we'll guarantee (up to
 42      /// some floating-point error) that if the two curves are further than
 43      /// `1.5 * tolerance` apart then we'll give them a strict order, and if they're
 44      /// less than `tolerance / 2.0` apart then we'll give then an "ish" order.
 45      pub fn new_without_y_slop(tolerance: f64, accuracy: f64) -> Self {
 46          ComparisonCache {
 47              inner: FxHashMap::default(),
 48              accuracy,
 49              tolerance,
 50              y_slop: 0.0,
 51          }
 52      }
 53  
 54      /// Compares two segments, returning their order.
 55      pub fn compare_segments(
 56          &mut self,
 57          segments: &Segments,
 58          i: SegIdx,
 59          j: SegIdx,
 60      ) -> &mut CurveOrder {
 61          use std::collections::hash_map::Entry;
 62  
 63          let (i, j, flipped) = if i.0 < j.0 {
 64              (i, j, false)
 65          } else {
 66              (j, i, true)
 67          };
 68  
 69          match self.inner.entry((i, j)) {
 70              Entry::Occupied(o) => {
 71                  if flipped {
 72                      &mut o.into_mut().1
 73                  } else {
 74                      &mut o.into_mut().0
 75                  }
 76              }
 77              Entry::Vacant(v) => {
 78                  let segi = &segments[i];
 79                  let segj = &segments[j];
 80  
 81                  let forward =
 82                      if let (Some(l0), Some(l1)) = (segi.as_kurbo_line(), segj.as_kurbo_line()) {
 83                          curve::line::intersect_lines(l0, l1, self.tolerance)
 84                      } else {
 85                          let c0 = segi.to_kurbo_cubic();
 86                          let c1 = segj.to_kurbo_cubic();
 87                          curve::intersect_cubics(c0, c1, self.tolerance, self.accuracy)
 88                              .with_y_slop(self.y_slop)
 89                      };
 90                  debug_assert_eq!(
 91                      forward.iter().last().unwrap().1,
 92                      segi.end().y.min(segj.end().y)
 93                  );
 94                  let reverse = forward.flip();
 95                  let v = v.insert((forward, reverse));
 96                  if flipped {
 97                      &mut v.1
 98                  } else {
 99                      &mut v.0
100                  }
101              }
102          }
103      }
104  }
105  
106  #[cfg(test)]
107  mod tests {
108      use crate::{curve::Order, SegIdx, Segments};
109  
110      use super::ComparisonCache;
111  
112      #[test]
113      fn slop_regression() {
114          let mut segments = Segments::default();
115          let eps = 0.1;
116          segments.add_points([(-0.5, -0.5), (-0.5, 0.5)]);
117          segments.add_points([(0.0, 0.0), (0.0, 1.0)]);
118          let mut cmp_cache = ComparisonCache::new(eps, eps / 2.0);
119          assert_eq!(
120              cmp_cache
121                  .compare_segments(&segments, SegIdx(0), SegIdx(1))
122                  .iter()
123                  .next()
124                  .unwrap()
125                  .2,
126              Order::Left
127          );
128      }
129  }