Files
FluidSim/Scenarios/EngineScenario.cs
2026-05-05 14:02:07 +02:00

183 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using FluidSim.Components;
using SFML.Graphics;
using SFML.System;
namespace FluidSim.Core
{
public class EngineScenario : Scenario
{
private Solver solver;
private Crankshaft crankshaft;
private EngineCylinder engineCyl;
private Pipe1D exhaustPipe;
private PipeVolumeConnection coupling;
private SoundProcessor soundProcessor;
private double dt;
private double ambientPressure = 101325.0;
private double time;
private int stepCount = 0;
private const int LogInterval = 10000;
// Throttle 0..1 → target combustion pressure
public double Throttle { get; set; } = 0.05; // tiny throttle to keep idle
private const double IdlePeakPressure = 5.0 * 101325.0; // 5 bar
private const double MaxPeakPressure = 50.0 * 101325.0; // 50 bar
public override void Initialize(int sampleRate)
{
dt = 1.0 / sampleRate;
// Crankshaft (inertia + friction)
crankshaft = new Crankshaft(initialRPM: 100.0) // starter speed
{
Inertia = 0.05,
FrictionConstant = 1.0,
FrictionViscous = 0.01
};
// Pipe
double pipeLength = 0.5;
double pipeRadius = 0.1;
double pipeArea = Math.PI * pipeRadius * pipeRadius;
exhaustPipe = new Pipe1D(pipeLength, pipeArea, sampleRate, forcedCellCount: 60);
exhaustPipe.SetUniformState(1.225, 0.0, ambientPressure);
exhaustPipe.EnergyRelaxationRate = 0f;
exhaustPipe.DampingMultiplier = 0;
// Cylinder (coupled to crankshaft)
engineCyl = new EngineCylinder(crankshaft,
bore: 0.065, stroke: 0.0565, compressionRatio: 10.0,
pipeArea: pipeArea, sampleRate: sampleRate);
// Coupling (valve → pipe)
coupling = new PipeVolumeConnection(engineCyl.Cylinder, exhaustPipe, true, orificeArea: 0.0);
// Solver
solver = new Solver();
solver.SetTimeStep(dt);
solver.AddVolume(engineCyl.Cylinder);
solver.AddPipe(exhaustPipe);
solver.AddConnection(coupling);
solver.SetPipeBoundary(exhaustPipe, false, BoundaryType.OpenEnd, ambientPressure);
// Sound (your tuned gains)
soundProcessor = new SoundProcessor(sampleRate, pipeRadius * 2, reverbTimeMs: 500.0f);
soundProcessor.MasterGain = 0.0f; //0.00001f;
soundProcessor.PressureGain = 0.1f;
soundProcessor.TurbulenceGain = 0.0f;
soundProcessor.Turbulence = 0.001f;
soundProcessor.SetAmbientPressure(ambientPressure);
Console.WriteLine("=== EngineScenario (torquedriven RPM, throttle = pressure) ===");
Console.WriteLine($"Crankshaft inertia: {crankshaft.Inertia}, friction: {crankshaft.FrictionConstant} + {crankshaft.FrictionViscous}*ω");
Console.WriteLine($"Throttle range: {IdlePeakPressure/101325:F0} {MaxPeakPressure/101325:F0} bar");
Console.WriteLine($"Pipe: {pipeLength} m, fundamental: {340/(4*pipeLength):F1} Hz");
}
public override float Process()
{
// 1. Map throttle to target peak pressure
double targetPressure = IdlePeakPressure + Throttle * (MaxPeakPressure - IdlePeakPressure);
engineCyl.TargetPeakPressure = targetPressure;
// 2. Step the cylinder (adds torque to crankshaft, updates valve)
engineCyl.Step(dt);
// 3. Integrate crankshaft (applies friction, updates RPM)
crankshaft.Step(dt);
// 4. Set orifice area for coupling
coupling.OrificeArea = engineCyl.OrificeArea;
// 5. Fluid solver step
float massFlow = solver.Step();
float endPressure = (float)exhaustPipe.GetCellPressure(exhaustPipe.GetCellCount() - 1);
// 6. Audio
float audioSample = soundProcessor.Process(massFlow, endPressure);
time += dt;
stepCount++;
if (stepCount % LogInterval == 0) {
Console.WriteLine(audioSample);
}
if (stepCount % 1000 == 0 && false)
{
Console.WriteLine($"{time,5:F3} {crankshaft.AngularVelocity*60/(2*Math.PI),5:F0} RPM " +
$"Thr:{Throttle:F2} P_target:{targetPressure/101325:F1} bar " +
$"mflow:{massFlow,14:E4} Comb#{engineCyl.CombustionCount} Mis#{engineCyl.MisfireCount}");
}
return audioSample;
}
// ---- Drawing (unchanged) ----
public override void Draw(RenderWindow target)
{
float winW = target.GetView().Size.X;
float winH = target.GetView().Size.Y;
float centerY = winH / 2f;
const float T_ambient = 293.15f;
const float T_hot = 1500f;
const float T_cold = 0f;
const float R = 287.05f;
float deltaHot = T_hot - T_ambient;
float deltaCold = T_ambient - T_cold;
float NormaliseTemperature(double T)
{
double t;
if (T >= T_ambient)
t = (T - T_ambient) / deltaHot;
else
t = (T - T_ambient) / deltaCold;
return (float)Math.Clamp(t, -1.0, 1.0);
}
float cylW = 80f, cylH = 150f;
var cylRect = new RectangleShape(new Vector2f(cylW, cylH));
cylRect.Position = new Vector2f(40f, centerY - cylH / 2f);
double tempCyl = engineCyl.Cylinder.Temperature;
float tnCyl = NormaliseTemperature(tempCyl);
byte rC = (byte)(tnCyl > 0 ? 255 * tnCyl : 0);
byte bC = (byte)(tnCyl < 0 ? -255 * tnCyl : 0);
byte gC = (byte)(255 * (1 - Math.Abs(tnCyl)));
cylRect.FillColor = new Color(rC, gC, bC);
target.Draw(cylRect);
int n = exhaustPipe.GetCellCount();
float pipeStartX = 120f, pipeEndX = winW - 60f;
float pipeLen = pipeEndX - pipeStartX;
float dx = pipeLen / (n - 1);
float baseRadius = 20f;
var vertices = new Vertex[n * 2];
float ambPress = 101325f;
for (int i = 0; i < n; i++)
{
float x = pipeStartX + i * dx;
double p = exhaustPipe.GetCellPressure(i);
double rho = exhaustPipe.GetCellDensity(i);
double T = p / (rho * R);
float r = baseRadius * 0.3f * (float)(1.0 + (p - ambPress) / ambPress);
if (r < 2f) r = 2f;
float tn = NormaliseTemperature(T);
byte rCol = (byte)(tn > 0 ? 255 * tn : 0);
byte bCol = (byte)(tn < 0 ? -255 * tn : 0);
byte gCol = (byte)(255 * (1 - Math.Abs(tn)));
var col = new Color(rCol, gCol, bCol);
vertices[i * 2] = new Vertex(new Vector2f(x, centerY - r), col);
vertices[i * 2 + 1] = new Vertex(new Vector2f(x, centerY + r), col);
}
target.Draw(vertices, PrimitiveType.TriangleStrip);
}
}
}