Getting started with Havok

matthieu foulon

nothing right now

- Titre : Getting started with Havok

- Auteur : matthieu foulon

- Parution : 26 avril 2011

- Licence : Copyright ® 2009 matthieu foulon. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 0. Introduction

- I. Download trial version of Havok

- II. Visual studio settings

- III. Physics engine initialization

- III-A. Headers required

- III-B. Initialisation multithread

- III-C. Configurer le « monde »

- IV. Step the simulation

- IV-A. Add objects to our simulation

- IV-B. Step the simulation

- IV-C. Clean up

- V. Display our simulation

- V-A. Use the framework

- V-B. Use the visual debugger

- V-C. Build your own display system

- VI. Conclusion

- VII. Liens

0 - Introduction

Havok is a physics engine often used in video games, it can be used for other simulations, not only entertainment.

/*some explanations about the fact there is only english documentation*/

First of all, i will talk about how to see the simulation's results.

Havok is a physics engine, then there is no tool to display.

But, Havok's archive contains 2 tools to help us to display and debug our samples :

- The demos framework, which contains all the samples, and can be extended.

- The visual debugger, which can help us debugging locally/on a network our applications.

By the way, we can construct our own display with all 3D libraries (directx, opengl, etc).

I - Download trial version of Havok

First go to http://www.havok.com :

Follow the link « Try Havok » :

Fill the formular, submit it, accept the license :

You have to download the file untitled « Havok Physics and Havok Animation SDKs for Programmers (6.6.0) » which is about 410Mo.

II - Visual studio settings

In order to use all Havok files, there is 2 Include directories to add in visual studio (Tools/Options/Projects and Solutions/VC++ Directories) :

- <Havok directory>\Demo

- <Havok directory>\Source

// settings window's screenshot

Here is link directory to fix, depending on which compile's profile we are using :

- <Havok directory>\Lib\win32_net_9-0\debug_multithreaded

- <Havok directory>\Lib\win32_net_9-0\debug_multithreaded_dll

- <Havok directory>\Lib\win32_net_9-0\hybrid_multithreaded_dll

- <Havok directory>\Lib\win32_net_9-0\release_multithreaded

- <Havok directory>\Lib\win32_net_9-0\release_multithreaded_dll

// idem screenshot

III - Physics engine initialization

III-A - Headers required

#include <Common/Base/hkBase.h>

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

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

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

#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 tout seul.

#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 :

// 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.

III-B - Initialisation multithread

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

// le premier paramètre est assez évident, le message que l'on souhaite
// voir affiché, 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 :

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

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 :

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);

III-C - Configurer le « monde »

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

Pour cela nous devons un monde ; c'est-à-dire quelque chose qui va contenir tous les objets dont on veut faire une simulation.

Il va nous falloir fixer ses caractéristiques telles que sa taille, parce qu'il en a une, quoi faire des objets s'aventurant un peu trop loin etc, tous les détails suivront l'extrait de code ci-dessous :

hkpWorld* physicsWorld;

hkpWorldCinfo worldInfo;

worldInfo.m_simulationType = hkpWorldCinfo::SIMULATION_TYPE_MULTITHREADED;

worldInfo.m_broadPhaseBorderBehaviour = hkpWorldCinfo::BROADPHASE_BORDER_REMOVE_ENTITY;

physicsWorld = new hkpWorld(worldInfo);

physicsWorld->m_wantDeactivation = false;

physicsWorld->markForWrite();

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

physicsWorld->registerWithJobQueue( jobQueue );

physicsWorld->unmarkForWrite();

La classe hkpWorldCinfo contient tout ce qu'il faut (normal me direz vous) pour paramétrer votre monde, ici je ne présenterai que les 2 paramètres ci-dessus.

- m_simulationType : le type de simulation

ici notre simulation est donc multithreadée et continue

- m_broadPhaseBorderBehaviour : comportement des objets lorsqu'ils sortent des limites de notre monde, entre autre 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 exclue les objets fixes (gagnant de par la même du temps dans la simulation), vu qu'il ne risque pas de bouger, et c'est là qu'intervient le champ si dessus, quand un objet se déplace trop lentement, cette variable l'empêche d'entrer dans un état fixe.

Pratique pour être sur 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.

IV - Step the simulation

IV-A - Add objects to our simulation

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 contentez 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, que je résumerai maintenant en boite, nous servira de sol

- un autre ainsi qu'une sphère seront lâchés d'une certaine hauteur sur ce sol

Tout d'abord ajoutons le sol à notre monde :

// 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
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 autre la forme de notre objet

- créer une instance de hkpRigidBody avec pour argument l'instance de hkpRigidBodyCinfo créée si 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 :

// 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éparti 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 devrait 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 » :

// quelque soit l'objet que l'on va créer, on rempli 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 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();

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

IV-B - Step the simulation

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 qu'il s'écoule un intervalle de temps quasiment identique.

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

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

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

Attention : ce morceau de code ne servira que lorsque on construira nous même notre système de visualisation !

IV-C - Clean up

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

// 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é la dedans.

V - Display our simulation

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

V-A - Use the framework

Qu'est ce que le framework ?

En fait c'est un environnement unique qui contient l'ensemble des démos :

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

- se créer un répertoire de travail dans l'arborescence du framework ;

personnellement je créé un répertoire MyTestDemo dans <répertoire d'extraction>\Demo\Demos\Physics\Api\

- créer dans ce dossier 2 fichiers : MyTestDemo.h et MyTestDemo.cpp, moins contraignant que les créer sous visual studio

- 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 :

#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 nous reste plus que le fichier cpp ; le code étant assez long, téléchargez l'archive zip (adresse à mettre)

Résultat :

V-B - Use the 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 ?

Connexion réseauApplicationVisual debugger

L'application, qui peut s'exécuter sur n'importe quel support (pc, console .), 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é par le débugger), seul 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 tout genre, ouverture de port, etc).

(code source)

Résultat :

V-C - Build your own display system

Etant 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

VI - Conclusion

VII - Liens