Ver código fonte

Fixed issue when compound shape vs enhanced internal edge removal (#1223)

The algorithm assumes convex vs polygons, so compounds must use the sub shape id to differentiate voided features

Fixes https://github.com/godot-jolt/godot-jolt/issues/945
Jorrit Rouwe 1 ano atrás
pai
commit
c572b8f0a6

+ 1 - 0
Docs/ReleaseNotes.md

@@ -10,6 +10,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 
 ### Bug fixes
 
+* Fixed an issue where enhanced internal edge removal would throw away valid contacts when a dynamic compound shape is colliding with another mesh / box shape.
 * Fixed an issue where the bounding volume of a HeightFieldShape was not properly adjusted when calling SetHeights leading to missed collisions.
 * Workaround for CMake error 'CMake Error: No output files for WriteBuild!' when using the 'Ninja Multi-Config' generator.
 

+ 29 - 17
Jolt/Physics/Collision/InternalEdgeRemovingCollector.h

@@ -23,10 +23,11 @@ class InternalEdgeRemovingCollector : public CollideShapeCollector
 	static constexpr uint cMaxVoidedFeatures = 128;
 
 	/// Check if a vertex is voided
-	inline bool				IsVoided(Vec3 inV) const
+	inline bool				IsVoided(const SubShapeID &inSubShapeID, Vec3 inV) const
 	{
-		for (const Float3 &vf : mVoidedFeatures)
-			if (inV.IsClose(Vec3::sLoadFloat3Unsafe(vf), 1.0e-8f))
+		for (const Voided &vf : mVoidedFeatures)
+			if (vf.mSubShapeID == inSubShapeID
+				&& inV.IsClose(Vec3::sLoadFloat3Unsafe(vf.mFeature), 1.0e-8f))
 				return true;
 		return false;
 	}
@@ -34,15 +35,17 @@ class InternalEdgeRemovingCollector : public CollideShapeCollector
 	/// Add all vertices of a face to the voided features
 	inline void				VoidFeatures(const CollideShapeResult &inResult)
 	{
-		for (const Vec3 &v : inResult.mShape2Face)
-			if (!IsVoided(v))
-			{
-				if (mVoidedFeatures.size() == cMaxVoidedFeatures)
-					break;
-				Float3 f;
-				v.StoreFloat3(&f);
-				mVoidedFeatures.push_back(f);
-			}
+		if (mVoidedFeatures.size() < cMaxVoidedFeatures)
+			for (const Vec3 &v : inResult.mShape2Face)
+				if (!IsVoided(inResult.mSubShapeID1, v))
+				{
+					Voided vf;
+					v.StoreFloat3(&vf.mFeature);
+					vf.mSubShapeID = inResult.mSubShapeID1;
+					mVoidedFeatures.push_back(vf);
+					if (mVoidedFeatures.size() == cMaxVoidedFeatures)
+						break;
+				}
 	}
 
 	/// Call the chained collector
@@ -193,16 +196,16 @@ public:
 			}
 
 			// Check if this vertex/edge is voided
-			bool voided = IsVoided(r.mShape2Face[best_v1_idx])
-				&& (best_v1_idx == best_v2_idx || IsVoided(r.mShape2Face[best_v2_idx]));
+			bool voided = IsVoided(r.mSubShapeID1, r.mShape2Face[best_v1_idx])
+				&& (best_v1_idx == best_v2_idx || IsVoided(r.mSubShapeID1, r.mShape2Face[best_v2_idx]));
 
 		#ifdef JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG
 			Color color = voided? Color::sRed : Color::sYellow;
 			DebugRenderer::sInstance->DrawText3D(RVec3(r.mContactPointOn2), StringFormat("%d: %g", i, r.mPenetrationDepth), color, 0.1f);
 			DebugRenderer::sInstance->DrawWirePolygon(RMat44::sIdentity(), r.mShape2Face, color);
 			DebugRenderer::sInstance->DrawArrow(RVec3(r.mContactPointOn2), RVec3(r.mContactPointOn2) + r.mPenetrationAxis.NormalizedOr(Vec3::sZero()), color, 0.1f);
