From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Spottedleaf 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 TEMP_COLLISION_LIST = new UnsafeList<>(1024); + static boolean tempCollisionListInUse; + + public static UnsafeList getTempCollisionList() { + if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { + return new UnsafeList<>(16); + } + tempCollisionListInUse = true; + return TEMP_COLLISION_LIST; + } + + public static void returnTempCollisionList(List list) { + if (list != TEMP_COLLISION_LIST) { + return; + } + ((UnsafeList)list).setSize(0); + tempCollisionListInUse = false; + } + static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); + static boolean tempGetEntitiesListInUse; + + public static UnsafeList getTempGetEntitiesList() { + if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { + return new UnsafeList<>(16); + } + tempGetEntitiesListInUse = true; + return TEMP_GET_ENTITIES_LIST; + } + + public static void returnTempGetEntitiesList(List 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..98ca1199a823cdf55b913396ce0a24554e85f116 --- /dev/null +++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java @@ -0,0 +1,645 @@ +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.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.Optional; +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 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 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 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 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 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 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 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 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 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 into, final boolean loadChunks, final boolean collidesWithUnloaded, + final boolean checkBorder, final boolean checkOnly, final BiPredicate 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; + } + } + } + + int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; + int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; + + int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; + int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; + + int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; + 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; + + BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); + CollisionContext collisionShape = null; + + // special cases: + if (minBlockY > maxBlock || maxBlockY < minBlock) { + // no point in checking + return ret; + } + + int minYIterate = Math.max(minBlock, minBlockY); + int maxYIterate = Math.min(maxBlock, maxBlockY); + + int minChunkX = minBlockX >> 4; + int maxChunkX = maxBlockX >> 4; + + int minChunkZ = minBlockZ >> 4; + int maxChunkZ = maxBlockZ >> 4; + + ServerChunkCache chunkProvider; + if (getter instanceof WorldGenRegion) { + chunkProvider = null; + } else if (getter instanceof ServerLevel) { + chunkProvider = ((ServerLevel)getter).getChunkSource(); + } else { + chunkProvider = null; + } + // TODO special case single chunk? + + for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { + int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk + int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk + + for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { + int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk + int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk + + int chunkXGlobalPos = currChunkX << 4; + int chunkZGlobalPos = currChunkZ << 4; + 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; + } + + LevelChunkSection[] sections = chunk.getSections(); + + // bound y + + for (int currY = minYIterate; currY <= maxYIterate; ++currY) { + LevelChunkSection section = sections[(currY >> 4) - minSection]; + if (section == null || section.isEmpty()) { + // empty + // skip to next section + currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one + continue; + } + + net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; + + for (int currZ = minZ; currZ <= maxZ; ++currZ) { + for (int currX = minX; currX <= maxX; ++currX) { + int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8); + int blockX = currX | chunkXGlobalPos; + int blockY = currY; + 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.isAir()) { + 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 into, final boolean checkOnly, final Predicate 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 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 into, final boolean loadChunks, final boolean collidesWithUnloadedChunks, + final boolean checkBorder, final boolean checkOnly, final BiPredicate blockPredicate, + final Predicate 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; + private final Entity entity; + + public LazyEntityCollisionContext(final Entity entity) { + super(false, 0.0, null, null, null, Optional.ofNullable(entity)); + this.entity = entity; + } + + public CollisionContext getDelegate() { + return this.delegate == null ? this.delegate = (this.entity == null ? CollisionContext.empty() : CollisionContext.of(this.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 hasItemOnFeet(final Item item) { + return this.getDelegate().hasItemOnFeet(item); + } + + @Override + public boolean isHoldingItem(final Item item) { + return this.getDelegate().isHoldingItem(item); + } + + @Override + public boolean canStandOnFluid(final FluidState state, final FlowingFluid fluid) { + return this.getDelegate().canStandOnFluid(state, fluid); + } + } + + 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 toAabbs() { // getAABBs + List 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 e40587b9c13d9170ef5dd0811e96b02ecaccebb2..52ffc39715def70a8ad3a99356be19bea5ef892b 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java @@ -419,7 +419,7 @@ public class ServerPlayer extends Player { if (blockposition1 != null) { this.moveTo(blockposition1, 0.0F, 0.0F); - if (world.noCollision(this)) { + if (world.noCollision(this, this.getBoundingBox(), null, true)) { // Paper - make sure this loads chunks, we default to NOT loading now break; } } @@ -427,7 +427,7 @@ public class ServerPlayer extends Player { } else { this.moveTo(blockposition, 0.0F, 0.0F); - while (!world.noCollision(this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { + while (!world.noCollision(this, this.getBoundingBox(), null, 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 cf8ed790e09987528178519ba99376f27b15245f..4c8f6d2d72194d313e7383b5a499c8ca1a84e1da 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java @@ -934,7 +934,7 @@ public abstract class PlayerList { 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 entityplayer1.forceCheckHighPriority(); // Player - Chunk priority - while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { + while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), null, 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 74fcc1b45a9e57280da82f7c181530d4183872a5..481e84fda6dccfaf684c922a12fa19ed35c87b3c 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -1025,9 +1025,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n 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((Tag) 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.getType(fx, fy, fz); + if (type.is((Tag) 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()); } @@ -1149,39 +1184,79 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n return offsetFactor; } - private Vec3 collide(Vec3 movement) { - AABB axisalignedbb = this.getBoundingBox(); - CollisionContext voxelshapecollision = CollisionContext.of(this); - VoxelShape voxelshape = this.level.getWorldBorder().getCollisionShape(); - Stream stream = !this.level.getWorldBorder().isWithinBounds(axisalignedbb) ? Stream.empty() : Stream.of(voxelshape); // Paper - Stream stream1 = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement), (entity) -> { - return true; - }); - RewindableStream streamaccumulator = new RewindableStream<>(Stream.concat(stream1, stream)); - Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBoxHeuristically(this, movement, axisalignedbb, this.level, voxelshapecollision, streamaccumulator); - 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.collideBoundingBoxHeuristically(this, new Vec3(movement.x, (double) this.maxUpStep, movement.z), axisalignedbb, this.level, voxelshapecollision, streamaccumulator); - Vec3 vec3d3 = Entity.collideBoundingBoxHeuristically(this, new Vec3(0.0D, (double) this.maxUpStep, 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level, voxelshapecollision, streamaccumulator); - - if (vec3d3.y < (double) this.maxUpStep) { - Vec3 vec3d4 = Entity.collideBoundingBoxHeuristically(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level, voxelshapecollision, streamaccumulator).add(vec3d3); - - if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { - vec3d2 = vec3d4; + private Vec3 collide(Vec3 moveVector) { + // Paper start - optimise collisions + // This is a copy of vanilla's except that it uses strictly AABB math + if (moveVector.x == 0.0 && moveVector.y == 0.0 && moveVector.z == 0.0) { + return moveVector; + } + + final Level world = this.level; + final AABB currBoundingBox = this.getBoundingBox(); + + if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) { + return moveVector; + } + + final List potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList(); + try { + final double stepHeight = (double)this.maxUpStep; + final AABB collisionBox; + + if (moveVector.x == 0.0 && moveVector.z == 0.0 && moveVector.y != 0.0) { + if (moveVector.y > 0.0) { + collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, moveVector.y); + } else { + collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, moveVector.y); + } + } else { + if (stepHeight > 0.0 && (this.onGround || (moveVector.y < 0.0)) && (moveVector.x != 0.0 || moveVector.z != 0.0)) { + // don't bother getting the collisions if we don't need them. + if (moveVector.y <= 0.0) { + collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(moveVector.x, moveVector.y, moveVector.z), stepHeight); + } else { + collisionBox = currBoundingBox.expandTowards(moveVector.x, Math.max(stepHeight, moveVector.y), moveVector.z); + } + } else { + collisionBox = currBoundingBox.expandTowards(moveVector.x, moveVector.y, moveVector.z); } } - if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { - return vec3d2.add(Entity.collideBoundingBoxHeuristically(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level, voxelshapecollision, streamaccumulator)); + 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(moveVector, currBoundingBox, potentialCollisions); + + if (stepHeight > 0.0 + && (this.onGround || (limitedMoveVector.y != moveVector.y && moveVector.y < 0.0)) + && (limitedMoveVector.x != moveVector.x || limitedMoveVector.z != moveVector.z)) { + Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(moveVector.x, stepHeight, moveVector.z), currBoundingBox, potentialCollisions); + final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(moveVector.x, 0.0, moveVector.z), potentialCollisions); + + if (vec3d3.y < stepHeight) { + final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(moveVector.x, 0.0D, moveVector.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 + moveVector.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 collideBoundingBoxHeuristically(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, CollisionContext context, RewindableStream collisions) { @@ -2320,9 +2395,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n float f = this.dimensions.width * 0.8F; AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); - return this.level.getBlockCollisions(this, axisalignedbb, (iblockdata, blockposition) -> { - return iblockdata.isSuffocating(this.level, blockposition); - }).findAny().isPresent(); + // Paper start + return io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this.level, this, axisalignedbb, null, + false, false, false, true, (iblockdata, blockposition) -> { + return iblockdata.isSuffocating(this.level, blockposition); + }); + // Paper end } } diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java index 2a784a8342e708e0813c7076a2ca8e429446ffd3..f38cd7e2c341ae2fbbe9dd26cf872da9571b416a 100644 --- a/src/main/java/net/minecraft/world/level/CollisionGetter.java +++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java @@ -36,28 +36,40 @@ public interface CollisionGetter extends BlockGetter { return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox())); } + // Paper start - optimise collisions + default boolean noCollision(Entity entity, AABB box, Predicate filter, 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, filter); + } + // Paper end - optimise collisions + default boolean noCollision(AABB box) { - return this.noCollision((Entity)null, box, (e) -> { - return true; - }); + // 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(), (e) -> { - return true; - }); + // 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(Entity entity, AABB box) { - return this.noCollision(entity, box, (e) -> { - return true; - }); + // 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 } default boolean noCollision(@Nullable Entity entity, AABB box, Predicate filter) { - try { if (entity != null) entity.collisionLoadChunks = true; // Paper - return this.getCollisions(entity, box, filter).allMatch(VoxelShape::isEmpty); - } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper + // 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, filter); + // Paper end - optimise collisions } Stream getEntityCollisions(@Nullable Entity entity, AABB box, Predicate predicate); diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java index 6124e3a32325e8c74bf839010a79d7c82c49aaff..cffb08bcb63f7ee2d8a163d865d87a9031f19407 100644 --- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java +++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java @@ -106,7 +106,7 @@ public class CollisionSpliterator extends AbstractSpliterator { 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/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java index 94130509e3a7980c378cc95c46821cf0fc753ce6..7224c56e8a68870364c6538c82c04f371b74aabd 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 entity2 : this.getEntities(entity, shape.bounds())) { - if (!entity2.isRemoved() && entity2.blocksBuilding && (entity == null || !entity2.isPassengerOfSameVehicle(entity)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity2.getBoundingBox()), BooleanOp.AND)) { + if (!entity2.isRemoved() && entity2.blocksBuilding && (entity == null || !entity2.isPassengerOfSameVehicle(entity)) && shape.intersects(entity2.getBoundingBox())) { // Paper return false; } } @@ -66,7 +66,7 @@ public interface EntityGetter { if (box.getSize() < 1.0E-7D) { return Stream.empty(); } else { - AABB aABB = box.inflate(1.0E-7D); + AABB aABB = box.inflate(-1.0E-7D); // Paper - needs to be negated, or else we get things we don't collide with Predicate hardCollides = (entityx) -> { // Paper - optimise entity hard collisions if (true || entityx.getBoundingBox().intersects(aABB)) { // Paper - always true if (entity == null) { 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 4a7fdea6a5f966db444dc41f7215faa99e3820b3..d87f8d106834678364f8720447d671de60c2454e 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 @@ -685,7 +685,7 @@ 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 - + // TODO optimise light } public Block getBlock() { 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 99427b6130895ddecee8bcf77db72d809c24c375..61f5339042290eeaea54964cd3838d0bf4646cb2 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 toAabbs() { + if (this.boundingBoxesRepresentation == null) { + return super.toAabbs(); + } + java.util.List 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 16bc18cacbf7a23fb744c8a12e7fd8da699b2fea..2fb416bd1d7a9879c13907b7e3c6b857fb1bf0ed 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java @@ -26,16 +26,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) { @@ -47,30 +48,11 @@ 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})); - } - } else { - return empty(); - } + return new io.papermc.paper.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Paper } 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 @@ -132,6 +114,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 { @@ -285,6 +281,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 { @@ -299,7 +332,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 } } @@ -324,6 +357,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 f325d76c79d63629200262a77eab7cdcc9beedfa..0ab742f38f79c150630bb9ba153d92d864aface1 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)) {