Gestion des collisions

Création de la classe Polygon2D

Nous allons faire en sorte de gérer les collisions avec des polygones (qui permettent de représenter n'importe quelle type de forme en la discretisant).

Sans perte de généralité, nous n'allons gérer que des polygones fermés (vous pourrez étendre le code facilement pour gérer des polygones ouverts). Un polygone sera réprésenté par la suite de ses sommets ainsi qu'une couleure. Nous stockerons également un booléen indiquant si le polygone est interne. Un polygone interne retient les particules à l'intérieur (utile pour l'écran).

Implantez une classe Polygon2D stockant un tableau de points (utilisez un std::vector), une couleur (sous la forme d'un glm::vec3) et un boolean m_bIsInner indiquant si le polygone est interne.

Création des fonctions de construction

Afin qu'il soit plus pratique de créer des polygones dans le code, nous allons coder deux fonctions de construction.

Implantez la fonction de prototype Polygon2D buildBox(glm::vec3 color, glm::vec2 position, float width, float height, bool isInner = false);. Celle ci doit construire un nouveau polygone ayant la forme d'un carré de coin inférieur-gauche position, de la largeur width et de hauteur height.

Implantez la fonction de prototype Polygon2D buildCircle(glm::vec3 color, glm::vec2 center, float radius, size_t discFactor, bool isInner = false);. Celle ci doit construire un nouveau polygone ayant la forme d'un cercle. Le paramètre discFactor indique le nombre de segments devant constituer le polygone approximant le cercle.

Testez et dessiner les polygones

Ajoutez à votre classe Polygon la méthode void draw(ParticleRenderer2D& renderer, float lineWidth = 1.f) const. Celle ci doit utiliser la méthode drawPolygon que de la classe Renderer2D pour dessiner le polygone représenté par this.

Dans un nouveau main, créez deux polygones à l'intialisation: une boite interne de coin $(-0.9, -0.9)$, de largeur $1.8$ et de hauteur $1.8$ (qui délimitera l'écran); et un cercle externe de centre $(0, 0.2)$, de rayon $0.2$ et de facteur de discretisation $32$. Dans la boucle, au moment du dessin des particules, ajoutez le dessin de vos deux polygones.

Un peu de maths

Il a plusieurs moyens pour résoudre les collisions avec des obstacles dans un moteur de particule. La méthode que je vous propose consiste simplement à ajouter une force à chaque particule qui risque de passer à travers un polygone entre l'instant courant et l'instant suivant. En effet la réaction d'un solide peut être vu comme l'application d'une force infiniment forte durant une durée infiniment courte suffisante pour repousser la particule. Dans notre cas, nous n'avons pas de durée "infiniment courte", nous avons une durée finie dt. Il est possible de dériver à partir d'une particule (son état $P_t$ et l'état suivant $P_{t + dt}$) une force suffisante pour la repousser lorsqu'elle entre en contact avec une arrête $[AB]$.

A partir du schéma et de vos connaissances en maths, trouvez les conditions necessaires et suffisantes pour que les segments $[P_tP_{t+dt}]$ et $[AB]$ s'intersectent.

Ecrivez la fonction bool intersect(const glm::vec2& p1, const glm::vec2& p2, const glm::vec2& A, const glm::vec2& B, glm::vec2* intersection, glm::vec2* normal) qui doit tester l'intersection entre les segments [p1p2] et [AB]. Si cette intersection existe, la fonction renvoit true et remplit les variables *intersection et *normal avec la position du point d'intersection et la normale en ce point (la normale est la même pour tous les points d'intersection puisqu'on traite des segments).

La classe PolygonForce2D

Vous allez maintenant implantez la classe de force qui repoussent les particules des polygones. Celle ci aura pour rôle, dans sa méthode apply, de tester l'intersection entre chaque particule et chaque arrête du polygone. Si une intersection est trouvée, la méthode doit ajouter une force suffisante à la particule pour la repousser.

Pour la suite il sera necessaire d'obtenir l'état suivant d'une particule. Ajoutez une structure ParticleState2D contenant une position et une vélocité puis la méthode ParticleState2D getNextState(uint32_t particleIdx, ParticleManager2D& pm, float dt) const à la classe LeapfrogSolver2D. Cette méthode doit renvoyer le prochain état de la particule demandée.

Ajoutez la classe PolygonForce2D (qui hérite de Force2D). Celle ci doit avoir 4 variable membre:

  • const Polygon2D* m_Polygon; cette variable pointera sur le Polygone repoussant les particules.
  • float m_fElasticity; cette variable permettra de controller à quelle point les particules rebondissent sur les polygones.
  • const LeapfrogSolver2D* m_Solver; cette variable nous permettra d'obtenir l'état suivant des particules afin de tester s'il y a collision.
  • float m_fDt; cette variable devra être passée à la méthode getNextState pour obtenir l'état suivant des particules.

Ajoutez le constructeur PolygonForce2D(const Polygon2D& polygon, float elasticity, const LeapfrogSolver2D& solver);. Ce dernier doit initialiser les variables membres de la classe et fixer m_fDt à 0.

Ajoutez la méthode setDt(float dt).

La force à ajouter pour repousser une particule $P$ de masse $m$ est $F = e \times \lt v_{t + dt}, -n \gt \times \frac{m}{dt} \times n$ où:

  • $e$ est l'elasticité. Une elasticité de $1$ implique que les particules se collent au polygone, une elasticité de $2$ fait rebondir les particules sans perte d'energie. Au dela les particules accélèrent après avoir rebondi.
  • $\lt v_{t + dt}, -n \gt$ est le produit scalaire entre la vélocité à l'instant suivant et la normale. Pour obtenir la vélocité à l'instant suivant il suffit d'utiliser votre méthode getNextState. Le produit scalaire peut être calculé avec la fonction glm::dot.

Implantez la méthode applyForce de la classe PolygonForce2D. Celle ci doit simplement boucler sur les arrêtes du polygone pointé par m_Polygon et pour chacune tester l'intersection. S'il y a intersection, ajoutez la force mentionnée ci-dessus à la particule concernée. Dans le cas ou le polygone est externe, il faut parcourir les arrêtes dans l'ordre contraire (de manière à renverser les normales).

Testez votre classe en ajoutant une force pour chacun de vos polygones dans le main. Essayez différentes valeurs pour l'elasticité. N'oubliez pas de mettre à jour le dt des forces associées au polygones avec votre méthode setDt.

Il est important de ne pas appliquer la force des polygones si dt = 0. En effet, dans ce cas la division par dt dans la formule provoquerait une force infinie et donc une erreur numérique.

Il faut appliquer les forces de collision après avoir appliqué toutes les autres forces.