sandpypi/Sand.App/SandApp.cs

180 lines
7.7 KiB
C#

using Raylib_cs;
using Sand.Core;
using System.Numerics;
using System.Diagnostics;
namespace Sand.App;
internal static partial class SandApp
{
private const int ScreenWidth = 1280;
private const int ScreenHeight = 800;
private const int SidebarWidth = 220;
private const int SettingsWidth = 320;
private const int ParticleSize = 4;
public static void Run()
{
var state = CreateState();
Raylib.SetConfigFlags(ConfigFlags.ResizableWindow);
Raylib.InitWindow(ScreenWidth, ScreenHeight, $"Sand - C# Port ({state.BackendName})");
Raylib.SetTargetFPS(120);
var frameImage = Raylib.GenImageColor(state.SimWidth, state.SimHeight, Color.Black);
state.FrameTexture = Raylib.LoadTextureFromImage(frameImage);
Raylib.UnloadImage(frameImage);
while (!Raylib.WindowShouldClose())
{
var frameStart = Stopwatch.GetTimestamp();
Update(state);
Draw(state);
var frameEnd = Stopwatch.GetTimestamp();
state.LastAppFrameTimeMicroseconds = ToMicroseconds(frameStart, frameEnd);
state.LastAppOtherTimeMicroseconds = Math.Max(
0,
state.LastAppFrameTimeMicroseconds -
state.LastUpdateTimeMicroseconds -
state.LastFrameBuildCallTimeMicroseconds -
state.LastTextureUploadTimeMicroseconds -
state.LastDrawTimeMicroseconds);
}
Raylib.UnloadTexture(state.FrameTexture);
Raylib.CloseWindow();
}
private static AppState CreateState()
{
var contentRoot = Path.Combine(AppContext.BaseDirectory, "Content", "part");
var library = ParticleLibraryLoader.LoadFromDirectory(contentRoot);
var settings = new SimulationSettings
{
StorageMode = SimulationStorageMode.Dense,
PauseSim = false,
EnableDebug = true,
EnableFps = true,
EnableCursor = true,
EnableGasEffect = true,
};
var simWidth = (ScreenWidth - SidebarWidth) / ParticleSize;
var simHeight = ScreenHeight / ParticleSize;
var simulation = CreateSimulationBackend(simWidth, simHeight, library, settings);
var categories = BuildCategories(library, simulation.BackendName);
var currentCategory = "Solids";
return new AppState
{
Library = library,
Settings = settings,
Simulation = simulation,
Categories = categories,
CurrentCategory = currentCategory,
CurrentParticle = categories[currentCategory][0].Id,
BackendName = simulation.BackendName,
BrushRadius = 3,
SimWidth = simWidth,
SimHeight = simHeight,
UploadBuffer = new byte[simWidth * simHeight * 4],
LastWindDirection = new Vector2(1f, 0f),
SettingItems =
[
new SettingItem("Cursor", () => settings.EnableCursor, () => settings.EnableCursor = !settings.EnableCursor),
new SettingItem("Glow", () => settings.EnableGlow, () => settings.EnableGlow = !settings.EnableGlow),
new SettingItem("Gas FX", () => settings.EnableGasEffect, () => settings.EnableGasEffect = !settings.EnableGasEffect),
new SettingItem("Wind FX", () => settings.EnableWindVisuals, () => settings.EnableWindVisuals = !settings.EnableWindVisuals),
new SettingItem("Pressure FX", () => settings.EnablePressureVisuals, () => settings.EnablePressureVisuals = !settings.EnablePressureVisuals),
new SettingItem("Debug", () => settings.EnableDebug, () => settings.EnableDebug = !settings.EnableDebug),
new SettingItem("FPS", () => settings.EnableFps, () => settings.EnableFps = !settings.EnableFps),
new SettingItem("Temp FX", () => settings.EnableTempVisuals, () => settings.EnableTempVisuals = !settings.EnableTempVisuals),
new SettingItem("Wrap", () => settings.WrapParticles, () => settings.WrapParticles = !settings.WrapParticles),
new SettingItem("Outer Wall", () => settings.OuterWall, () => settings.OuterWall = !settings.OuterWall),
],
};
}
private static ISimulationBackend CreateSimulationBackend(int simWidth, int simHeight, IParticleLibrary library, SimulationSettings settings)
{
var backend = Environment.GetEnvironmentVariable("SAND_BACKEND");
var storageMode = settings.StorageMode;
if (string.Equals(backend, "chunk", StringComparison.OrdinalIgnoreCase))
{
storageMode = SimulationStorageMode.ChunkPrototype;
}
else if (string.Equals(backend, "dense", StringComparison.OrdinalIgnoreCase))
{
storageMode = SimulationStorageMode.Dense;
}
if (storageMode == SimulationStorageMode.ChunkPrototype)
{
return new ChunkPrototypeSimulationBackend(simWidth, simHeight, ParticleSize, library, settings);
}
return new CoreSimulationBackend(new SandSimulation(simWidth, simHeight, ParticleSize, library, settings));
}
private static Dictionary<string, List<ParticleDef>> BuildCategories(IParticleLibrary library, string backendName)
{
if (string.Equals(backendName, "chunk", StringComparison.OrdinalIgnoreCase))
{
return BuildChunkPrototypeCategories(library);
}
var special = library.Definitions.Where(static d => d.IsSpecial).OrderBy(static d => d.Name).ToList();
if (!special.Any(static d => d.Id == "air"))
{
special.Add(new ParticleDef
{
Id = "air",
Name = "Air",
Kind = ParticleKind.Gas,
IsSpecial = true,
Color = new Rgb24(160, 210, 255),
});
}
special = special.OrderBy(static d => d.Name).ToList();
return new Dictionary<string, List<ParticleDef>>
{
["Solids"] = library.Definitions.Where(static d => d.Kind == ParticleKind.Solid && !d.IsSpecial).OrderBy(static d => d.Name).ToList(),
["Liquids"] = library.Definitions.Where(static d => d.Kind == ParticleKind.Liquid).OrderBy(static d => d.Name).ToList(),
["Gases"] = library.Definitions.Where(static d => d.Kind == ParticleKind.Gas).OrderBy(static d => d.Name).ToList(),
["Special"] = special,
};
}
private static Dictionary<string, List<ParticleDef>> BuildChunkPrototypeCategories(IParticleLibrary library)
{
var special = library.Definitions.Where(static d => d.IsSpecial).OrderBy(static d => d.Name).ToList();
if (!special.Any(static d => d.Id == "air"))
{
special.Add(new ParticleDef
{
Id = "air",
Name = "Air",
Kind = ParticleKind.Gas,
IsSpecial = true,
Color = new Rgb24(160, 210, 255),
});
}
special = special.OrderBy(static d => d.Name).ToList();
return new Dictionary<string, List<ParticleDef>>
{
["Solids"] = library.Definitions.Where(static d => d.Kind == ParticleKind.Solid && !d.IsSpecial).OrderBy(static d => d.Name).ToList(),
["Liquids"] = library.Definitions.Where(static d => d.Kind == ParticleKind.Liquid).OrderBy(static d => d.Name).ToList(),
["Gases"] = library.Definitions.Where(static d => d.Kind == ParticleKind.Gas).OrderBy(static d => d.Name).ToList(),
["Special"] = special,
};
}
private static long ToMicroseconds(long startTimestamp, long endTimestamp)
{
return (long)((endTimestamp - startTimestamp) * 1_000_000.0 / Stopwatch.Frequency);
}
}