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

Bukkit Changes:
6680169e [#660/Enum] Merge remote-tracking branch 'origin/pr/660' into experimental
8b97f215 Add missing AbstractTestingBase extension
9f21f42b [#660/Enum] Merge remote-tracking branch 'origin/pr/660' into experimental
fb59a4a0 Create experimental version
a7c1393b Merge branch 'master' into enums-to-registers
1af01165 Merge branch 'master' into enums-to-registers
4ee82e4e Implement feedback
af8ffd60 Merge branch 'master' into enums-to-registers
6a8ea63f Updated to 1.20
22ae9ebc Merge branch 'master' into enums-to-registers
b1d669be Some clean up
685d812e Merge branch 'master' into enums-to-registers
00d778c7 Convert MusicInstrument
bba2eb5f Convert GameEvent
ffbf67a1 Convert PotionType
eacaa45d Convert Particle
d08d21d1 Convert PatternType
c6b51f7c Convert Cat type
af6c2987 Make missing Frog variant abstract
a67a5f5c Add missing Annotation
6ab21c50 Change how converting from / to BlockType and ItemType work
e3e84e69 Add Objects.requireNonNull so that there are not marked as nullable
fceddab5 Add missing deprecation
e91906f5 Don't convert legacy in register instead, only in required method
2200b334 Use static constructors for ItemStack
b5f483b0 Deprecated Material
a995df2a Fix typo getItemTyp
9cedb664 import ItemType
27e282b2 getSteerItemType -> getSteerItem
d8d0e43b Better Piglin method names
3a2ab399 BLOCK_TYPE -> BLOCK, ITEM_TYPE -> ITEM
a0eb63ac Interface it is
4bb0b646 Split Material into BlockType and ItemType
b6bfcff5 Merge branch 'master' into enums-to-registers
1f86c847 Updated to Mockito 5.3.1
280ee1f7 Fix merge, updated to 1.19.4
9e0c7ad5 Merge branch 'master' into enums-to-registers
fdbed698 Updated to 1.19.3
85c3e2d3 Merge branch 'master' into enums-to-registers
b2c390af Fix merge
4e405647 Merge branch 'master' into enums-to-registers
d01b4c90 Fix copy/paste
ba2c8cb1 Update to 1.19
7e4f2db2 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
a1a974f0 Fix merge
7d3a91d3 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
499e22d9 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
a0cf419f Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
d5bd36a2 Fix / Implement merge changes
fe643952 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
cf1d2005 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
f18dce93 Make Statistic abstract
bbe3f791 Fix 1.18 merge Handle comment out test cases
0988647e Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
6e4f2c50 Populated BlockType and ItemType
7a58144d Convert Material enum, midpoint push, it compiles and runs
4771132c Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/bukkit into enums-to-registers
e6b179ff Convert Material enum, midpoint push to merge BlockState changes
f33b85a0 Change other enums / classes.
8b0d5418 Create seperat OldEnum class, which holds common enum methods.
45544426 Change Enums to classes to easier handle none standart minecraft values

CraftBukkit Changes:
8969b32d0 [#931/Enum] Merge remote-tracking branch 'origin/pr/931' into experimental
222257a67 Add missing AbstractTestingBase extension
0480af399 [#931/Enum] Merge remote-tracking branch 'origin/pr/931' into experimental
1afa1ddc2 Create experimental version
00780ea51 Ignore FactoryItemMaterialTest test
676969d01 SPIGOT-7389: Handle setting null items in ChiseledBookshelf Inventory
84f10cc36 Fix merge
9e114e13e Merge branch 'master' into enums-to-registers
941787e24 Add missing Commodore for 1.20 updated
6dac9a12d Updated tests
737426398 Only allow reference holder
aaaa5fa88 Merge branch 'master' into enums-to-registers
74957eb99 Merge branch 'master' into enums-to-registers
a1ca4e870 Merge branch 'master' into enums-to-registers
f293f4a61 Updated to 1.20
b434b3d15 Merge branch 'master' into enums-to-registers
e99dcbda7 Some clean up
fcead8aed Use correct primitive class
e955d9c50 Fix some Commodore errors
af5526ebb Allow Material to support older plugins
b83afd643 Add rewrite for Google enum set methods
067323765 Add missing method replacement in dynamic invocation
63e17e631 Merge branch 'master' into enums-to-registers
26dfcacf8 Bad copy and past
d50c9bd6a Convert MusicInstrument
c0c5312db Convert GameEvent
39daffe2c Convert PotionType
9b974f832 Convert Particle
f528fca63 Convert PatternType
525c65006 Convert Cat type
6832b8fbb More consistent to / from bukkit / minecraft methods
d31e38e16 Make missing Frog variant abstract
e4f0e7d8e This shouldn't be committed
6fee81baa Add Commodore for EnumSet
82a668683 Fix hasItemType / getItemType
f70162d66 Change how converting from / to BlockType and ItemType work
c3f7c7886 Don't convert legacy in register instead, only in required method
2039e05fa Use static constructors for ItemStack
fe221578b More Commodore
2b70bd171 More Commodore
70f4a89f5 Fix some Commodore
06544ed4b Fix typo getItemTyp
6269d2e42 getSteerItemType -> getSteerItem
a19ac46c0 Better Piglin method names
eef5f52c6 BLOCK_TYPE -> BLOCK, ITEM_TYPE -> ITEM
bbaff1348 Interface it is
c39e1316c Finish Commodore action for Material split
dd8552105 Work on Commodore
1d4ef8bf2 Split Material into BlockType and ItemType
6c5a98220 Merge branch 'master' into enums-to-registers
869658a96 Handle Material calls in lambda expression
523ac4ac0 Add reroute for Class#getEnumConstants
0a4463279 Use extra method for getting registry
602d9b404 Updated to Mockito 5.3.1
8ff87b77d Fix merge, updated to 1.19.4
9d739d313 Merge branch 'master' into enums-to-registers
eb6f702ff Reduce usage of BuiltInRegistries
b6f667cac Some more asm compatibility changes, add config option
87c931d38 Handle enum maps
a2c6699db Updated to 1.19.3
f7c27584f Merge branch 'master' into enums-to-registers
2f95b9951 Fix merge
184b05740 Merge branch 'master' into enums-to-registers
12bd8de26 Updated to 1.19
9c57831b7 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
4ed8eb402 Fix merge
a9faac8e4 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
0d2988603 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
3f8f9557d Fix merge, updated to 1.18.2
1560490c6 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
a0e4eb12c Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
8b59f682d Move getType method to CraftEntity class
b849c0147 Add missing patches
4644ba79f Fix / Implement merge changes
cf9ee732e Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
0c9125b67 Use Tag where possible
cc05153d9 Cache interactable call
ab5cc36de Use getHandle
83ebf4114 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
bc20aea0c Make Statistic abstract
3faa7e135 Add Tests for Material BlockType and ItemType
e10f74365 Fix 1.18 merge Handle comment out test cases
f72f70ec4 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
dbf4f5b7e Populated BlockType and ItemType
015afc1bc Convert Material enum, midpoint push, it compiles and runs
cc0112866 Merge branch 'master' of https://hub.spigotmc.org/stash/scm/~derfrzocker/craftbukkit into enums-to-registers
e26742c59 Convert Material enum, midpoint push to merge BlockState changes
796ad9295 Fix bug in legacy naming converting.
199c8278c Change other enums / classes.
fd513652a Seperated custom biome value handling. Fix compareTo call.
60c71ce07 Change Enums to classes to easier handle none standart minecraft values

Spigot Changes:
addcf45f [Enum] Rebuild patches
2023-06-20 09:50:05 -07:00

2122 lines
104 KiB
Diff

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