Browse Source

Created interface to update the ground velocity for less sliding on a moving platform (#466)

* Improving character lagging behind on platform
* Added rotating and translating body to test level 
* Added rotating wall to test level
Jorrit Rouwe 2 years ago
parent
commit
54e3ce602a

+ 45 - 40
Jolt/Physics/Character/CharacterVirtual.cpp

@@ -59,6 +59,24 @@ void CharacterVirtual::GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLine
 		mListener->OnAdjustBodyVelocity(this, inBody, outLinearVelocity, outAngularVelocity);
 }
 
+Vec3 CharacterVirtual::CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const
+{
+	// Get angular velocity
+	float angular_velocity_len_sq = inAngularVelocity.LengthSq();
+	if (angular_velocity_len_sq < 1.0e-12f)
+		return inLinearVelocity;
+	float angular_velocity_len = sqrt(angular_velocity_len_sq);
+
+	// Calculate the rotation that the object will make in the time step
+	Quat rotation = Quat::sRotation(inAngularVelocity / angular_velocity_len, angular_velocity_len * inDeltaTime);
+
+	// Calculate where the new character position will be
+	RVec3 new_position = inCenterOfMass + rotation * Vec3(mPosition - inCenterOfMass);
+
+	// Calculate the velocity
+	return inLinearVelocity + Vec3(new_position - mPosition) / inDeltaTime;
+}
+
 template <class taCollector>
 void CharacterVirtual::sFillContactProperties(const CharacterVirtual *inCharacter, Contact &outContact, const Body &inBody, Vec3Arg inUp, RVec3Arg inBaseOffset, const taCollector &inCollector, const CollideShapeResult &inResult)
 {
@@ -778,51 +796,22 @@ void CharacterVirtual::UpdateSupportingContact(bool inSkipContactVelocityCheck,
 				else
 				{
 					// For keyframed objects that support us calculate the velocity at our position rather than at the contact position so that we properly follow the object
-					// Note that we don't just take the point velocity because a point on an object with angular velocity traces an arc,
-					// so if you just take point velocity * delta time you get an error that accumulates over time
-
-					// Determine center of mass and angular velocity
-					Vec3 angular_velocity;
-					RVec3 com;
+					BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);
+					if (lock.SucceededAndIsInBroadPhase())
 					{
-						BodyLockRead lock(mSystem->GetBodyLockInterface(), c.mBodyB);
-						if (lock.SucceededAndIsInBroadPhase())
-						{
-							const Body &body = lock.GetBody();
+						const Body &body = lock.GetBody();
 
-							// Get adjusted body velocity
-							Vec3 linear_velocity;
-							GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
-							
-							// Add the linear velocity to the average velocity
-							avg_velocity += linear_velocity;
+						// Get adjusted body velocity
+						Vec3 linear_velocity, angular_velocity;
+						GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
 
-							com = body.GetCenterOfMassPosition();
-						}
-						else
-						{
-							// Fall back to contact velocity
-							avg_velocity += c.mLinearVelocity;
-
-							angular_velocity = Vec3::sZero();
-							com = RVec3::sZero();
-						}
+						// Calculate the ground velocity
+						avg_velocity += CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);
 					}
-
-					// Get angular velocity
-					float angular_velocity_len_sq = angular_velocity.LengthSq();
-					if (angular_velocity_len_sq > 1.0e-12f)
+					else
 					{
-						float angular_velocity_len = sqrt(angular_velocity_len_sq);
-
-						// Calculate the rotation that the object will make in the time step
-						Quat rotation = Quat::sRotation(angular_velocity / angular_velocity_len, angular_velocity_len * mLastDeltaTime);
-
-						// Calculate where the new contact position will be
-						RVec3 new_position = com + rotation * Vec3(mPosition - com);
-
-						// Calculate the velocity
-						avg_velocity += Vec3(new_position - mPosition) / mLastDeltaTime;
+						// Fall back to contact velocity
+						avg_velocity += c.mLinearVelocity;
 					}
 				}
 			}
@@ -1056,6 +1045,22 @@ void CharacterVirtual::RefreshContacts(const BroadPhaseLayerFilter &inBroadPhase
 	StoreActiveContacts(contacts, inAllocator);
 }
 
