static Entity FindTargetShortestPathLength(CartesianGridCoordinates gridCoordinates, CartesianGridOnCubeFace cubeFace, int rowCount, NativeArray <CartesianGridCoordinates> targetCoordinates, NativeArray <Entity> targetEntities, BufferFromEntity <CartesianGridTargetDistance> getCartesianGridTargetDistanceFromEntity) { var targetEntity = Entity.Null; if (!gridCoordinates.OnGrid(rowCount, rowCount)) { return(targetEntity); } var targetBestDistance = 6 * ((rowCount * rowCount) + 1); for (int i = 0; i < targetCoordinates.Length; i++) { // Note targets will be invisible (off grid) when transitioning between cube faces if (!targetCoordinates[i].OnGrid(rowCount, rowCount)) { continue; } var targetDistances = getCartesianGridTargetDistanceFromEntity[targetEntities[i]].Reinterpret <int>().AsNativeArray(); var targetDistance = CartesianGridOnCubeShortestPath.LookupDistanceToTarget(gridCoordinates, cubeFace, rowCount, targetDistances); if (targetDistance < targetBestDistance) { targetEntity = targetEntities[i]; targetBestDistance = targetDistance; } } return(targetEntity); }
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 static int CellIndex(CartesianGridCoordinates cellPosition, CartesianGridOnCubeFace cubeFace, int rowCount) { var rowStride = rowCount; var faceStride = rowCount * rowStride; var cellIndex = (cubeFace.Value * faceStride) + (cellPosition.y * rowStride) + cellPosition.x; return(cellIndex); }
/// <summary> /// Pick valid direction to "bounce" off (move along) walls. /// </summary> /// <param name="cellPosition">Position to test (must be valid on grid)</param> /// <param name="dir">Current movement direction</param> /// <param name="rowCount">Height of grid</param> /// <param name="colCount">Width of grid</param> /// <param name="gridWalls">Table representing walls of grid.</param> /// <param name="pathIndex">Index to select from multiple valid options. See: m_NextDirection</param> /// <returns></returns> // gridPosition needs to be on-grid (positive and < [colCount, rowCount]) when looking up next direction. public static byte BounceDirectionOffWalls(CartesianGridCoordinates cellPosition, byte dir, int rowCount, int colCount, byte *gridWalls, int pathIndex) { var pathOffset = pathIndex * 16; var walls = LookupWalls(cellPosition, rowCount, colCount, gridWalls); // New direction = f( grid index, movement direction ) return((byte)((m_NextDirection[pathOffset + walls] >> (dir * 2)) & 0x03)); }
static byte LookupWalls(CartesianGridCoordinates cartesianGridCoordinates, int rowCount, int colCount, byte *gridWalls) { var rowStride = ((colCount + 1) / 2); var gridWallsIndex = (cartesianGridCoordinates.y * rowStride) + (cartesianGridCoordinates.x / 2); // Walls in current grid element (odd columns in upper 4 bits of byte) var walls = (gridWalls[gridWallsIndex] >> ((cartesianGridCoordinates.x & 1) * 4)) & 0x0f; return((byte)walls); }
/// <summary> /// Find directions along shortest path(s) to target. /// </summary> /// <param name="cellPosition">Current position</param> /// <param name="rowCount">Height of grid</param> /// <param name="colCount">Width of grid</param> /// <param name="targetDirections">Lookup table generated by CalculatePathsToTarget</param> /// <returns>Any (or none) best directions along any shortest path in form of CartesianGridDirectionBit</returns> public static byte LookupDirectionToTarget(CartesianGridCoordinates cellPosition, int rowCount, int colCount, NativeArray <byte> targetDirections) { var rowStride = ((colCount + 1) / 2); var gridWallsIndex = (cellPosition.y * rowStride) + (cellPosition.x / 2); // Walls in current grid element (odd columns in upper 4 bits of byte) var directions = (targetDirections[gridWallsIndex] >> ((cellPosition.x & 1) * 4)) & 0x0f; return((byte)directions); }
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); } } }
// gridPosition needs to be on-grid (positive and < [colCount, rowCount]) when looking up next direction. public static byte LookupGridDirectionFromWalls(CartesianGridCoordinates cartesianGridCoordinates, byte dir, int rowStride, byte *gridWalls, int pathIndex) { var pathOffset = pathIndex * 16; // Index into grid array var gridWallsIndex = (cartesianGridCoordinates.y * rowStride) + (cartesianGridCoordinates.x / 2); // Walls in current grid element (odd columns in upper 4 bits of byte) var walls = (gridWalls[gridWallsIndex] >> ((cartesianGridCoordinates.x & 1) * 4)) & 0x0f; // New direction = f( grid index, movement direction ) return((byte)((NextDirection[pathOffset + walls] >> (dir * 2)) & 0x03)); }
public void PathOnEmptyGrid() { using (var grid = new TestGrid(16)) { var targetPosition = new CartesianGridCoordinates { x = 15, y = 15 }; var targetCubeFace = new CartesianGridOnCubeFace { Value = 0 }; var sourcePosition = new CartesianGridCoordinates { x = 8, y = 8 }; var sourceCubeFace0 = new CartesianGridOnCubeFace { Value = 0 }; var sourceCubeFace1 = new CartesianGridOnCubeFace { Value = 1 }; var sourceCubeFace2 = new CartesianGridOnCubeFace { Value = 2 }; var sourceCubeFace3 = new CartesianGridOnCubeFace { Value = 3 }; var sourceCubeFace4 = new CartesianGridOnCubeFace { Value = 4 }; var sourceCubeFace5 = new CartesianGridOnCubeFace { Value = 5 }; var dist0 = grid.WalkPathDistance(sourcePosition, sourceCubeFace0, targetPosition, targetCubeFace); Assert.AreEqual(dist0, 14); var dist1 = grid.WalkPathDistance(sourcePosition, sourceCubeFace1, targetPosition, targetCubeFace); Assert.AreEqual(dist1, 32); var dist2 = grid.WalkPathDistance(sourcePosition, sourceCubeFace2, targetPosition, targetCubeFace); Assert.AreEqual(dist2, 30); var dist3 = grid.WalkPathDistance(sourcePosition, sourceCubeFace3, targetPosition, targetCubeFace); Assert.AreEqual(dist3, 16); var dist4 = grid.WalkPathDistance(sourcePosition, sourceCubeFace4, targetPosition, targetCubeFace); Assert.AreEqual(dist4, 17); var dist5 = grid.WalkPathDistance(sourcePosition, sourceCubeFace5, targetPosition, targetCubeFace); Assert.AreEqual(dist5, 31); } }
/// <summary> /// Which edge of GridCube face is being exited (if any) /// </summary> /// <param name="cellPosition">Position to test</param> /// <param name="rowCount">Height/Width of cube face</param> /// <returns></returns> public static int CubeExitEdge(CartesianGridCoordinates cellPosition, int rowCount) { // Which edge of GridCube face is being exited (if any) var edge = -1; // Edge is in order specified in m_NextFaceIndex and m_NextFaceDirection // - Matches GridDirection values. edge = math.select(edge, 0, cellPosition.y >= rowCount); edge = math.select(edge, 1, cellPosition.y < 0); edge = math.select(edge, 2, cellPosition.x < 0); edge = math.select(edge, 3, cellPosition.x >= rowCount); return(edge); }
public static float3 ClampToGrid(float3 v, byte dir, CartesianGridCoordinates cartesianGridCoordinates, float2 cellCenterOffset) { // When (dir == N,S) clamp to grid cell center x // When (dir == W,E) clamp to grid cell center y var mx = (dir >> 1) * 1.0f; var my = ((dir >> 1) ^ 1) * 1.0f; return(new float3 { x = (mx * v.x) + (my * (cartesianGridCoordinates.x - cellCenterOffset.x)), z = (my * v.z) + (mx * (cartesianGridCoordinates.y - cellCenterOffset.y)), y = v.y }); }
/// <summary> /// Snap translation to center of grid cell along the direction movement. /// This ensures translation does not drift substantially over time with accumulated errors. /// </summary> /// <param name="translation">Translation (in grid space) to snap</param> /// <param name="dir">Direction of movement. See: CartesianGridDirection</param> /// <param name="cellPosition">Current cell to snap to</param> /// <param name="cellCenterOlffset">Offset relative to center of grid.</param> /// <returns></returns> public static float3 SnapToGridAlongDirection(float3 translation, byte dir, CartesianGridCoordinates cellPosition, float2 cellCenterOffset) { // When (dir == N,S) clamp to grid cell center x // When (dir == W,E) clamp to grid cell center y var mx = (dir >> 1) * 1.0f; var my = ((dir >> 1) ^ 1) * 1.0f; return(new float3 { x = (mx * translation.x) + (my * (cellPosition.x - cellCenterOffset.x)), z = (my * translation.z) + (mx * (cellPosition.y - cellCenterOffset.y)), y = translation.y }); }
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); }
static Entity FindTargetShortestManhattanDistance(CartesianGridCoordinates gridCoordinates, int rowCount, int colCount, NativeArray <CartesianGridCoordinates> targetCoordinates, NativeArray <Entity> targetEntities) { var targetEntity = Entity.Null; var targetBestDistance = (rowCount * colCount) + 1; for (int i = 0; i < targetCoordinates.Length; i++) { var targetDistance = math.abs(targetCoordinates[i].x - gridCoordinates.x) + math.abs(targetCoordinates[i].y - gridCoordinates.y); if (targetDistance < targetBestDistance) { targetEntity = targetEntities[i]; targetBestDistance = targetDistance; } } return(targetEntity); }
public void PathOnEmptyGrid() { using (var grid = new TestGrid(16, 16)) { var targetPosition = new CartesianGridCoordinates { x = 15, y = 15 }; var dist = 0; dist = grid.WalkPathDistance(new CartesianGridCoordinates { x = 0, y = 0 }, targetPosition); Assert.AreEqual(dist, 30); dist = grid.WalkPathDistance(new CartesianGridCoordinates { x = 8, y = 7 }, targetPosition); Assert.AreEqual(dist, 15); } }
public static int CellIndexFromExitEdge(int edge, int faceIndex, CartesianGridCoordinates facePosition, int rowCount) { var cellCount = rowCount * rowCount; var nextFaceIndex = NextFaceIndex[(edge * 6) + faceIndex]; var cx = (rowCount - 1) * 0.5f; var px = math.clamp(facePosition.x, 0, rowCount - 1); var py = math.clamp(facePosition.y, 0, rowCount - 1); var x0 = px - cx; var y0 = py - cx; var ax = m_GridToGrid[(edge * 6 * 2) + (faceIndex * 2) + 0]; var ay = m_GridToGrid[(edge * 6 * 2) + (faceIndex * 2) + 1]; var x1 = (ax.x * x0) + (ax.y * y0); var y1 = (ay.x * x0) + (ay.y * y0); var nx = (short)(x1 + cx + 0.5f); var ny = (short)(y1 + cx + 0.5f); var nextFaceCellIndex = (ny * rowCount) + nx; return((nextFaceIndex * cellCount) + nextFaceCellIndex); }
public void PathOnIsland() { using (var grid = new TestGrid(16, 16)) { // create island left/right side for (int y = 0; y < grid.RowCount; y++) { grid.AddWallWest(new CartesianGridCoordinates { x = 8, y = (short)y }); } var targetPosition = new CartesianGridCoordinates { x = 15, y = 15 }; var dist = 0; // right side has open path dist = grid.WalkPathDistance(new CartesianGridCoordinates { x = 8, y = 8 }, targetPosition); Assert.AreEqual(dist, 14); dist = grid.WalkPathDistance(new CartesianGridCoordinates { x = 8, y = 0 }, targetPosition); Assert.AreEqual(dist, 22); // left side blocked dist = grid.WalkPathDistance(new CartesianGridCoordinates { x = 0, y = 8 }, targetPosition); Assert.AreEqual(dist, 0); dist = grid.WalkPathDistance(new CartesianGridCoordinates { x = 0, y = 0 }, targetPosition); Assert.AreEqual(dist, 0); } }
public void PathRandomObstacles() { using (var grid = new TestGrid(32, 15)) { int obstacleCount = 32; var rand = new Unity.Mathematics.Random(0xF545AA3F); for (int i = 0; i < obstacleCount; i++) { var xy = rand.NextInt2(new int2(grid.ColCount, grid.RowCount)); if (rand.NextBool()) { grid.AddWallWest(new CartesianGridCoordinates { x = (short)xy.x, y = (short)xy.y }); } else { grid.AddWallSouth(new CartesianGridCoordinates { x = (short)xy.x, y = (short)xy.y }); } } int testCount = 64; for (int i = 0; i < testCount; i++) { var sourceXY = rand.NextInt2(new int2(grid.ColCount, grid.RowCount)); var targetXY = rand.NextInt2(new int2(grid.ColCount, grid.RowCount)); var sourcePosition = new CartesianGridCoordinates { x = (short)sourceXY.x, y = (short)sourceXY.y }; var targetPosition = new CartesianGridCoordinates { x = (short)targetXY.x, y = (short)targetXY.y }; grid.WalkPathDistance(sourcePosition, targetPosition); } } }
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); }
public int WalkPathDistance(CartesianGridCoordinates sourcePosition, CartesianGridCoordinates targetPosition) { var directionsRowStride = (ColCount + 1) / 2; var directionsSize = RowCount * directionsRowStride; var targetDirections = new NativeArray <byte>(directionsSize, Allocator.Temp); var sourceDirections = new NativeArray <byte>(directionsSize, Allocator.Temp); // For testing purposes, recalculate paths every time. CartesianGridOnPlaneShortestPath.CalculateShortestPathsToTarget(targetDirections, RowCount, ColCount, targetPosition, Walls); CartesianGridOnPlaneShortestPath.CalculateShortestPathsToTarget(sourceDirections, RowCount, ColCount, sourcePosition, Walls); // Test distance form source->target is same as target->source var sourceToTargetDistance = WalkPath(sourcePosition, targetDirections, 0); var targetToSourceDistance = WalkPath(targetPosition, sourceDirections, 0); Assert.AreEqual(sourceToTargetDistance, targetToSourceDistance); var expectedDistance = sourceToTargetDistance; // 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(sourcePosition, targetDirections, i); Assert.AreEqual(expectedDistance, sourceToTargetDistance); targetToSourceDistance = WalkPath(targetPosition, sourceDirections, i); Assert.AreEqual(expectedDistance, targetToSourceDistance); } targetDirections.Dispose(); sourceDirections.Dispose(); return(expectedDistance); }
/// <summary> /// Return directions from cellPosition which are not blocked by walls. /// </summary> /// <param name="cellPosition">Position to test</param> /// <param name="rowCount">Height of grid</param> /// <param name="colCount">Width of grid</param> /// <param name="gridWalls">Table representing walls of grid.</param> /// <returns></returns> public static byte ValidDirections(CartesianGridCoordinates cellPosition, int rowCount, int colCount, byte *gridWalls) { return((byte)(0x0f & ~LookupWalls(cellPosition, rowCount, colCount, gridWalls))); }
/// <summary> /// Find shortest path for every position on grid to target position /// </summary> /// <param name="targetDirections">Result table of all shortest paths for every position on grid to targetPosition</param> /// <param name="rowCount">Height of grid</param> /// <param name="targetPosition">Generate all shortest paths to this position.</param> /// <param name="gridWalls">Table representing walls/obstacles in grid. See: CartesianGridMovement</param> public static void CalculateShortestPathsToTarget(NativeArray <byte> targetDirections, NativeArray <int> targetDistances, int rowCount, CartesianGridCoordinates targetPosition, CartesianGridOnCubeFace cubeFace, byte *gridWalls, float4x4 *faceLocalToLocal) { CalculateShortestWalkableDistancesToTarget(targetDistances, rowCount, gridWalls, targetPosition, cubeFace, faceLocalToLocal); CalculateShortestPathGivenDistancesToTarget(targetDirections, rowCount, targetDistances, gridWalls, faceLocalToLocal); }
/// <summary> /// Find directions along shortest path(s) to target. /// </summary> /// <param name="cellPosition">Current position</param> /// <param name="rowCount">Height of grid</param> /// <param name="targetDirections">Lookup table generated by CalculatePathsToTarget</param> /// <returns>Any (or none) best directions along any shortest path in form of CartesianGridDirectionBit</returns> public static byte LookupDirectionToTarget(CartesianGridCoordinates cellPosition, CartesianGridOnCubeFace cubeFace, int rowCount, NativeArray <byte> targetDirections) { return(LookupDirectionToTarget(cellPosition.x, cellPosition.y, cubeFace.Value, rowCount, targetDirections)); }
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); }
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); }
public void AddWallWest(CartesianGridCoordinates cellPosition) { SetWallBit(cellPosition.x, cellPosition.y, CartesianGridDirectionBit.West); SetWallBit(cellPosition.x - 1, cellPosition.y, CartesianGridDirectionBit.East); }
public void AddWallSouth(CartesianGridCoordinates cellPosition) { SetWallBit(cellPosition.x, cellPosition.y, CartesianGridDirectionBit.South); SetWallBit(cellPosition.x, cellPosition.y - 1, CartesianGridDirectionBit.North); }
// 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(); }
/// <summary> /// Find directions along shortest path(s) to target. /// </summary> /// <param name="cellPosition">Current position</param> /// <param name="rowCount">Height of grid</param> /// <param name="targetDistances">Lookup table generated by CalculatePathsToTarget</param> /// <returns>Any (or none) best directions along any shortest path in form of CartesianGridDirectionBit</returns> public static int LookupDistanceToTarget(CartesianGridCoordinates cellPosition, CartesianGridOnCubeFace cubeFace, int rowCount, NativeArray <int> targetDistances) { return(LookupDistanceToTarget(cellPosition.x, cellPosition.y, cubeFace.Value, rowCount, targetDistances)); }
int WalkPath(CartesianGridCoordinates startPosition, NativeArray <byte> targetDirections, int pathOffset) { var validDirections = CartesianGridOnPlaneShortestPath.LookupDirectionToTarget(startPosition, RowCount, ColCount, targetDirections); var direction = CartesianGridMovement.PathVariation[((pathOffset & 3) * 16) + validDirections]; if (direction == 0xff) // No path { return(0); } var nextPosition = new CartesianGridCoordinates(); if (direction == 0) { nextPosition = new CartesianGridCoordinates { x = startPosition.x, y = (short)(startPosition.y + 1) } } ; else if (direction == 1) { nextPosition = new CartesianGridCoordinates { x = startPosition.x, y = (short)(startPosition.y - 1) } } ; else if (direction == 2) { nextPosition = new CartesianGridCoordinates { x = (short)(startPosition.x - 1), y = (short)startPosition.y } } ; else if (direction == 3) { nextPosition = new CartesianGridCoordinates { x = (short)(startPosition.x + 1), y = (short)startPosition.y } } ; else { Assert.Fail(); } // Test no wall in the direction given if (direction == 0) { Assert.IsFalse(TestWallBit(startPosition.x, startPosition.y, CartesianGridDirectionBit.North)); } else if (direction == 1) { Assert.IsFalse(TestWallBit(startPosition.x, startPosition.y, CartesianGridDirectionBit.South)); } else if (direction == 2) { Assert.IsFalse(TestWallBit(startPosition.x, startPosition.y, CartesianGridDirectionBit.West)); } else if (direction == 3) { Assert.IsFalse(TestWallBit(startPosition.x, startPosition.y, CartesianGridDirectionBit.East)); } return(1 + WalkPath(nextPosition, targetDirections, pathOffset)); }