Added target block ray trace functionality.

By: Raphfrk <raphfrk@gmail.com>
This commit is contained in:
Bukkit/Spigot 2011-02-13 17:03:57 +00:00
parent 09916f398f
commit 5ba9ae942a
4 changed files with 455 additions and 2 deletions

View file

@ -175,6 +175,26 @@ public class Location implements Cloneable {
return pitch;
}
/**
* Gets a Vector pointing in the direction that this Location is facing
*
* @return Vector
*/
public Vector getDirection() {
Vector vector = new Vector();
double rotX = this.getYaw();
double rotY = this.getPitch();
vector.setY(-Math.sin(Math.toRadians(rotY)));
double h = Math.cos(Math.toRadians(rotY));
vector.setX(-h*Math.sin(Math.toRadians(rotX)));
vector.setZ(h*Math.cos(Math.toRadians(rotX)));
return vector;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {

View file

@ -1,24 +1,72 @@
package org.bukkit.entity;
import java.util.HashSet;
import java.util.List;
import org.bukkit.block.Block;
/**
* Represents a living entity, such as a monster or player
*/
public interface LivingEntity extends Entity {
/**
* Gets the entitys health from 0-20, where 0 is dead and 20 is full
* Gets the entity's health from 0-20, where 0 is dead and 20 is full
*
* @return Health represented from 0-20
*/
public int getHealth();
/**
* Sets the entitys health from 0-20, where 0 is dead and 20 is full
* Sets the entity's health from 0-20, where 0 is dead and 20 is full
*
* @param health New health represented from 0-20
*/
public void setHealth(int health);
/**
* Gets the height of the entity's head above its Location
*
* @return Height of the entity's eyes above its Location
*/
public double getEyeHeight();
/**
* Gets the height of the entity's head above its Location
*
* @param boolean If set to true, the effects of sneaking will be ignored
* @return Height of the entity's eyes above its Location
*/
public double getEyeHeight(boolean ignoreSneaking);
/**
* Gets all blocks along the player's line of sight
* List iterates from player's position to target inclusive
*
* @param HashSet<Byte> HashSet containing all transparent block IDs. If set to null only air is considered transparent.
* @param int This is the maximum distance to scan. This may be further limited by the server, but never to less than 100 blocks.
* @return List containing all blocks along the player's line of sight
*/
public List<Block> getLineOfSight(HashSet<Byte> transparent, int maxDistance);
/**
* Gets the block that the player has targeted
*
* @param HashSet<Byte> HashSet containing all transparent block IDs. If set to null only air is considered transparent.
* @param int This is the maximum distance to scan. This may be further limited by the server, but never to less than 100 blocks.
* @return Block that the player has targeted
*/
public Block getTargetBlock(HashSet<Byte> transparent, int maxDistance);
/**
* Gets the last two blocks along the player's line of sight.
* The target block will be the last block in the list.
*
* @param HashSet<Byte> HashSet containing all transparent block IDs. If set to null only air is considered transparent.
* @param int This is the maximum distance to scan. This may be further limited by the server, but never to less than 100 blocks
* @return List containing the last 2 blocks along the player's line of sight
*/
public List<Block> getLastTwoTargetBlocks(HashSet<Byte> transparent, int maxDistance);
/**
* Throws an egg from the entity.
*/

View file

@ -0,0 +1,372 @@
package org.bukkit.util;
import org.bukkit.World;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.LivingEntity;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.lang.IllegalStateException;
import java.lang.Exception;
/**
* This class performs ray tracing and iterates along blocks on a line
*
* @author raphfrk
*/
public class BlockIterator implements Iterator<Block> {
private final World world;
private final int maxDistance;
private 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 = world.getBlockAt((int)Math.floor(startClone.getX()), (int)Math.floor(startClone.getY()), (int)Math.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;
secondError = (int)(Math.floor(secondd*gridSize));
secondStep = (int)(Math.round(secondDirection/mainDirection*gridSize));
thirdError = (int)(Math.floor(thirdd*gridSize));
thirdStep = (int)(Math.round(thirdDirection/mainDirection*gridSize));
// 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
if (secondError + secondStep <= 0) {
secondError = -secondStep + 1;
}
if (thirdError + thirdStep <= 0) {
thirdError = -thirdStep + 1;
}
Block lastBlock;
lastBlock = startBlock.getFace(reverseFace(mainFace));
if (secondError < 0) {
secondError += gridSize;
lastBlock = lastBlock.getFace(reverseFace(secondFace));
}
if (thirdError < 0) {
thirdError += gridSize;
lastBlock = lastBlock.getFace(reverseFace(thirdFace));
}
// 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 = (int)Math.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 reverseFace(BlockFace face) {
switch(face) {
case UP: return BlockFace.DOWN;
case DOWN: return BlockFace.UP;
case NORTH: return BlockFace.SOUTH;
case SOUTH: return BlockFace.NORTH;
case EAST: return BlockFace.WEST;
case WEST: return BlockFace.EAST;
default: return null;
}
}
private BlockFace getXFace(Vector direction) {
return ((direction.getX() > 0) ? BlockFace.SOUTH : BlockFace.NORTH);
}
private BlockFace getYFace(Vector direction) {
return ((direction.getY() > 0) ? BlockFace.UP : BlockFace.DOWN);
}
private BlockFace getZFace(Vector direction) {
return ((direction.getZ() > 0) ? BlockFace.WEST : BlockFace.EAST);
}
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].getFace(mainFace);
if (((long)secondStep) * ((long)thirdError) < ((long)thirdStep) * ((long)secondError)) {
blockQueue[1] = blockQueue[2].getFace(secondFace);
blockQueue[0] = blockQueue[1].getFace(thirdFace);
} else {
blockQueue[1] = blockQueue[2].getFace(thirdFace);
blockQueue[0] = blockQueue[1].getFace(secondFace);
}
thirdError -= gridSize;
secondError -= gridSize;
currentBlock = 2;
return;
} else if (secondError > 0) {
blockQueue[1] = blockQueue[0].getFace(mainFace);
blockQueue[0] = blockQueue[1].getFace(secondFace);
secondError -= gridSize;
currentBlock = 1;
return;
} else if (thirdError > 0) {
blockQueue[1] = blockQueue[0].getFace(mainFace);
blockQueue[0] = blockQueue[1].getFace(thirdFace);
thirdError -= gridSize;
currentBlock = 1;
return;
} else {
blockQueue[0] = blockQueue[0].getFace(mainFace);
currentBlock = 0;
return;
}
}
}

View file

@ -126,6 +126,19 @@ public class Vector implements Cloneable {
return this;
}
/**
* Copies another vector
*
* @param vec
* @return the same vector
*/
public Vector copy(Vector vec) {
x = vec.x;
y = vec.y;
z = vec.z;
return this;
}
/**
* Gets the magnitude of the vector, defined as sqrt(x^2+y^2+z^2). The value
* of this method is not cached and uses a costly square-root function, so