有没有办法从 4x4 矩阵计算 X 和 Y 轴上的 3D 旋转

S. *_*ert 6 javascript math rotation matrix euler-angles

首先,我根本不是数学专家。请容忍我的数学错误并在必要时纠正我,我很乐意学习。

我有一个立方体,它使用带有变换的 css 动画进行旋转:matrix3d(4x4)。我还可以手动旋转立方体,将用户操作转换为相同的矩阵3d 转换。

我想要的是当用户停止交互时带有 css 的旋转立方体,从用户离开的位置开始。这是我通过获取立方体的变换矩阵 3d 值并使用乘法动态设置 css 关键帧成功完成的事情。

然而,当用户开始与立方体交互时,立方体会跳转到其最后一个已知的手动旋转点并从那里继续,因为我无法弄清楚如何从 4x4 矩阵获取 X 和 Y 轴上的旋转。

我目前正在使用以下库Rematrix,它可以帮助我从手动旋转变为 css 旋转,如上所述。

我一直在研究有关欧拉的文章,以及如何从欧拉到矩阵,反之亦然,但正如我之前提到的,我认为这就是我缺乏数学知识阻碍我的地方。我似乎无法弄清楚。

作为参考,这里是我读过的一些文章,试图解决我的问题。

最后一个来源对我来说最有意义,但如果我是正确的,那么在这种情况下没有用,因为它是关于 2D 变换,而不是 3D。

我通过以下方式获取当前的matrix3d:

const style = getComputedStyle(this.element).transform
const matrix = Rematrix.parse(style)
Run Code Online (Sandbox Code Playgroud)

对于手动旋转,我使用基于用户鼠标位置(positionY、positionX)的矩阵乘法。

const r1 = Rematrix.rotateX(this.positionY)
const r2 = Rematrix.rotateY(this.positionX)

const transform = [r1, r2].reduce(Rematrix.multiply)

this.element.style[userPrefix.js + 'Transform'] = Rematrix.toString(transform)
Run Code Online (Sandbox Code Playgroud)

从手动旋转到 css 旋转,我使用以下函数:

const setCssAnimationKeyframes = (lastTransform, animationData) => {
  const rotationIncrement = 90

  let matrixes = []

  for (let i = 0; i < 5; i++) {
    const rX = Rematrix.rotateX(rotationIncrement * i)
    const rY = Rematrix.rotateY(rotationIncrement * i)

    const matrix = [lastTransform, rX, rY].reduce(Rematrix.multiply);

    matrixes.push(matrix)
  }

  animationData.innerHTML = `
    @keyframes rotateCube {
      0% {
        transform: ${Rematrix.toString(matrixes[0])};
      }
      25% {
        transform: ${Rematrix.toString(matrixes[1])};
      }
      50% {
        transform: ${Rematrix.toString(matrixes[2])};
      }
      75% {
        transform: ${Rematrix.toString(matrixes[3])}};
      }
      100% {
        transform: ${Rematrix.toString(matrixes[4])};
      }
    }
  `;
}
Run Code Online (Sandbox Code Playgroud)

请提供答案或评论并提供任何有用的信息。尽管非常欢迎,但我不希望您提供完整的工作代码示例。任何形式的有用信息都将受到高度赞赏。

Spe*_*tre 5

首先阅读:

因为我使用那里的术语。

好吧,我太懒了,无法将所有内容等同于我的环境,但基于此:

m对于任何旋转顺序,生成的 3D 旋转子矩阵将始终具有以下参数:

(+/-)sin(a)
(+/-)sin(b)cos(a)
(+/-)cos(b)cos(a)
(+/-)sin(c)cos(a)
(+/-)cos(c)cos(a)
Run Code Online (Sandbox Code Playgroud)

