From d0d6ee2829d1d4000685dee2c56f88278571bbc8 Mon Sep 17 00:00:00 2001 From: blablubbabc Date: Fri, 26 Oct 2018 19:59:36 +1100 Subject: Add ray tracing and bounding box API --- src/main/java/org/bukkit/FluidCollisionMode.java | 20 + src/main/java/org/bukkit/World.java | 225 ++++- src/main/java/org/bukkit/block/Block.java | 15 + src/main/java/org/bukkit/block/BlockFace.java | 15 + .../serialization/ConfigurationSerialization.java | 2 + src/main/java/org/bukkit/entity/Entity.java | 11 + src/main/java/org/bukkit/entity/LivingEntity.java | 75 +- src/main/java/org/bukkit/util/BlockIterator.java | 16 +- src/main/java/org/bukkit/util/BoundingBox.java | 1030 ++++++++++++++++++++ src/main/java/org/bukkit/util/RayTraceResult.java | 157 +++ 10 files changed, 1558 insertions(+), 8 deletions(-) create mode 100644 src/main/java/org/bukkit/FluidCollisionMode.java create mode 100644 src/main/java/org/bukkit/util/BoundingBox.java create mode 100644 src/main/java/org/bukkit/util/RayTraceResult.java (limited to 'src/main/java/org') 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,18 +428,232 @@ public interface World extends PluginMessageRecipient, Metadatable { public List 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. + *

+ * 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 getNearbyEntities(Location location, double x, double y, double z); + /** + * Returns a list of entities within a bounding box centered around a + * Location. + *

+ * 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 null to consider all entities + * @return the collection of entities near location. This will always be a + * non-null collection. + */ + public Collection getNearbyEntities(Location location, double x, double y, double z, Predicate filter); + + /** + * Returns a list of entities within the given bounding box. + *

+ * 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 getNearbyEntities(BoundingBox boundingBox); + + /** + * Returns a list of entities within the given bounding box. + *

+ * 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 null to consider all entities + * @return the collection of entities within the bounding box, will always + * be a non-null collection + */ + public Collection getNearbyEntities(BoundingBox boundingBox, Predicate filter); + + /** + * Performs a ray trace that checks for entity collisions. + *

+ * 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 null 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. + *

+ * 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 null 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. + *

+ * 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 null to consider all entities + * @return the closest ray trace hit result, or null if there + * is no hit + * @see #rayTraceEntities(Location, Vector, double, double, Predicate) + */ + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, Predicate filter); + + /** + * Performs a ray trace that checks for entity collisions. + *

+ * 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 null to consider all entities + * @return the closest ray trace hit result, or null if there + * is no hit + */ + public RayTraceResult rayTraceEntities(Location start, Vector direction, double maxDistance, double raySize, Predicate filter); + + /** + * Performs a ray trace that checks for block collisions using the blocks' + * precise collision shapes. + *

+ * This takes collisions with passable blocks into account, but ignores + * fluids. + *

+ * 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 null 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. + *

+ * This takes collisions with passable blocks into account. + *

+ * 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 null 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. + *

+ * If collisions with passable blocks are ignored, fluid collisions are + * ignored as well regardless of the fluid collision mode. + *

+ * 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. + *

+ * 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 null 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. + *

+ * Block collisions use the blocks' precise collision shapes. The + * raySize parameter is only taken into account for entity + * collision checks. + *

+ * If collisions with passable blocks are ignored, fluid collisions are + * ignored as well regardless of the fluid collision mode. + *

+ * 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. + *

+ * 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 null to consider all entities + * @return the closest ray trace hit result with either a block or an + * entity, or null if there is no hit + */ + public RayTraceResult rayTrace(Location start, Vector direction, double maxDistance, FluidCollisionMode fluidCollisionMode, boolean ignorePassableBlocks, double raySize, Predicate filter); + /** * Gets the unique 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 true 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 null 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 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; @@ -69,6 +70,16 @@ public interface Entity extends Metadatable, CommandSender, Nameable { */ public double getWidth(); + /** + * Gets the entity's current bounding box. + *

+ * 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 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. *

* 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. + *

+ * 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. *

- * 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 getLastTwoTargetBlocks(Set transparent, int maxDistance); + /** + * Gets the block that the living entity has targeted. + *

+ * This takes the blocks' precise collision shapes into account. Fluids are + * ignored. + *

+ * 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. + *

+ * This takes the blocks' precise collision shapes into account. + *

+ * 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. + *

+ * This takes the blocks' precise collision shapes into account. Fluids are + * ignored. + *

+ * 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 null 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. + *

+ * This takes the blocks' precise collision shapes into account. + *

+ * 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 null 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 { private BlockFace thirdFace; /** - * Constructs the BlockIterator + * Constructs the BlockIterator. + *

+ * 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 { } /** - * Constructs the BlockIterator + * Constructs the BlockIterator. + *

+ * 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 { /** * Constructs the BlockIterator. + *

+ * 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 { /** * Constructs the BlockIterator. + *

+ * 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 { /** * Constructs the BlockIterator. + *

+ * 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 { /** * Constructs the BlockIterator. + *

+ * 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). + *

+ * 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. + *

+ * The bounding box may be degenerate (one or more sides having the length 0). + *

+ * 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. + *

+ * 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 + * 0. + */ + 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * Bounding boxes that are only intersecting at the borders are not + * considered overlapping. + * + * @param other the other bounding box + * @return true 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. + *

+ * Bounding boxes that are only intersecting at the borders are not + * considered overlapping. + * + * @param min the first corner + * @param max the second corner + * @return true 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. + *

+ * 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 true 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. + *

+ * 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 true 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 true 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 true 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. + *

+ * 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 null 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 serialize() { + Map result = new LinkedHashMap(); + 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 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. + *

+ * 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 null if not available + */ + public Block getHitBlock() { + return hitBlock; + } + + /** + * Gets the hit block face. + * + * @return the hit block face, or null if not available + */ + public BlockFace getHitBlockFace() { + return hitBlockFace; + } + + /** + * Gets the hit entity. + * + * @return the hit entity, or null 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(); + } +} -- cgit v1.2.3