Bullet physics - tutoriel

Dorin GRIGORE

Ce tutoriel est consacré à ceux qui veulent découvrir la magnifique librairie Bullet physics. Vous apprendrez les types d'objets rigides ainsi que l'intégration dans votre application.

La version de Bullet physics utilisée dans ce tutoriel est la 2.75.

- Titre : Bullet physics - tutoriel

- Auteur : Dorin GRIGORE

- Parution : 9 octobre 2009

- Mise à jour : 10 octobre 2009

- Licence : Le code source présent sur cette page est utilisable à volonté, même pour utilisation commerciale. La reproduction de l'article est autorisée avec l'accord de l'auteur.

Sommaire

- I. Introduction

- II. Téléchargement et compilation

- II-A. Pré-requis

- II-B. Téléchargement

- II-C. Compilation

- II-D. Tester les démos

- III. Types de données basiques et la librairie mathématique

- IV. Les types d'objets rigides

- IV-A. Les primitives convexes

- IV-B. Formes composées (Compound shapes)

- IV-C. Formes convexes à partir de modèles 3D (Convex Hull Shapes)

- IV-D. Formes concaves statiques à partir de modèles 3D (Concave Triangle Meshes)

- IV-E. Décomposition convexe (Convex decomposition)

- IV-F. Hauteur du champ (Height field)

- IV-G. Plan infini

- V. Intégration dans notre application

- V-A. Le header Bullet

- V-B. Déclarations

- V-C. Une boite OpenGL

- V-D. Initialisation

- V-E. Création de la boite dynamique

- V-F. Création du sol

- VI. La boucle d'affichage de l'application/jeu

- VI-A. Mise à jour des transformations

- VI-B. Récupération des transformations et affichage des objets

- VII. Conclusion

- VII-A. Vidéo de ce qu'on peut arriver à faire avec un peu de motivation

- VII-B. Conclusion

- VIII. Code source

- IX. Remerciements

I - Introduction

Bullet est une librairie de détection de collisions gérant des objets dynamiques comme des boites, sphères, capsules et cetera pour rendre les scènes dynamiquement plus réalistes. Elle est utilisable dans des applications temps réel, par exemple des jeux.

La librairie est open-source, gratuite pour utilisation commerciale, sous licence zlib.

Il y a un forum pour la communauté autour de cette librairie traitant de détections de collisions et physiques à l'adresse suivante :
Lien : http://www.bulletphysics.com/Bullet/phpBB3/

II - Téléchargement et compilation

II-A - Pré-requis

Vous devez avoir de l'expérience avec C++ et OpenGL ainsi qu'une librairie pour créer un contexte OpenGL comme SFML, SDL ou encore GLUT.

II-B - Téléchargement

Vous pouvez télécharger le code source de bullet physics depuis Google Code.
Lien : http://code.google.com/p/bullet/downloads/list

II-C - Compilation

Bullet est livré avec des fichiers de projets auto-générés pour Microsoft Visual Studio 6, 7, 7.1 et 8. Le fichier de projet principal se trouve dans Bullet/msvc/8/wksbullet.sln (remplacez 8 avec votre version de M. Visual Studio).

Sur d'autres plateformes, comme Linux ou Mac OS-X, Bullet beut être compilé utilisant make, cmakehttp://www.cmake.org, ou jamhttp://www.perforce.com/jam/jam.html.
cmake peut auto-générer des fichiers de configuration pour Xcode, KDevelop, MSVC et d'autres systèmes de compilation.

Tapez juste cmake dans une console, étant dans le dossier principal de Bullet.

Si vous n'utilisez pas Microsoft Visual Studio ou cmake, vous pouvez lancer ./autogen.sh./configure pour créer des fichiers Makefile et Jam ensuite tapez make ou jam.

