Browse Source

Added BodyInterface::SetUseManifoldReduction function (#782)

This will clear the contact cache and ensure that you get consistent contact callbacks in case the body that you're changing already has contacts.

Should fix #780
Jorrit Rouwe 1 year ago
parent
commit
1939b95491

+ 1 - 0
Docs/ReleaseNotes.md

@@ -4,6 +4,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 
 
 ## Unreleased changes
 ## Unreleased changes
 
 
+* Added BodyInterface::SetUseManifoldReduction which will clear the contact cache and ensure that you get consistent contact callbacks in case the body that you're changing already has contacts.
 * Created implementations of BroadPhaseLayerInterface, ObjectVsBroadPhaseLayerFilter and ObjectLayerPairFilter that use a bit table internally. These make it easier to define ObjectLayers which object layers collide.
 * Created implementations of BroadPhaseLayerInterface, ObjectVsBroadPhaseLayerFilter and ObjectLayerPairFilter that use a bit table internally. These make it easier to define ObjectLayers which object layers collide.
 * Support for compiling with ninja on Windows.
 * Support for compiling with ninja on Windows.
 * Added wheel index and friction direction to VehicleConstraint::CombineFunction friction callback so you can have more differentiation between wheels.
 * Added wheel index and friction direction to VehicleConstraint::CombineFunction friction callback so you can have more differentiation between wheels.

+ 4 - 1
Jolt/Physics/Body/Body.h

@@ -88,6 +88,7 @@ public:
 	/// If PhysicsSettings::mUseManifoldReduction is true, this allows turning off manifold reduction for this specific body.
 	/// If PhysicsSettings::mUseManifoldReduction is true, this allows turning off manifold reduction for this specific body.
 	/// Manifold reduction by default will combine contacts with similar normals that come from different SubShapeIDs (e.g. different triangles in a mesh shape or different compound shapes).
 	/// Manifold reduction by default will combine contacts with similar normals that come from different SubShapeIDs (e.g. different triangles in a mesh shape or different compound shapes).
 	/// If the application requires tracking exactly which SubShapeIDs are in contact, you can turn off manifold reduction. Note that this comes at a performance cost.
 	/// If the application requires tracking exactly which SubShapeIDs are in contact, you can turn off manifold reduction. Note that this comes at a performance cost.
+	/// Consider using BodyInterface::SetUseManifoldReduction if the body could already be in contact with other bodies to ensure that the contact cache is invalidated and you get the correct contact callbacks.
 	inline void				SetUseManifoldReduction(bool inUseReduction)					{ JPH_ASSERT(IsRigidBody()); if (inUseReduction) mFlags.fetch_or(uint8(EFlags::UseManifoldReduction), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::UseManifoldReduction)), memory_order_relaxed); }
 	inline void				SetUseManifoldReduction(bool inUseReduction)					{ JPH_ASSERT(IsRigidBody()); if (inUseReduction) mFlags.fetch_or(uint8(EFlags::UseManifoldReduction), memory_order_relaxed); else mFlags.fetch_and(uint8(~uint8(EFlags::UseManifoldReduction)), memory_order_relaxed); }
 
 
 	/// Check if this body can use manifold reduction.
 	/// Check if this body can use manifold reduction.
@@ -96,8 +97,10 @@ public:
 	/// Checks if the combination of this body and inBody2 should use manifold reduction
 	/// Checks if the combination of this body and inBody2 should use manifold reduction
 	inline bool				GetUseManifoldReductionWithBody(const Body &inBody2) const		{ return ((mFlags.load(memory_order_relaxed) & inBody2.mFlags.load(memory_order_relaxed)) & uint8(EFlags::UseManifoldReduction)) != 0; }
 	inline bool				GetUseManifoldReductionWithBody(const Body &inBody2) const		{ return ((mFlags.load(memory_order_relaxed) & inBody2.mFlags.load(memory_order_relaxed)) & uint8(EFlags::UseManifoldReduction)) != 0; }
 
 
-	/// Motion type of this body
+	/// Get the bodies motion type.
 	inline EMotionType		GetMotionType() const											{ return mMotionType; }
 	inline EMotionType		GetMotionType() const											{ return mMotionType; }
+
+	/// Set the motion type of this body. Consider using BodyInterface::SetMotionType instead of this function if the body may be active or if it needs to be activated.
 	void					SetMotionType(EMotionType inMotionType);
 	void					SetMotionType(EMotionType inMotionType);
 
 
 	/// Get broadphase layer, this determines in which broad phase sub-tree the object is placed
 	/// Get broadphase layer, this determines in which broad phase sub-tree the object is placed

