Licence Informatique 2018-2019
II2D



TP 01 : Mise en place et gestion des particules

Objectifs :

1  Prise en main

Question 1. Inscrivez vous sur moodle au cours II2D : http://moodle.univ-lille1.fr/course/view.php?id=6235 (demander la clef à l’enseignant).

Question 2. Récupérez II2D.zip. Décompressez-le pour obtenir ii2D_Student1_Student2 qui sera votre dossier de travail.

Question 3. Sans attendre :

  1. renommez ce dossier en remplaçant Student1_Student2 avec votre nom (les 2 noms si vous êtes en binôme).
  2. indiquez dès à présent vos noms/prénoms dans le Readme.txt

Pour rendre votre projet, vous devrez en effet remettre le dossier complet (archive .zip) avec ces renommages et le fichier Readme.txt complété.

Question 4. Les fichiers inclus dans l’archive constituent un squelette rudimentaire. La décomposition en plusieurs fichiers Javascript est une proposition : vous êtes bien sûr libre d’organiser votre code comme bon vous semble. Décrivons fichier par fichier (ouvrez chacun d’eux au fur et à mesure).

Remarque : les exemples qui apparaissent dans les sujets (mini vidéos) illustrent les questions pour la compréhension : il ne s’agit pas d’obtenir exactement la même chose (le résultat dépend de vos choix de paramètres, de vos choix de résolution, de votre avancée, etc).

2  Affichage

On commence par regarder comment afficher quelque chose dans le canvas.

Question 5. Pour afficher quelque chose il faut passer par la variable (globale) ctx : c’est le contexte d’affichage du canvas (cf son initialisation dans ii2d_main.js).

Rendez-vous dans la méthode draw de la classe Engine : comprenez bien que cette méthode est appelée 60 fois par seconde ("normalement") par le biais de la boucle d’affichage loop (cf cette méthode loop qui se trouve aussi dans la classe Engine).

Dans cette méthode draw de la classe Engine affichez un rectangle (cf fillRect. Remarque : il suffit de cliquer sur les liens pour amener à la documentation MDN correspondante). Testez (vous devez voir un rectangle noir).

Question 6. Grâce à fillStyle, faire l’affichage du rectangle avec une couleur définie par rgb. Testez.

Question 7. On va afficher un rectangle d’origine aléatoire à chaque image. Par exemple pour l’origine du rectangle, on souhaite un point dont les coordonnées pixels (des entiers) se trouvent entre (100,150) et (200,250).

On commence par faire une méthode setRandInt(p1,p2) dans la classe Vector qui affecte aléatoirement un point entre les coordonnées des points p1 et p2. Par exemple pour fixer aléatoirement les coordonnées d’un point c, l’utilisation sera : c.setRandInt(new Vector(100,150),new Vector(200,250)).

Faites cette méthode. Vous pouvez exploiter la fonction randInt(a,b) déjà disponible qui donne un entier entre a et b.

Modifiez l’affichage de votre rectangle en exploitant cette méthode pour afficher un rectangle de position aléatoire à chaque image. Testez.

Question 8.

  1. Dans le draw de Particle affichez un rectangle (relativement petit : il s’agit de l’affichage pour une particule) à la position de la particule.
  2. Dans le draw de ParticleManager appelez le draw de chaque particule de l’ensemble du tableau all (all contient déjà toutes les particules pré-allouées : voir la définition de all dans le constructeur de ParticleManager).
  3. Et enfin dans le draw de Engine appelez le draw du particle manager (sans oublier de supprimer ou mettre en commentaire le tracé du rectangle de la question 7).

Testez : pour l’instant toutes les particules sont par défaut en (0,0) donc tous les rectangles se superposent en haut à gauche, mais comprenez l’ensemble de la démarche.

