Add support for animated collision shapes (Fixes #1549)

deque
scrawl 11 years ago
parent 64a4c2785e
commit 0b34d8d2fd

@ -12,6 +12,7 @@
#include <openengine/bullet/trace.h>
#include <openengine/bullet/physic.hpp>
#include <openengine/bullet/BtOgreExtras.h>
#include <openengine/ogre/renderer.hpp>
#include <components/nifbullet/bulletnifloader.hpp>
@ -26,10 +27,49 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "../apps/openmw/mwrender/animation.hpp"
#include "../apps/openmw/mwbase/world.hpp"
#include "../apps/openmw/mwbase/environment.hpp"
#include "ptr.hpp"
#include "class.hpp"
using namespace Ogre;
namespace
{
void animateCollisionShapes (std::map<OEngine::Physic::RigidBody*, OEngine::Physic::AnimatedShapeInstance>& map)
{
for (std::map<OEngine::Physic::RigidBody*, OEngine::Physic::AnimatedShapeInstance>::iterator it = map.begin();
it != map.end(); ++it)
{
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaHandle(it->first->mName);
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
OEngine::Physic::AnimatedShapeInstance& instance = it->second;
std::map<std::string, int>& shapes = instance.mAnimatedShapes;
for (std::map<std::string, int>::iterator shapeIt = shapes.begin();
shapeIt != shapes.end(); ++shapeIt)
{
Ogre::Node* bone = animation->getNode(shapeIt->first);
btCompoundShape* compound = dynamic_cast<btCompoundShape*>(instance.mCompound);
btTransform trans;
trans.setOrigin(BtOgre::Convert::toBullet(bone->_getDerivedPosition()));
trans.setRotation(BtOgre::Convert::toBullet(bone->_getDerivedOrientation()));
compound->getChildShape(shapeIt->second)->setLocalScaling(BtOgre::Convert::toBullet(bone->_getDerivedScale()));
compound->updateChildTransform(shapeIt->second, trans);
}
}
}
}
namespace MWWorld
{
@ -564,11 +604,10 @@ namespace MWWorld
std::string mesh = ptr.getClass().getModel(ptr);
Ogre::SceneNode* node = ptr.getRefData().getBaseNode();
handleToMesh[node->getName()] = mesh;
OEngine::Physic::RigidBody* body = mEngine->createAndAdjustRigidBody(
mEngine->createAndAdjustRigidBody(
mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, false, placeable);
OEngine::Physic::RigidBody* raycastingBody = mEngine->createAndAdjustRigidBody(
mEngine->createAndAdjustRigidBody(
mesh, node->getName(), node->getScale().x, node->getPosition(), node->getOrientation(), 0, 0, true, placeable);
mEngine->addRigidBody(body, true, raycastingBody);
}
void PhysicsSystem::addActor (const Ptr& ptr)
@ -770,4 +809,12 @@ namespace MWWorld
return mMovementResults;
}
void PhysicsSystem::stepSimulation(float dt)
{
animateCollisionShapes(mEngine->mAnimatedShapes);
animateCollisionShapes(mEngine->mAnimatedRaycastingShapes);
mEngine->stepSimulation(dt);
}
}

@ -53,6 +53,8 @@ namespace MWWorld
bool toggleCollisionMode();
void stepSimulation(float dt);
std::vector<std::string> getCollisions(const MWWorld::Ptr &ptr); ///< get handles this object collides with
Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr);

@ -1231,7 +1231,7 @@ namespace MWWorld
if(player != results.end())
moveObjectImp(player->first, player->second.x, player->second.y, player->second.z);
mPhysEngine->stepSimulation(duration);
mPhysics->stepSimulation(duration);
}
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)

