using System; public class Engine : RotatingComponent { public double ThrottlePosition { get; set; } public double CurrentPower { get; private set; } public double CombustionPower { get; private set; } public int CylinderCount { get; private set; } public EngineControlUnit Ecu = new EngineControlUnit(); // Friction private const double _baseFriction = 12.0; // Seals, oil pump, valvetrain (Nm) private const double _linearFriction = 0.025; // Hydrodynamic bearing drag (Nm/(rad/s)) private const double _quadraticFriction = 0.00002; // Windage & churning (Nm/(rad/s)²) // Engine geometry private double _displacementCC; private double _compressionRatio; // Volumetric efficiency tuning – now in rad/s public double VolumetricEfficiencyPeak { get; set; } = 1.15; // was 1.10 public double AngularVelocityForVEPeak { get; set; } = 550.0; // 5250 RPM (rad/s = 550) public double AngularVelocityForVEMin { get; set; } = 800.0; // 7639 RPM public double VEminAtRedline { get; set; } = 0.85; // much higher at redline // Physics constants private const double ATM_PRESSURE_KPA = 101.3; private const double AIR_DENSITY_KG_PER_M3 = 1.225; private const double FUEL_HEAT_VALUE_J_PER_KG = 43e6; private const double STOICHIOMETRIC_AIR_FUEL_RATIO = 14.7; public Engine(double inertia, int cylinders, double displacementCC, double compressionRatio) { CylinderCount = cylinders; _displacementCC = displacementCC; _compressionRatio = compressionRatio; MomentOfInertia = inertia; AngularVelocity = 100; // rad/s (~955 RPM) } public override void Update(double dt) { Ecu.Update(dt, this); ApplyThrottleTorque(); ApplyFrictionTorque(); CurrentPower = AccumulatedTorque * AngularVelocity; base.Update(dt); } private double ComputeIndicatedTorque() { double w = AngularVelocity; // rad/s double throttle = Math.Clamp(ThrottlePosition, 0.0, 1.0); // Volumetric Efficiency (WOT) as function of w (rad/s) double veWOT; if (w <= AngularVelocityForVEPeak) { // Start at a realistic 0.75 at zero RPM, peaking at your target double t = w / AngularVelocityForVEPeak; veWOT = 0.75 + (VolumetricEfficiencyPeak - 0.75) * t * (2 - t); } else { // Linear drop from VE_peak to VEminAtRedline double t = (w - AngularVelocityForVEPeak) / (AngularVelocityForVEMin - AngularVelocityForVEPeak); t = Math.Clamp(t, 0.0, 1.0); veWOT = VolumetricEfficiencyPeak - t * (VolumetricEfficiencyPeak - VEminAtRedline); } veWOT = Math.Clamp(veWOT, 0.25, VolumetricEfficiencyPeak); // Intake loss double maxEngineDemandSpeed = 700.0; // rad/s, roughly 6700 RPM double throttleArea = Math.Pow(throttle, 1.5); double engineDemand = (w / maxEngineDemandSpeed) * veWOT; const double intakeResistance = 0.03; // was 0.5 – now physically realistic double mapFraction = throttleArea / (throttleArea + intakeResistance * engineDemand); if (throttle == 0) mapFraction = 0; double manifoldPressureKpa = ATM_PRESSURE_KPA * mapFraction; manifoldPressureKpa = Math.Clamp(manifoldPressureKpa, 0, ATM_PRESSURE_KPA); double veActual = veWOT * (manifoldPressureKpa / ATM_PRESSURE_KPA); // Exhaust loss (backpressure) double exhaustBackpressureKpa = 2.0e-5 * w * w; double exhaustLossFactor = 1.0 - Math.Min(0.25, exhaustBackpressureKpa / ATM_PRESSURE_KPA); // Air & fuel mass per cycle double displacementM3 = _displacementCC * 1e-6; double airMassPerCycleKg = veActual * AIR_DENSITY_KG_PER_M3 * displacementM3; double fuelMassPerCycleKg = airMassPerCycleKg / STOICHIOMETRIC_AIR_FUEL_RATIO; double energyPerCycleJ = fuelMassPerCycleKg * FUEL_HEAT_VALUE_J_PER_KG; // Thermal efficiency double gamma = 1.4; double ottoEfficiency = 1.0 - 1.0 / Math.Pow(_compressionRatio, gamma - 1.0); double thermalEfficiency = 0.65 * ottoEfficiency; double workPerCycleJ = energyPerCycleJ * thermalEfficiency * exhaustLossFactor; double indicatedTorque = workPerCycleJ / (4.0 * Math.PI); indicatedTorque = Math.Min(600.0, Math.Max(0.0, indicatedTorque)); return indicatedTorque; } public void ApplyThrottleTorque() { double torque = ComputeIndicatedTorque(); CombustionPower = torque * AngularVelocity; ApplyTorque(torque); } public void ApplyFrictionTorque() { // Friction uses angular velocity (rad/s) directly double w = AngularVelocity; double frictionMag = _baseFriction + _linearFriction * Math.Abs(w) + _quadraticFriction * w * w; double frictionTorque = -Math.Sign(w) * frictionMag; ApplyTorque(frictionTorque); } }