从欧拉角到四元数
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]
算法
- 创建四元数:
使用 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;
}
定义平面方向(航向)向量:
p = [0,1,0,0]
-
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 时,仅解决此问题是不够的。您还需要您的模拟在帧之间使用四元数。
基本上每次你的对象改变它的旋转时,将当前旋转转换为四元数,乘以新的旋转增量,并将结果转换回欧拉角或任何你用来存储旋转的角度。
我建议重写您的应用程序以仅使用四元数。每当用户输入或您的游戏的某些其他逻辑想要旋转某些东西时,您立即将该输入转换为四元数并将其输入模拟。
有了 toQuaternion
和 HamiltonProduct
,您就拥有了所需的所有工具。
编辑 回应您对您的应用程序工作原理所做的编辑。
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
,做这个:
- 将旋转存储为四元数,而不是欧拉角;
- 将10度偏航角转换为四元数;
- 将存储的四元数与10度偏航四元数相乘;
- 存储结果。
澄清一下:您的第 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) * axis
和 cos(angle/2)
(这是你的 toQuaternion
方法产生的)。这很重要,因为它为您提供了很好的单位四元数,形成了 4D sphere,其中表面上的每个点 (surspace?) 代表 3D space 中的旋转,漂亮地允许之间的平滑插值任意两次旋转。
我正在研究平面运动的模拟。现在,我使用欧拉角将 "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]
算法
- 创建四元数:
使用 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;
}
定义平面方向(航向)向量:
p = [0,1,0,0]
-
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 时,仅解决此问题是不够的。您还需要您的模拟在帧之间使用四元数。
基本上每次你的对象改变它的旋转时,将当前旋转转换为四元数,乘以新的旋转增量,并将结果转换回欧拉角或任何你用来存储旋转的角度。
我建议重写您的应用程序以仅使用四元数。每当用户输入或您的游戏的某些其他逻辑想要旋转某些东西时,您立即将该输入转换为四元数并将其输入模拟。
有了 toQuaternion
和 HamiltonProduct
,您就拥有了所需的所有工具。
编辑 回应您对您的应用程序工作原理所做的编辑。
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
,做这个:
- 将旋转存储为四元数,而不是欧拉角;
- 将10度偏航角转换为四元数;
- 将存储的四元数与10度偏航四元数相乘;
- 存储结果。
澄清一下:您的第 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) * axis
和 cos(angle/2)
(这是你的 toQuaternion
方法产生的)。这很重要,因为它为您提供了很好的单位四元数,形成了 4D sphere,其中表面上的每个点 (surspace?) 代表 3D space 中的旋转,漂亮地允许之间的平滑插值任意两次旋转。