四元数绕轴旋转的分量

Posted

技术标签:

【中文标题】四元数绕轴旋转的分量【英文标题】:Component of a quaternion rotation around an axis 【发布时间】:2011-04-10 16:49:40 【问题描述】:

我无法找到有关此主题的任何有用信息。基本上我想找到四元数旋转的分量,即围绕给定轴(不一定是 X、Y 或 Z - 任何任意单位向量)。有点像将四元数投影到向量上。因此,如果我要要求围绕与四元数轴平行的某个轴进行旋转,我会得到相同的四元数。如果我要要求绕与四元数轴正交的轴旋转,我会得到一个恒等四元数。介于两者之间......嗯,这就是我想知道如何解决的问题:)

【问题讨论】:

向量的正交和垂直是相同的。对于身份四元数情况,您可能意味着并行。 哇;注意到了!我已经读了好几遍了(并且让同事们独立搜索同样的东西并找到了这个问题)并且以前从未注意到这一点。我的意思是平行于不变的情况(例如,围绕 Y 轴、围绕 Y 轴的旋转分量将保持不变),并更新了问题以反映这一点。谢谢! 【参考方案1】:

这个问题有一个优雅的解决方案,特别适用于四元数。它被称为“摆动扭曲分解”:

伪代码

/**
   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/

【讨论】:

请注意论文“SWING-TWIST DECOMPOSITION IN CLIFFORD ALGEBRA”的一般推导 @minorlogic 这看起来不错,但是我们如何处理奇点呢?我可能误解了一些东西,但如果这个解决方案在一小部分时间内中断,那么它肯定不是很有用吗? 检查“twist”长度不为零可以很容易地捕捉到奇点。它不在上面的代码中(为了简化代码)。 如何检查奇点以及它会出现在哪里? 这是最好的答案,但是少了一步:如果rap的点积是负数,那么你应该否定twist的所有四个分量,这样生成的旋转轴指向与direction 相同的方向。否则,如果您尝试测量旋转角度(忽略轴),您将混淆旋转及其倒数的混合,这取决于在projection(ra, direction) 期间旋转轴方向是否翻转。请注意,projection(ra, direction) 计算的是点积,因此您应该重复使用它而不是计算两次。【参考方案2】:

前几天我试图为动画编辑器找到完全相同的东西;这是我的做法:

    选择要绕其旋转的轴,并找到与其正交的向量。 使用四元数旋转这个新向量。 将此旋转矢量投影到以您的轴为法线的平面上

    这个投影向量和原始正交的点积的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 的旋转。

祝你好运,希望对你有所帮助。

【讨论】:

我现在正在工作,我稍后再试试,如果它有效,我会有点高兴:)在此特定文本框中,“输入”表示“提交”) 谢谢(尽管请检查我提出的第一个问题及其答案,看看您是否仍然这么认为!;))。以防万一,如果您需要将四元数分解为欧拉(这使得重新创建更容易),ed022 在这里有一个非常好的实现:forums.create.msdn.com/forums/p/4574/62520.aspx(第 17 篇文章)。祝你的应用好运! 有个小问题,返回值总是正数。这意味着如果我有-PI/4(或7*PI/4)的旋转,我会得到PI/4。我在这里错过了什么吗? @João,你没有遗漏任何东西,但可能稍微误解了它的工作原理:) 这种方法的核心是 resulting 向量的点积的 acos - 到换句话说——这种方法不是“直接”对四元数进行操作,而是“观察”应用该四元数的结果。因此,返回的角度将始终是两个向量之间的最小角度,并将限制在 0-360 度。您可以使用叉积来恢复变换角度是负的还是正的。祝你好运! @sebf 你能详细说明一下吗?您需要对哪些向量应用叉积才能找出答案?【参考方案3】:

minorlogic 指出摆动扭曲分解的答案是迄今为止最好的答案,但它缺少重要的一步。问题(使用 Minorlogic 的伪代码符号)是,如果 rap 的点积为负,则需要将 twist 的所有四个分量取反,以便生成的旋转轴指向与direction。否则,如果您尝试测量旋转角度(忽略轴),您将混淆正确旋转和正确旋转的 reverse 的混合,具体取决于旋转轴是否调用projection(ra, direction) 时方向发生了翻转。请注意,projection(ra, direction) 计算的是点积,因此您应该重复使用它而不是计算两次。

这是我自己的摆动扭曲投影版本(在某些情况下使用不同的变量名称而不是 Minorlogic 的变量名称),并进行了点积校正。代码用于 JOML JDK 库,例如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;

【讨论】:

【参考方案4】:

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;

【讨论】:

【参考方案5】:

我尝试实现了sebf的回答,看起来不错,除了步骤1中vector的选择:

    选择要绕其旋转的轴,然后找到 正交向量。

不足以获得可重复的结果。我已经在纸上开发了这个,我建议采取以下措施来选择与“你想要找到旋转的轴”正交的向量,即观察轴。有一个垂直于观察轴的平面。您必须将四元数的旋转轴投影到该平面上。使用这个结果向量作为与观察轴正交的向量会得到很好的结果。

感谢 sebf 让我走上正确的道路。

【讨论】:

如果观察轴等于旋转轴,那么这将产生一个长度为零的向量,因此必须检查这种情况。 看我的回答,它可能会解决您关于结果可重复性的问题。

以上是关于四元数绕轴旋转的分量的主要内容,如果未能解决你的问题,请参考以下文章

四元数使用记录

什么是四元数旋转?

四元数与轴+角度

UnityAPI.Quaternion四元数

四元数运动学笔记旋转的雅克比矩阵

四元数法