+void CharacterVirtual::UpdateGroundVelocity()
+{
+	BodyLockRead lock(mSystem->GetBodyLockInterface(), mGroundBodyID);
+	if (lock.SucceededAndIsInBroadPhase())
+	{
+		const Body &body = lock.GetBody();
+
+		// Get adjusted body velocity
+		Vec3 linear_velocity, angular_velocity;
+		GetAdjustedBodyVelocity(body, linear_velocity, angular_velocity);
+
+		// Calculate the ground velocity
+		mGroundVelocity = CalculateCharacterGroundVelocity(body.GetCenterOfMassPosition(), linear_velocity, angular_velocity, mLastDeltaTime);
+	}
+}
+
 void CharacterVirtual::MoveToContact(RVec3Arg inPosition, const Contact &inContact, const BroadPhaseLayerFilter &inBroadPhaseLayerFilter, const ObjectLayerFilter &inObjectLayerFilter, const BodyFilter &inBodyFilter, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator)
 {
 	// Set the new position

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

@@ -244,6 +244,10 @@ public:
 	/// 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, const ShapeFilter &inShapeFilter, TempAllocator &inAllocator);
 
+	/// Use the ground body ID to get an updated estimate of the ground velocity. This function can be used if the ground body has moved / changed velocity and you want a new estimate of the ground velocity.
+	/// It will not perform collision detection, so is less accurate than RefreshContacts but a lot faster.
+	void								UpdateGroundVelocity();
+
 	/// Switch the shape of the character (e.g. for stance).
 	/// @param inShape The shape to switch to.
 	/// @param inMaxPenetrationDepth When inMaxPenetrationDepth is not FLT_MAX, it checks if the new shape collides before switching shape. This is the max penetration we're willing to accept after the switch.
@@ -401,6 +405,11 @@ private:
 	// Get the velocity of a body adjusted by the contact listener
 	void								GetAdjustedBodyVelocity(const Body& inBody, Vec3 &outLinearVelocity, Vec3 &outAngularVelocity) const;
 
+	// Calculate the ground velocity of the character assuming it's standing on an object with specified linear and angular velocity and with specified center of mass.
+	// Note that we don't just take the point velocity because a point on an object with angular velocity traces an arc,
+	// so if you just take point velocity * delta time you get an error that accumulates over time
+	Vec3								CalculateCharacterGroundVelocity(RVec3Arg inCenterOfMass, Vec3Arg inLinearVelocity, Vec3Arg inAngularVelocity, float inDeltaTime) const;
+
 	// Handle contact with physics object that we're colliding against
 	bool								HandleContact(Vec3Arg inVelocity, Constraint &ioConstraint, float inDeltaTime) const;
 

+ 13 - 2
Samples/Tests/Character/CharacterBaseTest.cpp

@@ -39,6 +39,10 @@ const char *CharacterBaseTest::sSceneName = "ObstacleCourse";
 // Scene constants
 static const RVec3 cRotatingPosition(-5, 0.15f, 15);
 static const Quat cRotatingOrientation = Quat::sIdentity();
+static const RVec3 cRotatingWallPosition(5, 1.0f, 25.0f);
+static const Quat cRotatingWallOrientation = Quat::sIdentity();
+static const RVec3 cRotatingAndTranslatingPosition(-5, 0.15f, 27.5f);
+static const Quat cRotatingAndTranslatingOrientation = Quat::sIdentity();
 static const RVec3 cVerticallyMovingPosition(0, 2.0f, 15);
 static const Quat cVerticallyMovingOrientation = Quat::sIdentity();
 static const RVec3 cHorizontallyMovingPosition(5, 1, 15);
@@ -110,6 +114,8 @@ void CharacterBaseTest::Initialize()
 			// Kinematic blocks to test interacting with moving objects
 			Ref<Shape> kinematic = new BoxShape(Vec3(1, 0.15f, 3.0f));
 			mRotatingBody = mBodyInterface->CreateAndAddBody(BodyCreationSettings(kinematic, cRotatingPosition, cRotatingOrientation, EMotionType::Kinematic, Layers::MOVING), EActivation::Activate);
+			mRotatingWallBody = mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(3.0f, 1, 0.15f)), cRotatingWallPosition, cRotatingWallOrientation, EMotionType::Kinematic, Layers::MOVING), EActivation::Activate);
+			mRotatingAndTranslatingBody = mBodyInterface->CreateAndAddBody(BodyCreationSettings(kinematic, cRotatingAndTranslatingPosition, cRotatingAndTranslatingOrientation, EMotionType::Kinematic, Layers::MOVING), EActivation::Activate);
 			mVerticallyMovingBody = mBodyInterface->CreateAndAddBody(BodyCreationSettings(kinematic, cVerticallyMovingPosition, cVerticallyMovingOrientation, EMotionType::Kinematic, Layers::MOVING), EActivation::Activate);
 			mHorizontallyMovingBody = mBodyInterface->CreateAndAddBody(BodyCreationSettings(kinematic, cHorizontallyMovingPosition, cHorizontallyMovingOrientation, EMotionType::Kinematic, Layers::MOVING), EActivation::Activate);
 		}
