(C++) Simple Bullet Physics Class/Manager

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Buck1000
Posts: 93
Joined: Sun Dec 14, 2008 8:02 pm
Location: Seattle, WA

(C++) Simple Bullet Physics Class/Manager

Post by Buck1000 »

Well, I thought I might finally contribute to the Irrlicht community by sharing some of my code :) Below is a class that I have created for my own project, and that I have been using for Bullet physics integration. Its by no means a wrapper, as it only gets the basic necessities for physics up and running. I myself use it in other functions to control my physics world.

In its original state, it required pointers to some other classes that are specific to my project, so I did a bit of editing to remove that requirement. I have however not tested the edited version, but I believe it should work fine. If there are any issues, I'll be happy to fix them :)

Now, to give credit where it is due -> I borrowed code from several sources to create this, although I have forgotten who most of them are :? Some of it is from various Bullet-Irrlicht tutorials, other pieces are either taken from, or inspired by irrBullet.

Its meant for those that want to quickly implement Bullet into their projects, but don't want a full wrapper.

So, first off, some screenies ->

A standard box stack :D
Image

The same stack after an explosion ->
Image

And, the resulting rubble after an "attraction" :D ->
Image

So, here is CPhysics.h --

Code: Select all

#ifndef _CPHYSICS_H
#define _CPHYSICS_H

#include <btBulletCollisionCommon.h>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision\CollisionDispatch\btGhostObject.h>

class CPhysics
{
    public:
    CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, btVector3 Gravity, IAnimatedMeshSceneNode* Map);
    void Update();
    bool UpdateRender(btRigidBody* TObject);
    btTriangleMesh* ConvertIrrMeshToBulletTriangleMesh(IMesh* pMesh,const vector3df& scaling);
    void QuaternionToEuler(const btQuaternion &TQuat, btVector3 &TEuler);
    void getConvexHull(IMesh *collMesh, btConvexHullShape *hullShape, IAnimatedMeshSceneNode* node);
    list<btRigidBody *> getObjectList();

    btRigidBody* CreateBox(const btVector3 &TPosition, const vector3df &TScale, btScalar TMass);
    btRigidBody* loadConvex(std::string filename, const btVector3 &TPosition, const vector3df &TScale, btScalar TMass);

    btDiscreteDynamicsWorld* getWorld();

    private:
    ISceneManager* smgr;
    IVideoDriver* driver;
    ITimer* Timer;
    btDiscreteDynamicsWorld* World;
    list<btRigidBody *> Objects;
    btVector3 Gravity;
    u32 TimeStamp;
    u32 DeltaTime;
};

#endif
And CPhysics.cpp--

Code: Select all

#include "CPhysics.h"

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, btVector3 Gravity, IAnimatedMeshSceneNode* Map)
{
   btDefaultCollisionConfiguration* CollisionConfiguration = new btDefaultCollisionConfiguration();
	btBroadphaseInterface* BroadPhase = new btAxisSweep3(btVector3(-100000, -100000, -100000), btVector3(100000, 100000, 100000));
	BroadPhase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback());
	btCollisionDispatcher* Dispatcher = new btCollisionDispatcher(CollisionConfiguration);
	btSequentialImpulseConstraintSolver* Solver = new btSequentialImpulseConstraintSolver();
	CPhysics::World = new btDiscreteDynamicsWorld(Dispatcher, BroadPhase, Solver, CollisionConfiguration);
	CPhysics::World->setGravity(Gravity);
	CPhysics::Gravity = Gravity;
	CPhysics::smgr = smgr;
	CPhysics::driver = driver;
	CPhysics::Timer = Timer;
	CPhysics::TimeStamp = Timer->getTime();
	CPhysics::DeltaTime = 0;

	btTriangleMesh* indexVertexArrays = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Map->getMesh(),Map->getMesh()->getScale());
	btBvhTriangleMeshShape* trimeshShape = new btBvhTriangleMeshShape(indexVertexArrays, true);

	btQuaternion quat;
	quat.setEulerZYX(0,0,0);
	btTransform Transform2;
	Transform2.setIdentity();
	Transform2.setOrigin(btVector3(0,0,0));
	Transform2.setRotation(quat);

	btDefaultMotionState *MotionState2 = new btDefaultMotionState(Transform2);

	btRigidBody *RigidBody = new btRigidBody(0, MotionState2, trimeshShape);

	RigidBody->setUserPointer((void *)(Map->getNode()));

	CPhysics::World->addRigidBody(RigidBody);
	CPhysics::Objects.push_back(RigidBody);

	RigidBody->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
}

void CPhysics::Update()
{
    CPhysics::DeltaTime = CPhysics::Timer->getTime() - CPhysics::TimeStamp;
    CPhysics::TimeStamp = CPhysics::Timer->getTime();
    CPhysics::World->stepSimulation(CPhysics::DeltaTime * 0.001f, 2);
    for(list<btRigidBody *>::Iterator Iterator = CPhysics::Objects.begin(); Iterator != CPhysics::Objects.end();)
        if(!CPhysics::UpdateRender(*Iterator))
        {
            Iterator = CPhysics::Objects.erase(Iterator);
        }
        else
        {
            Iterator++;
        }
}

