从欧拉角到四元数

From Euler angles to Quaternions

我正在研究平面运动的模拟。现在,我使用欧拉角将 "body frame" 转换为 "world frame" 并且效果很好。

最近我了解了四元数及其相对于旋转矩阵(万向节锁)的优势,我尝试使用来自模拟器的 yaw/pitch/roll 角度来实现它。

四元数

如果我理解正确的话,四元数代表两件事。它有一个 x, y,z 分量,表示将发生旋转的轴。它还有一个 w 分量,表示围绕该轴发生的旋转量。简而言之,一个向量和一个浮点数。四元数可以表示为 4 元素向量:

q=[w,x,y,z]

计算结果(完全旋转后)方程式使用:

p'=qpq'

其中:

p=[0,x,y,z]-方向向量

q=[w,x,y,z]-旋转

q'=[w,-x,-y,-z]

算法

  1. 创建四元数:

使用 wikipedia 我通过围绕 3 个轴 (q) 旋转来创建四元数:

Quaterniond toQuaternion(double yaw, double pitch, double roll) // yaw (Z), pitch (Y), roll (X)
{
    //Degree to radius:
    yaw = yaw * M_PI / 180;
    pitch = pitch * M_PI / 180;
    roll = roll * M_PI / 180;


    // Abbreviations for the various angular functions
    double cy = cos(yaw * 0.5);
    double sy = sin(yaw * 0.5);
    double cp = cos(pitch * 0.5);
    double sp = sin(pitch * 0.5);
    double cr = cos(roll * 0.5);
    double sr = sin(roll * 0.5);

    Quaterniond q;
    q.w = cy * cp * cr + sy * sp * sr;
    q.x = cy * cp * sr - sy * sp * cr;
    q.y = sy * cp * sr + cy * sp * cr;
    q.z = sy * cp * cr - cy * sp * sr;
    return q;
}
  1. 定义平面方向(航向)向量:

    p = [0,1,0,0]

  2. 计算Hamilton product:

    p'=qpq'

    q'=[w,-qx,-qy,-qz]

    p' = (H(H(q, p), q')

Quaterniond HamiltonProduct(Quaterniond u, Quaterniond v)
{
    Quaterniond result;

    result.w = u.w*v.w - u.x*v.x - u.y*v.y - u.z*v.z;
    result.x = u.w*v.x + u.x*v.w + u.y*v.z - u.z*v.y;
    result.y = u.w*v.y - u.x*v.z + u.y*v.w + u.z*v.x;
    result.z = u.w*v.z + u.x*v.y - u.y*v.x + u.z*v.w;

    return result;

}

结果

我的结果将是一个向量:

v=[p'x,p'y,p'z]

它工作正常但与欧拉角旋转(万向节锁定)相同。是因为我在这里也使用了欧拉角吗?如果不绕 3 个轴旋转,我真的不知道它应该如何工作。我应该分别绕每个轴旋转吗?

对于任何建议和帮助理解这个问题,我将不胜感激。

编辑(应用程序如何工作)

1。我的应用程序基于数据流,这意味着每 1 毫秒后它检查是否有新数据(平面的新方向)。

示例:

开始 pitch/roll/yaw = 0,在 1ms 偏航改变 10 度后,应用程序读取 俯仰=0,横滚=0,偏航=10。在接下来的 1 毫秒后,偏航再次改变 20 度。所以输入数据将如下所示:pitch=0, roll=0, yaw = 30.

2。创建方向四元数 - p

在开始的时候,我定义了我的飞机的方向(头部)在X轴上。所以我当地的方向是 v=[1,0,0] 在四元数中(我的p)是 p=[0,1,0,0]

Vector3 LocalDirection, GlobalDirection; //head
    Quaterniond p,P,q, Q, pq; //P = p', Q=q'


    LocalDirection.x = 1;
    LocalDirection.y = 0;
    LocalDirection.z = 0;

    p.w = 0;
    p.x = direction.x;
    p.y = direction.y;
    p.z = direction.z;

3。创建旋转

每隔 1ms 我检查数据流的旋转角度(欧拉)并使用 toQuaternion[计算 q

q = toQuaternion(yaw, pitch, roll); // create quaternion after rotation


    Q.w = q.w;
    Q.x = -q.x;
    Q.y = -q.y;
    Q.z = -q.z;

4.计算"world direction"

使用 Hamilton 乘积 我计算旋转后的四元数,这是我的全局方向:

pq = HamiltonProduct(q, p); 

    P = HamiltonProduct(pq, Q);

    GlobalDirection.x = P.x;
    GlobalDirection.y = P.y;
    GlobalDirection.z = P.z;

5.每1ms

重复3-4次

您的模拟似乎每帧都使用欧拉角来旋转对象。然后将这些角度转换为四元数。那不会解决云台锁定问题。

当您将欧拉角添加到欧拉角时,万向节锁随时可能发生。从本地 space 到世界 space 时,仅解决此问题是不够的。您还需要您的模拟在帧之间使用四元数。

基本上每次你的对象改变它的旋转时,将当前旋转转换为四元数,乘以新的旋转增量,并将结果转换回欧拉角或任何你用来存储旋转的角度。

我建议重写您的应用程序以仅使用四元数。每当用户输入或您的游戏的某些其他逻辑想要旋转某些东西时,您立即将该输入转换为四元数并将其输入模拟。

有了 toQuaternionHamiltonProduct,您就拥有了所需的所有工具。


编辑 回应您对您的应用程序工作原理所做的编辑。

At the begging pitch/roll/yaw = 0, after 1ms yaw is changed by 10 degree so application reads pitch=0, roll=0, yaw = 10. After next 1ms yaw is changed again by 20 degrees. So input data will look like this: pitch=0, roll=0, yaw = 30.

这就是万向节锁定发生的地方。在计算旋转后 转换为四元数 。那是错的。您需要在这第一步中使用四元数 in。不要做after 1ms yaw is changed by 10 degree so application reads pitch=0, roll=0, yaw = 10,做这个:

  1. 将旋转存储为四元数,而不是欧拉角;
  2. 将10度偏航角转换为四元数;
  3. 将存储的四元数与10度偏航四元数相乘;
  4. 存储结果。

澄清一下:您的第 2、3 和 4 步没问题。问题出在第 1 步。


附带说明一下:

It has an x, y, and z component, which represents the axis about which a rotation will occur. It also has a w component, which represents the amount of rotation which will occur about this axis

不完全正确。四元数的组成部分不是直接的轴和角度,它们是 sin(angle/2) * axiscos(angle/2) (这是你的 toQuaternion 方法产生的)。这很重要,因为它为您提供了很好的单位四元数,形成了 4D sphere,其中表面上的每个点 (surspace?) 代表 3D space 中的旋转,漂亮地允许之间的平滑插值任意两次旋转。