Browse Source

Fixed character jittering issue when moving down on elevator (#708)

Because the character update and physics tick happen at different moments, we use the current velocities of the bodies to predict the state at the next physics tick and move the character accordingly. For a character on an elevator moving down this meant that it would predict a position that's correct for the next physics tick, but for the current state is inside the elevator a bit. The collision check that does a final check after the movement has been calculated (GetFirstContactForSweep) uses the current position of the physics bodies and it will prevent the character from moving into the elevator.

There was an issue in the way GetFirstContactForSweep handled character padding which meant it didn't actually detect a collision with the elevator (the sweep was not long enough) so the character would be allowed to sink in the elevator. Every X frames the character would have sunken into the elevator enough for GetFirstContactForSweep to detect a collision without the character padding and it would suddenly shift the character up with this padding. This resulted in a visual jitter.

Note that there are still some artifacts that are really hard to avoid:

* When moving down, the player will visually hover a little bit above the elevator because the collision check doesn't take into account that the elevator will move next tick (we have to be conservative because we cannot guarantee that it will actually move with the velocity we see in the character update, the velocity of a dynamic object cannot be predicted without running the full physics tick)
* When transitioning from moving down to up, the player will briefly lose contact with the floor because it sees the updated elevator velocity and moves clear of the elevator resulting in the InAir state.

Should fix #703
Jorrit Rouwe 1 year ago
parent
commit
1b21180c67
1 changed files with 11 additions and 5 deletions
  1. 11 5
      Jolt/Physics/Character/CharacterVirtual.cpp

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

@@ -340,10 +340,14 @@ bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDi
 	settings.mUseShrunkenShapeAndConvexRadius = true;
 	settings.mReturnDeepestPoint = false;
 
+	// Calculate how much extra fraction we need to add to the cast to account for the character padding
+	float character_padding_fraction = mCharacterPadding / sqrt(displacement_len_sq);
+
 	// Cast shape
 	Contact contact;
-	contact.mFraction = 1.0f + FLT_EPSILON;
+	contact.mFraction = 1.0f + character_padding_fraction;
 	ContactCastCollector collector(mSystem, this, inDisplacement, mUp, inIgnoredContacts, start.GetTranslation(), contact);
+	collector.ResetEarlyOutFraction(contact.mFraction);
 	RShapeCast shape_cast(mShape, Vec3::sReplicate(1.0f), start, inDisplacement);
 	mSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, start.GetTranslation(), collector, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter);
 	if (contact.mBodyB.IsInvalid())
@@ -371,9 +375,12 @@ bool CharacterVirtual::GetFirstContactForSweep(RVec3Arg inPosition, Vec3Arg inDi
 	{
 		// When there's only a single contact point or when we were unable to correct the fraction,
 		// we can just move the fraction back so that the character and its padding don't hit the contact point anymore
-		outContact.mFraction = max(0.0f, outContact.mFraction - mCharacterPadding / sqrt(displacement_len_sq));
+		outContact.mFraction = max(0.0f, outContact.mFraction - character_padding_fraction);
 	}
 
+	// Ensure that we never return a fraction that's bigger than 1 (which could happen due to float precision issues).
+	outContact.mFraction = min(outContact.mFraction, 1.0f);
+
 	return true;
 }
 
@@ -1237,9 +1244,8 @@ bool CharacterVirtual::WalkStairs(float inDeltaTime, Vec3Arg inStepUp, Vec3Arg i
 #endif // JPH_DEBUG_RENDERER
 
 	// Move down towards the floor.
-	// 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;
+	// Note that we travel the same amount down as we travelled up with the specified extra
+	Vec3 down = -up + inStepDownExtra;
 	if (!GetFirstContactForSweep(new_position, down, contact, dummy_ignored_contacts, inBroadPhaseLayerFilter, inObjectLayerFilter, inBodyFilter, inShapeFilter))
 		return false; // No floor found, we're in mid air, cancel stair walk