任意轴周围的圆形旋转

32

我正在编写《星际争霸2》的自定义地图,但在3D数学方面遇到了一些问题。目前,我正尝试创建和围绕由x、y和z给定的任意轴旋转一个点(xyz向量已归一化)。

我已经尝试了很多方法并阅读了许多相关资料,但是我无法正确地理解它的工作原理。我的当前脚本(您可能不知道该语言,但它并不特别)是为了解决这个问题而花费数小时后得出的(不能正确工作):

    point CP;
fixed AXY;
point D;
point DnoZ;
point DXY_Z;
fixed AZ;
fixed LXY;
missile[Missile].Angle = (missile[Missile].Angle + missile[Missile].Acceleration) % 360.0;
missile[Missile].Acceleration += missile[Missile].AirResistance;
if (missile[Missile].Parent > -1) {
    D = missile[missile[Missile].Parent].Direction;
    DnoZ = Point(PointGetX(D),0.0);
    DXY_Z = Normalize(Point(SquareRoot(PointDot(DnoZ,DnoZ)),PointGetHeight(D)));
    AZ = MaxF(ACos(PointGetX(DXY_Z)),ASin(PointGetY(DXY_Z)))+missile[Missile].Angle;
    DnoZ = Normalize(DnoZ);
    AXY = MaxF(ACos(PointGetX(DnoZ)),ASin(PointGetY(DnoZ)));
    CP = Point(Cos(AXY+90),Sin(AXY+90));
    LXY = SquareRoot(PointDot(CP,CP));
    if (LXY > 0) {
        CP = PointMult(CP,Cos(AZ)/LXY);
        PointSetHeight(CP,Sin(AZ));
    } else {
        CP = Point3(0.0,0.0,1.0);
    }
} else {
    CP = Point(Cos(missile[Missile].Angle),Sin(missile[Missile].Angle));
}
missile[Missile].Direction = Normalize(CP);
missile[Missile].Position = PointAdd(missile[Missile].Position,PointMult(missile[Missile].Direction,missile[Missile].Distance));

我真的无法理解这个数学问题。如果你能用简单的语言解释一下那就是最好的解决办法了,提供一些代码片段也可以(但不是特别有帮助,因为我计划未来会做更多的三维图形相关工作)。

7个回答

35

我也找到了那个矩阵,但我不知道如何使用它。而且我也不理解如何创建我想要旋转的点。谢谢你的帮助。 - Alexander
经过更深入的思考,旋转并不是问题,我认为我已经理解了旋转。我无法做到的是找到一个垂直于我的轴的向量,至少没有随着轴旋转的向量(轴每0.0625秒改变一次,我希望围绕它旋转并保持恒定距离)。 - Alexander
我意识到使用上向量可能有很多原因,但我发布的矩阵包含了完成旋转所需的所有必要信息。您是否有要旋转的点的坐标?您是否有以(x,y,z)形式表示的轴?您是否有θ并且可以计算sin(θ)和cos(θ)?如果是这样,那么这个公式应该足以实现您想要的效果。 - Gravity
不,我没有这些坐标。我希望它们被计算出来并旋转一个角度。而且我不知道如何计算一个点,使其始终保持相对于我的轴线方向不变(如果我转动轴线并显示该点,则应该看起来像是连接的)。 - Alexander
我并不是说你在编译时就已经拥有了它们。我的意思是,一旦你拥有了这些点,应用变换只需要编写一个矩阵乘法的代码,不需要任何额外的信息。 - Gravity
显示剩余2条评论

16
一种有用的进行旋转的方法是使用四元数。实际上,我发现它们更容易使用,并且有避免万向锁的额外好处。 这里有一个很好的教程,解释了为什么以及如何将其用于绕任意轴旋转(这是对用户问题的回答)。它有点高级,适合初学者,所以我建议从那里开始。 更新以避免链接失效 来自链接网站的文本: 显然,绕过原点和三维空间中单位球面上的一点(a,b,c)的轴旋转是一个线性变换,因此可以通过矩阵乘法表示。我们将介绍一种非常简洁的确定该矩阵的方法,但为了欣赏公式的紧凑性,最好先做一些说明。
三维旋转是一种非常特殊的线性变换,其中最重要的特点是保持向量长度和向量之间的角度(当两个向量被旋转时)。这样的变换被称为“正交变换”,它们由正交矩阵表示:
M M' = I

