A Pathfinding Optimization Using Flood Fill Source Code
The overview of this code is found here.
int gCellWidth = 20; int gNumRows = 10; int gNumColumns = 30; int[][] gCells; int gCurrentColorIndex = 0; // This number of colors we generate can certainly be reduced if we need to save space int gNumColors = gNumRows * gNumColumns; color[] gColors; // The number of cells referencing each color int[] gColorRefCount; // A percentage [0.0, 1.0] indicating how many of the cells are walls float gWallPercentage = 0.4; // We don't want to redraw the map every frame, only when the user clicks a cell bool bMapNeedsRedraw = true; // Returns: true if the specified row falls within legal bounds, otherwise false. bool validRow(int row) { if(row >= 0 && row < gNumRows) { return true; } return false; } // Returns: true if the specified column falls within legal bounds, otherwise false. bool validColumn(int column) { if(column >= 0 && column < gNumColumns) { return true; } return false; } // Returns: the next available color index int getNextColorIndex() { // TODO: this can certainly be optimized if need be for(int i = 0; i < gNumColors; i++) { if( gColorRefCount[i] == 0 ) { return i; } } // TODO: handle the case where we run out of free colors return -1; } // If the value of the specified cell matches the initialValue, changes the value of the cell // to the desiredValue. Then calls the same operation on all of the cells neighbors. // Note: This is a recursive implementation for simplicity. It might cause a stack overflow // when performed on larger maps. In that case we will need to switch to an iterative implementation. void floodFill(int row, int column, int initialValue, int desiredValue) { if(validRow(row) && validColumn(column) && initialValue != -1 && gCells[row][column] == initialValue && initialValue != desiredValue) { gCells[row][column] = desiredValue; gColorRefCount[initialValue]--; gColorRefCount[desiredValue]++; floodFill(row-1, column, initialValue, desiredValue); floodFill(row+1, column, initialValue, desiredValue); floodFill(row, column-1, initialValue, desiredValue); floodFill(row, column+1, initialValue, desiredValue); } } // Effects: Loops over cells in the map and performs // floodfill (with a unique color) on each walkable, uncolored cell. void colorInitialMap() { for(int row = 0; row < gNumRows; row++) { for(int column = 0; column < gNumColumns; column++) { if(gCells[row][column] == 0) { floodFill(row, column, 0, getNextColorIndex()); } } } } // This is the first function that gets called. It is the c++ equivalent of main() void setup() { // Pad by 2 pixels because we want lines drawn at the canvas edges to look the same as all other lines size(gCellWidth * gNumColumns + 2, gCellWidth * gNumRows + 2); smooth(); // anti-alias strokeWeight(1); // Draw lines one pixel thick stroke(0); // Draw shapes in black fill(255); // Fill shapes with white gColors = new color[gNumColors]; gColorRefCount = new int[gNumColors]; // Just create random colors for now. Constrain them so they aren't too dark. // Might want to make sure colors are distinct enough in the future, // but that will be purely cosmetic. for(int i = 0; i < gNumColors; i++) { gColors[i] = color(random(100, 255), random(100, 255), random(100, 255)); } gCells = new int[gNumRows][gNumColumns]; // Randomly initialize cells to indicate they are unwalkable (-1) or walkable (0) for(int x = 0; x < gNumRows; x += 1) { for(int y = 0; y < gNumColumns; y += 1) { float f = random(0.0, 1.0); if(f < gWallPercentage) { gCells[x][y] = -1; } else { gCells[x][y] = 0; gColorRefCount[0]++; } } } // The important part of this demo. Scan the map for walkable cells // and use floodfill make sure they are all colored. colorInitialMap(); } // Effects: Sets the color used to fill shapes based on the index. // Unwalkable cells are black and walkable cells are non-black. void setColorFromIndex(int index) { if( index == -1 ) { fill(0); } else { fill(gColors[index]); } } // This is the "game loop" that gets called continuously void draw() { if( bMapNeedsRedraw ) { bMapNeedsRedraw = false; for(int column = 0; column < gNumColumns; column += 1) { for(int row = 0; row < gNumRows; row += 1) { setColorFromIndex( gCells[row][column] ); rect(1 + column * gCellWidth, 1 + row * gCellWidth, gCellWidth, gCellWidth); } } } } // Returns: the max color index of the specified cell's neighbors int getMaxColorIndexOfNeighbors(int row, int column) { int result = -1; if( validRow(row) && validColumn(column) ) { if( validRow(row-1) ) { result = max(result, gCells[row-1][column]); } if( validRow(row+1) ) { result = max(result, gCells[row+1][column]); } if( validColumn(column-1) ) { result = max(result, gCells[row][column-1]); } if( validColumn(column+1) ) { result = max(result, gCells[row][column+1]); } } return result; } void mousePressed() { int row = int(float(mouseY) / gCellWidth); int column = int(float(mouseX) / gCellWidth); bMapNeedsRedraw = true; // Change cell from walkable to unwalkable if( gCells[row][column] >= 0 ) { int oldColor = gCells[row][column]; gCells[row][column] = -1; gColorRefCount[oldColor]--; // We don't know if (or how) the 4 neighbors are connected, // so take the easy way out and flood fill each with a new color if( validRow(row-1) ) { floodFill(row-1, column, oldColor, getNextColorIndex()); } if( validRow(row+1) ) { floodFill(row+1, column, oldColor, getNextColorIndex()); } if( validColumn(column-1) ) { floodFill(row, column-1, oldColor, getNextColorIndex()); } if( validColumn(column+1) ) { floodFill(row, column+1, oldColor, getNextColorIndex()); } } // Change cell from unwalkable to walkable else { // There are a handful of cases for how neighbors may be colored. // They may be walls and some, all, or none may be the same color. int newColor = getMaxColorIndexOfNeighbors(row, column); gCells[row][column] = newColor; gColorRefCount[newColor]++; //If all the neighbors aren't walkable, then this cell gets a new color if( newColor == -1 ) { newColor = getNextColorIndex(); gCells[row][column] = newColor; gColorRefCount[newColor]++; } else { if( validRow(row-1) ) { floodFill(row-1, column, gCells[row-1][column], newColor); } if( validRow(row+1) ) { floodFill(row+1, column, gCells[row+1][column], newColor); } if( validColumn(column-1) ) { floodFill(row, column-1, gCells[row][column-1], newColor); } if( validColumn(column+1) ) { floodFill(row, column+1, gCells[row][column+1], newColor); } } } }
[...] Source code [...]