Browse Source

Bugfix: Simulation ShapeFilter had a race condition (#1364)

Created a separate interface SimShapeFilter that receives the bodies as input instead of it being members (which is what caused the race condition since the shape filter was being called from multiple threads)
Jorrit Rouwe 8 months ago
parent
commit
efd6a5cbde

+ 1 - 1
Docs/Architecture.md

@@ -493,7 +493,7 @@ Now that we know about the basics, we list the order in which the collision dete
 * Object layer: Once the broad phase layer test succeeds, we will test object layers vs object layers through [ObjectLayerPairFilter](@ref ObjectLayerPairFilter) (used for simulation) and [ObjectLayerFilter](@ref ObjectLayerFilter) (used for collision queries). The default implementation of ObjectLayerFilter is DefaultObjectLayerFilter and uses ObjectLayerPairFilter so the behavior is consistent between simulation and collision queries.
 * [GroupFilter](@ref GroupFilter): Used only during simulation and runs after bounding boxes have found to be overlapping. Allows you fine tune collision e.g. by discarding collisions between bodies connected by a constraint. See e.g. [GroupFilterTable](@ref GroupFilterTable) which implements filtering for bodies within a ragdoll.
 * [BodyFilter](@ref BodyFilter): This filter is used instead of the group filter if you do collision queries like CastRay.
-* [ShapeFilter](@ref ShapeFilter): This filter is used both during queries and simulation and can be used to filter out individual shapes of a compound. To set the shape filter for the simulation use PhysicsSystem::SetSimulationShapeFilter.
+* Shape filter: This filter is used both during queries ([ShapeFilter](@ref ShapeFilter)) and simulation ([SimShapeFilter](@ref SimShapeFilter)) and can be used to filter out individual shapes of a compound. To set the shape filter for the simulation use PhysicsSystem::SetSimShapeFilter.
 * [ContactListener](@ref ContactListener): During simulation, after all collision detection work has been performed you can still choose to discard a contact point. This is a very expensive way of rejecting collisions as most of the work is already done.
 
 To avoid work, try to filter out collisions as early as possible.

+ 1 - 1
Docs/ReleaseNotes.md

@@ -11,7 +11,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 * Added MotionProperties::ScaleToMass. This lets you easily change the mass and inertia tensor of a body after creation.
 * Split up Body::ApplyBuoyancyImpulse into Body::GetSubmergedVolume and Body::ApplyBuoyancyImpulse. This allows you to use the calculated submerged volume for other purposes.
 * Fixed a number of issues when creating very large MeshShapes. MeshShapes of up to 110M triangles are possible now, but the actual maximum is very dependent on how the triangles in the mesh are connected.
-* Added PhysicsSystem::SetSimulationShapeFilter. This allows filtering out collisions between sub shapes within a body and can for example be used to have a single body that contains a low detail simulation shape an a high detail collision query shape.
+* Added PhysicsSystem::SetSimShapeFilter. This allows filtering out collisions between sub shapes within a body and can for example be used to have a single body that contains a low detail simulation shape an a high detail collision query shape.
 
 ### Bug fixes
 

+ 1 - 0
Jolt/Jolt.cmake

@@ -290,6 +290,7 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Physics/Collision/Shape/TriangleShape.h
 	${JOLT_PHYSICS_ROOT}/Physics/Collision/ShapeCast.h
 	${JOLT_PHYSICS_ROOT}/Physics/Collision/ShapeFilter.h
+	${JOLT_PHYSICS_ROOT}/Physics/Collision/SimShapeFilter.h
 	${JOLT_PHYSICS_ROOT}/Physics/Collision/SortReverseAndStore.h
 	${JOLT_PHYSICS_ROOT}/Physics/Collision/TransformedShape.cpp
 	${JOLT_PHYSICS_ROOT}/Physics/Collision/TransformedShape.h

+ 3 - 18
Jolt/Physics/Collision/ShapeFilter.h

@@ -33,7 +33,7 @@ public:
 	/// It is called at each level of the shape hierarchy, so if you have a compound shape with a box, this function will be called twice.
 	/// It will not be called on triangles that are part of another shape, i.e a mesh shape will not trigger a callback per triangle. You can filter out individual triangles in the CollisionCollector::AddHit function by their sub shape ID.
 	/// @param inShape1 1st shape that is colliding
-	/// @param inSubShapeIDOfShape1 The sub shape ID that will lead from the root shape to inShape1 (i.e. the shape that is used to collide or cast against shape 2 or the shape of mBodyID1)
+	/// @param inSubShapeIDOfShape1 The sub shape ID that will lead from the root shape to inShape1 (i.e. the shape that is used to collide or cast against shape 2)
 	/// @param inShape2 2nd shape that is colliding
 	/// @param inSubShapeIDOfShape2 The sub shape ID that will lead from the root shape to inShape2 (i.e. the shape of mBodyID2)
 	virtual bool			ShouldCollide([[maybe_unused]] const Shape *inShape1, [[maybe_unused]] const SubShapeID &inSubShapeIDOfShape1, [[maybe_unused]] const Shape *inShape2, [[maybe_unused]] const SubShapeID &inSubShapeIDOfShape2) const
@@ -41,11 +41,7 @@ public:
 		return true;
 	}
 
