Forráskód Böngészése

Moved part of the example code to CharacterVirtual so it is easier to reuse (#295)

Jorrit Rouwe 2 éve
szülő
commit
42eb736654

+ 87 - 0
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -885,6 +885,26 @@ void CharacterVirtual::MoveShape(Vec3 &ioPosition, Vec3Arg inVelocity, float inD
 	}
 }
 
+Vec3 CharacterVirtual::CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const
+{
+	Vec3 desired_velocity = inDesiredVelocity;
+	for (const Contact &c : mActiveContacts)
+		if (c.mHadCollision
+			&& IsSlopeTooSteep(c.mSurfaceNormal))
+		{
+			Vec3 normal = c.mSurfaceNormal;
+
+			// Remove normal vertical component
+			normal -= normal.Dot(mUp) * mUp;
+
+			// Cancel horizontal movement in opposite direction
+			float dot = normal.Dot(desired_velocity);
+			if (dot < 0.0f)
+				desired_velocity -= (dot * normal) / normal.LengthSq();
+		}
+	return desired_velocity;
+}
+
 void CharacterVirtual::Update(float inDeltaTime, Vec3Arg inGravity, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, TempAllocator &inAllocator)
 {
 	// If there's no delta time, we don't need to do anything
@@ -1145,6 +1165,73 @@ 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)
+{
+	// Update the velocity
+	Vec3 desired_velocity = mLinearVelocity;
+	mLinearVelocity = CancelVelocityTowardsSteepSlopes(desired_velocity);
+	
+	// Remember old position
+	Vec3 old_position = mPosition;
+
+	// Track if on ground before the update
+	bool ground_to_air = IsSupported();
+
+	// Update the character position (instant, do not have to wait for physics update)
+	Update(inDeltaTime, inGravity, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
+
+	// ... and that we got into air after
+	if (IsSupported())
+		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 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);
+	}
+
+	// If walk stairs enabled
+	if (!inWalkStairsStepUp.IsNearZero())
+	{
+		// Calculate how much we wanted to move horizontally
+		Vec3 desired_horizontal_step = desired_velocity * inDeltaTime;
+		desired_horizontal_step -= desired_horizontal_step.Dot(mUp) * mUp;
+		float desired_horizontal_step_len = desired_horizontal_step.Length();
+		if (desired_horizontal_step_len > 0.0f)
+		{
+			// Calculate how much we moved horizontally
+			Vec3 achieved_horizontal_step = mPosition - old_position;
+			achieved_horizontal_step -= achieved_horizontal_step.Dot(mUp) * mUp;
+
+			// 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;
+			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
+				&& CanWalkStairs(desired_velocity))
+			{
+				// Calculate how much we should step forward
+				// 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);
+
+				// 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;
+
+				WalkStairs(inDeltaTime, inWalkStairsStepUp, step_forward, step_forward_test, inWalkStairsStepDownExtra, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inAllocator);
+			}
+		}
+	}
+}
+
 void CharacterVirtual::SaveState(StateRecorder &inStream) const
 {
 	CharacterBase::SaveState(inStream);

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

@@ -129,6 +129,12 @@ public:
 	/// Character padding
 	float								GetCharacterPadding() const								{ return mCharacterPadding; }
 
+	/// This function can be called prior to calling Update() to convert a desired velocity into a velocity that won't make the character move further onto steep slopes.
+	/// This velocity can then be set on the character using SetLinearVelocity()
+	/// @param inDesiredVelocity Velocity to clamp against steep walls
+	/// @return A new velocity vector that won't make the character move up steep slopes
+	Vec3								CancelVelocityTowardsSteepSlopes(Vec3Arg inDesiredVelocity) const;
+
 	/// 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.
@@ -169,6 +175,23 @@ 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);
 
+	/// 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 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);
+
 	/// 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);
 

+ 21 - 76
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -15,9 +15,11 @@ JPH_IMPLEMENT_RTTI_VIRTUAL(CharacterVirtualTest)
 	JPH_ADD_BASE_CLASS(CharacterVirtualTest, CharacterBaseTest)
 }
 
