计算两个向量之间顺时针角度的直接方法

Direct way of computing the clockwise angle between two vectors

提问人:Mircea Ispas 提问时间:12/28/2012 最后编辑:Peter MortensenMircea Ispas 更新时间:11/20/2023 访问量:112175

问:

我想找出两个向量(二维或三维)之间的顺时针角度。

点积的经典方法给了我内角(0-180 度),我需要使用一些 if 语句来确定结果是我需要的角度还是它的补码。

有没有直接计算顺时针角度的方法?

C++ 数学 角度

评论

9赞 12/28/2012
为什么不使用?std::atan2()
5赞 Martin R 12/28/2012
如何定义 3D 矢量的“顺时针角度”?
1赞 kassak 12/28/2012
@Felics读了我的回答。顺时针方向在 3d 中没有明确定义。它是平面项
5赞 Martin R 12/28/2012
@Felics:“顺时针”在 2D 中定义明确,但在 3D 中则不然。检查叉积的 z 坐标(如 Nickolay O. 的答案)意味着在 3D 中:“对于从上方观察 x/y 平面的观察者来说,顺时针方向。
2赞 kassak 12/28/2012
@Felics 另外,我应该注意,由于毛球定理,您无法连续定义 3D 顺时针角度 en.wikipedia.org/wiki/Hairy_ball_theorem 你总是有一对向量,其中一个的 epsilon 运动将导致时钟的瞬间切换,从而产生角度符号

答:

2赞 Nickolay Olshevsky 12/28/2012 #1

两个向量的标量(点)积可以让你得到它们之间角度的余弦

要获得角度的“方向”,您还应该计算叉积。它可以让您检查(通过 z 坐标)角度是否顺时针(即,您是否应该从 360 度提取它)。

评论

1赞 Mircea Ispas 12/28/2012
即使这是正确的,这也是我想避免的 - 计算一些值并确定计算值是否代表我的角度或我的角度的补码。
0赞 Mircea Ispas 12/28/2012
我想知道这是否可行:) 如果有(也许!)更好的方法,为什么要使用一些不熟练的做事方式。如果没有更好的方法,我会选择“标准”的东西,但要求更好总是好的!
0赞 Nickolay Olshevsky 12/29/2012
实际上,标准方法并不总是有效的)
0赞 Ogen 4/12/2014
@NickolayOlshevsky 您所说的通过 z 坐标检查到底是什么意思,我该怎么做?
0赞 Nickolay Olshevsky 4/13/2014
据我所知,你应该检查z坐标的标志。
5赞 kassak 12/28/2012 #2

要计算角度,您只需要调用二维情况。其中是交叉生产的标量类似物(平行四边形的符号面积)。atan2(v1.s_cross(v2), v1.dot(v2))s_cross

对于二维情况,这将是楔形生产。

对于三维情况,您需要定义顺时针旋转,因为从平面的一侧顺时针是一个方向,从平面的另一侧是另一个方向=)

这是逆时针角度,顺时针角度正好相反。

评论

0赞 Mircea Ispas 12/28/2012
v1.cross(v2) 是一个向量,而不是标量,不能这样使用。Nickolay O. 在他的回答中描述了如何找出角度的“方向”。获取 2D 角度的一种方法是:angle = atan2f(v2.x, v2.y) - atan2f(v1.x, v1.y)
2赞 kassak 12/28/2012
@Felics 在 2D 交叉生产中,通常意味着楔形生产 en.wikipedia.org/wiki/Wedge_product 即平行四边形的有符号面积。对于 2D 情况,该公式是绝对正确的,因为它 dot = |v1||v2|*cos 和 cross = |v1||v2|sin。这就是为什么 atan2 在整个圆范围内给出正确角度的原因。正如我所说,对于 3d 情况,您需要做出一些假设才能顺时针方向进行一些扩展
1赞 Martin R 12/28/2012
@Felics:请注意,将 y 坐标作为第一个参数,因此它应该是 .atan2fangle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x)
1赞 Martin R 12/28/2012
@kassak:您可以替换 和 替换为 2D 情况下的显式公式,这将消除有关返回 3D 向量的所有疑虑(但这只是一个建议,您可以忽略)。- 否则我喜欢这个解决方案,因为它只需要一个函数调用。crossdotcrossatan2f
0赞 kassak 12/28/2012
@Martin R 感谢您的良好建议。我做了一些更正,使公式的含义更清晰
0赞 Vadim Khotilovich 12/28/2012 #3

如果“直接方式”是指避免这种说法,那么我认为没有一个真正通用的解决方案。if

