web-dev-qa-db-fra.com

Composant d'une rotation de quaternion autour d'un axe

J'ai du mal à trouver de bonnes informations sur ce sujet. En gros, je veux trouver le composant d’une rotation de quaternion, c’est-à-dire autour d’un axe donné (pas nécessairement X, Y ou Z - n’importe quel vecteur unitaire arbitraire). Un peu comme projeter un quaternion sur un vecteur. Donc, si je devais demander la rotation autour d'un axe parallèle à celui du quaternion, je retrouverais le même quaternion. Si je devais demander la rotation autour d'un axe orthogonal à l'axe du quaternion, je sortirais un quaternion identité. Et entre les deux… eh bien, c'est ce que j'aimerais savoir comment travailler :)

35
Ben Hymers

L'autre jour, j'ai essayé de trouver exactement la même chose pour un éditeur d'animation. voici comment je l'ai fait:

  1. Prenez l'axe sur lequel vous souhaitez trouver la rotation et trouvez-y un vecteur orthogonal.
  2. Faites pivoter ce nouveau vecteur en utilisant votre quaternion.
  3. Projetez ce vecteur pivoté sur le plan dont la normale est votre axe
  4. L'acos du produit scalaire de ce vecteur projeté et de l'orthogonal d'origine est votre angle.

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

Voici le code pour trouver les orthonormaux, mais vous pouvez probablement faire beaucoup mieux si vous ne voulez que celui de la méthode ci-dessus:

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();
}

Bien que ce qui précède fonctionne, vous constaterez peut-être qu'il ne se comporte pas comme prévu. Par exemple, si votre quaternion fait pivoter un vecteur de 90 degrés. autour de X et 90 deg. autour de Y, vous constaterez que si vous décomposez la rotation autour de Z, elle sera de 90 degrés. ainsi que. Si vous imaginez un vecteur effectuant ces rotations, cela est parfaitement logique mais, en fonction de votre application, ce comportement peut ne pas être souhaité. Pour mon application - contraindre les articulations du squelette - je me suis retrouvé avec un système hybride. Les matrices/quats utilisées tout au long de la méthode mais en ce qui concerne la méthode pour contraindre les joints, j’utilisais des angles euler en interne, décomposant la rotation quat en rotations autour de X, Y, Z à chaque fois. 

Bonne chance, j'espère que ça a aidé.

16
sebf

Il existe une solution élégante à ce problème, spécialement adaptée aux quaternions. C'est ce qu'on appelle la "décomposition swing twist":

en pseudocode

/**
   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();
}

Et la réponse longue et la dérivation de ce code peuvent être trouvées ici http://www.euclideanspace.com/maths/geometry/rotations/for/decomposition/

27
minorlogic

J'ai essayé d'implémenter la réponse de sebf, cela semble bien, sauf que le choix du vecteur à l'étape 1:

  1. Prenez l'axe sur lequel vous voulez trouver la rotation et trouvez-y un vecteur orthogonal .

n'est pas suffisant pour des résultats reproductibles. J'ai développé ceci sur papier et je suggère le plan d’action suivant pour le choix du vecteur orthogonal à "l’axe autour duquel vous souhaitez trouver la rotation", c’est-à-dire l’axe d’observation. Il y a un plan orthogonal à l'axe d'observation. Vous devez projeter l’axe de rotation de votre quaternion sur ce plan. L'utilisation de ce vecteur résultant comme vecteur orthogonal à l'axe d'observation donnera de bons résultats.

Merci à sebf de m'avoir orienté dans la bonne voie.

0
Edward Andò