Paper/patches/server/0739-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch
Nassim Jahnke 72e87abc2d
Strip raytracing for EntityLiving#hasLineOfSight
Co-authored-by: Paul Sauve <paul@technove.co>
2023-09-10 12:28:03 +10:00

2122 lines
104 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 4 May 2020 10:06:24 -0700
Subject: [PATCH] Highly optimise single and multi-AABB VoxelShapes and
collisions
diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java
index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644
--- a/src/main/java/io/papermc/paper/util/CachedLists.java
+++ b/src/main/java/io/papermc/paper/util/CachedLists.java
@@ -1,8 +1,57 @@
package io.papermc.paper.util;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.AABB;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.util.UnsafeList;
+import java.util.List;
+
public final class CachedLists {
- public static void reset() {
+ // Paper start - optimise collisions
+ static final UnsafeList<AABB> TEMP_COLLISION_LIST = new UnsafeList<>(1024);
+ static boolean tempCollisionListInUse;
+
+ public static UnsafeList<AABB> getTempCollisionList() {
+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) {
+ return new UnsafeList<>(16);
+ }
+ tempCollisionListInUse = true;
+ return TEMP_COLLISION_LIST;
+ }
+
+ public static void returnTempCollisionList(List<AABB> list) {
+ if (list != TEMP_COLLISION_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempCollisionListInUse = false;
+ }
+ static final UnsafeList<Entity> TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024);
+ static boolean tempGetEntitiesListInUse;
+
+ public static UnsafeList<Entity> getTempGetEntitiesList() {
+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) {
+ return new UnsafeList<>(16);
+ }
+ tempGetEntitiesListInUse = true;
+ return TEMP_GET_ENTITIES_LIST;
+ }
+
+ public static void returnTempGetEntitiesList(List<Entity> list) {
+ if (list != TEMP_GET_ENTITIES_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempGetEntitiesListInUse = false;
+ }
+ // Paper end - optimise collisions
+
+ public static void reset() {
+ // Paper start - optimise collisions
+ TEMP_COLLISION_LIST.completeReset();
+ TEMP_GET_ENTITIES_LIST.completeReset();
+ // Paper end - optimise collisions
}
}
diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..a87f6380b2c387fb0cdd40d5087b5c93492e3c88
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java
@@ -0,0 +1,899 @@
+package io.papermc.paper.util;
+
+import io.papermc.paper.voxel.AABBVoxelShape;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.WorldGenRegion;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.CollisionGetter;
+import net.minecraft.world.level.EntityGetter;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.WorldBorder;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.level.material.FlowingFluid;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import net.minecraft.world.phys.shapes.ArrayVoxelShape;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.EntityCollisionContext;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.List;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+public final class CollisionUtil {
+
+ public static final double COLLISION_EPSILON = 1.0E-7;
+
+ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to always have voxelshape of empty
+ public static final long KNOWN_FULL_BLOCK = 0b01; // known to always have voxelshape of full cube
+ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info
+ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions
+
+ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) {
+ return block.shapeExceedsCube() || block.getBlock() == Blocks.MOVING_PISTON;
+ }
+
+ public static boolean isEmpty(final AABB aabb) {
+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON;
+ }
+
+ public static boolean isEmpty(final double minX, final double minY, final double minZ,
+ final double maxX, final double maxY, final double maxZ) {
+ return (maxX - minX) < COLLISION_EPSILON && (maxY - minY) < COLLISION_EPSILON && (maxZ - minZ) < COLLISION_EPSILON;
+ }
+
+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) {
+ double x = (double)(chunkX << 4);
+ double z = (double)(chunkZ << 4);
+ // use a bounding box bigger than the chunk to prevent entities from entering it on move
+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON,
+ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false);
+ }
+
+ /*
+ A couple of rules for VoxelShape collisions:
+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement
+ checks.
+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite
+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code
+ will automatically round it to 0.
+ */
+
+ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1,
+ final double maxY1, final double maxZ1, final double minX2, final double minY2,
+ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) {
+ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON &&
+ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON &&
+ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON;
+ }
+
+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ,
+ final double maxX, final double maxY, final double maxZ) {
+ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON &&
+ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON &&
+ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON;
+ }
+
+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) {
+ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON &&
+ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON &&
+ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON;
+ }
+
+ public static double collideX(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideY(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideZ(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
+ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static AABB offsetX(final AABB box, final double dx) {
+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB offsetY(final AABB box, final double dy) {
+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB offsetZ(final AABB box, final double dz) {
+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0
+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0
+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0
+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0
+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0
+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0
+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0
+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false);
+ }
+
+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0
+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0
+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false);
+ }
+
+ public static double performCollisionsX(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideX(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static double performCollisionsY(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideY(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static double performCollisionsZ(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideZ(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, final List<AABB> potentialCollisions) {
+ double x = moveVector.x;
+ double y = moveVector.y;
+ double z = moveVector.z;
+
+ if (y != 0.0) {
+ y = performCollisionsY(axisalignedbb, y, potentialCollisions);
+ if (y != 0.0) {
+ axisalignedbb = offsetY(axisalignedbb, y);
+ }
+ }
+
+ final boolean xSmaller = Math.abs(x) < Math.abs(z);
+
+ if (xSmaller && z != 0.0) {
+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions);
+ if (z != 0.0) {
+ axisalignedbb = offsetZ(axisalignedbb, z);
+ }
+ }
+
+ if (x != 0.0) {
+ x = performCollisionsX(axisalignedbb, x, potentialCollisions);
+ if (!xSmaller && x != 0.0) {
+ axisalignedbb = offsetX(axisalignedbb, x);
+ }
+ }
+
+ if (!xSmaller && z != 0.0) {
+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions);
+ }
+
+ return new Vec3(x, y, z);
+ }
+
+ public static boolean addBoxesToIfIntersects(final VoxelShape shape, final AABB aabb, final List<AABB> list) {
+ if (shape instanceof AABBVoxelShape) {
+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape;
+ if (voxelShapeIntersect(shapeCasted.aabb, aabb) && !isEmpty(shapeCasted.aabb)) {
+ list.add(shapeCasted.aabb);
+ return true;
+ }
+ return false;
+ } else if (shape instanceof ArrayVoxelShape) {
+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape;
+ // this can be optimised by checking an "overall shape" first, but not needed
+
+ final double offX = shapeCasted.getOffsetX();
+ final double offY = shapeCasted.getOffsetY();
+ final double offZ = shapeCasted.getOffsetZ();
+
+ boolean ret = false;
+
+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) {
+ final double minX, minY, minZ, maxX, maxY, maxZ;
+ if (voxelShapeIntersect(aabb, minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ,
+ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ)
+ && !isEmpty(minX, minY, minZ, maxX, maxY, maxZ)) {
+ list.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ, false));
+ ret = true;
+ }
+ }
+
+ return ret;
+ } else {
+ final List<AABB> boxes = shape.toAabbs();
+
+ boolean ret = false;
+
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
+ final AABB box = boxes.get(i);
+ if (voxelShapeIntersect(box, aabb) && !isEmpty(box)) {
+ list.add(box);
+ ret = true;
+ }
+ }
+
+ return ret;
+ }
+ }
+
+ public static void addBoxesTo(final VoxelShape shape, final List<AABB> list) {
+ if (shape instanceof AABBVoxelShape) {
+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape;
+ if (!isEmpty(shapeCasted.aabb)) {
+ list.add(shapeCasted.aabb);
+ }
+ } else if (shape instanceof ArrayVoxelShape) {
+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape;
+
+ final double offX = shapeCasted.getOffsetX();
+ final double offY = shapeCasted.getOffsetY();
+ final double offZ = shapeCasted.getOffsetZ();
+
+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) {
+ final AABB box = boundingBox.move(offX, offY, offZ);
+ if (!isEmpty(box)) {
+ list.add(box);
+ }
+ }
+ } else {
+ final List<AABB> boxes = shape.toAabbs();
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
+ final AABB box = boxes.get(i);
+ if (!isEmpty(box)) {
+ list.add(box);
+ }
+ }
+ }
+ }
+
+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final AABB boundingBox) {
+ return isAlmostCollidingOnBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
+ }
+
+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX,
+ final double boxMinZ, final double boxMaxZ) {
+ final double borderMinX = worldborder.getMinX(); // -X
+ final double borderMaxX = worldborder.getMaxX(); // +X
+
+ final double borderMinZ = worldborder.getMinZ(); // -Z
+ final double borderMaxZ = worldborder.getMaxZ(); // +Z
+
+ return
+ // Not intersecting if we're smaller
+ !voxelShapeIntersect(
+ boxMinX + COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + COLLISION_EPSILON,
+ boxMaxX - COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - COLLISION_EPSILON,
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
+ )
+ &&
+
+ // Are intersecting if we're larger
+ voxelShapeIntersect(
+ boxMinX - COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - COLLISION_EPSILON,
+ boxMaxX + COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + COLLISION_EPSILON,
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
+ );
+ }
+
+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final AABB boundingBox) {
+ return isCollidingWithBorderEdge(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
+ }
+
+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final double boxMinX, final double boxMaxX,
+ final double boxMinZ, final double boxMaxZ) {
+ final double borderMinX = worldborder.getMinX() + COLLISION_EPSILON; // -X
+ final double borderMaxX = worldborder.getMaxX() - COLLISION_EPSILON; // +X
+
+ final double borderMinZ = worldborder.getMinZ() + COLLISION_EPSILON; // -Z
+ final double borderMaxZ = worldborder.getMaxZ() - COLLISION_EPSILON; // +Z
+
+ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ;
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into);
+ ret = true;
+ }
+ }
+ }
+
+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return ret;
+ }
+
+ final int minYIterate = Math.max(minBlock, minBlockY);
+ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ final int minChunkX = minBlockX >> 4;
+ final int maxChunkX = maxBlockX >> 4;
+
+ final int minChunkY = minBlockY >> 4;
+ final int maxChunkY = maxBlockY >> 4;
+
+ final int minChunkYIterate = minYIterate >> 4;
+ final int maxChunkYIterate = maxYIterate >> 4;
+
+ final int minChunkZ = minBlockZ >> 4;
+ final int maxChunkZ = maxBlockZ >> 4;
+
+ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
+ chunkProvider = ((ServerLevel)getter).getChunkSource();
+ } else {
+ chunkProvider = null;
+ }
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ final int chunkXGlobalPos = currChunkX << 4;
+ final int chunkZGlobalPos = currChunkZ << 4;
+ final ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ }
+ continue;
+ }
+
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+
+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
+ final LevelChunkSection section = sections[currChunkY - minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // empty
+ continue;
+ }
+ final PalettedContainer<BlockState> blocks = section.states;
+
+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
+ final int chunkYGlobalPos = currChunkY << 4;
+
+ final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks();
+
+ final int minXIterate;
+ final int maxXIterate;
+ final int minZIterate;
+ final int maxZIterate;
+ final int minYIterateLocal;
+ final int maxYIterateLocal;
+
+ if (!sectionHasSpecial) {
+ minXIterate = currChunkX == minChunkX ? minX + 1 : minX;
+ maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX;
+ minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ;
+ maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ;
+ minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY;
+ maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY;
+ if (minXIterate > maxXIterate || minZIterate > maxZIterate) {
+ continue;
+ }
+ } else {
+ minXIterate = minX;
+ maxXIterate = maxX;
+ minZIterate = minZ;
+ maxZIterate = maxZ;
+ minYIterateLocal = minY;
+ maxYIterateLocal = maxY;
+ }
+
+ for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) {
+ long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15);
+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ,
+ collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) {
+ // From getKnownBlockInfoHorizontalRaw:
+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
+ // We want to use a bitset to only iterate over non-empty blocks.
+ // We need to build a bitset mask to and out the other collisions we just don't care at all about
+ // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis
+ // It's important to note that the iterate values can be outside [0, 15], but if they are,
+ // then none of the x or z loops would meet their conditions. So we can assume they are never
+ // out of bounds here
+ final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32
+ long bitset = (1L << xAxisBits) - 1;
+ // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd)
+ int shift = (currZ & 1) << 5; // this will be a LEFT shift
+ // Now we need to offset shift so that the bitset first position is at minXIterate
+ shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ...
+
+ // all done
+ bitset = bitset << shift;
+ if ((collisionForHorizontal & bitset) == 0L) {
+ // All empty
+ continue;
+ }
+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
+ final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8);
+
+ final int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal);
+
+ switch (blockInfo) {
+ case (int) CollisionUtil.KNOWN_EMPTY_BLOCK: {
+ continue;
+ }
+ case (int) CollisionUtil.KNOWN_FULL_BLOCK: {
+ double blockX = (double)(currX | chunkXGlobalPos);
+ double blockY = (double)(currY | chunkYGlobalPos);
+ double blockZ = (double)(currZ | chunkZGlobalPos);
+ final AABB blockBox = new AABB(
+ blockX, blockY, blockZ,
+ blockX + 1.0, blockY + 1.0, blockZ + 1.0,
+ true
+ );
+ if (predicate != null) {
+ if (!voxelShapeIntersect(aabb, blockBox)) {
+ continue;
+ }
+ // fall through to get the block for the predicate
+ } else {
+ if (voxelShapeIntersect(aabb, blockBox)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(blockBox);
+ ret = true;
+ }
+ }
+ continue;
+ }
+ }
+ // default: fall through to standard logic
+ }
+
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY | chunkYGlobalPos;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorderReference(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into);
+ ret = true;
+ }
+ }
+ }
+
+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return ret;
+ }
+
+ final int minYIterate = Math.max(minBlock, minBlockY);
+ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ final int minChunkX = minBlockX >> 4;
+ final int maxChunkX = maxBlockX >> 4;
+
+ final int minChunkY = minBlockY >> 4;
+ final int maxChunkY = maxBlockY >> 4;
+
+ final int minChunkYIterate = minYIterate >> 4;
+ final int maxChunkYIterate = maxYIterate >> 4;
+
+ final int minChunkZ = minBlockZ >> 4;
+ final int maxChunkZ = maxBlockZ >> 4;
+
+ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
+ chunkProvider = ((ServerLevel)getter).getChunkSource();
+ } else {
+ chunkProvider = null;
+ }
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ final int chunkXGlobalPos = currChunkX << 4;
+ final int chunkZGlobalPos = currChunkZ << 4;
+ final ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ }
+ continue;
+ }
+
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
+ final LevelChunkSection section = sections[currChunkY - minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // empty
+ continue;
+ }
+ final PalettedContainer<BlockState> blocks = section.states;
+
+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
+ final int chunkYGlobalPos = currChunkY << 4;
+
+ for (int currY = minY; currY <= maxY; ++currY) {
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY | chunkYGlobalPos;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+ if (blockData.getBlockCollisionBehavior() == CollisionUtil.KNOWN_EMPTY_BLOCK) {
+ continue;
+ }
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb,
+ final List<AABB> into, final boolean checkOnly, final Predicate<Entity> predicate) {
+ if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) {
+ return false;
+ }
+
+ boolean ret = false;
+
+ // to comply with vanilla intersection rules, expand by -epsilon so we only get stuff we definitely collide with.
+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems
+ // specifically with boat collisions.
+ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON);
+ final List<Entity> entities = CachedLists.getTempGetEntitiesList();
+ try {
+ if (entity != null && entity.hardCollides()) {
+ entityGetter.getEntities(entity, aabb, predicate, entities);
+ } else {
+ entityGetter.getHardCollidingEntities(entity, aabb, predicate, entities);
+ }
+
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ final Entity otherEntity = entities.get(i);
+
+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(otherEntity.getBoundingBox());
+ ret = true;
+ }
+ }
+ }
+ } finally {
+ CachedLists.returnTempGetEntitiesList(entities);
+ }
+
+ return ret;
+ }
+
+ public static boolean getCollisions(final CollisionGetter view, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloadedChunks,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> blockPredicate,
+ final Predicate<Entity> entityPredicate) {
+ if (checkOnly) {
+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate)
+ || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate);
+ } else {
+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate)
+ | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate);
+ }
+ }
+
+ public static final class LazyEntityCollisionContext extends EntityCollisionContext {
+
+ private CollisionContext delegate;
+
+ public LazyEntityCollisionContext(final Entity entity) {
+ super(false, 0.0, null, null, entity);
+ }
+
+ public CollisionContext getDelegate() {
+ final Entity entity = this.getEntity();
+ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate;
+ }
+
+ @Override
+ public boolean isDescending() {
+ return this.getDelegate().isDescending();
+ }
+
+ @Override
+ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) {
+ return this.getDelegate().isAbove(shape, pos, defaultValue);
+ }
+
+ @Override
+ public boolean isHoldingItem(final Item item) {
+ return this.getDelegate().isHoldingItem(item);
+ }
+
+ @Override
+ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) {
+ return this.getDelegate().canStandOnFluid(state, fluidState);
+ }
+ }
+
+ private CollisionUtil() {
+ throw new RuntimeException();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d2fa2466fe40e0b9d7749498684587a11dfa80a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java
@@ -0,0 +1,200 @@
+package io.papermc.paper.voxel;
+
+import io.papermc.paper.util.CollisionUtil;
+import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
+import it.unimi.dsi.fastutil.doubles.DoubleList;
+import net.minecraft.core.Direction;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AABBVoxelShape extends VoxelShape {
+
+ public final AABB aabb;
+
+ public AABBVoxelShape(AABB aabb) {
+ super(Shapes.getFullUnoptimisedCube().shape);
+ this.aabb = aabb;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return CollisionUtil.isEmpty(this.aabb);
+ }
+
+ @Override
+ public double min(Direction.Axis enumdirection_enumaxis) {
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return this.aabb.minX;
+ case 1:
+ return this.aabb.minY;
+ case 2:
+ return this.aabb.minZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public double max(Direction.Axis enumdirection_enumaxis) {
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return this.aabb.maxX;
+ case 1:
+ return this.aabb.maxY;
+ case 2:
+ return this.aabb.maxZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public AABB bounds() {
+ return this.aabb;
+ }
+
+ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis.
+ @Override
+ protected double get(Direction.Axis enumdirection_enumaxis, int i) {
+ switch (enumdirection_enumaxis.ordinal() | (i << 2)) {
+ case (0 | (0 << 2)):
+ return this.aabb.minX;
+ case (1 | (0 << 2)):
+ return this.aabb.minY;
+ case (2 | (0 << 2)):
+ return this.aabb.minZ;
+ case (0 | (1 << 2)):
+ return this.aabb.maxX;
+ case (1 | (1 << 2)):
+ return this.aabb.maxY;
+ case (2 | (1 << 2)):
+ return this.aabb.maxZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ private DoubleList cachedListX;
+ private DoubleList cachedListY;
+ private DoubleList cachedListZ;
+
+ @Override
+ protected DoubleList getCoords(Direction.Axis enumdirection_enumaxis) {
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX;
+ case 1:
+ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY;
+ case 2:
+ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public VoxelShape move(double d0, double d1, double d2) {
+ return new AABBVoxelShape(this.aabb.move(d0, d1, d2));
+ }
+
+ @Override
+ public VoxelShape optimize() {
+ if (this.isEmpty()) {
+ return Shapes.empty();
+ } else if (this == Shapes.BLOCK_OPTIMISED || this.aabb.equals(Shapes.BLOCK_OPTIMISED.aabb)) {
+ return Shapes.BLOCK_OPTIMISED;
+ }
+ return this;
+ }
+
+ @Override
+ public void forAllBoxes(Shapes.DoubleLineConsumer voxelshapes_a) {
+ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ);
+ }
+
+ @Override
+ public List<AABB> toAabbs() { // getAABBs
+ List<AABB> ret = new ArrayList<>(1);
+ ret.add(this.aabb);
+ return ret;
+ }
+
+ @Override
+ protected int findIndex(Direction.Axis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1;
+ case 1:
+ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1;
+ case 2:
+ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ protected VoxelShape calculateFace(Direction direction) {
+ if (this.isEmpty()) {
+ return Shapes.empty();
+ }
+ if (this == Shapes.BLOCK_OPTIMISED) {
+ return this;
+ }
+ switch (direction) {
+ case EAST: // +X
+ case WEST: { // -X
+ final double from = direction == Direction.EAST ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON;
+ if (from > this.aabb.maxX || this.aabb.minX > from) {
+ return Shapes.empty();
+ }
+ return new AABBVoxelShape(new AABB(0.0, this.aabb.minY, this.aabb.minZ, 1.0, this.aabb.maxY, this.aabb.maxZ)).optimize();
+ }
+ case UP: // +Y
+ case DOWN: { // -Y
+ final double from = direction == Direction.UP ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON;
+ if (from > this.aabb.maxY || this.aabb.minY > from) {
+ return Shapes.empty();
+ }
+ return new AABBVoxelShape(new AABB(this.aabb.minX, 0.0, this.aabb.minZ, this.aabb.maxX, 1.0, this.aabb.maxZ)).optimize();
+ }
+ case SOUTH: // +Z
+ case NORTH: { // -Z
+ final double from = direction == Direction.SOUTH ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON;
+ if (from > this.aabb.maxZ || this.aabb.minZ > from) {
+ return Shapes.empty();
+ }
+ return new AABBVoxelShape(new AABB(this.aabb.minX, this.aabb.minY, 0.0, this.aabb.maxX, this.aabb.maxY, 1.0)).optimize();
+ }
+ default: {
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+ }
+
+ @Override
+ public double collide(Direction.Axis enumdirection_enumaxis, AABB axisalignedbb, double d0) {
+ if (CollisionUtil.isEmpty(this.aabb)) {
+ return d0;
+ }
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return CollisionUtil.collideX(this.aabb, axisalignedbb, d0);
+ case 1:
+ return CollisionUtil.collideY(this.aabb, axisalignedbb, d0);
+ case 2:
+ return CollisionUtil.collideZ(this.aabb, axisalignedbb, d0);
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public boolean intersects(AABB axisalingedbb) {
+ return CollisionUtil.voxelShapeIntersect(this.aabb, axisalingedbb);
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index 52dba7fad11a7280137ade67983e594487e20891..50f6a044385c22336e799efe471ab65e7196aaa6 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -461,7 +461,7 @@ public class ServerPlayer extends Player {
if (blockposition1 != null) {
this.moveTo(blockposition1, 0.0F, 0.0F);
- if (world.noCollision((Entity) this)) {
+ if (world.noCollision(this, this.getBoundingBox(), true)) { // Paper - make sure this loads chunks, we default to NOT loading now
break;
}
}
@@ -469,7 +469,7 @@ public class ServerPlayer extends Player {
} else {
this.moveTo(blockposition, 0.0F, 0.0F);
- while (!world.noCollision((Entity) this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) {
+ while (!world.noCollision(this, this.getBoundingBox(), true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Paper - make sure this loads chunks, we default to NOT loading now
this.setPos(this.getX(), this.getY() + 1.0D, this.getZ());
}
}
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index ffa831673805201932c36b814f4439f3bb5c4c04..64e40e3caa85bff9382a483237301a8f231d1991 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -895,7 +895,7 @@ public abstract class PlayerList {
// CraftBukkit end
worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper
- while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
+ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now
entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index f1d9b75eae4d42c02974c8bface6d73cb1e58182..23194249ab0add8cf2cbea190d3c6aee606b10a1 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -1210,9 +1210,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
float f = this.getBlockSpeedFactor();
this.setDeltaMovement(this.getDeltaMovement().multiply((double) f, 1.0D, (double) f));
- if (this.level().getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata2) -> {
- return iblockdata2.is(BlockTags.FIRE) || iblockdata2.is(Blocks.LAVA);
- })) {
+ // Paper start - remove expensive streams from here
+ boolean noneMatch = true;
+ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D);
+ {
+ int minX = Mth.floor(fireSearchBox.minX);
+ int minY = Mth.floor(fireSearchBox.minY);
+ int minZ = Mth.floor(fireSearchBox.minZ);
+ int maxX = Mth.floor(fireSearchBox.maxX);
+ int maxY = Mth.floor(fireSearchBox.maxY);
+ int maxZ = Mth.floor(fireSearchBox.maxZ);
+ fire_search_loop:
+ for (int fz = minZ; fz <= maxZ; ++fz) {
+ for (int fx = minX; fx <= maxX; ++fx) {
+ for (int fy = minY; fy <= maxY; ++fy) {
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4);
+ if (chunk == null) {
+ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true
+ // even if we're in lava/fire
+ noneMatch = true;
+ break fire_search_loop;
+ }
+ if (!noneMatch) {
+ // don't do get type, we already know we're in fire - we just need to check the chunks
+ // loaded state
+ continue;
+ }
+
+ BlockState type = chunk.getBlockStateFinal(fx, fy, fz);
+ if (type.is(BlockTags.FIRE) || type.is(Blocks.LAVA)) {
+ noneMatch = false;
+ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded
+ }
+ }
+ }
+ }
+ }
+ if (noneMatch) {
+ // Paper end - remove expensive streams from here
if (this.remainingFireTicks <= 0) {
this.setRemainingFireTicks(-this.getFireImmuneTicks());
}
@@ -1392,32 +1427,78 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
private Vec3 collide(Vec3 movement) {
- AABB axisalignedbb = this.getBoundingBox();
- List<VoxelShape> list = this.level().getEntityCollisions(this, axisalignedbb.expandTowards(movement));
- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level(), list);
- boolean flag = movement.x != vec3d1.x;
- boolean flag1 = movement.y != vec3d1.y;
- boolean flag2 = movement.z != vec3d1.z;
- boolean flag3 = this.onGround() || flag1 && movement.y < 0.0D;
-
- if (this.maxUpStep() > 0.0F && flag3 && (flag || flag2)) {
- Vec3 vec3d2 = Entity.collideBoundingBox(this, new Vec3(movement.x, (double) this.maxUpStep(), movement.z), axisalignedbb, this.level(), list);
- Vec3 vec3d3 = Entity.collideBoundingBox(this, new Vec3(0.0D, (double) this.maxUpStep(), 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level(), list);
-
- if (vec3d3.y < (double) this.maxUpStep()) {
- Vec3 vec3d4 = Entity.collideBoundingBox(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level(), list).add(vec3d3);
-
- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
- vec3d2 = vec3d4;
+ // Paper start - optimise collisions
+ // This is a copy of vanilla's except that it uses strictly AABB math
+ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) {
+ return movement;
+ }
+
+ final Level world = this.level;
+ final AABB currBoundingBox = this.getBoundingBox();
+
+ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) {
+ return movement;
+ }
+
+ final List<AABB> potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList();
+ try {
+ final double stepHeight = (double)this.maxUpStep();
+ final AABB collisionBox;
+
+ if (movement.x == 0.0 && movement.z == 0.0 && movement.y != 0.0) {
+ if (movement.y > 0.0) {
+ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y);
+ } else {
+ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y);
+ }
+ } else {
+ if (stepHeight > 0.0 && (this.onGround || (movement.y < 0.0)) && (movement.x != 0.0 || movement.z != 0.0)) {
+ // don't bother getting the collisions if we don't need them.
+ if (movement.y <= 0.0) {
+ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight);
+ } else {
+ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z);
+ }
+ } else {
+ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z);
}
}
- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) {
- return vec3d2.add(Entity.collideBoundingBox(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level(), list));
+ io.papermc.paper.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, this.level.paperConfig().chunks.preventMovingIntoUnloadedChunks,
+ false, false, null, null);
+
+ if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) {
+ io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions);
}
- }
- return vec3d1;
+ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisions);
+
+ if (stepHeight > 0.0
+ && (this.onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0))
+ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) {
+ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisions);
+ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisions);
+
+ if (vec3d3.y < stepHeight) {
+ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3);
+
+ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
+ vec3d2 = vec3d4;
+ }
+ }
+
+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) {
+ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions));
+ }
+
+ return limitedMoveVector;
+ } else {
+ return limitedMoveVector;
+ }
+ } finally {
+ io.papermc.paper.util.CachedLists.returnTempCollisionList(potentialCollisions);
+ }
+ // Paper end - optimise collisions
}
public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List<VoxelShape> collisions) {
@@ -2611,11 +2692,31 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
float f = this.dimensions.width * 0.8F;
AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f);
- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> {
- BlockState iblockdata = this.level().getBlockState(blockposition);
- return !iblockdata.isAir() && iblockdata.isSuffocating(this.level(), blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level(), blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND);
- });
+ BlockPos.MutableBlockPos blockposition = new BlockPos.MutableBlockPos();
+ int minX = Mth.floor(axisalignedbb.minX);
+ int minY = Mth.floor(axisalignedbb.minY);
+ int minZ = Mth.floor(axisalignedbb.minZ);
+ int maxX = Mth.floor(axisalignedbb.maxX);
+ int maxY = Mth.floor(axisalignedbb.maxY);
+ int maxZ = Mth.floor(axisalignedbb.maxZ);
+ for (int fz = minZ; fz <= maxZ; ++fz) {
+ for (int fx = minX; fx <= maxX; ++fx) {
+ for (int fy = minY; fy <= maxY; ++fy) {
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4);
+ if (chunk == null) {
+ continue;
+ }
+
+ BlockState iblockdata = chunk.getBlockStateFinal(fx, fy, fz);
+ blockposition.set(fx, fy, fz);
+ if (!iblockdata.isAir() && iblockdata.isSuffocating(this.level, blockposition) && Shapes.joinIsNotEmpty(iblockdata.getCollisionShape(this.level, blockposition).move((double) blockposition.getX(), (double) blockposition.getY(), (double) blockposition.getZ()), Shapes.create(axisalignedbb), BooleanOp.AND)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
}
}
diff --git a/src/main/java/net/minecraft/world/level/BlockCollisions.java b/src/main/java/net/minecraft/world/level/BlockCollisions.java
index a3eaf80b020c3bbc0306c5d17659ee661dfd275b..1b6f72932fbdd567a1534bcf15e8a610b00f974d 100644
--- a/src/main/java/net/minecraft/world/level/BlockCollisions.java
+++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java
@@ -105,7 +105,7 @@ public class BlockCollisions<T> extends AbstractIterator<T> {
VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context);
if (voxelShape == Shapes.block()) {
- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) {
+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Paper - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil
continue;
}
diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java
index 06107d69dff9f0b52a5f188095cbd9a9efa5684c..c0dd933c8e64484c4ae0d4fa0f6e19969cf09b37 100644
--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java
+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java
@@ -35,31 +35,33 @@ public interface CollisionGetter extends BlockGetter {
return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox()));
}
+ // Paper start - optimise collisions
+ default boolean noCollision(Entity entity, AABB box, boolean loadChunks) {
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, loadChunks, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null);
+ }
+ // Paper end - optimise collisions
+
default boolean noCollision(AABB box) {
- return this.noCollision((Entity)null, box);
+ // Paper start - optimise collisions
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, null, box, null, false, false, false, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, null, box, null, true, null);
+ // Paper end - optimise collisions
}
default boolean noCollision(Entity entity) {
- return this.noCollision(entity, entity.getBoundingBox());
+ // Paper start - optimise collisions
+ AABB box = entity.getBoundingBox();
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null);
+ // Paper end - optimise collisions
}
default boolean noCollision(@Nullable Entity entity, AABB box) {
- try { if (entity != null) entity.collisionLoadChunks = true; // Paper
- for(VoxelShape voxelShape : this.getBlockCollisions(entity, box)) {
- if (!voxelShape.isEmpty()) {
- return false;
- }
- }
- } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
-
- if (!this.getEntityCollisions(entity, box).isEmpty()) {
- return false;
- } else if (entity == null) {
- return true;
- } else {
- VoxelShape voxelShape2 = this.borderCollision(entity, box);
- return voxelShape2 == null || !Shapes.joinIsNotEmpty(voxelShape2, Shapes.create(box), BooleanOp.AND);
- }
+ // Paper start - optimise collisions
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null);
+ // Paper end - optimise collisions
}
List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box);
diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
index 66a5783e2a83c75ca46d1fd6f97d9de733c01a09..d860ddae508f53d06f74d8ae0efdfc500c1ddf07 100644
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
@@ -49,7 +49,7 @@ public interface EntityGetter {
return true;
} else {
for(Entity entity : this.getEntities(except, shape.bounds())) {
- if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) {
+ if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && shape.intersects(entity.getBoundingBox())) { // Paper
return false;
}
}
@@ -67,7 +67,7 @@ public interface EntityGetter {
return List.of();
} else {
Predicate<Entity> predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith);
- List<Entity> list = this.getEntities(entity, box.inflate(1.0E-7D), predicate);
+ List<Entity> list = this.getEntities(entity, box.inflate(-1.0E-7D), predicate); // Paper - needs to be negated, or else we get things we don't collide with
if (list.isEmpty()) {
return List.of();
} else {
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index a9e8d9c65a809562d4768df348dcd79bec4d0e3c..27680befd64967ef3c2ae0f35c9e7bd68d474314 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -863,6 +863,12 @@ public abstract class BlockBehaviour implements FeatureElement {
return this.conditionallyFullOpaque;
}
// Paper end - starlight
+ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
+
+ public final long getBlockCollisionBehavior() {
+ return this.blockCollisionBehavior;
+ }
+ // Paper end
public void initCache() {
this.fluidState = ((Block) this.owner).getFluidState(this.asState());
@@ -874,6 +880,35 @@ public abstract class BlockBehaviour implements FeatureElement {
this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - starlight - cache opacity for light
this.legacySolid = this.calculateSolid();
+ // Paper start
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(this)) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
+ } else {
+ try {
+ // There is NOTHING HACKY ABOUT THIS AT ALLLLLLLLLLLLLLL
+ VoxelShape constantShape = this.getCollisionShape(null, null, null);
+ if (constantShape == null) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
+ } else {
+ constantShape = constantShape.optimize();
+ if (constantShape.isEmpty()) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_EMPTY_BLOCK;
+ } else {
+ final List<net.minecraft.world.phys.AABB> boxes = constantShape.toAabbs();
+ if (constantShape == net.minecraft.world.phys.shapes.Shapes.getFullUnoptimisedCube() || (boxes.size() == 1 && boxes.get(0).equals(net.minecraft.world.phys.shapes.Shapes.BLOCK_OPTIMISED.aabb))) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_FULL_BLOCK;
+ } else {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
+ }
+ }
+ }
+ } catch (final Error error) {
+ throw error;
+ } catch (final Throwable throwable) {
+ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
+ }
+ }
+ // Paper end
}
public Block getBlock() {
diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
index 12edaf88cca54540c617239ac31b2263dfa0ecd7..7a57a75db00cd64e4c95740b575277a1f355e414 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -42,6 +42,110 @@ public class LevelChunkSection {
this.biomes = new PalettedContainer<>(biomeRegistry.asHolderIdMap(), biomeRegistry.getHolderOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
}
+ // Paper start
+ protected int specialCollidingBlocks;
+ // blockIndex = x | (z << 4) | (y << 8)
+ private long[] knownBlockCollisionData;
+
+ private long[] initKnownDataField() {
+ return this.knownBlockCollisionData = new long[16 * 16 * 16 * 2 / Long.SIZE];
+ }
+
+ public final boolean hasSpecialCollidingBlocks() {
+ return this.specialCollidingBlocks != 0;
+ }
+
+ public static long getKnownBlockInfo(final int blockIndex, final long value) {
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
+
+ return (value >>> (valueShift << 1)) & 0b11L;
+ }
+
+ public final long getKnownBlockInfo(final int blockIndex) {
+ if (this.knownBlockCollisionData == null) {
+ return 0L;
+ }
+
+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
+
+ final long value = this.knownBlockCollisionData[arrayIndex];
+
+ return (value >>> (valueShift << 1)) & 0b11L;
+ }
+
+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
+ public final long getKnownBlockInfoHorizontalRaw(final int localY, final int localZ) {
+ if (this.knownBlockCollisionData == null) {
+ return 0L;
+ }
+
+ final int horizontalIndex = (localZ << 4) | (localY << 8);
+ return this.knownBlockCollisionData[horizontalIndex >>> (6 - 1)];
+ }
+
+ private void initBlockCollisionData() {
+ this.specialCollidingBlocks = 0;
+ // In 1.18 all sections will be initialised, whether or not they have blocks (fucking stupid btw)
+ // This means we can't aggressively initialise the backing long[], or else memory usage will just skyrocket.
+ // So only init if we contain non-empty blocks.
+ if (this.nonEmptyBlockCount == 0) {
+ this.knownBlockCollisionData = null;
+ return;
+ }
+ this.initKnownDataField();
+ for (int index = 0; index < (16 * 16 * 16); ++index) {
+ final BlockState state = this.states.get(index);
+ this.setKnownBlockInfo(index, state);
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(state)) {
+ ++this.specialCollidingBlocks;
+ }
+ }
+ }
+
+ // only use for initBlockCollisionData
+ private void setKnownBlockInfo(final int blockIndex, final BlockState blockState) {
+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
+
+ long value = this.knownBlockCollisionData[arrayIndex];
+
+ value &= ~(0b11L << valueShift);
+ value |= blockState.getBlockCollisionBehavior() << valueShift;
+
+ this.knownBlockCollisionData[arrayIndex] = value;
+ }
+
+ public void updateKnownBlockInfo(final int blockIndex, final BlockState from, final BlockState to) {
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(from)) {
+ --this.specialCollidingBlocks;
+ }
+ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(to)) {
+ ++this.specialCollidingBlocks;
+ }
+
+ if (this.nonEmptyBlockCount == 0) {
+ this.knownBlockCollisionData = null;
+ return;
+ }
+
+ if (this.knownBlockCollisionData == null) {
+ this.initKnownDataField();
+ }
+
+ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
+ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
+
+ long value = this.knownBlockCollisionData[arrayIndex];
+
+ value &= ~(0b11L << valueShift);
+ value |= to.getBlockCollisionBehavior() << valueShift;
+
+ this.knownBlockCollisionData[arrayIndex] = value;
+ }
+ // Paper end
+
public BlockState getBlockState(int x, int y, int z) {
return (BlockState) this.states.get(x, y, z);
}
@@ -62,8 +166,8 @@ public class LevelChunkSection {
return this.setBlockState(x, y, z, state, true);
}
- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {
- BlockState iblockdata1;
+ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state
+ BlockState iblockdata1; // Paper - iblockdata1 -> oldState
if (lock) {
iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state);
@@ -102,6 +206,7 @@ public class LevelChunkSection {
++this.tickingFluidCount;
}
+ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper
return iblockdata1;
}
@@ -147,6 +252,7 @@ public class LevelChunkSection {
});
// Paper end
+ this.initBlockCollisionData(); // Paper
}
public PalettedContainer<BlockState> getStates() {
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index f80783dc163997626850189f5647c06f9d15da6c..ffc76354ead6937daf366c3d87bcb51d3e4c47f5 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java
+++ b/src/main/java/net/minecraft/world/phys/AABB.java
@@ -25,6 +25,17 @@ public class AABB {
this.maxZ = Math.max(z1, z2);
}
+ // Paper start
+ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) {
+ this.minX = minX;
+ this.minY = minY;
+ this.minZ = minZ;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ this.maxZ = maxZ;
+ }
+ // Paper end
+
public AABB(BlockPos pos) {
this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1));
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
index 9d627b8e6bf3140b894d38b9a720896e2d776369..ca5f01be5d5ccfcc56780ff93cca3824409ffc0d 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
@@ -6,6 +6,9 @@ import java.util.Arrays;
import net.minecraft.Util;
import net.minecraft.core.Direction;
+// Paper start
+import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
+// Paper end
public class ArrayVoxelShape extends VoxelShape {
private final DoubleList xs;
private final DoubleList ys;
@@ -16,6 +19,11 @@ public class ArrayVoxelShape extends VoxelShape {
}
ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) {
+ // Paper start - optimise multi-aabb shapes
+ this(shape, xPoints, yPoints, zPoints, null, 0.0, 0.0, 0.0);
+ }
+ ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints, net.minecraft.world.phys.AABB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) {
+ // Paper end - optimise multi-aabb shapes
super(shape);
int i = shape.getXSize() + 1;
int j = shape.getYSize() + 1;
@@ -27,6 +35,12 @@ public class ArrayVoxelShape extends VoxelShape {
} else {
throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape."));
}
+ // Paper start - optimise multi-aabb shapes
+ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.toAabbs().toArray(EMPTY) : boundingBoxesRepresentation;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.offsetZ = offsetZ;
+ // Paper end - optimise multi-aabb shapes
}
@Override
@@ -42,4 +56,152 @@ public class ArrayVoxelShape extends VoxelShape {
throw new IllegalArgumentException();
}
}
+
+ // Paper start
+ public static final class DoubleListOffsetExposed extends AbstractDoubleList {
+
+ public final DoubleArrayList list;
+ public final double offset;
+
+ public DoubleListOffsetExposed(final DoubleArrayList list, final double offset) {
+ this.list = list;
+ this.offset = offset;
+ }
+
+ @Override
+ public double getDouble(final int index) {
+ return this.list.getDouble(index) + this.offset;
+ }
+
+ @Override
+ public int size() {
+ return this.list.size();
+ }
+ }
+
+ static final net.minecraft.world.phys.AABB[] EMPTY = new net.minecraft.world.phys.AABB[0];
+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation;
+
+ final double offsetX;
+ final double offsetY;
+ final double offsetZ;
+
+ public final net.minecraft.world.phys.AABB[] getBoundingBoxesRepresentation() {
+ return this.boundingBoxesRepresentation;
+ }
+
+ public final double getOffsetX() {
+ return this.offsetX;
+ }
+
+ public final double getOffsetY() {
+ return this.offsetY;
+ }
+
+ public final double getOffsetZ() {
+ return this.offsetZ;
+ }
+
+ @Override
+ public java.util.List<net.minecraft.world.phys.AABB> toAabbs() {
+ if (this.boundingBoxesRepresentation == null) {
+ return super.toAabbs();
+ }
+ java.util.List<net.minecraft.world.phys.AABB> ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length);
+
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ ret.add(boundingBox.move(offX, offY, offZ));
+ }
+
+ return ret;
+ }
+
+ protected static DoubleArrayList getList(DoubleList from) {
+ if (from instanceof DoubleArrayList) {
+ return (DoubleArrayList)from;
+ } else {
+ return DoubleArrayList.wrap(from.toDoubleArray());
+ }
+ }
+
+ @Override
+ public VoxelShape move(double x, double y, double z) {
+ if (x == 0.0 && y == 0.0 && z == 0.0) {
+ return this;
+ }
+ DoubleListOffsetExposed xPoints, yPoints, zPoints;
+ double offsetX, offsetY, offsetZ;
+
+ if (this.xs instanceof DoubleListOffsetExposed) {
+ xPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.xs).list, offsetX = this.offsetX + x);
+ yPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.ys).list, offsetY = this.offsetY + y);
+ zPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.zs).list, offsetZ = this.offsetZ + z);
+ } else {
+ xPoints = new DoubleListOffsetExposed(getList(this.xs), offsetX = x);
+ yPoints = new DoubleListOffsetExposed(getList(this.ys), offsetY = y);
+ zPoints = new DoubleListOffsetExposed(getList(this.zs), offsetZ = z);
+ }
+
+ return new ArrayVoxelShape(this.shape, xPoints, yPoints, zPoints, this.boundingBoxesRepresentation, offsetX, offsetY, offsetZ);
+ }
+
+ @Override
+ public final boolean intersects(net.minecraft.world.phys.AABB axisalingedbb) {
+ // this can be optimised by checking an "overall shape" first, but not needed
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(axisalingedbb, boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ,
+ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void forAllBoxes(Shapes.DoubleLineConsumer doubleLineConsumer) {
+ if (this.boundingBoxesRepresentation == null) {
+ super.forAllBoxes(doubleLineConsumer);
+ return;
+ }
+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ doubleLineConsumer.consume(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ,
+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ);
+ }
+ }
+
+ @Override
+ public VoxelShape optimize() {
+ if (this == Shapes.empty() || this.boundingBoxesRepresentation.length == 0) {
+ return this;
+ }
+
+ VoxelShape simplified = Shapes.empty();
+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ simplified = Shapes.joinUnoptimized(simplified, Shapes.box(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ,
+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ), BooleanOp.OR);
+ }
+
+ if (!(simplified instanceof ArrayVoxelShape)) {
+ return simplified;
+ }
+
+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation = ((ArrayVoxelShape)simplified).getBoundingBoxesRepresentation();
+
+ if (boundingBoxesRepresentation.length == 1) {
+ return new io.papermc.paper.voxel.AABBVoxelShape(boundingBoxesRepresentation[0]).optimize();
+ }
+
+ return simplified;
+ }
+ // Paper end
+
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
index 9176735c08a75854209f24113b0e78332249dc4d..731c7dd15f131dc124be6af8f342b122cb89491b 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
@@ -19,16 +19,17 @@ public final class Shapes {
DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1);
discreteVoxelShape.fill(0, 0, 0);
return new CubeVoxelShape(discreteVoxelShape);
- });
+ }); public static VoxelShape getFullUnoptimisedCube() { return BLOCK; } // Paper - OBFHELPER
public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})));
+ public static final io.papermc.paper.voxel.AABBVoxelShape BLOCK_OPTIMISED = new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); // Paper
public static VoxelShape empty() {
return EMPTY;
}
public static VoxelShape block() {
- return BLOCK;
+ return BLOCK_OPTIMISED; // Paper
}
public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
@@ -41,29 +42,14 @@ public final class Shapes {
public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) {
- int i = findBits(minX, maxX);
- int j = findBits(minY, maxY);
- int k = findBits(minZ, maxZ);
- if (i >= 0 && j >= 0 && k >= 0) {
- if (i == 0 && j == 0 && k == 0) {
- return block();
- } else {
- int l = 1 << i;
- int m = 1 << j;
- int n = 1 << k;
- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n));
- return new CubeVoxelShape(bitSetDiscreteVoxelShape);
- }
- } else {
- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ}));
- }
+ return new io.papermc.paper.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Paper
} else {
return empty();
}
}
public static VoxelShape create(AABB box) {
- return create(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ);
+ return new io.papermc.paper.voxel.AABBVoxelShape(box); // Paper
}
@VisibleForTesting
@@ -125,6 +111,20 @@ public final class Shapes {
}
public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+ // Paper start - optimise voxelshape
+ if (predicate == BooleanOp.AND) {
+ if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape) {
+ return io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb, ((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb);
+ } else if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof ArrayVoxelShape) {
+ return ((ArrayVoxelShape)shape2).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb);
+ } else if (shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape1 instanceof ArrayVoxelShape) {
+ return ((ArrayVoxelShape)shape1).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb);
+ }
+ }
+ return joinIsNotEmptyVanilla(shape1, shape2, predicate);
+ }
+ public static boolean joinIsNotEmptyVanilla(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+ // Paper end - optimise voxelshape
if (predicate.apply(false, false)) {
throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
} else {
@@ -196,6 +196,43 @@ public final class Shapes {
}
public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) {
+ // Paper start - optimise shape creation here for lighting, as this shape is going to be used
+ // for transparency checks
+ if (shape == BLOCK || shape == BLOCK_OPTIMISED) {
+ return BLOCK_OPTIMISED;
+ } else if (shape == empty()) {
+ return empty();
+ }
+
+ if (shape instanceof io.papermc.paper.voxel.AABBVoxelShape) {
+ final AABB box = ((io.papermc.paper.voxel.AABBVoxelShape)shape).aabb;
+ switch (direction) {
+ case WEST: // -X
+ case EAST: { // +X
+ final boolean useEmpty = direction == Direction.EAST ? !DoubleMath.fuzzyEquals(box.maxX, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minX, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, box.minY, box.minZ, 1.0, box.maxY, box.maxZ)).optimize();
+ }
+ case DOWN: // -Y
+ case UP: { // +Y
+ final boolean useEmpty = direction == Direction.UP ? !DoubleMath.fuzzyEquals(box.maxY, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minY, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, 0.0, box.minZ, box.maxX, 1.0, box.maxZ)).optimize();
+ }
+ case NORTH: // -Z
+ case SOUTH: { // +Z
+ final boolean useEmpty = direction == Direction.SOUTH ? !DoubleMath.fuzzyEquals(box.maxZ, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minZ,0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, box.minY, 0.0, box.maxX, box.maxY, 1.0)).optimize();
+ }
+ }
+ }
+
+ // fall back to vanilla
+ return getFaceShapeVanilla(shape, direction);
+ }
+ public static VoxelShape getFaceShapeVanilla(VoxelShape shape, Direction direction) {
+ // Paper end
if (shape == block()) {
return block();
} else {
@@ -210,7 +247,7 @@ public final class Shapes {
i = 0;
}
- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i));
+ return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i).optimize().optimize()); // Paper - first optimize converts to ArrayVoxelShape, second optimize could convert to AABBVoxelShape
}
}
@@ -235,6 +272,53 @@ public final class Shapes {
}
public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) {
+ // Paper start - try to optimise for the case where the shapes do _not_ occlude
+ // which is _most_ of the time in lighting
+ if (one == getFullUnoptimisedCube() || one == BLOCK_OPTIMISED
+ || two == getFullUnoptimisedCube() || two == BLOCK_OPTIMISED) {
+ return true;
+ }
+ boolean v1Empty = one == empty();
+ boolean v2Empty = two == empty();
+ if (v1Empty && v2Empty) {
+ return false;
+ }
+ if ((one instanceof io.papermc.paper.voxel.AABBVoxelShape || v1Empty)
+ && (two instanceof io.papermc.paper.voxel.AABBVoxelShape || v2Empty)) {
+ if (!v1Empty && !v2Empty && (one != two)) {
+ AABB boundingBox1 = ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb;
+ AABB boundingBox2 = ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb;
+ // can call it here in some cases
+
+ // check overall bounding box
+ double minY = Math.min(boundingBox1.minY, boundingBox2.minY);
+ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY);
+ if (minY > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxY < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ double minX = Math.min(boundingBox1.minX, boundingBox2.minX);
+ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX);
+ if (minX > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxX < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ);
+ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ);
+ if (minZ > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxZ < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ // fall through to full merge check
+ } else {
+ AABB boundingBox = v1Empty ? ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb : ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb;
+ // check if the bounding box encloses the full cube
+ return (boundingBox.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+ (boundingBox.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+ (boundingBox.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON));
+ }
+ }
+ return faceShapeOccludesVanilla(one, two);
+ }
+ public static boolean faceShapeOccludesVanilla(VoxelShape one, VoxelShape two) {
+ // Paper end
if (one != block() && two != block()) {
if (one.isEmpty() && two.isEmpty()) {
return false;
diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
index c4ca051720f790f5b8eb860b14e268de8557454d..2182afd1b95acf14c55bddfeec17dae0a63e1f00 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
@@ -16,11 +16,17 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
public abstract class VoxelShape {
- protected final DiscreteVoxelShape shape;
+ public final DiscreteVoxelShape shape; // Paper - public
@Nullable
private VoxelShape[] faces;
- VoxelShape(DiscreteVoxelShape voxels) {
+ // Paper start
+ public boolean intersects(AABB shape) {
+ return Shapes.joinIsNotEmpty(this, new io.papermc.paper.voxel.AABBVoxelShape(shape), BooleanOp.AND);
+ }
+ // Paper end
+
+ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected
this.shape = voxels;
}
@@ -163,7 +169,7 @@ public abstract class VoxelShape {
}
}
- private VoxelShape calculateFace(Direction direction) {
+ protected VoxelShape calculateFace(Direction direction) { // Paper
Direction.Axis axis = direction.getAxis();
DoubleList doubleList = this.getCoords(axis);
if (doubleList.size() == 2 && DoubleMath.fuzzyEquals(doubleList.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0D, 1.0E-7D)) {