All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pepsoft.worldpainter.QueueLinearFloodFiller Maven / Gradle / Ivy

There is a newer version: 2.23.2
Show newest version
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