131 lines
5.0 KiB
C#
131 lines
5.0 KiB
C#
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);
|
||
}
|
||
} |