Jam est un système de construction qui peut construire (compiler) la librairie, les démos mais aussi d'auto-générer des fichiers de projet Microsoft Visual Studio. Si vous n'avez pas jam d'installé, vous pouvez le télécharger depuis ftp://ftp.perforce.com/jam [ftp://ftp.perforce.com/jam]

II-D - Tester les démos

Essayez d'exécuter et expérimentez avec l'exécutable BasicDemo comme point de départ. Bullet peut être utilisé de plusieurs façons : en tant que simulation de corps rigides, librairie de détection de collisions ou bas niveau comme GJK calculabilité du plus proche point.

III - Types de données basiques et la librairie mathématique

Les types de données basiques, gestion de mémoire et conteneurs sont localisés dans Bullet/src/LinearMath.

btScalar : un nombre à virgule flottante.
De façon à pouvoir compiler en simple précision en virgule flottante ou double précision, Bullet utilise le type de donnée btScalar. Par défaut, btScalar est un typedef de float. Il peut être double en définissant BT_USE_DOUBLE_PRECISION en haut du fichier Bullet/src/LinearMath/btScalar.h.

btVector3 : les positions 3D et les vecteurs peuvent être représentés en utilisant btVector3 qui contient 3 composants scalaires x, y, z.
Plusieurs opérations peuvent être effectuées sur un btVector3, par exemple, addition, soustraction et la longueur d'un vecteur.

btQuaternion et btMatrix3x3
Les orientations 3D et rotations peuvent être représentées en utilisant btQuaternion ou btMatrix3x3.

btTransform est une combinaison d'une position et d'une orientation.
Il peut être utilisé pour transformer des points et vecteurs d'un espace de coordonnées dans un autre. Aucune mise à l'échelle ou de cisaillement n'est autorisé.

Bullet utilise un système de coordonnées droitier :

Figure 1 : Système de coordonnées droitier

btTransformUtil et btAabbUtil fournissent des fonctions de transformations et des AABB (Aligned Axis Bounding Box).
Aligned Axis Bounding Box ou AABB signifient : boite englobante qui reste toujours orientée sur les axes X, Y et Z.
Pour augmenter les performances un calcul est avant tout fait sur les boites englobantes des objets pour savoir lesquelles se chevauchent. Les AABB qui ne se chevauchent pas, leurs objets ne nécessitent pas de calculs de collisions par exemple.

Figure 2 : Représentation d'une boite englobante ou AABB ainsi que sa représentation en 2D à droite

IV - Les types d'objets rigides

IV-A - Les primitives convexes

btBoxShape : boite définie par la moitié de longueur des côtés.
Très utile pour représenter des objets cubiques, voir Figure 3.

Figure 3 : Objet dynamique en forme de boite à gauche et un objet qu'on peut représenter par une boite dans un jeu, à droite

btSphereShape : sphère définie par son rayon.
Peut représenter les objets sphériques comme les ballons de foot, billes de billard et cetera, voir Figure 4.

Figure 4 : Objet dynamique sphérique à gauche et des objets qu'on peut représenter par une sphère, à droite

btCapsuleShape: capsule autour de l'axe Y.
btCapsuleShapeX/Z peuvent être utilisés pour spécifier autour de l'axe X ou Z.

Figure 5 : Objet dynamique en forme de capsule

btCylinderShape : cylindre autour de l'axe Y.
btCylinderShapeX/Z peuvent être utilisés pour spécifier autour de l'axe X ou Z.
Forme cylindrique, utile pour représenter des bidons et cetera, voir Figure 6.

Figure 6 : Objet dynamique en forme de cylindre à gauche, avec on peut représenter un bidon, à droite

btConeShape : cône autour de l'axe Y.
btConeShapeX/Z peuvent être utilisés pour spécifier autour de l'axe X ou Z.

Figure 7 : Objet dynamique en forme de cône

btMultiSphereShape : pour créer un convex hull à partir de plusieurs sphères qui peuvent par exemple être utilisées pour créer une capsule.
Objet dynamique composé de plusieurs sphères pour représenter certaines formes. Les sphères peuvent être animées également. Voir Figure 8.