我们方便地用“'”来表示转置。换句话说,正交矩阵的转置是其逆矩阵。

考虑定义变换所需的数据。您已经给出了旋转轴的符号表示,ai + bj + ck,方便地假定为单位向量。唯一的其他数据是旋转角度,由于没有更自然的字符,我将其表示为r(表示旋转?),并且我们将假定以弧度给出。

现在,即使在正交变换中,旋转也是有点特殊的,事实上,由于它们具有保持“方向”的属性,它们也被称为特殊正交变换(或矩阵)。将它们与反射进行比较,后者也可以保持长度和角度,您会发现保持方向(或“左右手性”,如果您愿意)的几何特征在矩阵的行列式中具有数值对应关系。旋转矩阵的行列式为1,而反射矩阵的行列式为-1。结果表明,两个旋转的乘积(或组合)仍然是一个旋转,这与乘积的行列式等于行列式的乘积(或旋转的情况下为1)的事实相符。

现在我们可以描述一种逐步的方法,来构建所需的矩阵(在我们快速跳到答案之前!)。首先考虑旋转单位向量的步骤:
u = ai + bj + ck

希望将其与“标准”单位向量之一重合,可能是k(正z轴)。现在我们知道如何围绕z轴旋转;这只是在x、y坐标上进行通常的2x2变换的问题:

       cos(r) sin(r)   0
M  =  -sin(r) cos(r)   0
         0      0      1

最后,我们需要“撤销”将u旋转到k的初始旋转,这很容易实现,因为该转换的逆转换(我们回想起)由矩阵转置表示。换句话说,如果矩阵R表示将u旋转到k的旋转,则R'将k旋转到u,并且我们可以像这样写出变换的组合:
R' M R

很容易验证这个矩阵乘积,当它与向量u相乘时,会得到u本身:

R' M R u = R' M k = R' k = u

因此,这确实是绕由u定义的轴旋转。
这种表达式的一个优点是,它清晰地将M对角度r的依赖性与Q和Q'对“轴”向量u的依赖性分离开来。然而,如果我们需要详细进行计算,我们显然需要进行大量的矩阵乘法。
所以,来看一下快捷方式。当所有的尘埃落定时,旋转之间的乘法等同于单位四元数的乘法。四元数,如果你以前没有见过,是复数的四维推广。它们是由威廉·哈密顿(William Hamilton)在1843年“发明”的:
[威廉·罗恩·哈密顿] http://www-gap.dcs.st-and.ac.uk/~history/Mathematicians/Hamilton.html 今天的三维图形程序员非常感激他。
每个单位四元数q=q0+q1*i+q2*j+q3*k都可以定义一个旋转矩阵。
     (q0² + q1² - q2² - q3²)      2(q1q2 - q0q3)          2(q1q3 + q0q2)

Q  =      2(q2q1 + q0q3)     (q0² - q1² + q2² - q3²)      2(q2q3 - q0q1)

          2(q3q1 - q0q2)          2(q3q2 + q0q1)     (q0² - q1² - q2² + q3²)

验证Q是否为正交矩阵,即Q Q' = I,实质上意味着Q的行形成一个标准正交基。例如,第一行应该长度为1:
(q0² + q1² - q2² - q3²)² + 4(q1q2 - q0q3)² + 4(q1q3 + q0q2)²

  = (q0² + q1² - q2² - q3²)² + 4(q1q2)² + 4(q0q3)² + 4(q1q3)² + 4(q0q2)²

  = (q0² + q1² + q2² + q3²)²

  =  1

前两行的点积应该为零:

  [ (q0² + q1² - q2² - q3²), 2(q1q2 - q0q3), 2(q1q3 + q0q2) ]

   * [ 2(q2q1 + q0q3), (q0² - q1² + q2² - q3²), 2(q2q3 - q0q1) ]

 = 2(q0² + q1² - q2² - q3²)(q2q1 + q0q3)

   + 2(q1q2 - q0q3)(q0² - q1² + q2² - q3²)

   + 4(q1q3 + q0q2)(q2q3 - q0q1)

 = 4(q0²q1q2 + q1²q0q3 - q2²q0q3 - q3²q2q1)

   + 4(q3²q1q2 - q1²q0q3 + q2²q0q3 - q0²q2q1)

 =  0

