166 lines
5.9 KiB
C#
166 lines
5.9 KiB
C#
using System;
|
|
|
|
namespace FluidSim.Components
|
|
{
|
|
public class Vehicle
|
|
{
|
|
// ---- Gearbox ----
|
|
public int CurrentGear { get; private set; } = 0;
|
|
public readonly float[] GearRatios = { 2.5f, 1.8f, 1.4f, 1.1f, 0.9f, 0.75f };
|
|
public float FinalDriveRatio = 3.0f;
|
|
public float PrimaryReduction = 2.5f;
|
|
|
|
// ---- Clutch ----
|
|
public float ClutchInput { get; set; }
|
|
public float ClutchDisengageTime = 0.15f;
|
|
private float _clutchTimer;
|
|
private float _currentEngagement = 0f;
|
|
|
|
/// <summary>Time constant for clutch engagement smoothing (seconds).</summary>
|
|
public float EngagementSmoothTime = 0.5f; // longer, gentler bite
|
|
|
|
private float TargetEngagement
|
|
{
|
|
get
|
|
{
|
|
if (ClutchInput > 0.01f) return 1f - ClutchInput;
|
|
if (CurrentGear == 0 || _clutchTimer > 0f) return 0f;
|
|
return 1f;
|
|
}
|
|
}
|
|
|
|
public float Engagement => _currentEngagement;
|
|
|
|
// ---- Clutch torque model ----
|
|
/// <summary>Peak clutch friction torque (Nm) when fully engaged at high RPM.</summary>
|
|
public float BaseMaxTorque = 80f; // much lower than before
|
|
|
|
/// <summary>Stiffness when slipping (Nm per rad/s). Lower = softer engagement.</summary>
|
|
public float ClutchStiffness = 50f; // very soft
|
|
|
|
/// <summary>Below this engine RPM, the clutch torque is progressively reduced to prevent stalling.</summary>
|
|
public float IdleRpm = 1200f;
|
|
public float StallPreventionRamp = 300f; // RPM band above idle where torque ramps up
|
|
|
|
// ---- Physical constants ----
|
|
public float Mass = 160f;
|
|
public float WheelRadius = 0.32f;
|
|
public float DragCoefficient = 0.35f;
|
|
public float FrontalArea = 0.8f;
|
|
public float AirDensity = 1.225f;
|
|
public float RollingFrictionCoeff = 0.01f;
|
|
public float Gravity = 9.81f;
|
|
|
|
// ---- State ----
|
|
public float Speed { get; private set; }
|
|
|
|
public (float clutchTorqueOnEngine, float effectiveEngineInertia) Update(float engineRpm, float engineInertia, float dt)
|
|
{
|
|
if (_clutchTimer > 0f)
|
|
{
|
|
_clutchTimer -= dt;
|
|
if (_clutchTimer < 0f) _clutchTimer = 0f;
|
|
}
|
|
|
|
float target = TargetEngagement;
|
|
float smoothing = 1f - MathF.Exp(-dt / Math.Max(EngagementSmoothTime, 0.001f));
|
|
_currentEngagement += (target - _currentEngagement) * smoothing;
|
|
if (MathF.Abs(_currentEngagement - target) < 0.001f)
|
|
_currentEngagement = target;
|
|
|
|
float engagement = _currentEngagement;
|
|
|
|
float totalGear = 1f;
|
|
if (CurrentGear > 0)
|
|
totalGear = GearRatios[CurrentGear - 1] * FinalDriveRatio * PrimaryReduction;
|
|
|
|
float engineRadPerSec = engineRpm * 2f * MathF.PI / 60f;
|
|
|
|
float v = MathF.Max(Speed, 0f);
|
|
float drag = 0.5f * AirDensity * DragCoefficient * FrontalArea * v * v;
|
|
float rolling = RollingFrictionCoeff * Mass * Gravity;
|
|
float resistanceForce = drag + rolling;
|
|
|
|
float clutchTorque = 0f;
|
|
float effectiveInertia = engineInertia;
|
|
|
|
if (engagement > 0f && CurrentGear > 0)
|
|
{
|
|
float vehicleReflectedRadPerSec = (Speed / WheelRadius) * totalGear;
|
|
float slip = engineRadPerSec - vehicleReflectedRadPerSec;
|
|
|
|
// Stall prevention: reduce max torque when engine RPM is near idle
|
|
float torqueLimit = BaseMaxTorque * engagement;
|
|
if (engineRpm < IdleRpm + StallPreventionRamp)
|
|
{
|
|
float factor = Math.Clamp((engineRpm - IdleRpm) / StallPreventionRamp, 0f, 1f);
|
|
torqueLimit *= factor;
|
|
}
|
|
|
|
float stiffnessTorque = ClutchStiffness * engagement * slip;
|
|
clutchTorque = Math.Clamp(stiffnessTorque, -torqueLimit, torqueLimit);
|
|
|
|
// Lock if slip negligible and engagement high
|
|
if (engagement >= 0.99f && MathF.Abs(slip) < 1.0f)
|
|
{
|
|
float vehicleInertia = Mass * WheelRadius * WheelRadius;
|
|
float reflectedVehicleInertia = vehicleInertia / (totalGear * totalGear);
|
|
effectiveInertia = engineInertia + reflectedVehicleInertia;
|
|
|
|
Speed = engineRadPerSec * WheelRadius / totalGear;
|
|
float loadTorque = resistanceForce * WheelRadius / totalGear;
|
|
return (loadTorque, effectiveInertia);
|
|
}
|
|
}
|
|
|
|
float driveTorqueAtWheel = clutchTorque * totalGear;
|
|
float driveForce = driveTorqueAtWheel / WheelRadius;
|
|
float netForce = driveForce - resistanceForce;
|
|
float acceleration = netForce / Mass;
|
|
Speed += acceleration * dt;
|
|
if (Speed < 0f) Speed = 0f;
|
|
|
|
return (clutchTorque, engineInertia);
|
|
}
|
|
|
|
public void ShiftUp()
|
|
{
|
|
if (CurrentGear < GearRatios.Length)
|
|
{
|
|
CurrentGear++;
|
|
AutoDisengageClutch();
|
|
}
|
|
}
|
|
|
|
public void ShiftDown()
|
|
{
|
|
if (CurrentGear > 1)
|
|
{
|
|
CurrentGear--;
|
|
AutoDisengageClutch();
|
|
}
|
|
}
|
|
|
|
public void SetNeutral()
|
|
{
|
|
CurrentGear = 0;
|
|
_clutchTimer = 0f;
|
|
}
|
|
|
|
public void SetFirstGear()
|
|
{
|
|
if (CurrentGear == 0)
|
|
{
|
|
CurrentGear = 1;
|
|
AutoDisengageClutch();
|
|
}
|
|
}
|
|
|
|
private void AutoDisengageClutch()
|
|
{
|
|
_clutchTimer = ClutchDisengageTime;
|
|
}
|
|
|
|
public float SpeedKmh => Speed * 3.6f;
|
|
}
|
|
} |