Last active
December 24, 2018 19:14
-
-
Save matt-curtis/cf7f925d8f48fa73c7e29f3f4deb9096 to your computer and use it in GitHub Desktop.
UnitBezier.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UnitBezier.swift | |
// | |
// 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) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment