using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using YinYang.CodeProject.Projects.SimplePathfinding.Helpers;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.AStar;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.BreadthFirst;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.DepthFirst;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.Dijkstra;
using YinYang.CodeProject.Projects.SimplePathfinding.PathFinders.Evasion;
using YinYang.CodeProject.Projects.SimplePathfinding.Properties;
using YinYang.CodeProject.Projects.SimplePathfinding.Scenarios;
namespace YinYang.CodeProject.Projects.SimplePathfinding
{
public partial class MainForm : Form
{
#region | Constant |
private const Int32 DefaultAreaWidth = 512;
private const Int32 DefaultAreaHeight = 512;
#endregion
#region | Fields |
private readonly Func<CheckBox, Boolean> getOption;
private readonly Action processTasks;
private readonly Action<Image> updateImage;
private readonly Action<String> updateCaption;
private readonly Func<Point, Point> getFormPosition;
private Int32 objectDiameter;
private Thread thread;
private Graphics graphics;
private Bitmap defaultImage;
private Boolean turnOnEvents;
private Graphics imageGraphics;
private PathScenario activeScenario;
private BasePathfinder activePathFinder;
private List<PathScenario> scenarioList;
private List<BasePathfinder> pathFinderList;
private readonly UInt16[,] distanceMap;
private readonly List<Action> taskList;
private readonly StopFunction stopFunction;
#endregion
#region | Constructors |
/// <summary>
/// Initializes a new instance of the <see cref="MainForm"/> class.
/// </summary>
public MainForm()
{
InitializeComponent();
taskList = new List<Action>();
processTasks = ProcessTasks;
objectDiameter = trackObjectDiameter.Value;
getOption = checkbox => checkbox.Checked;
updateCaption = caption => Text = caption;
updateImage = image => graphics.DrawImageUnscaled(image, 0, 0);
getFormPosition = PointToClient;
MouseUp += (sender, args) => { if (args.Button == MouseButtons.Right) taskList.Add(() => RecreateDefaultImage()); };
ClientSize = new Size(DefaultAreaWidth + 250, DefaultAreaHeight);
distanceMap = new UInt16[DefaultAreaWidth, DefaultAreaHeight];
// creates functions
stopFunction = (point, ignoreRadius) =>
{
Color color = Color.Black;
Int32 x = point.X;
Int32 y = point.Y;
UInt16 radius = 1;
if (x >= 0 && y >= 0 && x < defaultImage.Width && y < defaultImage.Height)
{
color = defaultImage.GetPixel(x, y);
radius = distanceMap[x, y];
}
return color.ToArgb() == Color.Black.ToArgb() || (!ignoreRadius && radius <= (objectDiameter + 1) >> 1);
};
}
#endregion
#region | Process path-finding scenario |
private void ProcessScenario()
{
// position starts at [0, 0]
Point position = Point.Empty;
Invoke(processTasks);
using (Bitmap image = new Bitmap(DefaultAreaWidth, DefaultAreaHeight, graphics))
{
imageGraphics = Graphics.FromImage(image);
imageGraphics.SmoothingMode = SmoothingMode.HighQuality;
do // repeats forever
{
// starts measuring total time
DateTime startTime = DateTime.Now;
// prepares parameters
Point cursor = (Point) Invoke(getFormPosition, MousePosition);
// changes start position
if (MouseButtons.HasFlag(MouseButtons.Middle)) position = cursor;
// performs path finding with a given path finder
TimeSpan pathFindTime;
Int32 pointCount = DrawPath(image, imageGraphics, position, cursor, out pathFindTime);
// draws the markers
PathScenario.DrawMarker(imageGraphics, position, Brushes.WhiteSmoke, true);
PathScenario.DrawMarker(imageGraphics, cursor, Brushes.WhiteSmoke, true);
// updates the form information
Invoke(updateCaption, string.Format("Cursor: [{0:000}, {1:000}], Points: {2:0000}, Path finding time: {3:ss\\:ffffff}, Total time: {4:ss\\:ffffff}", cursor.X, cursor.Y, pointCount, pathFindTime, DateTime.Now - startTime));
Invoke(updateImage, image);
// restores the default image look
imageGraphics.DrawImageUnscaled(defaultImage, Point.Empty);
Invoke(processTasks);
}
while (true);
}
}
#endregion
#region | Methods |
private void ChangePathFinder()
{
activePathFinder = pathFinderList[listMethod.SelectedIndex];
}
private void ChangeScenario()
{
activeScenario = scenarioList[listScenarios.SelectedIndex];
taskList.Add(() => RecreateDefaultImage(false));
}
private void RecreateDefaultImage(Boolean generateNew = true)
{
Text = Resources.MainForm_RecreateScenario_WaitCaption;
// disposes of old default image
DisposeDefaultImage();
// creates new default image for this scenario
Boolean flagMinimizeHollowAreas = (Boolean) Invoke(getOption, checkMinimizeHollowAreas);
defaultImage = activeScenario.CreateDefaultImage(generateNew, flagMinimizeHollowAreas);
UpdateDistanceMap();
}
private void UpdateDistanceMap()
{
// cannot create map for invalid image, just skip it
if (defaultImage == null) return;
// prepares for the processing
List<Point> nextRound = new List<Point>();
// resets the distance map to Empty = 0, Blocked = 1
for (Int32 y = 0; y < DefaultAreaHeight; y++)
for (Int32 x = 0; x < DefaultAreaWidth; x++)
{
Point point = new Point(x, y);
// determines whether the pixel is blocked, or not
if (stopFunction(point, true))
{
distanceMap[x, y] = 1;
nextRound.Add(point);
}
else
{
distanceMap[x, y] = 0;
}
}
// round to does actual classification
UInt16 round = 2;
do // perform rounds if there is still at least one pixel to process
{
// switches the next round for current round
List<Point> thisRound = nextRound;
nextRound = new List<Point>();
// processes all the neighbor points along the border of already classified pixels
foreach (Point point in thisRound)
foreach (DirectionType direction in DirectionHelper.GetValues())
{
Point neighborPoint = DirectionHelper.GetNextStep(point, direction);
// checks whether it is within valid bounds
if (neighborPoint.X >= 0 && neighborPoint.Y >= 0 &&
neighborPoint.X < DefaultAreaWidth && neighborPoint.Y < DefaultAreaHeight)
{
UInt16 value = distanceMap[neighborPoint.X, neighborPoint.Y];
// if this neighbor is still unclassified, do it, and add it for the next round
if (value == 0)
{
if ((Boolean) Invoke(getOption, checkShowDistanceMap))
{
Int32 intensity = 255 - round%254;
Color color = Color.FromArgb(255, 255, 255, intensity);
defaultImage.SetPixel(neighborPoint.X, neighborPoint.Y, color);
}
distanceMap[neighborPoint.X, neighborPoint.Y] = round;
nextRound.Add(neighborPoint);
}
}
}
round++;
}
while (nextRound.Count > 0);
}
private void ProcessTasks()
{
if (taskList.Count > 0)
{
try
{
foreach (var action in taskList)
{
action();
}
}
finally
{
taskList.Clear();
}
}
}
private void DisposeDefaultImage()
{
if (defaultImage != null)
{
defaultImage.Dispose();
defaultImage = null;
}
}
#endregion
#region | Common |
protected Int32 DrawPath(Bitmap image, Graphics graphics, Point start, Point end, out TimeSpan time)
{
// performs boundary check first
PathScenario.CheckBounds(ref start, ref end, DefaultAreaWidth, DefaultAreaHeight);
// determines the options
Boolean flagPerformOptimizations = (Boolean) Invoke(getOption, checkPerformOptimization);
Boolean flagDrawPivotPoints = (Boolean) Invoke(getOption, checkDrawPivotPoints);
Boolean flagDrawLargerPivots = (Boolean) Invoke(getOption, checkDrawLargerPivots);
// initializes the parameters
Int32 result = 0;
IReadOnlyCollection<Point> points;
IReadOnlyCollection<Point> pivotPoints;
// finds the path while measuring time elapsed
DateTime startDate = DateTime.Now;
Single precisionAlignment = objectDiameter%2 == 0 ? 0.5f : 0.0f;
Pen pen = new Pen(Color.DarkGreen) { Width = objectDiameter - precisionAlignment, StartCap = LineCap.Round, EndCap = LineCap.Round, LineJoin = LineJoin.Round };
Boolean found = activePathFinder.TryFindPath(start, end, stopFunction, out points, out pivotPoints, flagPerformOptimizations);
time = DateTime.Now - startDate;
if (found)
{
// draws connected lines through all the points
if (points.Count > 1)
{
graphics.DrawLines(pen, points.ToArray());
}
// draws markers at pivot points (if turned on)
if (flagDrawPivotPoints && pivotPoints != null)
{
foreach (Point highlightPoint in pivotPoints)
{
if (flagDrawLargerPivots)
{
PathScenario.DrawMarker(graphics, highlightPoint, Brushes.Red, true);
}
else
{
image.SetPixel(highlightPoint.X, highlightPoint.Y, Color.Red);
}
}
}
result = points.Count;
}
return result;
}
#endregion
#region << Form >>
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
graphics = CreateGraphics();
ThreadStart operation = ProcessScenario;
thread = new Thread(operation);
thread.Start();
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.KeyCode == Keys.D1) listMethod.SelectedIndex = 0;
if (e.KeyCode == Keys.D2) listMethod.SelectedIndex = 1;
if (e.KeyCode == Keys.D3) listMethod.SelectedIndex = 2;
if (e.KeyCode == Keys.D4) listMethod.SelectedIndex = 3;
if (e.KeyCode == Keys.D5) listMethod.SelectedIndex = 4;
if (e.KeyCode == Keys.O) checkPerformOptimization.Checked = !checkPerformOptimization.Checked;
if (e.KeyCode == Keys.P) checkDrawPivotPoints.Checked = !checkDrawPivotPoints.Checked;
if (e.KeyCode == Keys.H) checkMinimizeHollowAreas.Checked = !checkMinimizeHollowAreas.Checked;
if (e.KeyCode == Keys.L) checkDrawLargerPivots.Checked = !checkDrawLargerPivots.Checked;
if (e.KeyCode == Keys.Q) checkUseHighQuality.Checked = !checkUseHighQuality.Checked;
if (e.KeyCode == Keys.W) checkShowDistanceMap.Checked = !checkShowDistanceMap.Checked;
if (e.KeyCode == Keys.Add) trackObjectDiameter.Value = Math.Max(trackObjectDiameter.Minimum, Math.Min(trackObjectDiameter.Maximum, trackObjectDiameter.Value + 5));
if (e.KeyCode == Keys.Subtract) trackObjectDiameter.Value = Math.Max(trackObjectDiameter.Minimum, Math.Min(trackObjectDiameter.Maximum, trackObjectDiameter.Value - 5));
}
protected override void OnClosing(CancelEventArgs e)
{
if (thread != null)
{
thread.Abort();
}
DisposeDefaultImage();
base.OnClosing(e);
}
#endregion
#region << Events >>
private void MainFormLoad(object sender, EventArgs e)
{
pathFinderList = new List<BasePathfinder>
{
new AStarPathfinder(DefaultAreaWidth, DefaultAreaHeight),
new BreadthFirstPathfinder(DefaultAreaWidth, DefaultAreaHeight),
new DepthFirstPathfinder(DefaultAreaWidth, DefaultAreaHeight),
new DijkstraPathfinder(DefaultAreaWidth, DefaultAreaHeight),
new EvasionPathfinder(),
};
scenarioList = new List<PathScenario>
{
new RandomRectangleScenario(DefaultAreaWidth, DefaultAreaHeight),
new RandomMarkerScenario(DefaultAreaWidth, DefaultAreaHeight),
new RandomEllipseScenario(DefaultAreaWidth, DefaultAreaHeight),
new RandomLineScenario(DefaultAreaWidth, DefaultAreaHeight),
new BlackObeliskScenario(DefaultAreaWidth, DefaultAreaHeight),
new ImageFileScenario(DefaultAreaWidth, DefaultAreaHeight),
};
turnOnEvents = false;
listMethod.SelectedIndex = 4; // 0
listScenarios.SelectedIndex = 0; // 0
turnOnEvents = true;
ChangePathFinder();
ChangeScenario();
}
private void ListMethodSelectedIndexChanged(object sender, EventArgs e)
{
if (turnOnEvents) ChangePathFinder();
}
private void ListScenariosSelectedIndexChanged(object sender, EventArgs e)
{
if (turnOnEvents) ChangeScenario();
}
private void TrackDiameterValueChanged(object sender, EventArgs e)
{
taskList.Add(() => { objectDiameter = trackObjectDiameter.Value; });
}
private void CheckMinimizeHollowAreasCheckedChanged(object sender, EventArgs e)
{
taskList.Add(() => RecreateDefaultImage(false));
}
private void CheckUseHighQualityCheckedChanged(object sender, EventArgs e)
{
taskList.Add(() =>
{
imageGraphics.SmoothingMode = checkUseHighQuality.Checked ? SmoothingMode.HighQuality : SmoothingMode.HighSpeed;
imageGraphics.InterpolationMode = checkUseHighQuality.Checked ? InterpolationMode.HighQualityBicubic : InterpolationMode.Low;
});
}
private void CheckShowDistanceMapCheckedChanged(object sender, EventArgs e)
{
taskList.Add(() => RecreateDefaultImage(false));
}
#endregion
}
}