Browse Source

Character improvements (#230)

- Added drawing of constraints during solving
- Fixed issue where walk stairs wouldn't kick in if sliding backwards while trying to move forwards
- Ability to manually specify the stair walk velocity in CanWalkStairs
- Fixed issue where sweep down would be too short and would miss the ground during stair walk
- Added more bumps to the character test level
Jorrit Rouwe 2 years ago
parent
commit
691b0f42d9

+ 43 - 9
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -327,7 +327,11 @@ bool CharacterVirtual::HandleContact(Vec3Arg inVelocity, Constraint &ioConstrain
 	return true;
 }
 
-void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, Vec3Arg inGravity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator) const
+void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, Vec3Arg inGravity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator
+#ifdef JPH_DEBUG_RENDERER
+	, bool inDrawConstraints
+#endif // JPH_DEBUG_RENDERER
+	) const
 {
 	// If there are no constraints we can immediately move to our target
 	if (ioConstraints.empty())
@@ -516,12 +520,33 @@ void CharacterVirtual::SolveConstraints(Vec3Arg inVelocity, Vec3Arg inGravity, f
 
 			// Add all components together
 			new_velocity = velocity_in_slide_dir + perpendicular_velocity + other_perpendicular_velocity;
-		}			
+		}
 
 		// Allow application to modify calculated velocity
 		if (mListener != nullptr)
 			mListener->OnContactSolve(this, constraint->mContact->mBodyB, constraint->mContact->mSubShapeIDB, constraint->mContact->mPosition, constraint->mContact->mNormal, constraint->mContact->mLinearVelocity, constraint->mContact->mMaterial, velocity, new_velocity);
 
+#ifdef JPH_DEBUG_RENDERER
+		if (inDrawConstraints)
+		{
+			// Calculate where to draw
+			Vec3 offset = mPosition + Vec3(0, 0, 2.5f * (iteration + 1));
+
+			// Draw constraint plane
+			DebugRenderer::sInstance->DrawPlane(offset, constraint->mPlane.GetNormal(), Color::sCyan, 1.0f);
+
+			// Draw 2nd constraint plane
+			if (other_constraint != nullptr)
+				DebugRenderer::sInstance->DrawPlane(offset, other_constraint->mPlane.GetNormal(), Color::sBlue, 1.0f);
+
+			// Draw starting velocity
+			DebugRenderer::sInstance->DrawArrow(offset, offset + velocity, Color::sGreen, 0.05f);
+
+			// Draw resulting velocity
+			DebugRenderer::sInstance->DrawArrow(offset, offset + new_velocity, Color::sRed, 0.05f);
+		}
+#endif // JPH_DEBUG_RENDERER
+
 		// Update the velocity
 		velocity = new_velocity;
 
@@ -678,7 +703,11 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck,
 		float time_simulated;
 		IgnoredContactList ignored_contacts(inAllocator);
 		ignored_contacts.reserve(contacts.size());
-		SolveConstraints(-mUp, mSystem->GetGravity(), 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator);
+		SolveConstraints(-mUp, mSystem->GetGravity(), 1.0f, 1.0f, constraints, ignored_contacts, time_simulated, displacement, inAllocator
+		#ifdef JPH_DEBUG_RENDERER
+			, false
+		#endif // JPH_DEBUG_RENDERER
+			);
 
 		// If we're blocked then we're supported, otherwise we're sliding
 		float min_required_displacement_sq = Square(0.6f * mLastDeltaTime);
@@ -735,7 +764,7 @@ void CharacterVirtual::MoveShape(Vec3 &ioPosition, Vec3Arg inVelocity, Vec3Arg i
 				// Draw arrow towards surface that we're hitting
 				DebugRenderer::sInstance->DrawArrow(c.mContact->mPosition, c.mContact->mPosition - dist_to_plane, Color::sYellow, 0.05f);
 
-				// Draw plane around the player posiiton indicating the space that we can move
+				// Draw plane around the player position indicating the space that we can move
 				DebugRenderer::sInstance->DrawPlane(mPosition + dist_to_plane, c.mPlane.GetNormal(), Color::sCyan, 1.0f);
 			}
 		}
