Fix curve/edge intersection logic in clipping

This commit is contained in:
Patrick Walton 2019-01-31 10:12:37 -08:00
parent dd480feb52
commit 4801503dd8
5 changed files with 84 additions and 88 deletions

View File

@ -24,8 +24,10 @@ use sdl2::event::Event;
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
use sdl2::video::GLProfile; use sdl2::video::GLProfile;
use std::f32::consts::FRAC_PI_4; use std::f32::consts::FRAC_PI_4;
use std::time::Instant; use std::panic;
use std::path::PathBuf; use std::path::PathBuf;
use std::process;
use std::time::Instant;
use usvg::{Options as UsvgOptions, Tree}; use usvg::{Options as UsvgOptions, Tree};
#[global_allocator] #[global_allocator]
@ -259,9 +261,20 @@ fn load_scene(options: &Options, window_size: &Size2D<u32>) -> Scene {
fn build_scene(scene: &Scene, options: &Options) -> BuiltScene { fn build_scene(scene: &Scene, options: &Options) -> BuiltScene {
let z_buffer = ZBuffer::new(&scene.view_box); let z_buffer = ZBuffer::new(&scene.view_box);
let built_objects = match options.jobs { let built_objects = panic::catch_unwind(|| {
Some(1) => scene.build_objects_sequentially(&z_buffer), match options.jobs {
_ => scene.build_objects(&z_buffer), Some(1) => scene.build_objects_sequentially(&z_buffer),
_ => scene.build_objects(&z_buffer),
}
});
let built_objects = match built_objects {
Ok(built_objects) => built_objects,
Err(_) => {
eprintln!("Scene building crashed! Dumping scene:");
println!("{:?}", scene);
process::exit(1);
}
}; };
let mut built_scene = BuiltScene::new(&scene.view_box); let mut built_scene = BuiltScene::new(&scene.view_box);

View File

@ -11,8 +11,7 @@
use crate::line_segment::LineSegmentF32; use crate::line_segment::LineSegmentF32;
use crate::outline::{Contour, PointFlags}; use crate::outline::{Contour, PointFlags};
use crate::point::{Point2DF32, Point3DF32}; use crate::point::{Point2DF32, Point3DF32};
use crate::segment::Segment; use crate::segment::{CubicSegment, Segment};
use crate::simd::F32x4;
use crate::util::lerp; use crate::util::lerp;
use arrayvec::ArrayVec; use arrayvec::ArrayVec;
use euclid::Rect; use euclid::Rect;
@ -198,72 +197,37 @@ impl Edge {
-> Option<f32> { -> Option<f32> {
/*println!("... intersect_cubic_segment({:?}, {:?}, t=({}, {}))", /*println!("... intersect_cubic_segment({:?}, {:?}, t=({}, {}))",
self, segment, t_min, t_max);*/ self, segment, t_min, t_max);*/
let cubic_segment = segment.as_cubic_segment(); let mut segment = segment.as_cubic_segment().split_after(t_min);
segment = segment.as_cubic_segment().split_before(t_max / (1.0 - t_min));
if !self.intersects_cubic_segment_hull(segment.as_cubic_segment()) {
return None
}
loop { loop {
let t_mid = lerp(t_min, t_max, 0.5); let t_mid = lerp(t_min, t_max, 0.5);
if t_max - t_min < 0.00001 { if t_max - t_min < 0.00001 {
return Some(t_mid); return Some(t_mid);
} }
let min_sign = self.point_is_inside(&cubic_segment.sample(t_min)); let (prev_segment, next_segment) = segment.as_cubic_segment().split(0.5);
let mid_sign = self.point_is_inside(&cubic_segment.sample(t_mid)); if self.intersects_cubic_segment_hull(prev_segment.as_cubic_segment()) {
let max_sign = self.point_is_inside(&cubic_segment.sample(t_max)); t_max = t_mid;
/*println!("... ... ({}, {}, {}) ({}, {}, {})", segment = prev_segment;
t_min, t_mid, t_max, } else if self.intersects_cubic_segment_hull(next_segment.as_cubic_segment()) {
min_sign, mid_sign, max_sign);*/ t_min = t_mid;
segment = next_segment;
match (min_sign == mid_sign, max_sign == mid_sign) { } else {
(true, false) => t_min = t_mid, return None;
(false, true) => t_max = t_mid,
_ => return None,
} }
} }
} }
fn fixup_clipped_segments(&self, segment: &(Segment, Segment)) -> Option<(Segment, Segment)> { fn intersects_cubic_segment_hull(&self, cubic_segment: CubicSegment) -> bool {
let (mut prev, mut next) = *segment; let inside = self.point_is_inside(&cubic_segment.0.baseline.from());
inside != self.point_is_inside(&cubic_segment.0.ctrl.from()) ||
let point = prev.baseline.to(); inside != self.point_is_inside(&cubic_segment.0.ctrl.to()) ||
inside != self.point_is_inside(&cubic_segment.0.baseline.to())
let line_coords = self.0.line_coords();
let (a, b, c) = (line_coords[0], line_coords[1], line_coords[2]);
let denom = 1.0 / (a * a + b * b);
let factor = b * point.x() - a * point.y();
let snapped = Point2DF32::new(b * factor - a * c, a * -factor - b * c) *
Point2DF32::splat(denom);
prev.baseline.set_to(&snapped);
next.baseline.set_from(&snapped);
// FIXME(pcwalton): Do this more efficiently...
// FIXME(pcwalton): Remove duplication!
if self.0.from_x() == self.0.to_x() {
let x = self.0.from_x();
prev.baseline.set_to_x(x);
next.baseline.set_from_x(x);
}
if self.0.from_y() == self.0.to_y() {
let y = self.0.from_y();
prev.baseline.set_to_y(y);
next.baseline.set_from_y(y);
}
if prev.is_tiny() {
return None
}
/*match *self {
Edge::Left(x) | Edge::Right(x) => {
before.baseline.set_to_x(x);
after.baseline.set_from_x(x);
}
Edge::Top(y) | Edge::Bottom(y) => {
before.baseline.set_to_y(y);
after.baseline.set_from_y(y);
}
}*/
Some((prev, next))
} }
} }

View File

@ -320,9 +320,6 @@ impl Contour {
*point = transform.transform_point(point); *point = transform.transform_point(point);
union_rect(&mut self.bounds, *point, point_index == 0); union_rect(&mut self.bounds, *point, point_index == 0);
} }
// TODO(pcwalton): Skip this step if the transform is rectilinear.
self.make_monotonic();
} }
#[inline] #[inline]
@ -331,8 +328,6 @@ impl Contour {
*point = perspective.transform_point_2d(point); *point = perspective.transform_point_2d(point);
union_rect(&mut self.bounds, *point, point_index == 0); union_rect(&mut self.bounds, *point, point_index == 0);
} }
self.make_monotonic();
} }
#[inline] #[inline]

