Then I got curious: exactly how does the re-parametrization redistributes the points along this curve? In the original parametrization, the points are bunched up in the middle of the spiral, and more spaced on the outside. The arc-length parametrization makes them equally spaced along the whole path. So how do they compare?
First, I tried this with black points, but it was too confusing. Same thing for a few dots highlighted. So I decided to color them all based on the angle in the original parametrization. This is the result.
It is really interesting how the colors are bent around. It seems that the distribution is quite non-uniform, even though the spiral is rather uniform in growth.
I originally rendered this with four times as many frames, but due to the amount of colors and dimensions of the GIF, Tumblr wouldn’t accept it. It was too large. Below is the animation with twice as many frames.
Hint: try squinting! It blurs the colors and it looks really trippy!
Easing functions are an immensely useful tool for animators. They are very handy when we want to spice up an animation and give it an extra cool or polished look, and are incredibly simple to implement in code.
The main idea is that you have a starting point A and an ending point B, and you want something to move from A to B along a (not necessarily straight) path connecting both points.
However, the path between the points is not the only thing to consider: there’s also how the object will traverse this path, how fast it’ll move at each point, how it will accelerate, etc.
What we are looking for is a uniform “speed” parameterization of the path, that is, we want a function f(t) that returns a point in space along the path. The function is built so f(t=0) gives us the starting point and f(t=1) gives us the ending point. Additionally, for equally spaced values of t in the unit interval [0,1], we want equally spaced points along the path.
Unit speed parameterization of curves is not a trivial thing, but for a straight line path using linear interpolation — which is by far the most common — it is very straightforward: we don’t have to do anything. It is already uniform in speed!
This is where easing functions come in. The easing function e(t) takes an input value t, from 0 to 1, and returns a new value, not necessarily from 0 to 1 (to account for overshoots). The only constraint is is that e(0) = 0 and e(1) = 1. The value returned by the easing function is what we use to get the current position along the path.
In math terms, if our path is given by f(t) and our easing function is e(t), we’ll use f(e(t)) in our animation code.
In the animation above, you see the result of using several different easing functions on a simple linear path.
The horizontal value of each graph is the t time parameter, and the vertical value is the value returned by e(t). The box delimits the interval from [0,1] in both directions.
Shown at right of each graph is the movement you get with this easing function. You can see that even the slightest variation from the super-lame straight line (top left) is already much nicer to look at.
The functions shown here were all custom made, and are part of my personal animation library. Linear, power and sine are found everywhere, and are the most basic ones.
Most libraries also include “elastic” and “bounce”, among others, but these are always fixed Bézier curve or polynomial approximations, which are pretty bad since you can’t fine-tune them to your needs. So I wrote my own.
The trade off for being totally tunable is that they are not optimized for real time, but that isn’t an issue for me.
You’ll also notice that I haven’t included ease-in and ease-out separately. I find it mostly useless. I’ve never seen anyone using “elastic/bounce ease in”, for instance, and I hope it has never been used by anyone. It looks like garbage, as you can see when the animations run backwards.
In any case, creating mixed functions from these is very easy, just a matter of acting in reverse on half the interval, and subtracting the function from 1 for the ease-in parts.
This is usually found in three flavors out there: quad(tratic), cubic and quart(ic). I decided to just wrap them all in the same thing, as it’s the same construction, except using different powers
The idea is to use a variation of tp and its reflection to create the ease-in and ease-out bits.
In particular, you have (2t)p/2 for t in [0,0.5] and 1 - (2(1-x))p/2 (non-expanded for clarity) for t in (0.5,1]. All values p > 0 are well-behaved in the unit interval.
This one is just simply sin(t·π/2)2. You can easily get rid of that power using the familiar identity, but it looks cleaner this way.
The bounce one is based on the actual physics of parabolic motion. It is tuned by two parameters: decay power and number of times it hits the ground. This means you can set, precisely, how many times you want it to bounce around, and you can fine tune how sharply it will lose energy after each bounce.
I usually avoid using exponential decay on its own because it doesn’t reach zero exactly at the end of the interval, which is usually more desirable than physically accurate decay rates. So I tend to use a factor of (1-t)p for decays in general. It offers more freedom anyway.
Most libraries include “elastic” and “back” (which overshoots a bit). They look all right, but are not accurate models of physical motion, and you can’t fine tune them much.
My “physical” easing function replaces both with a solution for dampened harmonic oscillation, where you can manually set the decay rate and frequency of oscillation. This means you can have exactly as many back-and-forth motions as you want. The exponential decay rate was also replaced by the more malleable (1-t)p expression.
Using frequencies like 1 or 0.5 gives you a replacement for the “back” easing in other libraries, with the benefit of tuning. Frequencies that are not multiples of ½ tend to look bad, but thanks to the decay function they still end up at 1 no matter what.
This is one of the most useful ones, and something like it is lacking everywhere I looked. In a lot of situations, it is desirable to have a “mostly linear” movement, with a steady speed in the middle of it. The biggest problem with linear interpolation is the ending points. Having the object static and suddenly starting to move looks jarring and unrealistic.
The “uniform” easing I came up with is a way of keeping the best of both worlds: you can tune how much of the path will be linear, and how much of the remaining will be used by acceleration/deceleration. You can also tune how aggressive acceleration/deceleration will be.
Due to its almost-linear nature, it works exceptionally well with other easing functions. This is shown in the last one (bottom right), where I used it along with the bounce function to give it an extra anticipation in both ends. It makes the bounce feel heavier. Looks pretty good!
Can you release these functions somewhere?
I will write a detailed post about each of them along with pseudocode if there’s enough interest. Since these functions aren’t meant to be used in real time applications, they are not ready to be used in a lot of contexts out there with a lot of moving objects. It would be pretty easy to cache these and make it super fast during run time, though.
However, most people seem to be happy enough with their easing libraries, so I’m not sure if it’s worth the trouble, nor if tumblr is the best way to go about it.
So if you are interested, please drop me a request so I know I won’t be wasting time posting them here.
This simplified things a lot, and created some interesting uses for the functions. However, since I could only have one value of radius for each angle (they were based on polar equations), I could not draw arbitrary shapes with a continuous line based on the [0,2π] interval.
The solution is to extend the idea to general closed curves, by using the position along the curve to define the sine and cosine analogues. In other words, we want “path trigonometric functions” for which the input parameter is the position along the path, and whose periods are the curve’s total arc-length.
But the concept of “sine” and “cosine”, as well as “trigonometric”, completely lose their meaning at this point. It has nothing to do with triangles or angles.
We’re now dealing with the functions x(s) (in blue) and y(s) (in red) that together describe the curve, by being used in the parametric equation r(s) = ( x(s) ,y(s) ), where r(s) is a vector function and s is the arc-length. This is very standard stuff, so it isn’t incredibly exciting anymore.
Notice that if the green curve was a unit circle, the functions would become the usual sine and cosine.
But we do get to see what these functions look like and what they are doing. So here’s the coordinate functions for the arc-length parametrization of a pi curve!
Some anonymous person asked me to do this with a linear movement from the starting position to the ending position of each point, instead of along the spiral’s curve, as I did before.
Since it would require an incredibly tiny change to the code, I decided to give it a shot.
On the left, the colors are based on the angle in the original parametrization. On right, the colors are based on the number of turns. While the transformation is continuous, it is not smooth: this transition creates “kinks” in the curve partway through.