1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
5 //! Parametric Bézier curves.
7 //! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit.
11 use crate::values::CSSFloat;
13 const NEWTON_METHOD_ITERATIONS: u8 = 8;
15 /// A unit cubic Bézier curve, used for timing functions in CSS transitions and animations.
26 /// Calculate the output of a unit cubic Bézier curve from the two middle control points.
28 /// X coordinate is time, Y coordinate is function advancement.
29 /// The nominal range for both is 0 to 1.
31 /// The start and end points are always (0, 0) and (1, 1) so that a transition or animation
32 /// starts at 0% and ends at 100%.
33 pub fn calculate_bezier_output(
41 // Check for a linear curve.
42 if x1 == y1 && x2 == y2 {
46 // Ensure that we return 0 or 1 on both edges.
54 // For negative values, try to extrapolate with tangent (p1 - p0) or,
55 // if p1 is coincident with p0, with (p2 - p0).
58 return progress * y1 as f64 / x1 as f64;
60 if y1 == 0.0 && x2 > 0.0 {
61 return progress * y2 as f64 / x2 as f64;
63 // If we can't calculate a sensible tangent, don't extrapolate at all.
67 // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
68 // if p2 is coincident with p3, with (p1 - p3).
71 return 1.0 + (progress - 1.0) * (y2 as f64 - 1.0) / (x2 as f64 - 1.0);
73 if y2 == 1.0 && x1 < 1.0 {
74 return 1.0 + (progress - 1.0) * (y1 as f64 - 1.0) / (x1 as f64 - 1.0);
76 // If we can't calculate a sensible tangent, don't extrapolate at all.
80 Bezier::new(x1, y1, x2, y2).solve(progress, epsilon)
84 fn new(x1: CSSFloat, y1: CSSFloat, x2: CSSFloat, y2: CSSFloat) -> Bezier {
85 let cx = 3. * x1 as f64;
86 let bx = 3. * (x2 as f64 - x1 as f64) - cx;
88 let cy = 3. * y1 as f64;
89 let by = 3. * (y2 as f64 - y1 as f64) - cy;
102 fn sample_curve_x(&self, t: f64) -> f64 {
103 // ax * t^3 + bx * t^2 + cx * t
104 ((self.ax * t + self.bx) * t + self.cx) * t
108 fn sample_curve_y(&self, t: f64) -> f64 {
109 ((self.ay * t + self.by) * t + self.cy) * t
113 fn sample_curve_derivative_x(&self, t: f64) -> f64 {
114 (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
118 fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
119 // Fast path: Use Newton's method.
121 for _ in 0..NEWTON_METHOD_ITERATIONS {
122 let x2 = self.sample_curve_x(t);
123 if x2.approx_eq(x, epsilon) {
126 let dx = self.sample_curve_derivative_x(t);
127 if dx.approx_eq(0.0, 1e-6) {
133 // Slow path: Use bisection.
134 let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
144 let x2 = self.sample_curve_x(t);
145 if x2.approx_eq(x, epsilon) {
153 t = (hi - lo) / 2.0 + lo
159 /// Solve the bezier curve for a given `x` and an `epsilon`, that should be
160 /// between zero and one.
162 fn solve(&self, x: f64, epsilon: f64) -> f64 {
163 self.sample_curve_y(self.solve_curve_x(x, epsilon))
168 fn approx_eq(self, value: Self, epsilon: Self) -> bool;
171 impl ApproxEq for f64 {
173 fn approx_eq(self, value: f64, epsilon: f64) -> bool {
174 (self - value).abs() < epsilon