Created
September 24, 2014 03:11
-
-
Save aleclarson/7f86dc16b9897d92cb34 to your computer and use it in GitHub Desktop.
Ease.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
import QuartzCore | |
import Foundation | |
public let EaseNone = Ease(0, 0, 1, 1) | |
public let EaseOutQuad = Ease(0.25, 0.46, 0.45, 0.94) | |
public let EaseInQuad = Ease(0.55, 0.085, 0.68, 0.53) | |
public let EaseInOutQuad = Ease(0.455, 0.03, 0.515, 0.955) | |
@objc public class Ease { | |
public var c1x: CGFloat { | |
didSet { if c1x != oldValue { c1xChanged() } } | |
} | |
public var c1y: CGFloat { | |
didSet { if c1y != oldValue { c1yChanged() } } | |
} | |
public var c2x: CGFloat { | |
didSet { if c2x != oldValue { c2xChanged() } } | |
} | |
public var c2y: CGFloat { | |
didSet { if c2y != oldValue { c2yChanged() } } | |
} | |
public var timingFunction: CAMediaTimingFunction { | |
return CAMediaTimingFunction(controlPoints: Float(c1x), Float(c1y), Float(c2x), Float(c2y)) | |
} | |
public func valueAt (var #x: CGFloat, duration: CGFloat) -> CGFloat { | |
if x > 1 { x = 1 } | |
if x < 0 { x = 0 } | |
return sampleCurveY(solveCurve(x, epsilon(duration))) | |
} | |
public init (_ c1x: CGFloat, _ c1y: CGFloat, _ c2x: CGFloat, _ c2y: CGFloat) { | |
self.c1x = c1x | |
self.c1y = c1y | |
self.c2x = c2x | |
self.c2y = c2y | |
c1xChanged() | |
c1yChanged() | |
} | |
// MARK: Private | |
var a = CGPointZero | |
var b = CGPointZero | |
var c = CGPointZero | |
func c1xChanged () { | |
c.x = 3 * c1x | |
c2xChanged() | |
} | |
func c2xChanged () { | |
b.x = 3 * c2x - c1x - c.x | |
a.x = 1 - c.x - b.x | |
} | |
func c1yChanged () { | |
c.y = 3 * c1y | |
c2yChanged() | |
} | |
func c2yChanged () { | |
b.y = 3 * c2y - c1y - c.y | |
a.y = 1 - c.y - b.y | |
} | |
func epsilon (duration: CGFloat) -> CGFloat { | |
return 1.0 / 200.0 * duration | |
} | |
func sampleCurveX (t: CGFloat) -> CGFloat { | |
return ((a.x * t + b.x) * t + c.x) * t | |
} | |
func sampleCurveY (t: CGFloat) -> CGFloat { | |
return ((a.y * t + b.y) * t + c.y) * t | |
} | |
func sampleCurveDerivativeX (t: CGFloat) -> CGFloat { | |
return (3 * a.x * t + 2 * b.x) * t + c.x | |
} | |
func solveCurve (x: CGFloat, _ epsilon: CGFloat) -> CGFloat { | |
var t0: CGFloat | |
var t1: CGFloat | |
var t2 = x | |
var x2: CGFloat | |
var d2: CGFloat | |
var i: UInt = 0 | |
// First try a few iterations of Newton's method -- normally very fast. | |
for i in 0...8 { | |
x2 = sampleCurveX(t2) - x | |
if CGFloat(fabsf(CFloat(x2))) < epsilon { return t2 } | |
d2 = sampleCurveDerivativeX(t2) | |
if CGFloat(fabsf(CFloat(d2))) < 1e-6 { break } | |
t2 = t2 - x2 / d2 | |
} | |
// Fall back to the halfion method for reliability. | |
t0 = 0 | |
t1 = 1 | |
t2 = x | |
if t2 < t0 { return t0 } | |
if t2 > t1 { return t1 } | |
while t0 < t1 { | |
x2 = sampleCurveX(t2) | |
if abs(x2 - x) < epsilon { return t2 } | |
if x > x2 { t0 = t2 } | |
else { t1 = t2 } | |
t2 = (t1 - t0) * 0.5 + t0 | |
} | |
// Failure. | |
return t2 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment