first commit
This commit is contained in:
358
Code/Car/VeloXCar.Engine.cs
Normal file
358
Code/Car/VeloXCar.Engine.cs
Normal file
@@ -0,0 +1,358 @@
|
||||
using Sandbox;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
public partial class VeloXCar
|
||||
{
|
||||
[Property, Feature( "Engine" )] public EngineStream Stream { get; set; }
|
||||
public EngineStreamPlayer StreamPlayer { get; set; }
|
||||
|
||||
[Property, Feature( "Engine" )] public float MinRPM { get; set; } = 800;
|
||||
[Property, Feature( "Engine" )] public float MaxRPM { get; set; } = 7000;
|
||||
[Property, Feature( "Engine" ), Range( -1, 1 )]
|
||||
public float PowerDistribution
|
||||
{
|
||||
get => powerDistribution; set
|
||||
{
|
||||
powerDistribution = value;
|
||||
UpdatePowerDistribution();
|
||||
}
|
||||
}
|
||||
[Property, Feature( "Engine" )] public float FlyWheelMass { get; set; } = 80f;
|
||||
[Property, Feature( "Engine" )] public float FlyWheelRadius { get; set; } = 0.5f;
|
||||
[Property, Feature( "Engine" )] public float FlywheelFriction { get; set; } = -6000;
|
||||
[Property, Feature( "Engine" )] public float FlywheelTorque { get; set; } = 20000;
|
||||
[Property, Feature( "Engine" )] public float EngineBrakeTorque { get; set; } = 2000;
|
||||
[Property, Feature( "Engine" )]
|
||||
public Dictionary<int, float> Gears { get; set; } = new()
|
||||
{
|
||||
[-1] = 2.5f,
|
||||
[0] = 0f,
|
||||
[1] = 2.8f,
|
||||
[2] = 1.7f,
|
||||
[3] = 1.2f,
|
||||
[4] = 0.9f,
|
||||
[5] = 0.75f,
|
||||
[6] = 0.7f
|
||||
};
|
||||
[Property, Feature( "Engine" )] public float DifferentialRatio { get; set; } = 1f;
|
||||
[Property, Feature( "Engine" ), Range( 0, 1 )] public float TransmissionEfficiency { get; set; } = 0.8f;
|
||||
[Property, Feature( "Engine" )] private float MinRPMTorque { get; set; } = 5000f;
|
||||
[Property, Feature( "Engine" )] private float MaxRPMTorque { get; set; } = 8000f;
|
||||
[Sync] public int Gear { get; set; } = 0;
|
||||
[Sync] public float Clutch { get; set; } = 1;
|
||||
[Sync( SyncFlags.Interpolate )] public float EngineRPM { get; set; }
|
||||
public float RPMPercent => (EngineRPM - MinRPM) / MaxRPM;
|
||||
|
||||
private const float TAU = MathF.Tau;
|
||||
|
||||
private int MinGear { get; set; }
|
||||
private int MaxGear { get; set; }
|
||||
|
||||
[Sync] public bool IsRedlining { get; private set; }
|
||||
|
||||
private float flywheelVelocity;
|
||||
private TimeUntil switchCD = 0;
|
||||
private float groundedCount;
|
||||
private float burnout;
|
||||
|
||||
private float frontBrake;
|
||||
private float rearBrake;
|
||||
|
||||
private float availableFrontTorque;
|
||||
private float availableRearTorque;
|
||||
|
||||
private float avgSideSlip;
|
||||
private float avgPoweredRPM;
|
||||
private float avgForwardSlip;
|
||||
|
||||
private float inputThrottle, inputBrake;
|
||||
private bool inputHandbrake;
|
||||
private float transmissionRPM;
|
||||
private float powerDistribution;
|
||||
|
||||
public float FlywheelRPM
|
||||
{
|
||||
get => flywheelVelocity * 60 / TAU;
|
||||
set
|
||||
{
|
||||
flywheelVelocity = value * TAU / 60; EngineRPM = value;
|
||||
}
|
||||
}
|
||||
private void UpdateGearList()
|
||||
{
|
||||
int minGear = 0;
|
||||
int maxGear = 0;
|
||||
|
||||
foreach ( var (gear, ratio) in Gears )
|
||||
{
|
||||
|
||||
if ( gear < minGear )
|
||||
minGear = gear;
|
||||
|
||||
if ( gear > maxGear )
|
||||
maxGear = gear;
|
||||
|
||||
if ( minGear != 0 || maxGear != 0 )
|
||||
{
|
||||
SwitchGear( 0, false );
|
||||
}
|
||||
}
|
||||
|
||||
MinGear = minGear;
|
||||
MaxGear = maxGear;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void SwitchGear( int index, bool cooldown = true )
|
||||
{
|
||||
if ( Gear == index ) return;
|
||||
index = Math.Clamp( index, MinGear, MaxGear );
|
||||
|
||||
if ( index == 0 || !cooldown )
|
||||
switchCD = 0;
|
||||
else
|
||||
switchCD = 0.3f;
|
||||
Clutch = 1;
|
||||
Gear = index;
|
||||
}
|
||||
|
||||
public float TransmissionToEngineRPM( int gear ) => avgPoweredRPM * Gears[gear] * DifferentialRatio * 60 / TAU;
|
||||
public float GetTransmissionMaxRPM( int gear ) => FlywheelRPM / Gears[gear] / DifferentialRatio;
|
||||
|
||||
private void UpdatePowerDistribution()
|
||||
{
|
||||
if ( Wheels is null ) return;
|
||||
int frontCount = 0, rearCount = 0;
|
||||
|
||||
foreach ( var wheel in Wheels )
|
||||
{
|
||||
if ( wheel.IsFront )
|
||||
frontCount++;
|
||||
else
|
||||
rearCount++;
|
||||
|
||||
}
|
||||
|
||||
float frontDistribution = 0.5f + PowerDistribution * 0.5f;
|
||||
|
||||
float rearDistribution = 1 - frontDistribution;
|
||||
|
||||
frontDistribution /= frontCount;
|
||||
rearDistribution /= rearCount;
|
||||
|
||||
foreach ( var wheel in Wheels )
|
||||
if ( wheel.IsFront )
|
||||
wheel.DistributionFactor = frontDistribution;
|
||||
else
|
||||
wheel.DistributionFactor = rearDistribution;
|
||||
}
|
||||
|
||||
private void EngineAccelerate( float torque, float dt )
|
||||
{
|
||||
var inertia = 0.5f * FlyWheelMass * FlyWheelRadius * FlyWheelRadius;
|
||||
var angularAcceleration = torque / inertia;
|
||||
|
||||
flywheelVelocity += angularAcceleration * dt;
|
||||
}
|
||||
|
||||
private float GetTransmissionTorque( int gear, float minTorque, float maxTorque )
|
||||
{
|
||||
var torque = FlywheelRPM.Remap( MinRPM, MaxRPM, minTorque, maxTorque, true );
|
||||
torque *= (1 - Clutch);
|
||||
torque = torque * Gears[gear] * DifferentialRatio * TransmissionEfficiency;
|
||||
|
||||
return gear == -1 ? -torque : torque;
|
||||
}
|
||||
|
||||
private void AutoGearSwitch()
|
||||
{
|
||||
if ( ForwardSpeed < 100 && Input.Down( "Backward" ) )
|
||||
{
|
||||
SwitchGear( -1, false );
|
||||
return;
|
||||
}
|
||||
|
||||
var currentGear = Gear;
|
||||
|
||||
if ( currentGear < 0 && ForwardSpeed < -100 )
|
||||
return;
|
||||
|
||||
|
||||
if ( Math.Abs( avgForwardSlip ) > 10 )
|
||||
return;
|
||||
|
||||
var gear = Math.Clamp( currentGear, 1, MaxGear );
|
||||
|
||||
float minRPM = MinRPM, maxRPM = MaxRPM;
|
||||
|
||||
|
||||
maxRPM *= 0.98f;
|
||||
|
||||
float gearRPM;
|
||||
|
||||
|
||||
for ( int i = 1; i <= MaxGear; i++ )
|
||||
{
|
||||
gearRPM = TransmissionToEngineRPM( i );
|
||||
|
||||
if ( (i == 1 && gearRPM < minRPM) || (gearRPM > minRPM && gearRPM < maxRPM) )
|
||||
{
|
||||
gear = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var threshold = minRPM + (maxRPM - minRPM) * (0.5 - Throttle * 0.3);
|
||||
if ( gear < currentGear && gear > currentGear - 2 && EngineRPM > threshold )
|
||||
return;
|
||||
|
||||
SwitchGear( gear );
|
||||
}
|
||||
|
||||
private float EngineClutch( float dt )
|
||||
{
|
||||
if ( !switchCD )
|
||||
{
|
||||
inputThrottle = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( inputHandbrake )
|
||||
return 1;
|
||||
|
||||
var absForwardSpeed = Math.Abs( ForwardSpeed );
|
||||
|
||||
if ( groundedCount < 1 && absForwardSpeed > 30 )
|
||||
return 1;
|
||||
|
||||
if ( ForwardSpeed < -50 && inputBrake > 0 && Gear < 0 )
|
||||
return 1;
|
||||
|
||||
if ( absForwardSpeed > 200 )
|
||||
return 0;
|
||||
|
||||
return inputThrottle > 0.1f ? 0 : 1;
|
||||
}
|
||||
|
||||
private void EngineThink( float dt )
|
||||
{
|
||||
inputThrottle = Input.Down( "Forward" ) ? 1 : 0;
|
||||
inputBrake = Input.Down( "Backward" ) ? 1 : 0;
|
||||
inputHandbrake = Input.Down( "Jump" );
|
||||
if ( burnout > 0 )
|
||||
{
|
||||
SwitchGear( 1, false );
|
||||
if ( inputThrottle < 0.1f || inputBrake < 0.1f )
|
||||
burnout = 0;
|
||||
}
|
||||
else
|
||||
AutoGearSwitch();
|
||||
|
||||
if ( Gear < 0 )
|
||||
(inputBrake, inputThrottle) = (inputThrottle, inputBrake);
|
||||
|
||||
var rpm = FlywheelRPM;
|
||||
|
||||
var clutch = EngineClutch( dt );
|
||||
|
||||
if ( inputThrottle > 0.1 && inputBrake > 0.1 && Math.Abs( ForwardSpeed ) < 50 )
|
||||
{
|
||||
burnout = MathX.Approach( burnout, 1, dt * 2 );
|
||||
Clutch = 0;
|
||||
|
||||
}
|
||||
else if ( inputHandbrake )
|
||||
{
|
||||
frontBrake = 0f;
|
||||
rearBrake = 0.5f;
|
||||
Clutch = 1;
|
||||
clutch = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( (Gear == -1 || Gear == 1) && inputThrottle < 0.05f && inputBrake < 0.1f && groundedCount > 1 && rpm < MinRPM * 1.2f )
|
||||
inputBrake = 0.2f;
|
||||
|
||||
frontBrake = inputBrake * 0.5f;
|
||||
rearBrake = inputBrake * 0.5f;
|
||||
}
|
||||
|
||||
clutch = MathX.Approach( Clutch, clutch, dt * ((Gear < 2 && inputThrottle > 0.1f) ? 6 : 2) );
|
||||
|
||||
Clutch = clutch;
|
||||
|
||||
var isRedlining = false;
|
||||
transmissionRPM = 0;
|
||||
|
||||
if ( Gear != 0 )
|
||||
{
|
||||
transmissionRPM = TransmissionToEngineRPM( Gear );
|
||||
transmissionRPM = Gear < 0 ? -transmissionRPM : transmissionRPM;
|
||||
rpm = (rpm * clutch) + (MathF.Max( 0, transmissionRPM ) * (1 - clutch));
|
||||
}
|
||||
var throttle = Throttle;
|
||||
|
||||
var gearTorque = GetTransmissionTorque( Gear, MinRPMTorque, MaxRPMTorque );
|
||||
|
||||
var availableTorque = gearTorque * throttle;
|
||||
|
||||
if ( transmissionRPM < 0 )
|
||||
{
|
||||
availableTorque += gearTorque * 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
var engineBrakeTorque = GetTransmissionTorque( Gear, EngineBrakeTorque, EngineBrakeTorque );
|
||||
|
||||
availableTorque -= engineBrakeTorque * (1 - throttle) * 0.5f;
|
||||
}
|
||||
|
||||
var maxRPM = MaxRPM;
|
||||
|
||||
if ( rpm < MinRPM )
|
||||
{
|
||||
rpm = MinRPM;
|
||||
}
|
||||
else if ( rpm > maxRPM )
|
||||
{
|
||||
|
||||
if ( rpm > maxRPM * 1.2f )
|
||||
availableTorque = 0;
|
||||
|
||||
rpm = maxRPM;
|
||||
|
||||
if ( Gear != MaxGear || groundedCount < Wheels.Count )
|
||||
isRedlining = true;
|
||||
}
|
||||
|
||||
FlywheelRPM = Math.Clamp( rpm, 0, maxRPM );
|
||||
|
||||
if ( burnout > 0 )
|
||||
availableTorque += availableTorque * burnout * 0.1f;
|
||||
|
||||
var front = 0.5f + PowerDistribution * 0.5f;
|
||||
var rear = 1 - front;
|
||||
|
||||
availableFrontTorque = availableTorque * front;
|
||||
availableRearTorque = availableTorque * rear;
|
||||
|
||||
throttle = MathX.Approach( throttle, inputThrottle, dt * 4 );
|
||||
|
||||
|
||||
EngineAccelerate( FlywheelFriction + FlywheelTorque * throttle, dt );
|
||||
|
||||
Throttle = throttle;
|
||||
|
||||
IsRedlining = (isRedlining && inputThrottle > 0);
|
||||
}
|
||||
|
||||
public void StreamEngineUpdate()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
46
Code/Car/VeloXCar.Steering.cs
Normal file
46
Code/Car/VeloXCar.Steering.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Sandbox;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
public partial class VeloXCar
|
||||
{
|
||||
public static float ExpDecay( float a, float b, float decay, float dt ) => b + (a - b) * MathF.Exp( -decay * dt );
|
||||
[Property, Feature( "Steer" )] public float SteerConeMaxSpeed { get; set; } = 1800;
|
||||
[Property, Feature( "Steer" )] public float SteerConeMaxAngle { get; set; } = 0.25f;
|
||||
[Property, Feature( "Steer" )] public float SteerConeChangeRate { get; set; } = 8;
|
||||
[Property, Feature( "Steer" )] public float CounterSteer { get; set; } = 0.1f;
|
||||
[Property, Feature( "Steer" )] public float MaxSteerAngle { get; set; } = 35f;
|
||||
|
||||
[Sync] public float Steering { get; private set; }
|
||||
private float jTurnMultiplier;
|
||||
|
||||
private float inputSteer;
|
||||
private void UpdateSteering( float dt )
|
||||
{
|
||||
var inputSteer = Input.AnalogMove.y;
|
||||
var absInputSteer = Math.Abs( inputSteer );
|
||||
|
||||
var sideSlip = Math.Clamp( avgSideSlip, -1, 1 );
|
||||
|
||||
var steerConeFactor = Math.Clamp( TotalSpeed / SteerConeMaxSpeed, 0, 1 );
|
||||
var steerCone = 1 - steerConeFactor * (1 - SteerConeMaxAngle);
|
||||
|
||||
steerCone = Math.Clamp( steerCone, Math.Abs( sideSlip ), 1 );
|
||||
|
||||
inputSteer = ExpDecay( this.inputSteer, inputSteer * steerCone, SteerConeChangeRate, dt );
|
||||
this.inputSteer = inputSteer;
|
||||
var counterSteer = sideSlip * steerConeFactor * (1 - absInputSteer);
|
||||
counterSteer = Math.Clamp( counterSteer, -1, 1 ) * CounterSteer;
|
||||
|
||||
inputSteer = Math.Clamp( inputSteer + counterSteer, -1, 1 );
|
||||
Steering = inputSteer;
|
||||
SteerAngle = new( 0, inputSteer * MaxSteerAngle, 0 );
|
||||
|
||||
if ( ForwardSpeed < -100 )
|
||||
jTurnMultiplier = 0.5f;
|
||||
else
|
||||
jTurnMultiplier = ExpDecay( jTurnMultiplier, 1, 2, dt );
|
||||
}
|
||||
}
|
||||
46
Code/Car/VeloXCar.Wheel.cs
Normal file
46
Code/Car/VeloXCar.Wheel.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace VeloX;
|
||||
|
||||
public partial class VeloXCar
|
||||
{
|
||||
|
||||
private void WheelThink( in float dt )
|
||||
{
|
||||
var maxRPM = GetTransmissionMaxRPM( Gear );
|
||||
|
||||
|
||||
var frontTorque = availableFrontTorque;
|
||||
var rearTorque = availableRearTorque;
|
||||
|
||||
|
||||
groundedCount = 0;
|
||||
float avgRPM = 0, totalSideSlip = 0, totalForwardSlip = 0;
|
||||
|
||||
foreach ( var w in Wheels )
|
||||
{
|
||||
w.Update( this, dt );
|
||||
|
||||
totalSideSlip += w.SideSlip;
|
||||
totalForwardSlip += w.ForwardSlip;
|
||||
var rpm = w.RPM;
|
||||
|
||||
avgRPM += rpm * w.DistributionFactor;
|
||||
|
||||
w.Torque = w.DistributionFactor * (w.IsFront ? frontTorque : rearTorque);
|
||||
w.Brake = w.IsFront ? frontBrake : rearBrake;
|
||||
|
||||
if ( inputHandbrake && !w.IsFront )
|
||||
w.RPM = 0;
|
||||
|
||||
if ( rpm > maxRPM )
|
||||
w.RPM = maxRPM;
|
||||
|
||||
if ( w.IsOnGround )
|
||||
groundedCount++;
|
||||
|
||||
}
|
||||
avgPoweredRPM = avgRPM;
|
||||
avgSideSlip = totalSideSlip / Wheels.Count;
|
||||
avgForwardSlip = totalForwardSlip / Wheels.Count;
|
||||
|
||||
}
|
||||
}
|
||||
60
Code/Car/VeloXCar.cs
Normal file
60
Code/Car/VeloXCar.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Sandbox;
|
||||
using System;
|
||||
|
||||
namespace VeloX;
|
||||
|
||||
[Group( "VeloX" )]
|
||||
[Title( "VeloX - Car" )]
|
||||
public partial class VeloXCar : VeloXBase
|
||||
{
|
||||
protected override void OnAwake()
|
||||
{
|
||||
base.OnAwake();
|
||||
|
||||
StreamPlayer = new( Stream );
|
||||
}
|
||||
protected override void OnStart()
|
||||
{
|
||||
base.OnStart();
|
||||
|
||||
if ( !IsProxy )
|
||||
{
|
||||
UpdateGearList();
|
||||
UpdatePowerDistribution();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
base.OnUpdate();
|
||||
if ( StreamPlayer is not null )
|
||||
{
|
||||
|
||||
StreamPlayer.Throttle = Throttle;
|
||||
StreamPlayer.RPMPercent = RPMPercent;
|
||||
StreamPlayer.EngineState = EngineState;
|
||||
StreamPlayer.IsRedlining = IsRedlining;
|
||||
|
||||
if ( Driver.IsValid() && Driver.IsProxy )
|
||||
StreamPlayer.Update( Time.Delta, Scene.Camera.WorldPosition );
|
||||
else
|
||||
StreamPlayer.Update( Time.Delta, WorldPosition );
|
||||
}
|
||||
|
||||
}
|
||||
protected override void OnFixedUpdate()
|
||||
{
|
||||
if ( IsProxy )
|
||||
return;
|
||||
|
||||
base.OnFixedUpdate();
|
||||
|
||||
var dt = Time.Delta;
|
||||
EngineThink( dt );
|
||||
WheelThink( dt );
|
||||
UpdateSteering( dt );
|
||||
|
||||
Brake = Math.Clamp( frontBrake + rearBrake + (Input.Down( "Jump" ) ? 1 : 0), 0, 1 );
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user