Posts Tagged ‘Havok Visual Studio’

Havok is a physics middleware technology for fast, real-time rigid body simulation. It can be used in applications where objects need to interact realistically within a 3D space. In short, Havok is a physics engine for game development.

A physics engine simulates the motion and interaction of objects based on a physical (not graphical) description of the objects. This information may be used to generate a display that “tracks” the simulation.

A physics engine has three basic tasks to perform in its main simulation loop:

  1. Determine which objects are overlapping or about to overlap (e.g. a spaceship has collided with a hangar wall, or with another spaceship).
  2. Solve all forces acting on the objects.
  3. Having gathered up all the forces, the simulation is advanced by the simulation time step and the new state of the objects (position, orientation, velocity, acceleration etc.) is calculated.

In this post I’m not going to describe Havok and all its features. I’ve noticed the lack of basic tutorials such as the classical “Hello World!” you can write in five minutes. We have both a “Quick start guide” and a “User guide” but, in my opionion, they are not complete for this purpose (especially if you are a beginner).

We have a lot of demos“, you exalted.

Yes, you’re right, but they are not well-explained (just some comments) and only a handful of them is simple. Nothing about includes you need, libraries you have to link, compilation issues, etc. What if a poor programmer wants to write a trivial “Hello World!” from scratch?

The answer to this question is that Havok is such a challenging engine. Starting from its documentation. Stop: I’m not saying it’s bad! I’m just saying you have to toil to learn it. Havok is a professional stuff, it has been employed to realize games such as “Splinter Cell Double Agent“, “Need for Speed Undercover“, “Assassin’s Creed“, etc (see for yourself here).

Actually, I’ve taken my cue from some demos (the “Stand Alone Demos” in the “Demo” folder) and I’ve created my “Hello World!“. I’m going to be as detaild as I can, taking into account compilation and linking issues. My code works under “MS Visual Studio 2008 Professional” but recently I’ve upgraded to “Visual Studio 2010 Express” and it keeps on working.

I’m breaking this post into temporally-ordered subsections, from “Downloading Havok” to “Hey, what a cool post!“, then feel free to skip what you have already done, I won’t take offense, I swear!

Getting Havok

The first thing you have to do is to download Havok from its website: fill in the form and submit it. It’s for free, don’t worry (but read its License)! Extract all files wherever you prefer to (say C:\). You should have these folders:

  • Demo
  • Docs
  • Lib
  • Logo
  • Obj
  • Source
  • Tool

Explore them and you’ll discover no more than you imagine! Names are quite explanatory.

Hushing Visual Studio

Start Visual Studio and create a new project (for example, win32 project). Open project’s properties right-clicking on it. You’ll have to add some stuff:

  1. C/C++section:
    • General – “Additional Include Directories“: add Havok’s headers (<Havok’s Path>\Source);
    • Preprocessor – “Preprocessor Definitions“: add WIN32,_DEBUG,HK_DEBUG,_CONSOLE,HK_CONFIG_SIMD=2 ;
  2. Linker section:
    • General – “Additional Library Directories“: add Havok’s libs  (<Havok’s Path>\Lib\win32_net_9-0\debug_multithreaded_dll“);
    • Input – “Additional Dependencies“: add 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.

Are you still alive? If the answer is yes I have good news for you: you should be ready to compile and execute your first Havok physics simulation!

Bear in mind I’ve taken the stuff you added just now from the stand alone demos. That’s because Havok’s demos are provided with a solution file (it is often a godsend for Visual Studio users!).

Hello Havok World!

In spite of how tutorials are usually written, now I’m going to show you the code and next I’m going to explain it, I promise!

Why? Because you can copy and paste my code, compile and check for errors. If Visual Studio complains, it is probably due to configuration issues (then go back to “Hushing Visual Studio” and/or search the web for some sweeteners to feed your compiler!). I hope everything will go well.

Here is the code:


// Base

#include <Common/Base/hkBase.h>

#include <Common/Base/Memory/System/Util/hkMemoryInitUtil.h>

#include <Common/Base/Memory/Allocator/Malloc/hkMallocAllocator.h>

#include <cstdio>

// Physics

#include <Physics/Collide/Dispatch/hkpAgentRegisterUtil.h>

#include <Physics/Dynamics/World/hkpWorld.h>

#include <Physics/Dynamics/Entity/hkpRigidBody.h>

#include <Physics/Collide/Shape/Convex/Box/hkpBoxShape.h>

