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(); } }