Skip to content

Instantly share code, notes, and snippets.

@matt-curtis
Last active December 24, 2018 19:14
Show Gist options
  • Select an option

  • Save matt-curtis/cf7f925d8f48fa73c7e29f3f4deb9096 to your computer and use it in GitHub Desktop.

Select an option

Save matt-curtis/cf7f925d8f48fa73c7e29f3f4deb9096 to your computer and use it in GitHub Desktop.

Revisions

  1. matt-curtis revised this gist Dec 24, 2018. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion UnitBezier.swift
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,5 @@
    //
    // UnitBezier.swift
    // EpubRenderKit
    //
    // Created by Matt Curtis on 12/24/18.
    // Copyright © 2018 Matt Curtis. All rights reserved.
  2. matt-curtis revised this gist Dec 24, 2018. No changes.
  3. matt-curtis revised this gist Dec 24, 2018. No changes.
  4. matt-curtis created this gist Dec 24, 2018.
    124 changes: 124 additions & 0 deletions UnitBezier.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,124 @@
    //
    // UnitBezier.swift
    // EpubRenderKit
    //
    // Created by Matt Curtis on 12/24/18.
    // Copyright © 2018 Matt Curtis. All rights reserved.
    //

    import Foundation

    // Almost exact translation of WebKit's UnitBezier struct:
    // https://github.com/WebKit/webkit/blob/89c28d471fae35f1788a0f857067896a10af8974/Source/WebCore/platform/graphics/UnitBezier.h

    struct UnitBezier {

    // MARK: - Properties

    let ax: Double
    let bx: Double
    let cx: Double

    let ay: Double
    let by: Double
    let cy: Double


    // MARK: - Init

    init(_ p1x: Double, _ p1y: Double, _ p2x: Double, _ p2y: Double) {
    // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).

    cx = 3.0 * p1x
    bx = 3.0 * (p2x - p1x) - cx
    ax = 1.0 - cx - bx

    cy = 3.0 * p1y
    by = 3.0 * (p2y - p1y) - cy
    ay = 1.0 - cy - by
    }


    func sampleCurveX(_ t: Double) -> Double {
    // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.

    return ((ax * t + bx) * t + cx) * t;
    }

    func sampleCurveY(_ t: Double) -> Double {
    return ((ay * t + by) * t + cy) * t
    }

    func sampleCurveDerivativeX(_ t: Double) -> Double {
    return (3.0 * ax * t + 2.0 * bx) * t + cx
    }

    // Given an x value, find a parametric value it came from.

    func solveCurveX(_ x: Double, _ epsilon: Double) -> Double {
    var t0: Double = 0
    var t1: Double = 0
    var t2: Double = x
    var x2: Double = 0
    var d2: Double = 0

    // First try a few iterations of Newton's method -- normally very fast.

    for _ in 0..<8 {
    x2 = sampleCurveX(t2) - x

    if fabs(x2) < epsilon { return t2 }

    d2 = sampleCurveDerivativeX(t2)

    if fabs(d2) < 1e-6 { break }

    t2 = t2 - x2 / d2;
    }

    // Fall back to the bisection method for reliability.

    t0 = 0.0
    t1 = 1.0
    t2 = x

    if t2 < t0 { return t0 }
    if (t2 > t1) { return t1 }

    while t0 < t1 {
    x2 = sampleCurveX(t2)

    if fabs(x2 - x) < epsilon { return t2 }

    if x > x2 {
    t0 = t2
    } else {
    t1 = t2
    }

    t2 = (t1 - t0) * 0.5 + t0
    }

    // Failure.

    return t2
    }


    func progress(atPercent percent: Double, epsilon: Double) -> Double {
    return sampleCurveY(solveCurveX(percent, epsilon))
    }

    func progress(atPercent percent: Double, ofDuration duration: Double) -> Double {
    // Approach borrowed from WebKit's epsilon calculation, which is based on duration:
    // https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/animation/TimingFunction.cpp#L77

    // The epsilon value we pass to UnitBezier::solve given that the animation is going to run over |dur| seconds. The longer the
    // animation, the more precision we need in the timing function result to avoid ugly discontinuities.

    let epsilon = 1.0 / (1000.0 * duration)

    return progress(atPercent: percent, epsilon: epsilon)
    }

    }