From cb1b91ba0fb89e6b884508de98d306b09bd9dee4 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Tue, 21 Oct 2025 07:44:55 +0200 Subject: [PATCH] some C# cause why not. C# guy might like this? idk. some python too. mostly simulations. WindowsAPI C# is good i guess --- .gitignore | 134 +++ INF6B.sln | 59 ++ PLF/üben1.py | 27 + .../CubeCollision/CubeCollision.csproj | 11 + simulations/CubeCollision/Form1.Designer.cs | 38 + simulations/CubeCollision/Form1.cs | 9 + simulations/CubeCollision/Program.cs | 550 ++++++++++++ simulations/astar/astar/Program.cs | 316 +++++++ simulations/astar/astar/astar.csproj | 9 + simulations/astar/main.py | 2 +- .../balls/BallCollider/BallCollider.csproj | 11 + .../balls/BallCollider/Form1.Designer.cs | 21 + simulations/balls/BallCollider/Form1.cs | 743 +++++++++++++++ simulations/balls/BallCollider/Program.cs | 16 + simulations/fluids/fluidsC#/Camera.cs | 50 ++ .../fluids/fluidsC#/FluidSimulation.cs | 188 ++++ .../fluids/fluidsC#/FluidSimulation3D.cs | 313 +++++++ simulations/fluids/fluidsC#/FluidWindow.cs | 276 ++++++ .../fluids/fluidsC#/FluidWindow3D_GPU.cs | 844 ++++++++++++++++++ simulations/fluids/fluidsC#/Program.cs | 26 + simulations/fluids/fluidsC#/fluidsC#.csproj | 15 + simulations/fluids/pygl.py | 326 +++++++ simulations/mandelbrotset/cpp/readme.md | 5 + 23 files changed, 3988 insertions(+), 1 deletion(-) create mode 100644 INF6B.sln create mode 100644 PLF/üben1.py create mode 100644 simulations/CubeCollision/CubeCollision.csproj create mode 100644 simulations/CubeCollision/Form1.Designer.cs create mode 100644 simulations/CubeCollision/Form1.cs create mode 100644 simulations/CubeCollision/Program.cs create mode 100644 simulations/astar/astar/Program.cs create mode 100644 simulations/astar/astar/astar.csproj create mode 100644 simulations/balls/BallCollider/BallCollider.csproj create mode 100644 simulations/balls/BallCollider/Form1.Designer.cs create mode 100644 simulations/balls/BallCollider/Form1.cs create mode 100644 simulations/balls/BallCollider/Program.cs create mode 100644 simulations/fluids/fluidsC#/Camera.cs create mode 100644 simulations/fluids/fluidsC#/FluidSimulation.cs create mode 100644 simulations/fluids/fluidsC#/FluidSimulation3D.cs create mode 100644 simulations/fluids/fluidsC#/FluidWindow.cs create mode 100644 simulations/fluids/fluidsC#/FluidWindow3D_GPU.cs create mode 100644 simulations/fluids/fluidsC#/Program.cs create mode 100644 simulations/fluids/fluidsC#/fluidsC#.csproj create mode 100644 simulations/fluids/pygl.py create mode 100644 simulations/mandelbrotset/cpp/readme.md 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