Bonjour, je développe un moteur 3D depuis quelques temps et j'ai plusieurs fois été confronté à des problème de conversion matrice->quaternion (j'utilise les quaternions pour faire des interpolations sphériques linéaires). J'avais contourné le problème en passant tout en quaternion, ce qui m'évitait de devoir faire ce genre de conversion, mais il reste encore un endroit dans le moteur où je suis obligé de faire appel à cette conversion (dans l'exporteur 3dsmax plus exactement).
Donc j'ai une boite simple qui doit faire une rotation de 90 degrés et quand l'angle de rotation est légèrement inférieur à 90, ça ne pose pas de problème. Quand l'angle devient très proche de 90 degré ou légèrement supérieur, le quaternion calculé change de "signe"( il passe de (0.7, -0.7, 0, 0) à (-0.7, 0.7, 0, 0) ).
Voici le code de test que j'ai fais et qui reproduit l'erreur :
et voici la fonction de conversion de matrice en quaternion :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 CQuaternion q, q2; CMatrix m( -2.3841858e-007f, -1.0000000f, 5.8091246e-008f, 0.00000000f, -0.99999934f, 1.1920929e-007f, -0.0011870498f, -1.0773603e-007f, 0.0011870499f, -5.8265869e-008f, -0.99999940f, 2.4647131f, 0.f, 0.f, 0.f, 1.f ); m.GetQuaternion( q ); CMatrix m2( -1.1920929e-007f, -0.99999994f, 5.9604645e-008f, 0.00000000f , -0.99930769f, 2.3841858e-007f, 0.037203975f, -1.0773603e-007f, -0.037203975f, -5.4016709e-008f, -0.99930763f, 2.4647131f, 0.f, 0.f, 0.f, 1.f ); m2.GetQuaternion( q2 );
Voici les valeurs obtenues :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 void CMatrix::GetQuaternion( CQuaternion& quat ) const { float fTrace = m_00 + m_11 + m_22 + 1; if( fTrace > 3.9999 ) quat.Fill( 0, 0, 0, 1 ); else { if ( fTrace > 0.0001 ) { float S = sqrt( fTrace ) * 2; quat.Fill( ( m_21 - m_12 ) / S, ( m_02 - m_20 ) / S, ( m_10 - m_01 ) / S, 0.25f * S ); } else { if( ( abs( m_00 ) > abs( m_11 ) ) && ( abs( m_00 ) > abs( m_22 ) ) ) { if ( ( 1.0 + m_00 - m_11 - m_22 ) <= 0 ) throw 1; float S = sqrt( 1.0 + m_00 - m_11 - m_22 ) * 2; // S=4*qx quat.Fill( 0.25 * S, ( m_01 + m_10 ) / S, (m_02 + m_20 ) / S, ( m_21 - m_12 ) / S ); } else if ( abs( m_11 ) > abs( m_22 ) ) { if ( ( 1.f + m_11 - m_00 - m_22 ) <= 0.f ) throw 1; float S = sqrt( 1.f + m_11 - m_00 - m_22 ) * 2; // S=4*qy quat.Fill( ( m_01 + m_10 ) / S, 0.25f * S, ( m_12 + m_21 ) / S, ( m_02 - m_20 ) / S ); } else { float S; if ( ( 1.f + m_22 - m_00 - m_11 ) <= 0.f ) { throw 1; } else { S = sqrt( 1.f + m_22 - m_00 - m_11 ) * 2.f; // S=4*qz quat.Fill( ( m_02 + m_20 ) / S, ( m_12 + m_21 ) / S, 0.25f * S, ( m_10 - m_01 ) / S ); } } } } }
q = ( 0.70182616, -0.70182621, 0.00042286396, 0.00038762530 )
q2 = ( -0.70689392, 0.70689404, 0.013153042, 0.013157572 )
Ici, on voit que pour des valeurs de matrice très proches (elles représentent des positions extrêmement proches sous 3dsmax), les quaternions obtenus sont totalement différents, ce qui occasionne des problèmes lors de l'interpolation.
La position initiale et finale de mon objet sont bonnes, mais dans le 2eme cas, le changement de signe fait que l'objet tourne dans le sens contraire pour arriver à la position finale (en gros, au lieu d'aller de midi à 3h dans le sens des aiguilles d'une montre, il va de midi à 3h dans le sens contraire).
Donc je me dis qu'il y a 2 possibilités : soit la conversion de matrice en quaternion n'est pas correcte, soit c'est l'interpolation qui s’emmêle les pinceaux à cause du changement de signe (en interprétant ça comme une rotation dans l'autre sens...).
Pour info, voici le code pour l'interpolation de deux quaternions :
... ainsi que la conversion quaternion->matrice que j'utilise à la fin avant d'envoyer tout ça à opengl :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9 void CQuaternion::Slerp( const CQuaternion& q0, const CQuaternion& q1, float t, CQuaternion& qr ) { float fInitAngle = q0.GetAngle(); float fFinalAngle = q1.GetAngle(); float Omega = ( fFinalAngle - fInitAngle ) / 2.f; qr = ( q0 * sin( ( 1 - t ) * Omega ) + q1 * sin( t * Omega ) ) / sin( Omega ); CVector::Lerp( q0.m_vPosition, q1.m_vPosition, t, qr.m_vPosition ); }
Bref, ça fait depuis des jours que je suis sur ce problème et je bloque. Merci d'avance pour votre aide
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 void CQuaternion::GetMatrix( CMatrix& mat ) { mat.m_00 = 1 - 2 * m_y * m_y - 2 * m_z * m_z; mat.m_01 = 2 * m_x * m_y - 2 * m_z * m_w; mat.m_02 = 2 * m_x * m_z + 2 * m_y * m_w; mat.m_03 = m_vPosition.m_x; mat.m_10 = 2 * m_x * m_y + 2 * m_z * m_w; mat.m_11 = 1 - 2 * m_x * m_x - 2 * m_z * m_z; mat.m_12 = 2 * m_y * m_z - 2 * m_x * m_w; mat.m_13 = m_vPosition.m_y; mat.m_20 = 2 * m_x * m_z - 2 * m_y * m_w; mat.m_21 = 2 * m_y * m_z + 2 * m_x * m_w; mat.m_22 = 1 - 2 * m_x * m_x - 2 * m_y * m_y; mat.m_23 = m_vPosition.m_z; mat.m_30 = 0; mat.m_31 = 0; mat.m_32 = 0; mat.m_33 = 1; }
EDIT : Si jamais c'est un problème de conversion matrice->quaternion, ça pourrait se résoudre d'une autre manière, en récupérant par exemple directement le quaternion d'un bone à partir de 3dsmax.
Partager