276 lines
7.6 KiB
C#
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();
|
|
}
|
|
} |