Subdivide cubic Béziers in the legalizer if not monotonically increasing in X.

This commit is contained in:
Patrick Walton 2017-07-10 12:16:58 -07:00
parent 8bcf6de4f5
commit ca01e7eb6e
2 changed files with 127 additions and 10 deletions

View File

@ -2,9 +2,35 @@
use euclid::approxeq::ApproxEq;
use euclid::{Point2D, Vector2D};
use std::cmp::Ordering;
const NEWTON_RAPHSON_ITERATIONS: u8 = 8;
pub(crate) trait ApproxOrdered {
fn approx_ordered(&self) -> bool;
}
impl ApproxOrdered for [f32] {
fn approx_ordered(&self) -> bool {
let mut last_ordering = Ordering::Equal;
for window in self.windows(2) {
let (last_value, this_value) = (window[0], window[1]);
let this_ordering = if last_value - this_value < -f32::approx_epsilon() {
Ordering::Less
} else if last_value - this_value > f32::approx_epsilon() {
Ordering::Greater
} else {
Ordering::Equal
};
if last_ordering != Ordering::Equal && this_ordering != last_ordering {
return false
}
last_ordering = this_ordering
}
true
}
}
// https://stackoverflow.com/a/565282
pub fn line_line_crossing_point(a_p0: &Point2D<f32>,
a_p1: &Point2D<f32>,
@ -67,24 +93,35 @@ fn sample_cubic_bezier(t: f32,
p0p1p2.lerp(p1p2p3, t)
}
fn sample_cubic_bezier_deriv(t: f32,
p0: &Point2D<f32>,
p1: &Point2D<f32>,
p2: &Point2D<f32>,
p3: &Point2D<f32>)
-> Vector2D<f32> {
pub fn sample_cubic_bezier_deriv(t: f32,
p0: &Point2D<f32>,
p1: &Point2D<f32>,
p2: &Point2D<f32>,
p3: &Point2D<f32>)
-> Vector2D<f32> {
// https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
// FIXME(pcwalton): Can this be made faster?
let tt = 1.0 - t;
return (*p1 - *p0) * 3.0 * tt * tt + (*p2 - *p1) * 6.0 * tt * t + (*p3 - *p2) * 3.0 * t * t
}
pub fn sample_cubic_bezier_deriv_deriv(t: f32,
p0: &Point2D<f32>,
p1: &Point2D<f32>,
p2: &Point2D<f32>,
p3: &Point2D<f32>)
-> Vector2D<f32> {
// https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves
// FIXME(pcwalton): Can this be made faster?
(*p2 - *p1 * 2.0 + p0.to_vector()).lerp(*p3 - *p2 * 2.0 + p1.to_vector(), t) * 6.0
}
pub fn solve_line_y_for_x(x: f32, a: &Point2D<f32>, b: &Point2D<f32>) -> f32 {
a.lerp(*b, (x - a.x) / (b.x - a.x)).y
}
fn newton_raphson<F, DFDX>(f: F, dfdx: DFDX, mut x_guess: f32) -> f32
where F: Fn(f32) -> f32, DFDX: Fn(f32) -> f32 {
pub(crate) fn newton_raphson<F, DFDX>(f: F, dfdx: DFDX, mut x_guess: f32) -> f32
where F: Fn(f32) -> f32, DFDX: Fn(f32) -> f32 {
for _ in 0..NEWTON_RAPHSON_ITERATIONS {
let y = f(x_guess);
if y.approx_eq(&0.0) {
@ -115,3 +152,32 @@ pub fn solve_cubic_bezier_y_for_x(x: f32,
-> f32 {
sample_cubic_bezier(solve_cubic_bezier_t_for_x(x, p0, p1, p2, p3), p0, p1, p2, p3).y
}
#[derive(Clone, Copy, Debug)]
pub struct SubdividedCubicBezier {
pub ap0: Point2D<f32>,
pub ap1: Point2D<f32>,
pub ap2: Point2D<f32>,
pub ap3bp0: Point2D<f32>,
pub bp1: Point2D<f32>,
pub bp2: Point2D<f32>,
pub bp3: Point2D<f32>,
}
impl SubdividedCubicBezier {
pub fn new(t: f32, p0: &Point2D<f32>, p1: &Point2D<f32>, p2: &Point2D<f32>, p3: &Point2D<f32>)
-> SubdividedCubicBezier {
let (ap1, p1p2, bp2) = (p0.lerp(*p1, t), p1.lerp(*p2, t), p2.lerp(*p3, t));
let (ap2, bp1) = (ap1.lerp(p1p2, t), (p1p2.lerp(bp2, t)));
let ap3bp0 = ap2.lerp(bp1, t);
SubdividedCubicBezier {
ap0: *p0,
ap1: ap1,
ap2: ap2,
ap3bp0: ap3bp0,
bp1: bp1,
bp2: bp2,
bp3: *p3,
}
}
}

View File

@ -1,9 +1,12 @@
// partitionfinder/legalizer.rs
use euclid::Point2D;
use geometry::{self, ApproxOrdered, SubdividedCubicBezier};
use std::u32;
use {ControlPoints, Endpoint, Subpath};
const MAX_SUBDIVISIONS: u8 = 16;
pub struct Legalizer {
endpoints: Vec<Endpoint>,
control_points: Vec<ControlPoints>,
@ -73,8 +76,56 @@ impl Legalizer {
point1: &Point2D<f32>,
point2: &Point2D<f32>,
endpoint: &Point2D<f32>) {
// TODO(pcwalton): Make sure curve points are monotonically increasing in X. de Casteljau
// subdivide if not.
self.bezier_curve_to_subdividing_if_necessary(point1, point2, endpoint, 0)
}
fn bezier_curve_to_subdividing_if_necessary(&mut self,
point1: &Point2D<f32>,
point2: &Point2D<f32>,
endpoint: &Point2D<f32>,
iteration: u8) {
let last_endpoint_index = self.subpaths
.last()
.expect("`bezier_curve_to()` called with no current_subpath")
.last_endpoint_index;
let point0 = self.endpoints[last_endpoint_index as usize].position;
if iteration >= MAX_SUBDIVISIONS ||
[point0.x, point1.x, point2.x, endpoint.x].approx_ordered() {
return self.monotone_bezier_curve_to(point1, point2, endpoint)
}
let t = geometry::newton_raphson(|t| {
geometry::sample_cubic_bezier_deriv(t,
&point0,
point1,
point2,
endpoint).x
},
|t| {
geometry::sample_cubic_bezier_deriv_deriv(t,
&point0,
point1,
point2,
endpoint).x
},
0.5);
let subdivision = SubdividedCubicBezier::new(t, &point0, point1, point2, endpoint);
self.bezier_curve_to_subdividing_if_necessary(&subdivision.ap1,
&subdivision.ap2,
&subdivision.ap3bp0,
iteration + 1);
self.bezier_curve_to_subdividing_if_necessary(&subdivision.bp1,
&subdivision.bp2,
&subdivision.bp3,
iteration + 1);
}
fn monotone_bezier_curve_to(&mut self,
point1: &Point2D<f32>,
point2: &Point2D<f32>,
endpoint: &Point2D<f32>) {
self.subpaths
.last_mut()
.expect("`bezier_curve_to()` called with no current subpath")