Browse Source

Made character tests deterministic

- CharacterVirtual::SetShape will no longer update the contacts if inMaxPenetrationDepth is FLT_MAX
Jorrit Rouwe 3 years ago
parent
commit
8b8ed06053

+ 22 - 0
Jolt/Physics/Character/CharacterBase.cpp

@@ -4,6 +4,7 @@
 #include <Jolt/Jolt.h>
 
 #include <Jolt/Physics/Character/CharacterBase.h>
+#include <Jolt/Physics/StateRecorder.h>
 
 JPH_NAMESPACE_BEGIN
 
@@ -15,4 +16,25 @@ CharacterBase::CharacterBase(const CharacterBaseSettings *inSettings, PhysicsSys
 	SetMaxSlopeAngle(inSettings->mMaxSlopeAngle);
 }
 
+void CharacterBase::SaveState(StateRecorder &inStream) const
+{
+	inStream.Write(mGroundState);
+	inStream.Write(mGroundBodyID);
+	inStream.Write(mGroundBodySubShapeID);
+	inStream.Write(mGroundPosition);
+	inStream.Write(mGroundNormal);
+	inStream.Write(mGroundVelocity);
+	// Can't save or restore user data (may be a pointer) and material
+}
+
+void CharacterBase::RestoreState(StateRecorder &inStream)
+{
+	inStream.Read(mGroundState);
+	inStream.Read(mGroundBodyID);
+	inStream.Read(mGroundBodySubShapeID);
+	inStream.Read(mGroundPosition);
+	inStream.Read(mGroundNormal);
+	inStream.Read(mGroundVelocity);
+}
+
 JPH_NAMESPACE_END

+ 5 - 0
Jolt/Physics/Character/CharacterBase.h

@@ -13,6 +13,7 @@
 JPH_NAMESPACE_BEGIN
 
 class PhysicsSystem;
+class StateRecorder;
 
 /// Base class for configuration of a character
 class CharacterBaseSettings : public RefTarget<CharacterBaseSettings>
@@ -75,6 +76,10 @@ public:
 	/// User data value of the body that we're standing on
 	uint64								GetGroundUserData() const								{ return mGroundUserData; }
 
+	// Saving / restoring state for replay
+	virtual void						SaveState(StateRecorder &inStream) const;
+	virtual void						RestoreState(StateRecorder &inStream);
+
 protected:
 	// Cached physics system
 	PhysicsSystem *						mSystem;

+ 25 - 7
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -794,13 +794,13 @@ bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDept
 		RefConst<Shape> old_shape = mShape;
 		mShape = inShape;
 
-		// Check collision around the new shape
-		TempContactList contacts(inAllocator);
-		contacts.reserve(mMaxNumHits);
-		GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
-
 		if (inMaxPenetrationDepth < FLT_MAX)
 		{
+			// Check collision around the new shape
+			TempContactList contacts(inAllocator);
+			contacts.reserve(mMaxNumHits);
+			GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), inShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
+
 			// Test if this results in penetration, if so cancel the transition
 			for (const Contact &c : contacts)
 				if (c.mDistance < -inMaxPenetrationDepth)
@@ -808,12 +808,30 @@ bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDept
 					mShape = old_shape;
 					return false;
 				}
-		}
 
-		StoreActiveContacts(contacts, inAllocator);
+			StoreActiveContacts(contacts, inAllocator);
+		}
 	}
 
 	return mShape == inShape;
 }
 
+void CharacterVirtual::SaveState(StateRecorder &inStream) const
+{
+	CharacterBase::SaveState(inStream);
+
+	inStream.Write(mPosition);
+	inStream.Write(mRotation);
+	inStream.Write(mLinearVelocity);
+}
+
+void CharacterVirtual::RestoreState(StateRecorder &inStream)
+{
+	CharacterBase::RestoreState(inStream);
+
+	inStream.Read(mPosition);
+	inStream.Read(mRotation);
+	inStream.Read(mLinearVelocity);
+}
+
 JPH_NAMESPACE_END

+ 4 - 0
Jolt/Physics/Character/CharacterVirtual.h

@@ -136,6 +136,10 @@ public:
 	/// @return Returns true if the switch succeeded.
 	bool								SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator);
 
+	// Saving / restoring state for replay
+	virtual void						SaveState(StateRecorder &inStream) const override;
+	virtual void						RestoreState(StateRecorder &inStream) override;
+
 #ifdef JPH_DEBUG_RENDERER
 	static inline bool					sDrawConstraints = false;								///< Draw the current state of the constraints for iteration 0 when creating them
 #endif

+ 2 - 0
Samples/Tests/Character/CharacterBaseTest.cpp

