Le Club de Programmation est structuré en modules d'un à deux mois. L'accent est mis sur une pratique ludique et peu contraignante de la programmation. Ce document a été conçu pour servir de support de cours à une personne organisant un Club de Programmation. Les séances sont pensées au maximum pour qu'il n'y ait pas besoin d'avoir assisté aux précédentes pour réussir, le nombre de fonctions à utiliser dans chacune n'augmente d'ailleurs pas. Il faut laisser les participants faire des erreurs et créer du code éventuellement mal optimisé pour qu'ils puissent s'approprier les outils mis à leur disposition. Des bonnes pratiques de programmeur seront distillées au cours de l'année (le document ci-contre en est une très bonne source [http://www.research.att.com/~bs/JSF-AV-rules.pdf]). La recherche documentaire est une tâche très récurrente pour le programmeur, elle fera partie du Club. Enfin l'autonomie demandée aux membres augmentera au fil des modules. == Module découverte d'OpenGL == La personne responsable de ce module doit avoir au préalable lu un document sur OpenGL. La lecture d'un document sur la SDL n'est pas nécessaire, le fichier SDL_ginfo.h contenu dans l'archive fournie pouvant servir de référence. Le centre de documentation possède justement un livre assez conséquent mais excellent sur OpenGL, bien que quelques didacticiels sur Internet puissent suffire. === Séance 1 === Téléchargeons tout d'abord le projet de départ [http://ginfo.centrale-marseille.fr/ressources/ClubProgrammation.7z] et décompressons-le dans le dossier private de notre compte ECM. Dans le dossier ClubProgrammation qui est créé, lançons le projet ClubProg.dev qui s'ouvre automatiquement avec DevC++. Dans l'espace de travail qui s'affiche, on repère à gauche les fichiers constituant le projet, en bas le log de compilation et en haut les 4 boutons relatifs à la compilation. Compilons et exécutons le projet. Un cadre de fenêtre apparaît et disparaît. Ginfo_Init(); crée cette fenêtre et Ginfo_Quit() la supprime. C'est trop court, en ajoutant une ligne SDL_Delay(5000); on temporise pour 5s l'affichage du cadre de fenêtre. Cependant ce cadre reste vide. Ajoutons une ligne SDL_GL_SwapBuffers(); avant le délai pour rafraîchir l'affichage. Nous affichons ainsi une magnifique zone de dessin... noire. Nous sommes à présent prêts à dessiner. Commençons par indiquer à OpenGL que les points que nous allons lui envoyer doivent former des lignes: glBegin(GL_LINES); On envoie à présent les points un par un à l'aide de lignes de la forme glVertex2f(x, y); où x et y sont les coordonnées appartenant à [-1.33,1.33]x[-1.0,1.0]. Les coordonnées positives sont en haut à droite. Ajoutons un peu de couleur avec la ligne glColor3f(rouge, vert, bleu); où les couleurs sont des valeurs en virgule flottante entre 0.0 et 1.0. Chaque point que nous envoyons hérite de la couleur spécifiée dans le précédent glColor dans l'ordre des lignes. Voici un exemple type de ce que nous avons ajouté entre Ginfo_Init(); et Ginfo_Quit(); : glBegin(GL_LINES); glVertex2f(0.0, 0.0); glColor3f(1.0, 0.0, 0.0); glVertex2f(0.2, 0.2); glColor3f(0.0, 1.0, 0.0); glVertex2f(0.15, 0.05); glVertex2f(0.2, 0.0); glEnd(); SDL_GL_SwapBuffers(); SDL_Delay(5000); Passons à un peu d'exercice, il s'agit de dessiner une étoile à 5 branches en n'envoyant que 5 points. Il convient pour cela de faire une recherche sur la fonction glBegin et les constantes pouvant lui être envoyées en argument. Ceci fait, il faut à présent remplir l'étoile en n'ajoutant qu'un unique point et avec un autre argument de la fonction glBegin. Cet exercice fait apparaître une des méthodes de remplissage de forme se basant sur un centre à l'aide de GL_TRIANGLE_FAN. === Séance 2 === Cette séance est destinée à la fois aux personnes venues à la précédente et aux nouvelles. Dessinons un croissant de Lune. Celui-ci doit être bien courbe (pas un demi-cercle !) pour ne pas pouvoir être rempli avec un GL_TRIANGLE_FAN. A présent, remplissons la forme dessinée sans ajouter de point. On se base sur un nouvel argument de glBegin qui montre la deuxième méthode de remplissage de formes longues à l'aide de GL_TRIANGLE_STRIP. Pour finir cette séance, nous allons "caméléoniser" la Lune, c'est-à-dire que sa couleur va changer au cours du temps. Pour ce faire, on place le dessin dans une boucle for et on fait varier la couleur en fonction du paramètre de la boucle. Voici un exemple de ce que notre code est devenu (toujours entre les lignes Ginfo_Init(); et Ginfo_Quit();) : int i; for (i=0; i<100; i++) { glColor3f(1.0, 1.0-i/100.0, i/100.0); // Votre dessin vient ici. SDL_GL_SwapBuffers(); SDL_Delay(40); } Remarquez qu'à présent le délai n'est plus 5s après affichage d'une image unique, mais 40ms entre chaque image, permettant une animation fluide. === Séance 3 === Séance création libre ! Fixons tout de même un thème commun pour canaliser les indécis. Je propose "Crash test" mais vous pouvez en choisir un autre. On place comme toujours le dessin dans une boucle for, cependant cette fois ce sont les coordonnées des points qui dépendent du paramètre de boucle. Il manque cependant quelque chose, il faut réinitialiser l'affichage entre deux dessins de la scène, sinon les déplacements gardent une trainée pas belle sur l'écran. Il s'agit en fait ni plus ni moins de peindre l'écran en noir avant chaque affichage de la scène. Ca se fait en 4 points avec un GL_TRIANGLE_STRIP... === Séance 4 === Nouvelle séance création libre ! Le thème que je propose pour celle-ci est "Les miracles de la nature". === Séance 5 === Jusqu'à présent, nos dessins s'affichaient pendant un temps fixe, sans possibilité de déplacer ni fermer la fenêtre. Nous allons y remédier. Avec la SDL, lorsque l'utilisateur bouge la souris, appuie sur une touche du clavier ou clique sur la croix de fermeture de la fenêtre, un évènement est créé sous la forme d'une structure SDL_Event et stocké en attente d'être traité. Ce système d'évènements est issu du système d'exploitation : la SDL récupère les évènements de Windows, Linux, ... et nous les envoie. Afin que toute action de l'utilisateur ait un effet instantanément visible sur le programme, il nous faut consulter régulièrement les évènements auprès de la SDL. On utilise pour cela la fonction SDL_PollEvent qui remplit la structure SDL_Event qu'elle reçoit en argument avec un évènement à traiter. Elle renvoie 0 s'il ne restait plus d'évènements à traiter. La structure SDL_Event nous renseigne sur la nature de l'évènement (mouvement de souris, appui sur une touche, ...) avec la variable type et contient aussi pour chaque type d'évènement des informations supplémentaires (la nouvelle position de la souris dans la structure motion). Commençons par le type SDL_QUIT. La boucle typique de traitement des évènements ressemblera typiquement à ceci : SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: // Modification des variables adequates break; default: break; } } L'exercice pour cette séance consiste à afficher la fenêtre vide tant que l'utilisateur ne clique pas sur la croix de fermeture. On remarque que dans ce nouveau programme la fenêtre peut être déplacée en cours de fonctionnement. C'est dû à la circulation des évènements dans un système d'exploitation : les évènements fraîchement générés sont traités par la fenêtre au premier plan, puis successivement par celles apparaissant derrière, enfin par le système d'exploitation lui-même qui leur réserve un traitement par défaut. En traitant les évènements, nous n'en bloquons plus la circulation et l'évènement déplacement de fenêtre peut être traité par défaut par le système d'exploitation qui la déplace effectivement. Avec cette base de travail, il convient à présent d'ajouter une animation et pourquoi pas une précédemment réalisée. J'impose cependant deux contraintes : l'utilisateur doit pouvoir fermer la fenêtre à tout instant de l'animation, et il ne doit pas y avoir d'affichage dans la boucle de traitement des évènements. === Séance 6 === Le but de cette séance est d'afficher un objet se déplaçant avec la souris. Nous allons donc traiter l'évènement SDL_MOUSEMOTION. Lorsque ce type est détecté, la structure motion peut être consultée afin de récupérer la position de la souris. Si c'est un autre type, il est préférable de considérer que les données dans motion n'ont aucun sens. On stockera donc la position de la souris dans nos propres variables. Dans la précédente séance, j'ai été volontairement vague quant à la structure à adopter pour résoudre le problème de l'affichage d'une animation avec interaction utilisateur. Je donne ici une piste plus concrète pour structurer le programme. Cela nous permettra aussi de comprendre plus rapidement le code entre nous. Le voici donc (encore entre Ginfo_Init(); et Ginfo_Quit();) : SDL_Event event; int enCours=1, x, y; while (enCours) { // Dessin // Actualisation de l'affichage et temporisation SDL_GL_SwapBuffers(); SDL_Delay(40); // Traitement des evenements while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: enCours=0; break; case SDL_MOUSEMOTION: x = event.motion.x; y = event.motion.y; break; default: break; } } } Dans cette structure, le dessin et le traitement des évènements sont correctement séparés, et l'actualisation de l'affichage et la temporisation sont présents en un unique exemplaire. Je rappelle toutefois que l'effacement de l'écran doit se trouver au début de la partie dessin. Lorsque vous commencez un tel programme, créez d'abord cette structure en trois parties dans le premier while, puis remplissez-la, vous gagnerez du temps. On remarque au passage que les systèmes d'exploitation n'ont pas le même repaire de coordonnées qu'OpenGL. === Séance 7 === Encore une séance création libre ! Le thème est très abstrait, puisqu'il s'agit de chercher le beau en prenant en compte les actions de l'utilisateur. J'en vois tout de suite qui ne sauront jamais quoi faire, je leur propose donc de faire un pinceau pour peindre sur l'écran en cliquant avec la souris (SDL_MOUSEBUTTONDOWN), et dont la couleur change selon la vitesse du curseur (voir dans la structure motion). === Séance 8 === Oublions un instant le dessin pur pour passer à l'affichage de texte. En pratique, il faut créer en mémoire avec SDL_ttf le dessin représentant le texte à afficher, puis le passer à OpenGL qui l'affiche. Les deux ayant cependant un fonctionnement interne assez différent, je vous épargne les efforts avec la fonction Ginfo_printf. Celle-ci prend les mêmes arguments que la fonction printf, elle ne permet simplement pas le saut de ligne. La fonction glRasterPos2f permet de positionner le coin bas-gauche du texte à l'écran. Le texte hérite de plus de la couleur spécifiée avec glColor3f. Voici donc un exemple de dessin de texte : glColor3f(1.0, 0.5, 1.0); glRasterPos2f(-0.1, -0.01); Ginfo_printf("J'aime le rose !"); Ceci n'est que le dessin, il faut bien entendu y ajouter une structure avec interception d'évènements. Nous allons réaliser un QCM en deux séances. Pour cette séance, il convient de faire la partie dessin, je vous laisse donc préparer plusieurs affichages de texte possibles (avec l'instruction switch) selon une variable qui sera modifiée en séance suivante dans la partie évènements. === Séance 9 === Terminons la Question à Choix Multiples entamée au cours de la séance précédente. Vous avez normalement une (ou plusieurs) variable qui ne demande qu'à être modifiée pour changer l'affichage. Dans le traitement des évènements, nous allons donc ajouter un cas SDL_KEYDOWN (ou SDL_KEYUP). Lorsque ce type est détecté, il convient de faire un nouveau test sur la touche qui a été pressée. La variable à tester est event.key.keysym.sym, il convient de faire une recherche sur SDLKey pour connaître les valeurs possibles. Selon la touche qui a été pressée, l'affichage change, nous venons de réaliser un QCM à une question. Un petit défi pour terminer la séance, il s'agit de faire un QCM complet, c'est-à-dire une série de questions, avec pourquoi pas un petit résumé à la fin, comme un geekomètre ! === Séance 10 === A partir de cette séance, nous passons à la 3D ! Il nous va d'abord falloir modifier le fichier SDL_ginfo.h situé dans le dossier include. Ce fichier contient les fonctions Ginfo_Init, Ginfo_Quit et Ginfo_printf créées spécialement pour le Club Programmation. Nous allons modifier la fonction Ginfo_Init. Sous le troisième commentaire, une série de fonctions glDisable référence des modules intéressants d'OpenGL. Nous devons en particulier activer GL_DEPTH_TEST. Celui-ci permet que les objets au premier plan soient bien affichés devant les objets en arrière plan quel que soit l'ordre dans lequel on les envoie à OpenGL. Une nouvelle zone mémoire est utilisée pour stocker la profondeur de chaque pixel en plus de la couleur de chaque pixel. Le quatrième commentaire est déjà plus intéressant. L'affichage de la 3D est une projection de l'espace 3D sur un plan 2D, l'écran. C'est un simple calcul matriciel, et c'est pourquoi nous voyons les fonctions d'OpenGL manipuler des matrices. La fonction glViewport spécifie la portion de la fenêtre dans laquelle dessiner la scène, elle est réglée dans SDL_ginfo.h pour dessiner dans la totalité de la fenêtre. glMatrixMode(GL_PROJECTION); indique à OpenGL que les futures opérations matricielles affecteront la projection de l'univers sur l'écran. On en réinitialise la matrice avec glLoadIdentity(); car toute nouvelle matrice envoyée à OpenGL est multipliée à la précédente. La fonction gluOrtho2D envoie automatiquement à OpenGL une matrice de projection selon la direction z. Remplaçons cette fonction par gluPerspective (il convient de faire une recherche pour savoir quels arguments lui donner), nous aurons de la 3D. Les deux dernières fonctions indiquent que toutes les futures opérations matricielles concerneront le déplacement de la caméra. Voici un exemple de ce que ça peut donner : glViewport(0, 0, 800, 600); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(80.0, 800.0/600.0, 0.2, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); Revenons maintenant au main.c. Il faut positionner la caméra avant de dessiner. Pour ce faire, on réinitialise d'abord sa position avec glLoadIdentity(); et on la positionne avec la fonction gluLookAt qui prend comme arguments (position, point à regarder, vecteur pieds->tête), donc 9 valeurs. Remplaçons maintenant le code utilisé jusqu'à présent pour effacer l'écran par une fonction standard d'OpenGL qui effacera du même coup le tampon de profondeur que nous venons d'activer. On ajoute donc glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); Il convient à présent d'utiliser glVertex3f et glRasterPos3f pour envoyer des coordonnées 3D et le tour est joué ! Voici un exemple de dessin : glLoadIdentity(); gluLookAt(10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glBegin(GL_QUADS); glVertex3f(-1.0, -1.0, -1.0); glVertex3f(1.0, 1.0, -1.0); glVertex3f(1.0, 1.0, 1.0); glVertex3f(-1.0, -1.0, 1.0); glEnd(); Ceci n'est toujours que le dessin, il faut y ajouter la structure précédemment définie. === Séance 11 === Aujourd'hui, nous allons appliquer un effet sympathique et simple avec OpenGL : le motion blur. Il consiste à créer une rémanence de l'affichage à l'écran. Pour cela, nous allons mélanger l'image à afficher avec l'image précédemment affichée. Cette dernière est stockée dans un troisième tampon, le tampon d'accumulation. Il suffit d'ajouter les trois lignes suivantes avant l'appel à SDL_GL_SwapBuffers : glAccum(GL_ACCUM, 1.0/x); glAccum(GL_MULT, x/(x+1.0)); glAccum(GL_RETURN, 1.0); La première ligne additionne une fraction du tampon des couleurs au tampon d'accumulation. La seconde ligne renormalise les valeurs de couleur du tampon d'accumulation. Enfin la dernière ligne recopie le tampon d'accumulation sur le tampon des couleurs qui sera affiché à l'écran. Le motion blur est aussi simple que ça ! La variable x est un réel qui sert à contrôler la durée de la rémanence. Il convient de faire quelques tests pour trouver le nôtre. Pour la fin de cette séance (et du module Découverte d'OpenGL), pourquoi pas reprendre nos créations précédentes et leur donner des évènements, de la 3D ou même ce flou artistique que nous venons de créer. [[Catégorie : GCP - màj]]