Figure 8 : Objet dynamique composé de plusieurs sphères. Ceci est un seul objet.

IV-B - Formes composées (Compound shapes)

btCompoundShape
De multiples formes convexes peuvent être combinées dans un compound shape. Ceci deviens une forme concave composée de multiples sous formes convexes, appelées des formes enfant. Chaque forme enfant a sa propre transformation locale, relative à btCompoundShape.
Objet composé de plusieurs formes pour représenter des modèles 3D sans passer par des tableaux de sommets. Voir Figure 9.

Figure 9 : Exemple de compound shape ou forme composée. Ceci est un seul objet.

IV-C - Formes convexes à partir de modèles 3D (Convex Hull Shapes)

btConvexHullShape
Bullet supporte plusieurs façons de représenter des formes convexes à partir de meshes triangulés. La façon la plus simple et facile est de créer un btConvexHullShape et de passer un tableau de sommets.

IV-D - Formes concaves statiques à partir de modèles 3D (Concave Triangle Meshes)

Pour des objets statiques de l'environnement, un moyen très efficace pour représenter les maillages de triangles est d'utiliser un btBvhTriangleMeshShape.

IV-E - Décomposition convexe (Convex decomposition)

Idéalement, des modèles 3D concave devraient être utilisés seulement pour représenter du décor statique. Autrement, son enveloppe convexe doit être utilisée en faisant passer le maillage à btConvexHullShape. Si une forme convexe unique n'est pas assez détaillée, des parties convexes multiples peuvent être combinées en un objet composite appelé btCompoundShape.

La décomposition convexe peut être utilisée pour décomposer le maillage concave en plusieurs parties convexes. Regarder Demos/ConvexDecompositionDemo pour une façon automatique de faire une décomposition convexe.

IV-F - Hauteur du champ (Height field)

Bullet fournit un soutien pour le cas particulier d'un terrain 2D concave par l'intermédiaire du btHeightfieldTerrainShape.
Regardez Demos/TerrainDemo pour son utilisation. Voir Figure 10.

Figure 10 : Exemple d'un hauteur de champ (texture 2D) à gauche et sa transformation en 3D à droite

IV-G - Plan infini

btStaticPlaneShape : comme son nom l'indique btStaticPlaneShape représente un plan infini ou de taille définie. Idéal pour faire un sol pour ses tests et expérimentations rapides. Cette forme se doit d'être statique.

V - Intégration dans notre application

V-A - Le header Bullet

On commence par inclure Bullet dans notre application :

Header Bullet

#include "btBulletDynamicsCommon.h"

V-B - Déclarations

Ensuite on déclare le nom du monde physique :

Monde physique

btDiscreteDynamicsWorld *myWorld;

La classe btBroadphaseInterface fournit une interface pour détecter les objets où leurs AABB se chevauchent.

Broadphase

btBroadphaseInterface *myBroadphase;

btCollisionDispatcher supporte des algorithmes qui peuvent gérer des pairs de collisions ConvexConvex et ConvexConcave. Temps de l'impact, le point le plus proche et pénétration de profondeur.

Collision Dispatcher

btCollisionDispatcher *myDispatcher;

btCollisionConfiguration permet de configurer les allocataires de mémoire.

Default Collision Configuraiton

btDefaultCollisionConfiguration *myCollisionConfiguration;

btSequentialImpulseConstraintSolver est une implémentation SIMD rapide de la méthode Projected Gauss Seidel (iterative LCP).

Sequential Impulse Constraint Solver

btSequentialImpulseConstraintSolver *mySequentialImpulseConstraintSolver;

Position, orientation.

btTransform

btTransform myTransform;

btDefaultMotionState fournit une implémentation pour synchroniser les transformations.

Motionstate

btDefaultMotionState *myMotionState,
*myMotionState_Sol;