+ 25 - 0
Jolt/Physics/Body/BodyInterface.cpp

@@ -933,6 +933,31 @@ float BodyInterface::GetGravityFactor(const BodyID &inBodyID) const
 		return 1.0f;
 		return 1.0f;
 }
 }
 
 
+void BodyInterface::SetUseManifoldReduction(const BodyID &inBodyID, bool inUseReduction)
+{
+	BodyLockWrite lock(*mBodyLockInterface, inBodyID);
+	if (lock.Succeeded())
+	{
+		Body &body = lock.GetBody();
+		if (body.GetUseManifoldReduction() != inUseReduction)
+		{
+			body.SetUseManifoldReduction(inUseReduction);
+
+			// Flag collision cache invalid for this body
+			mBodyManager->InvalidateContactCacheForBody(body);
+		}
+	}
+}
+
+bool BodyInterface::GetUseManifoldReduction(const BodyID &inBodyID) const
+{
+	BodyLockRead lock(*mBodyLockInterface, inBodyID);
+	if (lock.Succeeded())
+		return lock.GetBody().GetUseManifoldReduction();
+	else
+		return true;
+}
+
 TransformedShape BodyInterface::GetTransformedShape(const BodyID &inBodyID) const
 TransformedShape BodyInterface::GetTransformedShape(const BodyID &inBodyID) const
 {
 {
 	BodyLockRead lock(*mBodyLockInterface, inBodyID);
 	BodyLockRead lock(*mBodyLockInterface, inBodyID);

+ 6 - 0
Jolt/Physics/Body/BodyInterface.h

@@ -250,6 +250,12 @@ public:
 	float						GetGravityFactor(const BodyID &inBodyID) const;
 	float						GetGravityFactor(const BodyID &inBodyID) const;
 	///@}
 	///@}
 
 
+	///@name Manifold reduction
+	///@{
+	void						SetUseManifoldReduction(const BodyID &inBodyID, bool inUseReduction);
+	bool						GetUseManifoldReduction(const BodyID &inBodyID) const;
+	///@}
+
 	/// Get transform and shape for this body, used to perform collision detection
 	/// Get transform and shape for this body, used to perform collision detection
 	TransformedShape			GetTransformedShape(const BodyID &inBodyID) const;
 	TransformedShape			GetTransformedShape(const BodyID &inBodyID) const;
 
 

+ 21 - 0
UnitTests/LoggingContactListener.h

@@ -108,6 +108,27 @@ public:
 		return Find(inType, inBody1, inBody2) >= 0;
 		return Find(inType, inBody1, inBody2) >= 0;
 	}
 	}
 
 
+	// Find first event with a particular type and involving two particular bodies and sub shape IDs
+	int								Find(EType inType, const BodyID &inBody1, const SubShapeID &inSubShapeID1, const BodyID &inBody2, const SubShapeID &inSubShapeID2) const
+	{
+		for (size_t i = 0; i < mLog.size(); ++i)
+		{
+			const LogEntry &e = mLog[i];
+			if (e.mType == inType
+				&& ((e.mBody1 == inBody1 && e.mManifold.mSubShapeID1 == inSubShapeID1 && e.mBody2 == inBody2 && e.mManifold.mSubShapeID2 == inSubShapeID2)
+					|| (e.mBody1 == inBody2 && e.mManifold.mSubShapeID1 == inSubShapeID2 && e.mBody2 == inBody1 && e.mManifold.mSubShapeID2 == inSubShapeID1)))
+				return int(i);
+		}
+
+		return -1;
+	}
+
+	// Check if event with a particular type and involving two particular bodies and sub shape IDs exists
+	bool							Contains(EType inType, const BodyID &inBody1, const SubShapeID &inSubShapeID1, const BodyID &inBody2, const SubShapeID &inSubShapeID2) const
+	{
+		return Find(inType, inBody1, inSubShapeID1, inBody2, inSubShapeID2) >= 0;
+	}
+
 private:
 private:
 	Mutex							mLogMutex; // Callbacks are made from a thread, make sure we don't corrupt the log
 	Mutex							mLogMutex; // Callbacks are made from a thread, make sure we don't corrupt the log
 	Array<LogEntry>					mLog;
 	Array<LogEntry>					mLog;

+ 70 - 0
UnitTests/Physics/PhysicsTests.cpp

