EuclideanEmbedding.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. #pragma warning disable 414
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. namespace Pathfinding {
  5. public enum HeuristicOptimizationMode {
  6. None,
  7. Random,
  8. RandomSpreadOut,
  9. Custom
  10. }
  11. /// <summary>
  12. /// Implements heuristic optimizations.
  13. ///
  14. /// See: heuristic-opt
  15. /// See: Game AI Pro - Pathfinding Architecture Optimizations by Steve Rabin and Nathan R. Sturtevant
  16. /// </summary>
  17. [System.Serializable]
  18. public class EuclideanEmbedding {
  19. /// <summary>
  20. /// If heuristic optimization should be used and how to place the pivot points.
  21. /// See: heuristic-opt
  22. /// See: Game AI Pro - Pathfinding Architecture Optimizations by Steve Rabin and Nathan R. Sturtevant
  23. /// </summary>
  24. public HeuristicOptimizationMode mode;
  25. public int seed;
  26. /// <summary>All children of this transform will be used as pivot points</summary>
  27. public Transform pivotPointRoot;
  28. public int spreadOutCount = 1;
  29. [System.NonSerialized]
  30. public bool dirty;
  31. /// <summary>
  32. /// Costs laid out as n*[int],n*[int],n*[int] where n is the number of pivot points.
  33. /// Each node has n integers which is the cost from that node to the pivot node.
  34. /// They are at around the same place in the array for simplicity and for cache locality.
  35. ///
  36. /// cost(nodeIndex, pivotIndex) = costs[nodeIndex*pivotCount+pivotIndex]
  37. /// </summary>
  38. uint[] costs = new uint[8];
  39. int maxNodeIndex;
  40. int pivotCount;
  41. GraphNode[] pivots;
  42. /*
  43. * Seed for random number generator.
  44. * Must not be zero
  45. */
  46. const uint ra = 12820163;
  47. /*
  48. * Seed for random number generator.
  49. * Must not be zero
  50. */
  51. const uint rc = 1140671485;
  52. /*
  53. * Parameter for random number generator.
  54. */
  55. uint rval;
  56. System.Object lockObj = new object();
  57. /// <summary>
  58. /// Simple linear congruential generator.
  59. /// See: http://en.wikipedia.org/wiki/Linear_congruential_generator
  60. /// </summary>
  61. uint GetRandom () {
  62. rval = (ra*rval + rc);
  63. return rval;
  64. }
  65. void EnsureCapacity (int index) {
  66. if (index > maxNodeIndex) {
  67. lock (lockObj) {
  68. if (index > maxNodeIndex) {
  69. if (index >= costs.Length) {
  70. var newCosts = new uint[System.Math.Max(index*2, pivots.Length*2)];
  71. for (int i = 0; i < costs.Length; i++) newCosts[i] = costs[i];
  72. costs = newCosts;
  73. }
  74. maxNodeIndex = index;
  75. }
  76. }
  77. }
  78. }
  79. public uint GetHeuristic (int nodeIndex1, int nodeIndex2) {
  80. nodeIndex1 *= pivotCount;
  81. nodeIndex2 *= pivotCount;
  82. if (nodeIndex1 >= costs.Length || nodeIndex2 >= costs.Length) {
  83. EnsureCapacity(nodeIndex1 > nodeIndex2 ? nodeIndex1 : nodeIndex2);
  84. }
  85. uint mx = 0;
  86. for (int i = 0; i < pivotCount; i++) {
  87. uint d = (uint)System.Math.Abs((int)costs[nodeIndex1+i] - (int)costs[nodeIndex2+i]);
  88. if (d > mx) mx = d;
  89. }
  90. return mx;
  91. }
  92. void GetClosestWalkableNodesToChildrenRecursively (Transform tr, List<GraphNode> nodes) {
  93. foreach (Transform ch in tr) {
  94. var info = AstarPath.active.GetNearest(ch.position, NNConstraint.Default);
  95. if (info.node != null && info.node.Walkable) {
  96. nodes.Add(info.node);
  97. }
  98. GetClosestWalkableNodesToChildrenRecursively(ch, nodes);
  99. }
  100. }
  101. /// <summary>
  102. /// Pick N random walkable nodes from all nodes in all graphs and add them to the buffer.
  103. ///
  104. /// Here we select N random nodes from a stream of nodes.
  105. /// Probability of choosing the first N nodes is 1
  106. /// Probability of choosing node i is min(N/i,1)
  107. /// A selected node will replace a random node of the previously
  108. /// selected ones.
  109. ///
  110. /// See: https://en.wikipedia.org/wiki/Reservoir_sampling
  111. /// </summary>
  112. void PickNRandomNodes (int count, List<GraphNode> buffer) {
  113. int n = 0;
  114. var graphs = AstarPath.active.graphs;
  115. // Loop through all graphs
  116. for (int j = 0; j < graphs.Length; j++) {
  117. // Loop through all nodes in the graph
  118. graphs[j].GetNodes(node => {
  119. if (!node.Destroyed && node.Walkable) {
  120. n++;
  121. if ((GetRandom() % n) < count) {
  122. if (buffer.Count < count) {
  123. buffer.Add(node);
  124. } else {
  125. buffer[(int)(GetRandom()%buffer.Count)] = node;
  126. }
  127. }
  128. }
  129. });
  130. }
  131. }
  132. GraphNode PickAnyWalkableNode () {
  133. var graphs = AstarPath.active.graphs;
  134. GraphNode first = null;
  135. // Find any node in the graphs
  136. for (int j = 0; j < graphs.Length; j++) {
  137. graphs[j].GetNodes(node => {
  138. if (node != null && node.Walkable && first == null) {
  139. first = node;
  140. }
  141. });
  142. }
  143. return first;
  144. }
  145. public void RecalculatePivots () {
  146. if (mode == HeuristicOptimizationMode.None) {
  147. pivotCount = 0;
  148. pivots = null;
  149. return;
  150. }
  151. // Reset the random number generator
  152. rval = (uint)seed;
  153. // Get a List<GraphNode> from a pool
  154. var pivotList = Pathfinding.Util.ListPool<GraphNode>.Claim();
  155. switch (mode) {
  156. case HeuristicOptimizationMode.Custom:
  157. if (pivotPointRoot == null) throw new System.Exception("heuristicOptimizationMode is HeuristicOptimizationMode.Custom, " +
  158. "but no 'customHeuristicOptimizationPivotsRoot' is set");
  159. GetClosestWalkableNodesToChildrenRecursively(pivotPointRoot, pivotList);
  160. break;
  161. case HeuristicOptimizationMode.Random:
  162. PickNRandomNodes(spreadOutCount, pivotList);
  163. break;
  164. case HeuristicOptimizationMode.RandomSpreadOut:
  165. if (pivotPointRoot != null) {
  166. GetClosestWalkableNodesToChildrenRecursively(pivotPointRoot, pivotList);
  167. }
  168. // If no pivot points were found, fall back to picking arbitrary nodes
  169. if (pivotList.Count == 0) {
  170. GraphNode first = PickAnyWalkableNode();
  171. if (first != null) {
  172. pivotList.Add(first);
  173. } else {
  174. Debug.LogError("Could not find any walkable node in any of the graphs.");
  175. Pathfinding.Util.ListPool<GraphNode>.Release(ref pivotList);
  176. return;
  177. }
  178. }
  179. // Fill remaining slots with null
  180. int toFill = spreadOutCount - pivotList.Count;
  181. for (int i = 0; i < toFill; i++) pivotList.Add(null);
  182. break;
  183. default:
  184. throw new System.Exception("Invalid HeuristicOptimizationMode: " + mode);
  185. }
  186. pivots = pivotList.ToArray();
  187. Pathfinding.Util.ListPool<GraphNode>.Release(ref pivotList);
  188. }
  189. public void RecalculateCosts () {
  190. if (pivots == null) RecalculatePivots();
  191. if (mode == HeuristicOptimizationMode.None) return;
  192. pivotCount = 0;
  193. for (int i = 0; i < pivots.Length; i++) {
  194. if (pivots[i] != null && (pivots[i].Destroyed || !pivots[i].Walkable)) {
  195. throw new System.Exception("Invalid pivot nodes (destroyed or unwalkable)");
  196. }
  197. }
  198. if (mode != HeuristicOptimizationMode.RandomSpreadOut)
  199. for (int i = 0; i < pivots.Length; i++)
  200. if (pivots[i] == null)
  201. throw new System.Exception("Invalid pivot nodes (null)");
  202. Debug.Log("Recalculating costs...");
  203. pivotCount = pivots.Length;
  204. System.Action<int> startCostCalculation = null;
  205. int numComplete = 0;
  206. OnPathDelegate onComplete = (Path path) => {
  207. numComplete++;
  208. if (numComplete == pivotCount) {
  209. // Last completed path
  210. ApplyGridGraphEndpointSpecialCase();
  211. }
  212. };
  213. startCostCalculation = (int pivotIndex) => {
  214. GraphNode pivot = pivots[pivotIndex];
  215. FloodPath floodPath = null;
  216. floodPath = FloodPath.Construct(pivot, onComplete);
  217. floodPath.immediateCallback = (Path _p) => {
  218. // Handle path pooling
  219. _p.Claim(this);
  220. // When paths are calculated on navmesh based graphs
  221. // the costs are slightly modified to match the actual target and start points
  222. // instead of the node centers
  223. // so we have to remove the cost for the first and last connection
  224. // in each path
  225. var meshNode = pivot as MeshNode;
  226. uint costOffset = 0;
  227. if (meshNode != null && meshNode.connections != null) {
  228. for (int i = 0; i < meshNode.connections.Length; i++) {
  229. costOffset = System.Math.Max(costOffset, meshNode.connections[i].cost);
  230. }
  231. }
  232. var graphs = AstarPath.active.graphs;
  233. // Process graphs in reverse order to raise probability that we encounter large NodeIndex values quicker
  234. // to avoid resizing the internal array too often
  235. for (int j = graphs.Length-1; j >= 0; j--) {
  236. graphs[j].GetNodes(node => {
  237. int idx = node.NodeIndex*pivotCount + pivotIndex;
  238. EnsureCapacity(idx);
  239. PathNode pn = ((IPathInternals)floodPath).PathHandler.GetPathNode(node);
  240. if (costOffset > 0) {
  241. costs[idx] = pn.pathID == floodPath.pathID && pn.parent != null ? System.Math.Max(pn.parent.G-costOffset, 0) : 0;
  242. } else {
  243. costs[idx] = pn.pathID == floodPath.pathID ? pn.G : 0;
  244. }
  245. });
  246. }
  247. if (mode == HeuristicOptimizationMode.RandomSpreadOut && pivotIndex < pivots.Length-1) {
  248. // If the next pivot is null
  249. // then find the node which is furthest away from the earlier
  250. // pivot points
  251. if (pivots[pivotIndex+1] == null) {
  252. int best = -1;
  253. uint bestScore = 0;
  254. // Actual number of nodes
  255. int totCount = maxNodeIndex/pivotCount;
  256. // Loop through all nodes
  257. for (int j = 1; j < totCount; j++) {
  258. // Find the minimum distance from the node to all existing pivot points
  259. uint mx = 1 << 30;
  260. for (int p = 0; p <= pivotIndex; p++) mx = System.Math.Min(mx, costs[j*pivotCount + p]);
  261. // Pick the node which has the largest minimum distance to the existing pivot points
  262. // (i.e pick the one furthest away from the existing ones)
  263. GraphNode node = ((IPathInternals)floodPath).PathHandler.GetPathNode(j).node;
  264. if ((mx > bestScore || best == -1) && node != null && !node.Destroyed && node.Walkable) {
  265. best = j;
  266. bestScore = mx;
  267. }
  268. }
  269. if (best == -1) {
  270. Debug.LogError("Failed generating random pivot points for heuristic optimizations");
  271. return;
  272. }
  273. pivots[pivotIndex+1] = ((IPathInternals)floodPath).PathHandler.GetPathNode(best).node;
  274. }
  275. // Start next path
  276. startCostCalculation(pivotIndex+1);
  277. }
  278. // Handle path pooling
  279. _p.Release(this);
  280. };
  281. AstarPath.StartPath(floodPath, true);
  282. };
  283. if (mode != HeuristicOptimizationMode.RandomSpreadOut) {
  284. // All calculated in parallel
  285. for (int i = 0; i < pivots.Length; i++) {
  286. startCostCalculation(i);
  287. }
  288. } else {
  289. // Recursive and serial
  290. startCostCalculation(0);
  291. }
  292. dirty = false;
  293. }
  294. /// <summary>
  295. /// Special case necessary for paths to unwalkable nodes right next to walkable nodes to be able to use good heuristics.
  296. ///
  297. /// This will find all unwalkable nodes in all grid graphs with walkable nodes as neighbours
  298. /// and set the cost to reach them from each of the pivots as the minimum of the cost to
  299. /// reach the neighbours of each node.
  300. ///
  301. /// See: ABPath.EndPointGridGraphSpecialCase
  302. /// </summary>
  303. void ApplyGridGraphEndpointSpecialCase () {
  304. #if !ASTAR_NO_GRID_GRAPH
  305. var graphs = AstarPath.active.graphs;
  306. for (int i = 0; i < graphs.Length; i++) {
  307. var gg = graphs[i] as GridGraph;
  308. if (gg != null) {
  309. // Found a grid graph
  310. var nodes = gg.nodes;
  311. // Number of neighbours as an int
  312. int mxnum = gg.neighbours == NumNeighbours.Four ? 4 : (gg.neighbours == NumNeighbours.Eight ? 8 : 6);
  313. for (int z = 0; z < gg.depth; z++) {
  314. for (int x = 0; x < gg.width; x++) {
  315. var node = nodes[z*gg.width + x];
  316. if (!node.Walkable) {
  317. var pivotIndex = node.NodeIndex*pivotCount;
  318. // Set all costs to reach this node to maximum
  319. for (int piv = 0; piv < pivotCount; piv++) {
  320. costs[pivotIndex + piv] = uint.MaxValue;
  321. }
  322. // Loop through all potential neighbours of the node
  323. // and set the cost to reach it as the minimum
  324. // of the costs to reach any of the adjacent nodes
  325. for (int d = 0; d < mxnum; d++) {
  326. int nx, nz;
  327. if (gg.neighbours == NumNeighbours.Six) {
  328. // Hexagon graph
  329. nx = x + gg.neighbourXOffsets[GridGraph.hexagonNeighbourIndices[d]];
  330. nz = z + gg.neighbourZOffsets[GridGraph.hexagonNeighbourIndices[d]];
  331. } else {
  332. nx = x + gg.neighbourXOffsets[d];
  333. nz = z + gg.neighbourZOffsets[d];
  334. }
  335. // Check if the position is still inside the grid
  336. if (nx >= 0 && nz >= 0 && nx < gg.width && nz < gg.depth) {
  337. var adjacentNode = gg.nodes[nz*gg.width + nx];
  338. if (adjacentNode.Walkable) {
  339. for (int piv = 0; piv < pivotCount; piv++) {
  340. uint cost = costs[adjacentNode.NodeIndex*pivotCount + piv] + gg.neighbourCosts[d];
  341. costs[pivotIndex + piv] = System.Math.Min(costs[pivotIndex + piv], cost);
  342. //Debug.DrawLine((Vector3)node.position, (Vector3)adjacentNode.position, Color.blue, 1);
  343. }
  344. }
  345. }
  346. }
  347. // If no adjacent nodes were found
  348. // set the cost to reach it back to zero
  349. for (int piv = 0; piv < pivotCount; piv++) {
  350. if (costs[pivotIndex + piv] == uint.MaxValue) {
  351. costs[pivotIndex + piv] = 0;
  352. }
  353. }
  354. }
  355. }
  356. }
  357. }
  358. }
  359. #endif
  360. }
  361. public void OnDrawGizmos () {
  362. if (pivots != null) {
  363. for (int i = 0; i < pivots.Length; i++) {
  364. Gizmos.color = new Color(159/255.0f, 94/255.0f, 194/255.0f, 0.8f);
  365. if (pivots[i] != null && !pivots[i].Destroyed) {
  366. Gizmos.DrawCube((Vector3)pivots[i].position, Vector3.one);
  367. }
  368. }
  369. }
  370. }
  371. }
  372. }