partcle2D

advertisement
p2D
Построение приложения двумерного движения частицы во многом повторяет методы построения
приложения Particle1D. Различия в следующих моментах
1. Область эволюции частицы ограничена не точками на отрезке, а границами
прямоугольника.
2. Для описания положения, скорости, силы используется не тип double, а тип Vector из
пространства имен System.Windows, записанный в библиотеке WindowsBase.dll из каталога
Program Files/Reference Assemblies/Microsoft/Framework. Эту библиотеку необходимо
подшить к ресурсам проекта, но не использовать в декларациях using пространство имен
System.Windows во избежание конфликта имен типов Size и т.п.
3. Изображение скорости частицы определяется не только ее величиной, регулируемой из
контекстного меню, но и направлением, регулируемым колесиком мышки. При этом
красная стрелка изображает направление скорости.
4. Фон изображения состоит не только из штриховки, но и из изображения потенциала
внешнего поля с помощью линейной градиентной кисти, смешивая белый цвет с черным в
пропорции, зависящей от потенциала в каждой точке.
5. Весь фон изображается на другом, невидимом контейнере pictureBox2 тех же размеров,
что и видимый pictureBox1. После этого фон записывается в битовую карту, в память
компьютера и формирует неизменный фон видимого контейнера pictureBox1. Это
значительно увеличивает скорость перерисовки при движении. Предполагается, конечно,
что потенциал поля не меняется во времени.
6. При движении частица оставляет след. След накапливается в специальном списке всех
положений, через которые проходит частица, и изображается в обработчике события Paint
видимого контейнера pictureBox1.
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;
using System.Drawing.Drawing2D;
namespace Particle2D
{
public partial class fParticle2D : Form
{
#region Fields
#region Graphics
/// <summary>
/// Хранит радиус частицы в пикселях
/// </summary>
const int pRadius = 10;
/// <summary>
/// Хранит размер прямоугольника частицы в пикселях
/// </summary>
static Size pSize = new Size(2 * pRadius, 2 * pRadius);
/// <summary>
/// Хранит ширину области эволюции в пикселях
/// </summary>
int pbWidth;
/// <summary>
/// Хранит высоту области эволюции в пикселях
/// </summary>
int pbHeight;
/// <summary>
/// Хранит прямоугольник частицы в пикселях
/// </summary>
Rectangle pRect = new Rectangle(new Point(), pSize);
/// <summary>
/// Хранит маршрут частицы в физических единицах
/// </summary>
List<System.Windows.Vector> pPath = new List<System.Windows.Vector>();
/// <summary>
/// Хранит текущее изображение фона
/// </summary>
Bitmap bckgrBitmap;
/// <summary>
/// Хранит стартовый цвет в изображении поля
/// </summary>
Color startColor = Color.FromArgb(240, Color.Black);
/// <summary>
/// Хранит конечный цвет в изображении поля
/// </summary>
Color endColor = Color.FromArgb(240, Color.White);
#endregion
#region Physics
/// <summary>
/// Хранит текущее время в физических единицах
/// </summary>
double t;
/// <summary>
/// Хранит б.малый промежуток времени между кадрами
/// </summary>
double dt = .005;
/// <summary>
/// Хранит текущее положение частицы в физических единицах
/// </summary>
System.Windows.Vector r;
/// <summary>
/// Хранит текущую скорость частицы в физических единицах
/// </summary>
RectangleF window = new RectangleF(-1, -1, 2, 2);
System.Windows.Vector v = new System.Windows.Vector(1, 1);
#endregion
/// <summary>
/// Хранит текущее состояние движения
/// </summary>
bool run;
#endregion
#region Physics
System.Windows.Vector f()
{
return new System.Windows.Vector(
2 * Math.PI * Math.Sin(2 * Math.PI * r.X) * Math.Sin(2 * Math.PI * r.Y),
-2 * Math.PI * Math.Cos(2 * Math.PI * r.X) * Math.Cos(2 * Math.PI * r.Y));
}
double U(System.Windows.Vector curR)
{
return Math.Cos(2 * Math.PI * curR.X) * Math.Sin(2 * Math.PI * curR.Y);
}
#endregion
public fParticle2D()
{
InitializeComponent();
ResizePanels();
RestoreStatus();
// Подключаем обработчик нажатия клавиши
vBox.KeyPress += new KeyPressEventHandler(vBox_MouseLeave);
if (SystemInformation.MouseWheelPresent)
// Подключаем обработчик колесика мышки
pictureBox1.MouseWheel += new MouseEventHandler(pictureBox1_MouseWheel);
}
#region Обработчики
void pictureBox1_MouseWheel(object sender, MouseEventArgs e)
{
// Код обработчика не выполняется, если система в состоянии движения
// либо если курсор не находится внутри пр-ка частицы
if (run || !pRect.IntersectsWith(new Rectangle(e.Location, new Size(1, 1))))
return;
// Текущее направление скорости
double alpha = Math.Atan2(v.Y, v.X);
// Меняется направление скорости
alpha += (double)e.Delta / 1000;
// Текущий модуль скорости
double vMod = v.Length;
// Новые проекции скорости
v.X = vMod * Math.Cos(alpha);
v.Y = vMod * Math.Sin(alpha);
// Обновляется строка статуса
RestoreStatus();
pictureBox1.Refresh();// обновляется изображение
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
#region Particle
// Параметр смешивания цветов зависит от скорости
int alpha = (int)(255 / (1 + v.Length / 3));
if (alpha < 120) alpha = 120;
// Определяются текущие цвета
Color curCenterColor = Color.FromArgb(alpha, Color.Yellow);
Color[] curSurColors = { Color.FromArgb(alpha, Color.Black) };
// Создается объект класса графического контура для формирования градиентной кисти
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddEllipse(pRect);// К объекту добавляется эллипс частицы
// Создается градиентная кисть для заданного графического контура
using (PathGradientBrush pBr = new PathGradientBrush(gp))
{
pBr.CenterColor = curCenterColor;// Определяется цвет центра частицы
pBr.SurroundColors = curSurColors;// Определяется цвет периметра частицы
e.Graphics.FillPath(pBr, gp);// Формируется изображение частицы
}
}
// Текущее направление скорости
double fi = Math.Atan2(v.Y, v.X);
// Положение частицы
Point center = pRect.Location + new Size(pRadius, pRadius);
using (Pen pen = new Pen(Color.Red))
{
pen.EndCap = LineCap.ArrowAnchor;
e.Graphics.DrawLine(pen, center,
new Point(center.X + (int)(pRadius * Math.Cos(fi)), center.Y + (int)(pRadius * Math.Sin(fi))));
}
if (pPath.Count < 2) return;
Point[] pathPoints = new Point[pPath.Count];
for (int i = 0; i < pPath.Count; i++)
pathPoints[i] = Point.Round(new PointF(
((float)pPath[i].X - window.Left) / window.Width * pbWidth,
((float)pPath[i].Y - window.Top) / window.Height * pbHeight));
e.Graphics.DrawLines(Pens.Aqua, pathPoints);
#endregion
}
private void pictureBox1_Resize(object sender, EventArgs e)
{
ResizePanels();
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
// Код обработчика выполняется только для покоящегося состояния.
// Туда помещается частица
if (run || e.Button != MouseButtons.Left)
return;
// Обновляется поле координаты частицы
r.X = window.Left + e.X * window.Width / pbWidth;
r.Y = window.Top + e.Y * window.Height / pbHeight;
t = 0;// Обнуляется время
RelocateParticle();
RestoreStatus();
pictureBox1.Refresh();
}
private void pictureBox1_MouseEnter(object sender, EventArgs e)
{
pictureBox1.Focus();
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Enabled = false;
move();
}
private void fParticle2D_FormClosed(object sender, FormClosedEventArgs e)
{
run = false;
}
private void startButton_Click(object sender, EventArgs e)
{
run = !run;// Переключаем флаг движения
startButton.Text = run ? "Stop" : "Start";// Меняем надпись на кнопке
if (run)
{
pPath.Clear();// Очищается траектория
RefreshBckGround();
timer1.Enabled = true;// Активируем таймер для включения цикла движения
}
}
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
// Условие появления контекстного меню:
// частица покоится, и курсор находится внутри пр-ка частицы
e.Cancel = run ||
!pRect.IntersectsWith(new Rectangle(pictureBox1.PointToClient(Cursor.Position), new Size(1, 1)));
// Вносится текущая скорость
if (!e.Cancel)
vBox.Text = v.Length.ToString("f");
}
private void vBox_MouseLeave(object sender, EventArgs e)
{
// Код обработчика не выполняется,
// если это событие нажатия клавиши, которая не является Enter
if (e is KeyPressEventArgs && (e as KeyPressEventArgs).KeyChar != 13)
return;
contextMenuStrip1.Close();// Закрываем контекстное меню
// Сохраняем новое значение модуля скорости
double vMod = Double.Parse(vBox.Text);
if (v.Length != 0)
{
double vX = vMod / v.Length * v.X, vY = vMod / v.Length * v.Y;
v.X = vX; v.Y = vY;
}
else
v.X = vMod;
t = 0;// Обнуляем время
RestoreStatus();// Обновляем строку статуса
pictureBox1.Refresh();// Обновляем изображение
}
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
#region Background
// Область изображения заполняется клеточкой
using (HatchBrush hb = new HatchBrush(HatchStyle.Cross, Color.LightGray, Color.Navy))
e.Graphics.FillRectangle(hb, pictureBox1.ClientRectangle);
#region Изображение поля
// Объект класса Blend необходим для смешивания цветов при изображении поля
// специальной кистью
Blend blnd = new Blend(pbHeight);// Создаем объект класса Blend
// Хранит вертикальное положение пикселей
float[] pstns = new float[pbHeight];
// определяем вертикальное положение пикселей
for (int i = 0; i < pstns.Length; i++)
pstns[i] = (float)i / (pbHeight - 1);
blnd.Positions = pstns;// Передаем позиции пикселей объекту класса Blend
float[] factors = new float[pbHeight];// Описываем массив множителей смешивания
// Текущий прямоугольник, заполняемый кистью
Rectangle curRect = new Rectangle(new Point(), new Size(1, pbHeight));
// Текущая точка пространства
System.Windows.Vector curR = new System.Windows.Vector();
// Проходим горизонтально по одному прямоугольнику
for (int clmn = 0; clmn < pbWidth; clmn++)
{
// Текущая горизонтальная координата
curR.X = window.Left + window.Width * clmn / pbWidth;
// проходим вертикально по всем точкам для получения факторов смешивания
for (int j = 0; j < pbHeight; j++)
{
// Текущая вертикальная координата
curR.Y = window.Top + window.Height * pstns[j];
// Фактор в данной точке пространства
factors[j] = (float)((U(curR) - (-1)) / (1 - (-1)));
}
// Сообщаем факторы объекту смешивания
blnd.Factors = factors;
// Заполняем текущий пр-к
using (LinearGradientBrush lb =
new LinearGradientBrush(curRect, startColor, endColor, LinearGradientMode.Vertical))
{
lb.Blend = blnd;// Сообщаем объект смешивания кисти
e.Graphics.FillRectangle(lb, curRect);// Заполняем текущий пр-к кистью
}
// Сдвигаем пр-к вправо на один пиксел
curRect.Offset(1, 0);
}
#endregion
#endregion
}
#endregion
void RelocateParticle()
{
pRect.Location = new Point((int)(pbWidth * (r.X - window.Left) / window.Width + .5) - pRadius,
(int)(pbHeight * (r.Y - window.Top) / window.Height + .5) - pRadius);
}
void ResizePanels()
{
pbWidth = pictureBox1.ClientSize.Width;
pbHeight = pictureBox1.ClientSize.Height;
RelocateParticle();
pictureBox2.ClientSize = pictureBox1.ClientSize;
pictureBox2.Refresh();
if (bckgrBitmap != null)
bckgrBitmap.Dispose();
bckgrBitmap = new Bitmap(pbWidth, pbHeight);
pictureBox2.DrawToBitmap(bckgrBitmap, pictureBox2.ClientRectangle);
RefreshBckGround();
}
void RestoreStatus()
{
stLabel.Text = "time = " + String.Format("{0,10:f}", t) +
" x = " + String.Format("{0,10:f}", r.X) + " y = " + String.Format("{0,10:f}", r.Y) +
" Vx = " + String.Format("{0,10:f}", v.X) + " Vy = " + String.Format("{0,10:f}", v.Y) +
" Energy = " + String.Format("{0,10:f}", .5 * v.Length * v.Length + U(r));
}
void RefreshBckGround()
{
if (bckgrBitmap != null)
pictureBox1.BackgroundImage = bckgrBitmap;
pictureBox1.Refresh();
}
/// <summary>
/// Обеспечивает цикл движения
/// </summary>
void move()
{
while (run)
{
DoStep();
Application.DoEvents();// Вызываем очередь событий для управления программой
}
}
/// <summary>
/// Изменяет положение частицы
/// </summary>
void DoStep()
{
// Метод Invalidate добавляет область, которую следует
// обновить при следующем событии Paint
pictureBox1.Invalidate(pRect);// Убираем прежнее положение частицы
t += dt;// Меняем время
r.X += v.X * dt;// Меняем координаты
r.Y += v.Y * dt;
if (pPath.Count > 10000)
pPath.Clear();
pPath.Add(r);
if (r.X > window.Right || r.X < window.Left) // Отражение от границ
v.X = -v.X;
if (r.Y > window.Bottom || r.Y < window.Top) // Отражение от границ
v.Y = -v.Y;
System.Windows.Vector curF = f();
v.X += dt * curF.X;
v.Y += dt * curF.Y;
RelocateParticle();// Меняем положение прямоугольника частицы
pictureBox1.Invalidate(pRect);// Добавляем новое положение частицы
pictureBox1.Update();// Обновляем изображение
RestoreStatus();// Обновляем строку статуса
}
}
}
Download