some C# cause why not. C# guy might like this? idk. some python too. mostly simulations. WindowsAPI C# is good i guess

This commit is contained in:
2025-10-21 07:44:55 +02:00
parent d0eaabdd87
commit cb1b91ba0f
23 changed files with 3988 additions and 1 deletions

134
.gitignore vendored
View File

@@ -75,3 +75,137 @@ out/
.pyre/
.dmypy.json
.pytype/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.svclog
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
*.pubxml
*.azurePubxml
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
packages/
## TODO: If the tool you use requires repositories.config, also uncomment the next line
!packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
![Ss]tyle[Cc]op.targets
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
# =========================
# Windows detritus
# =========================
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac desktop service store files
.DS_Store
_NCrunch*

59
INF6B.sln Normal file
View File

@@ -0,0 +1,59 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.2.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "simulations", "simulations", "{860328CB-9D8E-3E66-369A-41D2C4307385}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CubeCollision", "simulations\CubeCollision\CubeCollision.csproj", "{99A8A725-8377-1857-FB92-72D23CA3C7D2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "astar", "astar", "{41A3C625-1BCD-6A6C-6778-B66D9E0ECDCA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "astar", "simulations\astar\astar\astar.csproj", "{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "balls", "balls", "{762C3F1A-CDAA-3083-CDC8-BF35A4FFDB27}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BallCollider", "simulations\balls\BallCollider\BallCollider.csproj", "{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fluids", "fluids", "{6AF4715D-5BE8-8629-09F4-E179AEE0D848}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fluidsC#", "simulations\fluids\fluidsC#\fluidsC#.csproj", "{68981932-75A1-3464-E410-93D7C401DBEB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Release|Any CPU.Build.0 = Release|Any CPU
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Release|Any CPU.Build.0 = Release|Any CPU
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Release|Any CPU.Build.0 = Release|Any CPU
{68981932-75A1-3464-E410-93D7C401DBEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68981932-75A1-3464-E410-93D7C401DBEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68981932-75A1-3464-E410-93D7C401DBEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68981932-75A1-3464-E410-93D7C401DBEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{99A8A725-8377-1857-FB92-72D23CA3C7D2} = {860328CB-9D8E-3E66-369A-41D2C4307385}
{41A3C625-1BCD-6A6C-6778-B66D9E0ECDCA} = {860328CB-9D8E-3E66-369A-41D2C4307385}
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231} = {41A3C625-1BCD-6A6C-6778-B66D9E0ECDCA}
{762C3F1A-CDAA-3083-CDC8-BF35A4FFDB27} = {860328CB-9D8E-3E66-369A-41D2C4307385}
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077} = {762C3F1A-CDAA-3083-CDC8-BF35A4FFDB27}
{6AF4715D-5BE8-8629-09F4-E179AEE0D848} = {860328CB-9D8E-3E66-369A-41D2C4307385}
{68981932-75A1-3464-E410-93D7C401DBEB} = {6AF4715D-5BE8-8629-09F4-E179AEE0D848}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B8AE37F2-BCF9-4338-B055-602CB2A66DBE}
EndGlobalSection
EndGlobal

27
PLF/üben1.py Normal file
View File

@@ -0,0 +1,27 @@
land=["Argentinien","Bolivien", "Brasilien","Chile","Ecuador","Guyana","Kolumbien","Paraguay","Peru","Surimane"]
hdi=[0.83,0.66,0.76,0.83,0.73,0.64,0.72,0.68,0.73,0.71]
# Variables
max_index = hdi.index(max(hdi))
min_index = hdi.index(min(hdi))
max_value = max(hdi)
min_value = min(hdi)
land_max = land[max_index]
land_min = land[min_index]
print(f"""
HDI - Auswertung
{"-"*20}
Max:
Index: {max_index} => {max_value},
{land_max}
{"-"*20}
Min:
Index: {min_index} => {min_value},
{land_min}
{"-"*20}
mittelwert:
{sum(hdi) / len(hdi)}
""")

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,38 @@
namespace CubeCollision;
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
}
#endregion
}

View File

@@ -0,0 +1,9 @@
namespace CubeCollision;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,550 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Media;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;
#nullable enable
namespace CubeCollisionSimulator
{
public class Cube
{
public float X { get; set; }
public float Y { get; set; }
public float VelocityX { get; set; }
public float VelocityY { get; set; }
public float Size { get; set; }
public float Mass { get; set; }
public Color Color { get; set; }
public bool IsDragging { get; set; }
public Cube(float x, float y, float size, float mass, Color color)
{
X = x;
Y = y;
Size = size;
Mass = mass;
Color = color;
VelocityX = 0;
VelocityY = 0;
IsDragging = false;
}
public RectangleF GetBounds()
{
return new RectangleF(X, Y, Size, Size);
}
public bool Contains(Point point)
{
return GetBounds().Contains(point);
}
}
public class SimulatorForm : Form
{
private readonly List<Cube> cubes;
private readonly Timer timer;
private Cube? draggedCube;
private Point lastMousePos;
// Physics parameters
private float gravity = 0.5f;
private float restitution = 0.8f;
private float friction = 0.98f;
private float forceMultiplier = 0.3f;
// Sound management
private DateTime lastSoundTime = DateTime.MinValue;
private const int MinSoundIntervalMs = 50; // Minimum time between sounds
private int soundCooldownCounter = 0;
private CheckBox soundEnabledCheckbox;
// UI Controls
private TrackBar? gravitySlider;
private TrackBar? restitutionSlider;
private TrackBar? frictionSlider;
private TrackBar? forceSlider;
private Label? gravityLabel;
private Label? restitutionLabel;
private Label? frictionLabel;
private Label? forceLabel;
private Button? addCubeButton;
private Button? clearButton;
private Panel? controlPanel;
public SimulatorForm()
{
this.Text = "2D Cube Collision Simulator";
this.Size = new Size(1200, 800);
this.DoubleBuffered = true;
this.BackColor = Color.FromArgb(30, 30, 30);
cubes = new List<Cube>();
soundEnabledCheckbox = new CheckBox();
// Add initial cubes
Random rand = new Random();
for (int i = 0; i < 5; i++)
{
cubes.Add(new Cube(
rand.Next(100, 800),
rand.Next(100, 400),
50 + rand.Next(30),
1 + rand.Next(5),
Color.FromArgb(rand.Next(100, 255), rand.Next(100, 255), rand.Next(100, 255))
));
}
SetupControls();
timer = new Timer();
timer.Interval = 16; // ~60 FPS
timer.Tick += Update;
timer.Start();
this.Paint += OnPaint;
this.MouseDown += OnMouseDown;
this.MouseMove += OnMouseMove;
this.MouseUp += OnMouseUp;
}
private void SetupControls()
{
controlPanel = new Panel
{
Dock = DockStyle.Right,
Width = 250,
BackColor = Color.FromArgb(45, 45, 45),
Padding = new Padding(10)
};
int yPos = 20;
// Sound toggle
soundEnabledCheckbox = new CheckBox
{
Text = "Enable Collision Sound",
Location = new Point(10, yPos),
Size = new Size(230, 25),
ForeColor = Color.White,
Checked = false
};
controlPanel.Controls.Add(soundEnabledCheckbox);
yPos += 40;
// Gravity control
gravityLabel = new Label
{
Text = $"Gravity: {gravity:F2}",
Location = new Point(10, yPos),
Size = new Size(230, 20),
ForeColor = Color.White
};
controlPanel.Controls.Add(gravityLabel);
yPos += 25;
gravitySlider = new TrackBar
{
Location = new Point(10, yPos),
Size = new Size(230, 45),
Minimum = 0,
Maximum = 100,
Value = (int)(gravity * 10),
TickFrequency = 10
};
gravitySlider.ValueChanged += (s, e) =>
{
gravity = gravitySlider?.Value / 10f ?? 0.5f;
if (gravityLabel != null)
gravityLabel.Text = $"Gravity: {gravity:F2}";
};
controlPanel.Controls.Add(gravitySlider);
yPos += 60;
// Restitution control
restitutionLabel = new Label
{
Text = $"Bounciness: {restitution:F2}",
Location = new Point(10, yPos),
Size = new Size(230, 20),
ForeColor = Color.White
};
controlPanel.Controls.Add(restitutionLabel);
yPos += 25;
restitutionSlider = new TrackBar
{
Location = new Point(10, yPos),
Size = new Size(230, 45),
Minimum = 0,
Maximum = 100,
Value = (int)(restitution * 100),
TickFrequency = 10
};
restitutionSlider.ValueChanged += (s, e) =>
{
restitution = restitutionSlider?.Value / 100f ?? 0.8f;
if (restitutionLabel != null)
restitutionLabel.Text = $"Bounciness: {restitution:F2}";
};
controlPanel.Controls.Add(restitutionSlider);
yPos += 60;
// Friction control
frictionLabel = new Label
{
Text = $"Friction: {friction:F2}",
Location = new Point(10, yPos),
Size = new Size(230, 20),
ForeColor = Color.White
};
controlPanel.Controls.Add(frictionLabel);
yPos += 25;
frictionSlider = new TrackBar
{
Location = new Point(10, yPos),
Size = new Size(230, 45),
Minimum = 90,
Maximum = 100,
Value = (int)(friction * 100),
TickFrequency = 1
};
frictionSlider.ValueChanged += (s, e) =>
{
friction = frictionSlider?.Value / 100f ?? 0.98f;
if (frictionLabel != null)
frictionLabel.Text = $"Friction: {friction:F2}";
};
controlPanel.Controls.Add(frictionSlider);
yPos += 60;
// Force control
forceLabel = new Label
{
Text = $"Drag Force: {forceMultiplier:F2}",
Location = new Point(10, yPos),
Size = new Size(230, 20),
ForeColor = Color.White
};
controlPanel.Controls.Add(forceLabel);
yPos += 25;
forceSlider = new TrackBar
{
Location = new Point(10, yPos),
Size = new Size(230, 45),
Minimum = 1,
Maximum = 50,
Value = (int)(forceMultiplier * 10),
TickFrequency = 5
};
forceSlider.ValueChanged += (s, e) =>
{
forceMultiplier = forceSlider?.Value / 10f ?? 0.3f;
if (forceLabel != null)
forceLabel.Text = $"Drag Force: {forceMultiplier:F2}";
};
controlPanel.Controls.Add(forceSlider);
yPos += 60;
// Add cube button
addCubeButton = new Button
{
Text = "Add Cube",
Location = new Point(10, yPos),
Size = new Size(230, 35),
BackColor = Color.FromArgb(70, 130, 180),
ForeColor = Color.White,
FlatStyle = FlatStyle.Flat
};
addCubeButton.Click += (s, e) => AddRandomCube();
controlPanel.Controls.Add(addCubeButton);
yPos += 45;
// Clear button
clearButton = new Button
{
Text = "Clear All",
Location = new Point(10, yPos),
Size = new Size(230, 35),
BackColor = Color.FromArgb(180, 70, 70),
ForeColor = Color.White,
FlatStyle = FlatStyle.Flat
};
clearButton.Click += (s, e) => cubes.Clear();
controlPanel.Controls.Add(clearButton);
yPos += 45;
// Instructions
Label instructionsLabel = new Label
{
Text = "Instructions:\n\n<> Click and drag cubes\n<> Throw them by dragging\n<> Watch them collide!\n<> Adjust physics sliders\n<> Add more cubes\n<> Toggle sound on/off",
Location = new Point(10, yPos + 20),
Size = new Size(230, 200),
ForeColor = Color.LightGray
};
controlPanel.Controls.Add(instructionsLabel);
this.Controls.Add(controlPanel);
}
private void AddRandomCube()
{
Random rand = new Random();
cubes.Add(new Cube(
rand.Next(100, this.ClientSize.Width - 350),
rand.Next(100, 300),
40 + rand.Next(40),
1 + rand.Next(5),
Color.FromArgb(rand.Next(100, 255), rand.Next(100, 255), rand.Next(100, 255))
));
}
private void PlayCollisionSound()
{
if (!soundEnabledCheckbox.Checked)
return;
// Cooldown to prevent too many sounds
if (soundCooldownCounter > 0)
{
soundCooldownCounter--;
return;
}
DateTime now = DateTime.Now;
if ((now - lastSoundTime).TotalMilliseconds < MinSoundIntervalMs)
return;
lastSoundTime = now;
soundCooldownCounter = 3; // Skip next 3 collision sounds
try
{
// Shorter, quieter beep
System.Threading.Tasks.Task.Run(() =>
{
try
{
Console.Beep(400, 30); // Lower frequency, shorter duration
}
catch { }
});
}
catch { }
}
private void OnMouseDown(object? sender, MouseEventArgs e)
{
if (e.X > this.ClientSize.Width - 250) return;
foreach (var cube in cubes.OrderByDescending(c => c.Size))
{
if (cube.Contains(e.Location))
{
draggedCube = cube;
draggedCube.IsDragging = true;
lastMousePos = e.Location;
break;
}
}
}
private void OnMouseMove(object? sender, MouseEventArgs e)
{
if (draggedCube != null && e.Button == MouseButtons.Left)
{
float dx = e.X - lastMousePos.X;
float dy = e.Y - lastMousePos.Y;
draggedCube.X += dx;
draggedCube.Y += dy;
draggedCube.VelocityX = dx * forceMultiplier;
draggedCube.VelocityY = dy * forceMultiplier;
lastMousePos = e.Location;
}
}
private void OnMouseUp(object? sender, MouseEventArgs e)
{
if (draggedCube != null)
{
draggedCube.IsDragging = false;
draggedCube = null;
}
}
private void Update(object? sender, EventArgs e)
{
// Optimized physics loop
int cubeCount = cubes.Count;
for (int i = 0; i < cubeCount; i++)
{
var cube = cubes[i];
if (!cube.IsDragging)
{
// Apply gravity
cube.VelocityY += gravity;
// Apply friction
cube.VelocityX *= friction;
cube.VelocityY *= friction;
// Update position
cube.X += cube.VelocityX;
cube.Y += cube.VelocityY;
// Wall collisions
int rightBoundary = this.ClientSize.Width - 250;
if (cube.X <= 0)
{
cube.X = 0;
cube.VelocityX = -cube.VelocityX * restitution;
PlayCollisionSound();
}
else if (cube.X + cube.Size >= rightBoundary)
{
cube.X = rightBoundary - cube.Size;
cube.VelocityX = -cube.VelocityX * restitution;
PlayCollisionSound();
}
if (cube.Y <= 0)
{
cube.Y = 0;
cube.VelocityY = -cube.VelocityY * restitution;
PlayCollisionSound();
}
else if (cube.Y + cube.Size >= this.ClientSize.Height)
{
cube.Y = this.ClientSize.Height - cube.Size;
cube.VelocityY = -cube.VelocityY * restitution;
PlayCollisionSound();
}
}
}
// Optimized cube-to-cube collisions
for (int i = 0; i < cubeCount; i++)
{
for (int j = i + 1; j < cubeCount; j++)
{
if (CheckCollision(cubes[i], cubes[j]))
{
ResolveCollision(cubes[i], cubes[j]);
PlayCollisionSound();
}
}
}
this.Invalidate();
}
private bool CheckCollision(Cube a, Cube b)
{
return a.GetBounds().IntersectsWith(b.GetBounds());
}
private void ResolveCollision(Cube a, Cube b)
{
// Calculate centers
float aCenterX = a.X + a.Size / 2;
float aCenterY = a.Y + a.Size / 2;
float bCenterX = b.X + b.Size / 2;
float bCenterY = b.Y + b.Size / 2;
// Collision normal
float dx = bCenterX - aCenterX;
float dy = bCenterY - aCenterY;
float distance = (float)Math.Sqrt(dx * dx + dy * dy);
if (distance == 0) return;
float nx = dx / distance;
float ny = dy / distance;
// Separate cubes
float overlap = (a.Size + b.Size) / 2 - distance;
if (overlap > 0)
{
float separationX = nx * overlap * 0.5f;
float separationY = ny * overlap * 0.5f;
a.X -= separationX;
a.Y -= separationY;
b.X += separationX;
b.Y += separationY;
}
// Relative velocity
float dvx = b.VelocityX - a.VelocityX;
float dvy = b.VelocityY - a.VelocityY;
// Velocity along normal
float velAlongNormal = dvx * nx + dvy * ny;
if (velAlongNormal > 0) return;
// Calculate impulse
float impulse = -(1 + restitution) * velAlongNormal;
impulse /= (1 / a.Mass + 1 / b.Mass);
// Apply impulse
float impulseX = impulse * nx;
float impulseY = impulse * ny;
a.VelocityX -= impulseX / a.Mass;
a.VelocityY -= impulseY / a.Mass;
b.VelocityX += impulseX / b.Mass;
b.VelocityY += impulseY / b.Mass;
}
private void OnPaint(object? sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
foreach (var cube in cubes)
{
using (SolidBrush brush = new SolidBrush(cube.Color))
{
g.FillRectangle(brush, cube.GetBounds());
}
if (cube.IsDragging)
{
using (Pen pen = new Pen(Color.Yellow, 3))
{
g.DrawRectangle(pen, cube.X, cube.Y, cube.Size, cube.Size);
}
}
// Draw mass indicator
using (Font font = new Font("Arial", 8))
using (SolidBrush textBrush = new SolidBrush(Color.White))
{
string massText = $"M:{cube.Mass}";
g.DrawString(massText, font, textBrush, cube.X + 5, cube.Y + 5);
}
}
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SimulatorForm());
}
}
}

