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); }
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 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); }
protected override JobHandle OnUpdate(JobHandle lastJobHandle) { // Get component data from the GridPlane var cartesianGridCube = GetSingleton <CartesianGridOnCube>(); var rowCount = cartesianGridCube.Blob.Value.RowCount; var targetDirectionsBufferCapacity = 6 * (((rowCount + 1) / 2) * rowCount); var targetDistancesBufferCapacity = 6 * (rowCount * rowCount); var gridWalls = (byte *)cartesianGridCube.Blob.Value.Walls.GetUnsafePtr(); var faceLocalToLocal = (float4x4 *)cartesianGridCube.Blob.Value.FaceLocalToLocal.GetUnsafePtr(); Entities .WithName("InitializeTargets") .WithAll <CartesianGridTarget>() .WithAll <CartesianGridOnCubeFace>() .WithNone <CartesianGridTargetDirection>() .WithNone <CartesianGridTargetDistance>() .WithStructuralChanges() .ForEach((Entity entity) => { var directionBuffer = EntityManager.AddBuffer <CartesianGridTargetDirection>(entity); directionBuffer.ResizeUninitialized(targetDirectionsBufferCapacity); var distanceBuffer = EntityManager.AddBuffer <CartesianGridTargetDistance>(entity); distanceBuffer.ResizeUninitialized(targetDistancesBufferCapacity); }).Run(); // Rebuild all the paths to the target *only* when the target's grid position changes. Entities .WithName("UpdateTargetPaths") .WithNativeDisableUnsafePtrRestriction(faceLocalToLocal) .WithAll <CartesianGridTarget>() .ForEach((Entity entity, ref CartesianGridTargetCoordinates prevTargetPosition, in CartesianGridOnCubeFace cubeFace, in CartesianGridCoordinates targetPosition, in DynamicBuffer <CartesianGridTargetDirection> targetDirections, in DynamicBuffer <CartesianGridTargetDistance> targetDistances) => { if (prevTargetPosition.Equals(targetPosition)) { return; } if (targetPosition.OnGrid(rowCount, rowCount)) { prevTargetPosition = new CartesianGridTargetCoordinates(targetPosition); CartesianGridOnCubeShortestPath.CalculateShortestPathsToTarget(targetDirections.Reinterpret <byte>().AsNativeArray(), targetDistances.Reinterpret <int>().AsNativeArray(), rowCount, targetPosition, cubeFace, gridWalls, faceLocalToLocal); } }).Run();
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); }