写了一个模拟小球弹性碰撞的Demo,先贴上来。
场景说明:
随机生成N个小球,随机初始速度,初始方向,质量,大小,颜色。
每个小球都看做是质点。球和球,球和壁的碰撞都是弹性的(即碰撞后X,Y轴方向变反,大小不变),动能守恒。
左击鼠标,停止运动,用箭头标识当前的运动方向。再单击则开始运动。
右击鼠标,重新初始化场景重新开始。
程序由: 一个Ball类,Game类,和MainForm组成
MainForm提供场景的"画布", Game控制小球(Ball类)的运动。
Form代码:
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代码:
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代码:
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;
}
}
}