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); } } }
// 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(int rowCount, int colCount, byte *gridWalls, CartesianGridCoordinates targetPosition, NativeArray <int> targetDistances) { var cellCount = rowCount * colCount; var closed = new UnsafeBitArray(cellCount, Allocator.Temp, NativeArrayOptions.ClearMemory); var pending = new UnsafeBitArray(cellCount, Allocator.Temp, NativeArrayOptions.ClearMemory); var open = new UnsafeRingQueue <int>(cellCount, Allocator.Temp); var targetCellIndex = (targetPosition.y * colCount) + targetPosition.x; var cellIndexNorth = targetCellIndex + colCount; var cellIndexSouth = targetCellIndex - colCount; var cellIndexWest = targetCellIndex - 1; var cellIndexEast = targetCellIndex + 1; for (int i = 0; i < targetDistances.Length; i++) { targetDistances[i] = -1; } targetDistances[targetCellIndex] = 0; pending.Set(targetCellIndex, true); closed.Set(targetCellIndex, true); var validDirections = CartesianGridMovement.ValidDirections(targetPosition, rowCount, colCount, gridWalls); 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(rowCount, colCount, gridWalls, targetDistances, pending, closed, open); closed.Dispose(); pending.Dispose(); open.Dispose(); }
public static int CellIndexEast(int cellIndex, int rowCount, float4x4 *faceLocalToLocal) { var faceIndex = CellFaceIndex(cellIndex, rowCount); var facePosition = CellFaceCoordinates(cellIndex, rowCount); facePosition.x += 1; var edge = CartesianGridMovement.CubeExitEdge(facePosition, rowCount); if (edge == -1) { return(cellIndex + 1); } return(CellIndexFromExitEdge(edge, faceIndex, facePosition, rowCount)); }
protected override JobHandle OnUpdate(JobHandle lastJobHandle) { int pathOffset = CartesianGridMovement.NextPathIndex(ref m_NextPathCounter); // Get component data from the Grid (GridPlane or GridCube) var cartesianGridPlane = GetSingleton <CartesianGridOnPlane>(); var rowCount = cartesianGridPlane.Blob.Value.RowCount; var colCount = cartesianGridPlane.Blob.Value.ColCount; var rowStride = ((colCount + 1) / 2); var gridWalls = (byte *)cartesianGridPlane.Blob.Value.Walls.GetUnsafePtr(); var trailingOffsets = (float2 *)cartesianGridPlane.Blob.Value.TrailingOffsets.GetUnsafePtr(); // Offset center to grid cell var cellCenterOffset = new float2(((float)colCount * 0.5f) - 0.5f, ((float)rowCount * 0.5f) - 0.5f); lastJobHandle = Entities .WithName("CartesianGridPlaneChangeDirection") .WithNativeDisableUnsafePtrRestriction(trailingOffsets) .WithNativeDisableUnsafePtrRestriction(gridWalls) .ForEach((ref CartesianGridDirection gridDirection, ref CartesianGridCoordinates gridCoordinates, ref Translation translation) => { var dir = gridDirection.Value; var nextGridPosition = new CartesianGridCoordinates(translation.Value.xz + trailingOffsets[dir], rowCount, colCount); if (gridCoordinates.Equals(nextGridPosition)) { // Don't allow translation to drift translation.Value = CartesianGridMovement.ClampToGrid(translation.Value, dir, gridCoordinates, cellCenterOffset); return; // Still in the same grid cell. No need to change direction. } gridCoordinates = nextGridPosition; gridDirection.Value = CartesianGridMovement.LookupGridDirectionFromWalls(gridCoordinates, dir, rowStride, gridWalls, pathOffset); }).Schedule(lastJobHandle); return(lastJobHandle); }
protected override JobHandle OnUpdate(JobHandle lastJobHandle) { int pathOffset = CartesianGridMovement.NextPathIndex(ref m_NextPathCounter); // Get component data from the Grid (GridCube or GridCube) var cartesianGridCube = GetSingleton <CartesianGridOnCube>(); var rowCount = cartesianGridCube.Blob.Value.RowCount; var gridWalls = (byte *)cartesianGridCube.Blob.Value.Walls.GetUnsafePtr(); var trailingOffsets = (float2 *)cartesianGridCube.Blob.Value.TrailingOffsets.GetUnsafePtr(); var faceLocalToLocal = (float4x4 *)cartesianGridCube.Blob.Value.FaceLocalToLocal.GetUnsafePtr(); // Offset center to grid cell var cellCenterOffset = new float2(((float)rowCount * 0.5f) - 0.5f, ((float)rowCount * 0.5f) - 0.5f); lastJobHandle = Entities .WithName("CartesianGridOnCubeChangeDirection") .WithNativeDisableUnsafePtrRestriction(gridWalls) .WithNativeDisableUnsafePtrRestriction(faceLocalToLocal) .WithNativeDisableUnsafePtrRestriction(trailingOffsets) .WithEntityQueryOptions(EntityQueryOptions.FilterWriteGroup) .ForEach((ref CartesianGridDirection gridDirection, ref Translation translation, ref CartesianGridCoordinates gridCoordinates, ref CartesianGridOnCubeFace cubeFace) => { var prevDir = gridDirection.Value; var trailingOffset = trailingOffsets[prevDir]; var pos = translation.Value.xz + trailingOffset; var nextGridPosition = new CartesianGridCoordinates(pos, rowCount, rowCount); if (gridCoordinates.Equals(nextGridPosition)) { // Don't allow translation to drift translation.Value = CartesianGridMovement.SnapToGridAlongDirection(translation.Value, prevDir, gridCoordinates, cellCenterOffset); return; // Still in the same grid cell. No need to change direction. } var edge = CartesianGridMovement.CubeExitEdge(nextGridPosition, rowCount); // Change direction based on wall layout (within current face.) if (edge == -1) { var faceIndex = cubeFace.Value; var rowStride = (rowCount + 1) / 2; var faceStride = rowCount * rowStride; var faceGridWallsOffset = faceIndex * faceStride; gridCoordinates = nextGridPosition; gridDirection.Value = CartesianGridMovement.BounceDirectionOffWalls(gridCoordinates, prevDir, rowCount, rowCount, gridWalls + faceGridWallsOffset, pathOffset); } // Exiting face of GridCube, change face and direction relative to new face. else { int prevFaceIndex = cubeFace.Value; // Look up next direction given previous face and exit edge. var nextDir = CartesianGridOnCubeUtility.NextFaceDirection[(edge * 6) + prevFaceIndex]; gridDirection.Value = nextDir; // Lookup next face index given previous face and exit edge. var nextFaceIndex = CartesianGridOnCubeUtility.NextFaceIndex[(edge * 6) + prevFaceIndex]; cubeFace.Value = nextFaceIndex; // Transform translation relative to next face's grid-space // - This transform is only done to "smooth" the transition around the edges. // - Alternatively, you could "snap" to the same relative position in the next face by rotating the translation components. // - Note that Y position won't be at target value from one edge to another, so that is interpolated in movement update, // purely for "smoothing" purposes. var localToLocal = faceLocalToLocal[((prevFaceIndex * 6) + nextFaceIndex)]; translation.Value.xyz = math.mul(localToLocal, new float4(translation.Value, 1.0f)).xyz; // Update gridPosition relative to new face. gridCoordinates = new CartesianGridCoordinates(translation.Value.xz + trailingOffsets[nextDir], rowCount, rowCount); } }).Schedule(lastJobHandle); return(lastJobHandle); }
protected override JobHandle OnUpdate(JobHandle lastJobHandle) { int pathOffset = m_PathVariationOffset; m_PathVariationOffset = (m_PathVariationOffset + 1) & 3; // Get component data from the GridPlane var cartesianGridPlane = GetSingleton <CartesianGridOnPlane>(); var rowCount = cartesianGridPlane.Blob.Value.RowCount; var colCount = cartesianGridPlane.Blob.Value.ColCount; var trailingOffsets = (float2 *)cartesianGridPlane.Blob.Value.TrailingOffsets.GetUnsafePtr(); var targetEntities = m_TargetQuery.ToEntityArray(Allocator.TempJob); var targetCoordinates = m_TargetQuery.ToComponentDataArray <CartesianGridCoordinates>(Allocator.TempJob); var getCartesianGridTargetDirectionFromEntity = GetBufferFromEntity <CartesianGridTargetDirection>(true); // Offset center to grid cell var cellCenterOffset = new float2(((float)colCount * 0.5f) - 0.5f, ((float)rowCount * 0.5f) - 0.5f); // Whenever a CartesianGridFollowTarget reaches a new grid cell, make a decision about what next direction to turn. lastJobHandle = Entities .WithName("ChangeDirectionTowardNearestTarget") .WithNativeDisableUnsafePtrRestriction(trailingOffsets) .WithEntityQueryOptions(EntityQueryOptions.FilterWriteGroup) .WithReadOnly(targetCoordinates) .WithReadOnly(getCartesianGridTargetDirectionFromEntity) .WithAll <CartesianGridFollowTarget>() .ForEach((ref CartesianGridDirection gridDirection, ref CartesianGridCoordinates gridCoordinates, ref Translation translation) => { var dir = gridDirection.Value; if (dir != 0xff) // If moving, update grid based on trailing direction. { var nextGridPosition = new CartesianGridCoordinates(translation.Value.xz + trailingOffsets[dir], rowCount, colCount); if (gridCoordinates.Equals(nextGridPosition)) { // Don't allow translation to drift translation.Value = CartesianGridMovement.SnapToGridAlongDirection(translation.Value, dir, gridCoordinates, cellCenterOffset); return; // Still in the same grid cell. No need to change direction. } gridCoordinates = nextGridPosition; } var targetEntity = FindTargetShortestManhattanDistance(gridCoordinates, rowCount, colCount, targetCoordinates, targetEntities); if (targetEntity == Entity.Null) { // No target for whatever reason, don't move. gridDirection.Value = 0xff; return; } // Lookup next direction along shortest path to target from table stored in CartesianGridTargetDirection // - When multiple shortest path available, use pathOffset to select which option. var targetDirections = getCartesianGridTargetDirectionFromEntity[targetEntity].Reinterpret <byte>().AsNativeArray(); var validDirections = CartesianGridOnPlaneShortestPath.LookupDirectionToTarget(gridCoordinates, rowCount, colCount, targetDirections); gridDirection.Value = CartesianGridMovement.PathVariation[(pathOffset * 16) + validDirections]; }).Schedule(lastJobHandle); lastJobHandle = targetEntities.Dispose(lastJobHandle); lastJobHandle = targetCoordinates.Dispose(lastJobHandle); return(lastJobHandle); }
// 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))); } } }
protected override JobHandle OnUpdate(JobHandle lastJobHandle) { int pathOffset = m_PathVariationOffset; m_PathVariationOffset = (m_PathVariationOffset + 1) & 3; // Get component data from the GridCube var cartesianGridCube = GetSingleton <CartesianGridOnCube>(); var rowCount = cartesianGridCube.Blob.Value.RowCount; var gridWalls = (byte *)cartesianGridCube.Blob.Value.Walls.GetUnsafePtr(); var trailingOffsets = (float2 *)cartesianGridCube.Blob.Value.TrailingOffsets.GetUnsafePtr(); var faceLocalToLocal = (float4x4 *)cartesianGridCube.Blob.Value.FaceLocalToLocal.GetUnsafePtr(); var targetEntities = m_TargetQuery.ToEntityArray(Allocator.TempJob); var targetCoordinates = m_TargetQuery.ToComponentDataArray <CartesianGridCoordinates>(Allocator.TempJob); var getCartesianGridTargetDirectionFromEntity = GetBufferFromEntity <CartesianGridTargetDirection>(true); var getCartesianGridTargetDistanceFromEntity = GetBufferFromEntity <CartesianGridTargetDistance>(true); // Offset center to grid cell var cellCenterOffset = new float2(((float)rowCount * 0.5f) - 0.5f, ((float)rowCount * 0.5f) - 0.5f); // Whenever a CartesianGridFollowTarget reaches a new grid cell, make a decision about what next direction to turn. lastJobHandle = Entities .WithName("ChangeDirectionTowardNearestTarget") .WithNativeDisableUnsafePtrRestriction(trailingOffsets) .WithNativeDisableUnsafePtrRestriction(faceLocalToLocal) .WithNativeDisableUnsafePtrRestriction(gridWalls) .WithEntityQueryOptions(EntityQueryOptions.FilterWriteGroup) .WithReadOnly(targetCoordinates) .WithReadOnly(getCartesianGridTargetDirectionFromEntity) .WithReadOnly(getCartesianGridTargetDistanceFromEntity) .WithAll <CartesianGridFollowTarget>() .ForEach((ref CartesianGridDirection gridDirection, ref CartesianGridCoordinates gridCoordinates, ref Translation translation, ref CartesianGridOnCubeFace cubeFace) => { var dir = gridDirection.Value; if (dir != 0xff) // If moving, update grid based on trailing direction. { var nextGridPosition = new CartesianGridCoordinates(translation.Value.xz + trailingOffsets[dir], rowCount, rowCount); if (gridCoordinates.Equals(nextGridPosition)) { // Don't allow translation to drift translation.Value = CartesianGridMovement.SnapToGridAlongDirection(translation.Value, dir, gridCoordinates, cellCenterOffset); return; // Still in the same grid cell. No need to change direction. } var edge = CartesianGridMovement.CubeExitEdge(nextGridPosition, rowCount); // Change direction based on wall layout (within current face.) if (edge == -1) { gridCoordinates = nextGridPosition; } // Exiting face of GridCube, change face and direction relative to new face. else { int prevFaceIndex = cubeFace.Value; // Look up next direction given previous face and exit edge. var nextDir = CartesianGridOnCubeUtility.NextFaceDirection[(edge * 6) + prevFaceIndex]; gridDirection.Value = nextDir; // Lookup next face index given previous face and exit edge. var nextFaceIndex = CartesianGridOnCubeUtility.NextFaceIndex[(edge * 6) + prevFaceIndex]; cubeFace.Value = nextFaceIndex; // Transform translation relative to next face's grid-space // - This transform is only done to "smooth" the transition around the edges. // - Alternatively, you could "snap" to the same relative position in the next face by rotating the translation components. // - Note that Y position won't be at target value from one edge to another, so that is interpolated in movement update, // purely for "smoothing" purposes. var localToLocal = faceLocalToLocal[((prevFaceIndex * 6) + nextFaceIndex)]; translation.Value.xyz = math.mul(localToLocal, new float4(translation.Value, 1.0f)).xyz; // Update gridPosition relative to new face. gridCoordinates = new CartesianGridCoordinates(translation.Value.xz + trailingOffsets[nextDir], rowCount, rowCount); } } if (!gridCoordinates.OnGrid(rowCount, rowCount)) { return; } var targetEntity = FindTargetShortestPathLength(gridCoordinates, cubeFace, rowCount, targetCoordinates, targetEntities, getCartesianGridTargetDistanceFromEntity); if (targetEntity == Entity.Null) { // No target for whatever reason, look busy. int faceIndex = cubeFace.Value; var rowStride = (rowCount + 1) / 2; var faceStride = rowCount * rowStride; var faceGridWallsOffset = faceIndex * faceStride; gridDirection.Value = CartesianGridMovement.BounceDirectionOffWalls(gridCoordinates, dir, rowCount, rowCount, gridWalls + faceGridWallsOffset, pathOffset); return; } // Lookup next direction along shortest path to target from table stored in CartesianGridTargetDirection // - When multiple shortest path available, use pathOffset to select which option. var targetDirections = getCartesianGridTargetDirectionFromEntity[targetEntity].Reinterpret <byte>().AsNativeArray(); var validDirections = CartesianGridOnCubeShortestPath.LookupDirectionToTarget(gridCoordinates, cubeFace, rowCount, targetDirections); gridDirection.Value = CartesianGridMovement.PathVariation[(pathOffset * 16) + validDirections]; }).Schedule(lastJobHandle); lastJobHandle = targetEntities.Dispose(lastJobHandle); lastJobHandle = targetCoordinates.Dispose(lastJobHandle); return(lastJobHandle); }
static void CalculateShortestWalkableDistancesToTargetInner(int rowCount, int colCount, byte *gridWalls, NativeArray <int> targetDistances, UnsafeBitArray pending, UnsafeBitArray closed, UnsafeRingQueue <int> open) { var cellCount = rowCount * colCount; while (open.Count > 0) { var cellIndex = open.Dequeue(); var y = cellIndex / colCount; var x = cellIndex - (y * colCount); var cellPosition = new CartesianGridCoordinates { x = (short)x, y = (short)y }; var validDirections = CartesianGridMovement.ValidDirections(cellPosition, rowCount, colCount, gridWalls); 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 = cellIndex + colCount; var cellIndexSouth = cellIndex - colCount; var cellIndexWest = cellIndex - 1; var cellIndexEast = cellIndex + 1; var distanceNorth = cellCount + 1; var distanceSouth = cellCount + 1; var distanceEast = cellCount + 1; var distanceWest = cellCount + 1; if (validNorth) { if (closed.IsSet(cellIndexNorth)) { distanceNorth = targetDistances[cellIndexNorth]; } else if (!pending.IsSet(cellIndexNorth)) { open.Enqueue(cellIndexNorth); pending.Set(cellIndexNorth, true); } } if (validSouth) { if (closed.IsSet(cellIndexSouth)) { distanceSouth = targetDistances[cellIndexSouth]; } else if (!pending.IsSet(cellIndexSouth)) { open.Enqueue(cellIndexSouth); pending.Set(cellIndexSouth, true); } } if (validWest) { if (closed.IsSet(cellIndexWest)) { distanceWest = targetDistances[cellIndexWest]; } else if (!pending.IsSet(cellIndexWest)) { open.Enqueue(cellIndexWest); pending.Set(cellIndexWest, true); } } if (validEast) { if (closed.IsSet(cellIndexEast)) { distanceEast = targetDistances[cellIndexEast]; } else if (!pending.IsSet(cellIndexEast)) { open.Enqueue(cellIndexEast); pending.Set(cellIndexEast, true); } } var bestDist = math.cmin(new int4(distanceNorth, distanceSouth, distanceEast, distanceWest)) + 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, int colCount, NativeArray <int> cellDistances, byte *gridWalls) { var cellCount = rowCount * colCount; var rowStride = ((colCount + 1) / 2); for (var i = 0; i < (rowStride * rowCount); i++) { targetDirections[i] = 0; } for (var cellIndex = 0; cellIndex < cellCount; cellIndex++) { var y = cellIndex / colCount; var x = cellIndex - (y * colCount); var cellPosition = new CartesianGridCoordinates { x = (short)x, y = (short)y }; var validDirections = CartesianGridMovement.ValidDirections(cellPosition, rowCount, colCount, gridWalls); 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 = cellIndex + colCount; var cellIndexSouth = cellIndex - colCount; var cellIndexWest = cellIndex - 1; var cellIndexEast = cellIndex + 1; var distanceNorth = cellCount + 1; var distanceSouth = cellCount + 1; var distanceEast = cellCount + 1; var distanceWest = cellCount + 1; 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 <= cellCount)) { 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 = (y * rowStride) + (x / 2); targetDirections[targetDirectionsIndex] |= (byte)(bestDir << (4 * (x & 1))); } } }