2015-03-31 19:39:56 +03:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
//! Parametric Bézier curves.
|
|
|
|
//!
|
|
|
|
//! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit.
|
|
|
|
|
2017-01-01 02:34:51 +03:00
|
|
|
#![deny(missing_docs)]
|
|
|
|
|
2015-06-19 05:50:22 +03:00
|
|
|
use euclid::point::Point2D;
|
2015-03-31 19:39:56 +03:00
|
|
|
|
|
|
|
const NEWTON_METHOD_ITERATIONS: u8 = 8;
|
|
|
|
|
2017-01-01 02:34:51 +03:00
|
|
|
/// A Bézier curve.
|
2015-03-31 19:39:56 +03:00
|
|
|
pub struct Bezier {
|
2015-08-02 07:01:39 +03:00
|
|
|
ax: f64,
|
|
|
|
bx: f64,
|
|
|
|
cx: f64,
|
|
|
|
ay: f64,
|
|
|
|
by: f64,
|
|
|
|
cy: f64,
|
2015-03-31 19:39:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Bezier {
|
2017-01-01 02:34:51 +03:00
|
|
|
/// Create a Bézier curve from two control points.
|
2015-03-31 19:39:56 +03:00
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
pub fn new(p1: Point2D<f64>, p2: Point2D<f64>) -> Bezier {
|
2015-03-31 19:39:56 +03:00
|
|
|
let cx = 3.0 * p1.x;
|
|
|
|
let bx = 3.0 * (p2.x - p1.x) - cx;
|
|
|
|
|
|
|
|
let cy = 3.0 * p1.y;
|
|
|
|
let by = 3.0 * (p2.y - p1.y) - cy;
|
|
|
|
|
|
|
|
Bezier {
|
|
|
|
ax: 1.0 - cx - bx,
|
|
|
|
bx: bx,
|
|
|
|
cx: cx,
|
|
|
|
ay: 1.0 - cy - by,
|
|
|
|
by: by,
|
|
|
|
cy: cy,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
fn sample_curve_x(&self, t: f64) -> f64 {
|
2015-03-31 19:39:56 +03:00
|
|
|
// ax * t^3 + bx * t^2 + cx * t
|
|
|
|
((self.ax * t + self.bx) * t + self.cx) * t
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
fn sample_curve_y(&self, t: f64) -> f64 {
|
2015-03-31 19:39:56 +03:00
|
|
|
((self.ay * t + self.by) * t + self.cy) * t
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
fn sample_curve_derivative_x(&self, t: f64) -> f64 {
|
2015-03-31 19:39:56 +03:00
|
|
|
(3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
|
2015-03-31 19:39:56 +03:00
|
|
|
// Fast path: Use Newton's method.
|
|
|
|
let mut t = x;
|
2015-04-23 02:24:21 +03:00
|
|
|
for _ in 0..NEWTON_METHOD_ITERATIONS {
|
2015-03-31 19:39:56 +03:00
|
|
|
let x2 = self.sample_curve_x(t);
|
|
|
|
if x2.approx_eq(x, epsilon) {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
let dx = self.sample_curve_derivative_x(t);
|
|
|
|
if dx.approx_eq(0.0, 1e-6) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
t -= (x2 - x) / dx;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Slow path: Use bisection.
|
|
|
|
let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
|
|
|
|
|
|
|
|
if t < lo {
|
|
|
|
return lo
|
|
|
|
}
|
|
|
|
if t > hi {
|
|
|
|
return hi
|
|
|
|
}
|
|
|
|
|
|
|
|
while lo < hi {
|
|
|
|
let x2 = self.sample_curve_x(t);
|
|
|
|
if x2.approx_eq(x, epsilon) {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
if x > x2 {
|
|
|
|
lo = t
|
|
|
|
} else {
|
|
|
|
hi = t
|
|
|
|
}
|
|
|
|
t = (hi - lo) / 2.0 + lo
|
|
|
|
}
|
|
|
|
|
|
|
|
t
|
|
|
|
}
|
|
|
|
|
2017-01-01 02:34:51 +03:00
|
|
|
/// Solve the bezier curve for a given `x` and an `epsilon`, that should be
|
|
|
|
/// between zero and one.
|
2015-03-31 19:39:56 +03:00
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
|
2015-03-31 19:39:56 +03:00
|
|
|
self.sample_curve_y(self.solve_curve_x(x, epsilon))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
trait ApproxEq {
|
|
|
|
fn approx_eq(self, value: Self, epsilon: Self) -> bool;
|
|
|
|
}
|
|
|
|
|
2015-08-02 07:01:39 +03:00
|
|
|
impl ApproxEq for f64 {
|
2015-03-31 19:39:56 +03:00
|
|
|
#[inline]
|
2015-08-02 07:01:39 +03:00
|
|
|
fn approx_eq(self, value: f64, epsilon: f64) -> bool {
|
2015-03-31 19:39:56 +03:00
|
|
|
(self - value).abs() < epsilon
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|