Projet : étape 4.1
Basculement

Buts: Permettre à la simulation de basculer vers une vue interne correspondant à un fragment d'organe d'un hamster sélectionné.

Le but est maintenant de doter nos animaux d'organes dont il sera possible de visualiser un fragment en basculant sur une vue interne de la simulation. Nous nous contenterons d'un organe simulé générique (supposé être le même pour tous les animaux) et allons commencer par mettre en place le mécanisme qui permettra de sélectionner l'individu observé.

Préambule

Le noyau de simulation (classe Application) vous est fourni dans cette étape dans une nouvelle version qui permet le basculement de la simulation sur différentes «vues». Il y est notamment fait usage du type énuméré View défini dans Types.hpp. La valeur LAB de ce type désigne la vue externe (laboratoire avec hamsters), la valeur ECM désigne la vue interne et la valeur CONCENTRATION désigne la vue interne en mode observation de la diffusion des substances. Jetez un oeil à la nouvelle version de la classe Application, notamment aux contrôles qu'elle offre pour mieux en comprendre l'utilisation par la suite.

Focus sur un animal

Il vous est demandé dans un premier temps de coder la méthode void Lab::trackAnimal(const Vec2d&) permettant d'indiquer que l'on se focalise sur un animal particulier dans le laboratoire (cette méthode est utilisée pour la gestion de la touche 'T' dans FinalApplication).

L'animal «traqué» (celui sur lequel la simulation se focalise) devra être utilisé par plusieurs méthodes de la classe Lab. Il est donc judicieux de le mémoriser dans un attribut de type Animal* dans cette classe. Pensez à adapter vos constructeurs à chaque fois qu'un attribut est ajouté.

L'appel à la méthode void Lab::trackAnimal(const Vec2d&) devra permettre de répertorier l'animal à la position donnée comme «traqué». Pour qu'un animal soit considéré comme étant à la position donnée p, il faudra que la distance entre sa position et p soit inférieure à son rayon. Un animal «traqué» sera dessiné assorti d'une icône particulière (une texture est prévue via getAppConfig().entity_texture_tracked) :

[Image: hamster avec cible] Le hamster traqué est dessinée avec la petite icône «cible»
Dotez aussi Lab des membres suivants :

[Question Q4.1] Cela ne fait pas sens de traquer des tas de granulés. Pour éviter d'avoir à tester le type des entités pour mettre en oeuvre la méthode trackAnimal(const Vec2d&), il est possible par exemple de :1) introduire une collection d'animaux en parallèle de la collection d'entités ou 2) d'introduire une méthode polymorphique indiquant si une entité peut être traquée ou pas. Quelle méthode vous semble préférable et pourquoi :?

Répondez à cette question dans votre fichier REPONSES et codez les modifications appropriées.

