소스 검색

Respecting the mIsSensor flag for CCD bodies (#637)

When a body with the LinearCast motion quality type is converted to a sensor in the OnContactAdded callback, the body would still trigger a collision response and bounce off. Now the mIsSensor flag is respected and collision response handling is skipped. Note that since we only find the first hit for the LinearCast motion type, this makes it possible to penetrate another body that is right behind the body that got converted to a sensor.
Jorrit Rouwe 2 년 전
부모
커밋
75e5857907
3개의 변경된 파일92개의 추가작업 그리고 9개의 파일을 삭제
  1. 1 1
      Jolt/Physics/Constraints/ContactConstraintManager.cpp
  2. 17 8
      Jolt/Physics/PhysicsSystem.cpp
  3. 74 0
      UnitTests/Physics/SensorTests.cpp

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

@@ -1332,7 +1332,7 @@ void ContactConstraintManager::OnCCDContactAdded(ContactAllocator &ioContactAllo
 		}
 	}
 
-	JPH_ASSERT(!outSettings.mIsSensor, "CCD bodies cannot currently act as sensors");
+	JPH_ASSERT(outSettings.mIsSensor || !(inBody1.IsSensor() || inBody2.IsSensor()), "Sensors cannot be converted into regular bodies by a contact callback!");
 }
 
 void ContactConstraintManager::SortContacts(uint32 *inConstraintIdxBegin, uint32 *inConstraintIdxEnd) const

+ 17 - 8
Jolt/Physics/PhysicsSystem.cpp

@@ -1836,17 +1836,26 @@ void PhysicsSystem::JobFindCCDContacts(const PhysicsUpdateContext *ioContext, Ph
 			// Call contact point callbacks
 			mContactManager.OnCCDContactAdded(contact_allocator, body, body2, manifold, ccd_body.mContactSettings);
 
-			// Calculate the average position from the manifold (this will result in the same impulse applied as when we apply impulses to all contact points)
-			if (manifold.mRelativeContactPointsOn2.size() > 1)
+			if (ccd_body.mContactSettings.mIsSensor)
 			{
-				Vec3 average_contact_point = Vec3::sZero();
-				for (const Vec3 &v : manifold.mRelativeContactPointsOn2)
-					average_contact_point += v;
-				average_contact_point /= (float)manifold.mRelativeContactPointsOn2.size();
-				ccd_body.mContactPointOn2 = manifold.mBaseOffset + average_contact_point;
+				// If this is a sensor, we don't want to solve the contact
+				ccd_body.mFractionPlusSlop = 1.0f;
+				ccd_body.mBodyID2 = BodyID();
 			}
 			else
-				ccd_body.mContactPointOn2 = manifold.mBaseOffset + cast_shape_result.mContactPointOn2;
+			{
+				// Calculate the average position from the manifold (this will result in the same impulse applied as when we apply impulses to all contact points)
+				if (manifold.mRelativeContactPointsOn2.size() > 1)
+				{
+					Vec3 average_contact_point = Vec3::sZero();
+					for (const Vec3 &v : manifold.mRelativeContactPointsOn2)
+						average_contact_point += v;
+					average_contact_point /= (float)manifold.mRelativeContactPointsOn2.size();
+					ccd_body.mContactPointOn2 = manifold.mBaseOffset + average_contact_point;
+				}
+				else
+					ccd_body.mContactPointOn2 = manifold.mBaseOffset + cast_shape_result.mContactPointOn2;
+			}
 		}
 	}
 

+ 74 - 0
UnitTests/Physics/SensorTests.cpp

@@ -439,6 +439,80 @@ TEST_SUITE("SensorTests")
 		CHECK_APPROX_EQUAL(dynamic2.GetPosition(), RVec3(2, -1.5f - 3.0f * c.GetDeltaTime(), 0), 1.0e-4f);
 	}
 