一般情况下,可以证明det(Q) = 1,因此Q确实是一个旋转。

但是,Q的旋转轴在哪个方向?旋转角度是多少?给定角度r和单位向量:

u = ai + bj + ck

如前所述,相应的四元数为:

q = cos(r/2) + sin(r/2) * u

  = cos(r/2) + sin(r/2) ai + sin(r/2) bj + sin(r/2) ck

因此,使用:
q0 = cos(r/2), q1 = sin(r/2) a, q2 = sin(r/2) b, q3 = sin(r/2) c,

我们能够得到所需的属性,即Q乘以u会“固定”它。
Q u = u

不要费力地通过冗长的代数计算,让我们来做一个简单的例子。

u = 0i + 0.6j + 0.8k成为我们的单位向量,r = pi成为我们的旋转角度。

那么四元数就是:

q = cos(pi/2) + sin(pi/2) * u

  = 0 + 0i + 0.6j + 0.8k

和旋转矩阵:

        -1     0     0

Q =      0  -0.28  0.96

         0   0.96  0.28

在这个具体的案例中,很容易验证 Q Q' = I 并且 det(Q) = 1。
同时我们计算出:
Q u = [ 0, -0.28*0.6 + 0.96*0.8, 0.96*0.6 + 0.28*0.8 ]'

    = [ 0, 0.6, 0.8 ]'

    =  u

例如,单位向量u定义了旋转轴,因为它被Q“固定”。

最后,我们通过考虑Q如何作用于沿着正x轴方向的单位向量(与u垂直)来说明旋转角度为pi(或180度):

i + 0j + 0k,  or as a vector, [ 1, 0, 0 ]'

接下来是关于编程的内容,Q [ 1, 0, 0 ]' = [-1, 0, 0 ]'表示将向量[1,0,0]'绕着向量u旋转π角度。

关于用四元数表示旋转的方法和其他表示方法(以及它们的优点),可以参考以下链接:

[三维旋转的表示方法]http://gandalf-library.sourceforge.net/tutorial/report/node125.html

总结

给定弧度r和单位向量u=ai+bj+ck或[a,b,c]', 定义如下:

q0 = cos(r/2),  q1 = sin(r/2) a,  q2 = sin(r/2) b,  q3 = sin(r/2) c

并从这些值构建旋转矩阵:

     (q0² + q1² - q2² - q3²)      2(q1q2 - q0q3)          2(q1q3 + q0q2)

Q  =      2(q2q1 + q0q3)     (q0² - q1² + q2² - q3²)      2(q2q3 - q0q1)

          2(q3q1 - q0q2)          2(q3q2 + q0q1)     (q0² - q1² - q2² + q3²)

使用 Q 进行乘法,然后实现所需的旋转,特别地:
Q u = u

5

要进行3D旋转,只需要将旋转点偏移到原点,并依次绕每个轴旋转,存储每个轴旋转之间的结果以用于下一次旋转操作。算法如下:

将点偏移到原点。

Point of Rotation = (X1, Y1, Z1)
Point Location    = (X1+A, Y1+B, Z1+C)

(Point Location - Point of Rotation) = (A, B, C).

绕Z轴进行旋转操作。

    A' = A*cos ZAngle - B*sin ZAngle
    B' = A*sin ZAngle + B*cos ZAngle
    C' = C.

接下来,绕Y轴进行旋转。

    C'' = C'*cos YAngle - A'*sin YAngle
    A'' = C'*sin YAngle + A'*cos YAngle
    B'' = B'   

现在进行最后一次旋转,绕X轴旋转。
    B''' = B''*cos XAngle - C''*sin XAngle
    C''' = B''*sin XAngle + C''*cos XAngle
    A''' = A''

最后,将这些值添加回旋转的原始点。
Rotated Point = (X1+A''', Y1+B''', Z1+C''');

