This is a series of easing functions that can be used for tweening. Unlike most easing functions used everywhere else, none of these are defined piecewise, they are all one single function. And except for bounce, I believe they are all infinitely derivable in [0, 1], i.e.
smooth.
They are all intended for use with a lerp function. I posted a formulation that, as I later discovered, still has stability problems, so here's a better one:
Code: Select all
function lerp(from, to, t)
return t < 0.5 and from + (to-from)*t or to + (from-to)*(1-t)
end
And they are used like this. You're assumed to have a starting value and an ending value (from and to) and you want to generate a smooth transition between both with a certain profile. You then iterate a number 't' from 0 to 1 in regular time increments, and pass that number through the easing function. Then use it to do a lerp. Like this:
Code: Select all
current_value = lerp(from, to, ease_function(t))
Some tweening libraries come with a way to plug in your own easing function. If your tweening library doesn't support it, you can always do it manually by choosing 0 and 1 as source and target value and linear easing, so that you can use the result as above.
Now for the easing functions themselves. There's a whole family which I call the "sigmoid" because they resemble one. They are functions that meet these criteria:
- They are defined in the interval [0, 1].
- The value at 0 is 0, and the value at 1 is 1. (That happens for all functions presented here).
- They have rotational symmetry around the point (1/2, 1/2), that is, f(x) = 1-f(1-x).
- The slope at 0 is 0, the slope at 1 is 0, and the slope everywhere else is >= 0 (in theory strictly > 0).
- The second derivative is >= 0 in [0, 1/2], exactly 0 in 1/2, and <= 0 in [1/2, 1]. This means that it curls up from the left to the centre, and down from the centre to the right. As a consequence, the slope at the centre is maximum.
One obvious choice for a sigmoid-like function is a segment of the sine function, which already has that "S" shape we're looking for. Here's an easing function based on it:
Code: Select all
-- Slope at centre: 1.5708
function ease_sigmoid_sinusoidal(t)
return (1-math.cos(t*math.pi))/2
end
An exponential sigmoid. It's a bit "lazy" at the extremes (slow to take off and land).
Code: Select all
-- Slope at centre: 2
function ease_sigmoid_exp(t)
local partial = math.exp((-1)/t)
return partial/(math.exp((-1)/(1 - t)) + partial)
end
There's a whole class of sigmoids which is based on polynomials. I've done a bit of research looking for ways of generating them, and found three methods. The most well-known sigmoid (3t²-2t³) can be generated by two of them:
Code: Select all
-- Slope at centre: 1.5
function ease_sigmoid(t)
return t*t*(3-t*2)
end
If that slope is too sharp (goes too fast on the middle and too slow on the ends), here's a general way to generate any gentler slope (it also reveals one of the three methods mentioned):
Code: Select all
function ease_sigmoid_param(t, param)
param = param or 6
return lerp(t^param, (1-(1-t)^param), t)
end
When param is either 2 or 3, it generates the previous sigmoid (3t²-2t³). Greater values generate gentler slopes. A fractional number between 2 and 3 generates a slightly steeper slope, though using fractional numbers doesn't technically produce a polynomial.
The second generation method yields polynomials with a steeper slope, and involves using an algebra program, which Lua isn't, so I have no general function to generate them. I'll add here some of the sigmoids I've found in increasing order of slope:
Code: Select all
function ease_sigmoid_fast(t)
-- Slope at the middle: 1.875
return t^3*(10+t*(-15+t*6))
end
This happens to be the function used by Ken Perlin in his
improved Perlin noise article. He may have generated it the same way as I did, not sure.
Higher degree leads to steeper slopes, but the calculations get heavier and heavier:
Code: Select all
-- Slope at the middle: 2.1875
function ease_sigmoid_faster(t)
return t^4*(35+t*(-84+t*(70+t*-20)))
end
-- Slope at the middle: m=3.142
function ease_sigmoid_fastest(t)
return t^8*(6435+t*(-40040+t*(108108+t*(-163800+t*(150150+t*(-83160+t*(25740+t
*-3432)))))))
end
Note the latter is quite heavy on calculations (degree 15, with all terms up to degree 8 present).
The third generation method consists of chaining several sigmoids, and yields a slope which is the slope of the original one squared, e.g.
Code: Select all
-- Slope at centre: 2.25 (=1.5^2)
function ease_sigmoid_double(t)
return ease_sigmoid(ease_sigmoid(t))
end
The well-known and widely used "in-out-cubic", defined piecewise, is not "smooth". Its second derivative at the middle is not zero, therefore it's not a true sigmoid as defined here. Its slope at the middle is 3, which is why I've posted one with a slope >3. Note that despite the difference in slope, a better substitutive for it is ease_sigmoid_faster.
Enough sigmoids. Now for the other easing functions. Exponential:
Code: Select all
-- Exponentially reach target
function ease_exp(t)
return (1-math.exp(-8*t))/0.9996645373720975
end
The magic numbers are to select the speed and for making the result be exactly 1 when the input is 1. This function is approximately the explicit formula for the iterative one that Sulunia posted upthread. Being explicit has the advantage that it can use dt to adjust to changes in frame rate.
Bounce. This one is the non-derivable one. The first, commented out formula works, but does not start too well. Unfortunately it's not adjustable.
Code: Select all
function ease_bounce(t)
-- This one starts quite badly.
-- return (1-math.abs(math.cos(math.pi/(1-t)))*(1-t)^2)
-- Adjust the window to grab a better range. Plenty of magic numbers here,
-- sorry. These were obtained by doing the math for the adjustment.
t = (1-t*0.999999999)*0.5271666475893665
return 1-math.abs(math.cos(math.pi/t))*t^2*3.79559296602621
end
The rest of functions all go out of the 0-1 range deliberately (but are still 0 for an input of 0 and 1 for an input of 1).
Outback. This one is fairly well known. It overshoots the target then goes back and stops smoothly. The amount of overshoot can be adjusted using a parameter.
Code: Select all
function ease_outback(t, param)
t = 1-t
return 1-t*t*(t+(t-1)*(param or 1.701540198866824))
end
The magic number for the default parameter gives exactly a 10% overshoot. Most other sites use 1.70158 which is slightly above 10%. If you want to calculate the parameter for a given overshoot ratio, you can use the following function:
Code: Select all
function calc_outback_param(h)
local P = (91.125*h + 410.0625*h^2 + 307.546875*h^3
+ 0.5*math.sqrt(33215.0625*h^2*(h + 1))
)^(1/3)
return 2.25*h + (13.5*h + 15.1875*h^2)/P + P/3
end
Don't use negative values, and cache the result rather than using it every iteration, as it's computation-intensive as you can guess.
This one is similar. It goes the opposite way at the start then smoothly approaches the target. I didn't make a function to adjust the overshoot, but maybe I will in future.
Code: Select all
function ease_inback(t, param)
return 1-(1-t)^2*(1+t*(param or 3.48050701420725))
end
Use a parameter greater than 2. Using exactly 2 results in the classic sigmoid (3t²-2t³). I haven't yet found the formula to calculate the parameter using the ratio.
And last but not least, elastic out. I didn't like the default elastic one I saw everywhere because the initial overshoot was too large, so I made my own function (the total number of times it bounces back and forth is adjustable, it defaults to 6):
Code: Select all
function ease_elastic(t, times)
return (1 - (math.exp(-12*t) + math.exp(-6*t) * math.cos(((times or 6) + 0.5)
* math.pi * t))/2)/0.99999692789382332858
end