123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- #pragma warning disable 162
- #pragma warning disable 429
- #define DECREASE_KEY
- namespace Pathfinding {
- /// <summary>
- /// Binary heap implementation.
- /// Binary heaps are really fast for ordering nodes in a way that
- /// makes it possible to get the node with the lowest F score.
- /// Also known as a priority queue.
- ///
- /// This has actually been rewritten as a 4-ary heap
- /// for performance, but it's the same principle.
- ///
- /// See: http://en.wikipedia.org/wiki/Binary_heap
- /// See: https://en.wikipedia.org/wiki/D-ary_heap
- /// </summary>
- public class BinaryHeap {
- /// <summary>Number of items in the tree</summary>
- public int numberOfItems;
- /// <summary>The tree will grow by at least this factor every time it is expanded</summary>
- public float growthFactor = 2;
- /// <summary>
- /// Number of children of each node in the tree.
- /// Different values have been tested and 4 has been empirically found to perform the best.
- /// See: https://en.wikipedia.org/wiki/D-ary_heap
- /// </summary>
- const int D = 4;
- /// <summary>
- /// Sort nodes by G score if there is a tie when comparing the F score.
- /// Disabling this will improve pathfinding performance with around 2.5%
- /// but may break ties between paths that have the same length in a less
- /// desirable manner (only relevant for grid graphs).
- /// </summary>
- const bool SortGScores = true;
- public const ushort NotInHeap = 0xFFFF;
- /// <summary>Internal backing array for the heap</summary>
- private Tuple[] heap;
- /// <summary>True if the heap does not contain any elements</summary>
- public bool isEmpty {
- get {
- return numberOfItems <= 0;
- }
- }
- /// <summary>Item in the heap</summary>
- private struct Tuple {
- public PathNode node;
- public uint F;
- public Tuple (uint f, PathNode node) {
- this.F = f;
- this.node = node;
- }
- }
- /// <summary>
- /// Rounds up v so that it has remainder 1 when divided by D.
- /// I.e it is of the form n*D + 1 where n is any non-negative integer.
- /// </summary>
- static int RoundUpToNextMultipleMod1 (int v) {
- // I have a feeling there is a nicer way to do this
- return v + (4 - ((v-1) % D)) % D;
- }
- /// <summary>Create a new heap with the specified initial capacity</summary>
- public BinaryHeap (int capacity) {
- // Make sure the size has remainder 1 when divided by D
- // This allows us to always guarantee that indices used in the Remove method
- // will never throw out of bounds exceptions
- capacity = RoundUpToNextMultipleMod1(capacity);
- heap = new Tuple[capacity];
- numberOfItems = 0;
- }
- /// <summary>Removes all elements from the heap</summary>
- public void Clear () {
- #if DECREASE_KEY
- // Clear all heap indices
- // This is important to avoid bugs
- for (int i = 0; i < numberOfItems; i++) {
- heap[i].node.heapIndex = NotInHeap;
- }
- #endif
- numberOfItems = 0;
- }
- internal PathNode GetNode (int i) {
- return heap[i].node;
- }
- internal void SetF (int i, uint f) {
- heap[i].F = f;
- }
- /// <summary>Expands to a larger backing array when the current one is too small</summary>
- void Expand () {
- // 65533 == 1 mod 4 and slightly smaller than 1<<16 = 65536
- int newSize = System.Math.Max(heap.Length+4, System.Math.Min(65533, (int)System.Math.Round(heap.Length*growthFactor)));
- // Make sure the size has remainder 1 when divided by D
- // This allows us to always guarantee that indices used in the Remove method
- // will never throw out of bounds exceptions
- newSize = RoundUpToNextMultipleMod1(newSize);
- // Check if the heap is really large
- // Also note that heaps larger than this are not supported
- // since PathNode.heapIndex is a ushort and can only store
- // values up to 65535 (NotInHeap = 65535 is reserved however)
- if (newSize > (1<<16) - 2) {
- throw new System.Exception("Binary Heap Size really large (>65534). A heap size this large is probably the cause of pathfinding running in an infinite loop. ");
- }
- var newHeap = new Tuple[newSize];
- heap.CopyTo(newHeap, 0);
- #if ASTARDEBUG
- UnityEngine.Debug.Log("Resizing binary heap to "+newSize);
- #endif
- heap = newHeap;
- }
- /// <summary>Adds a node to the heap</summary>
- public void Add (PathNode node) {
- if (node == null) throw new System.ArgumentNullException("node");
- #if DECREASE_KEY
- // Check if node is already in the heap
- if (node.heapIndex != NotInHeap) {
- DecreaseKey(heap[node.heapIndex], node.heapIndex);
- return;
- }
- #endif
- if (numberOfItems == heap.Length) {
- Expand();
- }
- DecreaseKey(new Tuple(0, node), (ushort)numberOfItems);
- numberOfItems++;
- }
- void DecreaseKey (Tuple node, ushort index) {
- // This is where 'obj' is in the binary heap logically speaking
- // (for performance reasons we don't actually store it there until
- // we know the final index, that's just a waste of CPU cycles)
- int bubbleIndex = index;
- // Update F value, it might have changed since the node was originally added to the heap
- uint nodeF = node.F = node.node.F;
- uint nodeG = node.node.G;
- while (bubbleIndex != 0) {
- // Parent node of the bubble node
- int parentIndex = (bubbleIndex-1) / D;
- if (nodeF < heap[parentIndex].F || (SortGScores && nodeF == heap[parentIndex].F && nodeG > heap[parentIndex].node.G)) {
- // Swap the bubble node and parent node
- // (we don't really need to store the bubble node until we know the final index though
- // so we do that after the loop instead)
- heap[bubbleIndex] = heap[parentIndex];
- #if DECREASE_KEY
- heap[bubbleIndex].node.heapIndex = (ushort)bubbleIndex;
- #endif
- bubbleIndex = parentIndex;
- } else {
- break;
- }
- }
- heap[bubbleIndex] = node;
- #if DECREASE_KEY
- node.node.heapIndex = (ushort)bubbleIndex;
- #endif
- }
- /// <summary>Returns the node with the lowest F score from the heap</summary>
- public PathNode Remove () {
- PathNode returnItem = heap[0].node;
- #if DECREASE_KEY
- returnItem.heapIndex = NotInHeap;
- #endif
- numberOfItems--;
- if (numberOfItems == 0) return returnItem;
- // Last item in the heap array
- var swapItem = heap[numberOfItems];
- var swapItemG = swapItem.node.G;
- int swapIndex = 0, parent;
- // Trickle upwards
- while (true) {
- parent = swapIndex;
- uint swapF = swapItem.F;
- int pd = parent * D + 1;
- // If this holds, then the indices used
- // below are guaranteed to not throw an index out of bounds
- // exception since we choose the size of the array in that way
- if (pd <= numberOfItems) {
- // Loading all F scores here instead of inside the if statements
- // reduces data dependencies and improves performance
- uint f0 = heap[pd+0].F;
- uint f1 = heap[pd+1].F;
- uint f2 = heap[pd+2].F;
- uint f3 = heap[pd+3].F;
- // The common case is that all children of a node are present
- // so the first comparison in each if statement below
- // will be extremely well predicted so it is essentially free
- // (I tried optimizing for the common case, but it didn't affect performance at all
- // at the expense of longer code, the CPU branch predictor is really good)
- if (pd+0 < numberOfItems && (f0 < swapF || (SortGScores && f0 == swapF && heap[pd+0].node.G < swapItemG))) {
- swapF = f0;
- swapIndex = pd+0;
- }
- if (pd+1 < numberOfItems && (f1 < swapF || (SortGScores && f1 == swapF && heap[pd+1].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
- swapF = f1;
- swapIndex = pd+1;
- }
- if (pd+2 < numberOfItems && (f2 < swapF || (SortGScores && f2 == swapF && heap[pd+2].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
- swapF = f2;
- swapIndex = pd+2;
- }
- if (pd+3 < numberOfItems && (f3 < swapF || (SortGScores && f3 == swapF && heap[pd+3].node.G < (swapIndex == parent ? swapItemG : heap[swapIndex].node.G)))) {
- swapIndex = pd+3;
- }
- }
- // One if the parent's children are smaller or equal, swap them
- // (actually we are just pretenting we swapped them, we hold the swapData
- // in local variable and only assign it once we know the final index)
- if (parent != swapIndex) {
- heap[parent] = heap[swapIndex];
- #if DECREASE_KEY
- heap[parent].node.heapIndex = (ushort)parent;
- #endif
- } else {
- break;
- }
- }
- // Assign element to the final position
- heap[swapIndex] = swapItem;
- #if DECREASE_KEY
- swapItem.node.heapIndex = (ushort)swapIndex;
- #endif
- // For debugging
- // Validate ();
- return returnItem;
- }
- void Validate () {
- for (int i = 1; i < numberOfItems; i++) {
- int parentIndex = (i-1)/D;
- if (heap[parentIndex].F > heap[i].F) {
- throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + heap[parentIndex].F + " > " + heap[i].F + " ) ");
- }
- #if DECREASE_KEY
- if (heap[i].node.heapIndex != i) {
- throw new System.Exception("Invalid heap index");
- }
- #endif
- }
- }
- /// <summary>
- /// Rebuilds the heap by trickeling down all items.
- /// Usually called after the hTarget on a path has been changed
- /// </summary>
- public void Rebuild () {
- #if ASTARDEBUG
- int changes = 0;
- #endif
- for (int i = 2; i < numberOfItems; i++) {
- int bubbleIndex = i;
- var node = heap[i];
- uint nodeF = node.F;
- while (bubbleIndex != 1) {
- int parentIndex = bubbleIndex / D;
- if (nodeF < heap[parentIndex].F) {
- heap[bubbleIndex] = heap[parentIndex];
- #if DECREASE_KEY
- heap[bubbleIndex].node.heapIndex = (ushort)bubbleIndex;
- #endif
- heap[parentIndex] = node;
- #if DECREASE_KEY
- heap[parentIndex].node.heapIndex = (ushort)parentIndex;
- #endif
- bubbleIndex = parentIndex;
- #if ASTARDEBUG
- changes++;
- #endif
- } else {
- break;
- }
- }
- }
- #if ASTARDEBUG
- UnityEngine.Debug.Log("+++ Rebuilt Heap - "+changes+" changes +++");
- #endif
- }
- }
- }
|