随时间推移2D空间的角度,而不依赖于固定的时间步长

时间:2014-01-09 20:41:07

标签: c# xna interpolation angle

问题

如果给定一定的时间增量,如何在两个给定角度之间进行插值,以便当算法以不同的频率运行时,来自旋转A或旋转B的模拟运动将花费相似的时间量(没有固定的时间步长依赖性)。

Diagram

潜在解决方案

我一直在使用以下C#代码在两点之间进行这种插值。它解决了这种情况的差异:

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;            
}

通过将Z的{​​{1}}坐标设置为0,此代码可用于2D坐标。

“最后”和“当前”位置是因为目标可以在插值期间移动。如果不考虑这一因素会导致中等速度下的运动抖动。

我没有编写此代码,它似乎工作。我在修改角度方面遇到了麻烦,因为例如,角度350°和10°之间的插值将采用“长”方式而不是朝向20°角度差的方向。

我已经研究了四元数slerp但是却找不到考虑时间增量的实现。我曾经想过但也无法实现的东西是在两个角度之间插值两次,但第二次在每个角度上相位差为180°并输出两个中较小的一个乘以-1。

感谢任何帮助或指导!

3 个答案:

答案 0 :(得分:2)

我以前做过这种方式的方法是测试两个角度之间的差异是否大于180°,如果是,则将360°加到较小的值,然后使用这两个角度值进行测量。因此,在您的示例中,不是在350°和10°之间进行插值,而是在350°和370°之间插值。如果需要显示结果,您可以随时对结果进行模数设计。

答案 1 :(得分:1)

使用Slerp()并确保用其中一个辅助函数包装-π和π之间的角度

    /// <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);
    }

警告Inconsistency with Math.Round()

的相关帖子

答案 2 :(得分:1)

解决方案

我有一些使用四元数的工作代码。为了考虑时间步骤(以消除对固定步骤更新的依赖),使用amount = 1 - Math.Exp(-k * t)计算slerp / lerp的量。恒定k效果阻尼(1 - 非常缓慢,20 - 几乎立即对目标进行捕捉)。

我决定不尝试将其用于3D,因为我正在开发2D游戏。

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;
}

旋转速度在5Hz上是一致的 - > 1000Hz范围。我认为这些都是合适的极端。没有任何真正的理由让它高于60Hz。