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();// Обновляем строку статуса } } }