From 2c1864da9498733f8c549716abd7fa74c1c391d6 Mon Sep 17 00:00:00 2001 From: Jorrit Rouwe Date: Fri, 17 Jan 2025 17:07:23 +0100 Subject: [PATCH] An empty MutableCompoundShape now returns the same local bounding box as EmptyShape (a point at (0, 0, 0)). (#1461) This prevents floating point overflow exceptions. --- Docs/ReleaseNotes.md | 1 + .../Collision/Shape/MutableCompoundShape.cpp | 4 +- .../Physics/MutableCompoundShapeTests.cpp | 38 ++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Docs/ReleaseNotes.md b/Docs/ReleaseNotes.md index ed374181b..178edd64e 100644 --- a/Docs/ReleaseNotes.md +++ b/Docs/ReleaseNotes.md @@ -36,6 +36,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi * Fixed CharacterVirtual::Contact::mHadContact not being true for collisions with sensors. They will still be marked as mWasDiscarded to prevent any further interaction. * Fixed numerical inaccuracy in penetration depth calculation when CollideShapeSettings::mMaxSeparationDistance was set to a really high value (e.g. 1000). * Bugfix in Semaphore::Acquire for non-windows platform. The count was updated before waiting, meaning that the counter would become -(number of waiting threads) and the semaphore would not wake up until at least the same amount of releases was done. In practice this meant that the main thread had to do the last (number of threads) jobs, slowing down the simulation a bit. +* An empty MutableCompoundShape now returns the same local bounding box as EmptyShape (a point at (0, 0, 0)). This prevents floating point overflow exceptions. ## v5.2.0 diff --git a/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp b/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp index 696223fc0..16155bb22 100644 --- a/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp +++ b/Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp @@ -142,8 +142,8 @@ void MutableCompoundShape::CalculateLocalBounds() } else { - // There are no subshapes, set the bounding box to invalid - mLocalBounds.SetEmpty(); + // There are no subshapes, make the bounding box empty + mLocalBounds.mMin = mLocalBounds.mMax = Vec3::sZero(); } // Cache the inner radius as it can take a while to recursively iterate over all sub shapes diff --git a/UnitTests/Physics/MutableCompoundShapeTests.cpp b/UnitTests/Physics/MutableCompoundShapeTests.cpp index 8b336fda1..7dc6293ba 100644 --- a/UnitTests/Physics/MutableCompoundShapeTests.cpp +++ b/UnitTests/Physics/MutableCompoundShapeTests.cpp @@ -9,6 +9,7 @@ #include #include #include +#include TEST_SUITE("MutableCompoundShapeTests") { @@ -117,7 +118,7 @@ TEST_SUITE("MutableCompoundShapeTests") shape->RemoveShape(0); CHECK(shape->GetNumSubShapes() == 0); - CHECK(!shape->GetLocalBounds().IsValid()); + CHECK(shape->GetLocalBounds() == AABox(Vec3::sZero(), Vec3::sZero())); CHECK(check_shape_hit(Vec3::sZero()) == nullptr); CHECK(check_shape_hit(Vec3(10, 0, 0)) == nullptr); CHECK(check_shape_hit(Vec3(15, 0, 0)) == nullptr); @@ -170,4 +171,39 @@ TEST_SUITE("MutableCompoundShapeTests") CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2)); collector.Reset(); } + + TEST_CASE("TestEmptyMutableCompoundShape") + { + // Create an empty compound shape + PhysicsTestContext c; + MutableCompoundShapeSettings settings; + Ref shape = StaticCast(settings.Create().Get()); + BodyCreationSettings bcs(shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING); + bcs.mLinearDamping = 0.0f; + bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided; + bcs.mMassPropertiesOverride.mMass = 1.0f; + bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity(); + BodyID body_id = c.GetBodyInterface().CreateAndAddBody(bcs, EActivation::Activate); + + // Simulate with empty shape + c.Simulate(1.0f); + RVec3 expected_pos = c.PredictPosition(RVec3::sZero(), Vec3::sZero(), c.GetSystem()->GetGravity(), 1.0f); + CHECK_APPROX_EQUAL(c.GetBodyInterface().GetPosition(body_id), expected_pos); + + // Check that we can't hit the shape + Ref box_shape = new BoxShape(Vec3::sReplicate(10000)); + AllHitCollisionCollector collector; + c.GetSystem()->GetNarrowPhaseQuery().CollideShape(box_shape, Vec3::sOne(), RMat44::sIdentity(), CollideShapeSettings(), RVec3::sZero(), collector); + CHECK(collector.mHits.empty()); + + // Add a box to the compound shape + Vec3 com = shape->GetCenterOfMass(); + shape->AddShape(Vec3::sZero(), Quat::sIdentity(), new BoxShape(Vec3::sOne())); + c.GetBodyInterface().NotifyShapeChanged(body_id, com, false, EActivation::DontActivate); + + // Check that we can now hit the shape + c.GetSystem()->GetNarrowPhaseQuery().CollideShape(box_shape, Vec3::sOne(), RMat44::sIdentity(), CollideShapeSettings(), RVec3::sZero(), collector); + CHECK(collector.mHits.size() == 1); + CHECK(collector.mHits[0].mBodyID2 == body_id); + } }