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


78

我想找出2个向量(2D,3D)之间的顺时针角度。

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

您知道直接计算顺时针角度的方法吗?


6
为什么不使用std::atan2()

2
如何为3D中的矢量定义“顺时针角度”?
马丁R

@ H2CO3这似乎是2D角度的最佳解决方案。
Mircea Ispas 2012年

@MartinR“顺时针”是一个通用术语,它表示我要在特定的“方向”上而不是在最近的“方向”上倾斜。Nickolay O.在回答中指定了一种描述此“方向”的方式
Mircea Ispas 2012年

4
@Felics:“顺时针”在2D中定义良好,但在3D中却没有定义。检查叉积的z坐标(如Nickolay O.的回答)在3D中表示:“对于从x / y平面上俯视的观察者,应顺时针旋转”。
马丁·R

Answers:


190

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指向下方,这意味着您将获得顺时针角度的正号。如果坐标系的方向是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))

平面嵌入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的倍数。因此,点积将基本上测量该矢量的长度,但要附加正确的符号。


4
投票赞成-我不能不理会其他答案是否正确,您的答案最清晰,最易读,因此对我有帮助。
2013年

2
对于2D,我得到(0,180)和(-180,0)。可以检查结果是否为负,然后添加360以得到一个不错的顺时针角度(例如,如果是-180,则在180中添加360结果,对于-90,则在270中添加360结果,等等)。不知道这仅仅是我的计算还是qAtan2(y, x)Qt框架的实现,但是如果有人和我有同样的问题,这可能会有所帮助。
rbaleksandar

8
@rbaleksandar:atan2通常在[-180°,180°]范围内。为了得到[0°,360°]无的情况下区别,一个可以取代atan2(y,x)atan2(-y,-x) + 180°
MvG

1
@jianz:角度是相对于坐标系的正角。如果x正确且y向上,则角度为逆时针方向。如果y向下,则为顺时针方向。大多数计算机图形环境都使用后者。如果要反转方向,只需更改输入的顺序,这将翻转行列式的符号。
MvG

3
Noooooo绝对不会使用点产品的acos!从数学上讲这是正确的,但实际上却非常不准确。您可以用另一个atan2(det,dot)替换3d方法;在这种情况下,det将是叉积的长度。
唐·哈奇

5

要计算角度,您只需调用atan2(v1.s_cross(v2), v1.dot(v2))2D情况。哪里s_cross是叉积的标量模拟(平行四边形的有符号面积)。对于2D情况,将是楔形生产。对于3D情况,您需要定义顺时针旋转,因为从平面的一侧是顺时针方向是一个方向,从平面的另一侧是另一个方向=)

编辑:这是逆时针角度,顺时针角度正好相反


v1.cross(v2)是向量,不是标量,不能像这样使用。Nickolay O.在他的答案中描述了如何找出角度的“方向”。一种获得2D角度的方法是:angle = atan2f(v2.x,v2.y)-atan2f(v1.x,v1.y)
Mircea Ispas

1
@Felics在2D交叉生产中通常表示楔形生产en.wikipedia.org/wiki/Wedge_product这是平行四边形的有符号区域。对于2D情况,公式绝对正确,因为点= | v1 || v2 | * cos和叉= | v1 || v2 | sin。这就是为什么atan2在整个圆范围内给出正确角度的原因。就像我在3D情况下所说的那样,您需要做一些假设以扩大顺时针方向
kassak 2012年

1
@Felics:请注意,atan2f将y坐标作为第一个参数,因此应为angle = atan2f(v2.y, v2.x) - atan2f(v1.y, v1.x)
马丁R

1
@kassak:你可以取代cross,并dot通过在2D情况下的明确公式,这将消除所有疑虑cross返回三维矢量(但是这只是一个建议,你可以忽略)。-否则,我喜欢这种解决方案,因为它只需要一个atan2f函数调用。
马丁R

@Martin R感谢您的良好建议。我进行了一些更正,以使公式的含义更清楚
kassak 2012年

4

这个答案与MvG的答案相同,但解释不同(这是我努力理解MvG解决方案为何有效的结果)。我将其发布给其他人发现它很有帮助的机会。

相对于给定法线()的视点,thetax到的逆时针角度为yn||n|| = 1

atan2(dot(n,cross(x,y)),dot(x,y))

(1)= atan2(|| x || || y ||sinθ,|| x || || y ||cosθ)

(2)= atan2(sin(theta),cos(theta))

(3)= x轴与向量之间的逆时针角度(cos(theta),sin(theta))

(4)=θ

其中||x||表示的大小x

步骤(1)注意

cross(x,y)= || x || || y || sin(θ)n,

所以

点(n,交叉(x,y))

=点(n,|| x || || y ||sinθn)

= || x || || y || sin(θ)点(n,n)

等于

|| x || || y || 罪恶(theta)

如果||n|| = 1

步骤(2)从的定义开始atan2,注意atan2(cy, cx) = atan2(y,x),其中c是标量。步骤(3)从的定义开始atan2。步骤(4)从的几何定义如下cossin


2

两个向量的标量(点)乘积可让您获得它们之间角度的余弦值。要获得角度的“方向”,您还应该计算叉积,它将使您(通过z坐标)检查角度是否为顺时针方向(即,是否应从360度中提取角度)。


1
即使这是正确的,这也是我要避免的-计算一些值并确定计算出的值代表我的角度还是我的角度的补码。
Mircea Ispas 2012年

我想知道这是否可能:)如果有(也许!)更好的方法,为什么要使用一些效率低下的方法。如果没有更好的方法,我将采用“标准”的方法,但是要求更好总是一件好事!
Mircea Ispas 2012年

实际上,标准方法并不总是有效的)
Nickolay Olshevsky 2012

@NickolayOlshevsky通过z坐标检查到底是什么意思,我该怎么做?
Ogen 2014年

据我所知,您应该检查z坐标的符号。
Nickolay Olshevsky 2014年

1

对于2D方法,可以使用余弦定律和“方向”方法。

要计算段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;
    }

This has the same number of transcendental

上面建议的操作,只有一个或多个浮点运算。

它使用的方法是:

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

0

如果以“直接方式”表示避免 if声明,那么我认为没有真正通用的解决方案。

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

另外,如果您的问题详细信息使得在角度方向上肯定有更多可能的结果,那么您可以使用编译器的内置函数将此信息提供给编译器,从而可以更有效地优化分支。例如,在gcc的情况下,这就是__builtin_expect功能。将其包装到诸如此类likelyunlikely宏中(例如在Linux内核中)时,使用起来会更方便:

#define likely(x)      __builtin_expect(!!(x), 1)
#define unlikely(x)    __builtin_expect(!!(x), 0)

-1

在两个向量xa,ya和xb,yb之间的顺时针2D情况的公式。

角度(vec.a-vec,b)= pi()/ 2 *((1 + sign(ya))*(1-sign(xa ^ 2))-(1 + sign(yb))*(1-符号(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)))

-2

只需复制并粘贴。

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

别客气 ;-)


5
尽管此代码段可以回答问题,但包括解释其为何以及如何帮助解决问题的说明,可以提高回答的质量和寿命,尤其是对于像这样的较老问题。请参阅“我如何写一个好的答案?”
slothiful
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.