I. Introduction

Havok est un « petit » moteur physique pas mal utilisé dans le domaine des jeux vidéo, bien qu'il puisse servir pour autre chose.

Comme tous les outils professionnels, tout ce qui entoure cet outil est en anglais ; le but de cet article/tuto/truc est donc de franciser un peu la chose pour faciliter l'utilisation de cette usine à gaz.


Commençons par un point particulier, la visualisation.

Havok étant un moteur physique, de base il n'y a rien de prévu pour l'affichage, normal donc, mais cela ne va pas nous aider pour voir les bêtises que l'on souhaite réaliser.

Ceci dit, les ingénieurs d'Havok n'ont pas été méchants, dans l'archive contenant le moteur, il y a deux outils de base nous permettant de voir nos réalisations :

  • le framework de démos, contenant tous les fichiers exemples et nous permettant d'en rajouter ;
  • le visual debugger, comme son nom l'indique permet de déboguer en local comme à distance nos applications.

Il est également possible de faire l'affichage nous-même avec une quelconque bibliothèque 3D (directx, opengl ou autre).

II. Comment récupérer la version gratuite d'Havok

Tout d'abord rendez-vous sur le site http://www.havok.com :

000.png

Ensuite, sur la bannière en haut, nous avons un lien « Try Havok » :

001.png

Après avoir rempli le formulaire, validé, accepté le contrat qui vous est présenté :

002.png

Il ne reste plus qu'à télécharger le fichier dont le lien a pour nom «Havok Physics and Havok Animation SDKs for Programmers (6.6.0) » le fichier fait approximativement 410Mo.

Ce SDK sera utilisé pour cet article, les exemples ici devraient fonctionner sans trop de problèmes (ni trop de modifications) avec les autres versions du SDK.

III. Configurations de Visual Studio 2008

Pour pouvoir profiter de tout ce qui est fourni dans l'archive contenant Havok, il y a deux répertoires de fichiers Include à fixer dans Visual Studio (Outils/Options/Projets et solutions/Répertoires de VC++) :

  • <répertoire où est extrait Havok>\Demo\ ;
  • <répertoire où est extrait Havok>\Source\.

Il ne nous reste maintenant plus que l'étape d'édition de liens qui se déroule en 2 étapes :

Le répertoire à fixer dans les options de la solution dépendra de la configuration de compilation choisie (attention donc à ne pas ajouter tous les répertoires, seulement celui nécessaire à la configuration courante) :

  • <répertoire où est extrait Havok>\Lib\win32_net_9-0\debug_multithreaded ;
  • <répertoire où est extrait Havok>\Lib\win32_net_9-0\debug_multithreaded_dll ;
  • <répertoire où est extrait Havok>\Lib\win32_net_9-0\hybrid_multithreaded_dll ;
  • <répertoire où est extrait Havok>\Lib\win32_net_9-0\release_multithreaded ;
  • <répertoire où est extrait Havok>\Lib\win32_net_9-0\release_multithreaded_dll.

Image non disponible

Ici <répertoire où est extrait Havok> correspondant à E:\sources\hk650r1.

La seconde étape consiste à spécifier les bibliothèques nécessaires pour l'édition de lien :

Image non disponible

Voici une liste non exhaustive des possibilités : hkBase.lib, hkSerialize.lib, hkSceneData.lib, hkInternal.lib, hkGeometryUtilities.lib, hkVisualize.lib, hkCompat.lib, hkpCollide.lib, hkpConstraintSolver.lib, hkpDynamics.lib, hkpInternal.lib, hkpUtilities.lib, hkpVehicle.lib.

IV. Initialisation du moteur physique

IV-A. Headers nécessaires

 
Sélectionnez

#include <Common/Base/hkBase.h>

Ce fichier obligatoire définit les types de base utilisés dans tout Havok, désactive les avertissements émis par le compilateur de Visual Studio et inclut tout un tas de fonctions indispensables, bref il est nécessaire.

 
Sélectionnez

#include <Common/Base/System/hkBaseSystem.h>

Celui-ci déclare la classe permettant d'initialiser le moteur physique, obligatoire.

 
Sélectionnez

#include <Common/Base/Memory/hkThreadMemory.h>
#include <Common/Base/Memory/Pool/hkPoolMemory.h>

Déclare tout ce qui concerne les allocations mémoires. Mériterait un tuto à eux tous seuls.

 
Sélectionnez

#include <Common/Base/Thread/Job/ThreadPool/Cpu/hkCpuJobThreadPool.h>
#include <Common/Base/Thread/JobQueue/hkJobQueue.h>

Permet de coordonner la simulation multithread.

Pour l'instant nous avons tout ce qu'il faut pour initialiser le moteur physique, on n'aura pas grand-chose dedans mais on aura au moins la base.

Enfin je mens, il reste ça :

 
Sélectionnez

// Keycode
#include <Common/Base/keycode.cxx>

#if !defined USING_HAVOK_PHYSICS
#error Physics is needed to build this demo. It is included in the common package for reference only.
#endif

// Classlists
#define INCLUDE_HAVOK_PHYSICS_CLASSES
#define HK_CLASSES_FILE <Common/Serialize/Classlist/hkClasses.h>
#include <Common/Serialize/Util/hkBuiltinTypeRegistry.cxx>

// Generate a custom list to trim memory requirements
#define HK_COMPAT_FILE <Common/Compat/hkCompatVersions.h>
#include <Common/Compat/hkCompat_None.cxx>

Mais bon pour être honnête cette partie est encore floue pour moi.

IV-B. Initialisation multithread

Tout d'abord une petite fonction permettant de gérer les erreurs :

 
Sélectionnez

// le premier paramètre est assez évident, le message que l'on souhaite
// afficher, et le deuxième (ici on ne s'en sert pas) est l'adresse de
// l'objet générant l'erreur
static void HK_CALL errorReport(const char* msg, void*)
{
    printf("%s", msg);
}

Initialise la base du système de Havok, et alloue un espace mémoire pour l'usage exclusif de celui-ci :

 
Sélectionnez

hkPoolMemory* memoryManager = new hkPoolMemory();
hkThreadMemory* threadMemory = new hkThreadMemory(memoryManager);
hkBaseSystem::init( memoryManager, threadMemory, errorReport );
memoryManager->removeReference();

// affecte une certaine quantité de mémoire pour la gestion interne d'Havok
char* stackBuffer;
{
    int stackSize = 0x100000;
    stackBuffer = hkAllocate<char>( stackSize, HK_MEMORY_CLASS_BASE);
    hkThreadMemory::getInstance().setStackArea( stackBuffer, stackSize);
}

Initialise la partie multithread du moteur en fonction des caractéristiques de la machine :

 
Sélectionnez

hkJobThreadPool* threadPool;

int totalNumThreadsUsed;

hkHardwareInfo hwInfo;
hkGetHardwareInfo(hwInfo);
totalNumThreadsUsed = hwInfo.m_numThreads;

hkCpuJobThreadPoolCinfo threadPoolCinfo;
threadPoolCinfo.m_numThreads = totalNumThreadsUsed - 1;

threadPoolCinfo.m_timerBufferPerThreadAllocation = 200000;
threadPool = new hkCpuJobThreadPool( threadPoolCinfo );

hkJobQueueCinfo info;
info.m_jobQueueHwSetup.m_numCpuThreads = totalNumThreadsUsed;
hkJobQueue* jobQueue = new hkJobQueue(info);

hkMonitorStream::getInstance().resize(200000);

IV-C. Configurer le « monde »

Une fois Havok initialisé, il ne nous reste plus qu'à nous amuser avec.

Pour cela nous devons créer un monde ; c'est-à-dire un conteneur pour l'ensemble des objets de la simulation.

Il va nous falloir fixer ses caractéristiques telles que ses dimensions, quoi faire des objets s'aventurant un peu trop loin, etc., tous les détails suivront l'extrait de code ci-dessous :

 
Sélectionnez

hkpWorld* physicsWorld;

// structure contenant les caractéristiques du monde
hkpWorldCinfo worldInfo;

// type de simulation
worldInfo.m_simulationType = hkpWorldCinfo::SIMULATION_TYPE_MULTITHREADED;

// comportement des objets à la limite du monde
worldInfo.m_broadPhaseBorderBehaviour = hkpWorldCinfo::BROADPHASE_BORDER_REMOVE_ENTITY;

physicsWorld = new hkpWorld(worldInfo);

// comportement des objets dont la vitesse est quasiment nulle
physicsWorld->m_wantDeactivation = false;

// début de la section critique
physicsWorld->markForWrite();

hkpAgentRegisterUtil::registerAllAgents( physicsWorld->getCollisionDispatcher() );

physicsWorld->registerWithJobQueue( jobQueue );

// fin de la section critique
physicsWorld->unmarkForWrite();

La classe hkpWorldCinfo contient tout ce dont nous avons besoin pour paramétrer notre monde. Ici je ne présenterai que les deux paramètres ci-dessous.

? m_simulationType : le type de simulation :

        - discrète, SIMULATION_TYPE_DISCRETE ;
        - continue, SIMULATION_TYPE_CONTINUOUS ;
        - multithreadée (sous entendu ET continue), SIMULATION_TYPE_MULTITHREADED.
        -> Ici notre simulation est donc multithreadée et continue.

? m_broadPhaseBorderBehaviour : comportement des objets lorsqu'ils sortent des limites de notre monde :

        - ne rien faire ;
        - émettre une exception ;
        - retirer l'objet incriminé de la simulation.
        -> Ici on retire l'objet de notre simulation.

A la suite de l'initialisation de la variable physicsWorld, la variable m_wantDeactivation est passée à false, mais à quoi correspond cette variable ? Les objets dans notre monde ont en fait un « type de mouvement », fixe, mobile ou autre. La simulation en elle-même exclut les objets fixes (gagnant de par là même du temps dans la simulation), vu qu'il ne risque pas de bouger, et c'est là qu'intervient le champ ci-dessus, quand un objet se déplace trop lentement, cette variable l'empêche d'entrer dans un état fixe. Pratique pour être sûr qu'un objet mobile le sera toujours, mais ceci empêche le moteur physique d'exclure des objets immobiles et ainsi sauver du temps de calcul.

Nous avons ensuite un appel à la méthode markForWrite(); étant dans un environnement multithread, plusieurs de ceux-ci peuvent entrer en concurrence lors de la mise à jour/consultation, il faut donc fixer une section critique.

L'appel suivant (hkpAgentRegisterUtil::registerAllAgents) « enregistre » les différents modèles de collisions.

registerWithJobQueue() permet dans le cas d'une simulation multithreadée d'enregistrer les travaux à effectuer dans une file d'attente.

Enfin la méthode unmarkForWrite(), pendant de markForWrite(), permet de libérer le verrou, ainsi tous les threads peuvent à nouveau accéder à notre objet physicsWorld.

V. Déroulement de la simulation

V-A. Ajouter des objets à notre scène

Pour l'instant notre scène est bien vide, à quoi bon être arrivé jusque-là si on ne peut rien tester.

Pour commencer, nous allons nous contenter d'objets très simples comme une sphère et des parallélépipèdes rectangles.

Notre scène sera disposée de la façon suivante :

  • un parallélépipède rectangle (en gros une boite) nous servira de sol ;
  • une autre boite ainsi qu'une sphère seront lâchées d'une certaine hauteur sur ce sol.

Tout d'abord ajoutons le sol à notre monde :

 
Sélectionnez

// pour définir une boite (alignée sur les axes), on donne un vecteur
// correspondant à une demi-diagonale.
// Ici, notre boite « mesurera » 100.0f x 2.0f x 100.0f
hkVector4 demiDiagonaleDuSol( 50.0f, 1.0f, 50.0f );

// on crée l'objet qui va définir notre boite
hkpConvexShape *solide = new hkpBoxShape( demiDiagonaleDuSol, 0.0f );

// la variable ci contient des informations à propos de notre solide physique
hkpRigidBodyCinfo ci;

// on spécifie ici que notre solide physique aura les dimensions
// de notre boite ci-dessus
ci.m_shape = solide;

// notre solide sera fixe dans notre monde
ci.m_motionType = hkpMotion::MOTION_FIXED;

// position du centre de notre solide
ci.m_position = hkVector4( 0.0f, -1.0f, 0.0f );

// spécifie « l'algo » qui sera utilisé pour tester les collisions
// avec notre solide
ci.m_qualityType = HK_COLLIDABLE_QUALITY_FIXED;

// ajoute le solide au monde et libère les objets devenus inutiles
physicsWorld->addEntity( new hkpRigidBody( ci ) )->removeReference();
solide->removeReference();

Ajouter d'autres objets dans notre simulation sera quasiment toujours identique à ce que nous venons de faire :

  • définir la forme de notre objet ;
  • remplir une instance de classe hkpRigidBodyCinfo avec entre autres la forme de notre objet ;
  • créer une instance de hkpRigidBody avec pour argument l'instance de hkpRigidBodyCinfo créée ci-dessus ;
  • ajouter l'instance de hkpRigidBody à notre monde ;
  • libérer les ressources devenues inutiles.

Trêve de papotage, ajoutons maintenant la sphère qui va choir lamentablement sur notre sol :

 
Sélectionnez

// la classe pour les propriétés de notre future sphère
hkpRigidBodyCinfo sphereInfo;

// attention c'est technique, ici on calcule le centre de gravité, ainsi qu'une
// matrice qui va indiquer comment va se comporter notre objet lors de rotations
// sur lui-même, grosso modo comment se répartit la masse de notre objet
// autour de son centre de gravité
// notre sphère pèse 100.0f et a pour rayon 1.0f
hkpMassProperties massProperties;
hkpInertiaTensorComputer::computeSphereVolumeMassProperties(1.0f, 100.0f, massProperties);

// remplissage des informations sur notre sphère
sphereInfo.m_mass = massProperties.m_mass;
sphereInfo.m_centerOfMass  = massProperties.m_centerOfMass;
sphereInfo.m_inertiaTensor = massProperties.m_inertiaTensor;
sphereInfo.m_shape = new hkpSphereShape( 1.0f );
sphereInfo.m_position = hkVector4( 0.0f, 10.0f, 0.0f );
sphereInfo.m_motionType = hkpMotion::MOTION_SPHERE_INERTIA;

// voici un nouvel « algo » de collision, celui-ci permet de ne pas louper
// de collision quand un des objets peut avoir une vitesse élevée
sphereInfo.m_qualityType = HK_COLLIDABLE_QUALITY_BULLET;

// création de notre objet avec toutes ses caractéristiques
hkpRigidBody* sphereRigidBody = new hkpRigidBody( sphereInfo );

// ajout de notre objet à la scène et libération des ressources
physicsWorld->addEntity( sphereRigidBody );
sphereRigidBody->removeReference();
sphereInfo.m_shape->removeReference();

Les explications dans le code lui-même devraient suffire pour comprendre de quoi il en retourne.

Il nous manque encore le cube qui va accompagner notre sphère dans sa chute et ainsi avoir une scène moins « vide » :

 
Sélectionnez

// quel que soit l'objet que l'on va créer, on remplit encore la classe
// hkpRigidBodyCinfo
hkpRigidBodyCinfo boxInfo;

// on renseigne la masse de notre boite
boxInfo.m_mass = 10.0f;

