inline 4 testing

This commit is contained in:
max
2026-05-08 13:16:51 +02:00
parent f275937abb
commit 9c9e23147a
3 changed files with 282 additions and 113 deletions

View File

@@ -16,7 +16,7 @@ namespace FluidSim.Core
private double _dt; private double _dt;
/// <summary>CFL target for substepping (0.30.8). Lower values are safer for shocks.</summary> /// <summary>CFL target for substepping (0.30.8). Lower values are safer for shocks.</summary>
public double CflTarget { get; set; } = 0.8; public double CflTarget { get; set; } = 0.9;
// ---------- Timing accumulators (reset every LogInterval steps) ---------- // ---------- Timing accumulators (reset every LogInterval steps) ----------
private long _stepCount; private long _stepCount;

View File

@@ -50,7 +50,7 @@ public class Program
{ {
var window = CreateWindow(); var window = CreateWindow();
LoadFont(); LoadFont();
_scenario = new TestScenario(); _scenario = new Inline4Scenario();
_scenario.Initialize(SampleRate); _scenario.Initialize(SampleRate);
_lastThrottleUpdateTime = 0.0; _lastThrottleUpdateTime = 0.0;

View File

@@ -19,11 +19,15 @@ namespace FluidSim.Tests
private Pipe1D intakePipeBeforeThrottle; private Pipe1D intakePipeBeforeThrottle;
private Volume0D intakePlenum; private Volume0D intakePlenum;
// Runners // Runners (shorter, fewer cells)
private Pipe1D runner1, runner2, runner3, runner4; private Pipe1D runner1, runner2, runner3, runner4;
// Exhaust pipes // Exhaust collector + tailpipe
private Pipe1D exh1, exh2, exh3, exh4; private Volume0D exhaustCollector;
private Pipe1D tailPipe;
// Exhaust stubs (short pipes between cylinders and collector)
private Pipe1D exhStub1, exhStub2, exhStub3, exhStub4;
// Links intake // Links intake
private OpenEndLink intakeOpenEnd; private OpenEndLink intakeOpenEnd;
@@ -35,11 +39,17 @@ namespace FluidSim.Tests
// Intake valves // Intake valves
private OrificeLink intakeValve1, intakeValve2, intakeValve3, intakeValve4; private OrificeLink intakeValve1, intakeValve2, intakeValve3, intakeValve4;
// Exhaust valves // Exhaust valves (cylinder → stub)
private OrificeLink exhaustValve1, exhaustValve2, exhaustValve3, exhaustValve4; private OrificeLink exhaustValve1, exhaustValve2, exhaustValve3, exhaustValve4;
// Exhaust open ends // Stubtocollector orifices
private OpenEndLink exhaustOpenEnd1, exhaustOpenEnd2, exhaustOpenEnd3, exhaustOpenEnd4; private OrificeLink stubToCollector1, stubToCollector2, stubToCollector3, stubToCollector4;
// Collectortotailpipe orifice
private OrificeLink collectorToTailpipe;
// Exhaust open end (tailpipe exit)
private OpenEndLink exhaustOpenEnd;
private Solver solver; private Solver solver;
private SoundProcessor exhaustSoundProcessor; private SoundProcessor exhaustSoundProcessor;
@@ -48,7 +58,7 @@ namespace FluidSim.Tests
private double dt; private double dt;
private int stepCount; private int stepCount;
public double MaxThrottleArea { get; set; } = 3 * Units.cm2; public double MaxThrottleArea { get; set; } = 10 * Units.cm2;
public override void Initialize(int sampleRate) public override void Initialize(int sampleRate)
{ {
@@ -56,23 +66,23 @@ namespace FluidSim.Tests
solver = new Solver(); solver = new Solver();
solver.SetTimeStep(dt); solver.SetTimeStep(dt);
solver.CflTarget = 0.9; solver.CflTarget = 1;
// ---- Shared crankshaft ---- // ---- Shared crankshaft ----
crankshaft = new Crankshaft(800); crankshaft = new Crankshaft(800);
crankshaft.Inertia = 1; crankshaft.Inertia = 1;
crankshaft.FrictionConstant = 16; crankshaft.FrictionConstant = 3;
crankshaft.FrictionViscous = 0.5; crankshaft.FrictionViscous = 0.2;
// ---- Cylinder geometry ---- // ---- Cylinder geometry ----
double bore = 0.056, stroke = 0.057, conRod = 0.110, compRatio = 9.2; double bore = 0.056, stroke = 0.057, conRod = 0.110, compRatio = 10;
double ivo = 350.0, ivc = 580.0, evo = 120.0, evc = 370.0; double ivo = 350.0, ivc = 580.0, evo = 120.0, evc = 370.0;
// Firing order 1-3-4-2 → phase offsets in radians // Firing order 1-3-4-2 with 180° intervals (0°, 180°, 360°, 540°)
double phase0 = 0.0 * Math.PI / 180.0; double phaseCyl1 = 0.0;
double phase1 = 180.0 * Math.PI / 180.0; double phaseCyl3 = Math.PI; // 180°
double phase2 = 540.0 * Math.PI / 180.0; double phaseCyl4 = 2.0 * Math.PI; // 360°
double phase3 = 360.0 * Math.PI / 180.0; double phaseCyl2 = 3.0 * Math.PI; // 540°
cyl1 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft) cyl1 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
{ {
@@ -80,9 +90,9 @@ namespace FluidSim.Tests
IntakeValveLift = 5 * Units.mm, IntakeValveLift = 5 * Units.mm,
ExhaustValveDiameter = 28 * Units.mm, ExhaustValveDiameter = 28 * Units.mm,
ExhaustValveLift = 5 * Units.mm, ExhaustValveLift = 5 * Units.mm,
PhaseOffset = phase0, PhaseOffset = phaseCyl1,
EnergyVariationFraction = 0.03, EnergyVariationFraction = 0.03,
MisfireProbability = 0.01 MisfireProbability = 0.0
}; };
cyl2 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft) cyl2 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
{ {
@@ -90,9 +100,9 @@ namespace FluidSim.Tests
IntakeValveLift = 5 * Units.mm, IntakeValveLift = 5 * Units.mm,
ExhaustValveDiameter = 28 * Units.mm, ExhaustValveDiameter = 28 * Units.mm,
ExhaustValveLift = 5 * Units.mm, ExhaustValveLift = 5 * Units.mm,
PhaseOffset = phase1, PhaseOffset = phaseCyl2,
EnergyVariationFraction = 0.03, EnergyVariationFraction = 0.03,
MisfireProbability = 0.01 MisfireProbability = 0.0
}; };
cyl3 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft) cyl3 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
{ {
@@ -100,9 +110,9 @@ namespace FluidSim.Tests
IntakeValveLift = 5 * Units.mm, IntakeValveLift = 5 * Units.mm,
ExhaustValveDiameter = 28 * Units.mm, ExhaustValveDiameter = 28 * Units.mm,
ExhaustValveLift = 5 * Units.mm, ExhaustValveLift = 5 * Units.mm,
PhaseOffset = phase2, PhaseOffset = phaseCyl3,
EnergyVariationFraction = 0.03, EnergyVariationFraction = 0.03,
MisfireProbability = 0.01 MisfireProbability = 0.0
}; };
cyl4 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft) cyl4 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
{ {
@@ -110,28 +120,30 @@ namespace FluidSim.Tests
IntakeValveLift = 5 * Units.mm, IntakeValveLift = 5 * Units.mm,
ExhaustValveDiameter = 28 * Units.mm, ExhaustValveDiameter = 28 * Units.mm,
ExhaustValveLift = 5 * Units.mm, ExhaustValveLift = 5 * Units.mm,
PhaseOffset = phase3, PhaseOffset = phaseCyl4,
EnergyVariationFraction = 0.03, EnergyVariationFraction = 0.03,
MisfireProbability = 0.01 MisfireProbability = 0.0
}; };
solver.AddComponent(cyl1); solver.AddComponent(cyl1);
solver.AddComponent(cyl2); solver.AddComponent(cyl2);
solver.AddComponent(cyl3); solver.AddComponent(cyl3);
solver.AddComponent(cyl4); solver.AddComponent(cyl4);
double pipeDiameter = 2 * Units.cm; double pipeDiameter = 4 * Units.cm;
double pipeArea = Units.AreaFromDiameter(pipeDiameter); double pipeArea = Units.AreaFromDiameter(pipeDiameter);
exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f }; // Sound processors (only one exhaust source now)
intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f }; exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.2f };
intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.2f };
reverb = new OutdoorExhaustReverb(sampleRate); reverb = new OutdoorExhaustReverb(sampleRate);
// ---- Intake pipe before throttle ---- // ---- Intake pipe before throttle (shorter, fewer cells) ----
intakePipeBeforeThrottle = new Pipe1D(0.2, pipeArea, 10); intakePipeBeforeThrottle = new Pipe1D(0.1, pipeArea, 10);
intakePipeBeforeThrottle.Name = "Intake pipe";
solver.AddComponent(intakePipeBeforeThrottle); solver.AddComponent(intakePipeBeforeThrottle);
// ---- Plenum ---- // ---- Plenum ----
intakePlenum = new Volume0D(50 * Units.mL, 101325.0, 300.0); intakePlenum = new Volume0D(100 * Units.mL, 101325.0, 300.0);
var plenumInlet = intakePlenum.CreatePort(); // port 0 var plenumInlet = intakePlenum.CreatePort(); // port 0
var plenumOut1 = intakePlenum.CreatePort(); // port 1 var plenumOut1 = intakePlenum.CreatePort(); // port 1
var plenumOut2 = intakePlenum.CreatePort(); // port 2 var plenumOut2 = intakePlenum.CreatePort(); // port 2
@@ -139,65 +151,118 @@ namespace FluidSim.Tests
var plenumOut4 = intakePlenum.CreatePort(); // port 4 var plenumOut4 = intakePlenum.CreatePort(); // port 4
solver.AddComponent(intakePlenum); solver.AddComponent(intakePlenum);
// ---- Runners ---- // ---- Intake runners (shorter, fewer cells) ----
runner1 = new Pipe1D(0.2, pipeArea, 5); runner1 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 1" };
runner2 = new Pipe1D(0.2, pipeArea, 5); runner2 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 2" };
runner3 = new Pipe1D(0.2, pipeArea, 5); runner3 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 3" };
runner4 = new Pipe1D(0.2, pipeArea, 5); runner4 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 4" };
solver.AddComponent(runner1); solver.AddComponent(runner1);
solver.AddComponent(runner2); solver.AddComponent(runner2);
solver.AddComponent(runner3); solver.AddComponent(runner3);
solver.AddComponent(runner4); solver.AddComponent(runner4);
// ---- Exhaust pipes ---- // ---- Exhaust collector volume ----
exh1 = new Pipe1D(0.2, pipeArea, 10); exhaustCollector = new Volume0D(200 * Units.mL, 101325.0, 800.0);
exh2 = new Pipe1D(0.2, pipeArea, 10); var colIn1 = exhaustCollector.CreatePort(); // cylinder 1 stub
exh3 = new Pipe1D(0.2, pipeArea, 10); var colIn2 = exhaustCollector.CreatePort(); // cylinder 2 stub
exh4 = new Pipe1D(0.2, pipeArea, 10); var colIn3 = exhaustCollector.CreatePort(); // cylinder 3 stub
solver.AddComponent(exh1); var colIn4 = exhaustCollector.CreatePort(); // cylinder 4 stub
solver.AddComponent(exh2); var colOut = exhaustCollector.CreatePort(); // to tailpipe
solver.AddComponent(exh3); solver.AddComponent(exhaustCollector);
solver.AddComponent(exh4);
// ---- Plenum → runner orifices ---- // ---- Exhaust stub pipes (short connection cylinder → collector) ----
plenumToRunner1 = new OrificeLink(plenumOut1, runner1, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false }; exhStub1 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 1" };
plenumToRunner2 = new OrificeLink(plenumOut2, runner2, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false }; exhStub2 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 2" };
plenumToRunner3 = new OrificeLink(plenumOut3, runner3, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false }; exhStub3 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 3" };
plenumToRunner4 = new OrificeLink(plenumOut4, runner4, isPipeLeftEnd: true, areaProvider: () => pipeArea) { DischargeCoefficient = 1.0, UseInertance = false }; exhStub4 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 4" };
solver.AddComponent(exhStub1);
solver.AddComponent(exhStub2);
solver.AddComponent(exhStub3);
solver.AddComponent(exhStub4);
foreach (var p in new[] { runner1, runner2, runner3, runner4, exhStub1, exhStub2, exhStub3, exhStub4, intakePipeBeforeThrottle })
{
p.DampingMultiplier = 0.5;
p.EnergyRelaxationRate = 0.0;
}
// ---- Tailpipe (single exhaust pipe) ----
tailPipe = new Pipe1D(0.5, pipeArea, 20)
{
Name = "Tailpipe",
DampingMultiplier = 0.5,
EnergyRelaxationRate = 0.0
};
solver.AddComponent(tailPipe);
// ---- Plenum → runner orifices (volume port to pipe left end) ----
plenumToRunner1 = new OrificeLink(plenumOut1, runner1, isPipeLeftEnd: true, areaProvider: () => pipeArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
plenumToRunner2 = new OrificeLink(plenumOut2, runner2, isPipeLeftEnd: true, areaProvider: () => pipeArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
plenumToRunner3 = new OrificeLink(plenumOut3, runner3, isPipeLeftEnd: true, areaProvider: () => pipeArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
plenumToRunner4 = new OrificeLink(plenumOut4, runner4, isPipeLeftEnd: true, areaProvider: () => pipeArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOrificeLink(plenumToRunner1); solver.AddOrificeLink(plenumToRunner1);
solver.AddOrificeLink(plenumToRunner2); solver.AddOrificeLink(plenumToRunner2);
solver.AddOrificeLink(plenumToRunner3); solver.AddOrificeLink(plenumToRunner3);
solver.AddOrificeLink(plenumToRunner4); solver.AddOrificeLink(plenumToRunner4);
// ---- Intake valves ---- // ---- Intake valves (cylinder port to runner right end) ----
intakeValve1 = new OrificeLink(cyl1.IntakePort, runner1, isPipeLeftEnd: false, areaProvider: () => cyl1.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; intakeValve1 = new OrificeLink(cyl1.IntakePort, runner1, isPipeLeftEnd: false, areaProvider: () => cyl1.IntakeValveArea)
intakeValve2 = new OrificeLink(cyl2.IntakePort, runner2, isPipeLeftEnd: false, areaProvider: () => cyl2.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; { DischargeCoefficient = 1.0, UseInertance = false };
intakeValve3 = new OrificeLink(cyl3.IntakePort, runner3, isPipeLeftEnd: false, areaProvider: () => cyl3.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; intakeValve2 = new OrificeLink(cyl2.IntakePort, runner2, isPipeLeftEnd: false, areaProvider: () => cyl2.IntakeValveArea)
intakeValve4 = new OrificeLink(cyl4.IntakePort, runner4, isPipeLeftEnd: false, areaProvider: () => cyl4.IntakeValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; { DischargeCoefficient = 1.0, UseInertance = false };
intakeValve3 = new OrificeLink(cyl3.IntakePort, runner3, isPipeLeftEnd: false, areaProvider: () => cyl3.IntakeValveArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
intakeValve4 = new OrificeLink(cyl4.IntakePort, runner4, isPipeLeftEnd: false, areaProvider: () => cyl4.IntakeValveArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOrificeLink(intakeValve1); solver.AddOrificeLink(intakeValve1);
solver.AddOrificeLink(intakeValve2); solver.AddOrificeLink(intakeValve2);
solver.AddOrificeLink(intakeValve3); solver.AddOrificeLink(intakeValve3);
solver.AddOrificeLink(intakeValve4); solver.AddOrificeLink(intakeValve4);
// ---- Exhaust valves ---- // ---- Exhaust valves (cylinder port to stub left end) ----
exhaustValve1 = new OrificeLink(cyl1.ExhaustPort, exh1, isPipeLeftEnd: true, areaProvider: () => cyl1.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; exhaustValve1 = new OrificeLink(cyl1.ExhaustPort, exhStub1, isPipeLeftEnd: true, areaProvider: () => cyl1.ExhaustValveArea)
exhaustValve2 = new OrificeLink(cyl2.ExhaustPort, exh2, isPipeLeftEnd: true, areaProvider: () => cyl2.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; { DischargeCoefficient = 1.0, UseInertance = false };
exhaustValve3 = new OrificeLink(cyl3.ExhaustPort, exh3, isPipeLeftEnd: true, areaProvider: () => cyl3.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; exhaustValve2 = new OrificeLink(cyl2.ExhaustPort, exhStub2, isPipeLeftEnd: true, areaProvider: () => cyl2.ExhaustValveArea)
exhaustValve4 = new OrificeLink(cyl4.ExhaustPort, exh4, isPipeLeftEnd: true, areaProvider: () => cyl4.ExhaustValveArea) { DischargeCoefficient = 1.0, UseInertance = false }; { DischargeCoefficient = 1.0, UseInertance = false };
exhaustValve3 = new OrificeLink(cyl3.ExhaustPort, exhStub3, isPipeLeftEnd: true, areaProvider: () => cyl3.ExhaustValveArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
exhaustValve4 = new OrificeLink(cyl4.ExhaustPort, exhStub4, isPipeLeftEnd: true, areaProvider: () => cyl4.ExhaustValveArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOrificeLink(exhaustValve1); solver.AddOrificeLink(exhaustValve1);
solver.AddOrificeLink(exhaustValve2); solver.AddOrificeLink(exhaustValve2);
solver.AddOrificeLink(exhaustValve3); solver.AddOrificeLink(exhaustValve3);
solver.AddOrificeLink(exhaustValve4); solver.AddOrificeLink(exhaustValve4);
// ---- Exhaust open ends ---- // ---- Stub → collector orifices (collector port to stub right end) ----
exhaustOpenEnd1 = new OpenEndLink(exh1, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 }; stubToCollector1 = new OrificeLink(colIn1, exhStub1, isPipeLeftEnd: false, areaProvider: () => pipeArea)
exhaustOpenEnd2 = new OpenEndLink(exh2, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 }; { DischargeCoefficient = 1.0, UseInertance = false };
exhaustOpenEnd3 = new OpenEndLink(exh3, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 }; stubToCollector2 = new OrificeLink(colIn2, exhStub2, isPipeLeftEnd: false, areaProvider: () => pipeArea)
exhaustOpenEnd4 = new OpenEndLink(exh4, isLeftEnd: false) { AmbientPressure = 101325.0, Gamma = 1.4 }; { DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOpenEndLink(exhaustOpenEnd1); stubToCollector3 = new OrificeLink(colIn3, exhStub3, isPipeLeftEnd: false, areaProvider: () => pipeArea)
solver.AddOpenEndLink(exhaustOpenEnd2); { DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOpenEndLink(exhaustOpenEnd3); stubToCollector4 = new OrificeLink(colIn4, exhStub4, isPipeLeftEnd: false, areaProvider: () => pipeArea)
solver.AddOpenEndLink(exhaustOpenEnd4); { DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOrificeLink(stubToCollector1);
solver.AddOrificeLink(stubToCollector2);
solver.AddOrificeLink(stubToCollector3);
solver.AddOrificeLink(stubToCollector4);
// ---- Collector → tailpipe (collector port to tailpipe left end) ----
collectorToTailpipe = new OrificeLink(colOut, tailPipe, isPipeLeftEnd: true, areaProvider: () => pipeArea)
{ DischargeCoefficient = 1.0, UseInertance = false };
solver.AddOrificeLink(collectorToTailpipe);
// ---- Exhaust open end (tailpipe exit) ----
exhaustOpenEnd = new OpenEndLink(tailPipe, isLeftEnd: false)
{
AmbientPressure = 101325.0,
Gamma = 1.4
};
solver.AddOpenEndLink(exhaustOpenEnd);
// ---- Intake open end ---- // ---- Intake open end ----
intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true) intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true)
@@ -209,9 +274,9 @@ namespace FluidSim.Tests
// ---- Throttle ---- // ---- Throttle ----
throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false, throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.001, 1.0)) areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.0005, 1.0))
{ {
DischargeCoefficient = 0.2, DischargeCoefficient = 0.9,
UseInertance = false UseInertance = false
}; };
solver.AddOrificeLink(throttleOrifice); solver.AddOrificeLink(throttleOrifice);
@@ -242,13 +307,10 @@ namespace FluidSim.Tests
$"plenum P = {intakePlenum.Pressure / 1e5:F2} bar"); $"plenum P = {intakePlenum.Pressure / 1e5:F2} bar");
} }
// Mix all exhaust sounds // Sound: only one exhaust source now
float exhaustMix = exhaustSoundProcessor.Process(exhaustOpenEnd1) float exhaustSound = exhaustSoundProcessor.Process(exhaustOpenEnd);
+ exhaustSoundProcessor.Process(exhaustOpenEnd2) float intakeSound = intakeSoundProcessor.Process(intakeOpenEnd);
+ exhaustSoundProcessor.Process(exhaustOpenEnd3) return reverb.Process(exhaustSound * 0.25f + intakeSound);
+ exhaustSoundProcessor.Process(exhaustOpenEnd4);
float intakeDry = intakeSoundProcessor.Process(intakeOpenEnd);
return reverb.Process(exhaustMix * 0.25f + intakeDry);
} }
public override void Draw(RenderWindow target) public override void Draw(RenderWindow target)
@@ -256,48 +318,155 @@ namespace FluidSim.Tests
float winW = target.GetView().Size.X; float winW = target.GetView().Size.X;
float winH = target.GetView().Size.Y; float winH = target.GetView().Size.Y;
float startX = 60f; // --- Layout constants ---
float spacing = 80f; float leftMargin = 40f;
float intakeY = winH / 2f - 80f; float plenumW = 50f, plenumH = 120f;
float exhaustY = winH / 2f + 80f; float cylinderWidth = 60f, cylinderMaxHeight = 180f;
float cylinderSpacing = 90f;
float cylinderTopY = winH * 0.25f;
// Plenum // Plenum position
float plenW = 50f, plenH = 120f; float plenumCenterX = leftMargin + plenumW / 2f;
float plenX = startX; float plenumTopY = cylinderTopY - 20f;
float plenTopY = intakeY - plenH / 2f; DrawVolume(target, intakePlenum, plenumCenterX, plenumTopY, plenumW, plenumH);
DrawVolume(target, intakePlenum, plenX, plenTopY, plenW, plenH);
// Helper arrays just for drawing (no closures) // Throttle symbol (yellow rectangle) left of plenum
var cyls = new[] { cyl1, cyl2, cyl3, cyl4 }; float throttleWidth = 8f, throttleHeight = 30f;
var runners = new[] { runner1, runner2, runner3, runner4 }; float throttleCenterX = leftMargin - 10f;
var exhausts = new[] { exh1, exh2, exh3, exh4 }; var throttleRect = new RectangleShape(new Vector2f(throttleWidth, throttleHeight))
{
FillColor = Color.Yellow,
Position = new Vector2f(throttleCenterX - throttleWidth / 2f, plenumTopY + plenumH / 2f - throttleHeight / 2f)
};
target.Draw(throttleRect);
// Intake pipe before throttle (left of throttle)
float intakePipeEndX = throttleCenterX - throttleWidth / 2f;
float intakePipeStartX = intakePipeEndX - 100f;
float intakePipeY = plenumTopY + plenumH / 2f;
DrawPipe(target, intakePipeBeforeThrottle, intakePipeY, intakePipeStartX, intakePipeEndX);
// Intake open end marker
var intakeMark = new CircleShape(4f) { FillColor = Color.Magenta };
intakeMark.Position = new Vector2f(intakePipeStartX - 4f, intakePipeY - 4f);
target.Draw(intakeMark);
// Cylinders and runners
float runnerStartX = leftMargin + plenumW;
Cylinder[] cyls = { cyl1, cyl2, cyl3, cyl4 };
Pipe1D[] runners = { runner1, runner2, runner3, runner4 };
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
float cylX = plenX + plenW + 30f + i * spacing; float cylCenterX = runnerStartX + 40f + i * cylinderSpacing;
float runnerStartX = plenX + plenW + 5f; float runnerEndX = cylCenterX;
float runnerEndX = cylX - 20f; DrawPipe(target, runners[i], plenumTopY + plenumH / 2f, runnerStartX, runnerEndX);
DrawPipe(target, runners[i], intakeY, runnerStartX, runnerEndX); DrawCylinder(target, cyls[i], cylCenterX, cylinderTopY, cylinderWidth, cylinderMaxHeight);
float cylTopY = intakeY - 120f;
DrawCylinder(target, cyls[i], cylX, cylTopY, 70f, 200f);
float exhStartX = cylX + 35f;
float exhEndX = exhStartX + 100f;
DrawPipe(target, exhausts[i], exhaustY, exhStartX, exhEndX);
var mark = new CircleShape(4f) { FillColor = Color.Magenta };
mark.Position = new Vector2f(exhEndX - 4f, exhaustY - 4f);
target.Draw(mark);
} }
// Throttle symbol // Exhaust collector below cylinders
var throttleRect = new RectangleShape(new Vector2f(6f, 30f)) float collectorLeftX = runnerStartX + 40f - cylinderWidth / 2f;
float collectorWidth = 3 * cylinderSpacing + cylinderWidth;
float collectorTopY = cylinderTopY + cylinderMaxHeight + 40f;
float collectorHeight = 50f;
float collectorCenterX = collectorLeftX + collectorWidth / 2f;
DrawVolume(target, exhaustCollector, collectorCenterX, collectorTopY, collectorWidth, collectorHeight);
// Tailpipe from right edge of collector
float tailStartX = collectorLeftX + collectorWidth;
float tailEndX = tailStartX + 150f;
float tailCenterY = collectorTopY + collectorHeight / 2f;
DrawPipe(target, tailPipe, tailCenterY, tailStartX, tailEndX);
// Exhaust open end marker
var exhaustMark = new CircleShape(4f) { FillColor = Color.Magenta };
exhaustMark.Position = new Vector2f(tailEndX - 4f, tailCenterY - 4f);
target.Draw(exhaustMark);
// Exhaust stubs (vertical connections from cylinder bottom to collector)
Pipe1D[] stubs = { exhStub1, exhStub2, exhStub3, exhStub4 };
for (int i = 0; i < 4; i++)
{ {
FillColor = Color.Yellow, float cylCenterX = runnerStartX + 40f + i * cylinderSpacing;
Position = new Vector2f(plenX - 16f, intakeY - 15f) float vertStartY = cylinderTopY + cylinderMaxHeight;
}; float vertEndY = collectorTopY;
target.Draw(throttleRect); // Draw stub as a vertical pipe
DrawPipeVertical(target, stubs[i], cylCenterX, vertStartY, vertEndY);
}
}
// Helper to draw a pipe vertically (reuse temperature coloring)
private void DrawPipeVertical(RenderWindow target, Pipe1D pipe, float centerX, float topY, float bottomY)
{
int n = pipe.CellCount;
if (n < 2) return;
float pipeLengthPx = bottomY - topY;
float dy = pipeLengthPx / (n - 1);
float baseRadius = 25f;
float rangeFactor = 2f;
float scaleFactor = 2f;
static float SmoothStep(float edge0, float edge1, float x)
{
float t = Math.Clamp((x - edge0) / (edge1 - edge0), 0f, 1f);
return t * t * (3f - 2f * t);
}
var centersY = new float[n];
var radii = new float[n];
var temperatures = new double[n];
double R_gas = 287.0;
for (int i = 0; i < n; i++)
{
double p = pipe.GetCellPressure(i);
double rho = pipe.GetCellDensity(i);
double T = p / Math.Max(rho * R_gas, 1e-12);
temperatures[i] = T;
float deviation = (float)Math.Tanh((p - AmbientPressure) / AmbientPressure / rangeFactor);
radii[i] = baseRadius * (1f + deviation * scaleFactor);
if (radii[i] < 2f) radii[i] = 2f;
centersY[i] = topY + i * dy;
}
int segmentsPerCell = 8;
int totalPoints = n + (n - 1) * segmentsPerCell;
Vertex[] stripVertices = new Vertex[totalPoints * 2];
int idx = 0;
for (int i = 0; i < n; i++)
{
float y = centersY[i];
float r = radii[i];
Color col = TemperatureColor(temperatures[i]);
stripVertices[idx++] = new Vertex(new Vector2f(centerX - r, y), col);
stripVertices[idx++] = new Vertex(new Vector2f(centerX + r, y), col);
if (i < n - 1)
{
for (int s = 1; s <= segmentsPerCell; s++)
{
float t = s / (float)segmentsPerCell;
float st = SmoothStep(0f, 1f, t);
float yi = centersY[i] + (centersY[i + 1] - centersY[i]) * t;
float ri = radii[i] + (radii[i + 1] - radii[i]) * st;
double Ti = temperatures[i] + (temperatures[i + 1] - temperatures[i]) * st;
Color coli = TemperatureColor(Ti);
stripVertices[idx++] = new Vertex(new Vector2f(centerX - ri, yi), coli);
stripVertices[idx++] = new Vertex(new Vector2f(centerX + ri, yi), coli);
}
}
}
var pipeMesh = new VertexArray(PrimitiveType.TriangleStrip, (uint)stripVertices.Length);
for (int i = 0; i < stripVertices.Length; i++)
pipeMesh[(uint)i] = stripVertices[i];
target.Draw(pipeMesh);
} }
} }
} }