package speedytools.clientside.selections;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.util.*;
import net.minecraft.world.World;
import speedytools.common.selections.FillMatcher;
import speedytools.common.utilities.ErrorLog;
import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: TheGreyGhost
* Date: 28/10/13
* Time: 9:47 PM
* BlockMultiSelector is a group of methods used to select multiple blocks based on where the mouse is pointing.
*/
public class BlockMultiSelector
{
// public enum BlockTypeToSelect {AIR_ONLY, NON_SOLID_OK, SOLID_OK}
/**
* Used to specify the type of behaviour when selecting the starting block
* 0) should we perform a collision test, or just skip to placing a block in mid air?
* 1) if no block collided, should we select a position in mid air?
* 2) should we collide with water?
* 3) if the collided block is water, should we pull back one to the adjacent block?
* 4) if the collided block is non-solid (eg grass), should we pull back one to the adjacent block?
* 5) if the collided block is solid, should we pull back one to the adjacent block?
*/
public enum BlockSelectionBehaviour {
WAND_STYLE( true, true, false, false, false, true),
ORB_STYLE( true, false, true, false, false, false),
SCEPTRE_ADD_STYLE( true, false, true, true, false, true),
SCEPTRE_REPLACE_SYTLE( true, false, true, false, false, false),
BOUNDARY_STYLE(false, true, false, false, false, false);
BlockSelectionBehaviour(boolean i_performCollisionTest, boolean i_selectAirIfNoCollision, boolean i_waterCollision,
boolean i_waterPullback, boolean i_nonSolidPullback, boolean i_solidPullback)
{
performCollisionTest = i_performCollisionTest;
selectAirIfNoCollision = i_selectAirIfNoCollision;
waterCollision = i_waterCollision;
waterPullback = i_waterPullback;
nonSolidPullback = i_nonSolidPullback;
solidPullback = i_solidPullback;
}
public boolean isPerformCollisionTest() { return performCollisionTest;}
public boolean isSelectAirIfNoCollision() {
return selectAirIfNoCollision;
}
public boolean isWaterCollision() {
return waterCollision;
}
public boolean isWaterPullback() {
return waterPullback;
}
public boolean isNonSolidPullback() {
return nonSolidPullback;
}
public boolean isSolidPullback() {
return solidPullback;
}
private boolean performCollisionTest;
private boolean selectAirIfNoCollision;
private boolean waterCollision;
private boolean waterPullback;
private boolean nonSolidPullback;
private boolean solidPullback;
}
/**
* selectStartingBlock is used to select a starting block based on the player's position and look
* There are three distinct cases for the starting block:
* (1) the mouse is not on any target: the first block selected will be the one corresponding to the line of sight from the player's head:
* a) which doesn't intersect the player's bounding box
* b) which is at least 0.5 m from the player's eyes in each of the the x, y, and z directions.
* (2) the mouse is on a tile target: the first block selected will be according to blockSelectionBehaviour
* (3) the mouse is on an entity: no selection.
* The method also returns the look vector snapped to the midpoint of the face that was hit on the selected Block
* @param mouseTarget where the cursor is currently pointed
* @param blockSelectionBehaviour the types of blocks that can be selected.
* @param player the player (used for position and look information)
* @param partialTick used for calculating player head position
* @return the coordinates of the starting selection block plus the side hit plus the look vector snapped to the midpoint of
* side hit. null if no selection.
*/
public static MovingObjectPosition selectStartingBlock(MovingObjectPosition mouseTarget, BlockSelectionBehaviour blockSelectionBehaviour,
EntityPlayer player, float partialTick)
{
final double MINIMUMHITDISTANCE = 0.5; // minimum distance from the player's eyes (axis-aligned not oblique)
int blockx, blocky, blockz;
double playerOriginX = player.lastTickPosX + (player.posX - player.lastTickPosX) * (double)partialTick;
double playerOriginY = player.lastTickPosY + (player.posY - player.lastTickPosY) * (double)partialTick;
double playerOriginZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * (double)partialTick;
Vec3 playerLook = player.getLook(partialTick);
Vec3 playerEyesPos = player.getPositionEyes(partialTick); // new Vec3(playerOriginX, playerOriginY, playerOriginZ);
if (mouseTarget == null) { // no hit
if (!blockSelectionBehaviour.isSelectAirIfNoCollision()) {
return null;
}
// we need to find the closest [x,y,z] in the direction the player is looking in, that the player is not occupying.
// This will depend on the yaw but also the elevation.
// The algorithm is:
// (1) calculated an expanded AABB around the player (all sides at least 0.5m from the eyes) and snap it to the next largest enclosing blocks.
// (2) find the intersection of the look vector with this AABB
// (3) the selected block is the one just beyond the intersection point
AxisAlignedBB playerAABB = player.getEntityBoundingBox();
double AABBminX = Math.floor(Math.min(playerAABB.minX, playerOriginX - MINIMUMHITDISTANCE));
double AABBminY = Math.floor(Math.min(playerAABB.minY, playerOriginY - MINIMUMHITDISTANCE));
double AABBminZ = Math.floor(Math.min(playerAABB.minZ, playerOriginZ - MINIMUMHITDISTANCE));
double AABBmaxX = Math.ceil(Math.max(playerAABB.maxX, playerOriginX + MINIMUMHITDISTANCE));
double AABBmaxY = Math.ceil(Math.max(playerAABB.maxY, playerOriginY + MINIMUMHITDISTANCE));
double AABBmaxZ = Math.ceil(Math.max(playerAABB.maxZ, playerOriginZ + MINIMUMHITDISTANCE));
AxisAlignedBB expandedAABB = new AxisAlignedBB(AABBminX, AABBminY, AABBminZ, AABBmaxX, AABBmaxY, AABBmaxZ);
Vec3 startVec = playerEyesPos.addVector(0, 0, 0);
Vec3 endVec = playerEyesPos.addVector(playerLook.xCoord * 8.0, playerLook.yCoord * 8.0, playerLook.zCoord * 8.0);
MovingObjectPosition traceResult = expandedAABB.calculateIntercept(startVec, endVec);
if (traceResult == null) { // shouldn't be possible
return null;
}
blockx = MathHelper.floor_double(traceResult.hitVec.xCoord + playerLook.xCoord * 0.001);
blocky = MathHelper.floor_double(traceResult.hitVec.yCoord + playerLook.yCoord * 0.001);
blockz = MathHelper.floor_double(traceResult.hitVec.zCoord + playerLook.zCoord * 0.001);
traceResult = new MovingObjectPosition(traceResult.hitVec, traceResult.sideHit.getOpposite(), new BlockPos(blockx, blocky, blockz));
traceResult.hitVec = playerLook;
// traceResult.hitVec = snapLookToBlockFace(traceResult, playerEyesPos);
return traceResult;
} else if (mouseTarget.typeOfHit == MovingObjectPosition.MovingObjectType.BLOCK) {
World world = player.getEntityWorld();
boolean pullback = false;
BlockPos blockUnderCursor = mouseTarget.getBlockPos(); // get BlockPos
switch (BlockMultiSelector.checkBlockSolidity(world, blockUnderCursor.getX(), blockUnderCursor.getY(), blockUnderCursor.getZ())) {
case AIR: {
pullback = false;
break;
}
case WATER: {
pullback = blockSelectionBehaviour.isWaterPullback();
break;
}
case NON_SOLID: {
pullback = blockSelectionBehaviour.isNonSolidPullback();
break;
}
case SOLID: {
pullback = blockSelectionBehaviour.isSolidPullback();
break;
}
default: {
ErrorLog.defaultLog().severe("Illegal solidity");
break;
}
}
if (pullback) {
EnumFacing blockInFront = mouseTarget.sideHit;
blockx = blockUnderCursor.getX() + blockInFront.getFrontOffsetX();
blocky = blockUnderCursor.getY() + blockInFront.getFrontOffsetY();
blockz = blockUnderCursor.getZ() + blockInFront.getFrontOffsetZ();
mouseTarget.sideHit = mouseTarget.sideHit.getOpposite(); // if pullback, swap the sidehit to point to the solid block
} else {
blockx = blockUnderCursor.getX();
blocky = blockUnderCursor.getY();
blockz = blockUnderCursor.getZ();
}
mouseTarget = new MovingObjectPosition(mouseTarget.hitVec, mouseTarget.sideHit, new BlockPos(blockx, blocky, blockz));
mouseTarget.hitVec = snapLookToBlockFace(mouseTarget, playerEyesPos);
return mouseTarget;
} else { // currently only ENTITY
return null;
}
}
/**
* selectLine is used to select a straight line of blocks, and return a list of their coordinates.
* Starting from the startingBlock, the selection will continue in a line parallel to the direction vector, snapped to the six cardinal directions or
* alternatively to one of the twenty 45 degree directions (if diagonalOK == true).
* If stopWhenCollide == true and the snapped direction points directly into a solid block, the direction will be deflected up to lie flat along the surface
* Keeps going until it reaches maxLineLength, y goes outside the valid range, or hits a solid block (and stopWhenCollide is true)
* @param startingBlock the first block in the straight line
* @param world the world
* @param direction the direction to extend the selection
* @param maxLineLength the maximum number of blocks to select
* @param diagonalOK if true, diagonal 45 degree lines are allowed
* @param stopWhenCollide if true, stops when a solid block is encountered (canCollide == true). Otherwise, continues for maxLineLength
* @return a list of the coordinates of all blocks in the selection, including the startingBlock. May be zero length if the startingBlock is null
*/
public static List<BlockPos> selectLine(BlockPos startingBlock, World world, Vec3 direction,
int maxLineLength, boolean diagonalOK, CollisionOptions stopWhenCollide)
{
List<BlockPos> selection = new ArrayList<BlockPos>();
if (startingBlock == null) return selection;
Vec3 snappedCardinalDirection = snapToCardinalDirection(direction, diagonalOK);
if (snappedCardinalDirection == null) return selection;
BlockPos deltaPosition = convertToDelta(snappedCardinalDirection);
BlockPos nextCoordinate = new BlockPos(startingBlock);
selection.add(startingBlock);
int blocksCount = 1;
while (blocksCount < maxLineLength) {
nextCoordinate = nextCoordinate.add(deltaPosition);
if (nextCoordinate.getY() < 0 || nextCoordinate.getY() >= 256) break;
if ((stopWhenCollide == CollisionOptions.STOP_WHEN_SOLID_BLOCK_REACHED) && isBlockSolid(world, nextCoordinate)) {
if (blocksCount > 1) break;
deltaPosition = deflectDirectionVector(world, startingBlock, direction, deltaPosition);
nextCoordinate = startingBlock.add(deltaPosition);
if (isBlockSolid(world, nextCoordinate)) break;
}
selection.add(nextCoordinate);
++blocksCount;
}
return selection;
}
public static List<BlockPos> selectContourUnbounded(BlockPos startingBlockPosition, World world,
int maxBlockCount, boolean diagonalOK, FillMatcher fillMatcher, EnumFacing normalDirection)
{
return selectContourBounded(startingBlockPosition, world, maxBlockCount, diagonalOK, fillMatcher, normalDirection,
Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
/**
* selectContour is used to select a contoured line of blocks, and return a list of their coordinates.
* Starting from the block identified by mouseTarget, the selection will attempt to follow any contours in the same plane as the side hit.
* (for example: if there is a zigzagging wall, it will select the layer of blocks that follows the top of the wall.)
* Depending on fillMatcher, it will either select the non-solid blocks on top of the contour (to make the wall "taller"), or
* select the solid blocks that form the top layer of the contour (to remove the top layer of the wall).
* depending on diagonalOK it will follow diagonals or only the cardinal directions.
* Keeps going until it reaches maxBlockCount, y goes outside the valid range, or hits a solid block. The search algorithm is to look for closest blocks first
* ("closest" meaning the shortest distance travelled along the contour being created)
* @param startingBlockPosition the block to start from
* @param world the world
* @param maxBlockCount the maximum number of blocks to select
* @param diagonalOK if true, diagonal 45 degree lines are allowed
* @param fillMatcher the matcher used to determine which blocks should be added to the fill
* @param normalDirection specifies the plane that will be searched in (Facing directions; specifies the normal to the plane)
* @return a list of the coordinates of all blocks in the selection, including the mouseTarget block. Will be empty if the mouseTarget is not a tile.
*/
public static List<BlockPos> selectContourBounded(BlockPos startingBlockPosition, World world,
int maxBlockCount, boolean diagonalOK, FillMatcher fillMatcher, EnumFacing normalDirection,
int xMin, int xMax, int yMin, int yMax, int zMin, int zMax)
{
// 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 PLANE_XZ = 0;
final int PLANE_XY = 1;
final int PLANE_YZ = 2;
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}
};
List<BlockPos> selection = new ArrayList<BlockPos>();
// if (mouseTarget == null || mouseTarget.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK) return selection;
// BlockPos startingBlock = new BlockPos();
int searchPlane;
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: return selection; // illegal value so return nothing
}
// first step is to identify the starting block depending on whether this is an additive contour or subtractive contour,
// EnumFacing blockInFront = EnumFacing.getFront(mouseTarget.sideHit);
// startingBlock.posX = mouseTarget.blockX;
// startingBlock.posY = mouseTarget.blockY;
// startingBlock.posZ = mouseTarget.blockZ;
// if (selectAdditiveContour && isBlockSolid(world, startingBlock)) {
// startingBlock.posX += blockInFront.getFrontOffsetX();
// startingBlock.posY += blockInFront.getFrontOffsetY();
// startingBlock.posZ += blockInFront.getFrontOffsetZ();
// }
selection.add(startingBlockPosition);
final int INITIAL_CAPACITY = 128;
Set<BlockPos> locationsFilled = new HashSet<BlockPos>(INITIAL_CAPACITY); // locations which have been selected during the fill
Deque<SearchPosition> currentSearchPositions = new LinkedList<SearchPosition>();
Deque<SearchPosition> nextDepthSearchPositions = new LinkedList<SearchPosition>();
BlockPos checkPosition = new BlockPos(0,0,0);
// BlockPos checkPositionSupport = new BlockPos(0,0,0);
locationsFilled.add(startingBlockPosition);
currentSearchPositions.add(new SearchPosition(startingBlockPosition));
// algorithm is:
// for each block in the list of search positions, iterate through each adjacent block to see whether it meets the criteria for expansion:
// a) is not solid (for additive contours) or is solid (for subtractive contours)
// b) hasn't been filled already during this contour search
// c) for additive contours: if it is "supported" by a solid block (eg in the case of sideHit = top face, then test whether the block at [x,y-1,z] is solid
// if the criteria are met, select the block and add it to the list of blocks to be search next round.
// if the criteria aren't met, keep trying other directions from the same position until all positions are searched. Then delete the search position and move onto the next.
// This will ensure that the fill spreads evenly out from the starting point.
while (!currentSearchPositions.isEmpty() && selection.size() < maxBlockCount) {
SearchPosition currentSearchPosition = currentSearchPositions.getFirst();
checkPosition = currentSearchPosition.chunkCoordinates.add(
searchDirectionsX[searchPlane][currentSearchPosition.nextSearchDirection],
searchDirectionsY[searchPlane][currentSearchPosition.nextSearchDirection],
searchDirectionsZ[searchPlane][currentSearchPosition.nextSearchDirection]
);
if (checkPosition.getX() >= xMin && checkPosition.getX() <= xMax &&
checkPosition.getY() >= yMin && checkPosition.getY() <= yMax &&
checkPosition.getZ() >= zMin && checkPosition.getZ() <= zMax &&
!locationsFilled.contains(checkPosition)) {
FillMatcher.MatchResult matchResult = fillMatcher.matches(world, checkPosition.getX(), checkPosition.getY(), checkPosition.getZ());
// if (selectAdditiveContour) {
// if (!isBlockSolid(world, checkPosition)) {
// checkPositionSupport.set(checkPosition.posX - blockInFront.getFrontOffsetX(), // block behind
// checkPosition.posY - blockInFront.getFrontOffsetY(),
// checkPosition.posZ - blockInFront.getFrontOffsetZ()
// );
// blockIsSuitable = isBlockSolid(world, checkPositionSupport);
// }
// } else { // subtractive contour
// blockIsSuitable = isBlockSolid(world, checkPosition);
// }
if (matchResult == FillMatcher.MatchResult.MATCH) {
BlockPos newChunkCoordinate = new BlockPos(checkPosition);
SearchPosition nextSearchPosition = new SearchPosition(newChunkCoordinate);
nextDepthSearchPositions.addLast(nextSearchPosition);
locationsFilled.add(newChunkCoordinate);
selection.add(newChunkCoordinate);
}
}
currentSearchPosition.nextSearchDirection += diagonalOK ? 1 : 2; // no diagonals -> even numbers only
if (currentSearchPosition.nextSearchDirection >= 8) {
currentSearchPositions.removeFirst();
if (currentSearchPositions.isEmpty()) {
Deque<SearchPosition> temp = currentSearchPositions;
currentSearchPositions = nextDepthSearchPositions;
nextDepthSearchPositions = temp;
}
}
}
return selection;
}
/**
* selectFill is used to select a flood fill of blocks which match the starting block, and return a list of their coordinates.
* Starting from the block identified by mouseTarget, the selection will flood fill out in three directions
* depending on diagonalOK it will follow diagonals or only the cardinal directions.
* Keeps going until it reaches maxBlockCount, y goes outside the valid range. The search algorithm is to look for closest blocks first
* ("closest" meaning the shortest distance travelled along the blob being created)
*
* @param world the world
* @param maxBlockCount the maximum number of blocks to select
* @param diagonalOK if true, diagonal 45 degree lines are allowed
* @param fillMatcher the matcher used to determine which blocks should be added to the fill
* @param xMin the fill will not extend below xMin. Likewise it will not extend above xMax. Similar for y, z.
*/
public static List<BlockPos> selectFillBounded(BlockPos fillStartPosition, World world,
int maxBlockCount, boolean diagonalOK, FillMatcher fillMatcher,
int xMin, int xMax, int yMin, int yMax, int zMin, int zMax)
{
// 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
};
List<BlockPos> selection = new ArrayList<BlockPos>();
BlockPos startingBlock = new BlockPos(fillStartPosition);
if (FillMatcher.MatchResult.MATCH != fillMatcher.matches(world, fillStartPosition.getX(), fillStartPosition.getY(), fillStartPosition.getZ())) {
return selection;
}
selection.add(startingBlock);
// Block blockToReplace = world.getBlock(startingBlock.posX, startingBlock.posY, startingBlock.posZ);
// int blockToReplaceMetadata = world.getBlockMetadata(startingBlock.posX, startingBlock.posY, startingBlock.posZ);
final int INITIAL_CAPACITY = 128;
Set<BlockPos> locationsFilled = new HashSet<BlockPos>(INITIAL_CAPACITY); // locations which have been selected during the fill
Deque<SearchPosition> currentSearchPositions = new LinkedList<SearchPosition>();
Deque<SearchPosition> nextDepthSearchPositions = new LinkedList<SearchPosition>();
BlockPos checkPosition = new BlockPos(0,0,0);
BlockPos checkPositionSupport = new BlockPos(0,0,0);
locationsFilled.add(startingBlock);
currentSearchPositions.add(new SearchPosition(startingBlock));
// algorithm is:
// for each block in the list of search positions, iterate through each adjacent block to see whether it meets the criteria for expansion:
// a) matches the block-to-be-replaced (if matchAnyNonAir: non-air, otherwise if blockID and metaData match. For lava or water metadata doesn't need to match).
// b) hasn't been filled already during this contour search
// if the criteria are met, select the block and add it to the list of blocks to be search next round.
// if the criteria aren't met, keep trying other directions from the same position until all positions are searched. Then delete the search position and move onto the next.
// This will ensure that the fill spreads evenly out from the starting point. Check the boundary to stop fill spreading outside it.
while (!currentSearchPositions.isEmpty() && selection.size() < maxBlockCount) {
SearchPosition currentSearchPosition = currentSearchPositions.getFirst();
checkPosition = currentSearchPosition.chunkCoordinates.add(
searchDirectionsX[currentSearchPosition.nextSearchDirection],
searchDirectionsY[currentSearchPosition.nextSearchDirection],
searchDirectionsZ[currentSearchPosition.nextSearchDirection]
);
if ( checkPosition.getX() >= xMin && checkPosition.getX() <= xMax
&& checkPosition.getY() >= yMin && checkPosition.getY() <= yMax
&& checkPosition.getZ() >= zMin && checkPosition.getZ() <= zMax
&& !locationsFilled.contains(checkPosition)) {
FillMatcher.MatchResult matchResult = fillMatcher.matches(world, checkPosition.getX(), checkPosition.getY(), checkPosition.getZ());
// Block blockToCheck = world.getBlock();
//
// if (matchAnyNonAir && blockToCheck != Blocks.air) {
// blockIsSuitable = true;
// } else if (blockToCheck == blockToReplace) {
// if (world.getBlockMetadata(checkPosition.posX, checkPosition.posY, checkPosition.posZ) == blockToReplaceMetadata) {
// blockIsSuitable = true;
// } else {
// if (blockToCheck.getMaterial() == Material.lava
// || blockToCheck.getMaterial() == Material.water) {
// blockIsSuitable = true;
// }
// }
// }
if (matchResult == FillMatcher.MatchResult.MATCH) {
BlockPos newChunkCoordinate = new BlockPos(checkPosition);
SearchPosition nextSearchPosition = new SearchPosition(newChunkCoordinate);
nextDepthSearchPositions.addLast(nextSearchPosition);
locationsFilled.add(newChunkCoordinate);
selection.add(newChunkCoordinate);
}
}
++currentSearchPosition.nextSearchDirection;
if (currentSearchPosition.nextSearchDirection >= (diagonalOK ? ALL_DIRECTIONS : NON_DIAGONAL_DIRECTIONS)) {
currentSearchPositions.removeFirst();
if (currentSearchPositions.isEmpty()) {
Deque<SearchPosition> temp = currentSearchPositions;
currentSearchPositions = nextDepthSearchPositions;
nextDepthSearchPositions = temp;
}
}
}
return selection;
}
/**
* see selectFill above
* @param world
* @param maxBlockCount
* @param diagonalOK
* @return
*/
public static List<BlockPos> selectFillUnbounded(BlockPos fillStartPosition, World world,
int maxBlockCount, boolean diagonalOK, FillMatcher fillMatcher)
{
return selectFillBounded(fillStartPosition, world, maxBlockCount, diagonalOK, fillMatcher,
Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 255, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
/**
* Used to create vector from the starting point to the midpoint of the specified side of the block.
* @param movingObjectPosition the [x,y,z] of the target block, and the side. hitVec is ignored.
* @param startPos the origin of the vector to be created
* @return the direction vector, or null for failure
*/
public static Vec3 snapLookToBlockFace(MovingObjectPosition movingObjectPosition, Vec3 startPos)
{
// midpoint of each face based on side
final double[] X_FACE_OFFSET = {0.5, 0.5, 0.5, 0.5, 0.0, 1.0};
final double[] Y_FACE_OFFSET = {0.0, 1.0, 0.5, 0.5, 0.5, 0.5};
final double[] Z_FACE_OFFSET = {0.5, 0.5, 0.0, 1.0, 0.5, 0.5};
int sideHit = movingObjectPosition.sideHit.getIndex();
BlockPos blockPos = movingObjectPosition.getBlockPos();
Vec3 endPos = new Vec3(blockPos.getX() + X_FACE_OFFSET[sideHit],
blockPos.getY() + Y_FACE_OFFSET[sideHit],
blockPos.getZ() + Z_FACE_OFFSET[sideHit]);
// return startPos.subtract(endPos);
return endPos.subtract(startPos);
}
/**
* Snaps the given vector to the closest of the six cardinal directions, or alternatively to one of the twenty "45 degree" directions (if diagonalOK == true)
* @param vectorToSnap the vector to be snapped to a cardinal direction
* @param diagonalOK if true, diagonal "45 degree" directions are allowed
* @return the cardinal direction snapped to (unit length vector), or null if input vector is null or zero.
*/
public static Vec3 snapToCardinalDirection(Vec3 vectorToSnap, boolean diagonalOK)
{
final float R2 = 0.707107F; // 1 / sqrt(2)
final float R3 = 0.577350F; // 1 / sqrt(3)
final float cardinal[][] = { {1, 0, 0}, {0, 1, 0}, {0,0,1} };
final float cardinal45[][] = { {R2, R2, 0}, {-R2, R2, 0}, {R2, 0, R2}, {R2, 0, -R2}, {0, R2, R2}, {0, R2, -R2},
{R3, R3, R3}, {R3, -R3, R3}, {R3, R3, -R3}, {R3, -R3, -R3}
};
Vec3 cardinalVector;
Vec3 closestVector = null;
double highestDotProduct = 0.0;
// use the dot product to find the closest match (highest projection of vectorToSnap onto the cardinaldirection).
// if the best match has negative dot product, it points the opposite way so reverse it
int i;
for (i=0; i < 3; ++i) {
cardinalVector = new Vec3(cardinal[i][0], cardinal[i][1], cardinal[i][2]);
double dotProduct = cardinalVector.dotProduct(vectorToSnap);
if (Math.abs(dotProduct) > Math.abs(highestDotProduct)) {
highestDotProduct = dotProduct;
closestVector = cardinalVector;
}
}
if (diagonalOK) {
for (i=0; i < 10; ++i) {
cardinalVector = new Vec3(cardinal45[i][0], cardinal45[i][1], cardinal45[i][2]);
double dotProduct = cardinalVector.dotProduct(vectorToSnap);
if (Math.abs(dotProduct) > Math.abs(highestDotProduct)) {
highestDotProduct = dotProduct;
closestVector = cardinalVector;
}
}
}
if (closestVector == null) return null;
if (highestDotProduct < 0) {
Vec3 nullVector = new Vec3(0, 0, 0);
closestVector = nullVector.subtract(closestVector);
}
return closestVector;
}
/**
* "deflects" the direction vector so that it doesn't try to penetrate a solid block.
* for example: if the vector is [0.707, -0.707, 0] and the starting block is sitting on a flat plane:
* the direction vector will be "deflected" up to [0.707, 0, 0], converted to [+1, 0, 0] return value, so
* that the direction runs along the surface of the plane
* @param world the world
* @param startingBlock - the starting block, should be non-solid (isBlockSolid == false)
* @param direction - the direction vector to be deflected.
* @param deltaPosition - the current [deltax, deltay, deltaz] where each delta is -1, 0, or 1
* @return a [deltax,deltay,deltaz] where each delta is -1, 0, or 1
*/
public static BlockPos deflectDirectionVector(World world, BlockPos startingBlock, Vec3 direction, BlockPos deltaPosition)
{
// algorithm is:
// if deltaPosition has two or three non-zero components:
// re-snap the vector to the six cardinal axes only. If it still fails, perform further deflection as for deltaPosition with one non-zero component below
// if deltaPosition has one non-zero component (is parallel to one of the six coordinate axes):
// normalise the direction vector to unit length, eliminate the deltaPosition's non-zero axis from the direction vector, verify that at least one of the other two
// components is at least 0.1, renormalise and snap the vector to the cardinal axes again.
int nonZeroCount = Math.abs(deltaPosition.getX()) + Math.abs(deltaPosition.getY()) + Math.abs(deltaPosition.getZ());
Vec3 deflectedDirection;
BlockPos deflectedDeltaPosition;
if (nonZeroCount >= 2) {
deflectedDirection = snapToCardinalDirection(direction, false);
if (deflectedDirection == null) return new BlockPos(deltaPosition);
deflectedDeltaPosition = convertToDelta(deflectedDirection);
BlockPos nextCoordinate = startingBlock.add(deflectedDeltaPosition);
if (!isBlockSolid(world, nextCoordinate)) return deflectedDeltaPosition;
} else {
deflectedDeltaPosition = new BlockPos(deltaPosition);
}
deflectedDirection = direction.normalize();
if (deflectedDeltaPosition.getX() != 0) {
deflectedDirection = deflectedDirection.subtract(deflectedDirection.xCoord, 0, 0);
} else if (deflectedDeltaPosition.getY() != 0) {
deflectedDirection = deflectedDirection.subtract(0, deflectedDirection.yCoord, 0);
} else {
deflectedDirection = deflectedDirection.subtract(0, 0, deflectedDirection.zCoord);
}
deflectedDirection = deflectedDirection.normalize();
deflectedDirection = snapToCardinalDirection(deflectedDirection, false);
if (deflectedDirection == null) return new BlockPos(deltaPosition);
deflectedDeltaPosition = convertToDelta(deflectedDirection);
return deflectedDeltaPosition;
}
/**
* Converts the unit vector to a [deltax,deltay,deltaz] where each delta is -1, 0, or 1
* @param vector - valid inputs are unit length vectors parallel to [dx, dy, dz] where d{} is -1, 0, or +1
* @return a [deltax,deltay,deltaz] where each delta is -1, 0, or 1
*/
public static BlockPos convertToDelta(Vec3 vector)
{
final float EPSILON = 0.1F;
int dx = 0;
int dy = 0;
int dz = 0;
if (vector.xCoord > EPSILON) dx = 1;
if (vector.xCoord < -EPSILON) dx = -1;
if (vector.yCoord > EPSILON) dy = 1;
if (vector.yCoord < -EPSILON) dy = -1;
if (vector.zCoord > EPSILON) dz = 1;
if (vector.zCoord < -EPSILON) dz = -1;
return new BlockPos(dx, dy, dz);
}
/**
* returns true if the block is "solid" or is water.
* Non-solid appears to correlate with "doesn't interact with a piston" i.e. getMobilityFlag == 1
* @param world the world
* @param blockLocation the [x,y,z] of the block to be checked
*/
public static boolean isBlockSolid(World world, BlockPos blockLocation)
{
if (blockLocation.getY() < 0 || blockLocation.getY() >= 256) return false;
IBlockState iBlockState = world.getBlockState(blockLocation);
Block block = iBlockState.getBlock();
if (block == Blocks.air) {
return false;
}
return (block.getMaterial() == Material.water || block.getMobilityFlag() != 1);
}
public enum BlockSolidity {AIR, WATER, NON_SOLID, SOLID};
/** check how solid this block is
* @param world
* @return
*/
public static BlockSolidity checkBlockSolidity(World world, int wx, int wy, int wz)
{
if (wy < 0 || wy >= 256) return BlockSolidity.AIR;
Block block = world.getBlockState(new BlockPos(wx, wy, wz)).getBlock();
if (block == Blocks.air) return BlockSolidity.AIR;
if (block.getMaterial() == Material.water) return BlockSolidity.WATER;
if (block.getMobilityFlag() == 1) return BlockSolidity.NON_SOLID;
return BlockSolidity.SOLID;
}
/**
* SearchPosition contains the coordinates of a block and the current direction in which to search.
*/
public static class SearchPosition
{
public SearchPosition(BlockPos initBlockPos) {
chunkCoordinates = initBlockPos;
nextSearchDirection = 0;
}
public BlockPos chunkCoordinates;
public int nextSearchDirection;
}
public static enum CollisionOptions {
STOP_WHEN_SOLID_BLOCK_REACHED, CONTINUE_THROUGH_SOLID_BLOCKS
}
}