@@ -10,6 +10,7 @@
 #include <Jolt/Physics/Collision/Shape/BoxShape.h>
 #include <Jolt/Physics/Collision/Shape/BoxShape.h>
 #include <Jolt/Physics/Collision/Shape/SphereShape.h>
 #include <Jolt/Physics/Collision/Shape/SphereShape.h>
 #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
 #include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
+#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
 #include <Jolt/Physics/Body/BodyLockMulti.h>
 #include <Jolt/Physics/Body/BodyLockMulti.h>
 #include <Jolt/Physics/Constraints/PointConstraint.h>
 #include <Jolt/Physics/Constraints/PointConstraint.h>
 #include <Jolt/Physics/StateRecorderImpl.h>
 #include <Jolt/Physics/StateRecorderImpl.h>
@@ -1682,4 +1683,73 @@ TEST_SUITE("PhysicsTests")
 			CHECK(!sphere2.IsActive());
 			CHECK(!sphere2.IsActive());
 		}
 		}
 	}
 	}
+
+	// This tests that when switching UseManifoldReduction on/off we get the correct contact callbacks
+	TEST_CASE("TestSwitchUseManifoldReduction")
+	{
+		PhysicsTestContext c;
+
+		// Install listener
+		LoggingContactListener contact_listener;
+		c.GetSystem()->SetContactListener(&contact_listener);
+
+		// Create floor
+		Body &floor = c.CreateFloor();
+
+		// Create a compound with 4 boxes
+		Ref<BoxShape> box_shape = new BoxShape(Vec3::sReplicate(2));
+		Ref<StaticCompoundShapeSettings> shape_settings = new StaticCompoundShapeSettings();
+		shape_settings->AddShape(Vec3(5, 0, 0), Quat::sIdentity(), box_shape);
+		shape_settings->AddShape(Vec3(-5, 0, 0), Quat::sIdentity(), box_shape);
+		shape_settings->AddShape(Vec3(0, 0, 5), Quat::sIdentity(), box_shape);
+		shape_settings->AddShape(Vec3(0, 0, -5), Quat::sIdentity(), box_shape);
+		RefConst<StaticCompoundShape> compound_shape = static_cast<const StaticCompoundShape *>(shape_settings->Create().Get().GetPtr());
+		SubShapeID sub_shape_ids[] = {
+			compound_shape->GetSubShapeIDFromIndex(0, SubShapeIDCreator()).GetID(),
+			compound_shape->GetSubShapeIDFromIndex(1, SubShapeIDCreator()).GetID(),
+			compound_shape->GetSubShapeIDFromIndex(2, SubShapeIDCreator()).GetID(),
+			compound_shape->GetSubShapeIDFromIndex(3, SubShapeIDCreator()).GetID()
+		};
+
+		// Embed body a little bit into the floor so we immediately get contact callbacks
+		BodyCreationSettings body_settings(compound_shape, RVec3(0, 1.99_r, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
+		body_settings.mUseManifoldReduction = true;
+		BodyID body_id = c.GetBodyInterface().CreateAndAddBody(body_settings, EActivation::Activate);
+
+		// Trigger contact callbacks
+		c.SimulateSingleStep();
+
+		// Since manifold reduction is on and the contacts will be coplanar we should only get 1 contact with the floor
+		// Note that which sub shape ID we get is deterministic but not guaranteed to be a particular value, sub_shape_ids[3] is the one it currently returns!!
+		CHECK(contact_listener.GetEntryCount() == 5); // 4x validate + 1x add
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Add, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[3]));
+		contact_listener.Clear();
+
+		// Now disable manifold reduction
+		c.GetBodyInterface().SetUseManifoldReduction(body_id, false);
+
+		// Trigger contact callbacks
+		c.SimulateSingleStep();
+
+		// Now manifold reduction is off so we should get collisions with each of the sub shapes
+		CHECK(contact_listener.GetEntryCount() == 8); // 4x validate + 1x persist + 3x add
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Persist, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[3]));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Add, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[0]));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Add, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[1]));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Add, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[2]));
+		contact_listener.Clear();
+
+		// Now enable manifold reduction again
+		c.GetBodyInterface().SetUseManifoldReduction(body_id, true);
+
+		// Trigger contact callbacks
+		c.SimulateSingleStep();
+
+		// We should be back to the first state now where we only have 1 contact
+		CHECK(contact_listener.GetEntryCount() == 8); // 4x validate + 1x persist + 3x remove
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Persist, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[3]));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Remove, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[0]));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Remove, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[1]));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Remove, floor.GetID(), SubShapeID(), body_id, sub_shape_ids[2]));
+	}
 }
 }