-	/// Used during PhysicsSystem::Update only. Set to the body ID of inShape1 before calling ShouldCollide.
-	/// Provides context to the filter to indicate which body is colliding.
-	mutable BodyID			mBodyID1;
-
-	/// Used during PhysicsSystem::Update, NarrowPhase queries and TransformedShape queries. Set to the body ID of inShape2 before calling ShouldCollide.
+	/// Used during NarrowPhase queries and TransformedShape queries. Set to the body ID of inShape2 before calling ShouldCollide.
 	/// Provides context to the filter to indicate which body is colliding.
 	mutable BodyID			mBodyID2;
 };
@@ -57,18 +53,7 @@ public:
 	/// Constructor
 	explicit				ReversedShapeFilter(const ShapeFilter &inFilter) : mFilter(inFilter)
 	{
-		if (inFilter.mBodyID1.IsInvalid())
-		{
-			// If body 1 is not set then we're coming from a regular query and we should not swap the bodies
-			// because conceptually we're still colliding a shape against a body and not a body against a body.
-			mBodyID2 = inFilter.mBodyID2;
-		}
-		else
-		{
-			// If both bodies have been filled in then we swap the bodies
-			mBodyID1 = inFilter.mBodyID2;
-			mBodyID2 = inFilter.mBodyID1;
-		}
+		mBodyID2 = inFilter.mBodyID2;
 	}
 
 	virtual bool			ShouldCollide(const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override

+ 40 - 0
Jolt/Physics/Collision/SimShapeFilter.h

@@ -0,0 +1,40 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Core/NonCopyable.h>
+
+JPH_NAMESPACE_BEGIN
+
+class Body;
+class Shape;
+class SubShapeID;
+
+/// Filter class used during the simulation (PhysicsSystem::Update) to filter out collisions at shape level
+class SimShapeFilter : public NonCopyable
+{
+public:
+	/// Destructor
+	virtual					~SimShapeFilter() = default;
+
+	/// Filter function to determine if two shapes should collide. Returns true if the filter passes.
+	/// This overload is called during the simulation (PhysicsSystem::Update) and must be registered with PhysicsSystem::SetSimShapeFilter.
+	/// It is called at each level of the shape hierarchy, so if you have a compound shape with a box, this function will be called twice.
+	/// It will not be called on triangles that are part of another shape, i.e a mesh shape will not trigger a callback per triangle.
+	/// Note that this function is called from multiple threads and must be thread safe. All properties are read only.
+	/// @param inBody1 1st body that is colliding
+	/// @param inShape1 1st shape that is colliding
+	/// @param inSubShapeIDOfShape1 The sub shape ID that will lead from inBody1.GetShape() to inShape1
+	/// @param inBody2 2nd body that is colliding
+	/// @param inShape2 2nd shape that is colliding
+	/// @param inSubShapeIDOfShape2 The sub shape ID that will lead from inBody2.GetShape() to inShape2
+	virtual bool			ShouldCollide([[maybe_unused]] const Body &inBody1, [[maybe_unused]] const Shape *inShape1, [[maybe_unused]] const SubShapeID &inSubShapeIDOfShape1,
+										  [[maybe_unused]] const Body &inBody2, [[maybe_unused]] const Shape *inShape2, [[maybe_unused]] const SubShapeID &inSubShapeIDOfShape2) const
+	{
+		return true;
+	}
+};
+
+JPH_NAMESPACE_END

+ 77 - 10
Jolt/Physics/PhysicsSystem.cpp

@@ -19,6 +19,7 @@
 #include <Jolt/Physics/Collision/CollideConvexVsTriangles.h>
 #include <Jolt/Physics/Collision/ManifoldBetweenTwoFaces.h>
 #include <Jolt/Physics/Collision/Shape/ConvexShape.h>
+#include <Jolt/Physics/Collision/SimShapeFilter.h>
 #include <Jolt/Physics/Collision/InternalEdgeRemovingCollector.h>
 #include <Jolt/Physics/Constraints/CalculateSolverSteps.h>
 #include <Jolt/Physics/Constraints/ConstraintPart/AxisConstraintPart.h>
@@ -966,6 +967,74 @@ void PhysicsSystem::JobFindCollisions(PhysicsUpdateContext::Step *ioStep, int in
 	}
 }
 
