浏览代码

Automatic determination of convex radius (#1820)

* BoxShape, CylinderShape and TaperedCylinderShape will now automatically reduce the convex radius if the specified value is too big for the shape (instead of erroring out).
* Fixed memory leak when providing invalid parameters to TaperedCylinderShapeSettings and creating the shape.
Jorrit Rouwe 3 周之前
父节点
当前提交
7de8ba3552

+ 2 - 0
Docs/ReleaseNotes.md

@@ -8,6 +8,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 
 
 * Added new define `JPH_TRACK_SIMULATION_STATS` which tracks simulation statistics on a per body basis. This can be used to figure out bodies are the most expensive to simulate.
 * Added new define `JPH_TRACK_SIMULATION_STATS` which tracks simulation statistics on a per body basis. This can be used to figure out bodies are the most expensive to simulate.
 * Added `RagdollSettings::CalculateConstraintPriorities` which calculates constraint priorities that boost the priority of joints towards the root of the ragdoll.
 * Added `RagdollSettings::CalculateConstraintPriorities` which calculates constraint priorities that boost the priority of joints towards the root of the ragdoll.
+* BoxShape, CylinderShape and TaperedCylinderShape will now automatically reduce the convex radius if the specified value is too big for the shape (instead of erroring out).
 
 
 ### Bug Fixes
 ### Bug Fixes
 
 
@@ -17,6 +18,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 * Fixed compilation error when using Jolt in conjunction with the `_CRTDBG_MAP_ALLOC` define on Windows.
 * Fixed compilation error when using Jolt in conjunction with the `_CRTDBG_MAP_ALLOC` define on Windows.
 * Fixed cast shape possibly not returning a hit when a shape cast starts touching (but not intersecting) another shape and requesting the deepest hit.
 * Fixed cast shape possibly not returning a hit when a shape cast starts touching (but not intersecting) another shape and requesting the deepest hit.
 * Fixed division by zero when doing a really long (6 KM) sphere cast against a triangle. In this case the floating point accuracy became low enough so that the distance between the sphere center and the triangle (which should be 'radius') became zero instead.
 * Fixed division by zero when doing a really long (6 KM) sphere cast against a triangle. In this case the floating point accuracy became low enough so that the distance between the sphere center and the triangle (which should be 'radius') became zero instead.
+* Fixed memory leak when providing invalid parameters to TaperedCylinderShapeSettings and creating the shape.
 
 
 ## v5.4.0
 ## v5.4.0
 
 

+ 9 - 3
Jolt/Physics/Collision/Shape/BoxShape.cpp

@@ -55,11 +55,17 @@ ShapeSettings::ShapeResult BoxShapeSettings::Create() const
 BoxShape::BoxShape(const BoxShapeSettings &inSettings, ShapeResult &outResult) :
 BoxShape::BoxShape(const BoxShapeSettings &inSettings, ShapeResult &outResult) :
 	ConvexShape(EShapeSubType::Box, inSettings, outResult),
 	ConvexShape(EShapeSubType::Box, inSettings, outResult),
 	mHalfExtent(inSettings.mHalfExtent),
 	mHalfExtent(inSettings.mHalfExtent),
-	mConvexRadius(inSettings.mConvexRadius)
+	mConvexRadius(min(inSettings.mConvexRadius, inSettings.mHalfExtent.ReduceMin()))
 {
 {
+	// Check half extents
+	if (inSettings.mHalfExtent.ReduceMin() < 0.0f)
+	{
+		outResult.SetError("Invalid half extent");
+		return;
+	}
+
 	// Check convex radius
 	// Check convex radius
-	if (inSettings.mConvexRadius < 0.0f
-		|| inSettings.mHalfExtent.ReduceMin() < inSettings.mConvexRadius)
+	if (inSettings.mConvexRadius < 0.0f)
 	{
 	{
 		outResult.SetError("Invalid convex radius");
 		outResult.SetError("Invalid convex radius");
 		return;
 		return;

+ 1 - 1
Jolt/Physics/Collision/Shape/BoxShape.h

@@ -41,7 +41,7 @@ public:
 
 
 	/// Create a box with half edge length inHalfExtent and convex radius inConvexRadius.
 	/// Create a box with half edge length inHalfExtent and convex radius inConvexRadius.
 	/// (internally the convex radius will be subtracted from the half extent so the total box will not grow with the convex radius).
 	/// (internally the convex radius will be subtracted from the half extent so the total box will not grow with the convex radius).
-	explicit				BoxShape(Vec3Arg inHalfExtent, float inConvexRadius = cDefaultConvexRadius, const PhysicsMaterial *inMaterial = nullptr) : ConvexShape(EShapeSubType::Box, inMaterial), mHalfExtent(inHalfExtent), mConvexRadius(inConvexRadius) { JPH_ASSERT(inConvexRadius >= 0.0f); JPH_ASSERT(inHalfExtent.ReduceMin() >= inConvexRadius); }
+	explicit				BoxShape(Vec3Arg inHalfExtent, float inConvexRadius = cDefaultConvexRadius, const PhysicsMaterial *inMaterial = nullptr) : ConvexShape(EShapeSubType::Box, inMaterial), mHalfExtent(inHalfExtent), mConvexRadius(min(inConvexRadius, inHalfExtent.ReduceMin())) { JPH_ASSERT(inHalfExtent.ReduceMin() >= 0.0f); JPH_ASSERT(inConvexRadius >= 0.0f); }
 
 
 	/// Get half extent of box
 	/// Get half extent of box
 	Vec3					GetHalfExtent() const										{ return mHalfExtent; }
 	Vec3					GetHalfExtent() const										{ return mHalfExtent; }

+ 6 - 6
Jolt/Physics/Collision/Shape/CylinderShape.cpp

@@ -91,15 +91,15 @@ CylinderShape::CylinderShape(const CylinderShapeSettings &inSettings, ShapeResul
 	ConvexShape(EShapeSubType::Cylinder, inSettings, outResult),
 	ConvexShape(EShapeSubType::Cylinder, inSettings, outResult),
 	mHalfHeight(inSettings.mHalfHeight),
 	mHalfHeight(inSettings.mHalfHeight),
 	mRadius(inSettings.mRadius),
 	mRadius(inSettings.mRadius),
-	mConvexRadius(inSettings.mConvexRadius)
+	mConvexRadius(min(inSettings.mConvexRadius, min(inSettings.mHalfHeight, inSettings.mRadius)))
 {
 {
-	if (inSettings.mHalfHeight < inSettings.mConvexRadius)
+	if (inSettings.mHalfHeight < 0.0f)
 	{
 	{
 		outResult.SetError("Invalid height");
 		outResult.SetError("Invalid height");
 		return;
 		return;
 	}
 	}
 
 
-	if (inSettings.mRadius < inSettings.mConvexRadius)
+	if (inSettings.mRadius < 0.0f)
 	{
 	{
 		outResult.SetError("Invalid radius");
 		outResult.SetError("Invalid radius");
 		return;
 		return;
@@ -118,10 +118,10 @@ CylinderShape::CylinderShape(float inHalfHeight, float inRadius, float inConvexR
 	ConvexShape(EShapeSubType::Cylinder, inMaterial),
 	ConvexShape(EShapeSubType::Cylinder, inMaterial),
 	mHalfHeight(inHalfHeight),
 	mHalfHeight(inHalfHeight),
 	mRadius(inRadius),
 	mRadius(inRadius),
-	mConvexRadius(inConvexRadius)
+	mConvexRadius(min(inConvexRadius, min(inHalfHeight, inRadius)))
 {
 {
-	JPH_ASSERT(inHalfHeight >= inConvexRadius);
-	JPH_ASSERT(inRadius >= inConvexRadius);
+	JPH_ASSERT(inHalfHeight >= 0.0f);
+	JPH_ASSERT(inRadius >= 0.0f);
 	JPH_ASSERT(inConvexRadius >= 0.0f);
 	JPH_ASSERT(inConvexRadius >= 0.0f);
 }
 }
 
 

+ 1 - 2
Jolt/Physics/Collision/Shape/EmptyShape.cpp

@@ -23,8 +23,7 @@ JPH_IMPLEMENT_SERIALIZABLE_VIRTUAL(EmptyShapeSettings)
 ShapeSettings::ShapeResult EmptyShapeSettings::Create() const
 ShapeSettings::ShapeResult EmptyShapeSettings::Create() const
 {
 {
 	if (mCachedResult.IsEmpty())
 	if (mCachedResult.IsEmpty())
-		new EmptyShape(*this, mCachedResult);
-
+		Ref<Shape> shape = new EmptyShape(*this, mCachedResult);
 	return mCachedResult;
 	return mCachedResult;
 }
 }
 
 

+ 0 - 1
Jolt/Physics/Collision/Shape/MutableCompoundShape.cpp

@@ -23,7 +23,6 @@ ShapeSettings::ShapeResult MutableCompoundShapeSettings::Create() const
 	// Build a mutable compound shape
 	// Build a mutable compound shape
 	if (mCachedResult.IsEmpty())
 	if (mCachedResult.IsEmpty())
 		Ref<Shape> shape = new MutableCompoundShape(*this, mCachedResult);
 		Ref<Shape> shape = new MutableCompoundShape(*this, mCachedResult);
-
 	return mCachedResult;
 	return mCachedResult;
 }
 }
 
 

+ 6 - 18
Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp

@@ -55,12 +55,12 @@ ShapeSettings::ShapeResult TaperedCylinderShapeSettings::Create() const
 			settings.mRadius = mTopRadius;
 			settings.mRadius = mTopRadius;
 			settings.mMaterial = mMaterial;
 			settings.mMaterial = mMaterial;
 			settings.mConvexRadius = mConvexRadius;
 			settings.mConvexRadius = mConvexRadius;
-			new CylinderShape(settings, mCachedResult);
+			shape = new CylinderShape(settings, mCachedResult);
 		}
 		}
 		else
 		else
 		{
 		{
 			// Normal tapered cylinder shape
 			// Normal tapered cylinder shape
-			new TaperedCylinderShape(*this, mCachedResult);
+			shape = new TaperedCylinderShape(*this, mCachedResult);
 		}
 		}
 	}
 	}
 	return mCachedResult;
 	return mCachedResult;
@@ -79,7 +79,7 @@ TaperedCylinderShape::TaperedCylinderShape(const TaperedCylinderShapeSettings &i
 	ConvexShape(EShapeSubType::TaperedCylinder, inSettings, outResult),
 	ConvexShape(EShapeSubType::TaperedCylinder, inSettings, outResult),
 	mTopRadius(inSettings.mTopRadius),
 	mTopRadius(inSettings.mTopRadius),
 	mBottomRadius(inSettings.mBottomRadius),
 	mBottomRadius(inSettings.mBottomRadius),
-	mConvexRadius(inSettings.mConvexRadius)
+	mConvexRadius(min(inSettings.mConvexRadius, min(inSettings.mTopRadius, inSettings.mBottomRadius)))
 {
 {
 	if (mTopRadius < 0.0f)
 	if (mTopRadius < 0.0f)
 	{
 	{
@@ -93,27 +93,15 @@ TaperedCylinderShape::TaperedCylinderShape(const TaperedCylinderShapeSettings &i
 		return;
 		return;
 	}
 	}
 
 
-	if (inSettings.mHalfHeight <= 0.0f)
-	{
-		outResult.SetError("Invalid height");
-		return;
-	}
-
-	if (inSettings.mConvexRadius < 0.0f)
+	if (mConvexRadius < 0.0f)
 	{
 	{
 		outResult.SetError("Invalid convex radius");
 		outResult.SetError("Invalid convex radius");
 		return;
 		return;
 	}
 	}
 
 
-	if (inSettings.mTopRadius < inSettings.mConvexRadius)
-	{
-		outResult.SetError("Convex radius must be smaller than convex radius");
-		return;
-	}
-
-	if (inSettings.mBottomRadius < inSettings.mConvexRadius)
+	if (inSettings.mHalfHeight <= 0.0f)
 	{
 	{
-		outResult.SetError("Convex radius must be smaller than bottom radius");
+		outResult.SetError("Invalid height");
 		return;
 		return;
 	}
 	}
 
 

+ 172 - 0
UnitTests/Physics/ShapeTests.cpp

@@ -909,4 +909,176 @@ TEST_SUITE("ShapeTests")
 			CHECK(user_data == expected_user_data);
 			CHECK(user_data == expected_user_data);
 		}
 		}
 	}
 	}
+
+	TEST_CASE("TestBoxShape")
+	{
+		{
+			// Check half extents must be positive
+			BoxShapeSettings box_settings(Vec3(-1, 1, 1));
+			CHECK(box_settings.Create().HasError());
+		}
+
+		{
+			// Check convex radius must be positive
+			BoxShapeSettings box_settings(Vec3::sReplicate(1.0f), -1.0f);
+			CHECK(box_settings.Create().HasError());
+		}
+
+		{
+			// Create zero sized box
+			BoxShapeSettings box_settings(Vec3::sZero(), 1.0f);
+			RefConst<BoxShape> box = StaticCast<BoxShape>(box_settings.Create().Get());
+
+			// Create another box by using a different constructor
+			RefConst<BoxShape> box2 = new BoxShape(Vec3::sZero(), 1.0f);
+
+			// Check convex radius is adjusted to zero
+			CHECK(box->GetConvexRadius() == 0.0f);
+			CHECK(box2->GetConvexRadius() == 0.0f);
+
+			// Check that it successfully rests on a floor
+			PhysicsTestContext c;
+			c.CreateFloor();
+
+			// Override speculative contact distance and penetration slop to 0 so that we land exactly at (0, 0, 0)
+			PhysicsSettings settings;
+			settings.mSpeculativeContactDistance = 0.0f;
+			settings.mPenetrationSlop = 0.0f;
+			c.GetSystem()->SetPhysicsSettings(settings);
+
+			// Create bodies and check it lands on the floor
+			BodyCreationSettings bcs;
+			bcs.SetShape(box);
+			bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
+			bcs.mMassPropertiesOverride.mMass = 1.0f;
+			bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
+			bcs.mPosition = RVec3(0, 1, 0);
+			bcs.mObjectLayer = Layers::MOVING;
+			Body &body1 = c.CreateBody(bcs, EActivation::Activate);
+			bcs.mPosition = RVec3(1, 1, 0);
+			bcs.SetShape(box2);
+			Body &body2 = c.CreateBody(bcs, EActivation::Activate);
+			c.Simulate(1.0f);
+			CHECK_APPROX_EQUAL(body1.GetPosition(), RVec3::sZero());
+			CHECK_APPROX_EQUAL(body2.GetPosition(), RVec3(1, 0, 0));
+		}
+	}
+
+	TEST_CASE("TestCylinderShape")
+	{
+		{
+			// Check half height must be positive
+			CylinderShapeSettings cylinder_settings(-1.0f, 1.0f, 1.0f);
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Check radius must be positive
+			CylinderShapeSettings cylinder_settings(1.0f, -1.0f, 1.0f);
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Check convex radius must be positive
+			CylinderShapeSettings cylinder_settings(1.0f, 1.0f, -1.0f);
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Create zero sized cylinder
+			CylinderShapeSettings cylinder_settings(0.0f, 0.0f, 1.0f);
+			RefConst<CylinderShape> cylinder = StaticCast<CylinderShape>(cylinder_settings.Create().Get());
+
+			// Create another cylinder by using a different constructor
+			RefConst<CylinderShape> cylinder2 = new CylinderShape(0.0f, 0.0f, 1.0f);
+
+			// Check convex radius is adjusted to zero
+			CHECK(cylinder->GetConvexRadius() == 0.0f);
+			CHECK(cylinder2->GetConvexRadius() == 0.0f);
+
+			// Check that it successfully rests on a floor
+			PhysicsTestContext c;
+			c.CreateFloor();
+
+			// Override speculative contact distance and penetration slop to 0 so that we land exactly at (0, 0, 0)
+			PhysicsSettings settings;
+			settings.mSpeculativeContactDistance = 0.0f;
+			settings.mPenetrationSlop = 0.0f;
+			c.GetSystem()->SetPhysicsSettings(settings);
+
+			// Create bodies and check it lands on the floor
+			BodyCreationSettings bcs;
+			bcs.SetShape(cylinder);
+			bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
+			bcs.mMassPropertiesOverride.mMass = 1.0f;
+			bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
+			bcs.mPosition = RVec3(0, 1, 0);
+			bcs.mObjectLayer = Layers::MOVING;
+			Body &body1 = c.CreateBody(bcs, EActivation::Activate);
+			bcs.mPosition = RVec3(1, 1, 0);
+			bcs.SetShape(cylinder2);
+			Body &body2 = c.CreateBody(bcs, EActivation::Activate);
+			c.Simulate(1.0f);
+			CHECK_APPROX_EQUAL(body1.GetPosition(), RVec3::sZero());
+			CHECK_APPROX_EQUAL(body2.GetPosition(), RVec3(1, 0, 0));
+		}
+	}
+
+	TEST_CASE("TestTaperedCylinderShape")
+	{
+		{
+			// Check half height must be positive
+			TaperedCylinderShapeSettings cylinder_settings(-1.0f, 1.0f, 0.1f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Check top radius must be positive
+			TaperedCylinderShapeSettings cylinder_settings(1.0f, -1.0f, 0.1f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Check bottom radius must be positive
+			TaperedCylinderShapeSettings cylinder_settings(1.0f, 1.0f, -0.1f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Check convex radius must be positive
+			TaperedCylinderShapeSettings cylinder_settings(1.0f, 1.0f, 0.1f, -1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
+			CHECK(cylinder_settings.Create().HasError());
+		}
+
+		{
+			// Create zero sized cylinder
+			TaperedCylinderShapeSettings cylinder_settings(1.0e-12f, 0.0f, 1.0e-12f, 1.0f); // Top != bottom or else we'll be creating a CylinderShape instead
+			RefConst<TaperedCylinderShape> cylinder = StaticCast<TaperedCylinderShape>(cylinder_settings.Create().Get());
+
+			// Check convex radius is adjusted to zero
+			CHECK(cylinder->GetConvexRadius() == 0.0f);
+
+			// Check that it successfully rests on a floor
+			PhysicsTestContext c;
+			c.CreateFloor();
+
+			// Override speculative contact distance and penetration slop to 0 so that we land exactly at (0, 0, 0)
+			PhysicsSettings settings;
+			settings.mSpeculativeContactDistance = 0.0f;
+			settings.mPenetrationSlop = 0.0f;
+			c.GetSystem()->SetPhysicsSettings(settings);
+
+			// Create bodies and check it lands on the floor
+			BodyCreationSettings bcs;
+			bcs.SetShape(cylinder);
+			bcs.mOverrideMassProperties = EOverrideMassProperties::MassAndInertiaProvided;
+			bcs.mMassPropertiesOverride.mMass = 1.0f;
+			bcs.mMassPropertiesOverride.mInertia = Mat44::sIdentity();
+			bcs.mPosition = RVec3(0, 1, 0);
+			bcs.mObjectLayer = Layers::MOVING;
+			Body &body1 = c.CreateBody(bcs, EActivation::Activate);
+			c.Simulate(1.0f);
+			CHECK_APPROX_EQUAL(body1.GetPosition(), RVec3::sZero());
+		}
+	}
 }
 }

+ 8 - 3
UnitTests/PhysicsTestContext.cpp

@@ -59,6 +59,13 @@ Body &PhysicsTestContext::CreateFloor()
 	return floor;
 	return floor;
 }
 }
 
 
+Body &PhysicsTestContext::CreateBody(const BodyCreationSettings &inSettings, EActivation inActivation)
+{
+	Body &body = *mSystem->GetBodyInterface().CreateBody(inSettings);
+	mSystem->GetBodyInterface().AddBody(body.GetID(), inActivation);
+	return body;
+}
+
 Body &PhysicsTestContext::CreateBody(const ShapeSettings *inShapeSettings, RVec3Arg inPosition, QuatArg inRotation, EMotionType inMotionType, EMotionQuality inMotionQuality, ObjectLayer inLayer, EActivation inActivation)
 Body &PhysicsTestContext::CreateBody(const ShapeSettings *inShapeSettings, RVec3Arg inPosition, QuatArg inRotation, EMotionType inMotionType, EMotionQuality inMotionQuality, ObjectLayer inLayer, EActivation inActivation)
 {
 {
 	BodyCreationSettings settings;
 	BodyCreationSettings settings;
@@ -71,9 +78,7 @@ Body &PhysicsTestContext::CreateBody(const ShapeSettings *inShapeSettings, RVec3
 	settings.mLinearDamping = 0.0f;
 	settings.mLinearDamping = 0.0f;
 	settings.mAngularDamping = 0.0f;
 	settings.mAngularDamping = 0.0f;
 	settings.mCollisionGroup.SetGroupID(0);
 	settings.mCollisionGroup.SetGroupID(0);
-	Body &body = *mSystem->GetBodyInterface().CreateBody(settings);
-	mSystem->GetBodyInterface().AddBody(body.GetID(), inActivation);
-	return body;
+	return CreateBody(settings, inActivation);
 }
 }
 
 
 Body &PhysicsTestContext::CreateBox(RVec3Arg inPosition, QuatArg inRotation, EMotionType inMotionType, EMotionQuality inMotionQuality, ObjectLayer inLayer, Vec3Arg inHalfExtent, EActivation inActivation)
 Body &PhysicsTestContext::CreateBox(RVec3Arg inPosition, QuatArg inRotation, EMotionType inMotionType, EMotionQuality inMotionQuality, ObjectLayer inLayer, Vec3Arg inHalfExtent, EActivation inActivation)

+ 3 - 0
UnitTests/PhysicsTestContext.h

@@ -34,6 +34,9 @@ public:
 	// Create a floor at Y = 0
 	// Create a floor at Y = 0
 	Body &				CreateFloor();
 	Body &				CreateFloor();
 
 
+	/// Create a body and add it to the world
+	Body &				CreateBody(const BodyCreationSettings &inSettings, EActivation inActivation);
+
 	/// Create a body and add it to the world
 	/// Create a body and add it to the world
 	Body &				CreateBody(const ShapeSettings *inShapeSettings, RVec3Arg inPosition, QuatArg inRotation, EMotionType inMotionType, EMotionQuality inMotionQuality, ObjectLayer inLayer, EActivation inActivation);
 	Body &				CreateBody(const ShapeSettings *inShapeSettings, RVec3Arg inPosition, QuatArg inRotation, EMotionType inMotionType, EMotionQuality inMotionQuality, ObjectLayer inLayer, EActivation inActivation);