给定一个 2D 圆,其围绕坐标的 2 个角度在
-PI
-> PI
范围内,它们之间的最小角度的值是多少?
考虑到 PI 和 -PI 之间的差异不是 2 PI 而是零。
示例:
想象一个圆,有2条线从中心伸出,这些线之间有2个角度,它们在内部形成的角度又称为较小的角度,它们在外部形成的角度又称为较大的角度。
两个角相加就是一个完整的圆。考虑到每个角度都可以在一定范围内,考虑到翻转,较小的角度值是多少
这给出了任何角度的带符号角度:
a = targetA - sourceA
a = (a + 180) % 360 - 180
请注意,在许多语言中,
modulo
运算会返回一个与被除数具有相同符号的值(例如 C、C++、C#、JavaScript,此处有完整列表)。这需要一个自定义 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,那么它们之间的角度之一是 abs(x - y)。另一个角度是 (2 * PI) - abs(x - y)。所以 2 个角度中最小的值为:
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
请注意,
%
运算符在所有语言中的行为并不相同,特别是在涉及负值时,因此如果移植,可能需要进行一些符号调整。
适用于任何角度且适用于弧度和度数的高效 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
对于带符号的角度:
return fmod(fabs(x - y) + c, 2*c) - c;
在其他一些负数模为正的编程语言中,可以消除内部的abs。
我迎接挑战,提供签名答案:
def f(x,y):
import math
return min(y-x, y-x+2*math.pi, y-x-2*math.pi, key=abs)
对于UnityEngine用户来说,最简单的方法就是使用Mathf.DeltaAngle。
算术(相对于算法)解决方案:
angle = Pi - abs(abs(a1 - a2) - Pi);
我绝对喜欢上面Peter B的答案,但是如果您需要一种产生相同结果的极其简单的方法,那就是:
function absAngle(a) {
// this yields correct counter-clock-wise numbers, like 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));
let sign = absAngle(a) > absAngle(b) || delta >= 180 ? -1 : 1;
return (180 - Math.abs(delta - 180)) * sign;
}
// sample output
for (let angle = -370; angle <= 370; angle+=20) {
let testAngle = 10;
console.log(testAngle, "->", angle, "=", angleDelta(testAngle, angle));
}
需要注意的一件事是我故意颠倒了符号:逆时针增量为负,顺时针增量为正
比较两个向量的 Lua 代码,给出:
function angleComparing(x1, y1, x2, y2)
local a1 = math.atan2(y1, x1)
local a2 = math.atan2(y2, x2)
local diff = (a2 - a1 + math.pi)%(2*math.pi) - math.pi
return diff -- returns values in range [-pi, pi]
end
用途:
for i = 360, -360, -180 do
for j = -120, 120, 120 do
local a1 = math.rad (i)
local a2 = math.rad (j)
local dy1, dx1 = math.sin (a1), math.cos (a1)
local dy2, dx2 = math.sin (a2), math.cos (a2)
local dif = angleComparing(dx1, dy1, dx2, dy2)
print (i, j, string.format('%.1f', math.deg (dif)))
end
end
结果(角度1,角度2,差值):
360 -120 120.0
360 0 0.0
360 120 -120.0
180 -120 -60.0
180 0 -180.0
180 120 60.0
0 -120 120.0
0 0 0.0
0 120 -120.0
-180 -120 -60.0
-180 0 -180.0
-180 120 60.0
-360 -120 120.0
-360 0 0.0
-360 120 -120.0
无需计算三角函数。 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
没有 sin,没有 cos,没有 tan,....只有几何!!!!
一个简单的方法,我在C++中使用的是:
double deltaOrientation = angle1 - angle2;
double delta = remainder(deltaOrientation, 2*M_PI);