@ -49,20 +49,6 @@ typedef unsigned char ubyte;
namespace NifBullet
{
struct TriangleMeshShape : public btBvhTriangleMeshShape
{
TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression)
: btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression)
{
}
virtual ~TriangleMeshShape()
{
delete getTriangleInfoMap();
delete m_meshInterface;
}
};
ManualBulletShapeLoader::~ManualBulletShapeLoader()
{
}
@ -81,9 +67,8 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
mBoundingBox = NULL;
mShape->mBoxTranslation = Ogre::Vector3(0,0,0);
mShape->mBoxRotation = Ogre::Quaternion::IDENTITY;
mHasShape = false;
btTriangleMesh* mesh1 = new btTriangleMesh();
mCompoundShape = NULL;
mStaticMesh = NULL;
// Load the NIF. TODO: Wrap this in a try-catch block once we're out
// of the early stages of development. Right now we WANT to catch
@ -111,19 +96,35 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
mShape->mHasCollisionNode = hasRootCollisionNode(node);
//do a first pass
handleNode(mesh1, node,0,false,false,false);
handleNode(node,0,false,false,false);
if(mBoundingBox != NULL)
{
mShape->mCollisionShape = mBoundingBox;
delete mesh1;
delete mStaticMesh;
if (mCompoundShape)
{
int n = mCompoundShape->getNumChildShapes();
for(int i=0; i <n;i++)
delete (mCompoundShape->getChildShape(i));
delete mCompoundShape;
}
}
else if (mHasShape && mShape->mCollide)
else
{
mShape->mCollisionShape = new TriangleMeshShape(mesh1,true);
if (mCompoundShape)
{
mShape->mCollisionShape = mCompoundShape;
if (mStaticMesh)
{
btTransform trans;
trans.setIdentity();
mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true));
}
}
else if (mStaticMesh)
mShape->mCollisionShape = new TriangleMeshShape(mStaticMesh,true);
}
else
delete mesh1;
//second pass which create a shape for raycasting.
mResourceName = mShape->getName();
@ -131,18 +132,23 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource)
mBoundingBox = NULL;
mShape->mBoxTranslation = Ogre::Vector3(0,0,0);
mShape->mBoxRotation = Ogre::Quaternion::IDENTITY;
mHasShape = false;
btTriangleMesh* mesh2 = new btTriangleMesh();
mStaticMesh = NULL;
mCompoundShape = NULL;
handleNode(mesh2, node,0,true,true,false);
handleNode(node,0,true,true,false);
if (mHasShape)
if (mCompoundShape)
{
mShape->mRaycastingShape = new TriangleMeshShape(mesh2,true);
mShape->mRaycastingShape = mCompoundShape;
if (mStaticMesh)
{
btTransform trans;
trans.setIdentity();
mCompoundShape->addChildShape(trans, new TriangleMeshShape(mStaticMesh,true));
}
}
else
delete mesh2;
else if (mStaticMesh)
mShape->mRaycastingShape = new TriangleMeshShape(mStaticMesh,true);
}
bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node)
@ -167,14 +173,17 @@ bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node)
return false;
}
void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *node, int flags,
void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags,
bool isCollisionNode,
bool raycasting, bool isMarker)
bool raycasting, bool isMarker, bool isAnimated)
{
// Accumulate the flags from all the child nodes. This works for all
// the flags we currently use, at least.
flags |= node->flags;
if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController)
isAnimated = true;
if (!raycasting)
isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode);
else
@ -237,7 +246,7 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *
else if(node->recType == Nif::RC_NiTriShape)
{
mShape->mCollide = !(flags&0x800);
handleNiTriShape(mesh, static_cast<const Nif::NiTriShape*>(node), flags, node->getWorldTransform(), raycasting);
handleNiTriShape(static_cast<const Nif::NiTriShape*>(node), flags, node->getWorldTransform(), raycasting, isAnimated);
}
}
@ -249,13 +258,13 @@ void ManualBulletShapeLoader::handleNode(btTriangleMesh* mesh, const Nif::Node *
for(size_t i = 0;i < list.length();i++)
{
if(!list[i].empty())
handleNode(mesh, list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker);
handleNode(list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker, isAnimated);
}
}
}
void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform,
bool raycasting)
void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform,
bool raycasting, bool isAnimated)
{
assert(shape != NULL);
@ -278,17 +287,64 @@ void ManualBulletShapeLoader::handleNiTriShape(btTriangleMesh* mesh, const Nif::
// bother setting it up.
return;
mHasShape = true;
if (!shape->skin.empty())
isAnimated = false;
const Nif::NiTriShapeData *data = shape->data.getPtr();
const std::vector<Ogre::Vector3> &vertices = data->vertices;
const short *triangles = &data->triangles[0];
for(size_t i = 0;i < data->triangles.size();i+=3)
if (isAnimated)
{
Ogre::Vector3 b1 = transform*vertices[triangles[i+0]];
Ogre::Vector3 b2 = transform*vertices[triangles[i+1]];
Ogre::Vector3 b3 = transform*vertices[triangles[i+2]];
mesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z));
if (!mCompoundShape)
mCompoundShape = new btCompoundShape();
btTriangleMesh* childMesh = new btTriangleMesh();
const Nif::NiTriShapeData *data = shape->data.getPtr();
childMesh->preallocateVertices(data->vertices.size());
childMesh->preallocateIndices(data->triangles.size());
const std::vector<Ogre::Vector3> &vertices = data->vertices;
const std::vector<short> &triangles = data->triangles;
for(size_t i = 0;i < data->triangles.size();i+=3)
{
Ogre::Vector3 b1 = vertices[triangles[i+0]];
Ogre::Vector3 b2 = vertices[triangles[i+1]];
Ogre::Vector3 b3 = vertices[triangles[i+2]];
childMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z));
}
TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true);
childShape->setLocalScaling(btVector3(transform[0][0], transform[1][1], transform[2][2]));
Ogre::Quaternion q = transform.extractQuaternion();
Ogre::Vector3 v = transform.getTrans();
btTransform trans(btQuaternion(q.x, q.y, q.z, q.w), btVector3(v.x, v.y, v.z));
if (raycasting)
mShape->mAnimatedRaycastingShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes()));
else
mShape->mAnimatedShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes()));
mCompoundShape->addChildShape(trans, childShape);
}
else
{
if (!mStaticMesh)
mStaticMesh = new btTriangleMesh();
// Static shape, just transform all vertices into position
const Nif::NiTriShapeData *data = shape->data.getPtr();
const std::vector<Ogre::Vector3> &vertices = data->vertices;
const std::vector<short> &triangles = data->triangles;
for(size_t i = 0;i < data->triangles.size();i+=3)
{
Ogre::Vector3 b1 = transform*vertices[triangles[i+0]];
Ogre::Vector3 b2 = transform*vertices[triangles[i+1]];
Ogre::Vector3 b3 = transform*vertices[triangles[i+2]];
mStaticMesh->addTriangle(btVector3(b1.x,b1.y,b1.z),btVector3(b2.x,b2.y,b2.z),btVector3(b3.x,b3.y,b3.z));
}
}
}

