Projet : étape 3.2
Déambulations

Buts: Faire en sorte que les hamsters puissent se déplacer dans les limites de leurs boîtes.

Pour l'heure, le seul élément évoluant en cours de simulation est l'âge des entités simulées. Le rôle de leur méthode update est donc mineur. Nous allons y remédier pour permettre aux animaux d'avoir des comportements spécifiques plus visibles, comme la capacité à se déplacer.

Il est en effet raisonnable de modéliser le fait que le comportement d'un animal est plus spécifique que celui d'une entité simulée quelconque et qu'il soit dépendant d'un état (typiquement, un hamster affamé aura un autre comportement qu'un hamster rassasié).

Le comportement des animaux dépend de leur état

Commencez par modéliser le fait qu'un Animal A-UN état.

Un type énuméré avec comme valeurs par exemple :
	 {
	 TARGETING_FOOD, // se dirige vers la nourriture  
	 FEEDING,       // en train de manger (là en principe il arrête de se déplacer)
	 WANDERING,     // déambule
	 IDLE,          // au repos
    }
	 
semble tout indiqué pour le type associé à l'état de l'animal

L'idée est maintenant de formuler plus spécifiquement la méthode update des animaux (classe Animal). Cette dernière mettra en oeuvre le fait que les animaux ont un comportement dépendant des états décrits dans le type énuméré ci-dessus. Elle devrait pour cela prendre l'allure suivante :

  1. Mise à jour de l'état : l'état de l'animal pourra en effet être conditionné par des changements de l'environnement. Par exemple, si un animal est affamé et qu'une source de nourriture est disponible, il se mettra dans l'état où il doit «cibler la nourriture» (TARGETING_FOOD).
  2. Si l'état vaut WANDERING alors l'animal se déplacera au hasard à vitesse constante;
  3. si autre état: rien pour le moment ... mais nous sommes animés de sérieuses intentions pour que ça change bientôt;
L'instruction switch devrait se rappeler à votre bon souvenir ici !

Il vous est demandé maintenant d'ébaucher la méthode update de l'animal tel que nous venons de la décrire. Le déplacement à vitesse constante fera l'objet d'une méthode move(sf::Time) qui nous occupera un peu plus bas (le type de retour de cette méthode est laissé à votre libre choix).

Il est judicieux d'adopter une approche modulaire pour le codage de Animal::update. Typiquement, la mise à jour de l'état de l'animal après l'écoulement du pas de temps dt (pas 1 de l'algorithme) est un traitement à part entière et une méthode updateState(sf::Time dt) qui s'en chargerait est certainement une bonne idée. Pour l'instant, cette méthode se contentera d'affecter inconditionnellement l'état WANDERING à notre animal, car nous ne nous préoccupons pour l'heure que de gérer la façon dont il déambule au hasard. C'est à cette facette de son comportement que nous allons maintenant nous attaquer.

Déplacement rectiligne uniforme

Nous partirons d'un modèle simple où le déplacement est régi par une équation différentielle de type :
x⃗(t) est la position de l'animal, et v⃗(t)sa vitesse, dont la norme dans notre cas, sera constante.

Une méthode numérique de résolution d'un tel système (appelée «méthode d'Euler-Cromer») consiste à calculer la nouvelle vitesse et nouvelle position comme suit :

Pour implémenter le calcul du déplacement selon cette méthode numérique, il sera utile de doter l'animal:

Vous doterez aussi l'animal d'une méthode getMaxSpeed donnant la vélocité maximale d'un animal. Vous considérerez que cette vitesse ne peut être définie pour un animal quelconque, mais qu'elle aura une valeur spécifique (et paramétrable) pour chaque type concret d'animal. Pour un hamster ce sera getAppConfig().hamster_max_speed.

[Question Q3.10] : Comment proposez vous de procéder pour, que lors de son passage à l'état WANDERING, un animal adopte une vélocité constante de getMaxSpeed?

[Question Q3.11] Comment proposez vous pour faire en sorte qu'un animal continue de vieillir sans dupliquer le code qui le fait vieillir ?

Répondez aux questions ci-dessus dans votre fichiers REPONSES et apportez les changements qu'elles suggèrent à votre code.

Dotez enfin votre Animal d'une méthode move(sf::Time dt) mettant à jour la position et la vitesse de l'animal au bout de l'écoulement du pas de temps dt selon la méthode numérique décrite ci-dessus et en utilisant les méthodes utilitaires suggérées. Assurez-vous qu'elle soit invoquée au bon endroit.

Il est possible d'appliquer la méthode asSeconds() à un sf::Time pour le convertir en un float utilisable dans le calcul.

Test de collision

Avant de mettre en mouvement nos animaux, nous allons prendre quelques précautions pour éviter qu'ils ne s'échappent du laboratoire.

Soit p la position de l'animal et new_p, une nouvelle position potentielle (calculée par move). Si L'animal a pour rayon r , alors new_p n'est pas une position possible (et l'animal doit se retourner) si:

Pour faire en sorte que l'animal se retourne il suffit d'inverser sa direction. Cela peut se faire en affectant (-getHeading()).angle() à l'angle de direction de l'animal (voir Vec2d.hpp pour la méthode angle()).

[Question Q3.12] Quelles méthodes proposez-vous d'ajouter et à quelle(s) classe(s) pour garantir que l'animal se retourne dans la direction opposée à son déplacement si la nouvelle position calculée par la méthode move induit une collision avec l'un des murs de l'une des boites du laboratoire?

Répondez à cette question dans votre fichier REPONSES et apportez les changements qu'elle suggère à votre code

Test 13  : Déplacement rectiligne uniforme

Pour tester vos derniers développements, vous pouvez utiliser la cible labTest. Vous devriez pouvoir observer un hamster se déplaçant un peu comme une balle de ping pong et faire d'incessants va-et-vient d'un point à l'autre sans jamais changer de direction.

Notez que l'on peut changer le contenu du fichier de configuration, celui avec lequel vous avez lancé le programme (ici, app.json par défaut), sans arrêter la simulation puis le recharger en utilisant la touche 'C'. Essayez cela en changeant la vitesse de simulation: lancez le programme, ouvrez le fichier app.json et modifiez-y la vitesse de simulation (sous l'étiquette ["simulation"]["time"]["factor"]) en la faisant passer à 5 par exemple (sous l'étiquette ["simulation"]["time"]["factor"]). Sauvegardez le fichier app.json puis retournez sur la fenêtre de simulation toujours active et appuyez la touche 'C' puis 'R', vous devriez voir vos hamsters aller beaucoup plus vite. Faites l'exercice inverse en ramenant la vitesse à 0.5

[Video : déplacement rectiligne uniforme
(ici avec ["simulation"]["time"]["factor"] valant 5)]

Changements aléatoires de direction

Vous allez maintenant modifier votre algorithme de déplacement de sorte à ce que l'animal puisse faire de temps à autre des rotations aléatoires selon le modèle décrit dans ce document.

La fonction prédéfinie piecewise_linear (fournie dans Random.[hpp][cpp]) qui agit sur un ensemble Θ d'angles et un ensemble \ensuremath{P_\mathrm{m}} de probabilités (tels que décrits dans le document précité), permet de de tirer aléatoirement un angle selon une distribution linéaire par morceaux.

Définissez deux constantes statiques caractérisant les angles et probabilités que nous utiliseront pour l'appel à cette fonction.

Vous pourrez alors doter Animal d'une nouvelle méthode, getNewRotation(), retournant une valeur type Angle résultant d'un appel à piecewise_linear.

Pour plus de lisibilité nous avons choisi de formuler l'ensemble de angles en degré. La valeur fournie par piecewise_linear est donc dans cette unité. getNewRotation() prendra soin de la convertir en radians. Ceci peut se faire en multipliant sa valeur par la constante fournie DEG_TO_RAD.

L'idée est maintenant de modifier la méthode move de sorte à ce qu'avant de calculer la nouvelle position de l'animal, il se produise une tentative de rotation. Une telle tentative consiste à :

Nous parlons de "tentative de rotation" car il est possible que l'angle tiré soit zéro et dans ce cas, l'animal ne changera pas de direction Pour un effet un peu plus réaliste la tentative de rotation n'aura pas lieu à chaque pas de temps, mais à intervalles régulier toute de même.

Pour mettre en oeuvre cela, vous pouvez ajouter à l'animal un compteur de type sf::Time (initialisé par exemple à sf::Time::Zero) et mesurant le temps écoulé depuis la précédente rotation. Vous ferez alors en sorte que l'animal ne fasse de tentative de rotation qu'au bout de l'écoulement du laps de temps sf::seconds(temps_entre_deux_rotation)). Pour temps_entre_deux_rotations vous pouvez utiliser la constante paramétrable getAppConfig().animal_rotation_delay.

Test 14 : Déambulations

Une fois faites toutes les modifications suggérées ci-dessus, vous pouvez lancer le test comme précédemment et y créer des hamsters. Vous devriez alors les voir déambuler en procédant, de temps à autres, à des changements de direction aléatoires.

Votre programme devrait alors avoir le comportement montré par la petite vidéo ci-dessous

[Video : déplacement rectiligne uniforme avec rotations aléatoires]

Si vous avez bien codé le réajustement de position à l'étape précédente, créer un hamster très près des murs ne devrait pas causer de «bug» (où le hamster n'arriverait plus à se mouvoir, comme coincée par les parois du mur).

Mort de fatigue

Complétez enfin la méthode Animal::update de sorte à ce qu'au terme des traitements qu'elle effectue déjà, l'animal perde de l'énergie (il perd de l'énergie à chaque pas de temps de la simulation).

La perte d'énergie est fonction du pas de temps écoulé (en secondes) et de la norme de la vitesse (plus l'animal va vite et plus il se fatigue) selon la formule suivante:

  perte_energie = depense_energetique_de_base + norme_vitesse * facteur_perte_energie() * dt

depense_energetique_de_base est paramétrable (getAppConfig().animal_base_energy_consumption). Le facteur de perte d'énergie se calcule potentiellement de façon différente pour chaque type d'animaux (getAppConfig().hamster_energy_loss_factor pour les hamsters par exemple).

[Question Q3.13] Comment proposez-vous de modifier la méthode getMaxSpeed de sorte à ce qu'en dessous d'un seuil critique d'énergie l'animal se déplace plus lentement ? répondez à cette question dans votre fichier REPONSES et mettez en oeuvre cette fonctionnalité conformément à votre réponse (vous pouvez pour simplifier considérer que ce seuil est le même pour tout animal).

Test 15 : mort de «fatigue»

On utilisera la même cible de test que précédemment.

Commencez par configurer votre simulation comme suit:

Ensuite:

  1. lancez le test LabTest et mettez la simulation en pause (barre d'espace).
  2. créez une hamster
  3. relancez la simulation (barre espace);
Vous devriez voir l'énergie du hamster baisser au fil du temps (mettez éventuellement en pause de temps en temps pour bien observer le phénomène). Au bout d'un moment, le hamster devrait se déplacer plus lentement (énergie en dessous du seuil critique) et l'issue fatale devrait se traduire par la disparition de l'animal.
Retour à l'énoncé du projet (partie 3) Module suivant (partie 3.3)