Skip to content

Commit

Permalink
Added BodyInterface::SetUseManifoldReduction which will clear the con…
Browse files Browse the repository at this point in the history
…tact cache and ensure that you get consistent contact callbacks in case the body that you're changing already has contacts

Should fix #780
  • Loading branch information
jrouwe committed Dec 4, 2023
1 parent a5f498f commit e045358
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 1 deletion.
1 change: 1 addition & 0 deletions Docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion Jolt/Physics/Body/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
25 changes: 25 additions & 0 deletions Jolt/Physics/Body/BodyInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions Jolt/Physics/Body/BodyInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
21 changes: 21 additions & 0 deletions UnitTests/LoggingContactListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<LogEntry> mLog;
Expand Down
70 changes: 70 additions & 0 deletions UnitTests/Physics/PhysicsTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
#include <Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h>
#include <Jolt/Physics/Collision/Shape/StaticCompoundShape.h>
#include <Jolt/Physics/Body/BodyLockMulti.h>
#include <Jolt/Physics/Constraints/PointConstraint.h>
#include <Jolt/Physics/StateRecorderImpl.h>
Expand Down Expand Up @@ -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<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]));
}
}

0 comments on commit e045358

Please sign in to comment.