diff --git a/.gitignore b/.gitignore
index 0359e52..444bb3f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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*
\ No newline at end of file
diff --git a/INF6B.sln b/INF6B.sln
new file mode 100644
index 0000000..a80e472
--- /dev/null
+++ b/INF6B.sln
@@ -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
diff --git a/PLF/üben1.py b/PLF/üben1.py
new file mode 100644
index 0000000..ed19b32
--- /dev/null
+++ b/PLF/üben1.py
@@ -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)}
+""")
\ No newline at end of file
diff --git a/simulations/CubeCollision/CubeCollision.csproj b/simulations/CubeCollision/CubeCollision.csproj
new file mode 100644
index 0000000..c27cd77
--- /dev/null
+++ b/simulations/CubeCollision/CubeCollision.csproj
@@ -0,0 +1,11 @@
+
+
+
+ WinExe
+ net9.0-windows
+ enable
+ true
+ enable
+
+
+
\ No newline at end of file
diff --git a/simulations/CubeCollision/Form1.Designer.cs b/simulations/CubeCollision/Form1.Designer.cs
new file mode 100644
index 0000000..a3eae7f
--- /dev/null
+++ b/simulations/CubeCollision/Form1.Designer.cs
@@ -0,0 +1,38 @@
+namespace CubeCollision;
+
+partial class Form1
+{
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ 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
+}
diff --git a/simulations/CubeCollision/Form1.cs b/simulations/CubeCollision/Form1.cs
new file mode 100644
index 0000000..901dae7
--- /dev/null
+++ b/simulations/CubeCollision/Form1.cs
@@ -0,0 +1,9 @@
+namespace CubeCollision;
+
+public partial class Form1 : Form
+{
+ public Form1()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/simulations/CubeCollision/Program.cs b/simulations/CubeCollision/Program.cs
new file mode 100644
index 0000000..dd5dbdc
--- /dev/null
+++ b/simulations/CubeCollision/Program.cs
@@ -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 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();
+ 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/simulations/astar/astar/Program.cs b/simulations/astar/astar/Program.cs
new file mode 100644
index 0000000..daa77c0
--- /dev/null
+++ b/simulations/astar/astar/Program.cs
@@ -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 path = new List();
+ private HashSet openSet = new HashSet();
+ 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();
+ current.Visited = true;
+ var random = new Random();
+
+ while (true)
+ {
+ var neighbors = new List();
+ 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 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();
+ var gScore = new Dictionary();
+ var fScore = new Dictionary();
+
+ 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();
+ 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();
+ }
+
+ private List GetNeighbors(Cell cell)
+ {
+ var neighbors = new List();
+ 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());
+ }
+ }
+}
\ No newline at end of file
diff --git a/simulations/astar/astar/astar.csproj b/simulations/astar/astar/astar.csproj
new file mode 100644
index 0000000..677b72a
--- /dev/null
+++ b/simulations/astar/astar/astar.csproj
@@ -0,0 +1,9 @@
+
+
+ WinExe
+ net9.0-windows
+ enable
+ enable
+ true
+
+
\ No newline at end of file
diff --git a/simulations/astar/main.py b/simulations/astar/main.py
index 9c01934..bc6b620 100644
--- a/simulations/astar/main.py
+++ b/simulations/astar/main.py
@@ -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)
diff --git a/simulations/balls/BallCollider/BallCollider.csproj b/simulations/balls/BallCollider/BallCollider.csproj
new file mode 100644
index 0000000..c27cd77
--- /dev/null
+++ b/simulations/balls/BallCollider/BallCollider.csproj
@@ -0,0 +1,11 @@
+
+
+
+ WinExe
+ net9.0-windows
+ enable
+ true
+ enable
+
+
+
\ No newline at end of file
diff --git a/simulations/balls/BallCollider/Form1.Designer.cs b/simulations/balls/BallCollider/Form1.Designer.cs
new file mode 100644
index 0000000..0ad4c18
--- /dev/null
+++ b/simulations/balls/BallCollider/Form1.Designer.cs
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/simulations/balls/BallCollider/Form1.cs b/simulations/balls/BallCollider/Form1.cs
new file mode 100644
index 0000000..0dd9a66
--- /dev/null
+++ b/simulations/balls/BallCollider/Form1.cs
@@ -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> shapes;
+ private List 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 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 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();
+ tracers = new List();
+
+ // 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>();
+ shapeColors = new List();
+ 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());
+ shapeColors.Add(Color.White);
+ shapePens.Add(new Pen(Color.White, 4f));
+ }
+
+ List shapePoints = new List();
+
+ 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 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 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 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 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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/simulations/balls/BallCollider/Program.cs b/simulations/balls/BallCollider/Program.cs
new file mode 100644
index 0000000..48e33b9
--- /dev/null
+++ b/simulations/balls/BallCollider/Program.cs
@@ -0,0 +1,16 @@
+namespace BallCollider;
+
+static class Program
+{
+ ///
+ /// The main entry point for the application.
+ ///
+ [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());
+ }
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/Camera.cs b/simulations/fluids/fluidsC#/Camera.cs
new file mode 100644
index 0000000..e02cafb
--- /dev/null
+++ b/simulations/fluids/fluidsC#/Camera.cs
@@ -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));
+ }
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/FluidSimulation.cs b/simulations/fluids/fluidsC#/FluidSimulation.cs
new file mode 100644
index 0000000..517ef2b
--- /dev/null
+++ b/simulations/fluids/fluidsC#/FluidSimulation.cs
@@ -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);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/FluidSimulation3D.cs b/simulations/fluids/fluidsC#/FluidSimulation3D.cs
new file mode 100644
index 0000000..629a1f0
--- /dev/null
+++ b/simulations/fluids/fluidsC#/FluidSimulation3D.cs
@@ -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);
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/FluidWindow.cs b/simulations/fluids/fluidsC#/FluidWindow.cs
new file mode 100644
index 0000000..5a02a42
--- /dev/null
+++ b/simulations/fluids/fluidsC#/FluidWindow.cs
@@ -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("• Click and drag to add fluid");
+ Console.WriteLine("• Right click to add velocity");
+ Console.WriteLine("• R key to reset simulation");
+ Console.WriteLine("• ESC to exit");
+ }
+
+ private void SetupShaders()
+ {
+ const string vertexShaderSource = @"
+ #version 330 core
+ layout (location = 0) in vec2 aPosition;
+ layout (location = 1) in vec2 aTexCoord;
+
+ out vec2 texCoord;
+
+ void main()
+ {
+ gl_Position = vec4(aPosition, 0.0, 1.0);
+ texCoord = aTexCoord;
+ }";
+
+ const string fragmentShaderSource = @"
+ #version 330 core
+ in vec2 texCoord;
+ out vec4 fragColor;
+
+ uniform sampler2D fluidTexture;
+
+ void main()
+ {
+ vec4 color = texture(fluidTexture, texCoord);
+ // Create a nice blue-to-cyan color gradient based on density
+ fragColor = vec4(color.r * 0.2, color.r * 0.5, color.r, 1.0);
+ }";
+
+ var vertexShader = GL.CreateShader(ShaderType.VertexShader);
+ GL.ShaderSource(vertexShader, vertexShaderSource);
+ GL.CompileShader(vertexShader);
+
+ var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
+ GL.ShaderSource(fragmentShader, fragmentShaderSource);
+ GL.CompileShader(fragmentShader);
+
+ _shaderProgram = GL.CreateProgram();
+ GL.AttachShader(_shaderProgram, vertexShader);
+ GL.AttachShader(_shaderProgram, fragmentShader);
+ GL.LinkProgram(_shaderProgram);
+
+ GL.DeleteShader(vertexShader);
+ GL.DeleteShader(fragmentShader);
+ }
+
+ private void SetupTexture()
+ {
+ _texture = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, _texture);
+
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
+
+ // Initialize with empty data
+ GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f,
+ _fluidSizeX, _fluidSizeY, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
+ }
+
+ private void SetupQuad()
+ {
+ float[] vertices = {
+ // positions // texture coords
+ -1.0f, 1.0f, 0.0f, 1.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f,
+ 1.0f, -1.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f
+ };
+
+ uint[] indices = {
+ 0, 1, 2,
+ 2, 3, 0
+ };
+
+ _vertexArrayObject = GL.GenVertexArray();
+ _vertexBufferObject = GL.GenBuffer();
+ _elementBufferObject = GL.GenBuffer();
+
+ GL.BindVertexArray(_vertexArrayObject);
+
+ GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
+ GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
+
+ GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
+ GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw);
+
+ // Position attribute
+ GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 0);
+ GL.EnableVertexAttribArray(0);
+
+ // Texture coordinate attribute
+ GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 2 * sizeof(float));
+ GL.EnableVertexAttribArray(1);
+ }
+
+ protected override void OnRenderFrame(FrameEventArgs e)
+ {
+ base.OnRenderFrame(e);
+
+ GL.Clear(ClearBufferMask.ColorBufferBit);
+
+ UpdateTextureFromFluid();
+
+ GL.UseProgram(_shaderProgram);
+ GL.BindTexture(TextureTarget.Texture2D, _texture);
+ GL.BindVertexArray(_vertexArrayObject);
+ GL.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, 0);
+
+ SwapBuffers();
+ }
+
+ protected override void OnUpdateFrame(FrameEventArgs e)
+ {
+ base.OnUpdateFrame(e);
+
+ // Update fluid simulation
+ _fluid.Step(0.1f, 0.0001f, 0.0001f);
+ _fluid.FadeDensity(0.002f);
+
+ Title = $"Fluid Simulation :3 - FPS: {1.0 / e.Time:0}";
+ }
+
+ private void UpdateTextureFromFluid()
+ {
+ // Convert fluid density to pixel data
+ for (int y = 0; y < _fluidSizeY; y++)
+ {
+ for (int x = 0; x < _fluidSizeX; x++)
+ {
+ int index = (y * _fluidSizeX + x) * 4;
+ float density = Math.Clamp(_fluid.GetDensity(x, y), 0, 1);
+
+ _pixels[index] = density; // R
+ _pixels[index + 1] = density; // G
+ _pixels[index + 2] = density; // B
+ _pixels[index + 3] = 1.0f; // A
+ }
+ }
+
+ GL.BindTexture(TextureTarget.Texture2D, _texture);
+ GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, _fluidSizeX, _fluidSizeY,
+ PixelFormat.Rgba, PixelType.Float, _pixels);
+ }
+
+ protected override void OnMouseDown(MouseButtonEventArgs e)
+ {
+ base.OnMouseDown(e);
+ _mouseDown = true;
+ _activeButton = e.Button;
+ _lastMousePos = new Vector2(MousePosition.X, MousePosition.Y);
+ }
+
+ protected override void OnMouseUp(MouseButtonEventArgs e)
+ {
+ base.OnMouseUp(e);
+ _mouseDown = false;
+ }
+
+ protected override void OnMouseMove(MouseMoveEventArgs e)
+ {
+ base.OnMouseMove(e);
+
+ if (_mouseDown)
+ {
+ var mousePos = new Vector2(e.X, e.Y);
+ var delta = mousePos - _lastMousePos;
+
+ int fluidX = (int)(e.X / Size.X * _fluidSizeX);
+ int fluidY = (int)((Size.Y - e.Y) / Size.Y * _fluidSizeY);
+
+ // Ensure coordinates are within bounds
+ fluidX = Math.Clamp(fluidX, 1, _fluidSizeX - 2);
+ fluidY = Math.Clamp(fluidY, 1, _fluidSizeY - 2);
+
+ if (_activeButton == MouseButton.Left)
+ {
+ // Add density in a small area
+ for (int i = -2; i <= 2; i++)
+ {
+ for (int j = -2; j <= 2; j++)
+ {
+ _fluid.AddDensity(fluidX + i, fluidY + j, 10.0f);
+ }
+ }
+ }
+ else if (_activeButton == MouseButton.Right)
+ {
+ // Add velocity
+ _fluid.AddVelocity(fluidX, fluidY, delta.X * 0.5f, -delta.Y * 0.5f);
+ }
+
+ _lastMousePos = mousePos;
+ }
+ }
+
+ protected override void OnKeyDown(KeyboardKeyEventArgs e)
+ {
+ base.OnKeyDown(e);
+
+ if (e.Key == Keys.Escape)
+ Close();
+ else if (e.Key == Keys.R)
+ {
+ _fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
+ Console.WriteLine("Simulation reset!");
+ }
+ }
+
+ protected override void OnResize(ResizeEventArgs e)
+ {
+ base.OnResize(e);
+ GL.Viewport(0, 0, e.Width, e.Height);
+ }
+
+ protected override void OnUnload()
+ {
+ GL.DeleteBuffer(_vertexBufferObject);
+ GL.DeleteBuffer(_elementBufferObject);
+ GL.DeleteVertexArray(_vertexArrayObject);
+ GL.DeleteProgram(_shaderProgram);
+ GL.DeleteTexture(_texture);
+
+ base.OnUnload();
+ }
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/FluidWindow3D_GPU.cs b/simulations/fluids/fluidsC#/FluidWindow3D_GPU.cs
new file mode 100644
index 0000000..956041e
--- /dev/null
+++ b/simulations/fluids/fluidsC#/FluidWindow3D_GPU.cs
@@ -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();
+
+ // 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;
+ }
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/Program.cs b/simulations/fluids/fluidsC#/Program.cs
new file mode 100644
index 0000000..7c0b774
--- /dev/null
+++ b/simulations/fluids/fluidsC#/Program.cs
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/simulations/fluids/fluidsC#/fluidsC#.csproj b/simulations/fluids/fluidsC#/fluidsC#.csproj
new file mode 100644
index 0000000..a2decc1
--- /dev/null
+++ b/simulations/fluids/fluidsC#/fluidsC#.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net9.0
+ fluidsC_
+ enable
+ enable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/simulations/fluids/pygl.py b/simulations/fluids/pygl.py
new file mode 100644
index 0000000..db2dbb0
--- /dev/null
+++ b/simulations/fluids/pygl.py
@@ -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()
diff --git a/simulations/mandelbrotset/cpp/readme.md b/simulations/mandelbrotset/cpp/readme.md
new file mode 100644
index 0000000..9ed047e
--- /dev/null
+++ b/simulations/mandelbrotset/cpp/readme.md
@@ -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
+```
\ No newline at end of file
| | | | | | | | | | | | | |