@@ -232,11 +232,13 @@ Mat44 CharacterBaseTest::GetCameraPivot(float inCameraHeading, float inCameraPit
 void CharacterBaseTest::SaveState(StateRecorder &inStream) const
 {
 	inStream.Write(mTime);
+	inStream.Write(mRampBlocksTimeLeft);
 }
 
 void CharacterBaseTest::RestoreState(StateRecorder &inStream)
 {
 	inStream.Read(mTime);
+	inStream.Read(mRampBlocksTimeLeft);
 }
 
 void CharacterBaseTest::DrawCharacterState(const CharacterBase *inCharacter, Mat44Arg inCharacterTransform, Vec3Arg inCharacterVelocity)

+ 0 - 4
Samples/Tests/Character/CharacterBaseTest.h

@@ -31,10 +31,6 @@ public:
 	virtual bool			HasSettingsMenu() const override							{ return true; }
 	virtual void			CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
 
-	// Test will never be deterministic since various threads are trying to concurrently add / remove bodies
-	// Ramp blocks are not deterministic (they're teleported, we can't step back)
-	virtual bool			IsDeterministic() const override							{ return mRampBlocks.empty(); }
-
 	// Saving / restoring state for replay
 	virtual void			SaveState(StateRecorder &inStream) const override;
 	virtual void			RestoreState(StateRecorder &inStream) override;

+ 21 - 0
Samples/Tests/Character/CharacterTest.cpp

@@ -46,6 +46,27 @@ void CharacterTest::PostPhysicsUpdate(float inDeltaTime)
 	mCharacter->PostSimulation(cCollisionTolerance);
 }
 
+void CharacterTest::SaveState(StateRecorder &inStream) const
+{
+	CharacterBaseTest::SaveState(inStream);
+
+	mCharacter->SaveState(inStream);
+
+	bool is_standing = mCharacter->GetShape() == mStandingShape;
+	inStream.Write(is_standing);
+}
+
+void CharacterTest::RestoreState(StateRecorder &inStream)
+{
+	CharacterBaseTest::RestoreState(inStream);
+
+	mCharacter->RestoreState(inStream);
+
+	bool is_standing = mCharacter->GetShape() == mStandingShape; // Initialize variable for validation mode
+	inStream.Read(is_standing);
+	mCharacter->SetShape(is_standing? mStandingShape : mCrouchingShape, FLT_MAX);
+}
+
 void CharacterTest::HandleInput(Vec3Arg inMovementDirection, bool inJump, bool inSwitchStance, float inDeltaTime)
 {
 	// Cancel movement in opposite direction of normal when sliding

+ 4 - 0
Samples/Tests/Character/CharacterTest.h

@@ -24,6 +24,10 @@ public:
 	// Update the test, called after the physics update
 	virtual void			PostPhysicsUpdate(float inDeltaTime) override;
 
+	// Saving / restoring state for replay
+	virtual void			SaveState(StateRecorder &inStream) const override;
+	virtual void			RestoreState(StateRecorder &inStream) override;
+
 protected:
 	// Get position of the character
 	virtual Vec3			GetCharacterPosition() const override				{ return mCharacter->GetPosition(); }

+ 25 - 0
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -131,6 +131,31 @@ void CharacterVirtualTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMen
 	});
 }
 
+void CharacterVirtualTest::SaveState(StateRecorder &inStream) const
+{
+	CharacterBaseTest::SaveState(inStream);
+
+	mCharacter->SaveState(inStream);
+
+	bool is_standing = mCharacter->GetShape() == mStandingShape;
+	inStream.Write(is_standing);
+
+	inStream.Write(mSmoothMovementDirection);
+}
+
+void CharacterVirtualTest::RestoreState(StateRecorder &inStream)
+{
+	CharacterBaseTest::RestoreState(inStream);
+
+	mCharacter->RestoreState(inStream);
+
+	bool is_standing = mCharacter->GetShape() == mStandingShape; // Initialize variable for validation mode
+	inStream.Read(is_standing);
+	mCharacter->SetShape(is_standing? mStandingShape : mCrouchingShape, FLT_MAX, { }, { }, { }, *mTempAllocator);
+
+	inStream.Read(mSmoothMovementDirection);
+}
+
 void CharacterVirtualTest::OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, Vec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings)
 {
 	// Dynamic boxes on the ramp go through all permutations

+ 4 - 0
Samples/Tests/Character/CharacterVirtualTest.h

@@ -21,6 +21,10 @@ public:
 	// Optional settings menu
 	virtual void			CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
 
+	// Saving / restoring state for replay
+	virtual void			SaveState(StateRecorder &inStream) const override;
+	virtual void			RestoreState(StateRecorder &inStream) override;
+
 	// Called whenever the character collides with a body. Returns true if the contact can push the character.
 	virtual void			OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, Vec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings) override;