--- a/net/minecraft/world/phys/shapes/Shapes.java
+++ b/net/minecraft/world/phys/shapes/Shapes.java
@@ -16,13 +16,43 @@
     public static final double EPSILON = 1.0E-7D;
     public static final double BIG_EPSILON = 1.0E-6D;
     private static final VoxelShape BLOCK = Util.make(() -> {
-        DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1);
-        discreteVoxelShape.fill(0, 0, 0);
-        return new CubeVoxelShape(discreteVoxelShape);
+        // Paper start - optimise collisions - force arrayvoxelshape
+        final DiscreteVoxelShape shape = new BitSetDiscreteVoxelShape(1, 1, 1);
+        shape.fill(0, 0, 0);
+
+        return new ArrayVoxelShape(
+            shape,
+            io.papermc.paper.util.CollisionUtil.ZERO_ONE, io.papermc.paper.util.CollisionUtil.ZERO_ONE, io.papermc.paper.util.CollisionUtil.ZERO_ONE
+        );
+        // Paper end - optimise collisions - force arrayvoxelshape
     });
     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})));
 
+    // Paper start - optimise collisions - force arrayvoxelshape
+    private static final DoubleArrayList[] PARTS_BY_BITS = new DoubleArrayList[] {
+            DoubleArrayList.wrap(generateCubeParts(1 << 0)),
+            DoubleArrayList.wrap(generateCubeParts(1 << 1)),
+            DoubleArrayList.wrap(generateCubeParts(1 << 2)),
+            DoubleArrayList.wrap(generateCubeParts(1 << 3))
+    };
+
+    private static double[] generateCubeParts(final int parts) {
+        // note: parts is a power of two, so we do not need to worry about loss of precision here
+        // note: parts is from [2^0, 2^3]
+        final double inc = 1.0 / (double)parts;
+
+        final double[] ret = new double[parts + 1];
+        double val = 0.0;
+        for (int i = 0; i <= parts; ++i) {
+            ret[i] = val;
+            val += inc;
+        }
+
+        return ret;
+    }
+    // Paper end - optimise collisions - force arrayvoxelshape
+
     public static VoxelShape empty() {
         return EMPTY;
     }
@@ -41,22 +71,39 @@
 
     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();
+            // Paper start - optimise collisions
+            // force ArrayVoxelShape in every case
+            final int bitsX = findBits(minX, maxX);
+            final int bitsY = findBits(minY, maxY);
+            final int bitsZ = findBits(minZ, maxZ);
+            if (bitsX >= 0 && bitsY >= 0 && bitsZ >= 0) {
+                if (bitsX == 0 && bitsY == 0 && bitsZ == 0) {
+                    return BLOCK;
                 } else {
-                    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);
+                    final int sizeX = 1 << bitsX;
+                    final int sizeY = 1 << bitsY;
+                    final int sizeZ = 1 << bitsZ;
+                    final BitSetDiscreteVoxelShape shape = BitSetDiscreteVoxelShape.withFilledBounds(
+                            sizeX, sizeY, sizeZ,
+                            (int)Math.round(minX * (double)sizeX), (int)Math.round(minY * (double)sizeY), (int)Math.round(minZ * (double)sizeZ),
+                            (int)Math.round(maxX * (double)sizeX), (int)Math.round(maxY * (double)sizeY), (int)Math.round(maxZ * (double)sizeZ)
+                    );
+                    return new ArrayVoxelShape(
+                            shape,
+                            PARTS_BY_BITS[bitsX],
+                            PARTS_BY_BITS[bitsY],
+                            PARTS_BY_BITS[bitsZ]
+                    );
                 }
             } else {
-                return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ}));
+                return new ArrayVoxelShape(
+                        BLOCK.shape,
+                        minX == 0.0 && maxX == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minX, maxX }),
+                        minY == 0.0 && maxY == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minY, maxY }),
+                        minZ == 0.0 && maxZ == 1.0 ? io.papermc.paper.util.CollisionUtil.ZERO_ONE : DoubleArrayList.wrap(new double[] { minZ, maxZ })
+                );
             }
+            // Paper end - optimise collisions
         } else {
             return empty();
         }
@@ -95,67 +142,53 @@
     }
 
     public static VoxelShape or(VoxelShape first, VoxelShape... others) {
-        return Arrays.stream(others).reduce(first, Shapes::or);
+        // Paper start - optimise collisions
+        int size = others.length;
+        if (size == 0) {
+            return first;
+        }
+
+        // reduce complexity of joins by splitting the merges
+
+        // add extra slot for first shape
+        ++size;
+        final VoxelShape[] tmp = Arrays.copyOf(others, size);
+        // insert first shape
+        tmp[size - 1] = first;
+
+        while (size > 1) {
+            int newSize = 0;
+            for (int i = 0; i < size; i += 2) {
+                final int next = i + 1;
+                if (next >= size) {
+                    // nothing to merge with, so leave it for next iteration
+                    tmp[newSize++] = tmp[i];
+                    break;
+                } else {
+                    // merge with adjacent
+                    final VoxelShape one = tmp[i];
+                    final VoxelShape second = tmp[next];
+
+                    tmp[newSize++] = Shapes.or(one, second);
+                }
+            }
+            size = newSize;
+        }
+
+        return tmp[0];
+        // Paper end - optimise collisions
     }
 
     public static VoxelShape join(VoxelShape first, VoxelShape second, BooleanOp function) {
-        return joinUnoptimized(first, second, function).optimize();
+        return io.papermc.paper.util.CollisionUtil.joinOptimized(first, second, function); // Paper - optimise collisions
     }
 
     public static VoxelShape joinUnoptimized(VoxelShape one, VoxelShape two, BooleanOp function) {
-        if (function.apply(false, false)) {
-            throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
-        } else if (one == two) {
-            return function.apply(true, true) ? one : empty();
-        } else {
-            boolean bl = function.apply(true, false);
-            boolean bl2 = function.apply(false, true);
-            if (one.isEmpty()) {
-                return bl2 ? two : empty();
-            } else if (two.isEmpty()) {
-                return bl ? one : empty();
-            } else {
-                IndexMerger indexMerger = createIndexMerger(1, one.getCoords(Direction.Axis.X), two.getCoords(Direction.Axis.X), bl, bl2);
-                IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, one.getCoords(Direction.Axis.Y), two.getCoords(Direction.Axis.Y), bl, bl2);
-                IndexMerger indexMerger3 = createIndexMerger((indexMerger.size() - 1) * (indexMerger2.size() - 1), one.getCoords(Direction.Axis.Z), two.getCoords(Direction.Axis.Z), bl, bl2);
-                BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join(one.shape, two.shape, indexMerger, indexMerger2, indexMerger3, function);
-                return (VoxelShape)(indexMerger instanceof DiscreteCubeMerger && indexMerger2 instanceof DiscreteCubeMerger && indexMerger3 instanceof DiscreteCubeMerger ? new CubeVoxelShape(bitSetDiscreteVoxelShape) : new ArrayVoxelShape(bitSetDiscreteVoxelShape, indexMerger.getList(), indexMerger2.getList(), indexMerger3.getList()));
-            }
-        }
+        return io.papermc.paper.util.CollisionUtil.joinUnoptimized(one, two, function); // Paper - optimise collisions
     }
 
     public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) {
-        if (predicate.apply(false, false)) {
-            throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException());
-        } else {
-            boolean bl = shape1.isEmpty();
-            boolean bl2 = shape2.isEmpty();
-            if (!bl && !bl2) {
-                if (shape1 == shape2) {
-                    return predicate.apply(true, true);
-                } else {
-                    boolean bl3 = predicate.apply(true, false);
-                    boolean bl4 = predicate.apply(false, true);
-
-                    for(Direction.Axis axis : AxisCycle.AXIS_VALUES) {
-                        if (shape1.max(axis) < shape2.min(axis) - 1.0E-7D) {
-                            return bl3 || bl4;
-                        }
-
-                        if (shape2.max(axis) < shape1.min(axis) - 1.0E-7D) {
-                            return bl3 || bl4;
-                        }
-                    }
-
-                    IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4);
-                    IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4);
-                    IndexMerger indexMerger3 = createIndexMerger((indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4);
-                    return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, predicate);
-                }
-            } else {
-                return predicate.apply(!bl, !bl2);
-            }
-        }
+        return io.papermc.paper.util.CollisionUtil.isJoinNonEmpty(shape1, shape2, predicate); // Paper - optimise collisions
     }
 
     private static boolean joinIsNotEmpty(IndexMerger mergedX, IndexMerger mergedY, IndexMerger mergedZ, DiscreteVoxelShape shape1, DiscreteVoxelShape shape2, BooleanOp predicate) {
@@ -181,75 +214,137 @@
     }
 
     public static boolean blockOccudes(VoxelShape shape, VoxelShape neighbor, Direction direction) {
-        if (shape == block() && neighbor == block()) {
+        // Paper start - optimise collisions
+        final boolean firstBlock = shape == BLOCK;
+        final boolean secondBlock = neighbor == BLOCK;
+
+        if (firstBlock & secondBlock) {
             return true;
-        } else if (neighbor.isEmpty()) {
+        }
+
+        if (shape.isEmpty() | neighbor.isEmpty()) {
+            return false;
+        }
+
+        // we optimise getOpposite, so we can use it
+        // secondly, use our cache to retrieve sliced shape
+        final VoxelShape newFirst = shape.getFaceShapeClamped(direction);
+        if (newFirst.isEmpty()) {
             return false;
-        } else {
-            Direction.Axis axis = direction.getAxis();
-            Direction.AxisDirection axisDirection = direction.getAxisDirection();
-            VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : neighbor;
-            VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? neighbor : shape;
-            BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND;
-            return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0D, 1.0E-7D) && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp);
         }
