package net.minecraft.core; import com.google.common.collect.AbstractIterator; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayDeque; import java.util.Optional; import java.util.Queue; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.IntStream; import java.util.stream.Stream; import java.util.stream.StreamSupport; import net.minecraft.Util; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Unmodifiable; import org.slf4j.Logger; @Unmodifiable public class BlockPos extends Vec3i { public static final Codec CODEC = Codec.INT_STREAM .comapFlatMap( intStream -> Util.fixedSize(intStream, 3).map(is -> new BlockPos(is[0], is[1], is[2])), blockPos -> IntStream.of(new int[]{blockPos.getX(), blockPos.getY(), blockPos.getZ()}) ) .stable(); public static final StreamCodec STREAM_CODEC = new StreamCodec() { public BlockPos decode(ByteBuf byteBuf) { return FriendlyByteBuf.readBlockPos(byteBuf); } public void encode(ByteBuf byteBuf, BlockPos blockPos) { FriendlyByteBuf.writeBlockPos(byteBuf, blockPos); } }; private static final Logger LOGGER = LogUtils.getLogger(); /** * An immutable BlockPos with zero as all coordinates. */ public static final BlockPos ZERO = new BlockPos(0, 0, 0); public static final int PACKED_HORIZONTAL_LENGTH = 1 + Mth.log2(Mth.smallestEncompassingPowerOfTwo(30000000)); public static final int PACKED_Y_LENGTH = 64 - 2 * PACKED_HORIZONTAL_LENGTH; private static final long PACKED_X_MASK = (1L << PACKED_HORIZONTAL_LENGTH) - 1L; private static final long PACKED_Y_MASK = (1L << PACKED_Y_LENGTH) - 1L; private static final long PACKED_Z_MASK = (1L << PACKED_HORIZONTAL_LENGTH) - 1L; private static final int Y_OFFSET = 0; private static final int Z_OFFSET = PACKED_Y_LENGTH; private static final int X_OFFSET = PACKED_Y_LENGTH + PACKED_HORIZONTAL_LENGTH; public static final int MAX_HORIZONTAL_COORDINATE = (1 << PACKED_HORIZONTAL_LENGTH) / 2 - 1; public BlockPos(int x, int y, int z) { super(x, y, z); } public BlockPos(Vec3i vector) { this(vector.getX(), vector.getY(), vector.getZ()); } public static long offset(long pos, Direction direction) { return offset(pos, direction.getStepX(), direction.getStepY(), direction.getStepZ()); } public static long offset(long pos, int dx, int dy, int dz) { return asLong(getX(pos) + dx, getY(pos) + dy, getZ(pos) + dz); } public static int getX(long packedPos) { return (int)(packedPos << 64 - X_OFFSET - PACKED_HORIZONTAL_LENGTH >> 64 - PACKED_HORIZONTAL_LENGTH); } public static int getY(long packedPos) { return (int)(packedPos << 64 - PACKED_Y_LENGTH >> 64 - PACKED_Y_LENGTH); } public static int getZ(long packedPos) { return (int)(packedPos << 64 - Z_OFFSET - PACKED_HORIZONTAL_LENGTH >> 64 - PACKED_HORIZONTAL_LENGTH); } public static BlockPos of(long packedPos) { return new BlockPos(getX(packedPos), getY(packedPos), getZ(packedPos)); } public static BlockPos containing(double x, double y, double z) { return new BlockPos(Mth.floor(x), Mth.floor(y), Mth.floor(z)); } public static BlockPos containing(Position position) { return containing(position.x(), position.y(), position.z()); } public static BlockPos min(BlockPos pos1, BlockPos pos2) { return new BlockPos(Math.min(pos1.getX(), pos2.getX()), Math.min(pos1.getY(), pos2.getY()), Math.min(pos1.getZ(), pos2.getZ())); } public static BlockPos max(BlockPos pos1, BlockPos pos2) { return new BlockPos(Math.max(pos1.getX(), pos2.getX()), Math.max(pos1.getY(), pos2.getY()), Math.max(pos1.getZ(), pos2.getZ())); } public long asLong() { return asLong(this.getX(), this.getY(), this.getZ()); } public static long asLong(int x, int y, int z) { long l = 0L; l |= (x & PACKED_X_MASK) << X_OFFSET; l |= (y & PACKED_Y_MASK) << 0; return l | (z & PACKED_Z_MASK) << Z_OFFSET; } public static long getFlatIndex(long packedPos) { return packedPos & -16L; } public BlockPos offset(int i, int j, int k) { return i == 0 && j == 0 && k == 0 ? this : new BlockPos(this.getX() + i, this.getY() + j, this.getZ() + k); } public Vec3 getCenter() { return Vec3.atCenterOf(this); } public Vec3 getBottomCenter() { return Vec3.atBottomCenterOf(this); } public BlockPos offset(Vec3i vec3i) { return this.offset(vec3i.getX(), vec3i.getY(), vec3i.getZ()); } public BlockPos subtract(Vec3i vector) { return this.offset(-vector.getX(), -vector.getY(), -vector.getZ()); } public BlockPos multiply(int i) { if (i == 1) { return this; } else { return i == 0 ? ZERO : new BlockPos(this.getX() * i, this.getY() * i, this.getZ() * i); } } public BlockPos above() { return this.relative(Direction.UP); } public BlockPos above(int i) { return this.relative(Direction.UP, i); } public BlockPos below() { return this.relative(Direction.DOWN); } public BlockPos below(int i) { return this.relative(Direction.DOWN, i); } public BlockPos north() { return this.relative(Direction.NORTH); } public BlockPos north(int i) { return this.relative(Direction.NORTH, i); } public BlockPos south() { return this.relative(Direction.SOUTH); } public BlockPos south(int distance) { return this.relative(Direction.SOUTH, distance); } public BlockPos west() { return this.relative(Direction.WEST); } public BlockPos west(int i) { return this.relative(Direction.WEST, i); } public BlockPos east() { return this.relative(Direction.EAST); } public BlockPos east(int i) { return this.relative(Direction.EAST, i); } public BlockPos relative(Direction direction) { return new BlockPos(this.getX() + direction.getStepX(), this.getY() + direction.getStepY(), this.getZ() + direction.getStepZ()); } public BlockPos relative(Direction direction, int i) { return i == 0 ? this : new BlockPos(this.getX() + direction.getStepX() * i, this.getY() + direction.getStepY() * i, this.getZ() + direction.getStepZ() * i); } public BlockPos relative(Direction.Axis axis, int i) { if (i == 0) { return this; } else { int j = axis == Direction.Axis.X ? i : 0; int k = axis == Direction.Axis.Y ? i : 0; int l = axis == Direction.Axis.Z ? i : 0; return new BlockPos(this.getX() + j, this.getY() + k, this.getZ() + l); } } public BlockPos rotate(Rotation rotation) { switch (rotation) { case NONE: default: return this; case CLOCKWISE_90: return new BlockPos(-this.getZ(), this.getY(), this.getX()); case CLOCKWISE_180: return new BlockPos(-this.getX(), this.getY(), -this.getZ()); case COUNTERCLOCKWISE_90: return new BlockPos(this.getZ(), this.getY(), -this.getX()); } } public BlockPos cross(Vec3i vector) { return new BlockPos( this.getY() * vector.getZ() - this.getZ() * vector.getY(), this.getZ() * vector.getX() - this.getX() * vector.getZ(), this.getX() * vector.getY() - this.getY() * vector.getX() ); } public BlockPos atY(int y) { return new BlockPos(this.getX(), y, this.getZ()); } /** * Returns a version of this BlockPos that is guaranteed to be immutable. * *

When storing a BlockPos given to you for an extended period of time, make sure you * use this in case the value is changed internally.

*/ public BlockPos immutable() { return this; } public BlockPos.MutableBlockPos mutable() { return new BlockPos.MutableBlockPos(this.getX(), this.getY(), this.getZ()); } public Vec3 clampLocationWithin(Vec3 pos) { return new Vec3( Mth.clamp(pos.x, (double)(this.getX() + 1.0E-5F), this.getX() + 1.0 - 1.0E-5F), Mth.clamp(pos.y, (double)(this.getY() + 1.0E-5F), this.getY() + 1.0 - 1.0E-5F), Mth.clamp(pos.z, (double)(this.getZ() + 1.0E-5F), this.getZ() + 1.0 - 1.0E-5F) ); } public static Iterable randomInCube(RandomSource random, int amount, BlockPos center, int radius) { return randomBetweenClosed( random, amount, center.getX() - radius, center.getY() - radius, center.getZ() - radius, center.getX() + radius, center.getY() + radius, center.getZ() + radius ); } @Deprecated public static Stream squareOutSouthEast(BlockPos pos) { return Stream.of(pos, pos.south(), pos.east(), pos.south().east()); } public static Iterable randomBetweenClosed(RandomSource random, int amount, int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { int i = maxX - minX + 1; int j = maxY - minY + 1; int k = maxZ - minZ + 1; return () -> new AbstractIterator() { final BlockPos.MutableBlockPos nextPos = new BlockPos.MutableBlockPos(); int counter = amount; protected BlockPos computeNext() { if (this.counter <= 0) { return this.endOfData(); } else { BlockPos blockPos = this.nextPos.set(minX + random.nextInt(i), minY + random.nextInt(j), minZ + random.nextInt(k)); this.counter--; return blockPos; } } }; } public static Iterable withinManhattan(BlockPos pos, int xSize, int ySize, int zSize) { int i = xSize + ySize + zSize; int j = pos.getX(); int k = pos.getY(); int l = pos.getZ(); return () -> new AbstractIterator() { private final BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); private int currentDepth; private int maxX; private int maxY; private int x; private int y; private boolean zMirror; protected BlockPos computeNext() { if (this.zMirror) { this.zMirror = false; this.cursor.setZ(l - (this.cursor.getZ() - l)); return this.cursor; } else { BlockPos blockPos; for (blockPos = null; blockPos == null; this.y++) { if (this.y > this.maxY) { this.x++; if (this.x > this.maxX) { this.currentDepth++; if (this.currentDepth > i) { return this.endOfData(); } this.maxX = Math.min(xSize, this.currentDepth); this.x = -this.maxX; } this.maxY = Math.min(ySize, this.currentDepth - Math.abs(this.x)); this.y = -this.maxY; } int ix = this.x; int jx = this.y; int kx = this.currentDepth - Math.abs(ix) - Math.abs(jx); if (kx <= zSize) { this.zMirror = kx != 0; blockPos = this.cursor.set(j + ix, k + jx, l + kx); } } return blockPos; } } }; } public static Optional findClosestMatch(BlockPos pos, int width, int height, Predicate posFilter) { for (BlockPos blockPos : withinManhattan(pos, width, height, width)) { if (posFilter.test(blockPos)) { return Optional.of(blockPos); } } return Optional.empty(); } /** * Returns a stream of positions in a box shape, ordered by closest to furthest. Returns by definition the given position as first element in the stream. */ public static Stream withinManhattanStream(BlockPos pos, int xSize, int ySize, int zSize) { return StreamSupport.stream(withinManhattan(pos, xSize, ySize, zSize).spliterator(), false); } public static Iterable betweenClosed(AABB box) { BlockPos blockPos = containing(box.minX, box.minY, box.minZ); BlockPos blockPos2 = containing(box.maxX, box.maxY, box.maxZ); return betweenClosed(blockPos, blockPos2); } public static Iterable betweenClosed(BlockPos firstPos, BlockPos secondPos) { return betweenClosed( Math.min(firstPos.getX(), secondPos.getX()), Math.min(firstPos.getY(), secondPos.getY()), Math.min(firstPos.getZ(), secondPos.getZ()), Math.max(firstPos.getX(), secondPos.getX()), Math.max(firstPos.getY(), secondPos.getY()), Math.max(firstPos.getZ(), secondPos.getZ()) ); } public static Stream betweenClosedStream(BlockPos firstPos, BlockPos secondPos) { return StreamSupport.stream(betweenClosed(firstPos, secondPos).spliterator(), false); } public static Stream betweenClosedStream(BoundingBox box) { return betweenClosedStream( Math.min(box.minX(), box.maxX()), Math.min(box.minY(), box.maxY()), Math.min(box.minZ(), box.maxZ()), Math.max(box.minX(), box.maxX()), Math.max(box.minY(), box.maxY()), Math.max(box.minZ(), box.maxZ()) ); } public static Stream betweenClosedStream(AABB aabb) { return betweenClosedStream(Mth.floor(aabb.minX), Mth.floor(aabb.minY), Mth.floor(aabb.minZ), Mth.floor(aabb.maxX), Mth.floor(aabb.maxY), Mth.floor(aabb.maxZ)); } public static Stream betweenClosedStream(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { return StreamSupport.stream(betweenClosed(minX, minY, minZ, maxX, maxY, maxZ).spliterator(), false); } /** * Creates an Iterable that returns all positions in the box specified by the given corners. Coordinates must be in order. e.g. x1 <= x2. * * This method uses {@link BlockPos.MutableBlockPos MutableBlockPos} instead of regular BlockPos, which grants better performance. However, the resulting BlockPos instances can only be used inside the iteration loop (as otherwise the value will change), unless {@link #toImmutable()} is called. This method is ideal for searching large areas and only storing a few locations. * * @see #betweenClosed(BlockPos, BlockPos) * @see #betweenClosed(int, int, int, int, int, int) */ public static Iterable betweenClosed(int x1, int y1, int z1, int x2, int y2, int z2) { int i = x2 - x1 + 1; int j = y2 - y1 + 1; int k = z2 - z1 + 1; int l = i * j * k; return () -> new AbstractIterator() { private final BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos(); private int index; protected BlockPos computeNext() { if (this.index == l) { return this.endOfData(); } else { int ix = this.index % i; int jx = this.index / i; int kx = jx % j; int lx = jx / j; this.index++; return this.cursor.set(x1 + ix, y1 + kx, z1 + lx); } } }; } public static Iterable spiralAround(BlockPos center, int size, Direction rotationDirection, Direction expansionDirection) { Validate.validState(rotationDirection.getAxis() != expansionDirection.getAxis(), "The two directions cannot be on the same axis"); return () -> new AbstractIterator() { private final Direction[] directions = new Direction[]{ rotationDirection, expansionDirection, rotationDirection.getOpposite(), expansionDirection.getOpposite() }; private final BlockPos.MutableBlockPos cursor = center.mutable().move(expansionDirection); private final int legs = 4 * size; private int leg = -1; private int legSize; private int legIndex; private int lastX = this.cursor.getX(); private int lastY = this.cursor.getY(); private int lastZ = this.cursor.getZ(); protected BlockPos.MutableBlockPos computeNext() { this.cursor.set(this.lastX, this.lastY, this.lastZ).move(this.directions[(this.leg + 4) % 4]); this.lastX = this.cursor.getX(); this.lastY = this.cursor.getY(); this.lastZ = this.cursor.getZ(); if (this.legIndex >= this.legSize) { if (this.leg >= this.legs) { return this.endOfData(); } this.leg++; this.legIndex = 0; this.legSize = this.leg / 2 + 1; } this.legIndex++; return this.cursor; } }; } public static int breadthFirstTraversal( BlockPos startPos, int radius, int maxBlocks, BiConsumer> childrenGetter, Function action ) { Queue> queue = new ArrayDeque(); LongSet longSet = new LongOpenHashSet(); queue.add(Pair.of(startPos, 0)); int i = 0; while (!queue.isEmpty()) { Pair pair = (Pair)queue.poll(); BlockPos blockPos = pair.getLeft(); int j = pair.getRight(); long l = blockPos.asLong(); if (longSet.add(l)) { BlockPos.TraversalNodeStatus traversalNodeStatus = (BlockPos.TraversalNodeStatus)action.apply(blockPos); if (traversalNodeStatus != BlockPos.TraversalNodeStatus.SKIP) { if (traversalNodeStatus == BlockPos.TraversalNodeStatus.STOP) { break; } if (++i >= maxBlocks) { return i; } if (j < radius) { childrenGetter.accept(blockPos, (Consumer)blockPosx -> queue.add(Pair.of(blockPosx, j + 1))); } } } } return i; } public static class MutableBlockPos extends BlockPos { public MutableBlockPos() { this(0, 0, 0); } public MutableBlockPos(int i, int j, int k) { super(i, j, k); } public MutableBlockPos(double x, double y, double z) { this(Mth.floor(x), Mth.floor(y), Mth.floor(z)); } @Override public BlockPos offset(int i, int j, int k) { return super.offset(i, j, k).immutable(); } @Override public BlockPos multiply(int i) { return super.multiply(i).immutable(); } @Override public BlockPos relative(Direction direction, int i) { return super.relative(direction, i).immutable(); } @Override public BlockPos relative(Direction.Axis axis, int i) { return super.relative(axis, i).immutable(); } @Override public BlockPos rotate(Rotation rotation) { return super.rotate(rotation).immutable(); } public BlockPos.MutableBlockPos set(int x, int y, int z) { this.setX(x); this.setY(y); this.setZ(z); return this; } public BlockPos.MutableBlockPos set(double x, double y, double z) { return this.set(Mth.floor(x), Mth.floor(y), Mth.floor(z)); } public BlockPos.MutableBlockPos set(Vec3i vector) { return this.set(vector.getX(), vector.getY(), vector.getZ()); } public BlockPos.MutableBlockPos set(long packedPos) { return this.set(getX(packedPos), getY(packedPos), getZ(packedPos)); } public BlockPos.MutableBlockPos set(AxisCycle cycle, int x, int y, int z) { return this.set(cycle.cycle(x, y, z, Direction.Axis.X), cycle.cycle(x, y, z, Direction.Axis.Y), cycle.cycle(x, y, z, Direction.Axis.Z)); } public BlockPos.MutableBlockPos setWithOffset(Vec3i pos, Direction direction) { return this.set(pos.getX() + direction.getStepX(), pos.getY() + direction.getStepY(), pos.getZ() + direction.getStepZ()); } public BlockPos.MutableBlockPos setWithOffset(Vec3i vector, int offsetX, int offsetY, int offsetZ) { return this.set(vector.getX() + offsetX, vector.getY() + offsetY, vector.getZ() + offsetZ); } public BlockPos.MutableBlockPos setWithOffset(Vec3i pos, Vec3i offset) { return this.set(pos.getX() + offset.getX(), pos.getY() + offset.getY(), pos.getZ() + offset.getZ()); } public BlockPos.MutableBlockPos move(Direction direction) { return this.move(direction, 1); } public BlockPos.MutableBlockPos move(Direction direction, int n) { return this.set(this.getX() + direction.getStepX() * n, this.getY() + direction.getStepY() * n, this.getZ() + direction.getStepZ() * n); } public BlockPos.MutableBlockPos move(int x, int y, int z) { return this.set(this.getX() + x, this.getY() + y, this.getZ() + z); } public BlockPos.MutableBlockPos move(Vec3i offset) { return this.set(this.getX() + offset.getX(), this.getY() + offset.getY(), this.getZ() + offset.getZ()); } public BlockPos.MutableBlockPos clamp(Direction.Axis axis, int min, int max) { switch (axis) { case X: return this.set(Mth.clamp(this.getX(), min, max), this.getY(), this.getZ()); case Y: return this.set(this.getX(), Mth.clamp(this.getY(), min, max), this.getZ()); case Z: return this.set(this.getX(), this.getY(), Mth.clamp(this.getZ(), min, max)); default: throw new IllegalStateException("Unable to clamp axis " + axis); } } public BlockPos.MutableBlockPos setX(int i) { super.setX(i); return this; } public BlockPos.MutableBlockPos setY(int i) { super.setY(i); return this; } public BlockPos.MutableBlockPos setZ(int i) { super.setZ(i); return this; } @Override public BlockPos immutable() { return new BlockPos(this); } } public static enum TraversalNodeStatus { ACCEPT, SKIP, STOP; } }