using System;
using System.Collections.Generic;
using Sandbox;
namespace VeloX;
public partial class Clutch : PowertrainComponent
{
protected override void OnAwake()
{
base.OnAwake();
Name ??= "Clutch";
}
///
/// RPM at which automatic clutch will try to engage.
///
[Property] public float EngagementRPM { get; set; } = 1200f;
public float ThrottleEngagementOffsetRPM = 400f;
///
/// Clutch engagement in range [0,1] where 1 is fully engaged clutch.
/// Affected by Slip Torque field as the clutch can transfer [clutchEngagement * SlipTorque] Nm
/// meaning that higher value of SlipTorque will result in more sensitive clutch.
///
[Range( 0, 1 ), Property] public float ClutchInput { get; set; }
///
/// Curve representing pedal travel vs. clutch engagement. Should start at 0,0 and end at 1,1.
///
[Property] public Curve EngagementCurve { get; set; } = new( new List() { new( 0, 0 ), new( 1, 1 ) } );
public enum ClutchControlType
{
Automatic,
Manual
}
[Property] public ClutchControlType СontrolType { get; set; } = ClutchControlType.Automatic;
///
/// The RPM range in which the clutch will go from disengaged to engaged and vice versa.
/// E.g. if set to 400 and engagementRPM is 1000, 1000 will mean clutch is fully disengaged and
/// 1400 fully engaged. Setting it too low might cause clutch to hunt/oscillate.
///
[Property] public float EngagementRange { get; set; } = 400f;
///
/// Torque at which the clutch will slip / maximum torque that the clutch can transfer.
/// This value also affects clutch engagement as higher slip value will result in clutch
/// that grabs higher up / sooner. Too high slip torque value combined with low inertia of
/// powertrain components might cause instability in powertrain solver.
///
[Property] public float SlipTorque { get; set; } = 500f;
///
/// Amount of torque that will be passed through clutch even when completely disengaged
/// to emulate torque converter creep on automatic transmissions.
/// Should be higher than rolling resistance of the wheels to get the vehicle rolling.
///
[Range( 0, 100f ), Property] public float CreepTorque { get; set; } = 0;
[Property] public float CreepSpeedLimit { get; set; } = 1f;
///
/// Clutch engagement based on ClutchInput and the clutchEngagementCurve
///
[Property, ReadOnly]
public float Engagement => _clutchEngagement;
private float _clutchEngagement;
protected override void OnStart()
{
base.OnStart();
SlipTorque = Controller.Engine.EstimatedPeakTorque * 1.5f;
}
public override float QueryAngularVelocity( float angularVelocity, float dt )
{
InputAngularVelocity = angularVelocity;
// Return input angular velocity if InputNameHash or OutputNameHash is 0
if ( InputNameHash == 0 || OutputNameHash == 0 )
{
return InputAngularVelocity;
}
// Adjust clutch engagement based on conditions
if ( СontrolType == ClutchControlType.Automatic )
{
Engine engine = Controller.Engine;
// Override engagement when shifting to smoothly engage and disengage gears
if ( Controller.Transmission.IsShifting )
{
float shiftProgress = Controller.Transmission.ShiftProgress;
ClutchInput = MathF.Abs( MathF.Cos( MathF.PI * shiftProgress ) );
}
// Clutch engagement calculation for automatic clutch
else
{
// Calculate engagement
// Engage the clutch if the input spinning faster than the output, but also if vice versa.
float throttleInput = Controller.SwappedThrottle;
float finalEngagementRPM = 500 + ThrottleEngagementOffsetRPM * (throttleInput * throttleInput);
float referenceRPM = MathF.Max( InputRPM, OutputRPM );
ClutchInput = (referenceRPM - finalEngagementRPM) / EngagementRange;
ClutchInput = Math.Clamp( ClutchInput, 0f, 1f );
// Avoid disconnecting clutch at high speed
if ( engine.OutputRPM > engine.IdleRPM * 1.1f && Controller.TotalSpeed > 3f )
{
ClutchInput = 1f;
}
if ( Controller.SwappedBrakes > 0 && Controller.SwappedThrottle == 0 )
{
ClutchInput = 0;
}
}
if ( Controller.IsClutching > 0 )
{
ClutchInput = 1 - Controller.IsClutching;
}
}
else if ( СontrolType == ClutchControlType.Manual )
{
// Manual clutch engagement through user input
ClutchInput = Controller.IsClutching;
}
OutputAngularVelocity = InputAngularVelocity * _clutchEngagement;
float Wout = Output.QueryAngularVelocity( OutputAngularVelocity, dt ) * _clutchEngagement;
float Win = angularVelocity * (1f - _clutchEngagement);
return Wout + Win;
}
public override float QueryInertia()
{
if ( OutputNameHash == 0 )
{
return Inertia;
}
float I = Inertia + Output.QueryInertia() * _clutchEngagement;
return I;
}
public override float ForwardStep( float torque, float inertiaSum, float dt )
{
InputTorque = torque;
InputInertia = inertiaSum;
if ( OutputNameHash == 0 )
return torque;
// Get the clutch engagement point from the input value
// Do not use the clutchEnagement directly for any calculations!
_clutchEngagement = EngagementCurve.Evaluate( ClutchInput );
_clutchEngagement = Math.Clamp( _clutchEngagement, 0, 1 );
// Calculate output inertia and torque based on the clutch engagement
// Assume half of the inertia is on the input plate and the other half is on the output clutch plate.
float halfClutchInertia = Inertia * 0.5f;
OutputInertia = (inertiaSum + halfClutchInertia) * _clutchEngagement + halfClutchInertia;
// Allow the torque output to be only up to the slip torque valu
float outputTorqueClamp = Controller.Engine.EstimatedPeakTorque * 1.5f * _clutchEngagement;
OutputTorque = InputTorque;
OutputTorque = Math.Clamp( OutputTorque, 0, outputTorqueClamp );
float slipOverflowTorque = -Math.Min( outputTorqueClamp - OutputTorque, 0 );
// Apply the creep torque commonly caused by torque converter drag in automatic transmissions
//ApplyCreepTorque( ref OutputTorque, CreepTorque );
// Send the torque downstream
float returnTorque = _output.ForwardStep( OutputTorque, OutputInertia, dt ) * _clutchEngagement;
// Clamp the return torque to the slip torque of the clutch once again
//returnTorque = Math.Clamp( returnTorque, -SlipTorque, SlipTorque );
// Torque returned to the input is a combination of torque returned by the powertrain and the torque that
// was possibly never sent downstream
return returnTorque + slipOverflowTorque;
}
private void ApplyCreepTorque( ref float torque, float creepTorque )
{
// Apply creep torque to forward torque
if ( creepTorque != 0 && Controller.Engine.IsRunning && Controller.TotalSpeed < CreepSpeedLimit )
{
bool torqueWithinCreepRange = torque < creepTorque && torque > -creepTorque;
if ( torqueWithinCreepRange )
{
torque = creepTorque;
}
}
}
}