Une matrice OpenGL, pour récupérer la position, rotation d'un objet.

Matrice OpenGL

btScalar matrix[16];

Le corps d'une boite et de notre sol.
btRigidBody est la classe principale des objets rigides

Rigid body

btRigidBody *body,
*body_sol;

V-C - Une boite OpenGL

Une fonction pour afficher une boite à l'aide d'OpenGL

Boite OpenGL

void myBox(LDEfloat x, LDEfloat y, LDEfloat z)
{
glPushMatrix();
glScalef(x,y,z);
glBegin (GL_QUADS);
//face 1
glNormal3i(-1, 1,-1);
glVertex3i(-1, 1,-1); glVertex3i( 1, 1,-1);
glVertex3i( 1,-1,-1); glVertex3i(-1,-1,-1);
//face 2
glNormal3i(-1,-1,-1);
glVertex3i(-1,-1,-1); glVertex3i( 1,-1,-1);
glVertex3i( 1,-1, 1); glVertex3i(-1,-1, 1);
// face 3
glNormal3i( 1,-1, 1);
glVertex3i( 1,-1, 1); glVertex3i( 1,-1,-1);
glVertex3i( 1, 1,-1); glVertex3i( 1, 1, 1);
//face 4
glNormal3i( 1, 1,-1);
glVertex3i( 1, 1,-1); glVertex3i(-1, 1,-1);
glVertex3i(-1, 1, 1); glVertex3i( 1, 1, 1);
//face 5
glNormal3i(-1, 1, 1);
glVertex3i(-1, 1, 1); glVertex3i(-1, 1,-1);
glVertex3i(-1,-1,-1); glVertex3i(-1,-1, 1);
//face 6
glNormal3i( 1,-1, 1);
glVertex3i( 1,-1, 1); glVertex3i( 1, 1, 1);
glVertex3i(-1, 1, 1); glVertex3i(-1,-1, 1);
glEnd();
glPopMatrix();
}

Figure 11 : Le code donne cette magnifique boite

V-D - Initialisation

Contient la configuration par défaut pour la mémoire, la collision

Configuration

myCollisionConfiguration = new btDefaultCollisionConfiguration();

Le collision dispatcher par défaut. Pour du processus parallèle utilisez un dispatcher différent. Regardez Extras/BulletMultiThreaded.

Dispatcher

myDispatcher = new    btCollisionDispatcher(myCollisionConfiguration);

Initialisation du broadphase (détecteur des objets où leurs AABB se chevauchent)

Breadphase

myBroadphase = new btDbvtBroadphase();

Constraint solver par défaut. Pour du processus parallèle utilisez un constrint solver différent. Regardez Extras/BulletMultiThreaded.

Sequential Impulse Constraint Solver

mySequentialImpulseConstraintSolver = new btSequentialImpulseConstraintSolver;

On initialise le monde physique Bullet.

Initialisation

myWorld = new btDiscreteDynamicsWorld(myDispatcher,myBroadphase,mySequentialImpulseConstraintSolver,myCollisionConfiguration);

On définit la gravité, de façon à ce que les objets tombent vers le bas (-Y).

Gravité

myWorld->setGravity( btVector3(0,-10,0) );

V-E - Création de la boite dynamique

On déclare une forme et on l'initialise en tant que boite de la taille 1,1,1 (x, y, z)

Initialisation en forme de boite (x, y, z)

btCollisionShape* shape = new btBoxShape( btVector3(1,1,1) );

On initialise notre btTransform et on lui dit une position (la position de la boite)

Position de la boite

myTransform.setIdentity();
myTransform.setOrigin( btVector3(10,5,0) );

L'inertie de la boite

L'inertie

btVector3 localInertia(0,0,0);

Le poids de la boite

Le poids

btScalar mass = 0.5f;

On calcule l'inertie suivant le poids.
Note : inutile de calculer l'inertie si la boite n'aurait pas de poids puisqu'elle deviendrait statique.

