package speedytools.common.selections;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraft.util.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import speedytools.common.utilities.ErrorLog;
import java.io.ByteArrayOutputStream;
/**
* User: The Grey Ghost
* Date: 17/02/14
*
* Used to generate a voxel selection from defined region in the world
* Typical usage:
* 1) Create a BlockVoxelMultiSelector
* 2) call selectAllInBoxStart(), selectUnboundFillStart(), or selectBoundFillStart() to set the generation parameters
* 3) a) repeatedly call continueSelectionGeneration(), providing an optional timeout duration, until complete
* b) getEstimatedFractionComplete() can be used to get a rough estimate of task completion
* 4) After completion:
* a) the selection can be retrieved using getSelection(). if isEmpty(), there is no selection
* b) any unavailable voxels (eg chunks not loaded on client) are retrieved using getUnavailableVoxels
* containsUnavailableVoxels() returns true if there are any unavailable.
* 5) writeToBytes() can be used to write the selection to a byte array (eg for packet use)
*/
public class BlockVoxelMultiSelector
{
/**
* initialise conversion of the selected box to a VoxelSelection
*
* @param world
* @param corner1 one corner of the box
* @param corner2 opposite corner of the box
*/
public void selectAllInBoxStart(World world, BlockPos corner1, BlockPos corner2) {
initialiseSelectionSizeFromBoundary(corner1, corner2);
voxelIterator = new VoxelChunkwiseIterator(wxOrigin, wyOrigin, wzOrigin, xSize, ySize, zSize);
matcher = new FillMatcher.AnyNonAir();
mode = OperationInProgress.ALL_IN_BOX;
initialiseVoxelRange();
}
/**
* initialise conversion of the selected fill to a VoxelSelection
* From the starting block, performs a flood fill on all non-air blocks.
*
* @param world
*/
public void selectUnboundFillStart(World world, FillAlgorithmSettings fillAlgorithmSettings) {
BlockPos blockUnderCursor = fillAlgorithmSettings.getStartPosition();
final int BORDER_ALLOWANCE = 2;
final int MAXIMUM_Y = 255;
final int MINIMUM_Y = 0;
int c1x = blockUnderCursor.getX() - VoxelSelection.MAX_X_SIZE / 2 + BORDER_ALLOWANCE;
int c2x = blockUnderCursor.getX() + VoxelSelection.MAX_X_SIZE / 2 - BORDER_ALLOWANCE;
int c1y = fillAlgorithmSettings.isAutomaticLowerBound() ? blockUnderCursor.getY() : MINIMUM_Y;
c1y = Math.max(MINIMUM_Y, c1y);
int c2y = Math.min(MAXIMUM_Y, c1y + VoxelSelection.MAX_Y_SIZE - 2 * BORDER_ALLOWANCE);
int c1z = blockUnderCursor.getZ() - VoxelSelection.MAX_Z_SIZE / 2 + BORDER_ALLOWANCE;
int c2z = blockUnderCursor.getZ() + VoxelSelection.MAX_Z_SIZE / 2 - BORDER_ALLOWANCE;
BlockPos corner1 = new BlockPos(c1x, c1y, c1z);
BlockPos corner2 = new BlockPos(c2x, c2y, c2z);
selectBoundFillStart(world, fillAlgorithmSettings, corner1, corner2);
}
/**
* initialise conversion of the selected fill to a VoxelSelection
* From the starting block, performs a flood fill on all non-air blocks.
* Will not fill any blocks outside of the box defined by corner1 and corner2
*
* @param world
*/
// public void selectBoundFillStart(World world, BlockPos blockUnderCursor, Matcher i_matcher, BlockPos corner1, BlockPos corner2) {
public void selectBoundFillStart(World world, FillAlgorithmSettings fillAlgorithmSettings, BlockPos corner1, BlockPos corner2) {
initialiseSelectionSizeFromBoundary(corner1, corner2);
BlockPos blockUnderCursor = fillAlgorithmSettings.getStartPosition();
assert (blockUnderCursor.getX() >= wxOrigin && blockUnderCursor.getY() >= wyOrigin && blockUnderCursor.getZ() >= wzOrigin);
assert (blockUnderCursor.getX() < wxOrigin + xSize && blockUnderCursor.getY() < wyOrigin + ySize && blockUnderCursor.getZ() < wzOrigin + zSize);
mode = OperationInProgress.FILL;
initialiseVoxelRange();
IVoxelIterator newIterator = null;
switch(fillAlgorithmSettings.getPropagation()) {
case FLOODFILL: {
VoxelChunkwiseFillIterator newVCFIterator = new VoxelChunkwiseFillIterator(wxOrigin, wyOrigin, wzOrigin, xSize, ySize, zSize);
newVCFIterator.setStartPosition(blockUnderCursor.getX(), blockUnderCursor.getY(), blockUnderCursor.getZ());
newVCFIterator.setDiagonalAllowed(fillAlgorithmSettings.isDiagonalPropagationAllowed());
newIterator = newVCFIterator;
break;
}
case CONTOUR: {
VoxelChunkwiseContourIterator newVCCIterator = new VoxelChunkwiseContourIterator(wxOrigin, wyOrigin, wzOrigin, xSize, ySize, zSize);
newVCCIterator.setStartPositionAndPlane(blockUnderCursor.getX(), blockUnderCursor.getY(), blockUnderCursor.getZ(), fillAlgorithmSettings.getNormalDirection());
newVCCIterator.setDiagonalAllowed(fillAlgorithmSettings.isDiagonalPropagationAllowed());
newIterator = newVCCIterator;
break;
}
default: {
ErrorLog.defaultLog().debug("Illegal propagation:" + fillAlgorithmSettings.getPropagation());
break;
}
}
matcher = fillAlgorithmSettings.getFillMatcher();
// blockToMatch = new BlockWithMetadata();
// blockToMatch.block = world.getBlock(blockUnderCursor.posX, blockUnderCursor.posY, blockUnderCursor.posZ);
// blockToMatch.metaData = world.getBlockMetadata(blockUnderCursor.posX, blockUnderCursor.posY, blockUnderCursor.posZ);
voxelIterator = newIterator;
mode = OperationInProgress.FILL;
}
/**
* continue conversion of the selected box to a VoxelSelection. Call repeatedly until conversion complete.
*
* @param world
* @param maxTimeInNS maximum elapsed duration before processing stops & function returns
* @return fraction complete (0 - 1), -ve number for finished
*/
public float continueSelectionGeneration(World world, long maxTimeInNS) {
if (mode == OperationInProgress.IDLE) {
FMLLog.severe("Mode should be not be IDLE in BlockVoxelMultiSelector::selectFillContinue");
return -1;
}
if (mode == OperationInProgress.COMPLETE) return -1;
long startTime = System.nanoTime();
// System.out.print("Chunks ");
while (!voxelIterator.isAtEnd()) {
// System.out.print("[" + voxelIterator.getChunkX() + ", " + voxelIterator.getChunkZ() + "] ");
voxelIterator.hasEnteredNewChunk(); // reset flag
Chunk currentChunk = world.getChunkFromChunkCoords(voxelIterator.getChunkX(), voxelIterator.getChunkZ());
boolean voxelIsUnloaded = false;
if (currentChunk.isEmpty()) {
voxelIsUnloaded = true;
} else {
while (!voxelIterator.isAtEnd() && !voxelIterator.hasEnteredNewChunk()) {
FillMatcher.MatchResult matchResult = FillMatcher.MatchResult.NO_MATCH;
matchResult = matcher.matches(currentChunk, voxelIterator.getWX() & 0x0f, voxelIterator.getWY(), voxelIterator.getWZ() & 0x0f);
if (matchResult == FillMatcher.MatchResult.OUT_OF_BOUNDS) {
matchResult = matcher.matches(world, voxelIterator.getWX(), voxelIterator.getWY(), voxelIterator.getWZ());
}
switch (matchResult) {
case MATCH: {
selection.setVoxel(voxelIterator.getXpos(), voxelIterator.getYpos(), voxelIterator.getZpos());
expandVoxelRange(voxelIterator.getXpos(), voxelIterator.getYpos(), voxelIterator.getZpos());
voxelIterator.next(true);
break;
}
case NO_MATCH: {
voxelIterator.next(false);
break;
}
case NOT_LOADED: {
voxelIsUnloaded = true;
break;
}
default: {
ErrorLog.defaultLog().debug("Illegal matchResult:" + matchResult);
}
}
}
}
if (voxelIsUnloaded) {
containsUnavailableVoxels = true;
while (!voxelIterator.isAtEnd() && !voxelIterator.hasEnteredNewChunk()) {
unavailableVoxels.setVoxel(voxelIterator.getXpos(), voxelIterator.getYpos(), voxelIterator.getZpos());
expandVoxelRange(voxelIterator.getXpos(), voxelIterator.getYpos(), voxelIterator.getZpos());
voxelIterator.next(false);
}
}
if (System.nanoTime() - startTime >= maxTimeInNS) {
return voxelIterator.estimatedFractionComplete();
}
}
voxelIterator = null;
mode = OperationInProgress.COMPLETE;
shrinkToSmallestEnclosingCuboid();
return -1;
}
public VoxelSelectionWithOrigin getSelection() {
return selection;
}
public VoxelSelectionWithOrigin getUnavailableVoxels() {
return unavailableVoxels;
}
public boolean containsUnavailableVoxels() {
return containsUnavailableVoxels;
}
public float getEstimatedFractionComplete() {
if (voxelIterator == null) return -1;
return voxelIterator.estimatedFractionComplete();
}
/**
* returns true if there are no solid pixels at all in this selection.
*
* @return
*/
public boolean isEmpty() {
return empty;
}
/**
* gets the origin for the selection in world coordinates
*
* @return the origin for the selection in world coordinates
*/
public BlockPos getWorldOrigin() {
return new BlockPos(selection.getWxOrigin(), selection.getWyOrigin(), selection.getWzOrigin());
}
/**
* write the current selection in serialised form to a ByteArray
*
* @return the byte array, or null for failure
*/
public ByteArrayOutputStream writeToBytes() {
return selection.writeToBytes();
}
private void initialiseVoxelRange() {
smallestVoxelX = xSize;
largestVoxelX = -1;
smallestVoxelY = ySize;
largestVoxelY = -1;
smallestVoxelZ = zSize;
largestVoxelZ = -1;
empty = true;
containsUnavailableVoxels = false;
}
private void expandVoxelRange(int x, int y, int z) {
smallestVoxelX = Math.min(smallestVoxelX, x);
smallestVoxelY = Math.min(smallestVoxelY, y);
smallestVoxelZ = Math.min(smallestVoxelZ, z);
largestVoxelX = Math.max(largestVoxelX, x);
largestVoxelY = Math.max(largestVoxelY, y);
largestVoxelZ = Math.max(largestVoxelZ, z);
empty = false;
}
/**
* shrinks the voxel selection to the minimum size needed to contain the set voxels
*/
private void shrinkToSmallestEnclosingCuboid() {
if (smallestVoxelX == 0 && smallestVoxelY == 0 && smallestVoxelZ == 0
&& largestVoxelX == xSize - 1 && largestVoxelY == ySize - 1 && largestVoxelZ == zSize - 1) {
return;
}
if (smallestVoxelX > largestVoxelX) { // is empty!
smallestVoxelX = 0;
largestVoxelX = 0;
smallestVoxelY = 0;
largestVoxelY = 0;
smallestVoxelZ = 0;
largestVoxelZ = 0;
}
int newXsize = largestVoxelX - smallestVoxelX + 1;
int newYsize = largestVoxelY - smallestVoxelY + 1;
int newZsize = largestVoxelZ - smallestVoxelZ + 1;
VoxelSelectionWithOrigin smallerSelection = new VoxelSelectionWithOrigin(
wxOrigin + smallestVoxelX, wyOrigin + smallestVoxelY, wzOrigin + smallestVoxelZ,
newXsize, newYsize, newZsize);
for (int y = 0; y < newYsize; ++y) {
for (int z = 0; z < newZsize; ++z) {
for (int x = 0; x < newXsize; ++x) {
if (selection.getVoxel(x + smallestVoxelX, y + smallestVoxelY, z + smallestVoxelZ)) {
smallerSelection.setVoxel(x, y, z);
}
}
}
}
selection = smallerSelection;
VoxelSelectionWithOrigin smallerUnavailableVoxels = new VoxelSelectionWithOrigin(
wxOrigin + smallestVoxelX, wyOrigin + smallestVoxelY, wzOrigin + smallestVoxelZ,
newXsize, newYsize, newZsize);
for (int y = 0; y < newYsize; ++y) {
for (int z = 0; z < newZsize; ++z) {
for (int x = 0; x < newXsize; ++x) {
if (unavailableVoxels.getVoxel(x + smallestVoxelX, y + smallestVoxelY, z + smallestVoxelZ)) {
smallerUnavailableVoxels.setVoxel(x, y, z);
}
}
}
}
unavailableVoxels = smallerUnavailableVoxels;
wxOrigin += smallestVoxelX;
wyOrigin += smallestVoxelY;
wzOrigin += smallestVoxelZ;
smallestVoxelX = 0;
smallestVoxelY = 0;
smallestVoxelZ = 0;
largestVoxelX = newXsize - 1;
largestVoxelY = newYsize - 1;
largestVoxelZ = newZsize - 1;
xSize = newXsize;
ySize = newYsize;
zSize = newZsize;
}
private void initialiseSelectionSizeFromBoundary(BlockPos corner1, BlockPos corner2) {
wxOrigin = Math.min(corner1.getX(), corner2.getX());
wyOrigin = Math.min(corner1.getY(), corner2.getY());
wzOrigin = Math.min(corner1.getZ(), corner2.getZ());
xSize = 1 + Math.max(corner1.getX(), corner2.getX()) - wxOrigin;
ySize = 1 + Math.max(corner1.getY(), corner2.getY()) - wyOrigin;
zSize = 1 + Math.max(corner1.getZ(), corner2.getZ()) - wzOrigin;
if (selection == null) {
selection = new VoxelSelectionWithOrigin(wxOrigin, wyOrigin, wzOrigin, xSize, ySize, zSize);
unavailableVoxels = new VoxelSelectionWithOrigin(wxOrigin, wyOrigin, wzOrigin, xSize, ySize, zSize);
} else {
selection.resizeAndClear(xSize, ySize, zSize);
unavailableVoxels.resizeAndClear(xSize, ySize, zSize);
}
}
private enum OperationInProgress
{
IDLE, ALL_IN_BOX, FILL, COMPLETE
}
private VoxelSelectionWithOrigin selection;
private VoxelSelectionWithOrigin unavailableVoxels;
private boolean containsUnavailableVoxels;
private int smallestVoxelX;
private int largestVoxelX;
private int smallestVoxelY;
private int largestVoxelY;
private int smallestVoxelZ;
private int largestVoxelZ;
private int xSize;
private int ySize;
private int zSize;
private int wxOrigin;
private int wyOrigin;
private int wzOrigin;
private IVoxelIterator voxelIterator;
private boolean empty = true;
private OperationInProgress mode;
private FillMatcher matcher;
// private BlockWithMetadata blockToMatch;
}