Code/Vehicle/Wheel/WheelCollider.Suspension.cs
using System;
using Sandbox;
namespace Meteor.VehicleTool.Vehicle.Wheel;
public partial class WheelCollider
{
private float minSuspensionLength = 3;
private float maxSuspensionLength = 3;
private float suspensionTotalLength;
public float Load { get; private set; }
[Group( "Spring" ), Property] public float MinSuspensionLength { get => minSuspensionLength; set { minSuspensionLength = value; UpdateTotalSuspensionLength(); } }
[Group( "Spring" ), Property] public float MaxSuspensionLength { get => maxSuspensionLength; set { maxSuspensionLength = value; UpdateTotalSuspensionLength(); } }
[Group( "Spring" ), Property] public float SuspensionStiffness { get; set; } = 16000.0f;
public Vector3 SuspensionForce { get; private set; }
/// <summary>
/// The speed coefficient of the spring / suspension extension when not on the ground. how fast the wheels extend when in the air.
/// The setting of 1 will result in suspension fully extending in 1 second, 2 in 0.5s, 3 in 0.333s, etc.
/// Recommended value is 6-10.
/// </summary>
[Group( "Spring" ), Property, Range( 0.01f, 30f )]
public float SuspensionExtensionSpeed { get; set; } = 6f;
public float SuspensionLength { get; private set; }
private void UpdateTotalSuspensionLength()
=> suspensionTotalLength = maxSuspensionLength + minSuspensionLength;
public Vector3 GetCenter()
=> WorldTransform.PointToWorld( Vector3.Down * (SuspensionLength - minSuspensionLength) );
private float prevlength = 0;
private void UpdateSuspension( float dt )
{
prevlength = SuspensionLength;
if ( !IsGrounded )
{
SuspensionLength += dt * suspensionTotalLength * SuspensionExtensionSpeed;
}
else
{
SuspensionLength = GroundHit.Fraction * suspensionTotalLength;
}
if ( SuspensionLength <= 0f )
{
SuspensionLength = 0;
}
else if ( SuspensionLength >= suspensionTotalLength )
{
SuspensionLength = suspensionTotalLength;
IsGrounded = false;
}
if ( IsGrounded )
{
var suspensionCompression = (suspensionTotalLength - SuspensionLength) / suspensionTotalLength;
var compressionVelocity = (prevlength - SuspensionLength).InchToMeter() / dt;
var dampingForce = CalculateDamperForce( compressionVelocity ) + GroundVelocity.z.InchToMeter();
var springForce = SuspensionStiffness * suspensionCompression;
Load = Math.Max( 0, springForce + dampingForce );
SuspensionForce = GroundHit.Normal * Load;
CarBody.ApplyImpulseAt( WorldPosition, SuspensionForce );
}
else
{
Load = 0;
}
}
public float CalculateDamperForce( in float velocity )
{
if ( velocity > 0f )
return CalculateBumpForce( velocity );
return CalculateReboundForce( velocity );
}
/// <summary>
/// Bump rate of the damper in Ns/m.
/// </summary>
[Property, Group( "Damper" )] public float BumpRate { get; set; } = 7000f;
/// <summary>
/// Rebound rate of the damper in Ns/m.
/// </summary>
[Property, Group( "Damper" )] public float ReboundRate { get; set; } = 7000f;
/// <summary>
/// Slow bump slope for the damper, used for damper velocity below bumpDivisionVelocity.
/// Value of 1 means that the bump force increases proportionally to the compression velocity.
/// </summary>
[Property, Group( "Damper" )]
[Range( 0f, 3f )]
public float SlowBump { get; set; } = 1.4f;
/// <summary>
/// Fast bump slope for the damper, used for damper velocity above bumpDivisionVelocity.
/// Value of 1 means that the bump force increases proportionally to the compression velocity.
/// </summary>
[Property, Group( "Damper" )]
[Range( 0f, 3f )]
public float FastBump { get; set; } = 0.6f;
/// <summary>
/// Damper velocity at which the bump slope switches from the slowBump to fastBump.
/// </summary>
[Property, Group( "Damper" )]
[Range( 0f, 0.2f )]
public float BumpDivisionVelocity { get; set; } = 0.1f;
/// <summary>
/// Slow rebound slope for the damper, used for damper velocity below reboundDivisionVelocity.
/// Value of 1 means that the rebound force increases proportionally to the extension velocity.
/// </summary>
[Property, Group( "Damper" )]
[Range( 0f, 3f )]
public float SlowRebound { get; set; } = 1.6f;
/// <summary>
/// Fast rebound slope for the damper, used for damper velocity above reboundDivisionVelocity.
/// Value of 1 means that the rebound force increases proportionally to the extension velocity.
/// </summary>
[Property, Group( "Damper" )]
[Range( 0f, 3f )]
public float FastRebound { get; set; } = 0.6f;
/// <summary>
/// Damper velocity at which the rebound slope switches from the slowRebound to fastRebound.
/// </summary>
[Property, Group( "Damper" )]
[Range( 0f, 0.2f )]
public float ReboundDivisionVelocity { get; set; } = 0.1f;
private float CalculateBumpForce( in float velocity )
{
if ( velocity < 0f ) return 0f; // We are in rebound, return.
float x = velocity;
float y;
if ( x < BumpDivisionVelocity )
{
y = x * SlowBump;
}
else
{
y = BumpDivisionVelocity * SlowBump + (x - BumpDivisionVelocity) * FastBump;
}
return y * BumpRate;
}
private float CalculateReboundForce( in float velocity )
{
if ( velocity > 0f ) return 0f; // We are in bump, return.
float x = -velocity;
float y;
if ( x < ReboundDivisionVelocity )
{
y = x * SlowRebound;
}
else
{
y = ReboundDivisionVelocity * SlowRebound + (x - ReboundDivisionVelocity) * FastRebound;
}
return -y * ReboundRate;
}
}