hkVector4 demiDiagonaleDeLaBoite( 2.0f, 2.0f, 2.0f );

hkpConvexShape *solide = new hkpBoxShape( demiDiagonaleDeLaBoite, 0.0f );

// de la même manière que pour la sphère on calcule des propriétés
// avancées pour notre boite
hkpMassProperties massProperties;
hkpInertiaTensorComputer::computeBoxVolumeMassProperties(demiDiagonaleDeLaBoite, boxInfo.m_mass, massProperties);

boxInfo.m_mass = massProperties.m_mass;
boxInfo.m_centerOfMass = massProperties.m_centerOfMass;
boxInfo.m_inertiaTensor = massProperties.m_inertiaTensor;
boxInfo.m_shape = solide;

// voici un champ qu'on n'a pas encore vu, celui-ci (compris entre 0.0 et 1.0)
// permet de calculer la quantité d'énergie qui sera conservée
// lors de la collision entre notre boite et un autre objet de la scène
boxInfo.m_restitution = 1.0f;

boxInfo.m_motionType = hkpMotion::MOTION_BOX_INERTIA;

boxInfo.m_position = hkVector4( 0.1f, 15.0f, 0.0f );
hkpRigidBody* boxRigidBody = new hkpRigidBody( boxInfo );
physicsWorld->addEntity( boxRigidBody );
boxRigidBody->removeReference();
boxInfo.m_shape->removeReference();

Voilà maintenant notre scène est complète, il ne nous reste plus qu'à l'animer et afficher tout ça.

V-B. Faire avancer notre simulation

Attention : cette section ne servira que lorsqu'on construira nous-même notre système de visualisation !

Voilà la partie que je considère comme la plus compliquée de ces « premiers pas ».

Pour avoir une belle simulation, il faut impérativement qu'entre chaque appel à notre fonction de mise à jour, il s'écoule un intervalle de temps quasiment identique.

La cause de cette obligation est due à l'imprécision des calculs qu'on effectue, entrer dans les détails serait très long et compliqué, donc je vais juste donner un morceau de code permettant de s'assurer de cette constance :

 
Sélectionnez

// on souhaiterait 60 mises à jour de notre simulation par seconde
const float fTimeStep = 1.0f/60.0f;

// à insérer dans notre fonction de rendu
// elapsedTime correspondant au temps depuis le dernier appel
// ce code « garantit » donc un appel presque régulier à la mise à
// jour de notre scène du point de vue physique
fTimeAccumulateur += float(elapsedTime);
while( fTimeAccumulateur > fTimeStep )
{
    physicsWorld->stepMultithreaded( jobQueue, threadPool, fTimeStep );
    this->m_fTimeAccumulateur -= fTimeStep;
}

V-C. Nettoyage de nos objets

Malheureusement un jour on est bien obligé de quitter des yeux notre chef-d'oeuvre et on doit bien tout nettoyer derrière nous :

 
Sélectionnez

// on pose un verrou vu qu'on va modifier notre monde
physicsWorld->markForWrite();
physicsWorld->removeReference();

delete jobQueue;

threadPool->removeReference();

threadMemory->setStackArea(0, 0);
hkDeallocate( stackBuffer );

threadMemory->removeReference();

// pendant de la fonction init
hkBaseSystem::quit();

Rien de bien compliqué là-dedans.

VI. Lien avec l'affichage

Comme je l'ai mentionné un peu plus haut, Havok étant un moteur physique, il reste à notre charge de faire l'affichage.

VI-A. Utiliser le framework de démos

Qu'est-ce que ce framework ?

En fait c'est un environnement unique qui contient l'ensemble des démos, il permet de se concentrer sur la construction des différents éléments de la simulation sans se préoccuper de leur représentation graphique :

demo_framework.png

L'avantage de celui-ci est qu'on peut l'étendre avec nos propres créations, et c'est ce que je vais vous expliquer ci-dessous.

