
org.pepsoft.worldpainter.QueueLinearFloodFiller Maven / Gradle / Ivy
package org.pepsoft.worldpainter;
//Original algorithm by J. Dunlap http://www.codeproject.com/KB/GDI-plus/queuelinearfloodfill.aspx
//Java port by Owen Kaluza
// Adapted for WorldPainter by Pepijn Schmitz on 28-3-2011
import java.awt.Window;
import java.util.BitSet;
import java.util.Queue;
import java.util.LinkedList;
import org.pepsoft.util.ProgressReceiver;
import org.pepsoft.util.ProgressReceiver.OperationCancelled;
import org.pepsoft.util.swing.ProgressDialog;
import org.pepsoft.util.swing.ProgressTask;
import org.pepsoft.worldpainter.layers.FloodWithLava;
import static org.pepsoft.worldpainter.Constants.*;
public class QueueLinearFloodFiller {
// Dimension to flood
private final Dimension dimension;
// Level to flood to
private final int waterLevel;
// Whether to flood with lava instead of water
private final boolean floodWithLava;
// Whether to remove a layer of material instead of adding it
private final boolean undo;
//cached image properties
private final int width, height, offsetX, offsetY;
//internal, initialized per fill
protected BitSet blocksChecked;
//Queue of floodfill ranges
protected Queue ranges;
public QueueLinearFloodFiller(Dimension dimension, int waterLevel, boolean floodWithLava, boolean undo) {
this.dimension = dimension;
this.waterLevel = waterLevel;
this.floodWithLava = floodWithLava;
this.undo = undo;
width = dimension.getWidth() * TILE_SIZE;
height = dimension.getHeight() * TILE_SIZE;
offsetX = dimension.getLowestX() * TILE_SIZE;
offsetY = dimension.getLowestY() * TILE_SIZE;
}
public Dimension getDimension() {
return dimension;
}
protected void prepare() {
//Called before starting flood-fill
blocksChecked = new BitSet(width * height);
ranges = new LinkedList<>();
}
// Fills the specified point on the bitmap with the currently selected fill color.
// int x, int y: The starting coords for the fill
public boolean floodFill(int x, int y, Window parent) {
// Normalise coordinates (the algorithm needs them to always be
// positive)
x -= offsetX;
y -= offsetY;
//Setup
prepare();
long start = System.currentTimeMillis();
//***Do first call to floodfill.
linearFill(x, y);
//***Call floodfill routine while floodfill ranges still exist on the queue
FloodFillRange range;
while (ranges.size() > 0) {
//**Get Next Range Off the Queue
range = ranges.remove();
processRange(range);
long lap = System.currentTimeMillis();
if ((lap - start) > 2000) {
// We're taking more than two seconds. Do the rest in the
// background and show a progress dialog so the user can cancel
// the operation
if (ProgressDialog.executeTask(parent, new ProgressTask() {
@Override
public String getName() {
return undo ? "Draining" : "Flooding";
}
@Override
public Dimension execute(ProgressReceiver progressReceiver) throws OperationCancelled {
synchronized (dimension) {
//***Call floodfill routine while floodfill ranges still exist on the queue
FloodFillRange range;
while (ranges.size() > 0) {
//**Get Next Range Off the Queue
range = ranges.remove();
processRange(range);
progressReceiver.checkForCancellation();
}
return dimension;
}
}
}) == null) {
// Operation cancelled
return false;
}
return true;
}
}
return true;
}
private void processRange(FloodFillRange range) {
//**Check Above and Below Each block in the Floodfill Range
int downPxIdx = (width * (range.Y + 1)) + range.startX;
int upPxIdx = (width * (range.Y - 1)) + range.startX;
int upY = range.Y - 1;//so we can pass the y coord by ref
int downY = range.Y + 1;
for (int i = range.startX; i <= range.endX; i++) {
//*Start Fill Upwards
//if we're not above the top of the bitmap and the block above this one is within the color tolerance
if (range.Y > 0 && (!blocksChecked.get(upPxIdx)) && checkBlock(upPxIdx)) {
linearFill(i, upY);
}
//*Start Fill Downwards
//if we're not below the bottom of the bitmap and the block below this one is within the color tolerance
if (range.Y < (height - 1) && (!blocksChecked.get(downPxIdx)) && checkBlock(downPxIdx)) {
linearFill(i, downY);
}
downPxIdx++;
upPxIdx++;
}
}
// Finds the furthermost left and right boundaries of the fill area
// on a given y coordinate, starting from a given x coordinate, filling as it goes.
// Adds the resulting horizontal range to the queue of floodfill ranges,
// to be processed in the main loop.
//
// int x, int y: The starting coords
protected void linearFill(int x, int y) {
//***Find Left Edge of Color Area
int lFillLoc = x; //the location to check/fill on the left
int pxIdx = (width * y) + x;
int origPxIdx = pxIdx;
while (true) {
if (undo) {
//**remove a layer of material
dimension.setWaterLevelAt(offsetX + x + pxIdx - origPxIdx, offsetY + y, waterLevel - 1);
} else {
//**flood
dimension.setWaterLevelAt(offsetX + x + pxIdx - origPxIdx, offsetY + y, waterLevel);
dimension.setBitLayerValueAt(FloodWithLava.INSTANCE, offsetX + x + pxIdx - origPxIdx, offsetY + y, floodWithLava);
}
//**indicate that this block has already been checked and filled
blocksChecked.set(pxIdx);
//**de-increment
lFillLoc--; //de-increment counter
pxIdx--; //de-increment block index
//**exit loop if we're at edge of bitmap or color area
if (lFillLoc < 0 || blocksChecked.get(pxIdx) || !checkBlock(pxIdx)) {
break;
}
}
lFillLoc++;
//***Find Right Edge of Color Area
int rFillLoc = x; //the location to check/fill on the left
pxIdx = (width * y) + x;
origPxIdx = pxIdx;
while (true) {
if (undo) {
//**remove a layer of material
dimension.setWaterLevelAt(offsetX + x + pxIdx - origPxIdx, offsetY + y, waterLevel - 1);
} else {
//**flood
dimension.setWaterLevelAt(offsetX + x + pxIdx - origPxIdx, offsetY + y, waterLevel);
dimension.setBitLayerValueAt(FloodWithLava.INSTANCE, offsetX + x + pxIdx - origPxIdx, offsetY + y, floodWithLava);
}
//**indicate that this block has already been checked and filled
blocksChecked.set(pxIdx);
//**increment
rFillLoc++; //increment counter
pxIdx++; //increment block index
//**exit loop if we're at edge of bitmap or color area
if (rFillLoc >= width || blocksChecked.get(pxIdx) || !checkBlock(pxIdx)) {
break;
}
}
rFillLoc--;
//add range to queue
FloodFillRange r = new FloodFillRange(lFillLoc, rFillLoc, y);
ranges.offer(r);
}
//Sees if a block should be flooded (or unflooded)
protected boolean checkBlock(int px) {
int y = px / width;
int x = px % width;
if (dimension.getBitLayerValueAt(org.pepsoft.worldpainter.layers.Void.INSTANCE, offsetX + x, offsetY + y)) {
return false;
} else {
int height = dimension.getIntHeightAt(offsetX + x, offsetY + y);
if (undo) {
return (height != -1)
&& (dimension.getWaterLevelAt(offsetX + x, offsetY + y) >= waterLevel)
&& (height < waterLevel);
} else {
return (height != -1)
&& (waterLevel > height)
&& ((waterLevel > dimension.getWaterLevelAt(offsetX + x, offsetY + y))
|| (floodWithLava != dimension.getBitLayerValueAt(FloodWithLava.INSTANCE, offsetX + x, offsetY + y)));
}
}
}
// Represents a linear range to be filled and branched from.
protected class FloodFillRange {
public int startX;
public int endX;
public int Y;
public FloodFillRange(int startX, int endX, int y) {
this.startX = startX;
this.endX = endX;
this.Y = y;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy