我想找出两个向量(二维或三维)之间的顺时针角度。
使用点积的经典方法可以给出内角(0-180度),但我需要使用一些if语句来确定结果是我需要的角度还是其补角。
有没有直接计算顺时针角度的方法?
就像点积与角度的余弦成正比一样,行列式与角度的正弦成正比。因此,你可以通过以下方式计算角度:
dot = x1*x2 + y1*y2 # Dot product between [x1, y1] and [x2, y2]
det = x1*y2 - y1*x2 # Determinant
angle = atan2(det, dot) # atan2(y, x) or atan2(sin, cos)
dot = x1*x2 + y1*y2 + z1*z2 # Between [x1, y1, z1] and [x2, y2, z2]
lenSq1 = x1*x1 + y1*y1 + z1*z1
lenSq2 = x2*x2 + y2*y2 + z2*z2
angle = acos(dot/sqrt(lenSq1 * lenSq2))
acos
,原因是在测量的角度很小时会出现数值问题。dot = x1*x2 + y1*y2 + z1*z2
det = x1*y2*zn + x2*yn*z1 + xn*y1*z2 - z1*y2*xn - z2*yn*x1 - zn*y1*x2
angle = atan2(det, dot)
det = n · (v1 × v2)
qAtan2(y,x)
的实现,但如果有人和我遇到同样的问题,这可能会有所帮助。 - rbaleksandaratan2
通常的范围是[-180°,180°]。为了获得[0°,360°]的值而不需要进行情况判断,可以将 atan2(y,x)
替换为 atan2(-y,-x) + 180°
。 - MvG这个答案与MvG's相同,但是解释方式不同(这是我努力理解为什么MvG的解决方案有效的结果)。
逆时针角度theta
从给定法线n
(||n|| = 1
)的视点到x
和y
之间,由以下公式给出:
atan2( dot(n, cross(x,y)), dot(x,y) )
(1) = atan2( ||x|| ||y|| sin(theta), ||x|| ||y|| cos(theta) )
(2) = atan2( sin(theta), cos(theta) )
(3) = x轴与向量(cos(theta), sin(theta))之间的逆时针角度
(4) = theta
其中||x||
表示x
的大小。
步骤(1)通过注意到以下内容得出:
cross(x,y) = ||x|| ||y|| sin(theta) n,为了计算角度,您只需要在二维情况下调用atan2(v1.s_cross(v2), v1.dot(v2))
。其中s_cross
是叉积的标量类比(平行四边形的有向面积)。
在二维情况下,这将是楔形积。
在三维情况下,您需要定义顺时针旋转,因为从平面的一侧顺时针方向是一个方向,而从平面的另一侧则是另一个方向=)
这是逆时针角度,而顺时针角度则相反。
atan2f
的第一个参数是 y 坐标,因此应该为 angle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x)
。 - Martin Rcross
和dot
,这将消除有关cross
返回3D向量的所有疑虑(但这只是一个建议,您可以忽略)。-否则,我喜欢这个解决方案,因为它只需要一个atan2f
函数调用。 - Martin R由于最简单和最优雅的解决方案之一隐藏在评论中,我认为将其作为单独的答案发布可能会有用。
acos
对于非常小的角度可能会导致不准确性,因此通常更喜欢使用 atan2
。对于三维情况:
dot = x1*x2 + y1*y2 + z1*z2
cross_x = (y1*z2 – z1*y2)
cross_y = (z1*x2 – x1*z2)
cross_z = (x1*y2 – y1*x2)
det = sqrt(cross_x*cross_x + cross_y*cross_y + cross_z*cross_z)
angle = atan2(det, dot)
someFile.c:44:1: error: stray ‘\342’ in program. someFile.c:44:1: error: stray ‘\200’ in program. someFile.c:44:1: error: stray ‘\223’ in program
"。在大多数文本编辑器的正则表达式模式中,可以通过\x{2013}
进行搜索EN DASH(Unicode代码点U+2013)。 - Peter Mortensen对于二维方法,您可以使用余弦定理和“方向”方法。
计算线段P3:P1顺时针扫过线段P3:P2的角度。
P1 P2
P3
double d = direction(x3, y3, x2, y2, x1, y1);
// c
int d1d3 = distanceSqEucl(x1, y1, x3, y3);
// b
int d2d3 = distanceSqEucl(x2, y2, x3, y3);
// a
int d1d2 = distanceSqEucl(x1, y1, x2, y2);
//cosine A = (b^2 + c^2 - a^2)/2bc
double cosA = (d1d3 + d2d3 - d1d2)
/ (2 * Math.sqrt(d1d3 * d2d3));
double angleA = Math.acos(cosA);
if (d > 0) {
angleA = 2.*Math.PI - angleA;
}
这个和以上所提到的建议具有同样数量的超越操作,只多出一个或更少的浮点操作。
它使用的方法是:
public int distanceSqEucl(int x1, int y1,
int x2, int y2) {
int diffX = x1 - x2;
int diffY = y1 - y2;
return (diffX * diffX + diffY * diffY);
}
public int direction(int x1, int y1, int x2, int y2,
int x3, int y3) {
int d = ((x2 - x1)*(y3 - y1)) - ((y2 - y1)*(x3 - x1));
return d;
}
两个向量的数量积(点积)可以让你得到它们之间夹角的余弦。
为了获得夹角的“方向”,你还应该计算叉积。它将让你检查(通过 z 坐标)夹角是否顺时针旋转,以及是否需要从 360 度中减去它。
Atan2(y, x)
因此,您可以轻松地计算相对于x轴的两个角度之间的差异:
angle = -(atan2(y2, x2) - atan2(y1, x1))
为什么它不被用作默认解决方案?atan2 不够高效。顶部答案的解决方案更好。在 C# 上的测试表明,这种方法的性能比 atan2 低19.6%(100,000,000 次迭代)。
虽然不是关键问题,但仍然令人不愉快。
因此,其他有用的信息包括:
内外角度之间的最小角度:
abs(angle * 180 / PI)
角度制的完整角:
angle = angle * 180 / PI
angle = angle > 0 ? angle : 360 - angle
或者
angle = angle * 180 / PI
if (angle < 0)
angle = 360 - angle;
if
语句,那么我认为没有真正通用的解决方案。__builtin_expect
函数。当你将它包装成这样的likely
和unlikely
宏(就像在Linux内核中一样)时,它会更加方便。#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
计算二维情况下,两个向量 (xa,ya) 和 (xb,yb) 之间的顺时针夹角公式。
Angle(vec.a-vec,b) =
pi()/2*((1 + sign(ya))*
(1 - sign(xa^2)) - (1 + sign(yb))*
(1 - sign(xb^2))) + pi()/4*
((2 + sign(ya))*sign(xa) - (2 + sign(yb))*
sign(xb)) + sign(xa*ya)*
atan((abs(ya) - abs(xa))/(abs(ya) + abs(xa))) - sign(xb*yb)*
atan((abs(yb) - abs(xb))/(abs(yb) + abs(xb)))
vec.a-vec,b
" 看起来非常不对称?你是指 "vec.a-vec.b
" 吗? - Peter Mortensen只需复制并粘贴此内容:
angle = (acos((v1.x * v2.x + v1.y * v2.y)/((sqrt(v1.x*v1.x + v1.y*v1.y) * sqrt(v2.x*v2.x + v2.y*v2.y))))/pi*180);
std::atan2()
? - user529758