bool CPhysics::UpdateRender(btRigidBody* TObject)
{
    ISceneNode* Node = static_cast<ISceneNode *>(TObject->getUserPointer());

    if(Node == NULL)
    {
        return false;
    }
    const btVector3& Point = TObject->getCenterOfMassPosition();
    Node->setPosition(vector3df((f32)Point[0], (f32)Point[1], (f32)Point[2]));

    btVector3 EulerRotation;
    CPhysics::QuaternionToEuler(TObject->getOrientation(), EulerRotation);
    Node->setRotation(vector3df(EulerRotation[0], EulerRotation[1], EulerRotation[2]));
    return true;
}

btRigidBody* CPhysics::CreateBox(const btVector3 &TPosition, const vector3df &TScale, btScalar TMass)
{
    ISceneNode* Node = CPhysics::smgr->addCubeSceneNode(1.0f);
	Node->setScale(TScale);
	Node->setMaterialFlag(EMF_LIGHTING, true);
	Node->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
	//Node->setMaterialTexture(0, driver->getTexture("rust0.jpg"));

	btTransform Transform;
	Transform.setIdentity();
	Transform.setOrigin(TPosition);

	btDefaultMotionState *MotionState = new btDefaultMotionState(Transform);

	btVector3 HalfExtents(TScale.X * 0.5f, TScale.Y * 0.5f, TScale.Z * 0.5f);
	btCollisionShape *Shape = new btBoxShape(HalfExtents);

	btVector3 LocalInertia;
	Shape->calculateLocalInertia(TMass, LocalInertia);

	btRigidBody *RigidBody = new btRigidBody(TMass, MotionState, Shape, LocalInertia);

	RigidBody->setUserPointer((void *)(Node));
	RigidBody->setActivationState(DISABLE_DEACTIVATION);

	CPhysics::World->addRigidBody(RigidBody);
	CPhysics::Objects.push_back(RigidBody);
	return RigidBody;
}

btTriangleMesh* CPhysics::ConvertIrrMeshToBulletTriangleMesh(IMesh* pMesh,const vector3df& scaling)
{
  btVector3 vertices[3];
  u32 i,j,k,index,numVertices,numIndices;
  u16* mb_indices;
  btTriangleMesh *pTriMesh = new btTriangleMesh();
  for (i=0; i<pMesh->getMeshBufferCount(); i++)
  {
    IMeshBuffer* mb=pMesh->getMeshBuffer(i);
    if(mb->getVertexType()==EVT_STANDARD)
    {
      S3DVertex* mb_vertices=(S3DVertex*)mb->getVertices();
      mb_indices = mb->getIndices();
      numVertices = mb->getVertexCount();
      numIndices = mb->getIndexCount();
      for(j=0;j<numIndices;j+=3)
      {
        for (k=0;k<3;k++)
        {
          index = mb_indices[j+k];
          vertices[k] = btVector3(mb_vertices[index].Pos.X*scaling.X, mb_vertices[index].Pos.Y*scaling.Y, mb_vertices[index].Pos.Z*scaling.Z);
        }
        pTriMesh->addTriangle(vertices[0], vertices[1], vertices[2]);
      }
    }
    else if(mb->getVertexType()==EVT_2TCOORDS)
    {
      S3DVertex2TCoords* mb_vertices=(S3DVertex2TCoords*)mb->getVertices();
      mb_indices = mb->getIndices();
      numVertices = mb->getVertexCount();
      numIndices = mb->getIndexCount();
      for(j=0;j<numIndices;j+=3)
      {
        for (k=0;k<3;k++)
        {
          index = mb_indices[j+k];
          vertices[k] = btVector3(mb_vertices[index].Pos.X*scaling.X, mb_vertices[index].Pos.Y*scaling.Y, mb_vertices[index].Pos.Z*scaling.Z);
        }
        pTriMesh->addTriangle(vertices[0], vertices[1], vertices[2]);
      }
    }
  }
  return pTriMesh;
};

void CPhysics::QuaternionToEuler(const btQuaternion &TQuat, btVector3 &TEuler)
{
	btScalar W = TQuat.getW();
	btScalar X = TQuat.getX();
	btScalar Y = TQuat.getY();
	btScalar Z = TQuat.getZ();
	float WSquared = W * W;
	float XSquared = X * X;
	float YSquared = Y * Y;
	float ZSquared = Z * Z;
	TEuler.setX(atan2f(2.0f * (Y * Z + X * W), -XSquared - YSquared + ZSquared + WSquared));
	TEuler.setY(asinf(-2.0f * (X * Z - Y * W)));
	TEuler.setZ(atan2f(2.0f * (X * Y + Z * W), XSquared - YSquared - ZSquared + WSquared));
	TEuler *= RADTODEG;
};

