The Spring Mass Damper System and Its Application On Gameplay Camera

In this last post I introduced frame-independent interpolation and reached the conclusion of using the exponential function to interpolate values. It has a wide variety of uses in video games, e.g., be used to damp animation motions which constitute the building blocks of what you see when playing games. In today's post, I will introduce a related and more popular technique to damp values, that is, the spring mass damper system.

Deriving the Spring Mass Damper System

The very famous Hooke's law states that the force needed to extend or compress a spring by some distance scales linearly with respect to that distance by a factor . That is,

From the other side, Newton's Second Law tells us that the force that causes one object to move is the product of its mass and its acceleration:

Connecting these two equations:

An additional term of is added serving as the damper. The damper's force is proportional to the velocity.

is called the undamped natural frequency (named undamped as it comes only from ) and is the damping ratio. We will explain these two terms soon later.

Solving this differential equation we have:

This equation is quite intuitive:

  • When (Undamped), the solution becomes , a pure harmonic oscillator.
  • When (Underdamped), the solution becomes a sum of two decaying oscillations, both decayed by . Therefore, when a spring is underdamped, it will oscillate and eventually converge to its initial state. When this value is smaller (closer to zero), the spring will oscillate for a longer time.
  • When (Critically damped), the solution is simply an exponential function . We get to the same result as in our last article.
  • When (Overdamped), the solution is simply a sum of two decaying exponentials with no oscillation. In this case, the spring will return slowly to its relaxing position and generally the larger is, the slower it will be.

Assuming the initial values and , we can determine and for :

For we immediately have:

For , we can first compute 's two real solutions and take their linear combination. The two real solutions are and . Taking their linear combination and solving the coefficients:

So the final equation is:

For , we substitute the initial values and into the equation and get and . The expression then becomes:

Work done!

By the way, Daniel has a very great article on this topic, see here for more information.

Okay, let's summarize what we have reached so far. According to the value , a spring-mass-damper system can be categorized into four classes:

  • Undamped where . In this case, the trajectory of the spring is simply a harmonic oscillation. Given initial values and , the trajectory and velocity equations can be expressed as:

  • Underdamped where . In this case, the spring performs a decaying oscillation. Its trajectory and velocity equations are kind of complex:

  • Critically damped where . In this case, the spring will get to its relaxing state as soon as possible without oscillation. The trajectory and velocity equations are simply exponential functions:

  • Overdamped where . The spring will return slowly to its relaxing position. The trajectory and velocity equations are:

Application On Gameplay Camera

In this section, let's discuss one application of the spring-mass-damper system on video games, the gameplay camera, particularly the third person camera.

A third person camera tracks the player at a fixed distance. You can imagine the player is the origin and it's dragging the camera back and forth, just like a spring right? The relaxing distance is the length the camera tries to keep away from the player. When the player goes outwards from the camera, the spring extends; when the player goes inwards to the camera, the spring compresses. Simple to imagine.

Implementation

The implementation is quite simple: just carrying the formulas here and substituting the numbers into. That's all of it!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void SpringDampValue(const float& Frequency, const float& DampRatio, const float& CurrentVelocity, double& OutVelocity, const float& DeltaSeconds, const float& Input, double& Output)
{
// Underdamped
if (DampRatio < 1)
{
float SqrtOfOneMinusSquare = FMath::Sqrt(1.0 - DampRatio * DampRatio);
float OmegaDotSqrt = Frequency * SqrtOfOneMinusSquare;
float Inner = OmegaDotSqrt * DeltaSeconds;
float OmegaDotZeta = Frequency * DampRatio;
float Cosine = FMath::Cos(Inner);
float Sine = FMath::Sin(Inner);

float C1 = Input;
float C2 = (CurrentVelocity + OmegaDotZeta * Input) / (OmegaDotSqrt + 1e-5f);
float Decay = FMath::Exp(-OmegaDotZeta * DeltaSeconds);
float X = C1 * Decay * Cosine + C2 * Decay * Sine;
OutVelocity = -OmegaDotZeta * X + (CurrentVelocity + OmegaDotZeta * Input) * Decay * Cosine - Input * OmegaDotSqrt * Decay * Sine;
Output = Input - X;
}
// Critically damped
else if (DampRatio == 1)
{
float X = Input * FMath::Exp(-Frequency * DeltaSeconds);
OutVelocity = -Frequency * X;
Output = Input - X;
}
// Overdamped
else if (DampRatio > 1)
{
float SqrtOfSquareMinusOne = FMath::Sqrt(DampRatio * DampRatio - 1.0);
float ZetaPlusSqrt = DampRatio + SqrtOfSquareMinusOne;
float ZetaMinusSqrt = DampRatio - SqrtOfSquareMinusOne;
float NegOmegaDotPlus = -Frequency * ZetaPlusSqrt;
float NegOmegaDotMinus = -Frequency * ZetaMinusSqrt;

float C1 = (-CurrentVelocity / Frequency - ZetaMinusSqrt * Input) / (2.0 * SqrtOfSquareMinusOne + 1e-5f);
float C2 = Input - C1;
float T1 = C1 * FMath::Exp(DeltaSeconds * NegOmegaDotPlus);
float T2 = C2 * FMath::Exp(DeltaSeconds * NegOmegaDotMinus);
float X = T1 + T2;
OutVelocity = NegOmegaDotPlus * T1 + NegOmegaDotMinus * T2;
Output = Input - X;
}
// Undamped
else
{
float Cosine = FMath::Cos(Frequency * DeltaSeconds);
float Sine = FMath::Sin(Frequency * DeltaSeconds);
float X = CurrentVelocity / Frequency * Sine + Input * Cosine;
OutVelocity = CurrentVelocity * Cosine - Frequency * Input * Sine;
Output = Input - X;
}
}

Input is the distance to pass through, or the initial position . Output is the distance to pass for this frame. Frequency is and DampRatio is . CurrentVelocity is the initial velocity and OutVelocity is the velocity at timestep DeltaSeconds, i.e., the velocity when this frame completes.

One thing to note here is that the OutVelocity computed at this frame will be used as the CurrentVelocity for the next frame in order to synchronize initial values at consecutive time steps.

Results

The following four figures respectively show the result of undamped, underdamped, critically damped and overdamped springs. By twisting and , we can obtain a wide spectrum of spring effects. Pretty good!




Spring Mass Damper System is Framerate-Independent

The most interesting fact about the spring-mass-damper system is: it's framerate-independent. Actually, we can prove the following statement:

For any given , the result produced by sequentially applying the spring-mass-damper system on , is equal to the result produced by applying once on , assuming with the same initial values and .

That is, if we rewrite the spring-mass-damper function as:

where is a function depending on and the two initial values, we claim that:

Hence, is framerate-independent.

To prove this equality, we can first expand the left side term:

Then we expand the right hand side:

Note that are calculate based on the new initial values, and are different from .

Comparing two sides, they are equal when:

This holds because for the new , they satisfy the following equations:

This is obviously correct for our . Proof completes.

You may ask you're only considering the overdamped case, what about other cases. Well, you can verify that for all other cases, the claim still holds true. The proofs are left as exercises for you.

Conclusion

The spring-mass-damper system is intriguing. It's simple, effective and easy-to-implement. It has a very wide application on control systems, animations and architectures. The topic discussed today is only a small fraction of the spring system. I will cover more of it in future days. Hope you enjoy reading this blog post.