package net.minecraft.world.phys.shapes; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; import com.google.common.math.DoubleMath; import com.google.common.math.IntMath; import com.mojang.math.OctahedralGroup; import com.mojang.math.Quadrant; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import it.unimi.dsi.fastutil.doubles.DoubleList; import java.util.Arrays; import java.util.Map; import java.util.Objects; import net.minecraft.Util; import net.minecraft.core.AxisCycle; import net.minecraft.core.Direction; import net.minecraft.world.level.block.state.properties.AttachFace; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; public final class Shapes { public static final double EPSILON = 1.0E-7; public static final double BIG_EPSILON = 1.0E-6; private static final VoxelShape BLOCK = Util.make(() -> { DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); discreteVoxelShape.fill(0, 0, 0); return new CubeVoxelShape(discreteVoxelShape); }); private static final Vec3 BLOCK_CENTER = new Vec3(0.5, 0.5, 0.5); 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), new DoubleArrayList(new double[]{0.0}), new DoubleArrayList(new double[]{0.0}), new DoubleArrayList(new double[]{0.0}) ); public static VoxelShape empty() { return EMPTY; } public static VoxelShape block() { return BLOCK; } public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { if (!(minX > maxX) && !(minY > maxY) && !(minZ > maxZ)) { return create(minX, minY, minZ, maxX, maxY, maxZ); } else { throw new IllegalArgumentException("The min values need to be smaller or equals to the max values"); } } public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { if (!(maxX - minX < 1.0E-7) && !(maxY - minY < 1.0E-7) && !(maxZ - minZ < 1.0E-7)) { int i = findBits(minX, maxX); int j = findBits(minY, maxY); int k = findBits(minZ, maxZ); if (i < 0 || j < 0 || k < 0) { return new ArrayVoxelShape( BLOCK.shape, DoubleArrayList.wrap(new double[]{minX, maxX}), DoubleArrayList.wrap(new double[]{minY, maxY}), DoubleArrayList.wrap(new double[]{minZ, maxZ}) ); } else if (i == 0 && j == 0 && k == 0) { return block(); } else { int l = 1 << i; int m = 1 << j; int n = 1 << k; BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds( l, m, n, (int)Math.round(minX * l), (int)Math.round(minY * m), (int)Math.round(minZ * n), (int)Math.round(maxX * l), (int)Math.round(maxY * m), (int)Math.round(maxZ * n) ); return new CubeVoxelShape(bitSetDiscreteVoxelShape); } } else { return empty(); } } public static VoxelShape create(AABB aabb) { return create(aabb.minX, aabb.minY, aabb.minZ, aabb.maxX, aabb.maxY, aabb.maxZ); } @VisibleForTesting protected static int findBits(double minBits, double maxBits) { if (!(minBits < -1.0E-7) && !(maxBits > 1.0000001)) { for (int i = 0; i <= 3; i++) { int j = 1 << i; double d = minBits * j; double e = maxBits * j; boolean bl = Math.abs(d - Math.round(d)) < 1.0E-7 * j; boolean bl2 = Math.abs(e - Math.round(e)) < 1.0E-7 * j; if (bl && bl2) { return i; } } return -1; } else { return -1; } } protected static long lcm(int aa, int bb) { return (long)aa * (bb / IntMath.gcd(aa, bb)); } public static VoxelShape or(VoxelShape shape1, VoxelShape shape2) { return join(shape1, shape2, BooleanOp.OR); } public static VoxelShape or(VoxelShape shape1, VoxelShape... others) { return (VoxelShape)Arrays.stream(others).reduce(shape1, Shapes::or); } public static VoxelShape join(VoxelShape shape1, VoxelShape shape2, BooleanOp function) { return joinUnoptimized(shape1, shape2, function).optimize(); } public static VoxelShape joinUnoptimized(VoxelShape shape1, VoxelShape shape2, BooleanOp function) { if (function.apply(false, false)) { throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); } else if (shape1 == shape2) { return function.apply(true, true) ? shape1 : empty(); } else { boolean bl = function.apply(true, false); boolean bl2 = function.apply(false, true); if (shape1.isEmpty()) { return bl2 ? shape2 : empty(); } else if (shape2.isEmpty()) { return bl ? shape1 : empty(); } else { IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl, bl2); IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl, bl2); IndexMerger indexMerger3 = createIndexMerger( (indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl, bl2 ); BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.join( shape1.shape, shape2.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())); } } } public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp resultOperator) { if (resultOperator.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 resultOperator.apply(true, true); } else { boolean bl3 = resultOperator.apply(true, false); boolean bl4 = resultOperator.apply(false, true); for (Direction.Axis axis : AxisCycle.AXIS_VALUES) { if (shape1.max(axis) < shape2.min(axis) - 1.0E-7) { return bl3 || bl4; } if (shape2.max(axis) < shape1.min(axis) - 1.0E-7) { return bl3 || bl4; } } IndexMerger indexMerger = createIndexMerger(1, shape1.getCoords(Direction.Axis.X), shape2.getCoords(Direction.Axis.X), bl3, bl4); IndexMerger indexMerger2 = createIndexMerger(indexMerger.size() - 1, shape1.getCoords(Direction.Axis.Y), shape2.getCoords(Direction.Axis.Y), bl3, bl4); IndexMerger indexMerger3 = createIndexMerger( (indexMerger.size() - 1) * (indexMerger2.size() - 1), shape1.getCoords(Direction.Axis.Z), shape2.getCoords(Direction.Axis.Z), bl3, bl4 ); return joinIsNotEmpty(indexMerger, indexMerger2, indexMerger3, shape1.shape, shape2.shape, resultOperator); } } else { return resultOperator.apply(!bl, !bl2); } } } private static boolean joinIsNotEmpty( IndexMerger mergerX, IndexMerger mergerY, IndexMerger mergerZ, DiscreteVoxelShape primaryShape, DiscreteVoxelShape secondaryShape, BooleanOp resultOperator ) { return !mergerX.forMergedIndexes( (i, j, k) -> mergerY.forMergedIndexes( (kx, l, m) -> mergerZ.forMergedIndexes((mx, n, o) -> !resultOperator.apply(primaryShape.isFullWide(i, kx, mx), secondaryShape.isFullWide(j, l, n))) ) ); } public static double collide(Direction.Axis movementAxis, AABB collisionBox, Iterable possibleHits, double desiredOffset) { for (VoxelShape voxelShape : possibleHits) { if (Math.abs(desiredOffset) < 1.0E-7) { return 0.0; } desiredOffset = voxelShape.collide(movementAxis, collisionBox, desiredOffset); } return desiredOffset; } public static boolean blockOccludes(VoxelShape shape, VoxelShape adjacentShape, Direction side) { if (shape == block() && adjacentShape == block()) { return true; } else if (adjacentShape.isEmpty()) { return false; } else { Direction.Axis axis = side.getAxis(); Direction.AxisDirection axisDirection = side.getAxisDirection(); VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : adjacentShape; VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? adjacentShape : shape; BooleanOp booleanOp = axisDirection == Direction.AxisDirection.POSITIVE ? BooleanOp.ONLY_FIRST : BooleanOp.ONLY_SECOND; return DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7) && DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7) && !joinIsNotEmpty(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), booleanOp); } } public static boolean mergedFaceOccludes(VoxelShape shape, VoxelShape adjacentShape, Direction side) { if (shape != block() && adjacentShape != block()) { Direction.Axis axis = side.getAxis(); Direction.AxisDirection axisDirection = side.getAxisDirection(); VoxelShape voxelShape = axisDirection == Direction.AxisDirection.POSITIVE ? shape : adjacentShape; VoxelShape voxelShape2 = axisDirection == Direction.AxisDirection.POSITIVE ? adjacentShape : shape; if (!DoubleMath.fuzzyEquals(voxelShape.max(axis), 1.0, 1.0E-7)) { voxelShape = empty(); } if (!DoubleMath.fuzzyEquals(voxelShape2.min(axis), 0.0, 1.0E-7)) { voxelShape2 = empty(); } return !joinIsNotEmpty( block(), joinUnoptimized(new SliceShape(voxelShape, axis, voxelShape.shape.getSize(axis) - 1), new SliceShape(voxelShape2, axis, 0), BooleanOp.OR), BooleanOp.ONLY_FIRST ); } else { return true; } } public static boolean faceShapeOccludes(VoxelShape voxelShape1, VoxelShape voxelShape2) { if (voxelShape1 == block() || voxelShape2 == block()) { return true; } else { return voxelShape1.isEmpty() && voxelShape2.isEmpty() ? false : !joinIsNotEmpty(block(), joinUnoptimized(voxelShape1, voxelShape2, BooleanOp.OR), BooleanOp.ONLY_FIRST); } } @VisibleForTesting protected static IndexMerger createIndexMerger(int size, DoubleList list1, DoubleList list2, boolean excludeUpper, boolean excludeLower) { int i = list1.size() - 1; int j = list2.size() - 1; if (list1 instanceof CubePointRange && list2 instanceof CubePointRange) { long l = lcm(i, j); if (size * l <= 256L) { return new DiscreteCubeMerger(i, j); } } if (list1.getDouble(i) < list2.getDouble(0) - 1.0E-7) { return new NonOverlappingMerger(list1, list2, false); } else if (list2.getDouble(j) < list1.getDouble(0) - 1.0E-7) { return new NonOverlappingMerger(list2, list1, true); } else { return (IndexMerger)(i == j && Objects.equals(list1, list2) ? new IdenticalMerger(list1) : new IndirectMerger(list1, list2, excludeUpper, excludeLower)); } } public static VoxelShape rotate(VoxelShape shape, OctahedralGroup octohedralGroup) { return rotate(shape, octohedralGroup, BLOCK_CENTER); } public static VoxelShape rotate(VoxelShape shape, OctahedralGroup octohedralGroup, Vec3 pos) { if (octohedralGroup == OctahedralGroup.IDENTITY) { return shape; } else { DiscreteVoxelShape discreteVoxelShape = shape.shape.rotate(octohedralGroup); if (shape instanceof CubeVoxelShape && BLOCK_CENTER.equals(pos)) { return new CubeVoxelShape(discreteVoxelShape); } else { Direction.Axis axis = octohedralGroup.permute(Direction.Axis.X); Direction.Axis axis2 = octohedralGroup.permute(Direction.Axis.Y); Direction.Axis axis3 = octohedralGroup.permute(Direction.Axis.Z); DoubleList doubleList = shape.getCoords(axis); DoubleList doubleList2 = shape.getCoords(axis2); DoubleList doubleList3 = shape.getCoords(axis3); boolean bl = octohedralGroup.inverts(axis); boolean bl2 = octohedralGroup.inverts(axis2); boolean bl3 = octohedralGroup.inverts(axis3); boolean bl4 = axis.choose(bl, bl2, bl3); boolean bl5 = axis2.choose(bl, bl2, bl3); boolean bl6 = axis3.choose(bl, bl2, bl3); return new ArrayVoxelShape( discreteVoxelShape, makeAxis(doubleList, bl4, pos.get(axis), pos.x), makeAxis(doubleList2, bl5, pos.get(axis2), pos.y), makeAxis(doubleList3, bl6, pos.get(axis3), pos.z) ); } } } @VisibleForTesting static DoubleList makeAxis(DoubleList inputList, boolean reverseOrder, double referenceValue, double targetValue) { if (!reverseOrder && referenceValue == targetValue) { return inputList; } else { int i = inputList.size(); DoubleList doubleList = new DoubleArrayList(i); int j = reverseOrder ? -1 : 1; for (int k = reverseOrder ? i - 1 : 0; k >= 0 && k < i; k += j) { doubleList.add(targetValue + j * (inputList.getDouble(k) - referenceValue)); } return doubleList; } } public static boolean equal(VoxelShape first, VoxelShape second) { return !joinIsNotEmpty(first, second, BooleanOp.NOT_SAME); } public static Map rotateHorizontalAxis(VoxelShape shape) { return rotateHorizontalAxis(shape, BLOCK_CENTER); } public static Map rotateHorizontalAxis(VoxelShape shape, Vec3 pos) { return Maps.newEnumMap(Map.of(Direction.Axis.Z, shape, Direction.Axis.X, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R90), pos))); } public static Map rotateAllAxis(VoxelShape shape) { return rotateAllAxis(shape, BLOCK_CENTER); } public static Map rotateAllAxis(VoxelShape shape, Vec3 pos) { return Maps.newEnumMap( Map.of( Direction.Axis.Z, shape, Direction.Axis.X, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R90), pos), Direction.Axis.Y, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R90, Quadrant.R0), pos) ) ); } public static Map rotateHorizontal(VoxelShape shape) { return rotateHorizontal(shape, BLOCK_CENTER); } public static Map rotateHorizontal(VoxelShape shape, Vec3 pos) { return Maps.newEnumMap( Map.of( Direction.NORTH, shape, Direction.EAST, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R90), pos), Direction.SOUTH, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R180), pos), Direction.WEST, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R270), pos) ) ); } public static Map rotateAll(VoxelShape shape) { return rotateAll(shape, BLOCK_CENTER); } public static Map rotateAll(VoxelShape shape, Vec3 pos) { return Maps.newEnumMap( Map.of( Direction.NORTH, shape, Direction.EAST, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R90), pos), Direction.SOUTH, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R180), pos), Direction.WEST, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R0, Quadrant.R270), pos), Direction.UP, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R270, Quadrant.R0), pos), Direction.DOWN, rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R90, Quadrant.R0), pos) ) ); } public static Map> rotateAttachFace(VoxelShape shape) { return Map.of( AttachFace.WALL, rotateHorizontal(shape), AttachFace.FLOOR, rotateHorizontal(rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R270, Quadrant.R0))), AttachFace.CEILING, rotateHorizontal(rotate(shape, OctahedralGroup.fromXYAngles(Quadrant.R90, Quadrant.R180))) ); } public interface DoubleLineConsumer { void consume(double d, double e, double f, double g, double h, double i); } }