void TestWrappingEdgeEast(TestGrid grid) { int x; int y; for (int faceIndex = 0; faceIndex < 6; faceIndex++) { x = grid.RowCount - 1; for (y = 0; y < grid.RowCount; y++) { var sourcePosition = new CartesianGridCoordinates { x = (short)x, y = (short)y }; var sourceCubeFace = new CartesianGridOnCubeFace { Value = (byte)faceIndex }; var sourceCellIndex = CartesianGridOnCubeUtility.CellIndex(sourcePosition, sourceCubeFace, grid.RowCount); var exitPosition = new CartesianGridCoordinates { x = (short)(x + 1), y = (short)y }; var edge = CartesianGridMovement.CubeExitEdge(exitPosition, grid.RowCount); Assert.AreEqual(edge, 3); var nextCellIndex = CartesianGridOnCubeUtility.CellIndexEast(sourceCellIndex, grid.RowCount, grid.FaceLocalToLocal); var nextDirection = CartesianGridOnCubeUtility.NextFaceDirection[(edge * 6) + faceIndex]; var reverseDirection = CartesianGridMovement.ReverseDirection[nextDirection]; var returnCellIndex = CartesianGridOnCubeUtility.CellIndexFromExitEdge(reverseDirection, nextCellIndex, grid.RowCount); Assert.AreEqual(returnCellIndex, sourceCellIndex); } } }
public void AddWallSouth(int cellIndex) { SetWallBit(cellIndex, CartesianGridDirectionBit.South); var cellIndexSouth = CartesianGridOnCubeUtility.CellIndexSouth(cellIndex, RowCount, FaceLocalToLocal); SetWallBit(cellIndexSouth, CartesianGridDirectionBit.North); }
public void AddWallWest(int cellIndex) { SetWallBit(cellIndex, CartesianGridDirectionBit.West); var cellIndexWest = CartesianGridOnCubeUtility.CellIndexWest(cellIndex, RowCount, FaceLocalToLocal); SetWallBit(cellIndexWest, CartesianGridDirectionBit.East); }
int WalkPath(int cellIndex, NativeArray <byte> targetDirections, int pathOffset) { var cellPosition = CartesianGridOnCubeUtility.CellFaceCoordinates(cellIndex, RowCount); var faceIndex = CartesianGridOnCubeUtility.CellFaceIndex(cellIndex, RowCount); var validDirections = CartesianGridOnCubeShortestPath.LookupDirectionToTarget(cellPosition.x, cellPosition.y, faceIndex, RowCount, targetDirections); var direction = CartesianGridMovement.PathVariation[((pathOffset & 3) * 16) + validDirections]; if (direction == 0xff) // No path { return(0); } var nextCellIndex = -1; if (direction == 0) { nextCellIndex = CartesianGridOnCubeUtility.CellIndexNorth(cellIndex, RowCount, FaceLocalToLocal); } else if (direction == 1) { nextCellIndex = CartesianGridOnCubeUtility.CellIndexSouth(cellIndex, RowCount, FaceLocalToLocal); } else if (direction == 2) { nextCellIndex = CartesianGridOnCubeUtility.CellIndexWest(cellIndex, RowCount, FaceLocalToLocal); } else if (direction == 3) { nextCellIndex = CartesianGridOnCubeUtility.CellIndexEast(cellIndex, RowCount, FaceLocalToLocal); } else { Assert.Fail(); } // Test no wall in the direction given if (direction == 0) { Assert.IsFalse(TestWallBit(cellIndex, CartesianGridDirectionBit.North)); } else if (direction == 1) { Assert.IsFalse(TestWallBit(cellIndex, CartesianGridDirectionBit.South)); } else if (direction == 2) { Assert.IsFalse(TestWallBit(cellIndex, CartesianGridDirectionBit.West)); } else if (direction == 3) { Assert.IsFalse(TestWallBit(cellIndex, CartesianGridDirectionBit.East)); } return(1 + WalkPath(nextCellIndex, targetDirections, pathOffset)); }
public void PathRandomObstacles() { using (var grid = new TestGrid(31)) { int obstacleCount = 32; var rand = new Unity.Mathematics.Random(0xF545AA3F); for (int i = 0; i < obstacleCount; i++) { var faceIndex = rand.NextInt(0, 6); var xy = rand.NextInt2(new int2(grid.RowCount, grid.RowCount)); var sourcePosition = new CartesianGridCoordinates { x = (short)xy.x, y = (short)xy.y }; var cubeFace = new CartesianGridOnCubeFace { Value = (byte)faceIndex }; var cellIndex = CartesianGridOnCubeUtility.CellIndex(sourcePosition, cubeFace, grid.RowCount); if (rand.NextBool()) { grid.AddWallWest(cellIndex); } else { grid.AddWallSouth(cellIndex); } } int testCount = 64; for (int i = 0; i < testCount; i++) { var sourceXY = rand.NextInt2(new int2(grid.RowCount, grid.RowCount)); var sourceFaceIndex = rand.NextInt(0, 6); var sourceCubeFace = new CartesianGridOnCubeFace { Value = (byte)sourceFaceIndex }; var sourcePosition = new CartesianGridCoordinates { x = (short)sourceXY.x, y = (short)sourceXY.y }; var targetXY = rand.NextInt2(new int2(grid.RowCount, grid.RowCount)); var targetFaceIndex = rand.NextInt(0, 6); var targetCubeFace = new CartesianGridOnCubeFace { Value = (byte)targetFaceIndex }; var targetPosition = new CartesianGridCoordinates { x = (short)targetXY.x, y = (short)targetXY.y }; grid.WalkPathDistance(sourcePosition, sourceCubeFace, targetPosition, targetCubeFace); } } }
public int WalkPathDistance(CartesianGridCoordinates sourcePosition, CartesianGridOnCubeFace sourceCubeFace, CartesianGridCoordinates targetPosition, CartesianGridOnCubeFace targetCubeFace) { var directionsRowStride = (RowCount + 1) / 2; var directionsSize = 6 * RowCount * directionsRowStride; var distancesSize = RowCount * RowCount * 6; var targetDirections = new NativeArray <byte>(directionsSize, Allocator.Temp); var sourceDirections = new NativeArray <byte>(directionsSize, Allocator.Temp); var targetDistances = new NativeArray <int>(distancesSize, Allocator.Temp); var sourceDistances = new NativeArray <int>(distancesSize, Allocator.Temp); // For testing purposes, recalculate paths every time. CartesianGridOnCubeShortestPath.CalculateShortestPathsToTarget(targetDirections, targetDistances, RowCount, targetPosition, targetCubeFace, Walls, FaceLocalToLocal); CartesianGridOnCubeShortestPath.CalculateShortestPathsToTarget(sourceDirections, sourceDistances, RowCount, sourcePosition, sourceCubeFace, Walls, FaceLocalToLocal); // Test distance form source->target is same as target->source var sourceCellIndex = CartesianGridOnCubeUtility.CellIndex(sourcePosition, sourceCubeFace, RowCount); var targetCellIndex = CartesianGridOnCubeUtility.CellIndex(targetPosition, targetCubeFace, RowCount); var sourceToTargetDistance = WalkPath(sourceCellIndex, targetDirections, 0); var targetToSourceDistance = WalkPath(targetCellIndex, sourceDirections, 0); Assert.AreEqual(sourceToTargetDistance, targetToSourceDistance); var expectedDistance = sourceToTargetDistance; // No surprises on stored distances Assert.AreEqual(sourceToTargetDistance, sourceDistances[targetCellIndex]); Assert.AreEqual(sourceToTargetDistance, targetDistances[sourceCellIndex]); // Sample path variations (not exhaustive, always follow the variation path option) for (int i = 1; i < 4; i++) { // Test distance form source->target is same as target->source // Test distance is same for all variations sourceToTargetDistance = WalkPath(sourceCellIndex, targetDirections, i); Assert.AreEqual(expectedDistance, sourceToTargetDistance); targetToSourceDistance = WalkPath(targetCellIndex, sourceDirections, i); Assert.AreEqual(expectedDistance, targetToSourceDistance); } targetDirections.Dispose(); sourceDirections.Dispose(); targetDistances.Dispose(); sourceDistances.Dispose(); return(expectedDistance); }
public bool TestWallBit(int cellIndex, CartesianGridDirectionBit directionBit) { var cellPosition = CartesianGridOnCubeUtility.CellFaceCoordinates(cellIndex, RowCount); var faceIndex = CartesianGridOnCubeUtility.CellFaceIndex(cellIndex, RowCount); var rowStride = (RowCount + 1) / 2; var faceStride = RowCount * rowStride; var faceGridWallsOffset = faceIndex * faceStride; var x = cellPosition.x; var y = cellPosition.y; var gridWallsIndex = faceGridWallsOffset + ((y * ((RowCount + 1) / 2)) + (x / 2)); Assert.IsTrue(cellPosition.OnGrid(RowCount, RowCount)); return(((Walls[gridWallsIndex] >> (4 * (x & 1))) & (byte)directionBit) == (byte)directionBit); }
// Simple grassfire to calculate shortest distance along path to target position for every position. // - i.e. breadth-first expansion from target position. static void CalculateShortestWalkableDistancesToTarget(NativeArray <int> targetDistances, int rowCount, byte *gridWalls, CartesianGridCoordinates targetPosition, CartesianGridOnCubeFace cubeFace, float4x4 *faceLocalToLocal) { var cellCount = rowCount * rowCount; var closed = new UnsafeBitArray(6 * cellCount, Allocator.Temp, NativeArrayOptions.ClearMemory); var pending = new UnsafeBitArray(6 * cellCount, Allocator.Temp, NativeArrayOptions.ClearMemory); var open = new UnsafeRingQueue <int>(6 * cellCount, Allocator.Temp); var faceIndex = cubeFace.Value; var faceTargetCellIndex = (targetPosition.y * rowCount) + targetPosition.x; for (int i = 0; i < targetDistances.Length; i++) { targetDistances[i] = -1; } var targetCellIndex = (faceIndex * cellCount) + faceTargetCellIndex; targetDistances[targetCellIndex] = 0; pending.Set(targetCellIndex, true); closed.Set(targetCellIndex, true); var cellIndexNorth = CartesianGridOnCubeUtility.CellIndexNorth(targetCellIndex, rowCount, faceLocalToLocal); var cellIndexSouth = CartesianGridOnCubeUtility.CellIndexSouth(targetCellIndex, rowCount, faceLocalToLocal); var cellIndexWest = CartesianGridOnCubeUtility.CellIndexWest(targetCellIndex, rowCount, faceLocalToLocal); var cellIndexEast = CartesianGridOnCubeUtility.CellIndexEast(targetCellIndex, rowCount, faceLocalToLocal); var rowStride = (rowCount + 1) / 2; var faceStride = rowCount * rowStride; var faceGridWallsOffset = faceIndex * faceStride; var validDirections = CartesianGridMovement.ValidDirections(targetPosition, rowCount, rowCount, gridWalls + faceGridWallsOffset); var validNorth = ((validDirections & (byte)CartesianGridDirectionBit.North) != 0); var validSouth = ((validDirections & (byte)CartesianGridDirectionBit.South) != 0); var validWest = ((validDirections & (byte)CartesianGridDirectionBit.West) != 0); var validEast = ((validDirections & (byte)CartesianGridDirectionBit.East) != 0); if (validNorth) { open.Enqueue(cellIndexNorth); pending.Set(cellIndexNorth, true); } if (validSouth) { open.Enqueue(cellIndexSouth); pending.Set(cellIndexSouth, true); } if (validWest) { open.Enqueue(cellIndexWest); pending.Set(cellIndexWest, true); } if (validEast) { open.Enqueue(cellIndexEast); pending.Set(cellIndexEast, true); } CalculateShortestWalkableDistancesToTargetInner(targetDistances, rowCount, gridWalls, faceLocalToLocal, pending, closed, open); closed.Dispose(); pending.Dispose(); open.Dispose(); }
static void CalculateShortestWalkableDistancesToTargetInner(NativeArray <int> targetDistances, int rowCount, byte *gridWalls, float4x4 *faceLocalToLocal, UnsafeBitArray pending, UnsafeBitArray closed, UnsafeRingQueue <int> open) { var cellCount = rowCount * rowCount; var maxPathLength = 6 * (cellCount + 1); while (open.Length > 0) { var cellIndex = open.Dequeue(); var cellPosition = CartesianGridOnCubeUtility.CellFaceCoordinates(cellIndex, rowCount); var faceIndex = CartesianGridOnCubeUtility.CellFaceIndex(cellIndex, rowCount); var rowStride = (rowCount + 1) / 2; var faceStride = rowCount * rowStride; var faceGridWallsOffset = faceIndex * faceStride; var validDirections = CartesianGridMovement.ValidDirections(cellPosition, rowCount, rowCount, gridWalls + faceGridWallsOffset); var validNorth = ((validDirections & (byte)CartesianGridDirectionBit.North) != 0); var validSouth = ((validDirections & (byte)CartesianGridDirectionBit.South) != 0); var validWest = ((validDirections & (byte)CartesianGridDirectionBit.West) != 0); var validEast = ((validDirections & (byte)CartesianGridDirectionBit.East) != 0); var cellIndexNorth = CartesianGridOnCubeUtility.CellIndexNorth(cellIndex, rowCount, faceLocalToLocal); var cellIndexSouth = CartesianGridOnCubeUtility.CellIndexSouth(cellIndex, rowCount, faceLocalToLocal); var cellIndexWest = CartesianGridOnCubeUtility.CellIndexWest(cellIndex, rowCount, faceLocalToLocal); var cellIndexEast = CartesianGridOnCubeUtility.CellIndexEast(cellIndex, rowCount, faceLocalToLocal); var distanceNorth = maxPathLength; var distanceSouth = maxPathLength; var distanceEast = maxPathLength; var distanceWest = maxPathLength; var closedNorth = false; var closedSouth = false; var closedWest = false; var closedEast = false; if (validNorth) { if (closed.IsSet(cellIndexNorth)) { distanceNorth = targetDistances[cellIndexNorth]; closedNorth = true; } else if (!pending.IsSet(cellIndexNorth)) { open.Enqueue(cellIndexNorth); pending.Set(cellIndexNorth, true); } } if (validSouth) { if (closed.IsSet(cellIndexSouth)) { distanceSouth = targetDistances[cellIndexSouth]; closedSouth = true; } else if (!pending.IsSet(cellIndexSouth)) { open.Enqueue(cellIndexSouth); pending.Set(cellIndexSouth, true); } } if (validWest) { if (closed.IsSet(cellIndexWest)) { distanceWest = targetDistances[cellIndexWest]; closedWest = true; } else if (!pending.IsSet(cellIndexWest)) { open.Enqueue(cellIndexWest); pending.Set(cellIndexWest, true); } } if (validEast) { if (closed.IsSet(cellIndexEast)) { distanceEast = targetDistances[cellIndexEast]; closedEast = true; } else if (!pending.IsSet(cellIndexEast)) { open.Enqueue(cellIndexEast); pending.Set(cellIndexEast, true); } } var closedNeighbor = closedNorth || closedSouth || closedWest || closedEast; Assert.IsTrue(closedNeighbor); var bestDist = math.cmin(new int4(distanceNorth, distanceSouth, distanceEast, distanceWest)) + 1; Assert.IsFalse(bestDist > (maxPathLength + 1)); targetDistances[cellIndex] = bestDist; closed.Set(cellIndex, true); } }
// Sample valid neighboring distances from point. // - Smallest distance less than current position's distance is best next path to target. // - May result in more than one best direction (any of NSWE) // - May result in no best direction if on island (result=0xff) static void CalculateShortestPathGivenDistancesToTarget(NativeArray <byte> targetDirections, int rowCount, NativeArray <int> cellDistances, byte *gridWalls, float4x4 *faceLocalToLocal) { var cellCount = rowCount * rowCount; var maxPathLength = 6 * (cellCount + 1); for (int i = 0; i < targetDirections.Length; i++) { targetDirections[i] = 0; } for (var cellIndex = 0; cellIndex < (6 * cellCount); cellIndex++) { var cellPosition = CartesianGridOnCubeUtility.CellFaceCoordinates(cellIndex, rowCount); var faceIndex = CartesianGridOnCubeUtility.CellFaceIndex(cellIndex, rowCount); var rowStride = (rowCount + 1) / 2; var faceStride = rowCount * rowStride; var faceGridWallsOffset = faceIndex * faceStride; var validDirections = CartesianGridMovement.ValidDirections(cellPosition, rowCount, rowCount, gridWalls + faceGridWallsOffset); var validNorth = ((validDirections & (byte)CartesianGridDirectionBit.North) != 0); var validSouth = ((validDirections & (byte)CartesianGridDirectionBit.South) != 0); var validWest = ((validDirections & (byte)CartesianGridDirectionBit.West) != 0); var validEast = ((validDirections & (byte)CartesianGridDirectionBit.East) != 0); var cellIndexNorth = CartesianGridOnCubeUtility.CellIndexNorth(cellIndex, rowCount, faceLocalToLocal); var cellIndexSouth = CartesianGridOnCubeUtility.CellIndexSouth(cellIndex, rowCount, faceLocalToLocal); var cellIndexWest = CartesianGridOnCubeUtility.CellIndexWest(cellIndex, rowCount, faceLocalToLocal); var cellIndexEast = CartesianGridOnCubeUtility.CellIndexEast(cellIndex, rowCount, faceLocalToLocal); var distanceNorth = maxPathLength; var distanceSouth = maxPathLength; var distanceEast = maxPathLength; var distanceWest = maxPathLength; if (validNorth) { distanceNorth = cellDistances[cellIndexNorth]; } if (validSouth) { distanceSouth = cellDistances[cellIndexSouth]; } if (validWest) { distanceWest = cellDistances[cellIndexWest]; } if (validEast) { distanceEast = cellDistances[cellIndexEast]; } var bestDist = math.cmin(new int4(distanceNorth, distanceSouth, distanceEast, distanceWest)); var dist = cellDistances[cellIndex]; if ((bestDist < dist) && (bestDist < maxPathLength)) { var bestDir = 0; if (distanceNorth == bestDist) { bestDir |= (byte)CartesianGridDirectionBit.North; } if (distanceSouth == bestDist) { bestDir |= (byte)CartesianGridDirectionBit.South; } if (distanceWest == bestDist) { bestDir |= (byte)CartesianGridDirectionBit.West; } if (distanceEast == bestDist) { bestDir |= (byte)CartesianGridDirectionBit.East; } var targetDirectionsIndex = (faceIndex * faceStride) + (cellPosition.y * rowStride) + (cellPosition.x / 2); targetDirections[targetDirectionsIndex] |= (byte)(bestDir << (4 * (cellPosition.x & 1))); } } }