diff options
Diffstat (limited to 'patches/server/0812-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch')
-rw-r--r-- | patches/server/0812-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch | 2122 |
1 files changed, 2122 insertions, 0 deletions
diff --git a/patches/server/0812-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch b/patches/server/0812-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch new file mode 100644 index 0000000000..99a5334c59 --- /dev/null +++ b/patches/server/0812-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch @@ -0,0 +1,2122 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf <[email protected]> +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..d67a40e7be030142443680c89e1763fc9ecdfe0a +--- /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) || CollisionUtil.isEmpty(axisalignedbb)) { ++ 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 f6410ba180a85b114a296c64ce293f0891f6b96c..b4d20c06a19e021317cff64a9789f0579b5f921d 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java +@@ -413,7 +413,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; + } + } +@@ -421,7 +421,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 03d4d4d95e0cf617726a39ebd7191cd2d10ffdb7..6a3d444fcac8c7d561dcadb02f64eaa3c3d7b1cd 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -936,7 +936,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()); + } + // CraftBukkit start +diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java +index b736af871fefc05437d77c57375fbc57dca130f5..98a8a00d55f87fb210fa6d3825a7ff476f601021 100644 +--- a/src/main/java/net/minecraft/world/entity/Entity.java ++++ b/src/main/java/net/minecraft/world/entity/Entity.java +@@ -1095,9 +1095,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + float f2 = this.getBlockSpeedFactor(); + + this.setDeltaMovement(this.getDeltaMovement().multiply((double) f2, 1.0D, (double) f2)); +- if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata1) -> { +- return iblockdata1.is(BlockTags.FIRE) || iblockdata1.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()); + } +@@ -1231,32 +1266,78 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + } + + 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, true, ++ 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) { +@@ -2380,11 +2461,30 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i + 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); ++ 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; ++ } + +- 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); +- }); ++ 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 a733c91700a38634806e9155c693b227e6aa16b6..120e1778f2bdd64ca19ee21dc5c5f2f382895470 100644 +--- a/src/main/java/net/minecraft/world/level/BlockCollisions.java ++++ b/src/main/java/net/minecraft/world/level/BlockCollisions.java +@@ -106,7 +106,7 @@ public class BlockCollisions extends AbstractIterator<VoxelShape> { + + 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 56d94c94fb0d4dc468bb5d69be655ddd5c6b5360..d7d396ad73866a97cd9f63b34ad8c587f522e713 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 f9527d1d867f93b4e0e2758485cfa1f6efa0bf8b..1f4b72a0aca200b2e0860449c718e6e607d2fc47 100644 +--- a/src/main/java/net/minecraft/world/level/EntityGetter.java ++++ b/src/main/java/net/minecraft/world/level/EntityGetter.java +@@ -50,7 +50,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; + } + } +@@ -68,7 +68,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 d545b05cfbb14e5a15b26efe372509e498605016..61590f2f04a797235299f1bd6b78a08f5bfe4a33 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 +@@ -719,6 +719,13 @@ public abstract class BlockBehaviour { + return this.conditionallyFullOpaque; + } + // Paper end ++ // Paper start ++ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK; ++ ++ public final long getBlockCollisionBehavior() { ++ return this.blockCollisionBehavior; ++ } ++ // Paper end + + public void initCache() { + this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() +@@ -728,7 +735,35 @@ public abstract class BlockBehaviour { + } + this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here + this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light +- ++ // 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 6afad987f6dd1fd7243dfa6c50549c2a88768962..b11ad90c0956ac1b8ee069fa3f4553a2b4ce88e9 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -44,6 +44,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 static int getBottomBlockY(int chunkPos) { + return chunkPos << 4; + } +@@ -68,8 +172,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); +@@ -108,6 +212,7 @@ public class LevelChunkSection { + ++this.tickingFluidCount; + } + ++ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper + return iblockdata1; + } + +@@ -157,6 +262,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 120498a39b7ca7aee9763084507508d4a1c425aa..68cc6f2a78a06293a29317fda72ab3ee79b3533a 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 cdb785619b4fce3cb7f0b4a996a15fa43de5f4d1..6db47035fe940ef1f78a14cae6103e22aa1a184e 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)) { |