117 lines
4.5 KiB
C#
117 lines
4.5 KiB
C#
using System;
|
||
using FluidSim.Components; // if needed
|
||
|
||
namespace FluidSim.Components
|
||
{
|
||
public class Cylinder : EngineCylinder
|
||
{
|
||
public float IVO, IVC, EVO, EVC; // degrees in a 720° cycle
|
||
|
||
protected override float CycleLengthRad => 4f * MathF.PI;
|
||
protected override float MaxCycleDeg => 720f;
|
||
|
||
public override float IntakeValveArea =>
|
||
MathF.PI * IntakeValveDiameter * ValveLift(CrankDeg, IVO, IVC, IntakeValveLift);
|
||
public override float ExhaustValveArea =>
|
||
MathF.PI * ExhaustValveDiameter * ValveLift(CrankDeg, EVO, EVC, ExhaustValveLift);
|
||
|
||
public Cylinder(float bore, float stroke, float conRodLength, float compressionRatio,
|
||
float ivo, float ivc, float evo, float evc, Crankshaft crankshaft)
|
||
: base(bore, stroke, conRodLength, compressionRatio, crankshaft)
|
||
{
|
||
IVO = ivo; IVC = ivc; EVO = evo; EVC = evc;
|
||
}
|
||
|
||
private float ValveLift(float thetaDeg, float opens, float closes, float peakLift)
|
||
{
|
||
float deg = thetaDeg % 720f;
|
||
if (deg < 0f) deg += 720f;
|
||
|
||
float effectiveOpen = opens;
|
||
float effectiveClose = closes;
|
||
if (closes < opens) effectiveClose += 720f;
|
||
float duration = effectiveClose - effectiveOpen;
|
||
if (duration <= 0f) return 0f;
|
||
|
||
float mapped = deg;
|
||
if (mapped < opens) mapped += 720f;
|
||
if (mapped < opens || mapped > effectiveClose) return 0f;
|
||
|
||
float rampDur = duration * 0.25f;
|
||
float holdDur = duration - 2f * rampDur;
|
||
|
||
if (mapped >= opens && mapped < opens + rampDur)
|
||
{
|
||
float t = (mapped - opens) / rampDur;
|
||
return peakLift * t * t * (3f - 2f * t);
|
||
}
|
||
else if (mapped >= opens + rampDur && mapped < opens + rampDur + holdDur)
|
||
{
|
||
return peakLift;
|
||
}
|
||
else if (mapped >= opens + rampDur + holdDur && mapped <= effectiveClose)
|
||
{
|
||
float t = (mapped - (opens + rampDur + holdDur)) / rampDur;
|
||
return peakLift * (1f - t) * (1f - t) * (1f + 2f * t);
|
||
}
|
||
return 0f;
|
||
}
|
||
|
||
protected override void HandleCycleEvents(float prevDeg, float currDeg, float dt)
|
||
{
|
||
// Intake closing → fuel injection
|
||
if (prevDeg >= IVO && prevDeg < IVC && currDeg >= IVC)
|
||
{
|
||
trappedAirMass = _airMass;
|
||
fuelMass = trappedAirMass / StoichiometricAFR;
|
||
fuelInjected = true;
|
||
}
|
||
|
||
// Spark – occurs at TDC (0°) minus advance, every 720°
|
||
float sparkAngle = (0f - SparkAdvance + 720f) % 720f;
|
||
bool crossedSpark = false;
|
||
if (prevDeg < sparkAngle && currDeg >= sparkAngle)
|
||
crossedSpark = true;
|
||
else if (prevDeg > sparkAngle && currDeg < sparkAngle)
|
||
crossedSpark = true;
|
||
|
||
if (crossedSpark && !combustionActive && fuelInjected)
|
||
{
|
||
if (_random.NextDouble() < MisfireProbability)
|
||
{
|
||
combustionActive = false;
|
||
}
|
||
else
|
||
{
|
||
combustionActive = true; burnFraction = 0f;
|
||
float range = EnergyVariationFraction;
|
||
_energyFactor = 1f + range * (2f * (float)_random.NextDouble() - 1f);
|
||
}
|
||
}
|
||
|
||
// Combustion progression
|
||
if (combustionActive)
|
||
{
|
||
float angleSinceSpark = currDeg - sparkAngle;
|
||
if (angleSinceSpark < 0f) angleSinceSpark += 720f;
|
||
float newFraction = Wiebe(angleSinceSpark);
|
||
if (newFraction >= 1f || angleSinceSpark > (WiebeDuration + WiebeStart + SparkAdvance))
|
||
{
|
||
newFraction = 1f; combustionActive = false;
|
||
float totalMass = _airMass + _exhaustMass;
|
||
_airMass = 0f; _exhaustMass = totalMass;
|
||
}
|
||
fuelInjected = false;
|
||
|
||
float dFraction = newFraction - burnFraction;
|
||
if (dFraction > 0f)
|
||
{
|
||
float dQ = fuelMass * FuelLowerHeatingValue * _energyFactor * dFraction;
|
||
cylinderEnergy += dQ;
|
||
_exhaustMass += fuelMass * dFraction;
|
||
burnFraction = newFraction;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |