order.rs
1 //! Curve order comparisons, with caching. 2 3 use std::collections::HashMap; 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: HashMap<(SegIdx, SegIdx), 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: HashMap::new(), 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: HashMap::new(), 48 accuracy, 49 tolerance, 50 y_slop: 0.0, 51 } 52 } 53 54 /// Compares two segments, returning their order. 55 /// 56 /// TODO: this API will change to something with less cloning 57 pub fn compare_segments(&mut self, segments: &Segments, i: SegIdx, j: SegIdx) -> CurveOrder { 58 if let Some(order) = self.inner.get(&(i, j)) { 59 return order.clone(); 60 } 61 62 let segi = &segments[i]; 63 let segj = &segments[j]; 64 65 let c0 = segi.to_kurbo_cubic(); 66 let c1 = segj.to_kurbo_cubic(); 67 let forward = 68 curve::intersect_cubics(c0, c1, self.tolerance, self.accuracy).with_y_slop(self.y_slop); 69 let reverse = forward.flip(); 70 self.inner.insert((j, i), reverse); 71 self.inner.entry((i, j)).insert_entry(forward).get().clone() 72 } 73 } 74 75 #[cfg(test)] 76 mod tests { 77 use crate::{curve::Order, SegIdx, Segments}; 78 79 use super::ComparisonCache; 80 81 #[test] 82 fn slop_regression() { 83 let mut segments = Segments::default(); 84 let eps = 0.1; 85 segments.add_points([(-0.5, -0.5), (-0.5, 0.5)]); 86 segments.add_points([(0.0, 0.0), (0.0, 1.0)]); 87 let mut cmp_cache = ComparisonCache::new(eps, eps / 2.0); 88 assert_eq!( 89 cmp_cache 90 .compare_segments(&segments, SegIdx(0), SegIdx(1)) 91 .iter() 92 .next() 93 .unwrap() 94 .2, 95 Order::Left 96 ); 97 } 98 }