#include <Physics/Collide/Shape/Convex/Sphere/hkpSphereShape.h>

#include <Physics/Collide/Shape/Convex/Cylinder/hkpCylinderShape.h>

#include <Physics/Utilities/Dynamics/Inertia/hkpInertiaTensorComputer.h>

// Visual Debugger includes

#include <Common/Visualize/hkVisualDebugger.h>

#include <Physics/Utilities/VisualDebugger/hkpPhysicsContext.h>

// Hello Havok World: this demo demonstrates how to make a physics simulation using Havok.

// Running the demo will create a hkpWorld, with two objects of type hkpRigidBody : a floor and
// a ball bouncing on it.

// If the VisualDebugger is running whilst the demo is run, the demo will automatically

// connect to the VisualDebugger and show the scene.

// The demo runs for 20 seconds, and to show the simulation is operating, displays on the console

// the coordinates of the hkpRigidBody as it moves once every second.

// generic function for errorReport (don't care about this, just put it here!)

static void HK_CALL errorReport(const char* msg, void* userContext)

{

     using namespace std;

     printf("%s", msg);

}

// Add the floor into the world

static void addFixedSurface(hkpWorld* world, const hkVector4& position, const hkVector4& dimensions)

{

     hkReal m_fhkConvexShapeRadius = (hkReal) 0.05;

     hkpShape* fixedSurfaceShape = new hkpBoxShape(dimensions,m_fhkConvexShapeRadius);

     //create rigid body information structure

     hkpRigidBodyCinfo m_rigidBodyInfo;

     //MOTION_FIXED means static element in game scene

     m_rigidBodyInfo.m_mass = 10.0;

     m_rigidBodyInfo.m_shape = fixedSurfaceShape;

     m_rigidBodyInfo.m_motionType = hkpMotion::MOTION_FIXED;

     m_rigidBodyInfo.m_position = position;

     //create a new rigid body with supplied info

     hkpRigidBody* m_pRigidBody = new hkpRigidBody(m_rigidBodyInfo);

     //add rigid body to the world

     world->addEntity (m_pRigidBody);

     //decrease reference counter for rigid body and shape

     m_pRigidBody->removeReference();

     fixedSurfaceShape->removeReference();

}

// MAIN FUNCTION
int HK_CALL main(int argc, const char** argv)

{

     hkMallocAllocator baseMalloc;

     // Need to have memory allocated for the solver. Allocate 1mb for it.

     hkMemoryRouter* memoryRouter = hkMemoryInitUtil::initDefault(

     &baseMalloc, hkMemorySystem::FrameInfo(1024 * 1024) );

     hkBaseSystem::init( memoryRouter, errorReport );

     {

          // object for custom information about the world

          hkpWorldCinfo info;

          // e.g. set manually the gravity (try to tune this parameter!)

          info.m_gravity.set( 0,-9.8f,0);

          // use default information (try to use our "info" object)

          hkpWorld* world = new hkpWorld( hkpWorldCinfo() );

          // Register all collision agents (before adding entities, try doing the opposite!)

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

          // Bouncing sphere

          hkpRigidBody* rigidBody;

          {

              // Create a sphere with radius 2.0 m

              hkpSphereShape* sphereShape = new hkpSphereShape(2.0f);

              // Sphere information

              hkpRigidBodyCinfo bodyCinfo;

              // spherical shape

              bodyCinfo.m_shape = sphereShape;

              // start position

              bodyCinfo.m_position = hkVector4(0.0,15.0,0.0,0.0);

              //  Calculate the mass properties for the shape

              const hkReal sphereMass = 10.0f; // mass of the sphere

              hkpMassProperties massProperties; // output variable

              // Compute the inertia tensor (e.g. dynamic information)

              hkpInertiaTensorComputer::computeShapeVolumeMassProperties(

                    sphereShape, sphereMass, massProperties);

              // Update mass properties

              bodyCinfo.setMassProperties(massProperties);

              // Elasticity (value between 0 and 1.99, default = 0.4)

              bodyCinfo.m_restitution = (hkReal) 1.9;

              // Create the rigid body

              rigidBody = new hkpRigidBody(bodyCinfo);

             // No longer need the reference on the sphereShape, as the rigidBody now owns it

             sphereShape->removeReference();

          }

          // Add the rigidBody to the world

          world->addEntity(rigidBody);

          // Add a static floor

          addFixedSurface(world, hkVector4(0.0,0.0,0.0,0.0), hkVector4(30.0,1.0,30.0,1.0));

          // Register all the physics viewers

          hkpPhysicsContext::registerAllPhysicsProcesses();

          // Set up a physics context containing the world for the use in the visual debugger

          hkpPhysicsContext* context = new hkpPhysicsContext;

          context->addWorld(world);

          // Set up the visual debugger

          hkVisualDebugger* visualDebugger;

          {
             // Setup the visual debugger
             hkArray<hkProcessContext*> contexts;

             contexts.pushBack(context);

             visualDebugger = new hkVisualDebugger(contexts);

             visualDebugger->serve();

          }

          // A stopwatch (64-bit timer) for waiting until the real time has passed

          hkStopwatch stopWatch;

          stopWatch.start();

          hkReal lastTime = stopWatch.getElapsedSeconds();

          // Update as if running at 60 frames per second.

          const int numStepsPerSecond = 60;

          const hkReal timeStep = 1.0f / hkReal(numStepsPerSecond);

          // Run for 20 seconds

          for ( int i = 0; i < numStepsPerSecond * 20; ++i )

          {

            // Do a simulation step

            world->stepDeltaTime(timeStep);

            // Step the debugger

            visualDebugger->step();

            // Display (on the console) the position of the rigid body every second

            if (i % 60 == 0)

            {

                hkVector4 pos = rigidBody->getPosition();

                using namespace std;

                printf("[%f,%f,%f]\n", pos(0), pos(1), pos(2));

            }

            // Pause until the actual time has passed

            while (stopWatch.getElapsedSeconds() < lastTime + timeStep)
                  ;

            lastTime += timeStep;

        }

        // Release the visual debugger

        visualDebugger->removeReference();

        // No longer need the ref of rigidBody - as the world now owns it

        rigidBody->removeReference();

        // Release the reference on the world

        world->removeReference();

        // Contexts are not reference counted at the base class level by the VDB as

        // they are just interfaces really. So only delete the context after you have

        // finished using the VDB.

        context->removeReference();

     }
     hkBaseSystem::quit();

     hkMemoryInitUtil::quit();

     return 0;

}

// [id=keycode]

#include <Common/Base/keycode.cxx>

// [id=productfeatures]

// We're using only physics - we undef products even if the keycode is present so

// that we don't get the usual initialization for these products.

#undef HK_FEATURE_PRODUCT_AI

#undef HK_FEATURE_PRODUCT_ANIMATION

#undef HK_FEATURE_PRODUCT_CLOTH

#undef HK_FEATURE_PRODUCT_DESTRUCTION

#undef HK_FEATURE_PRODUCT_BEHAVIOR

#define HK_EXCLUDE_FEATURE_SerializeDeprecatedPre700

#define HK_EXCLUDE_FEATURE_RegisterVersionPatches

// Vdb needs the reflected classes

#define HK_EXCLUDE_FEATURE_MemoryTracker

#define HK_EXCLUDE_FEATURE_hkpAccurateInertiaTensorComputer

#define HK_EXCLUDE_FEATURE_CompoundShape

#define HK_EXCLUDE_FEATURE_hkpAabbTreeWorldManager

#define HK_EXCLUDE_FEATURE_hkpContinuousSimulation

#define HK_EXCLUDE_FEATURE_hkpKdTreeWorldManager

#include <Common/Base/Config/hkProductFeatures.cxx>

Compile and run. You should see a console writing some lines with spooky numbers. After a while it ends, closing the application.

What’s happened?!

You got it! You executed your first Havok program! You’re not thrilled, are you? I totally understand! You didn’t see anything: no airplanes, no cars darting at breakneck speed on the road, no collapsing buildings, …

Hey, hey, calm down! First, this is an “Hello World!“, not a “Goodbye World, I’m going to work for the NASA!“. Then, don’t expect nothing more a bouncing ball!

But I’m able to flavour your simulation! Let’s watch it!

Go to <Havok’s path>\Tools\VisualDebugger and execute hkVisualDebugger.exe. Next, run your application.

That’s cool! A ball is bouncing on the floor!” Is that better than numbers on a black console? It’s definitely better! You can “visual debug” you program and check for evident errors (such as “Oh no, the ball is pushing through the floor“). By the way,  it is nicer to visualize your simulation than to read numbers on the console!

Taking a trip through the code

Maybe this is the most important section of this post. If you have just arrived I take stock of the situation: we are having a party to celebrate the…hem…sorry I was wrong post! We’re talking about Havok and trying to write a complete “Hello World!

The time for code analysis has come.

