namespace Car_simulation { public class Engine { // Energy state public float FlywheelEnergy { get; set; } // Values public float RPM => GetRPM(); public float AngularVelocity => GetOmega(); public float CurrentPower { get; private set; } // Physical properties public float MomentOfInertia { get; set; } = 0.25f; public float IdleRPM { get; set; } = 800f; public float StallSpeed { get; set; } = 200f; public float Throttle { get; set; } = 0f; public bool IsRunning => RPM > StallSpeed; // Torque curve public Dictionary TorqueCurve { get; set; } = new() { { 0f, 0f }, { 800f, 150f }, { 2000f, 250 }, { 4500f, 250f }, { 6800f, 200f }, { 7200f, 150 }, { 7500f, 0f }, }; public Engine() { FlywheelEnergy = GetEnergyFromRPM(IdleRPM); } public float CalculateFrictionLoss(float deltaTime) { float frictionTorque; // Realistic friction based on RPM if (RPM < 500) frictionTorque = 15f; 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; } public float CalculateCombustionPower(float deltaTime) { float torque = GetTorqueOutput() * GetActualThrottle(); return torque * AngularVelocity * deltaTime; } public float GetActualThrottle() { // Idle control: maintain idle speed when throttle is low if (RPM < IdleRPM && Throttle < 0.1f) { float idleThrottle = (IdleRPM - RPM) / 200f; return Math.Clamp(idleThrottle, 0.1f, 0.3f); } return Throttle; } 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; } public float GetEnergyFromRPM(float rpm) { float omega = rpm * PhysicsUtil.RPM_TO_RAD_PER_SEC; return 0.5f * MomentOfInertia * omega * omega; } public float GetTorqueOutput() { if (RPM <= 400) 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 Update(float deltaTime) { // Combustion adds energy (if throttle > 0) float combustionEnergy = CalculateCombustionPower(deltaTime); // Friction always removes energy float frictionLoss = CalculateFrictionLoss(deltaTime); // Net energy change from combustion and friction ONLY // Note: Drivetrain energy transfer happens separately in Drivetrain.Update() float netEnergy = combustionEnergy - frictionLoss; CurrentPower = netEnergy / deltaTime; FlywheelEnergy += netEnergy; // Stall protection - keep engine running if it has throttle float stallEnergy = GetEnergyFromRPM(StallSpeed); if (FlywheelEnergy < stallEnergy && Throttle > 0.1f) { FlywheelEnergy = stallEnergy * 1.2f; } FlywheelEnergy = Math.Max(FlywheelEnergy, 0); } } }