package org.bukkit.util;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.LivingEntity;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static org.bukkit.util.NumberConversions.floor;
import static org.bukkit.util.NumberConversions.round;
/**
* This class performs ray tracing and iterates along blocks on a line
*/
public class BlockIterator implements Iterator<Block>
{
private final World world;
private final int maxDistance;
private static final int gridSize = 1 << 24;
private boolean end = false;
private Block[] blockQueue = new Block[3];
private int currentBlock = 0;
private int currentDistance = 0;
private int maxDistanceInt;
private int secondError;
private int thirdError;
private int secondStep;
private int thirdStep;
private BlockFace mainFace;
private BlockFace secondFace;
private BlockFace thirdFace;
/**
* Constructs the BlockIterator
*
* @param world The world to use for tracing
* @param start A Vector giving the initial location for the trace
* @param direction A Vector pointing in the direction for the trace
* @param yOffset The trace begins vertically offset from the start vector
* by this value
* @param maxDistance This is the maximum distance in blocks for the
* trace. Setting this value above 140 may lead to problems with
* unloaded chunks. A value of 0 indicates no limit
*/
public BlockIterator(World world, Vector start, Vector direction, double yOffset, int maxDistance)
{
this.world = world;
this.maxDistance = maxDistance;
Vector startClone = start.clone();
startClone.setY(startClone.getY() + yOffset);
currentDistance = 0;
double mainDirection = 0;
double secondDirection = 0;
double thirdDirection = 0;
double mainPosition = 0;
double secondPosition = 0;
double thirdPosition = 0;
Block startBlock = this.world.getBlockAt(floor(startClone.getX()), floor(startClone.getY()), floor(startClone.getZ()));
if(getXLength(direction) > mainDirection)
{
mainFace = getXFace(direction);
mainDirection = getXLength(direction);
mainPosition = getXPosition(direction, startClone, startBlock);
secondFace = getYFace(direction);
secondDirection = getYLength(direction);
secondPosition = getYPosition(direction, startClone, startBlock);
thirdFace = getZFace(direction);
thirdDirection = getZLength(direction);
thirdPosition = getZPosition(direction, startClone, startBlock);
}
if(getYLength(direction) > mainDirection)
{
mainFace = getYFace(direction);
mainDirection = getYLength(direction);
mainPosition = getYPosition(direction, startClone, startBlock);
secondFace = getZFace(direction);
secondDirection = getZLength(direction);
secondPosition = getZPosition(direction, startClone, startBlock);
thirdFace = getXFace(direction);
thirdDirection = getXLength(direction);
thirdPosition = getXPosition(direction, startClone, startBlock);
}
if(getZLength(direction) > mainDirection)
{
mainFace = getZFace(direction);
mainDirection = getZLength(direction);
mainPosition = getZPosition(direction, startClone, startBlock);
secondFace = getXFace(direction);
secondDirection = getXLength(direction);
secondPosition = getXPosition(direction, startClone, startBlock);
thirdFace = getYFace(direction);
thirdDirection = getYLength(direction);
thirdPosition = getYPosition(direction, startClone, startBlock);
}
// trace line backwards to find intercept with plane perpendicular to the main axis
double d = mainPosition / mainDirection; // how far to hit face behind
double secondd = secondPosition - secondDirection * d;
double thirdd = thirdPosition - thirdDirection * d;
// Guarantee that the ray will pass though the start block.
// It is possible that it would miss due to rounding
// This should only move the ray by 1 grid position
secondError = floor(secondd * gridSize);
secondStep = round(secondDirection / mainDirection * gridSize);
thirdError = floor(thirdd * gridSize);
thirdStep = round(thirdDirection / mainDirection * gridSize);
if(secondError + secondStep <= 0)
{
secondError = -secondStep + 1;
}
if(thirdError + thirdStep <= 0)
{
thirdError = -thirdStep + 1;
}
Block lastBlock;
lastBlock = startBlock.getRelative(mainFace.getOppositeFace());
if(secondError < 0)
{
secondError += gridSize;
lastBlock = lastBlock.getRelative(secondFace.getOppositeFace());
}
if(thirdError < 0)
{
thirdError += gridSize;
lastBlock = lastBlock.getRelative(thirdFace.getOppositeFace());
}
// This means that when the variables are positive, it means that the coord=1 boundary has been crossed
secondError -= gridSize;
thirdError -= gridSize;
blockQueue[0] = lastBlock;
currentBlock = -1;
scan();
boolean startBlockFound = false;
for(int cnt = currentBlock; cnt >= 0; cnt--)
{
if(blockEquals(blockQueue[cnt], startBlock))
{
currentBlock = cnt;
startBlockFound = true;
break;
}
}
if(!startBlockFound)
{
throw new IllegalStateException("Start block missed in BlockIterator");
}
// Calculate the number of planes passed to give max distance
maxDistanceInt = round(maxDistance / (Math.sqrt(mainDirection * mainDirection + secondDirection * secondDirection + thirdDirection * thirdDirection) / mainDirection));
}
private boolean blockEquals(Block a, Block b)
{
return a.getX() == b.getX() && a.getY() == b.getY() && a.getZ() == b.getZ();
}
private BlockFace getXFace(Vector direction)
{
return ((direction.getX() > 0) ? BlockFace.EAST : BlockFace.WEST);
}
private BlockFace getYFace(Vector direction)
{
return ((direction.getY() > 0) ? BlockFace.UP : BlockFace.DOWN);
}
private BlockFace getZFace(Vector direction)
{
return ((direction.getZ() > 0) ? BlockFace.SOUTH : BlockFace.NORTH);
}
private double getXLength(Vector direction)
{
return Math.abs(direction.getX());
}
private double getYLength(Vector direction)
{
return Math.abs(direction.getY());
}
private double getZLength(Vector direction)
{
return Math.abs(direction.getZ());
}
private double getPosition(double direction, double position, int blockPosition)
{
return direction > 0 ? (position - blockPosition) : (blockPosition + 1 - position);
}
private double getXPosition(Vector direction, Vector position, Block block)
{
return getPosition(direction.getX(), position.getX(), block.getX());
}
private double getYPosition(Vector direction, Vector position, Block block)
{
return getPosition(direction.getY(), position.getY(), block.getY());
}
private double getZPosition(Vector direction, Vector position, Block block)
{
return getPosition(direction.getZ(), position.getZ(), block.getZ());
}
/**
* Constructs the BlockIterator
*
* @param loc The location for the start of the ray trace
* @param yOffset The trace begins vertically offset from the start vector
* by this value
* @param maxDistance This is the maximum distance in blocks for the
* trace. Setting this value above 140 may lead to problems with
* unloaded chunks. A value of 0 indicates no limit
*/
public BlockIterator(Location loc, double yOffset, int maxDistance)
{
this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, maxDistance);
}
/**
* Constructs the BlockIterator.
*
* @param loc The location for the start of the ray trace
* @param yOffset The trace begins vertically offset from the start vector
* by this value
*/
public BlockIterator(Location loc, double yOffset)
{
this(loc.getWorld(), loc.toVector(), loc.getDirection(), yOffset, 0);
}
/**
* Constructs the BlockIterator.
*
* @param loc The location for the start of the ray trace
*/
public BlockIterator(Location loc)
{
this(loc, 0D);
}
/**
* Constructs the BlockIterator.
*
* @param entity Information from the entity is used to set up the trace
* @param maxDistance This is the maximum distance in blocks for the
* trace. Setting this value above 140 may lead to problems with
* unloaded chunks. A value of 0 indicates no limit
*/
public BlockIterator(LivingEntity entity, int maxDistance)
{
this(entity.getLocation(), entity.getEyeHeight(), maxDistance);
}
/**
* Constructs the BlockIterator.
*
* @param entity Information from the entity is used to set up the trace
*/
public BlockIterator(LivingEntity entity)
{
this(entity, 0);
}
/**
* Returns true if the iteration has more elements
*/
public boolean hasNext()
{
scan();
return currentBlock != -1;
}
/**
* Returns the next Block in the trace
*
* @return the next Block in the trace
*/
public Block next()
{
scan();
if(currentBlock <= -1)
{
throw new NoSuchElementException();
}
else
{
return blockQueue[currentBlock--];
}
}
public void remove()
{
throw new UnsupportedOperationException("[BlockIterator] doesn't support block removal");
}
private void scan()
{
if(currentBlock >= 0)
{
return;
}
if(maxDistance != 0 && currentDistance > maxDistanceInt)
{
end = true;
return;
}
if(end)
{
return;
}
currentDistance++;
secondError += secondStep;
thirdError += thirdStep;
if(secondError > 0 && thirdError > 0)
{
blockQueue[2] = blockQueue[0].getRelative(mainFace);
if(((long) secondStep) * ((long) thirdError) < ((long) thirdStep) * ((long) secondError))
{
blockQueue[1] = blockQueue[2].getRelative(secondFace);
blockQueue[0] = blockQueue[1].getRelative(thirdFace);
}
else
{
blockQueue[1] = blockQueue[2].getRelative(thirdFace);
blockQueue[0] = blockQueue[1].getRelative(secondFace);
}
thirdError -= gridSize;
secondError -= gridSize;
currentBlock = 2;
return;
}
else if(secondError > 0)
{
blockQueue[1] = blockQueue[0].getRelative(mainFace);
blockQueue[0] = blockQueue[1].getRelative(secondFace);
secondError -= gridSize;
currentBlock = 1;
return;
}
else if(thirdError > 0)
{
blockQueue[1] = blockQueue[0].getRelative(mainFace);
blockQueue[0] = blockQueue[1].getRelative(thirdFace);
thirdError -= gridSize;
currentBlock = 1;
return;
}
else
{
blockQueue[0] = blockQueue[0].getRelative(mainFace);
currentBlock = 0;
return;
}
}
}