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