2

The Problem

How would one interpolate between two given angles, given a certain time delta, so that the simulated motion from rotation A or rotation B would take a similar amount of time when the algorithm is ran at different frequencies (without a fixed time step dependency).

Diagram

Potential Solution

I have been using the following C# code to do this kind of interpolation between two points. It solves the differential for the situation:

Vector3 SmoothLerpVector3(Vector3 x0, Vector3 y0, Vector3 yt, double t, float k)
{
    // x0 = current position
    // y0 = last target position
    // yt = current target position
    // t = time delta between last and current target positions
    // k = damping

    Vector3 value = x0;

    if (t > 0)
    {
        Vector3 f = x0 - y0 + (yt - y0) / (k * (float)t);
        value = yt - (yt - y0) / (k * (float)t) + f * (float)Math.Exp(-k * t);
    }

    return value;            
}

This code is usable for 2D coordinates by having the Z coordinate of the Vector3 set as 0.

The "last" and "current" positions are because the target can move during the interpolation. Not taking this in to account causes motion jitter at moderately high speeds.

I did not write this code and it appears to work. I had trouble altering this for angles because, for example, an interpolation between the angles 350° and 10° would take the 'long' way round instead of going in the direction of the 20° difference in angle.

I've looked into quaternion slerp but haven't been able to find an implementation that takes a time delta into account. Something that I have thought of, but not been able to implement either, is to interpolate between both angles twice, but the second time with a phase difference of 180° on each angle and to output the smaller of the two multiplied by -1.

Would appreciate any help or direction!

Joe Shanahan
  • 816
  • 5
  • 21
  • What do "current position", "last target position", and "current target position" mean? What is the desired behavior that you want to implement, in terms of inputs and outputs? Can you describe some simple test cases, for example, at time 0 the output should equal one of the inputs? – Chris Culter Jan 09 '14 at 20:46
  • I'll edit that in, apologies for the ambiguity. – Joe Shanahan Jan 09 '14 at 20:49

3 Answers3

2

The way I've done this before, is to test if the difference between the two angles is greater than 180°, and if so, add 360° to the smaller value, and then do your stuff with those two angle values. So in your example, instead of interpolating between 350° and 10°, you interpolate between 350° and 370°. You can always modulo 360 the result if you need to display it.

Anachronist
  • 1,032
  • 12
  • 16
1

Use Slerp() and make sure you wrap the angles between -π and π with one of these helper functions

    /// <summary>
    /// Wraps angle between -π and π
    /// </summary>
    /// <param name="angle">The angle</param>
    /// <returns>A bounded angle value</returns>
    public static double WrapBetweenPI(this double angle)
    {
        return angle+(2*Math.PI)*Math.Floor((Math.PI-angle)/(2*Math.PI));
    }

    /// <summary>
    /// Wraps angle between -180 and 180
    /// </summary>
    /// <param name="angle">The angle</param>
    /// <returns>A bounded angle value</returns>
    public static double WrapBetween180(this double angle)
    {
        return angle+360*Math.Floor((180-angle)/360);
    }

Caution: Related Post for Inconsistency with Math.Round()

Community
  • 1
  • 1
John Alexiou
  • 28,472
  • 11
  • 77
  • 133
  • I can't use a native XNA method because they do not take time step into account. – Joe Shanahan Jan 09 '14 at 23:56
  • You can find the code for `Slerp()` online and implement it yourself. Also adjust the angle step to the time step you have. – John Alexiou Jan 10 '14 at 13:34
  • It's not as simple as just adjusting the angle step, time is not a scalar in this in this situation, at least I think not in the traditional sense anyway. – Joe Shanahan Jan 10 '14 at 17:46
1

The solution

I have some working code using quaternions. In order to take time steps into account (to remove reliance on a fixed step update) the amount slerp/lerp is calculated using amount = 1 - Math.Exp(-k * t). The constant k effects damping (1 - very sluggish, 20 - almost instant snap to target).

I decided to not try and get this to work for 3D as I'm developing a 2D game.

public static float SlerpAngle(
    float currentAngle, float targetAngle, double t, float k)
{
    // No time has passed, keep angle at current
    if (t == 0)
        return currentAngle;

    // Avoid unexpected large angles
    currentAngle = MathHelper.WrapAngle(currentAngle);
    targetAngle = MathHelper.WrapAngle(targetAngle);

    // Make sure the shortest path between 
    // current -> target doesn't overflow from
    // -pi -> pi range otherwise the 'long
    // way round' will be calculated
    float difference = Math.Abs(currentAngle - targetAngle);
    if (difference > MathHelper.Pi)
    {
        if (currentAngle > targetAngle)
        {
            targetAngle += MathHelper.TwoPi;
        }
        else
        {
            currentAngle += MathHelper.TwoPi;
        }
    }

    // Quaternion.Slerp was outputing a close-to-0 value 
    // when target was in the range (-pi, 0). Ensuring
    // positivity, halfing difference between current
    // and target then doubling result before output 
    // solves this. 
    currentAngle += MathHelper.TwoPi;
    targetAngle += MathHelper.TwoPi;
    currentAngle /= 2;
    targetAngle /= 2;

    // Calculate spherical interpolation
    Quaternion qCurrent = Quaternion.CreateFromAxisAngle(
        Vector3.UnitZ, currentAngle);
    Quaternion qTarget = Quaternion.CreateFromAxisAngle(
        Vector3.UnitZ, targetAngle);
    Quaternion qResult = Quaternion.Slerp(
        qCurrent, qTarget, (float)(1 - Math.Exp(-k * t)));

    // Double value as above
    float value = 2 * 2 * (float)Math.Acos(qResult.W);

    return value;
}

Rotation speed is consistent over the 5Hz -> 1000Hz range. I thought these were suitable extremes. There's no real reason the run this higher than 60Hz.

Joe Shanahan
  • 816
  • 5
  • 21