Language agnostic 是否有将四元数旋转转换为欧拉角旋转的算法?

Language agnostic 是否有将四元数旋转转换为欧拉角旋转的算法?,language-agnostic,math,animation,quaternions,euler-angles,Language Agnostic,Math,Animation,Quaternions,Euler Angles,是否存在将旋转的四元数表示转换为欧拉角表示的现有算法?欧拉表示的旋转顺序是已知的,可以是六种排列(即xyz、xzy、yxz、yzx、zxy、zyx)中的任何一种。我见过固定旋转顺序的算法(通常是NASA的航向、倾斜、滚动惯例),但不适用于任意旋转顺序 此外,由于单个方向有多个Euler角度表示,因此该结果将是不明确的。这是可以接受的(因为方向仍然有效,它可能不是用户期望看到的方向),但是如果有一个算法采用旋转限制(即自由度的数量和每个自由度的限制),它会更好考虑到这些约束条件,并得出“最合理”的

是否存在将旋转的四元数表示转换为欧拉角表示的现有算法?欧拉表示的旋转顺序是已知的,可以是六种排列(即xyz、xzy、yxz、yzx、zxy、zyx)中的任何一种。我见过固定旋转顺序的算法(通常是NASA的航向、倾斜、滚动惯例),但不适用于任意旋转顺序

此外,由于单个方向有多个Euler角度表示,因此该结果将是不明确的。这是可以接受的(因为方向仍然有效,它可能不是用户期望看到的方向),但是如果有一个算法采用旋转限制(即自由度的数量和每个自由度的限制),它会更好考虑到这些约束条件,并得出“最合理”的欧拉表示

我感觉这个问题(或类似问题)可能存在于IK或刚体动力学域中


已解决:我刚刚意识到,通过遵循Graphics Gems中Ken Shoemake的算法来解决这个问题可能并不清楚。当时我确实回答了我自己的问题,但我突然意识到,我这样做可能并不清楚。有关更多详细信息,请参见下面的答案


我只想澄清一下——我知道如何将四元数转换为所谓的“表示法”——我称之为“NASA”大会。这是zxy的旋转顺序(假设“Z”轴向上的约定)。我需要一个所有旋转顺序的算法

那么,可能的解决方案是采用zxy阶转换,并从中导出其他旋转阶的五个其他转换。我想我希望有一个更“全面”的解决方案。在任何情况下,我都很惊讶,我还没有找到现有的解决方案

此外,这可能是一个单独的问题,任何转换(当然,假设旋转顺序已知)都将选择一个Euler表示,但实际上有很多。例如,给定yxz的旋转顺序,两种表示法(0,0180)和(180180,0)是等效的(并将产生相同的四元数)。有没有一种方法可以使用自由度限制来约束解决方案?就像在IK和刚体动力学中一样?i、 e.在上述示例中,如果Z轴只有一个自由度,则可以忽略第二个表示



我已经找到了一篇论文,它可能是一个算法,但我必须承认,我发现逻辑和数学有点难以理解。当然还有其他解决办法吗?任意旋转顺序真的如此罕见吗?当然,每个允许骨骼动画和四元数插值的主要3D软件包(即Maya、Max、Blender等)都必须准确地解决这个问题?

展示了如何使用四元数的各个部分并计算euler角度。

在Z轴指向上的右手笛卡尔坐标系中,执行以下操作:

struct Quaternion
{
    double w, x, y, z;
};

