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.
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:
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:
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.
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.
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.
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.
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:
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} où 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 :
x = -i * rayon / 10.f y = rayon * sin(t) * sin(2 * i / 10.0)où 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.
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).
|
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
où 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].
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.
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: