namespace Car_simulation { public class Engine { // Energy state public float FlywheelEnergy { get; set; } // Joules // Values public float RPM => GetRPM(); public float AngularVelocity => GetOmega(); public float CurrentPower { get; private set; } // Physical properties public float MomentOfInertia { get; set; } = 0.25f; // kg·m² public float IdleRPM { get; set; } = 800f; public float StallSpeed { get; set; } = 200f; public float Throttle { get; set; } = 0f; public bool IsRunning => RPM > StallSpeed; // Torque characteristics public Dictionary TorqueCurve { get; set; } = new() { // RPM - Torque Nm { 0f, 0f }, { 800f, 150f }, // Idle { 2000f, 200f }, // Peak torque { 4500f, 250f }, { 7200f, 250f }, { 9200f, 250f }, { 10000f, 200f }, { 11000f, 0f } }; public Engine() { // Start with idle energy FlywheelEnergy = GetEnergyFromRPM(IdleRPM); } // Calculations public float CalculateFrictionEnergy(float deltaTime) { // Real friction torque data for 2.0L engine (Nm) float frictionTorque; if (RPM < 500) frictionTorque = 15f; // Static/breakaway else if (RPM < 1000) frictionTorque = 14f; else if (RPM < 2000) frictionTorque = 16f; else if (RPM < 3000) frictionTorque = 18f; else if (RPM < 4000) frictionTorque = 21f; else if (RPM < 5000) frictionTorque = 25f; else if (RPM < 6000) frictionTorque = 30f; else if (RPM < 7000) frictionTorque = 36f; else frictionTorque = 44f; float frictionPower = frictionTorque * AngularVelocity; return frictionPower * deltaTime; } private float CalculateCombustionEnergy(float deltaTime) { float torque = GetTorqueOutput() * GetActualThrottle(); return torque * AngularVelocity * deltaTime; } private float CalculateLoadEnergy(float deltaTime, float loadTorque) { return loadTorque * AngularVelocity * deltaTime; } // Get public float GetActualThrottle() { float idleThrottle = Math.Max((IdleRPM - RPM) / 10, 0); return Math.Clamp(Throttle + idleThrottle, 0, 1); } public float GetOmega() { if (FlywheelEnergy <= 0) return 0; return MathF.Sqrt(2f * FlywheelEnergy / MomentOfInertia); } public float GetRPM() { return GetOmega() * PhysicsUtil.RAD_PER_SEC_TO_RPM; } // Set public float GetEnergyFromRPM(float rpm) { float omega = rpm * PhysicsUtil.RPM_TO_RAD_PER_SEC; return 0.5f * MomentOfInertia * omega * omega; } // torque curve public float GetTorqueOutput() { if (RPM <= 0) return 0; var points = TorqueCurve.OrderBy(p => p.Key).ToList(); if (RPM <= points.First().Key) return points.First().Value; if (RPM >= points.Last().Key) return points.Last().Value; for (int i = 0; i < points.Count - 1; i++) { if (RPM >= points[i].Key && RPM <= points[i + 1].Key) { float t = (RPM - points[i].Key) / (points[i + 1].Key - points[i].Key); return PhysicsUtil.Lerp(points[i].Value, points[i + 1].Value, t); } } return 0f; } public void ApplyTorque(float torque, float deltaTime) { if (torque == 0) return; float work = torque * AngularVelocity * deltaTime; FlywheelEnergy += work; FlywheelEnergy = Math.Max(FlywheelEnergy, 0); } public void Update(float deltaTime, float loadTorque) { float combustionEnergy = CalculateCombustionEnergy(deltaTime); float frictionEnergy = CalculateFrictionEnergy(deltaTime); float loadEnergy = CalculateLoadEnergy(deltaTime, loadTorque); float netEnergy = combustionEnergy - frictionEnergy - loadEnergy; CurrentPower = netEnergy / deltaTime; FlywheelEnergy += netEnergy; FlywheelEnergy = Math.Max(FlywheelEnergy, 0); } } }