btRigidBody* CPhysics::loadConvex(std::string filename, const btVector3 &TPosition, const vector3df &TScale, btScalar TMass)
{
    IAnimatedMeshSceneNode* Node = CPhysics::smgr->addAnimatedMeshSceneNode((IAnimatedMesh*)CPhysics::smgr->getMesh(filename.c_str()));
    btTriangleMesh* trimesh = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Node->getMesh(), Node->getScale());

    btConvexShape* hull = new btConvexTriangleMeshShape(trimesh);
    hull->setUserPointer(hull);

    btVector3 localInertia(0,0,0);
    hull->calculateLocalInertia(TMass, localInertia);

	btQuaternion quat;
	quat.setEulerZYX(Node->getRotation().X,Node->getRotation().Y,Node->getRotation().Z);
	btTransform Transform2;
	Transform2.setIdentity();
	Transform2.setOrigin(TPosition);
	Transform2.setRotation(quat);

	btDefaultMotionState *MotionState2 = new btDefaultMotionState(Transform2);

	btRigidBody* RigidBody = new btRigidBody(TMass, MotionState2, hull, localInertia);

	RigidBody->setUserPointer((void *)(Node));
	RigidBody->setActivationState(DISABLE_DEACTIVATION);

	CPhysics::World->addRigidBody(RigidBody);
	CPhysics::Objects.push_back(RigidBody);

	return RigidBody;
}

list<btRigidBody *> CPhysics::getObjectList()
{
    return CPhysics::Objects;
}

btDiscreteDynamicsWorld* CPhysics::getWorld()
{
    return CPhysics::World;
}
So, to use this class, add

Code: Select all

CPhysics* Physics = new CPhysics(smgr, driver, Timer, btVector3(0, -500, 0), MapNode);
Somewhere before your main loop. Here, MapNode is a pointer to your Level geometry scene node (in this case, its an AnimatedMeshSceneNode, but you can change that to whatever type your Level is)

After that, simply call

Code: Select all

Physics->Update();
At the start of your Game Loop.

If you also want the debug drawer, then include DebugDraw.h -

Code: Select all

#ifndef _DEBUGDRAW_H
#define _DEBUGDRAW_H

enum DebugDrawModes
{
    DBG_NoDebug=0,
    DBG_DrawWireframe = 1,
    DBG_DrawAabb=2,
    DBG_DrawFeaturesText=4,
    DBG_DrawContactPoints=8,
    DBG_NoDeactivation=16,
    DBG_NoHelpText = 32,
    DBG_DrawText=64,
    DBG_ProfileTimings = 128,
    DBG_EnableSatComparison = 256,
    DBG_DisableBulletLCP = 512,
    DBG_EnableCCD = 1024,
    DBG_MAX_DEBUG_DRAW_MODE
};

//Courtesy of randomMESH
class DebugDraw : public btIDebugDraw
{

public:

   DebugDraw(IrrlichtDevice* const device) :
      mode(DBG_NoDebug), driver(device->getVideoDriver()), logger(device->getLogger())
   {

   }

   void drawLine(const btVector3& from, const btVector3& to, const btVector3& color)
   {
      SColor newColor(255, (u32)color[0], (u32)color[1], (u32)color[2]);
      if (color[0] <= 1.0 && color[0] > 0.0)
         newColor.setRed((u32)(color[0]*255.0));
      if (color[1] <= 1.0 && color[1] > 0.0)
         newColor.setGreen((u32)(color[1]*255.0));
      if (color[2] <= 1.0 && color[2] > 0.0)
         newColor.setBlue((u32)(color[2]*255.0));

      this->driver->draw3DLine(vector3df(from[0], from[1], from[2]), vector3df(to[0], to[1], to[2]), newColor);
   }

   void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color)
   {
      static const SColor CONTACTPOINT_COLOR(255, 255, 255, 0); 
      const btVector3 to(PointOnB + normalOnB*distance);
      this->driver->draw3DLine(vector3df(PointOnB[0], PointOnB[1], PointOnB[2]),vector3df(to[0], to[1], to[2]),CONTACTPOINT_COLOR);
   }

   void reportErrorWarning(const char* text)
   {
      this->logger->log(text, irr::ELL_ERROR);
   }

   void draw3dText(const btVector3& location, const char* text) { }
   void setDebugMode(int mode) { this->mode = mode; }
   int getDebugMode() const { return this->mode; }

private:

   int mode;
   IVideoDriver* const driver;
   ILogger* logger;
};

#endif
Add the following after creating the physics world -

Code: Select all

	DebugDraw* Debug = new DebugDraw(device);
	Physics->getWorld()->setDebugDrawer(Debug);
	Debug->setDebugMode(DBG_DrawAabb | DBG_DrawWireframe);
Add this before your Game Loop -

Code: Select all

    SMaterial debugMat;
    debugMat.Lighting = false;
And, finally, add this before your call to driver->endScene() -

