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:
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user