Created
April 6, 2021 13:24
-
-
Save IainS1986/39de43aadf7f32d2118cb8d1e4aaa716 to your computer and use it in GitHub Desktop.
iOS Timing Function lookup to be able to get any value from within a given iOS timing curve (EaseIn, EaseOut, EaseInOut etc). This is itself a port of a port. Original logic found in RSTimingFunction (https://gist.github.com/raphaelschaad/6739676) and the Swift 4.2 port (https://gist.github.com/tcldr/204c4bc87c5239a53239a46728214715)
This file contains 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
using System; | |
using CoreGraphics; | |
using UIKit; | |
// Xamarin port of the swift port of https://gist.github.com/raphaelschaad/6739676 | |
// Swift 4.2 port https://gist.github.com/tcldr/204c4bc87c5239a53239a46728214715 | |
// | |
// Basic Usage: | |
// var timingFunction = new TimingFunction(new UICubicTimingParameters(UIViewAnimationCurve.EaseInOut)); | |
// var progress = timingFunction.Progress(time); // where time is 0->duration, use 0-1 for easier normalised look ups | |
public struct TimingFunction | |
{ | |
private CGPoint _controlPoint1; | |
private CGPoint _controlPoint2; | |
private UnitBezier _unitBezier; | |
private float _epsilon; | |
public TimingFunction(UICubicTimingParameters timingParameters, float duration = 1) | |
{ | |
_controlPoint1 = timingParameters.ControlPoint1; | |
_controlPoint2 = timingParameters.ControlPoint2; | |
_unitBezier = new UnitBezier(_controlPoint1, _controlPoint2); | |
_epsilon = 1.0f / (200.0f * duration); | |
} | |
public TimingFunction(CGPoint controlPoint1, CGPoint controlPoint2, float duration = 1) | |
{ | |
_controlPoint1 = controlPoint1; | |
_controlPoint2 = controlPoint2; | |
_unitBezier = new UnitBezier(_controlPoint1, _controlPoint2); | |
_epsilon = 1.0f / (200.0f * duration); | |
} | |
/// Returns the progress along the timing function for the given time (`fractionComplete`) | |
/// with `0.0` equal to the start of the curve, and `1.0` equal to the end of the curve | |
public float Progress(float fractionComplete) | |
=> _unitBezier.Value(fractionComplete, _epsilon); | |
} | |
public struct UnitBezier | |
{ | |
private float _ax; | |
private float _bx; | |
private float _cx; | |
private float _ay; | |
private float _by; | |
private float _cy; | |
public UnitBezier(CGPoint controlPoint1, CGPoint controlPoint2) | |
{ | |
// Calculate the polynomial coefficients, implicit first | |
// and last control points are (0,0) and (1,1). | |
_cx = (float)(3.0f * controlPoint1.X); | |
_bx = (float)(3.0f * (controlPoint2.X - controlPoint1.X) - _cx); | |
_ax = 1.0f - _cx - _bx; | |
_cy = (float)(3.0f * controlPoint1.Y); | |
_by = (float)(3.0f * (controlPoint2.Y - controlPoint1.Y) - _cy); | |
_ay = 1.0f - _cy - _by; | |
} | |
public float Value(float x, float epsilon) | |
=> SampleCurveY(SolveCurveX(x, epsilon)); | |
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule. | |
private float SampleCurveX(float t) | |
=> ((_ax * t + _bx) * t + _cx) * t; | |
private float SampleCurveY(float t) | |
=> ((_ay * t + _by) * t + _cy) * t; | |
private float SampleCurveDerivativeX(float t) | |
=> (3.0f * _ax * t + 2.0f * _bx) * t + _cx; | |
private float SolveCurveX(float x, float epsilon) | |
{ | |
float t0, t1, t2, x2, d2; | |
// First try a few iterations of Newton's method -- normally very fast. | |
t2 = x; | |
for(int i=0; i<8; i++) | |
{ | |
x2 = SampleCurveX(t2) - x; | |
if (Math.Abs(x2) < epsilon) | |
return t2; | |
d2 = SampleCurveDerivativeX(t2); | |
if (Math.Abs(d2) < 0.00000001f) | |
break; | |
t2 = t2 - x2 / d2; | |
} | |
// Fall back to the bisection method for reliability. | |
t0 = 0.0f; | |
t1 = 1.0f; | |
t2 = x; | |
if (t2 < t0) | |
return t0; | |
if (t2 > t1) | |
return t1; | |
while (t0 < t1) | |
{ | |
x2 = SampleCurveX(t2); | |
if (Math.Abs(x2 - x) < epsilon) | |
return t2; | |
if (x > x2) | |
t0 = t2; | |
else | |
t1 = t2; | |
t2 = t0 + ((t1 - t0) / 2); | |
} | |
// Failure | |
return t2; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment