Files
2026-04-17 12:38:11 +02:00

131 lines
5.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}