Im working on a painting game but i can't get the flood fill algorithm to work on large areas. Its a recursive algorithm and i read that implementing a stack might work however i can get it to work as well. Here's the code;
private void FillCluster(int x, int y, int colorIndex, HashSet<string> traversedCells)
{
Debug.Log(colorIndex);
// Check if this cell is within the bounds of the picture and has a color number and the
if(x < 0 || x >= ActivePictureInfo.XCells ||
y < 0 || y >= ActivePictureInfo.YCells ||
ActivePictureInfo.ColorNumbers[y][x] == -1 ||
ActivePictureInfo.ColorNumbers[y][x] != colorIndex)
{
return;
}
string cellKey = string.Format("{0}_{1}", x, y);
// Check if this cell has already been traversed by FillBlob
if (traversedCells.Contains(cellKey))
{
return;
}
// Check if this cell is already colored in with the correct color
if (!ActivePictureInfo.HasProgress || ActivePictureInfo.Progress[y][x] != -1)
{
ColorCell(x, y, colorIndex);
}
// Add this cells key to the traversed hashset to indicate it has been processed
traversedCells.Add(cellKey);
// Recursively call recursively with the four cells adjacent to this cell
FillCluster(x - 1, y, colorIndex, traversedCells);
FillCluster(x + 1, y, colorIndex, traversedCells);
FillCluster(x, y - 1, colorIndex, traversedCells);
FillCluster(x, y + 1, colorIndex, traversedCells);
FillCluster(x - 1, y - 1, colorIndex, traversedCells);
FillCluster(x - 1, y + 1, colorIndex, traversedCells);
FillCluster(x + 1, y - 1, colorIndex, traversedCells);
FillCluster(x + 1, y + 1, colorIndex, traversedCells);
}
a version without having to remember all visited cells because they are already marked by the color
private void FillCluster(int x, int y, int colorIndex)
{
Debug.Log(colorIndex);
var currentSeam = new Queue<PointDirection>();
if (FillPoint(x, y, colorIndex))
{
currentSeam.Enqueue(new PointDirection(x - 1, y, Direction.Left));
currentSeam.Enqueue(new PointDirection(x + 1, y, Direction.Right));
currentSeam.Enqueue(new PointDirection(x, y - 1, Direction.Up));
currentSeam.Enqueue(new PointDirection(x, y + 1, Direction.Down));
}
while (currentSeam.Count > 0)
{
var current = currentSeam.Dequeue();
if (FillPoint(current.X, current.Y, colorIndex))
{
if (current.Direction != Direction.Right)
currentSeam.Enqueue(new PointDirection(x - 1, y, Direction.Left));
if (current.Direction != Direction.Left)
currentSeam.Enqueue(new PointDirection(x + 1, y, Direction.Right));
if (current.Direction != Direction.Down)
currentSeam.Enqueue(new PointDirection(x, y - 1, Direction.Up));
if (current.Direction != Direction.Up)
currentSeam.Enqueue(new PointDirection(x, y + 1, Direction.Down));
}
}
}
private bool FillPoint(int x, int y, int colorIndex)
{
if (x < 0 || x >= ActivePictureInfo.XCells ||
y < 0 || y >= ActivePictureInfo.YCells ||
ActivePictureInfo.ColorNumbers[y][x] == -1 ||
ActivePictureInfo.ColorNumbers[y][x] == colorIndex)
{
return false;
}
ActivePictureInfo.ColorNumbers[y][x] = colorIndex;
return true;
}
private struct PointDirection
{
public PointDirection(int x, int y, Direction direction)
{
X = x;
Y = y;
Direction = direction;
}
public int X { get; }
public int Y { get; }
public Direction Direction { get; }
}
private enum Direction : byte
{
Up,
Right,
Down,
Left
}
So the reason you are getting a stackOverflow is because the state of the previous iteration is saved on the stack unless both of these condition are met:
You are doing tail recursion (look it up, but it isn't relevant right now)
Your language optimizes tail recursion (C# doesn't)
As the stack size is limited and your algorithm very quickly reaches thousands of calls one after the other, you cannot make the recursive version work in C#, that is impossible, but it seems like you understood this already
Here is a pseudocode solution to do it without recursion. I don't know C# so the synthax is not correct, but the idea is correct, you will have to transform it into proper C#
private void FillCluster(int x, int y, int colorIndex, HashSet<string> traversedCells) {
Debug.Log(colorIndex);
// Check if this cell is within the bounds of the picture and has a color number and the
//Declare a set, add inside the current node
Set<Tuple> nodesToFill = new Set<>()
nodesToFill.add(new Tuple(x, y));
//Replace the recursion by a loop
for (Tuple tuple in nodesToFill) {
//initialize the current position
x = tuple.first
y = tuple.second
//Deal with the current node
if(FillClusterInner(x, y, colorIndex, traversedCells)) {
//Add the new nodes to the set instead of using recursion
nodesToFill.add(new Tuple(x-1, y))
nodesToFill.add(new Tuple(x + 1, y))
nodesToFill.add(new Tuple(x, y - 1))
nodesToFill.add(new Tuple(x, y + 1))
nodesToFill.add(new Tuple(x - 1, y - 1))
nodesToFill.add(new Tuple(x - 1, y + 1))
nodesToFill.add(new Tuple(x + 1, y - 1))
nodesToFill.add(new Tuple(x + 1, y + 1))
}
//Remove the current tuple from the set as you have dealt with it
nodesToFill.remove(tuple)
}
}
//This is a non-recursive method which fills a single specified node
bool FillClusterInner(int x, int y, int colorIndex, HashSet<string> traversedCells) {
if(x < 0 || x >= ActivePictureInfo.XCells ||
y < 0 || y >= ActivePictureInfo.YCells ||
ActivePictureInfo.ColorNumbers[y][x] == -1 ||
ActivePictureInfo.ColorNumbers[y][x] != colorIndex)
{
return false;
}
string cellKey = string.Format("{0}_{1}", x, y);
// Check if this cell has already been traversed by FillBlob
if (traversedCells.Contains(cellKey))
{
return false;
}
// Check if this cell is already colored in with the correct color
if (!ActivePictureInfo.HasProgress || ActivePictureInfo.Progress[y][x] != -1)
{
ColorCell(x, y, colorIndex);
}
// Add this cells key to the traversed hashset to indicate it has been processed
traversedCells.Add(cellKey);
return true;
}
You can see the idea: instead of recursion, we use a set, which contains all the indexes of the nodes we have yet to fill. You add to this set instead of calling the recursive function, and you remove from the set each position you have handled. When the set is empty you have filled every cell that had to be filled.
Related
I'm trying to calculate on a 2D array 8X8 grid all paths and have the algorithm go over all squares on the grid and configure the shortest path.
See code below:
public static int Mileage(int[,] arr, int x, int y, int miles)
{
if (x < 0 || y < 0 || x > 5 || y > 5 || arr[x, y] == 2) return 99;
if (arr[x, y] == 1) return miles;
arr[x, y] = 2;
miles++;
Console.WriteLine(miles);
int want2 = Math.Min(Mileage(arr, x - 1, y, miles), Mileage(arr, x + 1, y, miles));
int want1 = Math.Min(Mileage(arr, x, y - 1, miles), Mileage(arr, x, y + 1, miles));
return Math.Min(want1, want2);
}
I am working on an edge points extraction algorithm.
I have a List of points representing a blob (a group of connected pixels) and I want to extract edge points.
I have an example algorithm below, but I am wondering if there is a faster way.
I am using class for BlobPoint, because there is more to it than shown in the example below.
BlobPoint index value represent index value in the original image (image is 1D array of pixels, hence required width information).
This algorithm is build on idea that if a pixel on right or bottom or left or top does not exist in the list, then this point is an edge point.
The list will usually contain between 20 000 to 1 000 000 elements.
yr and yl are added for clarity.
public static List<BlobPoint> GetEdgePoints(this List<BlobPoint> points, int width)
{
int length = points.Count;
List<BlobPoint> temp = new List<BlobPoint>();
for (int i = 0; i < length; i++)
{
BlobPoint point = points[i];
int x = point.X;
int y = point.Y;
int xr = x + 1;
int yr = y;
int xb = x - width;
int yb = y - 1;
int xl = x - 1;
int yl = y;
int xt = x + width;
int yt = y + 1;
if (!points.Any(p => p.X == xb && p.Y == yb) || !points.Any(p => p.X == xl && p.Y == yl) || !points.Any(p => p.X == xr && p.Y == yr) || !points.Any(p => p.X == xt && p.Y == yt))
{
temp.Add(point);
}
}
return temp;
}
public class BlobPoint
{
public int X = 0;
public int Y = 0;
public int Index = 0;
public BlobPoint(int x, int y, int index)
{
X = x;
Y = y;
Index = index;
}
}
My solution:
Ok, I have done some testing and the method above is way too slow for my needs.
I will leave question as this might be of use to someone who looks for faster iteration method.
This is a solution I came up with, but it is still a bit slow:
public static Dictionary<int, List<BlobPoint>> GetEdgePoints(BlobPoint[] points, int[] labels, int image_width, int image_height)
{
int length = labels.Length;
if (image_height * image_width != length) throw new ArgumentException("image_width x image_height does not match labels.Length");
if (length == 0) throw new ArgumentException("label array cannot be empty!");
if (points.Length != length) throw new ArgumentException("points array length cannot be different from labels array length!");
var dict = new Dictionary<int, List<BlobPoint>>();
for (int i = 0; i < length; i++)
{
int label = labels[i];
if (label <= 0) continue;
BlobPoint point = points[i];
int x = point.X;
int y = point.Y;
int width_offset = image_width - 1;
int height_offset = image_height - 1;
if (x > 0 && x < width_offset && y > 0 && y < height_offset)
{
if (labels[i + 1] == label && labels[i - 1] == label && labels[i + image_width] == label && labels[i - image_width] == label)
{
continue;
}
}
if (dict.ContainsKey(label))
dict[label].Add(point);
else
dict.Add(label, new List<BlobPoint>() { point });
}
return dict;
}
Personally, I'd use a two-dimensional Boolean array representing the image for this, in which the indices for the points you have are set to true. In exchange for some memory, and a single loop over the points in advance, this allows lightning-fast checks of whether a point is inside the list, removing all internal iterations for lookups on the points list. There is not a single .Contains or .Any or .All performed here. Just two loops over the main list, and eight very simple checks inside the second loop.
public static List<BlobPoint> GetEdgePoints(this List<BlobPoint> points, Int32 imageWidth, Int32 imageHeight)
{
Boolean[,] pointInList = new Boolean[imageHeight, imageWidth];
foreach (BlobPoint p in points)
pointInList[p.Y, p.X] = true;
List<BlobPoint> edgePoints = new List<BlobPoint>();
Int32 lastX = imageWidth - 1;
Int32 lastY = imageHeight - 1;
foreach (BlobPoint p in points)
{
Int32 x = p.X;
Int32 y = p.Y;
// Image edge is obviously a blob edge too.
// Coordinates checks are completely safe after the edge checks.
if (x == 0 || y == 0 || x == lastX || y == lastY
|| !pointInList[y - 1, x]
|| !pointInList[y, x - 1]
|| !pointInList[y, x + 1]
|| !pointInList[y + 1, x])
edgePoints.Add(p);
}
return edgePoints;
}
This technically works without the image width and height, but then you need to do another loop in advance to get the maximum x and y present in your points, so you can make a Boolean[,] array that can contain all coordinates. If you have that image data, though, it's obviously a lot more efficient to just use it.
Not sure why you bother with the BlobPoint class over the standard Point struct, though. The index in the original array is just p.Y * stride + p.X anyway.
Developing a 2D tile-based "boardgame" I'm struggling with the restriction I have to make, when a player rolls the dice(Move 5 tiles if you land a 5 etc.)
I'm trying to use the following logic:
Start on starting point
Check the position to the sides, above and below
Check if the neighbour tiles are walkable, if they are, change them to reachable
Go to neighbour tile, repeat
I've been looking on A* and D* pathing, but it's a new subject to me and they seem more focused on getting from point A to B, not "reach" which is what I need.
How do I do this through code?
I created a 2D array from an array which was holding my tile(I need a normal array of the tilemap for another purpose):
for(int i = 0; i < 27; i++)
{
for(int j = 0; j < 33; j++)
{
tileMap[i, j] = goTile[i * 33 + j];
}
}
I now use the tileMap as my positiong factor, e.g. my players current position is tileMap[2,4].
I then tried to develop a function:
void pathFinding(Vector2 playerPosition, int diceNumber)
{
GameObject currentPos = tileMap[(int)playerPosition.x, (int)playerPosition.y];
for (int i = 0; i < diceNumber; i++) {
if (tileMap[(int)playerPosition.x + 1, (int)playerPosition.y].tag == "walkableGrid")
{
tileMap[(int)playerPosition.x + 1, (int)playerPosition.y].gameObject.tag = "reachable";
playerPosition.x++;
}
if (tileMap[(int)playerPosition.x - 1, (int)playerPosition.y].tag == "walkableGrid")
{
playerPosition.x--;
}
if (tileMap[(int)playerPosition.x, (int)playerPosition.y + 1].tag == "walkableGrid")
{
playerPosition.y++;
}
if (tileMap[(int)playerPosition.x, (int)playerPosition.y - 1].tag == "walkableGrid")
{
playerPosition.y--;
}
}
}
But as finishing this (if it even would work), would require MANY lines of code, and I believe there's a swifter method using a nested for loop maybe?
//I have now edited the code to better reflect your real data
public void ShowMoves(Vector2 playerPosition, int diceNumber, bool[] blocks)
{
int x = (int)playerPosition.x;
int y = (int)playerPosition.y;
if(tileMap.GetUpperBound(0) < x + 1)
{
if(tileMap[x + 1, y].tag == "walkableGrid" && blocks[0])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x + 1, y), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), false, y != tileMap.GetUpperBound(1), y != 0 });
}
}
if(x - 1 >= 0)
{
if(tileMap[x - 1, y].tag == "walkableGrid" && blocks[1])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x - 1, y), diceNumber - 1, new bool[] { false, x != 0, y != tileMap.GetUpperBound(1), y != 0 });
}
}
if(tileMap.GetUpperBound(1) < y + 1)
{
if(tileMap[x, y + 1].tag == "walkableGrid" && blocks[2])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x, y + 1), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), x != 0, y != tileMap.GetUpperBound(1), false });
}
}
if(y - 1 >= 0)
{
if(tileMap[x, y - 1].tag == "walkableGrid" && blocks[3])
{
/*Light up the tile*/
if(diceNumber > 0)
ShowMoves(new Vector2(x, y - 1), diceNumber - 1, new bool[] { x != tileMap.GetUpperBound(0), x != 0, false, y != 0 });
}
}
}
This code might not compile, but it's an example to help you along - it will loop until there are no available moves, and exhaust every option. It will also not go back on itself due to the blocks boolean array. The input format would be the position they're at two ints, one for x and one for y, the tiles available, the number of moves left in their roll, and the block available from the beginning (always new bool[] {true, true, true, true}
Be careful, there may be errors in my code, I wrote it in SO and have no clue how well it runs, if it runs at all or anything. Even if it does not, it should be a good starting point for you to create your logic and code it all
EDIT: Code has been changed so that it better fits how your code looks and the data types it uses
To avoid always calling the method by inputting a blocks variable of new bool[] {true, true, true, true}; you can make it an optional operator by making this the method parameters
public void ShowMoves(Vector2 playerPosition, int diceNumber, bool[] blocks = new bool[] {true, true, true, true})
I am creating a flowfield for AI units and am trying to speed up some threaded code. My test grid size is 2000 x 2000 and I currently have the generation time down to 5.5 seconds. Profiling the code pointed something out that seems strange. The method that I use for the threads reports on average 140 Inclusive Contentions. 65 of which are for this line.
var neighbours = GetNeighbors4(cell.Point);
and below is the method being called.
private IEnumerable<IntegrationCell> GetNeighbors4(Point point)
{
var sizeX = _size.X - 1;
var sizeY = _size.Y - 1;
var x = point.X;
var y = point.Y;
//corners
if (x == 0 && y == 0)
{
return new[]
{
Cells[1, 0],
Cells[0, 1]
};
}
if (x == sizeX && y == 0)
{
return new[]
{
Cells[sizeX - 1, 0],
Cells[sizeX, 1]
};
}
if (x == 0 && y == sizeY)
{
return new[]
{
Cells[0, sizeY - 1],
Cells[1, sizeY]
};
}
if (x == sizeX && y == sizeY)
{
return new[]
{
Cells[sizeX - 1, sizeY],
Cells[sizeX, sizeY - 1]
};
}
//top row
if (y == 0)
{
return new[]
{
Cells[x - 1, 0],
Cells[x + 1, 0],
Cells[x, 1]
};
}
//bottom row
if (y == sizeY)
{
return new[]
{
Cells[x - 1, y],
Cells[x + 1, y],
Cells[x, y - 1]
};
}
//left column
if (x == 0)
{
return new[]
{
Cells[0, y - 1],
Cells[0, y + 1],
Cells[1, y]
};
}
//right column
if (x == sizeX)
{
return new[]
{
Cells[x, y - 1],
Cells[x, y + 1],
Cells[x - 1, y]
};
}
//everything else
return new[]
{
Cells[x, y - 1],
Cells[x, y + 1],
Cells[x - 1, y],
Cells[x + 1, y]
};
}
Cells is just a simple 2 dimensional array to represent a grid
IntegrationCell[,] Cells;
Now the way it works is that given a target cell in the grid, I step out like a 'wave' or 'ripple' from the target. Each iteration of the wave steps out one further from the target. As I do this, each iteration has more cells as the distance from the target increases. For each cell in each iteration, I spawn a new thread that computes the cells cost and returns a list of new cells that need to be computed/recomputed. There is a lot more that happens, but that's basically it. At one point I peak at roughly 120 threads before I hit the edge of the map, and I begin to have less cells each iteration until there are none left.
This is the full method of the thread run for each cell. (I can have over 100 running at any one time)
private IEnumerable<IntegrationCell> CostStep(IntegrationCell cell)
{
var result = new List<IntegrationCell>(); \\14 contentions
var costBlock = _costfield.Cells[cell.Point.X, cell.Point.Y];
if (costBlock.Cost == 255)
return result;
var neighbours = GetNeighbors4(cell.Point); \\65 contentions
foreach (var neighbour in neighbours) \\18 contentions
{
var newCost = costBlock.Cost + neighbour.Cost;
if (cell.Cost > newCost)
cell.Cost = newCost;
var childCostBlock = _costfield.Cells[neighbour.Point.X, neighbour.Point.Y];
var newChildCost = cell.Cost + childCostBlock.Cost;
if (childCostBlock.Cost == 255)
neighbour.Cost = 255;
else if (neighbour.Cost > newChildCost)
{
neighbour.Cost = newChildCost;
result.Add(neighbour); \\39 contentions
}
}
return result;
}
I have placed comments of the contentions reported against each line. The contentions vary with each run, but what I cant understand is why I would have contentions reading from an array? Yes I'm updating the the array/cell and its neighbors if needed and each cell may be calculated more than once.
For each cell in each iteration, I spawn a new thread that ...
Probably that is the problem. Since you are doing CPU bound calculations, use only as many threads as CPU has your computer, indicated by the static property:
System.Environment.ProcessorCount
Otherwise, you are trying to schedule too much, causing a lot of contention and context switches.
I mean, more threads does not mean faster, actually, it may do the application slower because the thread management overhead. You use more threads when you have I/O bound operations, and therefore many threads are idle waiting for things to happen somewhere else (e.g.: a web service call, a database operation, an incoming request....)
Take a look at this answer: https://stackoverflow.com/a/12021285/307976, and also about how to limit the parallelism with PLINQ.
I am currently creating a little paint program for exercise. Right now i'm trying to do the paint bucket tool, or in other words a flood fill. The funny thing is: if the number of pixels which have to be filled is small, everything works fine. If hate number of to filling pixels is higher, it gives me a SO-Exception. Here is my code:
private void FloodFill(Bitmap picture, int x, int y)
{
if (x <= 0 || y <= 0 || x >= DrawingPanel.Width || y >= DrawingPanel.Height)
{
return;
}
if (picture.GetPixel(x, y) != löschFarbe)
{
return;
}
if (picture.GetPixel(x, y) == löschFarbe)
{
picture.SetPixel(x, y, ColorButton.BackColor);
}
FloodFill(picture, x + 1, y);
FloodFill(picture, x, y + 1);
FloodFill(picture, x - 1, y);
FloodFill(picture, x, y - 1);
FloodFill(picture, x + 1, y + 1);
FloodFill(picture, x - 1, y + 1);
FloodFill(picture, x + 1, y - 1);
FloodFill(picture, x - 1, y - 1);
}
"löschFarbe" is the color which is clicked (which will be erased/overwritten with another color)
Error occurring: If i want to fill the complete picture or a big space, I get an error here:
if (picture.GetPixel(x, y) != löschFarbe)
{
return;
}
Anyone knows how I can solve this?
BTW this is a picture of my program:
Even a moderate sized floodfill will blow your call stack.
Try converting this recursion based method to a stack based method as in this question.