Code/Vehicle/VehicleController.Springs.cs
using Sandbox;

namespace Bugges.VehicleController;

partial class VehicleController : Component
{
	public const string SPRINGS = "Springs";

	[Order( -7 )]
	[Property( Title = "Strength" ), Feature( SPRINGS )]
	private float Spring_Strength { get; set; } = 100000.0f;

	[Property( Title = "Damping" ), Feature( SPRINGS )]
	private float Spring_Damping { get; set; } = 10000.0f;

	[Property( Title = "Min Distance" ), Feature( SPRINGS )]
	private float Spring_MinDistance { get; set; } = 15.0f;

	[Property( Title = "Max Distance" ), Feature( SPRINGS )]
	private float Spring_MaxDistance { get; set; } = 35.0f;

	[Property( Title = "Rest Distance" ), Feature( SPRINGS )]
	private float Spring_RestDistance { get; set; } = 30.0f;

	[Property( Title = "Compression Multiplier" ), Feature( SPRINGS )]
	private float Spring_OverCompressionMultiplier { get; set; } = 5.0f;


	private void ApplySpring( Wheel wheel, SceneTraceResult trace )
	{
		if ( !trace.Hit )
		{
			wheel.CurrentDownforce = 0f;
			return;
		}

		float traceDistance = GetTraceDistance( wheel, trace );

		Vector3 contactPoint = GetWheelContactPoint( wheel, trace );
		Vector3 tireWorldVel = Body.GetVelocityAtPoint( contactPoint );
		float offset = Spring_RestDistance - traceDistance;

		float velocity = Vector3.Dot( trace.Normal, tireWorldVel );
		float force = (offset * Spring_Strength) - (velocity * Spring_Damping);

		if ( traceDistance < Spring_MinDistance )
		{
			float overCompression = Spring_MinDistance - traceDistance;
			force += overCompression * Spring_Strength * Spring_OverCompressionMultiplier;
		}

		if ( force < 0 ) force = 0;

		wheel.CurrentDownforce = force;

		Body.ApplyForceAt( wheel.Spring.WorldPosition, trace.Normal * force );
	}
}