Code: Select all

        driver->setMaterial(debugMat);
        driver->setTransform(ETS_WORLD, IdentityMatrix);
        Physics->getWorld()->debugDrawWorld();
For a simple test, here is the code to create the stack of boxes as shown in the screenie -

Code: Select all

	int xshift = 0;
	int yshift = 0;
	for(yshift=0;yshift<=7;yshift++)
		for(xshift=0;xshift<=7;xshift++)
		{
			btRigidBody* DummyRigidBody = Physics->CreateBox(btVector3((100+(50*xshift)),10+(50*yshift),1500),vector3df(50,50,50),40);
		};
Any comments/critiques are welcome :D
Enjoy!
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

I am really thankful to you .
Will try to integrate into my game Image
Definately worth atention Image

Edit: And here comes the ugly errors :cry:

Code: Select all

error C2719: 'Gravity': formal parameter with __declspec(align('16')) won't be aligned
 error C2719: 'Gravity': formal parameter with __declspec(align('16')) won't be aligned
: error C2039: 'getNode' : is not a member of 'irr::scene::IAnimatedMeshSceneNode'
Anyone knows what could the first two mean?
Working on game: Marrbles (Currently stopped).
shadowslair
Posts: 758
Joined: Mon Mar 31, 2008 3:32 pm
Location: Bulgaria

Post by shadowslair »

For the first two I`d try googling it without the keywords specific for your app. This is what I usually do for errors I don`t know.

For the second you may try removing the "getNode" call. Maybe (Map->getNode()) may become (Map) or sth alike.
"Although we walk on the ground and step in the mud... our dreams and endeavors reach the immense skies..."
Buck1000
Posts: 93
Joined: Sun Dec 14, 2008 8:02 pm
Location: Seattle, WA

Post by Buck1000 »

Ah, yes, sorry about that, I forgot some code specific to my project in there :wink:

In the constructor -

Code: Select all

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, btVector3 Gravity, IAnimatedMeshSceneNode* Map) 
There is code to automatically generate the map physics mesh, from an Animated Scene Node. If you look closely, down where the btRigidBody's user pointer is assigned, is where that call is being made (getNode). Just remove the ->getNode() part.

So, where you see this -

Code: Select all

RigidBody->setUserPointer((void *)(Map->getNode())); 
Change it too this -

Code: Select all

RigidBody->setUserPointer((void *)(Map)); 
And it should work :) I'm glad that someone found a use for it! If you have any more trouble, I'll be glad to help.

As for the first problem, concerning "Gravity", I have no idea. A bit of googling shows that the error might be specific to an MS compiler. You can keep on trying to find a solution, but if you can't, you can always replace it with a float, instead of a btVector.

In CPhysics.h, change

Code: Select all

 btVector3 Gravity;
to

Code: Select all

float Gravity;
and

Code: Select all

CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, btVector3 Gravity, IAnimatedMeshSceneNode* Map); 
to

Code: Select all

CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, float Gravity, IAnimatedMeshSceneNode* Map); 
and, in CPhysics.cpp, change the constructor too

Code: Select all

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, float Gravity, IAnimatedMeshSceneNode* Map) 
{ 
   btDefaultCollisionConfiguration* CollisionConfiguration = new btDefaultCollisionConfiguration(); 
   btBroadphaseInterface* BroadPhase = new btAxisSweep3(btVector3(-100000, -100000, -100000), btVector3(100000, 100000, 100000)); 
   BroadPhase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback()); 
   btCollisionDispatcher* Dispatcher = new btCollisionDispatcher(CollisionConfiguration); 
   btSequentialImpulseConstraintSolver* Solver = new btSequentialImpulseConstraintSolver(); 
   CPhysics::World = new btDiscreteDynamicsWorld(Dispatcher, BroadPhase, Solver, CollisionConfiguration); 
   CPhysics::World->setGravity(btVector3(0,Gravity,0); 
   CPhysics::Gravity = Gravity; 
   CPhysics::smgr = smgr; 
   CPhysics::driver = driver; 
   CPhysics::Timer = Timer; 
   CPhysics::TimeStamp = Timer->getTime(); 
   CPhysics::DeltaTime = 0; 

   btTriangleMesh* indexVertexArrays = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Map->getMesh(),Map->getMesh()->getScale()); 
   btBvhTriangleMeshShape* trimeshShape = new btBvhTriangleMeshShape(indexVertexArrays, true); 

   btQuaternion quat; 
   quat.setEulerZYX(0,0,0); 
   btTransform Transform2; 
   Transform2.setIdentity(); 
   Transform2.setOrigin(btVector3(0,0,0)); 
   Transform2.setRotation(quat); 

   btDefaultMotionState *MotionState2 = new btDefaultMotionState(Transform2); 

   btRigidBody *RigidBody = new btRigidBody(0, MotionState2, trimeshShape); 

   RigidBody->setUserPointer((void *)(Map->getNode())); 

   CPhysics::World->addRigidBody(RigidBody); 
   CPhysics::Objects.push_back(RigidBody); 

   RigidBody->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT); 
}
Gravity in this case should be a negative value. I personally use -500 :)

