Intro :
Un moteur physique dans un jeu vidéo permet de simuler les phénomènes physiques du monde réél. Il fait bouger les objets dans le jeu par des mouvements réalistes.
Ici nous allons utiliser le moteur physique Newton Dynamics pour l’intégrer dans le moteur 3D Irrlicht.
Site web de Newton Dynamics : www.newtondynamics.com
Prérequis :
Savoir lire un peu du C++.
Explications :
Mettez dans un fichier PhysicsManager.h le code suivant :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#ifndef PHYSICS_MANAGER_H #define PHYSICS_MANAGER_H #include <irrlicht.h> #include <Newton.h> #include <string> #include <iostream> #include <fstream> #define GAME_CUBE_MASS 15.0f using namespace irr; class PhysicsManager { public: PhysicsManager(); virtual ~PhysicsManager(); // Lance un cube depuis la position de la caméra void LaunchCube(scene::ISceneNode* pCubeSceneNode, const core::vector3df& direction); // Analyse un mesh et construit un objet de collision Newton correspondant NewtonCollision* CreateCollisionFromMesh(scene::IMesh* pIrrlichtMesh, const core::vector3df& v3Scale); // Créé un objet de collision Newton d'une forme englobante et correspondante avec la forme complexe du mesh passé // en paramètre NewtonCollision* CreateCollisionConvexHullFromMesh(scene::IMesh* pIrrlichtMesh); // Prépare un objet Newton Body pour la gestion physique void InitializeRigidBody(const scene::ISceneNode* pSceneNode, NewtonBody* pBody); // Retourne vrai si le fichier cache de collision Newton existe bool PhysicsManager::CollisionCacheFileExist(const std::string& sCacheFileName); /* Callbacks */ static void SetTransform(const NewtonBody* pBody, const float* matrix, int threadIndex); static void ApplyGravity(const NewtonBody* pBody, float timestep, int threadIndex); // Rafraichit le moteur physique Newton void Update(float fValue); // Ajoute un objet à traiter au moteur 3D void CreateDynamicBody(scene::IMeshSceneNode* pMeshNode, float fMass, bool bUseCache, const std::string& sCacheName); static void SerializeCollision(void* serializeHandle, const void* buffer, int size); static void DeSerializeCollision(void* serializeHandle, void* buffer, int size); private: // L'objet principale de la librairie Newton NewtonWorld* m_pNewtonWorld; }; #endif |
Mettez dans un fichier PhysicsManager.cpp le code suivant :
Cette fonction s’occupe de lancer un cube dans la direction spécifiée.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
void PhysicsManager::LaunchCube(scene::ISceneNode* pCubeSceneNode, const core::vector3df& direction) { NewtonCollision* pNewtonCollisionBox = nullptr; NewtonBody* pBody = nullptr; // Calcule la taille englobante de la scene node core::vector3df size(pCubeSceneNode->getBoundingBox().MaxEdge - pCubeSceneNode->getBoundingBox().MinEdge); // En effet un objet NewtonCollision doit être agrandi ou réduit par rapport à la véritable échelle de la SceneNode pNewtonCollisionBox = NewtonCreateBox(m_pNewtonWorld, size.X, size.Y, size.Z, 0, 0); // Créé l'objet NewtonBody const float* matrix = core::IdentityMatrix.pointer(); pBody = NewtonCreateDynamicBody(m_pNewtonWorld, pNewtonCollisionBox, matrix); // On détruit l'objet collision dont on n'a plus besoin NewtonDestroyCollision(pNewtonCollisionBox); // On lui envoit les transformations de la scène node (position, rotation). NewtonBodySetMatrix(pBody, pCubeSceneNode->getRelativeTransformation().pointer()); // On envoit un pointeur de la scène node pour pouvoir le récupérer plus tard. NewtonBodySetUserData(pBody, pCubeSceneNode); // Prépare l'objet pour le traitement physique 3D InitializeRigidBody(pCubeSceneNode, pBody); float directionArray[3]; directionArray[0] = direction.X; directionArray[1] = direction.Y; directionArray[2] = direction.Z; // On lui impulse un mouvement dans la direction directionArray NewtonBodySetVelocity(pBody, directionArray); } |
La fonction suivante s’occupe de créer un objet Newton Body complètement statique et muni d’une collision solide.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
NewtonCollision* PhysicsManager::CreateCollisionFromMesh(scene::IMesh* pIrrlichtMesh, const core::vector3df& v3Scale) { // On prépare un certain type d'objet de collision NewtonCollision* pNewtonCollision = NewtonCreateTreeCollision(m_pNewtonWorld, 0); NewtonTreeCollisionBeginBuild(pNewtonCollision); scene::IMeshBuffer* pMeshBuffer = nullptr; int index1, index2, index3; /** Faire attention au format des vertex !! **/ /** Si vous utilisez une autre déclaration de vertex, changez la ici ! **/ const video::S3DVertex2TCoords* pVertices = nullptr; const u16* pIndices = nullptr; float floatArray[9] = {0}; for (unsigned int iMeshBufferNumber = 0; iMeshBufferNumber < pIrrlichtMesh->getMeshBufferCount() ; ++iMeshBufferNumber) { pMeshBuffer = pIrrlichtMesh->getMeshBuffer(iMeshBufferNumber); pIndices = pMeshBuffer->getIndices(); pVertices = (video::S3DVertex2TCoords*) pMeshBuffer->getVertices(); for (unsigned int j = 0; j < pMeshBuffer->getIndexCount(); j += 3) { index1 = pIndices[j + 0]; index2 = pIndices[j + 1]; index3 = pIndices[j + 2]; floatArray[0] = pVertices[index1].Pos.X * v3Scale.X; floatArray[1] = pVertices[index1].Pos.Y * v3Scale.Y; floatArray[2] = pVertices[index1].Pos.Z * v3Scale.Z; floatArray[3] = pVertices[index2].Pos.X * v3Scale.X; floatArray[4] = pVertices[index2].Pos.Y * v3Scale.Y; floatArray[5] = pVertices[index2].Pos.Z * v3Scale.Z; floatArray[6] = pVertices[index3].Pos.X * v3Scale.X; floatArray[7] = pVertices[index3].Pos.Y * v3Scale.Y; floatArray[8] = pVertices[index3].Pos.Z * v3Scale.Z; NewtonTreeCollisionAddFace(pNewtonCollision, 3, floatArray, sizeof(float)*3, 0); } } // Finalise la contrustion de l'objet collision NewtonTreeCollisionEndBuild(pNewtonCollision, 1); return pNewtonCollision; } |
La fonction suivante créée un objet collision englobant la forme du mesh passé en paramètre.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// Créée un objet collision avec la forme englobante d'un mesh NewtonCollision* PhysicsManager::CreateCollisionConvexHullFromMesh(scene::IMesh* pIrrlichtMesh) { unsigned int iMeshBufferNumber = 0; scene::IMeshBuffer* pMeshBuffer = nullptr; float* afVertices = nullptr; unsigned int iTotalVertices = 0; for (iMeshBufferNumber = 0; iMeshBufferNumber < pIrrlichtMesh->getMeshBufferCount() ; ++iMeshBufferNumber) { iTotalVertices += pIrrlichtMesh->getMeshBuffer(iMeshBufferNumber)->getVertexCount(); } afVertices = new float[iTotalVertices * 3]; u32 iTmpCounter = 0; for (iMeshBufferNumber = 0 ; iMeshBufferNumber < pIrrlichtMesh->getMeshBufferCount() ; ++iMeshBufferNumber) { pMeshBuffer = pIrrlichtMesh->getMeshBuffer(iMeshBufferNumber); video::S3DVertex2TCoords* S3vertices = (video::S3DVertex2TCoords*)pMeshBuffer->getVertices(); for(unsigned int i = 0; i < pMeshBuffer->getVertexCount(); ++i) { afVertices[iTmpCounter++] = S3vertices[i].Pos.X; afVertices[iTmpCounter++] = S3vertices[i].Pos.Y; afVertices[iTmpCounter++] = S3vertices[i].Pos.Z; } } NewtonCollision* pNewtonCollision = NewtonCreateConvexHull(m_pNewtonWorld, iTotalVertices, afVertices, sizeof(float)*3, 0.1f, 0, nullptr); delete [] afVertices; return pNewtonCollision; } |
Les fonctions suivantes sont des callbacks (c’est-à-dire des fonctions appelées de manière
interne à Newton Dynamics).
La première sert à Irrlicht, elle permet de savoir comment doivent être mis en mouvement les SceneNode enregistrées.
La deuxième indique à Newton comment doivent être configuré la pesanteur.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
void PhysicsManager::SetTransform(const NewtonBody* const pBody, const float* matrix, int threadIndex) { scene::ISceneNode* pSceneNode = (scene::ISceneNode*) NewtonBodyGetUserData(pBody); if (pSceneNode) { core::matrix4 transform; transform.setM(matrix); pSceneNode->setPosition(transform.getTranslation()); pSceneNode->setRotation(transform.getRotationDegrees()); } } void PhysicsManager::ApplyGravity(const NewtonBody* pBody, float timestep, int threadIndex) { /* Cette fonction est une fonction Callback. Elle sera appelée à chaque fois qu'une modification aura lieu sur le corps. */ // On récupère en premier lieu la masse ainsi que l'inertie float masse; // Contiendra la masse de l'objet pris en paramètre par la fonction float inertieX, inertieY, inertieZ; // Contiendra l'inertie du corps float force[3]; // Spécifiera la force appliquée sur le corps NewtonBodyGetMassMatrix(pBody, &masse, &inertieX, &inertieY, &inertieZ); force[0] = 0.0f; // X force[1] = -masse * 9.81f; /// timestep; // 9.81 est l'attraction gravitationnelle de la Terre (Y) force[2] = 0.0f; // Z NewtonBodyAddForce(pBody, force); // On ajoute la force au corps } |
Cette fonction ajoute une MeshSceneNode au traitement de Newton Dynamics.
Elle fait appel à CreateCollisionFromMesh(…) pour créer l’objet de collision.
Elle s’occupe aussi de sauvegarder dans un fichier .cache les données de collisions. Cela permettra d’accélérer le calcul et le traitement de la structure de collision du Mesh si un fichier .cache à été trouvé.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
void PhysicsManager::CreateDynamicBody(scene::IMeshSceneNode* pMeshNode, float fMass, bool bUseCache, const std::string& sCacheName) { std::string sCacheFileName = ""; NewtonCollision* pNewtonCollision = nullptr; FILE* pCacheFile = nullptr; if (sCacheName == "") sCacheFileName = pMeshNode->getName(); // On utilise le nom de la scene node pour nommer le fichier cache if (sCacheName != "") sCacheFileName = sCacheName; // On utilise un cache spécifique if (!sCacheFileName.empty()) sCacheFileName += ".cache"; // On utilise le cache si le fichier existe déjà if (bUseCache && CollisionCacheFileExist(sCacheFileName)) { pCacheFile = fopen(sCacheFileName.c_str(), "rb"); // On créér un objet collision directement depuis un cache précédemment créé pNewtonCollision = NewtonCreateCollisionFromSerialization(m_pNewtonWorld, DeSerializeCollision, pCacheFile); } else { // On créér un objet collision directement depuis le ou les meshbuffers de la SceneNode pNewtonCollision = CreateCollisionFromMesh(pMeshNode->getMesh(), pMeshNode->getScale()); // On créé le cache if(!sCacheFileName.empty()) // ... si le fichier n'existe pas déjà { pCacheFile = fopen(sCacheFileName.c_str(), "wb"); // On sérialize l'objet NewtonCollision vers un fichier NewtonCollisionSerialize(m_pNewtonWorld, pNewtonCollision, SerializeCollision, pCacheFile); } } if (pCacheFile) { fclose(pCacheFile); pCacheFile = nullptr; } // On met la bonne échelle NewtonCollisionSetScale(pNewtonCollision, pMeshNode->getScale().X, pMeshNode->getScale().Y, pMeshNode->getScale().Z); const float* matrix = core::IdentityMatrix.pointer(); NewtonBody* pBody = NewtonCreateDynamicBody(m_pNewtonWorld, pNewtonCollision, matrix); NewtonDestroyCollision(pNewtonCollision); // On remet la matrice des correctes transformations core::matrix4 transformation; transformation.setTranslation(pMeshNode->getPosition()); transformation.setScale(pMeshNode->getScale()); NewtonBodySetMatrix(pBody, transformation.pointer()); // On envoit un pointeur de la scène node pour pouvoir le récupérer plus tard. NewtonBodySetUserData(pBody, pMeshNode); // On lui envoit les transformations de la scène node (position, rotation). NewtonBodySetMatrix(pBody, pMeshNode->getRelativeTransformation().pointer()); InitializeRigidBody(pMeshNode, pBody); } |
Ces fonctions suivantes indiquent à Newton Dynamics comment charger ou sauvegarder
un fichier de collision .cache.
|
1 2 3 4 5 6 7 8 9 |
void PhysicsManager::SerializeCollision(void* serializeHandle, const void* buffer, int size) { fwrite(buffer, size, 1, (FILE*)serializeHandle); } void PhysicsManager::DeSerializeCollision(void* serializeHandle, void* buffer, int size) { fread(buffer, size, 1, (FILE*)serializeHandle); } |
Voici les autres fonctions du fichier PhysicsManager.cpp :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
PhysicsManager::PhysicsManager() { m_pNewtonWorld = NewtonCreate(); NewtonSetSolverModel(m_pNewtonWorld, 1); } PhysicsManager::~PhysicsManager() { NewtonDestroyAllBodies(m_pNewtonWorld); NewtonDestroy(m_pNewtonWorld); } void PhysicsManager::Update(float fValue) { // Rafraichit le moteur Newton NewtonUpdate(m_pNewtonWorld, fValue); } |
Pour utiliser Newton dans Irrlicht, nous proposons le programme suivant :
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
#include <Irrlicht.h> #include <Newton.h> #include <Windows.h> #include <iostream> #include "PhysicsManager.h" using namespace irr; using namespace core; using namespace scene; /* Il faut intégrer la librairie d'Irrlicht dans les propriété du projet */ #pragma comment(lib, "Irrlicht.lib") /* J'ai aussi joint dans l'archive .zip plusieurs fichier .lib et .dll de la libraire de Newton */ #pragma comment(lib, "newton_d.lib") PhysicsManager *m_pPhysicsManager = nullptr; ICameraSceneNode* cam = nullptr; ISceneManager* smgr = nullptr; video::IVideoDriver* driver = nullptr; class MyEventReceiver : public IEventReceiver { public: virtual bool OnEvent(const SEvent& event) { /** Launch cube **/ if (event.KeyInput.Key == KEY_SPACE) { IMeshSceneNode* node = smgr->addCubeSceneNode(24, nullptr, -1, cam->getAbsolutePosition()); core::vector3df vect = cam->getTarget() - cam->getAbsolutePosition(); node->setMaterialTexture(0, driver->getTexture("box.jpg")); node->setMaterialFlag(video::EMF_LIGHTING, false); m_pPhysicsManager->LaunchCube(node, vect); } return false; } MyEventReceiver() { } private: }; int main() { m_pPhysicsManager = new PhysicsManager(); MyEventReceiver receiver; IrrlichtDevice *device = createDevice(video::EDT_OPENGL, dimension2d<u32>(1280, 1024), 16, false, false, false, &receiver); if (!device) return 1; device->setWindowCaption(L"Implémentation Newton Dynamics"); driver = device->getVideoDriver(); smgr = device->getSceneManager(); gui::IGUIEnvironment* guienv = device->getGUIEnvironment(); device->getFileSystem()->addZipFileArchive("map-20kdm2.pk3"); scene::IAnimatedMesh* mesh = smgr->getMesh("20kdm2.bsp"); IMeshSceneNode* node = 0; if (mesh) node = smgr->addOctreeSceneNode(mesh->getMesh(0), 0, -1, 1024); node->setPosition(core::vector3df(-1300,-144,-1249)); cam = smgr->addCameraSceneNodeFPS(); // On créé l'objet collision du niveau Quake3 pour Newton Dynamics m_pPhysicsManager->CreateDynamicBody(node, 10.0f, true, "Collision"); device->getCursorControl()->setVisible(false); while(device->run()) { driver->beginScene(true, true, video::SColor(255,100,101,140)); smgr->drawAll(); guienv->drawAll(); m_pPhysicsManager->Update(1/120.0f); driver->endScene(); } device->drop(); delete m_pPhysicsManager; return 0; } |
Résumé :
Nous avons intégrer le moteur physique Newton Dynamics dans le moteur 3D Irrlicht.


