Source code

Revision control

Other Tools

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/. */
4
5
//! Parametric Bézier curves.
6
//!
7
//! This is based on `WebCore/platform/graphics/UnitBezier.h` in WebKit.
8
9
#![deny(missing_docs)]
10
11
use crate::values::CSSFloat;
12
13
const NEWTON_METHOD_ITERATIONS: u8 = 8;
14
15
/// A unit cubic Bézier curve, used for timing functions in CSS transitions and animations.
16
pub struct Bezier {
17
ax: f64,
18
bx: f64,
19
cx: f64,
20
ay: f64,
21
by: f64,
22
cy: f64,
23
}
24
25
impl Bezier {
26
/// Create a unit cubic Bézier curve from the two middle control points.
27
///
28
/// X coordinate is time, Y coordinate is function advancement.
29
/// The nominal range for both is 0 to 1.
30
///
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
#[inline]
34
pub fn new(x1: CSSFloat, y1: CSSFloat, x2: CSSFloat, y2: CSSFloat) -> Bezier {
35
let cx = 3. * x1 as f64;
36
let bx = 3. * (x2 as f64 - x1 as f64) - cx;
37
38
let cy = 3. * y1 as f64;
39
let by = 3. * (y2 as f64 - y1 as f64) - cy;
40
41
Bezier {
42
ax: 1.0 - cx - bx,
43
bx: bx,
44
cx: cx,
45
ay: 1.0 - cy - by,
46
by: by,
47
cy: cy,
48
}
49
}
50
51
#[inline]
52
fn sample_curve_x(&self, t: f64) -> f64 {
53
// ax * t^3 + bx * t^2 + cx * t
54
((self.ax * t + self.bx) * t + self.cx) * t
55
}
56
57
#[inline]
58
fn sample_curve_y(&self, t: f64) -> f64 {
59
((self.ay * t + self.by) * t + self.cy) * t
60
}
61
62
#[inline]
63
fn sample_curve_derivative_x(&self, t: f64) -> f64 {
64
(3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
65
}
66
67
#[inline]
68
fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
69
// Fast path: Use Newton's method.
70
let mut t = x;
71
for _ in 0..NEWTON_METHOD_ITERATIONS {
72
let x2 = self.sample_curve_x(t);
73
if x2.approx_eq(x, epsilon) {
74
return t;
75
}
76
let dx = self.sample_curve_derivative_x(t);
77
if dx.approx_eq(0.0, 1e-6) {
78
break;
79
}
80
t -= (x2 - x) / dx;
81
}
82
83
// Slow path: Use bisection.
84
let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
85
86
if t < lo {
87
return lo;
88
}
89
if t > hi {
90
return hi;
91
}
92
93
while lo < hi {
94
let x2 = self.sample_curve_x(t);
95
if x2.approx_eq(x, epsilon) {
96
return t;
97
}
98
if x > x2 {
99
lo = t
100
} else {
101
hi = t
102
}
103
t = (hi - lo) / 2.0 + lo
104
}
105
106
t
107
}
108
109
/// Solve the bezier curve for a given `x` and an `epsilon`, that should be
110
/// between zero and one.
111
#[inline]
112
pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
113
self.sample_curve_y(self.solve_curve_x(x, epsilon))
114
}
115
}
116
117
trait ApproxEq {
118
fn approx_eq(self, value: Self, epsilon: Self) -> bool;
119
}
120
121
impl ApproxEq for f64 {
122
#[inline]
123
fn approx_eq(self, value: f64, epsilon: f64) -> bool {
124
(self - value).abs() < epsilon
125
}
126
}