Master Informatique 2017-2018
M3DS



TP 09 : Animation basée physique de boites rigides


Exercice 1. Introduction

La structure du squelette est assez similaire au tp sur les particules : on travaille principalement avec les classes Box, BoxList et EngineBox (on retrouve dans ce dernier la boucle principale, l’intégration par euler et la gestion des collisions).

L’origine du repère des boites est le centre de la boite, et les directions x et y sont respectivement sa largeur et sa hauteur. Les boites sont placées dans le repère de scène World en donnant la position du centre de la boite (b->position(Vector3) dans le source) et son angle d’orientation (b->theta(double)).

Les boites sont initialisées dans le constructeur GLApplication::GLApplication) (donnée des masses, des angles/positions/vitesses initiales, calcul du moment d’inertie déjà effectués).

Remarque : toutes les données sont en 3D, mais on raisonne dans le plan (toutes les coordonnées z sont nulles et on se contentera d’appliquer les lois physiques angulaires 2D).


Exercice 2. Intégration du mouvement

Question 1. Dans EngineBox.cpp (qui gère toute la physique de la scène), il faut calculer les forces qui s’appliquent sur chacune des boites dans la méthode EngineBox::computeForce(). Commencez par mettre une force de gravité vers le bas (i.e. y négatif; cf commentaires pour les aspects techniques). Remarque : il n’y a pas de moments pour la gravité, car elle est appliquée uniquement au centre de gravité.

Testez : vous devez avoir les 2 boites qui "tombent".

Remarque :

La méthode de collision avec les bords (les lignes bleues qui représentent des plans orthogonaux à l’écran) est similaire à ce qui a été vu sur les particules et est déjà faite pour vous (dans EngineBox::collisionPlane) : la collision est résolue en déterminant le point de la boite le plus éloigné dans la partie négative du plan, puis on répond à la collision par impulsion (correction en vitesse/en position/en force). Pour déterminer ce point le plus loin, on teste chacun des sommets de la boite (les 4 sommets de la boite sont donnés par une_boite->vertex(i)). Tous les calculs sont faits dans le repère world de la scène.

Question 2. Complétez EngineBox::euler() pour intégrer la vitesse angulaire (i.e. b->omega(??)) et l’angle de rotation (i.e. b->theta(??)). Remarque : omega() est un vecteur 3D; pour avoir la vitesse de l’angle theta il suffit de faire b->omega().z() (le vecteur vitesse angulaire omega est orthogonal au plan).

Testez vous devez constatez à présent un rebond naturel des boites (incluant la rotation)


Exercice 3. Application d’un ressort

On souhaite contrôler les boites avec la souris : on clique dans la boite, cela attache un ressort au point cliqué, et lorsqu’on déplace la souris, l’allongement du ressort déplace la boite.

Question 1. On doit d’abord localiser la position de la souris dans la boite : lorsqu’on clique, les coordonnées pixels de la souris M sont tout d’abord transformées dans le repère de la scène (fait dans GLApplication::update()).

Ensuite il faut savoir si ce point M est interne à une boite b : pour cela, on transforme le point M dans le repère local à la boite, puis on teste si ses coordonnées sont comprises dans [−width()/2.0,width()/2.0] et [−height()/2.0,height()/2.0] (cf schéma du repère de la boite en début de sujet).

Cette localisation d’un click souris dans une boite est déjà assurée par le squelette (méthodes Box::isInside et Box::toLocal). Remarque : la localisation d’un point dans une boite avait été faite lors du PJE Multi-Touch; elle doit être parfaitement comprise.

Testez : cliquez dans les boites. La boite doit apparaitre avec un contour épais lorsque le click est à l’intérieur (Remarque : bien qu’effectuées par le squelette, les étapes qui passent des coordonnées souris au repère local de la boite pour savoir si le click est à l’intérieur de la boite doivent être comprises).

Question 2. Lorsque vous déplacez la souris en maintenant le click dans une boite, vous voyez apparaitre un trait : on va interpréter ce trait comme un ressort.

Complétez EngineBox::computeForce() pour insérer la force issue du ressort. Le ressort est actif lorsque _cursorActive==true, et on récupère la boite qui est attachée au ressort par Box *b=_boxList->selected().

Pour calculer la force : l’allongement l du ressort est donné par l=_cursor-b->attachWorld() avec _cursor le point actuel de la souris, et b->attachWorld() le point d’attache dans la boite (i.e. le click initial); ces deux extrémités du ressort sont déjà exprimées dans le repère de scène. La longueur au repos (quand le ressort ne provoque pas de force) sera l’allongement nul (les boites seront ainsi toujours attirées par la souris, et jamais repoussées). On ajoute une force à la boite b par b->addForce(la_force);

