diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index 45e1acac..3f55b2ac 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -287,62 +287,67 @@ trait ContourClipper where Self::Edge: TEdge { } let input = self.contour_mut().take(); - for mut segment in input.iter() { - // Easy cases. - match edge.trivially_test_segment(&segment) { - EdgeRelativeLocation::Outside => continue, - EdgeRelativeLocation::Inside => { - //println!("trivial test inside, pushing segment"); - push_segment(self.contour_mut(), &segment); - continue; - } - EdgeRelativeLocation::Intersecting => {} + for segment in input.iter() { + self.clip_segment_against(segment, &edge); + } + } + + fn clip_segment_against(&mut self, mut segment: Segment, edge: &Self::Edge) { + // Easy cases. + match edge.trivially_test_segment(&segment) { + EdgeRelativeLocation::Outside => return, + EdgeRelativeLocation::Inside => { + //println!("trivial test inside, pushing segment"); + self.push_segment(&segment); + return; } + EdgeRelativeLocation::Intersecting => {} + } - // We have a potential intersection. - //println!("potential intersection: {:?} edge: {:?}", segment, edge); - let mut starts_inside = edge.point_is_inside(&segment.baseline.from()); - let intersection_ts = edge.intersect_segment(&segment); - let mut last_t = 0.0; - //println!("... intersections: {:?}", intersection_ts); - for t in intersection_ts { - let (before_split, after_split) = segment.split((t - last_t) / (1.0 - last_t)); + // We have a potential intersection. + //println!("potential intersection: {:?} edge: {:?}", segment, edge); + let mut starts_inside = edge.point_is_inside(&segment.baseline.from()); + let intersection_ts = edge.intersect_segment(&segment); + let mut last_t = 0.0; + //println!("... intersections: {:?}", intersection_ts); + for t in intersection_ts { + let (before_split, after_split) = segment.split((t - last_t) / (1.0 - last_t)); - // Push the split segment if appropriate. - /*println!("... ... edge={:?} before_split={:?} t={:?} starts_inside={:?}", - edge.0, - before_split, - t, - starts_inside);*/ - if starts_inside { - //println!("... split segment case, pushing segment"); - push_segment(self.contour_mut(), &before_split); - } - - // We've now transitioned from inside to outside or vice versa. - starts_inside = !starts_inside; - last_t = t; - segment = after_split; - } - - // No more intersections. Push the last segment if applicable. + // Push the split segment if appropriate. + /*println!("... ... edge={:?} before_split={:?} t={:?} starts_inside={:?}", + edge.0, + before_split, + t, + starts_inside);*/ if starts_inside { - //println!("... last segment case, pushing segment"); - push_segment(self.contour_mut(), &segment); + //println!("... split segment case, pushing segment"); + self.push_segment(&before_split); + } + + // We've now transitioned from inside to outside or vice versa. + starts_inside = !starts_inside; + last_t = t; + segment = after_split; + } + + // No more intersections. Push the last segment if applicable. + if starts_inside { + //println!("... last segment case, pushing segment"); + self.push_segment(&segment); + } + } + + fn push_segment(&mut self, segment: &Segment) { + //println!("... push_segment({:?}, edge={:?}", segment, edge); + let contour = self.contour_mut(); + if let Some(last_position) = contour.last_position() { + if last_position != segment.baseline.from() { + // Add a line to join up segments. + contour.push_point(segment.baseline.from(), PointFlags::empty()); } } - fn push_segment(contour: &mut Contour, segment: &Segment) { - //println!("... push_segment({:?}, edge={:?}", segment, edge); - if let Some(last_position) = contour.last_position() { - if last_position != segment.baseline.from() { - // Add a line to join up segments. - contour.push_point(segment.baseline.from(), PointFlags::empty()); - } - } - - contour.push_segment(*segment); - } + contour.push_segment(*segment); } fn check_for_fast_clip(&mut self, edge: &Self::Edge) -> FastClipResult { diff --git a/geometry/src/monotonic.rs b/geometry/src/monotonic.rs index 67aa35ec..6cb0c04d 100644 --- a/geometry/src/monotonic.rs +++ b/geometry/src/monotonic.rs @@ -8,12 +8,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Converts paths to monotonically increasing/decreasing segments. +//! Converts paths to monotonically increasing/decreasing segments in Y. use crate::segment::{Segment, SegmentKind}; use arrayvec::ArrayVec; -// TODO(pcwalton): I think we only need to be monotonic in Y, maybe? pub struct MonotonicConversionIter where I: Iterator, diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index c554b67a..b11108d7 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -356,7 +356,12 @@ impl Contour { #[inline] pub fn make_monotonic(&mut self) { - // TODO(pcwalton): Make monotonic in place? + // Fast path. + if self.iter().all(|segment| segment.is_monotonic()) { + return; + } + + // Slow path. let contour = self.take(); for segment in MonotonicConversionIter::new(contour.iter()) { self.push_segment(segment); diff --git a/geometry/src/segment.rs b/geometry/src/segment.rs index 597eb47c..77fb431f 100644 --- a/geometry/src/segment.rs +++ b/geometry/src/segment.rs @@ -112,6 +112,16 @@ impl Segment { new_segment } + #[inline] + pub fn is_monotonic(&self) -> bool { + // FIXME(pcwalton): Don't degree elevate! + match self.kind { + SegmentKind::None | SegmentKind::Line => true, + SegmentKind::Quadratic => self.to_cubic().as_cubic_segment().is_monotonic(), + SegmentKind::Cubic => self.as_cubic_segment().is_monotonic(), + } + } + #[inline] pub fn reversed(&self) -> Segment { Segment { @@ -265,19 +275,25 @@ impl<'s> CubicSegment<'s> { self.split(t).0.baseline.to() } + #[inline] + pub fn is_monotonic(self) -> bool { + // TODO(pcwalton): Optimize this. + let (p0, p3) = (self.0.baseline.from_y(), self.0.baseline.to_y()); + let (p1, p2) = (self.0.ctrl.from_y(), self.0.ctrl.to_y()); + (p0 <= p1 && p1 <= p2 && p2 <= p3) || (p0 >= p1 && p1 >= p2 && p2 >= p3) + } + #[inline] pub fn y_extrema(self) -> (Option, Option) { + if self.is_monotonic() { + return (None, None) + } + let p0p1p2p3 = F32x4::new(self.0.baseline.from_y(), self.0.ctrl.from_y(), self.0.ctrl.to_y(), self.0.baseline.to_y()); - // TODO(pcwalton): Optimize this. - if p0p1p2p3[0] <= p0p1p2p3[1] && p0p1p2p3[0] <= p0p1p2p3[2] && - p0p1p2p3[1] <= p0p1p2p3[3] && p0p1p2p3[2] <= p0p1p2p3[3] { - return (None, None); - } - let pxp0p1p2 = p0p1p2p3.wxyz(); let pxv0v1v2 = p0p1p2p3 - pxp0p1p2; let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]);