@@ -525,11 +531,13 @@ void CharacterBaseTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 			jump = true;
 	}
 
-	HandleInput(control_input, jump, switch_stance, inParams.mDeltaTime);
-
 	// Animate bodies
 	if (!mRotatingBody.IsInvalid())
 		mBodyInterface->MoveKinematic(mRotatingBody, cRotatingPosition, Quat::sRotation(Vec3::sAxisY(), JPH_PI * Sin(mTime)), inParams.mDeltaTime);
+	if (!mRotatingWallBody.IsInvalid())
+		mBodyInterface->MoveKinematic(mRotatingWallBody, cRotatingWallPosition, Quat::sRotation(Vec3::sAxisY(), JPH_PI * Sin(mTime)), inParams.mDeltaTime);
+	if (!mRotatingAndTranslatingBody.IsInvalid())
+		mBodyInterface->MoveKinematic(mRotatingAndTranslatingBody, cRotatingAndTranslatingPosition + 5.0f * Vec3(Sin(JPH_PI * mTime), 0, Cos(JPH_PI * mTime)), Quat::sRotation(Vec3::sAxisY(), JPH_PI * Sin(mTime)), inParams.mDeltaTime);
 	if (!mHorizontallyMovingBody.IsInvalid())
 		mBodyInterface->MoveKinematic(mHorizontallyMovingBody, cHorizontallyMovingPosition + Vec3(3.0f * Sin(mTime), 0, 0), cHorizontallyMovingOrientation, inParams.mDeltaTime);
 	if (!mVerticallyMovingBody.IsInvalid())
@@ -546,6 +554,9 @@ void CharacterBaseTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 		}
 		mRampBlocksTimeLeft = cRampBlocksTime;
 	}
+
+	// Call handle input after new velocities have been set to avoid frame delay
+	HandleInput(control_input, jump, switch_stance, inParams.mDeltaTime);
 }
 
 void CharacterBaseTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)

+ 2 - 0
Samples/Tests/Character/CharacterBaseTest.h

@@ -97,6 +97,8 @@ private:
 
 	// Moving bodies
 	BodyID					mRotatingBody;
+	BodyID					mRotatingWallBody;
+	BodyID					mRotatingAndTranslatingBody;
 	BodyID					mVerticallyMovingBody;
 	BodyID					mHorizontallyMovingBody;
 };

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

@@ -42,6 +42,7 @@ void CharacterTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	// Draw state of character
 	DrawCharacterState(mCharacter, mCharacter->GetWorldTransform(), mCharacter->GetLinearVelocity());
 }
+
 void CharacterTest::PostPhysicsUpdate(float inDeltaTime)
 {
 	// Fetch the new ground properties

+ 4 - 0
Samples/Tests/Character/CharacterVirtualTest.cpp

@@ -111,6 +111,10 @@ void CharacterVirtualTest::HandleInput(Vec3Arg inMovementDirection, bool inJump,
 	mCharacter->SetUp(character_up_rotation.RotateAxisY());
 	mCharacter->SetRotation(character_up_rotation);
 
+	// A cheaper way to update the character's ground velocity, 
+	// the platforms that the character is standing on may have changed velocity
+	mCharacter->UpdateGroundVelocity();
+
 	// Determine new basic velocity
 	Vec3 current_vertical_velocity = mCharacter->GetLinearVelocity().Dot(mCharacter->GetUp()) * mCharacter->GetUp();
 	Vec3 ground_velocity = mCharacter->GetGroundVelocity();