SpatialAStar.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. /*
  2. The MIT License
  3. Copyright (c) 2010 Christoph Husse
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. using UnityEngine;
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Linq;
  24. using System.Text;
  25. public interface IIndexedObject
  26. {
  27. int Index { get; set; }
  28. }
  29. public interface IPathNode<TUserContext>
  30. {
  31. Boolean IsWalkable(TUserContext inContext);
  32. }
  33. /// <summary>
  34. /// Uses about 50 MB for a 1024x1024 grid.
  35. /// </summary>
  36. public class SpatialAStar<TPathNode, TUserContext> where TPathNode : IPathNode<TUserContext>
  37. {
  38. private OpenCloseMap m_ClosedSet;
  39. private OpenCloseMap m_OpenSet;
  40. private PriorityQueue<PathNode> m_OrderedOpenSet;
  41. private PathNode[,] m_CameFrom;
  42. private OpenCloseMap m_RuntimeGrid;
  43. private PathNode[,] m_SearchSpace;
  44. public TPathNode[,] SearchSpace { get; private set; }
  45. public int Width { get; private set; }
  46. public int Height { get; private set; }
  47. protected class PathNode : IPathNode<TUserContext>, IComparer<PathNode>, IIndexedObject
  48. {
  49. public static readonly PathNode Comparer = new PathNode(0, 0, default(TPathNode));
  50. public TPathNode UserContext { get; internal set; }
  51. public Double G { get; internal set; }
  52. public Double H { get; internal set; }
  53. public Double F { get; internal set; }
  54. public int Index { get; set; }
  55. public Boolean IsWalkable(TUserContext inContext)
  56. {
  57. return UserContext.IsWalkable(inContext);
  58. }
  59. public int X { get; internal set; }
  60. public int Y { get; internal set; }
  61. public int Compare(PathNode x, PathNode y)
  62. {
  63. if (x.F < y.F)
  64. return -1;
  65. else if (x.F > y.F)
  66. return 1;
  67. return 0;
  68. }
  69. public PathNode(int inX, int inY, TPathNode inUserContext)
  70. {
  71. X = inX;
  72. Y = inY;
  73. UserContext = inUserContext;
  74. }
  75. }
  76. public SpatialAStar(TPathNode[,] inGrid)
  77. {
  78. SearchSpace = inGrid;
  79. Width = inGrid.GetLength(0);
  80. Height = inGrid.GetLength(1);
  81. m_SearchSpace = new PathNode[Width, Height];
  82. m_ClosedSet = new OpenCloseMap(Width, Height);
  83. m_OpenSet = new OpenCloseMap(Width, Height);
  84. m_CameFrom = new PathNode[Width, Height];
  85. m_RuntimeGrid = new OpenCloseMap(Width, Height);
  86. m_OrderedOpenSet = new PriorityQueue<PathNode>(PathNode.Comparer);
  87. for (int x = 0; x < Width; x++)
  88. {
  89. for (int y = 0; y < Height; y++)
  90. {
  91. if (inGrid[x, y] == null)
  92. throw new ArgumentNullException();
  93. m_SearchSpace[x, y] = new PathNode(x, y, inGrid[x, y]);
  94. }
  95. }
  96. }
  97. protected virtual Double Heuristic(PathNode inStart, PathNode inEnd)
  98. {
  99. return Math.Sqrt((inStart.X - inEnd.X) * (inStart.X - inEnd.X) + (inStart.Y - inEnd.Y) * (inStart.Y - inEnd.Y));
  100. }
  101. private static readonly Double SQRT_2 = Math.Sqrt(2);
  102. protected virtual Double NeighborDistance(PathNode inStart, PathNode inEnd)
  103. {
  104. int diffX = Math.Abs(inStart.X - inEnd.X);
  105. int diffY = Math.Abs(inStart.Y - inEnd.Y);
  106. switch (diffX + diffY)
  107. {
  108. case 1: return 1;
  109. case 2: return SQRT_2;
  110. case 0: return 0;
  111. default:
  112. throw new ApplicationException();
  113. }
  114. }
  115. //private List<Int64> elapsed = new List<long>();
  116. /// <summary>
  117. /// Returns null, if no path is found. Start- and End-Node are included in returned path. The user context
  118. /// is passed to IsWalkable().
  119. /// </summary>
  120. public LinkedList<TPathNode> Search(AStarPoint inStartNode, AStarPoint inEndNode, TUserContext inUserContext)
  121. {
  122. PathNode startNode = m_SearchSpace[inStartNode.x, inStartNode.y];
  123. PathNode endNode = m_SearchSpace[inEndNode.x, inEndNode.y];
  124. //System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
  125. //watch.Start();
  126. if (startNode == endNode)
  127. return new LinkedList<TPathNode>(new TPathNode[] { startNode.UserContext });
  128. PathNode[] neighborNodes = new PathNode[8];
  129. m_ClosedSet.Clear();
  130. m_OpenSet.Clear();
  131. m_RuntimeGrid.Clear();
  132. m_OrderedOpenSet.Clear();
  133. for (int x = 0; x < Width; x++)
  134. {
  135. for (int y = 0; y < Height; y++)
  136. {
  137. m_CameFrom[x, y] = null;
  138. }
  139. }
  140. startNode.G = 0;
  141. startNode.H = Heuristic(startNode, endNode);
  142. startNode.F = startNode.H;
  143. m_OpenSet.Add(startNode);
  144. m_OrderedOpenSet.Push(startNode);
  145. m_RuntimeGrid.Add(startNode);
  146. int nodes = 0;
  147. while (!m_OpenSet.IsEmpty)
  148. {
  149. PathNode x = m_OrderedOpenSet.Pop();
  150. if (x == endNode)
  151. {
  152. // watch.Stop();
  153. //elapsed.Add(watch.ElapsedMilliseconds);
  154. LinkedList<TPathNode> result = ReconstructPath(m_CameFrom, m_CameFrom[endNode.X, endNode.Y]);
  155. result.AddLast(endNode.UserContext);
  156. return result;
  157. }
  158. m_OpenSet.Remove(x);
  159. m_ClosedSet.Add(x);
  160. StoreNeighborNodes(x, neighborNodes);
  161. for (int i = 0; i < neighborNodes.Length; i++)
  162. {
  163. PathNode y = neighborNodes[i];
  164. Boolean tentative_is_better;
  165. if (y == null)
  166. continue;
  167. if (!y.UserContext.IsWalkable(inUserContext))
  168. continue;
  169. if (m_ClosedSet.Contains(y))
  170. continue;
  171. nodes++;
  172. Double tentative_g_score = m_RuntimeGrid[x].G + NeighborDistance(x, y);
  173. Boolean wasAdded = false;
  174. if (!m_OpenSet.Contains(y))
  175. {
  176. m_OpenSet.Add(y);
  177. tentative_is_better = true;
  178. wasAdded = true;
  179. }
  180. else if (tentative_g_score < m_RuntimeGrid[y].G)
  181. {
  182. tentative_is_better = true;
  183. }
  184. else
  185. {
  186. tentative_is_better = false;
  187. }
  188. if (tentative_is_better)
  189. {
  190. m_CameFrom[y.X, y.Y] = x;
  191. if (!m_RuntimeGrid.Contains(y))
  192. m_RuntimeGrid.Add(y);
  193. m_RuntimeGrid[y].G = tentative_g_score;
  194. m_RuntimeGrid[y].H = Heuristic(y, endNode);
  195. m_RuntimeGrid[y].F = m_RuntimeGrid[y].G + m_RuntimeGrid[y].H;
  196. if (wasAdded)
  197. m_OrderedOpenSet.Push(y);
  198. else
  199. m_OrderedOpenSet.Update(y);
  200. }
  201. }
  202. }
  203. return null;
  204. }
  205. private LinkedList<TPathNode> ReconstructPath(PathNode[,] came_from, PathNode current_node)
  206. {
  207. LinkedList<TPathNode> result = new LinkedList<TPathNode>();
  208. ReconstructPathRecursive(came_from, current_node, result);
  209. return result;
  210. }
  211. private void ReconstructPathRecursive(PathNode[,] came_from, PathNode current_node, LinkedList<TPathNode> result)
  212. {
  213. PathNode item = came_from[current_node.X, current_node.Y];
  214. if (item != null)
  215. {
  216. ReconstructPathRecursive(came_from, item, result);
  217. result.AddLast(current_node.UserContext);
  218. }
  219. else
  220. result.AddLast(current_node.UserContext);
  221. }
  222. private void StoreNeighborNodes(PathNode inAround, PathNode[] inNeighbors)
  223. {
  224. int x = inAround.X;
  225. int y = inAround.Y;
  226. if ((x > 0) && (y > 0))
  227. inNeighbors[0] = m_SearchSpace[x - 1, y - 1];
  228. else
  229. inNeighbors[0] = null;
  230. if (y > 0)
  231. inNeighbors[1] = m_SearchSpace[x, y - 1];
  232. else
  233. inNeighbors[1] = null;
  234. if ((x < Width - 1) && (y > 0))
  235. inNeighbors[2] = m_SearchSpace[x + 1, y - 1];
  236. else
  237. inNeighbors[2] = null;
  238. if (x > 0)
  239. inNeighbors[3] = m_SearchSpace[x - 1, y];
  240. else
  241. inNeighbors[3] = null;
  242. if (x < Width - 1)
  243. inNeighbors[4] = m_SearchSpace[x + 1, y];
  244. else
  245. inNeighbors[4] = null;
  246. if ((x > 0) && (y < Height - 1))
  247. inNeighbors[5] = m_SearchSpace[x - 1, y + 1];
  248. else
  249. inNeighbors[5] = null;
  250. if (y < Height - 1)
  251. inNeighbors[6] = m_SearchSpace[x, y + 1];
  252. else
  253. inNeighbors[6] = null;
  254. if ((x < Width - 1) && (y < Height - 1))
  255. inNeighbors[7] = m_SearchSpace[x + 1, y + 1];
  256. else
  257. inNeighbors[7] = null;
  258. }
  259. private class OpenCloseMap
  260. {
  261. private PathNode[,] m_Map;
  262. public int Width { get; private set; }
  263. public int Height { get; private set; }
  264. public int Count { get; private set; }
  265. public PathNode this[Int32 x, Int32 y]
  266. {
  267. get
  268. {
  269. return m_Map[x, y];
  270. }
  271. }
  272. public PathNode this[PathNode Node]
  273. {
  274. get
  275. {
  276. return m_Map[Node.X, Node.Y];
  277. }
  278. }
  279. public bool IsEmpty
  280. {
  281. get
  282. {
  283. return Count == 0;
  284. }
  285. }
  286. public OpenCloseMap(int inWidth, int inHeight)
  287. {
  288. m_Map = new PathNode[inWidth, inHeight];
  289. Width = inWidth;
  290. Height = inHeight;
  291. }
  292. public void Add(PathNode inValue)
  293. {
  294. PathNode item = m_Map[inValue.X, inValue.Y];
  295. if (item != null)
  296. Debuger.LogError("OpenCloseMap::Add map alreay has item at X:"+inValue.X+" Y:"+inValue.Y);
  297. Count++;
  298. m_Map[inValue.X, inValue.Y] = inValue;
  299. }
  300. public bool Contains(PathNode inValue)
  301. {
  302. PathNode item = m_Map[inValue.X, inValue.Y];
  303. if (item == null)
  304. return false;
  305. return true;
  306. }
  307. public void Remove(PathNode inValue)
  308. {
  309. PathNode item = m_Map[inValue.X, inValue.Y];
  310. if (!inValue.Equals(item))
  311. Debuger.LogError("OpenCloseMap::Remove inValue not in map");
  312. Count--;
  313. m_Map[inValue.X, inValue.Y] = null;
  314. }
  315. public void Clear()
  316. {
  317. Count = 0;
  318. for (int x = 0; x < Width; x++)
  319. {
  320. for (int y = 0; y < Height; y++)
  321. {
  322. m_Map[x, y] = null;
  323. }
  324. }
  325. }
  326. }
  327. }