Tutorial: Tokamak & Irrlicht
by Matthew Couch
Code based off Irrlicht tutorial 3, Creating a Custom Scene Node
Found here: http://irrlicht.sourceforge.net/tut003.html
With tokamak information taken from Adam Dawes' cube stacking
tutorial found here:
http://www.adamdawes.com/programming/tokamak/02_Cubes.html
I will go over the entire code, however the vast majority of it
comes from one tutorial or the other. I will note which is which,
and any alterations I have made. I will also note my own code.
A very important note is that irrlicht and tokamak use the same
names for some data types. This causes all sorts of errors. Thankfully
irrlicht has been written with integration in mind, and is built
using namespaces. Tokamak, on the other had, does not. This means
that you are forced to reference the irrlich namespace rather
use the "using" keyword to simplify tasks. You do this
by placing "irr::" in front of all irrlicht specific
calls.
I'm going to concentrate on the integration stuff, and skim over
the rest, since I don't want to cover what's already been taken
care of thanks to the tutorials above.
Irr denotes Irrlicht
Tok denotes Tokamak
Includes
These come from both tutorials. The irrlicht and tokamk libraries
are self explanitary, while the others are a little strange. These
are used for the timing functions. Nothing to magical beyond that.
#include <irrlicht.h> #include <tokamak.h> #include <windows.h> #include <time.h>; |
Inclusion of the specific libraries. Again, the abbaration is
the winmm library, used for the timeGetTime() function.
#pragma comment(lib, "Irrlicht.lib")
#ifdef _DEBUG #pragma comment(lib, "tokamak_d.lib") #else #pragma comment(lib, "tokamak.lib") #endif
#pragma comment(lib, "winmm.lib")
|
Defines to make playing with the simulation easier.
PI is used to convert from Degrees to Radians.
CUBECOUNT is the number of cubes to stack
CUBEX/Y/Z are the dimentions of the cube (any size box you want)
CUBEMASS is the mass of the cube
FLOORSIZE is the size of the square "floor" that the
cubes fall on.
Play with the cube information and see what happens (to heavy,
or to small cubes seem to fall through the floor)
#define PI 3.1415926 #define CUBECOUNT 30 #define CUBEX 5.0 #define CUBEY 5.0 #define CUBEZ 5.0 #define CUBEMASS 50.0f #define FLOORSIZE 300
|
irr - We need a camera pointer to recieve the
user's input.
Take note of the namespace references.
irr::scene::ICameraSceneNode* camera = 0; |
tok - Global variables for Tokamak:
First the simulator itself.
neSimulator *gSim = NULL;
|
The cube pointer array.
neRigidBody *gCubes[CUBECOUNT]; neAnimatedBody *gFloor = NULL;
|
Timer variables.
bool gbUseHFTimer; INT64 gCurrentTime; float gfTimeScale; |
irr - the reciever to take move camera events.
class MyEventReceiver : public irr::IEventReceiver { public: virtual bool OnEvent(irr::SEvent event) { if (camera) return camera->OnEvent(event);
return false; } }; |
irr - the next two sections are directly from the irrlicht tutorial
with one major alteration. Instead of a pyramid, we'rr creating
a cube and a big, flat square.
class PhysicsCubeNode: public irr::scene::ISceneNode { irr::core::aabbox3d<irr::f32> Box; irr::video::S3DVertex Vertices[8]; irr::video::SMaterial Material;
public:
PhysicsCubeNode(irr::scene::ISceneNode* parent, irr::scene::ISceneManager* mgr, irr::s32 id) : irr::scene::ISceneNode(parent, mgr, id) { Material.Wireframe = false; Material.Lighting = false; |
Eight Vertices rather than 4. Note the centering of the cube on
the origin. This isn't to set in stone, just make sure everything
you use is centered identically, otherwise you'll get overlap.
Vertices[0] = irr::video::S3DVertex( -CUBEX/2,-CUBEY / 2,-CUBEZ/2, 1,1,0, irr::video::SColor(255,0,255,255), 0, 1); Vertices[1] = irr::video::S3DVertex( CUBEX/2,-CUBEY / 2,-CUBEZ/2, 1,0,0, irr::video::SColor(255,255,0,255), 1, 1); Vertices[2] = irr::video::S3DVertex( CUBEX/2,-CUBEY / 2, CUBEZ/2, 0,1,1, irr::video::SColor(255,255,255,0), 1, 0); Vertices[3] = irr::video::S3DVertex( -CUBEX/2,-CUBEY / 2, CUBEZ/2, 0,0,1, irr::video::SColor(255,0,255,0), 0, 0); Vertices[4] = irr::video::S3DVertex( -CUBEX/2, CUBEY / 2,-CUBEZ/2, 0,0,1, irr::video::SColor(255,0,0,255), 0, 0); Vertices[5] = irr::video::S3DVertex( CUBEX/2, CUBEY / 2,-CUBEZ/2, 0,0,1, irr::video::SColor(255,255,0,0), 0, 0); Vertices[6] = irr::video::S3DVertex( CUBEX/2, CUBEY / 2, CUBEZ/2, 0,0,1, irr::video::SColor(255,0,0,0), 0, 0); Vertices[7] = irr::video::S3DVertex( -CUBEX/2, CUBEY / 2, CUBEZ/2, 0,0,1, irr::video::SColor(255,255,255,255), 0, 0);
Box.reset(Vertices[0].Pos); for (s32 i=1; i<8; ++i) Box.addInternalPoint(Vertices[i].Pos); }
virtual void OnPreRender() { if (IsVisible) SceneManager->registerNodeForRendering(this);
ISceneNode::OnPreRender(); }
|
Here we draw the cube, 12 triangles (2 per side).
virtual void render() { irr::u16 indices[] = {0,1,2, 0,2,3, 1,6,2, 1,5,6, 0,5,1, 0,4,5, 0,3,7, 0,7,4, 2,7,3, 2,6,7, 4,6,5, 4,7,6 };
irr::video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(Material); driver->setTransform(irr::video::TS_WORLD, AbsoluteTransformation); driver->drawIndexedTriangleList(&Vertices[0], 8, &indices[0], 12); }
virtual const irr::core::aabbox3d<f32>& getBoundingBox() const { return Box; } virtual s32 getMaterialCount() { return 1; }
virtual irr::video::SMaterial& getMaterial(s32 i) { return Material; } };
|
And the floor
class PhysicsFloorNode: public irr::scene::ISceneNode { private: irr::core::aabbox3d<irr::f32> Box; irr::video::S3DVertex Vertices[4]; irr::video::SMaterial Material;
public:
PhysicsFloorNode(irr::scene::ISceneNode* parent, irr::scene::ISceneManager* mgr, irr::s32 id) : irr::scene::ISceneNode(parent, mgr, id) { Material.Wireframe = false; Material.Lighting = false;
Vertices[0] = irr::video::S3DVertex( -FLOORSIZE/2,0.0,-FLOORSIZE/2, 1,1,0, irr::video::SColor(255,0,0,0), 0, 1); Vertices[1] = irr::video::S3DVertex( FLOORSIZE/2,0.0,-FLOORSIZE/2, 1,0,0, irr::video::SColor(255,0,0,0), 1, 1); Vertices[2] = irr::video::S3DVertex( FLOORSIZE/2,0.0, FLOORSIZE/2, 0,1,1, irr::video::SColor(255,0,0,0), 1, 0); Vertices[3] = irr::video::S3DVertex( -FLOORSIZE/2,0.0, FLOORSIZE/2, 0,0,1, irr::video::SColor(255,0,0,0), 0, 0);
Box.reset(Vertices[0].Pos); for (s32 i=1; i<4; ++i) Box.addInternalPoint(Vertices[i].Pos); }
virtual void OnPreRender() { if (IsVisible) SceneManager->registerNodeForRendering(this);
ISceneNode::OnPreRender(); }
virtual void render() { irr::u16 indices[] = {0,1,2, 0,2,3, 0,2,1, 0,3,2};
irr::video::IVideoDriver* driver = SceneManager->getVideoDriver();
driver->setMaterial(Material); driver->setTransform(irr::video::TS_WORLD, AbsoluteTransformation); driver->drawIndexedTriangleList(&Vertices[0], 4, &indices[0], 4); }
virtual const irr::core::aabbox3d<f32>& getBoundingBox() const { return Box; }
virtual s32 getMaterialCount() { return 1; }
virtual irr::video::SMaterial& getMaterial(s32 i) { return Material; } };
|
tok - almost straight from the tok tutorial, we initialize the physics
engine:
bool InitPhysics(void) { neGeometry *geom; // Pointer to a Geometry object // which we'll use to define the shape/size // of each cube neV3 boxSize1; // A variable to store the length, width // and height of the cube neV3 gravity; // A vector to store the direction and //intensity of gravity neV3 pos; // The position of a cube f32 mass; // The mass of our cubes neSimulatorSizeInfo sizeInfo; // SizeInfo stores data // about how manyobjects we are going to model int i;
|
Create and initialise the simulator.
Tell the simulator how many rigid bodies we have.
sizeInfo.rigidBodiesCount = CUBECOUNT;
|
Tell the simulator how many animated bodies we have.
sizeInfo.animatedBodiesCount = 1;
|
Tell the simulator how many bodies we have in total.
s32 totalBody = sizeInfo.rigidBodiesCount + sizeInfo.animatedBodiesCount; sizeInfo.geometriesCount = totalBody;
|
The overlapped pairs count defines how many bodies it is possible
to be in collision at a single time. The SDK states this should
be calculated as:
bodies * (bodies-1) / 2
So we'll take its word for it. :-)
sizeInfo.overlappedPairsCount = totalBody * (totalBody - 1) / 2; |
We're not using any of these so set them all to zero.
sizeInfo.rigidParticleCount = 0; sizeInfo.constraintsCount = 0; sizeInfo.terrainNodesStartCount = 0; |
Set the gravity. Try changing this to see the effect on the objects.
gravity.Set(0.0f, -10.0f, 0.0f); |
Ready to go, create the simulator object.
gSim = neSimulator::CreateSimulator(sizeInfo, NULL, &gravity); |
Now we need to add some other elements to the physics engine, namely
the cubes and the floor.
Create rigid bodies for the cubes.
srand(time(0));
for (i=0; i<CUBECOUNT; i++) { // Create a rigid body gCubes[i] = gSim->CreateRigidBody();
// Add geometry to the body and set it to be a box // of dimensions 1, 1, 1 geom = gCubes[i]->AddGeometry(); boxSize1.Set(CUBEX, CUBEY, CUBEZ); geom->SetBoxSize(boxSize1[0], boxSize1[1], boxSize1[2]);
// Update the bounding info of the object -- must always call this // after changing a body's geometry. gCubes[i]->UpdateBoundingInfo();
// Set other properties of the object (mass, position, etc.) mass =CUBEMASS; gCubes[i]->SetInertiaTensor( neBoxInertiaTensor(boxSize1[0], boxSize1[1], boxSize1[2], mass)); gCubes[i]->SetMass(mass);
// Vary the position so the cubes don't all exactly // stack on top of each other // (makes for a more interesting simulation)
// MC - You may need to play with the randomization // stuff, since your cubemay be to big to // be easily destabelized pos.Set((rand()%10) / 20.0f * CUBEX, 4.0f + i * (CUBEY + 1), (rand()%10)/ 20.0f * CUBEZ); gCubes[i]->SetPos(pos); }
|
Create an animated body for the floor
gFloor = gSim->CreateAnimatedBody(); |
Add geometry to the floor and set it to be a box with size as defined
by the FLOORSIZE constant.
geom = gFloor->AddGeometry(); boxSize1.Set(FLOORSIZE, 0.0f, FLOORSIZE); geom->SetBoxSize(boxSize1[0],boxSize1[1],boxSize1[2]); gFloor->UpdateBoundingInfo();
|
Set the position of the box within the simulator.
pos.Set(0.0f, 0.0f, 0.0f); gFloor->SetPos(pos); // All done return true; } |
tok - again, cut & pasted
void KillPhysics(void) { if (gSim) { // Destroy the simulator. // Note that this will release all related // resources that we've allocated. neSimulator::DestroySimulator(gSim); gSim = NULL; } } |
tok - cut/paste.
bool InitTimer(void) { INT64 TimerFrequency;
// Can we use the high performance timer? if (QueryPerformanceFrequency( (LARGE_INTEGER*)&TimerFrequency)) { // High performance timer available, let's get ready to use it gfTimeScale = 1.0f / TimerFrequency; QueryPerformanceCounter((LARGE_INTEGER *) &gCurrentTime); gbUseHFTimer = true; return true; } else { // No high precision timer to be seen, // let's use the Windows multimedia // timer instead gfTimeScale = 0.001f; gCurrentTime = timeGetTime(); gbUseHFTimer = false; return true; }
// No timer available at all (should never happen) return false; } |
tok - cut/paste...again
float GetElapsedTime() { INT64 newTime; float fElapsed;
if (gbUseHFTimer) QueryPerformanceCounter((LARGE_INTEGER *) &newTime); else newTime=timeGetTime();
// Scale accordingly fElapsed = (float)((newTime - gCurrentTime) * gfTimeScale);
// Save the new time value for the next time we're called gCurrentTime = newTime;
return fElapsed; } |
OK, the main function, where the meat is. Here's where I had the
most fun.
int main() { //irr - variable MyEventReceiver receiver;
//tok - timer stuff float fElapsed; static float fLastElapsed; int i;
//MC - I needed a vector3df to pass to the Node movement functions irr::core::vector3df TempVect;
irr::IrrlichtDevice *device = irr::createDevice(irr::video::DT_OPENGL, irr::core::dimension2d<irr::s32>(640, 480), 16, false, false, &receiver);
irr::video::IVideoDriver* driver = device->getVideoDriver(); irr::scene::ISceneManager* smgr = device->getSceneManager();
camera = smgr->addCameraSceneNodeFPS(0, 100.0f, 100.0f);
//MC - I moved the camera so the initial view is more interesting // then turned it toward the origin, there the cubes fall camera->setPosition(irr::core::vector3df(20,30,20)); camera->setTarget(irr::core::vector3df(-20,-30,-20)); device->getCursorControl()->setVisible(false);
//MC - This is a major step, in case it's not obvious. // Initialize Tokamak InitPhysics();
//MC - Here we set up our array of cubes then create them. // Make sure you DO NOT drop() the cubes, since we need to // use the pointer to refer back to the // cubes to position them PhysicsCubeNode *CubeNode[CUBECOUNT];
for (i=0; i<CUBECOUNT;i++) { CubeNode[i] = new PhysicsCubeNode( smgr->getRootSceneNode(), smgr, 666); }
//MC - I'm one of those programmers that will fit 2 // or more commands on one line if I can. Plus, I // think this is a really cute way of calling and // dropping a node without using a variable. I use // the magic of C++ to create a new Node and drop // that same node in the same look. If I was doing // this the long way I would need a PhysicsFloorNode // pointer (new PhysicsFloorNode(smgr->getRootSceneNode(), smgr, 666))->drop();
//tok - Initialize the timer and the variable to // count the last number of milliseconds. // this makes sure there isn't a wierd jump in the program. InitTimer(); fLastElapsed = 0;
//irr - This will give us something to reference the last FPS count int lastFPS = -1; |
Now the fun begins.
while(device->run()) {
//tok - Find out how much time has elapsed // since we were last called fElapsed = GetElapsedTime();
//tok - Prevent the elapsed time from being more // than 20% greater or less than the previous // elapsed time. if (fLastElapsed != 0) { if (fElapsed > fLastElapsed * 1.2f) fElapsed = fLastElapsed * 1.2f; if (fElapsed < fLastElapsed * 0.8f) fElapsed = fLastElapsed * 0.8f; }
// Stop the elapsed time from exceeding 1/45th of a second. if (fElapsed > 1.0f / 45.0f) fElapsed = 1.0f / 45.0f; // Store the elapsed time so that we can use //it the next time around fLastElapsed = fElapsed;
gSim->Advance(fElapsed);
//MC - here where the real magic happens. // We loop through each cube; for (i=0; i<CUBECOUNT; i++) {
//First, we get the position from the Tokamak cube
neV3 p = gCubes[i]->GetPos();
//Now, we set up out temporary vector TempVect.X = p[0]; TempVect.Y = p[1]; TempVect.Z = p[2];
//And set the position of the cube that Irrlicht is going to draw CubeNode[i]->setPosition(TempVect);
//Last, we get the rotation from the Tokamak cube // This is a the rotation quaternion, I believe // More information is found here: // http://www.gamedev.net/reference/articles/article1095.asp // (Thanks to unnamed forum user) neQ q = gCubes[i]->GetRotationQ();
//Again, the temporary vector is set TempVect.Y = q.Y; TempVect.X = q.X; TempVect.Z = q.Z;
//we convert it to degrees and set the Irrlicht cube's rotation TempVect *= 180 / PI; CubeNode[i]->setRotation(TempVect); }
//irr - Now we draw it all driver->beginScene(true, true, irr::video::SColor(0,100,100,100));
smgr->drawAll();
driver->endScene();
int fps = driver->getFPS();
if (lastFPS != fps) { wchar_t tmp[1024]; swprintf(tmp, 1024, L"Physics Example- Irrlicht & Tokamak"\ L" Engines(fps:%d)", fps); device->setWindowCaption(tmp); lastFPS = fps; } }
//tok - When we're one, we kill the physics engine KillPhysics();
//MC - And drop all the node pointers that we don't need anymore for (i=0; i<CUBECOUNT;i++) CubeNode[i]->drop();
//irr - and we finish off everything by dropping the device device->drop();
return 0; }
|
|