tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

bezier.rs (4876B)


      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    /// Calculate the output of 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    pub fn calculate_bezier_output(
     34        progress: f64,
     35        epsilon: f64,
     36        x1: f32,
     37        y1: f32,
     38        x2: f32,
     39        y2: f32,
     40    ) -> f64 {
     41        // Check for a linear curve.
     42        if x1 == y1 && x2 == y2 {
     43            return progress;
     44        }
     45 
     46        // Ensure that we return 0 or 1 on both edges.
     47        if progress == 0.0 {
     48            return 0.0;
     49        }
     50        if progress == 1.0 {
     51            return 1.0;
     52        }
     53 
     54        // For negative values, try to extrapolate with tangent (p1 - p0) or,
     55        // if p1 is coincident with p0, with (p2 - p0).
     56        if progress < 0.0 {
     57            if x1 > 0.0 {
     58                return progress * y1 as f64 / x1 as f64;
     59            }
     60            if y1 == 0.0 && x2 > 0.0 {
     61                return progress * y2 as f64 / x2 as f64;
     62            }
     63            // If we can't calculate a sensible tangent, don't extrapolate at all.
     64            return 0.0;
     65        }
     66 
     67        // For values greater than 1, try to extrapolate with tangent (p2 - p3) or,
     68        // if p2 is coincident with p3, with (p1 - p3).
     69        if progress > 1.0 {
     70            if x2 < 1.0 {
     71                return 1.0 + (progress - 1.0) * (y2 as f64 - 1.0) / (x2 as f64 - 1.0);
     72            }
     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);
     75            }
     76            // If we can't calculate a sensible tangent, don't extrapolate at all.
     77            return 1.0;
     78        }
     79 
     80        Bezier::new(x1, y1, x2, y2).solve(progress, epsilon)
     81    }
     82 
     83    #[inline]
     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;
     87 
     88        let cy = 3. * y1 as f64;
     89        let by = 3. * (y2 as f64 - y1 as f64) - cy;
     90 
     91        Bezier {
     92            ax: 1.0 - cx - bx,
     93            bx: bx,
     94            cx: cx,
     95            ay: 1.0 - cy - by,
     96            by: by,
     97            cy: cy,
     98        }
     99    }
    100 
    101    #[inline]
    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
    105    }
    106 
    107    #[inline]
    108    fn sample_curve_y(&self, t: f64) -> f64 {
    109        ((self.ay * t + self.by) * t + self.cy) * t
    110    }
    111 
    112    #[inline]
    113    fn sample_curve_derivative_x(&self, t: f64) -> f64 {
    114        (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
    115    }
    116 
    117    #[inline]
    118    fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
    119        // Fast path: Use Newton's method.
    120        let mut t = x;
    121        for _ in 0..NEWTON_METHOD_ITERATIONS {
    122            let x2 = self.sample_curve_x(t);
    123            if x2.approx_eq(x, epsilon) {
    124                return t;
    125            }
    126            let dx = self.sample_curve_derivative_x(t);
    127            if dx.approx_eq(0.0, 1e-6) {
    128                break;
    129            }
    130            t -= (x2 - x) / dx;
    131        }
    132 
    133        // Slow path: Use bisection.
    134        let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
    135 
    136        if t < lo {
    137            return lo;
    138        }
    139        if t > hi {
    140            return hi;
    141        }
    142 
    143        while lo < hi {
    144            let x2 = self.sample_curve_x(t);
    145            if x2.approx_eq(x, epsilon) {
    146                return t;
    147            }
    148            if x > x2 {
    149                lo = t
    150            } else {
    151                hi = t
    152            }
    153            t = (hi - lo) / 2.0 + lo
    154        }
    155 
    156        t
    157    }
    158 
    159    /// Solve the bezier curve for a given `x` and an `epsilon`, that should be
    160    /// between zero and one.
    161    #[inline]
    162    fn solve(&self, x: f64, epsilon: f64) -> f64 {
    163        self.sample_curve_y(self.solve_curve_x(x, epsilon))
    164    }
    165 }
    166 
    167 trait ApproxEq {
    168    fn approx_eq(self, value: Self, epsilon: Self) -> bool;
    169 }
    170 
    171 impl ApproxEq for f64 {
    172    #[inline]
    173    fn approx_eq(self, value: f64, epsilon: f64) -> bool {
    174        (self - value).abs() < epsilon
    175    }
    176 }