+        final VoxelShape newSecond = neighbor.getFaceShapeClamped(direction.getOpposite());
+        if (newSecond.isEmpty()) {
+            return false;
+        }
+
+        return !joinIsNotEmpty(newFirst, newSecond, BooleanOp.ONLY_FIRST);
+        // Paper end - optimise collisions
     }
 
     public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) {
-        if (shape == block()) {
-            return block();
-        } else {
-            Direction.Axis axis = direction.getAxis();
-            boolean bl;
-            int i;
-            if (direction.getAxisDirection() == Direction.AxisDirection.POSITIVE) {
-                bl = DoubleMath.fuzzyEquals(shape.max(axis), 1.0D, 1.0E-7D);
-                i = shape.shape.getSize(axis) - 1;
-            } else {
-                bl = DoubleMath.fuzzyEquals(shape.min(axis), 0.0D, 1.0E-7D);
-                i = 0;
-            }
+        return shape.getFaceShapeClamped(direction); // Paper - optimise collisions
+    }
 
-            return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i));
-        }
+    // Paper start - optimise collisions
+    private static boolean mergedMayOccludeBlock(final VoxelShape shape1, final VoxelShape shape2) {
+        // if the combined bounds of the two shapes cannot occlude, then neither can the merged
+        final AABB bounds1 = shape1.bounds();
+        final AABB bounds2 = shape2.bounds();
+
+        final double minX = Math.min(bounds1.minX, bounds2.minX);
+        final double minY = Math.min(bounds1.minY, bounds2.minY);
+        final double minZ = Math.min(bounds1.minZ, bounds2.minZ);
+
+        final double maxX = Math.max(bounds1.maxX, bounds2.maxX);
+        final double maxY = Math.max(bounds1.maxY, bounds2.maxY);
+        final double maxZ = Math.max(bounds1.maxZ, bounds2.maxZ);
+
+        return (minX <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxX >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+            (minY <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxY >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON)) &&
+            (minZ <= io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON && maxZ >= (1 - io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON));
     }
+    // Paper end - optimise collisions
 
     public static boolean mergedFaceOccludes(VoxelShape one, VoxelShape two, Direction direction) {
-        if (one != block() && two != block()) {
-            Direction.Axis axis = direction.getAxis();
-            Direction.AxisDirection axisDirection = direction.getAxisDirection();
-            VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? one : two;
-            VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? two : one;
-            if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0D, 1.0E-7D)) {
-                voxelShape = empty();
-            }
+        // Paper start - optimise collisions
+        // see if any of the shapes on their own occludes, only if cached
+        if (one.occludesFullBlockIfCached() || two.occludesFullBlockIfCached()) {
+            return true;
+        }
 
-            if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0D, 1.0E-7D)) {
-                voxelShape2 = empty();
-            }
+        if (one.isEmpty() & two.isEmpty()) {
+            return false;
+        }
 
