422 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			422 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| 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<VoxelShape> 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<Direction.Axis, VoxelShape> rotateHorizontalAxis(VoxelShape shape) {
 | |
| 		return rotateHorizontalAxis(shape, BLOCK_CENTER);
 | |
| 	}
 | |
| 
 | |
| 	public static Map<Direction.Axis, VoxelShape> 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<Direction.Axis, VoxelShape> rotateAllAxis(VoxelShape shape) {
 | |
| 		return rotateAllAxis(shape, BLOCK_CENTER);
 | |
| 	}
 | |
| 
 | |
| 	public static Map<Direction.Axis, VoxelShape> 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<Direction, VoxelShape> rotateHorizontal(VoxelShape shape) {
 | |
| 		return rotateHorizontal(shape, BLOCK_CENTER);
 | |
| 	}
 | |
| 
 | |
| 	public static Map<Direction, VoxelShape> 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<Direction, VoxelShape> rotateAll(VoxelShape shape) {
 | |
| 		return rotateAll(shape, BLOCK_CENTER);
 | |
| 	}
 | |
| 
 | |
| 	public static Map<Direction, VoxelShape> 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<AttachFace, Map<Direction, VoxelShape>> 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);
 | |
| 	}
 | |
| }
 |