diff --git a/patches/server/Anti-Xray.patch b/patches/server/Anti-Xray.patch index 2698a94bf4..ebd49e9b32 100644 --- a/patches/server/Anti-Xray.patch +++ b/patches/server/Anti-Xray.patch @@ -1181,7 +1181,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private org.spigotmc.TickLimiter entityLimiter; @@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl } - // Paper end - rewrite chunk system + // Paper end - optimise collisions - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator, java.util.concurrent.Executor executor) { // Paper - create paper world config & Anti-Xray @@ -1189,9 +1189,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.paperConfig = paperWorldConfigCreator.apply(this.spigotConfig); // Paper - create paper world config this.generator = gen; @@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); - this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); - this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system + this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this); + this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this); + // Paper end - optimise collisions + this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray } @@ -1247,7 +1247,7 @@ diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.jav index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -0,0 +0,0 @@ public class LevelChunkSection { +@@ -0,0 +0,0 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.collis this.recalcBlockCounts(); } @@ -1263,7 +1263,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public BlockState getBlockState(int x, int y, int z) { -@@ -0,0 +0,0 @@ public class LevelChunkSection { +@@ -0,0 +0,0 @@ public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.collis this.biomes = datapaletteblock; } @@ -1289,7 +1289,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private final PaletteResize dummyPaletteResize = (newSize, added) -> 0; public final IdMap registry; + private final T @org.jetbrains.annotations.Nullable [] presetValues; // Paper - Anti-Xray - Add preset values - private volatile PalettedContainer.Data data; + public volatile PalettedContainer.Data data; // Paper - optimise collisions - public private final PalettedContainer.Strategy strategy; // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused @@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize, PalettedContainer diff --git a/patches/server/Fix-entity-tracker-desync-when-new-players-are-added.patch b/patches/server/Fix-entity-tracker-desync-when-new-players-are-added.patch index 62ec2eaa06..ca5a99f81a 100644 --- a/patches/server/Fix-entity-tracker-desync-when-new-players-are-added.patch +++ b/patches/server/Fix-entity-tracker-desync-when-new-players-are-added.patch @@ -75,8 +75,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper end - fix desync when a player is added to the tracker + public void sendChanges() { - List list = this.entity.getPassengers(); - + // Paper start - optimise collisions + if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) { @@ -0,0 +0,0 @@ public class ServerEntity { } } diff --git a/patches/server/Moonrise-optimisation-patches.patch b/patches/server/Moonrise-optimisation-patches.patch index 78de22a70f..7e3196eff7 100644 --- a/patches/server/Moonrise-optimisation-patches.patch +++ b/patches/server/Moonrise-optimisation-patches.patch @@ -6,6 +6,7 @@ Subject: [PATCH] Moonrise optimisation patches Currently includes: - Starlight + Chunk System - Entity tracker optimisations + - Collision optimisations See https://github.com/Tuinity/Moonrise @@ -3362,6 +3363,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + throw new RuntimeException(); + } +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_getblock/GetBlockChunk.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.chunk_getblock; ++ ++import net.minecraft.world.level.block.state.BlockState; ++ ++public interface GetBlockChunk { ++ ++ public BlockState moonrise$getBlock(final int x, final int y, final int z); ++ ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java b/src/main/java/ca/spottedleaf/moonrise/patches/chunk_system/ChunkSystem.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -18266,6 +18282,2336 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public LevelChunk moonrise$getFullChunkIfLoaded(final int chunkX, final int chunkZ); + +} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/CollisionUtil.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions; ++ ++public final class CollisionUtil { ++ ++ public static final double COLLISION_EPSILON = 1.0E-7; ++ public static final it.unimi.dsi.fastutil.doubles.DoubleArrayList ZERO_ONE = it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(new double[] { 0.0, 1.0 }); ++ ++ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) { ++ return block.hasLargeCollisionShape() || block.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON; ++ } ++ ++ public static boolean isEmpty(final net.minecraft.world.phys.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 net.minecraft.world.phys.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 net.minecraft.world.phys.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)); ++ } ++ ++ /* ++ 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 net.minecraft.world.phys.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 net.minecraft.world.phys.AABB box1, final net.minecraft.world.phys.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; ++ } ++ ++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON ++ public static double collideX(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ 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; ++ } ++ ++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON ++ public static double collideY(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ 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; ++ } ++ ++ // assume !isEmpty(target) && abs(source_move) >= COLLISION_EPSILON ++ public static double collideZ(final net.minecraft.world.phys.AABB target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ 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; ++ } ++ ++ // startIndex and endIndex inclusive ++ // assumes indices are in range of array ++ private static int findFloor(final double[] values, final double value, int startIndex, int endIndex) { ++ do { ++ final int middle = (startIndex + endIndex) >>> 1; ++ final double middleVal = values[middle]; ++ ++ if (value < middleVal) { ++ endIndex = middle - 1; ++ } else { ++ startIndex = middle + 1; ++ } ++ } while (startIndex <= endIndex); ++ ++ return startIndex - 1; ++ } ++ ++ public static boolean voxelShapeIntersectNoEmpty(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.AABB aabb) { ++ if (voxel.isEmpty()) { ++ return false; ++ } ++ ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX(); ++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY(); ++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ(); ++ ++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_x = Math.max( ++ 0, ++ findFloor(coords_x, (aabb.minX - off_x) + COLLISION_EPSILON, 0, size_x) ++ ); ++ if (floor_min_x >= size_x) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int ceil_max_x = Math.min( ++ size_x, ++ findFloor(coords_x, (aabb.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 ++ ); ++ if (floor_min_x >= ceil_max_x) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int floor_min_y = Math.max( ++ 0, ++ findFloor(coords_y, (aabb.minY - off_y) + COLLISION_EPSILON, 0, size_y) ++ ); ++ if (floor_min_y >= size_y) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int ceil_max_y = Math.min( ++ size_y, ++ findFloor(coords_y, (aabb.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 ++ ); ++ if (floor_min_y >= ceil_max_y) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int floor_min_z = Math.max( ++ 0, ++ findFloor(coords_z, (aabb.minZ - off_z) + COLLISION_EPSILON, 0, size_z) ++ ); ++ if (floor_min_z >= size_z) { ++ // cannot intersect ++ return false; ++ } ++ ++ final int ceil_max_z = Math.min( ++ size_z, ++ findFloor(coords_z, (aabb.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 ++ ); ++ if (floor_min_z >= ceil_max_z) { ++ // cannot intersect ++ return false; ++ } ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ // check bitset to check if any shapes in range are full ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return true; ++ } ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ // assume !target.isEmpty() && abs(source_move) >= COLLISION_EPSILON ++ public static double collideX(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return collideX(single_aabb, source, source_move); ++ } ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); ++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); ++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); ++ ++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_y = Math.max( ++ 0, ++ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y) ++ ); ++ if (floor_min_y >= size_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_y = Math.min( ++ size_y, ++ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 ++ ); ++ if (floor_min_y >= ceil_max_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int floor_min_z = Math.max( ++ 0, ++ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z) ++ ); ++ if (floor_min_z >= size_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_z = Math.min( ++ size_z, ++ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 ++ ); ++ if (floor_min_z >= ceil_max_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ if (source_move > 0.0) { ++ final double source_max = source.maxX - off_x; ++ final int ceil_max_x = findFloor( ++ coords_x, source_max - COLLISION_EPSILON, 0, size_x ++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index size on the collision axis for forward movement ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_x = ceil_max_x; curr_x < size_x; ++curr_x) { ++ double max_dist = coords_x[curr_x] - source_max; ++ if (max_dist >= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.min(max_dist, source_move); ++ } ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } else { ++ final double source_min = source.minX - off_x; ++ final int floor_min_x = findFloor( ++ coords_x, source_min + COLLISION_EPSILON, 0, size_x ++ ); ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement ++ ++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the ++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] ++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid ++ final int mul_x = size_y*size_z; ++ for (int curr_x = floor_min_x - 1; curr_x >= 0; --curr_x) { ++ double max_dist = coords_x[curr_x + 1] - source_min; ++ if (max_dist <= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is possibly bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.max(max_dist, source_move); ++ } ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } ++ } ++ ++ public static double collideY(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return collideY(single_aabb, source, source_move); ++ } ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); ++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); ++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); ++ ++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_x = Math.max( ++ 0, ++ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x) ++ ); ++ if (floor_min_x >= size_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_x = Math.min( ++ size_x, ++ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 ++ ); ++ if (floor_min_x >= ceil_max_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int floor_min_z = Math.max( ++ 0, ++ findFloor(coords_z, (source.minZ - off_z) + COLLISION_EPSILON, 0, size_z) ++ ); ++ if (floor_min_z >= size_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_z = Math.min( ++ size_z, ++ findFloor(coords_z, (source.maxZ - off_z) - COLLISION_EPSILON, floor_min_z, size_z) + 1 ++ ); ++ if (floor_min_z >= ceil_max_z) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ if (source_move > 0.0) { ++ final double source_max = source.maxY - off_y; ++ final int ceil_max_y = findFloor( ++ coords_y, source_max - COLLISION_EPSILON, 0, size_y ++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index size on the collision axis for forward movement ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_y = ceil_max_y; curr_y < size_y; ++curr_y) { ++ double max_dist = coords_y[curr_y] - source_max; ++ if (max_dist >= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.min(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } else { ++ final double source_min = source.minY - off_y; ++ final int floor_min_y = findFloor( ++ coords_y, source_min + COLLISION_EPSILON, 0, size_y ++ ); ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement ++ ++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the ++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] ++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid ++ final int mul_x = size_y*size_z; ++ for (int curr_y = floor_min_y - 1; curr_y >= 0; --curr_y) { ++ double max_dist = coords_y[curr_y + 1] - source_min; ++ if (max_dist <= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is possibly bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.max(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_z = floor_min_z; curr_z < ceil_max_z; ++curr_z) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } ++ } ++ ++ public static double collideZ(final net.minecraft.world.phys.shapes.VoxelShape target, final net.minecraft.world.phys.AABB source, final double source_move) { ++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return collideZ(single_aabb, source, source_move); ++ } ++ // note: this function assumes that for any i in coords that coord[i + 1] - coord[i] > COLLISION_EPSILON is true ++ ++ // offsets that should be applied to coords ++ final double off_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetX(); ++ final double off_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetY(); ++ final double off_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$offsetZ(); ++ ++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$rootCoordinatesZ(); ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)target).moonrise$getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: voxel bitset with set index (x, y, z) indicates that ++ // an AABB(coords_x[x], coords_y[y], coords_z[z], coords_x[x + 1], coords_y[y + 1], coords_z[z + 1]) ++ // is collidable. this is the fundamental principle of operation for the voxel collision operation ++ ++ ++ // note: we should be offsetting coords, but we can also just subtract from source as well - which is ++ // a win in terms of ops / simplicity (see findFloor, allows us to not modify coords for that) ++ // note: for intersection, one we find the floor of the min we can use that as the start index ++ // for the next check as source max >= source min ++ // note: we can fast check intersection on the two other axis by seeing if the min index is >= size, ++ // as this implies that coords[coords.length - 1] < source min ++ // we can also fast check by seeing if max index is < 0, as this implies that coords[0] > source max ++ ++ final int floor_min_x = Math.max( ++ 0, ++ findFloor(coords_x, (source.minX - off_x) + COLLISION_EPSILON, 0, size_x) ++ ); ++ if (floor_min_x >= size_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_x = Math.min( ++ size_x, ++ findFloor(coords_x, (source.maxX - off_x) - COLLISION_EPSILON, floor_min_x, size_x) + 1 ++ ); ++ if (floor_min_x >= ceil_max_x) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int floor_min_y = Math.max( ++ 0, ++ findFloor(coords_y, (source.minY - off_y) + COLLISION_EPSILON, 0, size_y) ++ ); ++ if (floor_min_y >= size_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ final int ceil_max_y = Math.min( ++ size_y, ++ findFloor(coords_y, (source.maxY - off_y) - COLLISION_EPSILON, floor_min_y, size_y) + 1 ++ ); ++ if (floor_min_y >= ceil_max_y) { ++ // cannot intersect ++ return source_move; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ if (source_move > 0.0) { ++ final double source_max = source.maxZ - off_z; ++ final int ceil_max_z = findFloor( ++ coords_z, source_max - COLLISION_EPSILON, 0, size_z ++ ) + 1; // add one, we are not interested in (coords[i] + COLLISION_EPSILON) < max ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index size on the collision axis for forward movement ++ ++ final int mul_x = size_y*size_z; ++ for (int curr_z = ceil_max_z; curr_z < size_z; ++curr_z) { ++ double max_dist = coords_z[curr_z] - source_max; ++ if (max_dist >= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max < source_move, as coords[curr + n] < coords[curr + n + 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist >= -COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.min(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } else { ++ final double source_min = source.minZ - off_z; ++ final int floor_min_z = findFloor( ++ coords_z, source_min + COLLISION_EPSILON, 0, size_z ++ ); ++ ++ // note: only the order of the first loop matters ++ ++ // note: we cannot collide with the face at index 0 on the collision axis for backwards movement ++ ++ // note: we offset the collision axis by - 1 for the voxel bitset index, but use + 1 for the ++ // coordinate index as the voxelset stores whether the shape is solid for [index, index + 1] ++ // thus, we need to use the voxel index i-1 if we want to check that the face at index i is solid ++ final int mul_x = size_y*size_z; ++ for (int curr_z = floor_min_z - 1; curr_z >= 0; --curr_z) { ++ double max_dist = coords_z[curr_z + 1] - source_min; ++ if (max_dist <= source_move) { ++ // if we reach here, then we will never have a case where ++ // coords[curr + n] - source_max > source_move, as coords[curr + n] > coords[curr + n - 1] ++ // thus, we can return immediately ++ ++ // this optimization is important since this loop is possibly bounded by size, and _not_ by ++ // a calculated max index based off of source_move - so it would be possible to check ++ // the whole intersected shape for collisions when we didn't need to! ++ return source_move; ++ } ++ if (max_dist <= COLLISION_EPSILON) { // only push out by up to COLLISION_EPSILON ++ max_dist = Math.max(max_dist, source_move); ++ } ++ for (int curr_x = floor_min_x; curr_x < ceil_max_x; ++curr_x) { ++ for (int curr_y = floor_min_y; curr_y < ceil_max_y; ++curr_y) { ++ final int index = curr_z + curr_y*size_z + curr_x*mul_x; ++ // note: JLS states long shift operators ANDS shift by 63 ++ if ((bitset[index >>> 6] & (1L << index)) != 0L) { ++ return max_dist; ++ } ++ } ++ } ++ } ++ ++ return source_move; ++ } ++ } ++ ++ // does not use epsilon ++ public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, final net.minecraft.world.phys.Vec3 point) { ++ return strictlyContains(voxel, point.x, point.y, point.z); ++ } ++ ++ // does not use epsilon ++ public static boolean strictlyContains(final net.minecraft.world.phys.shapes.VoxelShape voxel, double x, double y, double z) { ++ final net.minecraft.world.phys.AABB single_aabb = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation(); ++ if (single_aabb != null) { ++ return single_aabb.contains(x, y, z); ++ } ++ ++ if (voxel.isEmpty()) { ++ // bitset is clear, no point in searching ++ return false; ++ } ++ ++ // offset input ++ x -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetX(); ++ y -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetY(); ++ z -= ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$offsetZ(); ++ ++ final double[] coords_x = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesX(); ++ final double[] coords_y = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesY(); ++ final double[] coords_z = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$rootCoordinatesZ(); ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cached_shape_data = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getCachedVoxelData(); ++ ++ // note: size = coords.length - 1 ++ final int size_x = cached_shape_data.sizeX(); ++ final int size_y = cached_shape_data.sizeY(); ++ final int size_z = cached_shape_data.sizeZ(); ++ ++ // note: should mirror AABB#contains, which is that for any point X that X >= min and X < max. ++ // specifically, it cannot collide on the max bounds of the shape ++ ++ final int index_x = findFloor(coords_x, x, 0, size_x); ++ if (index_x < 0 || index_x >= size_x) { ++ return false; ++ } ++ ++ final int index_y = findFloor(coords_y, y, 0, size_y); ++ if (index_y < 0 || index_y >= size_y) { ++ return false; ++ } ++ ++ final int index_z = findFloor(coords_z, z, 0, size_z); ++ if (index_z < 0 || index_z >= size_z) { ++ return false; ++ } ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ final int index = index_z + index_y*size_z + index_x*(size_z*size_y); ++ ++ final long[] bitset = cached_shape_data.voxelSet(); ++ ++ return (bitset[index >>> 6] & (1L << index)) != 0L; ++ } ++ ++ private static int makeBitset(final boolean ft, final boolean tf, final boolean tt) { ++ // idx ff -> 0 ++ // idx ft -> 1 ++ // idx tf -> 2 ++ // idx tt -> 3 ++ return ((ft ? 1 : 0) << 1) | ((tf ? 1 : 0) << 2) | ((tt ? 1 : 0) << 3); ++ } ++ ++ private static net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape merge(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond, ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY, ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ, ++ final int booleanOp) { ++ final int sizeX = mergedX.voxels; ++ final int sizeY = mergedY.voxels; ++ final int sizeZ = mergedZ.voxels; ++ ++ final long[] s1Voxels = shapeDataFirst.voxelSet(); ++ final long[] s2Voxels = shapeDataSecond.voxelSet(); ++ ++ final int s1Mul1 = shapeDataFirst.sizeZ(); ++ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY(); ++ ++ final int s2Mul1 = shapeDataSecond.sizeZ(); ++ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); ++ ++ // note: indices may contain -1, but nothing > size ++ final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape ret = new net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape(sizeX, sizeY, sizeZ); ++ ++ boolean empty = true; ++ ++ int mergedIdx = 0; ++ for (int idxX = 0; idxX < sizeX; ++idxX) { ++ final int s1x = mergedX.firstIndices[idxX]; ++ final int s2x = mergedX.secondIndices[idxX]; ++ boolean setX = false; ++ for (int idxY = 0; idxY < sizeY; ++idxY) { ++ final int s1y = mergedY.firstIndices[idxY]; ++ final int s2y = mergedY.secondIndices[idxY]; ++ boolean setY = false; ++ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) { ++ final int s1z = mergedZ.firstIndices[idxZ]; ++ final int s2z = mergedZ.secondIndices[idxZ]; ++ ++ int idx; ++ ++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); ++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); ++ ++ // idx ff -> 0 ++ // idx ft -> 1 ++ // idx tf -> 2 ++ // idx tt -> 3 ++ ++ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0; ++ setY |= res; ++ setX |= res; ++ ++ if (res) { ++ empty = false; ++ // inline and optimize fill operation ++ ret.zMin = Math.min(ret.zMin, idxZ); ++ ret.zMax = Math.max(ret.zMax, idxZ + 1); ++ ret.storage.set(mergedIdx); ++ } ++ ++ ++mergedIdx; ++ } ++ if (setY) { ++ ret.yMin = Math.min(ret.yMin, idxY); ++ ret.yMax = Math.max(ret.yMax, idxY + 1); ++ } ++ } ++ if (setX) { ++ ret.xMin = Math.min(ret.xMin, idxX); ++ ret.xMax = Math.max(ret.xMax, idxX + 1); ++ } ++ } ++ ++ return empty ? null : ret; ++ } ++ ++ private static boolean isMergeEmpty(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst, final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond, ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX, final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY, ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ, ++ final int booleanOp) { ++ final int sizeX = mergedX.voxels; ++ final int sizeY = mergedY.voxels; ++ final int sizeZ = mergedZ.voxels; ++ ++ final long[] s1Voxels = shapeDataFirst.voxelSet(); ++ final long[] s2Voxels = shapeDataSecond.voxelSet(); ++ ++ final int s1Mul1 = shapeDataFirst.sizeZ(); ++ final int s1Mul2 = s1Mul1 * shapeDataFirst.sizeY(); ++ ++ final int s2Mul1 = shapeDataSecond.sizeZ(); ++ final int s2Mul2 = s2Mul1 * shapeDataSecond.sizeY(); ++ ++ // note: indices may contain -1, but nothing > size ++ for (int idxX = 0; idxX < sizeX; ++idxX) { ++ final int s1x = mergedX.firstIndices[idxX]; ++ final int s2x = mergedX.secondIndices[idxX]; ++ for (int idxY = 0; idxY < sizeY; ++idxY) { ++ final int s1y = mergedY.firstIndices[idxY]; ++ final int s2y = mergedY.secondIndices[idxY]; ++ for (int idxZ = 0; idxZ < sizeZ; ++idxZ) { ++ final int s1z = mergedZ.firstIndices[idxZ]; ++ final int s2z = mergedZ.secondIndices[idxZ]; ++ ++ int idx; ++ ++ final int isS1Full = (s1x | s1y | s1z) < 0 ? 0 : (int)((s1Voxels[(idx = s1z + s1y*s1Mul1 + s1x*s1Mul2) >>> 6] >>> idx) & 1L); ++ final int isS2Full = (s2x | s2y | s2z) < 0 ? 0 : (int)((s2Voxels[(idx = s2z + s2y*s2Mul1 + s2x*s2Mul2) >>> 6] >>> idx) & 1L); ++ ++ // idx ff -> 0 ++ // idx ft -> 1 ++ // idx tf -> 2 ++ // idx tt -> 3 ++ ++ final boolean res = (booleanOp & (1 << (isS2Full | (isS1Full << 1)))) != 0; ++ ++ if (res) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return true; ++ } ++ ++ public static net.minecraft.world.phys.shapes.VoxelShape joinOptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { ++ return joinUnoptimized(first, second, operator).optimize(); ++ } ++ ++ public static net.minecraft.world.phys.shapes.VoxelShape joinUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { ++ final boolean ff = operator.apply(false, false); ++ if (ff) { ++ // technically, should be an infinite box but that's clearly an error ++ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true"); ++ } ++ ++ final boolean tt = operator.apply(true, true); ++ ++ if (first == second) { ++ return tt ? first : net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ ++ final boolean ft = operator.apply(false, true); ++ final boolean tf = operator.apply(true, false); ++ ++ if (first.isEmpty()) { ++ return ft ? second : net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ if (second.isEmpty()) { ++ return tf ? first : net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ ++ if (!tt) { ++ // try to check for no intersection, since tt = false ++ final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); ++ final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); ++ ++ final boolean intersect; ++ ++ final boolean hasAABBF = aabbF != null; ++ final boolean hasAABBS = aabbS != null; ++ if (hasAABBF | hasAABBS) { ++ if (hasAABBF & hasAABBS) { ++ intersect = voxelShapeIntersect(aabbF, aabbS); ++ } else if (hasAABBF) { ++ intersect = voxelShapeIntersectNoEmpty(second, aabbF); ++ } else { ++ intersect = voxelShapeIntersectNoEmpty(first, aabbS); ++ } ++ } else { ++ // expect cached bounds ++ intersect = voxelShapeIntersect(first.bounds(), second.bounds()); ++ } ++ ++ if (!intersect) { ++ if (!tf & !ft) { ++ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ if (!tf | !ft) { ++ return tf ? first : second; ++ } ++ } ++ } ++ ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(), ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(), ++ ft, tf ++ ); ++ if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(), ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(), ++ ft, tf ++ ); ++ if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(), ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(), ++ ft, tf ++ ); ++ if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData(); ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData(); ++ ++ final net.minecraft.world.phys.shapes.BitSetDiscreteVoxelShape mergedShape = merge( ++ shapeDataFirst, shapeDataSecond, ++ mergedX, mergedY, mergedZ, ++ makeBitset(ft, tf, tt) ++ ); ++ ++ if (mergedShape == null) { ++ return net.minecraft.world.phys.shapes.Shapes.empty(); ++ } ++ ++ return new net.minecraft.world.phys.shapes.ArrayVoxelShape( ++ mergedShape, mergedX.wrapCoords(), mergedY.wrapCoords(), mergedZ.wrapCoords() ++ ); ++ } ++ ++ public static boolean isJoinNonEmpty(final net.minecraft.world.phys.shapes.VoxelShape first, final net.minecraft.world.phys.shapes.VoxelShape second, final net.minecraft.world.phys.shapes.BooleanOp operator) { ++ final boolean ff = operator.apply(false, false); ++ if (ff) { ++ // technically, should be an infinite box but that's clearly an error ++ throw new UnsupportedOperationException("Ambiguous operator: (false, false) -> true"); ++ } ++ final boolean firstEmpty = first.isEmpty(); ++ final boolean secondEmpty = second.isEmpty(); ++ if (firstEmpty | secondEmpty) { ++ return operator.apply(!firstEmpty, !secondEmpty); ++ } ++ ++ final boolean tt = operator.apply(true, true); ++ ++ if (first == second) { ++ return tt; ++ } ++ ++ final boolean ft = operator.apply(false, true); ++ final boolean tf = operator.apply(true, false); ++ ++ // try to check intersection ++ final net.minecraft.world.phys.AABB aabbF = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getSingleAABBRepresentation(); ++ final net.minecraft.world.phys.AABB aabbS = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getSingleAABBRepresentation(); ++ ++ final boolean intersect; ++ ++ final boolean hasAABBF = aabbF != null; ++ final boolean hasAABBS = aabbS != null; ++ if (hasAABBF | hasAABBS) { ++ if (hasAABBF & hasAABBS) { ++ intersect = voxelShapeIntersect(aabbF, aabbS); ++ } else if (hasAABBF) { ++ intersect = voxelShapeIntersectNoEmpty(second, aabbF); ++ } else { ++ // hasAABBS -> true ++ intersect = voxelShapeIntersectNoEmpty(first, aabbS); ++ } ++ ++ if (!intersect) { ++ // is only non-empty if we take from first or second, as there is no overlap AND both shapes are non-empty ++ return tf | ft; ++ } else if (tt) { ++ // intersect = true && tt = true -> non-empty merged shape ++ return true; ++ } ++ } else { ++ // expect cached bounds ++ intersect = voxelShapeIntersect(first.bounds(), second.bounds()); ++ if (!intersect) { ++ // is only non-empty if we take from first or second, as there is no intersection ++ return tf | ft; ++ } ++ } ++ ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetX(), ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesX(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetX(), ++ ft, tf ++ ); ++ if (mergedX == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ return false; ++ } ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetY(), ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesY(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetY(), ++ ft, tf ++ ); ++ if (mergedY == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ return false; ++ } ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList mergedZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.merge( ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$offsetZ(), ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$rootCoordinatesZ(), ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$offsetZ(), ++ ft, tf ++ ); ++ if (mergedZ == ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList.EMPTY) { ++ return false; ++ } ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getCachedVoxelData(); ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeDataSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getCachedVoxelData(); ++ ++ return !isMergeEmpty( ++ shapeDataFirst, shapeDataSecond, ++ mergedX, mergedY, mergedZ, ++ makeBitset(ft, tf, tt) ++ ); ++ } ++ ++ private static final class MergedVoxelCoordinateList { ++ ++ private static final int[][] SIMPLE_INDICES_CACHE = new int[64][]; ++ static { ++ for (int i = 0; i < SIMPLE_INDICES_CACHE.length; ++i) { ++ SIMPLE_INDICES_CACHE[i] = getIndices(i); ++ } ++ } ++ ++ private static final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList EMPTY = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList( ++ new double[] { 0.0 }, 0.0, new int[0], new int[0], 0 ++ ); ++ ++ private static int[] getIndices(final int length) { ++ final int[] ret = new int[length]; ++ ++ for (int i = 1; i < length; ++i) { ++ ret[i] = i; ++ } ++ ++ return ret; ++ } ++ ++ // indices above voxel size are always set to -1 ++ public final double[] coordinates; ++ public final double coordinateOffset; ++ public final int[] firstIndices; ++ public final int[] secondIndices; ++ public final int voxels; ++ ++ private MergedVoxelCoordinateList(final double[] coordinates, final double coordinateOffset, ++ final int[] firstIndices, final int[] secondIndices, final int voxels) { ++ this.coordinates = coordinates; ++ this.coordinateOffset = coordinateOffset; ++ this.firstIndices = firstIndices; ++ this.secondIndices = secondIndices; ++ this.voxels = voxels; ++ } ++ ++ public it.unimi.dsi.fastutil.doubles.DoubleList wrapCoords() { ++ if (this.coordinateOffset == 0.0) { ++ return it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1); ++ } ++ return new net.minecraft.world.phys.shapes.OffsetDoubleList(it.unimi.dsi.fastutil.doubles.DoubleArrayList.wrap(this.coordinates, this.voxels + 1), this.coordinateOffset); ++ } ++ ++ // assume coordinates.length > 1 ++ public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList getForSingle(final double[] coordinates, final double offset) { ++ final int voxels = coordinates.length - 1; ++ final int[] indices = voxels < SIMPLE_INDICES_CACHE.length ? SIMPLE_INDICES_CACHE[voxels] : getIndices(voxels); ++ ++ return new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, offset, indices, indices, voxels); ++ } ++ ++ // assume coordinates.length > 1 ++ public static ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList merge(final double[] firstCoordinates, final double firstOffset, ++ final double[] secondCoordinates, final double secondOffset, ++ final boolean ft, final boolean tf) { ++ if (firstCoordinates == secondCoordinates && firstOffset == secondOffset) { ++ return getForSingle(firstCoordinates, firstOffset); ++ } ++ ++ final int firstCount = firstCoordinates.length; ++ final int secondCount = secondCoordinates.length; ++ ++ final int voxelsFirst = firstCount - 1; ++ final int voxelsSecond = secondCount - 1; ++ ++ final int maxCount = firstCount + secondCount; ++ ++ final double[] coordinates = new double[maxCount]; ++ final int[] firstIndices = new int[maxCount]; ++ final int[] secondIndices = new int[maxCount]; ++ ++ final boolean notTF = !tf; ++ final boolean notFT = !ft; ++ ++ int firstIndex = 0; ++ int secondIndex = 0; ++ int resultSize = 0; ++ ++ // note: operations on NaN are false ++ double last = Double.NaN; ++ ++ for (;;) { ++ final boolean noneLeftFirst = firstIndex >= firstCount; ++ final boolean noneLeftSecond = secondIndex >= secondCount; ++ ++ if ((noneLeftFirst & noneLeftSecond) | (noneLeftSecond & notTF) | (noneLeftFirst & notFT)) { ++ break; ++ } ++ ++ final boolean firstZero = firstIndex == 0; ++ final boolean secondZero = secondIndex == 0; ++ ++ final double select; ++ ++ if (noneLeftFirst) { ++ // noneLeftSecond -> false ++ // notFT -> false ++ select = secondCoordinates[secondIndex] + secondOffset; ++ ++secondIndex; ++ } else if (noneLeftSecond) { ++ // noneLeftFirst -> false ++ // notTF -> false ++ select = firstCoordinates[firstIndex] + firstOffset; ++ ++firstIndex; ++ } else { ++ // noneLeftFirst | noneLeftSecond -> false ++ // notTF -> ?? ++ // notFT -> ?? ++ final boolean breakFirst = notTF & secondZero; ++ final boolean breakSecond = notFT & firstZero; ++ ++ final double first = firstCoordinates[firstIndex] + firstOffset; ++ final double second = secondCoordinates[secondIndex] + secondOffset; ++ final boolean useFirst = first < (second + COLLISION_EPSILON); ++ final boolean cont = (useFirst & breakFirst) | (!useFirst & breakSecond); ++ ++ select = useFirst ? first : second; ++ firstIndex += useFirst ? 1 : 0; ++ secondIndex += 1 ^ (useFirst ? 1 : 0); ++ ++ if (cont) { ++ continue; ++ } ++ } ++ ++ int prevFirst = firstIndex - 1; ++ prevFirst = prevFirst >= voxelsFirst ? -1 : prevFirst; ++ int prevSecond = secondIndex - 1; ++ prevSecond = prevSecond >= voxelsSecond ? -1 : prevSecond; ++ ++ if (last >= (select - COLLISION_EPSILON)) { ++ // note: any operations on NaN is false ++ firstIndices[resultSize - 1] = prevFirst; ++ secondIndices[resultSize - 1] = prevSecond; ++ } else { ++ firstIndices[resultSize] = prevFirst; ++ secondIndices[resultSize] = prevSecond; ++ coordinates[resultSize] = select; ++ ++ ++resultSize; ++ last = select; ++ } ++ } ++ ++ return resultSize <= 1 ? EMPTY : new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.MergedVoxelCoordinateList(coordinates, 0.0, firstIndices, secondIndices, resultSize - 1); ++ } ++ } ++ ++ public static boolean equals(final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape1, final net.minecraft.world.phys.shapes.DiscreteVoxelShape shape2) { ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData1 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape1).moonrise$getOrCreateCachedShapeData(); ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData2 = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)shape2).moonrise$getOrCreateCachedShapeData(); ++ ++ final boolean isEmpty1 = cachedShapeData1.isEmpty(); ++ final boolean isEmpty2 = cachedShapeData2.isEmpty(); ++ ++ if (isEmpty1 & isEmpty2) { ++ return true; ++ } else if (isEmpty1 ^ isEmpty2) { ++ return false; ++ } ++ ++ if (cachedShapeData1.hasSingleAABB() != cachedShapeData2.hasSingleAABB()) { ++ return false; ++ } ++ ++ if (cachedShapeData1.sizeX() != cachedShapeData2.sizeX()) { ++ return false; ++ } ++ if (cachedShapeData1.sizeY() != cachedShapeData2.sizeY()) { ++ return false; ++ } ++ if (cachedShapeData1.sizeZ() != cachedShapeData2.sizeZ()) { ++ return false; ++ } ++ ++ return java.util.Arrays.equals(cachedShapeData1.voxelSet(), cachedShapeData2.voxelSet()); ++ } ++ ++ // useful only for testing ++ public static boolean equals(final net.minecraft.world.phys.shapes.VoxelShape shape1, final net.minecraft.world.phys.shapes.VoxelShape shape2) { ++ if (!equals(shape1.shape, shape2.shape)) { ++ return false; ++ } ++ ++ return shape1.getCoords(net.minecraft.core.Direction.Axis.X).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.X)) && ++ shape1.getCoords(net.minecraft.core.Direction.Axis.Y).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Y)) && ++ shape1.getCoords(net.minecraft.core.Direction.Axis.Z).equals(shape2.getCoords(net.minecraft.core.Direction.Axis.Z)); ++ } ++ ++ public static net.minecraft.world.phys.AABB offsetX(final net.minecraft.world.phys.AABB box, final double dx) { ++ return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB offsetY(final net.minecraft.world.phys.AABB box, final double dy) { ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB offsetZ(final net.minecraft.world.phys.AABB box, final double dz) { ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz); ++ } ++ ++ public static net.minecraft.world.phys.AABB expandRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB expandLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB expandUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB expandDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB expandForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz); ++ } ++ ++ public static net.minecraft.world.phys.AABB expandBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB cutRight(final net.minecraft.world.phys.AABB box, final double dx) { // dx > 0.0 ++ return new net.minecraft.world.phys.AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB cutLeft(final net.minecraft.world.phys.AABB box, final double dx) { // dx < 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB cutUpwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy > 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB cutDownwards(final net.minecraft.world.phys.AABB box, final double dy) { // dy < 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ); ++ } ++ ++ public static net.minecraft.world.phys.AABB cutForwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz > 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz); ++ } ++ ++ public static net.minecraft.world.phys.AABB cutBackwards(final net.minecraft.world.phys.AABB box, final double dz) { // dz < 0.0 ++ return new net.minecraft.world.phys.AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ); ++ } ++ ++ public static double performAABBCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); ++ value = collideX(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performAABBCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); ++ value = collideY(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performAABBCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final net.minecraft.world.phys.AABB target = potentialCollisions.get(i); ++ value = collideZ(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performVoxelCollisionsX(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); ++ value = collideX(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performVoxelCollisionsY(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); ++ value = collideY(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static double performVoxelCollisionsZ(final net.minecraft.world.phys.AABB currentBoundingBox, double value, final java.util.List potentialCollisions) { ++ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { ++ if (Math.abs(value) < COLLISION_EPSILON) { ++ return 0.0; ++ } ++ final net.minecraft.world.phys.shapes.VoxelShape target = potentialCollisions.get(i); ++ value = collideZ(target, currentBoundingBox, value); ++ } ++ ++ return value; ++ } ++ ++ public static net.minecraft.world.phys.Vec3 performVoxelCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List potentialCollisions) { ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performVoxelCollisionsY(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 = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performVoxelCollisionsX(axisalignedbb, x, potentialCollisions); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performVoxelCollisionsZ(axisalignedbb, z, potentialCollisions); ++ } ++ ++ return new net.minecraft.world.phys.Vec3(x, y, z); ++ } ++ ++ public static net.minecraft.world.phys.Vec3 performAABBCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, final java.util.List potentialCollisions) { ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performAABBCollisionsY(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 = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performAABBCollisionsX(axisalignedbb, x, potentialCollisions); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, potentialCollisions); ++ } ++ ++ return new net.minecraft.world.phys.Vec3(x, y, z); ++ } ++ ++ public static net.minecraft.world.phys.Vec3 performCollisions(final net.minecraft.world.phys.Vec3 moveVector, net.minecraft.world.phys.AABB axisalignedbb, ++ final java.util.List voxels, ++ final java.util.List aabbs) { ++ if (voxels.isEmpty()) { ++ // fast track only AABBs ++ return performAABBCollisions(moveVector, axisalignedbb, aabbs); ++ } ++ ++ double x = moveVector.x; ++ double y = moveVector.y; ++ double z = moveVector.z; ++ ++ if (y != 0.0) { ++ y = performAABBCollisionsY(axisalignedbb, y, aabbs); ++ y = performVoxelCollisionsY(axisalignedbb, y, voxels); ++ if (y != 0.0) { ++ axisalignedbb = offsetY(axisalignedbb, y); ++ } ++ } ++ ++ final boolean xSmaller = Math.abs(x) < Math.abs(z); ++ ++ if (xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, aabbs); ++ z = performVoxelCollisionsZ(axisalignedbb, z, voxels); ++ if (z != 0.0) { ++ axisalignedbb = offsetZ(axisalignedbb, z); ++ } ++ } ++ ++ if (x != 0.0) { ++ x = performAABBCollisionsX(axisalignedbb, x, aabbs); ++ x = performVoxelCollisionsX(axisalignedbb, x, voxels); ++ if (!xSmaller && x != 0.0) { ++ axisalignedbb = offsetX(axisalignedbb, x); ++ } ++ } ++ ++ if (!xSmaller && z != 0.0) { ++ z = performAABBCollisionsZ(axisalignedbb, z, aabbs); ++ z = performVoxelCollisionsZ(axisalignedbb, z, voxels); ++ } ++ ++ return new net.minecraft.world.phys.Vec3(x, y, z); ++ } ++ ++ public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, final net.minecraft.world.phys.AABB boundingBox) { ++ return isCollidingWithBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); ++ } ++ ++ public static boolean isCollidingWithBorder(final net.minecraft.world.level.border.WorldBorder worldborder, ++ final double boxMinX, final double boxMaxX, ++ final double boxMinZ, final double boxMaxZ) { ++ final double borderMinX = Math.floor(worldborder.getMinX()); // -X ++ final double borderMaxX = Math.ceil(worldborder.getMaxX()); // +X ++ ++ final double borderMinZ = Math.floor(worldborder.getMinZ()); // -Z ++ final double borderMaxZ = Math.ceil(worldborder.getMaxZ()); // +Z ++ ++ // inverted check for world border enclosing the specified box expanded by -EPSILON ++ return (borderMinX - boxMinX) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxX - boxMaxX) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || ++ (borderMinZ - boxMinZ) > ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON || (borderMaxZ - boxMaxZ) < -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON; ++ } ++ ++ /* Math.max/min specify that any NaN argument results in a NaN return, unlike these functions */ ++ private static double min(final double x, final double y) { ++ return x < y ? x : y; ++ } ++ ++ private static double max(final double x, final double y) { ++ return x > y ? x : y; ++ } ++ ++ public static final int COLLISION_FLAG_LOAD_CHUNKS = 1 << 0; ++ public static final int COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS = 1 << 1; ++ public static final int COLLISION_FLAG_CHECK_BORDER = 1 << 2; ++ public static final int COLLISION_FLAG_CHECK_ONLY = 1 << 3; ++ ++ public static boolean getCollisionsForBlocksOrWorldBorder(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb, ++ final java.util.List intoVoxel, final java.util.List intoAABB, ++ final int collisionFlags, final java.util.function.BiPredicate predicate) { ++ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; ++ boolean ret = false; ++ ++ if ((collisionFlags & COLLISION_FLAG_CHECK_BORDER) != 0) { ++ final net.minecraft.world.level.border.WorldBorder worldBorder = world.getWorldBorder(); ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isCollidingWithBorder(worldBorder, aabb) && entity != null && worldBorder.isInsideCloseToBorder(entity, aabb)) { ++ if (checkOnly) { ++ return true; ++ } else { ++ final net.minecraft.world.phys.shapes.VoxelShape borderShape = worldBorder.getCollisionShape(); ++ intoVoxel.add(borderShape); ++ ret = true; ++ } ++ } ++ } ++ ++ final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMinSection(); ++ ++ final int minBlockX = net.minecraft.util.Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; ++ final int maxBlockX = net.minecraft.util.Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; ++ ++ final int minBlockY = Math.max((minSection << 4) - 1, net.minecraft.util.Mth.floor(aabb.minY - COLLISION_EPSILON) - 1); ++ final int maxBlockY = Math.min((((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)world).moonrise$getMaxSection() << 4) + 16, net.minecraft.util.Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1); ++ ++ final int minBlockZ = net.minecraft.util.Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; ++ final int maxBlockZ = net.minecraft.util.Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; ++ ++ final net.minecraft.core.BlockPos.MutableBlockPos mutablePos = new net.minecraft.core.BlockPos.MutableBlockPos(); ++ final net.minecraft.world.phys.shapes.CollisionContext collisionShape = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); ++ ++ // special cases: ++ if (minBlockY > maxBlockY) { ++ // no point in checking ++ return ret; ++ } ++ ++ final int minChunkX = minBlockX >> 4; ++ final int maxChunkX = maxBlockX >> 4; ++ ++ final int minChunkY = minBlockY >> 4; ++ final int maxChunkY = maxBlockY >> 4; ++ ++ final int minChunkZ = minBlockZ >> 4; ++ final int maxChunkZ = maxBlockZ >> 4; ++ ++ final boolean loadChunks = (collisionFlags & COLLISION_FLAG_LOAD_CHUNKS) != 0; ++ final net.minecraft.world.level.chunk.ChunkSource chunkSource = world.getChunkSource(); ++ ++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { ++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { ++ final net.minecraft.world.level.chunk.ChunkAccess chunk = chunkSource.getChunk(currChunkX, currChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, loadChunks); ++ ++ if (chunk == null) { ++ if ((collisionFlags & COLLISION_FLAG_COLLIDE_WITH_UNLOADED_CHUNKS) != 0) { ++ if (checkOnly) { ++ return true; ++ } else { ++ intoAABB.add(getBoxForChunk(currChunkX, currChunkZ)); ++ ret = true; ++ } ++ } ++ continue; ++ } ++ ++ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections(); ++ ++ // bound y ++ for (int currChunkY = minChunkY; currChunkY <= maxChunkY; ++currChunkY) { ++ final int sectionIdx = currChunkY - minSection; ++ if (sectionIdx < 0 || sectionIdx >= sections.length) { ++ continue; ++ } ++ final net.minecraft.world.level.chunk.LevelChunkSection section = sections[sectionIdx]; ++ if (section == null || section.hasOnlyAir()) { ++ // empty ++ continue; ++ } ++ ++ final boolean hasSpecial = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection)section).moonrise$getSpecialCollidingBlocks() != 0; ++ final int sectionAdjust = !hasSpecial ? 1 : 0; ++ ++ final net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; ++ ++ final int minXIterate = currChunkX == minChunkX ? (minBlockX & 15) + sectionAdjust : 0; ++ final int maxXIterate = currChunkX == maxChunkX ? (maxBlockX & 15) - sectionAdjust : 15; ++ final int minZIterate = currChunkZ == minChunkZ ? (minBlockZ & 15) + sectionAdjust : 0; ++ final int maxZIterate = currChunkZ == maxChunkZ ? (maxBlockZ & 15) - sectionAdjust : 15; ++ final int minYIterate = currChunkY == minChunkY ? (minBlockY & 15) + sectionAdjust : 0; ++ final int maxYIterate = currChunkY == maxChunkY ? (maxBlockY & 15) - sectionAdjust : 15; ++ ++ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { ++ final int blockY = currY | (currChunkY << 4); ++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ) { ++ final int blockZ = currZ | (currChunkZ << 4); ++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) { ++ final int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8); ++ final int blockX = currX | (currChunkX << 4); ++ ++ final int edgeCount = hasSpecial ? ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + ++ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + ++ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0) : 0; ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ final net.minecraft.world.level.block.state.BlockState blockData = blocks.get(localBlockIndex); ++ ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$emptyCollisionShape()) { ++ continue; ++ } ++ ++ net.minecraft.world.phys.shapes.VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockData).moonrise$getConstantCollisionShape(); ++ ++ if (edgeCount == 0 || ((edgeCount != 1 || blockData.hasLargeCollisionShape()) && (edgeCount != 2 || blockData.getBlock() == net.minecraft.world.level.block.Blocks.MOVING_PISTON))) { ++ if (blockCollision == null) { ++ mutablePos.set(blockX, blockY, blockZ); ++ blockCollision = blockData.getCollisionShape(world, mutablePos, collisionShape); ++ } ++ ++ net.minecraft.world.phys.AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ singleAABB = singleAABB.move((double)blockX, (double)blockY, (double)blockZ); ++ if (!voxelShapeIntersect(aabb, singleAABB)) { ++ continue; ++ } ++ ++ if (predicate != null) { ++ mutablePos.set(blockX, blockY, blockZ); ++ if (!predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ } ++ ++ if (checkOnly) { ++ return true; ++ } else { ++ ret = true; ++ intoAABB.add(singleAABB); ++ continue; ++ } ++ } ++ ++ if (blockCollision.isEmpty()) { ++ continue; ++ } ++ ++ final net.minecraft.world.phys.shapes.VoxelShape blockCollisionOffset = blockCollision.move((double)blockX, (double)blockY, (double)blockZ); ++ ++ if (!voxelShapeIntersectNoEmpty(blockCollisionOffset, aabb)) { ++ continue; ++ } ++ ++ if (predicate != null) { ++ mutablePos.set(blockX, blockY, blockZ); ++ if (!predicate.test(blockData, mutablePos)) { ++ continue; ++ } ++ } ++ ++ if (checkOnly) { ++ return true; ++ } else { ++ ret = true; ++ intoVoxel.add(blockCollisionOffset); ++ continue; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getEntityHardCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, net.minecraft.world.phys.AABB aabb, ++ final java.util.List into, final int collisionFlags, final java.util.function.Predicate predicate) { ++ final boolean checkOnly = (collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0; ++ ++ boolean ret = false; ++ ++ // to comply with vanilla intersection rules, expand by -epsilon so that 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 java.util.List entities; ++ if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) { ++ entities = world.getEntities(entity, aabb, predicate); ++ } else { ++ entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)world).moonrise$getHardCollidingEntities(entity, aabb, predicate); ++ } ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final net.minecraft.world.entity.Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isSpectator()) { ++ continue; ++ } ++ ++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { ++ if (checkOnly) { ++ return true; ++ } else { ++ into.add(otherEntity.getBoundingBox()); ++ ret = true; ++ } ++ } ++ } ++ ++ return ret; ++ } ++ ++ public static boolean getCollisions(final net.minecraft.world.level.Level world, final net.minecraft.world.entity.Entity entity, final net.minecraft.world.phys.AABB aabb, ++ final java.util.List intoVoxel, final java.util.List intoAABB, final int collisionFlags, ++ final java.util.function.BiPredicate blockPredicate, ++ final java.util.function.Predicate entityPredicate) { ++ if ((collisionFlags & COLLISION_FLAG_CHECK_ONLY) != 0) { ++ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) ++ || getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); ++ } else { ++ return getCollisionsForBlocksOrWorldBorder(world, entity, aabb, intoVoxel, intoAABB, collisionFlags, blockPredicate) ++ | getEntityHardCollisions(world, entity, aabb, intoAABB, collisionFlags, entityPredicate); ++ } ++ } ++ ++ public static final class LazyEntityCollisionContext extends net.minecraft.world.phys.shapes.EntityCollisionContext { ++ ++ private net.minecraft.world.phys.shapes.CollisionContext delegate; ++ private boolean delegated; ++ ++ public LazyEntityCollisionContext(final net.minecraft.world.entity.Entity entity) { ++ super(false, 0.0, null, null, entity); ++ } ++ ++ public boolean isDelegated() { ++ final boolean delegated = this.delegated; ++ this.delegated = false; ++ return delegated; ++ } ++ ++ public net.minecraft.world.phys.shapes.CollisionContext getDelegate() { ++ this.delegated = true; ++ final net.minecraft.world.entity.Entity entity = this.getEntity(); ++ return this.delegate == null ? this.delegate = (entity == null ? net.minecraft.world.phys.shapes.CollisionContext.empty() : net.minecraft.world.phys.shapes.CollisionContext.of(entity)) : this.delegate; ++ } ++ ++ @Override ++ public boolean isDescending() { ++ return this.getDelegate().isDescending(); ++ } ++ ++ @Override ++ public boolean isAbove(final net.minecraft.world.phys.shapes.VoxelShape shape, final net.minecraft.core.BlockPos pos, final boolean defaultValue) { ++ return this.getDelegate().isAbove(shape, pos, defaultValue); ++ } ++ ++ @Override ++ public boolean isHoldingItem(final net.minecraft.world.item.Item item) { ++ return this.getDelegate().isHoldingItem(item); ++ } ++ ++ @Override ++ public boolean canStandOnFluid(final net.minecraft.world.level.material.FluidState state, final net.minecraft.world.level.material.FluidState fluidState) { ++ return this.getDelegate().canStandOnFluid(state, fluidState); ++ } ++ } ++ ++ private CollisionUtil() { ++ throw new RuntimeException(); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/ExplosionBlockCache.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions; ++ ++public final class ExplosionBlockCache { ++ ++ public final long key; ++ public final net.minecraft.core.BlockPos immutablePos; ++ public final net.minecraft.world.level.block.state.BlockState blockState; ++ public final net.minecraft.world.level.material.FluidState fluidState; ++ public final float resistance; ++ public final boolean outOfWorld; ++ public Boolean shouldExplode; // null -> not called yet ++ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape; ++ ++ public ExplosionBlockCache(final long key, final net.minecraft.core.BlockPos immutablePos, final net.minecraft.world.level.block.state.BlockState blockState, ++ final net.minecraft.world.level.material.FluidState fluidState, final float resistance, final boolean outOfWorld) { ++ this.key = key; ++ this.immutablePos = immutablePos; ++ this.blockState = blockState; ++ this.fluidState = fluidState; ++ this.resistance = resistance; ++ this.outOfWorld = outOfWorld; ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/block/CollisionBlockState.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.block; ++ ++public interface CollisionBlockState { ++ ++ // note: this does not consider canOcclude, it is only based on the cached collision shape (i.e hasCache()) ++ // and whether Shapes.faceShapeOccludes(EMPTY, cached shape) is true ++ public boolean moonrise$occludesFullBlock(); ++ ++ // whether the cached collision shape exists and is empty ++ public boolean moonrise$emptyCollisionShape(); ++ ++ // indicates that occludesFullBlock is cached for the collision shape ++ public boolean moonrise$hasCache(); ++ ++ // note: this is HashCommon#murmurHash3(incremental id); and since murmurHash3 has an inverse function the returned ++ // value is still unique ++ public int moonrise$uniqueId1(); ++ ++ // note: this is HashCommon#murmurHash3(incremental id); and since murmurHash3 has an inverse function the returned ++ // value is still unique ++ public int moonrise$uniqueId2(); ++ ++ public net.minecraft.world.phys.shapes.VoxelShape moonrise$getConstantCollisionShape(); ++ ++ public net.minecraft.world.phys.AABB moonrise$getConstantCollisionAABB(); ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedShapeData.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.shape; ++ ++public record CachedShapeData( ++ int sizeX, int sizeY, int sizeZ, ++ long[] voxelSet, ++ int minFullX, int minFullY, int minFullZ, ++ int maxFullX, int maxFullY, int maxFullZ, ++ boolean isEmpty, boolean hasSingleAABB ++) { ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CachedToAABBs.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.shape; ++ ++public record CachedToAABBs( ++ java.util.List aabbs, ++ boolean isOffset, ++ double offX, double offY, double offZ ++) { ++ ++ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs removeOffset() { ++ final java.util.List toOffset = this.aabbs; ++ final double offX = this.offX; ++ final double offY = this.offY; ++ final double offZ = this.offZ; ++ ++ final java.util.List ret = new java.util.ArrayList<>(toOffset.size()); ++ ++ for (int i = 0, len = toOffset.size(); i < len; ++i) { ++ ret.add(toOffset.get(i).move(offX, offY, offZ)); ++ } ++ ++ return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(ret, false, 0.0, 0.0, 0.0); ++ } ++ ++ public static ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs offset(final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cache, final double offX, final double offY, final double offZ) { ++ if (offX == 0.0 && offY == 0.0 && offZ == 0.0) { ++ return cache; ++ } ++ ++ final double resX = cache.offX + offX; ++ final double resY = cache.offY + offY; ++ final double resZ = cache.offZ + offZ; ++ ++ return new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(cache.aabbs, true, resX, resY, resZ); ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionDiscreteVoxelShape.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.shape; ++ ++public interface CollisionDiscreteVoxelShape { ++ ++ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getOrCreateCachedShapeData(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/CollisionVoxelShape.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.shape; ++ ++public interface CollisionVoxelShape { ++ ++ public double moonrise$offsetX(); ++ ++ public double moonrise$offsetY(); ++ ++ public double moonrise$offsetZ(); ++ ++ public double[] moonrise$rootCoordinatesX(); ++ ++ public double[] moonrise$rootCoordinatesY(); ++ ++ public double[] moonrise$rootCoordinatesZ(); ++ ++ public ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getCachedVoxelData(); ++ ++ // rets null if not possible to represent this shape as one AABB ++ public net.minecraft.world.phys.AABB moonrise$getSingleAABBRepresentation(); ++ ++ // ONLY USE INTERNALLY, ONLY FOR INITIALISING IN CONSTRUCTOR: VOXELSHAPES ARE STATIC ++ public void moonrise$initCache(); ++ ++ // this returns empty if not clamped to 1.0 or 0.0 depending on direction ++ public net.minecraft.world.phys.shapes.VoxelShape moonrise$getFaceShapeClamped(final net.minecraft.core.Direction direction); ++ ++ public boolean moonrise$isFullBlock(); ++ ++ public boolean moonrise$occludesFullBlock(); ++ ++ public boolean moonrise$occludesFullBlockIfCached(); ++ ++ // uses a cache internally ++ public net.minecraft.world.phys.shapes.VoxelShape moonrise$orUnoptimized(final net.minecraft.world.phys.shapes.VoxelShape other); ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/shape/MergedORCache.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.shape; ++ ++public record MergedORCache( ++ net.minecraft.world.phys.shapes.VoxelShape key, ++ net.minecraft.world.phys.shapes.VoxelShape result ++) { ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/CollisionDirection.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.util; ++ ++public interface CollisionDirection { ++ ++ // note: this is HashCommon#murmurHash3(some unique id) and since murmurHash3 has an inverse function the returned ++ // value is still unique ++ public int moonrise$uniqueId(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/EmptyStreamForMoveCall.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.util; ++ ++import java.util.Iterator; ++import java.util.Optional; ++import java.util.Spliterator; ++import java.util.stream.Stream; ++ ++public final class EmptyStreamForMoveCall implements java.util.stream.Stream { ++ ++ public static final ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall INSTANCE = new ca.spottedleaf.moonrise.patches.collisions.util.EmptyStreamForMoveCall(); ++ ++ @Override ++ public boolean noneMatch(java.util.function.Predicate predicate) { ++ return false; // important: ret false so the branch is never taken by mojang code ++ } ++ ++ @Override ++ public java.util.stream.Stream filter(java.util.function.Predicate predicate) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream map(java.util.function.Function mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.IntStream mapToInt(java.util.function.ToIntFunction mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.LongStream mapToLong(java.util.function.ToLongFunction mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.DoubleStream mapToDouble(java.util.function.ToDoubleFunction mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream flatMap(java.util.function.Function> mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.IntStream flatMapToInt(java.util.function.Function mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.LongStream flatMapToLong(java.util.function.Function mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.DoubleStream flatMapToDouble(java.util.function.Function mapper) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream distinct() { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream sorted() { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream sorted(java.util.Comparator comparator) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream peek(java.util.function.Consumer action) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream limit(long maxSize) { ++ return null; ++ } ++ ++ @Override ++ public java.util.stream.Stream skip(long n) { ++ return null; ++ } ++ ++ @Override ++ public void forEach(java.util.function.Consumer action) { ++ ++ } ++ ++ @Override ++ public void forEachOrdered(java.util.function.Consumer action) { ++ ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Object[] toArray() { ++ return new Object[0]; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public A[] toArray(java.util.function.IntFunction generator) { ++ return null; ++ } ++ ++ @Override ++ public T reduce(T identity, java.util.function.BinaryOperator accumulator) { ++ return null; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Optional reduce(java.util.function.BinaryOperator accumulator) { ++ return java.util.Optional.empty(); ++ } ++ ++ @Override ++ public U reduce(U identity, java.util.function.BiFunction accumulator, java.util.function.BinaryOperator combiner) { ++ return null; ++ } ++ ++ @Override ++ public R collect(java.util.function.Supplier supplier, java.util.function.BiConsumer accumulator, java.util.function.BiConsumer combiner) { ++ return null; ++ } ++ ++ @Override ++ public R collect(java.util.stream.Collector collector) { ++ return null; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Optional min(java.util.Comparator comparator) { ++ return java.util.Optional.empty(); ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Optional max(java.util.Comparator comparator) { ++ return java.util.Optional.empty(); ++ } ++ ++ @Override ++ public long count() { ++ return 0; ++ } ++ ++ @Override ++ public boolean anyMatch(java.util.function.Predicate predicate) { ++ return false; ++ } ++ ++ @Override ++ public boolean allMatch(java.util.function.Predicate predicate) { ++ return false; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Optional findFirst() { ++ return java.util.Optional.empty(); ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Optional findAny() { ++ return java.util.Optional.empty(); ++ } ++ ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Iterator iterator() { ++ return null; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Spliterator spliterator() { ++ return null; ++ } ++ ++ @Override ++ public boolean isParallel() { ++ return false; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Stream sequential() { ++ return null; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Stream parallel() { ++ return null; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Stream unordered() { ++ return null; ++ } ++ ++ @org.jetbrains.annotations.NotNull ++ @Override ++ public Stream onClose(Runnable closeHandler) { ++ return null; ++ } ++ ++ @Override ++ public void close() { ++ ++ } ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/util/FluidOcclusionCacheKey.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.util; ++ ++public record FluidOcclusionCacheKey(net.minecraft.world.level.block.state.BlockState first, net.minecraft.world.level.block.state.BlockState second, net.minecraft.core.Direction direction, boolean result) { ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevel.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.world; ++ ++public interface CollisionLevel { ++ ++ public int moonrise$getMinSection(); ++ ++ public int moonrise$getMaxSection(); ++ ++} +diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/ca/spottedleaf/moonrise/patches/collisions/world/CollisionLevelChunkSection.java +@@ -0,0 +0,0 @@ ++package ca.spottedleaf.moonrise.patches.collisions.world; ++ ++public interface CollisionLevelChunkSection { ++ ++ public int moonrise$getSpecialCollidingBlocks(); ++ ++} diff --git a/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java b/src/main/java/ca/spottedleaf/moonrise/patches/entity_tracker/EntityTrackerEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -23425,6 +25771,115 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public static boolean isTickThreadFor(final ServerLevel world, final int chunkX, final int chunkZ, final int radius) { return isTickThread(); } +diff --git a/src/main/java/net/minecraft/core/Direction.java b/src/main/java/net/minecraft/core/Direction.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/core/Direction.java ++++ b/src/main/java/net/minecraft/core/Direction.java +@@ -0,0 +0,0 @@ import org.joml.Quaternionf; + import org.joml.Vector3f; + import org.joml.Vector4f; + +-public enum Direction implements StringRepresentable { ++public enum Direction implements StringRepresentable, ca.spottedleaf.moonrise.patches.collisions.util.CollisionDirection { // Paper - optimise collisions + DOWN(0, 1, -1, "down", Direction.AxisDirection.NEGATIVE, Direction.Axis.Y, new Vec3i(0, -1, 0)), + UP(1, 0, -1, "up", Direction.AxisDirection.POSITIVE, Direction.Axis.Y, new Vec3i(0, 1, 0)), + NORTH(2, 3, 2, "north", Direction.AxisDirection.NEGATIVE, Direction.Axis.Z, new Vec3i(0, 0, -1)), +@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable { + private final int adjY; + private final int adjZ; + // Paper end - Perf: Inline shift direction fields ++ // Paper start - optimise collisions ++ private static final int RANDOM_OFFSET = 2017601568; ++ private Direction opposite; ++ private Quaternionf rotation; ++ private int id; ++ private int stepX; ++ private int stepY; ++ private int stepZ; ++ ++ private Quaternionf getRotationUncached() { ++ switch ((Direction)(Object)this) { ++ case DOWN: { ++ return new Quaternionf().rotationX(3.1415927F); ++ } ++ case UP: { ++ return new Quaternionf(); ++ } ++ case NORTH: { ++ return new Quaternionf().rotationXYZ(1.5707964F, 0.0F, 3.1415927F); ++ } ++ case SOUTH: { ++ return new Quaternionf().rotationX(1.5707964F); ++ } ++ case WEST: { ++ return new Quaternionf().rotationXYZ(1.5707964F, 0.0F, 1.5707964F); ++ } ++ case EAST: { ++ return new Quaternionf().rotationXYZ(1.5707964F, 0.0F, -1.5707964F); ++ } ++ default: { ++ throw new IllegalStateException(); ++ } ++ } ++ } ++ ++ @Override ++ public final int moonrise$uniqueId() { ++ return this.id; ++ } ++ // Paper end - optimise collisions + + private Direction( + final int id, +@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable { + } + + public Quaternionf getRotation() { +- return switch (this) { +- case DOWN -> new Quaternionf().rotationX((float) Math.PI); +- case UP -> new Quaternionf(); +- case NORTH -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) Math.PI); +- case SOUTH -> new Quaternionf().rotationX((float) (Math.PI / 2)); +- case WEST -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) (Math.PI / 2)); +- case EAST -> new Quaternionf().rotationXYZ((float) (Math.PI / 2), 0.0F, (float) (-Math.PI / 2)); +- }; ++ // Paper start - optimise collisions ++ try { ++ return (Quaternionf)this.rotation.clone(); ++ } catch (final CloneNotSupportedException ex) { ++ throw new InternalError(ex); ++ } ++ // Paper end - optimise collisions + } + + public int get3DDataValue() { +@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable { + } + + public Direction getOpposite() { +- return from3DDataValue(this.oppositeIndex); ++ return this.opposite; // Paper - optimise collisions + } + + public Direction getClockWise(Direction.Axis axis) { +@@ -0,0 +0,0 @@ public enum Direction implements StringRepresentable { + return this.faces.length; + } + } ++ ++ // Paper start - optimise collisions ++ static { ++ for (final Direction direction : VALUES) { ++ ((Direction)(Object)direction).opposite = from3DDataValue(((Direction)(Object)direction).oppositeIndex); ++ ((Direction)(Object)direction).rotation = ((Direction)(Object)direction).getRotationUncached(); ++ ((Direction)(Object)direction).id = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(direction.ordinal() + RANDOM_OFFSET) + RANDOM_OFFSET); ++ ((Direction)(Object)direction).stepX = ((Direction)(Object)direction).normal.getX(); ++ ((Direction)(Object)direction).stepY = ((Direction)(Object)direction).normal.getY(); ++ ((Direction)(Object)direction).stepZ = ((Direction)(Object)direction).normal.getZ(); ++ } ++ } ++ // Paper end - optimise collisions + } diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/Main.java @@ -26475,6 +28930,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // CraftBukkit end } } +diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/level/ServerEntity.java ++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java +@@ -0,0 +0,0 @@ public class ServerEntity { + } + + public void sendChanges() { ++ // Paper start - optimise collisions ++ if (((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)this.entity).moonrise$isHardColliding()) { ++ this.teleportDelay = 9999; ++ } ++ // Paper end - optimise collisions + List list = this.entity.getPassengers(); + + if (!list.equals(this.lastPassengers)) { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -27678,6 +30149,199 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess } + private Vec3 collide(Vec3 movement) { +- AABB axisalignedbb = this.getBoundingBox(); +- List 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 = flag1 && movement.y < 0.0D; +- +- if (this.maxUpStep() > 0.0F && (flag3 || this.onGround()) && (flag || flag2)) { +- AABB axisalignedbb1 = flag3 ? axisalignedbb.move(0.0D, vec3d1.y, 0.0D) : axisalignedbb; +- AABB axisalignedbb2 = axisalignedbb1.expandTowards(movement.x, (double) this.maxUpStep(), movement.z); +- +- if (!flag3) { +- axisalignedbb2 = axisalignedbb2.expandTowards(0.0D, -9.999999747378752E-6D, 0.0D); +- } ++ // Paper start - optimise collisions ++ final boolean xZero = movement.x == 0.0; ++ final boolean yZero = movement.y == 0.0; ++ final boolean zZero = movement.z == 0.0; ++ if (xZero & yZero & zZero) { ++ return movement; ++ } + +- List list1 = Entity.collectColliders(this, this.level, list, axisalignedbb2); +- float f = (float) vec3d1.y; +- float[] afloat = Entity.collectCandidateStepUpHeights(axisalignedbb1, list1, this.maxUpStep(), f); +- float[] afloat1 = afloat; +- int i = afloat.length; ++ final Level world = this.level; ++ final AABB currBoundingBox = this.getBoundingBox(); + +- for (int j = 0; j < i; ++j) { +- float f1 = afloat1[j]; +- Vec3 vec3d2 = Entity.collideWithShapes(new Vec3(movement.x, (double) f1, movement.z), axisalignedbb1, list1); ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(currBoundingBox)) { ++ return movement; ++ } + +- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { +- double d0 = axisalignedbb.minY - axisalignedbb1.minY; ++ final List potentialCollisionsBB = new ArrayList<>(); ++ final List potentialCollisionsVoxel = new ArrayList<>(); ++ final double stepHeight = (double)this.maxUpStep(); ++ final AABB collisionBox; ++ final boolean onGround = this.onGround; + +- return vec3d2.add(0.0D, -d0, 0.0D); ++ if (xZero & zZero) { ++ if (movement.y > 0.0) { ++ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutUpwards(currBoundingBox, movement.y); ++ } else { ++ collisionBox = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.cutDownwards(currBoundingBox, movement.y); ++ } ++ } else { ++ // note: xZero == false or zZero == false ++ if (stepHeight > 0.0 && (onGround || (movement.y < 0.0))) { ++ // don't bother getting the collisions if we don't need them. ++ if (movement.y <= 0.0) { ++ collisionBox = ca.spottedleaf.moonrise.patches.collisions.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); + } + } + +- return vec3d1; ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisions( ++ world, (Entity)(Object)this, collisionBox, potentialCollisionsVoxel, potentialCollisionsBB, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, ++ null, null ++ ); ++ ++ if (potentialCollisionsVoxel.isEmpty() && potentialCollisionsBB.isEmpty()) { ++ return movement; ++ } ++ ++ final Vec3 limitedMoveVector = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(movement, currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ ++ if (stepHeight > 0.0 ++ && (onGround || (limitedMoveVector.y != movement.y && movement.y < 0.0)) ++ && (limitedMoveVector.x != movement.x || limitedMoveVector.z != movement.z)) { ++ Vec3 vec3d2 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, stepHeight, movement.z), currBoundingBox, potentialCollisionsVoxel, potentialCollisionsBB); ++ final Vec3 vec3d3 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(movement.x, 0.0, movement.z), potentialCollisionsVoxel, potentialCollisionsBB); ++ ++ if (vec3d3.y < stepHeight) { ++ final Vec3 vec3d4 = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(movement.x, 0.0D, movement.z), currBoundingBox.move(vec3d3), potentialCollisionsVoxel, potentialCollisionsBB).add(vec3d3); ++ ++ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { ++ vec3d2 = vec3d4; ++ } ++ } ++ ++ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { ++ return vec3d2.add(ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisionsVoxel, potentialCollisionsBB)); ++ } ++ ++ return limitedMoveVector; ++ } else { ++ return limitedMoveVector; ++ } ++ // Paper end - optimise collisions + } + + private static float[] collectCandidateStepUpHeights(AABB collisionBox, List collisions, float f, float stepHeight) { +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + + public boolean isInWall() { ++ // Paper start - optimise collisions + if (this.noPhysics) { + return false; +- } else { +- 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); ++ final float reducedWith = this.dimensions.width() * 0.8F; ++ final AABB box = AABB.ofSize(this.getEyePosition(), reducedWith, 1.0E-6D, reducedWith); ++ ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) { ++ return false; ++ } + +- 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); +- }); ++ final BlockPos.MutableBlockPos tempPos = new BlockPos.MutableBlockPos(); ++ ++ final int minX = Mth.floor(box.minX); ++ final int minY = Mth.floor(box.minY); ++ final int minZ = Mth.floor(box.minZ); ++ final int maxX = Mth.floor(box.maxX); ++ final int maxY = Mth.floor(box.maxY); ++ final int maxZ = Mth.floor(box.maxZ); ++ ++ final net.minecraft.world.level.chunk.ChunkSource chunkProvider = this.level.getChunkSource(); ++ ++ long lastChunkKey = ChunkPos.INVALID_CHUNK_POS; ++ net.minecraft.world.level.chunk.LevelChunk lastChunk = null; ++ for (int fz = minZ; fz <= maxZ; ++fz) { ++ tempPos.setZ(fz); ++ for (int fx = minX; fx <= maxX; ++fx) { ++ final int newChunkX = fx >> 4; ++ final int newChunkZ = fz >> 4; ++ final net.minecraft.world.level.chunk.LevelChunk chunk = lastChunkKey == (lastChunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(newChunkX, newChunkZ)) ? ++ lastChunk : (lastChunk = (net.minecraft.world.level.chunk.LevelChunk)chunkProvider.getChunk(newChunkX, newChunkZ, net.minecraft.world.level.chunk.status.ChunkStatus.FULL, true)); ++ tempPos.setX(fx); ++ for (int fy = minY; fy <= maxY; ++fy) { ++ tempPos.setY(fy); ++ ++ final BlockState state = chunk.getBlockState(tempPos); ++ ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape() || !state.isSuffocating(this.level, tempPos)) { ++ continue; ++ } ++ ++ // Yes, it does not use the Entity context stuff. ++ final VoxelShape collisionShape = state.getCollisionShape(this.level, tempPos); ++ ++ if (collisionShape.isEmpty()) { ++ continue; ++ } ++ ++ final AABB toCollide = box.move(-(double)fx, -(double)fy, -(double)fz); ++ ++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, toCollide)) { ++ return true; ++ } ++ continue; ++ } ++ ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(collisionShape, toCollide)) { ++ return true; ++ } ++ continue; ++ } ++ } + } ++ ++ return false; ++ // Paper end - optimise collisions + } + + public InteractionResult interact(Player player, InteractionHand hand) { +@@ -0,0 +0,0 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess + } + public Iterable getIndirectPassengers() { - // Paper start - Optimize indirect passenger iteration - if (this.passengers.isEmpty()) { return ImmutableList.of(); } @@ -28001,6 +30665,32 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public PoiSection(Runnable updateListener) { this(updateListener, true, ImmutableList.of()); } +diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java ++++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java +@@ -0,0 +0,0 @@ public class ArmorStand extends LivingEntity { + @Override + protected void pushEntities() { + if (!this.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return; // Paper - Option to prevent armor stands from doing entity lookups +- List list = this.level().getEntities((Entity) this, this.getBoundingBox(), ArmorStand.RIDABLE_MINECARTS); ++ List list = this.level().getEntitiesOfClass(AbstractMinecart.class, this.getBoundingBox(), RIDABLE_MINECARTS); // Paper - optimise collisions + Iterator iterator = list.iterator(); + + while (iterator.hasNext()) { +diff --git a/src/main/java/net/minecraft/world/level/ClipContext.java b/src/main/java/net/minecraft/world/level/ClipContext.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/ClipContext.java ++++ b/src/main/java/net/minecraft/world/level/ClipContext.java +@@ -0,0 +0,0 @@ public class ClipContext { + private final Vec3 from; + private final Vec3 to; + private final ClipContext.Block block; +- private final ClipContext.Fluid fluid; ++ public final ClipContext.Fluid fluid; // Paper - optimise collisions - public + private final CollisionContext collisionContext; + + public ClipContext(Vec3 start, Vec3 end, ClipContext.Block shapeType, ClipContext.Fluid fluidHandling, Entity entity) { diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java @@ -28018,6 +30708,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return this.getEntities(except, box, EntitySelector.NO_SPECTATORS); } +- default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) { +- if (shape.isEmpty()) { +- 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)) { +- return false; + // Paper start - rewrite chunk system + @Override + default List moonrise$getHardCollidingEntities(final Entity entity, final AABB box, final Predicate predicate) { @@ -28025,19 +30725,607 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end - rewrite chunk system + - default boolean isUnobstructed(@Nullable Entity except, VoxelShape shape) { - if (shape.isEmpty()) { - return true; ++ // Paper start - optimise collisions ++ default boolean isUnobstructed(@Nullable Entity entity, VoxelShape voxel) { ++ if (voxel.isEmpty()) { ++ return false; ++ } ++ ++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)voxel).moonrise$getSingleAABBRepresentation(); ++ final List entities = this.getEntities( ++ entity, ++ singleAABB == null ? voxel.bounds() : singleAABB.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) ++ ); ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isRemoved() || !otherEntity.blocksBuilding || (entity != null && otherEntity.isPassengerOfSameVehicle(entity))) { ++ continue; ++ } ++ ++ if (singleAABB == null) { ++ final AABB entityBB = otherEntity.getBoundingBox(); ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(entityBB) || !ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(voxel, entityBB)) { ++ continue; + } + } + +- return true; ++ return false; + } ++ ++ return true; ++ // Paper end - optimise collisions + } + + default List getEntitiesOfClass(Class entityClass, AABB box) { +@@ -0,0 +0,0 @@ public interface EntityGetter { + } + + default List getEntityCollisions(@Nullable Entity entity, AABB box) { +- if (box.getSize() < 1.0E-7) { +- return List.of(); ++ // Paper start - optimise collisions ++ // first behavior change is to correctly check for empty AABB ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(box)) { ++ // reduce indirection by always returning type with same class ++ return new java.util.ArrayList<>(); ++ } ++ ++ // to comply with vanilla intersection rules, expand by -epsilon so that 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. ++ box = box.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON); ++ ++ final List entities; ++ if (entity != null && ((ca.spottedleaf.moonrise.patches.chunk_system.entity.ChunkSystemEntity)entity).moonrise$isHardColliding()) { ++ entities = this.getEntities(entity, box, null); + } else { +- Predicate predicate = entity == null ? EntitySelector.CAN_BE_COLLIDED_WITH : EntitySelector.NO_SPECTATORS.and(entity::canCollideWith); +- List list = this.getEntities(entity, box.inflate(1.0E-7), predicate); +- if (list.isEmpty()) { +- return List.of(); +- } else { +- Builder builder = ImmutableList.builderWithExpectedSize(list.size()); +- +- for (Entity entity2 : list) { +- builder.add(Shapes.create(entity2.getBoundingBox())); +- } ++ entities = ((ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter)this).moonrise$getHardCollidingEntities(entity, box, null); ++ } + +- return builder.build(); ++ final List ret = new java.util.ArrayList<>(Math.min(25, entities.size())); ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isSpectator()) { ++ continue; ++ } ++ ++ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { ++ ret.add(Shapes.create(otherEntity.getBoundingBox())); + } + } ++ ++ return ret; ++ // Paper end - optimise collisions + } + + // Paper start - Affects Spawning API +diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/Explosion.java ++++ b/src/main/java/net/minecraft/world/level/Explosion.java +@@ -0,0 +0,0 @@ public class Explosion { + public float yield; + // CraftBukkit end + ++ // Paper start - optimise collisions ++ private static final double[] CACHED_RAYS; ++ static { ++ final it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords = new it.unimi.dsi.fastutil.doubles.DoubleArrayList(); ++ ++ for (int x = 0; x <= 15; ++x) { ++ for (int y = 0; y <= 15; ++y) { ++ for (int z = 0; z <= 15; ++z) { ++ if ((x == 0 || x == 15) || (y == 0 || y == 15) || (z == 0 || z == 15)) { ++ double xDir = (double)((float)x / 15.0F * 2.0F - 1.0F); ++ double yDir = (double)((float)y / 15.0F * 2.0F - 1.0F); ++ double zDir = (double)((float)z / 15.0F * 2.0F - 1.0F); ++ ++ double mag = Math.sqrt( ++ xDir * xDir + yDir * yDir + zDir * zDir ++ ); ++ ++ rayCoords.add((xDir / mag) * (double)0.3F); ++ rayCoords.add((yDir / mag) * (double)0.3F); ++ rayCoords.add((zDir / mag) * (double)0.3F); ++ } ++ } ++ } ++ } ++ ++ CACHED_RAYS = rayCoords.toDoubleArray(); ++ } ++ ++ private static final int CHUNK_CACHE_SHIFT = 2; ++ private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1; ++ private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT; ++ ++ private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3; ++ private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1; ++ private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT; ++ ++ // resistance = (res + 0.3F) * 0.3F; ++ // so for resistance = 0, we need res = -0.3F ++ private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f); ++ private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap blockCache = null; ++ private long[] chunkPosCache = null; ++ private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null; ++ private ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z, ++ final long key, final boolean calculateResistance) { ++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache ret = this.blockCache.get(key); ++ if (ret != null) { ++ return ret; ++ } ++ ++ BlockPos pos = new BlockPos(x, y, z); ++ ++ if (!this.level.isInWorldBounds(pos)) { ++ ret = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache(key, pos, null, null, 0.0f, true); ++ } else { ++ net.minecraft.world.level.chunk.LevelChunk chunk; ++ long chunkKey = ca.spottedleaf.moonrise.common.util.CoordinateUtils.getChunkKey(x >> 4, z >> 4); ++ int chunkCacheKey = ((x >> 4) & CHUNK_CACHE_MASK) | (((z >> 4) << CHUNK_CACHE_SHIFT) & (CHUNK_CACHE_MASK << CHUNK_CACHE_SHIFT)); ++ if (this.chunkPosCache[chunkCacheKey] == chunkKey) { ++ chunk = this.chunkCache[chunkCacheKey]; ++ } else { ++ this.chunkPosCache[chunkCacheKey] = chunkKey; ++ this.chunkCache[chunkCacheKey] = chunk = this.level.getChunk(x >> 4, z >> 4); ++ } ++ ++ BlockState blockState = ((ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk)chunk).moonrise$getBlock(x, y, z); ++ FluidState fluidState = blockState.getFluidState(); ++ ++ Optional resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState); ++ ++ ret = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache( ++ key, pos, blockState, fluidState, ++ (resistance.orElse(ZERO_RESISTANCE).floatValue() + 0.3f) * 0.3f, ++ false ++ ); ++ } ++ ++ this.blockCache.put(key, ret); ++ ++ return ret; ++ } ++ ++ private boolean clipsAnything(final Vec3 from, final Vec3 to, ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext context, ++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache, ++ final BlockPos.MutableBlockPos currPos) { ++ // assume that context.delegated = false ++ final double adjX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.x - to.x); ++ final double adjY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.y - to.y); ++ final double adjZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.z - to.z); ++ ++ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) { ++ return false; ++ } ++ ++ final double toXAdj = to.x - adjX; ++ final double toYAdj = to.y - adjY; ++ final double toZAdj = to.z - adjZ; ++ final double fromXAdj = from.x + adjX; ++ final double fromYAdj = from.y + adjY; ++ final double fromZAdj = from.z + adjZ; ++ ++ int currX = Mth.floor(fromXAdj); ++ int currY = Mth.floor(fromYAdj); ++ int currZ = Mth.floor(fromZAdj); ++ ++ final double diffX = toXAdj - fromXAdj; ++ final double diffY = toYAdj - fromYAdj; ++ final double diffZ = toZAdj - fromZAdj; ++ ++ final double dxDouble = Math.signum(diffX); ++ final double dyDouble = Math.signum(diffY); ++ final double dzDouble = Math.signum(diffZ); ++ ++ final int dx = (int)dxDouble; ++ final int dy = (int)dyDouble; ++ final int dz = (int)dzDouble; ++ ++ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX; ++ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY; ++ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ; ++ ++ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj)); ++ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj)); ++ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj)); ++ ++ for (;;) { ++ currPos.set(currX, currY, currZ); ++ ++ // ClipContext.Block.COLLIDER -> BlockBehaviour.BlockStateBase::getCollisionShape ++ // ClipContext.Fluid.NONE -> ignore fluids ++ ++ // read block from cache ++ final long key = BlockPos.asLong(currX, currY, currZ); ++ ++ final int cacheKey = ++ (currX & BLOCK_EXPLOSION_CACHE_MASK) | ++ (currY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | ++ (currZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); ++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache cachedBlock = blockCache[cacheKey]; ++ if (cachedBlock == null || cachedBlock.key != key) { ++ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(currX, currY, currZ, key, false); ++ } ++ ++ final BlockState blockState = cachedBlock.blockState; ++ if (blockState != null && !((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockState).moonrise$emptyCollisionShape()) { ++ net.minecraft.world.phys.shapes.VoxelShape collision = cachedBlock.cachedCollisionShape; ++ if (collision == null) { ++ collision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)blockState).moonrise$getConstantCollisionShape(); ++ if (collision == null) { ++ collision = blockState.getCollisionShape(this.level, currPos, context); ++ if (!context.isDelegated()) { ++ // if it was not delegated during this call, assume that for any future ones it will not be delegated ++ // again, and cache the result ++ cachedBlock.cachedCollisionShape = collision; ++ } ++ } else { ++ cachedBlock.cachedCollisionShape = collision; ++ } ++ } ++ ++ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) { ++ return true; ++ } ++ } ++ ++ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) { ++ return false; ++ } ++ ++ // inc the smallest normalized coordinate ++ ++ if (normalizedCurrX < normalizedCurrY) { ++ if (normalizedCurrX < normalizedCurrZ) { ++ currX += dx; ++ normalizedCurrX += normalizedDiffX; ++ } else { ++ // x < y && x >= z <--> z < y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } else if (normalizedCurrY < normalizedCurrZ) { ++ // y <= x && y < z ++ currY += dy; ++ normalizedCurrY += normalizedDiffY; ++ } else { ++ // y <= x && z <= y <--> z <= y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } ++ } ++ ++ private float getSeenFraction(final Vec3 source, final Entity target, ++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache, ++ final BlockPos.MutableBlockPos blockPos) { ++ final AABB boundingBox = target.getBoundingBox(); ++ final double diffX = boundingBox.maxX - boundingBox.minX; ++ final double diffY = boundingBox.maxY - boundingBox.minY; ++ final double diffZ = boundingBox.maxZ - boundingBox.minZ; ++ ++ final double incX = 1.0 / (diffX * 2.0 + 1.0); ++ final double incY = 1.0 / (diffY * 2.0 + 1.0); ++ final double incZ = 1.0 / (diffZ * 2.0 + 1.0); ++ ++ if (incX < 0.0 || incY < 0.0 || incZ < 0.0) { ++ return 0.0f; ++ } ++ ++ final double offX = (1.0 - Math.floor(1.0 / incX) * incX) * 0.5 + boundingBox.minX; ++ final double offY = boundingBox.minY; ++ final double offZ = (1.0 - Math.floor(1.0 / incZ) * incZ) * 0.5 + boundingBox.minZ; ++ ++ final ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext context = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(target); ++ ++ int totalRays = 0; ++ int missedRays = 0; ++ ++ for (double dx = 0.0; dx <= 1.0; dx += incX) { ++ final double fromX = Math.fma(dx, diffX, offX); ++ for (double dy = 0.0; dy <= 1.0; dy += incY) { ++ final double fromY = Math.fma(dy, diffY, offY); ++ for (double dz = 0.0; dz <= 1.0; dz += incZ) { ++ ++totalRays; ++ ++ final Vec3 from = new Vec3( ++ fromX, ++ fromY, ++ Math.fma(dz, diffZ, offZ) ++ ); ++ ++ if (!this.clipsAnything(from, source, context, blockCache, blockPos)) { ++ ++missedRays; ++ } ++ } ++ } ++ } ++ ++ return (float)missedRays / (float)totalRays; ++ } ++ // Paper end - optimise collisions ++ + public static DamageSource getDefaultDamageSource(Level world, @Nullable Entity source) { + return world.damageSources().explosion(source, Explosion.getIndirectSourceEntityInternal(source)); + } +@@ -0,0 +0,0 @@ public class Explosion { + } + // CraftBukkit end + this.level.gameEvent(this.source, (Holder) GameEvent.EXPLODE, new Vec3(this.x, this.y, this.z)); +- Set set = Sets.newHashSet(); ++ ++ // Paper start - collision optimisations ++ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); ++ ++ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS); ++ ++ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH]; ++ ++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache = new ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH]; ++ ++ // use initial cache value that is most likely to be used: the source position ++ final ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache initialCache; ++ { ++ final int blockX = Mth.floor(this.x); ++ final int blockY = Mth.floor(this.y); ++ final int blockZ = Mth.floor(this.z); ++ ++ final long key = BlockPos.asLong(blockX, blockY, blockZ); ++ ++ initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); ++ } ++ // Paper end - collision optimisations ++ + boolean flag = true; + + int i; + int j; + +- for (int k = 0; k < 16; ++k) { +- for (i = 0; i < 16; ++i) { +- for (j = 0; j < 16; ++j) { +- if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) { +- double d0 = (double) ((float) k / 15.0F * 2.0F - 1.0F); +- double d1 = (double) ((float) i / 15.0F * 2.0F - 1.0F); +- double d2 = (double) ((float) j / 15.0F * 2.0F - 1.0F); +- double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2); +- +- d0 /= d3; +- d1 /= d3; +- d2 /= d3; +- float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); +- double d4 = this.x; +- double d5 = this.y; +- double d6 = this.z; +- +- for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) { +- BlockPos blockposition = BlockPos.containing(d4, d5, d6); +- BlockState iblockdata = this.level.getBlockState(blockposition); +- if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed +- FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions +- +- if (!this.level.isInWorldBounds(blockposition)) { +- break; ++ // Paper start - collision optimisations ++ for (int ray = 0, len = CACHED_RAYS.length; ray < len;) { ++ ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache cachedBlock = initialCache; ++ ++ double currX = this.x; ++ double currY = this.y; ++ double currZ = this.z; ++ ++ final double incX = CACHED_RAYS[ray]; ++ final double incY = CACHED_RAYS[ray + 1]; ++ final double incZ = CACHED_RAYS[ray + 2]; ++ ++ ray += 3; ++ float power = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F); ++ do { ++ final int blockX = Mth.floor(currX); ++ final int blockY = Mth.floor(currY); ++ final int blockZ = Mth.floor(currZ); ++ ++ final long key = BlockPos.asLong(blockX, blockY, blockZ); ++ ++ if (cachedBlock.key != key) { ++ final int cacheKey = ++ (blockX & BLOCK_EXPLOSION_CACHE_MASK) | ++ (blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) | ++ (blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT); ++ cachedBlock = blockCache[cacheKey]; ++ if (cachedBlock == null || cachedBlock.key != key) { ++ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true); ++ } + } + +- Optional optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid); +- +- if (optional.isPresent()) { +- f -= ((Float) optional.get() + 0.3F) * 0.3F; ++ if (cachedBlock.outOfWorld) { ++ break; + } +- +- if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) { +- set.add(blockposition); +- // Paper start - prevent headless pistons from forming +- if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { +- net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(blockposition); +- if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { +- net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); +- set.add(blockposition.relative(direction.getOpposite())); ++ // Paper end - collision optimisations ++ BlockState iblockdata = cachedBlock.blockState; // Paper - optimise collisions ++ if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed ++ // Paper - collision optimisations ++ ++ // Paper start - collision optimisations ++ power -= cachedBlock.resistance; ++ ++ if (power > 0.0f && cachedBlock.shouldExplode == null) { ++ // note: we expect shouldBlockExplode to be pure with respect to power, as Vanilla currently is. ++ // basically, it is unused, which allows us to cache the result ++ final boolean shouldExplode = this.damageCalculator.shouldBlockExplode((Explosion)(Object)this, this.level, cachedBlock.immutablePos, cachedBlock.blockState, power); ++ cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE; ++ if (shouldExplode) { ++ if (this.fire || !cachedBlock.blockState.isAir()) { ++ this.toBlow.add(cachedBlock.immutablePos); ++ // Paper start - prevent headless pistons from forming ++ if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) { ++ net.minecraft.world.level.block.entity.BlockEntity extension = this.level.getBlockEntity(cachedBlock.immutablePos); // Paper - optimise collisions ++ if (extension instanceof net.minecraft.world.level.block.piston.PistonMovingBlockEntity blockEntity && blockEntity.isSourcePiston()) { ++ net.minecraft.core.Direction direction = iblockdata.getValue(net.minecraft.world.level.block.piston.PistonHeadBlock.FACING); ++ this.toBlow.add(cachedBlock.immutablePos.relative(direction.getOpposite())); // Paper - optimise collisions ++ } ++ } ++ // Paper end - prevent headless pistons from forming + } + } +- // Paper end - prevent headless pistons from forming + } + +- d4 += d0 * 0.30000001192092896D; +- d5 += d1 * 0.30000001192092896D; +- d6 += d2 * 0.30000001192092896D; +- } ++ power -= 0.22500001F; ++ currX += incX; ++ currY += incY; ++ currZ += incZ; ++ } while (power > 0.0f); ++ // Paper end - collision optimisations + } +- } +- } +- } + +- this.toBlow.addAll(set); ++ // Paper - optimise collisions + float f2 = this.radius * 2.0F; + + i = Mth.floor(this.x - (double) f2 - 1.0D); +@@ -0,0 +0,0 @@ public class Explosion { + Vec3 vec3d = new Vec3(this.x, this.y, this.z); + Iterator iterator = list.iterator(); + ++ // Paper start - optimise collisions ++ final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); ++ // Paper end - optimise collisions ++ + while (iterator.hasNext()) { + Entity entity = (Entity) iterator.next(); + +@@ -0,0 +0,0 @@ public class Explosion { + d8 /= d11; + d9 /= d11; + d10 /= d11; ++ final double seenFraction; // Paper - optimise collisions + if (this.damageCalculator.shouldDamageEntity(this, entity)) { + // CraftBukkit start + +@@ -0,0 +0,0 @@ public class Explosion { + + entity.lastDamageCancelled = false; + ++ seenFraction = (double)this.getBlockDensity(vec3d, entity, blockCache, blockPos); // Paper - optimise collisions ++ + if (entity instanceof EnderDragon) { + for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) { + // Calculate damage separately for each EntityComplexPart +@@ -0,0 +0,0 @@ public class Explosion { + } + } + } else { +- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity)); ++ // Paper start - optimise collisions ++ // inline getEntityDamageAmount so that we can avoid double calling getSeenPercent, which is the MOST ++ // expensive part of this loop!!!! ++ final double factor = (1.0 - d7) * seenFraction; ++ entity.hurt(this.damageSource, (float)((factor * factor + factor) / 2.0 * 7.0 * (double)f2 + 1.0)); ++ // Paper end - optimise collisions + } + + if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled + continue; + } + // CraftBukkit end +- } ++ } else { seenFraction = (double)this.getBlockDensity(vec3d, entity, blockCache, blockPos); } // Paper - optimise collisions + +- double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions ++ double d12 = (1.0D - d7) * seenFraction * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions // Paper - optimise collisions + double d13; + + if (entity instanceof LivingEntity) { +@@ -0,0 +0,0 @@ public class Explosion { + } + } + } +- ++ // Paper start - optimise collisions ++ this.blockCache = null; ++ this.chunkPosCache = null; ++ this.chunkCache = null; ++ // Paper end - optimise collisions + } + + public void finalizeExplosion(boolean particles) { +@@ -0,0 +0,0 @@ public class Explosion { + private BlockInteraction() {} + } + // Paper start - Optimize explosions +- private float getBlockDensity(Vec3 vec3d, Entity entity) { ++ private float getBlockDensity(Vec3 vec3d, Entity entity, ca.spottedleaf.moonrise.patches.collisions.ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise collisions + if (!this.level.paperConfig().environment.optimizeExplosions) { +- return getSeenPercent(vec3d, entity); ++ return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise collisions + } + CacheKey key = new CacheKey(this, entity.getBoundingBox()); + Float blockDensity = this.level.explosionDensityCache.get(key); + if (blockDensity == null) { +- blockDensity = getSeenPercent(vec3d, entity); ++ blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise collisions + this.level.explosionDensityCache.put(key, blockDensity); + } + diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -0,0 +0,0 @@ import net.minecraft.world.level.storage.LevelData; + import net.minecraft.world.level.storage.WritableLevelData; + import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.Vec3; ++import net.minecraft.world.phys.shapes.VoxelShape; + import net.minecraft.world.scores.Scoreboard; + + // CraftBukkit start @@ -0,0 +0,0 @@ import org.bukkit.entity.SpawnCategory; import org.bukkit.event.block.BlockPhysicsEvent; // CraftBukkit end -public abstract class Level implements LevelAccessor, AutoCloseable { -+public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter { // Paper - rewrite chunk system ++public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemLevel, ca.spottedleaf.moonrise.patches.chunk_system.world.ChunkSystemEntityGetter, ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel { // Paper - rewrite chunk system // Paper - optimise collisions public static final Codec> RESOURCE_KEY_CODEC = ResourceKey.codec(Registries.DIMENSION); public static final ResourceKey OVERWORLD = ResourceKey.create(Registries.DIMENSION, ResourceLocation.withDefaultNamespace("overworld")); @@ -28101,6 +31389,382 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // no-op on ClientLevel + } + // Paper end - rewrite chunk system ++ // Paper start - optimise collisions ++ private final int minSection; ++ private final int maxSection; ++ ++ @Override ++ public final int moonrise$getMinSection() { ++ return this.minSection; ++ } ++ ++ @Override ++ public final int moonrise$getMaxSection() { ++ return this.maxSection; ++ } ++ ++ /** ++ * Route to faster lookup. ++ * See {@link EntityGetter#isUnobstructed(Entity, VoxelShape)} for expected behavior ++ * @author Spottedleaf ++ */ ++ @Override ++ public final boolean isUnobstructed(final Entity entity) { ++ final AABB boundingBox = entity.getBoundingBox(); ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isEmpty(boundingBox)) { ++ return false; ++ } ++ ++ final List entities = this.getEntities( ++ entity, ++ boundingBox.inflate(-ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON, -ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON), ++ null ++ ); ++ ++ for (int i = 0, len = entities.size(); i < len; ++i) { ++ final Entity otherEntity = entities.get(i); ++ ++ if (otherEntity.isSpectator() || otherEntity.isRemoved() || !otherEntity.blocksBuilding || otherEntity.isPassengerOfSameVehicle(entity)) { ++ continue; ++ } ++ ++ return false; ++ } ++ ++ return true; ++ } ++ ++ ++ private static net.minecraft.world.phys.BlockHitResult miss(final ClipContext clipContext) { ++ final Vec3 to = clipContext.getTo(); ++ final Vec3 from = clipContext.getFrom(); ++ ++ return net.minecraft.world.phys.BlockHitResult.miss(to, Direction.getNearest(from.x - to.x, from.y - to.y, from.z - to.z), BlockPos.containing(to.x, to.y, to.z)); ++ } ++ ++ private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState(); ++ ++ private static net.minecraft.world.phys.BlockHitResult fastClip(final Vec3 from, final Vec3 to, final Level level, ++ final ClipContext clipContext) { ++ final double adjX = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.x - to.x); ++ final double adjY = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.y - to.y); ++ final double adjZ = ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON * (from.z - to.z); ++ ++ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) { ++ return miss(clipContext); ++ } ++ ++ final double toXAdj = to.x - adjX; ++ final double toYAdj = to.y - adjY; ++ final double toZAdj = to.z - adjZ; ++ final double fromXAdj = from.x + adjX; ++ final double fromYAdj = from.y + adjY; ++ final double fromZAdj = from.z + adjZ; ++ ++ int currX = Mth.floor(fromXAdj); ++ int currY = Mth.floor(fromYAdj); ++ int currZ = Mth.floor(fromZAdj); ++ ++ final BlockPos.MutableBlockPos currPos = new BlockPos.MutableBlockPos(); ++ ++ final double diffX = toXAdj - fromXAdj; ++ final double diffY = toYAdj - fromYAdj; ++ final double diffZ = toZAdj - fromZAdj; ++ ++ final double dxDouble = Math.signum(diffX); ++ final double dyDouble = Math.signum(diffY); ++ final double dzDouble = Math.signum(diffZ); ++ ++ final int dx = (int)dxDouble; ++ final int dy = (int)dyDouble; ++ final int dz = (int)dzDouble; ++ ++ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX; ++ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY; ++ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ; ++ ++ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj)); ++ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj)); ++ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj)); ++ ++ net.minecraft.world.level.chunk.LevelChunkSection[] lastChunk = null; ++ net.minecraft.world.level.chunk.PalettedContainer lastSection = null; ++ int lastChunkX = Integer.MIN_VALUE; ++ int lastChunkY = Integer.MIN_VALUE; ++ int lastChunkZ = Integer.MIN_VALUE; ++ ++ final int minSection = ((ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevel)level).moonrise$getMinSection(); ++ ++ for (;;) { ++ currPos.set(currX, currY, currZ); ++ ++ final int newChunkX = currX >> 4; ++ final int newChunkY = currY >> 4; ++ final int newChunkZ = currZ >> 4; ++ ++ final int chunkDiff = ((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)); ++ final int chunkYDiff = newChunkY ^ lastChunkY; ++ ++ if ((chunkDiff | chunkYDiff) != 0) { ++ if (chunkDiff != 0) { ++ lastChunk = level.getChunk(newChunkX, newChunkZ).getSections(); ++ } ++ final int sectionY = newChunkY - minSection; ++ lastSection = sectionY >= 0 && sectionY < lastChunk.length ? lastChunk[sectionY].states : null; ++ ++ lastChunkX = newChunkX; ++ lastChunkY = newChunkY; ++ lastChunkZ = newChunkZ; ++ } ++ ++ final BlockState blockState; ++ if (lastSection != null && !(blockState = lastSection.get((currX & 15) | ((currZ & 15) << 4) | ((currY & 15) << (4+4)))).isAir()) { ++ final VoxelShape blockCollision = clipContext.getBlockShape(blockState, level, currPos); ++ ++ final net.minecraft.world.phys.BlockHitResult blockHit = blockCollision.isEmpty() ? null : level.clipWithInteractionOverride(from, to, currPos, blockCollision, blockState); ++ ++ final VoxelShape fluidCollision; ++ final FluidState fluidState; ++ if (clipContext.fluid != ClipContext.Fluid.NONE && (fluidState = blockState.getFluidState()) != AIR_FLUIDSTATE) { ++ fluidCollision = clipContext.getFluidShape(fluidState, level, currPos); ++ ++ final net.minecraft.world.phys.BlockHitResult fluidHit = fluidCollision.clip(from, to, currPos); ++ ++ if (fluidHit != null) { ++ if (blockHit == null) { ++ return fluidHit; ++ } ++ ++ return from.distanceToSqr(blockHit.getLocation()) <= from.distanceToSqr(fluidHit.getLocation()) ? blockHit : fluidHit; ++ } ++ } ++ ++ if (blockHit != null) { ++ return blockHit; ++ } ++ } // else: usually fall here ++ ++ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) { ++ return miss(clipContext); ++ } ++ ++ // inc the smallest normalized coordinate ++ ++ if (normalizedCurrX < normalizedCurrY) { ++ if (normalizedCurrX < normalizedCurrZ) { ++ currX += dx; ++ normalizedCurrX += normalizedDiffX; ++ } else { ++ // x < y && x >= z <--> z < y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } else if (normalizedCurrY < normalizedCurrZ) { ++ // y <= x && y < z ++ currY += dy; ++ normalizedCurrY += normalizedDiffY; ++ } else { ++ // y <= x && z <= y <--> z <= y && z <= x ++ currZ += dz; ++ normalizedCurrZ += normalizedDiffZ; ++ } ++ } ++ } ++ ++ /** ++ * @reason Route to optimized call ++ * @author Spottedleaf ++ */ ++ @Override ++ public final net.minecraft.world.phys.BlockHitResult clip(final ClipContext clipContext) { ++ // can only do this in this class, as not everything that implements BlockGetter can retrieve chunks ++ return fastClip(clipContext.getFrom(), clipContext.getTo(), (Level)(Object)this, clipContext); ++ } ++ ++ /** ++ * @reason Route to faster logic ++ * @author Spottedleaf ++ */ ++ @Override ++ public final boolean collidesWithSuffocatingBlock(final Entity entity, final AABB box) { ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder((Level)(Object)this, entity, box, null, null, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY, ++ (final BlockState state, final BlockPos pos) -> { ++ return state.isSuffocating((Level)(Object)Level.this, pos); ++ } ++ ); ++ } ++ ++ private static VoxelShape inflateAABBToVoxel(final AABB aabb, final double x, final double y, final double z) { ++ return net.minecraft.world.phys.shapes.Shapes.create( ++ aabb.minX - x, ++ aabb.minY - y, ++ aabb.minZ - z, ++ ++ aabb.maxX + x, ++ aabb.maxY + y, ++ aabb.maxZ + z ++ ); ++ } ++ ++ /** ++ * @reason Use optimised OR operator join strategy, avoid streams ++ * @author Spottedleaf ++ */ ++ @Override ++ public final java.util.Optional findFreePosition(final Entity entity, final VoxelShape boundsShape, final Vec3 fromPosition, ++ final double rangeX, final double rangeY, final double rangeZ) { ++ if (boundsShape.isEmpty()) { ++ return java.util.Optional.empty(); ++ } ++ ++ final double expandByX = rangeX * 0.5; ++ final double expandByY = rangeY * 0.5; ++ final double expandByZ = rangeZ * 0.5; ++ ++ // note: it is useless to look at shapes outside of range / 2.0 ++ final AABB collectionVolume = boundsShape.bounds().inflate(expandByX, expandByY, expandByZ); ++ ++ final List aabbs = new java.util.ArrayList<>(); ++ final List voxels = new java.util.ArrayList<>(); ++ ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder( ++ (Level)(Object)this, entity, collectionVolume, voxels, aabbs, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER, ++ null ++ ); ++ ++ // push voxels into aabbs ++ for (int i = 0, len = voxels.size(); i < len; ++i) { ++ aabbs.addAll(voxels.get(i).toAabbs()); ++ } ++ ++ // expand AABBs ++ final VoxelShape first = aabbs.isEmpty() ? net.minecraft.world.phys.shapes.Shapes.empty() : inflateAABBToVoxel(aabbs.get(0), expandByX, expandByY, expandByZ); ++ final VoxelShape[] rest = new VoxelShape[Math.max(0, aabbs.size() - 1)]; ++ ++ for (int i = 1, len = aabbs.size(); i < len; ++i) { ++ rest[i - 1] = inflateAABBToVoxel(aabbs.get(i), expandByX, expandByY, expandByZ); ++ } ++ ++ // use optimized implementation of ORing the shapes together ++ final VoxelShape joined = net.minecraft.world.phys.shapes.Shapes.or(first, rest); ++ ++ // find free space ++ // can use unoptimized join here (instead of join()), as closestPointTo uses toAabbs() ++ final VoxelShape freeSpace = net.minecraft.world.phys.shapes.Shapes.joinUnoptimized(boundsShape, joined, net.minecraft.world.phys.shapes.BooleanOp.ONLY_FIRST); ++ ++ return freeSpace.closestPointTo(fromPosition); ++ } ++ ++ /** ++ * @reason Route to faster logic ++ * @author Spottedleaf ++ */ ++ @Override ++ public final java.util.Optional findSupportingBlock(final Entity entity, final AABB aabb) { ++ final int minBlockX = Mth.floor(aabb.minX - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; ++ final int maxBlockX = Mth.floor(aabb.maxX + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; ++ ++ final int minBlockY = Mth.floor(aabb.minY - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; ++ final int maxBlockY = Mth.floor(aabb.maxY + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; ++ ++ final int minBlockZ = Mth.floor(aabb.minZ - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) - 1; ++ final int maxBlockZ = Mth.floor(aabb.maxZ + ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) + 1; ++ ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext collisionContext = null; ++ ++ final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); ++ BlockPos selected = null; ++ double selectedDistance = Double.MAX_VALUE; ++ ++ final Vec3 entityPos = entity.position(); ++ ++ LevelChunk lastChunk = null; ++ int lastChunkX = Integer.MIN_VALUE; ++ int lastChunkZ = Integer.MIN_VALUE; ++ ++ final ChunkSource chunkSource = this.getChunkSource(); ++ ++ for (int currZ = minBlockZ; currZ <= maxBlockZ; ++currZ) { ++ pos.setZ(currZ); ++ for (int currX = minBlockX; currX <= maxBlockX; ++currX) { ++ pos.setX(currX); ++ ++ final int newChunkX = currX >> 4; ++ final int newChunkZ = currZ >> 4; ++ ++ if (((newChunkX ^ lastChunkX) | (newChunkZ ^ lastChunkZ)) != 0) { ++ lastChunk = (LevelChunk)chunkSource.getChunk(newChunkX, newChunkZ, ChunkStatus.FULL, false); ++ } ++ ++ if (lastChunk == null) { ++ continue; ++ } ++ for (int currY = minBlockY; currY <= maxBlockY; ++currY) { ++ int edgeCount = ((currX == minBlockX || currX == maxBlockX) ? 1 : 0) + ++ ((currY == minBlockY || currY == maxBlockY) ? 1 : 0) + ++ ((currZ == minBlockZ || currZ == maxBlockZ) ? 1 : 0); ++ if (edgeCount == 3) { ++ continue; ++ } ++ ++ pos.setY(currY); ++ ++ final double distance = pos.distToCenterSqr(entityPos); ++ if (distance > selectedDistance || (distance == selectedDistance && selected.compareTo(pos) >= 0)) { ++ continue; ++ } ++ ++ final BlockState state = ((ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk)lastChunk).moonrise$getBlock(currX, currY, currZ); ++ if (((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$emptyCollisionShape()) { ++ continue; ++ } ++ ++ VoxelShape blockCollision = ((ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState)state).moonrise$getConstantCollisionShape(); ++ ++ if ((edgeCount != 1 || state.hasLargeCollisionShape()) && (edgeCount != 2 || state.getBlock() == Blocks.MOVING_PISTON)) { ++ if (collisionContext == null) { ++ collisionContext = new ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.LazyEntityCollisionContext(entity); ++ } ++ ++ if (blockCollision == null) { ++ blockCollision = state.getCollisionShape((Level)(Object)this, pos, collisionContext); ++ } ++ ++ if (blockCollision.isEmpty()) { ++ continue; ++ } ++ ++ // avoid VoxelShape#move by shifting the entity collision shape instead ++ final AABB shiftedAABB = aabb.move(-(double)currX, -(double)currY, -(double)currZ); ++ ++ final AABB singleAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)blockCollision).moonrise$getSingleAABBRepresentation(); ++ if (singleAABB != null) { ++ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersect(singleAABB, shiftedAABB)) { ++ continue; ++ } ++ ++ selected = pos.immutable(); ++ selectedDistance = distance; ++ continue; ++ } ++ ++ if (!ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.voxelShapeIntersectNoEmpty(blockCollision, shiftedAABB)) { ++ continue; ++ } ++ ++ selected = pos.immutable(); ++ selectedDistance = distance; ++ continue; ++ } ++ } ++ } ++ } ++ ++ return java.util.Optional.ofNullable(selected); ++ } ++ // Paper end - optimise collisions + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, RegistryAccess iregistrycustom, Holder holder, Supplier supplier, boolean flag, boolean flag1, long i, int j, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider, org.bukkit.World.Environment env, java.util.function.Function paperWorldConfigCreator) { // Paper - create paper world config this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot @@ -28110,6 +31774,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.entityLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.entityMaxTickTime); this.tileLimiter = new org.spigotmc.TickLimiter(this.spigotConfig.tileMaxTickTime); + this.entityLookup = new ca.spottedleaf.moonrise.patches.chunk_system.level.entity.dfl.DefaultEntityLookup(this); // Paper - rewrite chunk system ++ // Paper start - optimise collisions ++ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(this); ++ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(this); ++ // Paper end - optimise collisions } // Paper start - Cancel hit for vanished players @@ -28151,6 +31819,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } // Paper start - Option to prevent armor stands from doing entity lookups @Override + public boolean noCollision(@Nullable Entity entity, AABB box) { + if (entity instanceof net.minecraft.world.entity.decoration.ArmorStand && !entity.level().paperConfig().entities.armorStands.doCollisionEntityLookups) return false; +- return LevelAccessor.super.noCollision(entity, box); ++ // Paper start - optimise collisions ++ final int flags = entity == null ? (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_BORDER | ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY) : ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_FLAG_CHECK_ONLY; ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getCollisionsForBlocksOrWorldBorder((Level)(Object)this, entity, box, null, null, flags, null)) { ++ return false; ++ } ++ ++ return !ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.getEntityHardCollisions((Level)(Object)this, entity, box, null, flags, null); ++ // Paper end - optimise collisions + } + // Paper end - Option to prevent armor stands from doing entity lookups + @@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end - Perf: Optimize capturedTileEntities lookup @@ -28165,14 +31847,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); -+ // Paper start - rewrite chunk system -+ final List ret = new java.util.ArrayList<>(); - +- - this.getEntities().get(box, (entity1) -> { - if (entity1 != except && predicate.test(entity1)) { - list.add(entity1); - } -- ++ // Paper start - rewrite chunk system ++ final List ret = new java.util.ArrayList<>(); + - if (entity1 instanceof EnderDragon) { - EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); - int i = aentitycomplexpart.length; @@ -28317,6 +31999,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Nullable ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create); +diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/block/Block.java ++++ b/src/main/java/net/minecraft/world/level/block/Block.java +@@ -0,0 +0,0 @@ public class Block extends BlockBehaviour implements ItemLike { + } + + public static boolean isShapeFullBlock(VoxelShape shape) { +- return (Boolean) Block.SHAPE_FULL_BLOCK_CACHE.getUnchecked(shape); ++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); // Paper - optimise collisions + } + + public void animateTick(BlockState state, Level world, BlockPos pos, RandomSource random) {} 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -28326,7 +32021,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } - public abstract static class BlockStateBase extends StateHolder { -+ public abstract static class BlockStateBase extends StateHolder implements ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState { // Paper - rewrite chunk system ++ public abstract static class BlockStateBase extends StateHolder implements ca.spottedleaf.moonrise.patches.starlight.blockstate.StarlightAbstractBlockState, ca.spottedleaf.moonrise.patches.collisions.block.CollisionBlockState { // Paper - rewrite chunk system // Paper - optimise collisions private final int lightEmission; private final boolean useShapeForLightOcclusion; @@ -28348,6 +32043,61 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.opacityIfCached; + } + // Paper end - rewrite chunk system ++ // Paper start - optimise collisions ++ private static final int RANDOM_OFFSET = 704237939; ++ private static final Direction[] DIRECTIONS_CACHED = Direction.values(); ++ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); ++ private final int id1 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); ++ private final int id2 = it.unimi.dsi.fastutil.HashCommon.murmurHash3(it.unimi.dsi.fastutil.HashCommon.murmurHash3(ID_GENERATOR.getAndIncrement() + RANDOM_OFFSET) + RANDOM_OFFSET); ++ private boolean occludesFullBlock; ++ private boolean emptyCollisionShape; ++ private VoxelShape constantCollisionShape; ++ private AABB constantAABBCollision; ++ ++ private static void initCaches(final VoxelShape shape) { ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$isFullBlock(); ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$occludesFullBlock(); ++ shape.toAabbs(); ++ if (!shape.isEmpty()) { ++ shape.bounds(); ++ } ++ } ++ ++ @Override ++ public final boolean moonrise$hasCache() { ++ return this.cache != null; ++ } ++ ++ @Override ++ public final boolean moonrise$occludesFullBlock() { ++ return this.occludesFullBlock; ++ } ++ ++ @Override ++ public final boolean moonrise$emptyCollisionShape() { ++ return this.emptyCollisionShape; ++ } ++ ++ @Override ++ public final int moonrise$uniqueId1() { ++ return this.id1; ++ } ++ ++ @Override ++ public final int moonrise$uniqueId2() { ++ return this.id2; ++ } ++ ++ @Override ++ public final VoxelShape moonrise$getConstantCollisionShape() { ++ return this.constantCollisionShape; ++ } ++ ++ @Override ++ public final AABB moonrise$getConstantCollisionAABB() { ++ return this.constantAABBCollision; ++ } ++ // Paper end - optimise collisions + protected BlockStateBase(Block block, Reference2ObjectArrayMap, Comparable> propertyMap, MapCodec codec) { super(block, propertyMap, codec); @@ -28360,6 +32110,39 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.isConditionallyFullOpaque = this.canOcclude & this.useShapeForLightOcclusion; + this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque ? -1 : this.cache.lightBlock; + // Paper end - rewrite chunk system ++ // Paper start - optimise collisions ++ if (this.cache != null) { ++ final VoxelShape collisionShape = this.cache.collisionShape; ++ try { ++ this.constantCollisionShape = this.getCollisionShape(null, null, null); ++ this.constantAABBCollision = this.constantCollisionShape == null ? null : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this.constantCollisionShape).moonrise$getSingleAABBRepresentation(); ++ } catch (final Throwable throwable) { ++ this.constantCollisionShape = null; ++ this.constantAABBCollision = null; ++ } ++ this.occludesFullBlock = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)collisionShape).moonrise$occludesFullBlock(); ++ this.emptyCollisionShape = collisionShape.isEmpty(); ++ // init caches ++ initCaches(collisionShape); ++ if (collisionShape != Shapes.empty() && collisionShape != Shapes.block()) { ++ for (final Direction direction : DIRECTIONS_CACHED) { ++ // initialise the directional face shape cache as well ++ final VoxelShape shape = Shapes.getFaceShape(collisionShape, direction); ++ initCaches(shape); ++ } ++ } ++ if (this.cache.occlusionShapes != null) { ++ for (final VoxelShape shape : this.cache.occlusionShapes) { ++ initCaches(shape); ++ } ++ } ++ } else { ++ this.occludesFullBlock = false; ++ this.emptyCollisionShape = false; ++ this.constantCollisionShape = null; ++ this.constantAABBCollision = null; ++ } ++ // Paper end - optimise collisions } public Block getBlock() { @@ -28435,6 +32218,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.blockEmptinessMap = emptinessMap; + } + // Paper end - rewrite chunk system ++ // Paper start - get block chunk optimisation ++ private final int minSection; ++ private final int maxSection; ++ // Paper end - get block chunk optimisation + public ChunkAccess(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor heightLimitView, Registry biomeRegistry, long inhabitedTime, @Nullable LevelChunkSection[] sectionArray, @Nullable BlendingData blendingData) { this.locX = pos.x; this.locZ = pos.z; // Paper - reduce need for field lookups @@ -28458,10 +32245,52 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.starlight$setSkyNibbles(ca.spottedleaf.moonrise.patches.starlight.light.StarLightEngine.getFilledEmptyLight(heightLimitView)); + } + // Paper end - rewrite chunk system ++ // Paper start - get block chunk optimisation ++ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(levelHeightAccessor); ++ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(levelHeightAccessor); ++ // Paper end - get block chunk optimisation } public final Registry biomeRegistry; // CraftBukkit end @@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom + + @Override + public Holder getNoiseBiome(int biomeX, int biomeY, int biomeZ) { +- try { +- int l = QuartPos.fromBlock(this.getMinBuildHeight()); +- int i1 = l + QuartPos.fromBlock(this.getHeight()) - 1; +- int j1 = Mth.clamp(biomeY, l, i1); +- int k1 = this.getSectionIndex(QuartPos.toBlock(j1)); +- +- return this.sections[k1].getNoiseBiome(biomeX & 3, j1 & 3, biomeZ & 3); +- } catch (Throwable throwable) { +- CrashReport crashreport = CrashReport.forThrowable(throwable, "Getting biome"); +- CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Biome being got"); +- +- crashreportsystemdetails.setDetail("Location", () -> { +- return CrashReportCategory.formatLocation(this, biomeX, biomeY, biomeZ); +- }); +- throw new ReportedException(crashreport); ++ // Paper start - get block chunk optimisation ++ int sectionY = (biomeY >> 2) - this.minSection; ++ int rel = biomeY & 3; ++ ++ final LevelChunkSection[] sections = this.sections; ++ ++ if (sectionY < 0) { ++ sectionY = 0; ++ rel = 0; ++ } else if (sectionY >= sections.length) { ++ sectionY = sections.length - 1; ++ rel = 3; + } ++ ++ return sections[sectionY].getNoiseBiome(biomeX & 3, rel, biomeZ & 3); ++ // Paper end - get block chunk optimisation + } + + // CraftBukkit start +@@ -0,0 +0,0 @@ public abstract class ChunkAccess implements BlockGetter, BiomeManager.NoiseBiom } public void initializeLightSources() { @@ -28623,10 +32452,25 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 import org.slf4j.Logger; -public class LevelChunk extends ChunkAccess { -+public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk { // Paper - rewrite chunk system ++public class LevelChunk extends ChunkAccess implements ca.spottedleaf.moonrise.patches.chunk_system.level.chunk.ChunkSystemLevelChunk, ca.spottedleaf.moonrise.patches.starlight.chunk.StarlightChunk, ca.spottedleaf.moonrise.patches.chunk_getblock.GetBlockChunk { // Paper - rewrite chunk system // Paper - get block chunk optimisation static final Logger LOGGER = LogUtils.getLogger(); private static final TickingBlockEntity NULL_TICKER = new TickingBlockEntity() { +@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { + this.postLoad = entityLoader; + this.blockTicks = blockTickScheduler; + this.fluidTicks = fluidTickScheduler; ++ // Paper start - get block chunk optimisation ++ this.minSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMinSection(level); ++ this.maxSection = ca.spottedleaf.moonrise.common.util.WorldUtil.getMaxSection(level); ++ ++ final boolean empty = ((Object)this instanceof EmptyLevelChunk); ++ this.debug = !empty && this.level.isDebug(); ++ this.defaultBlockState = empty ? VOID_AIR_BLOCKSTATE : AIR_BLOCKSTATE; ++ // Paper end - get block chunk optimisation + } + + // CraftBukkit start @@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { // Paper start boolean loadedTicketLevel; @@ -28639,6 +32483,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.postProcessingDone; + } + // Paper end - rewrite chunk system ++ // Paper start - get block chunk optimisation ++ private static final BlockState AIR_BLOCKSTATE = Blocks.AIR.defaultBlockState(); ++ private static final FluidState AIR_FLUIDSTATE = Fluids.EMPTY.defaultFluidState(); ++ private static final BlockState VOID_AIR_BLOCKSTATE = Blocks.VOID_AIR.defaultBlockState(); ++ private final int minSection; ++ private final int maxSection; ++ private final boolean debug; ++ private final BlockState defaultBlockState; ++ ++ @Override ++ public final BlockState moonrise$getBlock(final int x, final int y, final int z) { ++ return this.getBlockStateFinal(x, y, z); ++ } ++ // Paper end - get block chunk optimisation public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) { this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData()); @@ -28749,10 +32607,153 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } @Nullable +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java +@@ -0,0 +0,0 @@ import net.minecraft.world.level.block.Blocks; + import net.minecraft.world.level.block.state.BlockState; + import net.minecraft.world.level.material.FluidState; + +-public class LevelChunkSection { ++public class LevelChunkSection implements ca.spottedleaf.moonrise.patches.collisions.world.CollisionLevelChunkSection { // Paper - optimise collisions + + public static final int SECTION_WIDTH = 16; + public static final int SECTION_HEIGHT = 16; +@@ -0,0 +0,0 @@ public class LevelChunkSection { + // CraftBukkit start - read/write + private PalettedContainer> biomes; + ++ // Paper start - optimise collisions ++ private int specialCollidingBlocks; ++ ++ @Override ++ public final int moonrise$getSpecialCollidingBlocks() { ++ return this.specialCollidingBlocks; ++ } ++ // Paper end - optimise collisions ++ + public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { + // CraftBukkit end + this.states = datapaletteblock; +@@ -0,0 +0,0 @@ public class LevelChunkSection { + ++this.tickingFluidCount; + } + ++ // Paper start - optimise collisions ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { ++ ++this.specialCollidingBlocks; ++ } ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(iblockdata1)) { ++ --this.specialCollidingBlocks; ++ } ++ // Paper end - optimise collisions ++ + return iblockdata1; + } + +@@ -0,0 +0,0 @@ public class LevelChunkSection { + } + + public void recalcBlockCounts() { +- class a implements PalettedContainer.CountConsumer { ++ // Paper start - optimise collisions ++ // reset, then recalculate ++ this.nonEmptyBlockCount = (short)0; ++ this.tickingBlockCount = (short)0; ++ this.tickingFluidCount = (short)0; ++ this.specialCollidingBlocks = (short)0; ++ ++ if (this.maybeHas((final BlockState state) -> !state.isAir())) { ++ final PalettedContainer.Data data = this.states.data; ++ final Palette palette = data.palette(); ++ final int paletteSize = palette.getSize(); ++ final net.minecraft.util.BitStorage storage = data.storage(); ++ ++ final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap counts = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(paletteSize); ++ if (paletteSize == 1) { ++ counts.addTo(0, storage.getSize()); ++ } else { ++ storage.getAll((final int paletteIdx) -> { ++ counts.addTo(paletteIdx, 1); ++ }); ++ } + +- public int nonEmptyBlockCount; +- public int tickingBlockCount; +- public int tickingFluidCount; ++ for (final java.util.Iterator iterator = counts.int2IntEntrySet().fastIterator(); iterator.hasNext();) { ++ final it.unimi.dsi.fastutil.ints.Int2IntMap.Entry entry = iterator.next(); ++ final int paletteIdx = entry.getIntKey(); ++ final int paletteCount = entry.getIntValue(); + +- a(final LevelChunkSection chunksection) {} ++ final BlockState state = palette.valueFor(paletteIdx); + +- public void accept(BlockState iblockdata, int i) { +- FluidState fluid = iblockdata.getFluidState(); ++ if (state.isAir()) { ++ continue; ++ } + +- if (!iblockdata.isAir()) { +- this.nonEmptyBlockCount += i; +- if (iblockdata.isRandomlyTicking()) { +- this.tickingBlockCount += i; +- } ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isSpecialCollidingBlock(state)) { ++ this.specialCollidingBlocks += paletteCount; + } ++ this.nonEmptyBlockCount += paletteCount; ++ if (state.isRandomlyTicking()) { ++ this.tickingBlockCount += paletteCount; ++ } ++ ++ final FluidState fluid = state.getFluidState(); + + if (!fluid.isEmpty()) { +- this.nonEmptyBlockCount += i; ++ //this.nonEmptyBlockCount += count; // fix vanilla bug: make non empty block count correct + if (fluid.isRandomlyTicking()) { +- this.tickingFluidCount += i; ++ this.tickingFluidCount += paletteCount; + } + } +- + } + } +- +- a a0 = new a(this); +- +- this.states.count(a0); +- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; +- this.tickingBlockCount = (short) a0.tickingBlockCount; +- this.tickingFluidCount = (short) a0.tickingFluidCount; ++ // Paper end - optimise collisions + } + + public PalettedContainer getStates() { +@@ -0,0 +0,0 @@ public class LevelChunkSection { + + datapaletteblock.read(buf); + this.biomes = datapaletteblock; ++ this.recalcBlockCounts(); // Paper - optimise collisions + } + + public void readBiomes(FriendlyByteBuf buf) { diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +@@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize, PalettedContainer + private static final int MIN_PALETTE_BITS = 0; + private final PaletteResize dummyPaletteResize = (newSize, added) -> 0; + public final IdMap registry; +- private volatile PalettedContainer.Data data; ++ public volatile PalettedContainer.Data data; // Paper - optimise collisions - public + private final PalettedContainer.Strategy strategy; + // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused + @@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize, PalettedContainer return this.get(this.strategy.getIndex(x, y, z)); } @@ -30032,6 +34033,1531 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } public int getLightSectionCount() { +diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/AABB.java ++++ b/src/main/java/net/minecraft/world/phys/AABB.java +@@ -0,0 +0,0 @@ public class AABB { + } + + @Nullable +- private static Direction getDirection( ++ public static Direction getDirection( // Paper - optimise collisions - public + AABB box, Vec3 intersectingVector, double[] traceDistanceResult, @Nullable Direction approachDirection, double deltaX, double deltaY, double deltaZ + ) { + if (deltaX > 1.0E-7) { +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java +@@ -0,0 +0,0 @@ public class ArrayVoxelShape extends VoxelShape { + ); + } + +- ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { ++ public ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { // Paper - optimise collisions - public + super(shape); + int i = shape.getXSize() + 1; + int j = shape.getYSize() + 1; +@@ -0,0 +0,0 @@ public class ArrayVoxelShape extends VoxelShape { + new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.") + ); + } ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions + } + + @Override +diff --git a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java +@@ -0,0 +0,0 @@ import java.util.BitSet; + import net.minecraft.core.Direction; + + public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape { +- private final BitSet storage; +- private int xMin; +- private int yMin; +- private int zMin; +- private int xMax; +- private int yMax; +- private int zMax; ++ public final BitSet storage; // Paper - optimise collisions - public ++ public int xMin; // Paper - optimise collisions - public ++ public int yMin; // Paper - optimise collisions - public ++ public int zMin; // Paper - optimise collisions - public ++ public int xMax; // Paper - optimise collisions - public ++ public int yMax; // Paper - optimise collisions - public ++ public int zMax; // Paper - optimise collisions - public + + public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) { + super(sizeX, sizeY, sizeZ); +@@ -0,0 +0,0 @@ public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape { + return bitSetDiscreteVoxelShape; + } + +- protected static void forAllBoxes(DiscreteVoxelShape voxelSet, DiscreteVoxelShape.IntLineConsumer callback, boolean coalesce) { +- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(voxelSet); ++ // Paper start - optimise collisions ++ public static void forAllBoxes(final DiscreteVoxelShape shape, final DiscreteVoxelShape.IntLineConsumer consumer, final boolean mergeAdjacent) { ++ // Paper - remove debug ++ // called with the shape of a VoxelShape, so we can expect the cache to exist ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cache = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape) shape).moonrise$getOrCreateCachedShapeData(); ++ ++ final int sizeX = cache.sizeX(); ++ final int sizeY = cache.sizeY(); ++ final int sizeZ = cache.sizeZ(); ++ ++ int indexX; ++ int indexY = 0; ++ int indexZ; ++ ++ int incY = sizeZ; ++ int incX = sizeZ * sizeY; ++ ++ long[] bitset = cache.voxelSet(); ++ ++ // index = z + y*size_z + x*(size_z*size_y) ++ ++ if (!mergeAdjacent) { ++ // due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply ++ // increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid ++ // the multiplication ++ for (int y = 0; y < sizeY; ++y, indexY += incY) { ++ indexX = indexY; ++ for (int x = 0; x < sizeX; ++x, indexX += incX) { ++ indexZ = indexX; ++ for (int z = 0; z < sizeZ; ++z, ++indexZ) { ++ if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) { ++ consumer.consume(x, y, z, x + 1, y + 1, z + 1); ++ } ++ } ++ } ++ } ++ } else { ++ // same notes about loop order as the above ++ // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize()) + +- for (int i = 0; i < bitSetDiscreteVoxelShape.ySize; i++) { +- for (int j = 0; j < bitSetDiscreteVoxelShape.xSize; j++) { +- int k = -1; ++ // only clone when we may write to it ++ bitset = ca.spottedleaf.moonrise.common.util.MixinWorkarounds.clone(bitset); + +- for (int l = 0; l <= bitSetDiscreteVoxelShape.zSize; l++) { +- if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) { +- if (coalesce) { +- if (k == -1) { +- k = l; +- } +- } else { +- callback.consume(j, i, l, j + 1, i + 1, l + 1); ++ for (int y = 0; y < sizeY; ++y, indexY += incY) { ++ indexX = indexY; ++ for (int x = 0; x < sizeX; ++x, indexX += incX) { ++ for (int zIdx = indexX, endIndex = indexX + sizeZ; zIdx < endIndex; ) { ++ final int firstSetZ = ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.firstSet(bitset, zIdx, endIndex); ++ ++ if (firstSetZ == -1) { ++ break; ++ } ++ ++ int lastSetZ = ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex); ++ if (lastSetZ == -1) { ++ lastSetZ = endIndex; + } +- } else if (k != -1) { +- int m = j; +- int n = i; +- bitSetDiscreteVoxelShape.clearZStrip(k, l, j, i); +- +- while (bitSetDiscreteVoxelShape.isZStripFull(k, l, m + 1, i)) { +- bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i); +- m++; ++ ++ ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ); ++ ++ // try to merge neighbouring on the X axis ++ int endX = x + 1; // exclusive ++ for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX; ++ endX < sizeX && ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd); ++ neighbourIdxStart += incX, neighbourIdxEnd += incX) { ++ ++ ++endX; ++ ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd); + } + +- while (bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) { +- for (int o = j; o <= m; o++) { +- bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1); ++ // try to merge neighbouring on the Y axis ++ ++ int endY; // exclusive ++ int firstSetZY, lastSetZY; ++ y_merge: ++ for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY; ++ firstSetZY += incY, lastSetZY += incY) { ++ ++ // test the whole XZ range ++ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX; ++ ++testX, start += incX, end += incX) { ++ if (!ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, start, end)) { ++ break y_merge; ++ } + } + +- n++; ++ ++endY; ++ ++ // passed, so we can clear it ++ for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX; ++ ++testX, start += incX, end += incX) { ++ ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, start, end); ++ } + } + +- callback.consume(j, i, k, m + 1, n + 1, l); +- k = -1; ++ consumer.consume(x, y, firstSetZ - indexX, endX, endY, lastSetZ - indexX); ++ zIdx = lastSetZ; + } + } + } + } + } ++ // Paper end - optimise collisions + + private boolean isZStripFull(int z1, int z2, int x, int y) { + return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, z1)) >= this.getIndex(x, y, z2); +diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +@@ -0,0 +0,0 @@ import net.minecraft.util.Mth; + public final class CubeVoxelShape extends VoxelShape { + protected CubeVoxelShape(DiscreteVoxelShape voxels) { + super(voxels); ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions + } + + @Override +diff --git a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/DiscreteVoxelShape.java +@@ -0,0 +0,0 @@ package net.minecraft.world.phys.shapes; + import net.minecraft.core.AxisCycle; + import net.minecraft.core.Direction; + +-public abstract class DiscreteVoxelShape { ++public abstract class DiscreteVoxelShape implements ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape { // Paper - optimise collisions + private static final Direction.Axis[] AXIS_VALUES = Direction.Axis.values(); + protected final int xSize; + protected final int ySize; + protected final int zSize; + ++ // Paper start - optimise collisions ++ // ignore race conditions on field read/write: the shape is static, so it doesn't matter ++ private ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData; ++ ++ @Override ++ public final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getOrCreateCachedShapeData() { ++ if (this.cachedShapeData != null) { ++ return this.cachedShapeData; ++ } ++ ++ final DiscreteVoxelShape discreteVoxelShape = (DiscreteVoxelShape)(Object)this; ++ ++ final int sizeX = discreteVoxelShape.getXSize(); ++ final int sizeY = discreteVoxelShape.getYSize(); ++ final int sizeZ = discreteVoxelShape.getZSize(); ++ ++ final int maxIndex = sizeX * sizeY * sizeZ; // exclusive ++ ++ final int longsRequired = (maxIndex + (Long.SIZE - 1)) >>> 6; ++ long[] voxelSet; ++ ++ final boolean isEmpty = discreteVoxelShape.isEmpty(); ++ ++ if (discreteVoxelShape instanceof BitSetDiscreteVoxelShape bitsetShape) { ++ voxelSet = bitsetShape.storage.toLongArray(); ++ if (voxelSet.length < longsRequired) { ++ // happens when the later long values are 0L, so we need to resize ++ voxelSet = java.util.Arrays.copyOf(voxelSet, longsRequired); ++ } ++ } else { ++ voxelSet = new long[longsRequired]; ++ if (!isEmpty) { ++ final int mulX = sizeZ * sizeY; ++ for (int x = 0; x < sizeX; ++x) { ++ for (int y = 0; y < sizeY; ++y) { ++ for (int z = 0; z < sizeZ; ++z) { ++ if (discreteVoxelShape.isFull(x, y, z)) { ++ // index = z + y*size_z + x*(size_z*size_y) ++ final int index = z + y * sizeZ + x * mulX; ++ ++ voxelSet[index >>> 6] |= 1L << index; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ final boolean hasSingleAABB = sizeX == 1 && sizeY == 1 && sizeZ == 1 && !isEmpty && discreteVoxelShape.isFull(0, 0, 0); ++ ++ final int minFullX = discreteVoxelShape.firstFull(Direction.Axis.X); ++ final int minFullY = discreteVoxelShape.firstFull(Direction.Axis.Y); ++ final int minFullZ = discreteVoxelShape.firstFull(Direction.Axis.Z); ++ ++ final int maxFullX = discreteVoxelShape.lastFull(Direction.Axis.X); ++ final int maxFullY = discreteVoxelShape.lastFull(Direction.Axis.Y); ++ final int maxFullZ = discreteVoxelShape.lastFull(Direction.Axis.Z); ++ ++ return this.cachedShapeData = new ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData( ++ sizeX, sizeY, sizeZ, voxelSet, ++ minFullX, minFullY, minFullZ, ++ maxFullX, maxFullY, maxFullZ, ++ isEmpty, hasSingleAABB ++ ); ++ } ++ // Paper end - optimise collisions ++ + protected DiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) { + if (sizeX >= 0 && sizeY >= 0 && sizeZ >= 0) { + this.xSize = sizeX; +diff --git a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/OffsetDoubleList.java +@@ -0,0 +0,0 @@ import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; + import it.unimi.dsi.fastutil.doubles.DoubleList; + + public class OffsetDoubleList extends AbstractDoubleList { +- private final DoubleList delegate; +- private final double offset; ++ public final DoubleList delegate; // Paper - optimise collisions - public ++ public final double offset; // Paper - optimise collisions - public + + public OffsetDoubleList(DoubleList oldList, double offset) { + this.delegate = oldList; +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java +@@ -0,0 +0,0 @@ public final class Shapes { + public static final double EPSILON = 1.0E-7; + public static final double BIG_EPSILON = 1.0E-6; + private static final VoxelShape BLOCK = Util.make(() -> { +- DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); +- discreteVoxelShape.fill(0, 0, 0); +- return new CubeVoxelShape(discreteVoxelShape); ++ // Paper start - optimise collisions ++ final DiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(1, 1, 1); ++ shape.fill(0, 0, 0); ++ ++ return new ArrayVoxelShape( ++ shape, ++ ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE ++ ); ++ // Paper end - optimise collisions + }); + public static final VoxelShape INFINITY = box( + Double.NEGATIVE_INFINITY, +@@ -0,0 +0,0 @@ public final class Shapes { + return BLOCK; + } + ++ // Paper start - optimise collisions ++ private static final DoubleArrayList[] PARTS_BY_BITS = new DoubleArrayList[] { ++ DoubleArrayList.wrap(generateCubeParts(1 << 0)), ++ DoubleArrayList.wrap(generateCubeParts(1 << 1)), ++ DoubleArrayList.wrap(generateCubeParts(1 << 2)), ++ DoubleArrayList.wrap(generateCubeParts(1 << 3)) ++ }; ++ ++ private static double[] generateCubeParts(final int parts) { ++ // note: parts is a power of two, so we do not need to worry about loss of precision here ++ // note: parts is from [2^0, 2^3] ++ final double inc = 1.0 / (double)parts; ++ ++ final double[] ret = new double[parts + 1]; ++ double val = 0.0; ++ for (int i = 0; i <= parts; ++i) { ++ ret[i] = val; ++ val += inc; ++ } ++ ++ return ret; ++ } ++ // Paper end - optimise collisions ++ + public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { + if (!(minX > maxX) && !(minY > maxY) && !(minZ > maxZ)) { + return create(minX, minY, minZ, maxX, maxY, maxZ); +@@ -0,0 +0,0 @@ public final class Shapes { + } + + public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { ++ // Paper start - optimise collisions + if (!(maxX - minX < 1.0E-7) && !(maxY - minY < 1.0E-7) && !(maxZ - minZ < 1.0E-7)) { +- int i = findBits(minX, maxX); +- int j = findBits(minY, maxY); +- int k = findBits(minZ, maxZ); +- if (i < 0 || j < 0 || k < 0) { ++ final int bitsX = findBits(minX, maxX); ++ final int bitsY = findBits(minY, maxY); ++ final int bitsZ = findBits(minZ, maxZ); ++ if (bitsX >= 0 && bitsY >= 0 && bitsZ >= 0) { ++ if (bitsX == 0 && bitsY == 0 && bitsZ == 0) { ++ return BLOCK; ++ } else { ++ final int sizeX = 1 << bitsX; ++ final int sizeY = 1 << bitsY; ++ final int sizeZ = 1 << bitsZ; ++ final BitSetDiscreteVoxelShape shape = BitSetDiscreteVoxelShape.withFilledBounds( ++ sizeX, sizeY, sizeZ, ++ (int)Math.round(minX * (double)sizeX), (int)Math.round(minY * (double)sizeY), (int)Math.round(minZ * (double)sizeZ), ++ (int)Math.round(maxX * (double)sizeX), (int)Math.round(maxY * (double)sizeY), (int)Math.round(maxZ * (double)sizeZ) ++ ); ++ return new ArrayVoxelShape( ++ shape, ++ PARTS_BY_BITS[bitsX], ++ PARTS_BY_BITS[bitsY], ++ PARTS_BY_BITS[bitsZ] ++ ); ++ } ++ } else { + return new ArrayVoxelShape( + BLOCK.shape, +- DoubleArrayList.wrap(new double[]{minX, maxX}), +- DoubleArrayList.wrap(new double[]{minY, maxY}), +- DoubleArrayList.wrap(new double[]{minZ, maxZ}) +- ); +- } else 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) ++ minX == 0.0 && maxX == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minX, maxX }), ++ minY == 0.0 && maxY == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minY, maxY }), ++ minZ == 0.0 && maxZ == 1.0 ? ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minZ, maxZ }) + ); +- return new CubeVoxelShape(bitSetDiscreteVoxelShape); + } + } else { +- return empty(); ++ return EMPTY; + } ++ // Paper end - optimise collisions + } + + public static VoxelShape create(AABB box) { +@@ -0,0 +0,0 @@ public final class Shapes { + return join(first, second, BooleanOp.OR); + } + +- public static VoxelShape or(VoxelShape first, VoxelShape... others) { +- return Arrays.stream(others).reduce(first, Shapes::or); ++ // Paper start - optimise collisions ++ public static VoxelShape or(VoxelShape shape, VoxelShape... others) { ++ int size = others.length; ++ if (size == 0) { ++ return shape; ++ } ++ ++ // reduce complexity of joins by splitting the merges ++ ++ // add extra slot for first shape ++ ++size; ++ final VoxelShape[] tmp = Arrays.copyOf(others, size); ++ // insert first shape ++ tmp[size - 1] = shape; ++ ++ while (size > 1) { ++ int newSize = 0; ++ for (int i = 0; i < size; i += 2) { ++ final int next = i + 1; ++ if (next >= size) { ++ // nothing to merge with, so leave it for next iteration ++ tmp[newSize++] = tmp[i]; ++ break; ++ } else { ++ // merge with adjacent ++ final VoxelShape first = tmp[i]; ++ final VoxelShape second = tmp[next]; ++ ++ tmp[newSize++] = Shapes.or(first, second); ++ } ++ } ++ size = newSize; ++ } ++ ++ return tmp[0]; ++ // Paper end - optimise collisions + } + + public static VoxelShape join(VoxelShape first, VoxelShape second, BooleanOp function) { +- return joinUnoptimized(first, second, function).optimize(); ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinOptimized(first, second, function); // Paper - optimise collisions + } + + public static VoxelShape joinUnoptimized(VoxelShape one, VoxelShape two, BooleanOp function) { +- if (function.apply(false, false)) { +- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); +- } else if (one == two) { +- return function.apply(true, true) ? one : empty(); +- } else { +- boolean bl = function.apply(true, false); +- boolean bl2 = function.apply(false, true); +- if (one.isEmpty()) { +- return bl2 ? two : empty(); +- } else if (two.isEmpty()) { +- return bl ? one : empty(); +- } else { +- IndexMerger indexMerger = createIndexMerger(1, one.getCoords(Direction.Axis.X), two.getCoords(Direction.Axis.X), bl, bl2); +- IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, one.getCoords(Direction.Axis.Y), two.getCoords(Direction.Axis.Y), bl, bl2); +- IndexMerger indexMerger3 = createIndexMerger( +- (indexMerger.size() - 1) * (indexMerger2.size() - 1), one.getCoords(Direction.Axis.Z), two.getCoords(Direction.Axis.Z), bl, bl2 +- ); +- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join( +- one.shape, two.shape, indexMerger, indexMerger2, indexMerger3, function +- ); +- return (VoxelShape)(indexMerger instanceof DiscreteCubeMerger +- && indexMerger2 instanceof DiscreteCubeMerger +- && indexMerger3 instanceof DiscreteCubeMerger +- ? new CubeVoxelShape(bitSetDiscreteVoxelShape) +- : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger2.getList(), indexMerger3.getList())); +- } +- } ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.joinUnoptimized(one, two, function); // Paper - optimise collisions + } + + public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { +- if (predicate.apply(false, false)) { +- throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); +- } else { +- boolean bl = shape1.isEmpty(); +- boolean bl2 = shape2.isEmpty(); +- if (!bl && !bl2) { +- if (shape1 == shape2) { +- return predicate.apply(true, true); +- } else { +- boolean bl3 = predicate.apply(true, false); +- boolean bl4 = predicate.apply(false, true); +- +- for (Direction.Axis axis : AxisCycle.AXIS_VALUES) { +- if (shape1.max(axis) < shape2.min(axis) - 1.0E-7) { +- return bl3 || bl4; +- } +- +- if (shape2.max(axis) < shape1.min(axis) - 1.0E-7) { +- return bl3 || bl4; +- } +- } +- +- IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4); +- IndexMerger indexMerger2 = createIndexMerger( +- indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4 +- ); +- IndexMerger indexMerger3 = createIndexMerger( +- (indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4 +- ); +- return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, predicate); +- } +- } else { +- return predicate.apply(!bl, !bl2); +- } +- } ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.isJoinNonEmpty(shape1, shape2, predicate); // Paper - optimise collisions + } + + private static boolean joinIsNotEmpty( +@@ -0,0 +0,0 @@ public final class Shapes { + return maxDist; + } + +- public static boolean blockOccudes(VoxelShape shape, VoxelShape neighbor, Direction direction) { +- if (shape == block() && neighbor == block()) { ++ // Paper start - optimise collisions ++ public static boolean blockOccudes(final VoxelShape first, final VoxelShape second, final Direction direction) { ++ final boolean firstBlock = first == BLOCK; ++ final boolean secondBlock = second == BLOCK; ++ ++ if (firstBlock & secondBlock) { + return true; +- } else if (neighbor.isEmpty()) { ++ } ++ ++ if (first.isEmpty() | second.isEmpty()) { ++ return false; ++ } ++ ++ // we optimise getOpposite, so we can use it ++ // secondly, use our cache to retrieve sliced shape ++ final VoxelShape newFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getFaceShapeClamped(direction); ++ if (newFirst.isEmpty()) { + return false; +- } else { +- Direction.Axis axis = direction.getAxis(); +- Direction.AxisDirection axisDirection = direction.getAxisDirection(); +- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : neighbor; +- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? neighbor : shape; +- BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND; +- return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7) +- && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7) +- && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp); + } ++ final VoxelShape newSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getFaceShapeClamped(direction.getOpposite()); ++ if (newSecond.isEmpty()) { ++ return false; ++ } ++ ++ return !joinIsNotEmpty(newFirst, newSecond, BooleanOp.ONLY_FIRST); ++ // Paper end - optimise collisions + } + + public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) { +- if (shape == block()) { +- return block(); +- } else { +- Direction.Axis axis = direction.getAxis(); +- boolean bl; +- int i; +- if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { +- bl = DoubleMath.fuzzyEquals(shape.max(axis), 1.0, 1.0E-7); +- i = shape.shape.getSize(axis) - 1; +- } else { +- bl = DoubleMath.fuzzyEquals(shape.min(axis), 0.0, 1.0E-7); +- i = 0; +- } ++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape).moonrise$getFaceShapeClamped(direction); // Paper - optimise collisions ++ } + +- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i)); +- } ++ // Paper start - optimise collisions ++ private static boolean mergedMayOccludeBlock(final VoxelShape shape1, final VoxelShape shape2) { ++ // if the combined bounds of the two shapes cannot occlude, then neither can the merged ++ final AABB bounds1 = shape1.bounds(); ++ final AABB bounds2 = shape2.bounds(); ++ ++ final double minX = Math.min(bounds1.minX, bounds2.minX); ++ final double minY = Math.min(bounds1.minY, bounds2.minY); ++ final double minZ = Math.min(bounds1.minZ, bounds2.minZ); ++ ++ final double maxX = Math.max(bounds1.maxX, bounds2.maxX); ++ final double maxY = Math.max(bounds1.maxY, bounds2.maxY); ++ final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ); ++ ++ return (minX <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && maxX >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && ++ (minY <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && maxY >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && ++ (minZ <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && maxZ >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)); + } ++ // Paper end - optimise collisions + +- public static boolean mergedFaceOccludes(VoxelShape one, VoxelShape two, Direction direction) { +- if (one != block() && two != block()) { +- Direction.Axis axis = direction.getAxis(); +- Direction.AxisDirection axisDirection = direction.getAxisDirection(); +- VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? one : two; +- VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? two : one; +- if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7)) { +- voxelShape = empty(); +- } ++ // Paper start - optimise collisions ++ public static boolean mergedFaceOccludes(final VoxelShape first, final VoxelShape second, final Direction direction) { ++ // see if any of the shapes on their own occludes, only if cached ++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$occludesFullBlockIfCached()) { ++ return true; ++ } + +- if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7)) { +- voxelShape2 = empty(); +- } ++ if (first.isEmpty() & second.isEmpty()) { ++ return false; ++ } + +- return !joinIsNotEmpty( +- block(), +- joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR), +- BooleanOp.ONLY_FIRST +- ); +- } else { ++ // we optimise getOpposite, so we can use it ++ // secondly, use our cache to retrieve sliced shape ++ final VoxelShape newFirst = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)first).moonrise$getFaceShapeClamped(direction); ++ final VoxelShape newSecond = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)second).moonrise$getFaceShapeClamped(direction.getOpposite()); ++ ++ // see if any of the shapes on their own occludes, only if cached ++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newSecond).moonrise$occludesFullBlockIfCached()) { + return true; + } ++ ++ final boolean firstEmpty = newFirst.isEmpty(); ++ final boolean secondEmpty = newSecond.isEmpty(); ++ ++ if (firstEmpty & secondEmpty) { ++ return false; ++ } ++ ++ if (firstEmpty | secondEmpty) { ++ return secondEmpty ? ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlock() : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newSecond).moonrise$occludesFullBlock(); ++ } ++ ++ if (newFirst == newSecond) { ++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$occludesFullBlock(); ++ } ++ ++ return mergedMayOccludeBlock(newFirst, newSecond) && ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)newFirst).moonrise$orUnoptimized(newSecond)).moonrise$occludesFullBlock(); + } ++ // Paper end - optimise collisions ++ ++ // Paper start - optimise collisions ++ public static boolean faceShapeOccludes(final VoxelShape shape1, final VoxelShape shape2) { ++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlockIfCached() || ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape2).moonrise$occludesFullBlockIfCached()) { ++ return true; ++ } ++ ++ final boolean s1Empty = shape1.isEmpty(); ++ final boolean s2Empty = shape2.isEmpty(); ++ if (s1Empty & s2Empty) { ++ return false; ++ } ++ ++ if (s1Empty | s2Empty) { ++ return s2Empty ? ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlock() : ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape2).moonrise$occludesFullBlock(); ++ } ++ ++ if (shape1 == shape2) { ++ return ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$occludesFullBlock(); ++ } + +- public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) { +- return one == block() +- || two == block() +- || (!one.isEmpty() || !two.isEmpty()) && !joinIsNotEmpty(block(), joinUnoptimized(one, two, BooleanOp.OR), BooleanOp.ONLY_FIRST); ++ return mergedMayOccludeBlock(shape1, shape2) && ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)shape1).moonrise$orUnoptimized(shape2)).moonrise$occludesFullBlock(); ++ // Paper end - optimise collisions + } + + @VisibleForTesting +diff --git a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/SliceShape.java +@@ -0,0 +0,0 @@ public class SliceShape extends VoxelShape { + super(makeSlice(shape.shape, axis, sliceWidth)); + this.delegate = shape; + this.axis = axis; ++ ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)this).moonrise$initCache(); // Paper - optimise collisions + } + + private static DiscreteVoxelShape makeSlice(DiscreteVoxelShape voxelSet, Direction.Axis axis, int sliceWidth) { +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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java ++++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java +@@ -0,0 +0,0 @@ import net.minecraft.world.phys.AABB; + import net.minecraft.world.phys.BlockHitResult; + import net.minecraft.world.phys.Vec3; + +-public abstract class VoxelShape { +- protected final DiscreteVoxelShape shape; ++public abstract class VoxelShape implements ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape { // Paper - optimise collisions ++ public final DiscreteVoxelShape shape; // Paper - optimise collisions - public + @Nullable + private VoxelShape[] faces; + ++ // Paper start - optimise collisions ++ private double offsetX; ++ private double offsetY; ++ private double offsetZ; ++ private AABB singleAABBRepresentation; ++ private double[] rootCoordinatesX; ++ private double[] rootCoordinatesY; ++ private double[] rootCoordinatesZ; ++ private ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cachedShapeData; ++ private boolean isEmpty; ++ private ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs; ++ private AABB cachedBounds; ++ private Boolean isFullBlock; ++ private Boolean occludesFullBlock; ++ ++ // must be power of two ++ private static final int MERGED_CACHE_SIZE = 16; ++ private ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache[] mergedORCache; ++ ++ @Override ++ public final double moonrise$offsetX() { ++ return this.offsetX; ++ } ++ ++ @Override ++ public final double moonrise$offsetY() { ++ return this.offsetY; ++ } ++ ++ @Override ++ public final double moonrise$offsetZ() { ++ return this.offsetZ; ++ } ++ ++ @Override ++ public final AABB moonrise$getSingleAABBRepresentation() { ++ return this.singleAABBRepresentation; ++ } ++ ++ @Override ++ public final double[] moonrise$rootCoordinatesX() { ++ return this.rootCoordinatesX; ++ } ++ ++ @Override ++ public final double[] moonrise$rootCoordinatesY() { ++ return this.rootCoordinatesY; ++ } ++ ++ @Override ++ public final double[] moonrise$rootCoordinatesZ() { ++ return this.rootCoordinatesZ; ++ } ++ ++ private static double[] extractRawArray(final DoubleList list) { ++ if (list instanceof it.unimi.dsi.fastutil.doubles.DoubleArrayList rawList) { ++ final double[] raw = rawList.elements(); ++ final int expected = rawList.size(); ++ if (raw.length == expected) { ++ return raw; ++ } else { ++ return java.util.Arrays.copyOf(raw, expected); ++ } ++ } else { ++ return list.toDoubleArray(); ++ } ++ } ++ ++ @Override ++ public final void moonrise$initCache() { ++ this.cachedShapeData = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape)this.shape).moonrise$getOrCreateCachedShapeData(); ++ this.isEmpty = this.cachedShapeData.isEmpty(); ++ ++ final DoubleList xList = this.getCoords(Direction.Axis.X); ++ final DoubleList yList = this.getCoords(Direction.Axis.Y); ++ final DoubleList zList = this.getCoords(Direction.Axis.Z); ++ ++ if (xList instanceof OffsetDoubleList offsetDoubleList) { ++ this.offsetX = offsetDoubleList.offset; ++ this.rootCoordinatesX = extractRawArray(offsetDoubleList.delegate); ++ } else { ++ this.rootCoordinatesX = extractRawArray(xList); ++ } ++ ++ if (yList instanceof OffsetDoubleList offsetDoubleList) { ++ this.offsetY = offsetDoubleList.offset; ++ this.rootCoordinatesY = extractRawArray(offsetDoubleList.delegate); ++ } else { ++ this.rootCoordinatesY = extractRawArray(yList); ++ } ++ ++ if (zList instanceof OffsetDoubleList offsetDoubleList) { ++ this.offsetZ = offsetDoubleList.offset; ++ this.rootCoordinatesZ = extractRawArray(offsetDoubleList.delegate); ++ } else { ++ this.rootCoordinatesZ = extractRawArray(zList); ++ } ++ ++ if (this.cachedShapeData.hasSingleAABB()) { ++ this.singleAABBRepresentation = new AABB( ++ this.rootCoordinatesX[0] + this.offsetX, this.rootCoordinatesY[0] + this.offsetY, this.rootCoordinatesZ[0] + this.offsetZ, ++ this.rootCoordinatesX[1] + this.offsetX, this.rootCoordinatesY[1] + this.offsetY, this.rootCoordinatesZ[1] + this.offsetZ ++ ); ++ this.cachedBounds = this.singleAABBRepresentation; ++ } ++ } ++ ++ @Override ++ public final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData moonrise$getCachedVoxelData() { ++ return this.cachedShapeData; ++ } ++ ++ private VoxelShape[] faceShapeClampedCache; ++ ++ @Override ++ public final VoxelShape moonrise$getFaceShapeClamped(final Direction direction) { ++ if (this.isEmpty) { ++ return (VoxelShape)(Object)this; ++ } ++ if ((VoxelShape)(Object)this == Shapes.block()) { ++ return (VoxelShape)(Object)this; ++ } ++ ++ VoxelShape[] cache = this.faceShapeClampedCache; ++ if (cache != null) { ++ final VoxelShape ret = cache[direction.ordinal()]; ++ if (ret != null) { ++ return ret; ++ } ++ } ++ ++ ++ if (cache == null) { ++ this.faceShapeClampedCache = cache = new VoxelShape[6]; ++ } ++ ++ final Direction.Axis axis = direction.getAxis(); ++ ++ final VoxelShape ret; ++ ++ if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) { ++ if (DoubleMath.fuzzyEquals(this.max(axis), 1.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { ++ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, this.shape.getSize(axis) - 1)); ++ } else { ++ ret = Shapes.empty(); ++ } ++ } else { ++ if (DoubleMath.fuzzyEquals(this.min(axis), 0.0, ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) { ++ ret = tryForceBlock(new SliceShape((VoxelShape)(Object)this, axis, 0)); ++ } else { ++ ret = Shapes.empty(); ++ } ++ } ++ ++ cache[direction.ordinal()] = ret; ++ ++ return ret; ++ } ++ ++ private static VoxelShape tryForceBlock(final VoxelShape other) { ++ if (other == Shapes.block()) { ++ return other; ++ } ++ ++ final AABB otherAABB = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)other).moonrise$getSingleAABBRepresentation(); ++ if (otherAABB == null) { ++ return other; ++ } ++ ++ if (((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionVoxelShape)Shapes.block()).moonrise$getSingleAABBRepresentation().equals(otherAABB)) { ++ return Shapes.block(); ++ } ++ ++ return other; ++ } ++ ++ private boolean computeOccludesFullBlock() { ++ if (this.isEmpty) { ++ this.occludesFullBlock = Boolean.FALSE; ++ return false; ++ } ++ ++ if (this.moonrise$isFullBlock()) { ++ this.occludesFullBlock = Boolean.TRUE; ++ return true; ++ } ++ ++ final AABB singleAABB = this.singleAABBRepresentation; ++ if (singleAABB != null) { ++ // check if the bounding box encloses the full cube ++ final boolean ret = ++ (singleAABB.minY <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && singleAABB.maxY >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && ++ (singleAABB.minX <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && singleAABB.maxX >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)) && ++ (singleAABB.minZ <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && singleAABB.maxZ >= (1 - ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON)); ++ this.occludesFullBlock = Boolean.valueOf(ret); ++ return ret; ++ } ++ ++ final boolean ret = !Shapes.joinIsNotEmpty(Shapes.block(), ((VoxelShape)(Object)this), BooleanOp.ONLY_FIRST); ++ this.occludesFullBlock = Boolean.valueOf(ret); ++ return ret; ++ } ++ ++ @Override ++ public final boolean moonrise$occludesFullBlock() { ++ final Boolean ret = this.occludesFullBlock; ++ if (ret != null) { ++ return ret.booleanValue(); ++ } ++ ++ return this.computeOccludesFullBlock(); ++ } ++ ++ @Override ++ public final boolean moonrise$occludesFullBlockIfCached() { ++ final Boolean ret = this.occludesFullBlock; ++ return ret != null ? ret.booleanValue() : false; ++ } ++ ++ private static int hash(final VoxelShape key) { ++ return it.unimi.dsi.fastutil.HashCommon.mix(System.identityHashCode(key)); ++ } ++ ++ @Override ++ public final VoxelShape moonrise$orUnoptimized(final VoxelShape other) { ++ // don't cache simple cases ++ if (((VoxelShape)(Object)this) == other) { ++ return other; ++ } ++ ++ if (this.isEmpty) { ++ return other; ++ } ++ ++ if (other.isEmpty()) { ++ return (VoxelShape)(Object)this; ++ } ++ ++ // try this cache first ++ final int thisCacheKey = hash(other) & (MERGED_CACHE_SIZE - 1); ++ final ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache cached = this.mergedORCache == null ? null : this.mergedORCache[thisCacheKey]; ++ if (cached != null && cached.key() == other) { ++ return cached.result(); ++ } ++ ++ // try other cache ++ final int otherCacheKey = hash((VoxelShape)(Object)this) & (MERGED_CACHE_SIZE - 1); ++ final ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache otherCache = ((VoxelShape)(Object)other).mergedORCache == null ? null : ((VoxelShape)(Object)other).mergedORCache[otherCacheKey]; ++ if (otherCache != null && otherCache.key() == (VoxelShape)(Object)this) { ++ return otherCache.result(); ++ } ++ ++ // note: unsure if joinUnoptimized(1, 2, OR) == joinUnoptimized(2, 1, OR) for all cases ++ final VoxelShape result = Shapes.joinUnoptimized((VoxelShape)(Object)this, other, BooleanOp.OR); ++ ++ if (cached != null && otherCache == null) { ++ // try to use second cache instead of replacing an entry in this cache ++ if (((VoxelShape)(Object)other).mergedORCache == null) { ++ ((VoxelShape)(Object)other).mergedORCache = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache[MERGED_CACHE_SIZE]; ++ } ++ ((VoxelShape)(Object)other).mergedORCache[otherCacheKey] = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache((VoxelShape)(Object)this, result); ++ } else { ++ // line is not occupied or other cache line is full ++ // always bias to replace this cache, as this cache is the first we check ++ if (this.mergedORCache == null) { ++ this.mergedORCache = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache[MERGED_CACHE_SIZE]; ++ } ++ this.mergedORCache[thisCacheKey] = new ca.spottedleaf.moonrise.patches.collisions.shape.MergedORCache(other, result); ++ } ++ ++ return result; ++ } ++ ++ private static DoubleList offsetList(final DoubleList src, final double by) { ++ if (src instanceof OffsetDoubleList offsetDoubleList) { ++ return new OffsetDoubleList(offsetDoubleList.delegate, by + offsetDoubleList.offset); ++ } ++ return new OffsetDoubleList(src, by); ++ } ++ ++ private List toAabbsUncached() { ++ final List ret = new java.util.ArrayList<>(); ++ if (this.singleAABBRepresentation != null) { ++ ret.add(this.singleAABBRepresentation); ++ } else { ++ final double[] coordsX = this.rootCoordinatesX; ++ final double[] coordsY = this.rootCoordinatesY; ++ final double[] coordsZ = this.rootCoordinatesZ; ++ ++ final double offX = this.offsetX; ++ final double offY = this.offsetY; ++ final double offZ = this.offsetZ; ++ ++ this.shape.forAllBoxes((final int minX, final int minY, final int minZ, ++ final int maxX, final int maxY, final int maxZ) -> { ++ ret.add(new AABB( ++ coordsX[minX] + offX, ++ coordsY[minY] + offY, ++ coordsZ[minZ] + offZ, ++ ++ ++ coordsX[maxX] + offX, ++ coordsY[maxY] + offY, ++ coordsZ[maxZ] + offZ ++ )); ++ }, true); ++ } ++ ++ // cache result ++ this.cachedToAABBs = new ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs(ret, false, 0.0, 0.0, 0.0); ++ ++ return ret; ++ } ++ ++ private boolean computeFullBlock() { ++ Boolean ret; ++ if (this.isEmpty) { ++ ret = Boolean.FALSE; ++ } else if ((VoxelShape)(Object)this == Shapes.block()) { ++ ret = Boolean.TRUE; ++ } else { ++ final AABB singleAABB = this.singleAABBRepresentation; ++ if (singleAABB == null) { ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData; ++ final int sMinX = shapeData.minFullX(); ++ final int sMinY = shapeData.minFullY(); ++ final int sMinZ = shapeData.minFullZ(); ++ ++ final int sMaxX = shapeData.maxFullX(); ++ final int sMaxY = shapeData.maxFullY(); ++ final int sMaxZ = shapeData.maxFullZ(); ++ ++ if (Math.abs(this.rootCoordinatesX[sMinX] + this.offsetX) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(this.rootCoordinatesY[sMinY] + this.offsetY) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(this.rootCoordinatesZ[sMinZ] + this.offsetZ) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ ++ Math.abs(1.0 - (this.rootCoordinatesX[sMaxX] + this.offsetX)) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - (this.rootCoordinatesY[sMaxY] + this.offsetY)) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - (this.rootCoordinatesZ[sMaxZ] + this.offsetZ)) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) { ++ ++ // index = z + y*sizeZ + x*(sizeZ*sizeY) ++ ++ final int sizeY = shapeData.sizeY(); ++ final int sizeZ = shapeData.sizeZ(); ++ ++ final long[] bitset = shapeData.voxelSet(); ++ ++ ret = Boolean.TRUE; ++ ++ check_full: ++ for (int x = sMinX; x < sMaxX; ++x) { ++ for (int y = sMinY; y < sMaxY; ++y) { ++ final int baseIndex = y*sizeZ + x*(sizeZ*sizeY); ++ if (!ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, baseIndex + sMinZ, baseIndex + sMaxZ)) { ++ ret = Boolean.FALSE; ++ break check_full; ++ } ++ } ++ } ++ } else { ++ ret = Boolean.FALSE; ++ } ++ } else { ++ ret = Boolean.valueOf( ++ Math.abs(singleAABB.minX) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(singleAABB.minY) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(singleAABB.minZ) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ ++ Math.abs(1.0 - singleAABB.maxX) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - singleAABB.maxY) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON && ++ Math.abs(1.0 - singleAABB.maxZ) <= ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON ++ ); ++ } ++ } ++ ++ this.isFullBlock = ret; ++ ++ return ret.booleanValue(); ++ } ++ ++ @Override ++ public final boolean moonrise$isFullBlock() { ++ final Boolean ret = this.isFullBlock; ++ ++ if (ret != null) { ++ return ret.booleanValue(); ++ } ++ ++ return this.computeFullBlock(); ++ } ++ ++ private static BlockHitResult clip(final AABB aabb, final Vec3 from, final Vec3 to, final BlockPos offset) { ++ final double[] minDistanceArr = new double[] { 1.0 }; ++ final double diffX = to.x - from.x; ++ final double diffY = to.y - from.y; ++ final double diffZ = to.z - from.z; ++ ++ final Direction direction = AABB.getDirection(aabb.move(offset), from, minDistanceArr, null, diffX, diffY, diffZ); ++ ++ if (direction == null) { ++ return null; ++ } ++ ++ final double minDistance = minDistanceArr[0]; ++ return new BlockHitResult(from.add(minDistance * diffX, minDistance * diffY, minDistance * diffZ), direction, offset, false); ++ } ++ // Paper end - optimise collisions ++ + protected VoxelShape(DiscreteVoxelShape voxels) { + this.shape = voxels; + } + + public double min(Direction.Axis axis) { +- int i = this.shape.firstFull(axis); +- return i >= this.shape.getSize(axis) ? Double.POSITIVE_INFINITY : this.get(axis, i); ++ // Paper start - optimise collisions ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData; ++ switch (axis) { ++ case X: { ++ final int idx = shapeData.minFullX(); ++ return idx >= shapeData.sizeX() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX); ++ } ++ case Y: { ++ final int idx = shapeData.minFullY(); ++ return idx >= shapeData.sizeY() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY); ++ } ++ case Z: { ++ final int idx = shapeData.minFullZ(); ++ return idx >= shapeData.sizeZ() ? Double.POSITIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ); ++ } ++ default: { ++ // should never get here ++ return Double.POSITIVE_INFINITY; ++ } ++ } ++ // Paper end - optimise collisions + } + + public double max(Direction.Axis axis) { +- int i = this.shape.lastFull(axis); +- return i <= 0 ? Double.NEGATIVE_INFINITY : this.get(axis, i); ++ // Paper start - optimise collisions ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData; ++ switch (axis) { ++ case X: { ++ final int idx = shapeData.maxFullX(); ++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesX[idx] + this.offsetX); ++ } ++ case Y: { ++ final int idx = shapeData.maxFullY(); ++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesY[idx] + this.offsetY); ++ } ++ case Z: { ++ final int idx = shapeData.maxFullZ(); ++ return idx <= 0 ? Double.NEGATIVE_INFINITY : (this.rootCoordinatesZ[idx] + this.offsetZ); ++ } ++ default: { ++ // should never get here ++ return Double.NEGATIVE_INFINITY; ++ } ++ } ++ // Paper end - optimise collisions + } + + public AABB bounds() { +- if (this.isEmpty()) { +- throw (UnsupportedOperationException)Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape.")); +- } else { +- return new AABB( +- this.min(Direction.Axis.X), +- this.min(Direction.Axis.Y), +- this.min(Direction.Axis.Z), +- this.max(Direction.Axis.X), +- this.max(Direction.Axis.Y), +- this.max(Direction.Axis.Z) +- ); ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ throw Util.pauseInIde(new UnsupportedOperationException("No bounds for empty shape.")); ++ } ++ AABB cached = this.cachedBounds; ++ if (cached != null) { ++ return cached; + } ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData shapeData = this.cachedShapeData; ++ ++ final double[] coordsX = this.rootCoordinatesX; ++ final double[] coordsY = this.rootCoordinatesY; ++ final double[] coordsZ = this.rootCoordinatesZ; ++ ++ final double offX = this.offsetX; ++ final double offY = this.offsetY; ++ final double offZ = this.offsetZ; ++ ++ // note: if not empty, then there is one full AABB so no bounds checks are needed on the minFull/maxFull indices ++ cached = new AABB( ++ coordsX[shapeData.minFullX()] + offX, ++ coordsY[shapeData.minFullY()] + offY, ++ coordsZ[shapeData.minFullZ()] + offZ, ++ ++ coordsX[shapeData.maxFullX()] + offX, ++ coordsY[shapeData.maxFullY()] + offY, ++ coordsZ[shapeData.maxFullZ()] + offZ ++ ); ++ ++ this.cachedBounds = cached; ++ return cached; ++ // Paper end - optimise collisions + } + + public VoxelShape singleEncompassing() { +@@ -0,0 +0,0 @@ public abstract class VoxelShape { + public abstract DoubleList getCoords(Direction.Axis axis); + + public boolean isEmpty() { +- return this.shape.isEmpty(); ++ return this.isEmpty; // Paper - optimise collisions + } + + public VoxelShape move(double x, double y, double z) { +- return (VoxelShape)(this.isEmpty() +- ? Shapes.empty() +- : new ArrayVoxelShape( +- this.shape, +- new OffsetDoubleList(this.getCoords(Direction.Axis.X), x), +- new OffsetDoubleList(this.getCoords(Direction.Axis.Y), y), +- new OffsetDoubleList(this.getCoords(Direction.Axis.Z), z) +- )); ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ return Shapes.empty(); ++ } ++ ++ final ArrayVoxelShape ret = new ArrayVoxelShape( ++ this.shape, ++ offsetList(this.getCoords(Direction.Axis.X), x), ++ offsetList(this.getCoords(Direction.Axis.Y), y), ++ offsetList(this.getCoords(Direction.Axis.Z), z) ++ ); ++ ++ final ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs; ++ if (cachedToAABBs != null) { ++ ((VoxelShape)(Object)ret).cachedToAABBs = ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs.offset(cachedToAABBs, x, y, z); ++ } ++ ++ return ret; ++ // Paper end - optimise collisions + } + + public VoxelShape optimize() { +- VoxelShape[] voxelShapes = new VoxelShape[]{Shapes.empty()}; +- this.forAllBoxes( +- (minX, minY, minZ, maxX, maxY, maxZ) -> voxelShapes[0] = Shapes.joinUnoptimized( +- voxelShapes[0], Shapes.box(minX, minY, minZ, maxX, maxY, maxZ), BooleanOp.OR +- ) +- ); +- return voxelShapes[0]; ++ // Paper start - optimise collisions ++ if (this.isEmpty) { ++ return Shapes.empty(); ++ } ++ ++ if (this.singleAABBRepresentation != null) { ++ // note: the isFullBlock() is fuzzy, and Shapes.create() is also fuzzy which would return block() ++ return this.moonrise$isFullBlock() ? Shapes.block() : (VoxelShape)(Object)this; ++ } ++ ++ final List aabbs = this.toAabbs(); ++ ++ if (aabbs.size() == 1) { ++ final AABB singleAABB = aabbs.get(0); ++ final VoxelShape ret = Shapes.create(singleAABB); ++ ++ // forward AABB cache ++ if (((VoxelShape)(Object)ret).cachedToAABBs == null) { ++ ((VoxelShape)(Object)ret).cachedToAABBs = this.cachedToAABBs; ++ } ++ ++ return ret; ++ } else { ++ // reduce complexity of joins by splitting the merges (old complexity: n^2, new: nlogn) ++ ++ // set up flat array so that this merge is done in-place ++ final VoxelShape[] tmp = new VoxelShape[aabbs.size()]; ++ ++ // initialise as unmerged ++ for (int i = 0, len = aabbs.size(); i < len; ++i) { ++ tmp[i] = Shapes.create(aabbs.get(i)); ++ } ++ ++ int size = aabbs.size(); ++ while (size > 1) { ++ int newSize = 0; ++ for (int i = 0; i < size; i += 2) { ++ final int next = i + 1; ++ if (next >= size) { ++ // nothing to merge with, so leave it for next iteration ++ tmp[newSize++] = tmp[i]; ++ break; ++ } else { ++ // merge with adjacent ++ final VoxelShape first = tmp[i]; ++ final VoxelShape second = tmp[next]; ++ ++ tmp[newSize++] = Shapes.joinUnoptimized(first, second, BooleanOp.OR); ++ } ++ } ++ size = newSize; ++ } ++ ++ final VoxelShape ret = tmp[0]; ++ ++ // forward AABB cache ++ if (((VoxelShape)(Object)ret).cachedToAABBs == null) { ++ ((VoxelShape)(Object)ret).cachedToAABBs = this.cachedToAABBs; ++ } ++ ++ return ret; ++ } ++ // Paper end - optimise collisions + } + + public void forAllEdges(Shapes.DoubleLineConsumer consumer) { +@@ -0,0 +0,0 @@ public abstract class VoxelShape { + } + + public List toAabbs() { +- List list = Lists.newArrayList(); +- this.forAllBoxes((x1, y1, z1, x2, y2, z2) -> list.add(new AABB(x1, y1, z1, x2, y2, z2))); +- return list; ++ // Paper start - optimise collisions ++ ca.spottedleaf.moonrise.patches.collisions.shape.CachedToAABBs cachedToAABBs = this.cachedToAABBs; ++ if (cachedToAABBs != null) { ++ if (!cachedToAABBs.isOffset()) { ++ return cachedToAABBs.aabbs(); ++ } ++ ++ // all we need to do is offset the cache ++ cachedToAABBs = cachedToAABBs.removeOffset(); ++ // update cache ++ this.cachedToAABBs = cachedToAABBs; ++ ++ return cachedToAABBs.aabbs(); ++ } ++ ++ // make new cache ++ return this.toAabbsUncached(); ++ // Paper end - optimise collisions + } + + public double min(Direction.Axis axis, double from, double to) { +@@ -0,0 +0,0 @@ public abstract class VoxelShape { + } + + @Nullable +- public BlockHitResult clip(Vec3 start, Vec3 end, BlockPos pos) { +- if (this.isEmpty()) { ++ // Paper start - optimise collisions ++ public BlockHitResult clip(final Vec3 from, final Vec3 to, final BlockPos offset) { ++ if (this.isEmpty) { + return null; +- } else { +- Vec3 vec3 = end.subtract(start); +- if (vec3.lengthSqr() < 1.0E-7) { +- return null; +- } else { +- Vec3 vec32 = start.add(vec3.scale(0.001)); +- return this.shape +- .isFullWide( +- this.findIndex(Direction.Axis.X, vec32.x - (double)pos.getX()), +- this.findIndex(Direction.Axis.Y, vec32.y - (double)pos.getY()), +- this.findIndex(Direction.Axis.Z, vec32.z - (double)pos.getZ()) +- ) +- ? new BlockHitResult(vec32, Direction.getNearest(vec3.x, vec3.y, vec3.z).getOpposite(), pos, true) +- : AABB.clip(this.toAabbs(), start, end, pos); ++ } ++ ++ final Vec3 directionOpposite = to.subtract(from); ++ if (directionOpposite.lengthSqr() < ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) { ++ return null; ++ } ++ ++ final Vec3 fromBehind = from.add(directionOpposite.scale(0.001)); ++ final double fromBehindOffsetX = fromBehind.x - (double)offset.getX(); ++ final double fromBehindOffsetY = fromBehind.y - (double)offset.getY(); ++ final double fromBehindOffsetZ = fromBehind.z - (double)offset.getZ(); ++ ++ final AABB singleAABB = this.singleAABBRepresentation; ++ if (singleAABB != null) { ++ if (singleAABB.contains(fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { ++ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); + } ++ return clip(singleAABB, from, to, offset); ++ } ++ ++ if (ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.strictlyContains((VoxelShape)(Object)this, fromBehindOffsetX, fromBehindOffsetY, fromBehindOffsetZ)) { ++ return new BlockHitResult(fromBehind, Direction.getNearest(directionOpposite.x, directionOpposite.y, directionOpposite.z).getOpposite(), offset, true); + } ++ ++ return AABB.clip(((VoxelShape)(Object)this).toAabbs(), from, to, offset); ++ // Paper end - optimise collisions + } + +- public Optional closestPointTo(Vec3 target) { +- if (this.isEmpty()) { ++ // Paper start - optimise collisions ++ public Optional closestPointTo(Vec3 point) { ++ if (this.isEmpty) { + return Optional.empty(); +- } else { +- Vec3[] vec3s = new Vec3[1]; +- this.forAllBoxes((minX, minY, minZ, maxX, maxY, maxZ) -> { +- double d = Mth.clamp(target.x(), minX, maxX); +- double e = Mth.clamp(target.y(), minY, maxY); +- double f = Mth.clamp(target.z(), minZ, maxZ); +- if (vec3s[0] == null || target.distanceToSqr(d, e, f) < target.distanceToSqr(vec3s[0])) { +- vec3s[0] = new Vec3(d, e, f); +- } +- }); +- return Optional.of(vec3s[0]); + } ++ ++ Vec3 ret = null; ++ double retDistance = Double.MAX_VALUE; ++ ++ final List aabbs = this.toAabbs(); ++ for (int i = 0, len = aabbs.size(); i < len; ++i) { ++ final AABB aabb = aabbs.get(i); ++ final double x = Mth.clamp(point.x, aabb.minX, aabb.maxX); ++ final double y = Mth.clamp(point.y, aabb.minY, aabb.maxY); ++ final double z = Mth.clamp(point.z, aabb.minZ, aabb.maxZ); ++ ++ double dist = point.distanceToSqr(x, y, z); ++ if (dist < retDistance) { ++ ret = new Vec3(x, y, z); ++ retDistance = dist; ++ } ++ } ++ ++ return Optional.ofNullable(ret); ++ // Paper end - optimise collisions + } + + public VoxelShape getFaceShape(Direction facing) { +@@ -0,0 +0,0 @@ public abstract class VoxelShape { + } + } + +- public double collide(Direction.Axis axis, AABB box, double maxDist) { +- return this.collideX(AxisCycle.between(axis, Direction.Axis.X), box, maxDist); ++ // Paper start - optimise collisions ++ public double collide(final Direction.Axis axis, final AABB source, final double source_move) { ++ if (this.isEmpty) { ++ return source_move; ++ } ++ if (Math.abs(source_move) < ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.COLLISION_EPSILON) { ++ return 0.0; ++ } ++ switch (axis) { ++ case X: { ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.collideX((VoxelShape)(Object)this, source, source_move); ++ } ++ case Y: { ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.collideY((VoxelShape)(Object)this, source, source_move); ++ } ++ case Z: { ++ return ca.spottedleaf.moonrise.patches.collisions.CollisionUtil.collideZ((VoxelShape)(Object)this, source, source_move); ++ } ++ default: { ++ throw new RuntimeException("Unknown axis: " + axis); ++ } ++ } ++ // Paper end - optimise collisions + } + + protected double collideX(AxisCycle axisCycle, AABB box, double maxDist) { diff --git a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java b/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/ticks/LevelChunkTicks.java diff --git a/patches/server/Optimise-random-block-ticking.patch b/patches/server/Optimise-random-block-ticking.patch deleted file mode 100644 index e634e805c5..0000000000 --- a/patches/server/Optimise-random-block-ticking.patch +++ /dev/null @@ -1,456 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 20 Jun 2021 16:19:26 -0700 -Subject: [PATCH] Optimise random block ticking - -Massive performance improvement for random block ticking. -The performance increase comes from the fact that the vast -majority of attempted block ticks (~95% in my testing) fail -because the randomly selected block is not tickable. - -Now only tickable blocks are targeted, however this means that -the maximum number of block ticks occurs per chunk. However, -not all chunks are going to be targeted. The percent chance -of a chunk being targeted is based on how many tickable blocks -are in the chunk. -This means that while block ticks are spread out less, the -total number of blocks ticked per world tick remains the same. -Therefore, the chance of a random tickable block being ticked -remains the same. - -diff --git a/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/math/ThreadUnsafeRandom.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.util.math; -+ -+import net.minecraft.util.RandomSource; -+import net.minecraft.world.level.levelgen.LegacyRandomSource; -+import net.minecraft.world.level.levelgen.PositionalRandomFactory; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+@DefaultQualifier(NonNull.class) -+public final class ThreadUnsafeRandom extends LegacyRandomSource { -+ -+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. -+ private static final long multiplier = 0x5DEECE66DL; -+ private static final long addend = 0xBL; -+ private static final long mask = (1L << 48) - 1; -+ -+ private static long initialScramble(long seed) { -+ return (seed ^ multiplier) & mask; -+ } -+ -+ private long seed; -+ -+ public ThreadUnsafeRandom(long seed) { -+ super(seed); -+ } -+ -+ @Override -+ public RandomSource fork() { -+ return new ThreadUnsafeRandom(this.nextLong()); -+ } -+ -+ @Override -+ public PositionalRandomFactory forkPositional() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void setSeed(long seed) { -+ // note: called by Random constructor -+ this.seed = initialScramble(seed); -+ } -+ -+ @Override -+ public int next(int bits) { -+ // avoid the expensive CAS logic used by superclass -+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); -+ } -+ -+ // Taken from -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c -+ // Original license is public domain -+ public static int fastRandomBounded(final long randomInteger, final long limit) { -+ // randomInteger must be [0, pow(2, 32)) -+ // limit must be [0, pow(2, 32)) -+ return (int)((randomInteger * limit) >>> 32); -+ } -+ -+ @Override -+ public int nextInt(int bound) { -+ // yes this breaks random's spec -+ // however there's nothing that uses this class that relies on it -+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. - entityplayer.stopSleepInBed(false, false); - }); - } -+ // Paper start - optimise random block ticking -+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); -+ private final io.papermc.paper.util.math.ThreadUnsafeRandom randomTickRandom = new io.papermc.paper.util.math.ThreadUnsafeRandom(this.random.nextLong()); -+ // Paper end - - public void tickChunk(LevelChunk chunk, int randomTickSpeed) { - ChunkPos chunkcoordintpair = chunk.getPos(); -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - gameprofilerfiller.push("thunder"); -+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change -+ - if (!this.paperConfig().environment.disableThunder && flag && this.isThundering() && this.spigotConfig.thunderChance > 0 && this.random.nextInt(this.spigotConfig.thunderChance) == 0) { // Spigot // Paper - Option to disable thunder -- BlockPos blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); -+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper - - if (this.isRainingAt(blockposition)) { - DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. - if (!this.paperConfig().environment.disableIceAndSnow) { // Paper - Option to disable ice and snow - for (int l = 0; l < randomTickSpeed; ++l) { - if (this.random.nextInt(48) == 0) { -- this.tickPrecipitation(this.getBlockRandomPos(j, 0, k, 15)); -+ // Paper start -+ this.getRandomBlockPosition(j, 0, k, 15, blockposition); -+ this.tickPrecipitation(blockposition, chunk); -+ // Paper end - } - } - } // Paper - Option to disable ice and snow -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. - gameprofilerfiller.popPush("tickBlocks"); - timings.chunkTicksBlocks.startTiming(); // Paper - if (randomTickSpeed > 0) { -- LevelChunkSection[] achunksection = chunk.getSections(); -- -- for (int i1 = 0; i1 < achunksection.length; ++i1) { -- LevelChunkSection chunksection = achunksection[i1]; -- -- if (chunksection.isRandomlyTicking()) { -- int j1 = chunk.getSectionYFromSectionIndex(i1); -- int k1 = SectionPos.sectionToBlockCoord(j1); -- -- for (int l1 = 0; l1 < randomTickSpeed; ++l1) { -- BlockPos blockposition1 = this.getBlockRandomPos(j, k1, k, 15); -- -- gameprofilerfiller.push("randomTick"); -- BlockState iblockdata = chunksection.getBlockState(blockposition1.getX() - j, blockposition1.getY() - k1, blockposition1.getZ() - k); -- -- if (iblockdata.isRandomlyTicking()) { -- iblockdata.randomTick(this, blockposition1, this.random); -- } -+ // Paper start - optimize random block ticking -+ LevelChunkSection[] sections = chunk.getSections(); -+ final int minSection = io.papermc.paper.util.WorldUtil.getMinSection(this); -+ for (int sectionIndex = 0; sectionIndex < sections.length; sectionIndex++) { -+ LevelChunkSection section = sections[sectionIndex]; -+ if (section == null || section.tickingList.size() == 0) continue; -+ -+ int yPos = (sectionIndex + minSection) << 4; -+ for (int a = 0; a < randomTickSpeed; ++a) { -+ int tickingBlocks = section.tickingList.size(); -+ int index = this.randomTickRandom.nextInt(16 * 16 * 16); -+ if (index >= tickingBlocks) { -+ continue; -+ } - -- FluidState fluid = iblockdata.getFluidState(); -+ long raw = section.tickingList.getRaw(index); -+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); -+ int randomX = location & 15; -+ int randomY = ((location >>> (4 + 4)) & 255) | yPos; -+ int randomZ = (location >>> 4) & 15; - -- if (fluid.isRandomlyTicking()) { -- fluid.randomTick(this, blockposition1, this.random); -- } -+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); -+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); - -- gameprofilerfiller.pop(); -- } -+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); - } -+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). -+ // TODO CHECK ON UPDATE (ping the Canadian) - } - } -+ // Paper end - optimise random block ticking - - timings.chunkTicksBlocks.stopTiming(); // Paper - gameprofilerfiller.pop(); -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. - - @VisibleForTesting - public void tickPrecipitation(BlockPos pos) { -- BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos); -- BlockPos blockposition2 = blockposition1.below(); -+ // Paper start - optimise chunk ticking -+ tickPrecipitation(pos.mutable(), this.getChunkAt(pos)); -+ } -+ public void tickPrecipitation(BlockPos.MutableBlockPos blockposition1, final LevelChunk chunk) { -+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition1.getX() & 15, blockposition1.getZ() & 15) + 1; -+ int downY = normalY - 1; -+ blockposition1.setY(normalY); -+ // Paper end - optimise chunk ticking - Biome biomebase = (Biome) this.getBiome(blockposition1).value(); - -- if (biomebase.shouldFreeze(this, blockposition2)) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition2, Blocks.ICE.defaultBlockState(), null); // CraftBukkit -+ blockposition1.setY(downY); -+ if (biomebase.shouldFreeze(this, blockposition1)) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit - } - - if (this.isRaining()) { - int i = this.getGameRules().getInt(GameRules.RULE_SNOW_ACCUMULATION_HEIGHT); - -+ blockposition1.setY(normalY); // Paper - optimise chunk ticking - if (i > 0 && biomebase.shouldSnow(this, blockposition1)) { - BlockState iblockdata = this.getBlockState(blockposition1); - -@@ -0,0 +0,0 @@ public class ServerLevel extends Level implements WorldGenLevel, ca.spottedleaf. - } - } - -- Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition2); -+ blockposition1.setY(downY); // Paper - optimise chunk ticking -+ Biome.Precipitation biomebase_precipitation = biomebase.getPrecipitationAt(blockposition1); // Paper - optimise chunk ticking - - if (biomebase_precipitation != Biome.Precipitation.NONE) { -- BlockState iblockdata2 = this.getBlockState(blockposition2); -+ BlockState iblockdata2 = this.getBlockState(blockposition1); // Paper - optimise chunk ticking - -- iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition2, biomebase_precipitation); -+ iblockdata2.getBlock().handlePrecipitation(iblockdata2, this, blockposition1, biomebase_precipitation); // Paper - optimise chunk ticking - } - } - -diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/util/BitStorage.java -+++ b/src/main/java/net/minecraft/util/BitStorage.java -@@ -0,0 +0,0 @@ public interface BitStorage { - void unpack(int[] out); - - BitStorage copy(); -+ -+ // Paper start -+ void forEach(DataBitConsumer consumer); -+ -+ @FunctionalInterface -+ interface DataBitConsumer { -+ -+ void accept(int location, int data); -+ -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/util/SimpleBitStorage.java b/src/main/java/net/minecraft/util/SimpleBitStorage.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/util/SimpleBitStorage.java -+++ b/src/main/java/net/minecraft/util/SimpleBitStorage.java -@@ -0,0 +0,0 @@ public class SimpleBitStorage implements BitStorage { - return this.bits; - } - -+ // Paper start -+ @Override -+ public final void forEach(DataBitConsumer consumer) { -+ int i = 0; -+ long[] along = this.data; -+ int j = along.length; -+ -+ for (int k = 0; k < j; ++k) { -+ long l = along[k]; -+ -+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { -+ consumer.accept(i, (int) (l & this.mask)); -+ l >>= this.bits; -+ ++i; -+ if (i >= this.size) { -+ return; -+ } -+ } -+ } -+ } -+ // Paper end -+ - @Override - public void getAll(IntConsumer action) { - int i = 0; -diff --git a/src/main/java/net/minecraft/util/ZeroBitStorage.java b/src/main/java/net/minecraft/util/ZeroBitStorage.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/util/ZeroBitStorage.java -+++ b/src/main/java/net/minecraft/util/ZeroBitStorage.java -@@ -0,0 +0,0 @@ public class ZeroBitStorage implements BitStorage { - return 0; - } - -+ // Paper start -+ @Override -+ public void forEach(DataBitConsumer consumer) { -+ for(int i = 0; i < this.size; ++i) { -+ consumer.accept(i, 0); -+ } -+ } -+ // Paper end -+ - @Override - public void getAll(IntConsumer action) { - for (int i = 0; i < this.size; i++) { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -0,0 +0,0 @@ public class Turtle extends Animal { - } - - public void setHomePos(BlockPos pos) { -- this.entityData.set(Turtle.HOME_POS, pos); -+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... - } - - public BlockPos getHomePos() { -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -0,0 +0,0 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl - public abstract RecipeManager getRecipeManager(); - - public BlockPos getBlockRandomPos(int x, int y, int z, int l) { -+ // Paper start - allow use of mutable pos -+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); -+ this.getRandomBlockPosition(x, y, z, l, ret); -+ return ret.immutable(); -+ } -+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { -+ // Paper end - this.randValue = this.randValue * 3 + 1013904223; - int i1 = this.randValue >> 2; - -- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); -+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call -+ return out; // Paper - } - - public boolean noSave() { -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -0,0 +0,0 @@ public class LevelChunkSection { - public final PalettedContainer states; - // CraftBukkit start - read/write - private PalettedContainer> biomes; -+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper - - public LevelChunkSection(PalettedContainer datapaletteblock, PalettedContainer> palettedcontainerro) { - // CraftBukkit end -@@ -0,0 +0,0 @@ public class LevelChunkSection { - --this.nonEmptyBlockCount; - if (iblockdata1.isRandomlyTicking()) { - --this.tickingBlockCount; -+ // Paper start -+ this.tickingList.remove(x, y, z); -+ // Paper end - } - } - -@@ -0,0 +0,0 @@ public class LevelChunkSection { - ++this.nonEmptyBlockCount; - if (state.isRandomlyTicking()) { - ++this.tickingBlockCount; -+ // Paper start -+ this.tickingList.add(x, y, z, state); -+ // Paper end - } - } - -@@ -0,0 +0,0 @@ public class LevelChunkSection { - } - - public void recalcBlockCounts() { -- class a implements PalettedContainer.CountConsumer { -- -- public int nonEmptyBlockCount; -- public int tickingBlockCount; -- public int tickingFluidCount; -- -- a(final LevelChunkSection chunksection) {} -- -- public void accept(BlockState iblockdata, int i) { -+ // Paper start - unfuck this -+ this.tickingList.clear(); -+ this.nonEmptyBlockCount = 0; -+ this.tickingBlockCount = 0; -+ this.tickingFluidCount = 0; -+ // Don't run this on clearly empty sections -+ if (this.maybeHas((BlockState state) -> !state.isAir() || !state.getFluidState().isEmpty())) { -+ this.states.forEachLocation((BlockState iblockdata, int i) -> { - FluidState fluid = iblockdata.getFluidState(); - - if (!iblockdata.isAir()) { -- this.nonEmptyBlockCount += i; -+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); - if (iblockdata.isRandomlyTicking()) { -- this.tickingBlockCount += i; -+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); -+ this.tickingList.add(i, iblockdata); - } - } - - if (!fluid.isEmpty()) { -- this.nonEmptyBlockCount += i; -+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); - if (fluid.isRandomlyTicking()) { -- this.tickingFluidCount += i; -+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); - } - } - -- } -+ }); - } -- -- a a0 = new a(this); -- -- this.states.count(a0); -- this.nonEmptyBlockCount = (short) a0.nonEmptyBlockCount; -- this.tickingBlockCount = (short) a0.tickingBlockCount; -- this.tickingFluidCount = (short) a0.tickingFluidCount; -+ // Paper end - unfuck this - } - - public PalettedContainer getStates() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -0,0 +0,0 @@ public class PalettedContainer implements PaletteResize, PalettedContainer - } - } - -+ // Paper start -+ public void forEachLocation(PalettedContainer.CountConsumer consumer) { -+ this.data.storage.forEach((int location, int data) -> { -+ consumer.accept(this.data.palette.valueFor(data), location); -+ }); -+ } -+ // Paper end -+ - @FunctionalInterface - public interface CountConsumer { - void accept(T object, int count);