Browse Source

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.
Jorrit Rouwe 7 months ago
parent
commit
2c1864da94

+ 1 - 0
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 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).
 * 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.
 * 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
 ## v5.2.0
 
 

+ 2 - 2
Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp

@@ -142,8 +142,8 @@ void MutableCompoundShape::CalculateLocalBounds()
 	}
 	}
 	else
 	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
 	// Cache the inner radius as it can take a while to recursively iterate over all sub shapes

+ 37 - 1
UnitTests/Physics/MutableCompoundShapeTests.cpp

@@ -9,6 +9,7 @@
 #include <Jolt/Physics/Collision/Shape/MutableCompoundShape.h>
 #include <Jolt/Physics/Collision/Shape/MutableCompoundShape.h>
 #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
 #include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
 #include <Jolt/Physics/Collision/CollidePointResult.h>
 #include <Jolt/Physics/Collision/CollidePointResult.h>
+#include <Jolt/Physics/Collision/CollideShape.h>
 
 
 TEST_SUITE("MutableCompoundShapeTests")
 TEST_SUITE("MutableCompoundShapeTests")
 {
 {
@@ -117,7 +118,7 @@ TEST_SUITE("MutableCompoundShapeTests")
 
 
 		shape->RemoveShape(0);
 		shape->RemoveShape(0);
 		CHECK(shape->GetNumSubShapes() == 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::sZero()) == nullptr);
 		CHECK(check_shape_hit(Vec3(10, 0, 0)) == nullptr);
 		CHECK(check_shape_hit(Vec3(10, 0, 0)) == nullptr);
 		CHECK(check_shape_hit(Vec3(15, 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));
 		CHECK((collector.mHits.size() == 1 && shape->GetSubShapeUserData(collector.mHits[0].mSubShapeID2) == 2));
 		collector.Reset();
 		collector.Reset();
 	}
 	}
+
+	TEST_CASE("TestEmptyMutableCompoundShape")
+	{
+		// Create an empty compound shape
+		PhysicsTestContext c;
+		MutableCompoundShapeSettings settings;
+		Ref<MutableCompoundShape> shape = StaticCast<MutableCompoundShape>(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<Shape> box_shape = new BoxShape(Vec3::sReplicate(10000));
+		AllHitCollisionCollector<CollideShapeCollector> 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);
+	}
 }
 }