Il faut pour commencer se créer un répertoire de travail dans l'arborescence du framework, cela va nous permettre de ne pas mélanger notre travail

avec ce qui existe déjà.

Par exemple créons un répertoire MyTestDemo dans <répertoire d'extraction>\Demo\Demos\Physics\Api\

  • Créer dans ce dossier deux fichiers : MyTestDemo.h et MyTestDemo.cpp, moins contraignant que les créer sous Visual Studio 2008.
  • Sous Visual Studio, créer un nouveau filtre dans la solution et ajouter les 2 fichiers créés précédemment.

Contenu du fichier MyTestDemo.h :

 
Sélectionnez

#ifndef HK_MyTestDemo_H
#define HK_MyTestDemo_H

#include <Demos/DemoCommon/DemoFramework/hkDefaultPhysicsDemo.h>

class MyTestDemo : public hkDefaultPhysicsDemo
{
    public:

        HK_DECLARE_CLASS_ALLOCATOR(HK_MEMORY_CLASS_DEMO);

        MyTestDemo(hkDemoEnvironment* env);
};

#endif

Voilà le contenu nécessaire pour ce fichier pour que notre démo soit utilisable dans le framework, il ne nous reste plus que le fichier cpp :

 
Sélectionnez

#include <Demos/demos.h>
#include <Demos/Physics/Api/MyTest/MyTestDemo.h>

