Intégration de Newton Dynamics dans Irrlicht 3D

physics

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.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *