I've implemented this so many times that I've settled on a specific function that gives me pretty good control of the smoothing and doesn't cause any bumps when settling with reasonable parameters.

Here's how it looks:

To show the components of the function that work together I'm breaking up the solution. Let's start with simply applying an acceleration limit to the difference from the start value to the target value:

The red line is the target value. Each black dash represents one frame step for the value. |

To show the components of the function that work together I'm breaking up the solution. Let's start with simply applying an acceleration limit to the difference from the start value to the target value:

We have a value that is updating, a rate of change for that value that is the speed and a target value. Each update we pass in the delta time (update interval in seconds). In these graphs the initial speed is zero to illustrate the functions, the value can already be moving and the target value can change each update.

**float deltaValue = target - value**

**float acceleration = deltaValue/deltaTime-speed**

**if (acceleration > maxAcc*deltaTime)**

**acceleration = maxAcc * deltaTime**

**else if (acceleration < -maxAcc*deltaTime)**

**acceleration = -maxAcc*deltaTime**

**speed += acceleration**

**value += speed * deltaTime.**

This works great for the beginning of the curve but will overshoot and swing back and forth a bit at the end:

Accelerating towards a target value |

The bumps and extreme change can be dampened by filtering the speed and introducing a dampening value that is less than and near one:

**speed = acceleration + damp * speed**

This results in a nicer curve but that still has a bump:

Acceleration with a damping function |

Let's focus on the tail of the curve. This introduces a gain parameter and ignores acceleration. The gain value is less than one and close to zero:

**float deltaValue = target - value**

**speed = gain * deltaValue / deltaTime;**

**value += speed * deltaTime.**

The result is a nice tail but the acceleration is harsh:

Scaling the difference of a value towards a target value |

If the frame rate is not constant or the game runs at both 60 and 50 the gain value can be expressed as 1-pow(1-gain_one_second, deltaTime) instead.

Now the individual components are defined and the complete smoothing function can be put together. We add a detection for whether the function is accelerating or decelerating to determine whether or not to apply gain instead of acceleration:

**float delta = target-value;**

**float acceleration = delta/deltaTime - speed**

**if (acceleration > maxAcc*deltaTime)**

**acceleration = maxAcc * deltaTime**

**else if (acceleration < -maxAcc*deltaTime)**

**acceleration = -maxAcc*deltaTime**

**speed = acceleration + damp*speed**

**if ((speed*delta)<0 && abs(speed*deltaTime)>abs(gain*delta))**

**speed = gain*delta/deltaTime**

**value += speed * deltaTime**

Which is represented in the graph at the top of this post.

If the smoothing is dealing with an angle or other cyclical value the function needs to apply wrapping to the delta and the result:

**float Wrap(value, low, high) {**

**float w = fmodf(value-low, high-low)+low;**

**return w>=low ? w : (w+high-low);**

**}**

Inserting "Wrap" into the smoothing function:

**float delta = Wrap(target-value, -PI, PI)**

**float acceleration = delta/deltaTime - speed**

**if (acceleration > maxAcc*deltaTime)**

**acceleration = maxAcc * deltaTime**

**else if (acceleration < -maxAcc*deltaTime)**

**acceleration = -maxAcc*deltaTime**

**speed = acceleration + damp*speed**

**if ((speed*delta)>=0 && abs(speed*deltaTime)>abs(gain*delta))**

**speed = gain*delta/deltaTime**

**value = Wrap(value + speed * deltaTime, -PI, PI)**

And it would look something like this if the closest delta crossed the boundary:

Smoothing function with wrapping around the limits |

## No comments:

## Post a Comment