只有它们的符号和位置会随着变换顺序和约定而改变。因此,要识别它们,请执行以下操作:

  1. 让我们先设置一些不平凡的欧拉角

    它们的|sin||cos|必须不同,因此 6 个值都不会相同,否则这将不起作用!

    我选择了这些:

    ex = 10 [deg]
    ey = 20 [deg]
    ez = 30 [deg]
    
    Run Code Online (Sandbox Code Playgroud)
  2. 计算旋转矩阵m

    因此按顺序在单位矩阵上应用 3 次欧拉旋转。在我的设置中,生成的矩阵如下所示:

    (+/-)sin(a)
    (+/-)sin(b)cos(a)
    (+/-)cos(b)cos(a)
    (+/-)sin(c)cos(a)
    (+/-)cos(c)cos(a)
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,我使用 OpenGL 约定,基向量X,Y,Z和原点O由矩阵线表示,并且矩阵是直接的。

  3. 识别(+/-)sin(a)

    可以a是任何欧拉角,因此将sin它们全部打印出来:

    ex = 10 [deg]
    ey = 20 [deg]
    ez = 30 [deg]
    
    Run Code Online (Sandbox Code Playgroud)

    现在看m[8] = sin(ey),我们找到了热源...现在我们知道:

    ey = a = asin(m[8]);
    
    Run Code Online (Sandbox Code Playgroud)
  4. 识别(+/-)???(?)*cos(a)热量

    只需打印 cos(?)*cos(ey) 来获取尚未使用的角度。所以如果ey是 20 度,我打印 10 和 30 度......

    sin(10 deg)*cos(20 deg) = 0.16317591116653482557414168661534
    cos(10 deg)*cos(20 deg) = 0.92541657839832335306523309767123
    sin(30 deg)*cos(20 deg) = 0.46984631039295419202705463866237
    cos(30 deg)*cos(20 deg) = 0.81379768134937369284469321724839
    
    Run Code Online (Sandbox Code Playgroud)

    当我们再次查看时,m我们可以交叉匹配:

    sin(ex)*cos(ey) = 0.16317591116653482557414168661534 = -m[9]
    cos(ex)*cos(ey) = 0.92541657839832335306523309767123 = +m[10]
    sin(ez)*cos(ey) = 0.46984631039295419202705463866237 = -m[4]
    cos(ez)*cos(ey) = 0.81379768134937369284469321724839 = +m[0]
    
    Run Code Online (Sandbox Code Playgroud)

    由此我们可以计算角度......

    sin(ex)*cos(ey) = -m[ 9]
    cos(ex)*cos(ey) = +m[10]
    sin(ez)*cos(ey) = -m[ 4]
    cos(ez)*cos(ey) = +m[ 0]
    ------------------------
    sin(ex) = -m[ 9]/cos(ey)
    cos(ex) = +m[10]/cos(ey)
    sin(ez) = -m[ 4]/cos(ey)
    cos(ez) = +m[ 0]/cos(ey)
    
    Run Code Online (Sandbox Code Playgroud)

    所以最后:

    ---------------------------------------------
    ey = asin(m[8]);
    ex = atan2( -m[ 9]/cos(ey) , +m[10]/cos(ey) )
    ez = atan2( -m[ 4]/cos(ey) , +m[ 0]/cos(ey) )
    ---------------------------------------------
    
    Run Code Online (Sandbox Code Playgroud)

就是这样。如果您有不同的布局/约定/转换顺序,这种方法仍然应该有效......只有索引和符号发生变化。这里是一个小的C++/VCL OpenGL示例,我对此进行了测试(X,Y,Z顺序):

double m[16] = 
 { 
  0.813797652721405, 0.543838143348694,-0.204874128103256, 0, // Xx,Xy,Xz,0.0
 -0.469846308231354, 0.823172926902771, 0.318795770406723, 0, // Yx,Yy,Yz,0.0
  0.342020153999329,-0.163175910711288, 0.925416529178619, 0, // Zx,Zy,Zz,0.0
  0                , 0                , 0                , 1  // Ox,Oy,Oz,1.0
 };
Run Code Online (Sandbox Code Playgroud)

它唯一重要的东西是matrix2euler将矩阵按顺序转换m为欧拉角的函数x,y,z。它渲染 3 个坐标系轴。左边m用作模型视图矩阵,中间是m使用恒等模型视图的基向量,右边是由计算的欧拉角构造的模型视图......

所有 3 个都应该匹配。如果左边和中间不匹配,那么你就会得到不同的矩阵或布局约定。

这里是测试用例的预览(10,20,30) [deg]

预览

即使经过多次旋转(箭头键)它也会匹配......

可以gl_simple.h在这里找到:

附言。根据平台/环境,计算可能需要一些边缘情况处理,例如asin大于的舍入大小1、除以零等。也atan2有其怪癖......

[Edit1] 这里是最终的 C++ 示例,它自动完成这一切:

sin(ex) = 0.17364817766693034885171662676931
sin(ey) = 0.34202014332566873304409961468226
sin(ez) = 0.5
Run Code Online (Sandbox Code Playgroud)

用法:

ey = a = asin(m[8]);
Run Code Online (Sandbox Code Playgroud)

这适用于任何顺序的转换和/或约定/布局。init 仅被调用一次,然后您可以将转换用于任何变换矩阵...您还可以根据euler_cfg您的环境的结果编写自己的优化版本。