Projet : étape 4.3
Dessin de strates

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.

Dessin d'un Organ : principe général

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.

Ainsi, si une CellsLayer contient une cellule sanguine il s'affichera comme une cellule sanguine. Sinon, s'il contient une cellule de l'organe, il s'affichera comme telle. Sinon, il s'affichera comme une cellule de l'ECM.

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) :

water

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); 
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 ... }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é.

Pour obtenir ces sommets, il faut bien sûr traduire chaque case de cellsLayers_ en un ensemble de sf:Vertex, dans le bon ordre, pour obtenir l'ensemble vertexes (c'est le rôle de la méthode initOrganTexture que nous avons introduite dans le premier module).

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);

Retouche à la méthode initOrganTexture

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).

Il vous sera expliqué un peu plus bas comment rendre transparents (invisibles) certains sf::Quads lors de l'affichage.

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.

La méthode generateVertexes fournie dans Vertex.[hpp][cpp] vous permet de générer ces sommets. Elle se charge de convertir les sommets des cellules de la grille en coordonnées graphiques. Vous devrez fournir en paramètre à cette méthode :
  1. l'ensemble des noms des fichiers de textures impliquées (cet ensemble est retourné par getAppConfig().simulation_organ["textures"];
  2. le nombre de cellule par ligne de la grille;
  3. la taille graphique de chaque cellule.
La méthode initOrganTexture ne devrait donc, à ce stade, pas faire plus que quatre lignes ;-)

Retouche à la méthode updateRepresentation

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é: