Add ray tracing and bounding box API

By: blablubbabc <lukas@wirsindwir.de>
This commit is contained in:
Bukkit/Spigot 2018-10-26 19:59:36 +11:00
parent 0283ef18f5
commit 55523cfcfc
12 changed files with 1770 additions and 14 deletions

View File

@ -0,0 +1,20 @@
package org.bukkit;
/**
* Determines the collision behavior when fluids get hit during ray tracing.
*/
public enum FluidCollisionMode {
/**
* Ignore fluids.
*/
NEVER,
/**
* Only collide with source fluid blocks.
*/
SOURCE_ONLY,
/**
* Collide with all fluids.
*/
ALWAYS;
}

View File

@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
@ -17,7 +18,9 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.metadata.Metadatable;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Consumer;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
/**
@ -425,18 +428,232 @@ public interface World extends PluginMessageRecipient, Metadatable {
public List<Player> getPlayers();
/**
* Returns a list of entities within a bounding box centered around a Location.
*
* Some implementations may impose artificial restrictions on the size of the search bounding box.
* Returns a list of entities within a bounding box centered around a
* Location.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the size of the
* search bounding box.
*
* @param location The center of the bounding box
* @param x 1/2 the size of the box along x axis
* @param y 1/2 the size of the box along y axis
* @param z 1/2 the size of the box along z axis
* @return the collection of entities near location. This will always be a non-null collection.
* @return the collection of entities near location. This will always be a
* non-null collection.
*/
public Collection<Entity> getNearbyEntities(Location location, double x, double y, double z);
/**
* Returns a list of entities within a bounding box centered around a
* Location.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the size of the
* search bounding box.
*
* @param location The center of the bounding box
* @param x 1/2 the size of the box along x axis
* @param y 1/2 the size of the box along y axis
* @param z 1/2 the size of the box along z axis
* @param filter only entities that fulfill this predicate are considered,
* or <code>null</code> to consider all entities
* @return the collection of entities near location. This will always be a
* non-null collection.
*/
public Collection<Entity> getNearbyEntities(Location location, double x, double y, double z, Predicate<Entity> filter);
/**
* Returns a list of entities within the given bounding box.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the size of the
* search bounding box.
*
* @param boundingBox the bounding box
* @return the collection of entities within the bounding box, will always
* be a non-null collection
*/
public Collection<Entity> getNearbyEntities(BoundingBox boundingBox);
/**
* Returns a list of entities within the given bounding box.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the size of the
* search bounding box.
*
* @param boundingBox the bounding box
* @param filter only entities that fulfill this predicate are considered,
* or <code>null</code> to consider all entities
* @return the collection of entities within the bounding box, will always
* be a non-null collection
*/
public Collection<Entity> getNearbyEntities(BoundingBox boundingBox, Predicate<Entity> filter);
/**
* Performs a ray trace that checks for entity collisions.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the maximum
* distance.
*
* @param start the start position
* @param direction the ray direction
* @param maxDistance the maximum distance
* @return the closest ray trace hit result, or <code>null</code> if there
* is no hit
* @see #rayTraceEntities(Location, Vector, double, double, Predicate)
*/
public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance);
/**
* Performs a ray trace that checks for entity collisions.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the maximum
* distance.
*
* @param start the start position
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param raySize entity bounding boxes will be uniformly expanded (or
* shrinked) by this value before doing collision checks
* @return the closest ray trace hit result, or <code>null</code> if there
* is no hit
* @see #rayTraceEntities(Location, Vector, double, double, Predicate)
*/
public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize);
/**
* Performs a ray trace that checks for entity collisions.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the maximum
* distance.
*
* @param start the start position
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param filter only entities that fulfill this predicate are considered,
* or <code>null</code> to consider all entities
* @return the closest ray trace hit result, or <code>null</code> if there
* is no hit
* @see #rayTraceEntities(Location, Vector, double, double, Predicate)
*/
public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, Predicate<Entity> filter);
/**
* Performs a ray trace that checks for entity collisions.
* <p>
* This may not consider entities in currently unloaded chunks. Some
* implementations may impose artificial restrictions on the maximum
* distance.
*
* @param start the start position
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param raySize entity bounding boxes will be uniformly expanded (or
* shrinked) by this value before doing collision checks
* @param filter only entities that fulfill this predicate are considered,
* or <code>null</code> to consider all entities
* @return the closest ray trace hit result, or <code>null</code> if there
* is no hit
*/
public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate<Entity> filter);
/**
* Performs a ray trace that checks for block collisions using the blocks'
* precise collision shapes.
* <p>
* This takes collisions with passable blocks into account, but ignores
* fluids.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param start the start location
* @param direction the ray direction
* @param maxDistance the maximum distance
* @return the ray trace hit result, or <code>null</code> if there is no hit
* @see #rayTraceBlocks(Location, Vector, double, FluidCollisionMode, boolean)
*/
public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance);
/**
* Performs a ray trace that checks for block collisions using the blocks'
* precise collision shapes.
* <p>
* This takes collisions with passable blocks into account.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param start the start location
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param fluidCollisionMode the fluid collision mode
* @return the ray trace hit result, or <code>null</code> if there is no hit
* @see #rayTraceBlocks(Location, Vector, double, FluidCollisionMode, boolean)
*/
public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode);
/**
* Performs a ray trace that checks for block collisions using the blocks'
* precise collision shapes.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within
* them. Apart from that collisions with portal blocks will be considered
* even if collisions with passable blocks are otherwise ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param start the start location
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param fluidCollisionMode the fluid collision mode
* @param ignorePassableBlocks whether to ignore passable but collidable
* blocks (ex. tall grass, signs, fluids, ..)
* @return the ray trace hit result, or <code>null</code> if there is no hit
*/
public RayTraceResult rayTraceBlocks(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks);
/**
* Performs a ray trace that checks for both block and entity collisions.
* <p>
* Block collisions use the blocks' precise collision shapes. The
* <code>raySize</code> parameter is only taken into account for entity
* collision checks.
* <p>
* If collisions with passable blocks are ignored, fluid collisions are
* ignored as well regardless of the fluid collision mode.
* <p>
* Portal blocks are only considered passable if the ray starts within them.
* Apart from that collisions with portal blocks will be considered even if
* collisions with passable blocks are otherwise ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param start the start location
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param fluidCollisionMode the fluid collision mode
* @param ignorePassableBlocks whether to ignore passable but collidable
* blocks (ex. tall grass, signs, fluids, ..)
* @param raySize entity bounding boxes will be uniformly expanded (or
* shrinked) by this value before doing collision checks
* @param filter only entities that fulfill this predicate are considered,
* or <code>null</code> to consider all entities
* @return the closest ray trace hit result with either a block or an
* entity, or <code>null</code> if there is no hit
*/
public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate<Entity> filter);
/**
* Gets the unique name of this world
*

View File

@ -3,12 +3,15 @@ package org.bukkit.block;
import java.util.Collection;
import org.bukkit.Chunk;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.Location;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.ItemStack;
import org.bukkit.metadata.Metadatable;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
/**
* Represents a block. This is a live object, and only one Block may exist for
@ -369,4 +372,16 @@ public interface Block extends Metadatable {
* @return <code>true</code> if passable
*/
boolean isPassable();
/**
* Performs a ray trace that checks for collision with this specific block
* in its current state using its precise collision shape.
*
* @param start the start location
* @param direction the ray direction
* @param maxDistance the maximum distance
* @param fluidCollisionMode the fluid collision mode
* @return the ray trace hit result, or <code>null</code> if there is no hit
*/
RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode);
}

