// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Pathfinding; /// /// Network which is built of a two-dimensional grid of nodes where /// each coordinate has a fixed cost to reach it from any direction. /// The network proves the nodes of a smaller scope of the grid. /// public sealed class ScopedGridNetwork : BaseGridNetwork { private readonly Node?[] _gridNodes; private readonly byte _maximumSegmentSideLength; private readonly byte _minimumSegmentSideLength; private int _bitsPerCoordinate; private byte _actualSegmentSideLength; /// /// The offset of the in which the calculation takes place. /// private Point _segmentOffset; /// /// Initializes a new instance of the class. /// /// If set to true, diagonal traveling is allowed. /// Maximum Length of the segment side. Should be a power of 2. /// Minimum length of the segment side. Should be a power of 2. public ScopedGridNetwork(bool allowDiagonals = true, byte maximumSegmentSideLength = 16, byte minimumSegmentSideLength = 8) : base(allowDiagonals) { this._gridNodes = new Node[maximumSegmentSideLength * maximumSegmentSideLength]; this._maximumSegmentSideLength = maximumSegmentSideLength; this._minimumSegmentSideLength = minimumSegmentSideLength; } /// public override Node? GetNodeAt(Point position) { var nodeIndex = this.GetIndexOfPoint(position.X, position.Y); if (nodeIndex < 0 || nodeIndex >= this._actualSegmentSideLength * this._actualSegmentSideLength) { return null; } var node = this._gridNodes[nodeIndex]; if (node is null) { node = new Node { Position = position }; this._gridNodes[nodeIndex] = node; } return node; } /// public override bool Prepare(Point start, Point end, byte[,] grid, bool includeSafezone) { var diffX = Math.Abs(end.X - start.X); var diffY = Math.Abs(end.Y - start.Y); if (diffX > this._maximumSegmentSideLength || diffY > this._maximumSegmentSideLength) { return false; } this._actualSegmentSideLength = this._minimumSegmentSideLength; while ((diffX > this._actualSegmentSideLength - 1 || diffY > this._actualSegmentSideLength - 1) && this._actualSegmentSideLength < this._maximumSegmentSideLength) { this._actualSegmentSideLength *= 2; } this._bitsPerCoordinate = (int)Math.Log(this._actualSegmentSideLength, 2); var avg = (start / 2) + (end / 2); var offsetX = GetOffset(avg.X, grid.GetUpperBound(0) + 1); var offsetY = GetOffset(avg.Y, grid.GetUpperBound(1) + 1); this._segmentOffset = new(offsetX, offsetY); var maxX = offsetX + this._actualSegmentSideLength; var maxY = offsetY + this._actualSegmentSideLength; for (byte x = offsetX; x < maxX; ++x) { for (byte y = offsetY; y < maxY; ++y) { var i = this.GetIndexOfPoint(x, y); var node = this._gridNodes[i]; if (node is not null) { node.Status = NodeStatus.Undefined; node.Position = new(x, y); } } } return base.Prepare(start, end, grid, includeSafezone); byte GetOffset(byte avgValue, int gridSize) { var offset = (byte)Math.Max(avgValue - (this._actualSegmentSideLength / 2), 0); offset = (byte)Math.Min(offset, gridSize - this._actualSegmentSideLength); return offset; } } /// protected override bool IsWithinBounds(byte x, byte y) { return base.IsWithinBounds(x, y) && x >= this._segmentOffset.X && y >= this._segmentOffset.Y && x < this._segmentOffset.X + this._actualSegmentSideLength && y < this._segmentOffset.Y + this._actualSegmentSideLength; } private int GetIndexOfPoint(int x, int y) { y -= this._segmentOffset.Y; x -= this._segmentOffset.X; if (x < 0 || y < 0 || x >= this._actualSegmentSideLength || y >= this._actualSegmentSideLength) { return -1; } return (y << this._bitsPerCoordinate) + x; } }