View File

@ -172,7 +172,7 @@ bitflags! {
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct CubicSegment<'s>(&'s Segment); pub struct CubicSegment<'s>(pub &'s Segment);
impl<'s> CubicSegment<'s> { impl<'s> CubicSegment<'s> {
// See Kaspar Fischer, "Piecewise Linear Approximation of Bézier Curves", 2000. // See Kaspar Fischer, "Piecewise Linear Approximation of Bézier Curves", 2000.
@ -199,41 +199,61 @@ impl<'s> CubicSegment<'s> {
#[inline] #[inline]
pub fn split(self, t: f32) -> (Segment, Segment) { pub fn split(self, t: f32) -> (Segment, Segment) {
let tttt = F32x4::splat(t); let (baseline0, ctrl0, baseline1, ctrl1);
if t <= 0.0 {
let from = &self.0.baseline.from();
baseline0 = LineSegmentF32::new(from, from);
ctrl0 = LineSegmentF32::new(from, from);
baseline1 = self.0.baseline;
ctrl1 = self.0.ctrl;
} else if t >= 1.0 {
let to = &self.0.baseline.to();
baseline0 = self.0.baseline;
ctrl0 = self.0.ctrl;
baseline1 = LineSegmentF32::new(to, to);
ctrl1 = LineSegmentF32::new(to, to);
} else {
let tttt = F32x4::splat(t);
let (p0p3, p1p2) = (self.0.baseline.0, self.0.ctrl.0); let (p0p3, p1p2) = (self.0.baseline.0, self.0.ctrl.0);
let p0p1 = p0p3.combine_axaybxby(p1p2); let p0p1 = p0p3.combine_axaybxby(p1p2);
// p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t) // p01 = lerp(p0, p1, t), p12 = lerp(p1, p2, t), p23 = lerp(p2, p3, t)
let p01p12 = p0p1 + tttt * (p1p2 - p0p1); let p01p12 = p0p1 + tttt * (p1p2 - p0p1);
let pxxp23 = p1p2 + tttt * (p0p3 - p1p2); let pxxp23 = p1p2 + tttt * (p0p3 - p1p2);
let p12p23 = p01p12.combine_azawbzbw(pxxp23); let p12p23 = p01p12.combine_azawbzbw(pxxp23);
// p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t) // p012 = lerp(p01, p12, t), p123 = lerp(p12, p23, t)
let p012p123 = p01p12 + tttt * (p12p23 - p01p12); let p012p123 = p01p12 + tttt * (p12p23 - p01p12);
let p123 = p012p123.zwzw(); let p123 = p012p123.zwzw();
// p0123 = lerp(p012, p123, t) // p0123 = lerp(p012, p123, t)
let p0123 = p012p123 + tttt * (p123 - p012p123); let p0123 = p012p123 + tttt * (p123 - p012p123);
let baseline0 = p0p3.combine_axaybxby(p0123); baseline0 = LineSegmentF32(p0p3.combine_axaybxby(p0123));
let ctrl0 = p01p12.combine_axaybxby(p012p123); ctrl0 = LineSegmentF32(p01p12.combine_axaybxby(p012p123));
let baseline1 = p0123.combine_axaybzbw(p0p3); baseline1 = LineSegmentF32(p0123.combine_axaybzbw(p0p3));
let ctrl1 = p012p123.combine_azawbzbw(p12p23); ctrl1 = LineSegmentF32(p012p123.combine_azawbzbw(p12p23));
}
(Segment { (Segment {
baseline: LineSegmentF32(baseline0), baseline: baseline0,
ctrl: LineSegmentF32(ctrl0), ctrl: ctrl0,
kind: SegmentKind::Cubic, kind: SegmentKind::Cubic,
flags: self.0.flags & SegmentFlags::FIRST_IN_SUBPATH, flags: self.0.flags & SegmentFlags::FIRST_IN_SUBPATH,
}, Segment { }, Segment {
baseline: LineSegmentF32(baseline1), baseline: baseline1,
ctrl: LineSegmentF32(ctrl1), ctrl: ctrl1,
kind: SegmentKind::Cubic, kind: SegmentKind::Cubic,
flags: self.0.flags & SegmentFlags::CLOSES_SUBPATH, flags: self.0.flags & SegmentFlags::CLOSES_SUBPATH,
}) })
} }
#[inline]
pub fn split_before(self, t: f32) -> Segment {
self.split(t).0
}
#[inline] #[inline]
pub fn split_after(self, t: f32) -> Segment { pub fn split_after(self, t: f32) -> Segment {
self.split(t).1 self.split(t).1

View File

@ -118,6 +118,7 @@ impl Scene {
pub fn prepare(&mut self) { pub fn prepare(&mut self) {
for object in &mut self.objects { for object in &mut self.objects {
object.outline.clip_against_rect(&self.view_box);
object.outline.make_monotonic(); object.outline.make_monotonic();
} }
} }
@ -126,6 +127,7 @@ impl Scene {
for object in &mut self.objects { for object in &mut self.objects {
object.outline.transform(transform); object.outline.transform(transform);
object.outline.clip_against_rect(&self.view_box); object.outline.clip_against_rect(&self.view_box);
object.outline.make_monotonic();
} }
self.update_bounds(); self.update_bounds();
@ -146,6 +148,7 @@ impl Scene {
object.outline.clip_against_polygon(&quad); object.outline.clip_against_polygon(&quad);
object.outline.apply_perspective(perspective); object.outline.apply_perspective(perspective);
object.outline.clip_against_rect(&self.view_box); object.outline.clip_against_rect(&self.view_box);
object.outline.make_monotonic();
} }
self.update_bounds(); self.update_bounds();
@ -159,6 +162,7 @@ impl Scene {
object.outline.clip_against_polygon(&quad); object.outline.clip_against_polygon(&quad);
object.outline.apply_perspective(perspective); object.outline.apply_perspective(perspective);
object.outline.clip_against_rect(&view_box); object.outline.clip_against_rect(&view_box);
object.outline.make_monotonic();
}); });
self.update_bounds(); self.update_bounds();