Helmholtz testing (no decay bug)
This commit is contained in:
152
Scenarios/HelmholtzScenario.cs
Normal file
152
Scenarios/HelmholtzScenario.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Interfaces;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using System;
|
||||
|
||||
namespace FluidSim.Tests
|
||||
{
|
||||
public class HelmholtzScenario : Scenario
|
||||
{
|
||||
private Volume0D cavity;
|
||||
private Port cavityPort;
|
||||
|
||||
private PipeSystem pipeSystem;
|
||||
private int[] pipeStart = { 0 };
|
||||
private int[] pipeEnd;
|
||||
|
||||
private BoundarySystem boundaries;
|
||||
private int cavityOrificeIdx = 0;
|
||||
private int openEndIdx = 0;
|
||||
|
||||
private Solver solver;
|
||||
private double dt;
|
||||
private int stepCount;
|
||||
|
||||
private SoundProcessor soundProcessor;
|
||||
|
||||
public override void Initialize(int sampleRate)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
|
||||
// --- Realistic Helmholtz resonator dimensions ---
|
||||
float cavityVolume = 1e-3f; // 1 liter
|
||||
float neckLength = 0.05f; // 5 cm
|
||||
float neckDiameter = 0.02f; // 2 cm diameter
|
||||
float neckArea = MathF.PI * 0.25f * neckDiameter * neckDiameter;
|
||||
int neckCells = 20;
|
||||
|
||||
// --- Volume (cavity) ---
|
||||
float initialPressure = 1.2f * 101325f; // slight overpressure
|
||||
float initialTemperature = 300f;
|
||||
cavity = new Volume0D(cavityVolume, initialPressure, initialTemperature);
|
||||
cavityPort = cavity.CreatePort();
|
||||
|
||||
// --- Pipe (neck) ---
|
||||
float[] areas = new float[neckCells];
|
||||
float[] dxs = new float[neckCells];
|
||||
float dx = neckLength / neckCells;
|
||||
for (int i = 0; i < neckCells; i++)
|
||||
{
|
||||
areas[i] = neckArea;
|
||||
dxs[i] = dx;
|
||||
}
|
||||
pipeEnd = new[] { neckCells };
|
||||
|
||||
float rho0 = 101325f / (287f * 300f);
|
||||
pipeSystem = new PipeSystem(neckCells, pipeStart, pipeEnd, areas, dxs, rho0, 0f, 101325f);
|
||||
|
||||
|
||||
// Energy loss
|
||||
cavity.EnergyRelaxationRate = 80f;
|
||||
pipeSystem.EnergyRelaxationRate = 0f;
|
||||
pipeSystem.DampingMultiplier = 2000f;
|
||||
|
||||
// --- Boundary system ---
|
||||
boundaries = new BoundarySystem(pipeSystem, maxOrifices: 1, maxOpenEnds: 1);
|
||||
|
||||
// Use steady orifice – the pipe already provides the inertia
|
||||
boundaries.AddOrifice(cavityPort, pipeIndex: 0, isLeftEnd: true, areaIndex: cavityOrificeIdx, dischargeCoeff: 1f, lossCoefficient: 0.1f);
|
||||
// LOSS COEFFICIENT BREAKS THE SYSTEM AT ~0.55, AT VALUES LOWER THAN THAT, IT SEEMS TO ONLY AFFECT VOLUME, NOT COMPOUND
|
||||
|
||||
// Open end at right side of pipe
|
||||
boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: false, 101325f, neckArea);
|
||||
|
||||
float[] orificeAreas = new float[1] { neckArea };
|
||||
boundaries.SetOrificeAreas(orificeAreas);
|
||||
|
||||
// --- Solver ---
|
||||
// Slightly higher sub‑step count to ensure stability of the resonant oscillation
|
||||
solver = new Solver { SubStepCount = 6, EnableProfiling = true };
|
||||
solver.SetTimeStep(dt);
|
||||
solver.SetPipeSystem(pipeSystem);
|
||||
solver.SetBoundarySystem(boundaries);
|
||||
solver.AddComponent(cavity);
|
||||
|
||||
// --- Sound ---
|
||||
soundProcessor = new SoundProcessor(sampleRate, 1f) { Gain = 2f };
|
||||
|
||||
Console.WriteLine("Helmholtz resonator ready.");
|
||||
stepCount = 0;
|
||||
}
|
||||
|
||||
public override float Process()
|
||||
{
|
||||
stepCount++;
|
||||
if (stepCount <= 8192) return 0f; // let buffer pre‑fill
|
||||
|
||||
solver.Step();
|
||||
|
||||
float flow = boundaries.GetOpenEndMassFlow(openEndIdx);
|
||||
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;
|
||||
}
|
||||
|
||||
public override void Draw(RenderWindow target)
|
||||
{
|
||||
float winW = target.GetView().Size.X;
|
||||
float winH = target.GetView().Size.Y;
|
||||
|
||||
float cavityCenterX = 100f;
|
||||
float cavityWidth = 80f, cavityHeight = 100f;
|
||||
float cavityTopY = winH / 2f - cavityHeight / 2f;
|
||||
DrawVolume(target, cavity, cavityCenterX, cavityTopY - 40f, cavityWidth, cavityHeight);
|
||||
|
||||
float pipeStartX = cavityCenterX + cavityWidth / 2f + 10f;
|
||||
float pipeEndX = winW - 50f;
|
||||
float pipeCenterY = winH / 2f;
|
||||
DrawPipe(target, pipeSystem, 0, pipeCenterY, pipeStartX, pipeEndX);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,472 +0,0 @@
|
||||
using System;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Utils;
|
||||
|
||||
namespace FluidSim.Tests
|
||||
{
|
||||
public class Inline4Scenario : Scenario
|
||||
{
|
||||
// Crankshaft
|
||||
private Crankshaft crankshaft;
|
||||
|
||||
// Cylinders
|
||||
private Cylinder cyl1, cyl2, cyl3, cyl4;
|
||||
|
||||
// Intake
|
||||
private Pipe1D intakePipeBeforeThrottle;
|
||||
private Volume0D intakePlenum;
|
||||
|
||||
// Runners (shorter, fewer cells)
|
||||
private Pipe1D runner1, runner2, runner3, runner4;
|
||||
|
||||
// Exhaust collector + tailpipe
|
||||
private Volume0D exhaustCollector;
|
||||
private Pipe1D tailPipe;
|
||||
|
||||
// Exhaust stubs (short pipes between cylinders and collector)
|
||||
private Pipe1D exhStub1, exhStub2, exhStub3, exhStub4;
|
||||
|
||||
// Links – intake
|
||||
private OpenEndLink intakeOpenEnd;
|
||||
private OrificeLink throttleOrifice;
|
||||
|
||||
// Plenum‑to‑runner orifices
|
||||
private OrificeLink plenumToRunner1, plenumToRunner2, plenumToRunner3, plenumToRunner4;
|
||||
|
||||
// Intake valves
|
||||
private OrificeLink intakeValve1, intakeValve2, intakeValve3, intakeValve4;
|
||||
|
||||
// Exhaust valves (cylinder → stub)
|
||||
private OrificeLink exhaustValve1, exhaustValve2, exhaustValve3, exhaustValve4;
|
||||
|
||||
// Stub‑to‑collector orifices
|
||||
private OrificeLink stubToCollector1, stubToCollector2, stubToCollector3, stubToCollector4;
|
||||
|
||||
// Collector‑to‑tailpipe orifice
|
||||
private OrificeLink collectorToTailpipe;
|
||||
|
||||
// Exhaust open end (tailpipe exit)
|
||||
private OpenEndLink exhaustOpenEnd;
|
||||
|
||||
private Solver solver;
|
||||
private SoundProcessor exhaustSoundProcessor;
|
||||
private SoundProcessor intakeSoundProcessor;
|
||||
private OutdoorExhaustReverb reverb;
|
||||
private double dt;
|
||||
private int stepCount;
|
||||
|
||||
public double MaxThrottleArea { get; set; } = 10 * Units.cm2;
|
||||
|
||||
public override void Initialize(int sampleRate)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
|
||||
solver = new Solver();
|
||||
solver.SetTimeStep(dt);
|
||||
solver.CflTarget = 1;
|
||||
|
||||
// ---- Shared crankshaft ----
|
||||
crankshaft = new Crankshaft(800);
|
||||
crankshaft.Inertia = 1;
|
||||
crankshaft.FrictionConstant = 3;
|
||||
crankshaft.FrictionViscous = 0.2;
|
||||
|
||||
// ---- Cylinder geometry ----
|
||||
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;
|
||||
|
||||
// Firing order 1-3-4-2 with 180° intervals (0°, 180°, 360°, 540°)
|
||||
double phaseCyl1 = 0.0;
|
||||
double phaseCyl3 = Math.PI; // 180°
|
||||
double phaseCyl4 = 2.0 * Math.PI; // 360°
|
||||
double phaseCyl2 = 3.0 * Math.PI; // 540°
|
||||
|
||||
cyl1 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phaseCyl1,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.0
|
||||
};
|
||||
cyl2 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phaseCyl2,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.0
|
||||
};
|
||||
cyl3 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phaseCyl3,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.0
|
||||
};
|
||||
cyl4 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm,
|
||||
IntakeValveLift = 5 * Units.mm,
|
||||
ExhaustValveDiameter = 28 * Units.mm,
|
||||
ExhaustValveLift = 5 * Units.mm,
|
||||
PhaseOffset = phaseCyl4,
|
||||
EnergyVariationFraction = 0.03,
|
||||
MisfireProbability = 0.0
|
||||
};
|
||||
solver.AddComponent(cyl1);
|
||||
solver.AddComponent(cyl2);
|
||||
solver.AddComponent(cyl3);
|
||||
solver.AddComponent(cyl4);
|
||||
|
||||
double pipeDiameter = 4 * Units.cm;
|
||||
double pipeArea = Units.AreaFromDiameter(pipeDiameter);
|
||||
|
||||
// Sound processors (only one exhaust source now)
|
||||
exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.2f };
|
||||
intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.2f };
|
||||
reverb = new OutdoorExhaustReverb(sampleRate);
|
||||
|
||||
// ---- Intake pipe before throttle (shorter, fewer cells) ----
|
||||
intakePipeBeforeThrottle = new Pipe1D(0.1, pipeArea, 10);
|
||||
intakePipeBeforeThrottle.Name = "Intake pipe";
|
||||
solver.AddComponent(intakePipeBeforeThrottle);
|
||||
|
||||
// ---- Plenum ----
|
||||
intakePlenum = new Volume0D(100 * Units.mL, 101325.0, 300.0);
|
||||
var plenumInlet = intakePlenum.CreatePort(); // port 0
|
||||
var plenumOut1 = intakePlenum.CreatePort(); // port 1
|
||||
var plenumOut2 = intakePlenum.CreatePort(); // port 2
|
||||
var plenumOut3 = intakePlenum.CreatePort(); // port 3
|
||||
var plenumOut4 = intakePlenum.CreatePort(); // port 4
|
||||
solver.AddComponent(intakePlenum);
|
||||
|
||||
// ---- Intake runners (shorter, fewer cells) ----
|
||||
runner1 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 1" };
|
||||
runner2 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 2" };
|
||||
runner3 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 3" };
|
||||
runner4 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 4" };
|
||||
solver.AddComponent(runner1);
|
||||
solver.AddComponent(runner2);
|
||||
solver.AddComponent(runner3);
|
||||
solver.AddComponent(runner4);
|
||||
|
||||
// ---- Exhaust collector volume ----
|
||||
exhaustCollector = new Volume0D(200 * Units.mL, 101325.0, 800.0);
|
||||
var colIn1 = exhaustCollector.CreatePort(); // cylinder 1 stub
|
||||
var colIn2 = exhaustCollector.CreatePort(); // cylinder 2 stub
|
||||
var colIn3 = exhaustCollector.CreatePort(); // cylinder 3 stub
|
||||
var colIn4 = exhaustCollector.CreatePort(); // cylinder 4 stub
|
||||
var colOut = exhaustCollector.CreatePort(); // to tailpipe
|
||||
solver.AddComponent(exhaustCollector);
|
||||
|
||||
// ---- Exhaust stub pipes (short connection cylinder → collector) ----
|
||||
exhStub1 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 1" };
|
||||
exhStub2 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 2" };
|
||||
exhStub3 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 3" };
|
||||
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(plenumToRunner2);
|
||||
solver.AddOrificeLink(plenumToRunner3);
|
||||
solver.AddOrificeLink(plenumToRunner4);
|
||||
|
||||
// ---- Intake valves (cylinder port to runner right end) ----
|
||||
intakeValve1 = new OrificeLink(cyl1.IntakePort, runner1, isPipeLeftEnd: false, areaProvider: () => cyl1.IntakeValveArea)
|
||||
{ DischargeCoefficient = 1.0, UseInertance = false };
|
||||
intakeValve2 = new OrificeLink(cyl2.IntakePort, runner2, isPipeLeftEnd: false, areaProvider: () => cyl2.IntakeValveArea)
|
||||
{ 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(intakeValve2);
|
||||
solver.AddOrificeLink(intakeValve3);
|
||||
solver.AddOrificeLink(intakeValve4);
|
||||
|
||||
// ---- Exhaust valves (cylinder port to stub left end) ----
|
||||
exhaustValve1 = new OrificeLink(cyl1.ExhaustPort, exhStub1, isPipeLeftEnd: true, areaProvider: () => cyl1.ExhaustValveArea)
|
||||
{ DischargeCoefficient = 1.0, UseInertance = false };
|
||||
exhaustValve2 = new OrificeLink(cyl2.ExhaustPort, exhStub2, isPipeLeftEnd: true, areaProvider: () => cyl2.ExhaustValveArea)
|
||||
{ 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(exhaustValve2);
|
||||
solver.AddOrificeLink(exhaustValve3);
|
||||
solver.AddOrificeLink(exhaustValve4);
|
||||
|
||||
// ---- Stub → collector orifices (collector port to stub right end) ----
|
||||
stubToCollector1 = new OrificeLink(colIn1, exhStub1, isPipeLeftEnd: false, areaProvider: () => pipeArea)
|
||||
{ DischargeCoefficient = 1.0, UseInertance = false };
|
||||
stubToCollector2 = new OrificeLink(colIn2, exhStub2, isPipeLeftEnd: false, areaProvider: () => pipeArea)
|
||||
{ DischargeCoefficient = 1.0, UseInertance = false };
|
||||
stubToCollector3 = new OrificeLink(colIn3, exhStub3, isPipeLeftEnd: false, areaProvider: () => pipeArea)
|
||||
{ DischargeCoefficient = 1.0, UseInertance = false };
|
||||
stubToCollector4 = new OrificeLink(colIn4, exhStub4, isPipeLeftEnd: false, areaProvider: () => pipeArea)
|
||||
{ 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 ----
|
||||
intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true)
|
||||
{
|
||||
AmbientPressure = 101325.0,
|
||||
Gamma = 1.4
|
||||
};
|
||||
solver.AddOpenEndLink(intakeOpenEnd);
|
||||
|
||||
// ---- Throttle ----
|
||||
throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
|
||||
areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.0005, 1.0))
|
||||
{
|
||||
DischargeCoefficient = 0.9,
|
||||
UseInertance = false
|
||||
};
|
||||
solver.AddOrificeLink(throttleOrifice);
|
||||
|
||||
stepCount = 0;
|
||||
Console.WriteLine("Inline-4 engine test");
|
||||
Console.WriteLine($"Bore {bore * 1000:F0}mm, Stroke {stroke * 1000:F0}mm, CR {compRatio}");
|
||||
Console.WriteLine("Firing order 1-3-4-2, 180° intervals");
|
||||
}
|
||||
|
||||
public override float Process()
|
||||
{
|
||||
crankshaft.Step(dt);
|
||||
|
||||
cyl1.PreStep(dt);
|
||||
cyl2.PreStep(dt);
|
||||
cyl3.PreStep(dt);
|
||||
cyl4.PreStep(dt);
|
||||
|
||||
solver.Step();
|
||||
stepCount++;
|
||||
|
||||
if (stepCount % 10000 == 0)
|
||||
{
|
||||
double rpm = crankshaft.AngularVelocity * 60.0 / (2.0 * Math.PI);
|
||||
Console.WriteLine($"Step {stepCount}, RPM = {rpm:F0}, " +
|
||||
$"cyl1 P = {cyl1.Pressure / 1e5:F2} bar, " +
|
||||
$"plenum P = {intakePlenum.Pressure / 1e5:F2} bar");
|
||||
}
|
||||
|
||||
// Sound: only one exhaust source now
|
||||
float exhaustSound = exhaustSoundProcessor.Process(exhaustOpenEnd);
|
||||
float intakeSound = intakeSoundProcessor.Process(intakeOpenEnd);
|
||||
return reverb.Process(exhaustSound * 0.25f + intakeSound);
|
||||
}
|
||||
|
||||
public override void Draw(RenderWindow target)
|
||||
{
|
||||
float winW = target.GetView().Size.X;
|
||||
float winH = target.GetView().Size.Y;
|
||||
|
||||
// --- Layout constants ---
|
||||
float leftMargin = 40f;
|
||||
float plenumW = 50f, plenumH = 120f;
|
||||
float cylinderWidth = 60f, cylinderMaxHeight = 180f;
|
||||
float cylinderSpacing = 90f;
|
||||
float cylinderTopY = winH * 0.25f;
|
||||
|
||||
// Plenum position
|
||||
float plenumCenterX = leftMargin + plenumW / 2f;
|
||||
float plenumTopY = cylinderTopY - 20f;
|
||||
DrawVolume(target, intakePlenum, plenumCenterX, plenumTopY, plenumW, plenumH);
|
||||
|
||||
// Throttle symbol (yellow rectangle) left of plenum
|
||||
float throttleWidth = 8f, throttleHeight = 30f;
|
||||
float throttleCenterX = leftMargin - 10f;
|
||||
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++)
|
||||
{
|
||||
float cylCenterX = runnerStartX + 40f + i * cylinderSpacing;
|
||||
float runnerEndX = cylCenterX;
|
||||
DrawPipe(target, runners[i], plenumTopY + plenumH / 2f, runnerStartX, runnerEndX);
|
||||
DrawCylinder(target, cyls[i], cylCenterX, cylinderTopY, cylinderWidth, cylinderMaxHeight);
|
||||
}
|
||||
|
||||
// Exhaust collector below cylinders
|
||||
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++)
|
||||
{
|
||||
float cylCenterX = runnerStartX + 40f + i * cylinderSpacing;
|
||||
float vertStartY = cylinderTopY + cylinderMaxHeight;
|
||||
float vertEndY = collectorTopY;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,60 @@
|
||||
using System;
|
||||
using SFML.Graphics;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Components;
|
||||
|
||||
namespace FluidSim.Tests
|
||||
{
|
||||
public abstract class Scenario
|
||||
{
|
||||
protected const float AmbientPressure = 101325f;
|
||||
protected const float AmbientTemperature = 300f;
|
||||
public float Throttle { get; set; }
|
||||
|
||||
public abstract void Initialize(int sampleRate);
|
||||
public abstract float Process();
|
||||
public abstract void Draw(RenderWindow target);
|
||||
|
||||
protected const double AmbientPressure = 101325.0;
|
||||
protected const double AmbientTemperature = 300.0;
|
||||
public double Throttle { get; set; } = 0.0;
|
||||
|
||||
// ---------- Color from pressure (volumes) ----------
|
||||
protected Color PressureColor(double pressurePa)
|
||||
protected Color PressureColor(float pressurePa)
|
||||
{
|
||||
double bar = pressurePa / 1e5; // convert to bar for easier mapping
|
||||
float bar = pressurePa / 1e5f;
|
||||
byte r, g, b;
|
||||
|
||||
if (bar < 1.0) // vacuum → blue to green
|
||||
if (bar < 1f)
|
||||
{
|
||||
double factor = Math.Clamp(bar, 0.0, 1.0);
|
||||
r = 0;
|
||||
g = (byte)(255 * factor);
|
||||
b = (byte)(255 * (1.0 - factor));
|
||||
}
|
||||
else // above ambient → green to red
|
||||
{
|
||||
double factor = Math.Min((bar - 1.0) / 9.0, 1.0); // 1→10 bar maps to 0→1
|
||||
r = (byte)(255 * factor);
|
||||
g = (byte)(255 * (1.0 - factor));
|
||||
b = 0;
|
||||
}
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
|
||||
// ---------- Color from temperature (pipes) ----------
|
||||
protected Color TemperatureColor(double temperature)
|
||||
{
|
||||
double t = Math.Clamp(temperature, 0.0, 2000.0);
|
||||
byte r, g, b;
|
||||
if (t < AmbientTemperature)
|
||||
{
|
||||
double factor = t / AmbientTemperature;
|
||||
r = 0;
|
||||
g = (byte)(255 * factor);
|
||||
b = (byte)(255 * (1.0 - factor));
|
||||
float f = Math.Clamp(bar, 0f, 1f);
|
||||
r = 0; g = (byte)(255 * f); b = (byte)(255 * (1 - f));
|
||||
}
|
||||
else
|
||||
{
|
||||
double factor = (t - AmbientTemperature) / (2000.0 - AmbientTemperature);
|
||||
r = (byte)(255 * factor);
|
||||
g = (byte)(255 * (1.0 - factor));
|
||||
b = 0;
|
||||
float f = Math.Min((bar - 1f) / 9f, 1f);
|
||||
r = (byte)(255 * f); g = (byte)(255 * (1 - f)); b = 0;
|
||||
}
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
|
||||
protected Color TemperatureColor(float t)
|
||||
{
|
||||
t = Math.Clamp(t, 0f, 2000f);
|
||||
byte r, g, b;
|
||||
if (t < AmbientTemperature)
|
||||
{
|
||||
float f = t / AmbientTemperature;
|
||||
r = 0; g = (byte)(255 * f); b = (byte)(255 * (1 - f));
|
||||
}
|
||||
else
|
||||
{
|
||||
float f = (t - AmbientTemperature) / (2000f - AmbientTemperature);
|
||||
r = (byte)(255 * f); g = (byte)(255 * (1 - f)); b = 0;
|
||||
}
|
||||
return new Color(r, g, b);
|
||||
}
|
||||
|
||||
// ---------- Draw a generic volume (e.g. plenum) ----------
|
||||
protected void DrawVolume(RenderWindow target, Volume0D volume,
|
||||
float centerX, float topY, float width, float height)
|
||||
{
|
||||
var rect = new RectangleShape(new Vector2f(width, height))
|
||||
{
|
||||
FillColor = PressureColor(volume.Pressure), // ← pressure‑based
|
||||
FillColor = PressureColor(volume.Pressure),
|
||||
Position = new Vector2f(centerX - width / 2f, topY)
|
||||
};
|
||||
target.Draw(rect);
|
||||
@@ -75,122 +63,99 @@ namespace FluidSim.Tests
|
||||
FillColor = Color.Transparent,
|
||||
OutlineColor = Color.White,
|
||||
OutlineThickness = 1f,
|
||||
Position = new Vector2f(centerX - width / 2f, topY)
|
||||
Position = rect.Position
|
||||
};
|
||||
target.Draw(border);
|
||||
}
|
||||
|
||||
// ---------- Draw an engine cylinder ----------
|
||||
protected void DrawCylinder(RenderWindow target, Cylinder cylinder,
|
||||
float centerX, float topY, float width, float maxHeight)
|
||||
{
|
||||
double fraction = cylinder.PistonFraction;
|
||||
float currentHeight = (float)(maxHeight * fraction);
|
||||
|
||||
// Walls
|
||||
var wall = new RectangleShape(new Vector2f(width, maxHeight));
|
||||
wall.FillColor = new Color(60, 60, 60);
|
||||
wall.Position = new Vector2f(centerX - width / 2f, topY);
|
||||
float fraction = cylinder.PistonFraction;
|
||||
float currentHeight = maxHeight * fraction;
|
||||
var wall = new RectangleShape(new Vector2f(width, maxHeight))
|
||||
{
|
||||
FillColor = new Color(60, 60, 60),
|
||||
Position = new Vector2f(centerX - width / 2f, topY)
|
||||
};
|
||||
target.Draw(wall);
|
||||
|
||||
// Gas – colored by pressure now
|
||||
float gasTop = topY;
|
||||
var gasRect = new RectangleShape(new Vector2f(width, currentHeight));
|
||||
gasRect.FillColor = PressureColor(cylinder.Pressure); // ← pressure‑based
|
||||
gasRect.Position = new Vector2f(centerX - width / 2f, gasTop);
|
||||
target.Draw(gasRect);
|
||||
|
||||
// Piston line
|
||||
var pistonLine = new RectangleShape(new Vector2f(width, 4f));
|
||||
pistonLine.FillColor = Color.White;
|
||||
pistonLine.Position = new Vector2f(centerX - width / 2f, topY + currentHeight);
|
||||
target.Draw(pistonLine);
|
||||
|
||||
// Valve indicators
|
||||
var gas = new RectangleShape(new Vector2f(width, currentHeight))
|
||||
{
|
||||
FillColor = PressureColor(cylinder.Pressure),
|
||||
Position = new Vector2f(centerX - width / 2f, topY)
|
||||
};
|
||||
target.Draw(gas);
|
||||
var piston = new RectangleShape(new Vector2f(width, 4f))
|
||||
{
|
||||
FillColor = Color.White,
|
||||
Position = new Vector2f(centerX - width / 2f, topY + currentHeight)
|
||||
};
|
||||
target.Draw(piston);
|
||||
float valveW = 6f, valveH = 10f, valveY = topY + 4f;
|
||||
var intakeValve = new RectangleShape(new Vector2f(valveW, valveH));
|
||||
intakeValve.FillColor = cylinder.IntakeValveArea > 0 ? Color.Green : Color.Red;
|
||||
intakeValve.Position = new Vector2f(centerX - width / 2f - valveW - 2f, valveY);
|
||||
target.Draw(intakeValve);
|
||||
|
||||
var exhaustValve = new RectangleShape(new Vector2f(valveW, valveH));
|
||||
exhaustValve.FillColor = cylinder.ExhaustValveArea > 0 ? Color.Green : Color.Red;
|
||||
exhaustValve.Position = new Vector2f(centerX + width / 2f + 2f, valveY);
|
||||
target.Draw(exhaustValve);
|
||||
var iv = new RectangleShape(new Vector2f(valveW, valveH))
|
||||
{
|
||||
FillColor = cylinder.IntakeValveArea > 0f ? Color.Green : Color.Red,
|
||||
Position = new Vector2f(centerX - width / 2f - valveW - 2f, valveY)
|
||||
};
|
||||
target.Draw(iv);
|
||||
var ev = new RectangleShape(new Vector2f(valveW, valveH))
|
||||
{
|
||||
FillColor = cylinder.ExhaustValveArea > 0f ? Color.Green : Color.Red,
|
||||
Position = new Vector2f(centerX + width / 2f + 2f, valveY)
|
||||
};
|
||||
target.Draw(ev);
|
||||
}
|
||||
|
||||
// ---------- Draw a pipe (unchanged) ----------
|
||||
protected void DrawPipe(RenderWindow target, Pipe1D pipe, float pipeCenterY, float pipeStartX, float pipeEndX)
|
||||
protected void DrawPipe(RenderWindow target, PipeSystem pipeSystem, int pipeIndex,
|
||||
float pipeCenterY, float pipeStartX, float pipeEndX)
|
||||
{
|
||||
int n = pipe.CellCount;
|
||||
int start = pipeSystem.GetPipeStart(pipeIndex);
|
||||
int end = pipeSystem.GetPipeEnd(pipeIndex);
|
||||
int n = end - start;
|
||||
if (n < 2) return;
|
||||
|
||||
float pipeLengthPx = pipeEndX - pipeStartX;
|
||||
float dx = pipeLengthPx / (n - 1);
|
||||
|
||||
float pipeLen = pipeEndX - pipeStartX;
|
||||
float dx = pipeLen / (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 centers = new float[n];
|
||||
var radii = new float[n];
|
||||
var temperatures = new double[n];
|
||||
double R_gas = 287.0;
|
||||
|
||||
var temps = new float[n];
|
||||
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);
|
||||
int cell = start + i;
|
||||
float p = pipeSystem.GetCellPressure(cell);
|
||||
float rho = pipeSystem.GetCellDensity(cell);
|
||||
temps[i] = p / MathF.Max(rho * 287f, 1e-12f);
|
||||
float dev = MathF.Tanh((p - AmbientPressure) / AmbientPressure * 0.5f);
|
||||
radii[i] = baseRadius * (1f + dev * 2f);
|
||||
if (radii[i] < 2f) radii[i] = 2f;
|
||||
centers[i] = pipeStartX + i * dx;
|
||||
}
|
||||
|
||||
int segmentsPerCell = 8;
|
||||
int totalPoints = n + (n - 1) * segmentsPerCell;
|
||||
Vertex[] stripVertices = new Vertex[totalPoints * 2];
|
||||
int idx = 0;
|
||||
|
||||
int segments = 8;
|
||||
var va = new VertexArray(PrimitiveType.TriangleStrip);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
float x = centers[i];
|
||||
float r = radii[i];
|
||||
Color col = TemperatureColor(temperatures[i]); // pipes still use temperature
|
||||
|
||||
stripVertices[idx++] = new Vertex(new Vector2f(x, pipeCenterY - r), col);
|
||||
stripVertices[idx++] = new Vertex(new Vector2f(x, pipeCenterY + r), col);
|
||||
|
||||
float x = centers[i], r = radii[i];
|
||||
Color col = TemperatureColor(temps[i]);
|
||||
va.Append(new Vertex(new Vector2f(x, pipeCenterY - r), col));
|
||||
va.Append(new Vertex(new Vector2f(x, pipeCenterY + r), col));
|
||||
if (i < n - 1)
|
||||
{
|
||||
for (int s = 1; s <= segmentsPerCell; s++)
|
||||
for (int s = 1; s <= segments; s++)
|
||||
{
|
||||
float t = s / (float)segmentsPerCell;
|
||||
float st = SmoothStep(0f, 1f, t);
|
||||
float t = s / (float)segments;
|
||||
float xi = centers[i] + (centers[i + 1] - centers[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(xi, pipeCenterY - ri), coli);
|
||||
stripVertices[idx++] = new Vertex(new Vector2f(xi, pipeCenterY + ri), coli);
|
||||
float ri = radii[i] + (radii[i + 1] - radii[i]) * t;
|
||||
float Ti = temps[i] + (temps[i + 1] - temps[i]) * t;
|
||||
Color colS = TemperatureColor(Ti);
|
||||
va.Append(new Vertex(new Vector2f(xi, pipeCenterY - ri), colS));
|
||||
va.Append(new Vertex(new Vector2f(xi, pipeCenterY + ri), colS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
target.Draw(va);
|
||||
}
|
||||
}
|
||||
}
|
||||
220
Scenarios/SingleCylScenario.cs
Normal file
220
Scenarios/SingleCylScenario.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Interfaces;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using System;
|
||||
|
||||
namespace FluidSim.Tests
|
||||
{
|
||||
public class SingleCylScenario : Scenario
|
||||
{
|
||||
private Crankshaft crankshaft;
|
||||
private Cylinder cylinder;
|
||||
|
||||
private PipeSystem pipeSystem;
|
||||
private BoundarySystem boundaries;
|
||||
private Solver solver;
|
||||
|
||||
private Volume0D intakePlenum;
|
||||
private Port plenumInlet, plenumOutlet;
|
||||
private Volume0D exhaustCollector;
|
||||
private Port colIn, colOut;
|
||||
|
||||
private int throttleAreaIdx, plenumRunnerAreaIdx, intakeValveIdx, exhaustValveIdx;
|
||||
private float[] orificeAreas;
|
||||
private int intakeOpenIdx, exhaustOpenIdx;
|
||||
|
||||
private SoundProcessor exhaustSound, intakeSound;
|
||||
private OutdoorExhaustReverb reverb;
|
||||
|
||||
private double dt;
|
||||
private int stepCount;
|
||||
public float MaxThrottleArea = 1e-4f; // 1 cm²
|
||||
|
||||
// pipe area for open end calculations
|
||||
private float pipeArea;
|
||||
|
||||
public override void Initialize(int sampleRate)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
|
||||
// ---- Crankshaft ----
|
||||
crankshaft = new Crankshaft(600);
|
||||
crankshaft.Inertia = 0.2f;
|
||||
crankshaft.FrictionConstant = 2f;
|
||||
crankshaft.FrictionViscous = 0.04f;
|
||||
|
||||
// ---- Cylinder ----
|
||||
float bore = 0.056f, stroke = 0.057f, conRod = 0.110f, compRatio = 9.2f;
|
||||
float ivo = 350f, ivc = 580f, evo = 120f, evc = 370f;
|
||||
cylinder = new Cylinder(bore, stroke, conRod, compRatio,
|
||||
ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 0.03f,
|
||||
IntakeValveLift = 0.005f,
|
||||
ExhaustValveDiameter = 0.028f,
|
||||
ExhaustValveLift = 0.005f
|
||||
};
|
||||
|
||||
// ---- Pipe system ----
|
||||
int totalCells = 10 + 10 + 50;
|
||||
int[] pipeStart = { 0, 10, 20 };
|
||||
int[] pipeEnd = { 10, 20, 70 };
|
||||
float[] area = new float[totalCells];
|
||||
float[] dx = new float[totalCells];
|
||||
float pipeDiameter = 0.02f; // 2 cm
|
||||
pipeArea = MathF.PI * 0.25f * pipeDiameter * pipeDiameter;
|
||||
float areaVal = pipeArea;
|
||||
float intakeLenBefore = 0.2f, intakeLenRunner = 0.2f, exhaustLen = 0.5f;
|
||||
for (int i = 0; i < totalCells; i++)
|
||||
{
|
||||
area[i] = areaVal;
|
||||
if (i < 10) dx[i] = intakeLenBefore / 10f;
|
||||
else if (i < 20) dx[i] = intakeLenRunner / 10f;
|
||||
else dx[i] = exhaustLen / 50f;
|
||||
}
|
||||
|
||||
pipeSystem = new PipeSystem(totalCells, pipeStart, pipeEnd, area, dx,
|
||||
1.225f, 0f, 101325f);
|
||||
pipeSystem.DampingMultiplier = 0.5f;
|
||||
pipeSystem.EnergyRelaxationRate = 0f;
|
||||
pipeSystem.AmbientPressure = 101325f;
|
||||
|
||||
// ---- Volumes ----
|
||||
intakePlenum = new Volume0D(5e-6f, 101325f, 300f); // 5 mL
|
||||
plenumInlet = 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 ----
|
||||
boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2);
|
||||
|
||||
throttleAreaIdx = 0;
|
||||
plenumRunnerAreaIdx = 1;
|
||||
intakeValveIdx = 2;
|
||||
exhaustValveIdx = 3;
|
||||
|
||||
// Intake open end (pipe0 left)
|
||||
boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, pipeArea);
|
||||
intakeOpenIdx = 0;
|
||||
|
||||
// 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;
|
||||
|
||||
orificeAreas = new float[4];
|
||||
orificeAreas[plenumRunnerAreaIdx] = areaVal; // fixed plenum->runner area
|
||||
|
||||
// ---- Solver ----
|
||||
solver = new Solver { SubStepCount = 4, EnableProfiling = false };
|
||||
solver.SetTimeStep(dt);
|
||||
solver.SetPipeSystem(pipeSystem);
|
||||
solver.SetBoundarySystem(boundaries);
|
||||
solver.AddComponent(cylinder);
|
||||
solver.AddComponent(intakePlenum);
|
||||
solver.AddComponent(exhaustCollector);
|
||||
|
||||
// ---- Sound ----
|
||||
exhaustSound = new SoundProcessor(sampleRate, 1f) { Gain = 1f };
|
||||
intakeSound = new SoundProcessor(sampleRate, 1f) { Gain = 1f };
|
||||
reverb = new OutdoorExhaustReverb(sampleRate);
|
||||
|
||||
stepCount = 0;
|
||||
Console.WriteLine("TestScenario ready.");
|
||||
}
|
||||
|
||||
public override float Process()
|
||||
{
|
||||
crankshaft.Step((float)dt);
|
||||
cylinder.PreStep((float)dt);
|
||||
|
||||
// Update variable orifice areas
|
||||
float throttledArea = MaxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1f);
|
||||
orificeAreas[throttleAreaIdx] = throttledArea;
|
||||
orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea;
|
||||
orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea;
|
||||
boundaries.SetOrificeAreas(orificeAreas);
|
||||
|
||||
solver.Step();
|
||||
stepCount++;
|
||||
|
||||
// Retrieve open‑end mass flows for sound synthesis
|
||||
float exhaustFlow = boundaries.GetOpenEndMassFlow(exhaustOpenIdx);
|
||||
float intakeFlow = boundaries.GetOpenEndMassFlow(intakeOpenIdx);
|
||||
|
||||
float exhaustDry = exhaustSound.Process(exhaustFlow);
|
||||
float intakeDry = intakeSound.Process(intakeFlow);
|
||||
|
||||
if (stepCount % 1000 == 0)
|
||||
{
|
||||
float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI);
|
||||
Console.WriteLine($"Step {stepCount}, RPM={rpm:F0}, CylP={cylinder.Pressure / 1e5f:F2} bar");
|
||||
Console.WriteLine($"intake flow: {intakeFlow:F12}, exhaust flow: {exhaustFlow:F16}");
|
||||
}
|
||||
|
||||
return reverb.Process(intakeDry);
|
||||
}
|
||||
|
||||
public override void Draw(RenderWindow target)
|
||||
{
|
||||
float winW = target.GetView().Size.X;
|
||||
float winH = target.GetView().Size.Y;
|
||||
|
||||
float intakeY = winH / 2f - 40f;
|
||||
float exhaustY = winH / 2f + 80f;
|
||||
float openEndX = 40f;
|
||||
|
||||
// Intake pipe before throttle (pipe 0)
|
||||
float pipe1StartX = openEndX;
|
||||
float pipe1EndX = pipe1StartX + 120f;
|
||||
DrawPipe(target, pipeSystem, 0, intakeY, pipe1StartX, pipe1EndX);
|
||||
|
||||
// Throttle symbol
|
||||
float throttleX = pipe1EndX + 5f;
|
||||
var throttleRect = new RectangleShape(new Vector2f(8f, 30f))
|
||||
{
|
||||
FillColor = Color.Yellow,
|
||||
Position = new Vector2f(throttleX, intakeY - 15f)
|
||||
};
|
||||
target.Draw(throttleRect);
|
||||
|
||||
// Plenum
|
||||
float plenW = 60f, plenH = 80f;
|
||||
float plenLeftX = throttleX + 10f;
|
||||
float plenCenterX = plenLeftX + plenW / 2f;
|
||||
float plenTopY = intakeY - plenH / 2f;
|
||||
DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);
|
||||
|
||||
// Runner pipe (pipe 1)
|
||||
float runnerStartX = plenLeftX + plenW + 5f;
|
||||
float runnerEndX = runnerStartX + 100f;
|
||||
DrawPipe(target, pipeSystem, 1, intakeY, runnerStartX, runnerEndX);
|
||||
|
||||
// Cylinder
|
||||
float cylCX = runnerEndX + 50f;
|
||||
float cylTopY = intakeY - 120f;
|
||||
float cylW = 80f, cylMaxH = 240f;
|
||||
DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH);
|
||||
|
||||
// Exhaust pipe (pipe 2)
|
||||
float exhStartX = cylCX + cylW / 2f + 20f;
|
||||
float exhEndX = winW - 60f;
|
||||
DrawPipe(target, pipeSystem, 2, exhaustY, exhStartX, exhEndX);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,176 +1,91 @@
|
||||
using System;
|
||||
using System;
|
||||
using SFML.Graphics;
|
||||
using SFML.System;
|
||||
using FluidSim.Components;
|
||||
using FluidSim.Core;
|
||||
using FluidSim.Utils;
|
||||
|
||||
namespace FluidSim.Tests
|
||||
{
|
||||
public class TestScenario : Scenario
|
||||
{
|
||||
// Engine
|
||||
private Cylinder cylinder;
|
||||
private Crankshaft crankshaft;
|
||||
|
||||
// Intake side
|
||||
private Pipe1D intakePipeBeforeThrottle;
|
||||
private Volume0D intakePlenum; // 5 mL
|
||||
private Pipe1D intakeRunner;
|
||||
|
||||
// Exhaust side
|
||||
private Pipe1D exhaustPipe;
|
||||
|
||||
// Links
|
||||
private OpenEndLink intakeOpenEnd;
|
||||
private OrificeLink throttleOrifice;
|
||||
private OrificeLink plenumToRunner;
|
||||
private OrificeLink intakeValve;
|
||||
private OrificeLink exhaustValve;
|
||||
private OpenEndLink exhaustOpenEnd;
|
||||
|
||||
private PipeSystem pipeSystem;
|
||||
private BoundarySystem boundaries;
|
||||
private Solver solver;
|
||||
private SoundProcessor exhaustSoundProcessor;
|
||||
private SoundProcessor intakeSoundProcessor;
|
||||
private OutdoorExhaustReverb reverb;
|
||||
|
||||
private int[] pipeStart = { 0 };
|
||||
private int[] pipeEnd;
|
||||
|
||||
private double dt;
|
||||
private int stepCount;
|
||||
|
||||
// ---------- Throttle control ----------
|
||||
public double MaxThrottleArea { get; set; } = 1 * Units.cm2; // 2 cm²
|
||||
// Sound output: use pressure at open end
|
||||
private SoundProcessor openEndSound;
|
||||
private int openEndIdx = 0; // index of the open end in BoundarySystem (we added only one)
|
||||
|
||||
public override void Initialize(int sampleRate)
|
||||
{
|
||||
dt = 1.0 / sampleRate;
|
||||
|
||||
solver = new Solver();
|
||||
const int cellCount = 200;
|
||||
float length = 2f;
|
||||
float dia = 0.02f;
|
||||
float area = MathF.PI * 0.25f * dia * dia;
|
||||
|
||||
float[] areas = new float[cellCount];
|
||||
float[] dxs = new float[cellCount];
|
||||
float dx = length / cellCount;
|
||||
for (int i = 0; i < cellCount; i++)
|
||||
{
|
||||
areas[i] = area;
|
||||
dxs[i] = dx;
|
||||
}
|
||||
|
||||
pipeEnd = new[] { cellCount };
|
||||
|
||||
float rho0 = 101325f / (287f * 300f);
|
||||
pipeSystem = new PipeSystem(cellCount, pipeStart, pipeEnd, areas, dxs,
|
||||
rho0, 0f, 101325f);
|
||||
pipeSystem.DampingMultiplier = 0f;
|
||||
pipeSystem.EnergyRelaxationRate = 0f;
|
||||
pipeSystem.AmbientPressure = 101325f;
|
||||
|
||||
// Pressure bubble near right end
|
||||
float pBubble = 10f * 101325f;
|
||||
float TBubble = 2000f;
|
||||
float rhoBubble = pBubble / (287f * TBubble);
|
||||
for (int i = 0; i <= 10; i++)
|
||||
pipeSystem.SetCellState(i, rhoBubble, 0f, pBubble);
|
||||
|
||||
// Boundaries: left closed, right open
|
||||
boundaries = new BoundarySystem(pipeSystem, maxOrifices: 1, maxOpenEnds: 1);
|
||||
boundaries.AddOrifice(null, pipeIndex: 0, isLeftEnd: true, areaIndex: 0, 1f);
|
||||
boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: false, 101325f, area);
|
||||
float[] orificeAreas = new float[1] { 0f };
|
||||
boundaries.SetOrificeAreas(orificeAreas);
|
||||
|
||||
solver = new Solver { SubStepCount = 3};
|
||||
solver.SetTimeStep(dt);
|
||||
solver.CflTarget = 0.9;
|
||||
solver.SetPipeSystem(pipeSystem);
|
||||
solver.SetBoundarySystem(boundaries);
|
||||
|
||||
// ---- Crankshaft (external, passed to cylinder) ----
|
||||
crankshaft = new Crankshaft(600);
|
||||
crankshaft.Inertia = 0.2;
|
||||
crankshaft.FrictionConstant = 2;
|
||||
crankshaft.FrictionViscous = 0.04;
|
||||
solver.EnableProfiling = true;
|
||||
pipeSystem.EnableProfiling = true;
|
||||
|
||||
// ---- Cylinder ----
|
||||
double bore = 0.056, stroke = 0.057, conRod = 0.110, compRatio = 9.2;
|
||||
double ivo = 350.0, ivc = 580.0, evo = 120.0, evc = 370.0;
|
||||
cylinder = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
|
||||
{
|
||||
IntakeValveDiameter = 30 * Units.mm, // 30 mm
|
||||
IntakeValveLift = 5 * Units.mm, // 5 mm
|
||||
ExhaustValveDiameter = 28 * Units.mm, // 28 mm
|
||||
ExhaustValveLift = 5 * Units.mm // 5 mm
|
||||
};
|
||||
solver.AddComponent(cylinder);
|
||||
|
||||
double pipeDiameter = 2 * Units.cm;
|
||||
double pipeArea = Units.AreaFromDiameter(pipeDiameter);
|
||||
|
||||
exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f };
|
||||
intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f };
|
||||
reverb = new OutdoorExhaustReverb(sampleRate);
|
||||
|
||||
// ---- Pipes ----
|
||||
intakePipeBeforeThrottle = new Pipe1D(0.2, pipeArea, 10);
|
||||
intakeRunner = new Pipe1D(0.2, pipeArea, 10);
|
||||
exhaustPipe = new Pipe1D(0.5, pipeArea, 50);
|
||||
solver.AddComponent(intakePipeBeforeThrottle);
|
||||
solver.AddComponent(intakeRunner);
|
||||
solver.AddComponent(exhaustPipe);
|
||||
|
||||
intakePlenum = new Volume0D(5 * Units.mL, 101325.0, 300.0);
|
||||
var plenumInlet = intakePlenum.CreatePort();
|
||||
var plenumOutlet = intakePlenum.CreatePort();
|
||||
solver.AddComponent(intakePlenum);
|
||||
|
||||
// ---- Intake open end ----
|
||||
intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true)
|
||||
{
|
||||
AmbientPressure = 101325.0,
|
||||
Gamma = 1.4
|
||||
};
|
||||
solver.AddOpenEndLink(intakeOpenEnd);
|
||||
|
||||
// ---- Throttle orifice (variable area) ----
|
||||
throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
|
||||
areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.0001, 1))
|
||||
{
|
||||
DischargeCoefficient = 0.2,
|
||||
UseInertance = false
|
||||
};
|
||||
solver.AddOrificeLink(throttleOrifice);
|
||||
|
||||
// ---- Plenum to runner (fixed area) ----
|
||||
plenumToRunner = new OrificeLink(plenumOutlet, intakeRunner, isPipeLeftEnd: true,
|
||||
areaProvider: () => pipeArea)
|
||||
{
|
||||
DischargeCoefficient = 1.0,
|
||||
UseInertance = false
|
||||
};
|
||||
solver.AddOrificeLink(plenumToRunner);
|
||||
|
||||
// ---- Intake valve ----
|
||||
intakeValve = new OrificeLink(cylinder.IntakePort, intakeRunner, isPipeLeftEnd: false,
|
||||
areaProvider: () => cylinder.IntakeValveArea)
|
||||
{
|
||||
DischargeCoefficient = 1.0,
|
||||
UseInertance = false
|
||||
};
|
||||
solver.AddOrificeLink(intakeValve);
|
||||
|
||||
// ---- Exhaust valve ----
|
||||
exhaustValve = new OrificeLink(cylinder.ExhaustPort, exhaustPipe, isPipeLeftEnd: true,
|
||||
areaProvider: () => cylinder.ExhaustValveArea)
|
||||
{
|
||||
DischargeCoefficient = 1.0,
|
||||
UseInertance = false
|
||||
};
|
||||
solver.AddOrificeLink(exhaustValve);
|
||||
|
||||
// ---- Exhaust open end ----
|
||||
exhaustOpenEnd = new OpenEndLink(exhaustPipe, isLeftEnd: false)
|
||||
{
|
||||
AmbientPressure = 101325.0,
|
||||
Gamma = 1.4
|
||||
};
|
||||
solver.AddOpenEndLink(exhaustOpenEnd);
|
||||
// Simple sound processor: convert mass flow rate to audio
|
||||
openEndSound = new SoundProcessor(sampleRate, 1f) { Gain = 2f };
|
||||
|
||||
Console.WriteLine("Pulse test ready.");
|
||||
stepCount = 0;
|
||||
Console.WriteLine("4‑Stroke engine test (plenum + two pipes)");
|
||||
Console.WriteLine($"Bore {bore * 1000:F0}mm, Stroke {stroke * 1000:F0}mm, CR {compRatio}");
|
||||
Console.WriteLine($"IVO {ivo}°, IVC {ivc}°, EVO {evo}°, EVC {evc}° (no overlap)");
|
||||
}
|
||||
|
||||
public override float Process()
|
||||
{
|
||||
cylinder.Crankshaft.Step(dt);
|
||||
cylinder.PreStep(dt);
|
||||
solver.Step();
|
||||
stepCount++;
|
||||
|
||||
if (stepCount % 10000 == 0)
|
||||
{
|
||||
double crankDeg = cylinder.Crankshaft.CrankAngle * 180.0 / Math.PI % 720.0;
|
||||
double cylP = cylinder.Pressure / 1e5;
|
||||
double cylT = cylinder.Temperature;
|
||||
double cylMass = cylinder.Mass * 1e6;
|
||||
double mdotI = intakeValve.LastMassFlowRate;
|
||||
double mdotE = exhaustValve.LastMassFlowRate;
|
||||
double pipeR = exhaustPipe.GetCellPressure(exhaustPipe.CellCount - 1) / 1e5;
|
||||
double plenumP = intakePlenum.Pressure / 1e5;
|
||||
double actualArea = MaxThrottleArea * Throttle;
|
||||
float flow = boundaries.GetOpenEndMassFlow(openEndIdx);
|
||||
float sample = openEndSound.Process(flow);
|
||||
|
||||
Console.WriteLine($"Step {stepCount}: Angle={crankDeg:F1}°, " +
|
||||
$"CylP={cylP:F2} bar, T={cylT:F0} K, mass={cylMass:F1} mg, " +
|
||||
$"mdotI={mdotI:E4} kg/s, mdotE={mdotE:E4} kg/s, PipeR={pipeR:F2} bar");
|
||||
Console.WriteLine($"Throttle = {Throttle * 100:F0}% area = {actualArea * 1e6:F2} mm², Plenum P = {plenumP:F3} bar");
|
||||
}
|
||||
|
||||
float exhaustDry = exhaustSoundProcessor.Process(exhaustOpenEnd);
|
||||
float intakeDry = intakeSoundProcessor.Process(intakeOpenEnd);
|
||||
return reverb.Process(exhaustDry + intakeDry);
|
||||
return sample;
|
||||
}
|
||||
|
||||
public override void Draw(RenderWindow target)
|
||||
@@ -178,56 +93,10 @@ namespace FluidSim.Tests
|
||||
float winW = target.GetView().Size.X;
|
||||
float winH = target.GetView().Size.Y;
|
||||
|
||||
float intakeY = winH / 2f - 40f;
|
||||
float exhaustY = winH / 2f + 80f;
|
||||
|
||||
// Open end marker
|
||||
float openEndX = 40f;
|
||||
var openEndMark = new CircleShape(5f) { FillColor = Color.Cyan };
|
||||
openEndMark.Position = new Vector2f(openEndX - 5f, intakeY - 5f);
|
||||
target.Draw(openEndMark);
|
||||
|
||||
// First intake pipe
|
||||
float pipe1StartX = openEndX;
|
||||
float pipe1EndX = pipe1StartX + 120f;
|
||||
DrawPipe(target, intakePipeBeforeThrottle, intakeY, pipe1StartX, pipe1EndX);
|
||||
|
||||
// Throttle symbol
|
||||
float throttleX = pipe1EndX + 5f;
|
||||
var throttleRect = new RectangleShape(new Vector2f(8f, 30f))
|
||||
{
|
||||
FillColor = Color.Yellow,
|
||||
Position = new Vector2f(throttleX, intakeY - 15f)
|
||||
};
|
||||
target.Draw(throttleRect);
|
||||
|
||||
// Plenum
|
||||
float plenW = 60f, plenH = 80f;
|
||||
float plenLeftX = throttleX + 10f;
|
||||
float plenCenterX = plenLeftX + plenW / 2f;
|
||||
float plenTopY = intakeY - plenH / 2f;
|
||||
DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);
|
||||
|
||||
// Runner pipe
|
||||
float runnerStartX = plenLeftX + plenW + 5f;
|
||||
float runnerEndX = runnerStartX + 100f;
|
||||
DrawPipe(target, intakeRunner, intakeY, runnerStartX, runnerEndX);
|
||||
|
||||
// Cylinder
|
||||
float cylCX = runnerEndX + 50f;
|
||||
float cylTopY = intakeY - 120f;
|
||||
float cylW = 80f, cylMaxH = 240f;
|
||||
DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH);
|
||||
|
||||
// Exhaust pipe
|
||||
float exhStartX = cylCX + cylW / 2f + 20f;
|
||||
float exhEndX = winW - 60f;
|
||||
DrawPipe(target, exhaustPipe, exhaustY, exhStartX, exhEndX);
|
||||
|
||||
// Exhaust open end marker
|
||||
var exhOpenEndMark = new CircleShape(5f) { FillColor = Color.Magenta };
|
||||
exhOpenEndMark.Position = new Vector2f(exhEndX - 5f, exhaustY - 5f);
|
||||
target.Draw(exhOpenEndMark);
|
||||
float startX = 50f;
|
||||
float endX = winW - 50f;
|
||||
float y = winH / 2f;
|
||||
DrawPipe(target, pipeSystem, 0, y, startX, endX);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user