View File

@@ -0,0 +1,316 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MazePathfinding
{
public class Cell
{
public int Row { get; set; }
public int Col { get; set; }
public int X { get; set; }
public int Y { get; set; }
public bool[] Walls { get; set; } = new bool[4]; // top, right, bottom, left
public bool Visited { get; set; }
public Cell(int row, int col, int cellSize)
{
Row = row;
Col = col;
X = col * cellSize;
Y = row * cellSize;
for (int i = 0; i < 4; i++) Walls[i] = true;
Visited = false;
}
public void Draw(Graphics g, int cellSize)
{
if (Visited)
{
using (var brush = new SolidBrush(Color.White))
g.FillRectangle(brush, X, Y, cellSize, cellSize);
}
using (var pen = new Pen(Color.Black, 2))
{
if (Walls[0]) g.DrawLine(pen, X, Y, X + cellSize, Y);
if (Walls[1]) g.DrawLine(pen, X + cellSize, Y, X + cellSize, Y + cellSize);
if (Walls[2]) g.DrawLine(pen, X + cellSize, Y + cellSize, X, Y + cellSize);
if (Walls[3]) g.DrawLine(pen, X, Y + cellSize, X, Y);
}
}
public void Highlight(Graphics g, Color color, int cellSize)
{
using (var brush = new SolidBrush(color))
g.FillRectangle(brush, X, Y, cellSize, cellSize);
}
}
public class MazeForm : Form
{
private const int WIDTH = 600;
private int rows = 20;
private int cellSize;
private Cell[,] grid;
private Cell start;
private Cell end;
private List<Cell> path = new List<Cell>();
private HashSet<Cell> openSet = new HashSet<Cell>();
private System.Windows.Forms.Timer timer;
private bool isSearching = false;
public MazeForm()
{
Text = "A* Maze Pathfinding - Press SPACE to solve, UP/DOWN to resize";
ClientSize = new Size(WIDTH, WIDTH);
DoubleBuffered = true;
KeyPreview = true;
FormBorderStyle = FormBorderStyle.FixedSingle;
MaximizeBox = false;
cellSize = WIDTH / rows;
CreateMaze(rows);
timer = new System.Windows.Forms.Timer { Interval = 16 };
timer.Tick += (s, e) => Invalidate();
timer.Start();
this.KeyDown += MazeForm_KeyDown;
this.Paint += MazeForm_Paint;
}
private void CreateMaze(int r)
{
rows = r;
cellSize = WIDTH / rows;
grid = new Cell[rows, rows];
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
grid[i, j] = new Cell(i, j, cellSize);
GenerateMaze(grid[0, 0]);
start = grid[0, 0];
end = grid[rows - 1, rows - 1];
path.Clear();
openSet.Clear();
}
private void GenerateMaze(Cell current)
{
var stack = new Stack<Cell>();
current.Visited = true;
var random = new Random();
while (true)
{
var neighbors = new List<Cell>();
int r = current.Row, c = current.Col;
if (r > 0 && !grid[r - 1, c].Visited) neighbors.Add(grid[r - 1, c]);
if (r < rows - 1 && !grid[r + 1, c].Visited) neighbors.Add(grid[r + 1, c]);
if (c > 0 && !grid[r, c - 1].Visited) neighbors.Add(grid[r, c - 1]);
if (c < rows - 1 && !grid[r, c + 1].Visited) neighbors.Add(grid[r, c + 1]);
if (neighbors.Count > 0)
{
var nextCell = neighbors[random.Next(neighbors.Count)];
stack.Push(current);
RemoveWalls(current, nextCell);
nextCell.Visited = true;
current = nextCell;
}
else if (stack.Count > 0)
{
current = stack.Pop();
}
else
{
break;
}
}
}
private void RemoveWalls(Cell a, Cell b)
{
int dx = a.Col - b.Col;
int dy = a.Row - b.Row;
if (dx == 1)
{
a.Walls[3] = false;
b.Walls[1] = false;
}
else if (dx == -1)
{
a.Walls[1] = false;
b.Walls[3] = false;
}
if (dy == 1)
{
a.Walls[0] = false;
b.Walls[2] = false;
}
else if (dy == -1)
{
a.Walls[2] = false;
b.Walls[0] = false;
}
}
private async void MazeForm_KeyDown(object sender, KeyEventArgs e)
{
if (isSearching) return;
if (e.KeyCode == Keys.Space)
{
isSearching = true;
path = await Task.Run(() => AStar(start, end));
isSearching = false;
Invalidate();
}
else if (e.KeyCode == Keys.Up)
{
CreateMaze(rows + 1);
Invalidate();
}
else if (e.KeyCode == Keys.Down && rows > 5)
{
CreateMaze(rows - 1);
Invalidate();
}
}
private int Heuristic(Cell a, Cell b)
{
return Math.Abs(a.Row - b.Row) + Math.Abs(a.Col - b.Col);
}
private List<Cell> AStar(Cell startCell, Cell endCell)
{
var openSetQueue = new SortedSet<(int f, int count, Cell cell)>(
Comparer<(int f, int count, Cell cell)>.Create((a, b) =>
{
int cmp = a.f.CompareTo(b.f);
return cmp != 0 ? cmp : a.count.CompareTo(b.count);
}));
int count = 0;
openSetQueue.Add((0, count, startCell));
var cameFrom = new Dictionary<Cell, Cell>();
var gScore = new Dictionary<Cell, int>();
var fScore = new Dictionary<Cell, int>();
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
{
gScore[grid[i, j]] = int.MaxValue;
fScore[grid[i, j]] = int.MaxValue;
}
gScore[startCell] = 0;
fScore[startCell] = Heuristic(startCell, endCell);
openSet.Clear();
openSet.Add(startCell);
while (openSetQueue.Count > 0)
{
var current = openSetQueue.Min.cell;
openSetQueue.Remove(openSetQueue.Min);
openSet.Remove(current);
if (current == endCell)
{
var pathResult = new List<Cell>();
while (cameFrom.ContainsKey(current))
{
current = cameFrom[current];
pathResult.Add(current);
}
openSet.Clear();
return pathResult;
}
foreach (var neighbor in GetNeighbors(current))
{
int tempG = gScore[current] + 1;
if (tempG < gScore[neighbor])
{
cameFrom[neighbor] = current;
gScore[neighbor] = tempG;
fScore[neighbor] = tempG + Heuristic(neighbor, endCell);
if (!openSet.Contains(neighbor))
{
count++;
openSetQueue.Add((fScore[neighbor], count, neighbor));
openSet.Add(neighbor);
}
}
}
try
{
Invoke(new Action(() => Invalidate()));
}
catch { }
Thread.Sleep(100);
}
openSet.Clear();
return new List<Cell>();
}
private List<Cell> GetNeighbors(Cell cell)
{
var neighbors = new List<Cell>();
int r = cell.Row, c = cell.Col;
if (!cell.Walls[0] && r > 0) neighbors.Add(grid[r - 1, c]);
if (!cell.Walls[1] && c < rows - 1) neighbors.Add(grid[r, c + 1]);
if (!cell.Walls[2] && r < rows - 1) neighbors.Add(grid[r + 1, c]);
if (!cell.Walls[3] && c > 0) neighbors.Add(grid[r, c - 1]);
return neighbors;
}
private void MazeForm_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.Clear(Color.White);
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
grid[i, j].Draw(g, cellSize);
foreach (var cell in openSet)
cell.Highlight(g, Color.LimeGreen, cellSize);
foreach (var cell in path)
cell.Highlight(g, Color.Yellow, cellSize);
start.Highlight(g, Color.Blue, cellSize);
end.Highlight(g, Color.Red, cellSize);
// Redraw walls over highlights
for (int i = 0; i < rows; i++)
for (int j = 0; j < rows; j++)
grid[i, j].Draw(g, cellSize);
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MazeForm());
}
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
</Project>