@ -28,6 +28,7 @@
#include <string>
#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btConvexTriangleMeshShape.h>
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
#include <btBulletDynamicsCommon.h>
#include <openengine/bullet/BulletShapeLoader.h>
@ -44,6 +45,22 @@ namespace Nif
namespace NifBullet
{
// Subclass btBhvTriangleMeshShape to auto-delete the meshInterface
struct TriangleMeshShape : public btBvhTriangleMeshShape
{
TriangleMeshShape(btStridingMeshInterface* meshInterface, bool useQuantizedAabbCompression)
: btBvhTriangleMeshShape(meshInterface, useQuantizedAabbCompression)
{
}
virtual ~TriangleMeshShape()
{
delete getTriangleInfoMap();
delete m_meshInterface;
}
};
/**
*Load bulletShape from NIF files.
*/
@ -52,8 +69,9 @@ class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader
public:
ManualBulletShapeLoader()
: mShape(NULL)
, mStaticMesh(NULL)
, mCompoundShape(NULL)
, mBoundingBox(NULL)
, mHasShape(false)
{
}
@ -88,7 +106,8 @@ private:
/**
*Parse a node.
*/
void handleNode(btTriangleMesh* mesh, Nif::Node const *node, int flags, bool isCollisionNode, bool raycasting, bool isMarker);
void handleNode(Nif::Node const *node, int flags, bool isCollisionNode,
bool raycasting, bool isMarker, bool isAnimated=false);
/**
*Helper function
@ -98,14 +117,17 @@ private:
/**
*convert a NiTriShape to a bullet trishape.
*/
void handleNiTriShape(btTriangleMesh* mesh, const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting);
void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const Ogre::Matrix4 &transform, bool raycasting, bool isAnimated);
std::string mResourceName;
OEngine::Physic::BulletShape* mShape;//current shape
btBoxShape *mBoundingBox;
bool mHasShape;
btCompoundShape* mCompoundShape;
btTriangleMesh* mStaticMesh;
btBoxShape *mBoundingBox;
};

@ -42,7 +42,7 @@ void BulletShape::deleteShape(btCollisionShape* shape)
{
if(shape->isCompound())
{
btCompoundShape* ms = static_cast<btCompoundShape*>(mCollisionShape);
btCompoundShape* ms = static_cast<btCompoundShape*>(shape);
int a = ms->getNumChildShapes();
for(int i=0; i <a;i++)
{
@ -51,13 +51,14 @@ void BulletShape::deleteShape(btCollisionShape* shape)
}
delete shape;
}
shape = NULL;
}
void BulletShape::unloadImpl()
{
deleteShape(mCollisionShape);
deleteShape(mRaycastingShape);
mCollisionShape = NULL;
mRaycastingShape = NULL;
}
//TODO:change this?