+// Helper class to forward ShapeFilter calls to the simulation shape filter
+class SimShapeFilterWrapper : public ShapeFilter
+{
+public:
+	// Constructor
+							SimShapeFilterWrapper(const SimShapeFilter *inFilter, const Body *inBody1) :
+		mFilter(inFilter),
+		mBody1(inBody1)
+	{
+	}
+
+	// Forward to the simulation shape filter
+	virtual bool			ShouldCollide(const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override
+	{
+		return mFilter->ShouldCollide(*mBody1, inShape1, inSubShapeIDOfShape1, *mBody2, inShape2, inSubShapeIDOfShape2);
+	}
+
+	// Set the body we're colliding against
+	void					SetBody2(const Body *inBody2)
+	{
+		mBody2 = inBody2;
+	}
+
+private:
+	// Dummy implemention to satisfy the ShapeFilter interface
+	virtual bool			ShouldCollide(const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override
+	{
+		JPH_ASSERT(false); // Should not be called
+		return true;
+	}
+
+	const SimShapeFilter *	mFilter;
+	const Body *			mBody1;
+	const Body *			mBody2;
+};
+
+// In case we don't have a simulation shape filter, we fall back to using a default shape filter that always returns true
+union SimShapeFilterWrapperUnion
+{
+public:
+	// Constructor
+							SimShapeFilterWrapperUnion(const SimShapeFilter *inFilter, const Body *inBody1)
+	{
+		// Dirty trick: if we don't have a filter, placement new a standard ShapeFilter so that we
+		// don't have to check for nullptr in the ShouldCollide function
+		if (inFilter != nullptr)
+			new (&mSimShapeFilterWrapper) SimShapeFilterWrapper(inFilter, inBody1);
+		else
+			new (&mSimShapeFilterWrapper) ShapeFilter();
+	}
+
+	// Destructor
+							~SimShapeFilterWrapperUnion()
+	{
+		// Doesn't need to be destructed
+	}
+
+	// Accessor
+	SimShapeFilterWrapper &	GetSimShapeFilterWrapper()
+	{
+		return mSimShapeFilterWrapper;
+	}
+
+private:
+	SimShapeFilterWrapper	mSimShapeFilterWrapper;
+	ShapeFilter				mShapeFilter;
+};
+
 void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const BodyPair &inBodyPair)
 {
 	JPH_PROFILE_FUNCTION();
@@ -1020,10 +1089,9 @@ void PhysicsSystem::ProcessBodyPair(ContactAllocator &ioContactAllocator, const
 		settings.mActiveEdgeMovementDirection = body1->GetLinearVelocity() - body2->GetLinearVelocity();
 
 		// Create shape filter
-		ShapeFilter default_shape_filter;
-		const ShapeFilter &shape_filter = mSimulationShapeFilter != nullptr? *mSimulationShapeFilter : default_shape_filter;
-		shape_filter.mBodyID1 = body1->GetID();
-		shape_filter.mBodyID2 = body2->GetID();
+		SimShapeFilterWrapperUnion shape_filter_union(mSimShapeFilter, body1);
+		SimShapeFilterWrapper &shape_filter = shape_filter_union.GetSimShapeFilterWrapper();
+		shape_filter.SetBody2(body2);
 
 		// Get transforms relative to body1
 		RVec3 offset = body1->GetCenterOfMassPosition();
@@ -1820,7 +1888,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 		class CCDBroadPhaseCollector : public CastShapeBodyCollector
 		{
 		public:
-										CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, const ShapeFilter &inShapeFilter, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::Step *inStep, float inDeltaTime) :
+										CCDBroadPhaseCollector(const CCDBody &inCCDBody, const Body &inBody1, const RShapeCast &inShapeCast, ShapeCastSettings &inShapeCastSettings, SimShapeFilterWrapper &inShapeFilter, CCDNarrowPhaseCollector &ioCollector, const BodyManager &inBodyManager, PhysicsUpdateContext::Step *inStep, float inDeltaTime) :
 				mCCDBody(inCCDBody),
 				mBody1(inBody1),
 				mBody1Extent(inShapeCast.mShapeWorldBounds.GetExtent()),
@@ -1879,7 +1947,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 				mCollector.mRejectAll = false;
 
 				// Set body ID on shape filter
-				mShapeFilter.mBodyID2 = inResult.mBodyID;
+				mShapeFilter.SetBody2(&body2);
 
 				// Provide direction as hint for the active edges algorithm
 				mShapeCastSettings.mActiveEdgeMovementDirection = direction;
@@ -1898,7 +1966,7 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 			Vec3						mBody1Extent;
 			RShapeCast					mShapeCast;
 			ShapeCastSettings &			mShapeCastSettings;
-			const ShapeFilter &			mShapeFilter;
+			SimShapeFilterWrapper &		mShapeFilter;
 			CCDNarrowPhaseCollector &	mCollector;
 			const BodyManager &			mBodyManager;
 			PhysicsUpdateContext::Step *mStep;
@@ -1906,9 +1974,8 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 		};
 
 		// Create shape filter
-		ShapeFilter default_shape_filter;
-		const ShapeFilter &shape_filter = mSimulationShapeFilter != nullptr? *mSimulationShapeFilter : default_shape_filter;
-		shape_filter.mBodyID1 = ccd_body.mBodyID1;
+		SimShapeFilterWrapperUnion shape_filter_union(mSimShapeFilter, &body);
+		SimShapeFilterWrapper &shape_filter = shape_filter_union.GetSimShapeFilterWrapper();
 
 		// Check if we collide with any other body. Note that we use the non-locking interface as we know the broadphase cannot be modified at this point.
 		RShapeCast shape_cast(body.GetShape(), Vec3::sReplicate(1.0f), body.GetCenterOfMassTransform(), ccd_body.mDeltaPosition);

+ 4 - 3
Jolt/Physics/PhysicsSystem.h

@@ -21,6 +21,7 @@ class StateRecorder;
 class TempAllocator;
 class PhysicsStepListener;
 class SoftBodyContactListener;
+class SimShapeFilter;
 
 /// The main class for the physics system. It contains all rigid bodies and simulates them.
 ///
@@ -72,8 +73,8 @@ public:
 	/// to exclude the high detail collision model when simulating and exclude the low detail collision model when casting rays. Note that in this case
 	/// you would need to pass the inverse of inShapeFilter to the CastRay function. Pass a nullptr to disable the shape filter.
 	/// The PhysicsSystem does not own the ShapeFilter, make sure it stays alive during the lifetime of the PhysicsSystem.
-	void						SetSimulationShapeFilter(const ShapeFilter *inShapeFilter)	{ mSimulationShapeFilter = inShapeFilter; }
-	const ShapeFilter *			GetSimulationShapeFilter() const							{ return mSimulationShapeFilter; }
+	void						SetSimShapeFilter(const SimShapeFilter *inShapeFilter)		{ mSimShapeFilter = inShapeFilter; }
+	const SimShapeFilter *		GetSimShapeFilter() const									{ return mSimShapeFilter; }
 
 	/// Control the main constants of the physics simulation
 	void						SetPhysicsSettings(const PhysicsSettings &inSettings)		{ mPhysicsSettings = inSettings; }
@@ -303,7 +304,7 @@ private:
 	SoftBodyContactListener *	mSoftBodyContactListener = nullptr;
 
 	/// The shape filter that is used to filter out sub shapes during simulation
-	const ShapeFilter *			mSimulationShapeFilter = nullptr;
+	const SimShapeFilter *		mSimShapeFilter = nullptr;
 
 	/// Simulation settings
 	PhysicsSettings				mPhysicsSettings;

+ 2 - 2
Samples/Samples.cmake

@@ -87,8 +87,8 @@ set(SAMPLES_SRC_FILES
 	${SAMPLES_ROOT}/Tests/General/EnhancedInternalEdgeRemovalTest.h
 	${SAMPLES_ROOT}/Tests/General/ShapeFilterTest.cpp
 	${SAMPLES_ROOT}/Tests/General/ShapeFilterTest.h
-	${SAMPLES_ROOT}/Tests/General/SimulationShapeFilterTest.cpp
-	${SAMPLES_ROOT}/Tests/General/SimulationShapeFilterTest.h
+	${SAMPLES_ROOT}/Tests/General/SimShapeFilterTest.cpp
+	${SAMPLES_ROOT}/Tests/General/SimShapeFilterTest.h
 	${SAMPLES_ROOT}/Tests/General/CenterOfMassTest.cpp
 	${SAMPLES_ROOT}/Tests/General/CenterOfMassTest.h
 	${SAMPLES_ROOT}/Tests/General/ChangeMotionQualityTest.cpp

+ 2 - 2
Samples/SamplesApp.cpp

@@ -107,7 +107,7 @@ JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, DynamicMeshTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, TwoDFunnelTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, AllowedDOFsTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, ShapeFilterTest)
-JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, SimulationShapeFilterTest)
+JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, SimShapeFilterTest)
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, GyroscopicForceTest)
 #ifdef JPH_OBJECT_STREAM
 JPH_DECLARE_RTTI_FOR_FACTORY(JPH_NO_EXPORT, LoadSaveSceneTest)
