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