Principe pour animer une scène
Utilisation du mécanisme d'Idle
Utilisation d'un Timer
Géré par Windows
Géré par Glut
Une manière simple pour animer une scène consiste à modifier les variables qui définissent les objets de notre scène. Par exemple, si un quadrilatère a pour coordonnées
(x1,y1;x2,y2;x3,y3;x4;y4) ((x1,y1) coordonnées du premier sommet, (x2,y2) celles du second sommet, ...) et que l'on souhaite déplacer ce quadrilatère, il faudra modifier les coordonnées
du quadrilatère en (x1+dx,y1+dy;x2+dx,y2+dy;x3+dx,y3+dy;x4+dx;y4+dy).
Le quadrilatère se sera alors déplacé de dx horizontalement et de dy verticalement.
Il faut réactualiser les valeurs des variables dx et dy pour que le quadrilatère bouge.
Quand le faire? Si on souhaite le faire en continu, il y a plusieurs façons de modifier des variables de manière automatique. Il est par exemple possible d'utiliser la fonction Glut glutIdleFunc
ou bien un timer (géré par Windows
ou bien par Glut via la fonction glutTimerFunc
).
La fonction
glutIdleFunc
permet de faire quelque chose lorsqu'il ne se passe rien d'autre. Il suffit donc d'appeler cette fonction
en lui donnant comme paramètre un pointeur vers une fonction qui met à jour les coordonnées du quadrilatère.
Le principal inconvénient de cette fonction est que le processeur est alors utilisé à cent pour cent par le programme, ce qui n'est pas du tout optimal.
En effet, la fonction glutIdleFunc
active la fonction passée comme paramètre dès que le processeur est au repos. De plus, la réactualisation des variables
dépend de la vitesse du processeur. Cette solution
n'est donc envisageable que pour des petites applications. Sinon, il vaut mieux utiliser un timer.
Voici un exemple utilisant la fonction glutIdleFunc
. Il s'agit dans ce cas de faire défiler des "voitures" qui ne sont pour l'instant que de simples
carrés bleus. On souhaite qu'il y ait toujours trois voitures affichées à l'écran. Pour cela, on en affiche quatre, et on les fait sortir de l'écran.
Dès que les voitures sortent de l'espace que l'on s'est défini, on les réaffiche de l'autre côté de l'écran. Ceci sera sans doute plus clair
sur un schéma :
On obtient donc le code suivant : (sont mises en bleu les étapes importantes concernant l'idle et l'animation)
Le tableau de variables fDistance est modifié dans l'Idle.
#include <windows.h>
#include <gl/gl.h>
#include <gl/glut.h>
#define WIDTH 640 // Taille de la fenêtre
#define HEIGHT 480
#define NBVOITURE 4 // Nombre de voitures dans une file
// Variables du programme
float fLigne=(float) 0.1; // Taille d'une ligne en pourcentage de la taille totale de la fenêtre
float fDistance[NBVOITURE];
float fEcart;
// Déclaration des fonctions
void vDisplay();
void vReshape(int w, int h);
void vIdle();
void vQuadrilatere(float fRouge, float fVert, float fBleu, float fAx, float fAy,
float fBx, float fBy, float fCx, float fCy, float fDx, float fDy,
float fHori, float fVerti);
// Programme principal
int main( int argc, char *argv[ ])
{
int i; //Compteur
fEcart=WIDTH/(NBVOITURE-1);
for (i=0;i<NBVOITURE;i++)
{
fDistance[i]=i*fEcart; // Réinitialisé lorsque la voiture sort de l'écran
}
glutIdleFunc(vIdle); // Activation du callback
glutInit(&argc,argv); // initialisation de GLUT : argc et argv sont respectivement
// le nombre et la liste des paramètres passées en ligne de commande
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
// GLUT_RGBA mode "vraies couleurs" 32 bits
// GLUT_DOUBLE "double buffering" - deux tampons
glutInitWindowSize(WIDTH,HEIGHT); // Initialisation de la largeur et de la hauteur de la fenêtre
glutInitWindowPosition(50,50); // Position de la fenêtre sur l'écran par rapport au coin haut gauche
glutCreateWindow("Grenouf"); // Titre de la fenêtre
glutDisplayFunc(vDisplay); // précise la fonction à utiliser pour l'affichage
glutReshapeFunc(vReshape); // précise la fonction à utiliser pour le redimensionnement
glutMainLoop(); // lance le gestionnaire glut
return 0;
}
void vDisplay()
{
//Variables locales
int i;
glClearColor(0,0,0,0); // selectionne la couleur noire (qui est celle par défaut)
glClear(GL_COLOR_BUFFER_BIT); // efface le frame buffer
// Trottoir de départ
vQuadrilatere(0.75,0.75,0.75,-WIDTH/2,-HEIGHT/2,
WIDTH/2,-HEIGHT/2, WIDTH/2,-(HEIGHT/2-fLigne*HEIGHT),
-WIDTH/2,-(HEIGHT/2-fLigne*HEIGHT),
0,0);
// 1ère file de la route
vQuadrilatere(0.40,0.40,0.40,WIDTH/2,-(HEIGHT/2-fLigne*HEIGHT),
-WIDTH/2,-(HEIGHT/2-fLigne*HEIGHT), -WIDTH/2,-(HEIGHT/2-2*fLigne*HEIGHT),
WIDTH/2,-(HEIGHT/2-2*fLigne*HEIGHT),
0,0);
// Trottoir du milieu
vQuadrilatere(0.75,0.75,0.75,WIDTH/2,-(HEIGHT/2-2*fLigne*HEIGHT),
-WIDTH/2,-(HEIGHT/2-2*fLigne*HEIGHT), -WIDTH/2,-(HEIGHT/2-3*fLigne*HEIGHT),
WIDTH/2,-(HEIGHT/2-3*fLigne*HEIGHT),
0,0);
// "Grenouille"
vQuadrilatere(0,0.46,0.25,-fLigne*HEIGHT/2,-HEIGHT/2,fLigne*HEIGHT/2,-HEIGHT/2,fLigne*HEIGHT/2,
-(HEIGHT/2-fLigne*HEIGHT),-fLigne*HEIGHT/2,-(HEIGHT/2-fLigne*HEIGHT),0,0);
// Boucle de création des voitures
for (i=0;i<NBVOITURE;i++)
{
// Voiture i
if(fDistance[i]>=WIDTH+fEcart) fDistance[i]=0;
vQuadrilatere(0.25,0,0.46,-WIDTH/2-fLigne*HEIGHT,-(HEIGHT/2-fLigne*HEIGHT),
-WIDTH/2,-(HEIGHT/2-fLigne*HEIGHT), -WIDTH/2,-(HEIGHT/2-2*fLigne*HEIGHT),
-WIDTH/2-fLigne*HEIGHT,-(HEIGHT/2-2*fLigne*HEIGHT),
fDistance[i],0);
}
// Deux rectangles sur les bords gauche et droits pour cacher les voitures qui sont hors du terrain
// lorsque l'utilisateur redimensionne la fenêtre
// Gauche
vQuadrilatere(0,0,0,-WIDTH-fEcart,-HEIGHT/2,-WIDTH/2,-HEIGHT/2,-WIDTH/2,HEIGHT/2,-WIDTH-fEcart,HEIGHT/2,0,0);
// Droite
vQuadrilatere(0,0,0,WIDTH/2,-HEIGHT/2,WIDTH+fEcart,-HEIGHT/2,WIDTH+fEcart,HEIGHT/2,WIDTH/2,HEIGHT/2,0,0);
glutSwapBuffers(); //Permutation des 2 buffers
}
void vIdle()
{
int i; // Compteur
for (i=0;i<NBVOITURE;i++)
{
fDistance[i]+=3;
}
glutPostRedisplay(); // force le réaffichage de la scène
}
void vReshape(int w, int h)
{
float L;
float H;
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION); // Choisit la matrice de projection
glLoadIdentity();
if (w<=h)
{
if (w==0) H=HEIGHT;
else H=(GLfloat) (WIDTH*h/w);
L=WIDTH;
}
else
{
H=HEIGHT;
if (h==0) L=WIDTH;
else L=(GLfloat) (HEIGHT*w/h);
}
gluOrtho2D(-L/2,L/2,-H/2,H/2);
}
void vQuadrilatere(float fRouge, float fVert, float fBleu, float fAx, float fAy,
float fBx, float fBy, float fCx, float fCy, float fDx, float fDy,
float fHori, float fVerti)
{
glBegin(GL_QUADS); //commence le dessin d'un quadrilatère
glColor3d(fRouge,fVert,fBleu); // couleur du quadrilatère
glVertex2d(fAx+fHori,fAy+fVerti); //coordonnées du premier sommet
glVertex2d(fBx+fHori,fBy+fVerti);
glVertex2d(fCx+fHori,fCy+fVerti);
glVertex2d(fDx+fHori,fDy+fVerti); // Ordre des sommets : sens trigonométrique ou sens horaire (ne pas mélanger les sommets!!!)
glEnd();
}
Nous avons dû ajouter comme paramètre GLUT_DOUBLE à la fonction glutInitDisplayMode()
. En effet, pour que l'animation
s'affiche correctement, il faut utiliser deux buffers. (Avec un seul buffer, le redessinement de la scène s'effectue pendant qu'elle s'affiche, ce qui provoque
des sursauts de l'image.)
Nous allons donc travailler avec 2 buffers (technique du double-buffering) : une image est affichée pendant que l'autre est calculée,
et lorsque celle-ci est terminée, on échange les deux.
L'animation sera alors produite par la permutation des deux buffers. Cette technique permet ainsi de créer une animation fluide puisque l'on n'affiche que des images complètes.
Il faut donc spécifier que l'on utilise le double buffering, ce qui se fait dans la fonction glutInitDisplayMode()
(paramètre GLUT_DOUBLE).
La permutation des deux buffers s'effectue à la fin du paramétrage de la scène, c'est-à-dire à la fin de la fonction
Display
, avec la fonction glutSwapBuffers()
. Il n'est pas nécessaire d'appeller glFlush()
dans
ce cas car glutSwapBuffers()
l'appelle implicitement.
On obtient le résultat suivant :
#include <windows.h>
#include <gl/gl.h>
#include <gl/glut.h>
int nCompteur[2]={0,0};
int nMsec[2]={100,500}; //100 ms et 500 ms
void vTimerIdle(int i)
{
printf("Evenement timer numero %d arrive!\n",i);
nCompteur[i-1]++;
if(nCompteur[i-1]<10) glutTimerFunc(nMsec[i-1],vTimerIdle,i);
}
void vDisplay()
{
glClearColor(0,0,0,0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
int main( int argc, char *argv[ ])
{
glutInit(&argc,argv); // initialisation de GLUT
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
glutInitWindowSize(100,100);
glutInitWindowPosition(50,50);
glutCreateWindow("Timer avec Glut");
glutDisplayFunc(vDisplay);
glutTimerFunc(nMsec[0], vTimerIdle, 1);
glutTimerFunc(nMsec[1], vTimerIdle, 2);
glutMainLoop();
return 0;
}
On obtient le résultat suivant :
Ainsi, pour réaliser l'animation d'une scène, il suffit de mettre à jour les coordonnées des
objets de la scène dans la fonction de rappel, de la même manière que pour la fonction Idle développée
plus haut. L'exemple des voitures qui défilent est donc modifié de la manière suivante : il faut supprimer dans ce programme tout ce qui concerne l'ancienne fonction Idle (void Idle();
dans la déclaration
des fonctions, glutIdleFunc(Idle);
dans le main
et la définition de la fonction void Idle
).
Il faut ajouter void vTimerIdle(int i);
dans la déclaration des fonctions, ainsi que sa définition :
void vTimerIdle(int i)
{
int k; // Compteur
for (k=0;k<NBVOITURE;k++)
{
fDistance[k]+=3;
}
glutPostRedisplay();
glutTimerFunc(20,vTimerIdle,i); // On choisit un timer de 20ms
}
Il faut également ajouter glutTimerFunc(20, vTimerIdle, 1); //20 ms
dans le main
avant le glutMainLoop();
.
Ainsi l'animation obtenue est la même qu'avec la fonction Idle, sauf qu'elle ne dépend plus du processeur sur lequel elle est exécutée et n'utilise plus toutes ses ressources!!