Add a fast path for monotonic conversion

This commit is contained in:
Patrick Walton 2019-02-02 11:12:54 -08:00
parent 70e615205e
commit f19757e4cf
4 changed files with 83 additions and 58 deletions

View File

@ -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 {

View File

@ -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<I>
where
I: Iterator<Item = Segment>,

View File

@ -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);

View File

@ -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<f32>, Option<f32>) {
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]);