从矩阵获得俯仰和滚动而没有奇点

时间:2015-06-27 11:54:56

标签: c++ math matrix 3d euler-angles

我正在使用2自由度(俯仰和转动)的运动模拟器。我正在从游戏中读取变换矩阵,我需要获得角度并发送到硬件来驱动电机。 由于欧拉角有奇点,我不能真正使用它们。它的行为如下:

什么时候应该这样:

我准备了在线示例以更好地展示问题:

// Get euler angles from model matrix
var mat = model.matrix;
mat.transpose();

var e = new THREE.Euler();
e.setFromRotationMatrix(mat, 'XZY');
var v = e.toVector3();

var pitch = -v.z;
var roll  = -v.x;

http://jsfiddle.net/qajro0ny/3/

据我了解,这里有两个问题。

  1. 模拟器上没有偏航轴。
  2. 即使有偏航轴,电机也不会像计算机图形那样,即它们需要时间到达目标位置。
  3. 我已经阅读了关于万向节锁,甚至实现了euler过滤器,但这没有按预期工作。 关于万向节锁的大多数建议是使用四元数,但我不能用四元数驱动物理运动(或者我可以吗?)。

    轴顺序在这里并不重要,因为改变它只会将奇点从一个轴移动到另一个轴。

    我需要以其他方式处理这个问题。

    我尝试用矩阵乘以轴向量,然后使用十字和点积来获得角度,但这也失败了。我认为还应该进行轴重投,以实现这一目标,但我无法弄明白。但有些事情告诉我,这是正确的方法。它是这样的:http://jsfiddle.net/qajro0ny/53/

    然后我提出了不同的想法。我知道以前的位置,所以可能会做以下事情:

    1. 将矩阵转换为四元数
    2. 计算当前和之前四元数之间的差异
    3. 将生成的四元数转换为欧拉角
    4. 将这些角度添加到静态俯仰,滚转和偏航变量。
    5. 所以我试过了......它奏效了!在任何方向上都没有奇点,在俯仰,滚转和偏航方面完美的360度旋转。完美的解决方案!除了......它不是。帧没有同步,所以经过一段时间的角度远离他们应该是什么。我一直在考虑某种同步机制,但我认为这不是正确的方法。

      看起来像这样:http://jsfiddle.net/qajro0ny/52/

      同样的逻辑,但直接与矩阵:http://jsfiddle.net/qajro0ny/54/

      我搜索了网页的高低,我已经阅读了几十篇论文和其他问题/帖子,我简直无法相信没有什么能真正适用于我的案例。

      我可能不理解或遗漏某些东西,所以这里是我发现和尝试的一切:

        

      链接:http://pastebin.com/3G0dYLvu

           

      代码:http://pastebin.com/PiZKwE2t(我把它们放在一起所以它很混乱)

      我一定是错过了什么,或者我是从错误的角度看这个。

4 个答案:

答案 0 :(得分:7)

如果您知道矩阵只包含两个旋转(按给定顺序T = RZ * RX),那么您可以执行以下操作:

局部x轴不受第二次旋转的影响。因此,您只能使用局部x轴计算第一个角度。然后,您可以从矩阵中移除此旋转,并从其他两个轴中的任何一个计算剩余角度:

function calculateAngles() {
    var mat = model.matrix;

    //calculates the angle from the local x-axis
    var pitch = Math.atan2(mat.elements[1], mat.elements[0]);

    //this matrix is used to remove the first rotation
    var invPitch = new THREE.Matrix4();
    invPitch.makeRotationZ(-pitch);

    //this matrix will only contain the roll rotation
    //  rollRot = RZ^-1 * T
    //          = RZ^-1 * RZ * RX
    //          = RX
    var rollRot = new THREE.Matrix4();
    rollRot.multiplyMatrices(invPitch, mat);

    //calculate the remaining angle from the local y-axis
    var roll  = Math.atan2(rollRot.elements[6], rollRot.elements[5]);

    updateSimAngles(pitch, roll);
}

这当然只有在给定矩阵符合要求时才有效。它不得包含第三个旋转。否则,您可能需要找到非线性最小二乘解。

答案 1 :(得分:2)

关于使用旋转增量的想法实际上听起来很有希望。

我不太清楚你对“帧没有同步”的意思。我可以想象你使用四元数的计算可能不是100%精确(你使用浮点吗?)这会导致小的错误堆积起来并最终导致异步移动。

关键是你应该使用单位四元数来表示旋转。您可以在理论模型中执行此操作,但如果您使用4个浮点数表示四元数,则在大多数情况下,四元数将不是单位四元数,但只是非常接近(对于某些小的,它们的范数为1+e - 可能是负面的 - 价值e)。这是可以的,因为你不会注意到这些小的差异,但是如果你在你的四元数上投入大量的操作(你通过不断地模拟你的模型并计算增量),那么这些小的错误就会堆积起来。因此,您需要不断重新规范化四元数以使它们尽可能接近单位四元数,以便您的计算 - 尤其是转换回欧拉角 - 保持(几乎)精确。

答案 2 :(得分:2)

我在http://jsfiddle.net/qajro0ny/58/加了我的两分钱,基本上应用了几年前我偶然发现的http://blogs.msdn.com/b/mikepelton/archive/2004/10/29/249501.aspx的作品。它基本归结为

var a = THREE.Math.clamp(-mat.elements[6], -1, 1);
var xPitch = Math.asin(a);
var yYaw = 0;
var zRoll = 0;
if (Math.cos(xPitch) > 0.0001)
{
  zRoll = -Math.atan2(mat.elements[4], mat.elements[5]);
  yYaw = -Math.atan2(mat.elements[2], mat.elements[10]);
}
else
{
  zRoll = -Math.atan2(-mat.elements[1], mat.elements[0]);
}

我注意到,在DirectX左手坐标系中使用的假设映射偏航,俯仰,滚动到轴(y,x,z)与使用OpenGL / WebGL时的不同,所以也许这个需要是终于整理出来了。

我希望这会有所帮助。

答案 3 :(得分:1)

我想你实际上可以用四元数驱动物理马达。请记住,四元数表示旋转轴(x,y,z)和角度。 我想你有三个电机(每个轴),旋转速度可以缩放。现在,通过缩放这三个电机的转速,您可以设置特定的旋转轴,并通过仔细测量时间,您可以设置特定的旋转角度。它应该比转换为欧拉角度增量更容易。