View File

@@ -7,7 +7,7 @@ WIDTH = 600
ROWS = 20
CELL_SIZE = WIDTH // ROWS
FPS = 60
DELAY = 0
DELAY = 0.1
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,21 @@
namespace BallCollider
{
partial class Form1
{
private System.ComponentModel.IContainer components = null;
private void InitializeComponent()
{
this.SuspendLayout();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 600);
this.Name = "Form1";
this.Text = "Ball Collider";
this.ResumeLayout(false);
}
}
}

View File

@@ -0,0 +1,743 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Media;
using System.Windows.Forms;
using WinTimer = System.Windows.Forms.Timer;
namespace BallCollider
{
public partial class Form1 : Form
{
// Ball properties
private PointF ballPos;
private PointF ballVel;
private const float BallRadius = 12f;
private const float MinSpeed = 6f;
private const float MaxSpeed = 20f;
// Shape properties
private List<List<PointF>> shapes;
private List<Color> shapeColors;
private const int MinWalls = 5;
private const int MaxWalls = 220;
private const float BaseShapeRadius = 220f;
private PointF shapeCenter;
// Game mode
private bool multiShapeMode = false;
private int currentShapeIndex = 0;
// Rendering optimization
private BufferedGraphicsContext context;
private BufferedGraphics buffer;
private readonly SolidBrush ballBrush;
private readonly List<Pen> shapePens;
// Game loop
private readonly WinTimer gameTimer;
private readonly Random rnd;
// Collision cooldown to prevent multiple hits
private int collisionCooldown = 0;
// Tracer system
private List<Tracer> tracers;
private const int MaxTracers = 80;
private int tracerCooldown = 0;
private const int TracerInterval = 2;
// Predictive collision detection
private PointF predictedPos;
private bool showPrediction = false;
public Form1()
{
InitializeComponent();
// Optimize form for rendering
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.Opaque, true);
this.ClientSize = new Size(800, 600);
this.Text = "balls";
this.BackColor = Color.Black;
rnd = new Random();
ballBrush = new SolidBrush(Color.White);
shapePens = new List<Pen>();
tracers = new List<Tracer>();
// Initialize buffered graphics
context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);
shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f);
InitializeGame();
// High-frequency timer for smooth gameplay
gameTimer = new WinTimer();
gameTimer.Interval = 20; // ~60 FPS
gameTimer.Tick += GameLoop;
gameTimer.Start();
this.Resize += (s, e) => RecreateBuffer();
this.KeyDown += Form1_KeyDown;
this.KeyPreview = true;
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.M)
{
multiShapeMode = !multiShapeMode;
if (multiShapeMode)
{
InitializeMultiShapeMode();
}
else
{
InitializeSingleShapeMode();
}
}
else if (e.KeyCode == Keys.P)
{
showPrediction = !showPrediction;
}
}
private void RecreateBuffer()
{
if (this.Width > 0 && this.Height > 0)
{
buffer?.Dispose();
context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);
shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f);
}
}
private void InitializeGame()
{
shapes = new List<List<PointF>>();
shapeColors = new List<Color>();
shapePens.Clear();
tracers.Clear();
CreateShape(MinWalls, 0);
// Spawn ball at center
ballPos = new PointF(shapeCenter.X, shapeCenter.Y);
// Random initial velocity
float angle = (float)(rnd.NextDouble() * Math.PI * 2);
float speed = MinSpeed * 1.5f;
ballVel = new PointF(
(float)Math.Cos(angle) * speed,
(float)Math.Sin(angle) * speed
);
}
private void InitializeSingleShapeMode()
{
shapes.Clear();
shapeColors.Clear();
shapePens.Clear();
tracers.Clear();
CreateShape(MinWalls, 0);
currentShapeIndex = 0;
}
private void InitializeMultiShapeMode()
{
shapes.Clear();
shapeColors.Clear();
shapePens.Clear();
tracers.Clear();
// Create 3 concentric shapes
for (int i = 0; i < 3; i++)
{
CreateShape(MinWalls + i * 2, i);
}
currentShapeIndex = 0;
}
private void CreateShape(int sides, int shapeIndex)
{
while (shapes.Count <= shapeIndex)
{
shapes.Add(new List<PointF>());
shapeColors.Add(Color.White);
shapePens.Add(new Pen(Color.White, 4f));
}
List<PointF> shapePoints = new List<PointF>();
float angleStep = (float)(2 * Math.PI / sides);
float startAngle = (float)(rnd.NextDouble() * Math.PI * 2);
float radius = multiShapeMode ?
BaseShapeRadius * (1.0f - shapeIndex * 0.2f) :
BaseShapeRadius;
for (int i = 0; i < sides; i++)
{
float angle = startAngle + i * angleStep;
shapePoints.Add(new PointF(
shapeCenter.X + (float)Math.Cos(angle) * radius,
shapeCenter.Y + (float)Math.Sin(angle) * radius
));
}
shapes[shapeIndex] = shapePoints;
Color newColor = Color.FromArgb(
rnd.Next(100, 256),
rnd.Next(100, 256),
rnd.Next(100, 256)
);
shapeColors[shapeIndex] = newColor;
shapePens[shapeIndex].Color = newColor;
}
private void GameLoop(object sender, EventArgs e)
{
UpdateBall();
UpdateTracers();
Render();
}
private void UpdateBall()
{
if (collisionCooldown > 0)
collisionCooldown--;
// Store previous position for tracer
PointF previousPos = ballPos;
predictedPos = new PointF(ballPos.X + ballVel.X, ballPos.Y + ballVel.Y);
if (collisionCooldown == 0)
{
if (CheckPredictiveCollision())
{
// Predictive collision detected - handle it immediately
HandlePredictiveCollision();
}
else
{
// No collision predicted - apply normal movement
ballPos = predictedPos;
}
}
else
{
// Apply normal movement during cooldown
ballPos = predictedPos;
}
// Gradually increase speed for excitement (capped)
float speed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
if (speed < MaxSpeed)
{
float factor = 1.005f;
ballVel.X *= factor;
ballVel.Y *= factor;
}
// Add tracer
if (tracerCooldown <= 0)
{
AddTracer(previousPos);
tracerCooldown = TracerInterval;
}
else
{
tracerCooldown--;
}
// Final boundary enforcement (safety net)
EnforceBoundaries();
}
private bool CheckPredictiveCollision()
{
if (shapes.Count == 0) return false;
List<PointF> currentShape = shapes[currentShapeIndex];
for (int i = 0; i < currentShape.Count; i++)
{
PointF p1 = currentShape[i];
PointF p2 = currentShape[(i + 1) % currentShape.Count];
PointF closest = ClosestPointOnSegment(p1, p2, predictedPos);
float dx = predictedPos.X - closest.X;
float dy = predictedPos.Y - closest.Y;
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
if (dist < BallRadius)
{
return true;
}
}
return false;
}
private void HandlePredictiveCollision()
{
if (shapes.Count == 0) return;
List<PointF> currentShape = shapes[currentShapeIndex];
PointF collisionPoint = PointF.Empty;
PointF wallNormal = PointF.Empty;
int wallIndex = -1;
float minDist = float.MaxValue;
// Find the closest wall that will be collided with
for (int i = 0; i < currentShape.Count; i++)
{
PointF p1 = currentShape[i];
PointF p2 = currentShape[(i + 1) % currentShape.Count];
PointF closest = ClosestPointOnSegment(p1, p2, predictedPos);
float dx = predictedPos.X - closest.X;
float dy = predictedPos.Y - closest.Y;
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
if (dist < BallRadius && dist < minDist)
{
minDist = dist;
collisionPoint = closest;
wallIndex = i;
// Calculate wall normal
float edgeX = p2.X - p1.X;
float edgeY = p2.Y - p1.Y;
float edgeLen = (float)Math.Sqrt(edgeX * edgeX + edgeY * edgeY);
if (edgeLen > 0)
{
wallNormal.X = -edgeY / edgeLen;
wallNormal.Y = edgeX / edgeLen;
// Ensure normal points inward
float midX = (p1.X + p2.X) / 2f;
float midY = (p1.Y + p2.Y) / 2f;
float toCenterX = shapeCenter.X - midX;
float toCenterY = shapeCenter.Y - midY;
if (wallNormal.X * toCenterX + wallNormal.Y * toCenterY < 0)
{
wallNormal.X = -wallNormal.X;
wallNormal.Y = -wallNormal.Y;
}
}
}
}
if (wallIndex >= 0 && wallNormal != PointF.Empty)
{
// Calculate exact collision position (ball surface touching wall)
float penetration = BallRadius - minDist;
PointF correctedPos = new PointF(
predictedPos.X + wallNormal.X * penetration,
predictedPos.Y + wallNormal.Y * penetration
);
// Set ball to corrected position
ballPos = correctedPos;
// Reflect velocity
float dotProduct = ballVel.X * wallNormal.X + ballVel.Y * wallNormal.Y;
if (dotProduct < 0)
{
ballVel.X -= 2 * dotProduct * wallNormal.X;
ballVel.Y -= 2 * dotProduct * wallNormal.Y;
// Apply boost and clamp speed
float boost = 1.05f;
ballVel.X *= boost;
ballVel.Y *= boost;
float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
if (currentSpeed > MaxSpeed)
{
ballVel.X = (ballVel.X / currentSpeed) * MaxSpeed;
ballVel.Y = (ballVel.Y / currentSpeed) * MaxSpeed;
}
PlayHitSound();
// Handle shape evolution
PointF p1 = currentShape[wallIndex];
PointF p2 = currentShape[(wallIndex + 1) % currentShape.Count];
if (currentShape.Count < MaxWalls)
{
// Add vertex to make shape more circular
PointF newPoint = new PointF(
(p1.X + p2.X) / 2f,
(p1.Y + p2.Y) / 2f
);
float radius = multiShapeMode ?
BaseShapeRadius * (1.0f - currentShapeIndex * 0.2f) :
BaseShapeRadius;
// Project to maintain circular shape
float angle = (float)Math.Atan2(newPoint.Y - shapeCenter.Y, newPoint.X - shapeCenter.X);
newPoint = new PointF(
shapeCenter.X + (float)Math.Cos(angle) * radius,
shapeCenter.Y + (float)Math.Sin(angle) * radius
);
currentShape.Insert(wallIndex + 1, newPoint);
}
else
{
if (multiShapeMode && currentShapeIndex < shapes.Count - 1)
{
currentShapeIndex++;
PlayShapeChangeSound();
}
else
{
int sides = multiShapeMode ? MinWalls + currentShapeIndex * 2 : MinWalls;
CreateShape(sides, currentShapeIndex);
PlayShapeChangeSound();
}
}
collisionCooldown = 8;
}
}
}
private void EnforceBoundaries()
{
bool isInsideAnyShape = false;
foreach (var shape in shapes)
{
if (IsPointInPolygon(ballPos, shape))
{
isInsideAnyShape = true;
break;
}
}
if (!isInsideAnyShape && shapes.Count > 0)
{
PointF closestBoundary = FindClosestBoundaryPoint(ballPos, shapes[0]);
float pushX = closestBoundary.X - ballPos.X;
float pushY = closestBoundary.Y - ballPos.Y;
float pushDist = (float)Math.Sqrt(pushX * pushX + pushY * pushY);
if (pushDist > 0)
{
float margin = BallRadius + 2f;
ballPos.X = closestBoundary.X - (pushX / pushDist) * margin;
ballPos.Y = closestBoundary.Y - (pushY / pushDist) * margin;
// Emergency bounce
float randomAngle = (float)(rnd.NextDouble() * Math.PI * 2);
float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
ballVel.X = (float)Math.Cos(randomAngle) * currentSpeed;
ballVel.Y = (float)Math.Sin(randomAngle) * currentSpeed;
}
}
}
private void UpdateTracers()
{
// Update and remove old tracers
for (int i = tracers.Count - 1; i >= 0; i--)
{
tracers[i].Update();
if (tracers[i].IsDead)
{
tracers.RemoveAt(i);
}
}
}
private void AddTracer(PointF position)
{
if (tracers.Count >= MaxTracers)
{
tracers.RemoveAt(0);
}
Color tracerColor = shapes.Count > 0 ?
Color.FromArgb(150, shapeColors[currentShapeIndex]) :
Color.FromArgb(150, Color.White);
tracers.Add(new Tracer(position, tracerColor, BallRadius * 0.7f));
}
private PointF FindClosestBoundaryPoint(PointF point, List<PointF> shape)
{
PointF closest = shape[0];
float closestDist = float.MaxValue;
for (int i = 0; i < shape.Count; i++)
{
PointF p1 = shape[i];
PointF p2 = shape[(i + 1) % shape.Count];
PointF segmentClosest = ClosestPointOnSegment(p1, p2, point);
float dist = Distance(point, segmentClosest);
if (dist < closestDist)
{
closestDist = dist;
closest = segmentClosest;
}
}
return closest;
}
private float Distance(PointF a, PointF b)
{
float dx = a.X - b.X;
float dy = a.Y - b.Y;
return (float)Math.Sqrt(dx * dx + dy * dy);
}
private bool IsPointInPolygon(PointF point, List<PointF> polygon)
{
bool inside = false;
int j = polygon.Count - 1;
for (int i = 0; i < polygon.Count; i++)
{
if ((polygon[i].Y > point.Y) != (polygon[j].Y > point.Y) &&
point.X < (polygon[j].X - polygon[i].X) * (point.Y - polygon[i].Y) /
(polygon[j].Y - polygon[i].Y) + polygon[i].X)
{
inside = !inside;
}
j = i;
}
return inside;
}
private PointF ClosestPointOnSegment(PointF a, PointF b, PointF p)
{
float dx = b.X - a.X;
float dy = b.Y - a.Y;
if (dx == 0 && dy == 0)
return a;
float len2 = dx * dx + dy * dy;
float t = ((p.X - a.X) * dx + (p.Y - a.Y) * dy) / len2;
t = Math.Max(0, Math.Min(1, t));
return new PointF(a.X + t * dx, a.Y + t * dy);
}
private void PlayHitSound()
{
try
{
int freq = rnd.Next(400, 1000);
System.Threading.Tasks.Task.Run(() => Console.Beep(freq, 30));
}
catch { }
}
private void PlayShapeChangeSound()
{
try
{
System.Threading.Tasks.Task.Run(() =>
{
Console.Beep(300, 80);
System.Threading.Thread.Sleep(50);
Console.Beep(500, 80);
});
}
catch { }
}
private void Render()
{
Graphics g = buffer.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.Black);
// Draw tracers first (behind everything)
foreach (var tracer in tracers)
{
tracer.Draw(g);
}
// Draw all shapes
for (int shapeIndex = 0; shapeIndex < shapes.Count; shapeIndex++)
{
var shape = shapes[shapeIndex];
if (shape.Count >= 2)
{
Color drawColor = shapeColors[shapeIndex];
if (multiShapeMode && shapeIndex == currentShapeIndex)
{
drawColor = Color.FromArgb(255,
Math.Min(255, drawColor.R + 50),
Math.Min(255, drawColor.G + 50),
Math.Min(255, drawColor.B + 50));
}
using (Pen pen = new Pen(drawColor, shapeIndex == currentShapeIndex ? 5f : 3f))
{
for (int i = 0; i < shape.Count; i++)
{
PointF p1 = shape[i];
PointF p2 = shape[(i + 1) % shape.Count];
g.DrawLine(pen, p1, p2);
}
}
}
}
// Draw prediction line if enabled
if (showPrediction)
{
using (Pen predictionPen = new Pen(Color.FromArgb(100, Color.Yellow), 1f))
{
g.DrawLine(predictionPen, ballPos, predictedPos);
}
// Draw predicted position
using (Brush predictionBrush = new SolidBrush(Color.FromArgb(100, Color.Red)))
{
g.FillEllipse(predictionBrush,
predictedPos.X - BallRadius / 2,
predictedPos.Y - BallRadius / 2,
BallRadius, BallRadius);
}
}
// Draw ball with glow effect
using (GraphicsPath path = new GraphicsPath())
{
path.AddEllipse(
ballPos.X - BallRadius,
ballPos.Y - BallRadius,
BallRadius * 2,
BallRadius * 2
);
using (PathGradientBrush brush = new PathGradientBrush(path))
{
Color activeColor = shapes.Count > 0 ? shapeColors[currentShapeIndex] : Color.White;
brush.CenterColor = Color.White;
brush.SurroundColors = new[] { Color.FromArgb(100, activeColor) };
g.FillPath(brush, path);
}
}
// Draw UI info
using (Font font = new Font("Arial", 14, FontStyle.Bold))
{
string sidesText = $"Sides: {shapes[currentShapeIndex].Count}";
string predictionText = $"Prediction: {(showPrediction ? "ON" : "OFF")} (P)";
g.DrawString(sidesText, font, Brushes.White, 10, 10);
g.DrawString(predictionText, font, Brushes.LightGreen, 10, 35);
}
buffer.Render();
}
protected override void OnPaint(PaintEventArgs e)
{
if (buffer != null)
{
buffer.Render(e.Graphics);
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
gameTimer?.Stop();
gameTimer?.Dispose();
buffer?.Dispose();
context?.Dispose();
ballBrush?.Dispose();
foreach (var pen in shapePens)
pen?.Dispose();
components?.Dispose();
}
base.Dispose(disposing);
}
}
// Tracer class for ball trail effect
public class Tracer
{
public PointF Position { get; private set; }
public Color Color { get; private set; }
public float Radius { get; private set; }
public float Life { get; private set; }
public bool IsDead => Life <= 0;
private readonly float initialRadius;
private readonly Color initialColor;
public Tracer(PointF position, Color color, float radius)
{
Position = position;
Color = color;
Radius = radius;
initialRadius = radius;
initialColor = color;
Life = 1.0f; // Start with full life
}
public void Update()
{
Life -= 0.03f; // Fade speed
if (Life < 0) Life = 0;
// Shrink and fade
Radius = initialRadius * Life;
// Fade color
Color = Color.FromArgb(
(int)(initialColor.A * Life),
initialColor.R,
initialColor.G,
initialColor.B
);
}
public void Draw(Graphics g)
{
if (IsDead) return;
using (Brush brush = new SolidBrush(Color))
{
g.FillEllipse(brush,
Position.X - Radius,
Position.Y - Radius,
Radius * 2,
Radius * 2);
}
}
}
}

