major rework

This commit is contained in:
max
2026-02-16 18:32:48 +01:00
parent bbd82da07e
commit 932734e5b4
24 changed files with 1706 additions and 893 deletions

View File

@@ -0,0 +1,29 @@
using Car_simulation.Core.Physics;
namespace Car_simulation.Core.Components
{
public class Aerodynamics
{
public float DragCoefficient { get; set; } = 0.3f;
public float FrontalArea { get; set; } = 2.2f; // m²
public float RollingResistanceCoefficient { get; set; } = 0.015f;
private readonly ResistanceCalculator _resistanceCalculator = new ResistanceCalculator();
public float CalculateDragForce(float speed)
{
return _resistanceCalculator.CalculateDragForce(speed, DragCoefficient, FrontalArea);
}
public float CalculateRollingResistanceForce(float mass)
{
return _resistanceCalculator.CalculateRollingResistanceForce(mass, RollingResistanceCoefficient);
}
public float CalculateTotalResistanceForce(float speed, float mass)
{
return _resistanceCalculator.CalculateTotalResistanceForce(
speed, mass, DragCoefficient, FrontalArea, RollingResistanceCoefficient);
}
}
}

View File

@@ -0,0 +1,30 @@
using Car_simulation.Core.Physics;
namespace Car_simulation.Core.Components
{
public class BrakeSystem : ICarComponent
{
public float BrakeInput { get; set; } = 0f;
public float MaxBrakeTorque { get; set; } = 3000f;
private WheelSystem _wheelSystem;
public BrakeSystem(WheelSystem wheelSystem)
{
_wheelSystem = wheelSystem;
}
public void Update(float deltaTime)
{
if (BrakeInput <= 0) return;
float brakeTorque = BrakeInput * MaxBrakeTorque;
_wheelSystem.ApplyTorque(-brakeTorque, deltaTime);
}
public float GetBrakeTorque()
{
return BrakeInput * MaxBrakeTorque;
}
}
}

View File

@@ -0,0 +1,126 @@
using Car_simulation.Core.Physics;
namespace Car_simulation.Core.Components
{
public class Drivetrain : ICarComponent
{
public Engine Engine { get; private set; }
public WheelSystem WheelSystem { get; private set; }
private int _currentGear = 1;
public float[] GearRatios { get; set; } = { 3.8f, 2.5f, 1.8f, 1.3f, 1.0f, 0.8f, 0.65f };
public float FinalDriveRatio { get; set; } = 4.0f;
public float Efficiency { get; set; } = 0.95f;
public float ClutchEngagement { get; set; } = 0f;
public float MaxClutchTorque { get; set; } = 400f;
public float ClutchStiffness { get; set; } = 50f;
public float ClutchTorque { get; private set; }
public float TransmittedPower { get; private set; }
public float ClutchSlipRatio { get; private set; }
public Drivetrain(Engine engine, WheelSystem wheelSystem)
{
Engine = engine;
WheelSystem = wheelSystem;
}
public void Update(float deltaTime)
{
if (ClutchEngagement <= 0.01f || TotalRatio == 0)
{
ClutchTorque = 0;
TransmittedPower = 0;
ClutchSlipRatio = 1f;
return;
}
float expectedWheelOmega = Engine.AngularVelocity / TotalRatio;
float actualWheelOmega = WheelSystem.AngularVelocity;
float omegaDifference = actualWheelOmega - expectedWheelOmega;
float maxClutchTorque = MaxClutchTorque * ClutchEngagement;
float desiredTorque = -omegaDifference * ClutchStiffness;
desiredTorque = Math.Clamp(desiredTorque, -maxClutchTorque, maxClutchTorque);
if (desiredTorque > 0)
{
float engineTorque = Engine.GetTorqueOutput() * Engine.GetActualThrottle();
float maxEngineTorqueAtWheels = engineTorque * TotalRatio * Efficiency;
desiredTorque = Math.Min(desiredTorque, maxEngineTorqueAtWheels);
}
ClutchTorque = desiredTorque;
float energyTransferred = 0f;
if (omegaDifference > 0.01f) // Wheels → Engine
{
float power = ClutchTorque * Engine.AngularVelocity;
energyTransferred = power * deltaTime;
float wheelEnergyLoss = Math.Abs(energyTransferred);
float engineEnergyGain = wheelEnergyLoss * Efficiency;
WheelSystem.TotalEnergy -= wheelEnergyLoss;
Engine.FlywheelEnergy += engineEnergyGain;
}
else if (omegaDifference < -0.01f) // Engine → Wheels
{
float power = -ClutchTorque * Engine.AngularVelocity;
energyTransferred = power * deltaTime;
float engineEnergyLoss = Math.Abs(energyTransferred);
float wheelEnergyGain = engineEnergyLoss * Efficiency;
Engine.FlywheelEnergy -= engineEnergyLoss;
WheelSystem.TotalEnergy += wheelEnergyGain;
}
TransmittedPower = energyTransferred / deltaTime;
if (maxClutchTorque > 0)
{
float torqueRatio = Math.Abs(ClutchTorque) / maxClutchTorque;
ClutchSlipRatio = torqueRatio;
}
else
{
ClutchSlipRatio = 1f;
}
}
public float GearRatio => GetCurrentGearRatio();
public float TotalRatio => GearRatio * FinalDriveRatio;
private float GetCurrentGearRatio()
{
if (_currentGear == 0) return 0f;
if (_currentGear == -1) return -3.5f;
if (_currentGear > 0 && _currentGear <= GearRatios.Length)
return GearRatios[_currentGear - 1];
return 0f;
}
public float GetSpeedDifferenceRPM()
{
float expectedWheelOmega = Engine.AngularVelocity / TotalRatio;
float actualWheelOmega = WheelSystem.AngularVelocity;
return (actualWheelOmega - expectedWheelOmega) * PhysicsUtil.RAD_PER_SEC_TO_RPM;
}
public string GetCurrentGearName()
{
return _currentGear switch
{
-1 => "R",
0 => "N",
_ => _currentGear.ToString()
};
}
public float GetClutchSlipPercent() => ClutchSlipRatio * 100f;
public void GearUp() { if (_currentGear < GearRatios.Length) _currentGear++; }
public void GearDown() { if (_currentGear > 1) _currentGear--; }
}
}

