some C# cause why not. C# guy might like this? idk. some python too. mostly simulations. WindowsAPI C# is good i guess
This commit is contained in:
134
.gitignore
vendored
134
.gitignore
vendored
@@ -75,3 +75,137 @@ out/
|
||||
.pyre/
|
||||
.dmypy.json
|
||||
.pytype/
|
||||
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.sln.docstates
|
||||
|
||||
# Build results
|
||||
|
||||
[Dd]ebug/
|
||||
[Rr]elease/
|
||||
x64/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.log
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.Publish.xml
|
||||
*.pubxml
|
||||
*.azurePubxml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
packages/
|
||||
## TODO: If the tool you use requires repositories.config, also uncomment the next line
|
||||
!packages/repositories.config
|
||||
|
||||
# Windows Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Windows Store app package directory
|
||||
AppPackages/
|
||||
|
||||
# Others
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
![Ss]tyle[Cc]op.targets
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.[Pp]ublish.xml
|
||||
|
||||
*.publishsettings
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file to a newer
|
||||
# Visual Studio version. Backup files are not needed, because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
App_Data/*.mdf
|
||||
App_Data/*.ldf
|
||||
|
||||
# =========================
|
||||
# Windows detritus
|
||||
# =========================
|
||||
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Mac desktop service store files
|
||||
.DS_Store
|
||||
|
||||
_NCrunch*
|
||||
59
INF6B.sln
Normal file
59
INF6B.sln
Normal file
@@ -0,0 +1,59 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.2.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "simulations", "simulations", "{860328CB-9D8E-3E66-369A-41D2C4307385}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CubeCollision", "simulations\CubeCollision\CubeCollision.csproj", "{99A8A725-8377-1857-FB92-72D23CA3C7D2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "astar", "astar", "{41A3C625-1BCD-6A6C-6778-B66D9E0ECDCA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "astar", "simulations\astar\astar\astar.csproj", "{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "balls", "balls", "{762C3F1A-CDAA-3083-CDC8-BF35A4FFDB27}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BallCollider", "simulations\balls\BallCollider\BallCollider.csproj", "{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fluids", "fluids", "{6AF4715D-5BE8-8629-09F4-E179AEE0D848}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fluidsC#", "simulations\fluids\fluidsC#\fluidsC#.csproj", "{68981932-75A1-3464-E410-93D7C401DBEB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{99A8A725-8377-1857-FB92-72D23CA3C7D2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{68981932-75A1-3464-E410-93D7C401DBEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{68981932-75A1-3464-E410-93D7C401DBEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{68981932-75A1-3464-E410-93D7C401DBEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{68981932-75A1-3464-E410-93D7C401DBEB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{99A8A725-8377-1857-FB92-72D23CA3C7D2} = {860328CB-9D8E-3E66-369A-41D2C4307385}
|
||||
{41A3C625-1BCD-6A6C-6778-B66D9E0ECDCA} = {860328CB-9D8E-3E66-369A-41D2C4307385}
|
||||
{A151DDB9-0A46-0243-EBC0-FC48BFE6D231} = {41A3C625-1BCD-6A6C-6778-B66D9E0ECDCA}
|
||||
{762C3F1A-CDAA-3083-CDC8-BF35A4FFDB27} = {860328CB-9D8E-3E66-369A-41D2C4307385}
|
||||
{DC5E3D77-CC43-42C6-BDFE-CE34754CB077} = {762C3F1A-CDAA-3083-CDC8-BF35A4FFDB27}
|
||||
{6AF4715D-5BE8-8629-09F4-E179AEE0D848} = {860328CB-9D8E-3E66-369A-41D2C4307385}
|
||||
{68981932-75A1-3464-E410-93D7C401DBEB} = {6AF4715D-5BE8-8629-09F4-E179AEE0D848}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B8AE37F2-BCF9-4338-B055-602CB2A66DBE}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
27
PLF/üben1.py
Normal file
27
PLF/üben1.py
Normal file
@@ -0,0 +1,27 @@
|
||||
land=["Argentinien","Bolivien", "Brasilien","Chile","Ecuador","Guyana","Kolumbien","Paraguay","Peru","Surimane"]
|
||||
hdi=[0.83,0.66,0.76,0.83,0.73,0.64,0.72,0.68,0.73,0.71]
|
||||
|
||||
# Variables
|
||||
max_index = hdi.index(max(hdi))
|
||||
min_index = hdi.index(min(hdi))
|
||||
max_value = max(hdi)
|
||||
min_value = min(hdi)
|
||||
|
||||
land_max = land[max_index]
|
||||
land_min = land[min_index]
|
||||
|
||||
|
||||
print(f"""
|
||||
HDI - Auswertung
|
||||
{"-"*20}
|
||||
Max:
|
||||
Index: {max_index} => {max_value},
|
||||
{land_max}
|
||||
{"-"*20}
|
||||
Min:
|
||||
Index: {min_index} => {min_value},
|
||||
{land_min}
|
||||
{"-"*20}
|
||||
mittelwert:
|
||||
{sum(hdi) / len(hdi)}
|
||||
""")
|
||||
11
simulations/CubeCollision/CubeCollision.csproj
Normal file
11
simulations/CubeCollision/CubeCollision.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
38
simulations/CubeCollision/Form1.Designer.cs
generated
Normal file
38
simulations/CubeCollision/Form1.Designer.cs
generated
Normal file
@@ -0,0 +1,38 @@
|
||||
namespace CubeCollision;
|
||||
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Text = "Form1";
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
9
simulations/CubeCollision/Form1.cs
Normal file
9
simulations/CubeCollision/Form1.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace CubeCollision;
|
||||
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
550
simulations/CubeCollision/Program.cs
Normal file
550
simulations/CubeCollision/Program.cs
Normal file
@@ -0,0 +1,550 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Media;
|
||||
using System.Windows.Forms;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace CubeCollisionSimulator
|
||||
{
|
||||
public class Cube
|
||||
{
|
||||
public float X { get; set; }
|
||||
public float Y { get; set; }
|
||||
public float VelocityX { get; set; }
|
||||
public float VelocityY { get; set; }
|
||||
public float Size { get; set; }
|
||||
public float Mass { get; set; }
|
||||
public Color Color { get; set; }
|
||||
public bool IsDragging { get; set; }
|
||||
|
||||
public Cube(float x, float y, float size, float mass, Color color)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Size = size;
|
||||
Mass = mass;
|
||||
Color = color;
|
||||
VelocityX = 0;
|
||||
VelocityY = 0;
|
||||
IsDragging = false;
|
||||
}
|
||||
|
||||
public RectangleF GetBounds()
|
||||
{
|
||||
return new RectangleF(X, Y, Size, Size);
|
||||
}
|
||||
|
||||
public bool Contains(Point point)
|
||||
{
|
||||
return GetBounds().Contains(point);
|
||||
}
|
||||
}
|
||||
|
||||
public class SimulatorForm : Form
|
||||
{
|
||||
private readonly List<Cube> cubes;
|
||||
private readonly Timer timer;
|
||||
private Cube? draggedCube;
|
||||
private Point lastMousePos;
|
||||
|
||||
// Physics parameters
|
||||
private float gravity = 0.5f;
|
||||
private float restitution = 0.8f;
|
||||
private float friction = 0.98f;
|
||||
private float forceMultiplier = 0.3f;
|
||||
|
||||
// Sound management
|
||||
private DateTime lastSoundTime = DateTime.MinValue;
|
||||
private const int MinSoundIntervalMs = 50; // Minimum time between sounds
|
||||
private int soundCooldownCounter = 0;
|
||||
private CheckBox soundEnabledCheckbox;
|
||||
|
||||
// UI Controls
|
||||
private TrackBar? gravitySlider;
|
||||
private TrackBar? restitutionSlider;
|
||||
private TrackBar? frictionSlider;
|
||||
private TrackBar? forceSlider;
|
||||
private Label? gravityLabel;
|
||||
private Label? restitutionLabel;
|
||||
private Label? frictionLabel;
|
||||
private Label? forceLabel;
|
||||
private Button? addCubeButton;
|
||||
private Button? clearButton;
|
||||
private Panel? controlPanel;
|
||||
|
||||
public SimulatorForm()
|
||||
{
|
||||
this.Text = "2D Cube Collision Simulator";
|
||||
this.Size = new Size(1200, 800);
|
||||
this.DoubleBuffered = true;
|
||||
this.BackColor = Color.FromArgb(30, 30, 30);
|
||||
|
||||
cubes = new List<Cube>();
|
||||
soundEnabledCheckbox = new CheckBox();
|
||||
|
||||
// Add initial cubes
|
||||
Random rand = new Random();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
cubes.Add(new Cube(
|
||||
rand.Next(100, 800),
|
||||
rand.Next(100, 400),
|
||||
50 + rand.Next(30),
|
||||
1 + rand.Next(5),
|
||||
Color.FromArgb(rand.Next(100, 255), rand.Next(100, 255), rand.Next(100, 255))
|
||||
));
|
||||
}
|
||||
|
||||
SetupControls();
|
||||
|
||||
timer = new Timer();
|
||||
timer.Interval = 16; // ~60 FPS
|
||||
timer.Tick += Update;
|
||||
timer.Start();
|
||||
|
||||
this.Paint += OnPaint;
|
||||
this.MouseDown += OnMouseDown;
|
||||
this.MouseMove += OnMouseMove;
|
||||
this.MouseUp += OnMouseUp;
|
||||
}
|
||||
|
||||
private void SetupControls()
|
||||
{
|
||||
controlPanel = new Panel
|
||||
{
|
||||
Dock = DockStyle.Right,
|
||||
Width = 250,
|
||||
BackColor = Color.FromArgb(45, 45, 45),
|
||||
Padding = new Padding(10)
|
||||
};
|
||||
|
||||
int yPos = 20;
|
||||
|
||||
// Sound toggle
|
||||
soundEnabledCheckbox = new CheckBox
|
||||
{
|
||||
Text = "Enable Collision Sound",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 25),
|
||||
ForeColor = Color.White,
|
||||
Checked = false
|
||||
};
|
||||
controlPanel.Controls.Add(soundEnabledCheckbox);
|
||||
yPos += 40;
|
||||
|
||||
// Gravity control
|
||||
gravityLabel = new Label
|
||||
{
|
||||
Text = $"Gravity: {gravity:F2}",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 20),
|
||||
ForeColor = Color.White
|
||||
};
|
||||
controlPanel.Controls.Add(gravityLabel);
|
||||
yPos += 25;
|
||||
|
||||
gravitySlider = new TrackBar
|
||||
{
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 45),
|
||||
Minimum = 0,
|
||||
Maximum = 100,
|
||||
Value = (int)(gravity * 10),
|
||||
TickFrequency = 10
|
||||
};
|
||||
gravitySlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
gravity = gravitySlider?.Value / 10f ?? 0.5f;
|
||||
if (gravityLabel != null)
|
||||
gravityLabel.Text = $"Gravity: {gravity:F2}";
|
||||
};
|
||||
controlPanel.Controls.Add(gravitySlider);
|
||||
yPos += 60;
|
||||
|
||||
// Restitution control
|
||||
restitutionLabel = new Label
|
||||
{
|
||||
Text = $"Bounciness: {restitution:F2}",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 20),
|
||||
ForeColor = Color.White
|
||||
};
|
||||
controlPanel.Controls.Add(restitutionLabel);
|
||||
yPos += 25;
|
||||
|
||||
restitutionSlider = new TrackBar
|
||||
{
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 45),
|
||||
Minimum = 0,
|
||||
Maximum = 100,
|
||||
Value = (int)(restitution * 100),
|
||||
TickFrequency = 10
|
||||
};
|
||||
restitutionSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
restitution = restitutionSlider?.Value / 100f ?? 0.8f;
|
||||
if (restitutionLabel != null)
|
||||
restitutionLabel.Text = $"Bounciness: {restitution:F2}";
|
||||
};
|
||||
controlPanel.Controls.Add(restitutionSlider);
|
||||
yPos += 60;
|
||||
|
||||
// Friction control
|
||||
frictionLabel = new Label
|
||||
{
|
||||
Text = $"Friction: {friction:F2}",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 20),
|
||||
ForeColor = Color.White
|
||||
};
|
||||
controlPanel.Controls.Add(frictionLabel);
|
||||
yPos += 25;
|
||||
|
||||
frictionSlider = new TrackBar
|
||||
{
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 45),
|
||||
Minimum = 90,
|
||||
Maximum = 100,
|
||||
Value = (int)(friction * 100),
|
||||
TickFrequency = 1
|
||||
};
|
||||
frictionSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
friction = frictionSlider?.Value / 100f ?? 0.98f;
|
||||
if (frictionLabel != null)
|
||||
frictionLabel.Text = $"Friction: {friction:F2}";
|
||||
};
|
||||
controlPanel.Controls.Add(frictionSlider);
|
||||
yPos += 60;
|
||||
|
||||
// Force control
|
||||
forceLabel = new Label
|
||||
{
|
||||
Text = $"Drag Force: {forceMultiplier:F2}",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 20),
|
||||
ForeColor = Color.White
|
||||
};
|
||||
controlPanel.Controls.Add(forceLabel);
|
||||
yPos += 25;
|
||||
|
||||
forceSlider = new TrackBar
|
||||
{
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 45),
|
||||
Minimum = 1,
|
||||
Maximum = 50,
|
||||
Value = (int)(forceMultiplier * 10),
|
||||
TickFrequency = 5
|
||||
};
|
||||
forceSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
forceMultiplier = forceSlider?.Value / 10f ?? 0.3f;
|
||||
if (forceLabel != null)
|
||||
forceLabel.Text = $"Drag Force: {forceMultiplier:F2}";
|
||||
};
|
||||
controlPanel.Controls.Add(forceSlider);
|
||||
yPos += 60;
|
||||
|
||||
// Add cube button
|
||||
addCubeButton = new Button
|
||||
{
|
||||
Text = "Add Cube",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 35),
|
||||
BackColor = Color.FromArgb(70, 130, 180),
|
||||
ForeColor = Color.White,
|
||||
FlatStyle = FlatStyle.Flat
|
||||
};
|
||||
addCubeButton.Click += (s, e) => AddRandomCube();
|
||||
controlPanel.Controls.Add(addCubeButton);
|
||||
yPos += 45;
|
||||
|
||||
// Clear button
|
||||
clearButton = new Button
|
||||
{
|
||||
Text = "Clear All",
|
||||
Location = new Point(10, yPos),
|
||||
Size = new Size(230, 35),
|
||||
BackColor = Color.FromArgb(180, 70, 70),
|
||||
ForeColor = Color.White,
|
||||
FlatStyle = FlatStyle.Flat
|
||||
};
|
||||
clearButton.Click += (s, e) => cubes.Clear();
|
||||
controlPanel.Controls.Add(clearButton);
|
||||
yPos += 45;
|
||||
|
||||
// Instructions
|
||||
Label instructionsLabel = new Label
|
||||
{
|
||||
Text = "Instructions:\n\n<> Click and drag cubes\n<> Throw them by dragging\n<> Watch them collide!\n<> Adjust physics sliders\n<> Add more cubes\n<> Toggle sound on/off",
|
||||
Location = new Point(10, yPos + 20),
|
||||
Size = new Size(230, 200),
|
||||
ForeColor = Color.LightGray
|
||||
};
|
||||
controlPanel.Controls.Add(instructionsLabel);
|
||||
|
||||
this.Controls.Add(controlPanel);
|
||||
}
|
||||
|
||||
private void AddRandomCube()
|
||||
{
|
||||
Random rand = new Random();
|
||||
cubes.Add(new Cube(
|
||||
rand.Next(100, this.ClientSize.Width - 350),
|
||||
rand.Next(100, 300),
|
||||
40 + rand.Next(40),
|
||||
1 + rand.Next(5),
|
||||
Color.FromArgb(rand.Next(100, 255), rand.Next(100, 255), rand.Next(100, 255))
|
||||
));
|
||||
}
|
||||
|
||||
private void PlayCollisionSound()
|
||||
{
|
||||
if (!soundEnabledCheckbox.Checked)
|
||||
return;
|
||||
|
||||
// Cooldown to prevent too many sounds
|
||||
if (soundCooldownCounter > 0)
|
||||
{
|
||||
soundCooldownCounter--;
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
if ((now - lastSoundTime).TotalMilliseconds < MinSoundIntervalMs)
|
||||
return;
|
||||
|
||||
lastSoundTime = now;
|
||||
soundCooldownCounter = 3; // Skip next 3 collision sounds
|
||||
|
||||
try
|
||||
{
|
||||
// Shorter, quieter beep
|
||||
System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Beep(400, 30); // Lower frequency, shorter duration
|
||||
}
|
||||
catch { }
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void OnMouseDown(object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.X > this.ClientSize.Width - 250) return;
|
||||
|
||||
foreach (var cube in cubes.OrderByDescending(c => c.Size))
|
||||
{
|
||||
if (cube.Contains(e.Location))
|
||||
{
|
||||
draggedCube = cube;
|
||||
draggedCube.IsDragging = true;
|
||||
lastMousePos = e.Location;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseMove(object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (draggedCube != null && e.Button == MouseButtons.Left)
|
||||
{
|
||||
float dx = e.X - lastMousePos.X;
|
||||
float dy = e.Y - lastMousePos.Y;
|
||||
|
||||
draggedCube.X += dx;
|
||||
draggedCube.Y += dy;
|
||||
|
||||
draggedCube.VelocityX = dx * forceMultiplier;
|
||||
draggedCube.VelocityY = dy * forceMultiplier;
|
||||
|
||||
lastMousePos = e.Location;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseUp(object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (draggedCube != null)
|
||||
{
|
||||
draggedCube.IsDragging = false;
|
||||
draggedCube = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update(object? sender, EventArgs e)
|
||||
{
|
||||
// Optimized physics loop
|
||||
int cubeCount = cubes.Count;
|
||||
|
||||
for (int i = 0; i < cubeCount; i++)
|
||||
{
|
||||
var cube = cubes[i];
|
||||
|
||||
if (!cube.IsDragging)
|
||||
{
|
||||
// Apply gravity
|
||||
cube.VelocityY += gravity;
|
||||
|
||||
// Apply friction
|
||||
cube.VelocityX *= friction;
|
||||
cube.VelocityY *= friction;
|
||||
|
||||
// Update position
|
||||
cube.X += cube.VelocityX;
|
||||
cube.Y += cube.VelocityY;
|
||||
|
||||
// Wall collisions
|
||||
int rightBoundary = this.ClientSize.Width - 250;
|
||||
|
||||
if (cube.X <= 0)
|
||||
{
|
||||
cube.X = 0;
|
||||
cube.VelocityX = -cube.VelocityX * restitution;
|
||||
PlayCollisionSound();
|
||||
}
|
||||
else if (cube.X + cube.Size >= rightBoundary)
|
||||
{
|
||||
cube.X = rightBoundary - cube.Size;
|
||||
cube.VelocityX = -cube.VelocityX * restitution;
|
||||
PlayCollisionSound();
|
||||
}
|
||||
|
||||
if (cube.Y <= 0)
|
||||
{
|
||||
cube.Y = 0;
|
||||
cube.VelocityY = -cube.VelocityY * restitution;
|
||||
PlayCollisionSound();
|
||||
}
|
||||
else if (cube.Y + cube.Size >= this.ClientSize.Height)
|
||||
{
|
||||
cube.Y = this.ClientSize.Height - cube.Size;
|
||||
cube.VelocityY = -cube.VelocityY * restitution;
|
||||
PlayCollisionSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimized cube-to-cube collisions
|
||||
for (int i = 0; i < cubeCount; i++)
|
||||
{
|
||||
for (int j = i + 1; j < cubeCount; j++)
|
||||
{
|
||||
if (CheckCollision(cubes[i], cubes[j]))
|
||||
{
|
||||
ResolveCollision(cubes[i], cubes[j]);
|
||||
PlayCollisionSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.Invalidate();
|
||||
}
|
||||
|
||||
private bool CheckCollision(Cube a, Cube b)
|
||||
{
|
||||
return a.GetBounds().IntersectsWith(b.GetBounds());
|
||||
}
|
||||
|
||||
private void ResolveCollision(Cube a, Cube b)
|
||||
{
|
||||
// Calculate centers
|
||||
float aCenterX = a.X + a.Size / 2;
|
||||
float aCenterY = a.Y + a.Size / 2;
|
||||
float bCenterX = b.X + b.Size / 2;
|
||||
float bCenterY = b.Y + b.Size / 2;
|
||||
|
||||
// Collision normal
|
||||
float dx = bCenterX - aCenterX;
|
||||
float dy = bCenterY - aCenterY;
|
||||
float distance = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance == 0) return;
|
||||
|
||||
float nx = dx / distance;
|
||||
float ny = dy / distance;
|
||||
|
||||
// Separate cubes
|
||||
float overlap = (a.Size + b.Size) / 2 - distance;
|
||||
if (overlap > 0)
|
||||
{
|
||||
float separationX = nx * overlap * 0.5f;
|
||||
float separationY = ny * overlap * 0.5f;
|
||||
|
||||
a.X -= separationX;
|
||||
a.Y -= separationY;
|
||||
b.X += separationX;
|
||||
b.Y += separationY;
|
||||
}
|
||||
|
||||
// Relative velocity
|
||||
float dvx = b.VelocityX - a.VelocityX;
|
||||
float dvy = b.VelocityY - a.VelocityY;
|
||||
|
||||
// Velocity along normal
|
||||
float velAlongNormal = dvx * nx + dvy * ny;
|
||||
|
||||
if (velAlongNormal > 0) return;
|
||||
|
||||
// Calculate impulse
|
||||
float impulse = -(1 + restitution) * velAlongNormal;
|
||||
impulse /= (1 / a.Mass + 1 / b.Mass);
|
||||
|
||||
// Apply impulse
|
||||
float impulseX = impulse * nx;
|
||||
float impulseY = impulse * ny;
|
||||
|
||||
a.VelocityX -= impulseX / a.Mass;
|
||||
a.VelocityY -= impulseY / a.Mass;
|
||||
b.VelocityX += impulseX / b.Mass;
|
||||
b.VelocityY += impulseY / b.Mass;
|
||||
}
|
||||
|
||||
private void OnPaint(object? sender, PaintEventArgs e)
|
||||
{
|
||||
Graphics g = e.Graphics;
|
||||
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
|
||||
|
||||
foreach (var cube in cubes)
|
||||
{
|
||||
using (SolidBrush brush = new SolidBrush(cube.Color))
|
||||
{
|
||||
g.FillRectangle(brush, cube.GetBounds());
|
||||
}
|
||||
|
||||
if (cube.IsDragging)
|
||||
{
|
||||
using (Pen pen = new Pen(Color.Yellow, 3))
|
||||
{
|
||||
g.DrawRectangle(pen, cube.X, cube.Y, cube.Size, cube.Size);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw mass indicator
|
||||
using (Font font = new Font("Arial", 8))
|
||||
using (SolidBrush textBrush = new SolidBrush(Color.White))
|
||||
{
|
||||
string massText = $"M:{cube.Mass}";
|
||||
g.DrawString(massText, font, textBrush, cube.X + 5, cube.Y + 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new SimulatorForm());
|
||||
}
|
||||
}
|
||||
}
|
||||
316
simulations/astar/astar/Program.cs
Normal file
316
simulations/astar/astar/Program.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace MazePathfinding
|
||||
{
|
||||
public class Cell
|
||||
{
|
||||
public int Row { get; set; }
|
||||
public int Col { get; set; }
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public bool[] Walls { get; set; } = new bool[4]; // top, right, bottom, left
|
||||
public bool Visited { get; set; }
|
||||
|
||||
public Cell(int row, int col, int cellSize)
|
||||
{
|
||||
Row = row;
|
||||
Col = col;
|
||||
X = col * cellSize;
|
||||
Y = row * cellSize;
|
||||
for (int i = 0; i < 4; i++) Walls[i] = true;
|
||||
Visited = false;
|
||||
}
|
||||
|
||||
public void Draw(Graphics g, int cellSize)
|
||||
{
|
||||
if (Visited)
|
||||
{
|
||||
using (var brush = new SolidBrush(Color.White))
|
||||
g.FillRectangle(brush, X, Y, cellSize, cellSize);
|
||||
}
|
||||
|
||||
using (var pen = new Pen(Color.Black, 2))
|
||||
{
|
||||
if (Walls[0]) g.DrawLine(pen, X, Y, X + cellSize, Y);
|
||||
if (Walls[1]) g.DrawLine(pen, X + cellSize, Y, X + cellSize, Y + cellSize);
|
||||
if (Walls[2]) g.DrawLine(pen, X + cellSize, Y + cellSize, X, Y + cellSize);
|
||||
if (Walls[3]) g.DrawLine(pen, X, Y + cellSize, X, Y);
|
||||
}
|
||||
}
|
||||
|
||||
public void Highlight(Graphics g, Color color, int cellSize)
|
||||
{
|
||||
using (var brush = new SolidBrush(color))
|
||||
g.FillRectangle(brush, X, Y, cellSize, cellSize);
|
||||
}
|
||||
}
|
||||
|
||||
public class MazeForm : Form
|
||||
{
|
||||
private const int WIDTH = 600;
|
||||
private int rows = 20;
|
||||
private int cellSize;
|
||||
private Cell[,] grid;
|
||||
private Cell start;
|
||||
private Cell end;
|
||||
private List<Cell> path = new List<Cell>();
|
||||
private HashSet<Cell> openSet = new HashSet<Cell>();
|
||||
private System.Windows.Forms.Timer timer;
|
||||
private bool isSearching = false;
|
||||
|
||||
public MazeForm()
|
||||
{
|
||||
Text = "A* Maze Pathfinding - Press SPACE to solve, UP/DOWN to resize";
|
||||
ClientSize = new Size(WIDTH, WIDTH);
|
||||
DoubleBuffered = true;
|
||||
KeyPreview = true;
|
||||
FormBorderStyle = FormBorderStyle.FixedSingle;
|
||||
MaximizeBox = false;
|
||||
|
||||
cellSize = WIDTH / rows;
|
||||
CreateMaze(rows);
|
||||
|
||||
timer = new System.Windows.Forms.Timer { Interval = 16 };
|
||||
timer.Tick += (s, e) => Invalidate();
|
||||
timer.Start();
|
||||
|
||||
this.KeyDown += MazeForm_KeyDown;
|
||||
this.Paint += MazeForm_Paint;
|
||||
}
|
||||
|
||||
private void CreateMaze(int r)
|
||||
{
|
||||
rows = r;
|
||||
cellSize = WIDTH / rows;
|
||||
grid = new Cell[rows, rows];
|
||||
|
||||
for (int i = 0; i < rows; i++)
|
||||
for (int j = 0; j < rows; j++)
|
||||
grid[i, j] = new Cell(i, j, cellSize);
|
||||
|
||||
GenerateMaze(grid[0, 0]);
|
||||
start = grid[0, 0];
|
||||
end = grid[rows - 1, rows - 1];
|
||||
path.Clear();
|
||||
openSet.Clear();
|
||||
}
|
||||
|
||||
private void GenerateMaze(Cell current)
|
||||
{
|
||||
var stack = new Stack<Cell>();
|
||||
current.Visited = true;
|
||||
var random = new Random();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var neighbors = new List<Cell>();
|
||||
int r = current.Row, c = current.Col;
|
||||
|
||||
if (r > 0 && !grid[r - 1, c].Visited) neighbors.Add(grid[r - 1, c]);
|
||||
if (r < rows - 1 && !grid[r + 1, c].Visited) neighbors.Add(grid[r + 1, c]);
|
||||
if (c > 0 && !grid[r, c - 1].Visited) neighbors.Add(grid[r, c - 1]);
|
||||
if (c < rows - 1 && !grid[r, c + 1].Visited) neighbors.Add(grid[r, c + 1]);
|
||||
|
||||
if (neighbors.Count > 0)
|
||||
{
|
||||
var nextCell = neighbors[random.Next(neighbors.Count)];
|
||||
stack.Push(current);
|
||||
RemoveWalls(current, nextCell);
|
||||
nextCell.Visited = true;
|
||||
current = nextCell;
|
||||
}
|
||||
else if (stack.Count > 0)
|
||||
{
|
||||
current = stack.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveWalls(Cell a, Cell b)
|
||||
{
|
||||
int dx = a.Col - b.Col;
|
||||
int dy = a.Row - b.Row;
|
||||
|
||||
if (dx == 1)
|
||||
{
|
||||
a.Walls[3] = false;
|
||||
b.Walls[1] = false;
|
||||
}
|
||||
else if (dx == -1)
|
||||
{
|
||||
a.Walls[1] = false;
|
||||
b.Walls[3] = false;
|
||||
}
|
||||
|
||||
if (dy == 1)
|
||||
{
|
||||
a.Walls[0] = false;
|
||||
b.Walls[2] = false;
|
||||
}
|
||||
else if (dy == -1)
|
||||
{
|
||||
a.Walls[2] = false;
|
||||
b.Walls[0] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async void MazeForm_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (isSearching) return;
|
||||
|
||||
if (e.KeyCode == Keys.Space)
|
||||
{
|
||||
isSearching = true;
|
||||
path = await Task.Run(() => AStar(start, end));
|
||||
isSearching = false;
|
||||
Invalidate();
|
||||
}
|
||||
else if (e.KeyCode == Keys.Up)
|
||||
{
|
||||
CreateMaze(rows + 1);
|
||||
Invalidate();
|
||||
}
|
||||
else if (e.KeyCode == Keys.Down && rows > 5)
|
||||
{
|
||||
CreateMaze(rows - 1);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private int Heuristic(Cell a, Cell b)
|
||||
{
|
||||
return Math.Abs(a.Row - b.Row) + Math.Abs(a.Col - b.Col);
|
||||
}
|
||||
|
||||
private List<Cell> AStar(Cell startCell, Cell endCell)
|
||||
{
|
||||
var openSetQueue = new SortedSet<(int f, int count, Cell cell)>(
|
||||
Comparer<(int f, int count, Cell cell)>.Create((a, b) =>
|
||||
{
|
||||
int cmp = a.f.CompareTo(b.f);
|
||||
return cmp != 0 ? cmp : a.count.CompareTo(b.count);
|
||||
}));
|
||||
|
||||
int count = 0;
|
||||
openSetQueue.Add((0, count, startCell));
|
||||
var cameFrom = new Dictionary<Cell, Cell>();
|
||||
var gScore = new Dictionary<Cell, int>();
|
||||
var fScore = new Dictionary<Cell, int>();
|
||||
|
||||
for (int i = 0; i < rows; i++)
|
||||
for (int j = 0; j < rows; j++)
|
||||
{
|
||||
gScore[grid[i, j]] = int.MaxValue;
|
||||
fScore[grid[i, j]] = int.MaxValue;
|
||||
}
|
||||
|
||||
gScore[startCell] = 0;
|
||||
fScore[startCell] = Heuristic(startCell, endCell);
|
||||
|
||||
openSet.Clear();
|
||||
openSet.Add(startCell);
|
||||
|
||||
while (openSetQueue.Count > 0)
|
||||
{
|
||||
var current = openSetQueue.Min.cell;
|
||||
openSetQueue.Remove(openSetQueue.Min);
|
||||
openSet.Remove(current);
|
||||
|
||||
if (current == endCell)
|
||||
{
|
||||
var pathResult = new List<Cell>();
|
||||
while (cameFrom.ContainsKey(current))
|
||||
{
|
||||
current = cameFrom[current];
|
||||
pathResult.Add(current);
|
||||
}
|
||||
openSet.Clear();
|
||||
return pathResult;
|
||||
}
|
||||
|
||||
foreach (var neighbor in GetNeighbors(current))
|
||||
{
|
||||
int tempG = gScore[current] + 1;
|
||||
if (tempG < gScore[neighbor])
|
||||
{
|
||||
cameFrom[neighbor] = current;
|
||||
gScore[neighbor] = tempG;
|
||||
fScore[neighbor] = tempG + Heuristic(neighbor, endCell);
|
||||
|
||||
if (!openSet.Contains(neighbor))
|
||||
{
|
||||
count++;
|
||||
openSetQueue.Add((fScore[neighbor], count, neighbor));
|
||||
openSet.Add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Invoke(new Action(() => Invalidate()));
|
||||
}
|
||||
catch { }
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
openSet.Clear();
|
||||
return new List<Cell>();
|
||||
}
|
||||
|
||||
private List<Cell> GetNeighbors(Cell cell)
|
||||
{
|
||||
var neighbors = new List<Cell>();
|
||||
int r = cell.Row, c = cell.Col;
|
||||
|
||||
if (!cell.Walls[0] && r > 0) neighbors.Add(grid[r - 1, c]);
|
||||
if (!cell.Walls[1] && c < rows - 1) neighbors.Add(grid[r, c + 1]);
|
||||
if (!cell.Walls[2] && r < rows - 1) neighbors.Add(grid[r + 1, c]);
|
||||
if (!cell.Walls[3] && c > 0) neighbors.Add(grid[r, c - 1]);
|
||||
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
private void MazeForm_Paint(object sender, PaintEventArgs e)
|
||||
{
|
||||
var g = e.Graphics;
|
||||
g.Clear(Color.White);
|
||||
|
||||
for (int i = 0; i < rows; i++)
|
||||
for (int j = 0; j < rows; j++)
|
||||
grid[i, j].Draw(g, cellSize);
|
||||
|
||||
foreach (var cell in openSet)
|
||||
cell.Highlight(g, Color.LimeGreen, cellSize);
|
||||
|
||||
foreach (var cell in path)
|
||||
cell.Highlight(g, Color.Yellow, cellSize);
|
||||
|
||||
start.Highlight(g, Color.Blue, cellSize);
|
||||
end.Highlight(g, Color.Red, cellSize);
|
||||
|
||||
// Redraw walls over highlights
|
||||
for (int i = 0; i < rows; i++)
|
||||
for (int j = 0; j < rows; j++)
|
||||
grid[i, j].Draw(g, cellSize);
|
||||
}
|
||||
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MazeForm());
|
||||
}
|
||||
}
|
||||
}
|
||||
9
simulations/astar/astar/astar.csproj
Normal file
9
simulations/astar/astar/astar.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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)
|
||||
|
||||
11
simulations/balls/BallCollider/BallCollider.csproj
Normal file
11
simulations/balls/BallCollider/BallCollider.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net9.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
21
simulations/balls/BallCollider/Form1.Designer.cs
generated
Normal file
21
simulations/balls/BallCollider/Form1.Designer.cs
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BallCollider
|
||||
{
|
||||
partial class Form1
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(800, 600);
|
||||
this.Name = "Form1";
|
||||
this.Text = "Ball Collider";
|
||||
this.ResumeLayout(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
743
simulations/balls/BallCollider/Form1.cs
Normal file
743
simulations/balls/BallCollider/Form1.cs
Normal file
@@ -0,0 +1,743 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Media;
|
||||
using System.Windows.Forms;
|
||||
using WinTimer = System.Windows.Forms.Timer;
|
||||
|
||||
namespace BallCollider
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
// Ball properties
|
||||
private PointF ballPos;
|
||||
private PointF ballVel;
|
||||
private const float BallRadius = 12f;
|
||||
private const float MinSpeed = 6f;
|
||||
private const float MaxSpeed = 20f;
|
||||
|
||||
// Shape properties
|
||||
private List<List<PointF>> shapes;
|
||||
private List<Color> shapeColors;
|
||||
private const int MinWalls = 5;
|
||||
private const int MaxWalls = 220;
|
||||
private const float BaseShapeRadius = 220f;
|
||||
private PointF shapeCenter;
|
||||
|
||||
// Game mode
|
||||
private bool multiShapeMode = false;
|
||||
private int currentShapeIndex = 0;
|
||||
|
||||
// Rendering optimization
|
||||
private BufferedGraphicsContext context;
|
||||
private BufferedGraphics buffer;
|
||||
private readonly SolidBrush ballBrush;
|
||||
private readonly List<Pen> shapePens;
|
||||
|
||||
// Game loop
|
||||
private readonly WinTimer gameTimer;
|
||||
private readonly Random rnd;
|
||||
|
||||
// Collision cooldown to prevent multiple hits
|
||||
private int collisionCooldown = 0;
|
||||
|
||||
// Tracer system
|
||||
private List<Tracer> tracers;
|
||||
private const int MaxTracers = 80;
|
||||
private int tracerCooldown = 0;
|
||||
private const int TracerInterval = 2;
|
||||
|
||||
// Predictive collision detection
|
||||
private PointF predictedPos;
|
||||
private bool showPrediction = false;
|
||||
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Optimize form for rendering
|
||||
this.DoubleBuffered = true;
|
||||
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
|
||||
ControlStyles.UserPaint |
|
||||
ControlStyles.Opaque, true);
|
||||
|
||||
this.ClientSize = new Size(800, 600);
|
||||
this.Text = "balls";
|
||||
this.BackColor = Color.Black;
|
||||
|
||||
rnd = new Random();
|
||||
ballBrush = new SolidBrush(Color.White);
|
||||
shapePens = new List<Pen>();
|
||||
tracers = new List<Tracer>();
|
||||
|
||||
// Initialize buffered graphics
|
||||
context = BufferedGraphicsManager.Current;
|
||||
context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
|
||||
buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);
|
||||
|
||||
shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f);
|
||||
InitializeGame();
|
||||
|
||||
// High-frequency timer for smooth gameplay
|
||||
gameTimer = new WinTimer();
|
||||
gameTimer.Interval = 20; // ~60 FPS
|
||||
gameTimer.Tick += GameLoop;
|
||||
gameTimer.Start();
|
||||
|
||||
this.Resize += (s, e) => RecreateBuffer();
|
||||
this.KeyDown += Form1_KeyDown;
|
||||
this.KeyPreview = true;
|
||||
}
|
||||
|
||||
private void Form1_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.M)
|
||||
{
|
||||
multiShapeMode = !multiShapeMode;
|
||||
if (multiShapeMode)
|
||||
{
|
||||
InitializeMultiShapeMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
InitializeSingleShapeMode();
|
||||
}
|
||||
}
|
||||
else if (e.KeyCode == Keys.P)
|
||||
{
|
||||
showPrediction = !showPrediction;
|
||||
}
|
||||
}
|
||||
|
||||
private void RecreateBuffer()
|
||||
{
|
||||
if (this.Width > 0 && this.Height > 0)
|
||||
{
|
||||
buffer?.Dispose();
|
||||
context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
|
||||
buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);
|
||||
shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeGame()
|
||||
{
|
||||
shapes = new List<List<PointF>>();
|
||||
shapeColors = new List<Color>();
|
||||
shapePens.Clear();
|
||||
tracers.Clear();
|
||||
|
||||
CreateShape(MinWalls, 0);
|
||||
|
||||
// Spawn ball at center
|
||||
ballPos = new PointF(shapeCenter.X, shapeCenter.Y);
|
||||
|
||||
// Random initial velocity
|
||||
float angle = (float)(rnd.NextDouble() * Math.PI * 2);
|
||||
float speed = MinSpeed * 1.5f;
|
||||
ballVel = new PointF(
|
||||
(float)Math.Cos(angle) * speed,
|
||||
(float)Math.Sin(angle) * speed
|
||||
);
|
||||
}
|
||||
|
||||
private void InitializeSingleShapeMode()
|
||||
{
|
||||
shapes.Clear();
|
||||
shapeColors.Clear();
|
||||
shapePens.Clear();
|
||||
tracers.Clear();
|
||||
CreateShape(MinWalls, 0);
|
||||
currentShapeIndex = 0;
|
||||
}
|
||||
|
||||
private void InitializeMultiShapeMode()
|
||||
{
|
||||
shapes.Clear();
|
||||
shapeColors.Clear();
|
||||
shapePens.Clear();
|
||||
tracers.Clear();
|
||||
|
||||
// Create 3 concentric shapes
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
CreateShape(MinWalls + i * 2, i);
|
||||
}
|
||||
currentShapeIndex = 0;
|
||||
}
|
||||
|
||||
private void CreateShape(int sides, int shapeIndex)
|
||||
{
|
||||
while (shapes.Count <= shapeIndex)
|
||||
{
|
||||
shapes.Add(new List<PointF>());
|
||||
shapeColors.Add(Color.White);
|
||||
shapePens.Add(new Pen(Color.White, 4f));
|
||||
}
|
||||
|
||||
List<PointF> shapePoints = new List<PointF>();
|
||||
|
||||
float angleStep = (float)(2 * Math.PI / sides);
|
||||
float startAngle = (float)(rnd.NextDouble() * Math.PI * 2);
|
||||
|
||||
float radius = multiShapeMode ?
|
||||
BaseShapeRadius * (1.0f - shapeIndex * 0.2f) :
|
||||
BaseShapeRadius;
|
||||
|
||||
for (int i = 0; i < sides; i++)
|
||||
{
|
||||
float angle = startAngle + i * angleStep;
|
||||
shapePoints.Add(new PointF(
|
||||
shapeCenter.X + (float)Math.Cos(angle) * radius,
|
||||
shapeCenter.Y + (float)Math.Sin(angle) * radius
|
||||
));
|
||||
}
|
||||
|
||||
shapes[shapeIndex] = shapePoints;
|
||||
|
||||
Color newColor = Color.FromArgb(
|
||||
rnd.Next(100, 256),
|
||||
rnd.Next(100, 256),
|
||||
rnd.Next(100, 256)
|
||||
);
|
||||
shapeColors[shapeIndex] = newColor;
|
||||
shapePens[shapeIndex].Color = newColor;
|
||||
}
|
||||
|
||||
private void GameLoop(object sender, EventArgs e)
|
||||
{
|
||||
UpdateBall();
|
||||
UpdateTracers();
|
||||
Render();
|
||||
}
|
||||
|
||||
private void UpdateBall()
|
||||
{
|
||||
if (collisionCooldown > 0)
|
||||
collisionCooldown--;
|
||||
|
||||
// Store previous position for tracer
|
||||
PointF previousPos = ballPos;
|
||||
|
||||
predictedPos = new PointF(ballPos.X + ballVel.X, ballPos.Y + ballVel.Y);
|
||||
|
||||
if (collisionCooldown == 0)
|
||||
{
|
||||
if (CheckPredictiveCollision())
|
||||
{
|
||||
// Predictive collision detected - handle it immediately
|
||||
HandlePredictiveCollision();
|
||||
}
|
||||
else
|
||||
{
|
||||
// No collision predicted - apply normal movement
|
||||
ballPos = predictedPos;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply normal movement during cooldown
|
||||
ballPos = predictedPos;
|
||||
}
|
||||
|
||||
// Gradually increase speed for excitement (capped)
|
||||
float speed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
|
||||
if (speed < MaxSpeed)
|
||||
{
|
||||
float factor = 1.005f;
|
||||
ballVel.X *= factor;
|
||||
ballVel.Y *= factor;
|
||||
}
|
||||
|
||||
// Add tracer
|
||||
if (tracerCooldown <= 0)
|
||||
{
|
||||
AddTracer(previousPos);
|
||||
tracerCooldown = TracerInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
tracerCooldown--;
|
||||
}
|
||||
|
||||
// Final boundary enforcement (safety net)
|
||||
EnforceBoundaries();
|
||||
}
|
||||
|
||||
private bool CheckPredictiveCollision()
|
||||
{
|
||||
if (shapes.Count == 0) return false;
|
||||
|
||||
List<PointF> currentShape = shapes[currentShapeIndex];
|
||||
|
||||
for (int i = 0; i < currentShape.Count; i++)
|
||||
{
|
||||
PointF p1 = currentShape[i];
|
||||
PointF p2 = currentShape[(i + 1) % currentShape.Count];
|
||||
|
||||
PointF closest = ClosestPointOnSegment(p1, p2, predictedPos);
|
||||
float dx = predictedPos.X - closest.X;
|
||||
float dy = predictedPos.Y - closest.Y;
|
||||
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < BallRadius)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void HandlePredictiveCollision()
|
||||
{
|
||||
if (shapes.Count == 0) return;
|
||||
|
||||
List<PointF> currentShape = shapes[currentShapeIndex];
|
||||
PointF collisionPoint = PointF.Empty;
|
||||
PointF wallNormal = PointF.Empty;
|
||||
int wallIndex = -1;
|
||||
float minDist = float.MaxValue;
|
||||
|
||||
// Find the closest wall that will be collided with
|
||||
for (int i = 0; i < currentShape.Count; i++)
|
||||
{
|
||||
PointF p1 = currentShape[i];
|
||||
PointF p2 = currentShape[(i + 1) % currentShape.Count];
|
||||
|
||||
PointF closest = ClosestPointOnSegment(p1, p2, predictedPos);
|
||||
float dx = predictedPos.X - closest.X;
|
||||
float dy = predictedPos.Y - closest.Y;
|
||||
float dist = (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist < BallRadius && dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
collisionPoint = closest;
|
||||
wallIndex = i;
|
||||
|
||||
// Calculate wall normal
|
||||
float edgeX = p2.X - p1.X;
|
||||
float edgeY = p2.Y - p1.Y;
|
||||
float edgeLen = (float)Math.Sqrt(edgeX * edgeX + edgeY * edgeY);
|
||||
|
||||
if (edgeLen > 0)
|
||||
{
|
||||
wallNormal.X = -edgeY / edgeLen;
|
||||
wallNormal.Y = edgeX / edgeLen;
|
||||
|
||||
// Ensure normal points inward
|
||||
float midX = (p1.X + p2.X) / 2f;
|
||||
float midY = (p1.Y + p2.Y) / 2f;
|
||||
float toCenterX = shapeCenter.X - midX;
|
||||
float toCenterY = shapeCenter.Y - midY;
|
||||
|
||||
if (wallNormal.X * toCenterX + wallNormal.Y * toCenterY < 0)
|
||||
{
|
||||
wallNormal.X = -wallNormal.X;
|
||||
wallNormal.Y = -wallNormal.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wallIndex >= 0 && wallNormal != PointF.Empty)
|
||||
{
|
||||
// Calculate exact collision position (ball surface touching wall)
|
||||
float penetration = BallRadius - minDist;
|
||||
PointF correctedPos = new PointF(
|
||||
predictedPos.X + wallNormal.X * penetration,
|
||||
predictedPos.Y + wallNormal.Y * penetration
|
||||
);
|
||||
|
||||
// Set ball to corrected position
|
||||
ballPos = correctedPos;
|
||||
|
||||
// Reflect velocity
|
||||
float dotProduct = ballVel.X * wallNormal.X + ballVel.Y * wallNormal.Y;
|
||||
|
||||
if (dotProduct < 0)
|
||||
{
|
||||
ballVel.X -= 2 * dotProduct * wallNormal.X;
|
||||
ballVel.Y -= 2 * dotProduct * wallNormal.Y;
|
||||
|
||||
// Apply boost and clamp speed
|
||||
float boost = 1.05f;
|
||||
ballVel.X *= boost;
|
||||
ballVel.Y *= boost;
|
||||
|
||||
float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
|
||||
if (currentSpeed > MaxSpeed)
|
||||
{
|
||||
ballVel.X = (ballVel.X / currentSpeed) * MaxSpeed;
|
||||
ballVel.Y = (ballVel.Y / currentSpeed) * MaxSpeed;
|
||||
}
|
||||
|
||||
PlayHitSound();
|
||||
|
||||
// Handle shape evolution
|
||||
PointF p1 = currentShape[wallIndex];
|
||||
PointF p2 = currentShape[(wallIndex + 1) % currentShape.Count];
|
||||
|
||||
if (currentShape.Count < MaxWalls)
|
||||
{
|
||||
// Add vertex to make shape more circular
|
||||
PointF newPoint = new PointF(
|
||||
(p1.X + p2.X) / 2f,
|
||||
(p1.Y + p2.Y) / 2f
|
||||
);
|
||||
|
||||
float radius = multiShapeMode ?
|
||||
BaseShapeRadius * (1.0f - currentShapeIndex * 0.2f) :
|
||||
BaseShapeRadius;
|
||||
|
||||
// Project to maintain circular shape
|
||||
float angle = (float)Math.Atan2(newPoint.Y - shapeCenter.Y, newPoint.X - shapeCenter.X);
|
||||
newPoint = new PointF(
|
||||
shapeCenter.X + (float)Math.Cos(angle) * radius,
|
||||
shapeCenter.Y + (float)Math.Sin(angle) * radius
|
||||
);
|
||||
|
||||
currentShape.Insert(wallIndex + 1, newPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (multiShapeMode && currentShapeIndex < shapes.Count - 1)
|
||||
{
|
||||
currentShapeIndex++;
|
||||
PlayShapeChangeSound();
|
||||
}
|
||||
else
|
||||
{
|
||||
int sides = multiShapeMode ? MinWalls + currentShapeIndex * 2 : MinWalls;
|
||||
CreateShape(sides, currentShapeIndex);
|
||||
PlayShapeChangeSound();
|
||||
}
|
||||
}
|
||||
|
||||
collisionCooldown = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnforceBoundaries()
|
||||
{
|
||||
bool isInsideAnyShape = false;
|
||||
|
||||
foreach (var shape in shapes)
|
||||
{
|
||||
if (IsPointInPolygon(ballPos, shape))
|
||||
{
|
||||
isInsideAnyShape = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isInsideAnyShape && shapes.Count > 0)
|
||||
{
|
||||
PointF closestBoundary = FindClosestBoundaryPoint(ballPos, shapes[0]);
|
||||
|
||||
float pushX = closestBoundary.X - ballPos.X;
|
||||
float pushY = closestBoundary.Y - ballPos.Y;
|
||||
float pushDist = (float)Math.Sqrt(pushX * pushX + pushY * pushY);
|
||||
|
||||
if (pushDist > 0)
|
||||
{
|
||||
float margin = BallRadius + 2f;
|
||||
ballPos.X = closestBoundary.X - (pushX / pushDist) * margin;
|
||||
ballPos.Y = closestBoundary.Y - (pushY / pushDist) * margin;
|
||||
|
||||
// Emergency bounce
|
||||
float randomAngle = (float)(rnd.NextDouble() * Math.PI * 2);
|
||||
float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
|
||||
ballVel.X = (float)Math.Cos(randomAngle) * currentSpeed;
|
||||
ballVel.Y = (float)Math.Sin(randomAngle) * currentSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTracers()
|
||||
{
|
||||
// Update and remove old tracers
|
||||
for (int i = tracers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
tracers[i].Update();
|
||||
if (tracers[i].IsDead)
|
||||
{
|
||||
tracers.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTracer(PointF position)
|
||||
{
|
||||
if (tracers.Count >= MaxTracers)
|
||||
{
|
||||
tracers.RemoveAt(0);
|
||||
}
|
||||
|
||||
Color tracerColor = shapes.Count > 0 ?
|
||||
Color.FromArgb(150, shapeColors[currentShapeIndex]) :
|
||||
Color.FromArgb(150, Color.White);
|
||||
|
||||
tracers.Add(new Tracer(position, tracerColor, BallRadius * 0.7f));
|
||||
}
|
||||
|
||||
private PointF FindClosestBoundaryPoint(PointF point, List<PointF> shape)
|
||||
{
|
||||
PointF closest = shape[0];
|
||||
float closestDist = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < shape.Count; i++)
|
||||
{
|
||||
PointF p1 = shape[i];
|
||||
PointF p2 = shape[(i + 1) % shape.Count];
|
||||
|
||||
PointF segmentClosest = ClosestPointOnSegment(p1, p2, point);
|
||||
float dist = Distance(point, segmentClosest);
|
||||
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closest = segmentClosest;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
private float Distance(PointF a, PointF b)
|
||||
{
|
||||
float dx = a.X - b.X;
|
||||
float dy = a.Y - b.Y;
|
||||
return (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private bool IsPointInPolygon(PointF point, List<PointF> polygon)
|
||||
{
|
||||
bool inside = false;
|
||||
int j = polygon.Count - 1;
|
||||
|
||||
for (int i = 0; i < polygon.Count; i++)
|
||||
{
|
||||
if ((polygon[i].Y > point.Y) != (polygon[j].Y > point.Y) &&
|
||||
point.X < (polygon[j].X - polygon[i].X) * (point.Y - polygon[i].Y) /
|
||||
(polygon[j].Y - polygon[i].Y) + polygon[i].X)
|
||||
{
|
||||
inside = !inside;
|
||||
}
|
||||
j = i;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
private PointF ClosestPointOnSegment(PointF a, PointF b, PointF p)
|
||||
{
|
||||
float dx = b.X - a.X;
|
||||
float dy = b.Y - a.Y;
|
||||
|
||||
if (dx == 0 && dy == 0)
|
||||
return a;
|
||||
|
||||
float len2 = dx * dx + dy * dy;
|
||||
float t = ((p.X - a.X) * dx + (p.Y - a.Y) * dy) / len2;
|
||||
|
||||
t = Math.Max(0, Math.Min(1, t));
|
||||
|
||||
return new PointF(a.X + t * dx, a.Y + t * dy);
|
||||
}
|
||||
|
||||
private void PlayHitSound()
|
||||
{
|
||||
try
|
||||
{
|
||||
int freq = rnd.Next(400, 1000);
|
||||
System.Threading.Tasks.Task.Run(() => Console.Beep(freq, 30));
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void PlayShapeChangeSound()
|
||||
{
|
||||
try
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(() =>
|
||||
{
|
||||
Console.Beep(300, 80);
|
||||
System.Threading.Thread.Sleep(50);
|
||||
Console.Beep(500, 80);
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void Render()
|
||||
{
|
||||
Graphics g = buffer.Graphics;
|
||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
||||
g.Clear(Color.Black);
|
||||
|
||||
// Draw tracers first (behind everything)
|
||||
foreach (var tracer in tracers)
|
||||
{
|
||||
tracer.Draw(g);
|
||||
}
|
||||
|
||||
// Draw all shapes
|
||||
for (int shapeIndex = 0; shapeIndex < shapes.Count; shapeIndex++)
|
||||
{
|
||||
var shape = shapes[shapeIndex];
|
||||
if (shape.Count >= 2)
|
||||
{
|
||||
Color drawColor = shapeColors[shapeIndex];
|
||||
if (multiShapeMode && shapeIndex == currentShapeIndex)
|
||||
{
|
||||
drawColor = Color.FromArgb(255,
|
||||
Math.Min(255, drawColor.R + 50),
|
||||
Math.Min(255, drawColor.G + 50),
|
||||
Math.Min(255, drawColor.B + 50));
|
||||
}
|
||||
|
||||
using (Pen pen = new Pen(drawColor, shapeIndex == currentShapeIndex ? 5f : 3f))
|
||||
{
|
||||
for (int i = 0; i < shape.Count; i++)
|
||||
{
|
||||
PointF p1 = shape[i];
|
||||
PointF p2 = shape[(i + 1) % shape.Count];
|
||||
g.DrawLine(pen, p1, p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw prediction line if enabled
|
||||
if (showPrediction)
|
||||
{
|
||||
using (Pen predictionPen = new Pen(Color.FromArgb(100, Color.Yellow), 1f))
|
||||
{
|
||||
g.DrawLine(predictionPen, ballPos, predictedPos);
|
||||
}
|
||||
|
||||
// Draw predicted position
|
||||
using (Brush predictionBrush = new SolidBrush(Color.FromArgb(100, Color.Red)))
|
||||
{
|
||||
g.FillEllipse(predictionBrush,
|
||||
predictedPos.X - BallRadius / 2,
|
||||
predictedPos.Y - BallRadius / 2,
|
||||
BallRadius, BallRadius);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw ball with glow effect
|
||||
using (GraphicsPath path = new GraphicsPath())
|
||||
{
|
||||
path.AddEllipse(
|
||||
ballPos.X - BallRadius,
|
||||
ballPos.Y - BallRadius,
|
||||
BallRadius * 2,
|
||||
BallRadius * 2
|
||||
);
|
||||
|
||||
using (PathGradientBrush brush = new PathGradientBrush(path))
|
||||
{
|
||||
Color activeColor = shapes.Count > 0 ? shapeColors[currentShapeIndex] : Color.White;
|
||||
brush.CenterColor = Color.White;
|
||||
brush.SurroundColors = new[] { Color.FromArgb(100, activeColor) };
|
||||
g.FillPath(brush, path);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw UI info
|
||||
using (Font font = new Font("Arial", 14, FontStyle.Bold))
|
||||
{
|
||||
string sidesText = $"Sides: {shapes[currentShapeIndex].Count}";
|
||||
string predictionText = $"Prediction: {(showPrediction ? "ON" : "OFF")} (P)";
|
||||
|
||||
g.DrawString(sidesText, font, Brushes.White, 10, 10);
|
||||
g.DrawString(predictionText, font, Brushes.LightGreen, 10, 35);
|
||||
}
|
||||
|
||||
buffer.Render();
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
if (buffer != null)
|
||||
{
|
||||
buffer.Render(e.Graphics);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
gameTimer?.Stop();
|
||||
gameTimer?.Dispose();
|
||||
buffer?.Dispose();
|
||||
context?.Dispose();
|
||||
ballBrush?.Dispose();
|
||||
foreach (var pen in shapePens)
|
||||
pen?.Dispose();
|
||||
components?.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
// Tracer class for ball trail effect
|
||||
public class Tracer
|
||||
{
|
||||
public PointF Position { get; private set; }
|
||||
public Color Color { get; private set; }
|
||||
public float Radius { get; private set; }
|
||||
public float Life { get; private set; }
|
||||
public bool IsDead => Life <= 0;
|
||||
|
||||
private readonly float initialRadius;
|
||||
private readonly Color initialColor;
|
||||
|
||||
public Tracer(PointF position, Color color, float radius)
|
||||
{
|
||||
Position = position;
|
||||
Color = color;
|
||||
Radius = radius;
|
||||
initialRadius = radius;
|
||||
initialColor = color;
|
||||
Life = 1.0f; // Start with full life
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Life -= 0.03f; // Fade speed
|
||||
if (Life < 0) Life = 0;
|
||||
|
||||
// Shrink and fade
|
||||
Radius = initialRadius * Life;
|
||||
|
||||
// Fade color
|
||||
Color = Color.FromArgb(
|
||||
(int)(initialColor.A * Life),
|
||||
initialColor.R,
|
||||
initialColor.G,
|
||||
initialColor.B
|
||||
);
|
||||
}
|
||||
|
||||
public void Draw(Graphics g)
|
||||
{
|
||||
if (IsDead) return;
|
||||
|
||||
using (Brush brush = new SolidBrush(Color))
|
||||
{
|
||||
g.FillEllipse(brush,
|
||||
Position.X - Radius,
|
||||
Position.Y - Radius,
|
||||
Radius * 2,
|
||||
Radius * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
simulations/balls/BallCollider/Program.cs
Normal file
16
simulations/balls/BallCollider/Program.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace BallCollider;
|
||||
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new Form1());
|
||||
}
|
||||
}
|
||||
50
simulations/fluids/fluidsC#/Camera.cs
Normal file
50
simulations/fluids/fluidsC#/Camera.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace fluidsC_;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
public Vector3 Position;
|
||||
public float AspectRatio;
|
||||
|
||||
public Vector3 Front = -Vector3.UnitZ;
|
||||
public Vector3 Up = Vector3.UnitY;
|
||||
public Vector3 Right = Vector3.UnitX;
|
||||
|
||||
public float Pitch;
|
||||
public float Yaw = -MathHelper.PiOver2;
|
||||
|
||||
public float Speed = 2.5f;
|
||||
public float Sensitivity = 0.002f;
|
||||
|
||||
public Camera(Vector3 position, float aspectRatio)
|
||||
{
|
||||
Position = position;
|
||||
AspectRatio = aspectRatio;
|
||||
UpdateVectors();
|
||||
}
|
||||
|
||||
public Matrix4 GetViewMatrix()
|
||||
{
|
||||
return Matrix4.LookAt(Position, Position + Front, Up);
|
||||
}
|
||||
|
||||
public Matrix4 GetProjectionMatrix()
|
||||
{
|
||||
return Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, AspectRatio, 0.01f, 100.0f);
|
||||
}
|
||||
|
||||
public void UpdateVectors()
|
||||
{
|
||||
// Calculate the new Front vector
|
||||
Front.X = MathF.Cos(Yaw) * MathF.Cos(Pitch);
|
||||
Front.Y = MathF.Sin(Pitch);
|
||||
Front.Z = MathF.Sin(Yaw) * MathF.Cos(Pitch);
|
||||
|
||||
Front = Vector3.Normalize(Front);
|
||||
|
||||
// Also re-calculate the Right and Up vector
|
||||
Right = Vector3.Normalize(Vector3.Cross(Front, Vector3.UnitY));
|
||||
Up = Vector3.Normalize(Vector3.Cross(Right, Front));
|
||||
}
|
||||
}
|
||||
188
simulations/fluids/fluidsC#/FluidSimulation.cs
Normal file
188
simulations/fluids/fluidsC#/FluidSimulation.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace fluidsC_;
|
||||
|
||||
public class FluidSimulation
|
||||
{
|
||||
private readonly int _sizeX;
|
||||
private readonly int _sizeY;
|
||||
|
||||
// Fluid fields
|
||||
private float[,] _density;
|
||||
private float[,] _densityOld;
|
||||
|
||||
// Velocity fields
|
||||
private float[,] _vx;
|
||||
private float[,] _vy;
|
||||
private float[,] _vxOld;
|
||||
private float[,] _vyOld;
|
||||
|
||||
public FluidSimulation(int sizeX, int sizeY)
|
||||
{
|
||||
_sizeX = sizeX;
|
||||
_sizeY = sizeY;
|
||||
|
||||
// Initialize arrays in constructor to fix nullable warnings
|
||||
_density = new float[_sizeX, _sizeY];
|
||||
_densityOld = new float[_sizeX, _sizeY];
|
||||
_vx = new float[_sizeX, _sizeY];
|
||||
_vy = new float[_sizeX, _sizeY];
|
||||
_vxOld = new float[_sizeX, _sizeY];
|
||||
_vyOld = new float[_sizeX, _sizeY];
|
||||
}
|
||||
|
||||
public void AddDensity(int x, int y, float amount)
|
||||
{
|
||||
if (x >= 0 && x < _sizeX && y >= 0 && y < _sizeY)
|
||||
{
|
||||
_density[x, y] += amount;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddVelocity(int x, int y, float amountX, float amountY)
|
||||
{
|
||||
if (x >= 0 && x < _sizeX && y >= 0 && y < _sizeY)
|
||||
{
|
||||
_vx[x, y] += amountX;
|
||||
_vy[x, y] += amountY;
|
||||
}
|
||||
}
|
||||
|
||||
public void Step(float dt, float diffusion, float viscosity)
|
||||
{
|
||||
Diffuse(1, _vxOld, _vx, viscosity, dt);
|
||||
Diffuse(2, _vyOld, _vy, viscosity, dt);
|
||||
|
||||
Project(_vxOld, _vyOld, _vx, _vy);
|
||||
|
||||
Advect(1, _vx, _vxOld, _vxOld, _vyOld, dt);
|
||||
Advect(2, _vy, _vyOld, _vxOld, _vyOld, dt);
|
||||
|
||||
Project(_vx, _vy, _vxOld, _vyOld);
|
||||
|
||||
Diffuse(0, _densityOld, _density, diffusion, dt);
|
||||
Advect(0, _density, _densityOld, _vx, _vy, dt);
|
||||
}
|
||||
|
||||
private void Diffuse(int b, float[,] x, float[,] x0, float diff, float dt)
|
||||
{
|
||||
float a = dt * diff * (_sizeX - 2) * (_sizeY - 2);
|
||||
LinearSolve(b, x, x0, a, 1 + 4 * a);
|
||||
}
|
||||
|
||||
private void LinearSolve(int b, float[,] x, float[,] x0, float a, float c)
|
||||
{
|
||||
for (int k = 0; k < 20; k++)
|
||||
{
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
x[i, j] = (x0[i, j] + a * (x[i - 1, j] + x[i + 1, j] + x[i, j - 1] + x[i, j + 1])) / c;
|
||||
}
|
||||
}
|
||||
SetBoundary(b, x);
|
||||
}
|
||||
}
|
||||
|
||||
private void Project(float[,] velocX, float[,] velocY, float[,] p, float[,] div)
|
||||
{
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
div[i, j] = -0.5f * (
|
||||
velocX[i + 1, j] - velocX[i - 1, j] +
|
||||
velocY[i, j + 1] - velocY[i, j - 1]
|
||||
) / _sizeX;
|
||||
p[i, j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SetBoundary(0, div);
|
||||
SetBoundary(0, p);
|
||||
LinearSolve(0, p, div, 1, 4);
|
||||
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
velocX[i, j] -= 0.5f * (p[i + 1, j] - p[i - 1, j]) * _sizeX;
|
||||
velocY[i, j] -= 0.5f * (p[i, j + 1] - p[i, j - 1]) * _sizeY;
|
||||
}
|
||||
}
|
||||
|
||||
SetBoundary(1, velocX);
|
||||
SetBoundary(2, velocY);
|
||||
}
|
||||
|
||||
private void Advect(int b, float[,] d, float[,] d0, float[,] velocX, float[,] velocY, float dt)
|
||||
{
|
||||
float dt0 = dt * (_sizeX - 2);
|
||||
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
float x = i - dt0 * velocX[i, j];
|
||||
float y = j - dt0 * velocY[i, j];
|
||||
|
||||
x = Math.Clamp(x, 0.5f, _sizeX - 1.5f);
|
||||
y = Math.Clamp(y, 0.5f, _sizeY - 1.5f);
|
||||
|
||||
int i0 = (int)Math.Floor(x);
|
||||
int i1 = i0 + 1;
|
||||
int j0 = (int)Math.Floor(y);
|
||||
int j1 = j0 + 1;
|
||||
|
||||
float s1 = x - i0;
|
||||
float s0 = 1 - s1;
|
||||
float t1 = y - j0;
|
||||
float t0 = 1 - t1;
|
||||
|
||||
d[i, j] = s0 * (t0 * d0[i0, j0] + t1 * d0[i0, j1]) +
|
||||
s1 * (t0 * d0[i1, j0] + t1 * d0[i1, j1]);
|
||||
}
|
||||
}
|
||||
|
||||
SetBoundary(b, d);
|
||||
}
|
||||
|
||||
private void SetBoundary(int b, float[,] x)
|
||||
{
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
x[i, 0] = b == 2 ? -x[i, 1] : x[i, 1];
|
||||
x[i, _sizeY - 1] = b == 2 ? -x[i, _sizeY - 2] : x[i, _sizeY - 2];
|
||||
}
|
||||
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
x[0, j] = b == 1 ? -x[1, j] : x[1, j];
|
||||
x[_sizeX - 1, j] = b == 1 ? -x[_sizeX - 2, j] : x[_sizeX - 2, j];
|
||||
}
|
||||
|
||||
x[0, 0] = 0.5f * (x[1, 0] + x[0, 1]);
|
||||
x[0, _sizeY - 1] = 0.5f * (x[1, _sizeY - 1] + x[0, _sizeY - 2]);
|
||||
x[_sizeX - 1, 0] = 0.5f * (x[_sizeX - 2, 0] + x[_sizeX - 1, 1]);
|
||||
x[_sizeX - 1, _sizeY - 1] = 0.5f * (x[_sizeX - 2, _sizeY - 1] + x[_sizeX - 1, _sizeY - 2]);
|
||||
}
|
||||
|
||||
public float GetDensity(int x, int y)
|
||||
{
|
||||
if (x >= 0 && x < _sizeX && y >= 0 && y < _sizeY)
|
||||
return _density[x, y];
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void FadeDensity(float amount)
|
||||
{
|
||||
for (int i = 0; i < _sizeX; i++)
|
||||
{
|
||||
for (int j = 0; j < _sizeY; j++)
|
||||
{
|
||||
_density[i, j] = Math.Max(0, _density[i, j] - amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
313
simulations/fluids/fluidsC#/FluidSimulation3D.cs
Normal file
313
simulations/fluids/fluidsC#/FluidSimulation3D.cs
Normal file
@@ -0,0 +1,313 @@
|
||||
using OpenTK.Mathematics;
|
||||
|
||||
namespace fluidsC_;
|
||||
|
||||
public class FluidSimulation3D
|
||||
{
|
||||
private readonly int _sizeX;
|
||||
private readonly int _sizeY;
|
||||
private readonly int _sizeZ;
|
||||
|
||||
// Fluid fields
|
||||
private float[,,] _density;
|
||||
private float[,,] _densityOld;
|
||||
|
||||
// Velocity fields
|
||||
private float[,,] _vx;
|
||||
private float[,,] _vy;
|
||||
private float[,,] _vz;
|
||||
private float[,,] _vxOld;
|
||||
private float[,,] _vyOld;
|
||||
private float[,,] _vzOld;
|
||||
|
||||
// Obstacle field
|
||||
private bool[,,] _obstacles;
|
||||
|
||||
public FluidSimulation3D(int sizeX, int sizeY, int sizeZ)
|
||||
{
|
||||
_sizeX = sizeX;
|
||||
_sizeY = sizeY;
|
||||
_sizeZ = sizeZ;
|
||||
|
||||
// Initialize arrays in constructor
|
||||
_density = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_densityOld = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_vx = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_vy = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_vz = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_vxOld = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_vyOld = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_vzOld = new float[_sizeX, _sizeY, _sizeZ];
|
||||
_obstacles = new bool[_sizeX, _sizeY, _sizeZ];
|
||||
|
||||
CreateDefaultObstacles();
|
||||
}
|
||||
|
||||
private void CreateDefaultObstacles()
|
||||
{
|
||||
// Create some default obstacles
|
||||
int centerX = _sizeX / 2;
|
||||
int centerZ = _sizeZ / 2;
|
||||
|
||||
// Create a sphere obstacle in the center
|
||||
int sphereRadius = Math.Min(_sizeX, Math.Min(_sizeY, _sizeZ)) / 6;
|
||||
for (int x = 0; x < _sizeX; x++)
|
||||
{
|
||||
for (int y = 0; y < _sizeY; y++)
|
||||
{
|
||||
for (int z = 0; z < _sizeZ; z++)
|
||||
{
|
||||
float dx = x - centerX;
|
||||
float dy = y - _sizeY / 3;
|
||||
float dz = z - centerZ;
|
||||
float dist = MathF.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
|
||||
if (dist <= sphereRadius)
|
||||
{
|
||||
_obstacles[x, y, z] = true;
|
||||
}
|
||||
|
||||
// Create walls
|
||||
if (x == 0 || x == _sizeX - 1 || z == 0 || z == _sizeZ - 1)
|
||||
{
|
||||
_obstacles[x, y, z] = true;
|
||||
}
|
||||
|
||||
// Create floor
|
||||
if (y == 0)
|
||||
{
|
||||
_obstacles[x, y, z] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddDensity(int x, int y, int z, float amount)
|
||||
{
|
||||
if (IsInBounds(x, y, z) && !_obstacles[x, y, z])
|
||||
{
|
||||
_density[x, y, z] += amount;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddVelocity(int x, int y, int z, float amountX, float amountY, float amountZ)
|
||||
{
|
||||
if (IsInBounds(x, y, z) && !_obstacles[x, y, z])
|
||||
{
|
||||
_vx[x, y, z] += amountX;
|
||||
_vy[x, y, z] += amountY;
|
||||
_vz[x, y, z] += amountZ;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddObstacle(int x, int y, int z, bool obstacle)
|
||||
{
|
||||
if (IsInBounds(x, y, z))
|
||||
{
|
||||
_obstacles[x, y, z] = obstacle;
|
||||
}
|
||||
}
|
||||
|
||||
public void Step(float dt, float diffusion, float viscosity)
|
||||
{
|
||||
Diffuse(1, _vxOld, _vx, viscosity, dt);
|
||||
Diffuse(2, _vyOld, _vy, viscosity, dt);
|
||||
Diffuse(3, _vzOld, _vz, viscosity, dt);
|
||||
|
||||
// Fixed Project3D calls - use correct parameters
|
||||
Project3D(_vxOld, _vyOld, _vzOld, _vxOld, _vyOld, _vzOld);
|
||||
|
||||
Advect(1, _vx, _vxOld, _vxOld, _vyOld, _vzOld, dt);
|
||||
Advect(2, _vy, _vyOld, _vxOld, _vyOld, _vzOld, dt);
|
||||
Advect(3, _vz, _vzOld, _vxOld, _vyOld, _vzOld, dt);
|
||||
|
||||
// Fixed Project3D calls - use correct parameters
|
||||
Project3D(_vx, _vy, _vz, _vx, _vy, _vz);
|
||||
|
||||
Diffuse(0, _densityOld, _density, diffusion, dt);
|
||||
Advect(0, _density, _densityOld, _vx, _vy, _vz, dt);
|
||||
}
|
||||
|
||||
private void Diffuse(int b, float[,,] x, float[,,] x0, float diff, float dt)
|
||||
{
|
||||
float a = dt * diff * (_sizeX - 2) * (_sizeY - 2) * (_sizeZ - 2);
|
||||
LinearSolve(b, x, x0, a, 1 + 6 * a);
|
||||
}
|
||||
|
||||
private void LinearSolve(int b, float[,,] x, float[,,] x0, float a, float c)
|
||||
{
|
||||
for (int k = 0; k < 20; k++)
|
||||
{
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
for (int l = 1; l < _sizeZ - 1; l++)
|
||||
{
|
||||
if (!_obstacles[i, j, l])
|
||||
{
|
||||
x[i, j, l] = (x0[i, j, l] + a * (
|
||||
x[i - 1, j, l] + x[i + 1, j, l] +
|
||||
x[i, j - 1, l] + x[i, j + 1, l] +
|
||||
x[i, j, l - 1] + x[i, j, l + 1])) / c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SetBoundary(b, x);
|
||||
}
|
||||
}
|
||||
|
||||
private void Project3D(float[,,] velocX, float[,,] velocY, float[,,] velocZ, float[,,] p, float[,,] div, float[,,] temp)
|
||||
{
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
for (int l = 1; l < _sizeZ - 1; l++)
|
||||
{
|
||||
if (!_obstacles[i, j, l])
|
||||
{
|
||||
div[i, j, l] = -0.5f * (
|
||||
velocX[i + 1, j, l] - velocX[i - 1, j, l] +
|
||||
velocY[i, j + 1, l] - velocY[i, j - 1, l] +
|
||||
velocZ[i, j, l + 1] - velocZ[i, j, l - 1]
|
||||
) / _sizeX;
|
||||
p[i, j, l] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetBoundary(0, div);
|
||||
SetBoundary(0, p);
|
||||
LinearSolve(0, p, div, 1, 6);
|
||||
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
for (int l = 1; l < _sizeZ - 1; l++)
|
||||
{
|
||||
if (!_obstacles[i, j, l])
|
||||
{
|
||||
velocX[i, j, l] -= 0.5f * (p[i + 1, j, l] - p[i - 1, j, l]) * _sizeX;
|
||||
velocY[i, j, l] -= 0.5f * (p[i, j + 1, l] - p[i, j - 1, l]) * _sizeY;
|
||||
velocZ[i, j, l] -= 0.5f * (p[i, j, l + 1] - p[i, j, l - 1]) * _sizeZ;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetBoundary(1, velocX);
|
||||
SetBoundary(2, velocY);
|
||||
SetBoundary(3, velocZ);
|
||||
}
|
||||
|
||||
private void Advect(int b, float[,,] d, float[,,] d0, float[,,] velocX, float[,,] velocY, float[,,] velocZ, float dt)
|
||||
{
|
||||
float dt0 = dt * (_sizeX - 2);
|
||||
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
for (int l = 1; l < _sizeZ - 1; l++)
|
||||
{
|
||||
if (!_obstacles[i, j, l])
|
||||
{
|
||||
float x = i - dt0 * velocX[i, j, l];
|
||||
float y = j - dt0 * velocY[i, j, l];
|
||||
float z = l - dt0 * velocZ[i, j, l];
|
||||
|
||||
x = Math.Clamp(x, 0.5f, _sizeX - 1.5f);
|
||||
y = Math.Clamp(y, 0.5f, _sizeY - 1.5f);
|
||||
z = Math.Clamp(z, 0.5f, _sizeZ - 1.5f);
|
||||
|
||||
int i0 = (int)Math.Floor(x);
|
||||
int i1 = i0 + 1;
|
||||
int j0 = (int)Math.Floor(y);
|
||||
int j1 = j0 + 1;
|
||||
int l0 = (int)Math.Floor(z);
|
||||
int l1 = l0 + 1;
|
||||
|
||||
float s1 = x - i0;
|
||||
float s0 = 1 - s1;
|
||||
float t1 = y - j0;
|
||||
float t0 = 1 - t1;
|
||||
float u1 = z - l0;
|
||||
float u0 = 1 - u1;
|
||||
|
||||
d[i, j, l] =
|
||||
s0 * (t0 * (u0 * d0[i0, j0, l0] + u1 * d0[i0, j0, l1]) +
|
||||
t1 * (u0 * d0[i0, j1, l0] + u1 * d0[i0, j1, l1])) +
|
||||
s1 * (t0 * (u0 * d0[i1, j0, l0] + u1 * d0[i1, j0, l1]) +
|
||||
t1 * (u0 * d0[i1, j1, l0] + u1 * d0[i1, j1, l1]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetBoundary(b, d);
|
||||
}
|
||||
|
||||
private void SetBoundary(int b, float[,,] x)
|
||||
{
|
||||
// Set boundary conditions with obstacles
|
||||
for (int i = 1; i < _sizeX - 1; i++)
|
||||
{
|
||||
for (int j = 1; j < _sizeY - 1; j++)
|
||||
{
|
||||
for (int l = 1; l < _sizeZ - 1; l++)
|
||||
{
|
||||
if (_obstacles[i, j, l])
|
||||
{
|
||||
// Reflect velocity at obstacles
|
||||
if (_obstacles[i - 1, j, l]) x[i, j, l] = b == 1 ? -x[i + 1, j, l] : x[i + 1, j, l];
|
||||
if (_obstacles[i + 1, j, l]) x[i, j, l] = b == 1 ? -x[i - 1, j, l] : x[i - 1, j, l];
|
||||
if (_obstacles[i, j - 1, l]) x[i, j, l] = b == 2 ? -x[i, j + 1, l] : x[i, j + 1, l];
|
||||
if (_obstacles[i, j + 1, l]) x[i, j, l] = b == 2 ? -x[i, j - 1, l] : x[i, j - 1, l];
|
||||
if (_obstacles[i, j, l - 1]) x[i, j, l] = b == 3 ? -x[i, j, l + 1] : x[i, j, l + 1];
|
||||
if (_obstacles[i, j, l + 1]) x[i, j, l] = b == 3 ? -x[i, j, l - 1] : x[i, j, l - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInBounds(int x, int y, int z)
|
||||
{
|
||||
return x >= 0 && x < _sizeX && y >= 0 && y < _sizeY && z >= 0 && z < _sizeZ;
|
||||
}
|
||||
|
||||
public float GetDensity(int x, int y, int z)
|
||||
{
|
||||
if (IsInBounds(x, y, z))
|
||||
return _density[x, y, z];
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool GetObstacle(int x, int y, int z)
|
||||
{
|
||||
if (IsInBounds(x, y, z))
|
||||
return _obstacles[x, y, z];
|
||||
return false;
|
||||
}
|
||||
|
||||
public void FadeDensity(float amount)
|
||||
{
|
||||
for (int i = 0; i < _sizeX; i++)
|
||||
{
|
||||
for (int j = 0; j < _sizeY; j++)
|
||||
{
|
||||
for (int l = 0; l < _sizeZ; l++)
|
||||
{
|
||||
_density[i, j, l] = Math.Max(0, _density[i, j, l] - amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public (int X, int Y, int Z) GetSize() => (_sizeX, _sizeY, _sizeZ);
|
||||
}
|
||||
276
simulations/fluids/fluidsC#/FluidWindow.cs
Normal file
276
simulations/fluids/fluidsC#/FluidWindow.cs
Normal file
@@ -0,0 +1,276 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace fluidsC_;
|
||||
|
||||
public class FluidWindow : GameWindow
|
||||
{
|
||||
private FluidSimulation _fluid;
|
||||
private int _vertexArrayObject;
|
||||
private int _vertexBufferObject;
|
||||
private int _elementBufferObject;
|
||||
private int _shaderProgram;
|
||||
private int _texture;
|
||||
|
||||
private readonly int _fluidSizeX = 128;
|
||||
private readonly int _fluidSizeY = 128;
|
||||
private float[] _pixels;
|
||||
|
||||
private bool _mouseDown = false;
|
||||
private Vector2 _lastMousePos;
|
||||
private MouseButton _activeButton;
|
||||
|
||||
public FluidWindow(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
|
||||
: base(gameWindowSettings, nativeWindowSettings)
|
||||
{
|
||||
_fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
|
||||
_pixels = new float[_fluidSizeX * _fluidSizeY * 4];
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
base.OnLoad();
|
||||
|
||||
GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
|
||||
SetupShaders();
|
||||
SetupTexture();
|
||||
SetupQuad();
|
||||
|
||||
Console.WriteLine("Fluid Simulation Controls:");
|
||||
Console.WriteLine("<22> Click and drag to add fluid");
|
||||
Console.WriteLine("<22> Right click to add velocity");
|
||||
Console.WriteLine("<22> R key to reset simulation");
|
||||
Console.WriteLine("<22> ESC to exit");
|
||||
}
|
||||
|
||||
private void SetupShaders()
|
||||
{
|
||||
const string vertexShaderSource = @"
|
||||
#version 330 core
|
||||
layout (location = 0) in vec2 aPosition;
|
||||
layout (location = 1) in vec2 aTexCoord;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(aPosition, 0.0, 1.0);
|
||||
texCoord = aTexCoord;
|
||||
}";
|
||||
|
||||
const string fragmentShaderSource = @"
|
||||
#version 330 core
|
||||
in vec2 texCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D fluidTexture;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture(fluidTexture, texCoord);
|
||||
// Create a nice blue-to-cyan color gradient based on density
|
||||
fragColor = vec4(color.r * 0.2, color.r * 0.5, color.r, 1.0);
|
||||
}";
|
||||
|
||||
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
|
||||
GL.ShaderSource(vertexShader, vertexShaderSource);
|
||||
GL.CompileShader(vertexShader);
|
||||
|
||||
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
|
||||
GL.ShaderSource(fragmentShader, fragmentShaderSource);
|
||||
GL.CompileShader(fragmentShader);
|
||||
|
||||
_shaderProgram = GL.CreateProgram();
|
||||
GL.AttachShader(_shaderProgram, vertexShader);
|
||||
GL.AttachShader(_shaderProgram, fragmentShader);
|
||||
GL.LinkProgram(_shaderProgram);
|
||||
|
||||
GL.DeleteShader(vertexShader);
|
||||
GL.DeleteShader(fragmentShader);
|
||||
}
|
||||
|
||||
private void SetupTexture()
|
||||
{
|
||||
_texture = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture2D, _texture);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||
|
||||
// Initialize with empty data
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32f,
|
||||
_fluidSizeX, _fluidSizeY, 0, PixelFormat.Rgba, PixelType.Float, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private void SetupQuad()
|
||||
{
|
||||
float[] vertices = {
|
||||
// positions // texture coords
|
||||
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||
-1.0f, -1.0f, 0.0f, 0.0f,
|
||||
1.0f, -1.0f, 1.0f, 0.0f,
|
||||
1.0f, 1.0f, 1.0f, 1.0f
|
||||
};
|
||||
|
||||
uint[] indices = {
|
||||
0, 1, 2,
|
||||
2, 3, 0
|
||||
};
|
||||
|
||||
_vertexArrayObject = GL.GenVertexArray();
|
||||
_vertexBufferObject = GL.GenBuffer();
|
||||
_elementBufferObject = GL.GenBuffer();
|
||||
|
||||
GL.BindVertexArray(_vertexArrayObject);
|
||||
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * sizeof(float), vertices, BufferUsageHint.StaticDraw);
|
||||
|
||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _elementBufferObject);
|
||||
GL.BufferData(BufferTarget.ElementArrayBuffer, indices.Length * sizeof(uint), indices, BufferUsageHint.StaticDraw);
|
||||
|
||||
// Position attribute
|
||||
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
|
||||
// Texture coordinate attribute
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * sizeof(float), 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
}
|
||||
|
||||
protected override void OnRenderFrame(FrameEventArgs e)
|
||||
{
|
||||
base.OnRenderFrame(e);
|
||||
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
UpdateTextureFromFluid();
|
||||
|
||||
GL.UseProgram(_shaderProgram);
|
||||
GL.BindTexture(TextureTarget.Texture2D, _texture);
|
||||
GL.BindVertexArray(_vertexArrayObject);
|
||||
GL.DrawElements(PrimitiveType.Triangles, 6, DrawElementsType.UnsignedInt, 0);
|
||||
|
||||
SwapBuffers();
|
||||
}
|
||||
|
||||
protected override void OnUpdateFrame(FrameEventArgs e)
|
||||
{
|
||||
base.OnUpdateFrame(e);
|
||||
|
||||
// Update fluid simulation
|
||||
_fluid.Step(0.1f, 0.0001f, 0.0001f);
|
||||
_fluid.FadeDensity(0.002f);
|
||||
|
||||
Title = $"Fluid Simulation :3 - FPS: {1.0 / e.Time:0}";
|
||||
}
|
||||
|
||||
private void UpdateTextureFromFluid()
|
||||
{
|
||||
// Convert fluid density to pixel data
|
||||
for (int y = 0; y < _fluidSizeY; y++)
|
||||
{
|
||||
for (int x = 0; x < _fluidSizeX; x++)
|
||||
{
|
||||
int index = (y * _fluidSizeX + x) * 4;
|
||||
float density = Math.Clamp(_fluid.GetDensity(x, y), 0, 1);
|
||||
|
||||
_pixels[index] = density; // R
|
||||
_pixels[index + 1] = density; // G
|
||||
_pixels[index + 2] = density; // B
|
||||
_pixels[index + 3] = 1.0f; // A
|
||||
}
|
||||
}
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _texture);
|
||||
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, _fluidSizeX, _fluidSizeY,
|
||||
PixelFormat.Rgba, PixelType.Float, _pixels);
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseDown(e);
|
||||
_mouseDown = true;
|
||||
_activeButton = e.Button;
|
||||
_lastMousePos = new Vector2(MousePosition.X, MousePosition.Y);
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseUp(e);
|
||||
_mouseDown = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseMoveEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
if (_mouseDown)
|
||||
{
|
||||
var mousePos = new Vector2(e.X, e.Y);
|
||||
var delta = mousePos - _lastMousePos;
|
||||
|
||||
int fluidX = (int)(e.X / Size.X * _fluidSizeX);
|
||||
int fluidY = (int)((Size.Y - e.Y) / Size.Y * _fluidSizeY);
|
||||
|
||||
// Ensure coordinates are within bounds
|
||||
fluidX = Math.Clamp(fluidX, 1, _fluidSizeX - 2);
|
||||
fluidY = Math.Clamp(fluidY, 1, _fluidSizeY - 2);
|
||||
|
||||
if (_activeButton == MouseButton.Left)
|
||||
{
|
||||
// Add density in a small area
|
||||
for (int i = -2; i <= 2; i++)
|
||||
{
|
||||
for (int j = -2; j <= 2; j++)
|
||||
{
|
||||
_fluid.AddDensity(fluidX + i, fluidY + j, 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_activeButton == MouseButton.Right)
|
||||
{
|
||||
// Add velocity
|
||||
_fluid.AddVelocity(fluidX, fluidY, delta.X * 0.5f, -delta.Y * 0.5f);
|
||||
}
|
||||
|
||||
_lastMousePos = mousePos;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Keys.Escape)
|
||||
Close();
|
||||
else if (e.Key == Keys.R)
|
||||
{
|
||||
_fluid = new FluidSimulation(_fluidSizeX, _fluidSizeY);
|
||||
Console.WriteLine("Simulation reset!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnResize(ResizeEventArgs e)
|
||||
{
|
||||
base.OnResize(e);
|
||||
GL.Viewport(0, 0, e.Width, e.Height);
|
||||
}
|
||||
|
||||
protected override void OnUnload()
|
||||
{
|
||||
GL.DeleteBuffer(_vertexBufferObject);
|
||||
GL.DeleteBuffer(_elementBufferObject);
|
||||
GL.DeleteVertexArray(_vertexArrayObject);
|
||||
GL.DeleteProgram(_shaderProgram);
|
||||
GL.DeleteTexture(_texture);
|
||||
|
||||
base.OnUnload();
|
||||
}
|
||||
}
|
||||
844
simulations/fluids/fluidsC#/FluidWindow3D_GPU.cs
Normal file
844
simulations/fluids/fluidsC#/FluidWindow3D_GPU.cs
Normal file
@@ -0,0 +1,844 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace fluidsC_;
|
||||
|
||||
public class FluidWindow3D_GPU : GameWindow
|
||||
{
|
||||
private int _fluidSizeX = 64;
|
||||
private int _fluidSizeY = 64;
|
||||
private int _fluidSizeZ = 64;
|
||||
|
||||
// GPU buffers - double buffered
|
||||
private int[] _densityTextures = new int[2];
|
||||
private int[] _velocityTextures = new int[2];
|
||||
private int[] _pressureTextures = new int[2];
|
||||
private int _divergenceTexture;
|
||||
private int _obstacleTexture;
|
||||
|
||||
// Shader programs
|
||||
private int _advectionShader;
|
||||
private int _forceShader;
|
||||
private int _divergenceShader;
|
||||
private int _pressureShader;
|
||||
private int _gradientShader;
|
||||
private int _renderShader;
|
||||
private int _clearShader;
|
||||
private int _obstacleShader;
|
||||
|
||||
private int _vertexArrayObject;
|
||||
private int _pointCount;
|
||||
|
||||
private Camera _camera;
|
||||
private bool _firstMove = true;
|
||||
private Vector2 _lastPos;
|
||||
private float _time = 0;
|
||||
|
||||
private int _currentDensity = 0;
|
||||
private int _currentVelocity = 0;
|
||||
private int _currentPressure = 0;
|
||||
|
||||
// Simulation parameters
|
||||
private float _diffusion = 0.0001f;
|
||||
private float _viscosity = 0.0001f;
|
||||
|
||||
public FluidWindow3D_GPU(GameWindowSettings gameWindowSettings, NativeWindowSettings nativeWindowSettings)
|
||||
: base(gameWindowSettings, nativeWindowSettings)
|
||||
{
|
||||
_camera = new Camera(Vector3.UnitZ * 3, Size.X / (float)Size.Y);
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
base.OnLoad();
|
||||
|
||||
Console.WriteLine($"OpenGL Version: {GL.GetString(StringName.Version)}");
|
||||
Console.WriteLine($"GLSL Version: {GL.GetString(StringName.ShadingLanguageVersion)}");
|
||||
|
||||
GL.ClearColor(0.02f, 0.02f, 0.05f, 1.0f);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
GL.Enable(EnableCap.ProgramPointSize);
|
||||
|
||||
CreateTextures();
|
||||
SetupShaders();
|
||||
SetupPointCloud();
|
||||
InitializeObstacles();
|
||||
|
||||
// Add initial fluid
|
||||
AddInitialFluid();
|
||||
|
||||
Console.WriteLine("🎯 WORKING GPU Fluid Simulation - CLICK AND DRAG!");
|
||||
Console.WriteLine("Left Click: Add Fluid | Right Click: Add Force | R: Reset");
|
||||
}
|
||||
|
||||
private void CreateTextures()
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
_densityTextures[i] = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
|
||||
_velocityTextures[i] = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, true);
|
||||
_pressureTextures[i] = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
|
||||
}
|
||||
_divergenceTexture = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
|
||||
_obstacleTexture = Create3DTexture(_fluidSizeX, _fluidSizeY, _fluidSizeZ, false);
|
||||
|
||||
ClearAllTextures();
|
||||
}
|
||||
|
||||
private int Create3DTexture(int width, int height, int depth, bool rgb)
|
||||
{
|
||||
int texture = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture3D, texture);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(TextureTarget.Texture3D, TextureParameterName.TextureWrapR, (int)TextureWrapMode.ClampToEdge);
|
||||
|
||||
var format = rgb ? PixelInternalFormat.Rgb32f : PixelInternalFormat.R32f;
|
||||
var pixelFormat = rgb ? PixelFormat.Rgb : PixelFormat.Red;
|
||||
|
||||
GL.TexImage3D(TextureTarget.Texture3D, 0, format, width, height, depth, 0, pixelFormat, PixelType.Float, IntPtr.Zero);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
private void SetupShaders()
|
||||
{
|
||||
// PROPER FLUID SIMULATION SHADERS
|
||||
_advectionShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
|
||||
|
||||
layout(r32f, binding = 0) uniform image3D densityIn;
|
||||
layout(rgba32f, binding = 1) uniform image3D velocityIn;
|
||||
layout(r32f, binding = 2) uniform image3D densityOut;
|
||||
layout(rgba32f, binding = 3) uniform image3D velocityOut;
|
||||
layout(r32f, binding = 4) uniform image3D obstacles;
|
||||
|
||||
uniform float dt;
|
||||
uniform float dissipation;
|
||||
|
||||
vec3 sampleVelocity(vec3 pos) {
|
||||
ivec3 coord = ivec3(clamp(pos, vec3(0), vec3(imageSize(velocityIn)) - vec3(1)));
|
||||
return imageLoad(velocityIn, coord).rgb;
|
||||
}
|
||||
|
||||
float sampleDensity(vec3 pos) {
|
||||
ivec3 coord = ivec3(clamp(pos, vec3(0), vec3(imageSize(densityIn)) - vec3(1)));
|
||||
return imageLoad(densityIn, coord).r;
|
||||
}
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
ivec3 size = imageSize(densityIn);
|
||||
|
||||
if(coord.x >= size.x || coord.y >= size.y || coord.z >= size.z)
|
||||
return;
|
||||
|
||||
if(imageLoad(obstacles, coord).r > 0.5) {
|
||||
imageStore(densityOut, coord, vec4(0.0));
|
||||
imageStore(velocityOut, coord, vec4(0.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Semi-Lagrangian advection
|
||||
vec3 vel = sampleVelocity(vec3(coord));
|
||||
vec3 prevPos = vec3(coord) - vel * dt * 10.0;
|
||||
|
||||
// Advect density
|
||||
float density = sampleDensity(prevPos);
|
||||
imageStore(densityOut, coord, vec4(density * dissipation, 0.0, 0.0, 1.0));
|
||||
|
||||
// Advect velocity (self-advection)
|
||||
vec3 prevVel = sampleVelocity(prevPos);
|
||||
imageStore(velocityOut, coord, vec4(prevVel, 1.0));
|
||||
}");
|
||||
|
||||
_forceShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
|
||||
|
||||
layout(rgba32f, binding = 0) uniform image3D velocity;
|
||||
layout(r32f, binding = 1) uniform image3D density;
|
||||
layout(r32f, binding = 2) uniform image3D obstacles;
|
||||
|
||||
uniform vec3 force;
|
||||
uniform vec3 position;
|
||||
uniform float radius;
|
||||
uniform float densityAmount;
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
|
||||
if(imageLoad(obstacles, coord).r > 0.5)
|
||||
return;
|
||||
|
||||
vec3 cellPos = vec3(coord);
|
||||
vec3 diff = cellPos - position;
|
||||
float dist = length(diff);
|
||||
|
||||
if(dist < radius) {
|
||||
float influence = (1.0 - dist/radius) * (1.0 - dist/radius);
|
||||
|
||||
// Add force to velocity
|
||||
vec3 currentVel = imageLoad(velocity, coord).rgb;
|
||||
vec3 newVel = currentVel + force * influence * 0.5;
|
||||
imageStore(velocity, coord, vec4(newVel, 1.0));
|
||||
|
||||
// Add density
|
||||
if(densityAmount > 0.0) {
|
||||
float currentDensity = imageLoad(density, coord).r;
|
||||
float newDensity = currentDensity + densityAmount * influence;
|
||||
imageStore(density, coord, vec4(newDensity, 0.0, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
}");
|
||||
|
||||
_divergenceShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
|
||||
|
||||
layout(rgba32f, binding = 0) uniform image3D velocity;
|
||||
layout(r32f, binding = 1) uniform image3D divergence;
|
||||
layout(r32f, binding = 2) uniform image3D obstacles;
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
ivec3 size = imageSize(velocity);
|
||||
|
||||
if(coord.x < 1 || coord.x >= size.x-1 ||
|
||||
coord.y < 1 || coord.y >= size.y-1 ||
|
||||
coord.z < 1 || coord.z >= size.z-1)
|
||||
return;
|
||||
|
||||
if(imageLoad(obstacles, coord).r > 0.5) {
|
||||
imageStore(divergence, coord, vec4(0.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate divergence
|
||||
float left = imageLoad(velocity, coord - ivec3(1,0,0)).r;
|
||||
float right = imageLoad(velocity, coord + ivec3(1,0,0)).r;
|
||||
float down = imageLoad(velocity, coord - ivec3(0,1,0)).g;
|
||||
float up = imageLoad(velocity, coord + ivec3(0,1,0)).g;
|
||||
float back = imageLoad(velocity, coord - ivec3(0,0,1)).b;
|
||||
float front = imageLoad(velocity, coord + ivec3(0,0,1)).b;
|
||||
|
||||
float div = (right - left + up - down + front - back) * 0.5;
|
||||
imageStore(divergence, coord, vec4(div, 0.0, 0.0, 1.0));
|
||||
}");
|
||||
|
||||
_pressureShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
|
||||
|
||||
layout(r32f, binding = 0) uniform image3D pressureIn;
|
||||
layout(r32f, binding = 1) uniform image3D pressureOut;
|
||||
layout(r32f, binding = 2) uniform image3D divergence;
|
||||
layout(r32f, binding = 3) uniform image3D obstacles;
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
ivec3 size = imageSize(pressureIn);
|
||||
|
||||
if(coord.x < 1 || coord.x >= size.x-1 ||
|
||||
coord.y < 1 || coord.y >= size.y-1 ||
|
||||
coord.z < 1 || coord.z >= size.z-1)
|
||||
return;
|
||||
|
||||
if(imageLoad(obstacles, coord).r > 0.5) {
|
||||
imageStore(pressureOut, coord, vec4(0.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Jacobi iteration for pressure
|
||||
float left = imageLoad(pressureIn, coord - ivec3(1,0,0)).r;
|
||||
float right = imageLoad(pressureIn, coord + ivec3(1,0,0)).r;
|
||||
float down = imageLoad(pressureIn, coord - ivec3(0,1,0)).r;
|
||||
float up = imageLoad(pressureIn, coord + ivec3(0,1,0)).r;
|
||||
float back = imageLoad(pressureIn, coord - ivec3(0,0,1)).r;
|
||||
float front = imageLoad(pressureIn, coord + ivec3(0,0,1)).r;
|
||||
|
||||
float div = imageLoad(divergence, coord).r;
|
||||
float pressure = (left + right + down + up + back + front - div) / 6.0;
|
||||
imageStore(pressureOut, coord, vec4(pressure, 0.0, 0.0, 1.0));
|
||||
}");
|
||||
|
||||
_gradientShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in;
|
||||
|
||||
layout(rgba32f, binding = 0) uniform image3D velocity;
|
||||
layout(r32f, binding = 1) uniform image3D pressure;
|
||||
layout(r32f, binding = 2) uniform image3D obstacles;
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
ivec3 size = imageSize(velocity);
|
||||
|
||||
if(coord.x < 1 || coord.x >= size.x-1 ||
|
||||
coord.y < 1 || coord.y >= size.y-1 ||
|
||||
coord.z < 1 || coord.z >= size.z-1)
|
||||
return;
|
||||
|
||||
if(imageLoad(obstacles, coord).r > 0.5) {
|
||||
imageStore(velocity, coord, vec4(0.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Subtract pressure gradient
|
||||
float pressureLeft = imageLoad(pressure, coord - ivec3(1,0,0)).r;
|
||||
float pressureRight = imageLoad(pressure, coord + ivec3(1,0,0)).r;
|
||||
float pressureDown = imageLoad(pressure, coord - ivec3(0,1,0)).r;
|
||||
float pressureUp = imageLoad(pressure, coord + ivec3(0,1,0)).r;
|
||||
float pressureBack = imageLoad(pressure, coord - ivec3(0,0,1)).r;
|
||||
float pressureFront = imageLoad(pressure, coord + ivec3(0,0,1)).r;
|
||||
|
||||
vec3 grad = vec3(
|
||||
pressureRight - pressureLeft,
|
||||
pressureUp - pressureDown,
|
||||
pressureFront - pressureBack
|
||||
) * 0.5;
|
||||
|
||||
vec3 currentVel = imageLoad(velocity, coord).rgb;
|
||||
vec3 newVel = currentVel - grad;
|
||||
imageStore(velocity, coord, vec4(newVel, 1.0));
|
||||
}");
|
||||
|
||||
_clearShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
|
||||
layout(r32f, binding = 0) uniform image3D textureToClear;
|
||||
|
||||
uniform float value;
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
ivec3 size = imageSize(textureToClear);
|
||||
if(coord.x >= size.x || coord.y >= size.y || coord.z >= size.z)
|
||||
return;
|
||||
|
||||
imageStore(textureToClear, coord, vec4(value, 0.0, 0.0, 1.0));
|
||||
}");
|
||||
|
||||
_obstacleShader = CreateComputeShader(@"
|
||||
#version 430
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 8) in;
|
||||
layout(r32f, binding = 0) uniform image3D obstacles;
|
||||
|
||||
uniform vec3 position;
|
||||
uniform float radius;
|
||||
uniform float setValue;
|
||||
|
||||
void main() {
|
||||
ivec3 coord = ivec3(gl_GlobalInvocationID.xyz);
|
||||
ivec3 size = imageSize(obstacles);
|
||||
if(coord.x >= size.x || coord.y >= size.y || coord.z >= size.z)
|
||||
return;
|
||||
|
||||
vec3 cellPos = vec3(coord);
|
||||
float dist = length(cellPos - position);
|
||||
|
||||
if(dist <= radius) {
|
||||
imageStore(obstacles, coord, vec4(setValue, 0.0, 0.0, 1.0));
|
||||
}
|
||||
}");
|
||||
|
||||
// RENDER SHADER
|
||||
_renderShader = CreateRenderShader();
|
||||
}
|
||||
|
||||
private int CreateComputeShader(string source)
|
||||
{
|
||||
var shader = GL.CreateShader(ShaderType.ComputeShader);
|
||||
GL.ShaderSource(shader, source);
|
||||
GL.CompileShader(shader);
|
||||
|
||||
GL.GetShader(shader, ShaderParameter.CompileStatus, out int success);
|
||||
if (success == 0)
|
||||
{
|
||||
var infoLog = GL.GetShaderInfoLog(shader);
|
||||
Console.WriteLine($"Compute Shader Compilation Failed:\n{infoLog}");
|
||||
}
|
||||
|
||||
var program = GL.CreateProgram();
|
||||
GL.AttachShader(program, shader);
|
||||
GL.LinkProgram(program);
|
||||
|
||||
GL.DeleteShader(shader);
|
||||
return program;
|
||||
}
|
||||
|
||||
private int CreateRenderShader()
|
||||
{
|
||||
const string vertexShaderSource = @"
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 aPosition;
|
||||
out vec3 fragTexCoord;
|
||||
uniform mat4 model;
|
||||
uniform mat4 view;
|
||||
uniform mat4 projection;
|
||||
void main()
|
||||
{
|
||||
gl_Position = projection * view * model * vec4(aPosition, 1.0);
|
||||
fragTexCoord = (aPosition + 1.0) * 0.5;
|
||||
gl_PointSize = 4.0;
|
||||
}";
|
||||
|
||||
const string fragmentShaderSource = @"
|
||||
#version 330 core
|
||||
in vec3 fragTexCoord;
|
||||
out vec4 fragColor;
|
||||
uniform sampler3D densityTexture;
|
||||
uniform sampler3D obstacleTexture;
|
||||
uniform float time;
|
||||
void main()
|
||||
{
|
||||
vec3 texCoord = fragTexCoord;
|
||||
if(texCoord.x < 0.0 || texCoord.x > 1.0 ||
|
||||
texCoord.y < 0.0 || texCoord.y > 1.0 ||
|
||||
texCoord.z < 0.0 || texCoord.z > 1.0)
|
||||
discard;
|
||||
|
||||
float density = texture(densityTexture, texCoord).r;
|
||||
float obstacle = texture(obstacleTexture, texCoord).r;
|
||||
|
||||
if(obstacle > 0.5) {
|
||||
fragColor = vec4(0.3, 0.3, 0.3, 1.0);
|
||||
} else if(density > 0.01) {
|
||||
vec3 color = mix(vec3(0.1, 0.3, 0.8), vec3(0.0, 0.8, 1.0), density * 2.0);
|
||||
color += 0.1 * sin(time + fragTexCoord.y * 10.0);
|
||||
fragColor = vec4(color, density * 0.9);
|
||||
} else {
|
||||
discard;
|
||||
}
|
||||
}";
|
||||
|
||||
var vertexShader = GL.CreateShader(ShaderType.VertexShader);
|
||||
GL.ShaderSource(vertexShader, vertexShaderSource);
|
||||
GL.CompileShader(vertexShader);
|
||||
|
||||
var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
|
||||
GL.ShaderSource(fragmentShader, fragmentShaderSource);
|
||||
GL.CompileShader(fragmentShader);
|
||||
|
||||
var program = GL.CreateProgram();
|
||||
GL.AttachShader(program, vertexShader);
|
||||
GL.AttachShader(program, fragmentShader);
|
||||
GL.LinkProgram(program);
|
||||
|
||||
GL.DeleteShader(vertexShader);
|
||||
GL.DeleteShader(fragmentShader);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
private void SetupPointCloud()
|
||||
{
|
||||
var vertices = new List<Vector3>();
|
||||
|
||||
// Reduced point count for performance but still good coverage
|
||||
for (int x = 0; x < _fluidSizeX; x += 1)
|
||||
{
|
||||
for (int y = 0; y < _fluidSizeY; y += 1)
|
||||
{
|
||||
for (int z = 0; z < _fluidSizeZ; z += 1)
|
||||
{
|
||||
float px = (x / (float)_fluidSizeX) * 2.0f - 1.0f;
|
||||
float py = (y / (float)_fluidSizeY) * 2.0f - 1.0f;
|
||||
float pz = (z / (float)_fluidSizeZ) * 2.0f - 1.0f;
|
||||
vertices.Add(new Vector3(px, py, pz));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_pointCount = vertices.Count;
|
||||
Console.WriteLine($"Rendering {_pointCount} points");
|
||||
|
||||
_vertexArrayObject = GL.GenVertexArray();
|
||||
int vbo = GL.GenBuffer();
|
||||
|
||||
GL.BindVertexArray(_vertexArrayObject);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, vertices.Count * 3 * sizeof(float), vertices.ToArray(), BufferUsageHint.StaticDraw);
|
||||
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 3 * sizeof(float), 0);
|
||||
GL.EnableVertexAttribArray(0);
|
||||
}
|
||||
|
||||
private void InitializeObstacles()
|
||||
{
|
||||
// Simple box boundaries
|
||||
AddBoxObstacle(new Vector3(0, 0, 0), new Vector3(_fluidSizeX, 2, _fluidSizeZ)); // Floor
|
||||
AddBoxObstacle(new Vector3(0, 0, 0), new Vector3(2, _fluidSizeY, _fluidSizeZ)); // Left
|
||||
AddBoxObstacle(new Vector3(_fluidSizeX - 2, 0, 0), new Vector3(2, _fluidSizeY, _fluidSizeZ)); // Right
|
||||
AddBoxObstacle(new Vector3(0, 0, 0), new Vector3(_fluidSizeX, _fluidSizeY, 2)); // Back
|
||||
AddBoxObstacle(new Vector3(0, 0, _fluidSizeZ - 2), new Vector3(_fluidSizeX, _fluidSizeY, 2)); // Front
|
||||
|
||||
// Central obstacle
|
||||
AddSphereObstacle(new Vector3(_fluidSizeX / 2, _fluidSizeY / 4, _fluidSizeZ / 2), 6.0f);
|
||||
}
|
||||
|
||||
private void AddInitialFluid()
|
||||
{
|
||||
// Add initial falling fluid
|
||||
Vector3 sourcePos = new Vector3(_fluidSizeX / 2, _fluidSizeY - 10, _fluidSizeZ / 2);
|
||||
AddForce(sourcePos, new Vector3(0.0f, -2.0f, 0.0f), 8.0f, 10.0f);
|
||||
|
||||
// Add some random fluid blobs
|
||||
Random rand = new Random();
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
Vector3 randomPos = new Vector3(
|
||||
rand.Next(10, _fluidSizeX - 10),
|
||||
rand.Next(10, _fluidSizeY - 10),
|
||||
rand.Next(10, _fluidSizeZ - 10)
|
||||
);
|
||||
AddForce(randomPos, Vector3.Zero, 4.0f, 3.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSphereObstacle(Vector3 center, float radius)
|
||||
{
|
||||
GL.UseProgram(_obstacleShader);
|
||||
BindImageTexture(0, _obstacleTexture, TextureAccess.WriteOnly);
|
||||
|
||||
GL.Uniform3(GL.GetUniformLocation(_obstacleShader, "position"), center);
|
||||
GL.Uniform1(GL.GetUniformLocation(_obstacleShader, "radius"), radius);
|
||||
GL.Uniform1(GL.GetUniformLocation(_obstacleShader, "setValue"), 1.0f);
|
||||
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
}
|
||||
|
||||
private void AddBoxObstacle(Vector3 min, Vector3 max)
|
||||
{
|
||||
Vector3 center = (min + max) * 0.5f;
|
||||
Vector3 size = max - min;
|
||||
AddSphereObstacle(center, Math.Max(size.X, Math.Max(size.Y, size.Z)) * 0.5f);
|
||||
}
|
||||
|
||||
protected override void OnRenderFrame(FrameEventArgs e)
|
||||
{
|
||||
base.OnRenderFrame(e);
|
||||
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
StepSimulation((float)e.Time);
|
||||
RenderFluid();
|
||||
|
||||
SwapBuffers();
|
||||
}
|
||||
|
||||
private void StepSimulation(float dt)
|
||||
{
|
||||
dt = Math.Min(dt, 0.016f); // Cap at ~60fps
|
||||
|
||||
// Full fluid simulation steps
|
||||
Advect(dt, 0.99f);
|
||||
AddExternalForces(dt);
|
||||
Project(dt);
|
||||
AddDemoSources();
|
||||
|
||||
// Swap buffers
|
||||
_currentDensity = 1 - _currentDensity;
|
||||
_currentVelocity = 1 - _currentVelocity;
|
||||
_currentPressure = 1 - _currentPressure;
|
||||
}
|
||||
|
||||
private void Advect(float dt, float dissipation)
|
||||
{
|
||||
GL.UseProgram(_advectionShader);
|
||||
|
||||
BindImageTexture(0, _densityTextures[_currentDensity], TextureAccess.ReadOnly);
|
||||
BindImageTexture(1, _velocityTextures[_currentVelocity], TextureAccess.ReadOnly);
|
||||
BindImageTexture(2, _densityTextures[1 - _currentDensity], TextureAccess.WriteOnly);
|
||||
BindImageTexture(3, _velocityTextures[1 - _currentVelocity], TextureAccess.WriteOnly);
|
||||
BindImageTexture(4, _obstacleTexture, TextureAccess.ReadOnly);
|
||||
|
||||
GL.Uniform1(GL.GetUniformLocation(_advectionShader, "dt"), dt);
|
||||
GL.Uniform1(GL.GetUniformLocation(_advectionShader, "dissipation"), dissipation);
|
||||
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
}
|
||||
|
||||
private void AddExternalForces(float dt)
|
||||
{
|
||||
// Gravity
|
||||
Vector3 gravity = new Vector3(0.0f, -9.8f, 0.0f) * dt;
|
||||
|
||||
GL.UseProgram(_forceShader);
|
||||
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadWrite);
|
||||
BindImageTexture(1, _densityTextures[_currentDensity], TextureAccess.ReadWrite);
|
||||
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
|
||||
|
||||
GL.Uniform3(GL.GetUniformLocation(_forceShader, "force"), gravity);
|
||||
GL.Uniform3(GL.GetUniformLocation(_forceShader, "position"), new Vector3(_fluidSizeX / 2, _fluidSizeY / 2, _fluidSizeZ / 2));
|
||||
GL.Uniform1(GL.GetUniformLocation(_forceShader, "radius"), _fluidSizeX * 2.0f); // Affect entire field
|
||||
GL.Uniform1(GL.GetUniformLocation(_forceShader, "densityAmount"), 0.0f);
|
||||
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
}
|
||||
|
||||
private void Project(float dt)
|
||||
{
|
||||
// Calculate divergence
|
||||
GL.UseProgram(_divergenceShader);
|
||||
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadOnly);
|
||||
BindImageTexture(1, _divergenceTexture, TextureAccess.WriteOnly);
|
||||
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
// Solve pressure (Jacobi iterations)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
GL.UseProgram(_pressureShader);
|
||||
BindImageTexture(0, _pressureTextures[_currentPressure], TextureAccess.ReadOnly);
|
||||
BindImageTexture(1, _pressureTextures[1 - _currentPressure], TextureAccess.WriteOnly);
|
||||
BindImageTexture(2, _divergenceTexture, TextureAccess.ReadOnly);
|
||||
BindImageTexture(3, _obstacleTexture, TextureAccess.ReadOnly);
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
_currentPressure = 1 - _currentPressure;
|
||||
}
|
||||
|
||||
// Subtract pressure gradient
|
||||
GL.UseProgram(_gradientShader);
|
||||
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadWrite);
|
||||
BindImageTexture(1, _pressureTextures[_currentPressure], TextureAccess.ReadOnly);
|
||||
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
}
|
||||
|
||||
private void AddDemoSources()
|
||||
{
|
||||
// Continuous falling source
|
||||
Vector3 sourcePos = new Vector3(_fluidSizeX / 2, _fluidSizeY - 8, _fluidSizeZ / 2);
|
||||
AddForce(sourcePos, new Vector3(0.0f, -3.0f, 0.0f), 6.0f, 2.0f);
|
||||
|
||||
// Random turbulence
|
||||
if ((int)(_time * 2) % 4 == 0)
|
||||
{
|
||||
Vector3 randomPos = new Vector3(
|
||||
_fluidSizeX * 0.3f + MathF.Sin(_time) * 5.0f,
|
||||
_fluidSizeY - 5,
|
||||
_fluidSizeZ * 0.7f + MathF.Cos(_time) * 5.0f
|
||||
);
|
||||
AddForce(randomPos, new Vector3(0.0f, -2.0f, 0.0f), 4.0f, 1.5f);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddForce(Vector3 position, Vector3 force, float radius, float densityAmount)
|
||||
{
|
||||
GL.UseProgram(_forceShader);
|
||||
|
||||
BindImageTexture(0, _velocityTextures[_currentVelocity], TextureAccess.ReadWrite);
|
||||
BindImageTexture(1, _densityTextures[_currentDensity], TextureAccess.ReadWrite);
|
||||
BindImageTexture(2, _obstacleTexture, TextureAccess.ReadOnly);
|
||||
|
||||
GL.Uniform3(GL.GetUniformLocation(_forceShader, "force"), force);
|
||||
GL.Uniform3(GL.GetUniformLocation(_forceShader, "position"), position);
|
||||
GL.Uniform1(GL.GetUniformLocation(_forceShader, "radius"), radius);
|
||||
GL.Uniform1(GL.GetUniformLocation(_forceShader, "densityAmount"), densityAmount);
|
||||
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 4, 4, 4);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
}
|
||||
|
||||
private void RenderFluid()
|
||||
{
|
||||
GL.UseProgram(_renderShader);
|
||||
|
||||
var model = Matrix4.Identity;
|
||||
var view = _camera.GetViewMatrix();
|
||||
var projection = _camera.GetProjectionMatrix();
|
||||
|
||||
GL.UniformMatrix4(GL.GetUniformLocation(_renderShader, "model"), false, ref model);
|
||||
GL.UniformMatrix4(GL.GetUniformLocation(_renderShader, "view"), false, ref view);
|
||||
GL.UniformMatrix4(GL.GetUniformLocation(_renderShader, "projection"), false, ref projection);
|
||||
GL.Uniform1(GL.GetUniformLocation(_renderShader, "time"), _time);
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture3D, _densityTextures[_currentDensity]);
|
||||
GL.Uniform1(GL.GetUniformLocation(_renderShader, "densityTexture"), 0);
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture3D, _obstacleTexture);
|
||||
GL.Uniform1(GL.GetUniformLocation(_renderShader, "obstacleTexture"), 1);
|
||||
|
||||
GL.BindVertexArray(_vertexArrayObject);
|
||||
GL.DrawArrays(PrimitiveType.Points, 0, _pointCount);
|
||||
}
|
||||
|
||||
private void BindImageTexture(int unit, int texture, TextureAccess access)
|
||||
{
|
||||
GL.BindImageTexture(unit, texture, 0, false, 0, access, SizedInternalFormat.R32f);
|
||||
}
|
||||
|
||||
private void DispatchCompute(int sizeX, int sizeY, int sizeZ, int localSizeX, int localSizeY, int localSizeZ)
|
||||
{
|
||||
int groupsX = (sizeX + localSizeX - 1) / localSizeX;
|
||||
int groupsY = (sizeY + localSizeY - 1) / localSizeY;
|
||||
int groupsZ = (sizeZ + localSizeZ - 1) / localSizeZ;
|
||||
|
||||
GL.DispatchCompute(groupsX, groupsY, groupsZ);
|
||||
}
|
||||
|
||||
private void ClearAllTextures()
|
||||
{
|
||||
GL.UseProgram(_clearShader);
|
||||
|
||||
foreach (var texture in _densityTextures)
|
||||
{
|
||||
BindImageTexture(0, texture, TextureAccess.WriteOnly);
|
||||
GL.Uniform1(GL.GetUniformLocation(_clearShader, "value"), 0.0f);
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
|
||||
}
|
||||
|
||||
foreach (var texture in _velocityTextures)
|
||||
{
|
||||
BindImageTexture(0, texture, TextureAccess.WriteOnly);
|
||||
GL.Uniform1(GL.GetUniformLocation(_clearShader, "value"), 0.0f);
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
|
||||
}
|
||||
|
||||
foreach (var texture in _pressureTextures)
|
||||
{
|
||||
BindImageTexture(0, texture, TextureAccess.WriteOnly);
|
||||
GL.Uniform1(GL.GetUniformLocation(_clearShader, "value"), 0.0f);
|
||||
DispatchCompute(_fluidSizeX, _fluidSizeY, _fluidSizeZ, 8, 8, 8);
|
||||
}
|
||||
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.AllBarrierBits);
|
||||
|
||||
// Re-add initial fluid
|
||||
AddInitialFluid();
|
||||
}
|
||||
|
||||
protected override void OnUpdateFrame(FrameEventArgs e)
|
||||
{
|
||||
base.OnUpdateFrame(e);
|
||||
|
||||
_time += (float)e.Time;
|
||||
|
||||
_camera.UpdateVectors();
|
||||
|
||||
var input = KeyboardState;
|
||||
float cameraSpeed = _camera.Speed * (float)e.Time;
|
||||
|
||||
if (input.IsKeyDown(Keys.W))
|
||||
_camera.Position += _camera.Front * cameraSpeed;
|
||||
if (input.IsKeyDown(Keys.S))
|
||||
_camera.Position -= _camera.Front * cameraSpeed;
|
||||
if (input.IsKeyDown(Keys.A))
|
||||
_camera.Position -= _camera.Right * cameraSpeed;
|
||||
if (input.IsKeyDown(Keys.D))
|
||||
_camera.Position += _camera.Right * cameraSpeed;
|
||||
if (input.IsKeyDown(Keys.Space))
|
||||
_camera.Position += _camera.Up * cameraSpeed;
|
||||
if (input.IsKeyDown(Keys.LeftShift))
|
||||
_camera.Position -= _camera.Up * cameraSpeed;
|
||||
|
||||
Title = $"🎯 WORKING Fluid Simulation - FPS: {1.0 / e.Time:0} - CLICK TO INTERACT!";
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseMoveEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
if (IsFocused)
|
||||
{
|
||||
if (_firstMove)
|
||||
{
|
||||
_lastPos = new Vector2(e.X, e.Y);
|
||||
_firstMove = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var deltaX = e.X - _lastPos.X;
|
||||
var deltaY = e.Y - _lastPos.Y;
|
||||
_lastPos = new Vector2(e.X, e.Y);
|
||||
|
||||
_camera.Yaw += deltaX * _camera.Sensitivity;
|
||||
_camera.Pitch -= deltaY * _camera.Sensitivity;
|
||||
|
||||
_camera.Pitch = Math.Clamp(_camera.Pitch, -MathHelper.PiOver2 + 0.1f, MathHelper.PiOver2 - 0.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseDown(e);
|
||||
|
||||
var mousePos = new Vector2(MouseState.X, MouseState.Y);
|
||||
|
||||
// Simple direct mapping
|
||||
Vector3 fluidPos = new Vector3(
|
||||
(mousePos.X / Size.X) * _fluidSizeX,
|
||||
(1.0f - mousePos.Y / Size.Y) * _fluidSizeY,
|
||||
_fluidSizeZ / 2
|
||||
);
|
||||
|
||||
// Clamp to reasonable bounds
|
||||
fluidPos.X = Math.Clamp(fluidPos.X, 5, _fluidSizeX - 5);
|
||||
fluidPos.Y = Math.Clamp(fluidPos.Y, 5, _fluidSizeY - 5);
|
||||
fluidPos.Z = Math.Clamp(fluidPos.Z, 5, _fluidSizeZ - 5);
|
||||
|
||||
if (e.Button == MouseButton.Left)
|
||||
{
|
||||
AddForce(fluidPos, Vector3.Zero, 8.0f, 15.0f);
|
||||
Console.WriteLine($"💧 Added DENSITY at: {fluidPos}");
|
||||
}
|
||||
else if (e.Button == MouseButton.Right)
|
||||
{
|
||||
// Add velocity away from center for explosion effect
|
||||
Vector3 center = new Vector3(_fluidSizeX / 2, _fluidSizeY / 2, _fluidSizeZ / 2);
|
||||
Vector3 dir = Vector3.Normalize(fluidPos - center);
|
||||
AddForce(fluidPos, dir * 20.0f, 6.0f, 0.0f);
|
||||
Console.WriteLine($"💨 Added VELOCITY at: {fluidPos}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyboardKeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Keys.Escape)
|
||||
Close();
|
||||
else if (e.Key == Keys.R)
|
||||
{
|
||||
ClearAllTextures();
|
||||
Console.WriteLine("🔄 Simulation RESET!");
|
||||
}
|
||||
else if (e.Key == Keys.O)
|
||||
{
|
||||
var mousePos = new Vector2(MouseState.X, MouseState.Y);
|
||||
Vector3 fluidPos = new Vector3(
|
||||
(mousePos.X / Size.X) * _fluidSizeX,
|
||||
(1.0f - mousePos.Y / Size.Y) * _fluidSizeY,
|
||||
_fluidSizeZ / 2
|
||||
);
|
||||
AddSphereObstacle(fluidPos, 5.0f);
|
||||
Console.WriteLine($"🧱 Added OBSTACLE at: {fluidPos}");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnResize(ResizeEventArgs e)
|
||||
{
|
||||
base.OnResize(e);
|
||||
GL.Viewport(0, 0, Size.X, Size.Y);
|
||||
_camera.AspectRatio = Size.X / (float)Size.Y;
|
||||
}
|
||||
}
|
||||
26
simulations/fluids/fluidsC#/Program.cs
Normal file
26
simulations/fluids/fluidsC#/Program.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
|
||||
namespace fluidsC_;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
var settings = GameWindowSettings.Default;
|
||||
var nativeSettings = new NativeWindowSettings()
|
||||
{
|
||||
Size = new OpenTK.Mathematics.Vector2i(1200, 800),
|
||||
Title = "GPU Fluid Simulation - FIXED",
|
||||
Flags = ContextFlags.ForwardCompatible,
|
||||
Profile = ContextProfile.Core,
|
||||
API = ContextAPI.OpenGL,
|
||||
APIVersion = new System.Version(4, 3)
|
||||
};
|
||||
|
||||
using (var window = new FluidWindow3D_GPU(settings, nativeSettings))
|
||||
{
|
||||
window.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
simulations/fluids/fluidsC#/fluidsC#.csproj
Normal file
15
simulations/fluids/fluidsC#/fluidsC#.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>fluidsC_</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK" Version="4.9.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
326
simulations/fluids/pygl.py
Normal file
326
simulations/fluids/pygl.py
Normal file
@@ -0,0 +1,326 @@
|
||||
"""
|
||||
Stable 2D fluid simulation (Jos Stam) using Pygame + PyOpenGL + NumPy.
|
||||
|
||||
- Single-file demo.
|
||||
- Left mouse: add density (smoke).
|
||||
- Right mouse: add velocity (drag to create flow).
|
||||
- Keys: C = clear, D = toggle display velocity, Esc or close window to quit.
|
||||
|
||||
Dependencies:
|
||||
pip install pygame PyOpenGL numpy
|
||||
|
||||
Run:
|
||||
python stable_fluid_pygame_opengl.py
|
||||
|
||||
Notes: This is a CPU solver on a grid (not GPU accelerated). Grid size defaults to 128x128 which is interactive on modern machines.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import math
|
||||
import numpy as np
|
||||
import pygame
|
||||
from pygame.locals import *
|
||||
from OpenGL.GL import *
|
||||
|
||||
# ----------------------- Fluid solver (Stable Fluids, Stam) -----------------------
|
||||
|
||||
class StableFluid:
|
||||
def __init__(self, N, diffusion, viscosity, dt):
|
||||
self.N = N # grid size (N x N)
|
||||
self.size = N + 2 # include boundary
|
||||
self.dt = dt
|
||||
self.diff = diffusion
|
||||
self.visc = viscosity
|
||||
|
||||
# fields (with boundary padding)
|
||||
self.s = np.zeros((self.size, self.size), dtype=np.float32) # temp
|
||||
self.density = np.zeros((self.size, self.size), dtype=np.float32) # smoke
|
||||
|
||||
self.Vx = np.zeros((self.size, self.size), dtype=np.float32)
|
||||
self.Vy = np.zeros((self.size, self.size), dtype=np.float32)
|
||||
self.Vx0 = np.zeros((self.size, self.size), dtype=np.float32)
|
||||
self.Vy0 = np.zeros((self.size, self.size), dtype=np.float32)
|
||||
|
||||
def add_density(self, x, y, amount):
|
||||
self.density[y, x] += amount
|
||||
|
||||
def add_velocity(self, x, y, amount_x, amount_y):
|
||||
self.Vx[y, x] += amount_x
|
||||
self.Vy[y, x] += amount_y
|
||||
|
||||
def step(self):
|
||||
N = self.N
|
||||
diff = self.diff
|
||||
visc = self.visc
|
||||
dt = self.dt
|
||||
|
||||
self.diffuse(1, self.Vx0, self.Vx, visc, dt)
|
||||
self.diffuse(2, self.Vy0, self.Vy, visc, dt)
|
||||
|
||||
self.project(self.Vx0, self.Vy0, self.Vx, self.Vy)
|
||||
|
||||
self.advect(1, self.Vx, self.Vx0, self.Vx0, self.Vy0, dt)
|
||||
self.advect(2, self.Vy, self.Vy0, self.Vx0, self.Vy0, dt)
|
||||
|
||||
self.project(self.Vx, self.Vy, self.Vx0, self.Vy0)
|
||||
|
||||
self.diffuse(0, self.s, self.density, diff, dt)
|
||||
self.advect(0, self.density, self.s, self.Vx, self.Vy, dt)
|
||||
|
||||
# helper routines
|
||||
def set_bnd(self, b, x):
|
||||
N = self.N
|
||||
# edges
|
||||
x[0, 1:N+1] = -x[1, 1:N+1] if b == 2 else x[1, 1:N+1]
|
||||
x[N+1, 1:N+1] = -x[N, 1:N+1] if b == 2 else x[N, 1:N+1]
|
||||
|
||||
x[1:N+1, 0] = -x[1:N+1, 1] if b == 1 else x[1:N+1, 1]
|
||||
x[1:N+1, N+1] = -x[1:N+1, N] if b == 1 else x[1:N+1, N]
|
||||
|
||||
# corners
|
||||
x[0, 0] = 0.5 * (x[1, 0] + x[0, 1])
|
||||
x[0, N+1] = 0.5 * (x[1, N+1] + x[0, N])
|
||||
x[N+1, 0] = 0.5 * (x[N, 0] + x[N+1, 1])
|
||||
x[N+1, N+1] = 0.5 * (x[N, N+1] + x[N+1, N])
|
||||
|
||||
def lin_solve(self, b, x, x0, a, c):
|
||||
N = self.N
|
||||
for _ in range(20): # Gauss-Seidel iterations
|
||||
x[1:N+1, 1:N+1] = (
|
||||
x0[1:N+1, 1:N+1] + a * (
|
||||
x[0:N, 1:N+1] + x[2:N+2, 1:N+1] + x[1:N+1, 0:N] + x[1:N+1, 2:N+2]
|
||||
)
|
||||
) / c
|
||||
self.set_bnd(b, x)
|
||||
|
||||
def diffuse(self, b, x, x0, diff, dt):
|
||||
N = self.N
|
||||
a = dt * diff * N * N
|
||||
self.lin_solve(b, x, x0, a, 1 + 4 * a)
|
||||
|
||||
def advect(self, b, d, d0, velocX, velocY, dt):
|
||||
N = self.N
|
||||
dt0 = dt * N
|
||||
for i in range(1, N+1):
|
||||
for j in range(1, N+1):
|
||||
x = i - dt0 * velocX[j, i]
|
||||
y = j - dt0 * velocY[j, i]
|
||||
|
||||
if x < 0.5:
|
||||
x = 0.5
|
||||
if x > N + 0.5:
|
||||
x = N + 0.5
|
||||
i0 = int(math.floor(x))
|
||||
i1 = i0 + 1
|
||||
|
||||
if y < 0.5:
|
||||
y = 0.5
|
||||
if y > N + 0.5:
|
||||
y = N + 0.5
|
||||
j0 = int(math.floor(y))
|
||||
j1 = j0 + 1
|
||||
|
||||
s1 = x - i0
|
||||
s0 = 1 - s1
|
||||
t1 = y - j0
|
||||
t0 = 1 - t1
|
||||
|
||||
d[j, i] = (
|
||||
s0 * (t0 * d0[j0, i0] + t1 * d0[j1, i0]) +
|
||||
s1 * (t0 * d0[j0, i1] + t1 * d0[j1, i1])
|
||||
)
|
||||
self.set_bnd(b, d)
|
||||
|
||||
def project(self, velocX, velocY, p, div):
|
||||
N = self.N
|
||||
div[1:N+1, 1:N+1] = -0.5 * (
|
||||
velocX[1:N+1, 2:N+2] - velocX[1:N+1, 0:N] + velocY[2:N+2, 1:N+1] - velocY[0:N, 1:N+1]
|
||||
) / N
|
||||
p[1:N+1, 1:N+1] = 0
|
||||
self.set_bnd(0, div)
|
||||
self.set_bnd(0, p)
|
||||
|
||||
self.lin_solve(0, p, div, 1, 4)
|
||||
|
||||
velocX[1:N+1, 1:N+1] -= 0.5 * (p[1:N+1, 2:N+2] - p[1:N+1, 0:N]) * N
|
||||
velocY[1:N+1, 1:N+1] -= 0.5 * (p[2:N+2, 1:N+1] - p[0:N, 1:N+1]) * N
|
||||
|
||||
self.set_bnd(1, velocX)
|
||||
self.set_bnd(2, velocY)
|
||||
|
||||
# ----------------------- OpenGL texture renderer -----------------------
|
||||
|
||||
class GLRenderer:
|
||||
def __init__(self, width, height, gridN):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.gridN = gridN
|
||||
self.tex_id = glGenTextures(1)
|
||||
self.init_texture()
|
||||
|
||||
def init_texture(self):
|
||||
glBindTexture(GL_TEXTURE_2D, self.tex_id)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
|
||||
# initial empty texture (RGBA)
|
||||
data = np.zeros((self.gridN, self.gridN, 4), dtype=np.uint8)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.gridN, self.gridN, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
|
||||
|
||||
def update_texture_from_density(self, density):
|
||||
# density is (N+2)x(N+2), we upload only 1..N
|
||||
N = self.gridN
|
||||
den = density[1:N+1, 1:N+1]
|
||||
# map density to 0-255
|
||||
img = np.empty((N, N, 4), dtype=np.uint8)
|
||||
# grayscale smoke map; store in rgb and alpha
|
||||
clipped = np.clip(den * 255.0, 0, 255).astype(np.uint8)
|
||||
img[:, :, 0] = clipped
|
||||
img[:, :, 1] = clipped
|
||||
img[:, :, 2] = clipped
|
||||
img[:, :, 3] = clipped
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, self.tex_id)
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, N, N, GL_RGBA, GL_UNSIGNED_BYTE, img)
|
||||
|
||||
def draw_fullscreen(self):
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
glBindTexture(GL_TEXTURE_2D, self.tex_id)
|
||||
glBegin(GL_QUADS)
|
||||
glTexCoord2f(0.0, 0.0); glVertex2f(-1.0, -1.0)
|
||||
glTexCoord2f(1.0, 0.0); glVertex2f(1.0, -1.0)
|
||||
glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0)
|
||||
glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, 1.0)
|
||||
glEnd()
|
||||
glDisable(GL_TEXTURE_2D)
|
||||
|
||||
def draw_velocity(self, fluid):
|
||||
# draw simple vector field lines
|
||||
N = fluid.N
|
||||
glPushAttrib(GL_CURRENT_BIT | GL_POINT_BIT | GL_LINE_BIT)
|
||||
glPointSize(2.0)
|
||||
glBegin(GL_LINES)
|
||||
step = max(1, N // 32)
|
||||
for i in range(1, N+1, step):
|
||||
for j in range(1, N+1, step):
|
||||
x = (i - 0.5) / N * 2 - 1
|
||||
y = (j - 0.5) / N * 2 - 1
|
||||
vx = fluid.Vx[j, i]
|
||||
vy = fluid.Vy[j, i]
|
||||
glVertex2f(x, y)
|
||||
glVertex2f(x + vx * 0.02, y + vy * 0.02)
|
||||
glEnd()
|
||||
glPopAttrib()
|
||||
|
||||
# ----------------------- Main app -----------------------
|
||||
|
||||
def main():
|
||||
# settings
|
||||
WIN_W, WIN_H = 800, 800
|
||||
GRID_N = 128
|
||||
DIFFUSION = 0.0001
|
||||
VISCOSITY = 0.0001
|
||||
DT = 0.1
|
||||
|
||||
pygame.init()
|
||||
pygame.display.set_mode((WIN_W, WIN_H), DOUBLEBUF | OPENGL)
|
||||
pygame.display.set_caption('Stable Fluid (Stam) - Pygame + PyOpenGL')
|
||||
|
||||
# setup orthographic projection
|
||||
glViewport(0, 0, WIN_W, WIN_H)
|
||||
glMatrixMode(GL_PROJECTION)
|
||||
glLoadIdentity()
|
||||
glOrtho(-1, 1, -1, 1, -1, 1)
|
||||
glMatrixMode(GL_MODELVIEW)
|
||||
glLoadIdentity()
|
||||
|
||||
fluid = StableFluid(GRID_N, DIFFUSION, VISCOSITY, DT)
|
||||
renderer = GLRenderer(WIN_W, WIN_H, GRID_N)
|
||||
|
||||
clock = pygame.time.Clock()
|
||||
running = True
|
||||
show_velocity = False
|
||||
|
||||
is_down_left = False
|
||||
is_down_right = False
|
||||
last_mouse = None
|
||||
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == QUIT:
|
||||
running = False
|
||||
elif event.type == KEYDOWN:
|
||||
if event.key == K_ESCAPE:
|
||||
running = False
|
||||
elif event.key == K_c:
|
||||
fluid.density.fill(0)
|
||||
fluid.Vx.fill(0)
|
||||
fluid.Vy.fill(0)
|
||||
elif event.key == K_d:
|
||||
show_velocity = not show_velocity
|
||||
elif event.type == MOUSEBUTTONDOWN:
|
||||
if event.button == 1:
|
||||
is_down_left = True
|
||||
elif event.button == 3:
|
||||
is_down_right = True
|
||||
last_mouse = pygame.mouse.get_pos()
|
||||
elif event.type == MOUSEBUTTONUP:
|
||||
if event.button == 1:
|
||||
is_down_left = False
|
||||
elif event.button == 3:
|
||||
is_down_right = False
|
||||
last_mouse = None
|
||||
|
||||
mx, my = pygame.mouse.get_pos()
|
||||
# convert to grid coords (i,x) with padding
|
||||
def screen_to_grid(px, py):
|
||||
gx = int((px / WIN_W) * GRID_N) + 1
|
||||
gy = int(((WIN_H - py) / WIN_H) * GRID_N) + 1
|
||||
gx = max(1, min(GRID_N, gx))
|
||||
gy = max(1, min(GRID_N, gy))
|
||||
return gx, gy
|
||||
|
||||
if last_mouse is not None:
|
||||
lx, ly = last_mouse
|
||||
if is_down_left or is_down_right:
|
||||
g1 = screen_to_grid(lx, ly)
|
||||
g2 = screen_to_grid(mx, my)
|
||||
gx1, gy1 = g1
|
||||
gx2, gy2 = g2
|
||||
# draw along line between points
|
||||
steps = max(abs(gx2 - gx1), abs(gy2 - gy1), 1)
|
||||
for s in range(steps + 1):
|
||||
t = s / steps
|
||||
gx = int(round(gx1 + (gx2 - gx1) * t))
|
||||
gy = int(round(gy1 + (gy2 - gy1) * t))
|
||||
if is_down_left:
|
||||
fluid.add_density(gx, gy, 100.0 * DT)
|
||||
if is_down_right:
|
||||
vx = (gx2 - gx1) * 5.0
|
||||
vy = (gy2 - gy1) * 5.0
|
||||
fluid.add_velocity(gx, gy, vx, vy)
|
||||
last_mouse = (mx, my)
|
||||
|
||||
# step simulation
|
||||
fluid.step()
|
||||
|
||||
# render
|
||||
glClear(GL_COLOR_BUFFER_BIT)
|
||||
renderer.update_texture_from_density(fluid.density)
|
||||
renderer.draw_fullscreen()
|
||||
|
||||
if show_velocity:
|
||||
glColor3f(1.0, 0.0, 0.0)
|
||||
renderer.draw_velocity(fluid)
|
||||
glColor3f(1.0, 1.0, 1.0)
|
||||
|
||||
pygame.display.flip()
|
||||
#
|
||||
clock.tick(10)
|
||||
|
||||
pygame.quit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
5
simulations/mandelbrotset/cpp/readme.md
Normal file
5
simulations/mandelbrotset/cpp/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Compilation Steps
|
||||
|
||||
```powershell
|
||||
cl /EHsc /std:c++17 main.cpp mandelbrot_app.cpp /link user32.lib gdi32.lib d3d11.lib d3dcompiler.lib
|
||||
```
|
||||
Reference in New Issue
Block a user