From e78d7ed57530f25c1e536fc628742b925c500b04 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 29 Sep 2017 16:30:17 -0700 Subject: [PATCH] =?UTF-8?q?Recursively=20approximate=20cubic=20B=C3=A9zier?= =?UTF-8?q?s=20with=20quadratics=20to=20an=20error=20bound.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gets the tiger rendering properly, as far as I can tell. --- demo/server/src/main.rs | 5 ++++- path-utils/src/cubic.rs | 39 ++++++++++++++++++++++++++-------- path-utils/src/intersection.rs | 7 +++++- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/demo/server/src/main.rs b/demo/server/src/main.rs index 9f2d6b9c..f847190e 100644 --- a/demo/server/src/main.rs +++ b/demo/server/src/main.rs @@ -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) &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)), diff --git a/path-utils/src/cubic.rs b/path-utils/src/cubic.rs index 9b2c63c2..59da8b78 100644 --- a/path-utils/src/cubic.rs +++ b/path-utils/src/cubic.rs @@ -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; 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, + 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 { - 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; diff --git a/path-utils/src/intersection.rs b/path-utils/src/intersection.rs index 834d957b..6084588d 100644 --- a/path-utils/src/intersection.rs +++ b/path-utils/src/intersection.rs @@ -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(&self, other: &T) -> Option> 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));