View File

@@ -0,0 +1,16 @@
namespace BallCollider;
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}

View File

@@ -0,0 +1,50 @@
using OpenTK.Mathematics;
namespace fluidsC_;
public class Camera
{
public Vector3 Position;
public float AspectRatio;
public Vector3 Front = -Vector3.UnitZ;
public Vector3 Up = Vector3.UnitY;
public Vector3 Right = Vector3.UnitX;
public float Pitch;
public float Yaw = -MathHelper.PiOver2;
public float Speed = 2.5f;
public float Sensitivity = 0.002f;
public Camera(Vector3 position, float aspectRatio)
{
Position = position;
AspectRatio = aspectRatio;
UpdateVectors();
}
public Matrix4 GetViewMatrix()
{
return Matrix4.LookAt(Position, Position + Front, Up);
}
public Matrix4 GetProjectionMatrix()
{
return Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, AspectRatio, 0.01f, 100.0f);
}
public void UpdateVectors()
{
// Calculate the new Front vector
Front.X = MathF.Cos(Yaw) * MathF.Cos(Pitch);
Front.Y = MathF.Sin(Pitch);
Front.Z = MathF.Sin(Yaw) * MathF.Cos(Pitch);
Front = Vector3.Normalize(Front);
// Also re-calculate the Right and Up vector
Right = Vector3.Normalize(Vector3.Cross(Front, Vector3.UnitY));
Up = Vector3.Normalize(Vector3.Cross(Right, Front));
}
}

View File

@@ -0,0 +1,188 @@
using OpenTK.Mathematics;
namespace fluidsC_;
public class FluidSimulation
{
private readonly int _sizeX;
private readonly int _sizeY;
// Fluid fields
private float[,] _density;
private float[,] _densityOld;
// Velocity fields
private float[,] _vx;
private float[,] _vy;
private float[,] _vxOld;
private float[,] _vyOld;
public FluidSimulation(int sizeX, int sizeY)
{
_sizeX = sizeX;
_sizeY = sizeY;
// Initialize arrays in constructor to fix nullable warnings
_density = new float[_sizeX, _sizeY];
_densityOld = new float[_sizeX, _sizeY];
_vx = new float[_sizeX, _sizeY];
_vy = new float[_sizeX, _sizeY];
_vxOld = new float[_sizeX, _sizeY];
_vyOld = new float[_sizeX, _sizeY];
}
public void AddDensity(int x, int y, float amount)
{
if (x >= 0 && x < _sizeX && y >= 0 && y < _sizeY)
{
_density[x, y] += amount;
}
}
public void AddVelocity(int x, int y, float amountX, float amountY)
{
if (x >= 0 && x < _sizeX && y >= 0 && y < _sizeY)
{
_vx[x, y] += amountX;
_vy[x, y] += amountY;
}
}
public void Step(float dt, float diffusion, float viscosity)
{
Diffuse(1, _vxOld, _vx, viscosity, dt);
Diffuse(2, _vyOld, _vy, viscosity, dt);
Project(_vxOld, _vyOld, _vx, _vy);
Advect(1, _vx, _vxOld, _vxOld, _vyOld, dt);
Advect(2, _vy, _vyOld, _vxOld, _vyOld, dt);
Project(_vx, _vy, _vxOld, _vyOld);
Diffuse(0, _densityOld, _density, diffusion, dt);
Advect(0, _density, _densityOld, _vx, _vy, dt);
}
private void Diffuse(int b, float[,] x, float[,] x0, float diff, float dt)
{
float a = dt * diff * (_sizeX - 2) * (_sizeY - 2);
LinearSolve(b, x, x0, a, 1 + 4 * a);
}
private void LinearSolve(int b, float[,] x, float[,] x0, float a, float c)
{
for (int k = 0; k < 20; k++)
{
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
x[i, j] = (x0[i, j] + a * (x[i - 1, j] + x[i + 1, j] + x[i, j - 1] + x[i, j + 1])) / c;
}
}
SetBoundary(b, x);
}
}
private void Project(float[,] velocX, float[,] velocY, float[,] p, float[,] div)
{
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
div[i, j] = -0.5f * (
velocX[i + 1, j] - velocX[i - 1, j] +
velocY[i, j + 1] - velocY[i, j - 1]
) / _sizeX;
p[i, j] = 0;
}
}
SetBoundary(0, div);
SetBoundary(0, p);
LinearSolve(0, p, div, 1, 4);
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
velocX[i, j] -= 0.5f * (p[i + 1, j] - p[i - 1, j]) * _sizeX;
velocY[i, j] -= 0.5f * (p[i, j + 1] - p[i, j - 1]) * _sizeY;
}
}
SetBoundary(1, velocX);
SetBoundary(2, velocY);
}
private void Advect(int b, float[,] d, float[,] d0, float[,] velocX, float[,] velocY, float dt)
{
float dt0 = dt * (_sizeX - 2);
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
float x = i - dt0 * velocX[i, j];
float y = j - dt0 * velocY[i, j];
x = Math.Clamp(x, 0.5f, _sizeX - 1.5f);
y = Math.Clamp(y, 0.5f, _sizeY - 1.5f);
int i0 = (int)Math.Floor(x);
int i1 = i0 + 1;
int j0 = (int)Math.Floor(y);
int j1 = j0 + 1;
float s1 = x - i0;
float s0 = 1 - s1;
float t1 = y - j0;
float t0 = 1 - t1;
d[i, j] = s0 * (t0 * d0[i0, j0] + t1 * d0[i0, j1]) +
s1 * (t0 * d0[i1, j0] + t1 * d0[i1, j1]);
}
}
SetBoundary(b, d);
}
private void SetBoundary(int b, float[,] x)
{
for (int i = 1; i < _sizeX - 1; i++)
{
x[i, 0] = b == 2 ? -x[i, 1] : x[i, 1];
x[i, _sizeY - 1] = b == 2 ? -x[i, _sizeY - 2] : x[i, _sizeY - 2];
}
for (int j = 1; j < _sizeY - 1; j++)
{
x[0, j] = b == 1 ? -x[1, j] : x[1, j];
x[_sizeX - 1, j] = b == 1 ? -x[_sizeX - 2, j] : x[_sizeX - 2, j];
}
x[0, 0] = 0.5f * (x[1, 0] + x[0, 1]);
x[0, _sizeY - 1] = 0.5f * (x[1, _sizeY - 1] + x[0, _sizeY - 2]);
x[_sizeX - 1, 0] = 0.5f * (x[_sizeX - 2, 0] + x[_sizeX - 1, 1]);
x[_sizeX - 1, _sizeY - 1] = 0.5f * (x[_sizeX - 2, _sizeY - 1] + x[_sizeX - 1, _sizeY - 2]);
}
public float GetDensity(int x, int y)
{
if (x >= 0 && x < _sizeX && y >= 0 && y < _sizeY)
return _density[x, y];
return 0;
}
public void FadeDensity(float amount)
{
for (int i = 0; i < _sizeX; i++)
{
for (int j = 0; j < _sizeY; j++)
{
_density[i, j] = Math.Max(0, _density[i, j] - amount);
}
}
}
}