I haven't tested that out, but it should work.
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

I think that this code

Code: Select all

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, btVector3 Gravity, IAnimatedMeshSceneNode* Map) 
Should be like this

Code: Select all

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, btVector3 & Gravity, IAnimatedMeshSceneNode* Map) 

Everything is okay now
Just need to make function that would destroy everything :]

Edit:
Any advice on how to remove whole physics stuff and irrlicht nodes that are used by physics?
Edit2: After I Destroy every single thing including the physics class it still leaks about 1,000K
Working on game: Marrbles (Currently stopped).
Buck1000
Posts: 93
Joined: Sun Dec 14, 2008 8:02 pm
Location: Seattle, WA

Post by Buck1000 »

Sorry for not replying quickly, I haven't bee online for long periods of time in awhile. Anyways, heres the code I use:

Code: Select all

void CPhysics::remove()
{
    for(list<btRigidBody *>::Iterator Iterator = CPhysics::Objects.begin(); Iterator != CPhysics::Objects.end();)
    {
        CPhysics::World->removeRigidBody((*Iterator));
        if((*Iterator)->getUserPointer()!=NULL)
            static_cast<ISceneNode *>((*Iterator)->getUserPointer())->remove();
        Iterator = CPhysics::Objects.erase(Iterator);
    }
    CPhysics::World->~btDiscreteDynamicsWorld();

    if(DEBUG_CONSOLE)
        cout<<"Cleaned Physics World"<<endl;
}
It works fine, but it may still have memory leaks, as I don't know how to check for those. Tell me what you think.
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

Thanks for your reply, tough I already found what I needed :wink:
And still I may need help in future :lol:
Working on game: Marrbles (Currently stopped).
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

the memory leak sits in the physics constructor

Code: Select all

 btTriangleMesh* indexVertexArrays = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Map->getMesh(),Map->getMesh()->getScale()); 
indexVertexArrays is new'ed so it needs to be deleted but since pointer is declared in the constructor it gets lost :wink:
Working on game: Marrbles (Currently stopped).
Garfinkle
Posts: 36
Joined: Wed Jul 07, 2010 11:35 am
Location: Manchester, UK

Post by Garfinkle »

I've done a convert of this code (as i think it is really helpful :D) for the iPhone as the irrBullet system doesn't work and there doesn't seem to be any information on getting physics working on the iPhone.

Note: You may need to make a slight change to the Bullet includes depending on how you have included them into your project

e.g. changing

#include "Bullet-C-Api.h"
to
#include "Bullet/Bullet-C-Api.h"

etc.




Here is the code.

CPhysics.h


Code: Select all

#ifndef __CPHYSICS_H__
#define __CPHYSICS_H__

#include <string.h>
#include "irrlicht.h"
#include "Bullet-C-Api.h"
#include "btBulletCollisionCommon.h"
#include "btBulletDynamicsCommon.h"
#include "BulletCollision/CollisionDispatch/btGhostObject.h"

using namespace irr;
using namespace core;
using namespace scene;
using namespace video;

class CPhysics
{
public:
    CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, float Gravity, IAnimatedMeshSceneNode* Map);
    void Update();
    bool UpdateRender(btRigidBody* TObject);
    btTriangleMesh* ConvertIrrMeshToBulletTriangleMesh(IAnimatedMesh* pMesh,const vector3df &scaling);
    void QuaternionToEuler(const btQuaternion &TQuat, btVector3 &TEuler);
    void getConvexHull(IMesh *collMesh, btConvexHullShape *hullShape, IAnimatedMeshSceneNode* node);
    list<btRigidBody *> getObjectList();
	void Remove();
	
    btRigidBody* CreateBox(const btVector3 &TPosition, const vector3df &TScale, btScalar TMass);
    btRigidBody* loadConvex(const char* filename, const btVector3 &TPosition, const vector3df &TScale, btScalar TMass);
	
    btDiscreteDynamicsWorld* getWorld();

    ISceneManager* smgr;
    IVideoDriver* driver;
    ITimer* Timer;
    btDiscreteDynamicsWorld* World;
    list<btRigidBody *> Objects;
    float Gravity;
    float TimeStamp;
    float DeltaTime;
};

#endif 
And the implementation file

CPhysics.mm

Code: Select all

#include "CPhysics.h"

