/*
Thor Server Project
Copyright 2008 Joe Hegarty
This file is part of The Thor Server Project.
The Thor Server Project is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
The Thor Server Project is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with The Thor Server Project. If not, see .
*/
using System;
using System.Text;
using System.Drawing;
using System.Threading;
using System.Collections;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using ThorServer.Utilities;
namespace ThorServer.Game.Rooms.Pathfinding
{
public struct PathFinderNode
{
public int F;
public int G;
public int H; // f = gone + heuristic
public int X;
public int Y;
public int PX; // Parent
public int PY;
}
public enum PathFinderNodeType
{
Start = 1,
End = 2,
Open = 4,
Close = 8,
Current = 16,
Path = 32
}
public enum HeuristicFormula
{
Manhattan = 1,
MaxDXDY = 2,
DiagonalShortCut = 3,
Euclidean = 4,
EuclideanNoSQR = 5,
Custom1 = 6
}
public delegate void PathFinderDebugHandler(int fromX, int fromY, int x, int y, PathFinderNodeType type, int totalCost, int cost);
public class PathFinder : IPathFinder
{
private byte[,] mGrid = null;
private float[,] mHeightmap = null;
private float mMaximumAscend = 1.5f;
private float mMaximumDescend = 4;
private bool mAccountHeights = true;
private PriorityQueueB mOpen = new PriorityQueueB(new ComparePFNode());
private List mClose = new List();
private bool mStop = false;
private bool mStopped = true;
private HeuristicFormula mFormula = HeuristicFormula.MaxDXDY;
private int mHEstimate = 2;
private bool mTieBreaker = true;
private int mSearchLimit = 25000;
private double mCompletedTime = 0;
public PathFinder(byte[,] grid)
{
if (grid == null) throw new Exception("Pathfinder: Grid cannot be null");
mGrid = grid;
}
public bool AccountHeights
{
get { return mAccountHeights; }
set { mAccountHeights = value; }
}
public float MaxAscend
{
get { return mMaximumAscend; }
set { mMaximumAscend = value; }
}
public float MaxDescend
{
get { return mMaximumDescend; }
set { mMaximumDescend = value; }
}
public float[,] Heightmap
{
get { return mHeightmap; }
set { mHeightmap = value; }
}
public bool Stopped
{
get { return mStopped; }
}
public HeuristicFormula Formula
{
get { return mFormula; }
set { mFormula = value; }
}
public int HeuristicEstimate
{
get { return mHEstimate; }
set { mHEstimate = value; }
}
public bool TieBreaker
{
get { return mTieBreaker; }
set { mTieBreaker = value; }
}
public int SearchLimit
{
get { return mSearchLimit; }
set { mSearchLimit = value; }
}
public double CompletedTime
{
get { return mCompletedTime; }
set { mCompletedTime = value; }
}
public void FindPathStop()
{
mStop = true;
}
public List FindPath(Point start, Point end)
{
lock (this)
{
PathFinderNode parentNode;
bool found = false;
int gridX = mGrid.GetUpperBound(0);
int gridY = mGrid.GetUpperBound(1);
mStop = false;
mStopped = false;
mOpen.Clear();
mClose.Clear();
sbyte[,] direction;
direction = new sbyte[4, 2] { { 0, -1 }, { 1, 0 }, { 0, 1 }, { -1, 0 } };
parentNode.G = 0;
parentNode.H = mHEstimate;
parentNode.F = parentNode.G + parentNode.H;
parentNode.X = start.X;
parentNode.Y = start.Y;
parentNode.PX = parentNode.X;
parentNode.PY = parentNode.Y;
mOpen.Push(parentNode);
while (mOpen.Count > 0 && !mStop)
{
parentNode = mOpen.Pop();
if (parentNode.X == end.X && parentNode.Y == end.Y)
{
mClose.Add(parentNode);
found = true;
break;
}
if (mClose.Count > mSearchLimit)
{
mStopped = true;
return null;
}
//Lets calculate each successors
for (int i = 0; i < 4; i++)
{
PathFinderNode newNode;
newNode.X = parentNode.X + direction[i, 0];
newNode.Y = parentNode.Y + direction[i, 1];
if (newNode.X < 0 || newNode.Y < 0 || newNode.X >= gridX || newNode.Y >= gridY)
continue;
int newG = parentNode.G + mGrid[newNode.X, newNode.Y];
if (newG == parentNode.G)
{
//Unbrekeable
continue;
}
//Take into account heights
if (mAccountHeights)
{
if (mMaximumDescend > -1)
{
if (mHeightmap[parentNode.X, parentNode.Y] - mMaximumDescend > mHeightmap[newNode.X, newNode.Y])
{
continue;
}
}
if (mMaximumAscend > -1)
{
if (mHeightmap[parentNode.X, parentNode.Y] + mMaximumAscend < mHeightmap[newNode.X, newNode.Y])
{
continue;
}
}
}
int foundInOpenIndex = -1;
for (int j = 0; j < mOpen.Count; j++)
{
if (mOpen[j].X == newNode.X && mOpen[j].Y == newNode.Y)
{
foundInOpenIndex = j;
break;
}
}
if (foundInOpenIndex != -1 && mOpen[foundInOpenIndex].G <= newG)
continue;
int foundInCloseIndex = -1;
for (int j = 0; j < mClose.Count; j++)
{
if (mClose[j].X == newNode.X && mClose[j].Y == newNode.Y)
{
foundInCloseIndex = j;
break;
}
}
if (foundInCloseIndex != -1 && mClose[foundInCloseIndex].G <= newG)
continue;
newNode.PX = parentNode.X;
newNode.PY = parentNode.Y;
newNode.G = newG;
switch (mFormula)
{
default:
case HeuristicFormula.Manhattan:
newNode.H = mHEstimate * (Math.Abs(newNode.X - end.X) + Math.Abs(newNode.Y - end.Y));
break;
case HeuristicFormula.MaxDXDY:
newNode.H = mHEstimate * (Math.Max(Math.Abs(newNode.X - end.X), Math.Abs(newNode.Y - end.Y)));
break;
case HeuristicFormula.DiagonalShortCut:
int h_diagonal = Math.Min(Math.Abs(newNode.X - end.X), Math.Abs(newNode.Y - end.Y));
int h_straight = (Math.Abs(newNode.X - end.X) + Math.Abs(newNode.Y - end.Y));
newNode.H = (mHEstimate * 2) * h_diagonal + mHEstimate * (h_straight - 2 * h_diagonal);
break;
case HeuristicFormula.Euclidean:
newNode.H = (int)(mHEstimate * Math.Sqrt(Math.Pow((newNode.X - end.X), 2) + Math.Pow((newNode.Y - end.Y), 2)));
break;
case HeuristicFormula.EuclideanNoSQR:
newNode.H = (int)(mHEstimate * (Math.Pow((newNode.X - end.X), 2) + Math.Pow((newNode.Y - end.Y), 2)));
break;
case HeuristicFormula.Custom1:
Point dxy = new Point(Math.Abs(end.X - newNode.X), Math.Abs(end.Y - newNode.Y));
int Orthogonal = Math.Abs(dxy.X - dxy.Y);
int Diagonal = Math.Abs(((dxy.X + dxy.Y) - Orthogonal) / 2);
newNode.H = mHEstimate * (Diagonal + Orthogonal + dxy.X + dxy.Y);
break;
}
if (mTieBreaker)
{
int dx1 = parentNode.X - end.X;
int dy1 = parentNode.Y - end.Y;
int dx2 = start.X - end.X;
int dy2 = start.Y - end.Y;
int cross = Math.Abs(dx1 * dy2 - dx2 * dy1);
newNode.H = (int)(newNode.H + cross * 0.001);
}
newNode.F = newNode.G + newNode.H;
mOpen.Push(newNode);
}
mClose.Add(parentNode);
}
if (found)
{
PathFinderNode fNode = mClose[mClose.Count - 1];
for (int i = mClose.Count - 1; i >= 0; i--)
{
if (fNode.PX == mClose[i].X && fNode.PY == mClose[i].Y || i == mClose.Count - 1)
{
fNode = mClose[i];
}
else
mClose.RemoveAt(i);
}
mStopped = true;
return mClose;
}
mStopped = true;
return null;
}
}
internal class ComparePFNode : IComparer
{
public int Compare(PathFinderNode x, PathFinderNode y)
{
if (x.F > y.F)
return 1;
else if (x.F < y.F)
return -1;
return 0;
}
}
}
}