Calcul de l'inertie suivant le poids

if ( mass )
shape->calculateLocalInertia( mass, localInertia );

Il est conseillé d'utiliser motionState car il fournit des capacités d'interpolation et synchronise seulement les objets "actifs".

Motionstate

myMotionState = new btDefaultMotionState(myTransform);

On regroupe les informations de la boite à partir de la masse, l'inertie et cetera

Informations sur ce corps

btRigidBody::btRigidBodyConstructionInfo myBoxRigidBodyConstructionInfo( mass, myMotionState, shape, localInertia );

On construis le corps de la boite à partir de l'information regroupée

Initialisation du corps

body = new btRigidBody(myBoxRigidBodyConstructionInfo);

Et enfin on ajoute notre boite dans le monde Bullet

Ajout de la boite dans le monde

myWorld->addRigidBody(body);

V-F - Création du sol

La création du sol consiste à créer une boite comme indiqué au dessus mais en la mettant statique.

sol

// Forme en tant que boite
btCollisionShape* shape_sol = new btBoxShape( btVector3(10,1,10) );

myTransform.setIdentity();

// Position du sol
myTransform.setOrigin( btVector3(0,0,0) );
btVector3 localInertiaSol(0,0,0);

mass = 0; // Le fait que le poids de cet objet soit zéro le rends statique

myMotionState_Sol = new btDefaultMotionState(myTransform);

btRigidBody::btRigidBodyConstructionInfo sol_info( mass, myMotionState_Sol, shape_sol, localInertiaSol );

body_sol = new btRigidBody(sol_info);

// On ajoute le sol dans le monde Bullet
myWorld->addRigidBody(body_sol);

VI - La boucle d'affichage de l'application/jeu

VI-A - Mise à jour des transformations

Dans la boucle d'affichage on devra appeler la fonction stepSimulation pour calculer et mettre à jour les transformations des objets dynamiques.
0.001 doit être le frametime de votre application/jeu. C'est à dire, le temps que prend une frame pour tout exécuter/afficher.

sol

if ( myWorld )
myWorld->stepSimulation( 0.001 );

VI-B - Récupération des transformations et affichage des objets

On recupère la matrice OpenGL de la boite. C'est grâce à cette matrice qu'on appliquera la transformation effectuée par la librairie Bullet à l'objet qu'on affichera avec OpenGL.

sol

myMotionState->m_graphicsWorldTrans.getOpenGLMatrix( matrix );

On affiche la boite à l'aide d'OpenGL

sol

/* grâce à glPushMatrix (ouverture) et glPopMatrix (fermeture) on retiens les transformations seulement appliquées à cette boite */
glPushMatrix();
/* On applique les transformations à la boite */
glMultMatrixf( matrix );
/* Ensuite, on affiche la boite */
myBox(1,1,1);
glPopMatrix();

On affiche le sol (toujours une boite)

sol

/* On affiche le sol */
myBox(10,1,10);

VII - Conclusion

VII-A - Vidéo de ce qu'on peut arriver à faire avec un peu de motivation

Capture d'écran. Par Arkham46.

Lien : youtube : Bullet

Il est facile d'ajouter d'autres types d'objets dynamiques en initialisant btCollisionShape avec btSphereShape par exemple. Vous avez les autres formes indiquées plus haut.

VII-B - Conclusion

Bullet physics est multi plateforme, largement utilisé, gratuit pour utilisation commerciale, open source et a une belle communauté, un excellent choix.

VIII - Code source

Voici le code source complet. La gestion du fenêtrage est faite à l'aide de la SFML.

Lien : main.cpp.rar

IX - Remerciements

Merci à raptor70 sans qui cet article n'aurait pas existé ainsi que pour son aide et ses idées.
Merci à Arkham46 pour sa précieuse aide ainsi que pour l'image GIF du résultat.