diff --git a/Docs/ReleaseNotes.md b/Docs/ReleaseNotes.md index 2ae6e0395..47fa89236 100644 --- a/Docs/ReleaseNotes.md +++ b/Docs/ReleaseNotes.md @@ -4,6 +4,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi ## 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. * 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. diff --git a/Jolt/Physics/Body/Body.h b/Jolt/Physics/Body/Body.h index 6db0ce9ba..312b4059b 100644 --- a/Jolt/Physics/Body/Body.h +++ b/Jolt/Physics/Body/Body.h @@ -88,6 +88,7 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public /// 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). /// 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); } /// Check if this body can use manifold reduction. @@ -96,8 +97,10 @@ class alignas(JPH_RVECTOR_ALIGNMENT) JPH_EXPORT_GCC_BUG_WORKAROUND Body : public /// 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; } - /// Motion type of this body + /// Get the bodies motion type. 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); /// Get broadphase layer, this determines in which broad phase sub-tree the object is placed diff --git a/Jolt/Physics/Body/BodyInterface.cpp b/Jolt/Physics/Body/BodyInterface.cpp index b870ea6c9..3c98e1fd3 100644 --- a/Jolt/Physics/Body/BodyInterface.cpp +++ b/Jolt/Physics/Body/BodyInterface.cpp @@ -933,6 +933,31 @@ float BodyInterface::GetGravityFactor(const BodyID &inBodyID) const 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 { BodyLockRead lock(*mBodyLockInterface, inBodyID); diff --git a/Jolt/Physics/Body/BodyInterface.h b/Jolt/Physics/Body/BodyInterface.h index 434cae975..a870b4e6a 100644 --- a/Jolt/Physics/Body/BodyInterface.h +++ b/Jolt/Physics/Body/BodyInterface.h @@ -250,6 +250,12 @@ class JPH_EXPORT BodyInterface : public NonCopyable 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 TransformedShape GetTransformedShape(const BodyID &inBodyID) const; diff --git a/UnitTests/LoggingContactListener.h b/UnitTests/LoggingContactListener.h index 9937620bd..44ebf8d64 100644 --- a/UnitTests/LoggingContactListener.h +++ b/UnitTests/LoggingContactListener.h @@ -108,6 +108,27 @@ class LoggingContactListener : public ContactListener 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: Mutex mLogMutex; // Callbacks are made from a thread, make sure we don't corrupt the log Array mLog; diff --git a/UnitTests/Physics/PhysicsTests.cpp b/UnitTests/Physics/PhysicsTests.cpp index b63cc9e11..d87b7c247 100644 --- a/UnitTests/Physics/PhysicsTests.cpp +++ b/UnitTests/Physics/PhysicsTests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -1682,4 +1683,73 @@ TEST_SUITE("PhysicsTests") 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 box_shape = new BoxShape(Vec3::sReplicate(2)); + Ref 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 compound_shape = static_cast(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])); + } }