@@ -744,7 +773,11 @@ void CharacterVirtual::MoveShape(Vec3 &ioPosition, Vec3Arg inVelocity, Vec3Arg i
 		// Solve the displacement using these constraints
 		Vec3 displacement;
 		float time_simulated;
-		SolveConstraints(inVelocity, inGravity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator);
+		SolveConstraints(inVelocity, inGravity, inDeltaTime, time_remaining, constraints, ignored_contacts, time_simulated, displacement, inAllocator
+		#ifdef JPH_DEBUG_RENDERER
+			, sDrawConstraints
+		#endif // JPH_DEBUG_RENDERER
+			);
 
 		// Store the contacts now that the colliding ones have been marked
 		if (outActiveContacts != nullptr)
@@ -827,10 +860,10 @@ bool CharacterVirtual::SetShape(const Shape *inShape, float inMaxPenetrationDept
 	return mShape == inShape;
 }
 
-bool CharacterVirtual::CanWalkStairs() const
+bool CharacterVirtual::CanWalkStairs(Vec3Arg inLinearVelocity) const
 {
 	// Check if there's enough horizontal velocity to trigger a stair walk
-	Vec3 horizontal_velocity = mLinearVelocity - mLinearVelocity.Dot(mUp) * mUp;
+	Vec3 horizontal_velocity = inLinearVelocity - inLinearVelocity.Dot(mUp) * mUp;
 	if (horizontal_velocity.IsNearZero(1.0e-6f))
 		return false;
 
@@ -879,8 +912,9 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inGravity, Vec3Arg
 #endif // JPH_DEBUG_RENDERER
 
 	// Move down towards the floor.
-	// Note that we travel the same amount down as we travelled up with the specified extra
-	Vec3 down = -up + inStepDownExtra;
+	// Note that we travel the same amount down as we travelled up with the character padding and the specified extra
+	// If we don't add the character padding, we may miss the floor (note that GetFirstContactForSweep will subtract the padding when it finds a hit)
+	Vec3 down = -up - mCharacterPadding * mUp + inStepDownExtra;
 	if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator))
 		return false; // No floor found, we're in mid air, cancel stair walk
 

+ 7 - 2
Jolt/Physics/Character/CharacterVirtual.h

@@ -140,7 +140,8 @@ public:
 
 	/// This function will return true if the character has moved into a slope that is too steep (e.g. a vertical wall).
 	/// You would call WalkStairs to attempt to step up stairs.
-	bool								CanWalkStairs() const;
+	/// @param inLinearVelocity The linear velocity that the player desired. This is used to determine if we're pusing into a step.
+	bool								CanWalkStairs(Vec3Arg inLinearVelocity) const;
 
 	/// 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.
@@ -296,7 +297,11 @@ private:
 	void								DetermineConstraints(TempContactList &inContacts, ConstraintList &outConstraints) const;
 
 	// Use the constraints to solve the displacement of the character. This will slide the character on the planes around the origin for as far as possible.
-	void								SolveConstraints(Vec3Arg inVelocity, Vec3Arg inGravity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator) const;
+	void								SolveConstraints(Vec3Arg inVelocity, Vec3Arg inGravity, float inDeltaTime, float inTimeRemaining, ConstraintList &ioConstraints, IgnoredContactList &ioIgnoredContacts, float &outTimeSimulated, Vec3 &outDisplacement, TempAllocator &inAllocator
+	#ifdef JPH_DEBUG_RENDERER
+		, bool inDrawConstraints
+	#endif // JPH_DEBUG_RENDERER
+		) const;
 
 	// Handle contact with physics object that we're colliding against
 	bool								HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, Vec3Arg inGravity, float inDeltaTime) const;

+ 21 - 7
Samples/Tests/Character/CharacterBaseTest.cpp