@@ -153,7 +153,7 @@ static TestNameAndRTTI sGeneralTests[] =
 	{ "Dynamic Mesh",						JPH_RTTI(DynamicMeshTest) },
 	{ "Allowed Degrees of Freedom",			JPH_RTTI(AllowedDOFsTest) },
 	{ "Shape Filter (Collision Detection)",	JPH_RTTI(ShapeFilterTest) },
-	{ "Shape Filter (Simulation)",			JPH_RTTI(SimulationShapeFilterTest) },
+	{ "Shape Filter (Simulation)",			JPH_RTTI(SimShapeFilterTest) },
 	{ "Gyroscopic Force",					JPH_RTTI(GyroscopicForceTest) },
 };
 

+ 10 - 10
Samples/Tests/General/SimulationShapeFilterTest.cpp → Samples/Tests/General/SimShapeFilterTest.cpp

@@ -4,7 +4,7 @@
 
 #include <TestFramework.h>
 
-#include <Tests/General/SimulationShapeFilterTest.h>
+#include <Tests/General/SimShapeFilterTest.h>
 #include <Jolt/Physics/Collision/Shape/BoxShape.h>
 #include <Jolt/Physics/Collision/Shape/SphereShape.h>
 #include <Jolt/Physics/Collision/Shape/CapsuleShape.h>
