I am trying to create a custom animation that has the same animation curve as if I were using a CAMediaTimingFunction
(I am doing the animation in OpenGL, so I am unable to use this). I would also like to stay away from UIKit
in general as I would like it to be as multi-platform as possible.
CAMediaTimingFunction
uses a cubic Bezier function to calculate the animation progress from the time that has elapsed. I have no idea how it does it though.
From what I understand, cubic Bezier functions are defined parametrically and it turns messy when you try to derive a cartesian equation from them.
To be clear: I want a method that takes an x-value input and returns a y-value output along a cubic Bezier curve. The control points would be limited from (0, 0) to (1, 1).
What I was planning on doing is generating a locus of points (say 100) for the bezier curve using the parametric equation in the following function twice for each point (once for x
, once for y
), using 100 values of t
from 0
to 1
static inline CGFloat bezierFunc1D(CGFloat t, CGFloat p0, CGFloat p1, CGFloat p2, CGFloat p3) {
return
(1-t)*(1-t)*(1-t) * p0
+ 3 * (1-t)*(1-t) * t * p1
+ 3 * (1-t) * t*t * p2
+ t*t*t * p3;
}
This is where p0
through to p3
are the control points and t
is the parametric scalar from 0
to 1
. I could optimise this fairly easily by pre-calculating the squared and cubed values of t
.
I was then going to use linear interpolation in order to generate an array of points where the values of x
are linearly seperated (e.g x = 0.01, 0.02... 0.99
) and then use this array to define my animation progress for the time elapsed.
Is this the best way of going about this problem? It seems like a fairly intensive process for quite a simple task, even though it could all be pre-calculated at launch.
I saw on this question the answerer recommended simply eliminating x
and define the function as y
against t
. However, this will give pretty inaccurate results, as a linear Bezier curve from 0.0
to 1.0
will not animate linearly.
Is there a more efficient approach to this?
Does anyone know how Apple achieves this in CAMediaTimingFunction
or is there a library out there that achieves the same result?
Or is there a simpler alternative to using a cubic Bezier function that will give a smooth animation curve on both the in and out stages of the animation?
This is how Apple samples the CSS equivalent of CAMediaTimingFunction
in WebKit. The function of interest is double solve(double x, double epsilon)
, which returns the y
for x ± epsilon
.
/*
* Copyright (C) 2008 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef UnitBezier_h
#define UnitBezier_h
#include <math.h>
namespace WebCore {
struct UnitBezier {
UnitBezier(double p1x, double p1y, double p2x, double p2y)
{
// 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;
}
double sampleCurveX(double t)
{
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((ax * t + bx) * t + cx) * t;
}
double sampleCurveY(double t)
{
return ((ay * t + by) * t + cy) * t;
}
double sampleCurveDerivativeX(double t)
{
return (3.0 * ax * t + 2.0 * bx) * t + cx;
}
// Given an x value, find a parametric value it came from.
double solveCurveX(double x, double epsilon)
{
double t0;
double t1;
double t2;
double x2;
double d2;
int i;
// First try a few iterations of Newton's method -- normally very fast.
for (t2 = x, i = 0; i < 8; i++) {
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) * .5 + t0;
}
// Failure.
return t2;
}
double solve(double x, double epsilon)
{
return sampleCurveY(solveCurveX(x, epsilon));
}
private:
double ax;
double bx;
double cx;
double ay;
double by;
double cy;
};
}
#endif