package speedytools.serverside.ingametester;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.BlockPos;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import speedytools.common.selections.*;
import speedytools.serverside.worldmanipulation.WorldFragment;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
/**
* User: The Grey Ghost
* Date: 5/10/2014
* Used to test the VoxelChunkwiseFillIterator against the old fill algorithm (should give same results)
*/
public class SelectionFillTester
{
// for each of the test regions:
// 1) generates an old-method fill, starting from the [0,0,0] corner of the source region
// 2) copies that fill selection from the source region to the expected outcome
// 3) generates a new-method fill, starting from the [0,0,0] corner of the source region
// 4) compares the old selection to the new selection
public boolean runFillTests(EntityPlayerMP entityPlayerMP, boolean performTest)
{
WorldServer worldServer = MinecraftServer.getServer().worldServerForDimension(0);
final int XORIGIN = 128; final int YORIGIN = 4; final int ZORIGIN = 0;
final int XSIZE = 48; final int YSIZE = 48; final int ZSIZE = 44;
final int NUMBER_OF_TEST_REGIONS = 4;
ArrayList<InGameTester.TestRegions> testRegions = new ArrayList<InGameTester.TestRegions>();
for (int i = 0; i < NUMBER_OF_TEST_REGIONS; ++i) {
InGameTester.TestRegions newRegion = new InGameTester.TestRegions(XORIGIN, YORIGIN, ZORIGIN + i * (ZSIZE+1), XSIZE, YSIZE, ZSIZE, true);
if (!performTest) {
newRegion.drawAllTestRegionBoundaries();
WorldFragment worldFragmentBlank = new WorldFragment(newRegion.xSize, newRegion.ySize, newRegion.zSize);
worldFragmentBlank.readFromWorld(worldServer, newRegion.testRegionInitialiser.getX(), newRegion.testRegionInitialiser.getY(), newRegion.testRegionInitialiser.getZ(), null);
worldFragmentBlank.writeToWorld(worldServer, newRegion.testOutputRegion.getX(), newRegion.testOutputRegion.getY(), newRegion.testOutputRegion.getZ(), null);
worldFragmentBlank.writeToWorld(worldServer, newRegion.expectedOutcome.getX(), newRegion.expectedOutcome.getY(), newRegion.expectedOutcome.getZ(), null);
}
testRegions.add(newRegion);
}
if (!performTest) return true;
int testRegionNumber = 0;
for (InGameTester.TestRegions testRegion : testRegions) {
BlockPos corner1 = new BlockPos(testRegion.sourceRegion.getX(), testRegion.sourceRegion.getY(), testRegion.sourceRegion.getZ());
BlockPos corner2 = new BlockPos(testRegion.sourceRegion.getX() + testRegion.xSize,
testRegion.sourceRegion.getY() + testRegion.ySize,
testRegion.sourceRegion.getZ() + testRegion.zSize);
BlockPos blockUnderCursor = corner1;
selectBoundFillStart(worldServer, blockUnderCursor, corner1, corner2);
selectFillContinue(worldServer, Long.MAX_VALUE);
BlockPos origin = getWorldOrigin();
VoxelSelectionWithOrigin oldSelection = new VoxelSelectionWithOrigin(origin.getX(), origin.getY(), origin.getZ(), getSelection());
WorldFragment worldFragmentOld = new WorldFragment(oldSelection.getxSize(), oldSelection.getySize(), oldSelection.getzSize());
worldFragmentOld.readFromWorld(worldServer, testRegion.sourceRegion.getX(), testRegion.sourceRegion.getY(), testRegion.sourceRegion.getZ(),
oldSelection);
worldFragmentOld.writeToWorld(worldServer, testRegion.testOutputRegion.getX(), testRegion.testOutputRegion.getY(), testRegion.testOutputRegion.getZ(),
null);
FillAlgorithmSettings fillAlgorithmSettings = new FillAlgorithmSettings();
fillAlgorithmSettings.setDiagonalPropagationAllowed(true);
fillAlgorithmSettings.setFillMatcher(new FillMatcher.AnyNonAir());
fillAlgorithmSettings.setStartPosition(blockUnderCursor);
BlockVoxelMultiSelector blockVoxelMultiSelector = new BlockVoxelMultiSelector();
blockVoxelMultiSelector.selectBoundFillStart(worldServer, fillAlgorithmSettings, corner1, corner2);
blockVoxelMultiSelector.continueSelectionGeneration(worldServer, Long.MAX_VALUE);
VoxelSelection selectionNew = blockVoxelMultiSelector.getSelection();
VoxelSelection selectionOld = getSelection();
if (!selectionNew.containsAllOfThisMask(selectionOld)) {
System.out.println("Test region " + testRegionNumber + " failed; New didn't contain all of Old");
return false;
}
if (!selectionOld.containsAllOfThisMask(selectionNew)) {
System.out.println("Test region " + testRegionNumber + " failed; Old didn't contain all of New");
return false;
}
++testRegionNumber;
}
return true;
}
public VoxelSelection getSelection() {
return selection;
}
public VoxelSelectionWithOrigin getUnavailableVoxels() {
return unavailableVoxels;
}
public boolean containsUnavailableVoxels() {
return containsUnavailableVoxels;
}
/**
* 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 with y less than the blockUnderCursor.
*
* @param world
* @param blockUnderCursor the block being highlighted by the cursor
*/
public void selectUnboundFillStart(World world, BlockPos blockUnderCursor) {
final int BORDER_ALLOWANCE = 2;
BlockPos corner1 = new BlockPos(
blockUnderCursor.getX() - VoxelSelection.MAX_X_SIZE / 2 + BORDER_ALLOWANCE,
blockUnderCursor.getY(),
blockUnderCursor.getZ() - VoxelSelection.MAX_Z_SIZE / 2 + BORDER_ALLOWANCE);
BlockPos corner2 = new BlockPos(
blockUnderCursor.getX() + VoxelSelection.MAX_X_SIZE / 2 - BORDER_ALLOWANCE,
Math.min(255, blockUnderCursor.getY() + VoxelSelection.MAX_Y_SIZE - 2 * BORDER_ALLOWANCE),
blockUnderCursor.getZ() + VoxelSelection.MAX_Z_SIZE / 2 - BORDER_ALLOWANCE);
selectBoundFillStart(world, blockUnderCursor, 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
* @param blockUnderCursor the block being highlighted by the cursor
*/
public void selectBoundFillStart(World world, BlockPos blockUnderCursor, BlockPos corner1, BlockPos corner2) {
initialiseSelectionSizeFromBoundary(corner1, corner2);
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();
BlockPos startingBlockCopy = new BlockPos(blockUnderCursor.getX() - wxOrigin, blockUnderCursor.getY() - wyOrigin, blockUnderCursor.getZ() - wzOrigin);
currentSearchPositions.clear();
nextDepthSearchPositions.clear();
currentSearchPositions.add(new SearchPosition(startingBlockCopy));
selection.setVoxel(startingBlockCopy.getX(), startingBlockCopy.getY(), startingBlockCopy.getZ());
expandVoxelRange(startingBlockCopy.getX(), startingBlockCopy.getY(), startingBlockCopy.getZ());
blocksAddedCount = 0;
}
/**
* returns true if there are no solid pixels at all in this selection.
*
* @return
*/
public boolean isEmpty() {
return empty;
}
/**
* write the current selection in serialised form to a ByteArray
*
* @return the byte array, or null for failure
*/
public ByteArrayOutputStream writeToBytes() {
return selection.writeToBytes();
}
/**
* 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());
}
public static class SearchPosition
{
public SearchPosition(BlockPos initBlockPos) {
chunkCoordinates = initBlockPos;
nextSearchDirection = 0;
}
public BlockPos chunkCoordinates;
public int nextSearchDirection;
}
/**
* 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
*/
private float selectFillContinue(World world, long maxTimeInNS) {
if (mode != OperationInProgress.FILL) {
FMLLog.severe("Mode should be FILL in BlockVoxelMultiSelector::selectFillContinue");
return -1;
}
long startTime = System.nanoTime();
// lookup table to give the possible search directions for non-diagonal and diagonal respectively
final int NON_DIAGONAL_DIRECTIONS = 6;
final int ALL_DIRECTIONS = 26;
final int searchDirectionsX[] = {+0, +0, +0, +0, -1, +1, // non-diagonal
+1, +0, -1, +0, +1, +1, -1, -1, +1, +0, -1, +0, // top, middle, bottom "edge" blocks
+1, +1, -1, -1, +1, +1, -1, -1 // top, bottom "corner" blocks
};
final int searchDirectionsY[] = {-1, +1, +0, +0, +0, +0, // non-diagonal
+1, +1, +1, +1, +0, +0, +0, +0, -1, -1, -1, -1, // top, middle, bottom "edge" blocks
+1, +1, +1, +1, -1, -1, -1, -1 // top, bottom "corner" blocks
};
final int searchDirectionsZ[] = {+0, +0, -1, +1, +0, +0, // non-diagonal
+0, -1, +0, +1, +1, -1, -1, +1, +0, -1, +0, +1, // top, middle, bottom "edge" blocks
+1, -1, -1, +1, +1, -1, -1, +1
};
BlockPos checkPosition = new BlockPos(0, 0, 0);
BlockPos checkPositionSupport = new BlockPos(0, 0, 0);
while (!currentSearchPositions.isEmpty()) {
SearchPosition currentSearchPosition = currentSearchPositions.getFirst();
checkPosition = new BlockPos(currentSearchPosition.chunkCoordinates.getX() + searchDirectionsX[currentSearchPosition.nextSearchDirection],
currentSearchPosition.chunkCoordinates.getY() + searchDirectionsY[currentSearchPosition.nextSearchDirection],
currentSearchPosition.chunkCoordinates.getZ() + searchDirectionsZ[currentSearchPosition.nextSearchDirection]);
if (checkPosition.getX() >= 0 && checkPosition.getX() < xSize
&& checkPosition.getY() >= 0 && checkPosition.getY() < ySize
&& checkPosition.getZ() >= 0 && checkPosition.getZ() < zSize
&& !selection.getVoxel(checkPosition.getX(), checkPosition.getY(), checkPosition.getZ())) {
boolean blockIsAir = world.isAirBlock(checkPosition.add(wxOrigin, wyOrigin, wzOrigin));
if (!blockIsAir) {
BlockPos newChunkCoordinate = new BlockPos(checkPosition);
SearchPosition nextSearchPosition = new SearchPosition(newChunkCoordinate);
nextDepthSearchPositions.addLast(nextSearchPosition);
selection.setVoxel(checkPosition.getX(), checkPosition.getY(), checkPosition.getZ());
expandVoxelRange(checkPosition.getX(), checkPosition.getY(), checkPosition.getZ());
++blocksAddedCount;
}
}
++currentSearchPosition.nextSearchDirection;
if (currentSearchPosition.nextSearchDirection >= ALL_DIRECTIONS) {
currentSearchPositions.removeFirst();
if (currentSearchPositions.isEmpty()) {
Deque<SearchPosition> temp = currentSearchPositions;
currentSearchPositions = nextDepthSearchPositions;
nextDepthSearchPositions = temp;
}
}
if (System.nanoTime() - startTime >= maxTimeInNS) { // completion fraction is hard to predict, so use a logarithmic function instead to provide some visual movement regardless of size
if (blocksAddedCount == 0) return 0;
double fillFraction = blocksAddedCount / (double) (xSize * ySize * zSize);
final double FULL_SCALE = Math.log(1.0 / (xSize * ySize * zSize)) - 1;
double fractionComplete = (1 - Math.log(fillFraction) / FULL_SCALE);
return (float) fractionComplete;
}
}
mode = OperationInProgress.COMPLETE;
shrinkToSmallestEnclosingCuboid();
return -1;
}
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;
}
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
}
Deque<SearchPosition> currentSearchPositions = new LinkedList<SearchPosition>();
Deque<SearchPosition> nextDepthSearchPositions = new LinkedList<SearchPosition>();
int blocksAddedCount;
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 boolean empty = true;
private OperationInProgress mode;
}