package me.ryanhamshire.GriefPrevention.util; import me.ryanhamshire.GriefPrevention.Claim; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.util.NumberConversions; import org.bukkit.util.Vector; import java.util.Collection; import java.util.Iterator; import java.util.Objects; /** * A mutable block-based axis-aligned bounding box. * *
This is a rectangular box defined by minimum and maximum corners * that can be used to represent a collection of blocks. * *
While similar to Bukkit's {@link org.bukkit.util.BoundingBox BoundingBox},
* this implementation is much more focused on performance and does not use as
* many input sanitization operations.
*/
public class BoundingBox implements Cloneable
{
/**
* Construct a new bounding box containing all of the given blocks.
*
* @param blocks a collection of blocks to construct a bounding box around
* @return the bounding box
*/
public static BoundingBox ofBlocks(Collection Note that center coordinates are world coordinates
* while all of the other coordinates are block coordinates.
*
* @return the center of the X axis
*/
public double getCenterX()
{
return this.minX + (this.getLength() / 2D);
}
/**
* Gets the center of the bounding box on the Y axis.
*
* Note that center coordinates are world coordinates
* while all of the other coordinates are block coordinates.
*
* @return the center of the X axis
*/
public double getCenterY()
{
return this.minY + (this.getHeight() / 2D);
}
/**
* Gets the center of the bounding box on the Z axis.
*
* Note that center coordinates are world coordinates
* while all of the other coordinates are block coordinates.
*
* @return the center of the X axis
*/
public double getCenterZ()
{
return this.minZ + (this.getWidth() / 2D);
}
/**
* Gets the center of the bounding box as a vector.
*
* Note that center coordinates are world coordinates
* while all of the other coordinates are block coordinates.
*
* @return the center of the X axis
*/
public Vector getCenter()
{
return new Vector(this.getCenterX(), this.getCenterY(), this.getCenterZ());
}
/**
* Gets the area of the base of the bounding box.
*
* The base is the lowest plane defined by the X and Z axis.
*
* @return the area of the base of the bounding box
*/
public int getArea()
{
return this.getLength() * this.getWidth();
}
/**
* Gets the volume of the bounding box.
*
* @return the volume of the bounding box
*/
public int getVolume()
{
return this.getArea() * getHeight();
}
/**
* Copies the dimensions and location of another bounding box.
*
* @param other the bounding box to copy
*/
public void copy(BoundingBox other)
{
this.minX = other.minX;
this.minY = other.minY;
this.minZ = other.minZ;
this.maxX = other.maxX;
this.maxY = other.maxY;
this.maxZ = other.maxZ;
}
/**
* Changes the size the bounding box in the direction specified by the Minecraft blockface.
*
* If the specified directional magnitude is negative, the box is contracted instead.
*
* When contracting, the box does not care if the contraction would cause a negative side length.
* In these cases, the lowest point is redefined by the new location of the maximum corner instead.
*
* @param direction the direction to change size in
* @param magnitude the magnitude of the resizing
*/
public void resize(BlockFace direction, int magnitude)
{
if (magnitude == 0 || direction == BlockFace.SELF) return;
Vector vector = direction.getDirection().multiply(magnitude);
// Force normalized rounding - prevents issues with non-cardinal directions.
int modX = NumberConversions.round(vector.getX());
int modY = NumberConversions.round(vector.getY());
int modZ = NumberConversions.round(vector.getZ());
if (modX == 0 && modY == 0 && modZ == 0) return;
// Modify correct point.
if (direction.getModX() > 0)
this.maxX += modX;
else
this.minX += modX;
if (direction.getModY() > 0)
this.maxY += modY;
else
this.minY += modY;
if (direction.getModZ() > 0)
this.maxZ += modZ;
else
this.minZ += modZ;
// If box is contracting, re-verify points in case corners have swapped.
if (magnitude < 0)
verify(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ);
}
/**
* Moves the bounding box in the direction specified by the Minecraft BlockFace and magnitude.
*
* Note that a negative direction will move in the opposite direction
* to the extent that the following example returns true:
*
* public boolean testBoxMove(BoundingBox box, BlockFace face, int magnitude)
* {
* BoundingBox box2 = box.clone();
* box.move(face, magnitude);
* box2.move(face.getOpposite(), -magnitude);
* return box.equals(box2);
* }
*
*
* @param direction the direction to move in
* @param magnitude the magnitude of the move
*/
public void move(BlockFace direction, int magnitude)
{
if (magnitude == 0 || direction == BlockFace.SELF) return;
Vector vector = direction.getDirection().multiply(magnitude);
int blockX = NumberConversions.round(vector.getX());
this.minX += blockX;
this.maxX += blockX;
int blockY = NumberConversions.round(vector.getY());
this.minY += blockY;
this.maxY += blockY;
int blockZ = NumberConversions.round(vector.getZ());
this.minZ += blockZ;
this.maxZ += blockZ;
}
/**
* Expands the bounding box to contain the position specified.
*
* @param x the X coordinate to include
* @param y the Y coordinate to include
* @param z the Z coordinate to include
*/
public void union(int x, int y, int z)
{
this.minX = Math.min(x, this.minX);
this.maxX = Math.max(x, this.maxX);
this.minY = Math.min(y, this.minY);
this.maxY = Math.max(y, this.maxY);
this.minZ = Math.min(z, this.minZ);
this.maxZ = Math.max(z, this.maxZ);
}
/**
* Expands the bounding box to contain the position specified.
*
* @param position the position to include
*/
public void union(Block position)
{
this.union(position.getX(), position.getY(), position.getZ());
}
/**
* Expands the bounding box to contain the position specified.
*
* @param position the position to include
*/
public void union(Vector position)
{
this.union(position.getBlockX(), position.getBlockY(), position.getBlockZ());
}
/**
* Expands the bounding box to contain the position specified.
*
* @param position the position to include
*/
public void union(Location position)
{
this.union(position.getBlockX(), position.getBlockY(), position.getBlockZ());
}
/**
* Expands the bounding box to contain the bounding box specified.
*
* @param other the bounding box to include
*/
public void union(BoundingBox other)
{
this.minX = Math.min(this.minX, other.minX);
this.maxX = Math.max(this.maxX, other.maxX);
this.minY = Math.min(this.minY, other.minY);
this.maxY = Math.max(this.maxY, other.maxY);
this.minZ = Math.min(this.minZ, other.minZ);
this.maxZ = Math.max(this.maxZ, other.maxZ);
}
/**
* Internal containment check ignoring vertical differences.
*
* @param minX the minimum X value to check for containment
* @param minZ the minimum Z value to check for containment
* @param maxX the maximum X value to check for containment
* @param maxZ the maximum X value to check for containment
* @return true if the specified values are inside the bounding box
*/
private boolean contains2dInternal(int minX, int minZ, int maxX, int maxZ) {
return minX >= this.minX && maxX <= this.maxX
&& minZ >= this.minZ && maxZ <= this.maxZ;
}
/**
* Checks if the bounding box contains the position specified.
*
* @param x the X coordinate of the position
* @param z the Z coordinate of the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains2d(int x, int z)
{
return contains2dInternal(x, z, x, z);
}
/**
* Checks if the bounding box contains the position specified.
*
* @param position the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains2d(Vector position)
{
return contains2d(position.getBlockX(), position.getBlockZ());
}
/**
* Checks if the bounding box contains the position specified.
*
* @param position the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains2d(Location position)
{
return contains2d(position.getBlockX(), position.getBlockZ());
}
/**
* Checks if the bounding box contains the position specified.
*
* @param position the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains2d(Block position)
{
return contains2d(position.getX(), position.getZ());
}
/**
* Checks if the bounding box contains another bounding box consisting of the positions specified.
*
* @param x1 the X coordinate of the first position
* @param z1 the Z coordinate of the first position
* @param x2 the X coordinate of the second position
* @param z2 the Z coordinate of the second position
* @return true if the specified positions are inside the bounding box
*/
public boolean contains2d(int x1, int z1, int x2, int z2)
{
int minX;
int maxX;
if (x1 < x2) {
minX = x1;
maxX = x2;
} else {
minX = x2;
maxX = x1;
}
int minZ;
int maxZ;
if (z1 < z2) {
minZ = z1;
maxZ = z2;
} else {
minZ = z2;
maxZ = z1;
}
return contains2dInternal(minX, minZ, maxX, maxZ);
}
/**
* Checks if the bounding box contains another bounding box.
*
* @param other the other bounding box
* @return true if the specified positions are inside the bounding box
*/
public boolean contains2d(BoundingBox other)
{
return contains2dInternal(other.minX, other.minZ, other.maxX, other.maxZ);
}
/**
* Internal containment check.
*
* @param minX the minimum X value to check for containment
* @param minY the minimum Y value to check for containment
* @param minZ the minimum Z value to check for containment
* @param maxX the maximum X value to check for containment
* @param maxY the maximum X value to check for containment
* @param maxZ the maximum X value to check for containment
* @return true if the specified values are inside the bounding box
*/
private boolean containsInternal(int minX, int minY, int minZ, int maxX, int maxY, int maxZ)
{
return contains2dInternal(minX, minZ, maxX, maxZ)
&& minY >= this.minY && maxY <= this.maxY;
}
/**
* Checks if the bounding box contains the position specified.
*
* @param x the X coordinate of the position
* @param y the Y coordinate of the position
* @param z the Z coordinate of the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains(int x, int y, int z)
{
return containsInternal(x, y, z, x, y, z);
}
/**
* Checks if the bounding box contains the position specified.
*
* @param position the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains(Vector position)
{
return contains(position.getBlockX(), position.getBlockY(), position.getBlockZ());
}
/**
* Checks if the bounding box contains the position specified.
*
* @param position the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains(Location position)
{
return contains(position.getBlockX(), position.getBlockY(), position.getBlockZ());
}
/**
* Checks if the bounding box contains the position specified.
*
* @param position the position
* @return true if the specified position is inside the bounding box
*/
public boolean contains(Block position)
{
return contains(position.getX(), position.getY(), position.getZ());
}
/**
* Checks if the bounding box contains another bounding box consisting of the positions specified.
*
* @param x1 the X coordinate of the first position
* @param y1 the Y coordinate of the first position
* @param z1 the Z coordinate of the first position
* @param x2 the X coordinate of the second position
* @param y2 the Y coordinate of the second position
* @param z2 the Z coordinate of the second position
* @return true if the specified positions are inside the bounding box
*/
public boolean contains(int x1, int y1, int z1, int x2, int y2, int z2)
{
return contains(new BoundingBox(x1, y1, z1, x2, y2, z2));
}
/**
* Checks if the bounding box contains another bounding box.
*
* @param other the other bounding box
* @return true if the specified positions are inside the bounding box
*/
public boolean contains(BoundingBox other)
{
return containsInternal(other.minX, other.minY, other.minZ, other.maxX, other.maxY, other.maxZ);
}
/**
* Checks if the bounding box intersects another bounding box.
*
* @param other the other bounding box
* @return true if the specified positions are inside the bounding box
*/
public boolean intersects(BoundingBox other)
{
// For help visualizing test cases, try https://silentmatt.com/rectangle-intersection/
return this.minX <= other.maxX && this.maxX >= other.minX
&& this.minY <= other.maxY && this.maxY >= other.minY
&& this.minZ <= other.maxZ && this.maxZ >= other.minZ;
}
/**
* Gets a bounding box containing the intersection of the bounding box with another.
*
* @param other the other bounding box
* @return the bounding box representing overlapping area or null if the boxes do not overlap.
*/
public BoundingBox intersection(BoundingBox other)
{
if (!intersects(other)) return null;
return new BoundingBox(
Math.max(this.minX, other.minX),
Math.max(this.minY, other.minY),
Math.max(this.minZ, other.minZ),
Math.min(this.maxX, other.maxX),
Math.min(this.maxY, other.maxY),
Math.min(this.maxZ, other.maxZ),
false);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BoundingBox other = (BoundingBox) o;
return this.minX == other.minX
&& this.minY == other.minY
&& this.minZ == other.minZ
&& this.maxX == other.maxX
&& this.maxY == other.maxY
&& this.maxZ == other.maxZ;
}
@Override
public int hashCode()
{
return Objects.hash(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ);
}
@Override
public String toString()
{
return "BoundingBox{" +
"minX=" + minX +
", minY=" + minY +
", minZ=" + minZ +
", maxX=" + maxX +
", maxY=" + maxY +
", maxZ=" + maxZ +
'}';
}
@Override
public BoundingBox clone()
{
try
{
return (BoundingBox) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new Error(e);
}
}
}