web-dev-qa-db-ja.com

軸を中心とした四元数回転のコンポーネント

このトピックに関する適切な情報を見つけることができません。基本的には、与えられた軸(必ずしもX、Y、またはZ-任意の単位ベクトルである必要はありません)の周りにある、四元数回転のコンポーネントを見つけたいです。クォータニオンをベクトルに投影するようなものです。したがって、クォータニオンの軸に平行な軸を中心とした回転を要求した場合、同じクォータニオンが戻されます。クォータニオンの軸に直交する軸の周りの回転を要求すると、アイデンティティクォータニオンが得られます。そしてその中間...まあ、それは私がうまくいく方法を知りたいものです:)

38
Ben Hymers

先日、アニメーションエディターとまったく同じものを見つけようとしました。ここに私がそれをした方法があります:

  1. 回転を見つけたい軸を取り、それに直交するベクトルを見つけます。
  2. クォータニオンを使用して、この新しいベクトルを回転させます。
  3. この回転したベクトルを、法線が軸である平面に投影します
  4. この投影されたベクトルと元の直交のドット積の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;
    }
    

正規直交を見つけるコードは次のとおりですが、上記の方法の1つだけが必要な場合は、おそらくはるかにうまくいくでしょう。

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を中心とした回転に分解しました。

幸運を祈ります。

19
sebf

この問題にはエレガントな解決策があり、特に四元数に適しています。 「スイングツイスト分解」として知られています。

疑似コードで

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

29
minorlogic

私はsebfの答えを実装しようとしましたが、ステップ1でのベクトルの選択の選択を除いて、それは良いようです。

  1. 回転を見つけたい軸を取り、それに直交するベクトルを見つけます。

再現性のある結果を得るには不十分です。私はこれを紙の上で開発しました。「回転を見つけたい軸」、つまり観測軸に直交するベクトルを選択するために、次の一連のアクションを提案します。観測軸に直交する平面があります。クォータニオンの回転軸をこの平面に投影する必要があります。この結果のベクトルを観測軸に直交するベクトルとして使用すると、良い結果が得られます。

私を正しい方向に導いてくれたsebfに感謝します。

0
Edward Andò

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;
0