Estimating turning circle based on steering angle

I came across this section while working on my driving game that uses Chaos Vehicles in UE5. Thought I would share my findings. The explanation of turning circles sparked my curiosity and came across this nice article that explains how a turning circle can be calculated based on the average steering angle from the Ackermann steering model:

I then used that formula to calculate it in my game. Since I am using chaos vehicles, I can read the max steering angle from the value configured on the wheel blueprints. My UVehicleMovementComponent extends the UE5 UChaosWheeledVehicleMovementComponent. I had to look at the source code of that class and some of the physics models classes to find the values I needed.

float UVehicleMovementComponent::GetTurningCircleRadius(float CurrentMaxSteeringAngle) const
{
	// Based on formula from https://www.ehow.co.uk/how_7225784_calculate-turning-circle.html
	// Turning circle = (track / 2) + (wheelbase / sin(average steer angle))
	// Track is the length of the wheel track/axle from one wheel to another
	// Wheelbase is length from center of front wheel track to centre of rear wheel track
	// average steer angle is GetCurrentMaxSteeringAngle

	if (CurrentMaxSteeringAngle < 0)
	{
		CurrentMaxSteeringAngle = GetCurrentMaxSteeringAngle();
	}

	if (FMath::IsNearlyZero(CurrentMaxSteeringAngle))
	{
		return 0.0f;
	}

	const auto& lWheelTrackDimensions = GetWheelLayoutDimensions();
	// In ChaosWheelVehicleComponent.h
	// PSteeringConfig.TrackWidth = WheelTrackDimensions.Y;
	// PSteeringConfig.WheelBase = WheelTrackDimensions.X;
	const float AxleLength = lWheelTrackDimensions.Y;
	const float WheelbaseLength = lWheelTrackDimensions.X;

	float TurningCircleRadius = 0.5f * AxleLength + WheelbaseLength / FMath::Sin(FMath::DegreesToRadians(CurrentMaxSteeringAngle));

	return TurningCircleRadius;
}
float UVehicleMovementComponent::GetCurrentMaxSteeringAngle() const
{
	check(VehicleSimulationPT && VehicleSimulationPT->PVehicle);

	auto VehicleSim = VehicleSimulationPT->PVehicle.Get();

	const auto& WheelSimArray = VehicleSim->Wheels;
	// GetSteeringAngle is not declared const so this must be a non-const ref
	auto& SteeringSim = VehicleSim->GetSteering();

	const float SteeringFraction = GetCurrentMaxSteeringFraction();

	float SteerAngleSum{};
	int NumSteerWheels{};

	for (int i = 0, Len = WheelSimArray.Num(); i < Len; ++i)
	{
		const auto& Wheel = WheelSimArray[i];

		if (!Wheel.SteeringEnabled)
		{
			continue;
		}

		const float WheelMaxSteerAngle = WheelSimArray[i].MaxSteeringAngle * SteeringFraction;
		if (FMath::IsNearlyZero(WheelMaxSteerAngle))
		{
			continue;
		}

		// See UChaosWheeledVehicleSimulation::ProcessSteering
		const float WheelSide = VehicleSim->GetSuspension(i).GetLocalRestingPosition().Y;
		const float WheelSteerAngle = SteeringSim.GetSteeringAngle(WheelMaxSteerAngle, WheelMaxSteerAngle, WheelSide);

		SteerAngleSum += WheelMaxSteerAngle;
		++NumSteerWheels;
	}


	if (NumSteerWheels == 0)
	{
		return 0.0f;
	}

	return SteerAngleSum / NumSteerWheels;
}
float UVehicleMovementComponent::GetCurrentMaxSteeringFraction() const
{
	check(VehicleSimulationPT && VehicleSimulationPT->PVehicle);

	return VehicleSimulationPT->PVehicle->GetSteering().GetSteeringFromVelocity(FMath::Abs(GetForwardSpeedMPH()));
}
1 Like