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 :
#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.
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.
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.
// 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.
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é.
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.
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 :
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 :
#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.