Le code est compilable à ce stade, vous pouvez décommenter la cible application. Il devrait être possible de marquer un hamster comme traqué avec la touche T (la petite icône rose s'affiche alors dessus) et cesser de traquer tout hamster avec la touche Z. Veillez à décommenter au préalable la gestion des touches T et Z dans Tests/GraphicalTests/FinalApplication.cpp.

Ebauche d'organe

La classe Organ que nous vous proposons d'introduire maintenant va représenter un fragment d'organe reposant sur une matrice extra-cellulaire («ECM») et irriguées par des cellules sanguines:

[Image: Organe discrétisé]

Le modèle que nous utiliserons consiste à quadriller le fragment au moyen d'une grille dont chaque case représentera un empilement de cellules. Dans l'image ci-dessus, délibérément donnée avec une taille assez grossière des cases, chaque carré rouge représente une cellule du réseau sanguin reposant sur une cellule de l'organe qui elle même repose sur une cellule de l'ECM. Chaque carré rose foncé représente une cellule de l'organe reposant sur une cellule de l'ECM. Le fond rose clair désigne les cases occupées uniquement par une cellule de l'ECM.

Pour modéliser un Organ comme une grille, commencez par introduire des attributs représentant le nombre de cases par ligne (un int) ainsi que la taille graphique de chaque cellule (un float). Nous désignerons ces attributs sous les noms de nbCells_ et cellSize_ dans ce qui suit mais libre à vous de nommer ces attributs comme vous le souhaitez.

Un Organ est un élément de la simulation dont nous allons raisonnablement pouvoir supposer:

La simulation du niveau interne se fait à une autre échelle de temps que celle au niveau externe. Il est en effet pertinent à l'échelle du laboratoire de simuler avec un pas de temps très faible et «en temps réél» (fluidité du déplacement). Cette échelle de temps n'est pas pertinente pour la simulation d'un organe qui doit se faire sur le plus long terme. Pour simplifier l'observation de l'évolution d'un organe il est aussi préférable que les cycles de simulation se fassent sur des intervalles de temps constants et identiques. Pour cette raison, la méthode void update() (que l'on aurait pû appeler void fixedTimeUpdate() mais c'est un peu long) est suggérée sans argument pour Organ. Elle utilisera, lorsque nécessaire, un pas de temps fixe paramétrable depuis les fichiers de configuration. Nous reviendrons à cela lors de l'étape suivante du projet.

Pour le moment, les méthodes Organ::update et Organ::drawOn ont un corps vide et le but des étapes à venir du projet et de les remplir. Il s'agira de simuler un organe dont la représentation au départ ressemblera à l'image ci-dessus.

Dessin d'un Organ

La méthode drawOn aura donc pour vocation de dessiner des carrés texturés, en fonction de la nature des cellules empilées au niveau de chaque case.

Pour dessiner de nombreux carrés texturés, il est plus efficace avec SFML de faire le rendu dans une texture (d'un type nommé sf::RenderTexture ) et d'afficher cette texture en une seule fois dans Organ::drawOn (plutôt que d'afficher directement chaque carré directement dans la fenêtre graphique).

Déclarez donc dans la classe Organ un attribut, appelons-le organTexture_, de type sf::RenderTexture représentant l'image associée à l'organe pour son dessin. La méthode de dessin d'un organe aura ainsi un corps tout simple ressemblant à ceci:

sf::Sprite image(organTexture_.getTexture()); // transforme l'image en texture
target.draw(image); // affiche la texture

Les modalités d'initialisation et de mise à jour de l'attribut organTexture_ sont ébauchées dans ce qui suit.

Génération d'un Organ

La génération du contenu d'un organe (qui déterminera quels types de cellules s'empilent au niveau de chaque case) est une procédure relativement complexe et il fait sens d'en faire une méthode à part. Introduisez à cet effet une méthode Organ::generate() en accès protégé.

Nous vous suggérons l'algorithme en cinq étapes ci-dessous pour sa mise ne oeuvre. Pour une meilleure modularisation, chaque étape fera l'objet d'une méthode spécifique. L'ensemble des méthodes peuvent être aussi en accès protégé, hormis updateRepresentation qui pourra être publiquement sollicitée. Vous conserverez les noms suggérés si vous voulez utiliser les tests fournis sans retouches.

Algorithme de génération d'un organe

  1. Initialiser les éléments paramétrables depuis un fichier de configuration : ce sera typiquement le cas pour le nombre de cases par ligne et colonne dans la grille discrétisant l'organe ou encore la taille graphique d'une case: méthode reloadConfig.
  2. Initialiser l'attribut organTexture_ en tenant compte des caractéristiques de la grille (typiquement ses dimensions) : méthode initOrganTexture.
  3. Créer le fragment d'organe : méthode createOrgan.
  4. Créer le réseau sanguin : méthode createBloodSystem.
  5. Mettre à jour la représentation graphique , c'est à dire l'attribut organTexture_, suite aux deux créations précédentes : méthode updateRepresentation.

Vous pouvez donc anticiper la présence de chacune de ces méthodes. createOrgan et createBloodSystem auront un corps vide pour le moment (ce sera l'objet de modules à venir). Nous ébauchons les autres méthodes dans ce qui suit.

Prenez soin de commenter le rôle de chacune de ces méthodes pour retrouver rapidement plus tard en quoi elle sont utiles.

[Question Q4.2] Nous vous suggérons de coder la méthode generate comme virtuelle. En quoi cela peut-être utile selon vous ? Répondez à cette question dans votre fichier REPONSES.

Méthode reloadConfig

Cette méthode doit :

  1. initialiser le nombre de cellules par ligne au moyen de la valeur paramétrable getAppConfig().simulation_organ_nbCells;
  2. initialiser la taille graphique de chaque cellule comme étant la largeur graphique associée à la vue interne (méthode getWidth suggérée ci-dessous) divisée par le nombre de cellules par ligne.

Pour être compatible avec les tests fournis, la largeur et la hauteur graphiques associées à la vue interne seront respectivement retournées par des méthodes publiques Organ::getWidth et Organ::getHeight. Ces deux «getters» retourneront tous les deux la valeur paramétrable getAppConfig().simulation_organ_size.

Méthode initOrganTexture

Cette méthode est charge d'initialiser l'attribut organTexture_ décrit plus haut. Pour le moment nous nous contenterons d'initialiser la taille de l'image que représente organTexture_. Ceci se fera au moyen d'une tournure de cette nature :

organTexture_.create(horizontalSize, verticalSize);

Dans notre cas, horizontalSize et verticalSize auront la même valeur (nombre de cellules par ligne multiplié par la taille graphique d'une cellule).

Méthode updateRepresentation

A ce stade, cette méthode efface simplement l'image organTexture_ en lui donnant un fond tout rose, ce qui peut se faire au moyen du code suivant :

organTexture_.clear(sf::Color(223,196,176));
organTexture_.display();
La mise à jour de la représentation (image) associée à l'organe doit en principe se faire à chaque cycle de simulation. La méthode Organ::update devrait donc aussi appeler méthode updateRepresentation().

Sur ce fond rose, seront dessinés par la suite carrés de couleurs/textures différentes.

Lien entre Animal et Organ

Faites maintenant en sorte qu'un Animal soit doté d'un Organ* (nous utilisons les pointeurs car il n'est pas exclu que nous ayons recours à de la «transplantation» d'organes, comme vous pourrez le voir au niveau ce certains programmes de test ;-)).

Vous doterez Organ d'un constructeur prenant en paramètre un booléen generation prenant true comme valeur par défaut. Le constructeur fera appel à generate si generation vaut true et ne fera rien sinon. Ceci permettra de générer, à la demande, des organes vides de contenu ou doté d'un contenu. Faites ensuite en sorte qu'un animal soit doté d'un organe dynamiquement alloué (avec un contenu) à sa construction.

Complétez enfin dans votre classe Lab:

  • la méthode drawCurrentOrgan((sf::RenderTarget& target) de sorte à ce qu'elle dessine l'organe de l'animal observé, s'il existe au moyen de sa méthode de dessin;
  • ainsi que la méthode updateTrackedAnimal() pour qu'elle fasse évoluer l'organe de l'animal observé, s'il existe (par le biais de la méthode Organ::update()).
    Comme évoqué plus haut, la classe Application utilise ces méthodes pour faire la mise à jour et le rendu de l'organe observé (celui de l'animal ciblé).

    [Question Q4.3] Quelles autres modifications devez vous apporter à votre programme pour coder ces méthodes? En particulier, comment proposez vous de coder updateTrackedAnimal() et drawCurrentOrgan sans coder de «getter» sur l'organe de l'animal (trop intrusif)  ? Répondez à ces questions dans votre fichier REPONSES et apportez les modifications suggérées.

    Nous avons maintenant mis en place les briques de base permettant le dessin et la génération d'un organe. Il est temps de tester le basculement vers la vue interne.

    Test 20 : Focus et basculement

    Lancez le programme au moyen de la cible application que vous prendrez soin de décommenter au préalable dans le fichier CMakeLists.txts. Posez le curseur sur un hamster à traquer et appuyez sur la touche 'T' vous devriez le voir se dessiner avec la petit icône. Appuyez ensuite sur la touche 'O' (qui permet de basculer vers la vue «ECM», et vous devriez voir s'afficher une vue organe (très épurée puisqu'il ne s'agit que d'un fond rose)). La touche 'L' héritée de Application doit permettre de retourner sur la vue «LAB» :

    [Video : focus sur un animal et basculement vers la vue organe]

    Dans la vidéo ci-dessus, la touche 'O' est utilisée pour basculer sur la vue interne, la touche 'L' pour revenir à la vue «laboratoire». Vérifiez que  :

    Une fois cette partie fonctionnelle, prenez le temps de re-examiner/commenter le contenu produit, voire faire un petit schéma de synthèse sur «papier», pour avoir une vue d'ensemble et bien comprendre le rôle de chacun des éléments introduits et les liens des différentes classes entre elles.

    Aller au module suivant (partie 4.2)
    Retour à l'énoncé du projet (partie 4)