View File

@@ -0,0 +1,313 @@
using OpenTK.Mathematics;
namespace fluidsC_;
public class FluidSimulation3D
{
private readonly int _sizeX;
private readonly int _sizeY;
private readonly int _sizeZ;
// Fluid fields
private float[,,] _density;
private float[,,] _densityOld;
// Velocity fields
private float[,,] _vx;
private float[,,] _vy;
private float[,,] _vz;
private float[,,] _vxOld;
private float[,,] _vyOld;
private float[,,] _vzOld;
// Obstacle field
private bool[,,] _obstacles;
public FluidSimulation3D(int sizeX, int sizeY, int sizeZ)
{
_sizeX = sizeX;
_sizeY = sizeY;
_sizeZ = sizeZ;
// Initialize arrays in constructor
_density = new float[_sizeX, _sizeY, _sizeZ];
_densityOld = new float[_sizeX, _sizeY, _sizeZ];
_vx = new float[_sizeX, _sizeY, _sizeZ];
_vy = new float[_sizeX, _sizeY, _sizeZ];
_vz = new float[_sizeX, _sizeY, _sizeZ];
_vxOld = new float[_sizeX, _sizeY, _sizeZ];
_vyOld = new float[_sizeX, _sizeY, _sizeZ];
_vzOld = new float[_sizeX, _sizeY, _sizeZ];
_obstacles = new bool[_sizeX, _sizeY, _sizeZ];
CreateDefaultObstacles();
}
private void CreateDefaultObstacles()
{
// Create some default obstacles
int centerX = _sizeX / 2;
int centerZ = _sizeZ / 2;
// Create a sphere obstacle in the center
int sphereRadius = Math.Min(_sizeX, Math.Min(_sizeY, _sizeZ)) / 6;
for (int x = 0; x < _sizeX; x++)
{
for (int y = 0; y < _sizeY; y++)
{
for (int z = 0; z < _sizeZ; z++)
{
float dx = x - centerX;
float dy = y - _sizeY / 3;
float dz = z - centerZ;
float dist = MathF.Sqrt(dx * dx + dy * dy + dz * dz);
if (dist <= sphereRadius)
{
_obstacles[x, y, z] = true;
}
// Create walls
if (x == 0 || x == _sizeX - 1 || z == 0 || z == _sizeZ - 1)
{
_obstacles[x, y, z] = true;
}
// Create floor
if (y == 0)
{
_obstacles[x, y, z] = true;
}
}
}
}
}
public void AddDensity(int x, int y, int z, float amount)
{
if (IsInBounds(x, y, z) && !_obstacles[x, y, z])
{
_density[x, y, z] += amount;
}
}
public void AddVelocity(int x, int y, int z, float amountX, float amountY, float amountZ)
{
if (IsInBounds(x, y, z) && !_obstacles[x, y, z])
{
_vx[x, y, z] += amountX;
_vy[x, y, z] += amountY;
_vz[x, y, z] += amountZ;
}
}
public void AddObstacle(int x, int y, int z, bool obstacle)
{
if (IsInBounds(x, y, z))
{
_obstacles[x, y, z] = obstacle;
}
}
public void Step(float dt, float diffusion, float viscosity)
{
Diffuse(1, _vxOld, _vx, viscosity, dt);
Diffuse(2, _vyOld, _vy, viscosity, dt);
Diffuse(3, _vzOld, _vz, viscosity, dt);
// Fixed Project3D calls - use correct parameters
Project3D(_vxOld, _vyOld, _vzOld, _vxOld, _vyOld, _vzOld);
Advect(1, _vx, _vxOld, _vxOld, _vyOld, _vzOld, dt);
Advect(2, _vy, _vyOld, _vxOld, _vyOld, _vzOld, dt);
Advect(3, _vz, _vzOld, _vxOld, _vyOld, _vzOld, dt);
// Fixed Project3D calls - use correct parameters
Project3D(_vx, _vy, _vz, _vx, _vy, _vz);
Diffuse(0, _densityOld, _density, diffusion, dt);
Advect(0, _density, _densityOld, _vx, _vy, _vz, dt);
}
private void Diffuse(int b, float[,,] x, float[,,] x0, float diff, float dt)
{
float a = dt * diff * (_sizeX - 2) * (_sizeY - 2) * (_sizeZ - 2);
LinearSolve(b, x, x0, a, 1 + 6 * a);
}
private void LinearSolve(int b, float[,,] x, float[,,] x0, float a, float c)
{
for (int k = 0; k < 20; k++)
{
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
for (int l = 1; l < _sizeZ - 1; l++)
{
if (!_obstacles[i, j, l])
{
x[i, j, l] = (x0[i, j, l] + a * (
x[i - 1, j, l] + x[i + 1, j, l] +
x[i, j - 1, l] + x[i, j + 1, l] +
x[i, j, l - 1] + x[i, j, l + 1])) / c;
}
}
}
}
SetBoundary(b, x);
}
}
private void Project3D(float[,,] velocX, float[,,] velocY, float[,,] velocZ, float[,,] p, float[,,] div, float[,,] temp)
{
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
for (int l = 1; l < _sizeZ - 1; l++)
{
if (!_obstacles[i, j, l])
{
div[i, j, l] = -0.5f * (
velocX[i + 1, j, l] - velocX[i - 1, j, l] +
velocY[i, j + 1, l] - velocY[i, j - 1, l] +
velocZ[i, j, l + 1] - velocZ[i, j, l - 1]
) / _sizeX;
p[i, j, l] = 0;
}
}
}
}
SetBoundary(0, div);
SetBoundary(0, p);
LinearSolve(0, p, div, 1, 6);
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
for (int l = 1; l < _sizeZ - 1; l++)
{
if (!_obstacles[i, j, l])
{
velocX[i, j, l] -= 0.5f * (p[i + 1, j, l] - p[i - 1, j, l]) * _sizeX;
velocY[i, j, l] -= 0.5f * (p[i, j + 1, l] - p[i, j - 1, l]) * _sizeY;
velocZ[i, j, l] -= 0.5f * (p[i, j, l + 1] - p[i, j, l - 1]) * _sizeZ;
}
}
}
}
SetBoundary(1, velocX);
SetBoundary(2, velocY);
SetBoundary(3, velocZ);
}
private void Advect(int b, float[,,] d, float[,,] d0, float[,,] velocX, float[,,] velocY, float[,,] velocZ, float dt)
{
float dt0 = dt * (_sizeX - 2);
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
for (int l = 1; l < _sizeZ - 1; l++)
{
if (!_obstacles[i, j, l])
{
float x = i - dt0 * velocX[i, j, l];
float y = j - dt0 * velocY[i, j, l];
float z = l - dt0 * velocZ[i, j, l];
x = Math.Clamp(x, 0.5f, _sizeX - 1.5f);
y = Math.Clamp(y, 0.5f, _sizeY - 1.5f);
z = Math.Clamp(z, 0.5f, _sizeZ - 1.5f);
int i0 = (int)Math.Floor(x);
int i1 = i0 + 1;
int j0 = (int)Math.Floor(y);
int j1 = j0 + 1;
int l0 = (int)Math.Floor(z);
int l1 = l0 + 1;
float s1 = x - i0;
float s0 = 1 - s1;
float t1 = y - j0;
float t0 = 1 - t1;
float u1 = z - l0;
float u0 = 1 - u1;
d[i, j, l] =
s0 * (t0 * (u0 * d0[i0, j0, l0] + u1 * d0[i0, j0, l1]) +
t1 * (u0 * d0[i0, j1, l0] + u1 * d0[i0, j1, l1])) +
s1 * (t0 * (u0 * d0[i1, j0, l0] + u1 * d0[i1, j0, l1]) +
t1 * (u0 * d0[i1, j1, l0] + u1 * d0[i1, j1, l1]));
}
}
}
}
SetBoundary(b, d);
}
private void SetBoundary(int b, float[,,] x)
{
// Set boundary conditions with obstacles
for (int i = 1; i < _sizeX - 1; i++)
{
for (int j = 1; j < _sizeY - 1; j++)
{
for (int l = 1; l < _sizeZ - 1; l++)
{
if (_obstacles[i, j, l])
{
// Reflect velocity at obstacles
if (_obstacles[i - 1, j, l]) x[i, j, l] = b == 1 ? -x[i + 1, j, l] : x[i + 1, j, l];
if (_obstacles[i + 1, j, l]) x[i, j, l] = b == 1 ? -x[i - 1, j, l] : x[i - 1, j, l];
if (_obstacles[i, j - 1, l]) x[i, j, l] = b == 2 ? -x[i, j + 1, l] : x[i, j + 1, l];
if (_obstacles[i, j + 1, l]) x[i, j, l] = b == 2 ? -x[i, j - 1, l] : x[i, j - 1, l];
if (_obstacles[i, j, l - 1]) x[i, j, l] = b == 3 ? -x[i, j, l + 1] : x[i, j, l + 1];
if (_obstacles[i, j, l + 1]) x[i, j, l] = b == 3 ? -x[i, j, l - 1] : x[i, j, l - 1];
}
}
}
}
}
private bool IsInBounds(int x, int y, int z)
{
return x >= 0 && x < _sizeX && y >= 0 && y < _sizeY && z >= 0 && z < _sizeZ;
}
public float GetDensity(int x, int y, int z)
{
if (IsInBounds(x, y, z))
return _density[x, y, z];
return 0;
}
public bool GetObstacle(int x, int y, int z)
{
if (IsInBounds(x, y, z))
return _obstacles[x, y, z];
return false;
}
public void FadeDensity(float amount)
{
for (int i = 0; i < _sizeX; i++)
{
for (int j = 0; j < _sizeY; j++)
{
for (int l = 0; l < _sizeZ; l++)
{
_density[i, j, l] = Math.Max(0, _density[i, j, l] - amount);
}
}
}
}
public (int X, int Y, int Z) GetSize() => (_sizeX, _sizeY, _sizeZ);
}

View File

@@ -0,0 +1,276 @@
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("<22> Click and drag to add fluid");
Console.WriteLine("<22> Right click to add velocity");
Console.WriteLine("<22> R key to reset simulation");
Console.WriteLine("<22> 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();
}
}

View File