MyTestDemo::MyTestDemo(hkDemoEnvironment* env) : hkDefaultPhysicsDemo(env)
{
    // configuration de la caméra
    {
        hkVector4 from(0.0f, 7.0f, 25.0f);
        hkVector4 to  (0.0f, 3.0f,  0.0f);
        hkVector4 up  (0.0f, 1.0f,  0.0f);
        setupDefaultCameras( env, from, to, up );
    }

    // création du monde
    {
        hkpWorldCinfo info;
        info.setupSolverInfo(hkpWorldCinfo::SOLVER_TYPE_4ITERS_MEDIUM); 
        info.setBroadPhaseWorldSize( 100.0f );
        // seuil de tolérance pour les collisions, les unités étant celles du système international
        // on obtient donc une tolérance de 4 cm
        info.m_collisionTolerance = 0.04f;
        info.m_contactRestingVelocity = 0.2f;
        m_world = new hkpWorld( info );
        m_world->lock();
		
        setupGraphics();
    }

    {
        hkpAgentRegisterUtil::registerAllAgents(m_world->getCollisionDispatcher());
    }

    // le sol
    {
        // pour définir une boite (alignée sur les axes) on donne un vecteur
        // correspondant à une demi-diagonale, donc ici, notre boite
        // « mesurera » 100.0f x 2.0f x 100.0f
        hkVector4 demiDiagonaleDuSol( 50.0f, 1.0f, 50.0f );

        // on créé l'objet qui va définir notre boite
        hkpConvexShape *solide = new hkpBoxShape( demiDiagonaleDuSol, 0.0f );

        // cet objet contient des informations à propos de notre solide physique
        hkpRigidBodyCinfo ci;

        // on spécifie ici que notre solide physique aura les dimensions
        // de notre boite ci-dessus
        ci.m_shape = solide;

        // notre solide sera fixe dans notre monde
        ci.m_motionType = hkpMotion::MOTION_FIXED;

        // position du centre de notre solide
        ci.m_position = hkVector4( 0.0f, -1.0f, 0.0f );

        // spécifie « l'algo » qui sera utilisé pour tester les collisions
        // avec notre solide
        ci.m_qualityType = HK_COLLIDABLE_QUALITY_FIXED;

        // ajoute le solide au monde et libère les objets devenus inutiles
        m_world->addEntity( new hkpRigidBody( ci ) )->removeReference();
        solide->removeReference();
    }

    // la sphère
    {
        // la classe pour les propriétés de notre future sphère
        hkpRigidBodyCinfo sphereInfo;

        // attention c'est technique, ici on calcule le centre de gravité, ainsi qu'une
        // matrice qui va indiquer comment va se comporter notre objet lors de rotations
        // sur lui-même, grosso modo comment se répartit la masse de notre objet
        // autour de son centre de gravité
        // notre sphère pèse 100.0f et a pour rayon 1.0f
        hkpMassProperties massProperties;
        hkpInertiaTensorComputer::computeSphereVolumeMassProperties(1.0f, 100.0f, massProperties);

        // remplissage des informations sur notre sphère
        sphereInfo.m_mass = massProperties.m_mass;
        sphereInfo.m_centerOfMass  = massProperties.m_centerOfMass;
        sphereInfo.m_inertiaTensor = massProperties.m_inertiaTensor;
        sphereInfo.m_shape = new hkpSphereShape( 1.0f );
        sphereInfo.m_position = hkVector4( 0.0f, 10.0f, 0.0f );
        sphereInfo.m_motionType = hkpMotion::MOTION_SPHERE_INERTIA;

        // voici un nouvel « algo » de collision, celui-ci permet de ne pas louper
        // de collision quand un des objets peut avoir une vitesse élevée
        sphereInfo.m_qualityType = HK_COLLIDABLE_QUALITY_BULLET;

        // création de notre objet avec toutes ses caractéristiques
        hkpRigidBody* sphereRigidBody = new hkpRigidBody( sphereInfo );

        // ajout de notre objet à la scène et libération des ressources
        m_world->addEntity( sphereRigidBody );
        sphereRigidBody->removeReference();
        sphereInfo.m_shape->removeReference();
    }

    // la boite
    {
        // quel que soit l'objet que l'on va créer, on remplit encore la classe
        // hkpRigidBodyCinfo
        hkpRigidBodyCinfo boxInfo;

        // on renseigne la masse de notre boite
        boxInfo.m_mass = 10.0f;

        hkVector4 demiDiagonaleDeLaBoite( 2.0f, 2.0f, 2.0f );

        hkpConvexShape *solide = new hkpBoxShape( demiDiagonaleDeLaBoite, 0.0f );

        // de la même manière que pour la sphère on calcule des propriétés
        // avancées pour notre boite
        hkpMassProperties massProperties;
        hkpInertiaTensorComputer::computeBoxVolumeMassProperties(demiDiagonaleDeLaBoite, boxInfo.m_mass, massProperties);

        boxInfo.m_mass = massProperties.m_mass;
        boxInfo.m_centerOfMass = massProperties.m_centerOfMass;
        boxInfo.m_inertiaTensor = massProperties.m_inertiaTensor;
        boxInfo.m_shape = solide;

        // voici un champ qu'on n'a pas encore vu, celui-ci (compris entre 0.0 et 1.0)
        // permet de calculer la quantité d'énergie qui sera conservée
        // lors de la collision entre notre boite et un autre objet de la scène
        boxInfo.m_restitution = 1.0f;

        boxInfo.m_motionType = hkpMotion::MOTION_BOX_INERTIA;

        boxInfo.m_position = hkVector4( 0.1f, 15.0f, 0.0f );
        hkpRigidBody* boxRigidBody = new hkpRigidBody( boxInfo );
        m_world->addEntity( boxRigidBody );
        boxRigidBody->removeReference();
        boxInfo.m_shape->removeReference();
    }

    m_world->unlock();
}

#if defined(HK_COMPILER_MWERKS)
#	pragma force_active on
#	pragma fullpath_file on
#endif

static const char helpString[] = "une simple application de test.";

HK_DECLARE_DEMO(MyTestDemo, HK_DEMO_TYPE_PRIME, "notre demo", helpString);

Résultat :

demo_framework_my_test.png
demo_framework_my_test_run.png

VI-B. Utiliser le visual debugger

Beaucoup plus difficile à utiliser que le framework, le visual debugger n'est pas à la base un outil de visualisation mais de débogage. Mais bon ça peut nous servir quand même.