#include "Bullet-C-Api.h"
#include "btBulletCollisionCommon.h"
#include "btBulletDynamicsCommon.h"
#include "BulletCollision/CollisionDispatch/btGhostObject.h"

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, float Gravity, IAnimatedMeshSceneNode* Map)
{
	btDefaultCollisionConfiguration* CollisionConfiguration = new btDefaultCollisionConfiguration();
	btBroadphaseInterface* BroadPhase = new btAxisSweep3(btVector3(-100000, -100000, -100000), btVector3(100000, 100000, 100000));
	BroadPhase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback());
	btCollisionDispatcher* Dispatcher = new btCollisionDispatcher(CollisionConfiguration);
	btSequentialImpulseConstraintSolver* Solver = new btSequentialImpulseConstraintSolver();
	CPhysics::World = new btDiscreteDynamicsWorld(Dispatcher, BroadPhase, Solver, CollisionConfiguration);
	CPhysics::World->setGravity(btVector3(0,Gravity,0));
	CPhysics::Gravity = Gravity;
	CPhysics::smgr = smgr;
	CPhysics::driver = driver;
	CPhysics::Timer = Timer;
	CPhysics::TimeStamp = Timer->getTime();
	CPhysics::DeltaTime = 0;
	printf("Mesh: %s", Map->getName());
	printf("Scale: X:%f Y:%f Z:%f", Map->getScale().X, Map->getScale().Y, Map->getScale().Z);
	btTriangleMesh* indexVertexArrays = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Map->getMesh(),Map->getScale());
	btBvhTriangleMeshShape* trimeshShape = new btBvhTriangleMeshShape(indexVertexArrays, true);
	
	btQuaternion quat;
	quat.setEulerZYX(0,0,0);
	btTransform Transform2;
	Transform2.setIdentity();
	Transform2.setOrigin(btVector3(0,0,0));
	Transform2.setRotation(quat);
	
	btDefaultMotionState *MotionState2 = new btDefaultMotionState(Transform2);
	
	btRigidBody *RigidBody = new btRigidBody(0, MotionState2, trimeshShape);
	
	RigidBody->setUserPointer((void *)(Map));
	
	CPhysics::World->addRigidBody(RigidBody);
	CPhysics::Objects.push_back(RigidBody);
	
	RigidBody->setCollisionFlags(btCollisionObject::CF_STATIC_OBJECT);
	delete(indexVertexArrays);
}

void CPhysics::Update()
{
    CPhysics::DeltaTime = CPhysics::Timer->getTime() - CPhysics::TimeStamp;
    CPhysics::TimeStamp = CPhysics::Timer->getTime();
    CPhysics::World->stepSimulation(CPhysics::DeltaTime * 0.001f, 2);
    for(list<btRigidBody *>::Iterator Iterator = CPhysics::Objects.begin(); Iterator != CPhysics::Objects.end();)
        if(!CPhysics::UpdateRender(*Iterator))
        {
            Iterator = CPhysics::Objects.erase(Iterator);
        }
        else
        {
            Iterator++;
        }
}

bool CPhysics::UpdateRender(btRigidBody* TObject)
{
    ISceneNode* Node = static_cast<ISceneNode *>(TObject->getUserPointer());
	
    if(Node == NULL)
    {
        return false;
    }
    const btVector3& Point = TObject->getCenterOfMassPosition();
    Node->setPosition(vector3df((f32)Point[0], (f32)Point[1], (f32)Point[2]));
	
    btVector3 EulerRotation;
    CPhysics::QuaternionToEuler(TObject->getOrientation(), EulerRotation);
    Node->setRotation(vector3df(EulerRotation[0], EulerRotation[1], EulerRotation[2]));
    return true;
}

btRigidBody* CPhysics::CreateBox(const btVector3 &TPosition, const vector3df &TScale, btScalar TMass)
{
    ISceneNode* Node = CPhysics::smgr->addCubeSceneNode(1.0f);
	Node->setScale(TScale);
	Node->setMaterialFlag(EMF_LIGHTING, true);
	Node->setMaterialFlag(EMF_NORMALIZE_NORMALS, true);
	//Node->setMaterialTexture(0, driver->getTexture("rust0.jpg"));
	
	btTransform Transform;
	Transform.setIdentity();
	Transform.setOrigin(TPosition);
	
	btDefaultMotionState *MotionState = new btDefaultMotionState(Transform);
	
	btVector3 HalfExtents(TScale.X * 0.5f, TScale.Y * 0.5f, TScale.Z * 0.5f);
	btCollisionShape *Shape = new btBoxShape(HalfExtents);
	
	btVector3 LocalInertia;
	Shape->calculateLocalInertia(TMass, LocalInertia);
	
	btRigidBody *RigidBody = new btRigidBody(TMass, MotionState, Shape, LocalInertia);
	
	RigidBody->setUserPointer((void *)(Node));
	RigidBody->setActivationState(DISABLE_DEACTIVATION);
	
	CPhysics::World->addRigidBody(RigidBody);
	CPhysics::Objects.push_back(RigidBody);
	return RigidBody;
}