@@ -43,11 +43,15 @@ static const Quat cRampOrientation = Quat::sRotation(Vec3::sAxisX(), -0.25f * JP
 static const Vec3 cRampBlocksStart = cRampPosition + Vec3(-3.0f, 3.0f, 1.5f);
 static const Vec3 cRampBlocksDelta = Vec3(2.0f, 0, 0);
 static const float cRampBlocksTime = 5.0f;
-static const Vec3 cBumpsPosition = Vec3(-5.0f, 0, 2.5f);
-static const float cBumpHeight = 0.05f;
-static const float cBumpWidth = 0.01f;
-static const float cBumpDelta = 0.5f;
-static const Vec3 cStairsPosition = Vec3(-10.0f, 0, 2.5f);
+static const Vec3 cSmallBumpsPosition = Vec3(-5.0f, 0, 2.5f);
+static const float cSmallBumpHeight = 0.05f;
+static const float cSmallBumpWidth = 0.01f;
+static const float cSmallBumpDelta = 0.5f;
+static const Vec3 cLargeBumpsPosition = Vec3(-10.0f, 0, 2.5f);
+static const float cLargeBumpHeight = 0.3f;
+static const float cLargeBumpWidth = 0.1f;
+static const float cLargeBumpDelta = 2.0f;
+static const Vec3 cStairsPosition = Vec3(-15.0f, 0, 2.5f);
 static const float cStairsStepHeight = 0.3f;
 
 void CharacterBaseTest::Initialize()
@@ -139,10 +143,20 @@ void CharacterBaseTest::Initialize()
 
 		// Create small bumps
 		{
-			BodyCreationSettings step(new BoxShape(Vec3(2.0f, 0.5f * cBumpHeight, 0.5f * cBumpWidth), 0.0f), Vec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
+			BodyCreationSettings step(new BoxShape(Vec3(2.0f, 0.5f * cSmallBumpHeight, 0.5f * cSmallBumpWidth), 0.0f), Vec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
 			for (int i = 0; i < 10; ++i)
 			{
-				step.mPosition = cBumpsPosition + Vec3(0, 0.5f * cBumpHeight, cBumpDelta * i);
+				step.mPosition = cSmallBumpsPosition + Vec3(0, 0.5f * cSmallBumpHeight, cSmallBumpDelta * i);
+				mBodyInterface->CreateAndAddBody(step, EActivation::DontActivate);
+			}
+		}
+
+		// Create large bumps
+		{
+			BodyCreationSettings step(new BoxShape(Vec3(2.0f, 0.5f * cLargeBumpHeight, 0.5f * cLargeBumpWidth)), Vec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING);
+			for (int i = 0; i < 5; ++i)
+			{
+				step.mPosition = cLargeBumpsPosition + Vec3(0, 0.5f * cLargeBumpHeight, cLargeBumpDelta * i);
 				mBodyInterface->CreateAndAddBody(step, EActivation::DontActivate);
 			}
 		}

+ 35 - 29
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -68,30 +68,35 @@ void CharacterVirtualTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	if (sEnableWalkStairs)
 	{
 		// Calculate how much we wanted to move horizontally
-		Vec3 desired_horizontal_step = mCharacter->GetLinearVelocity() * inParams.mDeltaTime;
-		desired_horizontal_step.SetY(0);
+		Vec3 desired_horizontal_step = mDesiredVelocity * inParams.mDeltaTime;
 		float desired_horizontal_step_len = desired_horizontal_step.Length();
-
-		// Calculate how much we moved horizontally
-		Vec3 achieved_horizontal_step = mCharacter->GetPosition() - old_position;
-		achieved_horizontal_step.SetY(0);
-		float achieved_horizontal_step_len = achieved_horizontal_step.Length();
-
-		// If we didn't move as far as we wanted and we're against a slope that's too steep
-		if (achieved_horizontal_step_len + 1.0e-4f < desired_horizontal_step_len
-			&& mCharacter->CanWalkStairs())
+		if (desired_horizontal_step_len > 0.0f)
 		{
-			// CanWalkStairs should not have returned true if we are in air
-			JPH_ASSERT(!ground_to_air);
+			// Calculate how much we moved horizontally
+			Vec3 achieved_horizontal_step = mCharacter->GetPosition() - old_position;
+			achieved_horizontal_step.SetY(0);
 
-			// Calculate how much we should step forward
+			// Only count movement in the direction of the desired movement
+			// (otherwise we find it ok if we're sliding downhill while we're trying to climb uphill)
 			Vec3 step_forward_normalized = desired_horizontal_step / desired_horizontal_step_len;
-			Vec3 step_forward = step_forward_normalized * (desired_horizontal_step_len - achieved_horizontal_step_len);
+			achieved_horizontal_step = max(0.0f, achieved_horizontal_step.Dot(step_forward_normalized)) * step_forward_normalized;
+			float achieved_horizontal_step_len = achieved_horizontal_step.Length();
+
+			// If we didn't move as far as we wanted and we're against a slope that's too steep
+			if (achieved_horizontal_step_len + 1.0e-4f < desired_horizontal_step_len
+				&& mCharacter->CanWalkStairs(mDesiredVelocity))
+			{
+				// CanWalkStairs should not have returned true if we are in air
+				JPH_ASSERT(!ground_to_air);
+
+				// Calculate how much we should step forward
+				Vec3 step_forward = step_forward_normalized * (desired_horizontal_step_len - achieved_horizontal_step_len);
 
-			// Calculate how far to scan ahead for a floor
-			Vec3 step_forward_test = step_forward_normalized * cMinStepForward;
+				// Calculate how far to scan ahead for a floor
+				Vec3 step_forward_test = step_forward_normalized * cMinStepForward;
 
-			mCharacter->WalkStairs(inParams.mDeltaTime, mPhysicsSystem->GetGravity(), cStepUpHeight, step_forward, step_forward_test, Vec3::sZero(), mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING), mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING), { }, *mTempAllocator);
+				mCharacter->WalkStairs(inParams.mDeltaTime, mPhysicsSystem->GetGravity(), cStepUpHeight, step_forward, step_forward_test, Vec3::sZero(), mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING), mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING), { }, *mTempAllocator);
+			}
 		}
 	}
 