@ -30,6 +30,14 @@ public:
virtual ~BulletShape();
// Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape
// will be a btCompoundShape (which consists of one or more child shapes).
// In this map, for each animated collision shape,
// we store the bone name mapped to the child index of the shape in the btCompoundShape.
std::map<std::string, int> mAnimatedShapes;
std::map<std::string, int> mAnimatedRaycastingShapes;
btCollisionShape* mCollisionShape;
btCollisionShape* mRaycastingShape;

@ -11,6 +11,59 @@
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
namespace
{
// Create a copy of the given collision shape (responsibility of user to delete the returned shape).
btCollisionShape *duplicateCollisionShape(btCollisionShape *shape)
{
if(shape->isCompound())
{
btCompoundShape *comp = static_cast<btCompoundShape*>(shape);
btCompoundShape *newShape = new btCompoundShape;
int numShapes = comp->getNumChildShapes();
for(int i = 0;i < numShapes;i++)
{
btCollisionShape *child = duplicateCollisionShape(comp->getChildShape(i));
btTransform trans = comp->getChildTransform(i);
newShape->addChildShape(trans, child);
}
return newShape;
}
if(btBvhTriangleMeshShape *trishape = dynamic_cast<btBvhTriangleMeshShape*>(shape))
{
btTriangleMesh* oldMesh = dynamic_cast<btTriangleMesh*>(trishape->getMeshInterface());
btTriangleMesh* newMesh = new btTriangleMesh(*oldMesh);
NifBullet::TriangleMeshShape *newShape = new NifBullet::TriangleMeshShape(newMesh, true);
return newShape;
}
throw std::logic_error(std::string("Unhandled Bullet shape duplication: ")+shape->getName());
}
void deleteShape(btCollisionShape* shape)
{
if(shape!=NULL)
{
if(shape->isCompound())
{
btCompoundShape* ms = static_cast<btCompoundShape*>(shape);
int a = ms->getNumChildShapes();
for(int i=0; i <a;i++)
{
deleteShape(ms->getChildShape(i));
}
}
delete shape;
}
}
}
namespace OEngine {
namespace Physic
{
@ -228,6 +281,11 @@ namespace Physic
PhysicEngine::~PhysicEngine()
{
for (std::map<RigidBody*, AnimatedShapeInstance>::iterator it = mAnimatedShapes.begin(); it != mAnimatedShapes.end(); ++it)
deleteShape(it->second.mCompound);
for (std::map<RigidBody*, AnimatedShapeInstance>::iterator it = mAnimatedRaycastingShapes.begin(); it != mAnimatedRaycastingShapes.end(); ++it)
deleteShape(it->second.mCompound);
HeightFieldContainer::iterator hf_it = mHeightFieldMap.begin();
for (; hf_it != mHeightFieldMap.end(); ++hf_it)
{
@ -386,17 +444,38 @@ namespace Physic
if (!shape->mRaycastingShape && raycasting)
return NULL;
if (!raycasting)
shape->mCollisionShape->setLocalScaling( btVector3(scale,scale,scale));
else
shape->mRaycastingShape->setLocalScaling( btVector3(scale,scale,scale));
btCollisionShape* collisionShape = raycasting ? shape->mRaycastingShape : shape->mCollisionShape;
// If this is an animated compound shape, we must duplicate it so we can animate
// multiple instances independently.
if (!raycasting && !shape->mAnimatedShapes.empty())
collisionShape = duplicateCollisionShape(collisionShape);
if (raycasting && !shape->mAnimatedRaycastingShapes.empty())
collisionShape = duplicateCollisionShape(collisionShape);
collisionShape->setLocalScaling( btVector3(scale,scale,scale));
//create the real body
btRigidBody::btRigidBodyConstructionInfo CI = btRigidBody::btRigidBodyConstructionInfo
(0,0, raycasting ? shape->mRaycastingShape : shape->mCollisionShape);
(0,0, collisionShape);
RigidBody* body = new RigidBody(CI,name);
body->mPlaceable = placeable;
if (!raycasting && !shape->mAnimatedShapes.empty())
{
AnimatedShapeInstance instance;
instance.mAnimatedShapes = shape->mAnimatedShapes;
instance.mCompound = collisionShape;
mAnimatedShapes[body] = instance;
}
if (raycasting && !shape->mAnimatedRaycastingShapes.empty())
{
AnimatedShapeInstance instance;
instance.mAnimatedShapes = shape->mAnimatedRaycastingShapes;
instance.mCompound = collisionShape;
mAnimatedRaycastingShapes[body] = instance;
}
if(scaledBoxTranslation != 0)
*scaledBoxTranslation = shape->mBoxTranslation * scale;
if(boxRotation != 0)
@ -404,33 +483,20 @@ namespace Physic
adjustRigidBody(body, position, rotation, shape->mBoxTranslation * scale, shape->mBoxRotation);
return body;
}
void PhysicEngine::addRigidBody(RigidBody* body, bool addToMap, RigidBody* raycastingBody)
{
if(!body && !raycastingBody)
return; // nothing to do
const std::string& name = (body ? body->mName : raycastingBody->mName);
if (body){
if (!raycasting)
{
assert (mCollisionObjectMap.find(name) == mCollisionObjectMap.end());
mCollisionObjectMap[name] = body;
mDynamicsWorld->addRigidBody(body,CollisionType_World,CollisionType_World|CollisionType_Actor|CollisionType_HeightMap);
}
if (raycastingBody)
mDynamicsWorld->addRigidBody(raycastingBody,CollisionType_Raycasting,CollisionType_Raycasting);
if(addToMap){
removeRigidBody(name);
deleteRigidBody(name);
if (body)
mCollisionObjectMap[name] = body;
if (raycastingBody)
mRaycastingObjectMap[name] = raycastingBody;
else
{
assert (mRaycastingObjectMap.find(name) == mRaycastingObjectMap.end());
mRaycastingObjectMap[name] = body;
mDynamicsWorld->addRigidBody(body,CollisionType_Raycasting,CollisionType_Raycasting);
}
return body;
}
void PhysicEngine::removeRigidBody(const std::string &name)
@ -464,6 +530,10 @@ namespace Physic
if(body != NULL)
{
if (mAnimatedShapes.find(body) != mAnimatedShapes.end())
deleteShape(mAnimatedShapes[body].mCompound);
mAnimatedShapes.erase(body);
delete body;
}
mCollisionObjectMap.erase(it);
@ -475,6 +545,10 @@ namespace Physic
if(body != NULL)
{
if (mAnimatedRaycastingShapes.find(body) != mAnimatedRaycastingShapes.end())
deleteShape(mAnimatedRaycastingShapes[body].mCompound);
mAnimatedRaycastingShapes.erase(body);
delete body;
}
mRaycastingObjectMap.erase(it);
@ -609,7 +683,6 @@ namespace Physic
return std::make_pair(callback.mObject, callback.mContactPoint);
}
void PhysicEngine::stepSimulation(double deltaT)
{
// This seems to be needed for character controller objects
@ -629,8 +702,6 @@ namespace Physic
PhysicActor* newActor = new PhysicActor(name, mesh, this, position, rotation, scale);
//dynamicsWorld->addAction( newActor->mCharacter );
mActorMap[name] = newActor;
}
@ -642,7 +713,6 @@ namespace Physic
PhysicActor* act = it->second;
if(act != NULL)
{
delete act;
}
mActorMap.erase(it);

@ -117,7 +117,7 @@ namespace Physic
const Ogre::Vector3& getPosition() const;
/**
* Returns the half extents for this PhysiActor
* Returns the (scaled) half extents
*/
Ogre::Vector3 getHalfExtents() const;
@ -178,6 +178,14 @@ namespace Physic
RigidBody* mBody;
};
struct AnimatedShapeInstance
{
btCollisionShape* mCompound;
// Maps bone name to child index in the compound shape
std::map<std::string, int> mAnimatedShapes;
};
/**
* The PhysicEngine class contain everything which is needed for Physic.
* It's needed that Ogre Resources are set up before the PhysicEngine is created.
@ -228,11 +236,6 @@ namespace Physic
*/
void removeHeightField(int x, int y);
/**
* Add a RigidBody to the simulation
*/
void addRigidBody(RigidBody* body, bool addToMap = true, RigidBody* raycastingBody = NULL);
/**
* Remove a RigidBody from the simulation. It does not delete it, and does not remove it from the RigidBodyMap.
*/
@ -335,8 +338,14 @@ namespace Physic
typedef std::map<std::string,RigidBody*> RigidBodyContainer;
RigidBodyContainer mCollisionObjectMap;
// Compound shapes that must be animated each frame based on bone positions
// the index refers to an element in mCollisionObjectMap
std::map<RigidBody*, AnimatedShapeInstance > mAnimatedShapes;
RigidBodyContainer mRaycastingObjectMap;
std::map<RigidBody*, AnimatedShapeInstance > mAnimatedRaycastingShapes;
typedef std::map<std::string, PhysicActor*> PhysicActorContainer;
PhysicActorContainer mActorMap;

Loading…
Cancel
Save