Comment marche celui-ci ?

schema_reseau.png

L'application, qui peut s'exécuter sur n'importe quel support (PC, console, cafetière, etc.), envoie des informations au visual debugger au travers du réseau. Ça permet soit de déporter le débogage sur une autre machine (pour ne pas biaiser la simulation par les traitements effectués par le déboguer), seule méthode possible pour les applications sur console, soit de déboguer en local une application avec un seul et même programme.

Inconvénient de taille : il faut programmer les envois de message dans notre application, pas insurmontable mais il faut aussi prévoir tous les accès au réseau (pare-feu en tous genres, ouverture de port, etc.).

 
Sélectionnez

// tout d'abord les nouveaux headers nécessaires pour l'utilisation du visual debugger
#include <Common/Visualize/hkVisualDebugger.h>
#include <Physics/Utilities/VisualDebugger/hkpPhysicsContext.h>

[...]

// contexts va contenir la liste des éléments qui vont être monitorés
hkArray<hkProcessContext*> contexts;

// on remplit un contexte avec les éléments que l'on veut monitorer
hkpPhysicsContext* context;
{
	context = new hkpPhysicsContext();
	hkpPhysicsContext::registerAllPhysicsProcesses();
	
	// on ajoute notre joli monde physique ...
	context->addWorld(physicsWorld);
	
	// ... et on ajoute le contexte à la liste de "monitorage"
	contexts.pushBack(context);
}

// et voilà la partie qui va servir à envoyer les informations à travers le réseau
hkVisualDebugger* vdb = new hkVisualDebugger(contexts);
vdb->serve();

// !!! pendant la simulation
// envoie un pas de simulation au visual debugger, on assure au préalable que les temps entre les différents processus sont synchronisés
context->syncTimers( threadPool );
vdb->step();

// nettoyage
vdb->removeReference();
context->removeReference();

Résultat :

visual_debugger_02.png

VI-C. Construire son système de visualisation

Étant donné que le but final de ce tutoriel est d'incorporer Havok dans nos propres applications, il va falloir nous occuper de comment récupérer les informations du moteur physique pour afficher nos objets, cette dernière source illustrera comment faire.

Qu'avons-nous besoin dans nos applications pour afficher un objet ?

  • la position ;
  • l'orientation.

Nous allons donc voir comment récupérer ces informations.

 
Sélectionnez

// pose d'un verrou en lecture sur les informations de notre simulation
physicsWorld->markForRead();

hkVector4 position;
hkQuaternion orientation;

// chassisRigidBody pointeur vers notre objet de classe hkpRigidBody
position = chassisRigidBody->getPosition();
orientation = chassisRigidBody->getRotation();

// libération du verrou
physicsWorld->unmarkForRead();

Soit rien de bien compliqué, les vecteurs et quaternions d'Havok ont exactement la même gestion que les D3DXVECTOR4 et D3DXQUATERNION de DirectX.

 
Sélectionnez

// code pour l'affectation d'un hkQuaternion et d'un hkVector4
// à respectivement un D3DXQUATERNION et un D3DXVECTOR4
hkQuaternion hkQuat;
D3DXQUATERNION dxQuat( hkQuat(0), hkQuat(1), hkQuat(2), hkQuat(3) );

hkVector4 hkV4;
D3DXVECTOR4 dxV4( hkV4(0), hkV4(1), hkV4(2), hkV4(3) );

VII. Conclusion

Nous avons vu au travers ce tuto comment initialiser et commencer une scène sous Havok. Cependant on est encore loin d'exploiter toutes les possibilités du moteur, mais bon, ça sera peut-être pour un autre épisode.

VIII. Liens

IX. Remerciements

Merci à raptor70, dourouc05 et fearyourself qui m'ont aidé pour la rédaction de cet article. Merci aussi à ClaudeLELOUP pour la relecture.