Original Blog Post with Pretty Equations This post is part of my
Game Math Series.
Previously, I talked about
numeric springing and provided some
examples. I have been saving up miscellaneous topics I would like to discuss about numeric spring, and now I have enough to write another post. Here are the topics:
1. Using The Semi-Implicit Euler Method
2. Numeric Springing vs. Tweening
3. Half-Life Parameterization
Using The Semi-Implicit Euler Method Recall the differential equation for a damped spring:
(d^2 x)/(d t^2) + 2 zeta omega (d x)(d t) + omega^2 (x - x_t) = 0
And the equations for simulating such system obtained by the
implicit Euler method:
x_{i+1} &= Delta_x / Delta
v_{i+1} &= Delta_v / Delta
where:
Delta = (1 + 2 h zeta) + h^2 omega^2
Delta_x = (1 + 2 h zeta omega) x_i + h v_i + h^2 omega^2 x_t
Delta_v = v_i + h ^2 (x_t - x_i)
I presented the equations obtained by the implicit Euler method, because they are
always stable. We can obtain a different set of equations that can also be used to simulate a damped spring system by the semi-implicit Euler method[/b]:
v_{i+1} = (1 - 2 h zeta omega) v_i + h omega^2 (x_t - x_i)
x_{i+1} = x_i + h v_{i+1}
The equations obtained by the semi-implicit Euler method involve much less arithmetic operations, compared to the equations obtained by the implicit Euler method. There is a catch: the equations obtained by the semi-implicit Euler method
can be unstable under certain configurations and the simulation will blow up over time. This can happen when you have a large zeta and omega. However, the zeta and omega on most use cases are far from the breaking values. You would almost never use an overdamped spring, so zeta is usually kept under 1. Even if zeta is set to 1 (critically damped spring), omega can be safely set up to 10 pi (5Hz), which is a very fast oscillation you probably would never use on anything.
So, if your choice of zeta and omega result in a stable simulation with the equations obtained by the semi-implicit Euler method, you can use them instead of the equations obtained by the implicit Euler method, making your simulation more efficient.
Here's a sample implementation:
/*
x - value (input/output)
v - velocity (input/output)
xt - target value (input)
zeta - damping ratio (input)
omega - angular frequency (input)
h - time step (input)
*/
void SpringSemiImplicitEuler
(
float &x, float &v, float xt,
float zeta, float omega, float h
)
{
v += -2.0f * h * zeta * omega * v
+ h * omega * omega * (xt - x);
x += h * v;
}
Numeric Springing vs. Tweening Numeric springing and tweening (usually used with the famous Robert Penner's easing equations[\url] might seem very similar at first, as they are both techniques to procedurally animate a numeric value towards a target value; however, they are actually fundamentally different. Tweening requires a pre-determined duration; numeric springing, on the other hand, does not have such requirement: numeric springing provides a simulation that goes on indefinitely. If you were to interrupt a procedural numeric animation and give it a new target value, numeric springing would handle this gracefully and the animation would still look very natural and smooth; it is a non-trivial task to interrupt a tweened animation, set up a new tween animation towards the new target value, and prevent the animation from looking visually jarring.
Don't get me wrong. I'm not saying numeric springing is absolutely superior over tweening. They both have their uses. If your target value can change dynamically and you still want your animation to look nice, use numeric springing. If your animation has a fixed duration with no interruption, then tweening seems to be a better choice; in addition, there are a lot of different easing equations you can choose from that look visually interesting and not necessarily have a springy feel (e.g. sine, circ, bounce, slow-mo).
Half-Life Parameterization Previously, I proposed a parameterization for numeric springing that consisted of 3 parameters: the
oscillation frequency f in Hz, and the
fraction of oscillation magnitude reduced p_d over a
specific duration t_d due to damping.
I have received various feedback from forum comments, private messages, friends, and colleagues. The most suggested alternative parameterizatoin was the half-life parameterization, i.e. you specify the duration when the oscillation magnitude is reduced by 50%. This seems like a fair suggestion; thus, I'll to show you how to derive zeta to plug into numeric springing simulations, based on a given half-life.
I'll use lambda to denote half-life. And yes, it is the same symbol from both
Chemistry and
the game.
As previously discussed, the curve representing the oscillation magnitude decreases exponentially with this curve:
x(t) = e^ {-zeta omega t}
By definition, half-life is the duration of reduction by 50%:
x(lambda) = e^ {-zeta omega lambda} = 0.5
So we have:
zeta omega = -ln(0.5) / lambda
Once we decide the desired lambda, we lock in omega and compute zeta:
zeta = -ln(0.5) / (omega lambda)
And here's a sample implementation:
void SpringByHalfLife
(
float &x, float &v, float xt,
float omega, float h, float lambda
)
{
const float zeta = (-lambda / omega) * ln(0.5f);
Spring(x, v, xt, zeta, omega, h);
}
Here's a graph showing lambda = 0.5 and omega = 4 pi (2Hz). You can clearly see that the oscillation magnitude is reduced by 50% every half second, i.e. 25% every second.
Conclusion I've discussed how to implement numeric springing using the faster but less stable semi-implicit Euler method, the difference between numeric springing versus tweening, and the half-life parameterization of numeric springing.
I hope this post has given you a better understanding of various aspects and applications of numeric springing.