Recursively approximate cubic Béziers with quadratics to an error bound.

Gets the tiger rendering properly, as far as I can tell.
This commit is contained in:
Patrick Walton 2017-09-29 16:30:17 -07:00
parent 613bc7c29d
commit e78d7ed575
3 changed files with 40 additions and 11 deletions

View File

@ -42,6 +42,8 @@ use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use std::u32;
const CUBIC_ERROR_TOLERANCE: f32 = 0.1;
static STATIC_INDEX_PATH: &'static str = "../client/index.html";
static STATIC_TEXT_DEMO_PATH: &'static str = "../client/text-demo.html";
static STATIC_SVG_DEMO_PATH: &'static str = "../client/svg-demo.html";
@ -528,7 +530,8 @@ fn partition_svg_paths(request: Json<PartitionSvgPathsRequest>)
&control_point_1,
&endpoint_1);
last_point = endpoint_1;
stream.extend(cubic.approximate_curve().map(|curve| curve.to_path_segment()));
stream.extend(cubic.approximate_curve(CUBIC_ERROR_TOLERANCE)
.map(|curve| curve.to_path_segment()));
}
'Z' => stream.push(PathSegment::ClosePath),
_ => return Json(Err(PartitionSvgPathsError::UnknownSvgPathSegmentType)),

View File

@ -14,6 +14,8 @@ use euclid::Point2D;
use curve::Curve;
const MAX_APPROXIMATION_ITERATIONS: u8 = 32;
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct CubicCurve {
pub endpoints: [Point2D<f32>; 2],
@ -51,22 +53,23 @@ impl CubicCurve {
CubicCurve::new(&p0p1p2p3, &p1p2p3, &p2p3, &p3))
}
pub fn approximate_curve(&self) -> ApproximateCurveIter {
ApproximateCurveIter::new(self)
pub fn approximate_curve(&self, error_bound: f32) -> ApproximateCurveIter {
ApproximateCurveIter::new(self, error_bound)
}
}
// FIXME(pcwalton): Do better. See: https://github.com/googlei18n/cu2qu
pub struct ApproximateCurveIter {
curves: [CubicCurve; 2],
curves: Vec<CubicCurve>,
error_bound: f32,
iteration: u8,
}
impl ApproximateCurveIter {
fn new(cubic: &CubicCurve) -> ApproximateCurveIter {
fn new(cubic: &CubicCurve, error_bound: f32) -> ApproximateCurveIter {
let (curve_a, curve_b) = cubic.subdivide(0.5);
ApproximateCurveIter {
curves: [curve_a, curve_b],
curves: vec![curve_b, curve_a],
error_bound: error_bound,
iteration: 0,
}
}
@ -76,11 +79,29 @@ impl Iterator for ApproximateCurveIter {
type Item = Curve;
fn next(&mut self) -> Option<Curve> {
let cubic = match self.curves.get(self.iteration as usize) {
Some(cubic) => *cubic,
let mut cubic = match self.curves.pop() {
Some(cubic) => cubic,
None => return None,
};
self.iteration += 1;
while self.iteration < MAX_APPROXIMATION_ITERATIONS {
self.iteration += 1;
// See Sederberg § 2.6, "Distance Between Two Bézier Curves".
let delta_control_point_0 = (cubic.endpoints[0] - cubic.control_points[0] * 3.0) +
(cubic.control_points[1] * 3.0 - cubic.endpoints[1]);
let delta_control_point_1 = (cubic.control_points[0] * 3.0 - cubic.endpoints[0]) +
(cubic.endpoints[1] - cubic.control_points[1] * 3.0);
let max_error = f32::max(delta_control_point_1.length(),
delta_control_point_0.length()) / 6.0;
if max_error < self.error_bound {
break
}
let (cubic_a, cubic_b) = cubic.subdivide(0.5);
self.curves.push(cubic_b);
cubic = cubic_a
}
let approx_control_point_0 = (cubic.control_points[0] * 3.0 - cubic.endpoints[0]) * 0.5;
let approx_control_point_1 = (cubic.control_points[1] * 3.0 - cubic.endpoints[1]) * 0.5;

View File

@ -17,6 +17,8 @@ use curve::Curve;
use line::Line;
use {lerp, sign};
const MAX_ITERATIONS: u8 = 32;
pub trait Intersect {
fn min_x(&self) -> f32;
fn max_x(&self) -> f32;
@ -31,8 +33,11 @@ pub trait Intersect {
fn intersect<T>(&self, other: &T) -> Option<Point2D<f32>> where T: Intersect {
let mut min_x = f32::max(self.min_x(), other.min_x());
let mut max_x = f32::min(self.max_x(), other.max_x());
let mut iteration = 0;
while iteration < MAX_ITERATIONS && max_x - min_x > f32::approx_epsilon() {
iteration += 1;
while max_x - min_x > f32::approx_epsilon() {
let mid_x = lerp(min_x, max_x, 0.5);
let min_sign = sign(self.solve_y_for_x(min_x) - other.solve_y_for_x(min_x));
let mid_sign = sign(self.solve_y_for_x(mid_x) - other.solve_y_for_x(mid_x));