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; } } } } }