Une classe caméra
Tout est relatif...
Petite introduction sur ce qui va suivre: Jusqu'à présent, j'utilisais pour positionner une caméra la fonction gluLookAt. Cela nous évitait de faire certaines choses. Mais voilà, il va falloir y venir à ces choses ! Donc oublions cette fonction et développons nos propres outils...
"Tout est relatif", car en fait, notre caméra ne bougera jamais. Elle est le centre du monde. Elle se trouvera donc tout naturellement en (0,0,0) et regardera vers l'axe -z. Ce sera aux objets autour de la caméra de se déplacer de tel sorte que l'on pense que la caméra a bougé. Donc dans ce monde virtuel, c'est bien le soleil qui va tourner autour de la terre qui elle est fixe et non l'inverse(n'est ce pas Galilée), en quelque sorte un vague géocentrisme d'Aristote en virtuel...
Avant tout une explication/traduction
Les termes suivants vont être utilisés: tangage, lacet (ou cap) et roulis. Ces termes sont ceux utilisés dans l'aviation et correspondent ici aux rotations suivant les axes X,Y ou Z (rappel annexe 3D).
Dans le monde 3D:
- Le tangage est une rotation sur l'axe X
- Le lacet est une rotation sur l'axe Y
- Le roulis est une rotation sur l'axe Z
Voici les explications en image (tiré de wikipédia)
Le tangage
, le lacet
,
le roulis
Si vous cherchez sur internet, vous trouverez souvent d'autres termes anglophones:
pitch pour tangage, yaw pour lacet ou head pour cap et roll pour roulis
Perte d'un degré de liberté avec les angles d'Euler
La première chose à laquelle on pense généralement dès que l'on parle de rotation, c'est cosinus, et sinus dans un mode 3D donc suivant trois axes...
Bon, si comme moi vous n'avez pas écouté ce que d'autres ont déjà testé avant, vous avez constaté un problème nommé blocage de cardan ou plus connu sur internet sous le nom anglais "gimbal lock" dans le monde 3D...Ce problème se produisant lorsque deux axes "se superposent". Ce qui signifie perte d'un degré de liberté (ben oui, il y en a deux qui tournent de la même manière).
Voyez sur le site de wikipédia pour plus de détail...
La solution, les quaternions
Pour résoudre ce problème, il faut passer dans la quatrième dimension (vous n'entendez pas cette petite musique du feuilleton de même nom ???)
La classe caméra
Vous vous dites : "Bon, ok, je veux bien croire que les quaternions vont nous aider... Mais comment ?"
Pas tout seul c'est certain ! J'ai donc créé une classe caméra pour gérer ... la caméra !
La méthode la plus importante sera la méthode camLookAt. Cette méthode permet d'orienter et de placer la caméra.
Le principe étant le suivant:
La caméra récupèrera les rotations en degré autour des axes x, y et z ainsi que la vélocité pour le déplacement. Déplacement qui se fera suivant la direction regardée.
On sait que pour les rotations, il faut passer par les quaternions. Donc nous allons convertir ses angles de rotation en quaternion unitaire, un par axe de rotation.
Puis nous allons effectuer les dites rotations en multipliant simplement les 3 quaternions.
Les quaternions
n'étant pas commutatifs, il y a un ordre à respecter: Z * X *
Y
Z passant en premier et non en dernier. Car en dernier, la rotation sur Z se ferait sur l'axe Z original. Or avec les rotations sur X et Y, notre nouveau Z n'est pas forcement parallèle au Z original. Il faut donc lui faire subir cette nouvelle orientation.
Ce qui nous donne un nouveau quaternion. Maintenant, il nous faut appliquer le résultat à la matrice courante.
Pour rappel,
sous OpenGL, il y a deux matrices,
la matrice de modélisation (modelview) et la matrice de projection. Pour
ma part, j'utilise la matrice modelview dans ma classe GLWindows au moment de
gérer la caméra. Mais rien n'aurait empêché de faire
le placement de la caméra en passant par la matrice de projection. Matrice
qui est peut-être plus adéquat avec l'utilisation de la caméra.
Cependant cette solution est plus lourde (voir explications
sur la mise en oeuvre du déplacement plus loin)
Cependant, il faut bien noter que dans mon code, ce n'est pas la caméra qui décide de la matrice à utiliser, elle utilise la matrice courante, à vous de choisir la matrice avant de faire appel à cette méthode.
OpenGL ne travail qu'avec des matrices. Nous devons donc reconvertir notre nouveau quaternion en matrice 4x4 compatible OpengL.
Ensuite, il faut simplement multiplier notre matrice avec la matrice courante via glMultMatrix.
Nous avions déjà vu que pour modifier la matrice courante, nous pouvions utiliser glTranslate et glRotation, glMultMatrix nous donne la possibilité de modifier la matrice courante par la matrice passée en paramètre. De cette manière, tous les objets qui seront dessinés par la suite subiront la rotation...
Voici la syntaxe de la fonction:
void glMultMatrixd( const GLdouble * m);
void glMultMatrixf( const GLfloat * m);
En entrée
m représentant 16 valeurs consécutives représentant un
tableau de matrice 4x4
En sortie
m est la nouvelle matrice...
Où une erreur GL_INVALID_OPERATION si glMultMatrix est exécutée entre glBegin et glEnd.
La rotation étant faite, il est nécessaire de faire le déplacement. Pour cela, il faut connaître la direction de la caméra.
La détermination de cette direction se fait en repartant de nos quaternions correspondant aux axes X, Yet Z.
Via l'axe X, nous récupérons j, indiquant la direction suivant Y
Via le quaternion correspondant quaternion Y * quaternion Z, nous récupérerons la direction i et k suivant X et Z.
Pour connaître
l'effet sur la matrice courante de ces transformations, voir ici.
Connaissant la direction et la velocité, nous pouvons en déduire un vecteur direction sur lequel il ne restera plus qu'à faire le produit scalaire avec cette vélocité pour déterminer la nouvelle position en X, Y et Z.
Seul hic, OpenGL ne traitre que des matices. Donc comme pour le quaternion, il y aura création d'une classe vector qui permettra de définir i,j et k (en fait correspondant chacun aux axes x, y et z...). Dans cette classe, la méthode * comme opérateur afin de permettre de définir le produit scalaire.
Tout ceci calculé, il restera à faire un simple glTranslate pour positionner notre caméra. Pour cela, nous utilisons une classe point pour définir les coordonnées d'un point (x,y,z). Rien d'autre dans cette classe pour le moment.
Sauf que vous n'avez pas oublié qu'en fait, ce n'est pas la caméra qui est déplacée, mais bien tous les objets autour de la caméra. Celle-ci restant toujours à sa position (0,0,0) !
Donc prendre toutes les coordonnées en multipliant par -1 ! Car quand la caméra est sensée se déplacer à gauche, ce sont les objets qui vont se déplacer à droite.
glTranslatef(-Position.x, -Position.y, Position.z); (pour rappel, z est positif ici, puisque nous regardons toujours vers -z)
Les autres méthodes de la classe caméra
Après, c'est plutôt basique, il y a trois méthodes permettant
de définir l'angle de rotation (ramené entre 0° et 360°
si vous passé une valeur hors [0, 360°]) autour des trois axes:
void ChangeTangageX(GLfloat degres);
void ChangeLacetY(GLfloat degres);
void ChangeRoulisZ(GLfloat degres);
Une méthode permettant de définir la vélocité:
void ChangeVelocity(GLfloat vel);
Ces quatres méthodes fonctionnent suivant un pas dont un maximum n'est pas à dépasser. Sinon, c'est la valeur maximum de ce pas qui sera affectée.
Il y a donc une methode permettant de définir les différents pas. Sachant que NULL ne change pas la valeur d'un pas:
void SetPasMax (GLfloat fPasTangage, GLfloat fPasLacet, GLfloat fPasRoulis, GLfloat fPasVeloc);
Information sur la caméra
J'ai ajouté deux méthodes qui vont permettre d'afficher à l'écran des informations concernant la caméra, cela implique la possibilité d'afficher du texte sous OpenGL. D'où la classe glTextOut que j'expliquerai ensuite...
Une méthode
void AffInfoCam();
qui va afficher des informations relative à la caméra, ...
Cette méthode
faisant de l'affichage, il faudra bien vérifier que vous êtes sur
la matrice modelview et que vous ayez bien restauré la matrice (via glLoadIndentity
par exemple) afin que le texte ne subissent pas lui aussi les déplacements
de la caméra ou autres...
void DebugCam(HDC hDC);
Cette méthode crée une instance glTextOut et aura donc besoin du device contexte. L'appel à cette méthode est évidemment nécessaire pour la méthode précédente AffInfoCam
Mais aussi des méthodes pour récupérer les différentes valeurs:
Une méthode pour récupérer les 4 valeurs des pas.
void GetPasMax (GLfloat &fPasTangage, GLfloat &fPasLacet, GLfloat &fPasRoulis, GLfloat &fPasVeloc);
Deux pour récupérer la position de la caméra:
void GetPosCam (glPoint &pPosition);
void GetPosCam (GLfloat &x, GLfloat &y, GLfloat &z);
Voici un exemple de classe caméra
Autres sites:
Trois sites intéressant dont deux sites en anglais:
Le site polytechnique de Montréal avec un pdf instructif.
Le site game-dev parle des caméras et de l'utilisation des quaternions avec un exemple de caméra euler vs caméra quaternion en C++...
Nehe parle aussi des quaternions avec un exemple en C++ sous OpenGL