Bear in mind our target: we want to simulate a ball bouncing on the floor. If we were in the real life, what would we need?

  • A ball;
  • the floor;
  • (implied) our World (equipped with gravity and motion laws).

What else?

Time. Who does not need it?! Our simulation starts at a given time (say the “instant 0”) and ends after a while.

In our digital world we have to deal with time in a particular way: remember we use a computer and a finite memory. We have to sample our simulation in steps. At every step we perform a bit of computations, obtaining all the information we need (for example, the new position of the ball). The smaller the time step taken, the more accurate the result at the end of the time step. To step forward in time by a large time step h it is better to split this into n consecutive steps of a smaller time interval h/n.

At the beginning, the bodies we embed in the World carry some relevant properties, such as their initial position, their mass, etc. Some of these stay fixed (for example, the mass of the bodies) and other change (the position of the ball).

Now we are ready to take a look at the code.

Friendly advices

Before starting, keep in mind:

  • I’m not going to talk about “includes” we have in the code (it’s clear we need some). I grouped them in sections, read my comments;
  • Lots of the code comes from the demos and I followed their using of blocks (for example, to allocate/deallocate memory on the stack when it is no more needed);
  • Havok uses reference counting, then don’t take fright at reading something like “sphereShape->removeReference()“, it’s similar to “delete sphereShape” but it doesn’t necessarily delete the object (because it can be referenced by other objects).
Havok Initialization

hkMallocAllocator baseMalloc;

hkMemoryRouter* memoryRouter = hkMemoryInitUtil::initDefault(
                               &baseMalloc, hkMemorySystem::FrameInfo(1024 * 1024) );

hkBaseSystem::init( memoryRouter, errorReport );

These instructions allow us to initialize Havok, allocating some space (1MB) for the solver (see the official guide for details). Furthermore, we need a special function to report errors (errorReport, see the code above for this function).

Call init to initialize Havok.

Populating the World

Ok, we have set Havok up. Now we have to create the World!

hkpWorld* world = new hkpWorld( hkpWorldCinfo() );

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

In the first line we created our world, passing it some standard information (try to use the “info” object and tune its parameter, for example setting the gravity to +9.8). The second line registers the collision agents (those who detect collisions and behave adequately”). Try to comment this line: the ball will nosedive like a plane, ignoring the floor!

We declare a pointer for the rigid body that represents the ball:

hkpRigidBody* rigidBody;

Our ball has a spherical shape then we create a spherical shape (with a 2-meter radius):

hkpSphereShape* sphereShape = new hkpSphereShape(2.0f);

We define the main information of our rigid body:

  • its shape (the sphere we created a moment ago);
  • its position (15 meters over the ground);
  • an elasticity coefficient (restitution acts for this purpose);
  • its mass (10 kg) and the inertia tensor computer.

hkpRigidBodyCinfo bodyCinfo;

bodyCinfo.m_shape = sphereShape;

bodyCinfo.m_position = hkVector4(0.0,15.0,0.0,0.0);

bodyCinfo.m_restitution = (hkReal) 1.9;

const hkReal sphereMass = 10.0f;

hkpMassProperties massProperties;

hkpInertiaTensorComputer::computeShapeVolumeMassProperties(sphereShape, sphereMass, massProperties);

bodyCinfo.setMassProperties(massProperties);

Finally, we are ready to create our rigid body:

rigidBody = new hkpRigidBody(bodyCinfo);
sphereShape->removeReference();

Now the rigid body is responsible of its shape (holding a pointer to it) then the sphereShape pointer has not to reference the shape object anymore. Thus, sphereShape takes away itself from referencing the shape.

world->addEntity(rigidBody);

addFixedSurface(world,hkVector4(0.0,0.0,0.0,0.0), hkVector4(30.0,1.0,30.0,1.0));

hkpPhysicsContext::registerAllPhysicsProcesses();

hkpPhysicsContext* context = new hkpPhysicsContext;

context->addWorld(world);

Let’s add our rigid body to the world! We use a static function to create the floor (the code is almost identical to those we wrote to create the ball then I’m not going to repeat it!).

Visualizing the simulation

Next we register the physics processes and create a context to store information about the world. These information are nedded by the Visual Debugger:

hkVisualDebugger* visualDebugger;

{

   hkArray<hkProcessContext*> contexts;

   contexts.pushBack(context);

   visualDebugger = new hkVisualDebugger(contexts);

   visualDebugger->serve();

}

The Visual Debugger is able to listen to more than one context then we have to add our context to an array of contexts and initialize the Visual Debugger passing this array to its contructor. Next we use the serve method to make the application behave like a server for the Visual Debugger (“if a Visual Debugger tries to connect then the application will accept it”).

Dealing with Time

You remember what I said before about the time, don’t you? We have to split the time of the simulation in steps. To be independent of the computer clock, we are going to use a timer to pause until the actual time has passed. For this purpose, Havok provides a 64bit timer: hkStopwatch. We start it and, immediately after, get the elapsed number of seconds (these should be zero).

hkStopwatch stopWatch;

stopWatch.start();

hkReal lastTime = stopWatch.getElapsedSeconds();

Suppose we want to simulate our bouncing ball at 60 frame per second. This means, we ask Havok to update our simulation 60 times every second (Havok will update the world 60 times in a second). We need to split one second in 60 steps:

const int numStepsPerSecond = 60;

const hkReal timeStep = 1.0f / hkReal(numStepsPerSecond);

to obtain the “step size” all we have to do is divide one by the frame rate (60).

Finally, we arrived to the simulation loop. A small piece of code that calls Havok’s update function and manages some stuff about timing. Let me list the logical steps we have to do:

  1. We want to simulate our ball for, say, 20 seconds. Then the total number of times we are going to call Havok’s update function is 20 by 60 (you remember 60 frame per second, don’t you?).
  2. For each step, we want two things: the first is to update Havok, the second is to send this update to the Visual Debugger.
  3. If we was too fast then we should wait for a while  (pausing our simulation), until we may simulate the next step
Finish! That’s all! Let me show you the code:
for ( int i = 0; i < numStepsPerSecond * 20; ++i )
{
   world->stepDeltaTime(timeStep);
   visualDebugger->step();

   if (i % 60 == 0)
   {
      hkVector4 pos = rigidBody->getPosition();
      using namespace std;
      printf("[%f,%f,%f]\n", pos(0), pos(1), pos(2));
   }

   while (stopWatch.getElapsedSeconds() < lastTime + timeStep)
      ;
   lastTime += timeStep;
}
We have a for loop ranging from the step 0 to the final step, that is, the number of steps per second (60) multiplied by the duration (in seconds) of our simulation. Total: 1200 steps.
Inside the loop we do a simulation step (a sort of “Havok’s update function”) through the stepDeltaTime function (passing it the step-size, that is, “how much it has to step“). Imagine your world is completely frozen, everything stands still. You can control the world (it was your dream, wasn’t it?!) and command Havok to make the time pass, a bit.

Immediately after, we make the Visual Debugger take a step too (imagine Havok sends it the information for rendering).
The next lines are trivial: every second (every time that i is divisible by 60) we write on the console the position of the ball.
The last lines are a little bit more complicated: do you remember what I said about being independent of the computer speed? Suppose we perform the first step of simulation. The variable lastTime is zero (or its value is very close to zero) and timeStep always stays the same. We can have three possibilities:
  1. We are fast and arrive too early (the time elasped is less than timeStep) to perform the next step of simulation. We have to pause until the time passed is at least as big as timeStep. Then we wait for this time (if you read closely then you’ll note that the while body is empty – just a semicolon).
  2. We are slow and, with no time to spare, we perform the next step of simulation.
  3. We are on time then we don’t need to pause!
In these cases lastTime was 0 then we just add timeStep to lastTime for the next checks. We’ll have to verify that the time elapsed is at least as big as the sum of lastTime and timeStep. It all adds up!
The image below shows the timing system: every loop we add timeStep to lastTime (starting from zero). If we arrive too early (red time-point) then we’ll have to pause until enough time passes (at least t1); otherwise, we are in late (purple time-point) then we have to perform the next step of simulation with no waiting.
Nice to have met you Havok
Ok, I’m about to finish! Final things about references and Havok shutdown you mustn’t forget:
   visualDebugger->removeReference();

   rigidBody->removeReference();

   world->removeReference();

   context->removeReference();

   }

hkBaseSystem::quit();

hkMemoryInitUtil::quit();

return 0;
}
Take a look at the official guide for a complete explanation of the last lines (undefines and other includes).

Hey, what a cool post!

Remember, I said “I’m breaking this post into temporally-ordered subsections, from ‘Downloading Havok’ to ‘Hey, what a cool post!‘ ” Now you understand the meaning of this last section 🙂

If you wonder how to get start with Havok then I hope this post can help you!

Happy physics to all of you!