PaperMC/patches/server/0764-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch

1676 lines
81 KiB
Diff
Raw Normal View History

Merge tuinity (#6413) This PR contains all of Tuinity's patches. Very notable ones are: - Highly optimised collisions - Optimised entity lookups by bounding box (Mojang made regressions in 1.17, this brings it back to 1.16) - Starlight https://github.com/PaperMC/Starlight - Rewritten dataconverter system https://github.com/PaperMC/DataConverter - Random block ticking optimisation (wrongly dropped from Paper 1.17) - Chunk ticking optimisations - Anything else I've forgotten in the 60 or so patches If you are a previous Tuinity user, your config will not migrate. You must do it yourself. The config options have simply been moved into paper.yml, so it will be an easy migration. However, please note that the chunk loading options in tuinity.yml are NOT compatible with the options in paper.yml. * Port tuinity, initial patchset * Update gradle to 7.2 jmp said it fixes rebuildpatches not working for me. it fucking better * Completely clean apply * Remove tuinity config, add per player api patch * Remove paper reobf mappings patch * Properly update gradlew * Force clean rebuild * Mark fixups Comments and ATs still need to be done * grep -r "Tuinity" * Fixup * Ensure gameprofile lastaccess is written only under the state lock * update URL for dataconverter * Only clean rebuild tuinity patches might fix merge conflicts * Use UTF-8 for gradlew * Clean rb patches again * Convert block ids used as item ids Neither the converters of pre 1.13 nor DFU handled these cases, as by the time they were written the game at the time didn't consider these ids valid - they would be air. Because of this, some worlds have logspam since only DataConverter (not DFU or legacy converters) will warn when an invalid id has been seen. While quite a few do need to now be considered as air, quite a lot do not. So it makes sense to add conversion for these items, instead of simply suppressing or ignoring the logs. I've now added id -> string conversion for all block ids that could be used as items that existed in the game before 1.7.10 (I have no interest in tracking down the exact version block ids stopped working) that were on https://minecraft-ids.grahamedgecombe.com/ Items that did not directly convert to new items will be instead converted to air: stems, wheat crops, piston head, tripwire wire block * Fix LightPopulated parsing in V1466 The DFU code was checking if the number existed, not if it didn't exist. I misread the original code. * Always parse protochunk light sources unless it is marked as non-lit Chunks not marked as lit will always go through the light engine, so they should always have their block sources parsed. * Update custom names to JSON for players Missed this fix from CB, as it was inside the DataFixers class. I decided to double check all of the CB changes again: DataFixers.java was the only area I missed, as I had inspected all datafixer diffs and implemented them all into DataConverter. I also checked Bootstrap.java again, and re-evaluated their changes. I had previously done this, but determined that they were all bad. The changes to make standing_sign block map to oak_sign block in V1450 is bad, because that's not the item id V1450 accepts. Only in 1.14 did oak_sign even exist, and as expected there is a converter to rename all existing sign items/blocks. The fix to register the portal block under id 1440 is useless, as the flattenning logic will default to the lowest registered id - which is the exact blockstate that CB registers into 1440. So it just doesn't do anything. The extra item ids in the id -> string converter are already added, but I found this from EMC originally. The change for the spawn egg id 23 -> Arrow is just wrong, that id DOES correspond to TippedArrow, NOT Arrow. As expected, the spawn egg already has a dedicated mapping for Arrow, which is id 10 - which was Arrow's entity id. I also ported a fix for the cooked_fished id update. This doesn't really matter since there is already a dataconverter to fix this, but the game didn't accept cooked_fished at the time. So I see no harm. * Review all converters and walkers - Refactor V99 to have helper methods for defining entity/tile entity types - Automatically namespace all ids that should be namespaced. While vanilla never saved non-namespaced data for things that are namespaced, plugins/users might have. - Synchronised the identity ensure map in HelperBlockFlatteningV1450 - Code style consistency - Add missing log warning in V102 for ITEM_NAME type conversion - Use getBoolean instead of getByte - Use ConverterAbstractEntityRename for V143 TippedArrow -> Arrow rename, as it will affect ENTITY_NAME type - Always set isVillager to false in V502 for Zombie - Register V808's converter under subversion 1 like DFU - Register a breakpoint for V1.17.1. In the future, all final versions of major releases will have a breakpoint so that the work required to determine if a converter needs a breakpoint is minimal - Validate that a dataconverter is only registered for a version that is registered - ConverterFlattenTileEntity is actually ConverterFlattenEntity It even registered the converters under TILE_ENTITY, instead of ENTITY. - Fix id comparison in V1492 STRUCTURE_FEATURE renamer - Use ConverterAbstractStatsRename for V1510 stats renamer At the time I had written that class, the abstract renamer didn't exist. - Ensure OwnerUUID is at least set to empty string in V1904 if the ocelot is converted to a cat (this is likely so that it retains a collar) - Use generic read/write for Records in V1946 Records is actually a list, not a map. So reading map was invalid. * Always set light to zero when propagating decrease This fixes an almost infinite loop where light values would be spam queued on a very small subset on blocks. This also likely fixes the memory issues people were seeing. * re-organize patches * Apply and fix conflicts * Revert some patches getChunkAt retains chunks so that plugins don't spam loads revert mc-4 fix will remain unless issues pop up * Shuffle iterated chunks if per player is not enabled Can help with some mob spawning stacking up at locations * Make per player default, migrate all configs * Adjust comments in fixups * Rework config for player chunk loader Old config is not compatible. Move all configs to be under `settings` in paper.yml The player chunk loader has been modified to less aggressively load chunks, but to send chunks at higher rates compared to tuinity. There are new config entries to tune this behavior. * Add back old constructor to CompressionEncoder/Decoder (fixes Tuinity #358) * Raise chunk loading default limits * Reduce worldgen thread workers for lower core count cpus * Raise limits for chunk loading config Also place it under `chunk-loading` * Disable max chunk send rate by default * Fix conflicts and rebuild patches * Drop default send rate again Appears to be still causing problems for no known reason * Raise chunk send limits to 100 per player While a low limit fixes ping issues for some people, most people do not suffer from this issue and thus should not suffer from an extremely slow load-in rate. * Rebase part 1 Autosquash the fixups * Move not implemented up * Fixup mc-dev fixes Missed this one * Rebase per player viewdistance api into the original api patch * Remove old light engine patch part 1 The prioritisation must be kept from it, so that part has been rebased into the priority patch. Part 2 will deal with rebasing all of the patches _after_ * Rebase remaining patches for old light patch removal * Remove other mid tick patch * Remove Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch Replaced by `Do not copy visible chunks` * Revert AT for Vec3i setX/Y/Z The class is immutable. set should not be exposed * Remove old IntegerUtil class * Replace old CraftChunk#getEntities patch * Remove import for SWMRNibbleArray in ChunkAccess * Finished merge checklist * Remove ensureTickThread impl in urgency patch Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com> Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2021-08-31 13:02:11 +02:00
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Spottedleaf <spottedleaf@spottedleaf.dev>
Date: Mon, 4 May 2020 10:06:24 -0700
Subject: [PATCH] Highly optimise single and multi-AABB VoxelShapes and
collisions
diff --git a/src/main/java/io/papermc/paper/util/CachedLists.java b/src/main/java/io/papermc/paper/util/CachedLists.java
index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5c5683504 100644
--- a/src/main/java/io/papermc/paper/util/CachedLists.java
+++ b/src/main/java/io/papermc/paper/util/CachedLists.java
@@ -1,8 +1,57 @@
package io.papermc.paper.util;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.phys.AABB;
+import org.bukkit.Bukkit;
+import org.bukkit.craftbukkit.util.UnsafeList;
+import java.util.List;
+
public final class CachedLists {
- public static void reset() {
+ // Paper start - optimise collisions
+ static final UnsafeList<AABB> TEMP_COLLISION_LIST = new UnsafeList<>(1024);
+ static boolean tempCollisionListInUse;
+
+ public static UnsafeList<AABB> getTempCollisionList() {
+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) {
+ return new UnsafeList<>(16);
+ }
+ tempCollisionListInUse = true;
+ return TEMP_COLLISION_LIST;
+ }
+
+ public static void returnTempCollisionList(List<AABB> list) {
+ if (list != TEMP_COLLISION_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempCollisionListInUse = false;
+ }
+ static final UnsafeList<Entity> TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024);
+ static boolean tempGetEntitiesListInUse;
+
+ public static UnsafeList<Entity> getTempGetEntitiesList() {
+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) {
+ return new UnsafeList<>(16);
+ }
+ tempGetEntitiesListInUse = true;
+ return TEMP_GET_ENTITIES_LIST;
+ }
+
+ public static void returnTempGetEntitiesList(List<Entity> list) {
+ if (list != TEMP_GET_ENTITIES_LIST) {
+ return;
+ }
+ ((UnsafeList)list).setSize(0);
+ tempGetEntitiesListInUse = false;
+ }
+ // Paper end - optimise collisions
+
+ public static void reset() {
+ // Paper start - optimise collisions
+ TEMP_COLLISION_LIST.completeReset();
+ TEMP_GET_ENTITIES_LIST.completeReset();
+ // Paper end - optimise collisions
}
}
diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..98ca1199a823cdf55b913396ce0a24554e85f116
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java
@@ -0,0 +1,645 @@
+package io.papermc.paper.util;
+
+import io.papermc.paper.voxel.AABBVoxelShape;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.WorldGenRegion;
+import net.minecraft.util.Mth;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.item.Item;
+import net.minecraft.world.level.CollisionGetter;
+import net.minecraft.world.level.EntityGetter;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.WorldBorder;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.LevelChunkSection;
+import net.minecraft.world.level.material.FlowingFluid;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.Vec3;
+import net.minecraft.world.phys.shapes.ArrayVoxelShape;
+import net.minecraft.world.phys.shapes.CollisionContext;
+import net.minecraft.world.phys.shapes.EntityCollisionContext;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
+public final class CollisionUtil {
+
+ public static final double COLLISION_EPSILON = 1.0E-7;
+
+ public static boolean isEmpty(final AABB aabb) {
+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON;
+ }
+
+ public static boolean isEmpty(final double minX, final double minY, final double minZ,
+ final double maxX, final double maxY, final double maxZ) {
+ return (maxX - minX) < COLLISION_EPSILON && (maxY - minY) < COLLISION_EPSILON && (maxZ - minZ) < COLLISION_EPSILON;
+ }
+
+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) {
+ double x = (double)(chunkX << 4);
+ double z = (double)(chunkZ << 4);
+ // use a bounding box bigger than the chunk to prevent entities from entering it on move
+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON,
+ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false);
+ }
+
+ /*
+ A couple of rules for VoxelShape collisions:
+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement
+ checks.
+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite
+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code
+ will automatically round it to 0.
+ */
+
+ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1,
+ final double maxY1, final double maxZ1, final double minX2, final double minY2,
+ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) {
+ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON &&
+ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON &&
+ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON;
+ }
+
+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ,
+ final double maxX, final double maxY, final double maxZ) {
+ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON &&
+ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON &&
+ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON;
+ }
+
+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) {
+ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON &&
+ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON &&
+ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON;
+ }
+
+ public static double collideX(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideY(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static double collideZ(final AABB target, final AABB source, final double source_move) {
+ if (source_move == 0.0) {
+ return 0.0;
+ }
+
+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON &&
+ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) {
+ if (source_move >= 0.0) {
+ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision
+ if (max_move < -COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.min(max_move, source_move);
+ } else {
+ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision
+ if (max_move > COLLISION_EPSILON) {
+ return source_move;
+ }
+ return Math.max(max_move, source_move);
+ }
+ }
+ return source_move;
+ }
+
+ public static AABB offsetX(final AABB box, final double dx) {
+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB offsetY(final AABB box, final double dy) {
+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB offsetZ(final AABB box, final double dz) {
+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0
+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0
+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0
+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0
+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0
+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0
+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false);
+ }
+
+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0
+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false);
+ }
+
+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0
+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false);
+ }
+
+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0
+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false);
+ }
+
+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0
+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false);
+ }
+
+ public static double performCollisionsX(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideX(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static double performCollisionsY(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideY(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static double performCollisionsZ(final AABB currentBoundingBox, double value, final List<AABB> potentialCollisions) {
+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) {
+ final AABB target = potentialCollisions.get(i);
+ value = collideZ(target, currentBoundingBox, value);
+ }
+
+ return value;
+ }
+
+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, final List<AABB> potentialCollisions) {
+ double x = moveVector.x;
+ double y = moveVector.y;
+ double z = moveVector.z;
+
+ if (y != 0.0) {
+ y = performCollisionsY(axisalignedbb, y, potentialCollisions);
+ if (y != 0.0) {
+ axisalignedbb = offsetY(axisalignedbb, y);
+ }
+ }
+
+ final boolean xSmaller = Math.abs(x) < Math.abs(z);
+
+ if (xSmaller && z != 0.0) {
+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions);
+ if (z != 0.0) {
+ axisalignedbb = offsetZ(axisalignedbb, z);
+ }
+ }
+
+ if (x != 0.0) {
+ x = performCollisionsX(axisalignedbb, x, potentialCollisions);
+ if (!xSmaller && x != 0.0) {
+ axisalignedbb = offsetX(axisalignedbb, x);
+ }
+ }
+
+ if (!xSmaller && z != 0.0) {
+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions);
+ }
+
+ return new Vec3(x, y, z);
+ }
+
+ public static boolean addBoxesToIfIntersects(final VoxelShape shape, final AABB aabb, final List<AABB> list) {
+ if (shape instanceof AABBVoxelShape) {
+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape;
+ if (voxelShapeIntersect(shapeCasted.aabb, aabb) && !isEmpty(shapeCasted.aabb)) {
+ list.add(shapeCasted.aabb);
+ return true;
+ }
+ return false;
+ } else if (shape instanceof ArrayVoxelShape) {
+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape;
+ // this can be optimised by checking an "overall shape" first, but not needed
+
+ final double offX = shapeCasted.getOffsetX();
+ final double offY = shapeCasted.getOffsetY();
+ final double offZ = shapeCasted.getOffsetZ();
+
+ boolean ret = false;
+
+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) {
+ final double minX, minY, minZ, maxX, maxY, maxZ;
+ if (voxelShapeIntersect(aabb, minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ,
+ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ)
+ && !isEmpty(minX, minY, minZ, maxX, maxY, maxZ)) {
+ list.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ, false));
+ ret = true;
+ }
+ }
+
+ return ret;
+ } else {
+ final List<AABB> boxes = shape.toAabbs();
+
+ boolean ret = false;
+
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
+ final AABB box = boxes.get(i);
+ if (voxelShapeIntersect(box, aabb) && !isEmpty(box)) {
+ list.add(box);
+ ret = true;
+ }
+ }
+
+ return ret;
+ }
+ }
+
+ public static void addBoxesTo(final VoxelShape shape, final List<AABB> list) {
+ if (shape instanceof AABBVoxelShape) {
+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape;
+ if (!isEmpty(shapeCasted.aabb)) {
+ list.add(shapeCasted.aabb);
+ }
+ } else if (shape instanceof ArrayVoxelShape) {
+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape;
+
+ final double offX = shapeCasted.getOffsetX();
+ final double offY = shapeCasted.getOffsetY();
+ final double offZ = shapeCasted.getOffsetZ();
+
+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) {
+ final AABB box = boundingBox.move(offX, offY, offZ);
+ if (!isEmpty(box)) {
+ list.add(box);
+ }
+ }
+ } else {
+ final List<AABB> boxes = shape.toAabbs();
+ for (int i = 0, len = boxes.size(); i < len; ++i) {
+ final AABB box = boxes.get(i);
+ if (!isEmpty(box)) {
+ list.add(box);
+ }
+ }
+ }
+ }
+
+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final AABB boundingBox) {
+ return isAlmostCollidingOnBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
+ }
+
+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX,
+ final double boxMinZ, final double boxMaxZ) {
+ final double borderMinX = worldborder.getMinX(); // -X
+ final double borderMaxX = worldborder.getMaxX(); // +X
+
+ final double borderMinZ = worldborder.getMinZ(); // -Z
+ final double borderMaxZ = worldborder.getMaxZ(); // +Z
+
+ return
+ // Not intersecting if we're smaller
+ !voxelShapeIntersect(
+ boxMinX + COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + COLLISION_EPSILON,
+ boxMaxX - COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - COLLISION_EPSILON,
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
+ )
+ &&
+
+ // Are intersecting if we're larger
+ voxelShapeIntersect(
+ boxMinX - COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - COLLISION_EPSILON,
+ boxMaxX + COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + COLLISION_EPSILON,
+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ
+ );
+ }
+
+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final AABB boundingBox) {
+ return isCollidingWithBorderEdge(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ);
+ }
+
+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final double boxMinX, final double boxMaxX,
+ final double boxMinZ, final double boxMaxZ) {
+ final double borderMinX = worldborder.getMinX() + COLLISION_EPSILON; // -X
+ final double borderMaxX = worldborder.getMaxX() - COLLISION_EPSILON; // +X
+
+ final double borderMinZ = worldborder.getMinZ() + COLLISION_EPSILON; // -Z
+ final double borderMaxZ = worldborder.getMaxZ() - COLLISION_EPSILON; // +Z
+
+ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ;
+ }
+
+ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloaded,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) {
+ if (checkOnly) {
+ return true;
+ } else {
+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into);
+ ret = true;
+ }
+ }
+ }
+
+ int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
+ int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
+
+ int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
+ int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
+
+ int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
+ int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1;
+
+ final int minSection = WorldUtil.getMinSection(getter);
+ final int maxSection = WorldUtil.getMaxSection(getter);
+ final int minBlock = minSection << 4;
+ final int maxBlock = (maxSection << 4) | 15;
+
+ BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
+ if (minBlockY > maxBlock || maxBlockY < minBlock) {
+ // no point in checking
+ return ret;
+ }
+
+ int minYIterate = Math.max(minBlock, minBlockY);
+ int maxYIterate = Math.min(maxBlock, maxBlockY);
+
+ int minChunkX = minBlockX >> 4;
+ int maxChunkX = maxBlockX >> 4;
+
+ int minChunkZ = minBlockZ >> 4;
+ int maxChunkZ = maxBlockZ >> 4;
+
+ ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
+ chunkProvider = ((ServerLevel)getter).getChunkSource();
+ } else {
+ chunkProvider = null;
+ }
+ // TODO special case single chunk?
+
+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
+
+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
+ int chunkXGlobalPos = currChunkX << 4;
+ int chunkZGlobalPos = currChunkZ << 4;
+ ChunkAccess chunk;
+ if (chunkProvider == null) {
+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ);
+ } else {
+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ);
+ }
+
+
+ if (chunk == null) {
+ if (collidesWithUnloaded) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(getBoxForChunk(currChunkX, currChunkZ));
+ ret = true;
+ }
+ }
+ continue;
+ }
+
+ LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+
+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
+ LevelChunkSection section = sections[(currY >> 4) - minSection];
+ if (section == null || section.isEmpty()) {
+ // empty
+ // skip to next section
+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
+ continue;
+ }
+
+ net.minecraft.world.level.chunk.PalettedContainer<BlockState> blocks = section.states;
+
+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
+ for (int currX = minX; currX <= maxX; ++currX) {
+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8);
+ int blockX = currX | chunkXGlobalPos;
+ int blockY = currY;
+ int blockZ = currZ | chunkZGlobalPos;
+
+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
+ if (edgeCount == 3) {
+ continue;
+ }
+
+ BlockState blockData = blocks.get(localBlockIndex);
+ if (blockData.isAir()) {
+ continue;
+ }
+
+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
+ mutablePos.set(blockX, blockY, blockZ);
+ if (collisionShape == null) {
+ collisionShape = new LazyEntityCollisionContext(entity);
+ }
+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape);
+ if (voxelshape2 != Shapes.empty()) {
+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ);
+
+ if (predicate != null && !predicate.test(blockData, mutablePos)) {
+ continue;
+ }
+
+ if (checkOnly) {
+ if (voxelshape3.intersects(aabb)) {
+ return true;
+ }
+ } else {
+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ }
+
+ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb,
+ final List<AABB> into, final boolean checkOnly, final Predicate<Entity> predicate) {
+ if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) {
+ return false;
+ }
+
+ boolean ret = false;
+
+ // to comply with vanilla intersection rules, expand by -epsilon so we only get stuff we definitely collide with.
+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems
+ // specifically with boat collisions.
+ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON);
+ final List<Entity> entities = CachedLists.getTempGetEntitiesList();
+ try {
+ if (entity != null && entity.hardCollides()) {
+ entityGetter.getEntities(entity, aabb, predicate, entities);
+ } else {
+ entityGetter.getHardCollidingEntities(entity, aabb, predicate, entities);
+ }
+
+ for (int i = 0, len = entities.size(); i < len; ++i) {
+ final Entity otherEntity = entities.get(i);
+
+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) {
+ if (checkOnly) {
+ return true;
+ } else {
+ into.add(otherEntity.getBoundingBox());
+ ret = true;
+ }
+ }
+ }
+ } finally {
+ CachedLists.returnTempGetEntitiesList(entities);
+ }
+
+ return ret;
+ }
+
+ public static boolean getCollisions(final CollisionGetter view, final Entity entity, final AABB aabb,
+ final List<AABB> into, final boolean loadChunks, final boolean collidesWithUnloadedChunks,
+ final boolean checkBorder, final boolean checkOnly, final BiPredicate<BlockState, BlockPos> blockPredicate,
+ final Predicate<Entity> entityPredicate) {
+ if (checkOnly) {
+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate)
+ || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate);
+ } else {
+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate)
+ | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate);
+ }
+ }
+
+ public static final class LazyEntityCollisionContext extends EntityCollisionContext {
+
+ private CollisionContext delegate;
+ private final Entity entity;
+
+ public LazyEntityCollisionContext(final Entity entity) {
+ super(false, 0.0, null, null, null, Optional.ofNullable(entity));
+ this.entity = entity;
+ }
+
+ public CollisionContext getDelegate() {
+ return this.delegate == null ? this.delegate = (this.entity == null ? CollisionContext.empty() : CollisionContext.of(this.entity)) : this.delegate;
+ }
+
+ @Override
+ public boolean isDescending() {
+ return this.getDelegate().isDescending();
+ }
+
+ @Override
+ public boolean isAbove(final VoxelShape shape, final BlockPos pos, final boolean defaultValue) {
+ return this.getDelegate().isAbove(shape, pos, defaultValue);
+ }
+
+ @Override
+ public boolean hasItemOnFeet(final Item item) {
+ return this.getDelegate().hasItemOnFeet(item);
+ }
+
+ @Override
+ public boolean isHoldingItem(final Item item) {
+ return this.getDelegate().isHoldingItem(item);
+ }
+
+ @Override
+ public boolean canStandOnFluid(final FluidState state, final FlowingFluid fluid) {
+ return this.getDelegate().canStandOnFluid(state, fluid);
+ }
+ }
+
+ private CollisionUtil() {
+ throw new RuntimeException();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java
new file mode 100644
index 0000000000000000000000000000000000000000..d67a40e7be030142443680c89e1763fc9ecdfe0a
--- /dev/null
+++ b/src/main/java/io/papermc/paper/voxel/AABBVoxelShape.java
@@ -0,0 +1,200 @@
+package io.papermc.paper.voxel;
+
+import io.papermc.paper.util.CollisionUtil;
+import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
+import it.unimi.dsi.fastutil.doubles.DoubleList;
+import net.minecraft.core.Direction;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class AABBVoxelShape extends VoxelShape {
+
+ public final AABB aabb;
+
+ public AABBVoxelShape(AABB aabb) {
+ super(Shapes.getFullUnoptimisedCube().shape);
+ this.aabb = aabb;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return CollisionUtil.isEmpty(this.aabb);
+ }
+
+ @Override
+ public double min(Direction.Axis enumdirection_enumaxis) {
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return this.aabb.minX;
+ case 1:
+ return this.aabb.minY;
+ case 2:
+ return this.aabb.minZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public double max(Direction.Axis enumdirection_enumaxis) {
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return this.aabb.maxX;
+ case 1:
+ return this.aabb.maxY;
+ case 2:
+ return this.aabb.maxZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public AABB bounds() {
+ return this.aabb;
+ }
+
+ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis.
+ @Override
+ protected double get(Direction.Axis enumdirection_enumaxis, int i) {
+ switch (enumdirection_enumaxis.ordinal() | (i << 2)) {
+ case (0 | (0 << 2)):
+ return this.aabb.minX;
+ case (1 | (0 << 2)):
+ return this.aabb.minY;
+ case (2 | (0 << 2)):
+ return this.aabb.minZ;
+ case (0 | (1 << 2)):
+ return this.aabb.maxX;
+ case (1 | (1 << 2)):
+ return this.aabb.maxY;
+ case (2 | (1 << 2)):
+ return this.aabb.maxZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ private DoubleList cachedListX;
+ private DoubleList cachedListY;
+ private DoubleList cachedListZ;
+
+ @Override
+ protected DoubleList getCoords(Direction.Axis enumdirection_enumaxis) {
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX;
+ case 1:
+ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY;
+ case 2:
+ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public VoxelShape move(double d0, double d1, double d2) {
+ return new AABBVoxelShape(this.aabb.move(d0, d1, d2));
+ }
+
+ @Override
+ public VoxelShape optimize() {
+ if (this.isEmpty()) {
+ return Shapes.empty();
+ } else if (this == Shapes.BLOCK_OPTIMISED || this.aabb.equals(Shapes.BLOCK_OPTIMISED.aabb)) {
+ return Shapes.BLOCK_OPTIMISED;
+ }
+ return this;
+ }
+
+ @Override
+ public void forAllBoxes(Shapes.DoubleLineConsumer voxelshapes_a) {
+ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ);
+ }
+
+ @Override
+ public List<AABB> toAabbs() { // getAABBs
+ List<AABB> ret = new ArrayList<>(1);
+ ret.add(this.aabb);
+ return ret;
+ }
+
+ @Override
+ protected int findIndex(Direction.Axis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1;
+ case 1:
+ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1;
+ case 2:
+ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1;
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ protected VoxelShape calculateFace(Direction direction) {
+ if (this.isEmpty()) {
+ return Shapes.empty();
+ }
+ if (this == Shapes.BLOCK_OPTIMISED) {
+ return this;
+ }
+ switch (direction) {
+ case EAST: // +X
+ case WEST: { // -X
+ final double from = direction == Direction.EAST ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON;
+ if (from > this.aabb.maxX || this.aabb.minX > from) {
+ return Shapes.empty();
+ }
+ return new AABBVoxelShape(new AABB(0.0, this.aabb.minY, this.aabb.minZ, 1.0, this.aabb.maxY, this.aabb.maxZ)).optimize();
+ }
+ case UP: // +Y
+ case DOWN: { // -Y
+ final double from = direction == Direction.UP ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON;
+ if (from > this.aabb.maxY || this.aabb.minY > from) {
+ return Shapes.empty();
+ }
+ return new AABBVoxelShape(new AABB(this.aabb.minX, 0.0, this.aabb.minZ, this.aabb.maxX, 1.0, this.aabb.maxZ)).optimize();
+ }
+ case SOUTH: // +Z
+ case NORTH: { // -Z
+ final double from = direction == Direction.SOUTH ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON;
+ if (from > this.aabb.maxZ || this.aabb.minZ > from) {
+ return Shapes.empty();
+ }
+ return new AABBVoxelShape(new AABB(this.aabb.minX, this.aabb.minY, 0.0, this.aabb.maxX, this.aabb.maxY, 1.0)).optimize();
+ }
+ default: {
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+ }
+
+ @Override
+ public double collide(Direction.Axis enumdirection_enumaxis, AABB axisalignedbb, double d0) {
+ if (CollisionUtil.isEmpty(this.aabb) || CollisionUtil.isEmpty(axisalignedbb)) {
+ return d0;
+ }
+ switch (enumdirection_enumaxis.ordinal()) {
+ case 0:
+ return CollisionUtil.collideX(this.aabb, axisalignedbb, d0);
+ case 1:
+ return CollisionUtil.collideY(this.aabb, axisalignedbb, d0);
+ case 2:
+ return CollisionUtil.collideZ(this.aabb, axisalignedbb, d0);
+ default:
+ throw new IllegalStateException("Unknown axis requested");
+ }
+ }
+
+ @Override
+ public boolean intersects(AABB axisalingedbb) {
+ return CollisionUtil.voxelShapeIntersect(this.aabb, axisalingedbb);
+ }
+}
diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
index e40587b9c13d9170ef5dd0811e96b02ecaccebb2..52ffc39715def70a8ad3a99356be19bea5ef892b 100644
Merge tuinity (#6413) This PR contains all of Tuinity's patches. Very notable ones are: - Highly optimised collisions - Optimised entity lookups by bounding box (Mojang made regressions in 1.17, this brings it back to 1.16) - Starlight https://github.com/PaperMC/Starlight - Rewritten dataconverter system https://github.com/PaperMC/DataConverter - Random block ticking optimisation (wrongly dropped from Paper 1.17) - Chunk ticking optimisations - Anything else I've forgotten in the 60 or so patches If you are a previous Tuinity user, your config will not migrate. You must do it yourself. The config options have simply been moved into paper.yml, so it will be an easy migration. However, please note that the chunk loading options in tuinity.yml are NOT compatible with the options in paper.yml. * Port tuinity, initial patchset * Update gradle to 7.2 jmp said it fixes rebuildpatches not working for me. it fucking better * Completely clean apply * Remove tuinity config, add per player api patch * Remove paper reobf mappings patch * Properly update gradlew * Force clean rebuild * Mark fixups Comments and ATs still need to be done * grep -r "Tuinity" * Fixup * Ensure gameprofile lastaccess is written only under the state lock * update URL for dataconverter * Only clean rebuild tuinity patches might fix merge conflicts * Use UTF-8 for gradlew * Clean rb patches again * Convert block ids used as item ids Neither the converters of pre 1.13 nor DFU handled these cases, as by the time they were written the game at the time didn't consider these ids valid - they would be air. Because of this, some worlds have logspam since only DataConverter (not DFU or legacy converters) will warn when an invalid id has been seen. While quite a few do need to now be considered as air, quite a lot do not. So it makes sense to add conversion for these items, instead of simply suppressing or ignoring the logs. I've now added id -> string conversion for all block ids that could be used as items that existed in the game before 1.7.10 (I have no interest in tracking down the exact version block ids stopped working) that were on https://minecraft-ids.grahamedgecombe.com/ Items that did not directly convert to new items will be instead converted to air: stems, wheat crops, piston head, tripwire wire block * Fix LightPopulated parsing in V1466 The DFU code was checking if the number existed, not if it didn't exist. I misread the original code. * Always parse protochunk light sources unless it is marked as non-lit Chunks not marked as lit will always go through the light engine, so they should always have their block sources parsed. * Update custom names to JSON for players Missed this fix from CB, as it was inside the DataFixers class. I decided to double check all of the CB changes again: DataFixers.java was the only area I missed, as I had inspected all datafixer diffs and implemented them all into DataConverter. I also checked Bootstrap.java again, and re-evaluated their changes. I had previously done this, but determined that they were all bad. The changes to make standing_sign block map to oak_sign block in V1450 is bad, because that's not the item id V1450 accepts. Only in 1.14 did oak_sign even exist, and as expected there is a converter to rename all existing sign items/blocks. The fix to register the portal block under id 1440 is useless, as the flattenning logic will default to the lowest registered id - which is the exact blockstate that CB registers into 1440. So it just doesn't do anything. The extra item ids in the id -> string converter are already added, but I found this from EMC originally. The change for the spawn egg id 23 -> Arrow is just wrong, that id DOES correspond to TippedArrow, NOT Arrow. As expected, the spawn egg already has a dedicated mapping for Arrow, which is id 10 - which was Arrow's entity id. I also ported a fix for the cooked_fished id update. This doesn't really matter since there is already a dataconverter to fix this, but the game didn't accept cooked_fished at the time. So I see no harm. * Review all converters and walkers - Refactor V99 to have helper methods for defining entity/tile entity types - Automatically namespace all ids that should be namespaced. While vanilla never saved non-namespaced data for things that are namespaced, plugins/users might have. - Synchronised the identity ensure map in HelperBlockFlatteningV1450 - Code style consistency - Add missing log warning in V102 for ITEM_NAME type conversion - Use getBoolean instead of getByte - Use ConverterAbstractEntityRename for V143 TippedArrow -> Arrow rename, as it will affect ENTITY_NAME type - Always set isVillager to false in V502 for Zombie - Register V808's converter under subversion 1 like DFU - Register a breakpoint for V1.17.1. In the future, all final versions of major releases will have a breakpoint so that the work required to determine if a converter needs a breakpoint is minimal - Validate that a dataconverter is only registered for a version that is registered - ConverterFlattenTileEntity is actually ConverterFlattenEntity It even registered the converters under TILE_ENTITY, instead of ENTITY. - Fix id comparison in V1492 STRUCTURE_FEATURE renamer - Use ConverterAbstractStatsRename for V1510 stats renamer At the time I had written that class, the abstract renamer didn't exist. - Ensure OwnerUUID is at least set to empty string in V1904 if the ocelot is converted to a cat (this is likely so that it retains a collar) - Use generic read/write for Records in V1946 Records is actually a list, not a map. So reading map was invalid. * Always set light to zero when propagating decrease This fixes an almost infinite loop where light values would be spam queued on a very small subset on blocks. This also likely fixes the memory issues people were seeing. * re-organize patches * Apply and fix conflicts * Revert some patches getChunkAt retains chunks so that plugins don't spam loads revert mc-4 fix will remain unless issues pop up * Shuffle iterated chunks if per player is not enabled Can help with some mob spawning stacking up at locations * Make per player default, migrate all configs * Adjust comments in fixups * Rework config for player chunk loader Old config is not compatible. Move all configs to be under `settings` in paper.yml The player chunk loader has been modified to less aggressively load chunks, but to send chunks at higher rates compared to tuinity. There are new config entries to tune this behavior. * Add back old constructor to CompressionEncoder/Decoder (fixes Tuinity #358) * Raise chunk loading default limits * Reduce worldgen thread workers for lower core count cpus * Raise limits for chunk loading config Also place it under `chunk-loading` * Disable max chunk send rate by default * Fix conflicts and rebuild patches * Drop default send rate again Appears to be still causing problems for no known reason * Raise chunk send limits to 100 per player While a low limit fixes ping issues for some people, most people do not suffer from this issue and thus should not suffer from an extremely slow load-in rate. * Rebase part 1 Autosquash the fixups * Move not implemented up * Fixup mc-dev fixes Missed this one * Rebase per player viewdistance api into the original api patch * Remove old light engine patch part 1 The prioritisation must be kept from it, so that part has been rebased into the priority patch. Part 2 will deal with rebasing all of the patches _after_ * Rebase remaining patches for old light patch removal * Remove other mid tick patch * Remove Optimize-PlayerChunkMap-memory-use-for-visibleChunks.patch Replaced by `Do not copy visible chunks` * Revert AT for Vec3i setX/Y/Z The class is immutable. set should not be exposed * Remove old IntegerUtil class * Replace old CraftChunk#getEntities patch * Remove import for SWMRNibbleArray in ChunkAccess * Finished merge checklist * Remove ensureTickThread impl in urgency patch Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com> Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2021-08-31 13:02:11 +02:00
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
@@ -419,7 +419,7 @@ public class ServerPlayer extends Player {
if (blockposition1 != null) {
this.moveTo(blockposition1, 0.0F, 0.0F);
- if (world.noCollision(this)) {
+ if (world.noCollision(this, this.getBoundingBox(), null, true)) { // Paper - make sure this loads chunks, we default to NOT loading now
break;
}
}
@@ -427,7 +427,7 @@ public class ServerPlayer extends Player {
} else {
this.moveTo(blockposition, 0.0F, 0.0F);
- while (!world.noCollision(this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) {
+ while (!world.noCollision(this, this.getBoundingBox(), null, true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Paper - make sure this loads chunks, we default to NOT loading now
this.setPos(this.getX(), this.getY() + 1.0D, this.getZ());
}
}
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index cf8ed790e09987528178519ba99376f27b15245f..4c8f6d2d72194d313e7383b5a499c8ca1a84e1da 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -934,7 +934,7 @@ public abstract class PlayerList {
worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper
entityplayer1.forceCheckHighPriority(); // Player - Chunk priority
- while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) {
+ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), null, true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Paper - make sure this loads chunks, we default to NOT loading now
entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ());
}
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
index 74fcc1b45a9e57280da82f7c181530d4183872a5..481e84fda6dccfaf684c922a12fa19ed35c87b3c 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -1025,9 +1025,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
float f2 = this.getBlockSpeedFactor();
this.setDeltaMovement(this.getDeltaMovement().multiply((double) f2, 1.0D, (double) f2));
- if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata1) -> {
- return iblockdata1.is((Tag) BlockTags.FIRE) || iblockdata1.is(Blocks.LAVA);
- })) {
+ // Paper start - remove expensive streams from here
+ boolean noneMatch = true;
+ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D);
+ {
+ int minX = Mth.floor(fireSearchBox.minX);
+ int minY = Mth.floor(fireSearchBox.minY);
+ int minZ = Mth.floor(fireSearchBox.minZ);
+ int maxX = Mth.floor(fireSearchBox.maxX);
+ int maxY = Mth.floor(fireSearchBox.maxY);
+ int maxZ = Mth.floor(fireSearchBox.maxZ);
+ fire_search_loop:
+ for (int fz = minZ; fz <= maxZ; ++fz) {
+ for (int fx = minX; fx <= maxX; ++fx) {
+ for (int fy = minY; fy <= maxY; ++fy) {
+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4);
+ if (chunk == null) {
+ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true
+ // even if we're in lava/fire
+ noneMatch = true;
+ break fire_search_loop;
+ }
+ if (!noneMatch) {
+ // don't do get type, we already know we're in fire - we just need to check the chunks
+ // loaded state
+ continue;
+ }
+
+ BlockState type = chunk.getType(fx, fy, fz);
+ if (type.is((Tag) BlockTags.FIRE) || type.is(Blocks.LAVA)) {
+ noneMatch = false;
+ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded
+ }
+ }
+ }
+ }
+ }
+ if (noneMatch) {
+ // Paper end - remove expensive streams from here
if (this.remainingFireTicks <= 0) {
this.setRemainingFireTicks(-this.getFireImmuneTicks());
}
@@ -1149,39 +1184,79 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
return offsetFactor;
}
- private Vec3 collide(Vec3 movement) {
- AABB axisalignedbb = this.getBoundingBox();
- CollisionContext voxelshapecollision = CollisionContext.of(this);
- VoxelShape voxelshape = this.level.getWorldBorder().getCollisionShape();
- Stream<VoxelShape> stream = !this.level.getWorldBorder().isWithinBounds(axisalignedbb) ? Stream.empty() : Stream.of(voxelshape); // Paper
- Stream<VoxelShape> stream1 = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement), (entity) -> {
- return true;
- });
- RewindableStream<VoxelShape> streamaccumulator = new RewindableStream<>(Stream.concat(stream1, stream));
- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBoxHeuristically(this, movement, axisalignedbb, this.level, voxelshapecollision, streamaccumulator);
- boolean flag = movement.x != vec3d1.x;
- boolean flag1 = movement.y != vec3d1.y;
- boolean flag2 = movement.z != vec3d1.z;
- boolean flag3 = this.onGround || flag1 && movement.y < 0.0D;
-
- if (this.maxUpStep > 0.0F && flag3 && (flag || flag2)) {
- Vec3 vec3d2 = Entity.collideBoundingBoxHeuristically(this, new Vec3(movement.x, (double) this.maxUpStep, movement.z), axisalignedbb, this.level, voxelshapecollision, streamaccumulator);
- Vec3 vec3d3 = Entity.collideBoundingBoxHeuristically(this, new Vec3(0.0D, (double) this.maxUpStep, 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level, voxelshapecollision, streamaccumulator);
-
- if (vec3d3.y < (double) this.maxUpStep) {
- Vec3 vec3d4 = Entity.collideBoundingBoxHeuristically(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level, voxelshapecollision, streamaccumulator).add(vec3d3);
-
- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
- vec3d2 = vec3d4;
+ private Vec3 collide(Vec3 moveVector) {
+ // Paper start - optimise collisions
+ // This is a copy of vanilla's except that it uses strictly AABB math
+ if (moveVector.x == 0.0 && moveVector.y == 0.0 && moveVector.z == 0.0) {
+ return moveVector;
+ }
+
+ final Level world = this.level;
+ final AABB currBoundingBox = this.getBoundingBox();
+
+ if (io.papermc.paper.util.CollisionUtil.isEmpty(currBoundingBox)) {
+ return moveVector;
+ }
+
+ final List<AABB> potentialCollisions = io.papermc.paper.util.CachedLists.getTempCollisionList();
+ try {
+ final double stepHeight = (double)this.maxUpStep;
+ final AABB collisionBox;
+
+ if (moveVector.x == 0.0 && moveVector.z == 0.0 && moveVector.y != 0.0) {
+ if (moveVector.y > 0.0) {
+ collisionBox = io.papermc.paper.util.CollisionUtil.cutUpwards(currBoundingBox, moveVector.y);
+ } else {
+ collisionBox = io.papermc.paper.util.CollisionUtil.cutDownwards(currBoundingBox, moveVector.y);
+ }
+ } else {
+ if (stepHeight > 0.0 && (this.onGround || (moveVector.y < 0.0)) && (moveVector.x != 0.0 || moveVector.z != 0.0)) {
+ // don't bother getting the collisions if we don't need them.
+ if (moveVector.y <= 0.0) {
+ collisionBox = io.papermc.paper.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(moveVector.x, moveVector.y, moveVector.z), stepHeight);
+ } else {
+ collisionBox = currBoundingBox.expandTowards(moveVector.x, Math.max(stepHeight, moveVector.y), moveVector.z);
+ }
+ } else {
+ collisionBox = currBoundingBox.expandTowards(moveVector.x, moveVector.y, moveVector.z);
}
}
- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) {
- return vec3d2.add(Entity.collideBoundingBoxHeuristically(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level, voxelshapecollision, streamaccumulator));
+ io.papermc.paper.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, true,
+ false, false, null, null);
+
+ if (io.papermc.paper.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) {
+ io.papermc.paper.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions);
}
- }
- return vec3d1;
+ final Vec3 limitedMoveVector = io.papermc.paper.util.CollisionUtil.performCollisions(moveVector, currBoundingBox, potentialCollisions);
+
+ if (stepHeight > 0.0
+ && (this.onGround || (limitedMoveVector.y != moveVector.y && moveVector.y < 0.0))
+ && (limitedMoveVector.x != moveVector.x || limitedMoveVector.z != moveVector.z)) {
+ Vec3 vec3d2 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(moveVector.x, stepHeight, moveVector.z), currBoundingBox, potentialCollisions);
+ final Vec3 vec3d3 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(moveVector.x, 0.0, moveVector.z), potentialCollisions);
+
+ if (vec3d3.y < stepHeight) {
+ final Vec3 vec3d4 = io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(moveVector.x, 0.0D, moveVector.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3);
+
+ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) {
+ vec3d2 = vec3d4;
+ }
+ }
+
+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) {
+ return vec3d2.add(io.papermc.paper.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + moveVector.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions));
+ }
+
+ return limitedMoveVector;
+ } else {
+ return limitedMoveVector;
+ }
+ } finally {
+ io.papermc.paper.util.CachedLists.returnTempCollisionList(potentialCollisions);
+ }
+ // Paper end - optimise collisions
}
public static Vec3 collideBoundingBoxHeuristically(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, CollisionContext context, RewindableStream<VoxelShape> collisions) {
@@ -2320,9 +2395,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
float f = this.dimensions.width * 0.8F;
AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f);
- return this.level.getBlockCollisions(this, axisalignedbb, (iblockdata, blockposition) -> {
- return iblockdata.isSuffocating(this.level, blockposition);
- }).findAny().isPresent();
+ // Paper start
+ return io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this.level, this, axisalignedbb, null,
+ false, false, false, true, (iblockdata, blockposition) -> {
+ return iblockdata.isSuffocating(this.level, blockposition);
+ });
+ // Paper end
}
}
diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java
index 2a784a8342e708e0813c7076a2ca8e429446ffd3..f38cd7e2c341ae2fbbe9dd26cf872da9571b416a 100644
--- a/src/main/java/net/minecraft/world/level/CollisionGetter.java
+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java
@@ -36,28 +36,40 @@ public interface CollisionGetter extends BlockGetter {
return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox()));
}
+ // Paper start - optimise collisions
+ default boolean noCollision(Entity entity, AABB box, Predicate<Entity> filter, boolean loadChunks) {
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, loadChunks, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, filter);
+ }
+ // Paper end - optimise collisions
+
default boolean noCollision(AABB box) {
- return this.noCollision((Entity)null, box, (e) -> {
- return true;
- });
+ // Paper start - optimise collisions
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, null, box, null, false, false, false, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, null, box, null, true, null);
+ // Paper end - optimise collisions
}
default boolean noCollision(Entity entity) {
- return this.noCollision(entity, entity.getBoundingBox(), (e) -> {
- return true;
- });
+ // Paper start - optimise collisions
+ AABB box = entity.getBoundingBox();
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null);
+ // Paper end - optimise collisions
}
default boolean noCollision(Entity entity, AABB box) {
- return this.noCollision(entity, box, (e) -> {
- return true;
- });
+ // Paper start - optimise collisions
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null);
+ // Paper end - optimise collisions
}
default boolean noCollision(@Nullable Entity entity, AABB box, Predicate<Entity> filter) {
- try { if (entity != null) entity.collisionLoadChunks = true; // Paper
- return this.getCollisions(entity, box, filter).allMatch(VoxelShape::isEmpty);
- } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
+ // Paper start - optimise collisions
+ return !io.papermc.paper.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null)
+ && !io.papermc.paper.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, filter);
+ // Paper end - optimise collisions
}
Stream<VoxelShape> getEntityCollisions(@Nullable Entity entity, AABB box, Predicate<Entity> predicate);
diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
index 6124e3a32325e8c74bf839010a79d7c82c49aaff..cffb08bcb63f7ee2d8a163d865d87a9031f19407 100644
--- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
+++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
@@ -106,7 +106,7 @@ public class CollisionSpliterator extends AbstractSpliterator<VoxelShape> {
VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context);
if (voxelShape == Shapes.block()) {
- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) {
+ if (!io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Paper - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil
continue;
}
diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
index 94130509e3a7980c378cc95c46821cf0fc753ce6..7224c56e8a68870364c6538c82c04f371b74aabd 100644
--- a/src/main/java/net/minecraft/world/level/EntityGetter.java
+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
@@ -49,7 +49,7 @@ public interface EntityGetter {
return true;
} else {
for(Entity entity2 : this.getEntities(entity, shape.bounds())) {
- if (!entity2.isRemoved() && entity2.blocksBuilding && (entity == null || !entity2.isPassengerOfSameVehicle(entity)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity2.getBoundingBox()), BooleanOp.AND)) {
+ if (!entity2.isRemoved() && entity2.blocksBuilding && (entity == null || !entity2.isPassengerOfSameVehicle(entity)) && shape.intersects(entity2.getBoundingBox())) { // Paper
return false;
}
}
@@ -66,7 +66,7 @@ public interface EntityGetter {
if (box.getSize() < 1.0E-7D) {
return Stream.empty();
} else {
- AABB aABB = box.inflate(1.0E-7D);
+ AABB aABB = box.inflate(-1.0E-7D); // Paper - needs to be negated, or else we get things we don't collide with
Predicate<Entity> hardCollides = (entityx) -> { // Paper - optimise entity hard collisions
if (true || entityx.getBoundingBox().intersects(aABB)) { // Paper - always true
if (entity == null) {
diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
index 4a7fdea6a5f966db444dc41f7215faa99e3820b3..d87f8d106834678364f8720447d671de60c2454e 100644
--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java
@@ -685,7 +685,7 @@ public abstract class BlockBehaviour {
}
this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light
-
+ // TODO optimise light
}
public Block getBlock() {
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index 120498a39b7ca7aee9763084507508d4a1c425aa..68cc6f2a78a06293a29317fda72ab3ee79b3533a 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java
+++ b/src/main/java/net/minecraft/world/phys/AABB.java
@@ -25,6 +25,17 @@ public class AABB {
this.maxZ = Math.max(z1, z2);
}
+ // Paper start
+ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) {
+ this.minX = minX;
+ this.minY = minY;
+ this.minZ = minZ;
+ this.maxX = maxX;
+ this.maxY = maxY;
+ this.maxZ = maxZ;
+ }
+ // Paper end
+
public AABB(BlockPos pos) {
this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1));
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
index 99427b6130895ddecee8bcf77db72d809c24c375..61f5339042290eeaea54964cd3838d0bf4646cb2 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java
@@ -6,6 +6,9 @@ import java.util.Arrays;
import net.minecraft.Util;
import net.minecraft.core.Direction;
+// Paper start
+import it.unimi.dsi.fastutil.doubles.AbstractDoubleList;
+// Paper end
public class ArrayVoxelShape extends VoxelShape {
private final DoubleList xs;
private final DoubleList ys;
@@ -16,6 +19,11 @@ public class ArrayVoxelShape extends VoxelShape {
}
ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) {
+ // Paper start - optimise multi-aabb shapes
+ this(shape, xPoints, yPoints, zPoints, null, 0.0, 0.0, 0.0);
+ }
+ ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints, net.minecraft.world.phys.AABB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) {
+ // Paper end - optimise multi-aabb shapes
super(shape);
int i = shape.getXSize() + 1;
int j = shape.getYSize() + 1;
@@ -27,6 +35,12 @@ public class ArrayVoxelShape extends VoxelShape {
} else {
throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape."));
}
+ // Paper start - optimise multi-aabb shapes
+ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.toAabbs().toArray(EMPTY) : boundingBoxesRepresentation;
+ this.offsetX = offsetX;
+ this.offsetY = offsetY;
+ this.offsetZ = offsetZ;
+ // Paper end - optimise multi-aabb shapes
}
@Override
@@ -42,4 +56,152 @@ public class ArrayVoxelShape extends VoxelShape {
throw new IllegalArgumentException();
}
}
+
+ // Paper start
+ public static final class DoubleListOffsetExposed extends AbstractDoubleList {
+
+ public final DoubleArrayList list;
+ public final double offset;
+
+ public DoubleListOffsetExposed(final DoubleArrayList list, final double offset) {
+ this.list = list;
+ this.offset = offset;
+ }
+
+ @Override
+ public double getDouble(final int index) {
+ return this.list.getDouble(index) + this.offset;
+ }
+
+ @Override
+ public int size() {
+ return this.list.size();
+ }
+ }
+
+ static final net.minecraft.world.phys.AABB[] EMPTY = new net.minecraft.world.phys.AABB[0];
+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation;
+
+ final double offsetX;
+ final double offsetY;
+ final double offsetZ;
+
+ public final net.minecraft.world.phys.AABB[] getBoundingBoxesRepresentation() {
+ return this.boundingBoxesRepresentation;
+ }
+
+ public final double getOffsetX() {
+ return this.offsetX;
+ }
+
+ public final double getOffsetY() {
+ return this.offsetY;
+ }
+
+ public final double getOffsetZ() {
+ return this.offsetZ;
+ }
+
+ @Override
+ public java.util.List<net.minecraft.world.phys.AABB> toAabbs() {
+ if (this.boundingBoxesRepresentation == null) {
+ return super.toAabbs();
+ }
+ java.util.List<net.minecraft.world.phys.AABB> ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length);
+
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ ret.add(boundingBox.move(offX, offY, offZ));
+ }
+
+ return ret;
+ }
+
+ protected static DoubleArrayList getList(DoubleList from) {
+ if (from instanceof DoubleArrayList) {
+ return (DoubleArrayList)from;
+ } else {
+ return DoubleArrayList.wrap(from.toDoubleArray());
+ }
+ }
+
+ @Override
+ public VoxelShape move(double x, double y, double z) {
+ if (x == 0.0 && y == 0.0 && z == 0.0) {
+ return this;
+ }
+ DoubleListOffsetExposed xPoints, yPoints, zPoints;
+ double offsetX, offsetY, offsetZ;
+
+ if (this.xs instanceof DoubleListOffsetExposed) {
+ xPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.xs).list, offsetX = this.offsetX + x);
+ yPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.ys).list, offsetY = this.offsetY + y);
+ zPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.zs).list, offsetZ = this.offsetZ + z);
+ } else {
+ xPoints = new DoubleListOffsetExposed(getList(this.xs), offsetX = x);
+ yPoints = new DoubleListOffsetExposed(getList(this.ys), offsetY = y);
+ zPoints = new DoubleListOffsetExposed(getList(this.zs), offsetZ = z);
+ }
+
+ return new ArrayVoxelShape(this.shape, xPoints, yPoints, zPoints, this.boundingBoxesRepresentation, offsetX, offsetY, offsetZ);
+ }
+
+ @Override
+ public final boolean intersects(net.minecraft.world.phys.AABB axisalingedbb) {
+ // this can be optimised by checking an "overall shape" first, but not needed
+ double offX = this.offsetX;
+ double offY = this.offsetY;
+ double offZ = this.offsetZ;
+
+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ if (io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(axisalingedbb, boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ,
+ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void forAllBoxes(Shapes.DoubleLineConsumer doubleLineConsumer) {
+ if (this.boundingBoxesRepresentation == null) {
+ super.forAllBoxes(doubleLineConsumer);
+ return;
+ }
+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ doubleLineConsumer.consume(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ,
+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ);
+ }
+ }
+
+ @Override
+ public VoxelShape optimize() {
+ if (this == Shapes.empty() || this.boundingBoxesRepresentation.length == 0) {
+ return this;
+ }
+
+ VoxelShape simplified = Shapes.empty();
+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) {
+ simplified = Shapes.joinUnoptimized(simplified, Shapes.box(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ,
+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ), BooleanOp.OR);
+ }
+
+ if (!(simplified instanceof ArrayVoxelShape)) {
+ return simplified;
+ }
+
+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation = ((ArrayVoxelShape)simplified).getBoundingBoxesRepresentation();
+
+ if (boundingBoxesRepresentation.length == 1) {
+ return new io.papermc.paper.voxel.AABBVoxelShape(boundingBoxesRepresentation[0]).optimize();
+ }
+
+ return simplified;
+ }
+ // Paper end
+
}
diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
index 16bc18cacbf7a23fb744c8a12e7fd8da699b2fea..2fb416bd1d7a9879c13907b7e3c6b857fb1bf0ed 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
@@ -26,16 +26,17 @@ public final class Shapes {
DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1);
discreteVoxelShape.fill(0, 0, 0);
return new CubeVoxelShape(discreteVoxelShape);
- });
+ }); public static VoxelShape getFullUnoptimisedCube() { return BLOCK; } // Paper - OBFHELPER
public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})));
+ public static final io.papermc.paper.voxel.AABBVoxelShape BLOCK_OPTIMISED = new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); // Paper
public static VoxelShape empty() {
return EMPTY;
}
public static VoxelShape block() {
- return BLOCK;
+ return BLOCK_OPTIMISED; // Paper
}
public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
@@ -47,30 +48,11 @@ public final class Shapes {
}
public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
- if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) {
- int i = findBits(minX, maxX);
- int j = findBits(minY, maxY);
- int k = findBits(minZ, maxZ);
- if (i >= 0 && j >= 0 && k >= 0) {
- if (i == 0 && j == 0 && k == 0) {
- return block();
- } else {
- int l = 1 << i;
- int m = 1 << j;
- int n = 1 << k;
- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n));
- return new CubeVoxelShape(bitSetDiscreteVoxelShape);
- }
- } else {
- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ}));
- }
- } else {
- return empty();
- }
+ return new io.papermc.paper.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Paper
}
public static VoxelShape create(AABB box) {
- return create(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ);
+ return new io.papermc.paper.voxel.AABBVoxelShape(box); // Paper
}
@VisibleForTesting
@@ -132,6 +114,20 @@ public final class Shapes {
}
public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+ // Paper start - optimise voxelshape
+ if (predicate == BooleanOp.AND) {
+ if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape) {
+ return io.papermc.paper.util.CollisionUtil.voxelShapeIntersect(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb, ((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb);
+ } else if (shape1 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape2 instanceof ArrayVoxelShape) {
+ return ((ArrayVoxelShape)shape2).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape1).aabb);
+ } else if (shape2 instanceof io.papermc.paper.voxel.AABBVoxelShape && shape1 instanceof ArrayVoxelShape) {
+ return ((ArrayVoxelShape)shape1).intersects(((io.papermc.paper.voxel.AABBVoxelShape)shape2).aabb);
+ }
+ }
+ return joinIsNotEmptyVanilla(shape1, shape2, predicate);
+ }
+ public static boolean joinIsNotEmptyVanilla(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
+ // Paper end - optimise voxelshape
if (predicate.apply(false, false)) {
throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
} else {
@@ -285,6 +281,43 @@ public final class Shapes {
}
public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) {
+ // Paper start - optimise shape creation here for lighting, as this shape is going to be used
+ // for transparency checks
+ if (shape == BLOCK || shape == BLOCK_OPTIMISED) {
+ return BLOCK_OPTIMISED;
+ } else if (shape == empty()) {
+ return empty();
+ }
+
+ if (shape instanceof io.papermc.paper.voxel.AABBVoxelShape) {
+ final AABB box = ((io.papermc.paper.voxel.AABBVoxelShape)shape).aabb;
+ switch (direction) {
+ case WEST: // -X
+ case EAST: { // +X
+ final boolean useEmpty = direction == Direction.EAST ? !DoubleMath.fuzzyEquals(box.maxX, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minX, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(0.0, box.minY, box.minZ, 1.0, box.maxY, box.maxZ)).optimize();
+ }
+ case DOWN: // -Y
+ case UP: { // +Y
+ final boolean useEmpty = direction == Direction.UP ? !DoubleMath.fuzzyEquals(box.maxY, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minY, 0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, 0.0, box.minZ, box.maxX, 1.0, box.maxZ)).optimize();
+ }
+ case NORTH: // -Z
+ case SOUTH: { // +Z
+ final boolean useEmpty = direction == Direction.SOUTH ? !DoubleMath.fuzzyEquals(box.maxZ, 1.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON) :
+ !DoubleMath.fuzzyEquals(box.minZ,0.0, io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON);
+ return useEmpty ? empty() : new io.papermc.paper.voxel.AABBVoxelShape(new AABB(box.minX, box.minY, 0.0, box.maxX, box.maxY, 1.0)).optimize();
+ }
+ }
+ }
+
+ // fall back to vanilla
+ return getFaceShapeVanilla(shape, direction);
+ }
+ public static VoxelShape getFaceShapeVanilla(VoxelShape shape, Direction direction) {
+ // Paper end
if (shape == block()) {
return block();
} else {
@@ -299,7 +332,7 @@ public final class Shapes {
i = 0;
}
- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i));
+ return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i).optimize().optimize()); // Paper - first optimize converts to ArrayVoxelShape, second optimize could convert to AABBVoxelShape
}
}
@@ -324,6 +357,53 @@ public final class Shapes {
}
public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) {
+ // Paper start - try to optimise for the case where the shapes do _not_ occlude
+ // which is _most_ of the time in lighting
+ if (one == getFullUnoptimisedCube() || one == BLOCK_OPTIMISED
+ || two == getFullUnoptimisedCube() || two == BLOCK_OPTIMISED) {
+ return true;
+ }
+ boolean v1Empty = one == empty();
+ boolean v2Empty = two == empty();
+ if (v1Empty && v2Empty) {
+ return false;
+ }
+ if ((one instanceof io.papermc.paper.voxel.AABBVoxelShape || v1Empty)
+ && (two instanceof io.papermc.paper.voxel.AABBVoxelShape || v2Empty)) {
+ if (!v1Empty && !v2Empty && (one != two)) {
+ AABB boundingBox1 = ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb;
+ AABB boundingBox2 = ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb;
+ // can call it here in some cases
+
+ // check overall bounding box
+ double minY = Math.min(boundingBox1.minY, boundingBox2.minY);
+ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY);
+ if (minY > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxY < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ double minX = Math.min(boundingBox1.minX, boundingBox2.minX);
+ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX);
+ if (minX > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxX < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ);
+ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ);
+ if (minZ > io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON || maxZ < (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) {
+ return false;
+ }
+ // fall through to full merge check
+ } else {
+ AABB boundingBox = v1Empty ? ((io.papermc.paper.voxel.AABBVoxelShape)two).aabb : ((io.papermc.paper.voxel.AABBVoxelShape)one).aabb;
+ // check if the bounding box encloses the full cube
+ return (boundingBox.minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+ (boundingBox.minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+ (boundingBox.minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON));
+ }
+ }
+ return faceShapeOccludesVanilla(one, two);
+ }
+ public static boolean faceShapeOccludesVanilla(VoxelShape one, VoxelShape two) {
+ // Paper end
if (one != block() && two != block()) {
if (one.isEmpty() && two.isEmpty()) {
return false;
diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
index f325d76c79d63629200262a77eab7cdcc9beedfa..0ab742f38f79c150630bb9ba153d92d864aface1 100644
--- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java
@@ -16,11 +16,17 @@ import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
public abstract class VoxelShape {
- protected final DiscreteVoxelShape shape;
+ public final DiscreteVoxelShape shape; // Paper - public
@Nullable
private VoxelShape[] faces;
- VoxelShape(DiscreteVoxelShape voxels) {
+ // Paper start
+ public boolean intersects(AABB shape) {
+ return Shapes.joinIsNotEmpty(this, new io.papermc.paper.voxel.AABBVoxelShape(shape), BooleanOp.AND);
+ }
+ // Paper end
+
+ protected VoxelShape(DiscreteVoxelShape voxels) { // Paper - protected
this.shape = voxels;
}
@@ -163,7 +169,7 @@ public abstract class VoxelShape {
}
}
- private VoxelShape calculateFace(Direction direction) {
+ protected VoxelShape calculateFace(Direction direction) { // Paper
Direction.Axis axis = direction.getAxis();
DoubleList doubleList = this.getCoords(axis);
if (doubleList.size() == 2 && DoubleMath.fuzzyEquals(doubleList.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0D, 1.0E-7D)) {