using System; using System.Collections; using System.Linq; using System.Text; namespace Holo.Virtual.Rooms.Pathfinding { public class IHIPathfinder { /// /// Stores the state of the tiles. /// private byte[,] CollisionMap; /// /// Stores the height of the tiles. /// private float[,] Height; public IHIPathfinder() { } /// /// Set the tile map. /// /// The state of the tiles. /// The height of the tiles. public void ApplyCollisionMap(byte[,] Map, float[,] Height) { // Is this replacing an existing tile map if (CollisionMap != null) // Yes, ensure thread safety. lock (CollisionMap) { // Set the tile states. CollisionMap = Map; // Set the tile heights this.Height = Height; } else { // No, don't worry about other threads. // Set the tile states. CollisionMap = Map; // Set the tile heights this.Height = Height; } } // TODO: Finish commenting /// /// Get the next step on a path. /// /// PointA X /// PointA Y /// PointB X /// PointB Y /// public int[] Path(byte StartX, byte StartY, byte EndX, byte EndY, bool[,] UnitMap) { IHIPathfinderValues Values; lock (CollisionMap) // Thread Safety { Values = new IHIPathfinderValues(CollisionMap, Height, UnitMap); if (EndX >= CollisionMap.GetLength(0) || EndY >= CollisionMap.GetLength(1) || CollisionMap[EndX, EndY] == 0 || (StartX == EndX && StartY == EndY)) // Does a path need to be found and if so, the end on the map. return new int[0]; // No, no path possible/required. #region Init if (StartX > CollisionMap.GetUpperBound(0) || StartY > CollisionMap.GetUpperBound(1)) // Is the start on the map return new int[0]; // No, no path possible. Values.LastID++; Values.Count++; Values.BinaryHeap[Values.Count] = Values.LastID; Values.X[Values.LastID] = StartX; Values.Y[Values.LastID] = StartY; Values.H[Values.LastID] = (ushort)GetH(StartX, StartY, EndX, EndY); Values.Parent[Values.LastID] = 0; Values.G[Values.LastID] = 0; Values.F[Values.LastID] = (ushort)(Values.G[Values.LastID] + Values.H[Values.LastID]); #endregion while (Values.Count != 0) { Values.Location = Values.BinaryHeap[1]; if (Values.X[Values.Location] == EndX && Values.Y[Values.Location] == EndY) break; Move(Values); Add(-1, 0, EndX, EndY, Values); Add(0, -1, EndX, EndY, Values); Add(1, 0, EndX, EndY, Values); Add(0, 1, EndX, EndY, Values); Add(-1, -1, EndX, EndY, Values); Add(-1, 1, EndX, EndY, Values); Add(1, -1, EndX, EndY, Values); Add(1, 1, EndX, EndY, Values); } } if (Values.Count == 0) return new int[0]; while (Values.X[Values.Parent[Values.Location]] != StartX || Values.Y[Values.Parent[Values.Location]] != StartY) { Values.Location = Values.Parent[Values.Location]; } return new int[] { Values.X[Values.Location], Values.Y[Values.Location] }; } int GetH(int X, int Y, int EndX, int EndY) { return (Math.Abs(X + EndX) + Math.Abs(Y + EndY)); } private void Add(sbyte iX, sbyte iY, byte EndX, byte EndY, IHIPathfinderValues Values) { byte X = (byte)(Values.X[Values.Location] + iX); byte Y = (byte)(Values.Y[Values.Location] + iY); ushort Parent = Values.Location; if (X > CollisionMap.GetUpperBound(0) || Y > CollisionMap.GetUpperBound(1)) return; if (Values.Tiles[X, Y] == 2) return; if ((CollisionMap[X, Y] == 0 || (CollisionMap[X, Y] == 2 && (X != EndX || Y != EndY)))) return; if (Values.UnitMap[X, Y]) return; float Z = Values.Z[X, Y]; float pZ = Values.Z[Values.X[Parent], Values.Y[Parent]]; if (Z > pZ + 1.2 || Z < pZ - 3.0) // TODO: Max/Min jump height from database return; if (Parent > 0) { if (Values.X[Parent] != X && Values.Y[Parent] != Y) { if (CollisionMap[X, Values.Y[Parent]] == 0 || CollisionMap[X, Values.Y[Parent]] == 2) return; if (CollisionMap[Values.X[Parent], Y] == 0 || CollisionMap[Values.X[Parent], Y] == 2) return; } } if (Values.Tiles[X, Y] == 1) { ushort i = 1; for (; i <= Values.Count; i++) { if (Values.X[i] == X && Values.Y[i] == Y) break; } if (Values.X[i] == EndX || Values.Y[i] == EndY) { if (10 + Values.G[Parent] < Values.G[i]) Values.Parent[i] = Parent; } else if (14 + Values.G[Parent] < Values.G[i]) Values.Parent[i] = Parent; return; } Values.LastID++; Values.Count++; Values.BinaryHeap[Values.Count] = Values.LastID; Values.X[Values.LastID] = X; Values.Y[Values.LastID] = Y; Values.H[Values.LastID] = (ushort)GetH(X, Y, EndX, EndY); Values.Parent[Values.LastID] = Parent; if (X == Values.X[Parent] || Y == Values.Y[Parent]) Values.G[Values.LastID] = (ushort)(10 + Values.G[Parent]); else Values.G[Values.LastID] = (ushort)(14 + Values.G[Parent]); Values.F[Values.LastID] = (ushort)(Values.G[Values.LastID] + Values.H[Values.LastID]); ushort Temp; for (ushort C = Values.Count; C != 1; C /= 2) { if (Values.F[Values.BinaryHeap[C]] <= Values.F[Values.BinaryHeap[C / 2]]) { Temp = Values.BinaryHeap[C / 2]; Values.BinaryHeap[C / 2] = Values.BinaryHeap[C]; Values.BinaryHeap[C] = Temp; } else break; } Values.Tiles[X, Y] = 1; } private static void Move(IHIPathfinderValues Values) { Values.Tiles[Values.X[Values.BinaryHeap[1]], Values.Y[Values.BinaryHeap[1]]] = 2; Values.BinaryHeap[1] = Values.BinaryHeap[Values.Count]; Values.Count--; ushort Location = 1; ushort High; while (true) { High = Location; if (2 * High + 1 <= Values.Count) { if (Values.F[Values.BinaryHeap[High]] >= Values.F[Values.BinaryHeap[2 * High]]) Location = (ushort)(2 * High); if (Values.F[Values.BinaryHeap[Location]] >= Values.F[Values.BinaryHeap[2 * High + 1]]) Location = (ushort)(2 * High + 1); } else if (2 * High <= Values.Count) { if (Values.F[Values.BinaryHeap[High]] >= Values.F[Values.BinaryHeap[2 * High]]) Location = (ushort)(2 * High); } if (High != Location) { ushort Temp = Values.BinaryHeap[High]; Values.BinaryHeap[High] = Values.BinaryHeap[Location]; Values.BinaryHeap[Location] = Temp; } else break; } } } public class IHIPathfinderValues { public byte[,] Tiles; public float[,] Z; public byte[] X; public byte[] Y; public ushort[] H; public ushort[] G; public ushort[] F; public ushort Count; public ushort LastID; public ushort[] BinaryHeap; public ushort[] Parent; public ushort Location; public bool[,] UnitMap; public IHIPathfinderValues(byte[,] CollisionMap, float[,] Height, bool[,] UnitMap) { Tiles = new byte[CollisionMap.GetLength(0), CollisionMap.GetLength(1)]; X = new byte[Tiles.Length]; Y = new byte[X.Length]; Z = Height; H = new ushort[X.Length]; G = new ushort[X.Length]; F = new ushort[X.Length]; Count = 0; LastID = 0; BinaryHeap = new ushort[X.Length]; Parent = new ushort[X.Length]; Location = 0; this.UnitMap = UnitMap; } } }