Merge branch 'Testing' of https://gitea.grillkol.net/grillkol/FluidSim into Testing

This commit is contained in:
max
2026-06-09 17:50:16 +02:00
4 changed files with 177 additions and 145 deletions

View File

@@ -100,24 +100,39 @@ namespace FluidSim.Components
{ {
float deg = thetaDeg % 720f; float deg = thetaDeg % 720f;
if (deg < 0f) deg += 720f; if (deg < 0f) deg += 720f;
float duration = closes - opens;
float duration;
float effectiveOpen = opens;
float effectiveClose = closes;
if (closes < opens)
{
// Wraparound case (e.g., exhaust: opens near 480°, closes near 30°)
effectiveClose += 720f;
}
duration = effectiveClose - effectiveOpen;
if (duration <= 0f) return 0f; if (duration <= 0f) return 0f;
// Map the angle into the [opens, opens+duration] window
float mapped = deg;
if (mapped < opens) mapped += 720f;
if (mapped < opens || mapped > effectiveClose) return 0f;
float rampDur = duration * 0.25f; float rampDur = duration * 0.25f;
float holdDur = duration - 2f * rampDur; float holdDur = duration - 2f * rampDur;
if (deg >= opens && deg < opens + rampDur) if (mapped >= opens && mapped < opens + rampDur)
{ {
float t = (deg - opens) / rampDur; float t = (mapped - opens) / rampDur;
return peakLift * t * t * (3f - 2f * t); return peakLift * t * t * (3f - 2f * t);
} }
else if (deg >= opens + rampDur && deg < opens + rampDur + holdDur) else if (mapped >= opens + rampDur && mapped < opens + rampDur + holdDur)
{ {
return peakLift; return peakLift;
} }
else if (deg >= opens + rampDur + holdDur && deg <= closes) else if (mapped >= opens + rampDur + holdDur && mapped <= effectiveClose)
{ {
float t = (deg - (opens + rampDur + holdDur)) / rampDur; float t = (mapped - (opens + rampDur + holdDur)) / rampDur;
return peakLift * (1f - t) * (1f - t) * (1f + 2f * t); return peakLift * (1f - t) * (1f - t) * (1f + 2f * t);
} }
return 0f; return 0f;
@@ -194,6 +209,7 @@ namespace FluidSim.Components
float totalMass = _airMass + _exhaustMass; float totalMass = _airMass + _exhaustMass;
_airMass = 0f; _exhaustMass = totalMass; _airMass = 0f; _exhaustMass = totalMass;
} }
fuelInjected = false;
float dFraction = newFraction - burnFraction; float dFraction = newFraction - burnFraction;
if (dFraction > 0f) if (dFraction > 0f)

View File