btTriangleMesh* CPhysics::ConvertIrrMeshToBulletTriangleMesh(IAnimatedMesh* pMesh,const vector3df &scaling)
{
	btVector3 vertices[3];
	u32 i,j,k,index,numVertices,numIndices;
	u16* mb_indices;
	btTriangleMesh *pTriMesh = new btTriangleMesh();
	for (i=0; i<pMesh->getMeshBufferCount(); i++)
	{
		IMeshBuffer* mb=pMesh->getMeshBuffer(i);
		if(mb->getVertexType()==EVT_STANDARD)
		{
			S3DVertex* mb_vertices=(S3DVertex*)mb->getVertices();
			mb_indices = mb->getIndices();
			numVertices = mb->getVertexCount();
			numIndices = mb->getIndexCount();
			for(j=0;j<numIndices;j+=3)
			{
				for (k=0;k<3;k++)
				{
					index = mb_indices[j+k];
					vertices[k] = btVector3(mb_vertices[index].Pos.X * scaling.X, 
											mb_vertices[index].Pos.Y * scaling.Y, 
											mb_vertices[index].Pos.Z * scaling.Z);
				}
				pTriMesh->addTriangle(vertices[0], vertices[1], vertices[2]);
			}
		}
		else if(mb->getVertexType()==EVT_2TCOORDS)
		{
			S3DVertex2TCoords* mb_vertices=(S3DVertex2TCoords*)mb->getVertices();
			mb_indices = mb->getIndices();
			numVertices = mb->getVertexCount();
			numIndices = mb->getIndexCount();
			for(j=0;j<numIndices;j+=3)
			{
				for (k=0;k<3;k++)
				{
					index = mb_indices[j+k];
					vertices[k] = btVector3(mb_vertices[index].Pos.X * scaling.X, 
											mb_vertices[index].Pos.Y * scaling.Y, 
											mb_vertices[index].Pos.Z * scaling.Z);
				}
				pTriMesh->addTriangle(vertices[0], vertices[1], vertices[2]);
			}
		}
	}
	return pTriMesh;
};

void CPhysics::QuaternionToEuler(const btQuaternion &TQuat, btVector3 &TEuler)
{
	btScalar W = TQuat.getW();
	btScalar X = TQuat.getX();
	btScalar Y = TQuat.getY();
	btScalar Z = TQuat.getZ();
	float WSquared = W * W;
	float XSquared = X * X;
	float YSquared = Y * Y;
	float ZSquared = Z * Z;
	TEuler.setX(atan2f(2.0f * (Y * Z + X * W), -XSquared - YSquared + ZSquared + WSquared));
	TEuler.setY(asinf(-2.0f * (X * Z - Y * W)));
	TEuler.setZ(atan2f(2.0f * (X * Y + Z * W), XSquared - YSquared - ZSquared + WSquared));
	TEuler *= RADTODEG;
};

btRigidBody* CPhysics::loadConvex(const char* filename, const btVector3 &TPosition, const vector3df &TScale, btScalar TMass)
{
    IAnimatedMeshSceneNode* Node = CPhysics::smgr->addAnimatedMeshSceneNode((IAnimatedMesh*)CPhysics::smgr->getMesh(filename));
    btTriangleMesh* trimesh = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Node->getMesh(), Node->getScale());
	
    btConvexShape* hull = new btConvexTriangleMeshShape(trimesh);
    hull->setUserPointer(hull);
	
    btVector3 localInertia(0,0,0);
    hull->calculateLocalInertia(TMass, localInertia);
	
	btQuaternion quat;
	quat.setEulerZYX(Node->getRotation().X,Node->getRotation().Y,Node->getRotation().Z);
	btTransform Transform2;
	Transform2.setIdentity();
	Transform2.setOrigin(TPosition);
	Transform2.setRotation(quat);
	
	btDefaultMotionState *MotionState2 = new btDefaultMotionState(Transform2);
	
	btRigidBody* RigidBody = new btRigidBody(TMass, MotionState2, hull, localInertia);
	
	RigidBody->setUserPointer((void *)(Node));
	RigidBody->setActivationState(DISABLE_DEACTIVATION);
	
	CPhysics::World->addRigidBody(RigidBody);
	CPhysics::Objects.push_back(RigidBody);
	
	return RigidBody;
}

list<btRigidBody *> CPhysics::getObjectList()
{
    return CPhysics::Objects;
}

btDiscreteDynamicsWorld* CPhysics::getWorld()
{
    return CPhysics::World;
} 

void CPhysics::Remove()
{
    for(list<btRigidBody *>::Iterator Iterator = CPhysics::Objects.begin(); Iterator != CPhysics::Objects.end();)
    {
        CPhysics::World->removeRigidBody((*Iterator));
        if((*Iterator)->getUserPointer()!=NULL)
            static_cast<ISceneNode *>((*Iterator)->getUserPointer())->remove();
        Iterator = CPhysics::Objects.erase(Iterator);
    }
    CPhysics::World->~btDiscreteDynamicsWorld();

}

I haven't converted the DebugDraw.h file yet as there seem to be quite a few changes required and it wasn't essential to get it up and running.

I do seem to be having one problem though

when creating the physics world I'm using the following code

Code: Select all

Physics = new CPhysics(smgr, driver, timer, -500.0f, ((IAnimatedMeshSceneNode*)node));
where node is an object loaded in a irrScene file.

When the creation gets to the following line in CPhysics.mm