+	TEST_CASE("TestContactListenerMakesSensorCCD")
+	{
+		PhysicsTestContext c;
+		c.ZeroGravity();
+
+		const float cPenetrationSlop = c.GetSystem()->GetPhysicsSettings().mPenetrationSlop;
+
+		class SensorOverridingListener : public LoggingContactListener
+		{
+		public:
+			virtual void		OnContactAdded(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
+			{
+				LoggingContactListener::OnContactAdded(inBody1, inBody2, inManifold, ioSettings);
+
+				JPH_ASSERT(ioSettings.mIsSensor == false);
+				if (inBody1.GetID() == mBodyThatBecomesSensor || inBody2.GetID() == mBodyThatBecomesSensor)
+					ioSettings.mIsSensor = true;
+			}
+
+			virtual void		OnContactPersisted(const Body &inBody1, const Body &inBody2, const ContactManifold &inManifold, ContactSettings &ioSettings) override
+			{
+				LoggingContactListener::OnContactPersisted(inBody1, inBody2, inManifold, ioSettings);
+
+				JPH_ASSERT(ioSettings.mIsSensor == false);
+				if (inBody1.GetID() == mBodyThatBecomesSensor || inBody2.GetID() == mBodyThatBecomesSensor)
+					ioSettings.mIsSensor = true;
+			}
+
+			BodyID				mBodyThatBecomesSensor;
+		};
+
+		// Register listener
+		SensorOverridingListener listener;
+		c.GetSystem()->SetContactListener(&listener);
+
+		// Body that blocks the path
+		BodyID static_id = c.GetBodyInterface().CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.1f, 10, 10)), RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
+
+		// Dynamic body moving to the static object that will do a normal CCD collision
+		RVec3 dynamic1_pos(-0.5f, 2, 0);
+		Vec3 initial_velocity(500, 0, 0);
+		Body &dynamic1 = c.CreateBox(dynamic1_pos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(0.1f));
+		dynamic1.SetAllowSleeping(false);
+		dynamic1.SetLinearVelocity(initial_velocity);
+		dynamic1.SetRestitution(1.0f);
+
+		// Dynamic body moving through the static object that will become a sensor an thus pass through
+		RVec3 dynamic2_pos(-0.5f, -2, 0);
+		Body &dynamic2 = c.CreateBox(dynamic2_pos, Quat::sIdentity(), EMotionType::Dynamic, EMotionQuality::LinearCast, Layers::MOVING, Vec3::sReplicate(0.1f));
+		dynamic2.SetAllowSleeping(false);
+		dynamic2.SetLinearVelocity(initial_velocity);
+		dynamic2.SetRestitution(1.0f);
+		listener.mBodyThatBecomesSensor = dynamic2.GetID();
+
+		// After a single step the we should have contact added callbacks for both bodies
+		c.SimulateSingleStep();
+		CHECK(listener.Contains(EType::Add, dynamic1.GetID(), static_id));
+		CHECK(listener.Contains(EType::Add, dynamic2.GetID(), static_id));
+		listener.Clear();
+		CHECK_APPROX_EQUAL(dynamic1.GetPosition(), dynamic1_pos + RVec3(0.3f + cPenetrationSlop, 0, 0), 1.0e-4f); // Dynamic 1 should have moved to the surface of the static body
+		CHECK_APPROX_EQUAL(dynamic2.GetPosition(), dynamic2_pos + initial_velocity * c.GetDeltaTime(), 1.0e-4f); // Dynamic 2 should have passed through the static body because it became a sensor
+
+		// The next step the sensor should have its contact removed and the CCD body should have its contact persisted because it starts penetrating
+		c.SimulateSingleStep();
+		CHECK(listener.Contains(EType::Persist, dynamic1.GetID(), static_id));
+		CHECK(listener.Contains(EType::Remove, dynamic2.GetID(), static_id));
+		listener.Clear();
+
+		// The next step all contacts have been removed
+		c.SimulateSingleStep();
+		CHECK(listener.Contains(EType::Remove, dynamic1.GetID(), static_id));
+		listener.Clear();
+	}
+
 	TEST_CASE("TestSensorVsSubShapes")
 	{
 		PhysicsTestContext c;