但是,如果您的特定问题允许在角度离散化中损失一些精度,并且您可以在类型转换中浪费一些时间,则可以将 [-pi,pi] 允许的 phi 角度范围映射到某些有符号整数类型的允许范围。然后您将免费获得互补性。但是,我在实践中并没有真正使用过这个技巧。最有可能的是,浮点数到整数和整数到浮点数转换的费用将超过直接性的任何好处。当这种角度计算被大量完成时,最好将优先级设置为编写可自动矢量化或可并行化的代码。

此外,如果您的问题细节使得角度方向有明确的更可能的结果,那么您可以使用编译器的内置函数向编译器提供此信息,以便它可以更有效地优化分支。例如,在 GCC 的情况下,这就是函数。当你把它包装成这样的宏(就像在 Linux 内核中一样)时,使用起来会更方便一些:__builtin_expectlikelyunlikely

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)
277赞 MvG 5/14/2013 #4

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π],您需要 [-π, π] 中的有符号角,请改用 .这个技巧实际上并不特定于这个问题,但可以应用于大多数使用的情况。请记住检查您的交易是以度数还是弧度为单位,并根据需要在这些交易之间进行转换。atan2atan2(-det, -dot) + πatan2atan2(-det, -dot) - πatan2atan2

评论

4赞 Excrubulent 7/18/2013
投赞成票 - 我懒得弄清楚其他答案是否正确,你的答案是最清晰、最易读的,所以它是帮助我的答案。
2赞 rbaleksandar 12/14/2016
对于 2D,我得到 (0,180) 和 (-180,0)。当结果为负数时,可以检查并添加 360 以获得一个很好的顺时针角度(例如,如果是 -180,则在 180 中添加 360 个结果,对于 -90,在 270 中添加 360 个结果,等等)。不知道这只是我的计算还是(来自 Qt 框架)的实现,但如果有人和我有同样的问题,这可能会有所帮助。qAtan2(y, x)
17赞 MvG 12/14/2016
@rbaleksandar:通常在[-180°,180°]范围内。要得到 [0°,360°] 而不区分大小写,可以用 替换 。atan2atan2(y,x)atan2(-y,-x) + 180°
3赞 Don Hatch 12/13/2018
不,永远不要服用 DOT 产品的 ACO!这在数学上是正确的,但在实践中却非常不准确。你可以用另一个 atan2(det,dot) 替换你的 3d 方法;在这种情况下,det 将是叉积的长度。
2赞 Don Hatch 9/11/2021
@N4ppeL 有关点积 acos 不良行为的更多信息,请尝试以下(在 2 个不同的网站上提出的问题,具有不同的答案和参考):math.stackexchange.com/questions/1143354/... scicomp.stackexchange.com/questions/27689/......
2赞 nichole 7/31/2016 #5

对于二维方法,您可以使用以下定律 余弦和“方向”方法。

计算线段 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;
}
6赞 sircolinton 11/24/2016 #6

这个答案与 MvG 的相同,但解释不同(这是我努力理解为什么 MvG 的解决方案有效的结果)。

从 到 的逆时针角度相对于其给定法线 () 的视点由下式给出thetaxyn||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)遵循和的几何定义。atan2atan2(cy, cx) = atan2(y,x)catan2cossin

-1赞 theodore panagos 11/23/2018 #7

两个向量 (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)))

评论

1赞 gdbdable 6/17/2021
它是什么语言?
0赞 Peter Mortensen 4/18/2023
为什么是“vec.a-vec,b(看起来很奇怪的不对称)?你的意思是“vec.a-vec.b吗?
0赞 Peter Mortensen 4/18/2023
好的,OP已经离开了大楼:“最后一次出现是在3年多前”
-4赞 Red 6/29/2019 #8

只需复制并粘贴以下内容:

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);

评论

7赞 slothiful 6/29/2019
尽管此代码片段可能会回答问题,但包括对它为什么以及如何帮助解决问题的解释可以提高答案的质量和寿命,尤其是对于此类较旧的问题。请参阅“如何写出一个好的答案?
4赞 Carlos Borau 5/27/2021 #9

由于最简单、最优雅的解决方案之一隐藏在其中一条评论中,我认为将其作为单独的答案发布可能会很有用。

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)

评论

2赞 N4ppeL 9/1/2021
感谢。链接到相应的答案或至少引用用户将是一件好事
0赞 Peter Mortensen 4/18/2023
由于 EN DASHes,这将导致编译错误。类似“”的东西。EN DASH(Unicode 码位 U+2013)可以在大多数文本编辑器中以正则表达式模式进行搜索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}
0赞 Peter Mortensen 4/18/2023
cont' - 这三个数字是 U+2013 的 UTF-8 字节序列的八进制数(十六进制0xE2 0x80 0x93)
0赞 Peter Mortensen 4/18/2023
此类错误的规范是编译错误:程序中的杂散“\302”等
0赞 Peter Mortensen 4/18/2023
这是从哪里复制的?一些网页?
0赞 Oko Lenmi 10/9/2022 #10

对于二维情况,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;