View File

@ -1,5 +1,7 @@
package org.bukkit.block;
import org.bukkit.util.Vector;
/**
* Represents the face of a block
*/
@ -67,6 +69,19 @@ public enum BlockFace {
return modZ;
}
/**
* Gets the normal vector corresponding to this block face.
*
* @return the normal vector
*/
public Vector getDirection() {
Vector direction = new Vector(modX, modY, modZ);
if (modX != 0 || modY != 0 || modZ != 0) {
direction.normalize();
}
return direction;
}
public BlockFace getOppositeFace() {
switch (this) {
case NORTH:

View File

@ -18,6 +18,7 @@ import org.bukkit.block.banner.Pattern;
import org.bukkit.configuration.Configuration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;
@ -39,6 +40,7 @@ public class ConfigurationSerialization {
registerClass(Pattern.class);
registerClass(Location.class);
registerClass(AttributeModifier.class);
registerClass(BoundingBox.class);
}
protected ConfigurationSerialization(Class<? extends ConfigurationSerializable> clazz) {

View File

@ -9,6 +9,7 @@ import org.bukkit.block.BlockFace;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.material.Directional;
import org.bukkit.metadata.Metadatable;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import java.util.List;
@ -69,6 +70,16 @@ public interface Entity extends Metadatable, CommandSender, Nameable {
*/
public double getWidth();
/**
* Gets the entity's current bounding box.
* <p>
* The returned bounding box reflects the entity's current location and
* size.
*
* @return the entity's current bounding box
*/
public BoundingBox getBoundingBox();
/**
* Returns true if the entity is supported by a block. This value is a
* state updated by the server and is not recalculated unless the entity

View File

@ -5,14 +5,18 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.attribute.Attributable;
import org.bukkit.block.Block;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.projectiles.ProjectileSource;
import org.bukkit.util.RayTraceResult;
import org.bukkit.util.Vector;
/**
* Represents a living entity, such as a monster or player
@ -46,7 +50,7 @@ public interface LivingEntity extends Attributable, Entity, Damageable, Projecti
* Gets all blocks along the living entity's line of sight.
* <p>
* This list contains all blocks from the living entity's eye position to
* target inclusive.
* target inclusive. This method considers all blocks as 1x1x1 in size.
*
* @param transparent HashSet containing all transparent block Materials (set to
* null for only air)
@ -59,6 +63,10 @@ public interface LivingEntity extends Attributable, Entity, Damageable, Projecti
/**
* Gets the block that the living entity has targeted.
* <p>
* This method considers all blocks as 1x1x1 in size. To take exact block
* collision shapes into account, see {@link #getTargetBlockExact(int,
* FluidCollisionMode)}.
*
* @param transparent HashSet containing all transparent block Materials (set to
* null for only air)
@ -71,7 +79,8 @@ public interface LivingEntity extends Attributable, Entity, Damageable, Projecti
/**
* Gets the last two blocks along the living entity's line of sight.
* <p>
* The target block will be the last block in the list.
* The target block will be the last block in the list. This method
* considers all blocks as 1x1x1 in size.
*
* @param transparent HashSet containing all transparent block Materials (set to
* null for only air)
@ -82,6 +91,68 @@ public interface LivingEntity extends Attributable, Entity, Damageable, Projecti
*/
public List<Block> getLastTwoTargetBlocks(Set<Material> transparent, int maxDistance);
/**
* Gets the block that the living entity has targeted.
* <p>
* This takes the blocks' precise collision shapes into account. Fluids are
* ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param maxDistance the maximum distance to scan
* @return block that the living entity has targeted
* @see #getTargetBlockExact(int, org.bukkit.FluidCollisionMode)
*/
public Block getTargetBlockExact(int maxDistance);
/**
* Gets the block that the living entity has targeted.
* <p>
* This takes the blocks' precise collision shapes into account.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param maxDistance the maximum distance to scan
* @param fluidCollisionMode the fluid collision mode
* @return block that the living entity has targeted
* @see #rayTraceBlocks(double, FluidCollisionMode)
*/
public Block getTargetBlockExact(int maxDistance, FluidCollisionMode fluidCollisionMode);
/**
* Performs a ray trace that provides information on the targeted block.
* <p>
* This takes the blocks' precise collision shapes into account. Fluids are
* ignored.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param maxDistance the maximum distance to scan
* @return information on the targeted block, or <code>null</code> if there
* is no targeted block in range
* @see #rayTraceBlocks(double, FluidCollisionMode)
*/
public RayTraceResult rayTraceBlocks(double maxDistance);
/**
* Performs a ray trace that provides information on the targeted block.
* <p>
* This takes the blocks' precise collision shapes into account.
* <p>
* This may cause loading of chunks! Some implementations may impose
* artificial restrictions on the maximum distance.
*
* @param maxDistance the maximum distance to scan
* @param fluidCollisionMode the fluid collision mode
* @return information on the targeted block, or <code>null</code> if there
* is no targeted block in range
* @see World#rayTraceBlocks(Location, Vector, double, FluidCollisionMode)
*/
public RayTraceResult rayTraceBlocks(double maxDistance, FluidCollisionMode fluidCollisionMode);
/**
* Returns the amount of air that the living entity has remaining, in
* ticks.

View File

@ -39,7 +39,9 @@ public class BlockIterator implements Iterator<Block> {
private BlockFace thirdFace;
/**
* Constructs the BlockIterator
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param world The world to use for tracing
* @param start A Vector giving the initial location for the trace
@ -220,7 +222,9 @@ public class BlockIterator implements Iterator<Block> {
}
/**
* Constructs the BlockIterator
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param loc The location for the start of the ray trace
* @param yOffset The trace begins vertically offset from the start vector
@ -235,6 +239,8 @@ public class BlockIterator implements Iterator<Block> {
/**
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param loc The location for the start of the ray trace
* @param yOffset The trace begins vertically offset from the start vector
@ -247,6 +253,8 @@ public class BlockIterator implements Iterator<Block> {
/**
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param loc The location for the start of the ray trace
*/
@ -257,6 +265,8 @@ public class BlockIterator implements Iterator<Block> {
/**
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param entity Information from the entity is used to set up the trace
* @param maxDistance This is the maximum distance in blocks for the
@ -270,6 +280,8 @@ public class BlockIterator implements Iterator<Block> {
/**
* Constructs the BlockIterator.
* <p>
* This considers all blocks as 1x1x1 in size.
*
* @param entity Information from the entity is used to set up the trace
*/

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,157 @@
package org.bukkit.util;
import java.util.Objects;
import org.apache.commons.lang.Validate;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
/**
* The hit result of a ray trace.
* <p>
* Only the hit position is guaranteed to always be available. The availability
* of the other attributes depends on what got hit and on the context in which
* the ray trace was performed.
*/
public class RayTraceResult {
private final Vector hitPosition;
private final Block hitBlock;
private final BlockFace hitBlockFace;
private final Entity hitEntity;
private RayTraceResult(Vector hitPosition, Block hitBlock, BlockFace hitBlockFace, Entity hitEntity) {
Validate.notNull(hitPosition, "Hit position is null!");
this.hitPosition = hitPosition.clone();
this.hitBlock = hitBlock;
this.hitBlockFace = hitBlockFace;
this.hitEntity = hitEntity;
}
/**
* Creates a RayTraceResult.
*
* @param hitPosition the hit position
*/
public RayTraceResult(Vector hitPosition) {
this(hitPosition, null, null, null);
}
/**
* Creates a RayTraceResult.
*
* @param hitPosition the hit position
* @param hitBlockFace the hit block face
*/
public RayTraceResult(Vector hitPosition, BlockFace hitBlockFace) {
this(hitPosition, null, hitBlockFace, null);
}
/**
* Creates a RayTraceResult.
*
* @param hitPosition the hit position
* @param hitBlock the hit block
* @param hitBlockFace the hit block face
*/
public RayTraceResult(Vector hitPosition, Block hitBlock, BlockFace hitBlockFace) {
this(hitPosition, hitBlock, hitBlockFace, null);
}
/**
* Creates a RayTraceResult.
*
* @param hitPosition the hit position
* @param hitEntity the hit entity
*/
public RayTraceResult(Vector hitPosition, Entity hitEntity) {
this(hitPosition, null, null, hitEntity);
}
/**
* Creates a RayTraceResult.
*
* @param hitPosition the hit position
* @param hitEntity the hit entity
* @param hitBlockFace the hit block face
*/
public RayTraceResult(Vector hitPosition, Entity hitEntity, BlockFace hitBlockFace) {
this(hitPosition, null, hitBlockFace, hitEntity);
}
/**
* Gets the exact position of the hit.
*
* @return a copy of the exact hit position
*/
public Vector getHitPosition() {
return hitPosition.clone();
}
/**
* Gets the hit block.
*
* @return the hit block, or <code>null</code> if not available
*/
public Block getHitBlock() {
return hitBlock;
}
/**
* Gets the hit block face.
*
* @return the hit block face, or <code>null</code> if not available
*/
public BlockFace getHitBlockFace() {
return hitBlockFace;
}
/**
* Gets the hit entity.
*
* @return the hit entity, or <code>null</code> if not available
*/
public Entity getHitEntity() {
return hitEntity;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + hitPosition.hashCode();
result = prime * result + ((hitBlock == null) ? 0 : hitBlock.hashCode());
result = prime * result + ((hitBlockFace == null) ? 0 : hitBlockFace.hashCode());
result = prime * result + ((hitEntity == null) ? 0 : hitEntity.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof RayTraceResult)) return false;
RayTraceResult other = (RayTraceResult) obj;
if (!hitPosition.equals(other.hitPosition)) return false;
if (!Objects.equals(hitBlock, other.hitBlock)) return false;
if (!Objects.equals(hitBlockFace, other.hitBlockFace)) return false;
if (!Objects.equals(hitEntity, other.hitEntity)) return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("RayTraceResult [hitPosition=");
builder.append(hitPosition);
builder.append(", hitBlock=");
builder.append(hitBlock);
builder.append(", hitBlockFace=");
builder.append(hitBlockFace);
builder.append(", hitEntity=");
builder.append(hitEntity);
builder.append("]");
return builder.toString();
}
}

View File

@ -17,7 +17,7 @@ import com.google.common.collect.ImmutableList;
@RunWith(Parameterized.class)
public class LocationTest {
private static final double δ = 1.0 / 1000000;
private static final double delta = 1.0 / 1000000;
/**
* <pre>
* a² + b² = c², a = b
@ -166,17 +166,17 @@ public class LocationTest {
public void testExpectedPitchYaw() {
Location location = getEmptyLocation().setDirection(getVector());
assertThat((double) location.getYaw(), is(closeTo(yaw, δ)));
assertThat((double) location.getPitch(), is(closeTo(pitch, δ)));
assertThat((double) location.getYaw(), is(closeTo(yaw, delta)));
assertThat((double) location.getPitch(), is(closeTo(pitch, delta)));
}
@Test
public void testExpectedXYZ() {
Vector vector = getLocation().getDirection();
assertThat(vector.getX(), is(closeTo(x, δ)));
assertThat(vector.getY(), is(closeTo(y, δ)));
assertThat(vector.getZ(), is(closeTo(z, δ)));
assertThat(vector.getX(), is(closeTo(x, delta)));
assertThat(vector.getY(), is(closeTo(y, delta)));
assertThat(vector.getZ(), is(closeTo(z, delta)));
}
private Vector getVector() {

View File

@ -0,0 +1,206 @@
package org.bukkit.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Map;
import org.bukkit.Location;
import org.bukkit.block.BlockFace;
import org.junit.Test;
public class BoundingBoxTest {
private static final double delta = 1.0 / 1000000;
@Test
public void testConstruction() {
BoundingBox expected = new BoundingBox(-1, -1, -1, 1, 2, 3);
assertThat(expected.getMin(), is(new Vector(-1, -1, -1)));
assertThat(expected.getMax(), is(new Vector(1, 2, 3)));
assertThat(expected.getCenter(), is(new Vector(0.0D, 0.5D, 1.0D)));
assertThat(expected.getWidthX(), is(2.0D));
assertThat(expected.getHeight(), is(3.0D));
assertThat(expected.getWidthZ(), is(4.0D));
assertThat(expected.getVolume(), is(24.0D));
assertThat(BoundingBox.of(new Vector(-1, -1, -1), new Vector(1, 2, 3)), is(expected));
assertThat(BoundingBox.of(new Vector(1, 2, 3), new Vector(-1, -1, -1)), is(expected));
assertThat(BoundingBox.of(new Location(null, -1, -1, -1), new Location(null, 1, 2, 3)), is(expected));
assertThat(BoundingBox.of(new Vector(0.0D, 0.5D, 1.0D), 1.0D, 1.5D, 2.0D), is(expected));
assertThat(BoundingBox.of(new Location(null, 0.0D, 0.5D, 1.0D), 1.0D, 1.5D, 2.0D), is(expected));
}
@Test
public void testContains() {
BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 2, 3);
assertThat(aabb.contains(-0.5D, 0.0D, 0.5D), is(true));
assertThat(aabb.contains(-1.0D, -1.0D, -1.0D), is(true));
assertThat(aabb.contains(1.0D, 2.0D, 3.0D), is(false));
assertThat(aabb.contains(-1.0D, 1.0D, 4.0D), is(false));
assertThat(aabb.contains(new Vector(-0.5D, 0.0D, 0.5D)), is(true));
assertThat(aabb.contains(new BoundingBox(-0.5D, -0.5D, -0.5D, 0.5D, 1.0D, 2.0D)), is(true));
assertThat(aabb.contains(aabb), is(true));
assertThat(aabb.contains(new BoundingBox(-1, -1, -1, 1, 1, 3)), is(true));
assertThat(aabb.contains(new BoundingBox(-2, -1, -1, 1, 2, 3)), is(false));
assertThat(aabb.contains(new Vector(-0.5D, -0.5D, -0.5D), new Vector(0.5D, 1.0D, 2.0D)), is(true));
}
@Test
public void testOverlaps() {
BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 2, 3);
assertThat(aabb.contains(aabb), is(true));
assertThat(aabb.overlaps(new BoundingBox(-2, -2, -2, 0, 0, 0)), is(true));
assertThat(aabb.overlaps(new BoundingBox(0.5D, 1.5D, 2.5D, 1, 2, 3)), is(true));
assertThat(aabb.overlaps(new BoundingBox(0.5D, 1.5D, 2.5D, 2, 3, 4)), is(true));
assertThat(aabb.overlaps(new BoundingBox(-2, -2, -2, -1, -1, -1)), is(false));
assertThat(aabb.overlaps(new BoundingBox(1, 2, 3, 2, 3, 4)), is(false));
assertThat(aabb.overlaps(new Vector(0.5D, 1.5D, 2.5D), new Vector(1, 2, 3)), is(true));
}
@Test
public void testDegenerate() {
BoundingBox aabb = new BoundingBox(0, 0, 0, 0, 0, 0);
assertThat(aabb.getWidthX(), is(0.0D));
assertThat(aabb.getHeight(), is(0.0D));
assertThat(aabb.getWidthZ(), is(0.0D));
assertThat(aabb.getVolume(), is(0.0D));
}
@Test
public void testShift() {
BoundingBox aabb = new BoundingBox(0, 0, 0, 1, 1, 1);
assertThat(aabb.clone().shift(1, 2, 3), is(new BoundingBox(1, 2, 3, 2, 3, 4)));
assertThat(aabb.clone().shift(-1, -2, -3), is(new BoundingBox(-1, -2, -3, 0, -1, -2)));
assertThat(aabb.clone().shift(new Vector(1, 2, 3)), is(new BoundingBox(1, 2, 3, 2, 3, 4)));
assertThat(aabb.clone().shift(new Location(null, 1, 2, 3)), is(new BoundingBox(1, 2, 3, 2, 3, 4)));
}
@Test
public void testUnion() {
BoundingBox aabb1 = new BoundingBox(0, 0, 0, 1, 1, 1);
assertThat(aabb1.clone().union(new BoundingBox(-2, -2, -2, -1, -1, -1)), is(new BoundingBox(-2, -2, -2, 1, 1, 1)));
assertThat(aabb1.clone().union(1, 2, 3), is(new BoundingBox(0, 0, 0, 1, 2, 3)));
assertThat(aabb1.clone().union(new Vector(1, 2, 3)), is(new BoundingBox(0, 0, 0, 1, 2, 3)));
assertThat(aabb1.clone().union(new Location(null, 1, 2, 3)), is(new BoundingBox(0, 0, 0, 1, 2, 3)));
}
@Test
public void testIntersection() {
BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 2, 3);
assertThat(aabb.clone().intersection(new BoundingBox(-2, -2, -2, 4, 4, 4)), is(aabb));
assertThat(aabb.clone().intersection(new BoundingBox(-2, -2, -2, 1, 1, 1)), is(new BoundingBox(-1, -1, -1, 1, 1, 1)));
}
@Test
public void testExpansion() {
BoundingBox aabb = new BoundingBox(0, 0, 0, 2, 2, 2);
assertThat(aabb.clone().expand(1, 2, 3, 1, 2, 3), is(new BoundingBox(-1, -2, -3, 3, 4, 5)));
assertThat(aabb.clone().expand(-1, -2, -3, 1, 2, 3), is(new BoundingBox(1, 2, 3, 3, 4, 5)));
assertThat(aabb.clone().expand(1, 2, 3, -1, -2, -3), is(new BoundingBox(-1, -2, -3, 1, 0, -1)));
assertThat(aabb.clone().expand(-1, -2, -3, -0.5D, -0.5, -3), is(new BoundingBox(1, 1.5D, 1, 1.5D, 1.5D, 1)));
assertThat(aabb.clone().expand(1, 2, 3), is(new BoundingBox(-1, -2, -3, 3, 4, 5)));
assertThat(aabb.clone().expand(-0.1, -0.5, -2), is(new BoundingBox(0.1D, 0.5D, 1, 1.9D, 1.5D, 1)));
assertThat(aabb.clone().expand(new Vector(1, 2, 3)), is(new BoundingBox(-1, -2, -3, 3, 4, 5)));
assertThat(aabb.clone().expand(1), is(new BoundingBox(-1, -1, -1, 3, 3, 3)));
assertThat(aabb.clone().expand(-0.5D), is(new BoundingBox(0.5D, 0.5D, 0.5D, 1.5D, 1.5D, 1.5D)));
assertThat(aabb.clone().expand(1, 0, 0, 0.5D), is(new BoundingBox(0, 0, 0, 2.5D, 2, 2)));
assertThat(aabb.clone().expand(1, 0, 0, -0.5D), is(new BoundingBox(0, 0, 0, 1.5D, 2, 2)));
assertThat(aabb.clone().expand(-1, 0, 0, 0.5D), is(new BoundingBox(-0.5D, 0, 0, 2, 2, 2)));
assertThat(aabb.clone().expand(-1, 0, 0, -0.5D), is(new BoundingBox(0.5D, 0, 0, 2, 2, 2)));
assertThat(aabb.clone().expand(0, 1, 0, 0.5D), is(new BoundingBox(0, 0, 0, 2, 2.5D, 2)));
assertThat(aabb.clone().expand(0, 1, 0, -0.5D), is(new BoundingBox(0, 0, 0, 2, 1.5D, 2)));
assertThat(aabb.clone().expand(0, -1, 0, 0.5D), is(new BoundingBox(0, -0.5D, 0, 2, 2, 2)));
assertThat(aabb.clone().expand(0, -1, 0, -0.5D), is(new BoundingBox(0, 0.5D, 0, 2, 2, 2)));
assertThat(aabb.clone().expand(0, 0, 1, 0.5D), is(new BoundingBox(0, 0, 0, 2, 2, 2.5D)));
assertThat(aabb.clone().expand(0, 0, 1, -0.5D), is(new BoundingBox(0, 0, 0, 2, 2, 1.5D)));
assertThat(aabb.clone().expand(0, 0, -1, 0.5D), is(new BoundingBox(0, 0, -0.5D, 2, 2, 2)));
assertThat(aabb.clone().expand(0, 0, -1, -0.5D), is(new BoundingBox(0, 0, 0.5D, 2, 2, 2)));
assertThat(aabb.clone().expand(new Vector(1, 0, 0), 0.5D), is(new BoundingBox(0, 0, 0, 2.5D, 2, 2)));
assertThat(aabb.clone().expand(BlockFace.EAST, 0.5D), is(new BoundingBox(0, 0, 0, 2.5D, 2, 2)));
assertThat(aabb.clone().expand(BlockFace.NORTH_NORTH_WEST, 1.0D), is(aabb.clone().expand(BlockFace.NORTH_NORTH_WEST.getDirection(), 1.0D)));
assertThat(aabb.clone().expand(BlockFace.SELF, 1.0D), is(aabb));
BoundingBox expanded = aabb.clone().expand(BlockFace.NORTH_WEST, 1.0D);
assertThat(expanded.getWidthX(), is(closeTo(aabb.getWidthX() + Math.sqrt(0.5D), delta)));
assertThat(expanded.getWidthZ(), is(closeTo(aabb.getWidthZ() + Math.sqrt(0.5D), delta)));
assertThat(expanded.getHeight(), is(aabb.getHeight()));
assertThat(aabb.clone().expandDirectional(1, 2, 3), is(new BoundingBox(0, 0, 0, 3, 4, 5)));
assertThat(aabb.clone().expandDirectional(-1, -2, -3), is(new BoundingBox(-1, -2, -3, 2, 2, 2)));
assertThat(aabb.clone().expandDirectional(new Vector(1, 2, 3)), is(new BoundingBox(0, 0, 0, 3, 4, 5)));
}
@Test
public void testRayTrace() {
BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 1, 1);
assertThat(aabb.rayTrace(new Vector(-2, 0, 0), new Vector(1, 0, 0), 10),
is(new RayTraceResult(new Vector(-1, 0, 0), BlockFace.WEST)));
assertThat(aabb.rayTrace(new Vector(2, 0, 0), new Vector(-1, 0, 0), 10),
is(new RayTraceResult(new Vector(1, 0, 0), BlockFace.EAST)));
assertThat(aabb.rayTrace(new Vector(0, -2, 0), new Vector(0, 1, 0), 10),
is(new RayTraceResult(new Vector(0, -1, 0), BlockFace.DOWN)));
assertThat(aabb.rayTrace(new Vector(0, 2, 0), new Vector(0, -1, 0), 10),
is(new RayTraceResult(new Vector(0, 1, 0), BlockFace.UP)));
assertThat(aabb.rayTrace(new Vector(0, 0, -2), new Vector(0, 0, 1), 10),
is(new RayTraceResult(new Vector(0, 0, -1), BlockFace.NORTH)));
assertThat(aabb.rayTrace(new Vector(0, 0, 2), new Vector(0, 0, -1), 10),
is(new RayTraceResult(new Vector(0, 0, 1), BlockFace.SOUTH)));
assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(1, 0, 0), 10),
is(new RayTraceResult(new Vector(1, 0, 0), BlockFace.EAST)));
assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(-1, 0, 0), 10),
is(new RayTraceResult(new Vector(-1, 0, 0), BlockFace.WEST)));
assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, 1, 0), 10),
is(new RayTraceResult(new Vector(0, 1, 0), BlockFace.UP)));
assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, -1, 0), 10),
is(new RayTraceResult(new Vector(0, -1, 0), BlockFace.DOWN)));
assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, 0, 1), 10),
is(new RayTraceResult(new Vector(0, 0, 1), BlockFace.SOUTH)));
assertThat(aabb.rayTrace(new Vector(0, 0, 0), new Vector(0, 0, -1), 10),
is(new RayTraceResult(new Vector(0, 0, -1), BlockFace.NORTH)));
assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(1, 0, 0), 10), is(nullValue()));
assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(0, 1, 0), 10), is(nullValue()));
assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(0, 0, 1), 10), is(nullValue()));
assertThat(aabb.rayTrace(new Vector(0, 0, -3), new Vector(1, 0, 1), 10), is(nullValue()));
assertThat(aabb.rayTrace(new Vector(0, 0, -2), new Vector(1, 0, 2), 10),
is(new RayTraceResult(new Vector(0.5D, 0, -1), BlockFace.NORTH)));
// corner/edge hits yield unspecified block face:
assertThat(aabb.rayTrace(new Vector(2, 2, 2), new Vector(-1, -1, -1), 10),
anyOf(is(new RayTraceResult(new Vector(1, 1, 1), BlockFace.EAST)),
is(new RayTraceResult(new Vector(1, 1, 1), BlockFace.UP)),
is(new RayTraceResult(new Vector(1, 1, 1), BlockFace.SOUTH))));
assertThat(aabb.rayTrace(new Vector(-2, -2, -2), new Vector(1, 1, 1), 10),
anyOf(is(new RayTraceResult(new Vector(-1, -1, -1), BlockFace.WEST)),
is(new RayTraceResult(new Vector(-1, -1, -1), BlockFace.DOWN)),
is(new RayTraceResult(new Vector(-1, -1, -1), BlockFace.NORTH))));
assertThat(aabb.rayTrace(new Vector(0, 0, -2), new Vector(1, 0, 1), 10),
anyOf(is(new RayTraceResult(new Vector(1, 0, -1), BlockFace.NORTH)),
is(new RayTraceResult(new Vector(1, 0, -1), BlockFace.EAST))));
}
@Test
public void testSerialization() {
BoundingBox aabb = new BoundingBox(-1, -1, -1, 1, 1, 1);
Map<String, Object> serialized = aabb.serialize();
BoundingBox deserialized = BoundingBox.deserialize(serialized);
assertThat(deserialized, is(aabb));
}
}