我很难找到关于这个主题的任何好的信息。基本上我想找到四元数旋转的分量,即围绕给定轴(不一定是 X、Y 或 Z - 任何任意单位向量)。有点像将四元数投影到向量上。因此,如果我要要求围绕与四元数轴平行的某个轴进行旋转,我会得到相同的四元数。如果我要要求绕与四元数轴正交的轴旋转,我会得到一个恒等四元数。介于两者之间......好吧,这就是我想知道如何解决的问题:)
5 回答
这个问题有一个优雅的解决方案,特别适用于四元数。它被称为“摆动扭曲分解”:
在伪代码中
/**
Decompose the rotation on to 2 parts.
1. Twist - rotation around the "direction" vector
2. Swing - rotation around axis that is perpendicular to "direction" vector
The rotation can be composed back by
rotation = swing * twist
has singularity in case of swing_rotation close to 180 degrees rotation.
if the input quaternion is of non-unit length, the outputs are non-unit as well
otherwise, outputs are both unit
*/
inline void swing_twist_decomposition( const xxquaternion& rotation,
const vector3& direction,
xxquaternion& swing,
xxquaternion& twist)
{
vector3 ra( rotation.x, rotation.y, rotation.z ); // rotation axis
vector3 p = projection( ra, direction ); // return projection v1 on to v2 (parallel component)
twist.set( p.x, p.y, p.z, rotation.w );
twist.normalize();
swing = rotation * twist.conjugated();
}
可以在此处找到此代码的详细答案和推导 http://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/
前几天我试图为动画编辑器找到完全相同的东西。我是这样做的:
- 取要绕其旋转的轴,并找到与其正交的向量。
- 使用四元数旋转这个新向量。
- 将此旋转矢量投影到以您的轴为法线的平面上
这个投影向量和原始正交的点积的 acos 是你的角度。
public static float FindQuaternionTwist(Quaternion q, Vector3 axis) { axis.Normalize(); // Get the plane the axis is a normal of Vector3 orthonormal1, orthonormal2; ExMath.FindOrthonormals(axis, out orthonormal1, out orthonormal2); Vector3 transformed = Vector3.Transform(orthonormal1, q); // Project transformed vector onto plane Vector3 flattened = transformed - (Vector3.Dot(transformed, axis) * axis); flattened.Normalize(); // Get angle between original vector and projected transform to get angle around normal float a = (float)Math.Acos((double)Vector3.Dot(orthonormal1, flattened)); return a; }
这是查找正交的代码,但是如果您只想要上述方法的一个,您可能会做得更好:
private static Matrix OrthoX = Matrix.CreateRotationX(MathHelper.ToRadians(90));
private static Matrix OrthoY = Matrix.CreateRotationY(MathHelper.ToRadians(90));
public static void FindOrthonormals(Vector3 normal, out Vector3 orthonormal1, out Vector3 orthonormal2)
{
Vector3 w = Vector3.Transform(normal, OrthoX);
float dot = Vector3.Dot(normal, w);
if (Math.Abs(dot) > 0.6)
{
w = Vector3.Transform(normal, OrthoY);
}
w.Normalize();
orthonormal1 = Vector3.Cross(normal, w);
orthonormal1.Normalize();
orthonormal2 = Vector3.Cross(normal, orthonormal1);
orthonormal2.Normalize();
}
尽管上述方法有效,但您可能会发现它的行为与您预期的不同。例如,如果您的四元数将矢量旋转 90 度。大约 X 和 90 度。围绕 Y 你会发现,如果你分解围绕 Z 的旋转,它将是 90 度。也是。如果您想象一个矢量进行这些旋转,那么这很有意义,但取决于您的应用程序,它可能不是所需的行为。对于我的应用程序——约束骨骼关节——我最终得到了一个混合系统。矩阵/四元组自始至终使用,但当涉及到约束关节的方法时,我在内部使用欧拉角,每次将旋转四元组分解为围绕 X、Y、Z 的旋转。
祝你好运,希望有帮助。
迄今为止,minorlogic 指出摆动扭曲分解的答案是最好的答案,但它缺少重要的一步。ra
问题(使用 Minorlogic 的伪代码符号)是,如果和的点积p
为负,那么您需要对 的所有四个分量求反twist
,以便生成的旋转轴指向与 相同的方向direction
。否则,如果您尝试测量旋转角度(忽略轴),您会混淆正确旋转和正确旋转的反向,这取决于调用时旋转轴方向是否发生翻转projection(ra, direction)
。请注意,projection(ra, direction)
计算点积,因此您应该重复使用它而不是计算两次。
这是我自己的摆动扭曲投影版本(在某些情况下使用不同的变量名称而不是 Minorlogic 的变量名称),并进行了点积校正。代码用于 JOML JDK 库,例如 computes v.mul(a, new Vector3d())
,a * v
并将其存储在一个新向量中,然后返回该向量。
/**
* Use the swing-twist decomposition to get the component of a rotation
* around the given axis.
*
* N.B. assumes direction is normalized (to save work in calculating projection).
*
* @param rotation The rotation.
* @param direction The axis.
* @return The component of rotation about the axis.
*/
private static Quaterniond getRotationComponentAboutAxis(
Quaterniond rotation, Vector3d direction) {
Vector3d rotationAxis = new Vector3d(rotation.x, rotation.y, rotation.z);
double dotProd = direction.dot(rotationAxis);
// Shortcut calculation of `projection` requires `direction` to be normalized
Vector3d projection = direction.mul(dotProd, new Vector3d());
Quaterniond twist = new Quaterniond(
projection.x, projection.y, projection.z, rotation.w).normalize();
if (dotProd < 0.0) {
// Ensure `twist` points towards `direction`
twist.x = -twist.x;
twist.y = -twist.y;
twist.z = -twist.z;
twist.w = -twist.w;
// Rotation angle `twist.angle()` is now reliable
}
return twist;
}
Unity3d 的代码
// We have some given data
Quaternion rotation = ...;
Vector3 directionAxis = ...;
// Transform quaternion to angle-axis form
rotation.ToAngleAxis(out float angle, out Vector3 rotationAxis);
// Projection magnitude is what we found - a component of a quaternion rotation around an axis to some direction axis
float proj = Vector3.Project(rotationAxis.normalized, directionAxis.normalized).magnitude;
我尝试实现sebf的回答,看起来不错,除了步骤1中vector的选择的选择:
- 取要绕其旋转的轴,并找到与其正交的向量。
不足以获得可重复的结果。我已经在纸上开发了这个,我建议采取以下措施来选择与“你想要找到旋转的轴”正交的向量,即观察轴。有一个垂直于观察轴的平面。您必须将四元数的旋转轴投影到该平面上。使用这个结果向量作为与观察轴正交的向量会得到很好的结果。
感谢 sebf 让我走上正确的道路。