Improved Easing Functions

Animation is just moving something over time. The rate at which the something moves is defined by a function called an easing equation or interpolation function. It is these equations which make something move slowly at the start and speed up, or slow down near the end. These equations give animation a more life like feel. The most common set of easing equations come from Robert Penner's book and webpage.

Penner created a very complete set of equations, including some fun ones like 'spring' and 'bounce'. Unfortunately their formulation isn't the best. Here is one of them in JavaScript

easeInCubic:function(x,t,b,c,d){
    returnc*(t/=d)*t*t+b;
},
easeOutCubic:function(x,t,b,c,d){
    returnc*((t=t/d-1)*t*t+1)+b;
},

They really obscure the meaning of the equations, making them hard to understand. I think they were designed this way for efficiency reasons since they were first implemented in Flash ActionScript, where speed would be important on the in-efficient. It makes them much harder to understand and extend, however. Fortunately, we can refactor it to be a lot clearer.

Let's start with the easeInCubic equation. Ignore the x parameter (I'm not sure why it's there since it's not used). t is the current time, starting at zero. d is the duration in time. b and c are the starting and ending values.

easeInCubic:function(x,t,b,c,d){
    returnc*(t/=d)*t*t+b;
},

If we divide t by d before calling this function, then t will always be in the range of 0 to 1, and d can be left out of the function. If we define the returned version of t to also be from 0 to 1, then the actual interpolation of b to c can also be done outside of the function. Here is some code which will call the easing equation then interpolate the results:

var t = t/d;
t = easeInCubic(t);
var val = b + t*(c-b);

We have moved all of the common code outside the actual easing equation. What that leaves is a value t from 0 to 1 which we must transform into a different t. Here's the new easeInCubic function.

function easeInCubic(t) {
    return Math.pow(t,3);
}

That is the essence of the equation. Simply raising t to the power of three. The other formulation might be slightly faster, but it's very confusing, and the speed difference today is largely irrelevant since modern VMs can easily optimize the cleaner form.

Now let's try to transform the second one. An ease out is the same as an ease in except in reverse. If t when from 0 to 1 then the out version will go from 1 - 0. To get this we subtract t from 1 as shown:

function cubicOut(t) {
   return 1-Math.pow(1-t,3);
}

However, this looks awfully close to the easeIn version. Rather than writing a new equation we can factor out the differences. Subtract t from 1 before passing it in, then subtract the result from one after getting the return value. The out form just invokes the in form:

function easeOutCubic(t) {
    return 1 - easeInCubic(1-t);
}

Now we can write other equations in a similarly compact form. easeInQuad and easeOutQuad go from:

easeInQuad:function(x,t,b,c,d){
    returnc*(t/=d)*t+b;
},
easeOutQuad:function(x,t,b,c,d){
    return-c*(t/=d)*(t-2)+b;
},

to

function easeInQuad(t) {  return t*t; }
function easeOutQuad(t) { return 1-easeInQuad(1-t); }

Now let's consider the easeInOutCubic. This one smooths both ends of the equation. In reality it's just scaling the easeIn to the first half of the t, from 0 to 0.5. Then it applies an easeOut to the second half, from 0.5 to 1. Rather than this complex form:

easeInOutQuad:function(x,t,b,c,d){
    if((t/=d/2)<1)returnc/2*t*t+b;
    return-c/2*((--t)*(t-2)-1)+b;
},

We can compose our previous functions to define it like so:

 function cubicInOut(t) {
     if(t < 0.5) return cubicIn(t*2.0)/2.0;
     return 1-cubicIn((1-t)*2)/2;                
 }

Much cleaner.

Here is the original form of elastic out, which gives you a cartoon like bouncing effect:

easeOutElastic:function(x,t,b,c,d){
   var s=1.70158;
   var p=0;
   var a=c;
   if(t==0)return b;
   if((t/=d)==1)return b+c;if(!p)p=d*.3;
   if(a<Math.abs(c)){ a=c; var s=p/4;}
   else var s=p/(2*Math.PI)*Math.asin(c/a);
   return a*Math.pow(2,-10*t)*Math.sin((t*d-s)*(2*Math.PI)/p)+c+b;
},

and here is the reduced form:

function easeOutElastic(t) {
    var p = 0.3;
    return Math.pow(2,-10*t) * Math.sin((t-p/4)*(2*Math.PI)/p) + 1;
}

Moral of the story: "Math is your friend" and "always refactor".

These new equations will be included in a future release of Amino, my cross platform graphics library.

Talk to me about it on Twitter

Posted March 1st, 2013

Tagged: programming rant graphics