Custom ball physics - bounce issue

Hi,

I’m trying to build the physics of a soccer ball from scratch, but I got into some issues while making it stop bouncing and resting on the ground. I’ve spent days on this, but I just don’t get it what I should do. It doesn’t work for different linear drag values or gravity, high and low. While I play with those values, some time the ball rests, sometimes it doesn’t. I’m calling my physics at a constant timestep of 0.016.

Next is my entire ball physics up until now. Any idea how to properly handle ball impact bounce and resting on the ground?

void  ABall::CalculatePhysics(FPhysicsData& Data, float DeltaTime, bool IsFutureStep) const
{
	Data.PreviousLocation = Data.CurrentLocation;
	Data.PreviousRotation = Data.CurrentRotation;

	if (MagnusEffect > 0.f)
	{
		Data.Forces.Add(MagnusEffect * FVector::CrossProduct(Data.AngularVelocity, Data.LinearVelocity));
	}

	// Linear motion
	FVector NetForce = FVector::ZeroVector;
	for (const FVector& Force : Data.Forces)
	{
		NetForce += Force;
	}
	NetForce += -LinearDrag * 0.5f * 1.225e-6f * (Data.LinearVelocity * Data.LinearVelocity) * (3.14f * Radius * Radius);
	FVector Acceleration = (NetForce / Mass);
	if (!Data.IsOnGround)
	{
		Acceleration += FVector(0.f, 0.f, Gravity);
	}
	Data.LinearVelocity += Acceleration * DeltaTime;
	Data.CurrentLocation += Data.LinearVelocity * DeltaTime;

	// Angular motion
	FVector NetAngularForce = FVector::ZeroVector;
	for (const FVector& Force : Data.AngularForces)
	{
		NetAngularForce += Force;
	}
	FVector AngularAcceleration = NetAngularForce / InertiaTensor;
	Data.AngularVelocity = Data.AngularVelocity * FMath::Pow(AngularDamping, DeltaTime) + AngularAcceleration * DeltaTime;
	if (Data.AngularVelocity.Size() > MaxAngularVelocity)
	{
		Data.AngularVelocity = Data.AngularVelocity.GetSafeNormal() * MaxAngularVelocity;
	}
	Data.CurrentRotation *= (FRotator(Data.AngularVelocity.Y, Data.AngularVelocity.Z, Data.AngularVelocity.X) * DeltaTime).Quaternion();

	// Collision
	FHitResult HitResult;
	FCollisionResponseParams Params;
	bool bHitFound = GetWorld()->SweepSingleByChannel(
		HitResult,
		Data.PreviousLocation + (Data.LinearVelocity.GetSafeNormal() * 0.0001f),
		Data.CurrentLocation,
		FQuat::Identity,
		ECollisionChannel::ECC_WorldDynamic,
		FCollisionShape::MakeSphere(Radius),
		FCollisionQueryParams("BallCollisionTrace", false, this),
		FCollisionResponseParams()
	);
	if (!bHitFound || !HitResult.bBlockingHit)
	{
		Data.IsColliding = false;
	}
	else if (!Data.IsOnGround)
	{
		Data.CurrentLocation = HitResult.ImpactNormal * Radius + HitResult.ImpactPoint;

		if (!IsFutureStep)
		{
			Data.LinearVelocity = Restitution * Data.LinearVelocity.MirrorByVector(HitResult.ImpactNormal);

			FPhysicsData FutureData = Data;
			CalculatePhysics(FutureData, PhysicsTimestep, true);

			if (FutureData.LinearVelocity.Size() < 10.f)
			{
				Data.LinearVelocity.Z = 0.f;
				Data.IsOnGround = true;
			}
		}

		Data.IsColliding = true;
	}
}

“It doesn’t work for different linear drag values or gravity, high and low.” so what happens exactly? Is your ball not stopping? (linear drag will never force your ball to stop completly due to its implementation)

Here’s a video: Custom ball physics - bounce issue - YouTube
I’ve used 0.5 for drag and -5000 for Z gravity

And here’s with -1000 gravity: Custom ball physics - bounce issue - YouTube

Finally got it! While bouncing, at some point the new impact velocity would be too weak to change the direction of the moving ball, so all I had to do was to check if the next future velocity would be in the same direction as the recent applied impact velocity. If the directions are different (the Dot Product returning a negative value, -1), I just assume that the ball should rest on the ground.

if (FVector::DotProduct(Data.LinearVelocity, FutureData.LinearVelocity) < 0.f)
{
	Data.LinearVelocity.Z = 0.f;
	Data.IsOnGround = true;
}

Actually, when the ball was also moving in X and Y direction the ball was still falling through the ground. I had to check for this instead:

if (FutureData.LinearVelocity.Z < 0.f)
{
	Data.LinearVelocity.Z = 0.f;
	Data.IsOnGround = true;
}

I’m assuming the gravity is pointing down. If the gravity needs to have a different direction, this won’t work and some changes needs to be done.