给定坐标周围的 -PI -> PI 范围内的 2 个角度,它们之间的 2 个角度中最小的值是多少?
考虑到 PI 和 -PI 之间的差异不是 2 PI 而是零。
例子:
想象一个圆,有 2 条线从中心出来,这些线之间有 2 个角度,它们在内侧形成的角度称为较小的角度,而它们在外侧形成的角度也称为较大的角度。两个角加起来就是一个完整的圆。假设每个角度都可以在一定范围内拟合,那么较小的角度值是多少,考虑到翻转
这给出了任何角度的有符号角度:
a = targetA - sourceA
a = (a + 180) % 360 - 180
请注意,在许多语言中,modulo
操作会返回一个与被除数符号相同的值(如 C、C++、C#、JavaScript、full list here)。这需要一个自定义 mod
函数,如下所示:
mod = (a, n) -> a - floor(a/n) * n
或者:
mod = (a, n) -> (a % n + n) % n
如果角度在 [-180, 180] 范围内,这也有效:
a = targetA - sourceA
a += (a>180) ? -360 : (a<-180) ? 360 : 0
以更详细的方式:
a = targetA - sourceA
a -= 360 if a > 180
a += 360 if a < -180
x 是目标角度。 y 是源或起始角度:
atan2(sin(x-y), cos(x-y))
它返回有符号的增量角。请注意,根据您的 API,atan2() 函数的参数顺序可能会有所不同。
x-y
为您提供角度差异,但可能超出所需范围。想想这个角度定义了单位圆上的一个点。该点的坐标是(cos(x-y), sin(x-y))
。 atan2
返回该点的角度(相当于 x-y
),但其范围是 [-PI, PI]。
如果您的两个角度是 x 和 y,那么它们之间的角度之一是 abs(x - y)。另一个角度是 (2 * PI) - abs(x - y)。所以两个角度中最小的一个值为:
min((2 * PI) - abs(x - y), abs(x - y))
这为您提供了角度的绝对值,并假设输入是标准化的(即:在范围 [0, 2π)
内)。
如果您想保留角度的符号(即:方向)并接受 [0, 2π)
范围之外的角度,您可以概括上述内容。这是通用版本的 Python 代码:
PI = math.pi
TAU = 2*PI
def smallestSignedAngleBetween(x, y):
a = (x - y) % TAU
b = (y - x) % TAU
return -a if a < b else b
请注意,%
运算符在所有语言中的行为并不相同,尤其是在涉及负值时,因此如果移植,可能需要进行一些符号调整。
from math import tau
。
我面临提供签名答案的挑战:
def f(x,y):
import math
return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
C++ 中适用于任何角度的有效代码:弧度和度数是:
inline double getAbsoluteDiff2Angles(const double x, const double y, const double c)
{
// c can be PI (for radians) or 180.0 (for degrees);
return c - fabs(fmod(fabs(x - y), 2*c) - c);
}
在这里看到它的工作:https://www.desmos.com/calculator/sbgxyfchjr
算术(相对于算法)解决方案:
angle = Pi - abs(abs(a1 - a2) - Pi);
我在 C++ 中使用的一个简单方法是:
double deltaOrientation = angle1 - angle2;
double delta = remainder(deltaOrientation, 2*M_PI);
我非常喜欢上面 Peter B 的答案,但是如果您需要一种产生相同结果的简单方法,这里是:
function absAngle(a) { // 这会产生正确的逆时针数字,例如 350deg for -370 return (360 + (a % 360)) % 360; } function angleDelta(a, b) { // https://gamedev.stackexchange.com/a/4472 let delta = Math.abs(absAngle(a) - absAngle(b));让符号 = absAngle(a) > absAngle(b) ||增量 >= 180 ? -1:1;返回 (180 - Math.abs(delta - 180)) * 符号; } // 示例输出 for (let angle = -370; angle <= 370; angle+=20) { let testAngle = 10; console.log(testAngle, "->", angle, "=", angleDelta(testAngle, angle)); }
需要注意的一点是,我故意翻转符号:逆时针增量为负,顺时针为正
无需计算三角函数。 C语言的简单代码是:
#include <math.h>
#define PIV2 M_PI+M_PI
#define C360 360.0000000000000000000
double difangrad(double x, double y)
{
double arg;
arg = fmod(y-x, PIV2);
if (arg < 0 ) arg = arg + PIV2;
if (arg > M_PI) arg = arg - PIV2;
return (-arg);
}
double difangdeg(double x, double y)
{
double arg;
arg = fmod(y-x, C360);
if (arg < 0 ) arg = arg + C360;
if (arg > 180) arg = arg - C360;
return (-arg);
}
让 dif = a - b ,以弧度为单位
dif = difangrad(a,b);
让 diff = a - b ,以度为单位
dif = difangdeg(a,b);
difangdeg(180.000000 , -180.000000) = 0.000000
difangdeg(-180.000000 , 180.000000) = -0.000000
difangdeg(359.000000 , 1.000000) = -2.000000
difangdeg(1.000000 , 359.000000) = 2.000000
没有罪,没有cos,没有棕褐色,....只有几何!!!!
arg = arg - PIV2;
扩展为 arg = arg - M_PI + M_PI
,因此什么也不做。
不定期副业成功案例分享
a -= 360*sgn(a)*(abs(a) > 180)
。 (想想看,如果您有sgn
和abs
的无分支实现,那么该特性实际上可能开始补偿需要两次乘法。)double targetA = 2; double sourceA = 359;
中,“a”将等于 -357.0 而不是 3.0%
运算符在您的语言中表现得像余数(保留符号),您可以简单地添加一个额外的 360 度而不是定义一个模函数:a = (a + 540) % 360 - 180
如上所述,这仅适用于彼此之间 360 度以内的角度,即可能经常是这样。否则:a = ((a % 360) + 540) % 360 - 180