Browse Source

Character tweaks (#232)

* Renamed EGroundState::Sliding -> EGroundState::OnSteepGround and added documentation
* CharacterBaseTest: Added stairs with not enough space between the steps for stair walk
* Improved walk stairs/stick to floor by ensuring that the contact that we find during the sweep is also returned as ground contact
Jorrit Rouwe 2 years ago
parent
commit
a314bc8a6e

+ 1 - 1
Jolt/Physics/Character/Character.cpp

@@ -169,7 +169,7 @@ void Character::PostSimulation(float inMaxSeparationDistance, bool inLockBodies)
 
 
 		// Update ground state
 		// Update ground state
 		if (IsSlopeTooSteep(mGroundNormal))
 		if (IsSlopeTooSteep(mGroundNormal))
-			mGroundState = EGroundState::Sliding;
+			mGroundState = EGroundState::OnSteepGround;
 		else
 		else
 			mGroundState = EGroundState::OnGround;
 			mGroundState = EGroundState::OnGround;
 
 

+ 3 - 3
Jolt/Physics/Character/CharacterBase.h

@@ -68,9 +68,9 @@ public:
 
 
 	enum class EGroundState
 	enum class EGroundState
 	{
 	{
-		OnGround,						///< Character is on the ground and can move freely
-		Sliding,						///< Character is on a slope that is too steep and should start sliding
-		InAir,							///< Character is in the air
+		OnGround,						///< Character is on the ground and can move freely.
+		OnSteepGround,					///< Character is on a slope that is too steep and can't climb up any further. The caller should start applying downward velocity if sliding from the slope is desired.
+		InAir,							///< Character is in the air.
 	};
 	};
 
 
 	///@name Properties of the ground this character is standing on
 	///@name Properties of the ground this character is standing on

+ 28 - 5
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -724,7 +724,7 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck,
 		if (time_simulated < 0.001f || displacement.LengthSq() < min_required_displacement_sq)
 		if (time_simulated < 0.001f || displacement.LengthSq() < min_required_displacement_sq)
 			mGroundState = EGroundState::OnGround;
 			mGroundState = EGroundState::OnGround;
 		else
 		else
-			mGroundState = EGroundState::Sliding;
+			mGroundState = EGroundState::OnSteepGround;
 	}
 	}
 	else
 	else
 	{
 	{
@@ -847,6 +847,31 @@ void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhase
 	StoreActiveContacts(contacts, inAllocator);
 	StoreActiveContacts(contacts, inAllocator);
 }
 }
 
 
