Use a much better technique for curve flattening

This commit is contained in:
Patrick Walton 2019-01-28 20:36:06 -08:00
parent a9c1760de5
commit d1ca5fe757
4 changed files with 56 additions and 58 deletions

1
Cargo.lock generated
View File

@ -534,6 +534,7 @@ dependencies = [
"pathfinder_geometry 0.3.0",
"quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]

View File

@ -175,38 +175,27 @@ bitflags! {
pub struct CubicSegment<'s>(&'s Segment);
impl<'s> CubicSegment<'s> {
// See Kaspar Fischer, "Piecewise Linear Approximation of Bézier Curves", 2000.
#[inline]
pub fn is_flat(self, tolerance: f32) -> bool {
let mut uv = F32x4::splat(3.0) * self.0.ctrl.0 -
self.0.baseline.0 - self.0.baseline.0 -
self.0.baseline.reversed().0;
uv = uv * uv;
uv = uv.max(uv.zwxy());
uv[0] + uv[1] <= 16.0 * tolerance * tolerance
}
/*
#[inline]
pub fn flatten_once(self, tolerance: f32) -> Option<Segment> {
let (baseline, ctrl) = (self.0.baseline.0, self.0.ctrl.0);
let from_from = baseline.xyxy();
let v0102 = ctrl - from_from;
// v01.x v01.y v02.x v02.y
// * v01.x v01.y v01.y v01.x
// -------------------------
// v01.x^2 v01.y^2 ad bc
// | | | |
// +-------+ +-----+
// + -
// v01 len^2 determinant
let products = v0102 * v0102.xyyx();
let det = products[2] - products[3];
if det == 0.0 {
return None;
if self.is_flat(tolerance) {
None
} else {
Some(self.split_after(0.5))
}
let s2inv = (products[0] + products[1]).sqrt() / det;
let t = 2.0 * ((tolerance / 3.0) * s2inv.abs()).sqrt();
if t >= 1.0 - EPSILON || t == 0.0 {
return None;
}
return Some(self.split_after(t));
const EPSILON: f32 = 0.005;
}
*/
#[inline]
pub fn split(self, t: f32) -> (Segment, Segment) {

View File

@ -10,6 +10,7 @@ euclid = "0.19"
fixedbitset = "0.1"
hashbrown = "0.1"
rayon = "1.0"
smallvec = "0.6"
[dependencies.pathfinder_geometry]
path = "../geometry"

View File

@ -18,6 +18,7 @@ use pathfinder_geometry::outline::{Contour, Outline, PointIndex};
use pathfinder_geometry::point::Point2DF32;
use pathfinder_geometry::segment::Segment;
use pathfinder_geometry::util;
use smallvec::SmallVec;
use std::cmp::Ordering;
use std::mem;
@ -405,40 +406,46 @@ impl ActiveEdge {
}
}
let mut oriented_segment = segment.orient(winding);
loop {
let rest_segment = match segment
.orient(winding)
.as_cubic_segment()
.flatten_once(FLATTENING_TOLERANCE)
{
None => {
let line_segment = segment.baseline;
self.segment =
match self.process_line_segment(&line_segment, built_object, tile_y) {
Some(ref lower_part) => Segment::line(lower_part),
None => Segment::none(),
};
return;
}
Some(rest_segment) => rest_segment.orient(winding),
};
let mut split_t = 1.0;
let mut before_segment = oriented_segment;
let mut after_segment = None;
debug_assert!(segment.baseline.min_y() <= tile_bottom);
let line_segment = LineSegmentF32::new(
&segment.baseline.upper_point(),
&rest_segment.baseline.upper_point(),
)
.orient(winding);
if self
.process_line_segment(&line_segment, built_object, tile_y)
.is_some()
{
self.segment = rest_segment;
return;
while !before_segment.as_cubic_segment().is_flat(FLATTENING_TOLERANCE) {
let next_t = 0.5 * split_t;
let (before, after) = oriented_segment.as_cubic_segment().split(next_t);
before_segment = before;
after_segment = Some(after);
split_t = next_t;
}
segment = rest_segment;
/*
println!("... tile_y={} winding={} segment={:?} t={} before_segment={:?} after_segment={:?}",
tile_y,
winding,
segment,
split_t,
before_segment,
after_segment);
*/
let line = before_segment.baseline.orient(winding);
match self.process_line_segment(&line, built_object, tile_y) {
Some(ref lower_part) if split_t == 1.0 => {
self.segment = Segment::line(&lower_part);
return;
}
None if split_t == 1.0 => {
self.segment = Segment::none();
return;
}
Some(_) => {
self.segment = after_segment.unwrap().orient(winding);
return;
}
None => oriented_segment = after_segment.unwrap(),
}
}
}