2021-07-14 02:34:13 -04:00

754 lines
23 KiB
Java

package com.volmit.iris.util;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.entity.Entity;
import java.util.*;
/**
* Cuboids
*
* @author cyberpwn
*/
public class Cuboid implements Iterable<Block>, Cloneable, ConfigurationSerializable {
protected final String worldName;
protected int x1, y1, z1;
protected int x2, y2, z2;
/**
* Construct a Cuboid given two Location objects which represent any two corners
* of the Cuboid.
*
* @param l1 one of the corners
* @param l2 the other corner
*/
public Cuboid(Location l1, Location l2) {
if (!l1.getWorld().equals(l2.getWorld())) {
throw new IllegalArgumentException("locations must be on the same world");
}
worldName = l1.getWorld().getName();
x1 = Math.min(l1.getBlockX(), l2.getBlockX());
y1 = Math.min(l1.getBlockY(), l2.getBlockY());
z1 = Math.min(l1.getBlockZ(), l2.getBlockZ());
x2 = Math.max(l1.getBlockX(), l2.getBlockX());
y2 = Math.max(l1.getBlockY(), l2.getBlockY());
z2 = Math.max(l1.getBlockZ(), l2.getBlockZ());
}
public KList<Entity> getEntities() {
KList<Entity> en = new KList<Entity>();
for (Chunk i : getChunks()) {
for (Entity j : i.getEntities()) {
if (contains(j.getLocation())) {
en.add(j);
}
}
}
return en;
}
/**
* Set the locations
*
* @param l1 a
* @param l2 b
*/
public void set(Location l1, Location l2) {
x1 = Math.min(l1.getBlockX(), l2.getBlockX());
y1 = Math.min(l1.getBlockY(), l2.getBlockY());
z1 = Math.min(l1.getBlockZ(), l2.getBlockZ());
x2 = Math.max(l1.getBlockX(), l2.getBlockX());
y2 = Math.max(l1.getBlockY(), l2.getBlockY());
z2 = Math.max(l1.getBlockZ(), l2.getBlockZ());
}
/**
* Construct a one-block Cuboid at the given Location of the Cuboid.
*
* @param l1 location of the Cuboid
*/
public Cuboid(Location l1) {
this(l1, l1);
}
/**
* Copy constructor.
*
* @param other the Cuboid to copy
*/
public Cuboid(Cuboid other) {
this(other.getWorld().getName(), other.x1, other.y1, other.z1, other.x2, other.y2, other.z2);
}
/**
* Construct a Cuboid in the given World and xyz co-ordinates
*
* @param world the Cuboid's world
* @param x1 X co-ordinate of corner 1
* @param y1 Y co-ordinate of corner 1
* @param z1 Z co-ordinate of corner 1
* @param x2 X co-ordinate of corner 2
* @param y2 Y co-ordinate of corner 2
* @param z2 Z co-ordinate of corner 2
*/
public Cuboid(World world, int x1, int y1, int z1, int x2, int y2, int z2) {
this.worldName = world.getName();
this.x1 = Math.min(x1, x2);
this.x2 = Math.max(x1, x2);
this.y1 = Math.min(y1, y2);
this.y2 = Math.max(y1, y2);
this.z1 = Math.min(z1, z2);
this.z2 = Math.max(z1, z2);
}
/**
* Construct a Cuboid in the given world name and xyz co-ordinates.
*
* @param worldName the Cuboid's world name
* @param x1 X co-ordinate of corner 1
* @param y1 Y co-ordinate of corner 1
* @param z1 Z co-ordinate of corner 1
* @param x2 X co-ordinate of corner 2
* @param y2 Y co-ordinate of corner 2
* @param z2 Z co-ordinate of corner 2
*/
private Cuboid(String worldName, int x1, int y1, int z1, int x2, int y2, int z2) {
this.worldName = worldName;
this.x1 = Math.min(x1, x2);
this.x2 = Math.max(x1, x2);
this.y1 = Math.min(y1, y2);
this.y2 = Math.max(y1, y2);
this.z1 = Math.min(z1, z2);
this.z2 = Math.max(z1, z2);
}
public Cuboid(Map<String, Object> map) {
worldName = (String) map.get("worldName");
x1 = (Integer) map.get("x1");
x2 = (Integer) map.get("x2");
y1 = (Integer) map.get("y1");
y2 = (Integer) map.get("y2");
z1 = (Integer) map.get("z1");
z2 = (Integer) map.get("z2");
}
@Override
public Map<String, Object> serialize() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("worldName", worldName);
map.put("x1", x1);
map.put("y1", y1);
map.put("z1", z1);
map.put("x2", x2);
map.put("y2", y2);
map.put("z2", z2);
return map;
}
public Cuboid flatten(int level) {
return new Cuboid(getWorld(), x1, level, z1, x2, level, z2);
}
/**
* Get the Location of the lower northeast corner of the Cuboid (minimum XYZ
* co-ordinates).
*
* @return Location of the lower northeast corner
*/
public Location getLowerNE() {
return new Location(getWorld(), x1, y1, z1);
}
/**
* Get the Location of the upper southwest corner of the Cuboid (maximum XYZ
* co-ordinates).
*
* @return Location of the upper southwest corner
*/
public Location getUpperSW() {
return new Location(getWorld(), x2, y2, z2);
}
/**
* Get the the centre of the Cuboid
*
* @return Location at the centre of the Cuboid
*/
public Location getCenter() {
int x1 = getUpperX() + 1;
int y1 = getUpperY() + 1;
int z1 = getUpperZ() + 1;
return new Location(getWorld(), getLowerX() + (x1 - getLowerX()) / 2.0, getLowerY() + (y1 - getLowerY()) / 2.0, getLowerZ() + (z1 - getLowerZ()) / 2.0);
}
/**
* Get the Cuboid's world.
*
* @return the World object representing this Cuboid's world
* @throws IllegalStateException if the world is not loaded
*/
public World getWorld() {
World world = Bukkit.getWorld(worldName);
if (world == null) {
throw new IllegalStateException("world '" + worldName + "' is not loaded");
}
return world;
}
/**
* Get the size of this Cuboid along the X axis
*
* @return Size of Cuboid along the X axis
*/
public int getSizeX() {
return (x2 - x1) + 1;
}
/**
* Get the size of this Cuboid along the Y axis
*
* @return Size of Cuboid along the Y axis
*/
public int getSizeY() {
return (y2 - y1) + 1;
}
/**
* Get the size of this Cuboid along the Z axis
*
* @return Size of Cuboid along the Z axis
*/
public int getSizeZ() {
return (z2 - z1) + 1;
}
/**
* Get the cuboid dimensions
*
* @return the dimensions
*/
public Dimension getDimension() {
return new Dimension(getSizeX(), getSizeY(), getSizeZ());
}
/**
* Get the minimum X co-ordinate of this Cuboid
*
* @return the minimum X co-ordinate
*/
public int getLowerX() {
return x1;
}
/**
* Get the minimum Y co-ordinate of this Cuboid
*
* @return the minimum Y co-ordinate
*/
public int getLowerY() {
return y1;
}
/**
* Get the minimum Z co-ordinate of this Cuboid
*
* @return the minimum Z co-ordinate
*/
public int getLowerZ() {
return z1;
}
/**
* Get the maximum X co-ordinate of this Cuboid
*
* @return the maximum X co-ordinate
*/
public int getUpperX() {
return x2;
}
/**
* Get the maximum Y co-ordinate of this Cuboid
*
* @return the maximum Y co-ordinate
*/
public int getUpperY() {
return y2;
}
/**
* Get the maximum Z co-ordinate of this Cuboid
*
* @return the maximum Z co-ordinate
*/
public int getUpperZ() {
return z2;
}
/**
* Get the Blocks at the eight corners of the Cuboid.
*
* @return array of Block objects representing the Cuboid corners
*/
public Block[] corners() {
Block[] res = new Block[8];
World w = getWorld();
res[0] = w.getBlockAt(x1, y1, z1);
res[1] = w.getBlockAt(x1, y1, z2);
res[2] = w.getBlockAt(x1, y2, z1);
res[3] = w.getBlockAt(x1, y2, z2);
res[4] = w.getBlockAt(x2, y1, z1);
res[5] = w.getBlockAt(x2, y1, z2);
res[6] = w.getBlockAt(x2, y2, z1);
res[7] = w.getBlockAt(x2, y2, z2);
return res;
}
/**
* Expand the Cuboid in the given direction by the given amount. Negative
* amounts will shrink the Cuboid in the given direction. Shrinking a cuboid's
* face past the opposite face is not an error and will return a valid Cuboid.
*
* @param dir the direction in which to expand
* @param amount the number of blocks by which to expand
* @return a new Cuboid expanded by the given direction and amount
*/
public Cuboid expand(CuboidDirection dir, int amount) {
switch (dir) {
case North:
return new Cuboid(worldName, x1 - amount, y1, z1, x2, y2, z2);
case South:
return new Cuboid(worldName, x1, y1, z1, x2 + amount, y2, z2);
case East:
return new Cuboid(worldName, x1, y1, z1 - amount, x2, y2, z2);
case West:
return new Cuboid(worldName, x1, y1, z1, x2, y2, z2 + amount);
case Down:
return new Cuboid(worldName, x1, y1 - amount, z1, x2, y2, z2);
case Up:
return new Cuboid(worldName, x1, y1, z1, x2, y2 + amount, z2);
default:
throw new IllegalArgumentException("invalid direction " + dir);
}
}
public Cuboid expand(Direction dir, int amount) {
int ax = dir.toVector().getBlockX() == 1 ? amount : 0;
int sx = dir.toVector().getBlockX() == -1 ? -amount : 0;
int ay = dir.toVector().getBlockY() == 1 ? amount : 0;
int sy = dir.toVector().getBlockY() == -1 ? -amount : 0;
int az = dir.toVector().getBlockZ() == 1 ? amount : 0;
int sz = dir.toVector().getBlockZ() == -1 ? -amount : 0;
return new Cuboid(worldName, x1 + sx, y1 + sy, z1 + sz, x2 + ax, y2 + ay, z2 + az);
}
/**
* Shift the Cuboid in the given direction by the given amount.
*
* @param dir the direction in which to shift
* @param amount the number of blocks by which to shift
* @return a new Cuboid shifted by the given direction and amount
*/
public Cuboid shift(CuboidDirection dir, int amount) {
return expand(dir, amount).expand(dir.opposite(), -amount);
}
/**
* Outset (grow) the Cuboid in the given direction by the given amount.
*
* @param dir the direction in which to outset (must be Horizontal, Vertical, or
* Both)
* @param amount the number of blocks by which to outset
* @return a new Cuboid outset by the given direction and amount
*/
public Cuboid outset(CuboidDirection dir, int amount) {
Cuboid c;
switch (dir) {
case Horizontal:
c = expand(CuboidDirection.North, amount).expand(CuboidDirection.South, amount).expand(CuboidDirection.East, amount).expand(CuboidDirection.West, amount);
break;
case Vertical:
c = expand(CuboidDirection.Down, amount).expand(CuboidDirection.Up, amount);
break;
case Both:
c = outset(CuboidDirection.Horizontal, amount).outset(CuboidDirection.Vertical, amount);
break;
default:
throw new IllegalArgumentException("invalid direction " + dir);
}
return c;
}
/**
* Inset (shrink) the Cuboid in the given direction by the given amount.
* Equivalent to calling outset() with a negative amount.
*
* @param dir the direction in which to inset (must be Horizontal, Vertical, or
* Both)
* @param amount the number of blocks by which to inset
* @return a new Cuboid inset by the given direction and amount
*/
public Cuboid inset(CuboidDirection dir, int amount) {
return outset(dir, -amount);
}
/**
* Return true if the point at (x,y,z) is contained within this Cuboid.
*
* @param x the X co-ordinate
* @param y the Y co-ordinate
* @param z the Z co-ordinate
* @return true if the given point is within this Cuboid, false otherwise
*/
public boolean contains(int x, int y, int z) {
return x >= x1 && x <= x2 && y >= y1 && y <= y2 && z >= z1 && z <= z2;
}
/**
* Check if the given Block is contained within this Cuboid.
*
* @param b the Block to check for
* @return true if the Block is within this Cuboid, false otherwise
*/
public boolean contains(Block b) {
return contains(b.getLocation());
}
/**
* Check if the given Location is contained within this Cuboid.
*
* @param l the Location to check for
* @return true if the Location is within this Cuboid, false otherwise
*/
public boolean contains(Location l) {
return worldName.equals(l.getWorld().getName()) && contains(l.getBlockX(), l.getBlockY(), l.getBlockZ());
}
/**
* Get the volume of this Cuboid.
*
* @return the Cuboid volume, in blocks
*/
public int volume() {
return getSizeX() * getSizeY() * getSizeZ();
}
/**
* Get the average light level of all empty (air) blocks in the Cuboid. Returns
* 0 if there are no empty blocks.
*
* @return the average light level of this Cuboid
*/
public byte averageLightLevel() {
long total = 0;
int n = 0;
for (Block b : this) {
if (b.isEmpty()) {
total += b.getLightLevel();
++n;
}
}
return n > 0 ? (byte) (total / n) : 0;
}
/**
* Contract the Cuboid, returning a Cuboid with any air around the edges
* removed, just large enough to include all non-air blocks.
*
* @return a new Cuboid with no external air blocks
*/
public Cuboid contract() {
return this.contract(CuboidDirection.Down).contract(CuboidDirection.South).contract(CuboidDirection.East).contract(CuboidDirection.Up).contract(CuboidDirection.North).contract(CuboidDirection.West);
}
/**
* Contract the Cuboid in the given direction, returning a new Cuboid which has
* no exterior empty space. E.g. a direction of Down will push the top face
* downwards as much as possible.
*
* @param dir the direction in which to contract
* @return a new Cuboid contracted in the given direction
*/
public Cuboid contract(CuboidDirection dir) {
Cuboid face = getFace(dir.opposite());
switch (dir) {
case Down:
while (face.containsOnly(Material.AIR) && face.getLowerY() > this.getLowerY()) {
face = face.shift(CuboidDirection.Down, 1);
}
return new Cuboid(worldName, x1, y1, z1, x2, face.getUpperY(), z2);
case Up:
while (face.containsOnly(Material.AIR) && face.getUpperY() < this.getUpperY()) {
face = face.shift(CuboidDirection.Up, 1);
}
return new Cuboid(worldName, x1, face.getLowerY(), z1, x2, y2, z2);
case North:
while (face.containsOnly(Material.AIR) && face.getLowerX() > this.getLowerX()) {
face = face.shift(CuboidDirection.North, 1);
}
return new Cuboid(worldName, x1, y1, z1, face.getUpperX(), y2, z2);
case South:
while (face.containsOnly(Material.AIR) && face.getUpperX() < this.getUpperX()) {
face = face.shift(CuboidDirection.South, 1);
}
return new Cuboid(worldName, face.getLowerX(), y1, z1, x2, y2, z2);
case East:
while (face.containsOnly(Material.AIR) && face.getLowerZ() > this.getLowerZ()) {
face = face.shift(CuboidDirection.East, 1);
}
return new Cuboid(worldName, x1, y1, z1, x2, y2, face.getUpperZ());
case West:
while (face.containsOnly(Material.AIR) && face.getUpperZ() < this.getUpperZ()) {
face = face.shift(CuboidDirection.West, 1);
}
return new Cuboid(worldName, x1, y1, face.getLowerZ(), x2, y2, z2);
default:
throw new IllegalArgumentException("Invalid direction " + dir);
}
}
/**
* Get the Cuboid representing the face of this Cuboid. The resulting Cuboid
* will be one block thick in the axis perpendicular to the requested face.
*
* @param dir which face of the Cuboid to get
* @return the Cuboid representing this Cuboid's requested face
*/
public Cuboid getFace(CuboidDirection dir) {
switch (dir) {
case Down:
return new Cuboid(worldName, x1, y1, z1, x2, y1, z2);
case Up:
return new Cuboid(worldName, x1, y2, z1, x2, y2, z2);
case North:
return new Cuboid(worldName, x1, y1, z1, x1, y2, z2);
case South:
return new Cuboid(worldName, x2, y1, z1, x2, y2, z2);
case East:
return new Cuboid(worldName, x1, y1, z1, x2, y2, z1);
case West:
return new Cuboid(worldName, x1, y1, z2, x2, y2, z2);
default:
throw new IllegalArgumentException("Invalid direction " + dir);
}
}
/**
* Check if the Cuboid contains only blocks of the given type
*
* @param material the material to check for
* @return true if this Cuboid contains only blocks of the given type
*/
public boolean containsOnly(Material material) {
for (Block b : this) {
if (b.getType() != material) {
return false;
}
}
return true;
}
/**
* Get the Cuboid big enough to hold both this Cuboid and the given one.
*
* @param other the other Cuboid to include
* @return a new Cuboid large enough to hold this Cuboid and the given Cuboid
*/
public Cuboid getBoundingCuboid(Cuboid other) {
if (other == null) {
return this;
}
int xMin = Math.min(getLowerX(), other.getLowerX());
int yMin = Math.min(getLowerY(), other.getLowerY());
int zMin = Math.min(getLowerZ(), other.getLowerZ());
int xMax = Math.max(getUpperX(), other.getUpperX());
int yMax = Math.max(getUpperY(), other.getUpperY());
int zMax = Math.max(getUpperZ(), other.getUpperZ());
return new Cuboid(worldName, xMin, yMin, zMin, xMax, yMax, zMax);
}
/**
* Get a block relative to the lower NE point of the Cuboid.
*
* @param x the X co-ordinate
* @param y the Y co-ordinate
* @param z the Z co-ordinate
* @return the block at the given position
*/
public Block getRelativeBlock(int x, int y, int z) {
return getWorld().getBlockAt(x1 + x, y1 + y, z1 + z);
}
/**
* Get a block relative to the lower NE point of the Cuboid in the given World.
* This version of getRelativeBlock() should be used if being called many times,
* to avoid excessive calls to getWorld().
*
* @param w the World
* @param x the X co-ordinate
* @param y the Y co-ordinate
* @param z the Z co-ordinate
* @return the block at the given position
*/
public Block getRelativeBlock(World w, int x, int y, int z) {
return w.getBlockAt(x1 + x, y1 + y, z1 + z);
}
/**
* Get a list of the chunks which are fully or partially contained in this
* cuboid.
*
* @return a list of Chunk objects
*/
public List<Chunk> getChunks() {
List<Chunk> res = new ArrayList<Chunk>();
World w = getWorld();
int x1 = getLowerX() & ~0xf;
int x2 = getUpperX() & ~0xf;
int z1 = getLowerZ() & ~0xf;
int z2 = getUpperZ() & ~0xf;
for (int x = x1; x <= x2; x += 16) {
for (int z = z1; z <= z2; z += 16) {
res.add(w.getChunkAt(x >> 4, z >> 4));
}
}
return res;
}
/**
* Set all the blocks within the Cuboid to the given MaterialData, using a
* MassBlockUpdate object for fast updates.
*
* @param mat
* the MaterialData to set
* @param mbu
* the MassBlockUpdate object
*/
/**
* Reset the light level of all blocks within this Cuboid.
*/
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<Block> iterator() {
return new CuboidIterator(getWorld(), x1, y1, z1, x2, y2, z2);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#clone()
*/
@Override
public Cuboid clone() throws CloneNotSupportedException {
return new Cuboid(this);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Cuboid: " + worldName + "," + x1 + "," + y1 + "," + z1 + "=>" + x2 + "," + y2 + "," + z2;
}
public class CuboidIterator implements Iterator<Block> {
private final World w;
private final int baseX;
private final int baseY;
private final int baseZ;
private int x, y, z;
private final int sizeX;
private final int sizeY;
private final int sizeZ;
public CuboidIterator(World w, int x1, int y1, int z1, int x2, int y2, int z2) {
this.w = w;
baseX = x1;
baseY = y1;
baseZ = z1;
sizeX = Math.abs(x2 - x1) + 1;
sizeY = Math.abs(y2 - y1) + 1;
sizeZ = Math.abs(z2 - z1) + 1;
x = y = z = 0;
}
@Override
public boolean hasNext() {
return x < sizeX && y < sizeY && z < sizeZ;
}
@Override
public Block next() {
Block b = w.getBlockAt(baseX + x, baseY + y, baseZ + z);
if (++x >= sizeX) {
x = 0;
if (++y >= sizeY) {
y = 0;
++z;
}
}
return b;
}
@Override
public void remove() {
// nop
}
}
public enum CuboidDirection {
North,
East,
South,
West,
Up,
Down,
Horizontal,
Vertical,
Both,
Unknown;
public CuboidDirection opposite() {
switch (this) {
case North:
return South;
case East:
return West;
case South:
return North;
case West:
return East;
case Horizontal:
return Vertical;
case Vertical:
return Horizontal;
case Up:
return Down;
case Down:
return Up;
case Both:
return Both;
default:
return Unknown;
}
}
}
}