Testez : vous pouvez maintenant contrôler les boites avec ce ressort (testez éventuellement avec différentes raideurs).

Remarquez cependant que la force appliquée ne provoque pas de rotation (testez en attachant le ressort sur une extrémité d’une boite : la boite devrait intuitivement tourner). C’est l’objet de la question suivante.

Question 3. Ajoutez dans EngineBox::computeForce, le moment de la force du ressort (cf cours) en utilisant b->addMoment(un Vector3).

Testez : vous devez maintenant pouvoir contrôler l’orientation des boites avec le ressort. Dans la suite on va contrôler l’inertie des objets en mouvement (pour l’instant si vous faites tourner un objet, il va tourner indéfiniment).

Question 4. Ajoutez une force de frottement à chacune des boites dans EngineBox::computeForce() avec b->addForce(???), ainsi qu’un moment de frottement avec b->addMoment(???) (moment allant dans le sens opposé au vecteur vitesse angulaire).

Testez et choisissez des coefficients qui vous conviennent (ni trop lent, ni trop rapide).


Exercice 4. Détection des collisions entre boites par axes séparateurs
On cherche à déterminer si les boites entrent en collision, puis à répondre à cette collision. La collision sera détectée grâce aux axes séparateurs.

La méthode principale pour la détection est Box::detectCollision(b1,b2,collisionInfo) (méthode de classe). Le paramètre collisionInfo contiendra, en sortie, toutes les informations nécessaires à la réponse. La méthode doit tester l’existence d’un axe séparateur entre b1 et b2 parmi les 4 possibles. Elle utilise Box::distance(b1,b2,axe,...) qui permet d’avoir la distance de recouvrement entre b1 et b2 sur l’axe donné. Pour cela, il faut projeter les boites sur l’axe : cela est fait par Box::project(axe,mini,maxi) (le résultat sera donné par l’intervalle [mini,maxi]).

Question 1. Complétez Box::project(...) qui doit projeter tous les sommets de la boite sur le vecteur axe, et donner en retour l’intervalle de projection sur cet axe (2 réels en résultat).

Testez : vous devez voir apparaitre 2 traits qui correspondent aux intervalles de projection des 2 boites sur un des axes (il s’agit de l’axe x de la plus petite boite). Vérifiez visuellement que les projections sont correctes, et constatez la séparation/superposition des intervalles (l’axe est séparateur si les 2 intervalles ne se recouvrent pas).

Question 2. Complétez à présent Box::distance(...) : elle doit affecter *distance avec la distance de recouvrement des 2 boites selon l’axe donné (par convention la distance doit être négative si les projections se recouvrent et positive sinon). Le résultat direction doit être affecté pour la réponse à la collision qui suivra : Le paramètre direction doit être affecté avec +1 si, pour séparer b2 de b1, il faut bouger b2 dans le même sens que l’axe séparateur, et −1 s’il faut aller dans le sens opposé.

Recouvrement positifRecouvrement négatifRecouvrement négatif
 (et b2 doit être déplacé dans le sens de u)(et b2 doit être déplacé dans le sens de −u)

Testez pour voir apparaitre la distance de séparation sur la boite b1.

Question 3. Il reste enfin à faire la boucle sur les 4 axes possibles, et retenir la distance minimale ainsi que l’axe correspondant : complétez Box::detectCollision (déterminer les affectations à faire à detect, dist_min et axe_min). Enlevez le detect=false qui apparait avant if (detect) (réponse à la collision).

Testez : la collision doit être maintenant effective entre les boites (Remarque : le calcul de l’impulsion et la correction en vitesse/etc est déjà effectuée dans EngineBox::interCollision : il suffit de décommenter le bloc qui provoque ce calcul dans cette méthode).

Attention : axe_min doit donner la direction de séparation u, mais également le sens de séparation : selon la position relative des intervalles de b1 et b2, la séparation doit se faire en déplaçant b1 dans le sens de u ou dans le sens opposé (cf schéma). C’est le paramètre direction (qui est soit +1, soit −1) calculé dans la procédure distance qui doit indiquer ce sens (i.e. multiplier la direction choisie par direction; direction doit bien sûr être correctement affecté dans la procédure distance).

(une fois testé, vous pouvez enlever les tests visuels : commentez l’appel à drawDebugProject dans Box::distance et l’appel à p3d::addDebug dans Box::detectCollision).

Question 4. Créez une scène (si vous souhaitez rajouter des boites, il suffit de copier/coller la création d’une boite dans le constructeur GLApplication::GLApplication()) puis règlez les forces qui conviennent pour avoir une interaction naturelle.


Ce document a été traduit de LATEX par HEVEA