-			DebugRenderer::sInstance->DrawMarker(RVec3(r.mShape2Face[best_v1_idx]), IsVoided(r.mShape2Face[best_v1_idx])? Color::sRed : Color::sYellow, 0.1f);
-			DebugRenderer::sInstance->DrawMarker(RVec3(r.mShape2Face[best_v2_idx]), IsVoided(r.mShape2Face[best_v2_idx])? Color::sRed : Color::sYellow, 0.1f);
+			DebugRenderer::sInstance->DrawMarker(RVec3(r.mShape2Face[best_v1_idx]), IsVoided(r.mSubShapeID1, r.mShape2Face[best_v1_idx])? Color::sRed : Color::sYellow, 0.1f);
+			DebugRenderer::sInstance->DrawMarker(RVec3(r.mShape2Face[best_v2_idx]), IsVoided(r.mSubShapeID1, r.mShape2Face[best_v2_idx])? Color::sRed : Color::sYellow, 0.1f);
 		#endif // JPH_INTERNAL_EDGE_REMOVING_COLLECTOR_DEBUG
 
 			// No voided features, accept the contact
@@ -229,8 +232,17 @@ public:
 	}
 
 private:
+	// This algorithm tests a convex shape (shape 1) against a set of polygons (shape 2).
+	// This assumption doesn't hold if the shape we're testing is a compound shape, so we must also
+	// store the sub shape ID and ignore voided features that belong to another sub shape ID.
+	struct Voided
+	{
+		Float3				mFeature;				// Feature that is voided (of shape 2). Read with Vec3::sLoadFloat3Unsafe so must not be the last member.
+		SubShapeID			mSubShapeID;			// Sub shape ID of the shape that is colliding against the feature (of shape 1).
+	};
+
 	CollideShapeCollector &	mChainedCollector;
-	StaticArray<Float3, cMaxVoidedFeatures> mVoidedFeatures; // Read with Vec3::sLoadFloat3Unsafe so must not be the last member
+	StaticArray<Voided, cMaxVoidedFeatures> mVoidedFeatures;
 	StaticArray<CollideShapeResult, cMaxDelayedResults> mDelayedResults;
 };
 

+ 19 - 0
Samples/Tests/General/EnhancedInternalEdgeRemovalTest.cpp

@@ -167,6 +167,25 @@ void EnhancedInternalEdgeRemovalTest::Initialize()
 		slope_ball.mMassPropertiesOverride.mMass = 1;
 		mBodyInterface->CreateAndAddBody(slope_ball, EActivation::Activate);
 	}
+
+	// This tests a previous bug where a compound shape will fall through a box because features are voided by accident.
+	// This is because both boxes of the compound shape collide with the top face of the static box. The big box will have a normal
+	// that is aligned with the face so will be processed immediately. This will void the top face of the static box. The small box,
+	// which collides with an edge of the top face will not be processed. This will cause the small box to penetrate the face.
+	{
+		// A box
+		BodyCreationSettings box_bcs(new BoxShape(Vec3::sReplicate(2.5f)), RVec3(0, 0, 70), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
+		mBodyInterface->CreateAndAddBody(box_bcs, EActivation::DontActivate);
+
+		// Compound
+		StaticCompoundShapeSettings compound;
+		compound.SetEmbedded();
+		compound.AddShape(Vec3(-2.5f, 0, 0), Quat::sIdentity(), new BoxShape(Vec3(2.5f, 0.1f, 0.1f)));
+		compound.AddShape(Vec3(0.1f, 0, 0), Quat::sIdentity(), new BoxShape(Vec3(0.1f, 1, 1)));
+		BodyCreationSettings compound_bcs(&compound, RVec3(2, 5, 70), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
+		compound_bcs.mEnhancedInternalEdgeRemoval = true;
+		mBodyInterface->CreateAndAddBody(compound_bcs, EActivation::Activate);
+	}
 }
 
 void EnhancedInternalEdgeRemovalTest::PrePhysicsUpdate(const PreUpdateParams &inParams)