minecraft-src/net/minecraft/world/phys/shapes/Shapes.java
2025-07-04 03:45:38 +03:00

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);
}
}