@@ -0,0 +1,844 @@
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Mathematics;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace fluidsC_;
public class FluidWindow3D_GPU : GameWindow
{
private int _fluidSizeX = 64;
private int _fluidSizeY = 64;
private int _fluidSizeZ = 64;
// GPU buffers - double buffered
private int[] _densityTextures = new int[2];
private int[] _velocityTextures = new int[2];
private int[] _pressureTextures = new int[2];
private int _divergenceTexture;
private int _obstacleTexture;
// Shader programs
private int _advectionShader;
private int _forceShader;
private int _divergenceShader;
private int _pressureShader;
private int _gradientShader;
private int _renderShader;
private int _clearShader;
private int _obstacleShader;
private int _vertexArrayObject;
private int _pointCount;
private Camera _camera;
private bool _firstMove = true;
private Vector2 _lastPos;
private float _time = 0;
private int _currentDensity = 0;
private int _currentVelocity = 0;
private int _currentPressure = 0;
// Simulation parameters
private float _diffusion = 0.0001f;
private float _viscosity = 0.0001f;
public FluidWindow3D_GPU(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
: base(gameWindowSettings, nativeWindowSettings)
{
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
}
protected override void OnLoad()
{
base.OnLoad();
Console.WriteLine($"OpenGL Version: {GL.GetString(StringName.Version)}");
Console.WriteLine($"GLSL Version: {GL.GetString(StringName.ShadingLanguageVersion)}");
GL.ClearColor(0.02f, 0.02f, 0.05f, 1.0f);
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.Blend);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
GL.Enable(EnableCap.ProgramPointSize);
CreateTextures();
SetupShaders();
SetupPointCloud();
InitializeObstacles();
// Add initial fluid
AddInitialFluid();
Console.WriteLine("🎯 WORKING GPU Fluid Simulation - CLICK AND DRAG!");
Console.WriteLine("Left Click: Add Fluid | Right Click: Add Force | R: Reset");
}
private void CreateTextures()
{
for (int i = 0; i < 2; i++)
{
_densityTextures[i] = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
_velocityTextures[i] = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, true);
_pressureTextures[i] = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
}
_divergenceTexture = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
_obstacleTexture = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
ClearAllTextures();
}
private int Create3DTexture(int width, int height, int depth, bool rgb)
{
int texture = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture3D, texture);
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureWrapR, (int)TextureWrapMode.ClampToEdge);
var format = rgb ? PixelInternalFormat.Rgb32f : PixelInternalFormat.R32f;
var pixelFormat = rgb ? PixelFormat.Rgb : PixelFormat.Red;
GL.TexImage3D(TextureTarget.Texture3D, 0, format, width, height, depth, 0, pixelFormat, PixelType.Float, IntPtr.Zero);
return texture;
}
private void SetupShaders()
{
// PROPER FLUID SIMULATION SHADERS
_advectionShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
layout(r32f, binding = 0) uniform image3D densityIn;
layout(rgba32f, binding = 1) uniform image3D velocityIn;
layout(r32f, binding = 2) uniform image3D densityOut;
layout(rgba32f, binding = 3) uniform image3D velocityOut;
layout(r32f, binding = 4) uniform image3D obstacles;
uniform float dt;
uniform float dissipation;
vec3 sampleVelocity(vec3 pos) {
ivec3 coord = ivec3(clamp(pos, vec3(0), vec3(imageSize(velocityIn)) - vec3(1)));
return imageLoad(velocityIn, coord).rgb;
}
float sampleDensity(vec3 pos) {
ivec3 coord = ivec3(clamp(pos, vec3(0), vec3(imageSize(densityIn)) - vec3(1)));
return imageLoad(densityIn, coord).r;
}
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 size = imageSize(densityIn);
if(coord.x >= size.x || coord.y >= size.y || coord.z >= size.z)
return;
if(imageLoad(obstacles, coord).r > 0.5) {
imageStore(densityOut, coord, vec4(0.0));
imageStore(velocityOut, coord, vec4(0.0));
return;
}
// Semi-Lagrangian advection
vec3 vel = sampleVelocity(vec3(coord));
vec3 prevPos = vec3(coord) - vel * dt * 10.0;
// Advect density
float density = sampleDensity(prevPos);
imageStore(densityOut, coord, vec4(density * dissipation, 0.0, 0.0, 1.0));
// Advect velocity (self-advection)
vec3 prevVel = sampleVelocity(prevPos);
imageStore(velocityOut, coord, vec4(prevVel, 1.0));
}");
_forceShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
layout(rgba32f, binding = 0) uniform image3D velocity;
layout(r32f, binding = 1) uniform image3D density;
layout(r32f, binding = 2) uniform image3D obstacles;
uniform vec3 force;
uniform vec3 position;
uniform float radius;
uniform float densityAmount;
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
if(imageLoad(obstacles, coord).r > 0.5)
return;
vec3 cellPos = vec3(coord);
vec3 diff = cellPos - position;
float dist = length(diff);
if(dist < radius) {
float influence = (1.0 - dist/radius) * (1.0 - dist/radius);
// Add force to velocity
vec3 currentVel = imageLoad(velocity, coord).rgb;
vec3 newVel = currentVel + force * influence * 0.5;
imageStore(velocity, coord, vec4(newVel, 1.0));
// Add density
if(densityAmount > 0.0) {
float currentDensity = imageLoad(density, coord).r;
float newDensity = currentDensity + densityAmount * influence;
imageStore(density, coord, vec4(newDensity, 0.0, 0.0, 1.0));
}
}
}");
_divergenceShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
layout(rgba32f, binding = 0) uniform image3D velocity;
layout(r32f, binding = 1) uniform image3D divergence;
layout(r32f, binding = 2) uniform image3D obstacles;
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 size = imageSize(velocity);
if(coord.x < 1 || coord.x >= size.x-1 ||
coord.y < 1 || coord.y >= size.y-1 ||
coord.z < 1 || coord.z >= size.z-1)
return;
if(imageLoad(obstacles, coord).r > 0.5) {
imageStore(divergence, coord, vec4(0.0));
return;
}
// Calculate divergence
float left = imageLoad(velocity, coord - ivec3(1,0,0)).r;
float right = imageLoad(velocity, coord + ivec3(1,0,0)).r;
float down = imageLoad(velocity, coord - ivec3(0,1,0)).g;
float up = imageLoad(velocity, coord + ivec3(0,1,0)).g;
float back = imageLoad(velocity, coord - ivec3(0,0,1)).b;
float front = imageLoad(velocity, coord + ivec3(0,0,1)).b;
float div = (right - left + up - down + front - back) * 0.5;
imageStore(divergence, coord, vec4(div, 0.0, 0.0, 1.0));
}");
_pressureShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
layout(r32f, binding = 0) uniform image3D pressureIn;
layout(r32f, binding = 1) uniform image3D pressureOut;
layout(r32f, binding = 2) uniform image3D divergence;
layout(r32f, binding = 3) uniform image3D obstacles;
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 size = imageSize(pressureIn);
if(coord.x < 1 || coord.x >= size.x-1 ||
coord.y < 1 || coord.y >= size.y-1 ||
coord.z < 1 || coord.z >= size.z-1)
return;
if(imageLoad(obstacles, coord).r > 0.5) {
imageStore(pressureOut, coord, vec4(0.0));
return;
}
// Jacobi iteration for pressure
float left = imageLoad(pressureIn, coord - ivec3(1,0,0)).r;
float right = imageLoad(pressureIn, coord + ivec3(1,0,0)).r;
float down = imageLoad(pressureIn, coord - ivec3(0,1,0)).r;
float up = imageLoad(pressureIn, coord + ivec3(0,1,0)).r;
float back = imageLoad(pressureIn, coord - ivec3(0,0,1)).r;
float front = imageLoad(pressureIn, coord + ivec3(0,0,1)).r;
float div = imageLoad(divergence, coord).r;
float pressure = (left + right + down + up + back + front - div) / 6.0;
imageStore(pressureOut, coord, vec4(pressure, 0.0, 0.0, 1.0));
}");
_gradientShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
layout(rgba32f, binding = 0) uniform image3D velocity;
layout(r32f, binding = 1) uniform image3D pressure;
layout(r32f, binding = 2) uniform image3D obstacles;
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 size = imageSize(velocity);
if(coord.x < 1 || coord.x >= size.x-1 ||
coord.y < 1 || coord.y >= size.y-1 ||
coord.z < 1 || coord.z >= size.z-1)
return;
if(imageLoad(obstacles, coord).r > 0.5) {
imageStore(velocity, coord, vec4(0.0));
return;
}
// Subtract pressure gradient
float pressureLeft = imageLoad(pressure, coord - ivec3(1,0,0)).r;
float pressureRight = imageLoad(pressure, coord + ivec3(1,0,0)).r;
float pressureDown = imageLoad(pressure, coord - ivec3(0,1,0)).r;
float pressureUp = imageLoad(pressure, coord + ivec3(0,1,0)).r;
float pressureBack = imageLoad(pressure, coord - ivec3(0,0,1)).r;
float pressureFront = imageLoad(pressure, coord + ivec3(0,0,1)).r;
vec3 grad = vec3(
pressureRight - pressureLeft,
pressureUp - pressureDown,
pressureFront - pressureBack
) * 0.5;
vec3 currentVel = imageLoad(velocity, coord).rgb;
vec3 newVel = currentVel - grad;
imageStore(velocity, coord, vec4(newVel, 1.0));
}");
_clearShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
layout(r32f, binding = 0) uniform image3D textureToClear;
uniform float value;
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 size = imageSize(textureToClear);
if(coord.x >= size.x || coord.y >= size.y || coord.z >= size.z)
return;
imageStore(textureToClear, coord, vec4(value, 0.0, 0.0, 1.0));
}");
_obstacleShader = CreateComputeShader(@"
#version 430
layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
layout(r32f, binding = 0) uniform image3D obstacles;
uniform vec3 position;
uniform float radius;
uniform float setValue;
void main() {
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
ivec3 size = imageSize(obstacles);
if(coord.x >= size.x || coord.y >= size.y || coord.z >= size.z)
return;
vec3 cellPos = vec3(coord);
float dist = length(cellPos - position);
if(dist <= radius) {
imageStore(obstacles, coord, vec4(setValue, 0.0, 0.0, 1.0));
}
}");
// RENDER SHADER
_renderShader = CreateRenderShader();
}
private int CreateComputeShader(string source)
{
var shader = GL.CreateShader(ShaderType.ComputeShader);
GL.ShaderSource(shader, source);
GL.CompileShader(shader);
GL.GetShader(shader, ShaderParameter.CompileStatus, out int success);
if (success == 0)
{
var infoLog = GL.GetShaderInfoLog(shader);
Console.WriteLine($"Compute Shader Compilation Failed:\n{infoLog}");
}
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
GL.LinkProgram(program);
GL.DeleteShader(shader);
return program;
}
private int CreateRenderShader()
{
const string vertexShaderSource = @"
#version 330 core
layout (location = 0) in vec3 aPosition;
out vec3 fragTexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(aPosition, 1.0);
fragTexCoord = (aPosition + 1.0) * 0.5;
gl_PointSize = 4.0;
}";
const string fragmentShaderSource = @"
#version 330 core
in vec3 fragTexCoord;
out vec4 fragColor;
uniform sampler3D densityTexture;
uniform sampler3D obstacleTexture;
uniform float time;
void main()
{
vec3 texCoord = fragTexCoord;
if(texCoord.x < 0.0 || texCoord.x > 1.0 ||
texCoord.y < 0.0 || texCoord.y > 1.0 ||
texCoord.z < 0.0 || texCoord.z > 1.0)
discard;
float density = texture(densityTexture, texCoord).r;
float obstacle = texture(obstacleTexture, texCoord).r;
if(obstacle > 0.5) {
fragColor = vec4(0.3, 0.3, 0.3, 1.0);
} else if(density > 0.01) {
vec3 color = mix(vec3(0.1, 0.3, 0.8), vec3(0.0, 0.8, 1.0), density * 2.0);
color += 0.1 * sin(time + fragTexCoord.y * 10.0);
fragColor = vec4(color, density * 0.9);
} else {
discard;
}
}";
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);
var program = GL.CreateProgram();
GL.AttachShader(program, vertexShader);
GL.AttachShader(program, fragmentShader);
GL.LinkProgram(program);
GL.DeleteShader(vertexShader);
GL.DeleteShader(fragmentShader);
return program;
}
private void SetupPointCloud()
{
var vertices = new List<Vector3>();
// Reduced point count for performance but still good coverage
for (int x = 0; x < _fluidSizeX; x += 1)
{
for (int y = 0; y < _fluidSizeY; y += 1)
{
for (int z = 0; z < _fluidSizeZ; z += 1)
{
float px = (x / (float)_fluidSizeX) * 2.0f - 1.0f;
float py = (y / (float)_fluidSizeY) * 2.0f - 1.0f;
float pz = (z / (float)_fluidSizeZ) * 2.0f - 1.0f;
vertices.Add(new Vector3(px, py, pz));
}
}
}
_pointCount = vertices.Count;
Console.WriteLine($"Rendering {_pointCount} points");
_vertexArrayObject = GL.GenVertexArray();
int vbo = GL.GenBuffer();
GL.BindVertexArray(_vertexArrayObject);
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Count * 3 * sizeof(float), vertices.ToArray(), BufferUsageHint.StaticDraw);
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
GL.EnableVertexAttribArray(0);
}
private void InitializeObstacles()
{
// Simple box boundaries
AddBoxObstacle(new Vector3(0, 0, 0), new Vector3(_fluidSizeX, 2, _fluidSizeZ)); // Floor
AddBoxObstacle(new Vector3(0, 0, 0), new Vector3(2, _fluidSizeY, _fluidSizeZ)); // Left
AddBoxObstacle(new Vector3(_fluidSizeX - 2, 0, 0), new Vector3(2, _fluidSizeY, _fluidSizeZ)); // Right
AddBoxObstacle(new Vector3(0, 0, 0), new Vector3(_fluidSizeX, _fluidSizeY, 2)); // Back
AddBoxObstacle(new Vector3(0, 0, _fluidSizeZ - 2), new Vector3(_fluidSizeX, _fluidSizeY, 2)); // Front
// Central obstacle
AddSphereObstacle(new Vector3(_fluidSizeX / 2, _fluidSizeY / 4, _fluidSizeZ / 2), 6.0f);
}
private void AddInitialFluid()
{
// Add initial falling fluid
Vector3 sourcePos = new Vector3(_fluidSizeX / 2, _fluidSizeY - 10, _fluidSizeZ / 2);
AddForce(sourcePos, new Vector3(0.0f, -2.0f, 0.0f), 8.0f, 10.0f);
// Add some random fluid blobs
Random rand = new Random();
for (int i = 0; i < 5; i++)
{
Vector3 randomPos = new Vector3(
rand.Next(10, _fluidSizeX - 10),
rand.Next(10, _fluidSizeY - 10),
rand.Next(10, _fluidSizeZ - 10)
);
AddForce(randomPos, Vector3.Zero, 4.0f, 3.0f);
}
}
private void AddSphereObstacle(Vector3 center, float radius)
{
GL.UseProgram(_obstacleShader);
BindImageTexture(0, _obstacleTexture, TextureAccess.WriteOnly);
GL.Uniform3(GL.GetUniformLocation(_obstacleShader, "position"), center);
GL.Uniform1(GL.GetUniformLocation(_obstacleShader, "radius"), radius);
GL.Uniform1(GL.GetUniformLocation(_obstacleShader, "setValue"), 1.0f);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
}
private void AddBoxObstacle(Vector3 min, Vector3 max)
{
Vector3 center = (min + max) * 0.5f;
Vector3 size = max - min;
AddSphereObstacle(center, Math.Max(size.X, Math.Max(size.Y, size.Z)) * 0.5f);
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
StepSimulation((float)e.Time);
RenderFluid();
SwapBuffers();
}
private void StepSimulation(float dt)
{
dt = Math.Min(dt, 0.016f); // Cap at ~60fps
// Full fluid simulation steps
Advect(dt, 0.99f);
AddExternalForces(dt);
Project(dt);
AddDemoSources();
// Swap buffers
_currentDensity = 1 - _currentDensity;
_currentVelocity = 1 - _currentVelocity;
_currentPressure = 1 - _currentPressure;
}
private void Advect(float dt, float dissipation)
{
GL.UseProgram(_advectionShader);
BindImageTexture(0, _densityTextures[_currentDensity], TextureAccess.ReadOnly);
BindImageTexture(1, _velocityTextures[_currentVelocity], TextureAccess.ReadOnly);
BindImageTexture(2, _densityTextures[1 - _currentDensity], TextureAccess.WriteOnly);
BindImageTexture(3, _velocityTextures[1 - _currentVelocity], TextureAccess.WriteOnly);
BindImageTexture(4, _obstacleTexture, TextureAccess.ReadOnly);
GL.Uniform1(GL.GetUniformLocation(_advectionShader, "dt"), dt);
GL.Uniform1(GL.GetUniformLocation(_advectionShader, "dissipation"), dissipation);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
}
private void AddExternalForces(float dt)
{
// Gravity
Vector3 gravity = new Vector3(0.0f, -9.8f, 0.0f) * dt;
GL.UseProgram(_forceShader);
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadWrite);
BindImageTexture(1, _densityTextures[_currentDensity], TextureAccess.ReadWrite);
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
GL.Uniform3(GL.GetUniformLocation(_forceShader, "force"), gravity);
GL.Uniform3(GL.GetUniformLocation(_forceShader, "position"), new Vector3(_fluidSizeX / 2, _fluidSizeY / 2, _fluidSizeZ / 2));
GL.Uniform1(GL.GetUniformLocation(_forceShader, "radius"), _fluidSizeX * 2.0f); // Affect entire field
GL.Uniform1(GL.GetUniformLocation(_forceShader, "densityAmount"), 0.0f);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
}
private void Project(float dt)
{
// Calculate divergence
GL.UseProgram(_divergenceShader);
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadOnly);
BindImageTexture(1, _divergenceTexture, TextureAccess.WriteOnly);
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
// Solve pressure (Jacobi iterations)
for (int i = 0; i < 10; i++)
{
GL.UseProgram(_pressureShader);
BindImageTexture(0, _pressureTextures[_currentPressure], TextureAccess.ReadOnly);
BindImageTexture(1, _pressureTextures[1 - _currentPressure], TextureAccess.WriteOnly);
BindImageTexture(2, _divergenceTexture, TextureAccess.ReadOnly);
BindImageTexture(3, _obstacleTexture, TextureAccess.ReadOnly);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
_currentPressure = 1 - _currentPressure;
}
// Subtract pressure gradient
GL.UseProgram(_gradientShader);
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadWrite);
BindImageTexture(1, _pressureTextures[_currentPressure], TextureAccess.ReadOnly);
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
}
private void AddDemoSources()
{
// Continuous falling source
Vector3 sourcePos = new Vector3(_fluidSizeX / 2, _fluidSizeY - 8, _fluidSizeZ / 2);
AddForce(sourcePos, new Vector3(0.0f, -3.0f, 0.0f), 6.0f, 2.0f);
// Random turbulence
if ((int)(_time * 2) % 4 == 0)
{
Vector3 randomPos = new Vector3(
_fluidSizeX * 0.3f + MathF.Sin(_time) * 5.0f,
_fluidSizeY - 5,
_fluidSizeZ * 0.7f + MathF.Cos(_time) * 5.0f
);
AddForce(randomPos, new Vector3(0.0f, -2.0f, 0.0f), 4.0f, 1.5f);
}
}
private void AddForce(Vector3 position, Vector3 force, float radius, float densityAmount)
{
GL.UseProgram(_forceShader);
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadWrite);
BindImageTexture(1, _densityTextures[_currentDensity], TextureAccess.ReadWrite);
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
GL.Uniform3(GL.GetUniformLocation(_forceShader, "force"), force);
GL.Uniform3(GL.GetUniformLocation(_forceShader, "position"), position);
GL.Uniform1(GL.GetUniformLocation(_forceShader, "radius"), radius);
GL.Uniform1(GL.GetUniformLocation(_forceShader, "densityAmount"), densityAmount);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
}
private void RenderFluid()
{
GL.UseProgram(_renderShader);
var model = Matrix4.Identity;
var view = _camera.GetViewMatrix();
var projection = _camera.GetProjectionMatrix();
GL.UniformMatrix4(GL.GetUniformLocation(_renderShader, "model"), false, ref model);
GL.UniformMatrix4(GL.GetUniformLocation(_renderShader, "view"), false, ref view);
GL.UniformMatrix4(GL.GetUniformLocation(_renderShader, "projection"), false, ref projection);
GL.Uniform1(GL.GetUniformLocation(_renderShader, "time"), _time);
GL.ActiveTexture(TextureUnit.Texture0);
GL.BindTexture(TextureTarget.Texture3D, _densityTextures[_currentDensity]);
GL.Uniform1(GL.GetUniformLocation(_renderShader, "densityTexture"), 0);
GL.ActiveTexture(TextureUnit.Texture1);
GL.BindTexture(TextureTarget.Texture3D, _obstacleTexture);
GL.Uniform1(GL.GetUniformLocation(_renderShader, "obstacleTexture"), 1);
GL.BindVertexArray(_vertexArrayObject);
GL.DrawArrays(PrimitiveType.Points, 0, _pointCount);
}
private void BindImageTexture(int unit, int texture, TextureAccess access)
{
GL.BindImageTexture(unit, texture, 0, false, 0, access, SizedInternalFormat.R32f);
}
private void DispatchCompute(int sizeX, int sizeY, int sizeZ, int localSizeX, int localSizeY, int localSizeZ)
{
int groupsX = (sizeX + localSizeX - 1) / localSizeX;
int groupsY = (sizeY + localSizeY - 1) / localSizeY;
int groupsZ = (sizeZ + localSizeZ - 1) / localSizeZ;
GL.DispatchCompute(groupsX, groupsY, groupsZ);
}
private void ClearAllTextures()
{
GL.UseProgram(_clearShader);
foreach (var texture in _densityTextures)
{
BindImageTexture(0, texture, TextureAccess.WriteOnly);
GL.Uniform1(GL.GetUniformLocation(_clearShader, "value"), 0.0f);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
}
foreach (var texture in _velocityTextures)
{
BindImageTexture(0, texture, TextureAccess.WriteOnly);
GL.Uniform1(GL.GetUniformLocation(_clearShader, "value"), 0.0f);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
}
foreach (var texture in _pressureTextures)
{
BindImageTexture(0, texture, TextureAccess.WriteOnly);
GL.Uniform1(GL.GetUniformLocation(_clearShader, "value"), 0.0f);
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
}
GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits);
// Re-add initial fluid
AddInitialFluid();
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
_time += (float)e.Time;
_camera.UpdateVectors();
var input = KeyboardState;
float cameraSpeed = _camera.Speed * (float)e.Time;
if (input.IsKeyDown(Keys.W))
_camera.Position += _camera.Front * cameraSpeed;
if (input.IsKeyDown(Keys.S))
_camera.Position -= _camera.Front * cameraSpeed;
if (input.IsKeyDown(Keys.A))
_camera.Position -= _camera.Right * cameraSpeed;
if (input.IsKeyDown(Keys.D))
_camera.Position += _camera.Right * cameraSpeed;
if (input.IsKeyDown(Keys.Space))
_camera.Position += _camera.Up * cameraSpeed;
if (input.IsKeyDown(Keys.LeftShift))
_camera.Position -= _camera.Up * cameraSpeed;
Title = $"🎯 WORKING Fluid Simulation - FPS: {1.0 / e.Time:0} - CLICK TO INTERACT!";
}
protected override void OnMouseMove(MouseMoveEventArgs e)
{
base.OnMouseMove(e);
if (IsFocused)
{
if (_firstMove)
{
_lastPos = new Vector2(e.X, e.Y);
_firstMove = false;
}
else
{
var deltaX = e.X - _lastPos.X;
var deltaY = e.Y - _lastPos.Y;
_lastPos = new Vector2(e.X, e.Y);
_camera.Yaw += deltaX * _camera.Sensitivity;
_camera.Pitch -= deltaY * _camera.Sensitivity;
_camera.Pitch = Math.Clamp(_camera.Pitch, -MathHelper.PiOver2 + 0.1f, MathHelper.PiOver2 - 0.1f);
}
}
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
var mousePos = new Vector2(MouseState.X, MouseState.Y);
// Simple direct mapping
Vector3 fluidPos = new Vector3(
(mousePos.X / Size.X) * _fluidSizeX,
(1.0f - mousePos.Y / Size.Y) * _fluidSizeY,
_fluidSizeZ / 2
);
// Clamp to reasonable bounds
fluidPos.X = Math.Clamp(fluidPos.X, 5, _fluidSizeX - 5);
fluidPos.Y = Math.Clamp(fluidPos.Y, 5, _fluidSizeY - 5);
fluidPos.Z = Math.Clamp(fluidPos.Z, 5, _fluidSizeZ - 5);
if (e.Button == MouseButton.Left)
{
AddForce(fluidPos, Vector3.Zero, 8.0f, 15.0f);
Console.WriteLine($"💧 Added DENSITY at: {fluidPos}");
}
else if (e.Button == MouseButton.Right)
{
// Add velocity away from center for explosion effect
Vector3 center = new Vector3(_fluidSizeX / 2, _fluidSizeY / 2, _fluidSizeZ / 2);
Vector3 dir = Vector3.Normalize(fluidPos - center);
AddForce(fluidPos, dir * 20.0f, 6.0f, 0.0f);
Console.WriteLine($"💨 Added VELOCITY at: {fluidPos}");
}
}
protected override void OnKeyDown(KeyboardKeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Keys.Escape)
Close();
else if (e.Key == Keys.R)
{
ClearAllTextures();
Console.WriteLine("🔄 Simulation RESET!");
}
else if (e.Key == Keys.O)
{
var mousePos = new Vector2(MouseState.X, MouseState.Y);
Vector3 fluidPos = new Vector3(
(mousePos.X / Size.X) * _fluidSizeX,
(1.0f - mousePos.Y / Size.Y) * _fluidSizeY,
_fluidSizeZ / 2
);
AddSphereObstacle(fluidPos, 5.0f);
Console.WriteLine($"🧱 Added OBSTACLE at: {fluidPos}");
}
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, Size.X, Size.Y);
_camera.AspectRatio = Size.X / (float)Size.Y;
}
}

