两个角度之间插值的逻辑问题?

Logical issue in interpolation between two angles?

我想做的是使二维对象旋转一定度数,例如 A 度,旋转以面向鼠标,其方向为 B 度。这是在 openGl.

我已经能够使用 glRotatef 函数立即旋转对象,但我不想做的是能够在一定秒数内控制旋转。

我正在使用两种增加或减少旋转的方法:

    void GameObject::increaseRot(int millis) {
        rotation += getRotDeg(millis);
    }

    void GameObject::decreaseRot(int millis) {
        rotation -= getRotDeg(millis);
    }

    double GameObject::getRotDeg(int millis) {
        double rot = 360 / this->rotSpeed;
        rot = rot * millis / 1000.0;
        return rot;
    }

毫秒来自一个正常工作的计时器,所以我可以让一个物体以每 rotSpeed 秒的速度旋转 360 度。

编辑:我在互联网上找到了一个解决方案,似乎大部分都有效。将该解决方案的公式与我自己的代码一起使用,代码为

    shortest_angle=((((end - start) % 360) + 540) % 360) - 180;

    /*  check which way to rotate
     *  this part of the code appears to work fine, all it does is
     *    rotate a certain number of degrees, it's my code that I've been
     *   using the whole time
     */
    if(rotateA < 0)
        game.getPlayer().decreaseRot(deltaT);
    else if(rotateA > 0)
        game.getPlayer().increaseRot(deltaT);

但是,代码在某些值下仍然采用更长的路线,我不明白为什么。 . .

我注意到发生这种情况的价值是:

    45 trying for 135
    225 trying for 315
    315 trying for 45

当然,这些是近似值,这些区域周围的任何值都会搞砸。我一直在想这与极限90、180、270和360/0有关,但我无法弄清楚实际问题是什么。

我已经找到并找到了这个问题的解决方案,以供其他可能遇到同样问题的人使用。

使用 "user151496"、Rotation Interpolation

中的代码
      shortest_angle=((((end - start) % 360) + 540) % 360) - 180;
      return shortest_angle * amount;

您可以更改它以应用您自己的自定义旋转速度,就像我使用

等功能一样
    if(rotateA < 0 && rotateA >= -180)
        game.getPlayer().decreaseRot(deltaT);
    else if (rotateA < 0 && rotateA <= -180)    //for the crossing of the boundary
        game.getPlayer().increaseRot(deltaT);
    else if(rotateA > 0 && rotateA <= 180)
        game.getPlayer().increaseRot(deltaT);
    else if(rotateA > 0 && rotateA >= 180)
        game.getPlayer().decreaseRot(deltaT);   //for the crossing of the boundary

^ 所做的就是检查 shortest_angle 是正数还是负数 (rotateA)。然后它相应地顺时针旋转(decreaseRot)或逆时针旋转(increaseRot)。 但是,请注意额外的两条线,检查 -180 度和 180 度条件。这些是必要的——我发现很难用数字来解释,但它与穿过 0/360 度线有关。

如果你没有这些额外的条件,当你必须越过这样的边界时,旋转将以更大的角度旋转。

可以在不使用任何条件分支的情况下插入角度,如下所示:

template <typename T>
T lerp_fixed_degrees(T a, T b, T x) {
    T d = wrap_degrees(b - a);
    return wrap_degrees(a + std::copysign(std::fmin(std::abs(x), std::abs(d)), d));
}
  • 使用 wrap_degrees(b - a) 计算 ab[-180, 180) 范围内的差值。函数 wrap_degrees 计算 [-180, 180) 范围内的等效角度(例如 wrap_degrees(190) == -170wrap_degrees(-190) == 170)。
  • 使用std::fmin(std::abs(x), std::abs(d))确定步长和差值中较小的一个。这可以防止我们超过目标(如果你知道 x 是正数,你可以使用 x 而不是 std::abs(x),但我在这里选择了稳健性)。
  • 使用 std::copysign 以正确的方向插值。
  • 将结果添加到起始角度和 return 范围 [-180, 180) 中的等效角度。

函数wrap_degrees是这样实现的:

template <typename T>
T wrap_degrees(T x) {
    return fmod_floor(x + T(180), T(360)) - T(180);
}
  • 180 添加到输入值。
  • 使用 fmod_floor[0, 360) 范围内的值换行。 fmod_floor 函数类似于 std::fmod, except it produces the remainder of floored division, which always has the same sign as the divisor。这是我们想要的行为(例如 fmod(-10, 360) == -10fmod_floor(-10, 360) == 350)。
  • 通过减去 180 将包装值移回范围 [-180, 180)(这由之前添加的 180 平衡)。

fmod_floor函数是这样实现的:

template <typename T>
T fmod_floor(T a, T n) {
    return std::fmod(std::fmod(a, n) + n, n);
}
  • 确保值在 (-n, n)std::fmod 范围内。
  • 将值移入范围 (0, 2n)
  • 再次确保值在 [0, n)std::fmod 范围内。

这是一个快速演示:

double a = 90.;
double prev;
do {
    std::cout << a << "\n";
    prev = a;
    a = lerp_fixed_degrees(a, -100., 45.);
} while (a != prev);

输出:

90
135
-180
-135
-100

您还可以使用从 01 的值执行简单的线性插值,如下所示:

template <typename T>
T lerp_degrees(T a, T b, T t) {
    return wrap_degrees(a + wrap_degrees(b - a) * t);
}

我认为这个是不言自明的。