我发现这个链接非常有用。它定义了如何分别围绕X、Y和Z轴执行单独的旋转。
在数学上,您可以这样定义一组操作: enter image description here

5
一种非常简洁的编程方法,特别是如果您能够使用矩阵(如Matlab)进行操作,那么可以使用Rodrigues旋转公式。该公式通过一个非常简单的方程式,围绕由单位向量\hat{u} = [u_x,u_y,u_z]定义的轴,以角度phi创建一个旋转矩阵R

R=I+sin(phi)·W + (1-cos(phi))·W²

其中I是单位矩阵,W是由单位向量u的分量给出的矩阵:

W= [0 -uz uy; uz 0 -ux; -uy ux 0];

请注意,非常重要的是向量u必须是单位向量,即u的范数必须为1。

您可以检查欧几里得轴是否与维基百科上找到的公式完全相同,并由Aakash Anuj发布在此处。

自从我发现它以来,我只在旋转中使用这个公式。希望对任何人有所帮助。


4

以下是您可以用来绕任何轴旋转的方法,无论是x、y还是z轴。 Rx、Ry和Rz分别表示绕x、y、z轴旋转。

enter image description here


10
那张图片是来自这里,对吗?你可能应该注明出处。 - Brian McCutchon

3
对于三维空间中任意轴的旋转,可以使用矩阵进行计算。你可以在这里找到相关页面。该页面中提供了关于矩阵的解释和推导(这里),其中包括以下旋转/平移矩阵。该矩阵可将点(x,y,z)沿着以(a,b,c)为线心,方向向量为⟨u,v,w⟩,旋转角度为theta的直线旋转。

matrix of rotation about an arbitrary axis

旋转后的三维坐标如下:

rotated point

该页面还提供了源代码下载链接。如果您想进行交互式旋转操作,可以访问此网站。点击示例旋转链接,了解操作过程。

Twist and Shout!


1
Axis-angle可以直接转换成四元数;假设轴是一个单位向量,并且角度是绕该轴旋转的角度。该轴给出了常规的(x,y,z)方向坐标。四元数为(cos(theta),sin(theta)*x, sin(theta)*y, sin(theta)*z),然后根据需要进行乘法运算。
基础可以通过使用 Rodrigues Rotation Formula绕着轴角旋转(1,0,0),(0,1,0),(0,0,1)来形成,结果如下...然后可以使用前进(forward)、右(right)和上(up)向量来缩放点到正确的位置。(这只是一个矩阵,但本质上是转置的,使得轴线本身可以立即被提取,从而在任何特定点给出相对的'up')
    const nt = q.θ * del;  // scaled angle by some dT
    const s  = Math.sin( nt ); // sin/cos are the function of exp()
    const c1 = Math.cos( nt );
    const c = 1- c1;

    const qx = /*Axis unit vector X*/;
    const qy = /*Axis unit vector Y*/;
    const qz = /*Axis unit vector Z*/;

    const cnx = c*qx;
    const cny = c*qy;
    const cnz = c*qz;

    const xy = cnx*qy;  // x * y / (xx+yy+zz) * (1 - cos(2t))
    const yz = cny*qz;  // y * z / (xx+yy+zz) * (1 - cos(2t))
    const xz = cnz*qx;  // x * z / (xx+yy+zz) * (1 - cos(2t))

    const wx = s*qx;     // x / sqrt(xx+yy+zz) * sin(2t)
    const wy = s*qy;     // y / sqrt(xx+yy+zz) * sin(2t)
    const wz = s*qz;     // z / sqrt(xx+yy+zz) * sin(2t)

    const xx = cnx*qx;  // y * y / (xx+yy+zz) * (1 - cos(2t))
    const yy = cny*qy;  // x * x / (xx+yy+zz) * (1 - cos(2t))
    const zz = cnz*qz;  // z * z / (xx+yy+zz) * (1 - cos(2t))

    const basis = { right  :{ x : c1 + xx, y : wz + xy, z : xz - wy }
                  , up     :{ x : xy - wz, y : c1 + yy, z : wx + yz }
                  , forward:{ x : wy + xz, y : yz - wx, z : c1 + zz }
                  };


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接