Tutorial: Ageia PhysX & Irrlicht
by Andrew J Santa Maria
Original tutorial can be found at: http://www.twilightstar.net/~andrew/works/tutorial/irrlicht_physx/physxtutorial.htm
Basic
Integration of Ageia PhysX (formerly called Novodex) with Irrlicht
by Andrew J. "andyZER0"
Santa Maria
I'm assuming you have:
- Irrlicht
SDK 0.14.0
- Ageia
PhysX SDK 2.3.1
- Enough knowledge of C++ :) I won't spend time describing every
single line of code. (That's what comments are for right?
:) I'm hoping all the function names and variables are
self explanatory.
- Your paths set up correctly pointing to the proper PhysX and
Irrlicht headers etc.
You can download the sourcecode/binaries
used in this tutorial here.
Be aware that the binaries
provided in the tutorial rely
on .NET Framework 2.0, so you may wanna compile it
for yourself if you don't have it.
I successfully compiled
this under Microsoft
Visual C++ 2005 Express Edition. If you experience problems
with your compiler, you can email
me and I'll see what I can do :) Otherwise, the forums
are a great resource too.
This tutorial uses some existing and modified code from the PhysX
SDK 2.3.1 Training Program Beginner Lesson 101 written by Bob Schade.
This tutorial uses a Quake3 map from Mercior's "Integrating
Newton Game Dynamics with Irrlicht" tutorial.
It also uses a free wood texture from http://www.mayang.com/
Now
let's begin!
We'll be using this libraries and including these headers
#pragma
comment(lib, "PhysXLoader.lib")
#pragma comment(lib,
"NxCooking.lib")
#pragma comment(lib,
"Irrlicht.lib")
#include <NxPhysics.h>
#include <NxCooking.h>
#include <Stream.h>
#include <irrlicht.h> |
Custom
Scene Node
Using
code from the original Irrlicht engine SDK (in source.zip), we create
a new scene node similar to CAnimatedMeshSceneNode
class
CPhysXAnimatedMeshSceneNode : public IAnimatedMeshSceneNode; |
Give it some special variables
private:
// NEW STUFF
NxActor *Actor;
bool PhysXControlled; |
Along with special functions...
virtual
bool isPhysXControlled() const { return PhysXControlled;
}
virtual void setPhysXControlled(const
bool &controlled) { PhysXControlled = controlled; } |
The only difference between CPhysXAnimatedMeshSceneNode and the
original CAnimatedMeshSceneNode is the pointer to an NxActor and
a bool declaring whether or not the scenenode should be controlled
by PhysX. Thus, you can assume the only other difference is
the constructor and a modified post render function to dynamically
reposition the scenenode.
CPhysXAnimatedMeshSceneNode(IAnimatedMesh*
mesh, NxActor* actor, ISceneNode* parent, ISceneManager*
mgr, s32 id,
const core::vector3df& position =
core::vector3df(0,0,0),
const core::vector3df& rotation =
core::vector3df(0,0,0),
const core::vector3df& scale = core::vector3df(1.0f,
1.0f, 1.0f));
/***** moving along...
you can find the rest of the code in the source :) ****/
void CPhysXAnimatedMeshSceneNode::OnPreRender()
{
if (IsVisible)
{
// reorient/reposition
the scene node every frame
if (Actor &&
!Actor->isSleeping() && PhysXControlled)
{
// this code's from the Beginner Lesson 101 tutorial
/* Ageia and PhysX, both stylized and non-stylized, are
trademarks or registered trademarks
of Ageia Technologies Inc. Copyright 2006 Ageia Technologies
Inc. */
// modified to use for a irrlicht matrix4
NxMat34 pose = Actor->getGlobalPose();
const NxVec3 pos = pose.t;
const NxMat33 orient = pose.M;
core::matrix4 irrMat; // a 4x4 matrix in the irrlicht engine
orient.getColumnMajorStride4(&irrMat.M[0]);
pos.get(&irrMat.M[12]);
//clear the elements we don't need:
irrMat.M[3] = irrMat.M[7] = irrMat.M[11] = 0.0f;
irrMat.M[15] = 1.0f;
// with that newly made matrix, let's use it to transform/rotate
the node
setPosition(irrMat.getTranslation());
setRotation(irrMat.getRotationDegrees());
}
// because this node
supports rendering of mixed mode meshes consisting of
// transparent and
solid material at the same time, we need to go through all
// materials, check
of what type they are and register this node for the right
// render pass according
to that.
video::IVideoDriver*
driver = SceneManager->getVideoDriver();
PassCount = 0;
int transparentCount
= 0;
int solidCount = 0;
// count transparent
and solid materials in this scene node
for (u32 i=0; i<Materials.size();
++i)
{
video::IMaterialRenderer* rnd =
driver->getMaterialRenderer(Materials[i].MaterialType);
if (rnd && rnd->isTransparent())
++transparentCount;
else
++solidCount;
if (solidCount && transparentCount)
break;
}
// register according
to material types counted
//! but first, check
if it's in our camera's frustum before we decide we want
to register for rendering
if (SceneManager->getActiveCamera()->getViewFrustrum()->getBoundingBox().isPointInside(getPosition()))
{
if (solidCount)
SceneManager->registerNodeForRendering(this,
scene::ESNRP_SOLID);
if (transparentCount)
SceneManager->registerNodeForRendering(this,
scene::ESNRP_TRANSPARENT);
}
}
ISceneNode::OnPreRender();
if (IsVisible)
for (s32 i=0; i<(s32)JointChildSceneNodes.size();
++i)
if (JointChildSceneNodes[i])
JointChildSceneNodes[i]->OnPreRender();
} |
Then, to make things easier on ourselves, let's create a function
similar to addAnimatedMeshSceneNode.
IAnimatedMeshSceneNode*
addPhysXAnimatedMeshSceneNode(IAnimatedMesh* mesh, NxActor*
actor, ISceneNode* parent=0, s32 id=-1,
const core::vector3df& position = core::vector3df(0,0,0),
const core::vector3df& rotation = core::vector3df(0,0,0),
const core::vector3df& scale = core::vector3df(1.0f,
1.0f, 1.0f))
{
if (!mesh)
return 0;
if (!parent)
parent = smgr->getRootSceneNode();
IAnimatedMeshSceneNode* node =
new CPhysXAnimatedMeshSceneNode(mesh,
actor, parent, smgr, id, position, rotation, scale);
node->drop();
return node;
} |
Initialization
and Shuttin' Down functions
Now that we have our new, dynamic scene node class ready to use,
let's declare some variables and create some functions that that'll
make everything work together. (pretty much the use for...every
function)
//
Physics SDK globals
NxPhysicsSDK* gPhysicsSDK = NULL;
// pointer to the
SDK
NxScene*
gScene = NULL;
// pointer to the scene
NxVec3
gDefaultGravity(0,-9.81f,0); // NxVec3
representing gravity
// Force globals
NxVec3 gForceVec(0,0,0);
NxReal gForceStrength = 75000000.0f; // some force big enough
to toss them balls and boxes around
IAnimatedMesh *cubeMesh,
*sphereMesh; // pointers to the actual cube and sphere meshes
ITexture *texture0,
*texture1; // pointers to their textures
////////////////////////////////////////////////////////
// function to initialize
PhysX
bool InitNx()
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright
2006 Ageia Technologies Inc. */
// Create the physics SDK
gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION);
if (!gPhysicsSDK) return false;
// Set the physics parameters
gPhysicsSDK->setParameter(NX_SKIN_WIDTH,
0.0f); // usually 0.02
// Set the debug visualization parameters
gPhysicsSDK->setParameter(NX_VISUALIZATION_SCALE,
1);
gPhysicsSDK->setParameter(NX_VISUALIZE_COLLISION_SHAPES,
1);
gPhysicsSDK->setParameter(NX_VISUALIZE_ACTOR_AXES,
1);
// Create the scene
NxSceneDesc sceneDesc;
sceneDesc.gravity
= gDefaultGravity;
sceneDesc.broadPhase
= NX_BROADPHASE_COHERENT;
sceneDesc.collisionDetection
= true;
gScene = gPhysicsSDK->createScene(sceneDesc);
if (!gScene)
return false;
// Create the default material
NxMaterial* defaultMaterial = gScene->getMaterialFromIndex(0);
defaultMaterial->setRestitution(0.125f);
//! 0.5
defaultMaterial->setStaticFriction(0.5f);
//! 0.5
defaultMaterial->setDynamicFriction(0.5f);
//! 0.5
// Get the current time
UpdateTime();
// Start the first frame of the simulation
StartPhysics();
return true;
}
// run this to close
it
void ReleaseNx()
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright
2006 Ageia Technologies Inc. */
if (gScene)
{
//GetPhysicsResults();
// Make sure to fetchResults() before shutting down
gPhysicsSDK->releaseScene(*gScene);
}
if (gPhysicsSDK) gPhysicsSDK->release();
} |
Per
Frame Operations
Next, we create functions to execute every frame to perform
physical simulation, get
some time based movement, and what not.
NxReal
UpdateTime()
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright 2006 Ageia
Technologies Inc. */
NxReal deltaTime;
#ifndef LINUX
static __int64 gTime,gLastTime;
__int64 freq;
QueryPerformanceCounter((LARGE_INTEGER
*)&gTime); // Get current count
QueryPerformanceFrequency((LARGE_INTEGER
*)&freq); // Get processor freq
deltaTime = (double)(gTime - gLastTime)/(double)freq;
#else
static clock_t gTime, gLastTime;
gTime = clock();
deltaTime = (NxReal)((double)(gTime -
gLastTime) / 1000000.0f);
#endif
gLastTime = gTime;
return deltaTime;
}
// run this every frame
void StartPhysics()
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright
2006 Ageia Technologies Inc. */
// Update the time step
NxReal deltaTime = UpdateTime();
// Start collision and dynamics for delta
time since the last frame
gScene->simulate(deltaTime * 5.0f);
// i multiply by five for more faster, realistic looking
dynamics
gScene->flushStream();
}
// this too
void GetPhysicsResults()
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright
2006 Ageia Technologies Inc. */
// Get results from gScene->simulate(deltaTime)
while (!gScene->fetchResults(NX_RIGID_BODY_FINISHED,
false));
} |
Entity
Creation
Then, we use these functions to create either boxes or cubes to
throw around in the environment.
NxActor*
CreateBox(const core::vector3df &pos, const core::vector3df
&scale = core::vector3df(1.0f,1.0f,1.0f))
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright
2006 Ageia Technologies Inc. */
// function slightly modified to modify
dimensions/scale
// Add a single-shape actor to the scene
NxActorDesc actorDesc;
NxBodyDesc bodyDesc;
bodyDesc.angularDamping = 0.5f;
// The actor has one shape, a box, 1m
on a side
NxBoxShapeDesc boxDesc;
boxDesc.dimensions.set(scale.X,scale.Y,scale.Z);
// should be 0.5
actorDesc.shapes.pushBack(&boxDesc);
actorDesc.body = &bodyDesc;
actorDesc.density = 1.0f;
actorDesc.globalPose.t = NxVec3(pos.X,pos.Y,pos.Z);
return gScene->createActor(actorDesc);
}
NxActor* CreateSphere(const core::vector3df &pos, const
f32 &radius = 1.0f)
{
/* Ageia and PhysX, both stylized and
non-stylized, are trademarks or registered trademarks
of Ageia Technologies Inc. Copyright
2006 Ageia Technologies Inc. */
// function slightly modified to create
spheres
// Add a single-shape actor to the scene
NxActorDesc actorDesc;
NxBodyDesc bodyDesc;
bodyDesc.angularDamping = 0.5f;
// The actor has one shape, a box, 1m
on a side
NxSphereShapeDesc sphereDesc;
sphereDesc.radius = radius; // should
be 0.5
actorDesc.shapes.pushBack(&sphereDesc);
actorDesc.body = &bodyDesc;
actorDesc.density = 1.0f;
actorDesc.globalPose.t = NxVec3(pos.X,pos.Y,pos.Z);
return gScene->createActor(actorDesc);
}
|
Then, we have a function to load a Quake 3 map and properly convert
it into a PMAP to use for PhysX. This way, efficient collisions
can be performed with the entities against the actual map.
NxActor
*CreateQuake3Map(IAnimatedMesh *q3map, const vector3df &pos
= vector3df(0.0f,0.0f,0.0f), const core::vector3df &scale
= core::vector3df(1.0f,1.0f,1.0f))
{
if (!q3map)
return NULL;
// retrieve how many meshes there are
irr::s32 meshBufferCount = q3map->getMesh(0)->getMeshBufferCount();
core::array<NxVec3> vertices; //
used for allocating vertices
core::array<NxU32> indices; //
used for allocating indices
NxU32 tempIndexCount = 0; // used for
offsetting indices
for (int i = 0; i < meshBufferCount;
++i)
{
// pointer to the
map's mesh buffer
IMeshBuffer *mb =
q3map->getMesh(0)->getMeshBuffer(i); // for every
mesh buffer
s32 numVertices =
mb->getVertexCount(); // get vertex num every mesh buffer
s32 numIndices = mb->getIndexCount();
// get index num every mesh buffer
video::S3DVertex2TCoords
*mbVertices = (irr::video::S3DVertex2TCoords*)mb->getVertices();
// get pointer to vertices in the mesh buffer
irr::u16 *mbIndices
= mb->getIndices(); // get pointer to indices in the
mesh buffer
for (int j = 0; j
< numVertices; ++j) // push vertices into an array
vertices.push_back(NxVec3(mbVertices[j].Pos.X * scale.X,
mbVertices[j].Pos.Y * scale.Y, mbVertices[j].Pos.Z * scale.Z));
for (int j = 0; j
< numIndices; ++j) // push indices into an array
indices.push_back(NxU32(mbIndices[j]) + tempIndexCount);
// the q3 map when
loaded into irrlicht, is divided into multiply mesh buffers.
// we want the sum
of all mesh buffer indices. this way, when it's loaded
into physx
// it's offsetted
correctly instead of the bug i previously had where it indices
only
// pointed to the
first 400 or whatever vertices because each set of indices
pointed to only
// its own pair of
vertices in the mesh buffer.
tempIndexCount +=
numIndices;
}
NxPMap q3mapPMap;
q3mapPMap.dataSize
= 0;
q3mapPMap.data
= NULL;
NxTriangleMeshDesc mapMeshDesc; //
mesh description
// Build physical model
mapMeshDesc.numVertices = vertices.size();
mapMeshDesc.numTriangles = indices.size()
/ 3;
mapMeshDesc.pointStrideBytes = sizeof(NxVec3);
mapMeshDesc.triangleStrideBytes = 3*sizeof(NxU32);
mapMeshDesc.points = vertices.const_pointer();
mapMeshDesc.triangles = indices.const_pointer();
mapMeshDesc.flags = 0;
cout << endl << mapMeshDesc.numVertices
<< " vertices.\n";
cout << indices.size() <<
" indices.\n";
cout << mapMeshDesc.numTriangles
<< " triangles.\n";
MemoryWriteBuffer buf;
NxCookTriangleMesh(mapMeshDesc, buf);
MemoryReadBuffer readBuffer(buf.data);
NxTriangleMesh *q3mapTriangleMesh = gPhysicsSDK->createTriangleMesh(readBuffer);
// PMap stuff
// Try loading PMAP from disk
fstream file("q3map.pmap",ios::in|ios::binary|ios::ate);
if (!file.good()) // if the file doesn't
exist, we write ourselves a new PMAP
{
file.close(); // close
it. it failed :(
file.clear();// CLEAR
THEM FLAGS!! (so we can attempt opening again properly)
cout << "Please
wait while precomputing pmap...\n";
file.open("q3map.pmap",ios::out|ios::binary|ios::trunc);
if(NxCreatePMap(q3mapPMap,
*q3mapTriangleMesh, 64))
{
// The pmap has successfully been created, save it to disk
for later use
if (file.good())
{
cout << "writing data size:\t"
<< q3mapPMap.dataSize << endl;
file.write((char*)q3mapPMap.data, q3mapPMap.dataSize);
}
else
cout << "Unable to write to file.\n";
//assign pmap to mesh
q3mapTriangleMesh->loadPMap(q3mapPMap);
// sdk created data => sdk deletes it
NxReleasePMap(q3mapPMap);
}
file.close();
}
else
{
cout << "Found
pmap and using it...\n";
// Found pmap file
q3mapPMap.dataSize
= file.tellg(); //getFileSize("q3map.pmap");
file.seekg(0,ios::beg);
cout << "reading
data size:\t" << q3mapPMap.dataSize << endl;
q3mapPMap.data
= new NxU8[q3mapPMap.dataSize];
file.read((char*)q3mapPMap.data,
q3mapPMap.dataSize);//fread(q3mapPMap.data, q3mapPMap.dataSize,
1, fp);
file.close();
//assign pmap to mesh
q3mapTriangleMesh->loadPMap(q3mapPMap);
//we created data
=> we delete it
delete [] q3mapPMap.data;
}
NxTriangleMeshShapeDesc mapMeshShapeDesc;
mapMeshShapeDesc.meshData = q3mapTriangleMesh;
NxActorDesc actorDesc;
actorDesc.shapes.pushBack(&mapMeshShapeDesc);
actorDesc.globalPose.t = NxVec3(pos.X,pos.Y,pos.Z);
indices.clear();
vertices.clear();
return gScene->createActor(actorDesc);
} |
Finally, whenever we left-click, we shoot out some spheres or boxes
(depending if the variable sphereOrCube has a remainder when divided
by 2).
irr::u16
sphereOrCube = 0;
bool createShape(const
vector3df &position = camera->getPosition(), const
vector3df &target = camera->getTarget(), const bool
&applyForce = true)
{
NxActor *newActor; // pointer to newly
created object
IAnimatedMeshSceneNode *newNode; // pointer
to its scene node
core::vector3df irrDir = (target - position).normalize();
// get and normalize the direction
f32 dim = 20.0f; // the typical dimension/radius
is 20
// we're either shooting out cubes or
spheres
if (!(sphereOrCube % 2))
{
newActor = CreateBox(position,
vector3df(dim,dim,dim));
if (!newActor)
return false;
newNode = addPhysXAnimatedMeshSceneNode(cubeMesh,newActor);
newNode->setMaterialTexture(0,
texture0);
}
else
{
newActor = CreateSphere(position,dim);
if (!newActor)
return false;
newNode = addPhysXAnimatedMeshSceneNode(sphereMesh,newActor);
newNode->setMaterialTexture(0,
texture1);
}
newNode->setMaterialFlag(EMF_LIGHTING,
true);
newNode->setScale(core::vector3df(dim,dim,dim));
//newNode->addShadowVolumeSceneNode();
if (applyForce)
ApplyForceToActor(newActor,NxVec3(irrDir.X,
irrDir.Y, irrDir.Z),gForceStrength); // apply force to "shoot"
it
return true;
}
|
So that's all you really need to use PhysX in your Irrlicht applications.
The full source/binaries is included here.
This is my first tutorial, so if you need any clarification,
just email me
or message me on AIM--my screenname is dr0wsy
MANdrew. Enjoy some physics in Irrlicht :)
Ageia
and PhysX, both stylized and non-stylized, are trademarks or registered
trademarks of Ageia Technologies Inc. Copyright 2006 Ageia Technologies
Inc.
|