+void CharacterVirtual::MoveToContact(const Vec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
+{
+	// Set the new position
+	SetPosition(inPosition);
+
+	// Determine the contacts
+	TempContactList contacts(inAllocator);
+	contacts.reserve(mMaxNumHits);
+	GetContactsAtPosition(mPosition, mLinearVelocity.NormalizedOr(Vec3::sZero()), mShape, contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter);
+
+	// Ensure that we mark inContact as colliding
+	JPH_IF_ENABLE_ASSERTS(bool found_contact = false;)
+	for (Contact &c : contacts)
+		if (c.mBodyB == inContact.mBodyB
+			&& c.mSubShapeIDB == inContact.mSubShapeIDB)
+		{
+			c.mHadCollision = true;
+			JPH_IF_ENABLE_ASSERTS(found_contact = true;)
+		}
+	JPH_ASSERT(found_contact);
+
+	StoreActiveContacts(contacts, inAllocator);
+	JPH_ASSERT(mGroundState != EGroundState::InAir);
+}
+
 bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
 bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDepth, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
 {
 {
 	if (mShape == nullptr || mSystem == nullptr)
 	if (mShape == nullptr || mSystem == nullptr)
@@ -996,8 +1021,7 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inGravity, Vec3Arg
 	new_position += down;
 	new_position += down;
 
 
 	// Move the character to the new location
 	// Move the character to the new location
-	SetPosition(new_position);
-	RefreshContacts(inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
+	MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
 	return true;
 	return true;
 }
 }
 
 
@@ -1024,8 +1048,7 @@ bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFil
 #endif // JPH_DEBUG_RENDERER
 #endif // JPH_DEBUG_RENDERER
 
 
 	// Move the character to the new location
 	// Move the character to the new location
-	SetPosition(new_position);
-	RefreshContacts(inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
+	MoveToContact(new_position, contact, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
 	return true;
 	return true;
 }
 }
 
 

+ 8 - 3
Jolt/Physics/Character/CharacterVirtual.h

@@ -129,9 +129,11 @@ public:
 	/// Character padding
 	/// Character padding
 	float								GetCharacterPadding() const								{ return mCharacterPadding; }
 	float								GetCharacterPadding() const								{ return mCharacterPadding; }
 
 
-	/// This is the main update function. It moves the character according to its current velocity. Note it's your own responsibility to apply gravity!
+	/// This is the main update function. It moves the character according to its current velocity (the character is similar to a kinematic body in the sense
+	/// that you set the velocity and the character will follow unless collision is blocking the way). Note it's your own responsibility to apply gravity to the character velocity!
+	/// Different surface materials (like ice) can be emulated by getting the ground material and adjusting the velocity and/or the max slope angle accordingly every frame.
 	/// @param inDeltaTime Time step to simulate.
 	/// @param inDeltaTime Time step to simulate.
-	/// @param inGravity Gravity vector (m/s^2)
+	/// @param inGravity Gravity vector (m/s^2). This gravity vector is only used when the character is standing on top of another object to apply downward force.
 	/// @param inBroadPhaseLayerFilter Filter that is used to check if the character collides with something in the broadphase.
 	/// @param inBroadPhaseLayerFilter Filter that is used to check if the character collides with something in the broadphase.
 	/// @param inObjectLayerFilter Filter that is used to check if a character collides with a layer.
 	/// @param inObjectLayerFilter Filter that is used to check if a character collides with a layer.
 	/// @param inBodyFilter Filter that is used to check if a character collides with a body.
 	/// @param inBodyFilter Filter that is used to check if a character collides with a body.
@@ -145,7 +147,7 @@ public:
 
 
 	/// When stair walking is needed, you can call the WalkStairs function to cast up, forward and down again to try to find a valid position
 	/// When stair walking is needed, you can call the WalkStairs function to cast up, forward and down again to try to find a valid position
 	/// @param inDeltaTime Time step to simulate.
 	/// @param inDeltaTime Time step to simulate.
-	/// @param inGravity Gravity vector (m/s^2)
+	/// @param inGravity Gravity vector (m/s^2). This gravity vector is only used when the character is standing on top of another object to apply downward force.
 	/// @param inStepUp The direction and distance to step up (this corresponds to the max step height)
 	/// @param inStepUp The direction and distance to step up (this corresponds to the max step height)
 	/// @param inStepForward The direction and distance to step forward after the step up
 	/// @param inStepForward The direction and distance to step forward after the step up
 	/// @param inStepForwardTest When running at a high frequency, inStepForward can be very small and it's likely that you hit the side of the stairs on the way down. This could produce a normal that violates the max slope angle. If this happens, we test again using this distance from the up position to see if we find a valid slope.
 	/// @param inStepForwardTest When running at a high frequency, inStepForward can be very small and it's likely that you hit the side of the stairs on the way down. This could produce a normal that violates the max slope angle. If this happens, we test again using this distance from the up position to see if we find a valid slope.
@@ -322,6 +324,9 @@ private:
 	// This function will determine which contacts are touching the character and will calculate the one that is supporting us
 	// This function will determine which contacts are touching the character and will calculate the one that is supporting us
 	void								UpdateSupportingContact(bool inSkipContactVelocityCheck, TempAllocator &inAllocator);
 	void								UpdateSupportingContact(bool inSkipContactVelocityCheck, TempAllocator &inAllocator);
 
 
+	/// This function can be called after moving the character to a new colliding position
+	void								MoveToContact(const Vec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator);
+
 	// This function returns the actual center of mass of the shape, not corrected for the character padding
 	// This function returns the actual center of mass of the shape, not corrected for the character padding
 	inline Mat44						GetCenterOfMassTransform(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape) const
 	inline Mat44						GetCenterOfMassTransform(Vec3Arg inPosition, QuatArg inRotation, const Shape *inShape) const
 	{
 	{

+ 14 - 1
Samples/Tests/Character/CharacterBaseTest.cpp

@@ -54,6 +54,9 @@ static const float cLargeBumpWidth = 0.1f;
 static const float cLargeBumpDelta = 2.0f;
 static const float cLargeBumpDelta = 2.0f;
 static const Vec3 cStairsPosition = Vec3(-15.0f, 0, 2.5f);
 static const Vec3 cStairsPosition = Vec3(-15.0f, 0, 2.5f);
 static const float cStairsStepHeight = 0.3f;
 static const float cStairsStepHeight = 0.3f;
+static const Vec3 cNoStairsPosition = Vec3(-15.0f, 0, 10.0f);
+static const float cNoStairsStepHeight = 0.3f;
+static const float cNoStairsStepDelta = 0.05f;
 static const Vec3 cMeshWallPosition = Vec3(-20.0f, 0, -27.0f);
 static const Vec3 cMeshWallPosition = Vec3(-20.0f, 0, -27.0f);
 static const float cMeshWallHeight = 3.0f;
 static const float cMeshWallHeight = 3.0f;
 static const float cMeshWallWidth = 2.0f;
 static const float cMeshWallWidth = 2.0f;
@@ -178,6 +181,16 @@ void CharacterBaseTest::Initialize()
 			}
 			}
 		}
 		}
 
 