@@ -12,21 +12,21 @@
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 #include <Layers.h>
 
-JPH_IMPLEMENT_RTTI_VIRTUAL(SimulationShapeFilterTest)
+JPH_IMPLEMENT_RTTI_VIRTUAL(SimShapeFilterTest)
 {
-	JPH_ADD_BASE_CLASS(SimulationShapeFilterTest, Test)
+	JPH_ADD_BASE_CLASS(SimShapeFilterTest, Test)
 }
 
-SimulationShapeFilterTest::~SimulationShapeFilterTest()
+SimShapeFilterTest::~SimShapeFilterTest()
 {
 	// Unregister shape filter
-	mPhysicsSystem->SetSimulationShapeFilter(nullptr);
+	mPhysicsSystem->SetSimShapeFilter(nullptr);
 }
 
-void SimulationShapeFilterTest::Initialize()
+void SimShapeFilterTest::Initialize()
 {
 	// Register shape filter
-	mPhysicsSystem->SetSimulationShapeFilter(&mShapeFilter);
+	mPhysicsSystem->SetSimShapeFilter(&mShapeFilter);
 
 	// Floor
 	CreateFloor();
@@ -47,12 +47,12 @@ void SimulationShapeFilterTest::Initialize()
 	mShapeFilter.mCompoundID = mBodyInterface->CreateAndAddBody(BodyCreationSettings(compound, RVec3(0, 15, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
 }
 
-bool SimulationShapeFilterTest::Filter::ShouldCollide(const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const
+bool SimShapeFilterTest::Filter::ShouldCollide(const Body &inBody1, const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Body &inBody2, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const
 {
 	// If the platform is colliding with the compound, filter out collisions where the shape has user data 1
-	if (mBodyID1 == mPlatformID && mBodyID2 == mCompoundID)
+	if (inBody1.GetID() == mPlatformID && inBody2.GetID() == mCompoundID)
 		return inShape2->GetUserData() != 1;
-	else if (mBodyID1 == mCompoundID && mBodyID2 == mPlatformID)
+	else if (inBody1.GetID() == mCompoundID && inBody2.GetID() == mPlatformID)
 		return inShape1->GetUserData() != 1;
 	return true;
 }

+ 34 - 0
Samples/Tests/General/SimShapeFilterTest.h

@@ -0,0 +1,34 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Tests/Test.h>
+#include <Jolt/Physics/Collision/SimShapeFilter.h>
+
+// This test shows how to use a shape filter during the simulation to disable contacts between certain sub shapes
+class SimShapeFilterTest : public Test
+{
+public:
+	JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SimShapeFilterTest)
+
+	// Destructor
+	virtual				~SimShapeFilterTest() override;
+
+	// See: Test
+	virtual void		Initialize() override;
+
+private:
+	// A simulation shape filter
+	class Filter : public SimShapeFilter
+	{
+	public:
+		virtual bool	ShouldCollide(const Body &inBody1, const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Body &inBody2, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override;
+
+		BodyID			mPlatformID;
+		BodyID			mCompoundID;
+	};
+
+	Filter				mShapeFilter;
+};

+ 0 - 36
Samples/Tests/General/SimulationShapeFilterTest.h

@@ -1,36 +0,0 @@
-// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
-// SPDX-FileCopyrightText: 2024 Jorrit Rouwe
-// SPDX-License-Identifier: MIT
-
-#pragma once
-
-#include <Tests/Test.h>
-
-// This test shows how to use a shape filter during the simulation to disable contacts between certain sub shapes
-class SimulationShapeFilterTest : public Test
-{
-public:
-	JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, SimulationShapeFilterTest)
-
-	// Destructor
-	virtual				~SimulationShapeFilterTest() override;
-
-	// See: Test
-	virtual void		Initialize() override;
-
-private:
-	// A demo of the shape filter
-	class Filter : public ShapeFilter
-	{
-	public:
-		virtual bool	ShouldCollide(const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override;
-
-		// We're not interested in the other overload as it is only used by collision queries and not by the simulation
-		using ShapeFilter::ShouldCollide;
-
-		BodyID			mPlatformID;
-		BodyID			mCompoundID;
-	};
-
-	Filter				mShapeFilter;
-};

+ 8 - 10
UnitTests/Physics/ShapeFilterTests.cpp

@@ -9,12 +9,13 @@
 #include <Jolt/Physics/Collision/Shape/BoxShape.h>
 #include <Jolt/Physics/Collision/Shape/SphereShape.h>
 #include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
+#include <Jolt/Physics/Collision/SimShapeFilter.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 
 TEST_SUITE("ShapeFilterTests")
 {
 	// Tests two spheres in one simulated body, one collides with a static platform, the other doesn't
-	TEST_CASE("TestSimulationShapeFilter")
+	TEST_CASE("TestSimShapeFilter")
 	{
 		// Test once per motion quality type
 		for (int q = 0; q < 2; ++q)
@@ -25,28 +26,25 @@ TEST_SUITE("ShapeFilterTests")
 			LoggingContactListener contact_listener;
 			c.GetSystem()->SetContactListener(&contact_listener);
 
-			// Install shape filter
-			class Filter : public ShapeFilter
+			// Install simulation shape filter
+			class Filter : public SimShapeFilter
 			{
 			public:
-				virtual bool	ShouldCollide(const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override
+				virtual bool	ShouldCollide(const Body &inBody1, const Shape *inShape1, const SubShapeID &inSubShapeIDOfShape1, const Body &inBody2, const Shape *inShape2, const SubShapeID &inSubShapeIDOfShape2) const override
 				{
 					// If the platform is colliding with the compound, filter out collisions where the shape has user data 1
-					if (mBodyID1 == mPlatformID && mBodyID2 == mCompoundID)
+					if (inBody1.GetID() == mPlatformID && inBody2.GetID() == mCompoundID)
 						return inShape2->GetUserData() != 1;
-					else if (mBodyID1 == mCompoundID && mBodyID2 == mPlatformID)
+					else if (inBody1.GetID() == mCompoundID && inBody2.GetID() == mPlatformID)
 						return inShape1->GetUserData() != 1;
 					return true;
 				}
 
-				// We're not interested in the other overload as it is only used by collision queries and not by the simulation
-				using ShapeFilter::ShouldCollide;
-
 				BodyID			mPlatformID;
 				BodyID			mCompoundID;
 			};
 			Filter shape_filter;
-			c.GetSystem()->SetSimulationShapeFilter(&shape_filter);
+			c.GetSystem()->SetSimShapeFilter(&shape_filter);
 
 			// Floor
 			BodyID floor_id = c.CreateFloor().GetID();