两个角度之间插值的逻辑问题?
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)
计算 a
和 b
在 [-180, 180)
范围内的差值。函数 wrap_degrees
计算 [-180, 180)
范围内的等效角度(例如 wrap_degrees(190) == -170
和 wrap_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) == -10
而 fmod_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
您还可以使用从 0
到 1
的值执行简单的线性插值,如下所示:
template <typename T>
T lerp_degrees(T a, T b, T t) {
return wrap_degrees(a + wrap_degrees(b - a) * t);
}
我认为这个是不言自明的。
我想做的是使二维对象旋转一定度数,例如 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)
计算a
和b
在[-180, 180)
范围内的差值。函数wrap_degrees
计算[-180, 180)
范围内的等效角度(例如wrap_degrees(190) == -170
和wrap_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) == -10
而fmod_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
您还可以使用从 0
到 1
的值执行简单的线性插值,如下所示:
template <typename T>
T lerp_degrees(T a, T b, T t) {
return wrap_degrees(a + wrap_degrees(b - a) * t);
}
我认为这个是不言自明的。