Projet : étape 3.4
Les bactéries à flagelle unique
(déplacement)

Buts : Simuler le déplacement spécifique d'un premier type concret de bactéries.

Notre première colonie concrète de bactéries sera constituée de bactéries dites à flagelle unique.

Ces dernières vont reprendre en grande partie les caractéristiques mises en place dans la classe Bacterium mais vont se distinguer par un mode de déplacement (méthode move) spécifique.

Reprenez la classe MonotrichousBacterium qu'il vous a été suggéré d'ébaucher précédemment et commencez par la doter d'un constructeur prenant en paramètre uniquement une valeur pour sa position (un Vec2d). Les autres attributs seront initialisés comme suit :

Pour le moment, vous ne vous préoccuperez pas des caractéristiques mutables de la bactérie à flagelle unique.

La cible permettant de compiler et lancer cette partie est monotrichousTest. Il faut la décommenter le moment venu dans le CmakeLists.txt. Notez que Ctrl-F permet de faire une recherche dans l'éditeur de QtCreator (pour chercher où se trouve monotrichousTest par exemple).

Déplacement spécifique

Les bactéries à flagelle unique se déplacent au départ selon une direction initiale aléatoire et à vitesse constante.

Il vous est demandé de coder la méthode de déplacement (move) d'une bactérie à flagelle unique selon l'algorithme suivant:

  1. calcul de la nouvelle position et de la nouvelle vitesse (après écoulement d'un seul pas de temps) : voir indication ci-dessous
  2. baisse du niveau d'énergie liée au déplacement : énergie -= longueur du déplacement * facteur de consommation d'énergie (rappelez-vous de la méthode consumeEnergy)
    • la longueur du déplacement est la norme (méthode length) de la différence entre la nouvelle position et l'ancienne position de la bactérie (des Vec2d). Notez que l'opérateur - est défini pour les Vec2d;
    • le facteur de consommation d'énergie est le paramètre associé à ["energy"]["consumption factor"] (revoyez vos getters).
Comment calculer les nouvelles positions et vitesses ?

Comme indiqué dans le descriptif du projet, nous considérons que l'évolution de la position et de la vitesse d'une bactérie est régie par une équation de type:

item
que nous allons résoudre par des méthodes d'intégration numérique (si vous voulez avoir plus de détails, voir les compléments mathématiques, section 3).

Ces méthodes ont besoin d'évaluer une force f pour une position, une vitesse et un temps donnés.

Par exemple le schéma d'intégration d'Euler-Cromer (voir compléments, section 3.1) permet de calculer :

nouvelle_vitesse = vitesse_courante  +  dt * f(position_courante, vitesse_courante, t)
nouvelle_position = position_courante + dt * nouvelle_vitesse

Il existe d'autres schémas d'intégration plus précis mais fonctionnant selon le même principe (notamment l'algorithme de Runge-Kutta explicite).

Les méthodes d'Euler-Cromer et de Runge-Kutta sont mises à votre disposition dans Utility/DiffEqSolver.[hpp][cpp] : appeler la fonction stepDiffEq permet de calculer la nouvelle position et la nouvelle vitesse d'un objet soumis à une force (appelée eq dans les paramètres) en partant de sa position et de sa vitesse courantes, au bout de l'écoulement du temps dt. Le dernier paramètre de la fonction stepDiffEq permet de choisir la méthode d'intégration utilisée (EC ou RG4 : pour Euler-Cromer ou Runge-Kutta d'ordre 4). Ces méthodes ont toutes les deux besoin d'évaluer la force (eq) pour une position et une vitesse données.

[Question Q3.18] La classe DiffEqFunction (aussi contenue dans le fichier Utility/DiffEqSolver.hpp) permet justement de modéliser une force f à évaluer en fonction d'une position et d'une vitesse. Expliquez dans votre fichier REPONSES comment vous proposez d'utiliser cette classe pour doter une bactérie à flagelle unique d'une méthode qui calcule la force f régissant son déplacement ? (indication : utilise-t-on la composition ou l'héritage ?)

Vous préciserez quels éventuels attributs/méthodes ou quel lien d'héritage vous avez dû ajouter à la classe MonotrichousBacterium pour réaliser les calculs requis par la mise en oeuvre du déplacement, en justifiant vos choix d'implémentation.

Programmez la méthode de calcul de la force pour une bactérie à flagelle unique, sachant que dans ce cas ... la force est simplement nulle (vecteur bidimensionnel nul, indépendamment de la vitesse et la position). Ceci permet d'assurer à la bactérie un mouvement rectiligne uniforme.

Le calcul de la force est trivial et peut sembler inutile dans ce cas. Le modèle adopté (le fait d'associer une force à la bactérie) permettra cependant de gérer des modes de déplacement plus complexes, comme les mouvements de groupes de la partie suivante du projet.

Pour le calcul de la vitesse courante, codez une méthode getSpeedVector calculant la vitesse de la bactérie comme étant sa direction (vecteur unitaire) multipliée par une certaine valeur (prenez la valeur 5, nous rendrons cela un peu plus général plus loin).

Vous disposez maintenant de toutes les informations nécessaires au codage de l'algorithme de déplacement décrit plus haut pour les bactéries à flagelle unique. Intégrez-le à votre programme.

Pour éviter les «sorties de route»

Avec un pas de simulation accéléré (par exemple un ["simulation"]["time"]["factor"] valant 5), il peut se produire que la bactérie "sorte" de la boite avant même qu'il y ait eu le temps de faire le test de collision! Elle serait alors perdue pour la simulation. Pour éviter ce problème, vous ferez en sorte que si la nouvelle position calculée par la méthode move place la bactérie en dehors de la boite, elle rebondisse aussi dans la direction opposée.

Test 9 : Déplacement rectiligne uniforme

Pour pouvoir tester votre classe MonotrichousBacterium telle que codée jusqu'ici, il va être nécessaire de la rendre instantiable.

Lancez le test au moyen de la cible monotrichousTest. Vous aurez pris soin de la décommenter dans le fichier CMakelists.txt et d'avoir intégré ce nouveau matériel au moyen de Build > Run CMake.

Laissez quelques sources de nutriments se générer puis créez une bactérie à flagelle unique (ou plusieurs) au moyen de la touche 'M'.

Elle doit se déplacer toujours droit devant elle, consommer des nutriments qui se trouvent sur son passage et rebondir sur les bords de la boite de culture comme dans la vidéo ci-dessous.

Amélioration du graphisme

Pour améliorer le rendu visuel du programme, dotons maintenant l'arrière de la bactérie à flagelle unique d'un flagelle à mouvement sinusoïdal.

Le cercle coloré qui lui sert de représentation graphique va donc être complété par le dessin et l'animation de ce flagelle.

Deux aspects sont à gérer pour réaliser cet ajout:

  1. dessiner le flagelle animé;
  2. tenir compte du changement de direction de la bactérie lors du déplacement, pour garantir que le flagelle soit toujours dessiné à l'arrière.

Dessin du flagelle sinusoïdale

La librairie SFML permet de dessiner un ensemble de points sur une fenêtre graphique target en utilisant la tournure suivante :

 auto set_of_points = sf::VertexArray(<type>); // nous parlerons de <type> un peu plus bas
  // ajout de points à l'ensemble:
  set_of_points.append(<point1>)
  ...
  set_of_points.append(<pointN>)
  target.draw(set_of_points);
pointX peut être donné sous la forme {{x, y}, couleur}x et y sont les coordonnées du points (de type float) et couleur est la couleur SFML utilisée pour le colorer le point.

Pour dessiner un ensemble de points correspondant à un flagelle sinusoïdale, il s'agit donc de remplir l'ensemble des points de sorte qu'ils forment une sinusoïde.

Le paramètre <type> sera choisi comme étant le type sf::PrimitiveType::TriangleStrip, ce qui permettra de relier les points de l'ensemble deux à deux lors du dessin.

On remplira l'ensemble en coordonnées absolues (en centrant à l'origine du repère utilisé par la SFML). Dans le paragraphe suivant, on fera subir une transformation à cet ensemble pour le placer au bon endroit.

Voici quelques indications pour remplir l'ensemble des points de sorte à ce qu'ils correspondent à une sinusoïde d'amplitude variant avec le temps :

  1. ajouter {0,0} comme premier point à l'ensemble;
  2. Pour i de 1 à N (30 par exemple) : calculer les coordonnées des points à ajouter à l'ensemble comme étant :
    x = -i * rayon / 10.f
    y = rayon * sin(t) * sin(2 * i / 10.0)
    
    t est un compteur de type double permettant de faire varier l'amplitude de la sinusoïde avec le temps. rayon est le rayon de la bactérie (l'amplitude de la sinusoïde doit en effet être proportionnelle à la taille graphique de la bactérie).

[Question Q3.19] Où déclarer et initialiser le temps t ? Répondez à cette question dans votre fichier REPONSES et codez les modifications suggérées.

Nous savons donc à ce stade remplir l'ensemble des points de la sinusoïde en coordonnées absolues. Avant de lancer le dessin, il faut placer cet ensemble au bon endroit, c'est-à dire à l'arrière de la bactérie. Des indications sont données dans ce qui suit.

Incorporation des changements de direction pour le dessin du flagelle

Le repère cartésien dans lequel se font les rendus graphiques SFML est un repère qui prend son origine en haut à gauche de la fenêtre graphique.

La direction de déplacement de la bactérie est un vecteur l'orientant droit devant elle (à l'image de la ligne bleue orientant l'insecte ci-dessous devant lui). Nous appellerons angle de direction l'angle que fait le vecteur direction de la bactérie par rapport à l'axe des x dans ce repère (ou dans un repère orthogonal centré sur la bactérie, orienté comme l'est celui de la SFML).

coordonnees
La SFML travaille en degrés alors que les fonctions trigonométriques de C++ utilisent le radians. Il faudra y être attentif.

Pour placer l'ensemble de points au bon endroit dans le dessin, il est possible de procéder comme ceci:

 auto transform = sf::Transform(); // déclare une matrice de transformation
 // ici ensemble d'opérations comme des translations ou rotations faites  sur transform:
 transform.translate(position);
 rotate_and_translate(transform,
                      angle_direction / DEG_TO_RAD, // rotation
                      static_cast(-rayon + 2), // translation en x
                      0); // translation en y
 // puis:
 target.draw(ensemble_de_points, transform); // dessin de l'ensemble des points
                                             // fait après leur transformation
                                             //selon la matrice transform
position est la position de la bactérie, rayon, son rayon en tant que contour circulaire, et angle_direction est l'angle de direction (en radians) présenté ci-dessus . On tient compte par ce biais de l'angle de direction pour bien placer le flagelle à l'arrière de la bactérie.

Notez que dans la méthode de déplacement, après un changement de direction, l'angle de direction, exprimé en radians, peut être mis à jour comme suit :

  auto const angleDiff = angleDelta(direction.angle(), rotation); // calcule la différence entre le nouvel
                                                                  // angle de direction et l'ancien
 auto dalpha = PI * dt.asSeconds();    // calcule dα
 dalpha = std::min(dalpha, std::abs(angleDiff)); // on ne peut tourner plus que de angleDiff
   
 dalpha = std::copysign(dalpha, angleDiff); // on tourne dans la direction indiquée par angleDiff
 rotation += dalpha; // angle de rotation mis à jour 

Les fonctions/méthodes angleDelta, angle et rotate_and_translate sont fournies dans Utility.[hpp][cpp] et Vec2d.[hpp][cpp].

Le fichier Utility/Constants.hpp fournit la constante DEG_TO_RAD permettant la conversion de degrés en radians (ou vice-versa).

Vous noterez que si dir est la direction de la bactérie, dir.angle() retourne l'angle de rotation en radians associé à dir.

[Question Q3.20] Comment mémoriser l'angle de direction et à quel endroit du code l'initialiser et le mettre à jour si l'on considère que toutes les bactéries ont une direction et un angle de direction ? Répondez à cette question dans votre fichier REPONSES et apportez à votre code les ajouts suggérés.

Test 10 : Déplacement rectiligne uniforme avec dessin du flagelle

Relancez le test comme précédemment, vous devriez voir vos bactéries dotées d'un splendide flagelle animé ... et qui reste à l'arrière même en cas de rebonds:


Retour à la description du projet (Partie 3)