package net.minecraft.world.level.levelgen.structure; import com.google.common.base.MoreObjects; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import java.util.Iterator; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; import java.util.stream.IntStream; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.SectionPos; import net.minecraft.core.Vec3i; import net.minecraft.world.level.ChunkPos; import org.slf4j.Logger; /** * A simple three-dimensional mutable integer bounding box. * Note that this box is both mutable, and has an implementation of {@code hashCode()} and {@code equals()}. * This can be used as {@code HashMap} keys for example, if the user can ensure the instances themselves are not modified. */ public class BoundingBox { private static final Logger LOGGER = LogUtils.getLogger(); public static final Codec CODEC = Codec.INT_STREAM .comapFlatMap( intStream -> Util.fixedSize(intStream, 6).map(is -> new BoundingBox(is[0], is[1], is[2], is[3], is[4], is[5])), boundingBox -> IntStream.of(new int[]{boundingBox.minX, boundingBox.minY, boundingBox.minZ, boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ}) ) .stable(); private int minX; private int minY; private int minZ; private int maxX; private int maxY; private int maxZ; public BoundingBox(BlockPos pos) { this(pos.getX(), pos.getY(), pos.getZ(), pos.getX(), pos.getY(), pos.getZ()); } public BoundingBox(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { this.minX = minX; this.minY = minY; this.minZ = minZ; this.maxX = maxX; this.maxY = maxY; this.maxZ = maxZ; if (maxX < minX || maxY < minY || maxZ < minZ) { Util.logAndPauseIfInIde("Invalid bounding box data, inverted bounds for: " + this); this.minX = Math.min(minX, maxX); this.minY = Math.min(minY, maxY); this.minZ = Math.min(minZ, maxZ); this.maxX = Math.max(minX, maxX); this.maxY = Math.max(minY, maxY); this.maxZ = Math.max(minZ, maxZ); } } public static BoundingBox fromCorners(Vec3i first, Vec3i second) { return new BoundingBox( Math.min(first.getX(), second.getX()), Math.min(first.getY(), second.getY()), Math.min(first.getZ(), second.getZ()), Math.max(first.getX(), second.getX()), Math.max(first.getY(), second.getY()), Math.max(first.getZ(), second.getZ()) ); } public static BoundingBox infinite() { return new BoundingBox(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); } /** * Create a bounding box with the specified dimensions and rotate it. Used to project a possible new component Bounding Box - to check if it would cut anything already spawned. */ public static BoundingBox orientBox( int structureMinX, int structureMinY, int structureMinZ, int xMin, int yMin, int zMin, int xMax, int yMax, int zMax, Direction facing ) { switch (facing) { case SOUTH: default: return new BoundingBox( structureMinX + xMin, structureMinY + yMin, structureMinZ + zMin, structureMinX + xMax - 1 + xMin, structureMinY + yMax - 1 + yMin, structureMinZ + zMax - 1 + zMin ); case NORTH: return new BoundingBox( structureMinX + xMin, structureMinY + yMin, structureMinZ - zMax + 1 + zMin, structureMinX + xMax - 1 + xMin, structureMinY + yMax - 1 + yMin, structureMinZ + zMin ); case WEST: return new BoundingBox( structureMinX - zMax + 1 + zMin, structureMinY + yMin, structureMinZ + xMin, structureMinX + zMin, structureMinY + yMax - 1 + yMin, structureMinZ + xMax - 1 + xMin ); case EAST: return new BoundingBox( structureMinX + zMin, structureMinY + yMin, structureMinZ + xMin, structureMinX + zMax - 1 + zMin, structureMinY + yMax - 1 + yMin, structureMinZ + xMax - 1 + xMin ); } } public Stream intersectingChunks() { int i = SectionPos.blockToSectionCoord(this.minX()); int j = SectionPos.blockToSectionCoord(this.minZ()); int k = SectionPos.blockToSectionCoord(this.maxX()); int l = SectionPos.blockToSectionCoord(this.maxZ()); return ChunkPos.rangeClosed(new ChunkPos(i, j), new ChunkPos(k, l)); } /** * @return {@code true} if {@code box} intersects this box. */ public boolean intersects(BoundingBox box) { return this.maxX >= box.minX && this.minX <= box.maxX && this.maxZ >= box.minZ && this.minZ <= box.maxZ && this.maxY >= box.minY && this.minY <= box.maxY; } /** * @return {@code true} if this bounding box intersects the horizontal x/z region described by the min and max parameters. */ public boolean intersects(int minX, int minZ, int maxX, int maxZ) { return this.maxX >= minX && this.minX <= maxX && this.maxZ >= minZ && this.minZ <= maxZ; } public static Optional encapsulatingPositions(Iterable positions) { Iterator iterator = positions.iterator(); if (!iterator.hasNext()) { return Optional.empty(); } else { BoundingBox boundingBox = new BoundingBox((BlockPos)iterator.next()); iterator.forEachRemaining(boundingBox::encapsulate); return Optional.of(boundingBox); } } public static Optional encapsulatingBoxes(Iterable boxes) { Iterator iterator = boxes.iterator(); if (!iterator.hasNext()) { return Optional.empty(); } else { BoundingBox boundingBox = (BoundingBox)iterator.next(); BoundingBox boundingBox2 = new BoundingBox(boundingBox.minX, boundingBox.minY, boundingBox.minZ, boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ); iterator.forEachRemaining(boundingBox2::encapsulate); return Optional.of(boundingBox2); } } /** * Expands this box to be at least large enough to contain {@code box}. */ @Deprecated public BoundingBox encapsulate(BoundingBox box) { this.minX = Math.min(this.minX, box.minX); this.minY = Math.min(this.minY, box.minY); this.minZ = Math.min(this.minZ, box.minZ); this.maxX = Math.max(this.maxX, box.maxX); this.maxY = Math.max(this.maxY, box.maxY); this.maxZ = Math.max(this.maxZ, box.maxZ); return this; } /** * Expands this box to be at least large enough to contain {@code pos}. */ @Deprecated public BoundingBox encapsulate(BlockPos pos) { this.minX = Math.min(this.minX, pos.getX()); this.minY = Math.min(this.minY, pos.getY()); this.minZ = Math.min(this.minZ, pos.getZ()); this.maxX = Math.max(this.maxX, pos.getX()); this.maxY = Math.max(this.maxY, pos.getY()); this.maxZ = Math.max(this.maxZ, pos.getZ()); return this; } /** * Translates this box by the given coordinates, modifying the current box. */ @Deprecated public BoundingBox move(int x, int y, int z) { this.minX += x; this.minY += y; this.minZ += z; this.maxX += x; this.maxY += y; this.maxZ += z; return this; } /** * Translates this box by the given vector, modifying the current box. */ @Deprecated public BoundingBox move(Vec3i vector) { return this.move(vector.getX(), vector.getY(), vector.getZ()); } /** * @return A new bounding box equal to this box, translated by the given coordinates. */ public BoundingBox moved(int x, int y, int z) { return new BoundingBox(this.minX + x, this.minY + y, this.minZ + z, this.maxX + x, this.maxY + y, this.maxZ + z); } /** * Expands this box by a fixed {@code value} in all directions. */ public BoundingBox inflatedBy(int value) { return this.inflatedBy(value, value, value); } public BoundingBox inflatedBy(int x, int y, int z) { return new BoundingBox(this.minX() - x, this.minY() - y, this.minZ() - z, this.maxX() + x, this.maxY() + y, this.maxZ() + z); } /** * @return {@code true} if the bounding box contains the {@code vector}. */ public boolean isInside(Vec3i vector) { return this.isInside(vector.getX(), vector.getY(), vector.getZ()); } public boolean isInside(int x, int y, int z) { return x >= this.minX && x <= this.maxX && z >= this.minZ && z <= this.maxZ && y >= this.minY && y <= this.maxY; } /** * Returns a vector describing the dimensions of this bounding box. * Note that unlike {@code getXSpan()}, {@code getYSpan()}, and {@code getZSpan()}, the length is interpreted here as the difference in coordinates. So a box over a 1x1x1 area, which still contains a single point, will report length zero. */ public Vec3i getLength() { return new Vec3i(this.maxX - this.minX, this.maxY - this.minY, this.maxZ - this.minZ); } /** * @return The length of this bounding box along the x-axis. */ public int getXSpan() { return this.maxX - this.minX + 1; } /** * @return The length of this bounding box along the y-axis. */ public int getYSpan() { return this.maxY - this.minY + 1; } /** * @return The length of this bounding box along the z-axis. */ public int getZSpan() { return this.maxZ - this.minZ + 1; } /** * @return The center of this bounding box. Note in even-sized dimensions the center position will be offset in the positive direction. */ public BlockPos getCenter() { return new BlockPos(this.minX + (this.maxX - this.minX + 1) / 2, this.minY + (this.maxY - this.minY + 1) / 2, this.minZ + (this.maxZ - this.minZ + 1) / 2); } public void forAllCorners(Consumer pos) { BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); pos.accept(mutableBlockPos.set(this.maxX, this.maxY, this.maxZ)); pos.accept(mutableBlockPos.set(this.minX, this.maxY, this.maxZ)); pos.accept(mutableBlockPos.set(this.maxX, this.minY, this.maxZ)); pos.accept(mutableBlockPos.set(this.minX, this.minY, this.maxZ)); pos.accept(mutableBlockPos.set(this.maxX, this.maxY, this.minZ)); pos.accept(mutableBlockPos.set(this.minX, this.maxY, this.minZ)); pos.accept(mutableBlockPos.set(this.maxX, this.minY, this.minZ)); pos.accept(mutableBlockPos.set(this.minX, this.minY, this.minZ)); } public String toString() { return MoreObjects.toStringHelper(this) .add("minX", this.minX) .add("minY", this.minY) .add("minZ", this.minZ) .add("maxX", this.maxX) .add("maxY", this.maxY) .add("maxZ", this.maxZ) .toString(); } public boolean equals(Object object) { if (this == object) { return true; } else { return !(object instanceof BoundingBox boundingBox) ? false : this.minX == boundingBox.minX && this.minY == boundingBox.minY && this.minZ == boundingBox.minZ && this.maxX == boundingBox.maxX && this.maxY == boundingBox.maxY && this.maxZ == boundingBox.maxZ; } } public int hashCode() { return Objects.hash(new Object[]{this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ}); } public int minX() { return this.minX; } public int minY() { return this.minY; } public int minZ() { return this.minZ; } public int maxX() { return this.maxX; } public int maxY() { return this.maxY; } public int maxZ() { return this.maxZ; } }