View File

@@ -0,0 +1,26 @@
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
namespace fluidsC_;
public static class Program
{
public static void Main()
{
var settings = GameWindowSettings.Default;
var nativeSettings = new NativeWindowSettings()
{
Size = new OpenTK.Mathematics.Vector2i(1200, 800),
Title = "GPU Fluid Simulation - FIXED",
Flags = ContextFlags.ForwardCompatible,
Profile = ContextProfile.Core,
API = ContextAPI.OpenGL,
APIVersion = new System.Version(4, 3)
};
using (var window = new FluidWindow3D_GPU(settings, nativeSettings))
{
window.Run();
}
}
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<RootNamespace>fluidsC_</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK" Version="4.9.2" />
</ItemGroup>
</Project>

326
simulations/fluids/pygl.py Normal file
View File

@@ -0,0 +1,326 @@
"""
Stable 2D fluid simulation (Jos Stam) using Pygame + PyOpenGL + NumPy.
- Single-file demo.
- Left mouse: add density (smoke).
- Right mouse: add velocity (drag to create flow).
- Keys: C = clear, D = toggle display velocity, Esc or close window to quit.
Dependencies:
pip install pygame PyOpenGL numpy
Run:
python stable_fluid_pygame_opengl.py
Notes: This is a CPU solver on a grid (not GPU accelerated). Grid size defaults to 128x128 which is interactive on modern machines.
"""
import sys
import math
import numpy as np
import pygame
from pygame.locals import *
from OpenGL.GL import *
# ----------------------- Fluid solver (Stable Fluids, Stam) -----------------------
class StableFluid:
def __init__(self, N, diffusion, viscosity, dt):
self.N = N # grid size (N x N)
self.size = N + 2 # include boundary
self.dt = dt
self.diff = diffusion
self.visc = viscosity
# fields (with boundary padding)
self.s = np.zeros((self.size, self.size), dtype=np.float32) # temp
self.density = np.zeros((self.size, self.size), dtype=np.float32) # smoke
self.Vx = np.zeros((self.size, self.size), dtype=np.float32)
self.Vy = np.zeros((self.size, self.size), dtype=np.float32)
self.Vx0 = np.zeros((self.size, self.size), dtype=np.float32)
self.Vy0 = np.zeros((self.size, self.size), dtype=np.float32)
def add_density(self, x, y, amount):
self.density[y, x] += amount
def add_velocity(self, x, y, amount_x, amount_y):
self.Vx[y, x] += amount_x
self.Vy[y, x] += amount_y
def step(self):
N = self.N
diff = self.diff
visc = self.visc
dt = self.dt
self.diffuse(1, self.Vx0, self.Vx, visc, dt)
self.diffuse(2, self.Vy0, self.Vy, visc, dt)
self.project(self.Vx0, self.Vy0, self.Vx, self.Vy)
self.advect(1, self.Vx, self.Vx0, self.Vx0, self.Vy0, dt)
self.advect(2, self.Vy, self.Vy0, self.Vx0, self.Vy0, dt)
self.project(self.Vx, self.Vy, self.Vx0, self.Vy0)
self.diffuse(0, self.s, self.density, diff, dt)
self.advect(0, self.density, self.s, self.Vx, self.Vy, dt)
# helper routines
def set_bnd(self, b, x):
N = self.N
# edges
x[0, 1:N+1] = -x[1, 1:N+1] if b == 2 else x[1, 1:N+1]
x[N+1, 1:N+1] = -x[N, 1:N+1] if b == 2 else x[N, 1:N+1]
x[1:N+1, 0] = -x[1:N+1, 1] if b == 1 else x[1:N+1, 1]
x[1:N+1, N+1] = -x[1:N+1, N] if b == 1 else x[1:N+1, N]
# corners
x[0, 0] = 0.5 * (x[1, 0] + x[0, 1])
x[0, N+1] = 0.5 * (x[1, N+1] + x[0, N])
x[N+1, 0] = 0.5 * (x[N, 0] + x[N+1, 1])
x[N+1, N+1] = 0.5 * (x[N, N+1] + x[N+1, N])
def lin_solve(self, b, x, x0, a, c):
N = self.N
for _ in range(20): # Gauss-Seidel iterations
x[1:N+1, 1:N+1] = (
x0[1:N+1, 1:N+1] + a * (
x[0:N, 1:N+1] + x[2:N+2, 1:N+1] + x[1:N+1, 0:N] + x[1:N+1, 2:N+2]
)
) / c
self.set_bnd(b, x)
def diffuse(self, b, x, x0, diff, dt):
N = self.N
a = dt * diff * N * N
self.lin_solve(b, x, x0, a, 1 + 4 * a)
def advect(self, b, d, d0, velocX, velocY, dt):
N = self.N
dt0 = dt * N
for i in range(1, N+1):
for j in range(1, N+1):
x = i - dt0 * velocX[j, i]
y = j - dt0 * velocY[j, i]
if x < 0.5:
x = 0.5
if x > N + 0.5:
x = N + 0.5
i0 = int(math.floor(x))
i1 = i0 + 1
if y < 0.5:
y = 0.5
if y > N + 0.5:
y = N + 0.5
j0 = int(math.floor(y))
j1 = j0 + 1
s1 = x - i0
s0 = 1 - s1
t1 = y - j0
t0 = 1 - t1
d[j, i] = (
s0 * (t0 * d0[j0, i0] + t1 * d0[j1, i0]) +
s1 * (t0 * d0[j0, i1] + t1 * d0[j1, i1])
)
self.set_bnd(b, d)
def project(self, velocX, velocY, p, div):
N = self.N
div[1:N+1, 1:N+1] = -0.5 * (
velocX[1:N+1, 2:N+2] - velocX[1:N+1, 0:N] + velocY[2:N+2, 1:N+1] - velocY[0:N, 1:N+1]
) / N
p[1:N+1, 1:N+1] = 0
self.set_bnd(0, div)
self.set_bnd(0, p)
self.lin_solve(0, p, div, 1, 4)
velocX[1:N+1, 1:N+1] -= 0.5 * (p[1:N+1, 2:N+2] - p[1:N+1, 0:N]) * N
velocY[1:N+1, 1:N+1] -= 0.5 * (p[2:N+2, 1:N+1] - p[0:N, 1:N+1]) * N
self.set_bnd(1, velocX)
self.set_bnd(2, velocY)
# ----------------------- OpenGL texture renderer -----------------------
class GLRenderer:
def __init__(self, width, height, gridN):
self.width = width
self.height = height
self.gridN = gridN
self.tex_id = glGenTextures(1)
self.init_texture()
def init_texture(self):
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
# initial empty texture (RGBA)
data = np.zeros((self.gridN, self.gridN, 4), dtype=np.uint8)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.gridN, self.gridN, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
def update_texture_from_density(self, density):
# density is (N+2)x(N+2), we upload only 1..N
N = self.gridN
den = density[1:N+1, 1:N+1]
# map density to 0-255
img = np.empty((N, N, 4), dtype=np.uint8)
# grayscale smoke map; store in rgb and alpha
clipped = np.clip(den * 255.0, 0, 255).astype(np.uint8)
img[:, :, 0] = clipped
img[:, :, 1] = clipped
img[:, :, 2] = clipped
img[:, :, 3] = clipped
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, N, N, GL_RGBA, GL_UNSIGNED_BYTE, img)
def draw_fullscreen(self):
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.tex_id)
glBegin(GL_QUADS)
glTexCoord2f(0.0, 0.0); glVertex2f(-1.0, -1.0)
glTexCoord2f(1.0, 0.0); glVertex2f(1.0, -1.0)
glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0)
glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, 1.0)
glEnd()
glDisable(GL_TEXTURE_2D)
def draw_velocity(self, fluid):
# draw simple vector field lines
N = fluid.N
glPushAttrib(GL_CURRENT_BIT | GL_POINT_BIT | GL_LINE_BIT)
glPointSize(2.0)
glBegin(GL_LINES)
step = max(1, N // 32)
for i in range(1, N+1, step):
for j in range(1, N+1, step):
x = (i - 0.5) / N * 2 - 1
y = (j - 0.5) / N * 2 - 1
vx = fluid.Vx[j, i]
vy = fluid.Vy[j, i]
glVertex2f(x, y)
glVertex2f(x + vx * 0.02, y + vy * 0.02)
glEnd()
glPopAttrib()
# ----------------------- Main app -----------------------
def main():
# settings
WIN_W, WIN_H = 800, 800
GRID_N = 128
DIFFUSION = 0.0001
VISCOSITY = 0.0001
DT = 0.1
pygame.init()
pygame.display.set_mode((WIN_W, WIN_H), DOUBLEBUF | OPENGL)
pygame.display.set_caption('Stable Fluid (Stam) - Pygame + PyOpenGL')
# setup orthographic projection
glViewport(0, 0, WIN_W, WIN_H)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(-1, 1, -1, 1, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
fluid = StableFluid(GRID_N, DIFFUSION, VISCOSITY, DT)
renderer = GLRenderer(WIN_W, WIN_H, GRID_N)
clock = pygame.time.Clock()
running = True
show_velocity = False
is_down_left = False
is_down_right = False
last_mouse = None
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
running = False
elif event.key == K_c:
fluid.density.fill(0)
fluid.Vx.fill(0)
fluid.Vy.fill(0)
elif event.key == K_d:
show_velocity = not show_velocity
elif event.type == MOUSEBUTTONDOWN:
if event.button == 1:
is_down_left = True
elif event.button == 3:
is_down_right = True
last_mouse = pygame.mouse.get_pos()
elif event.type == MOUSEBUTTONUP:
if event.button == 1:
is_down_left = False
elif event.button == 3:
is_down_right = False
last_mouse = None
mx, my = pygame.mouse.get_pos()
# convert to grid coords (i,x) with padding
def screen_to_grid(px, py):
gx = int((px / WIN_W) * GRID_N) + 1
gy = int(((WIN_H - py) / WIN_H) * GRID_N) + 1
gx = max(1, min(GRID_N, gx))
gy = max(1, min(GRID_N, gy))
return gx, gy
if last_mouse is not None:
lx, ly = last_mouse
if is_down_left or is_down_right:
g1 = screen_to_grid(lx, ly)
g2 = screen_to_grid(mx, my)
gx1, gy1 = g1
gx2, gy2 = g2
# draw along line between points
steps = max(abs(gx2 - gx1), abs(gy2 - gy1), 1)
for s in range(steps + 1):
t = s / steps
gx = int(round(gx1 + (gx2 - gx1) * t))
gy = int(round(gy1 + (gy2 - gy1) * t))
if is_down_left:
fluid.add_density(gx, gy, 100.0 * DT)
if is_down_right:
vx = (gx2 - gx1) * 5.0
vy = (gy2 - gy1) * 5.0
fluid.add_velocity(gx, gy, vx, vy)
last_mouse = (mx, my)
# step simulation
fluid.step()
# render
glClear(GL_COLOR_BUFFER_BIT)
renderer.update_texture_from_density(fluid.density)
renderer.draw_fullscreen()
if show_velocity:
glColor3f(1.0, 0.0, 0.0)
renderer.draw_velocity(fluid)
glColor3f(1.0, 1.0, 1.0)
pygame.display.flip()
#
clock.tick(10)
pygame.quit()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,5 @@
# Compilation Steps
```powershell
cl /EHsc /std:c++17 main.cpp mandelbrot_app.cpp /link user32.lib gdi32.lib d3d11.lib d3dcompiler.lib
```