Minestom/src/main/java/net/minestom/server/instance/light/BlockLightCompute.java

121 lines
5.5 KiB
Java

package net.minestom.server.instance.light;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockFace;
import net.minestom.server.instance.palette.Palette;
import net.minestom.server.utils.Direction;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Objects;
final class BlockLightCompute {
private static final BlockFace[] FACES = BlockFace.values();
private static final Direction[] DIRECTIONS = Direction.values();
static final int SECTION_SIZE = 16;
static final int LIGHT_LENGTH = 16 * 16 * 16 / 2;
static final int SIDE_LENGTH = 16 * 16 * DIRECTIONS.length / 2;
static @NotNull Result compute(Palette blockPalette) {
Block[] blocks = new Block[4096];
byte[] lightArray = new byte[LIGHT_LENGTH];
byte[][] borders = new byte[DIRECTIONS.length][];
Arrays.setAll(borders, i -> new byte[SIDE_LENGTH]);
IntArrayFIFOQueue lightSources = new IntArrayFIFOQueue();
blockPalette.getAllPresent((x, y, z, stateId) -> {
final Block block = Block.fromStateId((short) stateId);
assert block != null;
final byte lightEmission = (byte) block.registry().lightEmission();
final int index = x | (z << 4) | (y << 8);
blocks[index] = block;
if (lightEmission > 0) {
lightSources.enqueue(index | (lightEmission << 12));
placeLight(lightArray, index, lightEmission);
}
});
while (!lightSources.isEmpty()) {
final int index = lightSources.dequeueInt();
final int x = index & 15;
final int z = (index >> 4) & 15;
final int y = (index >> 8) & 15;
final int lightLevel = (index >> 12) & 15;
for (BlockFace face : FACES) {
Direction dir = face.toDirection();
final int xO = x + dir.normalX();
final int yO = y + dir.normalY();
final int zO = z + dir.normalZ();
final byte newLightLevel = (byte) (lightLevel - 1);
// Handler border
if (xO < 0 || xO >= SECTION_SIZE || yO < 0 || yO >= SECTION_SIZE || zO < 0 || zO >= SECTION_SIZE) {
final byte[] border = borders[face.ordinal()];
final int borderIndex = switch (face) {
case WEST, EAST -> y * SECTION_SIZE + z;
case BOTTOM, TOP -> x * SECTION_SIZE + z;
case NORTH, SOUTH -> x * SECTION_SIZE + y;
};
border[borderIndex] = newLightLevel;
continue;
}
// Section
final int newIndex = xO | (zO << 4) | (yO << 8);
final Block currentBlock = Objects.requireNonNullElse(blocks[x | (z << 4) | (y << 8)], Block.AIR);
final Block propagatedBlock = Objects.requireNonNullElse(blocks[newIndex], Block.AIR);
if (currentBlock.registry().collisionShape().isOccluded(propagatedBlock.registry().collisionShape(), face))
continue;
if (getLight(lightArray, newIndex) + 2 <= lightLevel) {
placeLight(lightArray, newIndex, newLightLevel);
lightSources.enqueue(newIndex | (newLightLevel << 12));
}
}
}
return new Result(lightArray, borders);
}
record Result(byte[] light, byte[][] borders) {
Result {
assert light.length == LIGHT_LENGTH : "Only 16x16x16 sections are supported: " + light.length;
assert borders.length == FACES.length;
}
public byte getLight(int x, int y, int z) {
final boolean outX = x < 0 || x >= SECTION_SIZE;
final boolean outY = y < 0 || y >= SECTION_SIZE;
final boolean outZ = z < 0 || z >= SECTION_SIZE;
if (outX || outY || outZ) {
final boolean multipleOut = outX ? (outY || outZ) : (outY && outZ);
if (multipleOut)
throw new IllegalArgumentException("Coordinates are out of bounds: " + x + ", " + y + ", " + z);
if (outX) {
// WEST or EAST
if (x < 0) return borders[BlockFace.WEST.ordinal()][y * SECTION_SIZE + z];
else return borders[BlockFace.EAST.ordinal()][y * SECTION_SIZE + z];
} else if (outY) {
// BOTTOM or TOP
if (y < 0) return borders[BlockFace.BOTTOM.ordinal()][x * SECTION_SIZE + z];
else return borders[BlockFace.TOP.ordinal()][x * SECTION_SIZE + z];
} else {
// NORTH or SOUTH
if (z < 0) return borders[BlockFace.NORTH.ordinal()][x * SECTION_SIZE + y];
else return borders[BlockFace.SOUTH.ordinal()][x * SECTION_SIZE + y];
}
} else return (byte) BlockLightCompute.getLight(light, x | (z << 4) | (y << 8));
}
}
private static void placeLight(byte[] light, int index, int value) {
final int shift = (index & 1) << 2;
final int i = index >>> 1;
light[i] = (byte) ((light[i] & (0xF0 >>> shift)) | (value << shift));
}
private static int getLight(byte[] light, int index) {
final int value = light[index >>> 1];
return ((value >>> ((index & 1) << 2)) & 0xF);
}
}