In traditional vector animation, there is a concept called interpolation curves,
which describes the way we move between one animation keyframe and the next
Depending on the shape of this curve, we can control motion to slowly ease in,
to slowly ease out, or even to anticipate and overshoot the keyframe
The choice of animation curves can greatly affect the feeling of inertia and energy in the motion
This grants the animator a good deal of artistic expression in describing motion
while keeping the number of keyframes economical
In procedural animation, however, it can be very powerful to do away with the concept of
keyframes entirely, freeing the system to move however the situation demands
This adaptability is one of the main reasons to incorporate procedural animation in games
For example, in the game I’m working on, I have this little robot character which moves its body
and legs using inverse kinematics in response to realtime requests to move and change orientation
I’m controlling it with my mouse here, and you can find tutorials for how to
and you can find tutorials for how to implement this sort of thing online
In game, this same flexibility allows the character to immediately respond
to changing gameplay situations
This is pretty nice, but because we don’t have keyframes for these inputs,
we can't really use interpolation curves to add that sense of inertia
What we’d really like is the ability to achieve something like this,
using an intuitive abstraction similar to interpolation curves,
but without losing out on the dynamic responsiveness of a keyframe-free system
Let’s start off by describing the problem we want to solve a bit more precisely
Given some dynamically changing input x from our game world,
we want to produce some dynamic output y which tracks the input x, but is imbued with
some sort of characteristic that conveys the way that we want the motion to feel
In the simplest case, y=x, and our system rigidly conforms to whatever input we give it
This isn’t very useful on its own, but it is a good starting point
Whatever changes we make to this equation, we’ll want to ensure that in the long term,
when x isn’t changing, y should eventually settle at x
To introduce the possibility for interesting dynamics,
let’s add some velocity and acceleration terms, represented here as
first and second order derivatives with respect to time, scaled by some values k1, k2, and k3
The dot notation here is just a shorthand way of writing time derivatives, which
you may have also seen written like this, or this
This type of relationship involving up to a second order time derivative
is known as a second order system
Mechanical motions in the real world tend to be second order, which you can think of as
being related to the fact that forces act on acceleration, the second derivative of
position, as described by Newton’s second law These forces usually arise out of stiffness,
where they are a function of displacement, or out of friction, where they are a function of
velocity, hence our choice of terms
For example, this is the equation of motion of a rigid body
being dragged along one axis through a linear damped spring
And this is the equation of motion for the SmoothDamp function in Unity
By playing around with these values k1, k2, and k3, we can generate a whole host of different
interesting behaviors, but as an artist, it might not be very intuitive
how these values map to those different behaviors
First, let’s simplify the visualization to look at the response to a single sudden change in the input
The resulting output is known as the step response, which is a standard way
of visualizing the dynamics of a system, and captures the essence of the system’s behavior
Next, using k1 k2 and k3, we’ll define 3 new variables: f, zeta, and r
We’ll use f, zeta, and r (which I’ll explain in a moment)
as the design parameters for the system’s motion
Under the hood, we can use them to compute k1, k2, and k3
In this new parameterization of f, zeta, and r, each term controls something
that we can develop some reasonable intuition about
f is the natural frequency of the system measured in Hz, or cycles per second
It describes the speed at which the system will respond to changes in the input, as well as the
frequency the system will tend to vibrate at, but does not affect the shape of the resulting motion
zeta is known as the damping coefficient
It describes how the system comes to settle at the target
When zeta is 0, vibration never dies down, and the system is undamped
Between values of 0 and 1, the system is underdamped, and will vibrate more,
or less, depending on the magnitude of zeta
When zeta is greater than 1, the system will not vibrate,
and instead slowly settle toward the target x, depending on the magnitude of zeta
The SmoothDamp function in Unity uses a zeta equal to exactly 1 which is known as critical damping
r is a value which controls the initial response of the system
When r is 0, the system takes time to begin accelerating from rest
When r is positive, the system reacts immediately to movement in x
When r is greater than 1, the system will overshoot the target
When r is negative, the system will anticipate the motion
When modeling a mechanical connection, a value of 2 for r is typical
Now, as artists, we have all we need to start playing with the
characteristics of the motion of our procedurally animated object
If we want to control the settling behavior of the system, we can play with zeta
If we want to control the initial response of the system, we can play with r
If we want to control how fast or slowly the system behaves, we can play with f
Coming back to this robot character, you can see I used an underdamped movement for the
body with zeta of 0.5, and r of -2 to give it some anticipation before fast movements
For a bit of extra flavor, I tilt the body towards the target position,
to further hint at the intent behind the motion I want the turning to be smooth, so for the body
orientation I use a zeta of 1 and r of 0 I do the same for the head orientation,
but with a smaller f, to make it lag behind the body as a sort of secondary motion
The cables are animated with a little trick that I explained over on twitter a while ago
Let’s now look at an approach for solving this equation in real-time,
so that we can begin using it in practice
There are many ways of numerically solving differential equations
such as our second order one, but a particularly straightforward one is called Euler’s method
Today, I will present a slight variation known as the semi-implicit Euler method,
which, for our system, happens to have the same accuracy as the more complex Verlet integration
First, we need to allocate some state variables: position and velocity
These are our estimates of y and its first derivative
At timestep 0, they need to be initialized to some initial values
Now, when the game is running, we’ll want to iteratively update these variables each frame
Let’s call the amount of time that passes between frames T
First, we update the position estimate by taking the previous iteration’s position
and adding T times the velocity
Next, we update the velocity by taking the previous iteration’s velocity
and adding T times the acceleration
To compute this acceleration, recall our equation of motion
We can solve for acceleration here
And substitute in our most recent estimates of position and velocity
Note that in semi-implicit Euler, we are using the updated position to compute velocity,
which is slightly different from the regular Euler method where we use the previous frame’s position
In the case that the input velocity, x dot, is unknown, we can also estimate it using historical
measurements, for example, by approximating it as the average velocity since the previous sample
This algorithm naturally translates almost line by line into code
If we were to implement this in-game, we would see that it does work as designed
I hooked up the code to allow for f, zeta, and r to be changed in real time,
and the system responds appropriately
There is one big issue that we’ll run into after a bit of experimentation though
Set the resonant frequency f too high relative to the frame rate, and the system becomes
completely unstable, launching itself to infinity
While it’s possible to simply keep f low enough such that this type of glitch is unlikely,
in a game engine, we would really like to have a stronger guarantee that something like
this doesn’t happen in corner cases, such as a lag spike
causing the time step to be much larger than anticipated
This is a problem that can be solved, but to understand it properly, we’ll have to
get a bit more technical with our analysis
The reason why physics solvers like Euler’s method can become unstable is that fundamentally,
they are feedback systems whose outputs from one iteration, here y and its derivative,
are fed back into later iterations of the computation
When the time step between frames is too large compared to the parameters,
it will start accumulating errors over time
Beyond a critical threshold, these errors will start to compound on themselves,
quickly leading to catastrophic failure
To compute this threshold, we’ll need to organize the problem into a more standard form
What we want to do is write the values of our state variables at frame n+1
in terms of our state variables at frame n
We can do this by substituting the first equation into the second, so now both our equations
have y and its derivative at frame n on the right-hand side
Now let’s expand this and collect like terms
This is a system of linear equations, which we can write in matrix notation
The system, written this way, is known as the state-space representation
This matrix A, known as the state transition matrix,
describes how each iteration of the state variables influence the next iteration
Intuitively, you can imagine that the feedback will be stable
If A doesn’t cause the state variables to grow
if A was a number instead of a matrix,
it would be easier to reason about the effect that it has on the system
For example, in this simpler system with only a single state variable and no external forces,
each iteration is equal to the previous iteration scaled by A
When the magnitude of A is less than 1, the magnitude of y at each iteration
will be smaller than the previous iteration, causing the system to stabilize over time
When the magnitude of A is greater than 1, the magnitude of y at each iteration will
be larger than the previous iteration, so the system will quickly grow beyond control
This is what we’d like to avoid
In our system, A is a 2x2 matrix, which doesn’t have
a magnitude that can directly be compared to 1
Instead, it has what we can think of as two separate magnitudes, called eigenvalues
We won’t get into it here, but suffice to say that both of these eigenvalues z1 and z2,
which are computed by finding the two solutions to this equation, must have a magnitude less than 1
for the system to settle over time
This is computed like this
Which expands to this quadratic polynomial of z
Which we can solve using the quadratic formula
Our system will be stable when the magnitude of this expression is less than 1
Solving this inequality gives us the following constraint,
that the time step T must be less than the square root of 4 times k2 plus k1 squared, subtract k1
In our code, we can compute this maximum stable time step, T critical,
and when running in-game, compare the time step against it
If the time step is too large,
we can divide it up into smaller steps that are below the stability threshold
Taking smaller steps requires performing more computations per frame
To avoid this, we might be able to get away with slowing down the dynamics instead
In this case, rather than constraining T, we can solve for k2 and constrain that
Clamping k2 to be above this value is not physically correct, but recall that our goal
here was just to prevent catastrophic failure, not to produce an accurate physics simulation
There are, of course, much more esoteric places you can take this analysis,
and in a previous draft of this video, I did go a bit further
For example, this frame-to-frame jittering behavior that sneaks in when the frequency
is sufficiently high, is caused by negative eigenvalues, so if we also take that into account
during our analysis, we can impose additional restrictions to ensure it doesn’t happen
In my own implementation, in case accuracy is needed, I opt to use a method known as
pole-zero matching to compute values for k1 and k2 per-frame based on the time step
This produces generally more accurate results for very fast movement,
but comes with this additional computation cost that might not suit every application
The idea behind using simple parametric motion models
like our f zeta r one extends some interesting possibilities
Just as an example, we could modulate the values of these parameters
to convey gameplay information, so characters could move with different parameters
depending on their state of awareness, health, status effects, and so on
This would help communicate the situation to players through motion
in a way that’s cheap and easy to iterate on
In any case, I’ve spent long enough working on this video instead of my game,
so
hopefully this has been an approachable but substantial introduction to
some interesting concepts, or maybe a presentation of some applications of
mathematics in an unusual but interesting context
As always, thanks for watching