@@ -116,23 +121,24 @@ void CharacterVirtualTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 
 void CharacterVirtualTest::HandleInput(Vec3Arg inMovementDirection, bool inJump, bool inSwitchStance, float inDeltaTime)
 {
+	// Smooth the player input
+	mDesiredVelocity = 0.25f * inMovementDirection * cCharacterSpeed + 0.75f * mDesiredVelocity;
+
+	// True if the player intended to move
+	mAllowSliding = !inMovementDirection.IsNearZero();
+
 	// Cancel movement in opposite direction of normal when sliding
 	CharacterVirtual::EGroundState ground_state = mCharacter->GetGroundState();
+	Vec3 desired_velocity = mDesiredVelocity;
 	if (ground_state == CharacterVirtual::EGroundState::Sliding)
 	{
 		Vec3 normal = mCharacter->GetGroundNormal();
 		normal.SetY(0.0f);
-		float dot = normal.Dot(inMovementDirection);
+		float dot = normal.Dot(desired_velocity);
 		if (dot < 0.0f)
-			inMovementDirection -= (dot * normal) / normal.LengthSq();
+			desired_velocity -= (dot * normal) / normal.LengthSq();
 	}
 
-	// Smooth the player input
-	mSmoothMovementDirection = 0.25f * inMovementDirection + 0.75f * mSmoothMovementDirection;
-
-	// True if the player intended to move
-	mAllowSliding = !inMovementDirection.IsNearZero();
-
 	Vec3 current_vertical_velocity = Vec3(0, mCharacter->GetLinearVelocity().GetY(), 0);
 
 	Vec3 ground_velocity = mCharacter->GetGroundVelocity();
@@ -155,7 +161,7 @@ void CharacterVirtualTest::HandleInput(Vec3Arg inMovementDirection, bool inJump,
 	new_velocity += mPhysicsSystem->GetGravity() * inDeltaTime;
 
 	// Player input
-	new_velocity += mSmoothMovementDirection * cCharacterSpeed;
+	new_velocity += desired_velocity;
 
 	// Update the velocity
 	mCharacter->SetLinearVelocity(new_velocity);
@@ -193,7 +199,7 @@ void CharacterVirtualTest::SaveState(StateRecorder &inStream) const
 	bool is_standing = mCharacter->GetShape() == mStandingShape;
 	inStream.Write(is_standing);
 
-	inStream.Write(mSmoothMovementDirection);
+	inStream.Write(mDesiredVelocity);
 }
 
 void CharacterVirtualTest::RestoreState(StateRecorder &inStream)
@@ -206,7 +212,7 @@ void CharacterVirtualTest::RestoreState(StateRecorder &inStream)
 	inStream.Read(is_standing);
 	mCharacter->SetShape(is_standing? mStandingShape : mCrouchingShape, FLT_MAX, { }, { }, { }, *mTempAllocator);
 
-	inStream.Read(mSmoothMovementDirection);
+	inStream.Read(mDesiredVelocity);
 }
 
 void CharacterVirtualTest::OnContactAdded(const CharacterVirtual *inCharacter, const BodyID &inBodyID2, const SubShapeID &inSubShapeID2, Vec3Arg inContactPosition, Vec3Arg inContactNormal, CharacterContactSettings &ioSettings)

+ 1 - 1
Samples/Tests/Character/CharacterVirtualTest.h

@@ -52,7 +52,7 @@ private:
 	Ref<CharacterVirtual>	mCharacter;
 
 	// Smoothed value of the player input
-	Vec3					mSmoothMovementDirection = Vec3::sZero();
+	Vec3					mDesiredVelocity = Vec3::sZero();
 
 	// True when the player is pressing movement controls
 	bool					mAllowSliding = false;