Paper/patches/server/0756-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch
Nassim Jahnke 058d7c1aa3
Updated Upstream (Bukkit/CraftBukkit/Spigot)
Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
13eb4146 SPIGOT-5217, SPIGOT-6183: Add RespawnReason to PlayerRespawnEvent

CraftBukkit Changes:
5ee3419b7 SPIGOT-5217, SPIGOT-6183: Add RespawnReason to PlayerRespawnEvent

Spigot Changes:
514cf03a Rebuild patches and add RespawnReason.PLUGIN to Player#respawn
2023-04-10 10:03:36 +02:00

2121 lines
104 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
Date: Mon, 4 May 2020 10:06:24 -0700
Subject: [PATCH] Highly optimise single and multi-AABB VoxelShapes and
collisions
diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java
index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644
--- a/src/main/java/io/papermc/paper/util/CachedLists.java
+++ b/src/main/java/io/papermc/paper/util/CachedLists.java
@@ -1,8 +1,57 @@
package io.papermc.paper.util;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.AABB;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.util.UnsafeList;
+import java.util.List;
+
public final class CachedLists {
- public static void reset() {
+ // Paper start - optimise collisions
+ static final UnsafeList<AABB> TEMP_COLLISION_LIST = new UnsafeList<>(1024);
+ static boolean tempCollisionListInUse;
+
+ public static UnsafeList<AABB> getTempCollisionList() {
+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) {
+ return new UnsafeList<>(16);
+ }
+ tempCollisionListInUse = true;
+ return TEMP_COLLISION_LIST;
+ }
+
+ public static void returnTempCollisionList(List<AABB> list) {
+ if (list != TEMP_COLLISION_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempCollisionListInUse = false;
+ }
+ static final UnsafeList<Entity> TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024);
+ static boolean tempGetEntitiesListInUse;
+
+ public static UnsafeList<Entity> getTempGetEntitiesList() {
+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) {
+ return new UnsafeList<>(16);
+ }
+ tempGetEntitiesListInUse = true;
+ return TEMP_GET_ENTITIES_LIST;
+ }
+
+ public static void returnTempGetEntitiesList(List<Entity> list) {
+ if (list != TEMP_GET_ENTITIES_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempGetEntitiesListInUse = false;
+ }
+ // Paper end - optimise collisions
+
+ public static void reset() {
+ // Paper start - optimise collisions
+ TEMP_COLLISION_LIST.completeReset();
+ TEMP_GET_ENTITIES_LIST.completeReset();
+ // Paper end - optimise collisions
}
}
diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..a87f6380b2c387fb0cdd40d5087b5c93492e3c88
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java
@@ -0,0 +1,899 @@
+package io.papermc.paper.util;
+
+import io.papermc.paper.voxel.AABBVoxelShape;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.WorldGenRegion;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.CollisionGetter;
+import net.minecraft.world.level.EntityGetter;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.WorldBorder;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.chunk.PalettedContainer;
+import net.minecraft.world.level.material.FlowingFluid;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import net.minecraft.world.phys.shapes.ArrayVoxelShape;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.EntityCollisionContext;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.List;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+public final class CollisionUtil {
+
+ public static final double COLLISION_EPSILON = 1.0E-7;
+
+ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to always have voxelshape of empty
+ public static final long KNOWN_FULL_BLOCK = 0b01; // known to always have voxelshape of full cube
+ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info
+ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions
+
+ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) {
+ return block.shapeExceedsCube() || block.getBlock() == Blocks.MOVING_PISTON;
+ }
+
+ public static boolean isEmpty(final AABB aabb) {
+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON;
+ }
+
+ public static boolean isEmpty(final double minX, final double minY, final double minZ,
+ final double maxX, final double maxY, final double maxZ) {
+ return (maxX - minX) < COLLISION_EPSILON && (maxY - minY) < COLLISION_EPSILON && (maxZ - minZ) < COLLISION_EPSILON;
+ }
+
+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) {
+ double x = (double)(chunkX << 4);
+ double z = (double)(chunkZ << 4);
+ // use a bounding box bigger than the chunk to prevent entities from entering it on move
+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON,
+ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false);
+ }
+
+ /*
+ A couple of rules for VoxelShape collisions:
+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement
+ checks.
+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite
+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code
+ will automatically round it to 0.
+ */
+
+ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1,
+ final double maxY1, final double maxZ1, final double minX2, final double minY2,
+ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) {
+ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON &&
+ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON &&
+ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON;
+ }
+
+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ,
+ final double maxX, final double maxY, final double maxZ) {
+ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON &&
+ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON &&
+ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON;
+ }
+
+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) {
+ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON &&
+ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON &&
+ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON;
+ }
+
+ public static double collideX(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideY(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideZ(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
+ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static AABB offsetX(final AABB box, final double dx) {
+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB offsetY(final AABB box, final double dy) {
+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB offsetZ(final AABB box, final double dz) {
+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0
+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0
+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0
+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0
+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0
+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0
+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0
+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false);
+ }
+
+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0
+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0
+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false);
+ }
+
+ public static double performCollisionsX(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideX(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static double performCollisionsY(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideY(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static double performCollisionsZ(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideZ(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, final List<AABB> potentialCollisions) {
+ double x = moveVector.x;
+ double y = moveVector.y;
+ double z = moveVector.z;
+
+ if (y != 0.0) {
+ y = performCollisionsY(axisalignedbb, y, potentialCollisions);
+ if (y != 0.0) {
+ axisalignedbb = offsetY(axisalignedbb, y);
+ }
+ }
+
+ final boolean xSmaller = Math.abs(x) < Math.abs(z);
+
+ if (xSmaller && z != 0.0) {
+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions);
+ if (z != 0.0) {
+ axisalignedbb = offsetZ(axisalignedbb, z);
+ }
+ }
+
+ if (x != 0.0) {
+ x = performCollisionsX(axisalignedbb, x, potentialCollisions);
+ if (!xSmaller && x != 0.0) {
+ axisalignedbb = offsetX(axisalignedbb, x);
+ }
+ }
+
+ if (!xSmaller && z != 0.0) {
+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions);
+ }
+
+ return new Vec3(x, y, z);
+ }
+
+ public static boolean addBoxesToIfIntersects(final VoxelShape shape, final AABB aabb, final List<AABB> list) {
+ if (shape instanceof AABBVoxelShape) {
+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape;
+ if (voxelShapeIntersect(shapeCasted.aabb, aabb) && !isEmpty(shapeCasted.aabb)) {
+ list.add(shapeCasted.aabb);
+ return true;
+ }
+ return false;
+ } else if (shape instanceof ArrayVoxelShape) {
+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape;
+ // this can be optimised by checking an "overall shape" first, but not needed
+
+ final double offX = shapeCasted.getOffsetX();
+ final double offY = shapeCasted.getOffsetY();
+ final double offZ = shapeCasted.getOffsetZ();
+
+ boolean ret = false;
+
+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) {
+ final double minX, minY, minZ, maxX, maxY, maxZ;
+ if (voxelShapeIntersect(aabb, minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ,
+ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ)
+ && !isEmpty(minX, minY, minZ, maxX, maxY, maxZ)) {
+ list.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ, false));
+ ret = true;
+ }
+ }
+
+ return ret;
+ } else {
+ final List<AABB> boxes = shape.toAabbs();
+
+ boolean ret = false;
+
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
+ final AABB box = boxes.get(i);
+ if (voxelShapeIntersect(box, aabb) && !isEmpty(box)) {
+ list.add(box);
+ ret = true;
+ }
+ }
+
+ return ret;
+ }
+ }
+
+ public static void addBoxesTo(final VoxelShape shape, final List<AABB> list) {
+ if (shape instanceof AABBVoxelShape) {
+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape;
+ if (!isEmpty(shapeCasted.aabb)) {
+ list.add(shapeCasted.aabb);
+ }
+ } else if (shape instanceof ArrayVoxelShape) {
+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape;
+
+ final double offX = shapeCasted.getOffsetX();
+ final double offY = shapeCasted.getOffsetY();
+ final double offZ = shapeCasted.getOffsetZ();
+
+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) {
+ final AABB box = boundingBox.move(offX, offY, offZ);
+ if (!isEmpty(box)) {
+ list.add(box);
+ }
+ }
+ } else {
+ final List<AABB> boxes = shape.toAabbs();
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
+ final AABB box = boxes.get(i);
+ if (!isEmpty(box)) {
+ list.add(box);
+ }
+ }
+ }
+ }
+
+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final AABB boundingBox) {
+ return isAlmostCollidingOnBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
+ }
+
+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX,
+ final double boxMinZ, final double boxMaxZ) {
+ final double borderMinX = worldborder.getMinX(); // -X
+ final double borderMaxX = worldborder.getMaxX(); // +X
+
+ final double borderMinZ = worldborder.getMinZ(); // -Z
+ final double borderMaxZ = worldborder.getMaxZ(); // +Z
+
+ return
+ // Not intersecting if we're smaller
+ !voxelShapeIntersect(
+ boxMinX + COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + COLLISION_EPSILON,
+ boxMaxX - COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - COLLISION_EPSILON,
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
+ )
+ &&
+
+ // Are intersecting if we're larger
+ voxelShapeIntersect(
+ boxMinX - COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - COLLISION_EPSILON,
+ boxMaxX + COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + COLLISION_EPSILON,
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
+ );
+ }
+
+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final AABB boundingBox) {
+ return isCollidingWithBorderEdge(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
+ }
+
+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final double boxMinX, final double boxMaxX,
+ final double boxMinZ, final double boxMaxZ) {
+ final double borderMinX = worldborder.getMinX() + COLLISION_EPSILON; // -X
+ final double borderMaxX = worldborder.getMaxX() - COLLISION_EPSILON; // +X
+
+ final double borderMinZ = worldborder.getMinZ() + COLLISION_EPSILON; // -Z
+ final double borderMaxZ = worldborder.getMaxZ() - COLLISION_EPSILON; // +Z
+
+ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ;
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into);
+ ret = true;
+ }
+ }
+ }
+
+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return ret;
+ }
+
+ final int minYIterate = Math.max(minBlock, minBlockY);
+ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ final int minChunkX = minBlockX >> 4;
+ final int maxChunkX = maxBlockX >> 4;
+
+ final int minChunkY = minBlockY >> 4;
+ final int maxChunkY = maxBlockY >> 4;
+
+ final int minChunkYIterate = minYIterate >> 4;
+ final int maxChunkYIterate = maxYIterate >> 4;
+
+ final int minChunkZ = minBlockZ >> 4;
+ final int maxChunkZ = maxBlockZ >> 4;
+
+ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
+ chunkProvider = ((ServerLevel)getter).getChunkSource();
+ } else {
+ chunkProvider = null;
+ }
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ final int chunkXGlobalPos = currChunkX << 4;
+ final int chunkZGlobalPos = currChunkZ << 4;
+ final ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ }
+ continue;
+ }
+
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+
+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
+ final LevelChunkSection section = sections[currChunkY - minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // empty
+ continue;
+ }
+ final PalettedContainer<BlockState> blocks = section.states;
+
+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
+ final int chunkYGlobalPos = currChunkY << 4;
+
+ final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks();
+
+ final int minXIterate;
+ final int maxXIterate;
+ final int minZIterate;
+ final int maxZIterate;
+ final int minYIterateLocal;
+ final int maxYIterateLocal;
+
+ if (!sectionHasSpecial) {
+ minXIterate = currChunkX == minChunkX ? minX + 1 : minX;
+ maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX;
+ minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ;
+ maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ;
+ minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY;
+ maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY;
+ if (minXIterate > maxXIterate || minZIterate > maxZIterate) {
+ continue;
+ }
+ } else {
+ minXIterate = minX;
+ maxXIterate = maxX;
+ minZIterate = minZ;
+ maxZIterate = maxZ;
+ minYIterateLocal = minY;
+ maxYIterateLocal = maxY;
+ }
+
+ for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) {
+ long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15);
+ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ,
+ collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) {
+ // From getKnownBlockInfoHorizontalRaw:
+ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
+ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
+ // We want to use a bitset to only iterate over non-empty blocks.
+ // We need to build a bitset mask to and out the other collisions we just don't care at all about
+ // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis
+ // It's important to note that the iterate values can be outside [0, 15], but if they are,
+ // then none of the x or z loops would meet their conditions. So we can assume they are never
+ // out of bounds here
+ final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32
+ long bitset = (1L << xAxisBits) - 1;
+ // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd)
+ int shift = (currZ & 1) << 5; // this will be a LEFT shift
+ // Now we need to offset shift so that the bitset first position is at minXIterate
+ shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ...
+
+ // all done
+ bitset = bitset << shift;
+ if ((collisionForHorizontal & bitset) == 0L) {
+ // All empty
+ continue;
+ }
+ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
+ final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8);
+
+ final int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal);
+
+ switch (blockInfo) {
+ case (int) CollisionUtil.KNOWN_EMPTY_BLOCK: {
+ continue;
+ }
+ case (int) CollisionUtil.KNOWN_FULL_BLOCK: {
+ double blockX = (double)(currX | chunkXGlobalPos);
+ double blockY = (double)(currY | chunkYGlobalPos);
+ double blockZ = (double)(currZ | chunkZGlobalPos);
+ final AABB blockBox = new AABB(
+ blockX, blockY, blockZ,
+ blockX + 1.0, blockY + 1.0, blockZ + 1.0,
+ true
+ );
+ if (predicate != null) {
+ if (!voxelShapeIntersect(aabb, blockBox)) {
+ continue;
+ }
+ // fall through to get the block for the predicate
+ } else {
+ if (voxelShapeIntersect(aabb, blockBox)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(blockBox);
+ ret = true;
+ }
+ }
+ continue;
+ }
+ }
+ // default: fall through to standard logic
+ }
+
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY | chunkYGlobalPos;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorderReference(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into);
+ ret = true;
+ }
+ }
+ }
+
+ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ final int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return ret;
+ }
+
+ final int minYIterate = Math.max(minBlock, minBlockY);
+ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ final int minChunkX = minBlockX >> 4;
+ final int maxChunkX = maxBlockX >> 4;
+
+ final int minChunkY = minBlockY >> 4;
+ final int maxChunkY = maxBlockY >> 4;
+
+ final int minChunkYIterate = minYIterate >> 4;
+ final int maxChunkYIterate = maxYIterate >> 4;
+
+ final int minChunkZ = minBlockZ >> 4;
+ final int maxChunkZ = maxBlockZ >> 4;
+
+ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
+ chunkProvider = ((ServerLevel)getter).getChunkSource();
+ } else {
+ chunkProvider = null;
+ }
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ final int chunkXGlobalPos = currChunkX << 4;
+ final int chunkZGlobalPos = currChunkZ << 4;
+ final ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ }
+ continue;
+ }
+
+ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
+ final LevelChunkSection section = sections[currChunkY - minSection];
+ if (section == null || section.hasOnlyAir()) {
+ // empty
+ continue;
+ }
+ final PalettedContainer<BlockState> blocks = section.states;
+
+ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
+ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
+ final int chunkYGlobalPos = currChunkY << 4;
+
+ for (int currY = minY; currY <= maxY; ++currY) {
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY | chunkYGlobalPos;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+ if (blockData.getBlockCollisionBehavior() == CollisionUtil.KNOWN_EMPTY_BLOCK) {
+ continue;
+ }
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb,
+ final List<AABB> into, final boolean checkOnly, final Predicate<Entity> predicate) {
+ if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) {
+ return false;
+ }
+
+ boolean ret = false;
+
+ // to comply with vanilla intersection rules, expand by -epsilon so we only get stuff we definitely collide with.
+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems
+ // specifically with boat collisions.
+ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON);
+ final List<Entity> entities = CachedLists.getTempGetEntitiesList();
+ try {
+ if (entity != null && entity.hardCollides()) {
+ entityGetter.getEntities(entity, aabb, predicate, entities);
+ } else {
+ entityGetter.getHardCollidingEntities(entity, aabb, predicate, entities);
+ }
+
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ final Entity otherEntity = entities.get(i);
+
+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(otherEntity.getBoundingBox());
+ ret = true;
+ }
+ }
+ }
+ } finally {
+ CachedLists.returnTempGetEntitiesList(entities);
+ }
+
+ return ret;
+ }
+
+ public static boolean getCollisions(final CollisionGetter view, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloadedChunks,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> blockPredicate,
+ final Predicate<Entity> entityPredicate) {
+ if (checkOnly) {
+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate)
+ || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate);
+ } else {
+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate)
+ | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate);
+ }
+ }
+
+ public static final class LazyEntityCollisionContext extends EntityCollisionContext {
+
+ private CollisionContext delegate;
+
+ public LazyEntityCollisionContext(final Entity entity) {
+ super(false, 0.0, null, null, entity);
+ }
+
+ public CollisionContext getDelegate() {
+ final Entity entity = this.getEntity();
+ return this.delegate == null ? this.delegate = (entity == null ? CollisionContext.empty() : CollisionContext.of(entity)) : this.delegate;
+ }
+
+ @Override
+ public boolean isDescending() {
+ return this.getDelegate().isDescending();
+ }
+
+ @Override
+ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) {
+ return this.getDelegate().isAbove(shape, pos, defaultValue);
+ }
+
+ @Override
+ public boolean isHoldingItem(final Item item) {
+ return this.getDelegate().isHoldingItem(item);
+ }
+
+ @Override
+ public boolean canStandOnFluid(final FluidState state, final FluidState fluidState) {
+ return this.getDelegate().canStandOnFluid(state, fluidState);
+ }
+ }
+
+ private CollisionUtil() {
+ throw new RuntimeException();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java
new file mode 100644
index 0000000000000000000000000000000000000000..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 d9719e544261b9d2dc85fa9936ae23ea8e8dd08c..5afb17ccc16e614e91ff3cad2f714b4ab40fb979 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -418,7 +418,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;
}
}
@@ -426,7 +426,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 0b3b926e6d9a0afbd2f4674517e39b3addd8acf5..3c3e110f7caaf42783638aac74ef5e69513d1fff 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -886,7 +886,7 @@ public abstract class PlayerList {
// CraftBukkit end
worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper
- while (avoidSuffocation && !worldserver1.noCollision((Entity) entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
+ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now
entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 45eeef03ab3c885b4fbc332287089e9cc5d85afa..63802f5304f23a6e1e469e383692376d1f4d8f0d 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -1161,9 +1161,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
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());
}
@@ -1315,32 +1350,78 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
}
private Vec3 collide(Vec3 movement) {
- AABB axisalignedbb = this.getBoundingBox();
- List<VoxelShape> list = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement));
- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBox(this, movement, axisalignedbb, this.level, list);
- boolean flag = movement.x != vec3d1.x;
- boolean flag1 = movement.y != vec3d1.y;
- boolean flag2 = movement.z != vec3d1.z;
- boolean flag3 = this.onGround || flag1 && movement.y < 0.0D;
-
- if (this.maxUpStep() > 0.0F && flag3 && (flag || flag2)) {
- Vec3 vec3d2 = Entity.collideBoundingBox(this, new Vec3(movement.x, (double) this.maxUpStep(), movement.z), axisalignedbb, this.level, list);
- Vec3 vec3d3 = Entity.collideBoundingBox(this, new Vec3(0.0D, (double) this.maxUpStep(), 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level, list);
-
- if (vec3d3.y < (double) this.maxUpStep()) {
- Vec3 vec3d4 = Entity.collideBoundingBox(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level, list).add(vec3d3);
-
- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
- vec3d2 = vec3d4;
+ // Paper start - optimise collisions
+ // This is a copy of vanilla's except that it uses strictly AABB math
+ if (movement.x == 0.0 && movement.y == 0.0 && movement.z == 0.0) {
+ return movement;
+ }
+
+ final Level world = this.level;
+ final AABB currBoundingBox = this.getBoundingBox();
+
+ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) {
+ return movement;
+ }
+
+ final List<AABB> potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList();
+ try {
+ final double stepHeight = (double)this.maxUpStep();
+ final AABB collisionBox;
+
+ if (movement.x == 0.0 && movement.z == 0.0 && movement.y != 0.0) {
+ if (movement.y > 0.0) {
+ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, movement.y);
+ } else {
+ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, movement.y);
+ }
+ } else {
+ if (stepHeight > 0.0 && (this.onGround || (movement.y < 0.0)) && (movement.x != 0.0 || movement.z != 0.0)) {
+ // don't bother getting the collisions if we don't need them.
+ if (movement.y <= 0.0) {
+ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(movement.x, movement.y, movement.z), stepHeight);
+ } else {
+ collisionBox = currBoundingBox.expandTowards(movement.x, Math.max(stepHeight, movement.y), movement.z);
+ }
+ } else {
+ collisionBox = currBoundingBox.expandTowards(movement.x, movement.y, movement.z);
}
}
- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) {
- return vec3d2.add(Entity.collideBoundingBox(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level, list));
+ io.papermc.paper.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, this.level.paperConfig().chunks.preventMovingIntoUnloadedChunks,
+ false, false, null, null);
+
+ if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) {
+ io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions);
}
- }
- return vec3d1;
+ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisions);
+
+ if (stepHeight > 0.0
+ && (this.onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0))
+ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) {
+ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisions);
+ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisions);
+
+ if (vec3d3.y < stepHeight) {
+ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3);
+
+ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
+ vec3d2 = vec3d4;
+ }
+ }
+
+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) {
+ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions));
+ }
+
+ return limitedMoveVector;
+ } else {
+ return limitedMoveVector;
+ }
+ } finally {
+ io.papermc.paper.util.CachedLists.returnTempCollisionList(potentialCollisions);
+ }
+ // Paper end - optimise collisions
}
public static Vec3 collideBoundingBox(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, List<VoxelShape> collisions) {
@@ -2491,11 +2572,30 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
float f = this.dimensions.width * 0.8F;
AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f);
- return BlockPos.betweenClosedStream(axisalignedbb).anyMatch((blockposition) -> {
- BlockState iblockdata = this.level.getBlockState(blockposition);
+ 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 be578f14146b0184d5419d5b961c5d681f9ba7a3..018cbf866bbe39b69a4afa039166e8d34ec3ab5f 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 66a5783e2a83c75ca46d1fd6f97d9de733c01a09..d860ddae508f53d06f74d8ae0efdfc500c1ddf07 100644
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
@@ -49,7 +49,7 @@ public interface EntityGetter {
return true;
} else {
for(Entity entity : this.getEntities(except, shape.bounds())) {
- if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity.getBoundingBox()), BooleanOp.AND)) {
+ if (!entity.isRemoved() && entity.blocksBuilding && (except == null || !entity.isPassengerOfSameVehicle(except)) && shape.intersects(entity.getBoundingBox())) { // Paper
return false;
}
}
@@ -67,7 +67,7 @@ public interface EntityGetter {
return List.of();
} else {
Predicate<Entity> predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith);
- List<Entity> list = this.getEntities(entity, box.inflate(1.0E-7D), predicate);
+ List<Entity> list = this.getEntities(entity, box.inflate(-1.0E-7D), predicate); // Paper - needs to be negated, or else we get things we don't collide with
if (list.isEmpty()) {
return List.of();
} else {
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 61c814a8042d3d4be5ea86ce339c90100bdbe597..cd939384f022609c96b055c25db7e098e4256336 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
@@ -787,6 +787,12 @@ public abstract class BlockBehaviour implements FeatureElement {
return this.conditionallyFullOpaque;
}
// Paper end - starlight
+ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
+
+ public final long getBlockCollisionBehavior() {
+ return this.blockCollisionBehavior;
+ }
+ // Paper end
public void initCache() {
this.fluidState = ((Block) this.owner).getFluidState(this.asState());
@@ -797,6 +803,35 @@ public abstract class BlockBehaviour implements FeatureElement {
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 - starlight - 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 c7e2796e136ee8fb7d7e438a7fc59826c05b761b..1b80a91fa36c59a31b57ef7ef4a68eacbb0f17f5 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
@@ -46,6 +46,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;
}
@@ -70,8 +174,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);
@@ -110,6 +214,7 @@ public class LevelChunkSection {
++this.tickingFluidCount;
}
+ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper
return iblockdata1;
}
@@ -159,6 +264,7 @@ public class LevelChunkSection {
});
// Paper end
+ this.initBlockCollisionData(); // Paper
}
public PalettedContainer<BlockState> getStates() {
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index f80783dc163997626850189f5647c06f9d15da6c..ffc76354ead6937daf366c3d87bcb51d3e4c47f5 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java
+++ b/src/main/java/net/minecraft/world/phys/AABB.java
@@ -25,6 +25,17 @@ public class AABB {
this.maxZ = Math.max(z1, z2);
}
+ // Paper start
+ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) {
+ this.minX = minX;
+ this.minY = minY;
+ this.minZ = minZ;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ this.maxZ = maxZ;
+ }
+ // Paper end
+
public AABB(BlockPos pos) {
this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1));
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
index 9d627b8e6bf3140b894d38b9a720896e2d776369..ca5f01be5d5ccfcc56780ff93cca3824409ffc0d 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
@@ -6,6 +6,9 @@ import java.util.Arrays;
import net.minecraft.Util;
import net.minecraft.core.Direction;
+// Paper start
+import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
+// Paper end
public class ArrayVoxelShape extends VoxelShape {
private final DoubleList xs;
private final DoubleList ys;
@@ -16,6 +19,11 @@ public class ArrayVoxelShape extends VoxelShape {
}
ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) {
+ // Paper start - optimise multi-aabb shapes
+ this(shape, xPoints, yPoints, zPoints, null, 0.0, 0.0, 0.0);
+ }
+ ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints, net.minecraft.world.phys.AABB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) {
+ // Paper end - optimise multi-aabb shapes
super(shape);
int i = shape.getXSize() + 1;
int j = shape.getYSize() + 1;
@@ -27,6 +35,12 @@ public class ArrayVoxelShape extends VoxelShape {
} else {
throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape."));
}
+ // Paper start - optimise multi-aabb shapes
+ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.toAabbs().toArray(EMPTY) : boundingBoxesRepresentation;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.offsetZ = offsetZ;
+ // Paper end - optimise multi-aabb shapes
}
@Override
@@ -42,4 +56,152 @@ public class ArrayVoxelShape extends VoxelShape {
throw new IllegalArgumentException();
}
}
+
+ // Paper start
+ public static final class DoubleListOffsetExposed extends AbstractDoubleList {
+
+ public final DoubleArrayList list;
+ public final double offset;
+
+ public DoubleListOffsetExposed(final DoubleArrayList list, final double offset) {
+ this.list = list;
+ this.offset = offset;
+ }
+
+ @Override
+ public double getDouble(final int index) {
+ return this.list.getDouble(index) + this.offset;
+ }
+
+ @Override
+ public int size() {
+ return this.list.size();
+ }
+ }
+
+ static final net.minecraft.world.phys.AABB[] EMPTY = new net.minecraft.world.phys.AABB[0];
+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation;
+
+ final double offsetX;
+ final double offsetY;
+ final double offsetZ;
+
+ public final net.minecraft.world.phys.AABB[] getBoundingBoxesRepresentation() {
+ return this.boundingBoxesRepresentation;
+ }
+
+ public final double getOffsetX() {
+ return this.offsetX;
+ }
+
+ public final double getOffsetY() {
+ return this.offsetY;
+ }
+
+ public final double getOffsetZ() {
+ return this.offsetZ;
+ }
+
+ @Override
+ public java.util.List<net.minecraft.world.phys.AABB> toAabbs() {
+ if (this.boundingBoxesRepresentation == null) {
+ return super.toAabbs();
+ }
+ java.util.List<net.minecraft.world.phys.AABB> ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length);
+
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ ret.add(boundingBox.move(offX, offY, offZ));
+ }
+
+ return ret;
+ }
+
+ protected static DoubleArrayList getList(DoubleList from) {
+ if (from instanceof DoubleArrayList) {
+ return (DoubleArrayList)from;
+ } else {
+ return DoubleArrayList.wrap(from.toDoubleArray());
+ }
+ }
+
+ @Override
+ public VoxelShape move(double x, double y, double z) {
+ if (x == 0.0 && y == 0.0 && z == 0.0) {
+ return this;
+ }
+ DoubleListOffsetExposed xPoints, yPoints, zPoints;
+ double offsetX, offsetY, offsetZ;
+
+ if (this.xs instanceof DoubleListOffsetExposed) {
+ xPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.xs).list, offsetX = this.offsetX + x);
+ yPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.ys).list, offsetY = this.offsetY + y);
+ zPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.zs).list, offsetZ = this.offsetZ + z);
+ } else {
+ xPoints = new DoubleListOffsetExposed(getList(this.xs), offsetX = x);
+ yPoints = new DoubleListOffsetExposed(getList(this.ys), offsetY = y);
+ zPoints = new DoubleListOffsetExposed(getList(this.zs), offsetZ = z);
+ }
+
+ return new ArrayVoxelShape(this.shape, xPoints, yPoints, zPoints, this.boundingBoxesRepresentation, offsetX, offsetY, offsetZ);
+ }
+
+ @Override
+ public final boolean intersects(net.minecraft.world.phys.AABB axisalingedbb) {
+ // this can be optimised by checking an "overall shape" first, but not needed
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(axisalingedbb, boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ,
+ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void forAllBoxes(Shapes.DoubleLineConsumer doubleLineConsumer) {
+ if (this.boundingBoxesRepresentation == null) {
+ super.forAllBoxes(doubleLineConsumer);
+ return;
+ }
+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ doubleLineConsumer.consume(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ,
+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ);
+ }
+ }
+
+ @Override
+ public VoxelShape optimize() {
+ if (this == Shapes.empty() || this.boundingBoxesRepresentation.length == 0) {
+ return this;
+ }
+
+ VoxelShape simplified = Shapes.empty();
+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ simplified = Shapes.joinUnoptimized(simplified, Shapes.box(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ,
+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ), BooleanOp.OR);
+ }
+
+ if (!(simplified instanceof ArrayVoxelShape)) {
+ return simplified;
+ }
+
+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation = ((ArrayVoxelShape)simplified).getBoundingBoxesRepresentation();
+
+ if (boundingBoxesRepresentation.length == 1) {
+ return new io.papermc.paper.voxel.AABBVoxelShape(boundingBoxesRepresentation[0]).optimize();
+ }
+
+ return simplified;
+ }
+ // Paper end
+
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
index 9176735c08a75854209f24113b0e78332249dc4d..731c7dd15f131dc124be6af8f342b122cb89491b 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
@@ -19,16 +19,17 @@ public final class Shapes {
DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1);
discreteVoxelShape.fill(0, 0, 0);
return new CubeVoxelShape(discreteVoxelShape);
- });
+ }); public static VoxelShape getFullUnoptimisedCube() { return BLOCK; } // Paper - OBFHELPER
public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})));
+ public static final io.papermc.paper.voxel.AABBVoxelShape BLOCK_OPTIMISED = new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); // Paper
public static VoxelShape empty() {
return EMPTY;
}
public static VoxelShape block() {
- return BLOCK;
+ return BLOCK_OPTIMISED; // Paper
}
public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
@@ -41,29 +42,14 @@ public final class Shapes {
public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) {
- int i = findBits(minX, maxX);
- int j = findBits(minY, maxY);
- int k = findBits(minZ, maxZ);
- if (i >= 0 && j >= 0 && k >= 0) {
- if (i == 0 && j == 0 && k == 0) {
- return block();
- } else {
- int l = 1 << i;
- int m = 1 << j;
- int n = 1 << k;
- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n));
- return new CubeVoxelShape(bitSetDiscreteVoxelShape);
- }
- } else {
- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ}));
- }
+ return new io.papermc.paper.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Paper
} else {
return empty();
}
}
public static VoxelShape create(AABB box) {
- return create(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ);
+ return new io.papermc.paper.voxel.AABBVoxelShape(box); // Paper
}
@VisibleForTesting
@@ -125,6 +111,20 @@ public final class Shapes {
}
public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+ // Paper start - optimise voxelshape
+ if (predicate == BooleanOp.AND) {
+ if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape) {
+ return io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb, ((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb);
+ } else if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof ArrayVoxelShape) {
+ return ((ArrayVoxelShape)shape2).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb);
+ } else if (shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape1 instanceof ArrayVoxelShape) {
+ return ((ArrayVoxelShape)shape1).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb);
+ }
+ }
+ return joinIsNotEmptyVanilla(shape1, shape2, predicate);
+ }
+ public static boolean joinIsNotEmptyVanilla(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+ // Paper end - optimise voxelshape
if (predicate.apply(false, false)) {
throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
} else {
@@ -196,6 +196,43 @@ public final class Shapes {
}
public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) {
+ // Paper start - optimise shape creation here for lighting, as this shape is going to be used
+ // for transparency checks
+ if (shape == BLOCK || shape == BLOCK_OPTIMISED) {
+ return BLOCK_OPTIMISED;
+ } else if (shape == empty()) {
+ return empty();
+ }
+
+ if (shape instanceof io.papermc.paper.voxel.AABBVoxelShape) {
+ final AABB box = ((io.papermc.paper.voxel.AABBVoxelShape)shape).aabb;
+ switch (direction) {
+ case WEST: // -X
+ case EAST: { // +X
+ final boolean useEmpty = direction == Direction.EAST ? !DoubleMath.fuzzyEquals(box.maxX, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minX, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, box.minY, box.minZ, 1.0, box.maxY, box.maxZ)).optimize();
+ }
+ case DOWN: // -Y
+ case UP: { // +Y
+ final boolean useEmpty = direction == Direction.UP ? !DoubleMath.fuzzyEquals(box.maxY, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minY, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, 0.0, box.minZ, box.maxX, 1.0, box.maxZ)).optimize();
+ }
+ case NORTH: // -Z
+ case SOUTH: { // +Z
+ final boolean useEmpty = direction == Direction.SOUTH ? !DoubleMath.fuzzyEquals(box.maxZ, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minZ,0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, box.minY, 0.0, box.maxX, box.maxY, 1.0)).optimize();
+ }
+ }
+ }
+
+ // fall back to vanilla
+ return getFaceShapeVanilla(shape, direction);
+ }
+ public static VoxelShape getFaceShapeVanilla(VoxelShape shape, Direction direction) {
+ // Paper end
if (shape == block()) {
return block();
} else {
@@ -210,7 +247,7 @@ public final class Shapes {
i = 0;
}
- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i));
+ return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i).optimize().optimize()); // Paper - first optimize converts to ArrayVoxelShape, second optimize could convert to AABBVoxelShape
}
}
@@ -235,6 +272,53 @@ public final class Shapes {
}
public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) {
+ // Paper start - try to optimise for the case where the shapes do _not_ occlude
+ // which is _most_ of the time in lighting
+ if (one == getFullUnoptimisedCube() || one == BLOCK_OPTIMISED
+ || two == getFullUnoptimisedCube() || two == BLOCK_OPTIMISED) {
+ return true;
+ }
+ boolean v1Empty = one == empty();
+ boolean v2Empty = two == empty();
+ if (v1Empty && v2Empty) {
+ return false;
+ }
+ if ((one instanceof io.papermc.paper.voxel.AABBVoxelShape || v1Empty)
+ && (two instanceof io.papermc.paper.voxel.AABBVoxelShape || v2Empty)) {
+ if (!v1Empty && !v2Empty && (one != two)) {
+ AABB boundingBox1 = ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb;
+ AABB boundingBox2 = ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb;
+ // can call it here in some cases
+
+ // check overall bounding box
+ double minY = Math.min(boundingBox1.minY, boundingBox2.minY);
+ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY);
+ if (minY > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxY < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ double minX = Math.min(boundingBox1.minX, boundingBox2.minX);
+ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX);
+ if (minX > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxX < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ);
+ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ);
+ if (minZ > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxZ < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ // fall through to full merge check
+ } else {
+ AABB boundingBox = v1Empty ? ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb : ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb;
+ // check if the bounding box encloses the full cube
+ return (boundingBox.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+ (boundingBox.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+ (boundingBox.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON));
+ }
+ }
+ return faceShapeOccludesVanilla(one, two);
+ }
+ public static boolean faceShapeOccludesVanilla(VoxelShape one, VoxelShape two) {
+ // Paper end
if (one != block() && two != block()) {
if (one.isEmpty() && two.isEmpty()) {
return false;
diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
index c4ca051720f790f5b8eb860b14e268de8557454d..2182afd1b95acf14c55bddfeec17dae0a63e1f00 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
@@ -16,11 +16,17 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
public abstract class VoxelShape {
- protected final DiscreteVoxelShape shape;
+ public final DiscreteVoxelShape shape; // Paper - public
@Nullable
private VoxelShape[] faces;
- VoxelShape(DiscreteVoxelShape voxels) {
+ // Paper start
+ public boolean intersects(AABB shape) {
+ return Shapes.joinIsNotEmpty(this, new io.papermc.paper.voxel.AABBVoxelShape(shape), BooleanOp.AND);
+ }
+ // Paper end
+
+ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected
this.shape = voxels;
}
@@ -163,7 +169,7 @@ public abstract class VoxelShape {
}
}
- private VoxelShape calculateFace(Direction direction) {
+ protected VoxelShape calculateFace(Direction direction) { // Paper
Direction.Axis axis = direction.getAxis();
DoubleList doubleList = this.getCoords(axis);
if (doubleList.size() == 2 && DoubleMath.fuzzyEquals(doubleList.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0D, 1.0E-7D)) {