同一平面内两个原点相同的三维矢量之间的符号角

我需要的是两个矢量 Va 和 Vb 在同一个3D 平面上,有符号的旋转角度,并且有相同的原点,知道:

  1. 包含两个向量的平面是任意的,并且不平行于 XY 或任何其他基数平面
  2. 是一架正常的飞机
  3. 两个向量和法线具有相同的原点 O = {0,0,0}
  4. Va-是测量 Vn 处左手旋转的参考

角度应该用这样的方法测量,如果平面是 XY 平面,Va 代表 X 轴的单位向量。

我想我应该用 Va 作为 X 轴,Vb 和 Vn 的交叉乘积作为 Y 轴,然后用2d 方法,比如 atan2()或者别的什么,来执行一种坐标空间转换。有什么想法吗?配方?

89255 次浏览

将一个向量交叉到另一个向量,并进行归一化得到单位向量。

两个向量之间夹角的正弦值等于交叉乘积的大小除以两个向量的大小:

Http://mathworld.wolfram.com/crossproduct.html

您可以使用 点积获得签名的角度。要得到角度的符号,取 Vn * (Va x Vb)的符号。在 XY 平面的特殊情况下,这仅仅减少到 Va_x*Vb_y - Va_y*Vb_x

设 θ 是两个向量之间的夹角,设 C = VA 与 Vb 交叉乘积,然后

Sin theta = length (C)/(length (Va) * 长度(Vb)

为了确定 θ 是正还是负,请记住 C 与 Va 和 Vb 垂直,指向 右手法则确定的方向。所以 C 和 Vn 是平行的。在你的例子中,如果 C 指向与 Vn 相同的方向,那么 θ 就是负的,因为你想要左旋。要快速检查 Vn 和 C 是否指向同一个方向,最简单的计算方法可能就是取它们的点乘; 如果是正的,它们指向同一个方向。

所有这些都源于 交叉积的基本属性。

利用两个向量的交叉乘积得到由两个向量构成的平面的法线。然后检查它和原始平面之间的点积,看看它们是否面向相同的方向。

angle = acos(dotProduct(Va.normalize(), Vb.normalize()));
cross = crossProduct(Va, Vb);
if (dotProduct(Vn, cross) < 0) { // Or > 0
angle = -angle;
}

你可以分两步来做:

  1. 确定两个向量之间的夹角

    Theta = acos (Va,Vb 的点积)。假设 Va,Vb 是标准化的。这将给出两个向量之间的最小夹角

  2. 确定角度的符号

    找到向量 V3 = VA,Vb 的叉积(顺序很重要)

    如果(V3,Vn 的点积)是负的,θ 是负的,否则 θ 是正的。

假设 Vx 是 x 轴,给定法向量 Vn,你可以通过交叉积得到 y 轴,你可以把向量 Vb 投影到 Vx 和 Vy (通过点积,你可以得到 Vb 投影到 Vx 和 Vy 的长度) ,给定平面上的(x,y)坐标,你可以用 atan2(y,x)得到范围[-pi,+ pi ]内的角度

Advanced Customer 提供了以下解决方案(最初是对问题的编辑) :

SOLUTION:


sina = |Va x Vb| / ( |Va| * |Vb| )
cosa = (Va . Vb) / ( |Va| * |Vb| )


angle = atan2( sina, cosa )


sign = Vn . ( Va x Vb )
if(sign<0)
{
angle=-angle
}

我目前使用的解决方案似乎在这里丢失了。 假设平面法线是标准化的(|Vn| == 1) ,符号角是简单的:

从 VA 到 Vb 的右旋转:

atan2((Va x Vb) . Vn, Va . Vb)

对于从 VA 到 Vb 的左旋转:

atan2((Vb x Va) . Vn, Va . Vb)

它返回范围[-PI,+ PI ]内的一个角度(或者任何可用的 atan2实现返回的值)。

.x分别是点乘和叉乘。

不需要显式的分支和除法/向量长度计算。

解释为什么这样做: 让 alpha 成为矢量(0 ° 到180 °)和 β 之间的直角,我们在 beta == alphabeta == 360° - alpha中寻找的角度(0 ° 到360 °)

Va . Vb == |Va| * |Vb| * cos(alpha)    (by definition)
== |Va| * |Vb| * cos(beta)     (cos(alpha) == cos(-alpha) == cos(360° - alpha)




Va x Vb == |Va| * |Vb| * sin(alpha) * n1
(by definition; n1 is a unit vector perpendicular to Va and Vb with
orientation matching the right-hand rule)


Therefore (again assuming Vn is normalized):
n1 . Vn == 1 when beta < 180
n1 . Vn == -1 when beta > 180


==>  (Va x Vb) . Vn == |Va| * |Vb| * sin(beta)

终于来了

tan(beta) = sin(beta) / cos(beta) == ((Va x Vb) . Vn) / (Va . Vb)

这是用来计算二维或三维向量 u,v 之间有符号角度的 Matlab 代码。代码是不言而喻的。符号约定是在 ix 和 iy ([1,0,0] ,[0,1,0])或 iy 和 iz ([0,1,0] ,[0,0,1])之间输出正的 + 90 °

function thetaDEG = angDist2Vecs(u,v)


if length(u)==3
%3D, can use cross to resolve sign
uMod = sqrt(sum(u.^2));
vMod = sqrt(sum(v.^2));
uvPr = sum(u.*v);
costheta = min(uvPr/uMod/vMod,1);


thetaDEG = acos(costheta)*180/pi;


%resolve sign
cp=(cross(u,v));
idxM=find(abs(cp)==max(abs(cp)));
s=sign(cp(idxM(1)));
if s < 0
thetaDEG = -thetaDEG;
end
elseif length(u)==2
%2D use atan2
thetaDEG = (atan2(v(2),v(1))-atan2(u(2),u(1)))*180/pi;
else
error('u,v must be 2D or 3D vectors');
end

对于那些使用 Python 的人来说,艾德里安 · 莱昂哈德提供的解决方案现在已经在 Scikit 空间库中实现了(最新版本很快就会出现)。寻找 Vector 同学们angle_signed_3d

这里有两个例子:

>>> import numpy as np
>>> from skspatial.objects import Vector
>>> np.degrees(Vector([1, 0, 0]).angle_signed_3d([0, -1, 0], direction_positive=[0, 0, 2]))
-90.0


>>> np.degrees(Vector([1, 0, 0]).angle_signed_3d([0, -1, 0], direction_positive=[0, 0, -5]))
90.0