Code: Select all

	btTriangleMesh* indexVertexArrays = CPhysics::ConvertIrrMeshToBulletTriangleMesh(Map->getMesh(),Map->getScale());
the app just crashes with a EXC_BAD_ACCESS error, which if you've not done iPhone programming, means that the code is trying to access an object that has
a) not yet been created (does not exist in memory)
b) has been deleted/released or dropped and the memory pointer no longer exists

The issue seems to be with the Map->getMesh() part of the call.

Has anyone had issues loading scene file objects into the physics engine or referencing the Meshes?

Thanks in advance and hope the changes above help any fellow iPhone developers get started with physics.

And thanks to Buck1000 for providing this framework that got me started :D[/b]
Garfinkle
Posts: 36
Joined: Wed Jul 07, 2010 11:35 am
Location: Manchester, UK

Post by Garfinkle »

Worked out where I was going wrong.

I was trying to cast an cast a ISceneNode straight to a IAnimatedMeshSceneNode when the node was in fact a IMeshSceneNode so the cast was failing.

I have improved the code to handle loading objects from scene files.

in CPhysics.h add the following constructor above the existing one.

Code: Select all

CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, float Gravity, ISceneNode* Map);
and in the CPhysics.m add the following constructor above the existing one.

Code: Select all

CPhysics::CPhysics(ISceneManager* smgr, IVideoDriver* driver, ITimer* Timer, float Gravity, ISceneNode* Map)
{
	IAnimatedMeshSceneNode* animatedMesh = 0;
	switch(Map->getType())
	{
		case scene::ESNT_MESH:
			animatedMesh = static_cast<IAnimatedMeshSceneNode*>(Map);
			printf("\nSetting Mesh to IAnimatedMeshSceneNode\n");
			break;
		
	}
	CPhysics::CPhysics(smgr, driver, Timer, Gravity, animatedMesh);
}
What this now does is allows a second constructor to intercept if you are loading an object as a ISceneNode. If you are then it will convert it to type IAnimatedMeshSceneNode if it is an IMeshSceneNode using dynamic casting and then pass it to the original constructor.

If you call the same constructor with a IAnimatedMeshSceneNode and not a ISceneNode then it will use the default constructor and run as normal.

This could be expanded in the future to handle conversion of other Node Types to create a more dynamic loading system.


EDIT: Corrected errors in my conversion from IMeshSceneNode to IAnimateMeshSceneNode
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

Remember not to loose the indexVertexArrays pointer in the constructor or nasty memory leaks will occur due to conversion function which returns new'ed trimesh object! :wink:
Working on game: Marrbles (Currently stopped).
Garfinkle
Posts: 36
Joined: Wed Jul 07, 2010 11:35 am
Location: Manchester, UK

Post by Garfinkle »

@serengeor: Does the indexVertexArray need to be kept or can it just simply be deleted at the end of the function using delete(indexVertexArray)? Does it need to be maintained for the new trimeshShape or is it never used again after the trimeshShape is created?


I seem to be having a slight issue with the following lines in the constructor


Code: Select all

   CPhysics::Gravity = Gravity;
   CPhysics::smgr = smgr;
   CPhysics::driver = driver;
   CPhysics::Timer = Timer;
   CPhysics::TimeStamp = Timer->getTime();
   CPhysics::DeltaTime = 0; 
The variables are set here perfectly but in the other methods they always appear to be set to 0x0. It seems that once the method ends these variables are being cleared and not retained throughout the whole instance of the class.

The class instance is only ever being created once and I have not yet implement any calls to the Remove function.

Is there any reason why the variables would not be retained and keep setting themselves back to 0x0 (NULL)?

Thanks in advance.

[/b]
Garfinkle
Posts: 36
Joined: Wed Jul 07, 2010 11:35 am
Location: Manchester, UK

Post by Garfinkle »

I decided to aim at fixing the 170 errors being thrown by using DebugDraw.h on the iPhone.

Well....turned out easier than expected.

Add the following to the top of DebugDraw.h and its all working fine.

Code: Select all

#include "irrlicht.h"
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

Garfinkle wrote:I decided to aim at fixing the 170 errors being thrown by using DebugDraw.h on the iPhone.

Well....turned out easier than expected.

Add the following to the top of DebugDraw.h and its all working fine.

Code: Select all

#include "irrlicht.h"
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
Who could have guessed that ! :shock:
Good job anyways :)
Working on game: Marrbles (Currently stopped).
serengeor
Posts: 1712
Joined: Tue Jan 13, 2009 7:34 pm
Location: Lithuania

Post by serengeor »

@serengeor: Does the indexVertexArray need to be kept or can it just simply be deleted at the end of the function using delete(indexVertexArray)? Does it need to be maintained for the new trimeshShape or is it never used again after the trimeshShape is created?
It does have to be kept as long as you use trimesh shape when you delete it you should delete indexvertexarray too.
deleting it in the same function would result in a crash.
Working on game: Marrbles (Currently stopped).
Post Reply