我想找出 2 个向量(2D,3D)之间的顺时针角度。
点积的经典方式给了我内角(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)
该角度的方向与坐标系的方向相匹配。在左手坐标系中,即x指向右和y下,这在计算机图形学中很常见,这意味着你会得到一个顺时针角度的正号。如果坐标系的方向是数学的,y向上,你会得到逆时针角度,这是数学中的惯例。更改输入的顺序将更改符号,因此如果您对符号不满意,只需交换输入即可。
在 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))
一种特殊情况是您的向量不是任意放置的,而是位于具有已知法线向量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的倍数。因此,点积将基本上测量该向量的长度,但附加了正确的符号。
要计算角度,您只需要调用atan2(v1.s_cross(v2), v1.dot(v2))
2D 案例。哪里s_cross
是交叉产生的标量模拟(平行四边形的符号区域)。对于将是楔形生产的 2D 案例。对于 3D 情况,您需要定义顺时针旋转,因为从平面的一侧顺时针是一个方向,从平面的另一侧是另一个方向 =)
编辑:这是逆时针角度,顺时针角度正好相反
这个答案与 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|| ||是|| sin(θ) n,
所以
点(n,交叉(x,y))
= 点(n, ||x|| ||y|| sin(theta) n)
= ||x|| ||是|| 罪(θ)点(n,n)
这等于
||x|| ||是|| 罪(θ)
如果||n|| = 1
.
步骤 (2) 遵循 的定义atan2
,注意atan2(cy, cx) = atan2(y,x)
, 其中c
是标量。步骤 (3) 遵循 的定义atan2
。步骤 (4) 遵循 和 的几何cos
定义sin
。
两个向量的标量(点)积可让您获得它们之间角度的余弦。要获得角度的“方向”,您还应该计算叉积,它将让您检查(通过 z 坐标)角度是否顺时针(即是否应该从 360 度提取)。
由于最简单和最优雅的解决方案之一隐藏在评论中,我认为将其作为单独的答案发布可能会很有用。
acos
可能会导致非常小的角度不准确,因此atan2
通常是首选。对于 3D 案例:
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)
对于 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;
}
如果通过“直接方式”您的意思是避免if
声明,那么我认为没有一个真正通用的解决方案。
但是,如果您的特定问题允许在角度离散化中失去一些精度并且您可以在类型转换中失去一些时间,您可以将 [-pi,pi) phi 角的允许范围映射到某些有符号整数类型的允许范围. 然后您将免费获得互补性。但是,我在实践中并没有真正使用过这个技巧。最有可能的是,浮点到整数和整数到浮点转换的开销将超过直接性的任何好处。当这个角度计算完成很多时,最好将您的优先级设置为编写可自动矢量化或可并行化的代码。
此外,如果您的问题详细信息使得角度方向的结果更可能确定,那么您可以使用编译器的内置函数将此信息提供给编译器,以便更有效地优化分支。例如,在 gcc 的情况下,这就是__builtin_expect
函数。likely
当您将它包装到此类和unlikely
宏中时(如在 linux 内核中),使用起来会更方便:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
2 个向量 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)))
只需复制并粘贴即可。
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);
别客气 ;-)