提问人:Mircea Ispas 提问时间:12/28/2012 最后编辑:Peter MortensenMircea Ispas 更新时间:11/20/2023 访问量:112175
计算两个向量之间顺时针角度的直接方法
Direct way of computing the clockwise angle between two vectors
问:
我想找出两个向量(二维或三维)之间的顺时针角度。
点积的经典方法给了我内角(0-180 度),我需要使用一些 if 语句来确定结果是我需要的角度还是它的补码。
有没有直接计算顺时针角度的方法?
答:
两个向量的标量(点)积可以让你得到它们之间角度的余弦。
要获得角度的“方向”,您还应该计算叉积。它可以让您检查(通过 z 坐标)角度是否顺时针(即,您是否应该从 360 度提取它)。
评论
要计算角度,您只需要调用二维情况。其中是交叉生产的标量类似物(平行四边形的符号面积)。atan2(v1.s_cross(v2), v1.dot(v2))
s_cross
对于二维情况,这将是楔形生产。
对于三维情况,您需要定义顺时针旋转,因为从平面的一侧顺时针是一个方向,从平面的另一侧是另一个方向=)
这是逆时针角度,顺时针角度正好相反。
评论
atan2f
angle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x)
cross
dot
cross
atan2f
如果“直接方式”是指避免这种说法,那么我认为没有一个真正通用的解决方案。if
但是,如果您的特定问题允许在角度离散化中损失一些精度,并且您可以在类型转换中浪费一些时间,则可以将 [-pi,pi] 允许的 phi 角度范围映射到某些有符号整数类型的允许范围。然后您将免费获得互补性。但是,我在实践中并没有真正使用过这个技巧。最有可能的是,浮点数到整数和整数到浮点数转换的费用将超过直接性的任何好处。当这种角度计算被大量完成时,最好将优先级设置为编写可自动矢量化或可并行化的代码。
此外,如果您的问题细节使得角度方向有明确的更可能的结果,那么您可以使用编译器的内置函数向编译器提供此信息,以便它可以更有效地优化分支。例如,在 GCC 的情况下,这就是函数。当你把它包装成这样的宏(就像在 Linux 内核中一样)时,使用起来会更方便一些:__builtin_expect
likely
unlikely
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
2D案例
就像点积与角度的余弦成正比一样,行列式与其正弦成正比。因此,您可以像这样计算角度:
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)
此角度的方向与坐标系的方向相匹配。在左手坐标系中,即 x 指向右边,y 向下,这在计算机图形学中很常见,这意味着您会得到顺时针角度的正号。如果坐标系的方向是数学方向,并且 y 向上,则按照数学惯例,您将获得逆时针角度。更改输入的顺序将更改符号,因此如果您对符号不满意,只需交换输入即可。
3D案例
在 3D 中,两个任意放置的矢量定义它们自己的旋转轴,垂直于两者。该旋转轴不具有固定方向,这意味着您也无法唯一固定旋转角度的方向。一个常见的约定是让角度始终为正,并以适合正角度的方式定向轴。在这种情况下,归一化向量的点积足以计算角度。
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))
请注意,一些注释和替代答案建议不要出于数字原因使用 aco
,特别是如果要测量的角度很小。
嵌入 3D 的平面
一种特殊情况是,向量不是任意放置的,而是位于具有已知法向向量 n 的平面内。然后旋转轴也将在方向 n 上,而 n 的方向将固定该轴的方向。在这种情况下,您可以调整上面的 2D 计算,包括 n 行列式,使其大小为 3×3。
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)
要做到这一点,一个条件是法向量 n 具有单位长度。如果没有,则必须对其进行规范化。
作为三重产品
这个决定因素也可以表示为三乘积,正如@Excrubulent在建议的编辑中指出的那样。
det = n · (v1 × v2)
在某些 API 中,这可能更容易实现,并且对这里发生的事情给出了不同的视角:叉积与角度的正弦成正比,并且垂直于平面,因此是 n 的倍数。因此,点积基本上将测量该向量的长度,但附有正确的符号。
范围 0 – 360°
大多数实现将返回 [-π, π] 的角度(以弧度为单位),即 [-180°, 180°] 度。如果您需要正角 [0, 2π] 或 [0°, 360°],您只需将 2π 添加到您得到的任何负结果中即可。或者您可以避免大小写区分并无条件使用。如果您处于需要相反校正的罕见设置中,即 返回非负 [0, 2π],您需要 [-π, π] 中的有符号角,请改用 .这个技巧实际上并不特定于这个问题,但可以应用于大多数使用的情况。请记住检查您的交易是以度数还是弧度为单位,并根据需要在这些交易之间进行转换。atan2
atan2(-det, -dot) + π
atan2
atan2(-det, -dot) - π
atan2
atan2
评论
qAtan2(y, x)
atan2
atan2(y,x)
atan2(-y,-x) + 180°
对于二维方法,您可以使用以下定律 余弦和“方向”方法。
计算线段 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;
}
这个答案与 MvG 的相同,但解释不同(这是我努力理解为什么 MvG 的解决方案有效的结果)。
从 到 的逆时针角度相对于其给定法线 () 的视点由下式给出theta
x
y
n
||n|| = 1
atan2( 点(n, 交叉(x,y)), 点(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) = θ
其中表示 的大小。||x||
x
步骤(1)紧随其后,指出
交叉(x,y) = ||x||||y||sin(theta) n,
等等
点(n, 十字(x,y))
= 点(n, ||x||||y||sin(theta) n)
= ||x||||y||sin(theta) 点(n, n)
等于
||x||||y||sin(theta)
如果。||n|| = 1
步骤 (2) 遵循 的定义,注意 ,其中 是标量。步骤(3)遵循 的定义。步骤(4)遵循和的几何定义。atan2
atan2(cy, cx) = atan2(y,x)
c
atan2
cos
sin
两个向量 (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
”吗?
只需复制并粘贴以下内容:
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);
评论
由于最简单、最优雅的解决方案之一隐藏在其中一条评论中,我认为将其作为单独的答案发布可能会很有用。
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}
对于二维情况,atan2 可以轻松计算 (1, 0) 向量(x 轴)与其中一个向量之间的角度。
公式为:
Atan2(y, x)
因此,您可以轻松计算两个角度相对于 x 轴的差值:
angle = -(atan2(y2, x2) - atan2(y1, x1))
为什么不将其用作默认解决方案?ATAN2 效率不够高。来自最高答案的解决方案更好。在 C# 上的测试表明,该方法的性能降低了 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;
评论
std::atan2()