package speedytools.common.selections;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import speedytools.common.utilities.ErrorLog;
import java.util.*;
/**
* User: The Grey Ghost
* Date: 23/09/2014
* Used to contour floodfill through a Voxel region in a chunkwise fashion:
* Will fill as far as possible within a chunk before starting to search in the next one, and will prefer to search chunks it
* has already visited before
* Usage:
* 1) Create the iterator with the boundaries of the region that limit the fill
* 2) setStartPositionAndPlane() to set the fill start point and the searching plane (eg XY, XZ, or YZ)
* 3) Repeat until iterator.isAtEnd:
* a) Check the block at .getWX(), .getWY(), .getWZ().
* b) if it belongs to the fill, call .next(true), otherwise .next(false)
* c) use hasEnteredNewChunk() to determine when to load a new chunk. getChunkX() and getChunkZ() give the coordinates
* 4) estimatedFractionComplete() returns a number that indicates an estimate of how complete the fill process is
*/
public class VoxelChunkwiseContourIterator implements IVoxelIterator
{
public VoxelChunkwiseContourIterator(int i_wxOrigin, int i_wyOrigin, int i_wzOrigin, int i_xSize, int i_ySize, int i_zSize) {
if (i_xSize < 0) throw new IllegalArgumentException("xSize < 0: " + i_xSize);
if (i_ySize < 0) throw new IllegalArgumentException("ySize < 0: " + i_ySize);
if (i_zSize < 0) throw new IllegalArgumentException("zSize < 0: " + i_zSize);
wxOrigin = i_wxOrigin;
wyOrigin = i_wyOrigin;
wzOrigin = i_wzOrigin;
xSize = i_xSize;
ySize = i_ySize;
zSize = i_zSize;
cxMin = wxOrigin >> 4;
czMin = wzOrigin >> 4;
int cxMax = (wxOrigin + xSize - 1) >> 4;
int czMax = (wzOrigin + zSize - 1) >> 4;
cxCount = cxMax - cxMin + 1;
czCount = czMax - czMin + 1;
chunkCheckPositions = new ArrayList<LinkedList<BlockPos>>(cxCount * czCount);
chunksVisited = new BitSet(cxCount * czCount);
chunksToVisitFirst = new BitSet(cxCount * czCount);
chunksToVisitLater = new BitSet(cxCount * czCount);
blocksChecked = new BitSet(xSize * ySize * zSize);
diagonalAllowed = true;
reset();
}
/**
* Add a start position for the search
*
* @param wx
* @param wy
* @param wz
* @param normalDirection specifies the plane that will be searched in (Facing directions; specifies the normal to the plane)
*/
public void setStartPositionAndPlane(int wx, int wy, int wz, EnumFacing normalDirection) {
if (!isWithinBounds(wx, wy, wz)) return;
currentCheckPosition = new BlockPos(wx, wy, wz);
switch (normalDirection) {
case DOWN:
case UP:
searchPlane = PLANE_XZ;
break;
case EAST:
case WEST:
searchPlane = PLANE_XY;
break;
case NORTH:
case SOUTH:
searchPlane = PLANE_YZ;
break;
default: {
ErrorLog.defaultLog().debug("Illegal normalDirection:" + normalDirection);
searchPlane = PLANE_XY;
break;
}
}
}
/**
* true if diagonal filling is allowed; false if cardinal directions only
* @param i_diagonalAllowed
*/
public void setDiagonalAllowed(boolean i_diagonalAllowed)
{
diagonalAllowed = i_diagonalAllowed;
}
/**
* resets the iterator to start at the beginning
*/
@Override
public void reset() {
atEnd = false;
enteredNewChunk = true;
chunksToVisitFirst.clear();
chunksToVisitLater.clear();
currentSearchStartPositions.clear();
chunksVisited.clear();
blocksChecked.clear();
blocksAddedCount = 0;
for (int i = 0; i < cxCount * czCount; ++i) {
chunkCheckPositions.add(i, new LinkedList<BlockPos>());
}
}
/**
* advances to the next voxel coordinate
* @param currentPositionWasFilled true if the current iterator position was incorporated into the fill, i.e. met the
* criteria to be added to the floodfill selection
* @return true if the coordinate position is valid, false if not (there are no more positions)
*/
@Override
public boolean next(boolean currentPositionWasFilled) {
if (atEnd) return false;
++blocksAddedCount;
// lookup table to give the possible search directions for any given search plane
// row index 0 = xz plane, 1 = xy plane, 2 = yz plane
// column index = the eight directions within the plane (even = cardinal, odd = diagonal)
final int NUMBER_OF_SEARCH_DIRECTIONS = 8;
final int searchDirectionsX[][] = { {+0, -1, -1, -1, +0, +1, +1, +1},
{+0, -1, -1, -1, +0, +1, +1, +1},
{+0, +0, +0, +0, +0, +0, +0, +0}
};
final int searchDirectionsY[][] = { {+0, +0, +0, +0, +0, +0, +0, +0},
{+1, +1, +0, -1, -1, -1, +0, +1},
{+1, +1, +0, -1, -1, -1, +0, +1}
};
final int searchDirectionsZ[][] = { {+1, +1, +0, -1, -1, -1, +0, +1},
{+0, +0, +0, +0, +0, +0, +0, +0},
{+0, -1, -1, -1, +0, +1, +1, +1}
};
if (currentPositionWasFilled) {
currentSearchStartPositions.add(new SearchPosition(currentCheckPosition));
}
while (!currentSearchStartPositions.isEmpty()) {
SearchPosition currentPosition = currentSearchStartPositions.peekFirst();
int wx = currentPosition.chunkCoordinates.getX() + searchDirectionsX[searchPlane][currentPosition.nextSearchDirection];
int wy = currentPosition.chunkCoordinates.getY() + searchDirectionsY[searchPlane][currentPosition.nextSearchDirection];
int wz = currentPosition.chunkCoordinates.getZ() + searchDirectionsZ[searchPlane][currentPosition.nextSearchDirection];
currentPosition.nextSearchDirection += diagonalAllowed ? 1 : 2; // no diagonals -> even numbers only
if (currentPosition.nextSearchDirection >= NUMBER_OF_SEARCH_DIRECTIONS) {
currentSearchStartPositions.removeFirst();
}
if (isWithinBounds(wx, wy, wz) && !blocksChecked.get(getBlockIndex(wx, wy, wz))) {
blocksChecked.set(getBlockIndex(wx, wy, wz));
if (getChunkIndex(wx, wy, wz) == getChunkIndex(currentPosition.chunkCoordinates.getX(),
currentPosition.chunkCoordinates.getY(),
currentPosition.chunkCoordinates.getZ())) {
currentCheckPosition = new BlockPos(wx, wy, wz);
return true;
}
// different chunk, so queue it up
int chunkIdx = getChunkIndex(wx, wy, wz);
LinkedList<BlockPos> chunkStartSearchPositions = chunkCheckPositions.get(chunkIdx);
chunkStartSearchPositions.add(new BlockPos(wx, wy, wz));
if (chunksVisited.get(chunkIdx)) {
chunksToVisitFirst.set(chunkIdx);
} else {
chunksToVisitLater.set(chunkIdx);
}
}
}
int chunkIdx = getChunkIndex(currentCheckPosition.getX(), currentCheckPosition.getY(), currentCheckPosition.getZ());
LinkedList<BlockPos> currentChunkStartSearchPositions = chunkCheckPositions.get(chunkIdx);
if (!currentChunkStartSearchPositions.isEmpty()) {
currentCheckPosition = currentChunkStartSearchPositions.removeFirst();
return true;
}
while (!chunksToVisitFirst.isEmpty()) {
int chunkToVisitIdx = chunksToVisitFirst.previousSetBit(Integer.MAX_VALUE);
chunksToVisitFirst.clear(chunkToVisitIdx);
currentCheckPosition = chunkCheckPositions.get(chunkToVisitIdx).removeFirst();
enteredNewChunk = true;
return true;
}
while (!chunksToVisitLater.isEmpty()) {
int chunkToVisitIdx = chunksToVisitLater.previousSetBit(Integer.MAX_VALUE);
chunksToVisitLater.clear(chunkToVisitIdx);
currentCheckPosition = chunkCheckPositions.get(chunkToVisitIdx).removeFirst();
chunksVisited.set(chunkToVisitIdx);
enteredNewChunk = true;
return true;
}
atEnd = true;
return false; // nothing left to do!
}
/**
* returns true on the first call after the iterator has moved into a new chunk
*
* @return
*/
public boolean hasEnteredNewChunk() {
boolean retval = enteredNewChunk;
enteredNewChunk = false;
return retval;
}
/**
* has the iterator reached the end of the region?
*
* @return
*/
@Override
public boolean isAtEnd() {
return atEnd;
}
/**
* return the chunk x, z coordinate the iterator is currently in
*
* @return
*/
public int getChunkX() {
return currentCheckPosition.getX() >> 4;
}
public int getChunkZ() {
return currentCheckPosition.getZ() >> 4;
}
/**
* return the world x, y, z of the current iterator position
*
* @return
*/
public int getWX() {
return currentCheckPosition.getX();
}
public int getWY() {
return currentCheckPosition.getY();
}
public int getWZ() {
return currentCheckPosition.getZ();
}
/**
* get the [x,y,z] index of the current iterator position, i.e. relative to the origin
*
* @return
*/
public int getXpos() {
return getWX() - wxOrigin;
}
public int getYpos() {
return getWY() - wyOrigin;
}
public int getZpos() {
return getWZ() - wzOrigin;
}
/**
* estimate the fraction of the range that has been iterated through
* (logarithmic transformation to show progress over a much wider range)
* @return [0 .. 1]
*/
@Override
public float estimatedFractionComplete() {
if (blocksAddedCount == 0) return 0;
double fillFraction = blocksAddedCount / (double)(xSize * ySize * zSize);
final double FULL_SCALE = Math.log(1.0 / (xSize * (double)ySize * zSize)) - 1;
double fractionComplete = (1 - Math.log(fillFraction) / FULL_SCALE);
return (float)fractionComplete;
}
private static class SearchPosition
{
public SearchPosition(BlockPos initBlockPos) {
chunkCoordinates = new BlockPos(initBlockPos);
nextSearchDirection = 0;
}
public BlockPos chunkCoordinates;
public int nextSearchDirection;
}
/**
* gets the index into the chunk arrays for a given set of world coordinates
*
* @param wx world [x,y,z]
* @param wy
* @param wz
*/
private int getChunkIndex(int wx, int wy, int wz) {
return ((wx >> 4) - cxMin) + cxCount * ((wz >> 4) - czMin);
}
/**
* gets the index into the block arrays for a given set of world coordinates
*
* @param wx world [x,y,z]
* @param wy
* @param wz
*/
private int getBlockIndex(int wx, int wy, int wz) {
return (wx - wxOrigin) + xSize * (wy - wyOrigin) + xSize * ySize * (wz - wzOrigin);
}
/**
* checks whether the given point is within the boundary region
*
* @param wx world [x,y,z]
* @param wy
* @param wz
* @return true if within, false otherwise
*/
private boolean isWithinBounds(int wx, int wy, int wz) {
return (wx >= wxOrigin && wx < wxOrigin + xSize
&& wy >= wyOrigin && wy < wyOrigin + ySize
&& wz >= wzOrigin && wz < wzOrigin + zSize);
}
private Deque<SearchPosition> currentSearchStartPositions = new LinkedList<SearchPosition>(); // search positions within the current chunk
private BlockPos currentCheckPosition;
// for each chunk in the boundary, a list of block positions to be checked. Chunks arranged in idx = cx + cz * cxCount order
private ArrayList<LinkedList<BlockPos>> chunkCheckPositions;
private BitSet chunksVisited; // true for each chunk which we have already visited
private BitSet chunksToVisitFirst;
private BitSet chunksToVisitLater;
private BitSet blocksChecked; // true for each block which has been checked already; or which is queued for checking in chunkCheckPositions
private int blocksAddedCount;
private int cxMin;
private int czMin;
private int cxCount; // number of x chunks in the fill region (xwide * zlong)
private int czCount; // number of z chunks in the fill region (xwide * zlong)
private boolean atEnd;
private boolean enteredNewChunk;
private int wxOrigin;
private int wyOrigin;
private int wzOrigin;
private int xSize;
private int ySize;
private int zSize;
private boolean diagonalAllowed;
private int searchPlane;
private final int PLANE_XZ = 0;
private final int PLANE_XY = 1;
private final int PLANE_YZ = 2;
}