summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblablubbabc <lukas@wirsindwir.de>2018-10-26 19:59:36 +1100
committermd_5 <git@md-5.net>2018-10-26 20:07:23 +1100
commitd0d6ee2829d1d4000685dee2c56f88278571bbc8 (patch)
tree829517ae2cd448d0f9b1c52c1211653ea7fab436
parente6583aca258eee05ee4dda7641ea1d2fb0707294 (diff)
downloadbukkit-d0d6ee2829d1d4000685dee2c56f88278571bbc8.tar
bukkit-d0d6ee2829d1d4000685dee2c56f88278571bbc8.tar.gz
bukkit-d0d6ee2829d1d4000685dee2c56f88278571bbc8.tar.lz
bukkit-d0d6ee2829d1d4000685dee2c56f88278571bbc8.tar.xz
bukkit-d0d6ee2829d1d4000685dee2c56f88278571bbc8.zip
Add ray tracing and bounding box API
-rw-r--r--src/main/java/org/bukkit/FluidCollisionMode.java20
-rw-r--r--src/main/java/org/bukkit/World.java225
-rw-r--r--src/main/java/org/bukkit/block/Block.java15
-rw-r--r--src/main/java/org/bukkit/block/BlockFace.java15
-rw-r--r--src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java2
-rw-r--r--src/main/java/org/bukkit/entity/Entity.java11
-rw-r--r--src/main/java/org/bukkit/entity/LivingEntity.java75
-rw-r--r--src/main/java/org/bukkit/util/BlockIterator.java16
-rw-r--r--src/main/java/org/bukkit/util/BoundingBox.java1030
-rw-r--r--src/main/java/org/bukkit/util/RayTraceResult.java157
-rw-r--r--src/test/java/org/bukkit/LocationTest.java12
-rw-r--r--src/test/java/org/bukkit/util/BoundingBoxTest.java206
12 files changed, 1770 insertions, 14 deletions
diff --git a/src/main/java/org/bukkit/FluidCollisionMode.java b/src/main/java/org/bukkit/FluidCollisionMode.java
new file mode 100644
index 00000000..ae289589
--- /dev/null
+++ b/src/main/java/org/bukkit/FluidCollisionMode.java
@@ -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;
+}
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
index 93ff5f5a..89a08cc9 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
@@ -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,19 +428,233 @@ 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
*
* @return Name of this world
diff --git a/src/main/java/org/bukkit/block/Block.java b/src/main/java/org/bukkit/block/Block.java
index f3fe0b47..24100a6a 100644
--- a/src/main/java/org/bukkit/block/Block.java
+++ b/src/main/java/org/bukkit/block/Block.java
@@ -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);
}
diff --git a/src/main/java/org/bukkit/block/BlockFace.java b/src/main/java/org/bukkit/block/BlockFace.java
index 58fb195d..959ee3a6 100644
--- a/src/main/java/org/bukkit/block/BlockFace.java
+++ b/src/main/java/org/bukkit/block/BlockFace.java
@@ -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:
diff --git a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java
index f5441bfd..1803d57a 100644
--- a/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java
+++ b/src/main/java/org/bukkit/configuration/serialization/ConfigurationSerialization.java
@@ -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) {
diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java
index c798998c..1a7ec6da 100644
--- a/src/main/java/org/bukkit/entity/Entity.java
+++ b/src/main/java/org/bukkit/entity/Entity.java
@@ -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;
@@ -70,6 +71,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
* moves.
diff --git a/src/main/java/org/bukkit/entity/LivingEntity.java b/src/main/java/org/bukkit/entity/LivingEntity.java
index 6a5de671..ed1d5064 100644
--- a/src/main/java/org/bukkit/entity/LivingEntity.java
+++ b/src/main/java/org/bukkit/entity/LivingEntity.java
@@ -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)
@@ -83,6 +92,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.
*
diff --git a/src/main/java/org/bukkit/util/BlockIterator.java b/src/main/java/org/bukkit/util/BlockIterator.java
index 5c85778c..ec672e7f 100644
--- a/src/main/java/org/bukkit/util/BlockIterator.java
+++ b/src/main/java/org/bukkit/util/BlockIterator.java
@@ -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
*/
diff --git a/src/main/java/org/bukkit/util/BoundingBox.java b/src/main/java/org/bukkit/util/BoundingBox.java
new file mode 100644
index 00000000..67a3322f
--- /dev/null
+++ b/src/main/java/org/bukkit/util/BoundingBox.java
@@ -0,0 +1,1030 @@
+package org.bukkit.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Location;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockFace;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.SerializableAs;
+
+/**
+ * A mutable axis aligned bounding box (AABB).
+ * <p>
+ * This basically represents a rectangular box (specified by minimum and maximum
+ * corners) that can for example be used to describe the position and extents of
+ * an object (such as an entity, block, or rectangular region) in 3D space. Its
+ * edges and faces are parallel to the axes of the cartesian coordinate system.
+ * <p>
+ * The bounding box may be degenerate (one or more sides having the length 0).
+ * <p>
+ * Because bounding boxes are mutable, storing them long term may be dangerous
+ * if they get modified later. If you want to keep around a bounding box, it may
+ * be wise to call {@link #clone()} in order to get a copy.
+ */
+@SerializableAs("BoundingBox")
+public class BoundingBox implements Cloneable, ConfigurationSerializable {
+
+ /**
+ * Creates a new bounding box using the coordinates of the given vectors as
+ * corners.
+ *
+ * @param corner1 the first corner
+ * @param corner2 the second corner
+ * @return the bounding box
+ */
+ public static BoundingBox of(Vector corner1, Vector corner2) {
+ Validate.notNull(corner1, "Corner1 is null!");
+ Validate.notNull(corner2, "Corner2 is null!");
+ return new BoundingBox(corner1.getX(), corner1.getY(), corner1.getZ(), corner2.getX(), corner2.getY(), corner2.getZ());
+ }
+
+ /**
+ * Creates a new bounding box using the coordinates of the given locations
+ * as corners.
+ *
+ * @param corner1 the first corner
+ * @param corner2 the second corner
+ * @return the bounding box
+ */
+ public static BoundingBox of(Location corner1, Location corner2) {
+ Validate.notNull(corner1, "Corner1 is null!");
+ Validate.notNull(corner2, "Corner2 is null!");
+ Validate.isTrue(Objects.equals(corner1.getWorld(), corner2.getWorld()), "Locations from different worlds!");
+ return new BoundingBox(corner1.getX(), corner1.getY(), corner1.getZ(), corner2.getX(), corner2.getY(), corner2.getZ());
+ }
+
+ /**
+ * Creates a new bounding box using the coordinates of the given blocks as
+ * corners.
+ * <p>
+ * The bounding box will be sized to fully contain both blocks.
+ *
+ * @param corner1 the first corner block
+ * @param corner2 the second corner block
+ * @return the bounding box
+ */
+ public static BoundingBox of(Block corner1, Block corner2) {
+ Validate.notNull(corner1, "Corner1 is null!");
+ Validate.notNull(corner2, "Corner2 is null!");
+ Validate.isTrue(Objects.equals(corner1.getWorld(), corner2.getWorld()), "Blocks from different worlds!");
+
+ int x1 = corner1.getX();
+ int y1 = corner1.getY();
+ int z1 = corner1.getZ();
+ int x2 = corner2.getX();
+ int y2 = corner2.getY();
+ int z2 = corner2.getZ();
+
+ int minX = Math.min(x1, x2);
+ int minY = Math.min(y1, y2);
+ int minZ = Math.min(z1, z2);
+ int maxX = Math.max(x1, x2) + 1;
+ int maxY = Math.max(y1, y2) + 1;
+ int maxZ = Math.max(z1, z2) + 1;
+
+ return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ);
+ }
+
+ /**
+ * Creates a new 1x1x1 sized bounding box containing the given block.
+ *
+ * @param block the block
+ * @return the bounding box
+ */
+ public static BoundingBox of(Block block) {
+ Validate.notNull(block, "Block is null!");
+ return new BoundingBox(block.getX(), block.getY(), block.getZ(), block.getX() + 1, block.getY() + 1, block.getZ() + 1);
+ }
+
+ /**
+ * Creates a new bounding box using the given center and extents.
+ *
+ * @param center the center
+ * @param x 1/2 the size of the bounding box along the x axis
+ * @param y 1/2 the size of the bounding box along the y axis
+ * @param z 1/2 the size of the bounding box along the z axis
+ * @return the bounding box
+ */
+ public static BoundingBox of(Vector center, double x, double y, double z) {
+ Validate.notNull(center, "Center is null!");
+ return new BoundingBox(center.getX() - x, center.getY() - y, center.getZ() - z, center.getX() + x, center.getY() + y, center.getZ() + z);
+ }
+
+ /**
+ * Creates a new bounding box using the given center and extents.
+ *
+ * @param center the center
+ * @param x 1/2 the size of the bounding box along the x axis
+ * @param y 1/2 the size of the bounding box along the y axis
+ * @param z 1/2 the size of the bounding box along the z axis
+ * @return the bounding box
+ */
+ public static BoundingBox of(Location center, double x, double y, double z) {
+ Validate.notNull(center, "Center is null!");
+ return new BoundingBox(center.getX() - x, center.getY() - y, center.getZ() - z, center.getX() + x, center.getY() + y, center.getZ() + z);
+ }
+
+ private double minX;
+ private double minY;
+ private double minZ;
+ private double maxX;
+ private double maxY;
+ private double maxZ;
+
+ /**
+ * Creates a new (degenerate) bounding box with all corner coordinates at
+ * <code>0</code>.
+ */
+ public BoundingBox() {
+ this.resize(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);
+ }
+
+ /**
+ * Creates a new bounding box from the given corner coordinates.
+ *
+ * @param x1 the first corner's x value
+ * @param y1 the first corner's y value
+ * @param z1 the first corner's z value
+ * @param x2 the second corner's x value
+ * @param y2 the second corner's y value
+ * @param z2 the second corner's z value
+ */
+ public BoundingBox(double x1, double y1, double z1, double x2, double y2, double z2) {
+ this.resize(x1, y1, z1, x2, y2, z2);
+ }
+
+ /**
+ * Resizes this bounding box.
+ *
+ * @param x1 the first corner's x value
+ * @param y1 the first corner's y value
+ * @param z1 the first corner's z value
+ * @param x2 the second corner's x value
+ * @param y2 the second corner's y value
+ * @param z2 the second corner's z value
+ * @return this bounding box (resized)
+ */
+ public BoundingBox resize(double x1, double y1, double z1, double x2, double y2, double z2) {
+ NumberConversions.checkFinite(x1, "x1 not finite");
+ NumberConversions.checkFinite(y1, "y1 not finite");
+ NumberConversions.checkFinite(z1, "z1 not finite");
+ NumberConversions.checkFinite(x2, "x2 not finite");
+ NumberConversions.checkFinite(y2, "y2 not finite");
+ NumberConversions.checkFinite(z2, "z2 not finite");
+
+ this.minX = Math.min(x1, x2);
+ this.minY = Math.min(y1, y2);
+ this.minZ = Math.min(z1, z2);
+ this.maxX = Math.max(x1, x2);
+ this.maxY = Math.max(y1, y2);
+ this.maxZ = Math.max(z1, z2);
+ return this;
+ }
+
+ /**
+ * Gets the minimum x value.
+ *
+ * @return the minimum x value
+ */
+ public double getMinX() {
+ return minX;
+ }
+
+ /**
+ * Gets the minimum y value.
+ *
+ * @return the minimum y value
+ */
+ public double getMinY() {
+ return minY;
+ }
+
+ /**
+ * Gets the minimum z value.
+ *
+ * @return the minimum z value
+ */
+ public double getMinZ() {
+ return minZ;
+ }
+
+ /**
+ * Gets the minimum corner as vector.
+ *
+ * @return the minimum corner as vector
+ */
+ public Vector getMin() {
+ return new Vector(minX, minY, minZ);
+ }
+
+ /**
+ * Gets the maximum x value.
+ *
+ * @return the maximum x value
+ */
+ public double getMaxX() {
+ return maxX;
+ }
+
+ /**
+ * Gets the maximum y value.
+ *
+ * @return the maximum y value
+ */
+ public double getMaxY() {
+ return maxY;
+ }
+
+ /**
+ * Gets the maximum z value.
+ *
+ * @return the maximum z value
+ */
+ public double getMaxZ() {
+ return maxZ;
+ }
+
+ /**
+ * Gets the maximum corner as vector.
+ *
+ * @return the maximum corner vector
+ */
+ public Vector getMax() {
+ return new Vector(maxX, maxY, maxZ);
+ }
+
+ /**
+ * Gets the width of the bounding box in the x direction.
+ *
+ * @return the width in the x direction
+ */
+ public double getWidthX() {
+ return (this.maxX - this.minX);
+ }
+
+ /**
+ * Gets the width of the bounding box in the z direction.
+ *
+ * @return the width in the z direction
+ */
+ public double getWidthZ() {
+ return (this.maxZ - this.minZ);
+ }
+
+ /**
+ * Gets the height of the bounding box.
+ *
+ * @return the height
+ */
+ public double getHeight() {
+ return (this.maxY - this.minY);
+ }
+
+ /**
+ * Gets the volume of the bounding box.
+ *
+ * @return the volume
+ */
+ public double getVolume() {
+ return (this.getHeight() * this.getWidthX() * this.getWidthZ());
+ }
+
+ /**
+ * Gets the x coordinate of the center of the bounding box.
+ *
+ * @return the center's x coordinate
+ */
+ public double getCenterX() {
+ return (this.minX + this.getWidthX() * 0.5D);
+ }
+
+ /**
+ * Gets the y coordinate of the center of the bounding box.
+ *
+ * @return the center's y coordinate
+ */
+ public double getCenterY() {
+ return (this.minY + this.getHeight() * 0.5D);
+ }
+
+ /**
+ * Gets the z coordinate of the center of the bounding box.
+ *
+ * @return the center's z coordinate
+ */
+ public double getCenterZ() {
+ return (this.minZ + this.getWidthZ() * 0.5D);
+ }
+
+ /**
+ * Gets the center of the bounding box.
+ *
+ * @return the center
+ */
+ public Vector getCenter() {
+ return new Vector(this.getCenterX(), this.getCenterY(), this.getCenterZ());
+ }
+
+ /**
+ * Copies another bounding box.
+ *
+ * @param other the other bounding box
+ * @return this bounding box
+ */
+ public BoundingBox copy(BoundingBox other) {
+ Validate.notNull(other, "Other bounding box is null!");
+ return this.resize(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ());
+ }
+
+ /**
+ * Expands this bounding box by the given values in the corresponding
+ * directions.
+ * <p>
+ * Negative values will shrink the bounding box in the corresponding
+ * direction. Shrinking will be limited to the point where the affected
+ * opposite faces would meet if the they shrank at uniform speeds.
+ *
+ * @param negativeX the amount of expansion in the negative x direction
+ * @param negativeY the amount of expansion in the negative y direction
+ * @param negativeZ the amount of expansion in the negative z direction
+ * @param positiveX the amount of expansion in the positive x direction
+ * @param positiveY the amount of expansion in the positive y direction
+ * @param positiveZ the amount of expansion in the positive z direction
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(double negativeX, double negativeY, double negativeZ, double positiveX, double positiveY, double positiveZ) {
+ if (negativeX == 0.0D && negativeY == 0.0D && negativeZ == 0.0D && positiveX == 0.0D && positiveY == 0.0D && positiveZ == 0.0D) {
+ return this;
+ }
+ double newMinX = this.minX - negativeX;
+ double newMinY = this.minY - negativeY;
+ double newMinZ = this.minZ - negativeZ;
+ double newMaxX = this.maxX + positiveX;
+ double newMaxY = this.maxY + positiveY;
+ double newMaxZ = this.maxZ + positiveZ;
+
+ // limit shrinking:
+ if (newMinX > newMaxX) {
+ double centerX = this.getCenterX();
+ if (newMaxX >= centerX) {
+ newMinX = newMaxX;
+ } else if (newMinX <= centerX) {
+ newMaxX = newMinX;
+ } else {
+ newMinX = centerX;
+ newMaxX = centerX;
+ }
+ }
+ if (newMinY > newMaxY) {
+ double centerY = this.getCenterY();
+ if (newMaxY >= centerY) {
+ newMinY = newMaxY;
+ } else if (newMinY <= centerY) {
+ newMaxY = newMinY;
+ } else {
+ newMinY = centerY;
+ newMaxY = centerY;
+ }
+ }
+ if (newMinZ > newMaxZ) {
+ double centerZ = this.getCenterZ();
+ if (newMaxZ >= centerZ) {
+ newMinZ = newMaxZ;
+ } else if (newMinZ <= centerZ) {
+ newMaxZ = newMinZ;
+ } else {
+ newMinZ = centerZ;
+ newMaxZ = centerZ;
+ }
+ }
+ return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);
+ }
+
+ /**
+ * Expands this bounding box uniformly by the given values in both positive
+ * and negative directions.
+ * <p>
+ * Negative values will shrink the bounding box. Shrinking will be limited
+ * to the bounding box's current size.
+ *
+ * @param x the amount of expansion in both positive and negative x
+ * direction
+ * @param y the amount of expansion in both positive and negative y
+ * direction
+ * @param z the amount of expansion in both positive and negative z
+ * direction
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(double x, double y, double z) {
+ return this.expand(x, y, z, x, y, z);
+ }
+
+ /**
+ * Expands this bounding box uniformly by the given values in both positive
+ * and negative directions.
+ * <p>
+ * Negative values will shrink the bounding box. Shrinking will be limited
+ * to the bounding box's current size.
+ *
+ * @param expansion the expansion values
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(Vector expansion) {
+ Validate.notNull(expansion, "Expansion is null!");
+ double x = expansion.getX();
+ double y = expansion.getY();
+ double z = expansion.getZ();
+ return this.expand(x, y, z, x, y, z);
+ }
+
+ /**
+ * Expands this bounding box uniformly by the given value in all directions.
+ * <p>
+ * A negative value will shrink the bounding box. Shrinking will be limited
+ * to the bounding box's current size.
+ *
+ * @param expansion the amount of expansion
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(double expansion) {
+ return this.expand(expansion, expansion, expansion, expansion, expansion, expansion);
+ }
+
+ /**
+ * Expands this bounding box in the specified direction.
+ * <p>
+ * The magnitude of the direction will scale the expansion. A negative
+ * expansion value will shrink the bounding box in this direction. Shrinking
+ * will be limited to the bounding box's current size.
+ *
+ * @param dirX the x direction component
+ * @param dirY the y direction component
+ * @param dirZ the z direction component
+ * @param expansion the amount of expansion
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(double dirX, double dirY, double dirZ, double expansion) {
+ if (expansion == 0.0D) return this;
+ if (dirX == 0.0D && dirY == 0.0D && dirZ == 0.0D) return this;
+
+ double negativeX = (dirX < 0.0D ? (-dirX * expansion) : 0.0D);
+ double negativeY = (dirY < 0.0D ? (-dirY * expansion) : 0.0D);
+ double negativeZ = (dirZ < 0.0D ? (-dirZ * expansion) : 0.0D);
+ double positiveX = (dirX > 0.0D ? (dirX * expansion) : 0.0D);
+ double positiveY = (dirY > 0.0D ? (dirY * expansion) : 0.0D);
+ double positiveZ = (dirZ > 0.0D ? (dirZ * expansion) : 0.0D);
+ return this.expand(negativeX, negativeY, negativeZ, positiveX, positiveY, positiveZ);
+ }
+
+ /**
+ * Expands this bounding box in the specified direction.
+ * <p>
+ * The magnitude of the direction will scale the expansion. A negative
+ * expansion value will shrink the bounding box in this direction. Shrinking
+ * will be limited to the bounding box's current size.
+ *
+ * @param direction the direction
+ * @param expansion the amount of expansion
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(Vector direction, double expansion) {
+ Validate.notNull(direction, "Direction is null!");
+ return this.expand(direction.getX(), direction.getY(), direction.getZ(), expansion);
+ }
+
+ /**
+ * Expands this bounding box in the direction specified by the given block
+ * face.
+ * <p>
+ * A negative expansion value will shrink the bounding box in this
+ * direction. Shrinking will be limited to the bounding box's current size.
+ *
+ * @param blockFace the block face
+ * @param expansion the amount of expansion
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expand(BlockFace blockFace, double expansion) {
+ Validate.notNull(blockFace, "Block face is null!");
+ if (blockFace == BlockFace.SELF) return this;
+
+ return this.expand(blockFace.getDirection(), expansion);
+ }
+
+ /**
+ * Expands this bounding box in the specified direction.
+ * <p>
+ * Negative values will expand the bounding box in the negative direction,
+ * positive values will expand it in the positive direction. The magnitudes
+ * of the direction components determine the corresponding amounts of
+ * expansion.
+ *
+ * @param dirX the x direction component
+ * @param dirY the y direction component
+ * @param dirZ the z direction component
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expandDirectional(double dirX, double dirY, double dirZ) {
+ return this.expand(dirX, dirY, dirZ, 1.0D);
+ }
+
+ /**
+ * Expands this bounding box in the specified direction.
+ * <p>
+ * Negative values will expand the bounding box in the negative direction,
+ * positive values will expand it in the positive direction. The magnitude
+ * of the direction vector determines the amount of expansion.
+ *
+ * @param direction the direction and magnitude of the expansion
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox expandDirectional(Vector direction) {
+ Validate.notNull(direction, "Expansion is null!");
+ return this.expand(direction.getX(), direction.getY(), direction.getZ(), 1.0D);
+ }
+
+ /**
+ * Expands this bounding box to contain (or border) the specified position.
+ *
+ * @param posX the x position value
+ * @param posY the y position value
+ * @param posZ the z position value
+ * @return this bounding box (now expanded)
+ * @see #contains(double, double, double)
+ */
+ public BoundingBox union(double posX, double posY, double posZ) {
+ double newMinX = Math.min(this.minX, posX);
+ double newMinY = Math.min(this.minY, posY);
+ double newMinZ = Math.min(this.minZ, posZ);
+ double newMaxX = Math.max(this.maxX, posX);
+ double newMaxY = Math.max(this.maxY, posY);
+ double newMaxZ = Math.max(this.maxZ, posZ);
+ if (newMinX == this.minX && newMinY == this.minY && newMinZ == this.minZ && newMaxX == this.maxX && newMaxY == this.maxY && newMaxZ == this.maxZ) {
+ return this;
+ }
+ return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);
+ }
+
+ /**
+ * Expands this bounding box to contain (or border) the specified position.
+ *
+ * @param position the position
+ * @return this bounding box (now expanded)
+ * @see #contains(double, double, double)
+ */
+ public BoundingBox union(Vector position) {
+ Validate.notNull(position, "Position is null!");
+ return this.union(position.getX(), position.getY(), position.getZ());
+ }
+
+ /**
+ * Expands this bounding box to contain (or border) the specified position.
+ *
+ * @param position the position
+ * @return this bounding box (now expanded)
+ * @see #contains(double, double, double)
+ */
+ public BoundingBox union(Location position) {
+ Validate.notNull(position, "Position is null!");
+ return this.union(position.getX(), position.getY(), position.getZ());
+ }
+
+ /**
+ * Expands this bounding box to contain both this and the given bounding
+ * box.
+ *
+ * @param other the other bounding box
+ * @return this bounding box (now expanded)
+ */
+ public BoundingBox union(BoundingBox other) {
+ Validate.notNull(other, "Other bounding box is null!");
+ if (this.contains(other)) return this;
+ double newMinX = Math.min(this.minX, other.minX);
+ double newMinY = Math.min(this.minY, other.minY);
+ double newMinZ = Math.min(this.minZ, other.minZ);
+ double newMaxX = Math.max(this.maxX, other.maxX);
+ double newMaxY = Math.max(this.maxY, other.maxY);
+ double newMaxZ = Math.max(this.maxZ, other.maxZ);
+ return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);
+ }
+
+ /**
+ * Resizes this bounding box to represent the intersection of this and the
+ * given bounding box.
+ *
+ * @param other the other bounding box
+ * @return this bounding box (now representing the intersection)
+ * @throws IllegalArgumentException if the bounding boxes don't overlap
+ */
+ public BoundingBox intersection(BoundingBox other) {
+ Validate.notNull(other, "Other bounding box is null!");
+ Validate.isTrue(this.overlaps(other), "The bounding boxes do not overlap!");
+ double newMinX = Math.max(this.minX, other.minX);
+ double newMinY = Math.max(this.minY, other.minY);
+ double newMinZ = Math.max(this.minZ, other.minZ);
+ double newMaxX = Math.min(this.maxX, other.maxX);
+ double newMaxY = Math.min(this.maxY, other.maxY);
+ double newMaxZ = Math.min(this.maxZ, other.maxZ);
+ return this.resize(newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);
+ }
+
+ /**
+ * Shifts this bounding box by the given amounts.
+ *
+ * @param shiftX the shift in x direction
+ * @param shiftY the shift in y direction
+ * @param shiftZ the shift in z direction
+ * @return this bounding box (now shifted)
+ */
+ public BoundingBox shift(double shiftX, double shiftY, double shiftZ) {
+ if (shiftX == 0.0D && shiftY == 0.0D && shiftZ == 0.0D) return this;
+ return this.resize(this.minX + shiftX, this.minY + shiftY, this.minZ + shiftZ,
+ this.maxX + shiftX, this.maxY + shiftY, this.maxZ + shiftZ);
+ }
+
+ /**
+ * Shifts this bounding box by the given amounts.
+ *
+ * @param shift the shift
+ * @return this bounding box (now shifted)
+ */
+ public BoundingBox shift(Vector shift) {
+ Validate.notNull(shift, "Shift is null!");
+ return this.shift(shift.getX(), shift.getY(), shift.getZ());
+ }
+
+ /**
+ * Shifts this bounding box by the given amounts.
+ *
+ * @param shift the shift
+ * @return this bounding box (now shifted)
+ */
+ public BoundingBox shift(Location shift) {
+ Validate.notNull(shift, "Shift is null!");
+ return this.shift(shift.getX(), shift.getY(), shift.getZ());
+ }
+
+ private boolean overlaps(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
+ return this.minX < maxX && this.maxX > minX
+ && this.minY < maxY && this.maxY > minY
+ && this.minZ < maxZ && this.maxZ > minZ;
+ }
+
+ /**
+ * Checks if this bounding box overlaps with the given bounding box.
+ * <p>
+ * Bounding boxes that are only intersecting at the borders are not
+ * considered overlapping.
+ *
+ * @param other the other bounding box
+ * @return <code>true</code> if overlapping
+ */
+ public boolean overlaps(BoundingBox other) {
+ Validate.notNull(other, "Other bounding box is null!");
+ return this.overlaps(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ);
+ }
+
+ /**
+ * Checks if this bounding box overlaps with the bounding box that is
+ * defined by the given corners.
+ * <p>
+ * Bounding boxes that are only intersecting at the borders are not
+ * considered overlapping.
+ *
+ * @param min the first corner
+ * @param max the second corner
+ * @return <code>true</code> if overlapping
+ */
+ public boolean overlaps(Vector min, Vector max) {
+ Validate.notNull(min, "Min is null!");
+ Validate.notNull(max, "Max is null!");
+ double x1 = min.getX();
+ double y1 = min.getY();
+ double z1 = min.getZ();
+ double x2 = max.getX();
+ double y2 = max.getY();
+ double z2 = max.getZ();
+ return this.overlaps(Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2),
+ Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2));
+ }
+
+ /**
+ * Checks if this bounding box contains the specified position.
+ * <p>
+ * Positions exactly on the minimum borders of the bounding box are
+ * considered to be inside the bounding box, while positions exactly on the
+ * maximum borders are considered to be outside. This allows bounding boxes
+ * to reside directly next to each other with positions always only residing
+ * in exactly one of them.
+ *
+ * @param x the position's x coordinates
+ * @param y the position's y coordinates
+ * @param z the position's z coordinates
+ * @return <code>true</code> if the bounding box contains the position
+ */
+ public boolean contains(double x, double y, double z) {
+ return x >= this.minX && x < this.maxX
+ && y >= this.minY && y < this.maxY
+ && z >= this.minZ && z < this.maxZ;
+ }
+
+ /**
+ * Checks if this bounding box contains the specified position.
+ * <p>
+ * Positions exactly on the minimum borders of the bounding box are
+ * considered to be inside the bounding box, while positions exactly on the
+ * maximum borders are considered to be outside. This allows bounding boxes
+ * to reside directly next to each other with positions always only residing
+ * in exactly one of them.
+ *
+ * @param position the position
+ * @return <code>true</code> if the bounding box contains the position
+ */
+ public boolean contains(Vector position) {
+ Validate.notNull(position, "Position is null!");
+ return this.contains(position.getX(), position.getY(), position.getZ());
+ }
+
+ private boolean contains(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
+ return this.minX <= minX && this.maxX >= maxX
+ && this.minY <= minY && this.maxY >= maxY
+ && this.minZ <= minZ && this.maxZ >= maxZ;
+ }
+
+ /**
+ * Checks if this bounding box fully contains the given bounding box.
+ *
+ * @param other the other bounding box
+ * @return <code>true</code> if the bounding box contains the given bounding
+ * box
+ */
+ public boolean contains(BoundingBox other) {
+ Validate.notNull(other, "Other bounding box is null!");
+ return this.contains(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ);
+ }
+
+ /**
+ * Checks if this bounding box fully contains the bounding box that is
+ * defined by the given corners.
+ *
+ * @param min the first corner
+ * @param max the second corner
+ * @return <code>true</code> if the bounding box contains the specified
+ * bounding box
+ */
+ public boolean contains(Vector min, Vector max) {
+ Validate.notNull(min, "Min is null!");
+ Validate.notNull(max, "Max is null!");
+ double x1 = min.getX();
+ double y1 = min.getY();
+ double z1 = min.getZ();
+ double x2 = max.getX();
+ double y2 = max.getY();
+ double z2 = max.getZ();
+ return this.contains(Math.min(x1, x2), Math.min(y1, y2), Math.min(z1, z2),
+ Math.max(x1, x2), Math.max(y1, y2), Math.max(z1, z2));
+ }
+
+ /**
+ * Calculates the intersection of this bounding box with the specified line
+ * segment.
+ * <p>
+ * Intersections at edges and corners yield one of the affected block faces
+ * as hit result, but it is not defined which of them.
+ *
+ * @param start the start position
+ * @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
+ */
+ public RayTraceResult rayTrace(Vector start, Vector direction, double maxDistance) {
+ Validate.notNull(start, "Start is null!");
+ start.checkFinite();
+ Validate.notNull(direction, "Direction is null!");
+ direction.checkFinite();
+ Validate.isTrue(direction.lengthSquared() > 0, "Direction's magnitude is 0!");
+ if (maxDistance < 0.0D) return null;
+
+ // ray start:
+ double startX = start.getX();
+ double startY = start.getY();
+ double startZ = start.getZ();
+
+ // ray direction:
+ Vector dir = direction.clone().normalize();
+ double dirX = dir.getX();
+ double dirY = dir.getY();
+ double dirZ = dir.getZ();
+
+ // saving a few divisions below:
+ double divX = 1.0D / dirX;
+ double divY = 1.0D / dirY;
+ double divZ = 1.0D / dirZ;
+
+ double tMin;
+ double tMax;
+ BlockFace hitBlockFaceMin;
+ BlockFace hitBlockFaceMax;
+
+ // intersections with x planes:
+ if (dirX >= 0.0D) {
+ tMin = (this.minX - startX) * divX;
+ tMax = (this.maxX - startX) * divX;
+ hitBlockFaceMin = BlockFace.WEST;
+ hitBlockFaceMax = BlockFace.EAST;
+ } else {
+ tMin = (this.maxX - startX) * divX;
+ tMax = (this.minX - startX) * divX;
+ hitBlockFaceMin = BlockFace.EAST;
+ hitBlockFaceMax = BlockFace.WEST;
+ }
+
+ // intersections with y planes:
+ double tyMin;
+ double tyMax;
+ BlockFace hitBlockFaceYMin;
+ BlockFace hitBlockFaceYMax;
+ if (dirY >= 0.0D) {
+ tyMin = (this.minY - startY) * divY;
+ tyMax = (this.maxY - startY) * divY;
+ hitBlockFaceYMin = BlockFace.DOWN;
+ hitBlockFaceYMax = BlockFace.UP;
+ } else {
+ tyMin = (this.maxY - startY) * divY;
+ tyMax = (this.minY - startY) * divY;
+ hitBlockFaceYMin = BlockFace.UP;
+ hitBlockFaceYMax = BlockFace.DOWN;
+ }
+ if ((tMin > tyMax) || (tMax < tyMin)) {
+ return null;
+ }
+ if (tyMin > tMin) {
+ tMin = tyMin;
+ hitBlockFaceMin = hitBlockFaceYMin;
+ }
+ if (tyMax < tMax) {
+ tMax = tyMax;
+ hitBlockFaceMax = hitBlockFaceYMax;
+ }
+
+ // intersections with z planes:
+ double tzMin;
+ double tzMax;
+ BlockFace hitBlockFaceZMin;
+ BlockFace hitBlockFaceZMax;
+ if (dirZ >= 0.0D) {
+ tzMin = (this.minZ - startZ) * divZ;
+ tzMax = (this.maxZ - startZ) * divZ;
+ hitBlockFaceZMin = BlockFace.NORTH;
+ hitBlockFaceZMax = BlockFace.SOUTH;
+ } else {
+ tzMin = (this.maxZ - startZ) * divZ;
+ tzMax = (this.minZ - startZ) * divZ;
+ hitBlockFaceZMin = BlockFace.SOUTH;
+ hitBlockFaceZMax = BlockFace.NORTH;
+ }
+ if ((tMin > tzMax) || (tMax < tzMin)) {
+ return null;
+ }
+ if (tzMin > tMin) {
+ tMin = tzMin;
+ hitBlockFaceMin = hitBlockFaceZMin;
+ }
+ if (tzMax < tMax) {
+ tMax = tzMax;
+ hitBlockFaceMax = hitBlockFaceZMax;
+ }
+
+ // intersections are behind the start:
+ if (tMax < 0.0D) return null;
+ // intersections are to far away:
+ if (tMin > maxDistance) {
+ return null;
+ }
+
+ // find the closest intersection:
+ double t;
+ BlockFace hitBlockFace;
+ if (tMin < 0.0D) {
+ t = tMax;
+ hitBlockFace = hitBlockFaceMax;
+ } else {
+ t = tMin;
+ hitBlockFace = hitBlockFaceMin;
+ }
+ // reusing the newly created direction vector for the hit position:
+ Vector hitPosition = dir.multiply(t).add(start);
+ return new RayTraceResult(hitPosition, hitBlockFace);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ long temp;
+ temp = Double.doubleToLongBits(maxX);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxY);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(maxZ);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minX);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minY);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ temp = Double.doubleToLongBits(minZ);
+ result = prime * result + (int) (temp ^ (temp >>> 32));
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof BoundingBox)) return false;
+ BoundingBox other = (BoundingBox) obj;
+ if (Double.doubleToLongBits(maxX) != Double.doubleToLongBits(other.maxX)) return false;
+ if (Double.doubleToLongBits(maxY) != Double.doubleToLongBits(other.maxY)) return false;
+ if (Double.doubleToLongBits(maxZ) != Double.doubleToLongBits(other.maxZ)) return false;
+ if (Double.doubleToLongBits(minX) != Double.doubleToLongBits(other.minX)) return false;
+ if (Double.doubleToLongBits(minY) != Double.doubleToLongBits(other.minY)) return false;
+ if (Double.doubleToLongBits(minZ) != Double.doubleToLongBits(other.minZ)) return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("BoundingBox [minX=");
+ builder.append(minX);
+ builder.append(", minY=");
+ builder.append(minY);
+ builder.append(", minZ=");
+ builder.append(minZ);
+ builder.append(", maxX=");
+ builder.append(maxX);
+ builder.append(", maxY=");
+ builder.append(maxY);
+ builder.append(", maxZ=");
+ builder.append(maxZ);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ /**
+ * Creates a copy of this bounding box.
+ *
+ * @return the cloned bounding box
+ */
+ @Override
+ public BoundingBox clone() {
+ try {
+ return (BoundingBox) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new Error(e);
+ }
+ }
+
+ @Override
+ public Map<String, Object> serialize() {
+ Map<String, Object> result = new LinkedHashMap<String, Object>();
+ result.put("minX", minX);
+ result.put("minY", minY);
+ result.put("minZ", minZ);
+ result.put("maxX", maxX);
+ result.put("maxY", maxY);
+ result.put("maxZ", maxZ);
+ return result;
+ }
+
+ public static BoundingBox deserialize(Map<String, Object> args) {
+ double minX = 0.0D;
+ double minY = 0.0D;
+ double minZ = 0.0D;
+ double maxX = 0.0D;
+ double maxY = 0.0D;
+ double maxZ = 0.0D;
+
+ if (args.containsKey("minX")) {
+ minX = ((Number) args.get("minX")).doubleValue();
+ }
+ if (args.containsKey("minY")) {
+ minY = ((Number) args.get("minY")).doubleValue();
+ }
+ if (args.containsKey("minZ")) {
+ minZ = ((Number) args.get("minZ")).doubleValue();
+ }
+ if (args.containsKey("maxX")) {
+ maxX = ((Number) args.get("maxX")).doubleValue();
+ }
+ if (args.containsKey("maxY")) {
+ maxY = ((Number) args.get("maxY")).doubleValue();
+ }
+ if (args.containsKey("maxZ")) {
+ maxZ = ((Number) args.get("maxZ")).doubleValue();
+ }
+
+ return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ);
+ }
+}
diff --git a/src/main/java/org/bukkit/util/RayTraceResult.java b/src/main/java/org/bukkit/util/RayTraceResult.java
new file mode 100644
index 00000000..5a3a05ef
--- /dev/null
+++ b/src/main/java/org/bukkit/util/RayTraceResult.java
@@ -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();
+ }
+}
diff --git a/src/test/java/org/bukkit/LocationTest.java b/src/test/java/org/bukkit/LocationTest.java
index fa247763..48d20761 100644
--- a/src/test/java/org/bukkit/LocationTest.java
+++ b/src/test/java/org/bukkit/LocationTest.java
@@ -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() {
diff --git a/src/test/java/org/bukkit/util/BoundingBoxTest.java b/src/test/java/org/bukkit/util/BoundingBoxTest.java
new file mode 100644
index 00000000..1332aa26
--- /dev/null
+++ b/src/test/java/org/bukkit/util/BoundingBoxTest.java
@@ -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));
+ }
+}