写了一个模拟小球弹性碰撞的Demo,先贴上来。
场景说明:
随机生成N个小球,随机初始速度,初始方向,质量,大小,颜色。
每个小球都看做是质点。球和球,球和壁的碰撞都是弹性的(即碰撞后X,Y轴方向变反,大小不变),动能守恒。
左击鼠标,停止运动,用箭头标识当前的运动方向。再单击则开始运动。
右击鼠标,重新初始化场景重新开始。
程序由: 一个Ball类,Game类,和MainForm组成
MainForm提供场景的"画布", Game控制小球(Ball类)的运动。
Form代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Balls { public partial class Form1 : Form { private Game _game = null; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } private void Form1_Shown(object sender, EventArgs e) { _game = new Game(this.panel1.CreateGraphics()); _game.Start(); } private void timer1_Tick(object sender, EventArgs e) { _game.Refresh(); } private void panel1_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { this.timer1.Enabled = !this.timer1.Enabled; if (!this.timer1.Enabled) _game.Pause(); } else if (e.Button == MouseButtons.Right) { DialogResult result = MessageBox.Show("Restart this game?", "Balls", MessageBoxButtons.OKCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2); if (result == DialogResult.Cancel) return; _game = new Game(this.panel1.CreateGraphics()); _game.Start(); } } } }
Game代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace Balls { class Game { private List<Ball> balls = new List<Ball>(); private Graphics _g = null; private int _top = 0; private int _bottom = 0; private int _left = 0; private int _right = 0; private int _seqenceNo = 0; private List<Point> locations = new List<Point>(); public Game(Graphics g) { _g = g; _top = Convert.ToInt32(_g.VisibleClipBounds.Top); _bottom = Convert.ToInt32(_g.VisibleClipBounds.Bottom); _left = Convert.ToInt32(_g.VisibleClipBounds.Left); _right = Convert.ToInt32(_g.VisibleClipBounds.Right); int x = Convert.ToInt32(_g.VisibleClipBounds.Width / 4); int y = Convert.ToInt32(_g.VisibleClipBounds.Height / 5); for (int i = 0; i < 4; i++) { for (int j = 0; j < 5; j++) { Point p = new Point(); p.X = i * x; p.Y = j * y; locations.Add(p); } } } public void Start() { _g.Clear(Color.White); _seqenceNo = 0; balls.Clear(); int ballNum = (new Random()).Next(2, 10); for (int i = 0; i < ballNum; i++) { System.Threading.Thread.Sleep(500); GenerateBall(); } } public void Pause() { _g.Clear(Color.White); foreach (Ball b in balls) { b.Move(); b.DrawWithDirection(); } } public void Refresh() { _g.Clear(Color.White); foreach (Ball b in balls) { b.Checked = false; } foreach (Ball b in balls) { b.Move(); CheckBallAndBall(b); CheckBallAndWall(b); b.Draw(); } } private void CheckBallAndBall(Ball b1) { foreach (Ball b2 in balls) { if (b1.SequenceNo == b2.SequenceNo) continue; int distanceOfBalls = b1.Distance(b2); Point newSpeed1 = b1.Speed; Point newSpeed2 = b2.Speed; Point newPosition1 = b1.Position; Point newPosition2 = b2.Position; if (distanceOfBalls <= b1.Radius + b2.Radius) { int totalWeight = b1.Weight + b2.Weight; double s1_x = (b1.Weight - b2.Weight) * b1.Speed.X + 2 * b2.Weight * b2.Speed.X; s1_x = Math.Round(s1_x / totalWeight, MidpointRounding.AwayFromZero); double s1_y = (b1.Weight - b2.Weight) * b1.Speed.Y + 2 * b2.Weight * b2.Speed.Y; s1_y = Math.Round(s1_y / totalWeight, MidpointRounding.AwayFromZero); double s2_x = (b2.Weight - b1.Weight) * b2.Speed.X + 2 * b1.Weight * b1.Speed.X; s2_x = Math.Round(s2_x / totalWeight, MidpointRounding.AwayFromZero); double s2_y = (b2.Weight - b1.Weight) * b2.Speed.Y + 2 * b1.Weight * b1.Speed.Y; s2_y = Math.Round(s2_y / totalWeight, MidpointRounding.AwayFromZero); newSpeed1.X = Convert.ToInt32(s1_x); newSpeed1.Y = Convert.ToInt32(s1_y); newSpeed2.X = Convert.ToInt32(s2_x); newSpeed2.Y = Convert.ToInt32(s2_y); b1.Speed = newSpeed1; b2.Speed = newSpeed2; int val = b1.Radius + b2.Radius - distanceOfBalls; int x = Convert.ToInt32(val * Math.Abs(Math.Cos(b1.Angle))); int y = Convert.ToInt32(val * Math.Abs(Math.Sin(b1.Angle))); if (newSpeed1.X != 0) x = x * newSpeed1.X / Math.Abs(newSpeed1.X); else x = x * -1; if (newSpeed1.Y != 0) y = y * newSpeed1.Y / Math.Abs(newSpeed1.Y); else y = y * -1; newPosition1.X = newPosition1.X + x; newPosition1.Y = newPosition1.Y + y; b1.Position = newPosition1; b1.Move(); b2.Move(); if (b1.Distance(b2) <= b1.Radius + b2.Radius) { Console.WriteLine("======="); Console.WriteLine(b1.ToString()); Console.WriteLine(b2.ToString()); Console.WriteLine("======="); } } } } private void CheckBallAndWall(Ball b) { Point newSpeed = b.Speed; Point newPosition = b.Position; if (b.Position.X < _left || b.Position.X + b.Radius * 2 > _right) { newSpeed.X = b.Speed.X * (-1); if (b.Position.X < _left) newPosition.X = _left; if (b.Position.X + b.Radius * 2 > _right) newPosition.X = _right - b.Radius * 2; } if (b.Position.Y < _top || b.Position.Y + b.Radius * 2 > _bottom) { newSpeed.Y = b.Speed.Y * (-1); if (b.Position.Y < _top) newPosition.Y = _top; if (b.Position.Y + b.Radius * 2 > _bottom) newPosition.Y = _bottom - b.Radius * 2; } b.Speed = newSpeed; b.Position = newPosition; } private void GenerateBall() { Random rdm = new Random(); int radius = rdm.Next(10, 30); int weight = rdm.Next(10, 50); int red = rdm.Next(0, 255); int green = rdm.Next(0, 255); int blue = rdm.Next(0, 255); Color color = Color.FromArgb(red, green, blue); int locationIdx = rdm.Next(0, locations.Count - 1); Point p = locations[locationIdx]; locations.Remove(p); Point speed = new Point(); speed.X = Convert.ToInt32(rdm.Next(-200, 200) / 20); speed.Y = Convert.ToInt32(rdm.Next(-200, 200) / 20); _seqenceNo = _seqenceNo + 1; Ball ball = new Ball(_g, p, speed, radius, weight, color, _seqenceNo); balls.Add(ball); } } }
Ball代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace Balls { class Ball { private Graphics _g; private Point _position; private Point _corePosition; private int _radius; private Point _speed; private int _weight; private Color _color; private double _angle; private int _sequenceNo; private bool _checked; public bool Checked { get { return _checked; } set { _checked = value; } } public int SequenceNo { get { return _sequenceNo; } } public Point CorePosition { get { return _corePosition; } } public Point Position { get { return _position; } set { _position = value; _corePosition.X = _position.X + _radius; _corePosition.Y = _position.Y + _radius; } } public Point Speed { get { return _speed; } set { _speed = value; _angle = Math.Atan2(0 - _speed.Y, _speed.X) * (180 / Math.PI); if (_angle < 0) _angle = 360 + _angle; } } public int Radius { get { return _radius; } } public double Angle { get { return _angle; } } public int Weight { get { return _weight; } } public Ball(Graphics g, Point position, Point speed, int radius, int weight, Color color, int seqNo) { _sequenceNo = seqNo; _g = g; _radius = radius; _position = position; _corePosition = new Point(); _corePosition.X = _position.X + _radius; _corePosition.Y = _position.Y + _radius; _speed = speed; _weight = weight; _color = color; _angle = Math.Atan2(0 - _speed.Y, _speed.X) * (180 / Math.PI); if (_angle < 0) _angle = 360 + _angle; DrawWithDirection(); } public void Draw() { _g.DrawEllipse(new Pen(Color.Black, 3), _position.X, _position.Y, 2 * _radius, 2 * _radius); _g.FillEllipse(new SolidBrush(_color), _position.X, _position.Y, 2 * _radius, 2 * _radius); } public void DrawWithDirection() { Draw(); DisplayDirection(this._corePosition, _angle, 20); } private void DisplayDirection(Point p, double angle, int length) { Point p2 = DrawLineByAngle(p, angle, length); if (angle < 180) { DrawLineByAngle(p2, 180 + angle - 30, 5); DrawLineByAngle(p2, 180 + angle + 30, 5); } else if (angle == 180) { DrawLineByAngle(p2, 180 + angle - 30, 5); DrawLineByAngle(p2, -180 + angle + 30, 5); } else { DrawLineByAngle(p2, -180 + angle - 30, 5); DrawLineByAngle(p2, -180 + angle + 30, 5); } } private Point DrawLineByAngle(Point p1, double a, int length) { double angle = Math.Abs(a); if (angle > 360) angle -= 360; double radians = angle * (Math.PI / 180); int offsetX = Math.Abs(Convert.ToInt32(length * Math.Cos(radians))); int offsetY = Math.Abs(Convert.ToInt32(length * Math.Sin(radians))); Point p2 = new Point(); p2 = p1; if (angle >= 0 && angle <= 90) { p2.X = p1.X + offsetX; p2.Y = p1.Y - offsetY; } else if (angle > 90 && angle <= 180) { p2.X = p1.X - offsetX; p2.Y = p1.Y - offsetY; } else if (angle > 180 && angle <= 270) { p2.X = p1.X - offsetX; p2.Y = p1.Y + offsetY; } else if (angle > 270 && angle <= 360) { p2.X = p1.X + offsetX; p2.Y = p1.Y + offsetY; } _g.DrawLine(new Pen(Color.Red), p1, p2); return p2; } public void Move() { _position.X = _position.X + _speed.X; _position.Y = _position.Y + _speed.Y; _corePosition.X = _position.X + _radius; _corePosition.Y = _position.Y + _radius; } public int Distance(Ball b2) { double value1 = Math.Pow((_corePosition.X - b2.CorePosition.X), 2); double value2 = Math.Pow((_corePosition.Y - b2.CorePosition.Y), 2); double distance = Math.Sqrt(value1 + value2); int distanceOfBalls = Convert.ToInt32(Math.Round(distance, MidpointRounding.AwayFromZero)); return distanceOfBalls; } public int NextDistance(Ball b2) { double value1 = Math.Pow((_corePosition.X + _speed.X - b2.CorePosition.X + b2.Speed.X), 2); double value2 = Math.Pow((_corePosition.Y + _speed.Y - b2.CorePosition.Y + b2.Speed.Y), 2); double distance = Math.Sqrt(value1 + value2); int distanceOfBalls = Convert.ToInt32(Math.Round(distance, MidpointRounding.AwayFromZero)); return distanceOfBalls; } public override string ToString() { return "Ball(" + _sequenceNo.ToString() + ") M:" + _weight.ToString() + ",R:" + _radius.ToString() + ",S_X:" + _speed.X + ",S_Y:" + _speed.Y; } } }