200 lines
6.8 KiB
C#
200 lines
6.8 KiB
C#
using SFML.Graphics;
|
|
using SFML.Window;
|
|
using SFML.System;
|
|
using System.Diagnostics;
|
|
using FluidSim.Core;
|
|
using FluidSim.Tests;
|
|
|
|
namespace FluidSim;
|
|
|
|
public class Program
|
|
{
|
|
private const int SampleRate = 44100;
|
|
private const double DrawFrequency = 60.0;
|
|
private static Scenario scenario;
|
|
|
|
// Speed control
|
|
private static double desiredSpeed = 0.01;
|
|
private static double currentSpeed = desiredSpeed;
|
|
private const double MinSpeed = 0.0001;
|
|
private const double MaxSpeed = 1.0;
|
|
private const double ScrollFactor = 1.1;
|
|
|
|
private static double lastDesiredSpeed = 0.1;
|
|
private static bool isRealTime = false;
|
|
|
|
// Throttle smoothing (unused but kept)
|
|
private static double targetThrottle = 0.0;
|
|
private static double currentThrottle = 0.0;
|
|
private const double ThrottleSmoothing = 20.0;
|
|
|
|
private static volatile bool running = true;
|
|
|
|
// ---- Overlay text ----
|
|
private static Font? overlayFont;
|
|
private static Text? overlayText;
|
|
|
|
public static void Main()
|
|
{
|
|
var mode = new VideoMode(new Vector2u(1280, 720));
|
|
var window = new RenderWindow(mode, "FluidSim");
|
|
window.SetVerticalSyncEnabled(true);
|
|
window.Closed += (_, _) => { running = false; window.Close(); };
|
|
window.MouseWheelScrolled += OnMouseWheel;
|
|
window.KeyPressed += OnKeyPressed;
|
|
|
|
// ---- Load font ----
|
|
try
|
|
{
|
|
overlayFont = new Font("fonts/FiraCodeNerdFont-Medium.ttf");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"Failed to load font 'fonts/LiberationMono-Regular.ttf': {ex.Message}");
|
|
overlayFont = null; // will skip text drawing
|
|
}
|
|
|
|
if (overlayFont != null)
|
|
{
|
|
// SFML 3 Text(font, character size in pixels)
|
|
overlayText = new Text(overlayFont)
|
|
{
|
|
FillColor = Color.White,
|
|
Position = new Vector2f(10, 10)
|
|
};
|
|
}
|
|
|
|
var soundEngine = new SoundEngine(bufferCapacity: 16384);
|
|
soundEngine.Volume = 100;
|
|
soundEngine.Start();
|
|
|
|
scenario = new TestScenario();
|
|
scenario.Initialize(SampleRate);
|
|
|
|
var stopwatch = Stopwatch.StartNew();
|
|
double lastDrawTime = 0.0;
|
|
double drawInterval = 1.0 / DrawFrequency;
|
|
double lastSpeedUpdateTime = stopwatch.Elapsed.TotalSeconds;
|
|
|
|
var simBuffer = new List<float>(4096);
|
|
double readIndex = 0.0;
|
|
for (int i = 0; i < 4; i++)
|
|
simBuffer.Add(scenario.Process());
|
|
|
|
long totalSimSteps = simBuffer.Count;
|
|
long totalOutputSamples = 0;
|
|
const int outputChunk = 256;
|
|
float[] outputBuf = new float[outputChunk];
|
|
|
|
while (window.IsOpen)
|
|
{
|
|
window.DispatchEvents();
|
|
|
|
double currentRealTime = stopwatch.Elapsed.TotalSeconds;
|
|
double dtClock = currentRealTime - lastSpeedUpdateTime;
|
|
lastSpeedUpdateTime = currentRealTime;
|
|
|
|
// Smooth simulation speed
|
|
double speedSmoothing = 8.0;
|
|
currentSpeed += (desiredSpeed - currentSpeed) * (1.0 - Math.Exp(-speedSmoothing * dtClock));
|
|
|
|
// Generate audio
|
|
double targetAudioClock = currentRealTime + 0.05;
|
|
while (totalOutputSamples < targetAudioClock * SampleRate && running)
|
|
{
|
|
int toGenerate = (int)Math.Min(
|
|
(long)outputChunk,
|
|
(long)(targetAudioClock * SampleRate) - totalOutputSamples
|
|
);
|
|
if (toGenerate <= 0) break;
|
|
|
|
double maxIndex = readIndex + (toGenerate - 1) * currentSpeed + 2;
|
|
int requiredSimIndex = (int)Math.Ceiling(maxIndex);
|
|
while (simBuffer.Count - 1 < requiredSimIndex)
|
|
{
|
|
simBuffer.Add(scenario.Process());
|
|
totalSimSteps++;
|
|
}
|
|
|
|
for (int i = 0; i < toGenerate; i++)
|
|
{
|
|
int i0 = (int)readIndex;
|
|
int i1 = i0 + 1;
|
|
double frac = readIndex - i0;
|
|
float y0 = simBuffer[Math.Clamp(i0, 0, simBuffer.Count - 1)];
|
|
float y1 = simBuffer[Math.Clamp(i1, 0, simBuffer.Count - 1)];
|
|
outputBuf[i] = (float)(y0 + (y1 - y0) * frac);
|
|
readIndex += currentSpeed;
|
|
|
|
while (readIndex >= 1.0 && simBuffer.Count > 2)
|
|
{
|
|
simBuffer.RemoveAt(0);
|
|
readIndex -= 1.0;
|
|
}
|
|
}
|
|
|
|
int accepted = soundEngine.WriteSamples(outputBuf, toGenerate);
|
|
totalOutputSamples += accepted;
|
|
if (accepted < toGenerate)
|
|
break;
|
|
}
|
|
|
|
// Drawing
|
|
if (currentRealTime - lastDrawTime >= drawInterval)
|
|
{
|
|
double actualSpeed = totalOutputSamples / (currentRealTime * SampleRate);
|
|
double realtimePercent = totalOutputSamples / (currentRealTime * SampleRate) * 100.0;
|
|
|
|
// Update overlay text
|
|
if (overlayText != null)
|
|
{
|
|
string toggleHint = isRealTime ? "[Space] slow mo" : "[Space] real time";
|
|
string throttleHint = Keyboard.IsKeyPressed(Keyboard.Key.W) ? "W held" : "W released";
|
|
overlayText.DisplayedString =
|
|
$"{toggleHint} {throttleHint} " +
|
|
$"Speed: {currentSpeed:F3}x " +
|
|
$"RT: {realtimePercent:F1}%";
|
|
}
|
|
|
|
window.Clear(Color.Black);
|
|
scenario.Draw(window);
|
|
|
|
// Draw the overlay on top
|
|
if (overlayText != null)
|
|
window.Draw(overlayText);
|
|
|
|
window.Display();
|
|
lastDrawTime = currentRealTime;
|
|
}
|
|
}
|
|
|
|
soundEngine.Dispose();
|
|
window.Dispose();
|
|
}
|
|
|
|
private static void OnMouseWheel(object? sender, MouseWheelScrollEventArgs e)
|
|
{
|
|
bool wasRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;
|
|
if (e.Delta > 0)
|
|
desiredSpeed *= ScrollFactor;
|
|
else if (e.Delta < 0)
|
|
desiredSpeed /= ScrollFactor;
|
|
|
|
desiredSpeed = Math.Clamp(desiredSpeed, MinSpeed, MaxSpeed);
|
|
if (!wasRealTime || Math.Abs(desiredSpeed - 1.0) > 1e-6)
|
|
lastDesiredSpeed = desiredSpeed;
|
|
isRealTime = Math.Abs(desiredSpeed - 1.0) < 1e-6;
|
|
}
|
|
|
|
private static void OnKeyPressed(object? sender, KeyEventArgs e)
|
|
{
|
|
if (e.Code == Keyboard.Key.Space)
|
|
{
|
|
if (isRealTime)
|
|
desiredSpeed = lastDesiredSpeed;
|
|
else
|
|
desiredSpeed = 1.0;
|
|
isRealTime = !isRealTime;
|
|
}
|
|
}
|
|
} |