Question 9. A chaque image, on fixe aléatoirement la position de chaque particule : dans le update de particleManager, affectez la position de l’ensemble des particules aléatoirement (grâce au setRandInt vu précédemment). Appelez cette méthode update depuis le updateData de Engine. Testez (effectuez plusieurs tests en modifiant nbAliveMax pour cerner la conséquence du nombre de particules à l’écran)

Question 10. Dans la suite, l’initialisation d’une particule sera le rôle des générateurs (classe GeneratorBox dans le squelette).

  1. Créez un attribut generator (par exemple) dans le constructeur du ParticleManager
  2. Définissez des attributs min et max (ce sont des Vector) dans le constructeur de GeneratorBox (ce sont les bornes de la boite où seront initialisées aléatoirement les particules).
  3. Dans GeneratorBox.initParticle(p), affectez aléatoirement la position de la particule p entre min et max.
  4. Enfin, modifiez ParticleManager.update pour appeler le initParticle du générateur pour chaque particule de all (sans oublier de supprimer ce que vous avez fait pour la question précédente).

Testez le tout (le résultat dépend des valeurs fixées pour min et max, et du nombre de particules).

Question 11. On affecte à présent une couleur aux particules :

  1. Ajoutez un attribut de couleur à la classe Particule dans son constructeur (vous pouvez faire directement this.color={r:0,g:0,b:0} pour représenter les composantes rouge, vert, bleu; inutile de faire une classe dédiée à la représentation des couleurs pour le tp).
  2. Générez une couleur aléatoire dans GeneratorBox.initParticle (p.color.r = randInt(0,255); par exemple).
  3. Exploitez cette couleur dans Particle.draw (il suffit de construire une chaine de caractère avec 'rgb('+this.color.r+ etc)# pour affecter ctx.fillStyle).

Testez

Question 12. On peut souhaiter plusieurs générateurs :

  1. Créez un attribut generatorList comme tableau vide dans ParticleManager (mettez en commentaire l’attribut generator que vous aviez créé à une question précédente : il ne sera plus utilisé).
  2. Nous allons configurer la liste des générateurs depuis l’extérieur pour initialiser la simulation. Par exemple, dans ii2d_main.js après le engine=new Engine() faire :
    var gen1 = new GeneratorBox(); gen1.min.setXY(50,50); // setXY à faire dans la classe Vector gen1.max.setXY(100,200); var gen2 = new GeneratorBox(); gen2.min.setXY(150,150); gen2.max.setXY(250,300); engine.particleManager.generatorList.push(gen1,gen2); // ajoute au tableau generatorList

    (on pourrait bien sûr pousser l’abstraction pour éviter l’accès direct aux attributs et l’enchainement des ".").

  3. Dans ParticleManager.update initialisez alors la moitié des particules avec generatorList[0] et l’autre moitié avec le second générateur (on suppose qu’il y a bien 2 générateurs dans la liste).

Testez (vous devez distinguer clairement les boites qui correspondent aux 2 générateurs).

3  Naissances

On effectue dans cette partie les modifications nécessaires pour gérer les naissances des particules.

Question 13. Mettez en commentaire ce qui se trouve dans ParticleManager.update. L’objectif sera dans la suite d’initialiser une particule uniquement quand il y a une naissance.