-static const Vec3 cStepUpHeight = Vec3(0.0f, 0.4f, 0.0f);
-static const float cMinStepForward = 0.02f;
-static const float cStepForwardTest = 0.15f;
+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()
 {
@@ -56,61 +58,18 @@ void CharacterVirtualTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	// Remember old position
 	Vec3 old_position = mCharacter->GetPosition();
 
-	// Track that on ground before the update
-	bool ground_to_air = mCharacter->IsSupported();
-
-	// 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);
-
-	// ... and that we got into air after
-	if (mCharacter->IsSupported())
-		ground_to_air = false;
-
-	// If we're in air for the first frame and the user has enabled stick to floor
-	if (sEnableStickToFloor && ground_to_air)
-	{
-		// If we're not moving up, stick to the floor
-		float velocity = (mCharacter->GetPosition().GetY() - old_position.GetY()) / inParams.mDeltaTime;
-		if (velocity <= 1.0e-6f)
-			mCharacter->StickToFloor(Vec3(0, -0.5f, 0), mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING), mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING), { }, *mTempAllocator);
-	}
-
-	// Allow user to turn off walk stairs algorithm
-	if (sEnableWalkStairs)
-	{
-		// Calculate how much we wanted to move horizontally
-		Vec3 desired_horizontal_step = mDesiredVelocity * inParams.mDeltaTime;
-		float desired_horizontal_step_len = desired_horizontal_step.Length();
-		if (desired_horizontal_step_len > 0.0f)
-		{
-			// Calculate how much we moved horizontally
-			Vec3 achieved_horizontal_step = mCharacter->GetPosition() - old_position;
-			achieved_horizontal_step.SetY(0);
-
-			// 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;
-			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))
-			{
-				// Calculate how much we should step forward
-				// 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(cMinStepForward, 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 * cStepForwardTest;
-
-				mCharacter->WalkStairs(inParams.mDeltaTime, cStepUpHeight, step_forward, step_forward_test, Vec3::sZero(), mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING), mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING), { }, *mTempAllocator);
-			}
-		}
-	}
+	// Update the character position
+	mCharacter->ExtendedUpdate(inParams.mDeltaTime,
+		mPhysicsSystem->GetGravity(),
+		sEnableStickToFloor? cStickToFloorStepDown : Vec3::sZero(),
+		sEnableWalkStairs? cWalkStairsStepUp : Vec3::sZero(),
+		cWalkStairsMinStepForward,
+		cWalkStairsStepForwardTest,
+		cWalkStairsStepDownExtra,
+		mPhysicsSystem->GetDefaultBroadPhaseLayerFilter(Layers::MOVING),
+		mPhysicsSystem->GetDefaultLayerFilter(Layers::MOVING),
+		{ },
+		*mTempAllocator);
 
 	// Calculate effective velocity
 	Vec3 new_position = mCharacter->GetPosition();
@@ -132,25 +91,11 @@ void CharacterVirtualTest::HandleInput(Vec3Arg inMovementDirection, bool inJump,
 	// True if the player intended to move
 	mAllowSliding = !inMovementDirection.IsNearZero();
 
-	// Cancel movement in opposite direction of normal when touching something we can't walk up
-	CharacterVirtual::EGroundState ground_state = mCharacter->GetGroundState();
-	Vec3 desired_velocity = mDesiredVelocity;
-	if (ground_state == CharacterVirtual::EGroundState::OnSteepGround
-		|| ground_state == CharacterVirtual::EGroundState::NotSupported)
-	{
-		Vec3 normal = mCharacter->GetGroundNormal();
-		normal.SetY(0.0f);
-		float dot = normal.Dot(desired_velocity);
-		if (dot < 0.0f)
-			desired_velocity -= (dot * normal) / normal.LengthSq();
-	}
-
+	// Determine new basic velocity
 	Vec3 current_vertical_velocity = Vec3(0, mCharacter->GetLinearVelocity().GetY(), 0);
-
 	Vec3 ground_velocity = mCharacter->GetGroundVelocity();
-
 	Vec3 new_velocity;
-	if (ground_state == CharacterVirtual::EGroundState::OnGround // If on ground
+	if (mCharacter->GetGroundState() == CharacterVirtual::EGroundState::OnGround // If on ground
 		&& (current_vertical_velocity.GetY() - ground_velocity.GetY()) < 0.1f) // And not moving away from ground
 	{
 		// Assume velocity of ground when on ground
@@ -167,9 +112,9 @@ void CharacterVirtualTest::HandleInput(Vec3Arg inMovementDirection, bool inJump,
 	new_velocity += mPhysicsSystem->GetGravity() * inDeltaTime;
 
 	// Player input
-	new_velocity += desired_velocity;
+	new_velocity += mDesiredVelocity;
 
-	// Update the velocity
+	// Update character velocity
 	mCharacter->SetLinearVelocity(new_velocity);
 
 	// Stance switch