mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 01:50:56 +01:00
114 lines
4.8 KiB
Java
114 lines
4.8 KiB
Java
package net.xevianlight.aphelion.util;
|
|
|
|
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
|
|
import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet;
|
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.AABB;
|
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Set;
|
|
|
|
public final class FloodFill3D {
|
|
|
|
private static final Direction[] DIRECTIONS = Direction.values();
|
|
|
|
public static final SolidBlockPredicate TEST_FULL_SEAL = (level, pos, state, positions, queue, direction) -> {
|
|
if (state.isAir()) return true;
|
|
if (state.is(ModTags.Blocks.PASSES_FLOOD_FILL)) return true;
|
|
if (state.is(ModTags.Blocks.BLOCKS_FLOOD_FILL)) return false;
|
|
if (state.isCollisionShapeFullBlock(level, pos)) return false;
|
|
|
|
VoxelShape collisionShape = state.getCollisionShape(level, pos);
|
|
|
|
if (collisionShape.isEmpty()) return true;
|
|
if (!isSideSolid(collisionShape, direction)) return true;
|
|
if (!isFaceSturdy(collisionShape, direction) && !isFaceSturdy(collisionShape, direction.getOpposite())) {
|
|
return true;
|
|
}
|
|
|
|
// Check the other directions to find a potential path for the partial block.
|
|
for (Direction dir : DIRECTIONS) {
|
|
if (dir.getAxis() == direction.getAxis()) continue;
|
|
var adjacentPos = pos.relative(dir);
|
|
var adjacentState = level.getBlockState(adjacentPos);
|
|
if (adjacentState.isAir()) return true;
|
|
}
|
|
|
|
positions.add(pos.asLong());
|
|
return false;
|
|
};
|
|
|
|
public static Set<BlockPos> run(Level level, BlockPos start, int limit, SolidBlockPredicate predicate, boolean retainOrder) {
|
|
level.getProfiler().push("aphelion-floodfill");
|
|
|
|
LongSet positions = retainOrder ? new LongLinkedOpenHashSet(limit) : new LongOpenHashSet(limit);
|
|
LongArrayFIFOQueue queue = new LongArrayFIFOQueue(limit);
|
|
queue.enqueue(start.asLong());
|
|
|
|
while (!queue.isEmpty() && positions.size() < limit) {
|
|
long packedPos = queue.dequeueLong();
|
|
if (positions.contains(packedPos)) continue;
|
|
positions.add(packedPos);
|
|
|
|
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(BlockPos.getX(packedPos), BlockPos.getY(packedPos), BlockPos.getZ(packedPos));
|
|
for (Direction direction : DIRECTIONS) {
|
|
pos.set(packedPos);
|
|
pos.move(direction);
|
|
BlockState state = level.getBlockState(pos);
|
|
if (!predicate.test(level, pos, state, positions, queue, direction)) continue;
|
|
queue.enqueue(pos.asLong());
|
|
}
|
|
}
|
|
|
|
Set<BlockPos> result = retainOrder ? new LinkedHashSet<>(positions.size()) : new HashSet<>(positions.size());
|
|
for (long pos : positions) {
|
|
result.add(BlockPos.of(pos));
|
|
}
|
|
|
|
level.getProfiler().pop();
|
|
return result;
|
|
}
|
|
|
|
private static boolean isSideSolid(VoxelShape collisionShape, Direction dir) {
|
|
return switch (dir.getAxis()) {
|
|
case X -> isAxisCovered(collisionShape, Direction.Axis.Y, Direction.Axis.Z);
|
|
case Y -> isAxisCovered(collisionShape, Direction.Axis.X, Direction.Axis.Z);
|
|
case Z -> isAxisCovered(collisionShape, Direction.Axis.X, Direction.Axis.Y);
|
|
};
|
|
}
|
|
|
|
private static boolean isAxisCovered(VoxelShape shape, Direction.Axis axis1, Direction.Axis axis2) {
|
|
return shape.min(axis1) <= 0 && shape.max(axis1) >= 1 && shape.min(axis2) <= 0 && shape.max(axis2) >= 1;
|
|
}
|
|
|
|
|
|
private static boolean isFaceSturdy(VoxelShape collisionShape, Direction dir) {
|
|
VoxelShape faceShape = collisionShape.getFaceShape(dir);
|
|
if (faceShape.isEmpty()) return true;
|
|
var aabbs = faceShape.toAabbs();
|
|
if (aabbs.isEmpty()) return true;
|
|
return checkBounds(aabbs.get(0), dir.getAxis());
|
|
}
|
|
|
|
private static boolean checkBounds(AABB bounds, Direction.Axis axis) {
|
|
return switch (axis) {
|
|
case X -> bounds.minY <= 0 && bounds.maxY >= 1 && bounds.minZ <= 0 && bounds.maxZ >= 1;
|
|
case Y -> bounds.minX <= 0 && bounds.maxX >= 1 && bounds.minZ <= 0 && bounds.maxZ >= 1;
|
|
case Z -> bounds.minX <= 0 && bounds.maxX >= 1 && bounds.minY <= 0 && bounds.maxY >= 1;
|
|
};
|
|
}
|
|
|
|
@FunctionalInterface
|
|
public interface SolidBlockPredicate {
|
|
|
|
boolean test(Level level, BlockPos pos, BlockState state, LongSet positions, LongArrayFIFOQueue queue, Direction direction);
|
|
}
|
|
} |