void GetEulerAngles(Quaternion q, double& yaw, double& pitch, double& roll)
{
    const double w2 = q.w*q.w;
    const double x2 = q.x*q.x;
    const double y2 = q.y*q.y;
    const double z2 = q.z*q.z;
    const double unitLength = w2 + x2 + y2 + z2;    // Normalised == 1, otherwise correction divisor.
    const double abcd = q.w*q.x + q.y*q.z;
    const double eps = 1e-7;    // TODO: pick from your math lib instead of hardcoding.
    const double pi = 3.14159265358979323846;   // TODO: pick from your math lib instead of hardcoding.
    if (abcd > (0.5-eps)*unitLength)
    {
        yaw = 2 * atan2(q.y, q.w);
        pitch = pi;
        roll = 0;
    }
    else if (abcd < (-0.5+eps)*unitLength)
    {
        yaw = -2 * ::atan2(q.y, q.w);
        pitch = -pi;
        roll = 0;
    }
    else
    {
        const double adbc = q.w*q.z - q.x*q.y;
        const double acbd = q.w*q.y - q.x*q.z;
        yaw = ::atan2(2*adbc, 1 - 2*(z2+x2));
        pitch = ::asin(2*abcd/unitLength);
        roll = ::atan2(2*acbd, 1 - 2*(y2+x2));
    }
}
结构四元数 { 双w,x,y,z; }; void geteularangles(四元数q、双偏航、双俯仰、双横滚) { 常数双w2=q.w*q.w; 常数双x2=q.x*q.x; 常数双y2=q.y*q.y; 常数双z2=q.z*q.z; 常数double unitLength=w2+x2+y2+z2;//归一化==1,否则为修正因子。 常数双abcd=q.w*q.x+q.y*q.z; const double eps=1e-7;//TODO:从数学库中选择,而不是硬编码。 const double pi=3.14159265358979323846;//TODO:从数学库中选择,而不是硬编码。 如果(abcd>(0.5-eps)*单位长度) { 偏航=2*atan2(q.y,q.w); 螺距=π; 滚动=0; } 否则如果(abcd<(-0.5+eps)*单位长度) { 偏航=-2*:atan2(q.y,q.w); 螺距=-pi; 滚动=0; } 其他的 { 常数双adbc=q.w*q.z-q.x*q.y; 常数双acbd=q.w*q.y-q.x*q.z; 偏航=::atan2(2*adbc,1-2*(z2+x2)); 音高=::asin(2*abcd/单位长度); 滚动=::atan2(2*acbd,1-2*(y2+x2)); } }
这看起来像是一个老技术被忽视的经典案例——我设法从车库里挖出了一份,看起来Ken Shoemake不仅有一个从任意旋转顺序的Euler角度转换的算法,而且还回答了我关于这个主题的大多数其他问题。好书啊。要是我能投票支持Shoemake先生的答案并奖励他名誉点数就好了

我想建议任何与Euler angles合作的人都应该从当地图书馆获得一份Graphics Gems IV,并阅读222页开始的部分。它必须是我读过的关于这个问题的最清晰、最简洁的解释


从那以后,我发现了一个有用的链接——它遵循与鞋匠相同的系统;旋转顺序的24种不同排列被编码为四个独立的参数-内轴、奇偶校验、重复和帧-这允许您将算法从24种情况减少到2种。一般来说,它可能是一个有用的维基——我以前从未见过它

提供给旧链接的似乎是另一个“从旋转矩阵计算欧拉角”的副本

我已经在我的网站noelhughes.net上发表了题为“使用几何方法将四元数转换为任意旋转序列的欧拉角”的论文。我还有一些算法,可以将任何一组欧拉角转换成四元数,四元数转换成方向余弦矩阵,我将在本周末发布这些算法。这些也在马丁·贝克的网站上,尽管有点难找到。用谷歌搜索我的名字Noel Hughes和四元数,你应该能找到它。

我用以下方法解决它:

步骤1:确定
R11 =  cos(theta)*cos(psi)
R12 = -cos(theta)*sin(psi)
R13 =  sin(theta)
R21 =  sin(psi)*cos(phi) + sin(theta)*cos(psi)*sin(phi)
R22 =  cos(psi)*cos(phi) - sin(theta)*sin(psi)*sin(phi)
R23 = -cos(theta)*sin(phi)
R31 =  sin(psi)*sin(phi) - sin(theta)*cos(psi)*cos(phi)
R32 =  cos(psi)sin(phi) + sin(theta)*sin(psi)*cos(phi)
R33 =  cos(theta)*cos(phi) 
tan(phi) = -R23/R33

sin(theta) = -R13

tan(psi) = -R12/R11
///////////////////////////////
// Quaternion to Euler
///////////////////////////////
enum RotSeq{zyx, zyz, zxy, zxz, yxz, yxy, yzx, yzy, xyz, xyx, xzy,xzx};

void twoaxisrot(double r11, double r12, double r21, double r31, double r32, double res[]){
  res[0] = atan2( r11, r12 );
  res[1] = acos ( r21 );
  res[2] = atan2( r31, r32 );
}

void threeaxisrot(double r11, double r12, double r21, double r31, double r32, double res[]){
  res[0] = atan2( r31, r32 );
  res[1] = asin ( r21 );
  res[2] = atan2( r11, r12 );
}

void quaternion2Euler(const Quaternion& q, double res[], RotSeq rotSeq)
{
    switch(rotSeq){
    case zyx:
      threeaxisrot( 2*(q.x*q.y + q.w*q.z),
                     q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                    -2*(q.x*q.z - q.w*q.y),
                     2*(q.y*q.z + q.w*q.x),
                     q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                     res);
      break;

    case zyz:
      twoaxisrot( 2*(q.y*q.z - q.w*q.x),
                   2*(q.x*q.z + q.w*q.y),
                   q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                   2*(q.y*q.z + q.w*q.x),
                  -2*(q.x*q.z - q.w*q.y),
                  res);
      break;

    case zxy:
      threeaxisrot( -2*(q.x*q.y - q.w*q.z),
                      q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                      2*(q.y*q.z + q.w*q.x),
                     -2*(q.x*q.z - q.w*q.y),
                      q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                      res);
      break;

    case zxz:
      twoaxisrot( 2*(q.x*q.z + q.w*q.y),
                  -2*(q.y*q.z - q.w*q.x),
                   q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                   2*(q.x*q.z - q.w*q.y),
                   2*(q.y*q.z + q.w*q.x),
                   res);
      break;

    case yxz:
      threeaxisrot( 2*(q.x*q.z + q.w*q.y),
                     q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                    -2*(q.y*q.z - q.w*q.x),
                     2*(q.x*q.y + q.w*q.z),
                     q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                     res);
      break;

    case yxy:
      twoaxisrot( 2*(q.x*q.y - q.w*q.z),
                   2*(q.y*q.z + q.w*q.x),
                   q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                   2*(q.x*q.y + q.w*q.z),
                  -2*(q.y*q.z - q.w*q.x),
                  res);
      break;

    case yzx:
      threeaxisrot( -2*(q.x*q.z - q.w*q.y),
                      q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                      2*(q.x*q.y + q.w*q.z),
                     -2*(q.y*q.z - q.w*q.x),
                      q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                      res);
      break;

    case yzy:
      twoaxisrot( 2*(q.y*q.z + q.w*q.x),
                  -2*(q.x*q.y - q.w*q.z),
                   q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                   2*(q.y*q.z - q.w*q.x),
                   2*(q.x*q.y + q.w*q.z),
                   res);
      break;

    case xyz:
      threeaxisrot( -2*(q.y*q.z - q.w*q.x),
                    q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z,
                    2*(q.x*q.z + q.w*q.y),
                   -2*(q.x*q.y - q.w*q.z),
                    q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                    res);
      break;

    case xyx:
      twoaxisrot( 2*(q.x*q.y + q.w*q.z),
                  -2*(q.x*q.z - q.w*q.y),
                   q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                   2*(q.x*q.y - q.w*q.z),
                   2*(q.x*q.z + q.w*q.y),
                   res);
      break;

    case xzy:
      threeaxisrot( 2*(q.y*q.z + q.w*q.x),
                     q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z,
                    -2*(q.x*q.y - q.w*q.z),
                     2*(q.x*q.z + q.w*q.y),
                     q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                     res);
      break;

    case xzx:
      twoaxisrot( 2*(q.x*q.z - q.w*q.y),
                   2*(q.x*q.y + q.w*q.z),
                   q.w*q.w + q.x*q.x - q.y*q.y - q.z*q.z,
                   2*(q.x*q.z + q.w*q.y),
                  -2*(q.x*q.y - q.w*q.z),
                  res);
      break;
    default:
      std::cout << "Unknown rotation sequence" << std::endl;
      break;
   }
}