Kaynağa Gözat

Character tweaks (#296)

- ExtendedUpdate will now adjust the 'forward test' direction towards the slope so that stair walking under an angle works better
- Moved config parameters of ExtendedUpdate to a struct
Jorrit Rouwe 2 yıl önce
ebeveyn
işleme
8f82525a41

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

@@ -887,6 +887,12 @@ void CharacterVirtual::MoveShape(Vec3 &ioPosition, Vec3Arg inVelocity, float inD
 
 Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const
 {
+	// If we're not pushing against a steep slope, return the desired velocity
+	// Note: This is important as WalkStairs overrides the ground state to OnGround when its first check fails but the second succeeds
+	if (mGroundState == CharacterVirtual::EGroundState::OnGround
+		|| mGroundState == CharacterVirtual::EGroundState::InAir)
+		return inDesiredVelocity;
+		
 	Vec3 desired_velocity = inDesiredVelocity;
 	for (const Contact &c : mActiveContacts)
 		if (c.mHadCollision
@@ -1165,7 +1171,7 @@ bool CharacterVirtual::StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFil
 	return true;
 }
 
-void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, Vec3Arg inStickToFloorStepDown, Vec3Arg inWalkStairsStepUp, float inWalkStairsMinStepForward, float inWalkStairsStepForwardTest, Vec3Arg inWalkStairsStepDownExtra, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
+void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
 {
 	// Update the velocity
 	Vec3 desired_velocity = mLinearVelocity;
@@ -1185,16 +1191,16 @@ void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, Vec3
 		ground_to_air = false;
 
 	// If stick to floor enabled and we're going from supported to not supported
-	if (ground_to_air && !inStickToFloorStepDown.IsNearZero())
+	if (ground_to_air && !inSettings.mStickToFloorStepDown.IsNearZero())
 	{
 		// If we're not moving up, stick to the floor
 		float velocity = (mPosition - old_position).Dot(mUp) / inDeltaTime;
 		if (velocity <= 1.0e-6f)
-			StickToFloor(Vec3(0, -0.5f, 0), inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
+			StickToFloor(inSettings.mStickToFloorStepDown, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
 	}
 
 	// If walk stairs enabled
-	if (!inWalkStairsStepUp.IsNearZero())
+	if (!inSettings.mWalkStairsStepUp.IsNearZero())
 	{
 		// Calculate how much we wanted to move horizontally
 		Vec3 desired_horizontal_step = desired_velocity * inDeltaTime;
@@ -1220,13 +1226,24 @@ void CharacterVirtual::ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, Vec3
 				// Note that we clamp the step forward to a minimum distance. This is done because at very high frame rates the delta time
 				// may be very small, causing a very small step forward. If the step becomes small enough, we may not move far enough
 				// horizontally to actually end up at the top of the step.
-				Vec3 step_forward = step_forward_normalized * max(inWalkStairsMinStepForward, desired_horizontal_step_len - achieved_horizontal_step_len);
+				Vec3 step_forward = step_forward_normalized * max(inSettings.mWalkStairsMinStepForward, desired_horizontal_step_len - achieved_horizontal_step_len);
 
 				// Calculate how far to scan ahead for a floor. This is only used in case the floor normal at step_forward is too steep.
 				// In that case an additional check will be performed at this distance to check if that normal is not too steep.
-				Vec3 step_forward_test = step_forward_normalized * inWalkStairsStepForwardTest;
+				// Start with the ground normal in the horizontal plane and normalizing it
+				Vec3 step_forward_test = -mGroundNormal;
+				step_forward_test -= step_forward_test.Dot(mUp) * mUp;
+				step_forward_test = step_forward_test.NormalizedOr(step_forward_normalized);
 
-				WalkStairs(inDeltaTime, inWalkStairsStepUp, step_forward, step_forward_test, inWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
+				// If this normalized vector and the character forward vector is bigger than a preset angle, we use the character forward vector instead of the ground normal
+				// to do our forward test
+				if (step_forward_test.Dot(step_forward_normalized) < inSettings.mWalkStairsCosAngleForwardContact)
+					step_forward_test = step_forward_normalized;
+
+				// Calculate the correct magnitude for the test vector
+				step_forward_test *= inSettings.mWalkStairsStepForwardTest;
+
+				WalkStairs(inDeltaTime, inSettings.mWalkStairsStepUp, step_forward, step_forward_test, inSettings.mWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
 			}
 		}
 	}

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

@@ -156,7 +156,7 @@ public:
 	/// @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 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 inStepDownExtra An additional translation that is added when stepping down at the end. Allows you to step further down than up. Set to zero if you don't want this.
+	/// @param inStepDownExtra An additional translation that is added when stepping down at the end. Allows you to step further down than up. Set to zero if you don't want this. Should be in the opposite direction of up.
 	/// @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 inBodyFilter Filter that is used to check if a character collides with a body.
@@ -175,22 +175,29 @@ public:
 	/// @return True if the character was successfully projected onto the floor.
 	bool								StickToFloor(Vec3Arg inStepDown, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator);
 
+	/// Settings struct with settings for ExtendedUpdate
+	struct ExtendedUpdateSettings
+	{
+		Vec3Arg							mStickToFloorStepDown { 0, -0.5f, 0 };									///< See StickToFloor inStepDown parameter. Can be zero to turn off.
+		Vec3Arg							mWalkStairsStepUp { 0, 0.4f, 0 };										///< See WalkStairs inStepUp parameter. Can be zero to turn off.
+		float							mWalkStairsMinStepForward { 0.02f };									///< See WalkStairs inStepForward parameter. Note that the parameter only indicates a magnitude, direction is taken from current velocity.
+		float							mWalkStairsStepForwardTest { 0.15f };									///< See WalkStairs inStepForwardTest parameter. Note that the parameter only indicates a magnitude, direction is taken from current velocity.
+		float							mWalkStairsCosAngleForwardContact { cos(DegreesToRadians(75.0f)) };		///< Cos(angle) where angle is the maximum angle between the ground normal in the horizontal plane and the character forward vector where we're willing to adjust the step forward test towards the contact normal.
+		Vec3Arg							mWalkStairsStepDownExtra { Vec3::sZero() };								///< See WalkStairs inStepDownExtra
+	};
+
 	/// This function combines Update, StickToFloor and WalkStairs. This function serves as an example of how these functions could be combined.
 	/// Before calling, call SetLinearVelocity to update the horizontal/vertical speed of the character, typically this is:
 	/// - When on OnGround and not moving away from ground: velocity = GetGroundVelocity() + horizontal speed as input by player + optional vertical jump velocity + delta time * gravity
 	/// - Else: velocity = current vertical velocity + horizontal speed as input by player + delta time * gravity
 	/// @param inDeltaTime Time step to simulate.
 	/// @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 inStickToFloorStepDown See StickToFloor inStepDown parameter.
-	/// @param inWalkStairsStepUp See WalkStairs inStepUp parameter.
-	/// @param inWalkStairsMinStepForward See WalkStairs inStepForward parameter. Note that the parameter only indicates a magnitude, direction is taken from current velocity.
-	/// @param inWalkStairsStepForwardTest See WalkStairs inStepForwardTest parameter. Note that the parameter only indicates a magnitude, direction is taken from current velocity.
-	/// @param inWalkStairsStepDownExtra See WalkStairs inStepDownExtra
+	/// @param inSettings A structure containing settings for the algorithm.
 	/// @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 inBodyFilter Filter that is used to check if a character collides with a body.
 	/// @param inAllocator An allocator for temporary allocations. All memory will be freed by the time this function returns.
-	void								ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, Vec3Arg inStickToFloorStepDown, Vec3Arg inWalkStairsStepUp, float inWalkStairsMinStepForward, float inWalkStairsStepForwardTest, Vec3Arg inWalkStairsStepDownExtra, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator);
+	void								ExtendedUpdate(float inDeltaTime, Vec3Arg inGravity, const ExtendedUpdateSettings &inSettings, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator);
 
 	/// This function can be used after a character has teleported to determine the new contacts with the world.
 	void								RefreshContacts(const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator);

+ 8 - 11
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -15,12 +15,6 @@ JPH_IMPLEMENT_RTTI_VIRTUAL(CharacterVirtualTest)
 	JPH_ADD_BASE_CLASS(CharacterVirtualTest, CharacterBaseTest)
 }
 
-static const Vec3 cStickToFloorStepDown(0, -0.5f, 0);
-static const Vec3 cWalkStairsStepUp = Vec3(0.0f, 0.4f, 0.0f);
-static const float cWalkStairsMinStepForward = 0.02f;
-static const float cWalkStairsStepForwardTest = 0.15f;
-static const Vec3 cWalkStairsStepDownExtra = Vec3::sZero();
-
 void CharacterVirtualTest::Initialize()
 {
 	CharacterBaseTest::Initialize();
@@ -58,14 +52,17 @@ void CharacterVirtualTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	// Remember old position
 	Vec3 old_position = mCharacter->GetPosition();
 
+	// Settings for our update function
+	CharacterVirtual::ExtendedUpdateSettings update_settings;
+	if (!sEnableStickToFloor)
+		update_settings.mStickToFloorStepDown = Vec3::sZero();
+	if (!sEnableWalkStairs)
+		update_settings.mWalkStairsStepUp = Vec3::sZero();
+
 	// Update the character position
 	mCharacter->ExtendedUpdate(inParams.mDeltaTime,
 		mPhysicsSystem->GetGravity(),
-		sEnableStickToFloor? cStickToFloorStepDown : Vec3::sZero(),
-		sEnableWalkStairs? cWalkStairsStepUp : Vec3::sZero(),
-		cWalkStairsMinStepForward,
-		cWalkStairsStepForwardTest,
-		cWalkStairsStepDownExtra,
+		update_settings,
 		mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING),
 		mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING),
 		{ },