// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Pathfinding; /// /// Base class for grid networks. /// public abstract class BaseGridNetwork : INetwork { /// /// The grid node value of an unreachable grid coordinate. /// private const byte UnreachableGridNodeValue = 0; /// /// The bit flag which marks a safezone node. /// private const byte SafezoneBitFlag = 0b1000_0000; /// /// The bit mask for the cost of a node. /// private const byte CostBitMask = 0b0111_1111; private static readonly sbyte[] DirectionOffsetsX = { 0, 1, 0, -1, 1, 1, -1, -1 }; private static readonly sbyte[] DirectionOffsetsY = { -1, 0, 1, 0, -1, 1, 1, -1 }; private readonly int _numberOfDirections; private ushort _gridWidth; private ushort _gridHeight; /// /// The two-dimensional grid. /// For each coordinate it contains the cost of traveling to it from a neighbor coordinate. /// The value of 0 means, that the coordinate is unreachable. /// private byte[,]? _grid; /// /// A flag, if safezone nodes should be included in the network. /// private bool _includeSafezone; /// /// Initializes a new instance of the class. /// /// If set to true, diagonal traveling is allowed. protected BaseGridNetwork(bool allowDiagonals) { this._numberOfDirections = allowDiagonals ? 8 : 4; } /// public virtual bool Prepare(Point start, Point end, byte[,] grid, bool includeSafezone) { this._grid = grid; this._gridWidth = (ushort)(grid.GetUpperBound(0) + 1); this._gridHeight = (ushort)(grid.GetUpperBound(1) + 1); this._includeSafezone = includeSafezone; return true; } /// /// /// Not sure, if the implementation should really filter out nodes based on their status and cost. /// public IEnumerable GetPossibleNextNodes(Node node) { var grid = this._grid ?? throw new InvalidOperationException("Call Prepare before"); for (int i = 0; i < this._numberOfDirections; i++) { var calculatedX = node.X + DirectionOffsetsX[i]; var calculatedY = node.Y + DirectionOffsetsY[i]; if ((uint)calculatedX > byte.MaxValue || (uint)calculatedY > byte.MaxValue) { continue; } var newX = (byte)calculatedX; var newY = (byte)calculatedY; if (!this.IsWithinBounds(newX, newY)) { continue; } var gridValue = grid[newX, newY]; if (!this._includeSafezone && (gridValue & SafezoneBitFlag) > 0) { continue; } var costToNode = gridValue & CostBitMask; if (costToNode == UnreachableGridNodeValue) { continue; } var newPoint = new Point(newX, newY); var newNode = this.GetNodeAt(newPoint); if (newNode is null || newNode.Status == NodeStatus.Closed) { continue; } var newG = node.CostUntilNow + costToNode; if (newNode.Status == NodeStatus.Open && newNode.CostUntilNow <= newG) { // The current node has less cost than the previous? then skip this node continue; } newNode.CostUntilNow = newG; yield return newNode; } } /// public abstract Node? GetNodeAt(Point position); /// /// Determines whether the coordinates are within bounds of this network. /// /// The x. /// The y. /// /// true if the coordinates are within bounds of this network; otherwise, false. /// protected virtual bool IsWithinBounds(byte x, byte y) { return x < this._gridWidth && y < this._gridHeight; } }