use num::{Float, NumCast};
use super::{Approximation, Curve};
#[derive(Copy, Clone)]
pub struct Bezier<T>
where
T: Float,
{
pub from_value: T,
pub to_value: T,
pub duration: T,
pub control_points: [(T, T); 2],
}
pub struct BezierCoefficients<T>
where
T: Float,
{
pub c: (T, T),
pub b: (T, T),
pub a: (T, T),
}
impl<T> BezierCoefficients<T>
where
T: Float,
{
pub fn new(points: [(T, T); 2]) -> BezierCoefficients<T> {
let one = T::one();
let two = one + one;
let three = one + two;
let alpha = <T as NumCast>::from(0.001).unwrap();
let beta = one - alpha;
let points = [
(
points[0].0.max(alpha).min(beta),
points[0].1.max(alpha).min(beta),
),
(
points[1].0.max(alpha).min(beta),
points[1].1.max(alpha).min(beta),
),
];
let c = (three * points[0].0, three * points[0].1);
let b = (
three * (points[1].0 - points[0].0) - c.0,
three * (points[1].1 - points[0].1) - c.1,
);
let a = (one - c.0 - b.0, one - c.1 - b.1);
BezierCoefficients { c, b, a }
}
pub fn sample_x(&self, t: T) -> T {
((self.a.0 * t + self.b.0) * t + self.c.0) * t
}
pub fn sample_y(&self, t: T) -> T {
((self.a.1 * t + self.b.1) * t + self.c.1) * t
}
pub fn sample_dxdt(&self, t: T) -> T {
let one = T::one();
let two = one + one;
let three = one + two;
(three * self.a.0 * t + two * self.b.0) * t + self.c.0
}
pub fn sample_dydt(&self, t: T) -> T {
let one = T::one();
let two = one + one;
let three = one + two;
(three * self.a.1 * t + two * self.b.1) * t + self.c.1
}
pub fn sample_dydx(&self, t: T) -> T {
let dxdt = self.sample_dxdt(t);
self.sample_dydt(t) / dxdt
}
fn solve_x(&self, x: T, eps: T) -> T {
let two = T::one() + T::one();
let x = x.max(T::zero()).min(T::one());
let mut t2 = x;
for _ in 0..8 {
let x2 = self.sample_x(t2) - x;
if x2.abs() < eps {
return t2;
}
let d2 = self.sample_dxdt(t2);
if d2.abs() < eps {
break;
}
t2 = t2 - x2 / d2;
}
let mut t0 = T::zero();
let mut t1 = T::one();
let mut t2 = x;
while t0 < t1 {
let x2 = self.sample_x(t2);
if (x2 - x).abs() < eps {
return t2;
}
if x > x2 {
t0 = t2;
} else {
t1 = t2;
}
t2 = (t1 - t0) / two + t0;
}
t2
}
}
impl<T> Bezier<T>
where
T: Float,
{
fn coefficients(&self) -> BezierCoefficients<T> {
BezierCoefficients::new(self.control_points)
}
}
impl<T> Curve<T> for Bezier<T>
where
T: Float,
{
fn approximate(&self, time: T) -> Approximation<T> {
let coeffs = self.coefficients();
let x = (time / self.duration).max(T::zero()).min(T::one());
let t = coeffs.solve_x(x, <T as NumCast>::from(0.001).unwrap());
let delta = self.to_value - self.from_value;
Approximation {
time,
value: coeffs.sample_y(t) * delta + self.from_value,
velocity: if x.is_one() {
T::zero()
} else {
coeffs.sample_dydx(t) * delta
},
}
}
fn target(&self) -> T {
self.to_value
}
}
#[cfg(test)]
mod tests {
use super::BezierCoefficients;
#[test]
fn test_2nd_derivative() {
let coeffs = BezierCoefficients::new([(0.0, 0.0), (1.0, 1.0)]);
assert_eq!((coeffs.sample_dydx(0.0f32) * 1000.0).round() / 1000.0, 1.0);
assert_eq!((coeffs.sample_dydx(0.5f32) * 1000.0).round() / 1000.0, 1.0);
}
#[test]
fn test_3rd_derivative() {
let coeffs = BezierCoefficients::new([(0.0, 0.0), (0.0, 1.0)]);
assert!(coeffs.sample_dydx(0.0f32).is_finite());
}
#[test]
fn test_infinite_gradient() {
let coeffs = BezierCoefficients::new([(1.0, 0.0), (0.0, 1.0)]);
assert!(coeffs.sample_dydx(0.5f32).is_finite());
}
#[test]
fn test_zero_gradient() {
let coeffs = BezierCoefficients::new([(0.0, 1.0), (1.0, 0.0)]);
assert_eq!((coeffs.sample_dydx(0.5f32) * 100.0).round() / 100.0, 0.0);
}
}