-            return !joinIsNotEmpty(block(), joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR), BooleanOp.ONLY_FIRST);
-        } else {
+        // we optimise getOpposite, so we can use it
+        // secondly, use our cache to retrieve sliced shape
+        final VoxelShape newFirst = one.getFaceShapeClamped(direction);
+        final VoxelShape newSecond = two.getFaceShapeClamped(direction.getOpposite());
+
+        // see if any of the shapes on their own occludes, only if cached
+        if (newFirst.occludesFullBlockIfCached() || newSecond.occludesFullBlockIfCached()) {
             return true;
         }
+
+        final boolean firstEmpty = newFirst.isEmpty();
+        final boolean secondEmpty = newSecond.isEmpty();
+
+        if (firstEmpty & secondEmpty) {
+            return false;
+        }
+
+        if (firstEmpty | secondEmpty) {
+            return secondEmpty ? newFirst.occludesFullBlock() : newSecond.occludesFullBlock();
+        }
+
+        if (newFirst == newSecond) {
+            return newFirst.occludesFullBlock();
+        }
+
+        return mergedMayOccludeBlock(newFirst, newSecond) && newFirst.orUnoptimized(newSecond).occludesFullBlock();
+        // Paper end - optimise collisions
     }
 
     public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) {
-        if (one != block() && two != block()) {
-            if (one.isEmpty() && two.isEmpty()) {
-                return false;
-            } else {
-                return !joinIsNotEmpty(block(), joinUnoptimized(one, two, BooleanOp.OR), BooleanOp.ONLY_FIRST);
-            }
-        } else {
+        // Paper start - optimise collisions
+        if (one.occludesFullBlockIfCached() || two.occludesFullBlockIfCached()) {
             return true;
         }
+
+        final boolean s1Empty = one.isEmpty();
+        final boolean s2Empty = two.isEmpty();
+        if (s1Empty & s2Empty) {
+            return false;
+        }
+
+        if (s1Empty | s2Empty) {
+            return s2Empty ? one.occludesFullBlock() : two.occludesFullBlock();
+        }
+
+        if (one == two) {
+            return one.occludesFullBlock();
+        }
+
+        return mergedMayOccludeBlock(one, two) && (one.orUnoptimized(two)).occludesFullBlock();
+        // Paper end - optimise collisions
     }
 
     @VisibleForTesting
-    protected static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
+    private static IndexMerger createIndexMerger(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) { // Paper - private
+        // Paper start - fast track the most common scenario
+        // doublelist is usually a DoubleArrayList with Infinite head/tails that falls to the final else clause
+        // This is actually the most common path, so jump to it straight away
+        if (first.getDouble(0) == Double.NEGATIVE_INFINITY && first.getDouble(first.size() - 1) == Double.POSITIVE_INFINITY) {
+            return new IndirectMerger(first, second, includeFirst, includeSecond);
+        }
+        // Split out rest to hopefully inline the above
+        return lessCommonMerge(size, first, second, includeFirst, includeSecond);
+    }
+
+    private static IndexMerger lessCommonMerge(int size, DoubleList first, DoubleList second, boolean includeFirst, boolean includeSecond) {
         int i = first.size() - 1;
         int j = second.size() - 1;
+        // Paper note - Rewrite below as optimized order if instead of nasty ternary
         if (first instanceof CubePointRange && second instanceof CubePointRange) {
             long l = lcm(i, j);
             if ((long)size * l <= 256L) {
@@ -257,13 +352,22 @@
             }
         }
 
-        if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) {
+        // Paper start - Identical happens more often than Disjoint
+        if (i == j && Objects.equals(first, second)) {
+            if (first instanceof IdenticalMerger) {
+                return (IndexMerger) first;
+            } else if (second instanceof IdenticalMerger) {
+                return (IndexMerger) second;
+            }
+            return new IdenticalMerger(first);
+        } else if (first.getDouble(i) < second.getDouble(0) - 1.0E-7D) {
             return new NonOverlappingMerger(first, second, false);
         } else if (second.getDouble(j) < first.getDouble(0) - 1.0E-7D) {
             return new NonOverlappingMerger(second, first, true);
         } else {
-            return (IndexMerger)(i == j && Objects.equals(first, second) ? new IdenticalMerger(first) : new IndirectMerger(first, second, includeFirst, includeSecond));
+            return new IndirectMerger(first, second, includeFirst, includeSecond);
         }
+        // Paper end
     }
 
     public interface DoubleLineConsumer {
