Add collision and raycast filtering using bits mask

This commit is contained in:
Daniel Chappuis 2014-12-31 01:19:14 +01:00
parent aae4da54d0
commit c15b83db4a
10 changed files with 263 additions and 50 deletions

View File

@ -171,9 +171,12 @@ void CollisionDetection::computeNarrowPhase() {
assert(shape1->mBroadPhaseID != shape2->mBroadPhaseID);
// Check that the two shapes are overlapping. If the shapes are not overlapping
// anymore, we remove the overlapping pair.
if (!mBroadPhaseAlgorithm.testOverlappingShapes(shape1, shape2)) {
// Check if the collision filtering allows collision between the two shapes and
// that the two shapes are still overlapping. Otherwise, we destroy the
// overlapping pair
if (((shape1->getCollideWithMaskBits() & shape2->getCollisionCategoryBits()) == 0 ||
(shape1->getCollisionCategoryBits() & shape2->getCollideWithMaskBits()) == 0) ||
!mBroadPhaseAlgorithm.testOverlappingShapes(shape1, shape2)) {
std::map<overlappingpairid, OverlappingPair*>::iterator itToRemove = it;
++it;
@ -252,12 +255,6 @@ void CollisionDetection::computeNarrowPhaseBetweenShapes(CollisionCallback* call
assert(shape1->mBroadPhaseID != shape2->mBroadPhaseID);
bool test1 = shapes1.count(shape1->mBroadPhaseID) == 0;
bool test2 = shapes2.count(shape2->mBroadPhaseID) == 0;
bool test3 = shapes1.count(shape2->mBroadPhaseID) == 0;
bool test4 = shapes2.count(shape1->mBroadPhaseID) == 0;
bool test5 = !shapes1.empty() && !shapes2.empty();
// If both shapes1 and shapes2 sets are non-empty, we check that
// shape1 is among on set and shape2 is among the other one
if (!shapes1.empty() && !shapes2.empty() &&
@ -279,9 +276,12 @@ void CollisionDetection::computeNarrowPhaseBetweenShapes(CollisionCallback* call
continue;
}
// Check that the two shapes are overlapping. If the shapes are not overlapping
// anymore, we remove the overlapping pair.
if (!mBroadPhaseAlgorithm.testOverlappingShapes(shape1, shape2)) {
// Check if the collision filtering allows collision between the two shapes and
// that the two shapes are still overlapping. Otherwise, we destroy the
// overlapping pair
if (((shape1->getCollideWithMaskBits() & shape2->getCollisionCategoryBits()) == 0 ||
(shape1->getCollisionCategoryBits() & shape2->getCollideWithMaskBits()) == 0) ||
!mBroadPhaseAlgorithm.testOverlappingShapes(shape1, shape2)) {
std::map<overlappingpairid, OverlappingPair*>::iterator itToRemove = it;
++it;
@ -345,6 +345,10 @@ void CollisionDetection::broadPhaseNotifyOverlappingPair(ProxyShape* shape1, Pro
// If the two proxy collision shapes are from the same body, skip it
if (shape1->getBody()->getID() == shape2->getBody()->getID()) return;
// Check if the collision filtering allows collision between the two shapes
if ((shape1->getCollideWithMaskBits() & shape2->getCollisionCategoryBits()) == 0 ||
(shape1->getCollisionCategoryBits() & shape2->getCollideWithMaskBits()) == 0) return;
// Compute the overlapping pair ID
overlappingpairid pairID = OverlappingPair::computeID(shape1, shape2);
@ -358,6 +362,10 @@ void CollisionDetection::broadPhaseNotifyOverlappingPair(ProxyShape* shape1, Pro
std::pair<map<overlappingpairid, OverlappingPair*>::iterator, bool> check =
mOverlappingPairs.insert(make_pair(pairID, newPair));
assert(check.second);
// Wake up the two bodies
shape1->getBody()->setIsSleeping(false);
shape2->getBody()->setIsSleeping(false);
}
// Remove a body from the collision detection

View File

@ -153,7 +153,8 @@ class CollisionDetection {
const std::set<uint>& shapes2) ;
/// Ray casting method
void raycast(RaycastCallback* raycastCallback, const Ray& ray) const;
void raycast(RaycastCallback* raycastCallback, const Ray& ray,
unsigned short raycastWithCategoryMaskBits) const;
/// Test if the AABBs of two bodies overlap
bool testAABBOverlap(const CollisionBody* body1,
@ -228,13 +229,14 @@ inline void CollisionDetection::updateProxyCollisionShape(ProxyShape* shape, con
// Ray casting method
inline void CollisionDetection::raycast(RaycastCallback* raycastCallback,
const Ray& ray) const {
const Ray& ray,
unsigned short raycastWithCategoryMaskBits) const {
RaycastTest rayCastTest(raycastCallback);
// Ask the broad-phase algorithm to call the testRaycastAgainstShape()
// callback method for each proxy shape hit by the ray in the broad-phase
mBroadPhaseAlgorithm.raycast(ray, rayCastTest);
mBroadPhaseAlgorithm.raycast(ray, rayCastTest, raycastWithCategoryMaskBits);
}
// Test if the AABBs of two proxy shapes overlap

View File

@ -8,7 +8,8 @@ using namespace reactphysics3d;
ProxyShape::ProxyShape(CollisionBody* body, CollisionShape* shape, const Transform& transform,
decimal mass)
:mBody(body), mCollisionShape(shape), mLocalToBodyTransform(transform), mMass(mass),
mNext(NULL), mBroadPhaseID(-1), mCachedCollisionData(NULL), mUserData(NULL) {
mNext(NULL), mBroadPhaseID(-1), mCachedCollisionData(NULL), mUserData(NULL),
mCollisionCategoryBits(0x0001), mCollideWithMaskBits(0xFFFF) {
}

View File

@ -47,6 +47,19 @@ class ProxyShape {
/// Pointer to user data
void* mUserData;
/// Bits used to define the collision category of this shape.
/// You can set a single bit to one to define a category value for this
/// shape. This value is one (0x0001) by default. This variable can be used
/// together with the mCollideWithMaskBits variable so that given
/// categories of shapes collide with each other and do not collide with
/// other categories.
unsigned short mCollisionCategoryBits;
/// Bits mask used to state which collision categories this shape can
/// collide with. This value is 0xFFFF by default. It means that this
/// proxy shape will collide with every collision categories by default.
unsigned short mCollideWithMaskBits;
// -------------------- Methods -------------------- //
/// Private copy-constructor
@ -108,6 +121,18 @@ class ProxyShape {
/// Raycast method with feedback information
bool raycast(const Ray& ray, RaycastInfo& raycastInfo);
/// Return the collision category bits
unsigned short getCollisionCategoryBits() const;
/// Set the collision category bits
void setCollisionCategoryBits(unsigned short collisionCategoryBits);
/// Return the collision bits mask
unsigned short getCollideWithMaskBits() const;
/// Set the collision bits mask
void setCollideWithMaskBits(unsigned short collideWithMaskBits);
// -------------------- Friendship -------------------- //
friend class OverlappingPair;
@ -192,6 +217,26 @@ inline const ProxyShape* ProxyShape::getNext() const {
return mNext;
}
// Return the collision category bits
inline unsigned short ProxyShape::getCollisionCategoryBits() const {
return mCollisionCategoryBits;
}
// Set the collision category bits
inline void ProxyShape::setCollisionCategoryBits(unsigned short collisionCategoryBits) {
mCollisionCategoryBits = collisionCategoryBits;
}
// Return the collision bits mask
inline unsigned short ProxyShape::getCollideWithMaskBits() const {
return mCollideWithMaskBits;
}
// Set the collision bits mask
inline void ProxyShape::setCollideWithMaskBits(unsigned short collideWithMaskBits) {
mCollideWithMaskBits = collideWithMaskBits;
}
}
#endif

View File

@ -159,7 +159,8 @@ class BroadPhaseAlgorithm {
bool testOverlappingShapes(const ProxyShape* shape1, const ProxyShape* shape2) const;
/// Ray casting method
void raycast(const Ray& ray, RaycastTest& raycastTest) const;
void raycast(const Ray& ray, RaycastTest& raycastTest,
unsigned short raycastWithCategoryMaskBits) const;
};
// Method used to compare two pairs for sorting algorithm
@ -185,8 +186,9 @@ inline bool BroadPhaseAlgorithm::testOverlappingShapes(const ProxyShape* shape1,
// Ray casting method
inline void BroadPhaseAlgorithm::raycast(const Ray& ray,
RaycastTest& raycastTest) const {
mDynamicAABBTree.raycast(ray, raycastTest);
RaycastTest& raycastTest,
unsigned short raycastWithCategoryMaskBits) const {
mDynamicAABBTree.raycast(ray, raycastTest, raycastWithCategoryMaskBits);
}
}

View File

@ -606,7 +606,8 @@ void DynamicAABBTree::reportAllShapesOverlappingWith(int nodeID, const AABB& aab
}
// Ray casting method
void DynamicAABBTree::raycast(const Ray& ray, RaycastTest& raycastTest) const {
void DynamicAABBTree::raycast(const Ray& ray, RaycastTest& raycastTest,
unsigned short raycastWithCategoryMaskBits) const {
decimal maxFraction = ray.maxFraction;
@ -638,35 +639,39 @@ void DynamicAABBTree::raycast(const Ray& ray, RaycastTest& raycastTest) const {
// If the node is a leaf of the tree
if (node->isLeaf()) {
Ray rayTemp(ray.point1, ray.point2, maxFraction);
// Check if the raycast filtering mask allows raycast against this shape
if ((raycastWithCategoryMaskBits & node->proxyShape->getCollisionCategoryBits()) != 0) {
// Ask the collision detection to perform a ray cast test against
// the proxy shape of this node because the ray is overlapping
// with the shape in the broad-phase
decimal hitFraction = raycastTest.raycastAgainstShape(node->proxyShape,
rayTemp);
Ray rayTemp(ray.point1, ray.point2, maxFraction);
// If the user returned a hitFraction of zero, it means that
// the raycasting should stop here
if (hitFraction == decimal(0.0)) {
return;
}
// Ask the collision detection to perform a ray cast test against
// the proxy shape of this node because the ray is overlapping
// with the shape in the broad-phase
decimal hitFraction = raycastTest.raycastAgainstShape(node->proxyShape,
rayTemp);
// If the user returned a positive fraction
if (hitFraction > decimal(0.0)) {
// We update the maxFraction value and the ray
// AABB using the new maximum fraction
if (hitFraction < maxFraction) {
maxFraction = hitFraction;
// If the user returned a hitFraction of zero, it means that
// the raycasting should stop here
if (hitFraction == decimal(0.0)) {
return;
}
endPoint = ray.point1 + maxFraction * (ray.point2 - ray.point1);
rayAABB.mMinCoordinates = Vector3::min(ray.point1, endPoint);
rayAABB.mMaxCoordinates = Vector3::max(ray.point1, endPoint);
}
// If the user returned a negative fraction, we continue
// the raycasting as if the proxy shape did not exist
// If the user returned a positive fraction
if (hitFraction > decimal(0.0)) {
// We update the maxFraction value and the ray
// AABB using the new maximum fraction
if (hitFraction < maxFraction) {
maxFraction = hitFraction;
}
endPoint = ray.point1 + maxFraction * (ray.point2 - ray.point1);
rayAABB.mMinCoordinates = Vector3::min(ray.point1, endPoint);
rayAABB.mMaxCoordinates = Vector3::max(ray.point1, endPoint);
}
// If the user returned a negative fraction, we continue
// the raycasting as if the proxy shape did not exist
}
}
else { // If the node has children

View File

@ -168,7 +168,8 @@ class DynamicAABBTree {
void reportAllShapesOverlappingWith(int nodeID, const AABB& aabb);
/// Ray casting method
void raycast(const Ray& ray, RaycastTest& raycastTest) const;
void raycast(const Ray& ray, RaycastTest& raycastTest,
unsigned short raycastWithCategoryMaskBits) const;
};
// Return true if the node is a leaf of the tree

View File

@ -124,7 +124,8 @@ class CollisionWorld {
void destroyCollisionBody(CollisionBody* collisionBody);
/// Ray cast method
void raycast(const Ray& ray, RaycastCallback* raycastCallback) const;
void raycast(const Ray& ray, RaycastCallback* raycastCallback,
unsigned short raycastWithCategoryMaskBits = 0xFFFF) const;
/// Test if the AABBs of two bodies overlap
bool testAABBOverlap(const CollisionBody* body1,
@ -177,8 +178,9 @@ inline std::set<CollisionBody*>::iterator CollisionWorld::getBodiesEndIterator()
// Ray cast method
inline void CollisionWorld::raycast(const Ray& ray,
RaycastCallback* raycastCallback) const {
mCollisionDetection.raycast(raycastCallback, ray);
RaycastCallback* raycastCallback,
unsigned short raycastWithCategoryMaskBits) const {
mCollisionDetection.raycast(raycastCallback, ray, raycastWithCategoryMaskBits);
}
// Test if the AABBs of two proxy shapes overlap

View File

@ -32,6 +32,13 @@
/// Reactphysics3D namespace
namespace reactphysics3d {
// Enumeration for categories
enum CollisionCategory {
CATEGORY_1 = 0x0001,
CATEGORY_2 = 0x0002,
CATEGORY_3 = 0x0004
};
// Class
class WorldCollisionCallback : public CollisionCallback
{
@ -60,7 +67,6 @@ class WorldCollisionCallback : public CollisionCallback
sphere1CollideWithSphere2 = false;
}
// This method will be called for contact
virtual void notifyContact(const ContactPointInfo& contactPointInfo) {
@ -145,6 +151,12 @@ class TestCollisionWorld : public Test {
CylinderShape cylinderShape(2, 5);
mCylinderShape = mCylinderBody->addCollisionShape(cylinderShape, Transform::identity());
// Assign collision categories to proxy shapes
mBoxShape->setCollisionCategoryBits(CATEGORY_1);
mSphere1Shape->setCollisionCategoryBits(CATEGORY_1);
mSphere2Shape->setCollisionCategoryBits(CATEGORY_2);
mCylinderShape->setCollisionCategoryBits(CATEGORY_3);
mCollisionCallback.boxBody = mBoxBody;
mCollisionCallback.sphere1Body = mSphere1Body;
mCollisionCallback.sphere2Body = mSphere2Body;
@ -235,7 +247,11 @@ class TestCollisionWorld : public Test {
test(!mCollisionCallback.sphere1CollideWithCylinder);
test(!mCollisionCallback.sphere1CollideWithSphere2);
// Test collision with inactive bodies
// Move sphere 1 to collide with box
mSphere1Body->setTransform(Transform(Vector3(10, 5, 0), Quaternion::identity()));
// --------- Test collision with inactive bodies --------- //
mCollisionCallback.reset();
mBoxBody->setIsActive(false);
mCylinderBody->setIsActive(false);
@ -261,6 +277,50 @@ class TestCollisionWorld : public Test {
mCylinderBody->setIsActive(true);
mSphere1Body->setIsActive(true);
mSphere2Body->setIsActive(true);
// --------- Test collision with collision filtering -------- //
mBoxShape->setCollideWithMaskBits(CATEGORY_1 | CATEGORY_3);
mSphere1Shape->setCollideWithMaskBits(CATEGORY_1 | CATEGORY_2);
mSphere2Shape->setCollideWithMaskBits(CATEGORY_1);
mCylinderShape->setCollideWithMaskBits(CATEGORY_1);
mCollisionCallback.reset();
mWorld->testCollision(&mCollisionCallback);
test(mCollisionCallback.boxCollideWithSphere1);
test(mCollisionCallback.boxCollideWithCylinder);
test(!mCollisionCallback.sphere1CollideWithCylinder);
test(!mCollisionCallback.sphere1CollideWithSphere2);
// Move sphere 1 to collide with sphere 2
mSphere1Body->setTransform(Transform(Vector3(30, 15, 10), Quaternion::identity()));
mCollisionCallback.reset();
mWorld->testCollision(&mCollisionCallback);
test(!mCollisionCallback.boxCollideWithSphere1);
test(mCollisionCallback.boxCollideWithCylinder);
test(!mCollisionCallback.sphere1CollideWithCylinder);
test(mCollisionCallback.sphere1CollideWithSphere2);
mBoxShape->setCollideWithMaskBits(CATEGORY_2);
mSphere1Shape->setCollideWithMaskBits(CATEGORY_2);
mSphere2Shape->setCollideWithMaskBits(CATEGORY_3);
mCylinderShape->setCollideWithMaskBits(CATEGORY_1);
mCollisionCallback.reset();
mWorld->testCollision(&mCollisionCallback);
test(!mCollisionCallback.boxCollideWithSphere1);
test(!mCollisionCallback.boxCollideWithCylinder);
test(!mCollisionCallback.sphere1CollideWithCylinder);
test(!mCollisionCallback.sphere1CollideWithSphere2);
// Move sphere 1 to collide with box
mSphere1Body->setTransform(Transform(Vector3(10, 5, 0), Quaternion::identity()));
mBoxShape->setCollideWithMaskBits(0xFFFF);
mSphere1Shape->setCollideWithMaskBits(0xFFFF);
mSphere2Shape->setCollideWithMaskBits(0xFFFF);
mCylinderShape->setCollideWithMaskBits(0xFFFF);
}
};

View File

@ -40,6 +40,12 @@
/// Reactphysics3D namespace
namespace reactphysics3d {
// Enumeration for categories
enum Category {
CATEGORY1 = 0x0001,
CATEGORY2 = 0x0002
};
/// Class WorldRaycastCallback
class WorldRaycastCallback : public RaycastCallback {
@ -220,6 +226,17 @@ class TestRaycast : public Test {
mLocalShape2ToWorld = mBodyTransform * shapeTransform2;
mCompoundCylinderShape = mCompoundBody->addCollisionShape(cylinderShape, mShapeTransform);
mCompoundSphereShape = mCompoundBody->addCollisionShape(sphereShape, shapeTransform2);
// Assign proxy shapes to the different categories
mBoxShape->setCollisionCategoryBits(CATEGORY1);
mSphereShape->setCollisionCategoryBits(CATEGORY1);
mCapsuleShape->setCollisionCategoryBits(CATEGORY1);
mConeShape->setCollisionCategoryBits(CATEGORY2);
mConvexMeshShape->setCollisionCategoryBits(CATEGORY2);
mConvexMeshShapeEdgesInfo->setCollisionCategoryBits(CATEGORY2);
mCylinderShape->setCollisionCategoryBits(CATEGORY2);
mCompoundSphereShape->setCollisionCategoryBits(CATEGORY2);
mCompoundCylinderShape->setCollisionCategoryBits(CATEGORY2);
}
/// Run the tests
@ -256,6 +273,16 @@ class TestRaycast : public Test {
test(approxEqual(callback.raycastInfo.worldPoint.y, hitPoint.y, epsilon));
test(approxEqual(callback.raycastInfo.worldPoint.z, hitPoint.z, epsilon));
// Correct category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY1);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY2);
test(!callback.isHit);
// CollisionBody::raycast()
RaycastInfo raycastInfo2;
test(mBoxBody->raycast(ray, raycastInfo2));
@ -458,6 +485,16 @@ class TestRaycast : public Test {
test(approxEqual(callback.raycastInfo.worldPoint.y, hitPoint.y, epsilon));
test(approxEqual(callback.raycastInfo.worldPoint.z, hitPoint.z, epsilon));
// Correct category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY1);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY2);
test(!callback.isHit);
// CollisionBody::raycast()
RaycastInfo raycastInfo2;
test(mSphereBody->raycast(ray, raycastInfo2));
@ -668,6 +705,16 @@ class TestRaycast : public Test {
test(approxEqual(callback.raycastInfo.worldPoint.y, hitPoint.y, epsilon));
test(approxEqual(callback.raycastInfo.worldPoint.z, hitPoint.z, epsilon));
// Correct category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY1);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY2);
test(!callback.isHit);
// CollisionBody::raycast()
RaycastInfo raycastInfo2;
test(mCapsuleBody->raycast(ray, raycastInfo2));
@ -893,6 +940,16 @@ class TestRaycast : public Test {
test(approxEqual(callback.raycastInfo.worldPoint.y, hitPoint.y));
test(approxEqual(callback.raycastInfo.worldPoint.z, hitPoint.z));
// Correct category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY2);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY1);
test(!callback.isHit);
// CollisionBody::raycast()
RaycastInfo raycastInfo2;
test(mConeBody->raycast(ray, raycastInfo2));
@ -1125,6 +1182,16 @@ class TestRaycast : public Test {
test(approxEqual(callback.raycastInfo.worldPoint.y, hitPoint.y, epsilon));
test(approxEqual(callback.raycastInfo.worldPoint.z, hitPoint.z, epsilon));
// Correct category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY2);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY1);
test(!callback.isHit);
// CollisionBody::raycast()
RaycastInfo raycastInfo2;
test(mConvexMeshBody->raycast(ray, raycastInfo2));
@ -1389,6 +1456,16 @@ class TestRaycast : public Test {
test(approxEqual(callback.raycastInfo.worldPoint.y, hitPoint.y, epsilon));
test(approxEqual(callback.raycastInfo.worldPoint.z, hitPoint.z, epsilon));
// Correct category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY2);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray, &callback, CATEGORY1);
test(!callback.isHit);
// CollisionBody::raycast()
RaycastInfo raycastInfo2;
test(mCylinderBody->raycast(ray, raycastInfo2));
@ -1604,6 +1681,16 @@ class TestRaycast : public Test {
callback.shapeToTest = mCompoundSphereShape;
// Correct category filter mask
callback.reset();
mWorld->raycast(ray1, &callback, CATEGORY2);
test(callback.isHit);
// Wrong category filter mask
callback.reset();
mWorld->raycast(ray1, &callback, CATEGORY1);
test(!callback.isHit);
RaycastInfo raycastInfo;
test(mCompoundBody->raycast(ray1, raycastInfo));
callback.reset();