View File

@@ -0,0 +1,163 @@
using Car_simulation.Core.Physics;
namespace Car_simulation.Core.Components
{
public class Engine : ICarComponent
{
// Energy state
public float FlywheelEnergy { get; set; }
// Physical properties
public float MomentOfInertia { get; set; } = 0.25f;
public float IdleRPM { get; set; } = 800f;
public float RevLimit { get; set; } = 7000;
public float StallSpeed { get; set; } = 200f;
public float Throttle { get; set; } = 0f;
public bool IsRunning => RPM > StallSpeed;
private float _cutoffUntil = 0;
private bool _cutoff = false;
// Torque curve
private TorqueCurve _torqueCurve;
public Engine()
{
FlywheelEnergy = GetEnergyFromRPM(IdleRPM);
_torqueCurve = new TorqueCurve();
InitializeDefaultCurve();
}
private void InitializeDefaultCurve()
{
_torqueCurve.AddPoint(0f, 0f);
_torqueCurve.AddPoint(800f, 95f);
_torqueCurve.AddPoint(1500f, 160f);
_torqueCurve.AddPoint(2500f, 200f);
_torqueCurve.AddPoint(4000f, 235f);
_torqueCurve.AddPoint(5000f, 230f);
_torqueCurve.AddPoint(6000f, 210f);
_torqueCurve.AddPoint(6800f, 185f);
_torqueCurve.AddPoint(7200f, 170f);
}
public float RPM => GetRPM();
public float AngularVelocity => GetOmega();
public float CurrentPower { get; private set; }
public void Update(float deltaTime)
{
// Engine updates are now handled through Car with totalTime parameter
}
public void UpdateWithTime(float deltaTime, float totalTime)
{
UpdateRevLimiter(totalTime);
float combustionEnergy = CalculateCombustionPower(deltaTime);
float frictionLoss = CalculateFrictionLoss(deltaTime);
float netEnergy = combustionEnergy - frictionLoss;
CurrentPower = netEnergy / deltaTime;
FlywheelEnergy += netEnergy;
// Stall protection
float stallEnergy = GetEnergyFromRPM(StallSpeed);
if (FlywheelEnergy < stallEnergy && Throttle > 0.1f)
{
FlywheelEnergy = stallEnergy * 1.2f;
}
FlywheelEnergy = Math.Max(FlywheelEnergy, 0);
}
private void UpdateRevLimiter(float totalTime)
{
if (RPM > RevLimit)
{
_cutoffUntil = totalTime + 0.01f;
}
_cutoff = (totalTime < _cutoffUntil);
}
private float CalculateFrictionLoss(float deltaTime)
{
float frictionTorque = GetFrictionTorque();
float frictionPower = frictionTorque * AngularVelocity;
return frictionPower * deltaTime;
}
private float GetFrictionTorque()
{
return RPM switch
{
< 500 => 15f,
< 1000 => 14f,
< 2000 => 16f,
< 3000 => 18f,
< 4000 => 21f,
< 5000 => 25f,
< 6000 => 30f,
< 7000 => 36f,
_ => 44f
};
}
private float CalculateCombustionPower(float deltaTime)
{
float throttle = GetActualThrottle();
if (_cutoff) throttle = 0;
float torque = GetTorqueOutput() * throttle;
return torque * AngularVelocity * deltaTime;
}
public float GetActualThrottle()
{
if (RPM < IdleRPM && Throttle < 0.1f)
{
float idleThrottle = (IdleRPM - RPM) / 200f;
return Math.Clamp(idleThrottle, 0.1f, 0.3f);
}
return Throttle;
}
public float GetOmega() => MathF.Sqrt(2f * FlywheelEnergy / MomentOfInertia);
public float GetRPM() => GetOmega() * PhysicsUtil.RAD_PER_SEC_TO_RPM;
public float GetEnergyFromRPM(float rpm)
{
float omega = rpm * PhysicsUtil.RPM_TO_RAD_PER_SEC;
return 0.5f * MomentOfInertia * omega * omega;
}
public float GetTorqueOutput() => _torqueCurve.GetTorqueAtRPM(RPM);
}
public class TorqueCurve
{
private List<(float RPM, float Torque)> _points = new List<(float, float)>();
public void AddPoint(float rpm, float torque) => _points.Add((rpm, torque));
public float GetTorqueAtRPM(float rpm)
{
if (rpm <= 400) return 0;
if (_points.Count == 0) return 0;
var orderedPoints = _points.OrderBy(p => p.RPM).ToList();
if (rpm <= orderedPoints.First().RPM) return orderedPoints.First().Torque;
if (rpm >= orderedPoints.Last().RPM) return orderedPoints.Last().Torque;
for (int i = 0; i < orderedPoints.Count - 1; i++)
{
if (rpm >= orderedPoints[i].RPM && rpm <= orderedPoints[i + 1].RPM)
{
float t = (rpm - orderedPoints[i].RPM) / (orderedPoints[i + 1].RPM - orderedPoints[i].RPM);
return PhysicsUtil.Lerp(orderedPoints[i].Torque, orderedPoints[i + 1].Torque, t);
}
}
return 0f;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Car_simulation.Core.Components
{
public interface ICarComponent
{
void Update(float deltaTime);
}
}

View File

@@ -0,0 +1,95 @@
using Car_simulation.Core.Physics;
namespace Car_simulation.Core.Components
{
public class WheelSystem : ICarComponent
{
// Physical properties
public float Radius { get; set; } = 0.3f;
public float WheelInertia { get; set; } = 2.0f;
public float CarMass { get; set; } = 1500f;
public int WheelCount { get; set; } = 4;
public int DrivenWheels { get; set; } = 2;
// State
public float TotalEnergy { get; set; } = 0f;
public float AngularVelocity => GetOmega();
public float RPM => GetRPM();
public float CarSpeed => GetCarSpeed();
// Derived properties
public float GetTotalRotationalInertia() => WheelInertia * WheelCount;
public float GetEquivalentCarInertia() => CarMass * Radius * Radius;
public float GetTotalInertia() => GetTotalRotationalInertia() + GetEquivalentCarInertia();
// Calculations
public float GetOmega()
{
if (TotalEnergy <= 0 || GetTotalInertia() <= 0) return 0f;
return MathF.Sqrt(2f * TotalEnergy / GetTotalInertia());
}
public float GetRPM() => AngularVelocity * PhysicsUtil.RAD_PER_SEC_TO_RPM;
public float GetCarSpeed() => AngularVelocity * Radius;
public float GetRotationalEnergy()
{
float omega = GetOmega();
return 0.5f * GetTotalRotationalInertia() * omega * omega;
}
public float GetTranslationalEnergy()
{
float speed = GetCarSpeed();
return 0.5f * CarMass * speed * speed;
}
public float GetEnergyFromSpeed(float speed)
{
float omega = speed / Radius;
float rotationalEnergy = 0.5f * GetTotalRotationalInertia() * omega * omega;
float translationalEnergy = 0.5f * CarMass * speed * speed;
return rotationalEnergy + translationalEnergy;
}
public void SetSpeed(float speed) => TotalEnergy = GetEnergyFromSpeed(speed);
public void ApplyWork(float work)
{
TotalEnergy += work;
TotalEnergy = Math.Max(TotalEnergy, 0);
}
public void ApplyTorque(float torque, float deltaTime)
{
if (torque == 0) return;
float work = torque * AngularVelocity * deltaTime;
ApplyWork(work);
}
public void ApplyResistance(float resistanceTorque, float deltaTime)
{
if (resistanceTorque <= 0 || AngularVelocity == 0) return;
float omega = AngularVelocity;
if (MathF.Abs(omega) < 0.1f) return;
float resistanceSign = -MathF.Sign(omega);
float alpha = (resistanceSign * resistanceTorque) / GetTotalInertia();
float omegaNew = omega + alpha * deltaTime;
if (MathF.Sign(omegaNew) != MathF.Sign(omega))
{
omegaNew = 0;
}
float energyNew = 0.5f * GetTotalInertia() * omegaNew * omegaNew;
TotalEnergy = Math.Max(energyNew, 0);
}
public void Update(float deltaTime)
{
// WheelSystem updates are handled by Car through other components
}
}
}