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

Logical issue in interpolation between two angles?

本文关键字:问题 插值 两个      更新时间:2023-10-16

我想做的是让一个旋转一定度的 2D 对象,例如 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;
}

Millis来自一个正常工作的计时器,所以我可以使一个物体以每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"中的代码,旋转插值

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 度线有关。

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

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

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在正确的方向上插值。
  • 将结果添加到起始角度,并返回范围[-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类似,只是它产生地板除法的余数,它总是与除数具有相同的符号。这是我们想要的行为(例如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);
}
  • 确保值在与std::fmod(-n, n)的范围内。
  • 将值移动到范围(0, 2n)
  • 确保该值再次在std::fmod[0, n)范围内。

下面是一个快速演示:

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

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