Buts: Mettre en place les outils permettant de visualiser les différentes «couches» de cellules d'un organe.
Dans les modules précédents, nous avons commencé à coder la méthode generate qui permet d'initialiser un Organ. Nous allons poursuivre le codage de cette méthode en nous intéressant à la facette de la représentation graphique.
Un Organ est pour nous une entité dessinable et nous y avons déjà anticipé une méthode drawOn(sf::RenderTarget& target) qui se borne à dessiner une image. Le but maintenant est de créer cette image de façon adéquate en tenant compte du contenu de la grille.
Chaque case de la grille contient un CellsLayer et l'affichage se fera avec un certain ordre de priorité : les cellules de l'organes recouvrent celles de l'ECM et sont recouvertes par les cellules sanguines.
Les cellules sanguines et celles de l'organe s'afficheront comme des carrés texturés. Pour les cellules de l'ECM, vu qu'elles sont toujours présentes, il n'est pas nécessaire de les dessiner explicitement et le fond clair dessiné jusqu'ici fera l'affaire.
Chaque carré texturé sera représentée par quatre sommets SFML (des sf::Vertex) : les sommets NW, NE, SE et SW ci-dessous qui forment un carré (un sf::Quads) :
Il est possible avec SFML de dessiner un ensemble de sf::Quads en leur associant une texture. Voici comment cela peut se faire en utilisant la texture liée au cellules sanguines par exemple.
sf::RenderStates rs; auto textures = getAppConfig().simulation_organ["textures"]; rs.texture = &getAppTexture(textures["blood cell"].toString()); // ici pour la texture liée une cellule sanguine // nous dessinons sur l'image associée à Organ // et non pas directement dans la fenêtre: organTexture_.draw(vertexes.data(), vertexes.size(), sf::Quads, rs);où vertexes est l'ensemble des sf::Vertex constituant les sf::Quads que l'on veut dessiner.
Nous verrons un peu plus loin, où ces instructions doivent être placées.
L'ensemble vertexes contiendrait {NW0,NE0,SE0,SW0, ... NWi,NEi,SEi,SWi ... } où NWi,NEi,SEi,SWi sont les sommets (sf::Vertex) Nord-Ouest, Nord-Est, Sud-Est et Sud-Ouest (dans cet ordre) du ième carré texturé.
Une idée naturelle est donc de stocker dans 2 nouveaux attributs de type vector <sf::Vertex> l'ensemble des sommets représentant des cellules sanguines (nous l'appellerons bloodVertexes_ dans ce qui suit) et l'ensemble de ceux représentant des cellules de l'organe(organVertexes_) et de les afficher par ce biais.
Le rôle de la méthode initOrganTexture est précisément d'initialiser les ensembles bloodVertexes_ et organVertexes_ à partir de la structure de la grille correspondant à cellsLayers_ (son nombre de cellules et la taille graphique des cellules);
Cette méthode se contente pour le moment de donner une taille à l'image organTexture_. Elle doit en plus créer les éléments à dessiner sur l'image et doit donc pour cela initialiser les attributs bloodVertexes_ et organVertexes_ à partir de la structure de la grille correspondant à l'ensemble cellsLayers_.
Pour le moment cellsLayers_ est initialisé pour ne contenir que des cellules de type «ECM», mais cela est amené à changer lors des étapes suivantes où il s'agira de coder les méthodes createOrgan et createBloodSystem.
Il nous faut donc anticiper le fait que l'ensemble cellsLayers_ peut évoluer au cours du temps (un CellsLayer qui ne contenait qu'une cellule «ECM» peut, par la suite, contenir en plus une cellule de l'organe et/ou une cellule sanguine). De ce fait, les ensembles bloodVertexes_e et organVertexes_ peuvent en principe évoluer aussi.
Pour éviter de devoir remettre à jour ces ensembles à chaque modification de la nature d'une cellule (au gré de l'évolution de l'organe), nous allons avoir recours à une petite astuce.
Il est en effet possible d'afficher un sf:Quads de façon transparente (invisible). Il devient du coup possible de stocker dans bloodVertexes_ et organVertexes_ tous les sommets de la grille, puis de considérer les sf::Quads correspondant comme transparents ou non selon les besoins.
Concrètement, supposons qu'une entrée de cellsLayers_ ait pour sommets graphiques NWi,NEi,SEi,SWi. Ces quatre sommets seront stockés dans tous les ensembles bloodVertexes_ et organVertexes_. Si maintenant cette entrée contient une cellule sanguine, alors le sf:Quads correspondant à NWi,NEi,SEi,SWi devra s'afficher de façon transparente dans organVertexes_ et de façon visible dans bloodVertexes_ (on procédera bien sûr de façon analogue dans les autres cas).
Ce qu'il faut retenir de cela est que dans la méthode initOrganTexture il suffira d'initialiser bloodVertexes_ et organVertexes_ avec le même ensemble de sommets, celui correspondant aux sommets de toutes les cases de la grille.
Pour le moment updateRepresentation réinitialise le contenu de l'image en lui donnant un fond rose puis l'affiche. Entre ces deux instructions elle doit désormais dessiner les carrés texturés (sf::Quads) de bloodVertexes_ et organVertexes_ (dont certains seront tantôt transparents, tantôt visibles selon leur nature).
Le dessin d'un ensemble se fait comme évoqué plus haut (par exemple ici pour les cellules de bloodVertexes_ ) :
sf::RenderStates rs; auto textures = getAppConfig().simulation_organ["textures"]; rs.texture = &getAppTexture(textures["blood cell"].toString()); // ici pour la texture liée une cellule sanguine organTexture_.draw(bloodVertexes_.data(), bloodVertexes_.size(), sf::Quads, rs);Le dessin est analogue pour organVertexes_ ("organ cell" remplacera "blood cell" pour l'accès à la texture).
Commencez par invoquer le dessin des deux ensembles à l'endroit suggéré.
Comme ces deux ensembles contiennent les mêmes sommets, il devient nécessaire de spécifier quels sf::Quads doivent s'afficher de façon transparente selon l'ensemble auquel ils appartiennent.
Avant les traitements qu'elle exécute pour effectuer le dessin (c'est à dire toutes les instructions qui y sont présentes pour l'heure), la méthode updateRepresentation doit donc procéder à la mise à jour de la représentation à proprement parlé:
Comme l'organe évolue, il est utile de se munir d'outils permettant d'en modifier le contenu.
A cet effet, définissez dans Organ.hpp, un type énuméré public, Kind, permettant de modéliser les «types» possibles d'une cellule:
enum class Kind : short { ECM, Organ, Artery, Capillary };Pour être conforme aux tests fournis, vous pouvez placer cette déclaration à l'intérieur du prototype de la classe Organ (en publique). Cela permet d'avoir un type énuméré qui n'est défini que pour l'organe. L'accès aux valeurs se fera alors selon la syntaxe Organ::Kind::ECM par exemple.
Dotez la classe Organ d'une méthode protégée virtuelle void updateCellsLayer(const CellCoord& pos, Kind kind) dotant le CellsLayer à la position pos d'une cellule du type approprié selon la valeur de kind (souvenez vous de vos setters).
[Question Q4.10] Quelle(s) retouche(s) devez vous apporter à à setBlood et setOrganCell pour que le changement de contenu d'un CellsLayer se répercute correctement sur sa représentation graphique ? Pourquoi setECM n'a t-il a priori pas besoin de retouches de son côté ? Répondez à cette question dans votre fichier REPONSES en justifiant vos choix et apportez les modifications nécessaires à votre code.
Pour tester le travail fait jusqu'ici, vous utiliserez le test fourni dans Tests/GraphicalTests/DrawOrganTest.cpp avec le fichier de configuration appOrgan.json (saisissez appOrgan.json en argument de ligne de commande dans QTCreator).
Pour pouvoir utiliser ce test, vous introduirez un «setter» transplant(Organ*) dans la classe Animal. Ce «setter» devra être en accès protégé et prendre les mesures nécessaires pour éviter les fuites de mémoire.
La touche 'B' permet de créer une cellule sanguine à l'endroit du curseur, la touche 'J' un cellule de l'organe.
Vous devriez pouvoir afficher à la demande des cellules sanguines et des cellules de l'organes au fur et à mesure que vous appuyez sur ces touches.
[Video : Dessins des niveaux de cellules] |
Tout ça pour ça, vous direz vous? Haut les coeurs ... même si cela n'est pas encore spectaculaire, vous avez mis en place l'essentiel de l'outillage nécessaire au rendu graphique d'un organe (ce qui n'est pas une mince affaire !)
Dans le module suivant il s'agit de créer le réseau sanguin (donc en clair remplir Organ::createBloodSystem).