Browse Source

Bugfix in speculative contact handling (#545)

When an object wasn't moving fast enough to trigger restitution for a speculative contact, the contact was enforced at the current position rather than at the distance of the speculative contact

Should help with godot-jolt/godot-jolt#374
Jorrit Rouwe 2 years ago
parent
commit
d54df69aa0

+ 2 - 2
.github/workflows/determinism_check.yml

@@ -1,8 +1,8 @@
 name: Determinism Check
 
 env:
-    CONVEX_VS_MESH_HASH: '0x3c55fb5709fda28c'
-    RAGDOLL_HASH: '0x5f3b8e4a9e166453'
+    CONVEX_VS_MESH_HASH: '0x219df2ceee6baff2'
+    RAGDOLL_HASH: '0x9bace3bd537e6759'
 
 on:
   push:

+ 3 - 1
Jolt/Physics/Constraints/ContactConstraintManager.cpp

@@ -96,7 +96,9 @@ JPH_INLINE void ContactConstraintManager::WorldContactPoint::CalculateFrictionAn
 		if (normal_velocity < -speculative_contact_velocity_bias)
 			normal_velocity_bias = inCombinedRestitution * normal_velocity;
 		else
-			normal_velocity_bias = 0.0f;
+			// In this case we have predicted that we don't hit the other object, but if we do (due to other constraints changing velocities)
+			// the speculative contact will prevent penetration but will not apply restitution leading to another artifact.
+			normal_velocity_bias = speculative_contact_velocity_bias;
 	}
 	else
 	{

+ 53 - 0
UnitTests/Physics/PhysicsTests.cpp

@@ -929,6 +929,59 @@ TEST_SUITE("PhysicsTests")
 		CHECK(contact_listener.Contains(LoggingContactListener::EType::Remove, sphere.GetID(), floor.GetID()));
 	}
 
+	TEST_CASE("TestPhysicsInsideSpeculativeContactDistanceNoHit")
+	{
+		PhysicsTestContext c(1.0f / 60.0f, 1, 1);
+		Body &floor = c.CreateFloor();
+		floor.SetRestitution(1.0f);
+		c.ZeroGravity();
+
+		// Turn off the minimum velocity for restitution, our velocity is lower than the default
+		PhysicsSettings settings = c.GetSystem()->GetPhysicsSettings();
+		settings.mMinVelocityForRestitution = 0.0f;
+		c.GetSystem()->SetPhysicsSettings(settings);
+
+		LoggingContactListener contact_listener;
+		c.GetSystem()->SetContactListener(&contact_listener);
+
+		// Create a sphere inside speculative contact distance from the ground
+		const float cSpeculativeContactDistance = c.GetSystem()->GetPhysicsSettings().mSpeculativeContactDistance;
+		const float cDistanceAboveFloor = 0.9f * cSpeculativeContactDistance;
+		const RVec3 cInitialPosSphere(0, 1.0f + cDistanceAboveFloor, 0.0f);
+
+		// Make it move slow enough so that it will not touch the floor in 1 time step
+		const Vec3 cVelocity(0, -0.9f * cDistanceAboveFloor / c.GetDeltaTime(), 0);
+
+		Body &sphere = c.CreateSphere(cInitialPosSphere, 1.0f, EMotionType::Dynamic, EMotionQuality::Discrete, Layers::MOVING);
+		sphere.SetLinearVelocity(cVelocity);
+		sphere.SetRestitution(1.0f);
+		sphere.GetMotionProperties()->SetLinearDamping(0.0f);
+
+		// Simulate a step
+		c.SimulateSingleStep();
+
+		// Check that it has triggered contact points from the speculative contacts
+		CHECK(contact_listener.GetEntryCount() == 2);
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Validate, sphere.GetID(), floor.GetID()));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Add, sphere.GetID(), floor.GetID()));
+		contact_listener.Clear();
+
+		// Check that sphere didn't actually change velocity (it hasn't actually interacted with the floor, the speculative contact was not an actual contact)
+		CHECK(sphere.GetLinearVelocity() == cVelocity);
+
+		// Simulate a step
+		c.SimulateSingleStep();
+
+		// Check again that it triggered contact points
+		CHECK(contact_listener.GetEntryCount() == 2);
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Validate, sphere.GetID(), floor.GetID()));
+		CHECK(contact_listener.Contains(LoggingContactListener::EType::Persist, sphere.GetID(), floor.GetID()));
+		contact_listener.Clear();
+
+		// It should have bounced back up and inverted velocity due to restitution being 1
+		CHECK_APPROX_EQUAL(-sphere.GetLinearVelocity(), cVelocity);
+	}
+
 	TEST_CASE("TestPhysicsInsideSpeculativeContactDistanceMovingAway")
 	{
 		PhysicsTestContext c(1.0f / 60.0f, 1, 1);