+		// Create stairs with too little space between the steps
+		{
+			BodyCreationSettings step(new BoxShape(Vec3(2.0f, 0.5f * cNoStairsStepHeight, 0.5f * cNoStairsStepHeight)), Vec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
+			for (int i = 0; i < 10; ++i)
+			{
+				step.mPosition = cNoStairsPosition + Vec3(0, cNoStairsStepHeight * (0.5f + i), cNoStairsStepDelta * i);
+				mBodyInterface->CreateAndAddBody(step, EActivation::DontActivate);
+			}
+		}
+
 		// Create mesh with walls at varying angles
 		// Create mesh with walls at varying angles
 		{
 		{
 			TriangleList triangles;
 			TriangleList triangles;
@@ -323,7 +336,7 @@ void CharacterBaseTest::DrawCharacterState(const CharacterBase *inCharacter, Mat
 	case CharacterBase::EGroundState::OnGround:
 	case CharacterBase::EGroundState::OnGround:
 		color = Color::sGreen;
 		color = Color::sGreen;
 		break;
 		break;
-	case CharacterBase::EGroundState::Sliding:
+	case CharacterBase::EGroundState::OnSteepGround:
 		color = Color::sOrange;
 		color = Color::sOrange;
 		break;
 		break;
 	case CharacterBase::EGroundState::InAir:
 	case CharacterBase::EGroundState::InAir:

+ 1 - 1
Samples/Tests/Character/CharacterTest.cpp

@@ -71,7 +71,7 @@ void CharacterTest::HandleInput(Vec3Arg inMovementDirection, bool inJump, bool i
 {
 {
 	// Cancel movement in opposite direction of normal when sliding
 	// Cancel movement in opposite direction of normal when sliding
 	Character::EGroundState ground_state = mCharacter->GetGroundState();
 	Character::EGroundState ground_state = mCharacter->GetGroundState();
-	if (ground_state == Character::EGroundState::Sliding)
+	if (ground_state == Character::EGroundState::OnSteepGround)
 	{
 	{
 		Vec3 normal = mCharacter->GetGroundNormal();
 		Vec3 normal = mCharacter->GetGroundNormal();
 		normal.SetY(0.0f);
 		normal.SetY(0.0f);

+ 2 - 2
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -55,7 +55,7 @@ void CharacterVirtualTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	Vec3 old_position = mCharacter->GetPosition();
 	Vec3 old_position = mCharacter->GetPosition();
 
 
 	// Track that on ground before the update
 	// Track that on ground before the update
-	bool ground_to_air = mCharacter->GetGroundState() == CharacterBase::EGroundState::OnGround;
+	bool ground_to_air = mCharacter->GetGroundState() != CharacterBase::EGroundState::InAir;
 
 
 	// Update the character position (instant, do not have to wait for physics update)
 	// Update the character position (instant, do not have to wait for physics update)
 	mCharacter->Update(inParams.mDeltaTime, mPhysicsSystem->GetGravity(), mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING), mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING), { }, *mTempAllocator);
 	mCharacter->Update(inParams.mDeltaTime, mPhysicsSystem->GetGravity(), mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING), mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING), { }, *mTempAllocator);
@@ -130,7 +130,7 @@ void CharacterVirtualTest::HandleInput(Vec3Arg inMovementDirection, bool inJump,
 	// Cancel movement in opposite direction of normal when sliding
 	// Cancel movement in opposite direction of normal when sliding
 	CharacterVirtual::EGroundState ground_state = mCharacter->GetGroundState();
 	CharacterVirtual::EGroundState ground_state = mCharacter->GetGroundState();
 	Vec3 desired_velocity = mDesiredVelocity;
 	Vec3 desired_velocity = mDesiredVelocity;
-	if (ground_state == CharacterVirtual::EGroundState::Sliding)
+	if (ground_state == CharacterVirtual::EGroundState::OnSteepGround)
 	{
 	{
 		Vec3 normal = mCharacter->GetGroundNormal();
 		Vec3 normal = mCharacter->GetGroundNormal();
 		normal.SetY(0.0f);
 		normal.SetY(0.0f);