From c15b83db4a738cd9cce699e3cf907efb50592ea8 Mon Sep 17 00:00:00 2001 From: Daniel Chappuis Date: Wed, 31 Dec 2014 01:19:14 +0100 Subject: [PATCH] Add collision and raycast filtering using bits mask --- src/collision/CollisionDetection.cpp | 32 ++++--- src/collision/CollisionDetection.h | 8 +- src/collision/ProxyShape.cpp | 3 +- src/collision/ProxyShape.h | 45 ++++++++++ .../broadphase/BroadPhaseAlgorithm.h | 8 +- src/collision/broadphase/DynamicAABBTree.cpp | 55 ++++++------ src/collision/broadphase/DynamicAABBTree.h | 3 +- src/engine/CollisionWorld.h | 8 +- test/tests/collision/TestCollisionWorld.h | 64 +++++++++++++- test/tests/collision/TestRaycast.h | 87 +++++++++++++++++++ 10 files changed, 263 insertions(+), 50 deletions(-) diff --git a/src/collision/CollisionDetection.cpp b/src/collision/CollisionDetection.cpp index ba0c77a9..f550e6c2 100644 --- a/src/collision/CollisionDetection.cpp +++ b/src/collision/CollisionDetection.cpp @@ -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::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::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::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 diff --git a/src/collision/CollisionDetection.h b/src/collision/CollisionDetection.h index 5e851ffc..443874e8 100644 --- a/src/collision/CollisionDetection.h +++ b/src/collision/CollisionDetection.h @@ -153,7 +153,8 @@ class CollisionDetection { const std::set& 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 diff --git a/src/collision/ProxyShape.cpp b/src/collision/ProxyShape.cpp index 81e0d322..d0f90ae3 100644 --- a/src/collision/ProxyShape.cpp +++ b/src/collision/ProxyShape.cpp @@ -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) { } diff --git a/src/collision/ProxyShape.h b/src/collision/ProxyShape.h index 3f498178..25be73ab 100644 --- a/src/collision/ProxyShape.h +++ b/src/collision/ProxyShape.h @@ -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 diff --git a/src/collision/broadphase/BroadPhaseAlgorithm.h b/src/collision/broadphase/BroadPhaseAlgorithm.h index da9c6579..1227b750 100644 --- a/src/collision/broadphase/BroadPhaseAlgorithm.h +++ b/src/collision/broadphase/BroadPhaseAlgorithm.h @@ -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); } } diff --git a/src/collision/broadphase/DynamicAABBTree.cpp b/src/collision/broadphase/DynamicAABBTree.cpp index 886d4279..ede50bf6 100644 --- a/src/collision/broadphase/DynamicAABBTree.cpp +++ b/src/collision/broadphase/DynamicAABBTree.cpp @@ -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 diff --git a/src/collision/broadphase/DynamicAABBTree.h b/src/collision/broadphase/DynamicAABBTree.h index 49591632..332493e3 100644 --- a/src/collision/broadphase/DynamicAABBTree.h +++ b/src/collision/broadphase/DynamicAABBTree.h @@ -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 diff --git a/src/engine/CollisionWorld.h b/src/engine/CollisionWorld.h index 69495c37..8a89ac35 100644 --- a/src/engine/CollisionWorld.h +++ b/src/engine/CollisionWorld.h @@ -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::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 diff --git a/test/tests/collision/TestCollisionWorld.h b/test/tests/collision/TestCollisionWorld.h index 465d1493..230e14dc 100644 --- a/test/tests/collision/TestCollisionWorld.h +++ b/test/tests/collision/TestCollisionWorld.h @@ -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); } }; diff --git a/test/tests/collision/TestRaycast.h b/test/tests/collision/TestRaycast.h index b89b4264..8ce0907f 100644 --- a/test/tests/collision/TestRaycast.h +++ b/test/tests/collision/TestRaycast.h @@ -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();