Question 14. Ajoutez un attribut isAlive à Particule initialisée à false dans son constructeur. Cet attribut permettra de savoir si une particule est active ou non ( (aucune particule n’est active au lancement de l’application).

Question 15. Dans ParticleManager.update, il faut déterminer pour chaque générateur combien de naissances doivent être faites. Il suffit de mettre à jour l’attribut nbBirth selon la fréquence birthRate : ces attributs sont déjà définis. La valeur par défaut correspond à ajouter 0.2 particules à chaque update (i.e. à chaque image). Faites le.

Question 16. On rappelle qu’on a choisi de travailler avec un tableau all, de taille fixe, de particules déjà allouées (ceci pour éviter la création, par new, d’une particule à chaque naissance lors de l’animation : cf cours). Lorsqu’une particule nait, c’est donc une des particules inactives de all qui devient active.

Cela impose de parcourir tout le tableau des particules à chaque appel de ParticleManager.update pour gérer les naissances.

Pour chaque particule de all, si elle est active, on ne fait rien (pour l’instant...), mais sinon, on regarde s’il y a une naissance à effectuer par un générateur (parcours des générateurs en testant le nbBirth; on pourrait également faire un seul while pour le tout qui "avance" soit sur les particules, soit sur les générateurs selon les cas).

Si oui, on initialise la particule avec ce générateur (avec GeneratorBox.initParticle) pour la faire naitre (sans oublier de décrémenter le nbBirth du générateur concerné; et sans oublier de rendre active la particule).

Mettez en place ce principe (boucle sur les particules dans ParticleManager.update). Il faut encore faire la question suivante pour pouvoir tester le tout.

Question 17. Modifiez l’affichage du ParticleManager pour afficher une particule uniquement si elle est active.

Testez avec différentes valeurs pour les birthRate des 2 générateurs, ainsi que pour différentes valeurs de nbAliveMax (nombre de particules totales).

Remarques :

  1. Cela ne doit plus "clignoter" : les particules sont initialisées uniquement à leur naissance (et non pas à chaque affichage).
  2. Comprenez qu’il y a "saturation" au bout d’un moment lorsque que toutes les particules de all sont devenues actives : les générateurs ne peuvent plus donner de naissances.

4  Disparitions

On traite à présent la disparition des particules.

Question 18. On affecte une longévité à une particule à sa naissance : lorsqu’elle sera en fin de vie, elle repassera inactive (i.e. particule "morte"). Cette longévité sera aléatoire et son affectation sera de la responsabilité des générateurs. La longévité est un entier en nombre d’image (par exemple, une particule avec longévité de 100 disparaitra après 100 images).

Définissez des attributs pour la longévité minimale et maximale dans la classe GeneratorBox. Créez un attribut de durée de vie pour la classe Particule. Initialisez cette durée de vie aléatoirement selon le générateur lors d’une naissance (dans la méthode GeneratorBox.initParticle).

Il vous reste, lors du parcours des particules, à décrémenter cette durée de vie et à passer la particule à inactive en fin de vie.

Testez

Remarque : lorsqu’il y a saturation (la fréquence des naissances est plus importante que la fréquence des morts), vous constaterez que seul le premier générateur assure des naissances. Pour résoudre cet inconvénient, il faudrait choisir un générateur différent à chaque naissance potentielle. On ne s’en préoccupera pas (sauf éventuellement en travail bonus), car de toute façon, la situation de saturation est une situation critique (i.e. on ne pourra pas de toute façon assurer le nombre de naissances nécessaires).

5  Disparition progressive

Le coeur du moteur, pour la génération des particules, est maintenant réalisé. On peut bien sûr y apporter de nombreuses améliorations, et nous en proposons une ici : estomper la couleur des particules lorsqu’elle vont bientôt disparaitre pour adoucir l’effet visuel.

Question 19. Il y a plusieurs façon d’estomper une couleur, mais ici on exploite un coefficient de transparence appelé coefficient alpha. Ce coefficient est un nombre réel entre 0 et 1. La valeur 1 correspond à une couleur totalement opaque (et donc en pleine couleur) et la valeur 0 à une couleur totalement transparente (et donc invisible).

  1. Ajoutez un champ à la couleur d’une particule pour traduire ce coefficient alpha (initialement à 1).
  2. Modifiez le style de remplissage pour tenir compte de ce coefficient lors de l’affichage d’une particule. Il suffit d’utiliser ’rgba’ avec un paramètre supplémentaire, et non plus ’rgb’.
  3. Lors du parcours des particules, diminuez ce coefficient alpha lorsque la durée de vie devient faible (selon le nombre d’images qui lui reste à vivre; traduisez ce principe comme vous voulez).

Testez


Ce document a été traduit de LATEX par HEVEA