@@ -25,7 +25,8 @@ namespace FluidSim.Core
public float EffectiveLength; public float EffectiveLength;
public float CurrentMdot; // kg/s, positive = volume → pipe public float CurrentMdot; // kg/s, positive = volume → pipe
// --- Loss coefficient (linear resistance) --- // --- Loss coefficient (linear resistance) inertance only ---
// If 0 when UseInertance is true, a stable default is autocomputed at runtime.
public float LossCoefficient; // N·s/m⁵ or kg/(m⁴·s) public float LossCoefficient; // N·s/m⁵ or kg/(m⁴·s)
} }
@@ -57,9 +58,10 @@ namespace FluidSim.Core
public int OpenEndCount { get; private set; } public int OpenEndCount { get; private set; }
// ---------- Add orifice (no inertance) ---------- // ---------- Add orifice (no inertance) ----------
// Simple isentropic nozzle no builtin loss. For dissipation use pipe damping
// or the inertance model if you need a damped resonator.
public void AddOrifice(Port volumePort, int pipeIndex, bool isLeftEnd, public void AddOrifice(Port volumePort, int pipeIndex, bool isLeftEnd,
int areaIndex, float dischargeCoeff = 1f, int areaIndex, float dischargeCoeff = 1f)
float lossCoefficient = 0f)
{ {
_orifices[OrificeCount] = new OrificeDesc _orifices[OrificeCount] = new OrificeDesc
{ {
@@ -71,22 +73,24 @@ namespace FluidSim.Core
UseInertance = false, UseInertance = false,
EffectiveLength = 0f, EffectiveLength = 0f,
CurrentMdot = 0f, CurrentMdot = 0f,
LossCoefficient = lossCoefficient LossCoefficient = 0f
}; };
OrificeCount++; OrificeCount++;
} }
// ---------- Add orifice with inertance ---------- // ---------- Add orifice with inertance ----------
// effectiveLength length of the inertial slug (m), typically the physical neck length.
// lossCoefficient linear resistance (N·s/m⁵). If 0 (or omitted) an automatic stable
// value will be computed from the pipe's characteristic impedance.
public void AddOrificeWithInertance(Port volumePort, int pipeIndex, bool isLeftEnd, public void AddOrificeWithInertance(Port volumePort, int pipeIndex, bool isLeftEnd,
int areaIndex, float dischargeCoeff, int areaIndex, float dischargeCoeff,
float effectiveLength, float lossCoefficient = 0f) float effectiveLength, float lossCoefficient = 0f)
{ {
// Reuse the base AddOrifice and then override fields AddOrifice(volumePort, pipeIndex, isLeftEnd, areaIndex, dischargeCoeff);
AddOrifice(volumePort, pipeIndex, isLeftEnd, areaIndex, dischargeCoeff, lossCoefficient);
ref var d = ref _orifices[OrificeCount - 1]; ref var d = ref _orifices[OrificeCount - 1];
d.UseInertance = true; d.UseInertance = true;
d.EffectiveLength = effectiveLength; d.EffectiveLength = effectiveLength;
d.LossCoefficient = lossCoefficient; // store the linear resistance d.LossCoefficient = lossCoefficient;
} }
public void AddOpenEnd(int pipeIndex, bool isLeftEnd, public void AddOpenEnd(int pipeIndex, bool isLeftEnd,
@@ -146,7 +150,7 @@ namespace FluidSim.Core
? _pipeSystem.GetInteriorAirFractionLeft(d.PipeIndex) ? _pipeSystem.GetInteriorAirFractionLeft(d.PipeIndex)
: _pipeSystem.GetInteriorAirFractionRight(d.PipeIndex); : _pipeSystem.GetInteriorAirFractionRight(d.PipeIndex);
// ---- Handle closed orifice (area ≈ 0) as a wall ---- // ---- Handle closed orifice as a wall ----
if (area < 1e-12f || d.VolumePort == null) if (area < 1e-12f || d.VolumePort == null)
{ {
var (rInt, uInt, pInt) = d.IsLeftEnd var (rInt, uInt, pInt) = d.IsLeftEnd
@@ -184,10 +188,10 @@ namespace FluidSim.Core
if (d.UseInertance) if (d.UseInertance)
{ {
// ---- Inertance ODE with (possibly automatic) linear loss ----
float rhoUp = d.CurrentMdot >= 0 ? volRho : pipeRho; float rhoUp = d.CurrentMdot >= 0 ? volRho : pipeRho;
float inertance = rhoUp * d.EffectiveLength / MathF.Max(area, 1e-12f); float inertance = rhoUp * d.EffectiveLength / MathF.Max(area, 1e-12f);
float dp = volP - pipeP; float dp = volP - pipeP;
float Rlin = d.LossCoefficient;
float dmdot_dt = (dp - Rlin * d.CurrentMdot) / MathF.Max(inertance, 1e-12f); float dmdot_dt = (dp - Rlin * d.CurrentMdot) / MathF.Max(inertance, 1e-12f);
float mdotNew = d.CurrentMdot + dmdot_dt * dt; float mdotNew = d.CurrentMdot + dmdot_dt * dt;

View File

@@ -38,7 +38,7 @@ namespace FluidSim.Tests
int neckCells = 20; int neckCells = 20;
// --- Volume (cavity) --- // --- Volume (cavity) ---
float initialPressure = 1.2f * 101325f; // slight overpressure float initialPressure = 1.1f * 101325f; // slight overpressure
float initialTemperature = 300f; float initialTemperature = 300f;
cavity = new Volume0D(cavityVolume, initialPressure, initialTemperature); cavity = new Volume0D(cavityVolume, initialPressure, initialTemperature);
cavityPort = cavity.CreatePort(); cavityPort = cavity.CreatePort();
@@ -56,24 +56,16 @@ namespace FluidSim.Tests
float rho0 = 101325f / (287f * 300f); float rho0 = 101325f / (287f * 300f);
pipeSystem = new PipeSystem(neckCells, pipeStart, pipeEnd, areas, dxs, rho0, 0f, 101325f); pipeSystem = new PipeSystem(neckCells, pipeStart, pipeEnd, areas, dxs, rho0, 0f, 101325f);
pipeSystem.DampingMultiplier = 500f;
// --- Boundary system --- // --- Boundary system ---
boundaries = new BoundarySystem(pipeSystem, maxOrifices: 1, maxOpenEnds: 1); boundaries = new BoundarySystem(pipeSystem, maxOrifices: 1, maxOpenEnds: 1);
float ComputeResistance(float decayTimeSeconds, float rho, float L_eff, float A) // Standard orifice with builtin minor loss (K = 0.5) no inertance needed
{
// R = 2 * rho * L_eff / (A * decayTimeSeconds)
return 2f * rho * L_eff / (A * MathF.Max(decayTimeSeconds, 1e-6f));
}
// Use steady orifice the pipe already provides the inertia
boundaries.AddOrificeWithInertance( boundaries.AddOrificeWithInertance(
cavityPort, pipeIndex: 0, isLeftEnd: true, cavityPort, pipeIndex: 0, isLeftEnd: true,
areaIndex: cavityOrificeIdx, areaIndex: cavityOrificeIdx,
dischargeCoeff: 0.9f, dischargeCoeff: 0.9f,
effectiveLength: neckLength, // physical length (or L_eff) effectiveLength: neckLength // physical neck length
lossCoefficient: 7000 // start with this, adjust for decay time
); );
// Open end at right side of pipe // Open end at right side of pipe
@@ -83,8 +75,7 @@ namespace FluidSim.Tests
boundaries.SetOrificeAreas(orificeAreas); boundaries.SetOrificeAreas(orificeAreas);
// --- Solver --- // --- Solver ---
// Slightly higher substep count to ensure stability of the resonant oscillation solver = new Solver { SubStepCount = 8, EnableProfiling = false };
solver = new Solver { SubStepCount = 6, EnableProfiling = false };
solver.SetTimeStep(dt); solver.SetTimeStep(dt);
solver.SetPipeSystem(pipeSystem); solver.SetPipeSystem(pipeSystem);
solver.SetBoundarySystem(boundaries); solver.SetBoundarySystem(boundaries);
@@ -99,43 +90,12 @@ namespace FluidSim.Tests
public override float Process() public override float Process()
{ {
stepCount++;
if (stepCount <= 8192) return 0f; // let buffer prefill
solver.Step(); solver.Step();
stepCount++;
float flow = boundaries.GetOpenEndMassFlow(openEndIdx); float flow = boundaries.GetOpenEndMassFlow(openEndIdx);
float sample = soundProcessor.Process(flow); float sample = soundProcessor.Process(flow);
if (stepCount % 10000 == 0)
{
float cavityP = cavity.Pressure;
float cavityT = cavity.Temperature;
float cavityRho = cavity.Density;
float cCavity = MathF.Sqrt(1.4f * cavityP / MathF.Max(cavityRho, 1e-12f));
// Temperature in the middle of the neck
int midCell = 10;
float pMid = pipeSystem.GetCellPressure(midCell);
float rhoMid = pipeSystem.GetCellDensity(midCell);
float tMid = pMid / MathF.Max(rhoMid * 287f, 1e-12f);
// Neck effective length (physical + end correction)
float neckLen = 0.05f; // physical
float neckDia = 0.02f;
float neckArea = MathF.PI * 0.25f * neckDia * neckDia;
float endCorr = 0.85f * neckDia; // unflanged end
float L_eff = neckLen + endCorr;
// Theoretical Helmholtz frequency from current cavity sound speed
float fHelmholtz = cCavity / (2f * MathF.PI) *
MathF.Sqrt(neckArea / (cavity.Volume * L_eff));
Console.WriteLine(
$"Step {stepCount}: cav P={cavityP / 1e5f:F4} bar, T={cavityT:F1} K, " +
$"pipeMid T={tMid:F1} K, est f={fHelmholtz:F1} Hz");
}
return sample; return sample;
} }

View File

@@ -9,31 +9,71 @@ namespace FluidSim.Tests
{ {
public class SingleCylScenario : Scenario public class SingleCylScenario : Scenario
{ {
// ---------- Engine components ----------
private Crankshaft crankshaft; private Crankshaft crankshaft;
private Cylinder cylinder; private Cylinder cylinder;
// ---------- Fluid network ----------
private PipeSystem pipeSystem; private PipeSystem pipeSystem;
private BoundarySystem boundaries; private BoundarySystem boundaries;
private Solver solver; private Solver solver;
// Volumes
private Volume0D intakePlenum; private Volume0D intakePlenum;
// Ports
private Port plenumInlet, plenumOutlet; private Port plenumInlet, plenumOutlet;
private Volume0D exhaustCollector;
private Port colIn, colOut;
private int throttleAreaIdx, plenumRunnerAreaIdx, intakeValveIdx, exhaustValveIdx; // Orifice / openend indices
private float[] orificeAreas; private int throttleAreaIdx, plenumRunnerIdx, intakeValveIdx, exhaustValveIdx;
private int intakeOpenIdx, exhaustOpenIdx; private int intakeOpenIdx, exhaustOpenIdx;
private float[] orificeAreas;
// Sound
private SoundProcessor exhaustSound, intakeSound; private SoundProcessor exhaustSound, intakeSound;
private OutdoorExhaustReverb reverb; private OutdoorExhaustReverb reverb;
// ---------- Simulation state ----------
private double dt; private double dt;
private int stepCount; private int stepCount;
public float MaxThrottleArea = 100e-4f; // 1 cm² public float MaxThrottleArea = 100e-4f; // 1 cm²
// pipe area for open end calculations // ---------- Geometry (Lifan YX140) ----------
private float pipeArea; // Bore 56 mm, Stroke 57 mm, CR 9.5
private const float Bore = 0.056f;
private const float Stroke = 0.057f;
private const float ConRod = 0.110f; // typical for 57 mm stroke
private const float CompressionRatio = 9.5f;
// Valve diameters (intake 27 mm, exhaust 23 mm)
private const float IntakeValveDiam = 0.027f;
private const float ExhaustValveDiam = 0.023f;
private const float ValveLift = 0.006f; // 6 mm peak lift
// Valve timings (degrees, 720° fourstroke)
// Intake: 15° BTDC → 45° ABDC
private const float IVO = 345f; // 15° BTDC
private const float IVC = 585f; // 45° ABDC (180°+45°)
// Exhaust: 45° BBDC → 15° ATDC
private const float EVO = 135f; // 45° BBDC (180°-45°)
private const float EVC = 375f; // 15° ATDC (360°+15°)
// Spark advance: 30° BTDC
private const float SparkAdv = 30f;
// Pipe / plenum sizes
private const float PipeDiam = 0.025f; // 25 mm intake / exhaust
private const float PipeArea = 0.00049087f; // π*D²/4
private const float PlenumVolume = 0.0005f; // 500 mL
private const float MaxThrottleArea = 1e-4f; // ~1 cm² (fully open)
// Pipe lengths and cell counts
private const float IntakeLenBefore = 0.15f; // 15 cm before throttle
private const float RunnerLen = 0.25f; // 25 cm runner
private const float ExhaustLen = 0.60f; // 60 cm exhaust
private const int CellsBefore = 6;
private const int CellsRunner = 10;
private const int CellsExhaust = 24;
public override void Initialize(int sampleRate) public override void Initialize(int sampleRate)
{ {
@@ -45,34 +85,39 @@ namespace FluidSim.Tests
crankshaft.FrictionConstant = 2f; crankshaft.FrictionConstant = 2f;
crankshaft.FrictionViscous = 0.01f; crankshaft.FrictionViscous = 0.01f;
// ---- Cylinder ---- // ---------- Cylinder ----------
float bore = 0.056f, stroke = 0.057f, conRod = 0.110f, compRatio = 9.2f; cylinder = new Cylinder(Bore, Stroke, ConRod, CompressionRatio,
float ivo = 350f, ivc = 580f, evo = 120f, evc = 370f; IVO, IVC, EVO, EVC, crankshaft)
cylinder = new Cylinder(bore, stroke, conRod, compRatio,
ivo, ivc, evo, evc, crankshaft)
{ {
IntakeValveDiameter = 0.03f, IntakeValveDiameter = IntakeValveDiam,
IntakeValveLift = 0.005f, ExhaustValveDiameter = ExhaustValveDiam,
ExhaustValveDiameter = 0.028f, IntakeValveLift = ValveLift,
ExhaustValveLift = 0.005f ExhaustValveLift = ValveLift,
SparkAdvance = SparkAdv,
EnergyVariationFraction = 0.03f, // small cycletocycle variation
MisfireProbability = 0.0f
}; };
// ---- Pipe system ---- // ---------- Pipe system ----------
int totalCells = 10 + 10 + 50; int totalCells = CellsBefore + CellsRunner + CellsExhaust;
int[] pipeStart = { 0, 10, 20 }; int[] pipeStart = { 0, CellsBefore, CellsBefore + CellsRunner };
int[] pipeEnd = { 10, 20, 70 }; int[] pipeEnd = { CellsBefore, CellsBefore + CellsRunner, totalCells };
float[] area = new float[totalCells];
float[] dx = new float[totalCells]; float[] areas = new float[totalCells];
float pipeDiameter = 0.02f; // 2 cm float[] dxs = new float[totalCells];
pipeArea = MathF.PI * 0.25f * pipeDiameter * pipeDiameter; float dxBefore = IntakeLenBefore / CellsBefore;
float areaVal = pipeArea; float dxRunner = RunnerLen / CellsRunner;
float intakeLenBefore = 0.2f, intakeLenRunner = 0.2f, exhaustLen = 0.5f; float dxExh = ExhaustLen / CellsExhaust;
for (int i = 0; i < totalCells; i++) for (int i = 0; i < totalCells; i++)
{ {
area[i] = areaVal; areas[i] = PipeArea;
if (i < 10) dx[i] = intakeLenBefore / 10f; if (i < CellsBefore)
else if (i < 20) dx[i] = intakeLenRunner / 10f; dxs[i] = dxBefore;
else dx[i] = exhaustLen / 50f; else if (i < CellsBefore + CellsRunner)
dxs[i] = dxRunner;
else
dxs[i] = dxExh;
} }
pipeSystem = new PipeSystem(totalCells, pipeStart, pipeEnd, area, dx, pipeSystem = new PipeSystem(totalCells, pipeStart, pipeEnd, area, dx,
@@ -85,49 +130,45 @@ namespace FluidSim.Tests
intakePlenum = new Volume0D(100e-6f, 101325f, 300f); // 100 mL intakePlenum = new Volume0D(100e-6f, 101325f, 300f); // 100 mL
plenumInlet = intakePlenum.CreatePort(); plenumInlet = intakePlenum.CreatePort();
plenumOutlet = intakePlenum.CreatePort(); plenumOutlet = intakePlenum.CreatePort();
exhaustCollector = new Volume0D(10e-6f, 101325f, 800f); // 10 mL (unused but present)
colIn = exhaustCollector.CreatePort();
colOut = exhaustCollector.CreatePort();
// ---- Boundary system ---- // ---------- Boundary system ----------
boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2); boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2);
throttleAreaIdx = 0; throttleAreaIdx = 0;
plenumRunnerAreaIdx = 1; plenumRunnerIdx = 1;
intakeValveIdx = 2; intakeValveIdx = 2;
exhaustValveIdx = 3; exhaustValveIdx = 3;
// Intake open end (pipe0 left) // Open ends
boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, pipeArea); boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, PipeArea);
intakeOpenIdx = 0; intakeOpenIdx = 0;
boundaries.AddOpenEnd(pipeIndex: 2, isLeftEnd: false, 101325f, PipeArea);
// Throttle orifice (plenum inlet to pipe0 right)
boundaries.AddOrifice(plenumInlet, pipeIndex: 0, isLeftEnd: false, throttleAreaIdx, 0.2f);
// Plenum to runner (plenum outlet to pipe1 left)
boundaries.AddOrifice(plenumOutlet, pipeIndex: 1, isLeftEnd: true, plenumRunnerAreaIdx, 1f);
// Intake valve (cylinder intake to pipe1 right)
boundaries.AddOrifice(cylinder.IntakePort, pipeIndex: 1, isLeftEnd: false, intakeValveIdx, 1f);
// Exhaust valve (cylinder exhaust to pipe2 left)
boundaries.AddOrifice(cylinder.ExhaustPort, pipeIndex: 2, isLeftEnd: true, exhaustValveIdx, 1f);
// Exhaust open end (pipe2 right)
boundaries.AddOpenEnd(pipeIndex: 2, isLeftEnd: false, 101325f, pipeArea);
exhaustOpenIdx = 1; exhaustOpenIdx = 1;
orificeAreas = new float[4]; // Orifices
orificeAreas[plenumRunnerAreaIdx] = areaVal; // fixed plenum->runner area // throttle variable area, low discharge for restriction
boundaries.AddOrifice(plenumInlet, pipeIndex: 0, isLeftEnd: false,
throttleAreaIdx, dischargeCoeff: 0.8f);
// plenum → runner
boundaries.AddOrifice(plenumOutlet, pipeIndex: 1, isLeftEnd: true,
plenumRunnerIdx, dischargeCoeff: 1.0f);
// intake valve
boundaries.AddOrifice(cylinder.IntakePort, pipeIndex: 1, isLeftEnd: false,
intakeValveIdx, dischargeCoeff: 1.0f);
// exhaust valve
boundaries.AddOrifice(cylinder.ExhaustPort, pipeIndex: 2, isLeftEnd: true,
exhaustValveIdx, dischargeCoeff: 1.0f);
// ---- Solver ---- orificeAreas = new float[4];
solver = new Solver { SubStepCount = 4, EnableProfiling = false }; orificeAreas[plenumRunnerIdx] = PipeArea; // fixed fullbore
// ---------- Solver ----------
solver = new Solver { SubStepCount = 5, EnableProfiling = false };
solver.SetTimeStep(dt); solver.SetTimeStep(dt);
solver.SetPipeSystem(pipeSystem); solver.SetPipeSystem(pipeSystem);
solver.SetBoundarySystem(boundaries); solver.SetBoundarySystem(boundaries);
solver.AddComponent(cylinder); solver.AddComponent(cylinder);
solver.AddComponent(intakePlenum); solver.AddComponent(intakePlenum);
solver.AddComponent(exhaustCollector);
// ---- Sound ---- // ---- Sound ----
exhaustSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f }; exhaustSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f };
@@ -135,32 +176,34 @@ namespace FluidSim.Tests
reverb = new OutdoorExhaustReverb(sampleRate); reverb = new OutdoorExhaustReverb(sampleRate);
stepCount = 0; stepCount = 0;
Console.WriteLine("TestScenario ready."); Console.WriteLine("Singlecylinder engine (YX140) ready.");
} }
public override float Process() public override float Process()
{ {
// ---- Crank and cylinder prestep ----
crankshaft.Step((float)dt); crankshaft.Step((float)dt);
cylinder.PreStep((float)dt); cylinder.PreStep((float)dt);
// Update variable orifice areas // ---- Update variable areas ----
float throttledArea = MaxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1f); float throttledArea = MaxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1.0f);
orificeAreas[throttleAreaIdx] = throttledArea; orificeAreas[throttleAreaIdx] = throttledArea;
orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea; orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea;
orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea; orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea;
boundaries.SetOrificeAreas(orificeAreas); boundaries.SetOrificeAreas(orificeAreas);
// ---- Fluids step ----
solver.Step(); solver.Step();
stepCount++; stepCount++;
// Retrieve openend mass flows for sound synthesis // ---- Sound ----
float exhaustFlow = boundaries.GetOpenEndMassFlow(exhaustOpenIdx); float exhaustFlow = boundaries.GetOpenEndMassFlow(exhaustOpenIdx);
float intakeFlow = boundaries.GetOpenEndMassFlow(intakeOpenIdx); float intakeFlow = boundaries.GetOpenEndMassFlow(intakeOpenIdx);
float exhaustDry = exhaustSound.Process(exhaustFlow); float exhaustDry = exhaustSound.Process(exhaustFlow);
float intakeDry = intakeSound.Process(intakeFlow); float intakeDry = intakeSound.Process(intakeFlow);
if (stepCount % 1000 == 0) if (stepCount % 2000 == 0)
{ {
float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI); float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI);
float crankDeg = crankshaft.CrankAngle; // public property on Cylinder float crankDeg = crankshaft.CrankAngle; // public property on Cylinder
@@ -197,44 +240,53 @@ namespace FluidSim.Tests
float intakeY = winH / 2f - 40f; float intakeY = winH / 2f - 40f;
float exhaustY = winH / 2f + 80f; float exhaustY = winH / 2f + 80f;
float openEndX = 40f; float leftX = 40f;
// Intake pipe before throttle (pipe 0) // Intake open end marker
float pipe1StartX = openEndX; var om = new CircleShape(5f) { FillColor = Color.Cyan };
float pipe1EndX = pipe1StartX + 120f; om.Position = new Vector2f(leftX - 5f, intakeY - 5f);
DrawPipe(target, pipeSystem, 0, intakeY, pipe1StartX, pipe1EndX); target.Draw(om);
// Pipe 0 before throttle
float p0EndX = leftX + 80f;
DrawPipe(target, pipeSystem, 0, intakeY, leftX, p0EndX);
// Throttle symbol // Throttle symbol
float throttleX = pipe1EndX + 5f; float thrX = p0EndX + 5f;
var throttleRect = new RectangleShape(new Vector2f(8f, 30f)) var thr = new RectangleShape(new Vector2f(8f, 30f))
{ {
FillColor = Color.Yellow, FillColor = Color.Yellow,
Position = new Vector2f(throttleX, intakeY - 15f) Position = new Vector2f(thrX, intakeY - 15f)
}; };
target.Draw(throttleRect); target.Draw(thr);
// Plenum // Plenum volume
float plenW = 60f, plenH = 80f; float plenW = 60f, plenH = 50f;
float plenLeftX = throttleX + 10f; float plenLeftX = thrX + 12f;
float plenCenterX = plenLeftX + plenW / 2f; float plenCenterX = plenLeftX + plenW / 2f;
float plenTopY = intakeY - plenH / 2f; float plenTopY = intakeY - plenH / 2f;
DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH); DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);
// Runner pipe (pipe 1) // Pipe 1 runner
float runnerStartX = plenLeftX + plenW + 5f; float rStartX = plenLeftX + plenW + 10f;
float runnerEndX = runnerStartX + 100f; float rEndX = rStartX + 100f;
DrawPipe(target, pipeSystem, 1, intakeY, runnerStartX, runnerEndX); DrawPipe(target, pipeSystem, 1, intakeY, rStartX, rEndX);
// Cylinder // Cylinder
float cylCX = runnerEndX + 50f; float cylCX = rEndX + 50f;
float cylTopY = intakeY - 120f; float cylTopY = intakeY - 120f;
float cylW = 80f, cylMaxH = 240f; float cylW = 80f, cylMaxH = 240f;
DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH); DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH);
// Exhaust pipe (pipe 2) // Pipe 2 exhaust
float exhStartX = cylCX + cylW / 2f + 20f; float exhStartX = cylCX + cylW / 2f + 20f;
float exhEndX = winW - 60f; float exhEndX = winW - 60f;
DrawPipe(target, pipeSystem, 2, exhaustY, exhStartX, exhEndX); DrawPipe(target, pipeSystem, 2, exhaustY, exhStartX, exhEndX);
// Exhaust open end
var em = new CircleShape(5f) { FillColor = Color.Magenta };
em.Position = new Vector2f(exhEndX - 5f, exhaustY - 5f);
target.Draw(em);
} }
} }
} }