Nous allons cette semaine donner un aspect un peu plus complet/unifié à notre projet en créant un « système de simulation » regroupant de façon plus générique tous les composants nécessaire pour coder un exercice de Physique.
Je vous conseille une dernière fois de vous assurer avoir bien compris les concepts présentés en cours avant de vous lancer directement dans la réalisation de la nouvelle partie du projet, par exemple en faisant au préalable quelques exercices si nécessaire.
Pensez aussi à relire le descriptif afin de bien savoir ce que vous êtes en train de faire.
Commençons par la vue générale, la conception globale de nos programmes. J'en donne ici une version minimale. La même démarche est expliquée plus en détail dans le tutoriel sur le graphisme, mais dans un cadre un peu différent (celui du graphisme en général), ce qui fait qu'elle y est un peu plus compliquée, mais plus complète. Cela vaut peut-être la peine d'aller y jeter un œil si la version résumée ici n'est pas assez claire.
L'idée de cette conception générale est d'être très modulaire et de systématiquement séparer d'un coté les simulations physiques et de l'autre leur visualisation (sous forme textuelle, graphique, ou d'autres comme, par exemple, l'écriture dans un fichier, non abordée dans ce projet). On distingue pour cela (au moins) trois choses : le programme principal (le main()), le système à simuler (classe Systeme décrite ci-dessous) et la façon de l'afficher (classes Dessinable et SupportADessin, décrites ici).
Par ailleurs, nous souhaiterions que les objets simulés dans notre système soient de différente nature. On peut imaginer des points matériels, des charges électriques, des solides non déformables (hors projet), des «solides» déformables (encore plus hors projet), etc.
Le but de ce projet étant avant tout de vous faire concrètement
travailler les différents aspects de la POO, je vous demande
d'imaginer que la classe PointMateriel (ou,
encore plus, la classe ObjetMobile) aurait
différentes sous-classes, même si vous n'allez peut être pas en
implémenter beaucoup.
Plusieurs de ces objets de vos simulations seront «digne d'intérêt» dans ce sens que c'est ce que nous souhaitons observer. Cela nous conduit à l'abstraction suivante décrite ci-dessous: les «objets dessinables» (ou «observables»)
.
Pour peut être clarifier mon propos: le but des ObjetMobile est d'être intégrable, mais peut être est-ce une abstraction de trop haut niveau pour que tout ObjetMobile soit intéressant à suivre, à « regarder ». Par contre, il me semble clair que tout PointMateriel dans un exercice de Physique serait intéressant à suivre. C'est cette notion de « je veux voir ce qui se passe avec cet objet » que nous représentons sous l'abstraction Dessinable décrite maintenant.
On souhaite pouvoir « interroger l'état » de différents objets, soit en mode texte comme cette semaine, soit de façon graphique comme abordé dans deux semaines, les différents objets qui seront présents dans nos simulations (typiquement les PointMateriel dans le cadre de ce projet-ci). On veut le faire de façon unifiée au moyen d'une méthode dessine_sur(SupportADessin&), laquelle écrira simplement du texte dans la version « mode texte » et dessinera effectivement quelque chose lorsque vous ajouterez du graphisme. Bien sûr, chaque dessin est spécifique à l'objet dessiné !
[Question P8.1] En termes de POO, quelle est donc la nature de la méthode dessine_sur() ?
Répondez à cette question dans votre fichier REPONSES.
Concrètement, je vous demande donc de créer une classe Dessinable contenant une méthode dessine_sur(SupportADessin&).
Pour bien séparer les différents moyens de visualisation, nous introduisons également une classe (abstraite) SupportADessin, qui représentera de façon générique le moyen choisi pour dessiner (écran mode texte, mode graphique avec telle bibliothèque (p.ex. Qt), ou telle autre, ou dans un fichier (non abordé dans ce projet), etc.). Ces moyens possibles de dessiner seront représentés par des sous-classes (à faire plus tard), p.ex. TextViewer pour la « visualisation » en mode texte, VueOpenGL pour la visualisation avec du graphisme en 3D utilisant OpenGL, ou encore FileWritter (non abordé dans ce projet) pour écrire les résultats dans un fichier, etc..
Pour pouvoir être correctement « dessinées », de façon appropriée à chaque SupportADessin, toutes les classes « dessinables » devront posséder la même méthode dessine_sur() , exactement rigoureusement la même (« mot pour mot » ; cette méthode doit être :
virtual void dessine_sur(SupportADessin& support) override { support.dessine(*this); }
ATTENTION ICI ! Pour des raisons techniques
dépassant le cadre de ce cours, il ne faut pas que cette méthode soit
définie dans la classe Dessinable puis
héritée, mais bien que ce même bout de code soit recopié à l'identique
(copié-collé, une fois n'est pas coutume !) dans chaque sous-classe de Dessinable (masquage).
[ C'est
le prix à payer du fait qu'en C++ il n'y a pas de ce qu'on appelle « double dispatch ». ]
La classe SupportADessin est une classe abstraite qui ne doit contenir que (les méthodes usuelles et) les méthodes virtuelles de dessin pour tous les objets que l'on voudra dessiner. Par exemple (à adapter à votre propre projet et au fur et à mesure de son avancement) :
class SupportADessin { public: virtual ~SupportADessin() = default; // on suppose ici que les supports ne seront ni copiés ni déplacés virtual void dessine(PointMateriel const&) = 0; virtual void dessine(Systeme const&) = 0; virtual void dessine(Solide const&) = 0; // exemple, non abordé dans ce projet // ... autres choses que vous voudriez « dessiner »... };
Faites déjà tout ceci, puis répercutez sur les classes PointMateriel ou autres, les modifications que vous jugez nécessaires.
Créez une classe TextViewer qui est un SupportADessin dédié à la « visualisation » en mode texte dans un iostream qu'il aura comme attribut (par référence, bien sûr ! -- on ne peut pas faire de copie d'iostream).
Les méthodes dessine() de cette classe se contentent simplement d'appeler l'opérateur << de leur argument.
Pour plus de détails, voir si nécessaire cette partie du tutoriel graphique.
Avant de commencer, je voudrais attirer plus spécialement votre attention sur un point évoqué en cours :
[Question P8.1] A quoi faut-il faire attention pour les classes contenant des pointeurs ? Quelle(s) solution(s) est/sont envisageable(s) ?
Répondez à cette question dans votre fichier REPONSES.
La classe Systeme à laquelle on s'intéresse ici a pour but de représenter tout ce qui forme le système physique simulé, c'est-à-dire une collection d'objets, de contraintes, de champs de forces et un intégrateur (rien que ça ! ;-)). Un Systeme aura aussi la « maîtrise du temps » (c'est lui qui connaît et décide du temps).
Comme discuté précédemment dans la conception générale : on
veillera à clairement dissocier ce qui relève de la simulation du
système physique (et qui devra être rattaché à la
classe Systeme) de tout ce qui relève de sa
représentation graphique, sa « visualisation » au sens
large.
En plus clair : il devra être possible, en utilisant la classe
Systeme, de faire tourner une simulation sans aucune
interface graphique (simulation en mode texte).
De plus, on devra pouvoir visualiser/« dessiner » un Systeme. Son « dessin » consistera simplement à dessiner tous les composants qu'il contient.
[Question P8.2] Comment représentez vous
la classe Systeme ?
Expliquez votre conception (attributs, interface, ...).
Répondez à cette question dans votre fichier REPONSES.
Cette classe devra au moins avoir comme méthodes (mais vous pouvez bien sûr en ajouter d'autres) :
Exemple d'affichage :
Systeme : à t = 0 : Objet no 1 : Point Matériel : 0 0 0 # parametre 0 0 0 # vitesse 0 0 0 # position physique 0 0 0 # vitesse physique 5.972e+24 # masse contrainte : contrainte Libre Objet no 2 : P: 6.37101e+06 0 0 # parametre 0 0 0 # vitesse 6.37101e+06 0 0 # position physique 0 0 0 # vitesse physique 0.1 # masse contrainte : contrainte Libre Champ no 1 : champ newtonien, centre : 0 0 0, masse : 5.972e+24 Champ no 2 : champ newtonien, centre : 6.37101e+06 0 0, masse : 0.1 Contrainte no 1 : contrainte Libre
Créez un fichier testSystem.cc pour y tester votre nouvelle classe en reproduisant la sortie ci-dessus.
On ne s'est pour l'instant intéressé qu'à la conception générale du système, pas à son évolution. Ceci est l'objet du prochain exercice.
On veut ici créer notre première simulation d'un système complet (et peut être quelques autres). Pour cela, on va commencer par reproduire le résultat de la chute d'une pomme, mais dans un Systeme.
Pour cela, implémentez dans une méthode evolue(double dt)
de la classe Systeme, une boucle sur tous les objets (pouvant évoluer) qu'il contient. Cette boucle fait avancer (grâce au même intégrateur, composant interne du système) chacun de ces objets du pas de temps dt fourni.
Dans un fichier exerciceP9.cc, créez puis faîtes évoluer un système ayant les mêmes caractéristiques que celles de l'exemple de la chute d'une pomme de la semaine passée, et vérifiez que vous obtenez les mêmes résultats
Note : la méthode dessine_sur() d'un objet en mode texte (sur un TextViewer, donc) peut très bien être différente de son affichage (avec <<). On peut par exemple vouloir que l'affichage (avec <<) soit plus complet, avec plus d'informations, alors que la méthode dessine_sur() serait plus simple, n'affichant que le strict nécessaire (si cela vous aide pour dessiner avec gnuplot, Excel ou tout autre logiciel de dessin externe).
Vous êtes libres de faire ces affichages comme bon vous semble (même si cela (le résultat de dessine_sur()) n'est plus très lisible par un humain).
Après, vous pouvez aussi faire plusieurs SupportADessin textuels différents suivant vos besoin (p.ex. GnuplotViewer, DebugViewer, PositionOnlyViewer qui sont des TextViewer).
Si vous avez le temps, vous pouvez maintenant créer d'autres exercices de Physique, par exemple (références à vérifier SVP -- pas sûr de mes sources ;-)) :
(Note: on a sélectionné ici que des exercices avec champ gravitationnel constant ou champ newtonien; nous aborderons des exercices plus compliqués dans le sujet P11, en semaine 10.)
Pour chaque nouvel exercice, développez le dans un fichier C++ séparé, de nom exerciceP9-SUFFIXE.cc où le SUFFIXE est assez clair, et indiquez dans votre fichier README plus précisément de quel exercice de Physique il s'agit (et en deux mots sur quoi porte cet exercice; comme fait ci-dessus). Vous pouvez, bien sûr, choisir d'autres exercices de Physique qui vous semblent pertinents.