--- a/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
+++ b/net/minecraft/world/phys/shapes/BitSetDiscreteVoxelShape.java
@@ -4,13 +4,13 @@
 import net.minecraft.core.Direction;
 
 public final class BitSetDiscreteVoxelShape extends DiscreteVoxelShape {
-    private final BitSet storage;
-    private int xMin;
-    private int yMin;
-    private int zMin;
-    private int xMax;
-    private int yMax;
-    private int zMax;
+    public final BitSet storage; // Paper - optimise collisions - public
+    public int xMin; // Paper - optimise collisions - public
+    public int yMin; // Paper - optimise collisions - public
+    public int zMin; // Paper - optimise collisions - public
+    public int xMax; // Paper - optimise collisions - public
+    public int yMax; // Paper - optimise collisions - public
+    public int zMax; // Paper - optimise collisions - public
 
     public BitSetDiscreteVoxelShape(int sizeX, int sizeY, int sizeZ) {
         super(sizeX, sizeY, sizeZ);
@@ -150,47 +150,109 @@
         return bitSetDiscreteVoxelShape;
     }
 
-    protected static void forAllBoxes(DiscreteVoxelShape voxelSet, DiscreteVoxelShape.IntLineConsumer callback, boolean coalesce) {
-        BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = new BitSetDiscreteVoxelShape(voxelSet);
+    // Paper start - optimise collisions
+    public static void forAllBoxes(final DiscreteVoxelShape shape, final DiscreteVoxelShape.IntLineConsumer consumer, final boolean mergeAdjacent) {
+        // Paper - remove debug
+        // called with the shape of a VoxelShape, so we can expect the cache to exist
+        final ca.spottedleaf.moonrise.patches.collisions.shape.CachedShapeData cache = ((ca.spottedleaf.moonrise.patches.collisions.shape.CollisionDiscreteVoxelShape) shape).moonrise$getOrCreateCachedShapeData();
 
-        for (int i = 0; i < bitSetDiscreteVoxelShape.ySize; i++) {
-            for (int j = 0; j < bitSetDiscreteVoxelShape.xSize; j++) {
-                int k = -1;
+        final int sizeX = cache.sizeX();
+        final int sizeY = cache.sizeY();
+        final int sizeZ = cache.sizeZ();
 
-                for (int l = 0; l <= bitSetDiscreteVoxelShape.zSize; l++) {
-                    if (bitSetDiscreteVoxelShape.isFullWide(j, i, l)) {
-                        if (coalesce) {
-                            if (k == -1) {
-                                k = l;
-                            }
-                        } else {
-                            callback.consume(j, i, l, j + 1, i + 1, l + 1);
+        int indexX;
+        int indexY = 0;
+        int indexZ;
+
+        int incY = sizeZ;
+        int incX = sizeZ * sizeY;
+
+        long[] bitset = cache.voxelSet();
+
+        // index = z + y*size_z + x*(size_z*size_y)
+
+        if (!mergeAdjacent) {
+            // due to the odd selection of loop order (which does affect behavior, unfortunately) we can't simply
+            // increment an index in the Z loop, and have to perform this trash (keeping track of 3 counters) to avoid
+            // the multiplication
+            for (int y = 0; y < sizeY; ++y, indexY += incY) {
+                indexX = indexY;
+                for (int x = 0; x < sizeX; ++x, indexX += incX) {
+                    indexZ = indexX;
+                    for (int z = 0; z < sizeZ; ++z, ++indexZ) {
+                        if ((bitset[indexZ >>> 6] & (1L << indexZ)) != 0L) {
+                            consumer.consume(x, y, z, x + 1, y + 1, z + 1);
                         }
-                    } else if (k != -1) {
-                        int m = j;
-                        int n = i;
-                        bitSetDiscreteVoxelShape.clearZStrip(k, l, j, i);
+                    }
+                }
+            }
+        } else {
+            // same notes about loop order as the above
+            // this branch is actually important to optimise, as it affects uncached toAabbs() (which affects optimize())
 
-                        while (bitSetDiscreteVoxelShape.isZStripFull(k, l, m + 1, i)) {
-                            bitSetDiscreteVoxelShape.clearZStrip(k, l, m + 1, i);
-                            m++;
+            // only clone when we may write to it
+            bitset = ca.spottedleaf.moonrise.common.util.MixinWorkarounds.clone(bitset);
+
+            for (int y = 0; y < sizeY; ++y, indexY += incY) {
+                indexX = indexY;
+                for (int x = 0; x < sizeX; ++x, indexX += incX) {
+                    for (int zIdx = indexX, endIndex = indexX + sizeZ; zIdx < endIndex; ) {
+                        final int firstSetZ = ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.firstSet(bitset, zIdx, endIndex);
+
+                        if (firstSetZ == -1) {
+                            break;
                         }
 
-                        while (bitSetDiscreteVoxelShape.isXZRectangleFull(j, m + 1, k, l, n + 1)) {
-                            for (int o = j; o <= m; o++) {
-                                bitSetDiscreteVoxelShape.clearZStrip(k, l, o, n + 1);
+                        int lastSetZ = ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.firstClear(bitset, firstSetZ, endIndex);
+                        if (lastSetZ == -1) {
+                            lastSetZ = endIndex;
+                        }
+
+                        ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, firstSetZ, lastSetZ);
+
+                        // try to merge neighbouring on the X axis
+                        int endX = x + 1; // exclusive
+                        for (int neighbourIdxStart = firstSetZ + incX, neighbourIdxEnd = lastSetZ + incX;
+                             endX < sizeX && ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, neighbourIdxStart, neighbourIdxEnd);
+                             neighbourIdxStart += incX, neighbourIdxEnd += incX) {
+
+                            ++endX;
+                            ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, neighbourIdxStart, neighbourIdxEnd);
+                        }
+
+                        // try to merge neighbouring on the Y axis
+
+                        int endY; // exclusive
+                        int firstSetZY, lastSetZY;
+                        y_merge:
+                        for (endY = y + 1, firstSetZY = firstSetZ + incY, lastSetZY = lastSetZ + incY; endY < sizeY;
+                             firstSetZY += incY, lastSetZY += incY) {
+
+                            // test the whole XZ range
+                            for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
+                                 ++testX, start += incX, end += incX) {
+                                if (!ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.isRangeSet(bitset, start, end)) {
+                                    break y_merge;
+                                }
                             }
 
-                            n++;
+                            ++endY;
+
+                            // passed, so we can clear it
+                            for (int testX = x, start = firstSetZY, end = lastSetZY; testX < endX;
+                                 ++testX, start += incX, end += incX) {
+                                ca.spottedleaf.moonrise.common.util.FlatBitsetUtil.clearRange(bitset, start, end);
+                            }
                         }
 
-                        callback.consume(j, i, k, m + 1, n + 1, l);
-                        k = -1;
+                        consumer.consume(x, y, firstSetZ - indexX, endX, endY, lastSetZ - indexX);
+                        zIdx = lastSetZ;
                     }
                 }
             }
         }
     }
+    // Paper end - optimise collisions
 
     private boolean isZStripFull(int z1, int z2, int x, int y) {
         return x < this.xSize && y < this.ySize && this.storage.nextClearBit(this.getIndex(x, y, z1)) >= this.getIndex(x, y, z2);
