Logical issue in interpolation between two angles?

时间:2017-04-10 02:08:27

标签: c++ rotation interpolation angle

What I'm trying to do is make an object in 2D that is rotated a certain degrees, A degrees, for example, rotate to face the mouse, whose direction FROM that object is B degrees. This is in openGl.

I'm already able to instantly rotate the object, using the glRotatef function, but what I wan't to do is to be able to control the rotation over a certain number of seconds.

I'm using two methods which either increase, or decrease the rotation:

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

Millis comes from a timer which works properly, and so I can cause an object to rotate at a speed of 360 degrees every rotSpeed seconds.

Edit: I've found a solution on the internet, that seems to mostly work. Using the formula of that solution with my own code, the code is

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

However, the code still takes the longer route at certain values, and I can't figure out why . . .

The value I've noticed this happening are:

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

These are approximate values of course, any values around those areas will screw up. I've been thinking that it's something to do with the limits 90, 180, 270 and 360/0, but I can't figure out what the actual problem is.

2 个答案:

答案 0 :(得分:0)

我找到并找出了这个问题的解决方案,对于其他可能也遇到同样问题的人来说。

使用&#34; user151496&#34;,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)。然后它相应地顺时针旋转(减速)或逆时针旋转(增加旋转)。 但请注意两条额外的线,检查-180和180度的条件。这些是必要的 - 我发现很难用数字解释,但它与穿越0/360度线有关。

如果你没有那些额外条件,只要你必须越过这样的边界,旋转就会以更大的角度旋转。

答案 1 :(得分:0)

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

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));
}
  • 使用a计算b范围[-180, 180)wrap_degrees(b - a)之间的差异。函数wrap_degrees计算[-180, 180)范围内的等效角度(例如wrap_degrees(190) == -170wrap_degrees(-190) == 170)。
  • 使用std::fmin(std::abs(x), std::abs(d))确定步长和差异中较小的一个。这可以防止我们超越目标(如果您知道x是正面的,则可以使用std::abs(x)代替x,但我选择了稳健性。)
  • 使用std::copysign以正确的方向进行插值。
  • 将结果添加到起始角度并返回[-180, 180)范围内的等效角度。

函数wrap_degrees的实现如下:

template <typename T>
T wrap_degrees(T x) {
    return fmod_floor(x + T(180), T(360)) - T(180);
}
  • 180添加到输入值。
  • 使用[0, 360)将值包裹在fmod_floor范围内。 fmod_floor函数类似于std::fmod,除了它生成 floored division 的其余部分,它总是与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);
}

我认为这个问题非常明显。