Animer une scène

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 :

Bien sûr, il faudra alors penser à cacher ces "voitures parasites" en affichant par exemple deux rectangles noirs sur les bords.
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.



L'utilisation d'un timer est beaucoup plus optimale pour réaliser une animation par rapport à l'utilisation d'un "Idle" puisque le processeur n'est pas utilisé tout le temps par le programme. Il est possible d'utiliser un timer fourni par Windows, ou bien la fonction glutTimerFunc fournie par Glut. Pour réaliser notre jeu, nous avons utilisé le fonction glutTimerFunc, mais nous allons tout de même donner un exemple utilisant les timers de Windows.



Voici donc un exemple d'utilisation de la gestion des timers fournie par Windows. Pour que le timer puisse "s'exprimer", il faut qu'il se passe quelque chose dans le programme. Nous allons donc réaliser une fenêtre (avec Glut) que l'on appelle "Fenêtre Glut" et un timer qui envoie sur la fenêtre "console" cinq fois la phrase "Evenement timer arrive!" (par intervalle d'une seconde à chaque fois) puis qui se détruit et détruit la fenêtre "Fenêtre Glut" créée au départ : (sont mises en bleu les étapes importantes concernant le timer)



#include <windows.h>
#include <winuser.h> //Pour le timer
#include <gl/gl.h>
#include <gl/glut.h>

int nFin=0;	//Compteur

void vDisplay();	//Fonction d'affichage

VOID CALLBACK MonTimer(); // Fonction Timer

UINT mytimer; // Timer

VOID CALLBACK MonTimer(
  HWND hwnd,     // handle of window for timer messages
  UINT uMsg,     // WM_TIMER message
  UINT idEvent,  // timer identifier
  DWORD dwTime   // current system time
)
{
	printf("Evenement timer arrive!\n");
	nFin++;
	if (nFin==5)
	{
		KillTimer (0,mytimer);
		printf("Timer tue\n");
		glutDestroyWindow(glutGetWindow()); //Destruction de la fenêtre courante
	}
}


int main( int argc, char *argv[ ])
{
	glutInit(&argc,argv);	// initialisation de GLUT
    	glutInitWindowSize(200,200);
	glutInitWindowPosition(300,50);
	glutCreateWindow("Fenêtre Glut");
	glutDisplayFunc(vDisplay);

	mytimer = SetTimer(0, 1, 1000, MonTimer);
		// hwnd = 0, identifiant = 1, intervalle = 1 s (1000 ms)
	
	glutMainLoop();
	return 0;
}

void vDisplay()
{
	glClear(GL_COLOR_BUFFER_BIT);
	glFlush();
}


On obtient le résultat suivant :

      



La gestion de timers avec Glut se fait avec la fonction glutTimerFunc.

Le premier argument de la fonction glutTimerFunc est le nombre de millisecondes avant que la fonction de rappel soit appelée. Ce nombre de millisecondes est un minimum, GLUT appellant la fonction de rappel dès que possible après l'expiration du délai.

Le deuxième argument est un pointeur vers la fonction de rappel.

Le troisième argument est la valeur du paramètre de la fonction de rappel.

void glutTimerFunc ( unsigned int msecs, void (*func)(int value), value);

Voici un exemple d'utilisation de cette fonction. Nous créons une fonction de "rappel du timer" void vTimerIdle(int i) puis deux timers
glutTimerFunc(nMsec[0], vTimerIdle, 1);
glutTimerFunc(nMsec[1], vTimerIdle, 2);

appelant la même fonction de rappel void vTimerIdle(int i).
Notre programme réalise deux timers qui impriment chacun dix fois sur la fenêtre "console" "Evenement timer numero i arrive!" toutes les 100ms pour le premier et toutes les 500ms pour le second :


Les étapes importantes concernant le timer sont mises en bleu :



#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!!