Files
INF6B/simulations/fluids/fluidsC#/FluidWindow.cs

276 lines
7.6 KiB
C#

using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Mathematics;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace fluidsC_;
public class FluidWindow : GameWindow
{
private FluidSimulation _fluid;
private int _vertexArrayObject;
private int _vertexBufferObject;
private int _elementBufferObject;
private int _shaderProgram;
private int _texture;
private readonly int _fluidSizeX = 128;
private readonly int _fluidSizeY = 128;
private float[] _pixels;
private bool _mouseDown = false;
private Vector2 _lastMousePos;
private MouseButton _activeButton;
public FluidWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
_fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
_pixels = new float[_fluidSizeX * _fluidSizeY * 4];
}
protected override void OnLoad()
{
base.OnLoad();
GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
SetupShaders();
SetupTexture();
SetupQuad();
Console.WriteLine("Fluid Simulation Controls:");
Console.WriteLine("• Click and drag to add fluid");
Console.WriteLine("• Right click to add velocity");
Console.WriteLine("• R key to reset simulation");
Console.WriteLine("• ESC to exit");
}
private void SetupShaders()
{
const string vertexShaderSource = @"
#version 330 core
layout (location = 0) in vec2 aPosition;
layout (location = 1) in vec2 aTexCoord;
out vec2 texCoord;
void main()
{
gl_Position = vec4(aPosition, 0.0, 1.0);
texCoord = aTexCoord;
}";
const string fragmentShaderSource = @"
#version 330 core
in vec2 texCoord;
out vec4 fragColor;
uniform sampler2D fluidTexture;
void main()
{
vec4 color = texture(fluidTexture, texCoord);
// Create a nice blue-to-cyan color gradient based on density
fragColor = vec4(color.r * 0.2, color.r * 0.5, color.r, 1.0);
}";
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
GL.ShaderSource(vertexShader, vertexShaderSource);
GL.CompileShader(vertexShader);
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
GL.ShaderSource(fragmentShader, fragmentShaderSource);
GL.CompileShader(fragmentShader);
_shaderProgram = GL.CreateProgram();
GL.AttachShader(_shaderProgram, vertexShader);
GL.AttachShader(_shaderProgram, fragmentShader);
GL.LinkProgram(_shaderProgram);
GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);
}
private void SetupTexture()
{
_texture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, _texture);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
// Initialize with empty data
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f,
_fluidSizeX, _fluidSizeY, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
}
private void SetupQuad()
{
float[] vertices = {
// positions // texture coords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
uint[] indices = {
0, 1, 2,
2, 3, 0
};
_vertexArrayObject = GL.GenVertexArray();
_vertexBufferObject = GL.GenBuffer();
_elementBufferObject = GL.GenBuffer();
GL.BindVertexArray(_vertexArrayObject);
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw);
// Position attribute
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
// Texture coordinate attribute
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 2 * sizeof(float));
GL.EnableVertexAttribArray(1);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
UpdateTextureFromFluid();
GL.UseProgram(_shaderProgram);
GL.BindTexture(TextureTarget.Texture2D, _texture);
GL.BindVertexArray(_vertexArrayObject);
GL.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, 0);
SwapBuffers();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
// Update fluid simulation
_fluid.Step(0.1f, 0.0001f, 0.0001f);
_fluid.FadeDensity(0.002f);
Title = $"Fluid Simulation :3 - FPS: {1.0 / e.Time:0}";
}
private void UpdateTextureFromFluid()
{
// Convert fluid density to pixel data
for (int y = 0; y < _fluidSizeY; y++)
{
for (int x = 0; x < _fluidSizeX; x++)
{
int index = (y * _fluidSizeX + x) * 4;
float density = Math.Clamp(_fluid.GetDensity(x, y), 0, 1);
_pixels[index] = density; // R
_pixels[index + 1] = density; // G
_pixels[index + 2] = density; // B
_pixels[index + 3] = 1.0f; // A
}
}
GL.BindTexture(TextureTarget.Texture2D, _texture);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, _fluidSizeX, _fluidSizeY,
PixelFormat.Rgba, PixelType.Float, _pixels);
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
_mouseDown = true;
_activeButton = e.Button;
_lastMousePos = new Vector2(MousePosition.X, MousePosition.Y);
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
_mouseDown = false;
}
protected override void OnMouseMove(MouseMoveEventArgs e)
{
base.OnMouseMove(e);
if (_mouseDown)
{
var mousePos = new Vector2(e.X, e.Y);
var delta = mousePos - _lastMousePos;
int fluidX = (int)(e.X / Size.X * _fluidSizeX);
int fluidY = (int)((Size.Y - e.Y) / Size.Y * _fluidSizeY);
// Ensure coordinates are within bounds
fluidX = Math.Clamp(fluidX, 1, _fluidSizeX - 2);
fluidY = Math.Clamp(fluidY, 1, _fluidSizeY - 2);
if (_activeButton == MouseButton.Left)
{
// Add density in a small area
for (int i = -2; i <= 2; i++)
{
for (int j = -2; j <= 2; j++)
{
_fluid.AddDensity(fluidX + i, fluidY + j, 10.0f);
}
}
}
else if (_activeButton == MouseButton.Right)
{
// Add velocity
_fluid.AddVelocity(fluidX, fluidY, delta.X * 0.5f, -delta.Y * 0.5f);
}
_lastMousePos = mousePos;
}
}
protected override void OnKeyDown(KeyboardKeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Keys.Escape)
Close();
else if (e.Key == Keys.R)
{
_fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
Console.WriteLine("Simulation reset!");
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, e.Width, e.Height);
}
protected override void OnUnload()
{
GL.DeleteBuffer(_vertexBufferObject);
GL.DeleteBuffer(_elementBufferObject);
GL.DeleteVertexArray(_vertexArrayObject);
GL.DeleteProgram(_shaderProgram);
GL.DeleteTexture(_texture);
base.OnUnload();
}
}