summaryrefslogtreecommitdiff
path: root/src/main/java/com/encrox/instancedregions/chunkmap
diff options
context:
space:
mode:
authorLeonard Kugis <leonard@kug.is>2022-04-25 18:41:24 +0200
committerLeonard Kugis <leonard@kug.is>2022-04-25 18:41:24 +0200
commit7abf31ea821a0bbddb836adb1a63d0fec2ceee4f (patch)
treead9f4f4a1e6bbf5000b8eeb78180eed3923d72e7 /src/main/java/com/encrox/instancedregions/chunkmap
Initial commitHEADmaster
Diffstat (limited to 'src/main/java/com/encrox/instancedregions/chunkmap')
-rwxr-xr-xsrc/main/java/com/encrox/instancedregions/chunkmap/ChunkData.java16
-rwxr-xr-xsrc/main/java/com/encrox/instancedregions/chunkmap/ChunkLayer.java11
-rwxr-xr-xsrc/main/java/com/encrox/instancedregions/chunkmap/ChunkMapBuffer.java65
-rwxr-xr-xsrc/main/java/com/encrox/instancedregions/chunkmap/ChunkMapManager.java384
-rwxr-xr-xsrc/main/java/com/encrox/instancedregions/chunkmap/ChunkReader.java100
-rwxr-xr-xsrc/main/java/com/encrox/instancedregions/chunkmap/ChunkWriter.java108
6 files changed, 684 insertions, 0 deletions
diff --git a/src/main/java/com/encrox/instancedregions/chunkmap/ChunkData.java b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkData.java
new file mode 100755
index 0000000..eccac2f
--- /dev/null
+++ b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkData.java
@@ -0,0 +1,16 @@
+/**
+ * @author Aleksey Terzi
+ *
+ */
+
+package com.encrox.instancedregions.chunkmap;
+
+public class ChunkData {
+ public int chunkX;
+ public int chunkZ;
+ public boolean groundUpContinuous;
+ public int primaryBitMask;
+ public byte[] data;
+ public boolean isOverworld;
+ public boolean useCache;
+}
diff --git a/src/main/java/com/encrox/instancedregions/chunkmap/ChunkLayer.java b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkLayer.java
new file mode 100755
index 0000000..04478bd
--- /dev/null
+++ b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkLayer.java
@@ -0,0 +1,11 @@
+/**
+ * @author Aleksey Terzi
+ *
+ */
+
+package com.encrox.instancedregions.chunkmap;
+
+public class ChunkLayer {
+ public boolean hasData;
+ public int[] map;
+}
diff --git a/src/main/java/com/encrox/instancedregions/chunkmap/ChunkMapBuffer.java b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkMapBuffer.java
new file mode 100755
index 0000000..e138c9f
--- /dev/null
+++ b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkMapBuffer.java
@@ -0,0 +1,65 @@
+/**
+ * @author Aleksey Terzi
+ *
+ */
+
+package com.encrox.instancedregions.chunkmap;
+
+public class ChunkMapBuffer {
+ private static final int BITS_PER_BLOCK_SIZE = 1;
+ private static final int PALETTE_LENGTH_SIZE = 5;
+ private static final int DATA_ARRAY_LENGTH_SIZE = 5;
+ private static final int BLOCKS_PER_CHUNK_SECTION = 16 * 16 * 16;
+ private static final int DATA_ARRAY_SIZE = BLOCKS_PER_CHUNK_SECTION * 13 / 8;
+ private static final int BLOCK_LIGHT_SIZE = BLOCKS_PER_CHUNK_SECTION / 2;
+ private static final int SKY_LIGHT_SIZE = BLOCKS_PER_CHUNK_SECTION / 2;
+ private static final int COLUMNS_PER_CHUNK = 16;
+
+ private static final int MAX_BYTES_PER_CHUNK =
+ COLUMNS_PER_CHUNK *
+ (
+ BITS_PER_BLOCK_SIZE
+ + PALETTE_LENGTH_SIZE
+ + DATA_ARRAY_LENGTH_SIZE
+ + DATA_ARRAY_SIZE
+ + BLOCK_LIGHT_SIZE
+ + SKY_LIGHT_SIZE
+ );
+
+ public int[] palette;
+ public byte[] output;
+ public int[] outputPalette;
+ public byte[] outputPaletteMap;
+ public ChunkWriter writer;
+ public ChunkLayer prevLayer;
+ public ChunkLayer curLayer;
+ public ChunkLayer nextLayer;
+
+ public int bitsPerBlock;
+ public int paletteLength;
+ public int dataArrayLength;
+ public int lightArrayLength;
+ public int dataArrayStartIndex;
+ public int outputPaletteLength;
+ public int outputBitsPerBlock;
+
+ public ChunkMapBuffer() {
+ this.palette = new int[256];
+ this.output = new byte[MAX_BYTES_PER_CHUNK];
+ this.outputPalette = new int[256];
+ this.outputPaletteMap = new byte[65536];
+ this.writer = new ChunkWriter(this.output);
+ this.prevLayer = new ChunkLayer();
+ this.prevLayer.map = new int[16 * 16];
+ this.curLayer = new ChunkLayer();
+ this.curLayer.map = new int[16 * 16];
+ this.nextLayer = new ChunkLayer();
+ this.nextLayer.map = new int[16 * 16];
+ }
+
+ public void clearLayers() {
+ this.prevLayer.hasData = false;
+ this.curLayer.hasData = false;
+ this.nextLayer.hasData = false;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/encrox/instancedregions/chunkmap/ChunkMapManager.java b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkMapManager.java
new file mode 100755
index 0000000..87fb3a1
--- /dev/null
+++ b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkMapManager.java
@@ -0,0 +1,384 @@
+/**
+ * @author Aleksey Terzi
+ *
+ */
+
+package com.encrox.instancedregions.chunkmap;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import com.encrox.instancedregions.types.BlockState;
+
+public class ChunkMapManager {
+ private static final ThreadLocal<ChunkMapBuffer> _buffer = new ThreadLocal<ChunkMapBuffer>() {
+ @Override
+ protected ChunkMapBuffer initialValue() {
+ return new ChunkMapBuffer();
+ }
+ };
+
+ private ChunkMapBuffer buffer;
+ private ChunkData chunkData;
+ private ChunkReader reader;
+ private int sectionCount;
+ private int sectionIndex;
+ private int y;
+ private int minX;
+ private int maxX;
+ private int minZ;
+ private int maxZ;
+ private int blockIndex;
+
+ public int getSectionCount() {
+ return this.sectionCount;
+ }
+
+ public int getY() {
+ return this.y;
+ }
+
+ public ChunkData getChunkData() {
+ return this.chunkData;
+ }
+
+ public ChunkMapManager(ChunkData chunkData) {
+ this.buffer = _buffer.get();
+ this.chunkData = chunkData;
+ }
+
+ public void init() throws IOException {
+ this.reader = new ChunkReader(this.chunkData.data);
+ this.sectionCount = 0;
+ this.sectionIndex = -1;
+ this.minX = this.chunkData.chunkX << 4;
+ this.maxX = this.minX + 15;
+ this.minZ = this.chunkData.chunkZ << 4;
+ this.maxZ = this.minZ + 15;
+
+ this.buffer.lightArrayLength = 2048;
+
+ if(this.chunkData.isOverworld) {
+ this.buffer.lightArrayLength <<= 1;
+ }
+
+ this.buffer.writer.init();
+
+ int mask = this.chunkData.primaryBitMask;
+
+ while(mask != 0) {
+ if((mask & 0x1) != 0) {
+ this.sectionCount++;
+ }
+
+ mask >>>= 1;
+ }
+
+ this.buffer.clearLayers();
+
+ moveToNextLayer();
+ }
+
+ public boolean inputHasNonAirBlock() {
+ return this.buffer.paletteLength > 1 || this.buffer.palette[0] != 0;
+ }
+
+ public static void blockDataToState(int blockData, BlockState blockState) {
+ blockState.id = blockData >>> 4;
+ blockState.meta = blockData & 0xf;
+ }
+
+ public static int getBlockIdFromData(int blockData) {
+ return blockData >>> 4;
+ }
+
+ public static int getBlockMetaFromData(int blockData) {
+ return blockData & 0xf;
+ }
+
+ public static int blockStateToData(BlockState blockState) {
+ return (blockState.id << 4) | blockState.meta;
+ }
+
+ public static int getBlockDataFromId(int id) {
+ return id << 4;
+ }
+
+ public boolean initOutputPalette() {
+ if(this.buffer.paletteLength == 0 || this.buffer.paletteLength == 255) {
+ this.buffer.outputPaletteLength = 0;
+ return false;
+ }
+
+ Arrays.fill(this.buffer.outputPaletteMap, (byte)-1);
+
+ this.buffer.outputPaletteLength = this.buffer.paletteLength;
+
+ for(int i = 0; i < this.buffer.paletteLength; i++) {
+ int blockData = this.buffer.palette[i];
+
+ this.buffer.outputPalette[i] = blockData;
+
+ if(blockData >= 0) {
+ this.buffer.outputPaletteMap[blockData] = (byte)i;
+ }
+ }
+
+ return true;
+ }
+
+ public boolean addToOutputPalette(int blockData) {
+ if(this.buffer.outputPaletteMap[blockData] >= 0) return true;
+
+ //255 (-1 for byte) is special code in my algorithm
+ if(this.buffer.outputPaletteLength == 254) {
+ this.buffer.outputPaletteLength = 0;
+ return false;
+ }
+
+ this.buffer.outputPalette[this.buffer.outputPaletteLength] = blockData;
+ this.buffer.outputPaletteMap[blockData] = (byte)this.buffer.outputPaletteLength;
+
+ this.buffer.outputPaletteLength++;
+
+ return true;
+ }
+
+ public void initOutputSection() throws IOException {
+ calcOutputBitsPerBlock();
+
+ this.buffer.writer.setBitsPerBlock(this.buffer.outputBitsPerBlock);
+
+ //Bits Per Block
+ this.buffer.writer.writeByte((byte)this.buffer.outputBitsPerBlock);
+
+ //Palette Length
+ this.buffer.writer.writeVarInt(this.buffer.outputPaletteLength);
+
+ //Palette
+ for(int i = 0; i < this.buffer.outputPaletteLength; i++) {
+ this.buffer.writer.writeVarInt(this.buffer.outputPalette[i]);
+ }
+
+ int dataArrayLengthInBits = this.buffer.outputBitsPerBlock << 12;// multiply by 4096
+ int outputDataArrayLength = dataArrayLengthInBits >>> 6;//divide by 64
+
+ if((dataArrayLengthInBits & 0x3f) != 0) {
+ outputDataArrayLength++;
+ }
+
+ //Data Array Length
+ this.buffer.writer.writeVarInt(outputDataArrayLength);
+
+ //Copy Block Light and Sky Light arrays
+ int lightArrayStartIndex = this.buffer.dataArrayStartIndex + (this.buffer.dataArrayLength << 3);
+ int outputLightArrayStartIndex = this.buffer.writer.getByteIndex() + (outputDataArrayLength << 3);
+
+ System.arraycopy(
+ this.chunkData.data,
+ lightArrayStartIndex,
+ this.buffer.output,
+ outputLightArrayStartIndex,
+ this.buffer.lightArrayLength
+ );
+ }
+
+ public void writeOutputBlock(int blockData) throws IOException {
+ if(this.buffer.outputPaletteLength > 0) {
+ long paletteIndex = this.buffer.outputPaletteMap[blockData] & 0xffL;
+
+ if(paletteIndex == 255) {
+ BlockState blockState = new BlockState();
+ blockDataToState(blockData, blockState);
+ throw new IllegalArgumentException("Block " + blockState.id + ":" + blockState.meta + " is absent in output palette.");
+ }
+
+ this.buffer.writer.writeBlockBits(paletteIndex);
+ } else {
+ this.buffer.writer.writeBlockBits(blockData);
+ }
+ }
+
+ public void finalizeOutput() throws IOException {
+ if(this.buffer.writer.getByteIndex() == 0) return;
+
+ this.buffer.writer.save();
+ this.buffer.writer.skip(this.buffer.lightArrayLength);
+ }
+
+ public byte[] createOutput() {
+ int readerByteIndex = this.reader.getByteIndex();
+ int writerByteIndex = this.buffer.writer.getByteIndex();
+ int biomesSize = this.chunkData.data.length - readerByteIndex;
+ byte[] output = new byte[writerByteIndex + biomesSize];
+
+ System.arraycopy(this.buffer.output, 0, output, 0, writerByteIndex);
+
+ if(biomesSize > 0) {
+ System.arraycopy(
+ this.chunkData.data,
+ readerByteIndex,
+ output,
+ writerByteIndex,
+ biomesSize
+ );
+ }
+
+ return output;
+ }
+
+ private void calcOutputBitsPerBlock() {
+ if(this.buffer.outputPaletteLength == 0) {
+ this.buffer.outputBitsPerBlock = 13;
+ } else {
+ byte mask = (byte)this.buffer.outputPaletteLength;
+ int index = 0;
+
+ while((mask & 0x80) == 0) {
+ index++;
+ mask <<= 1;
+ }
+
+ this.buffer.outputBitsPerBlock = 8 - index;
+
+ if(this.buffer.outputBitsPerBlock < 4) {
+ this.buffer.outputBitsPerBlock = 4;
+ }
+ }
+ }
+
+ public int readNextBlock() throws IOException {
+ if(this.blockIndex == 16 * 16) {
+ if(!moveToNextLayer()) return -1;
+ }
+
+ return this.buffer.curLayer.map[this.blockIndex++];
+ }
+
+ public int get(int x, int y, int z) throws IOException {
+ if(x < minX || x > maxX
+ || z < minZ || z > maxZ
+ || y > 255 || y < this.y - 1 || y > this.y + 1
+ ) {
+ return -1;
+ }
+
+ ChunkLayer layer;
+
+ if(y == this.y) layer = this.buffer.curLayer;
+ else if(y == this.y - 1) layer = this.buffer.prevLayer;
+ else layer = this.buffer.nextLayer;
+
+ if(!layer.hasData) return -1;
+
+ int blockIndex = ((z - this.minZ) << 4) | (x - this.minX);
+
+ return layer.map[blockIndex];
+ }
+
+ private boolean moveToNextLayer() throws IOException {
+ if(!increaseY()) return false;
+
+ shiftLayersDown();
+
+ if(!this.buffer.curLayer.hasData) {
+ readLayer(this.buffer.curLayer);
+ }
+
+ if(((this.y + 1) >>> 4) > this.sectionIndex) {
+ int oldSectionIndex = this.sectionIndex;
+
+ moveToNextSection();
+
+ if(this.sectionIndex < 16 && oldSectionIndex + 1 == this.sectionIndex) {
+ readLayer(this.buffer.nextLayer);
+ }
+ } else {
+ readLayer(this.buffer.nextLayer);
+ }
+
+ this.blockIndex = 0;
+
+ return true;
+ }
+
+ private boolean increaseY() throws IOException {
+ if(this.sectionIndex < 0) {
+ if(!moveToNextSection()) return false;
+
+ this.y = this.sectionIndex << 4;
+ }
+ else {
+ this.y++;
+
+ if((this.y & 0xf) == 0) {
+ if(this.sectionIndex > 15) return false;
+
+ if((this.y >>> 4) != this.sectionIndex) {
+ this.buffer.clearLayers();
+ this.y = this.sectionIndex << 4;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void shiftLayersDown() {
+ ChunkLayer temp = this.buffer.prevLayer;
+
+ this.buffer.prevLayer = this.buffer.curLayer;
+ this.buffer.curLayer = this.buffer.nextLayer;
+ this.buffer.nextLayer = temp;
+ this.buffer.nextLayer.hasData = false;
+ }
+
+ private boolean moveToNextSection() throws IOException {
+ if(this.sectionIndex >= 0) {
+ this.reader.skip(this.buffer.lightArrayLength);
+ }
+
+ do {
+ this.sectionIndex++;
+ } while(this.sectionIndex < 16 && (this.chunkData.primaryBitMask & (1 << this.sectionIndex)) == 0);
+
+ if(this.sectionIndex >= 16) return false;
+
+ readSectionHeader();
+
+ return true;
+ }
+
+ private void readLayer(ChunkLayer layer) throws IOException {
+ for(int i = 0; i < 16 * 16; i++) {
+ int blockData = this.reader.readBlockBits();
+
+ if(this.buffer.paletteLength > 0) {
+ blockData = blockData >= 0 && blockData < this.buffer.paletteLength
+ ? this.buffer.palette[blockData]
+ : 0;
+ }
+
+ layer.map[i] = blockData;
+ }
+
+ layer.hasData = true;
+ }
+
+ private void readSectionHeader() throws IOException {
+ this.buffer.bitsPerBlock = this.reader.readByte();
+ this.buffer.paletteLength = this.reader.readVarInt();
+
+ for(int i = 0; i < this.buffer.paletteLength; i++) {
+ int paletteData = this.reader.readVarInt();
+
+ this.buffer.palette[i] = paletteData;
+ }
+
+ this.buffer.dataArrayLength = this.reader.readVarInt();
+
+ this.buffer.dataArrayStartIndex = this.reader.getByteIndex();
+
+ this.reader.setBitsPerBlock(this.buffer.bitsPerBlock);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/encrox/instancedregions/chunkmap/ChunkReader.java b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkReader.java
new file mode 100755
index 0000000..a05fa7a
--- /dev/null
+++ b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkReader.java
@@ -0,0 +1,100 @@
+/**
+ * @author Aleksey Terzi
+ *
+ */
+
+package com.encrox.instancedregions.chunkmap;
+
+import java.io.IOException;
+
+public class ChunkReader {
+ private byte[] data;
+ private int bitsPerBlock;
+ private long maxValueMask;
+ private int byteIndex;
+ private int bitIndex;
+ private long buffer;
+
+ public ChunkReader(byte[] data) {
+ this.data = data;
+ this.byteIndex = 0;
+ this.bitIndex = 0;
+ }
+
+ public int getByteIndex() {
+ return this.byteIndex;
+ }
+
+ public void skip(int count) {
+ this.byteIndex += count;
+ this.bitIndex = 0;
+ }
+
+ public void setBitsPerBlock(int bitsPerBlock) {
+ this.bitsPerBlock = bitsPerBlock;
+ this.maxValueMask = (1L << this.bitsPerBlock) - 1;
+ }
+
+ public int readBlockBits() throws IOException {
+ if(this.bitIndex == 0 || this.bitIndex >= 64) {
+ readLong();
+ this.bitIndex = 0;
+ }
+
+ int leftBits = 64 - this.bitIndex;
+ long result = this.buffer >>> this.bitIndex;
+
+ if(leftBits >= this.bitsPerBlock) {
+ this.bitIndex += this.bitsPerBlock;
+ } else {
+ readLong();
+
+ result |= this.buffer << leftBits;
+
+ this.bitIndex = this.bitsPerBlock - leftBits;
+ }
+
+ return (int)(result & this.maxValueMask);
+ }
+
+ private void readLong() throws IOException {
+ if(this.byteIndex + 7 >= this.data.length) {
+ throw new IOException("No data to read.");
+ }
+
+ this.buffer = ((this.data[this.byteIndex] & 0xffL) << 56)
+ | ((this.data[this.byteIndex + 1] & 0xffL) << 48)
+ | ((this.data[this.byteIndex + 2] & 0xffL) << 40)
+ | ((this.data[this.byteIndex + 3] & 0xffL) << 32)
+ | ((this.data[this.byteIndex + 4] & 0xffL) << 24)
+ | ((this.data[this.byteIndex + 5] & 0xffL) << 16)
+ | ((this.data[this.byteIndex + 6] & 0xffL) << 8)
+ | (this.data[this.byteIndex + 7] & 0xffL);
+
+ this.byteIndex += 8;
+ }
+
+ public int readVarInt() throws IOException {
+ int value = 0;
+ int size = 0;
+ int b;
+
+ while(((b = readByte()) & 0x80) == 0x80) {
+ value |= (b & 0x7F) << (size++ * 7);
+
+ if(size > 5) {
+ throw new IOException("Invalid VarInt.");
+ }
+ }
+
+ return value | ((b & 0x7F) << (size * 7));
+ }
+
+ public int readByte() throws IOException {
+ if(this.byteIndex >= this.data.length) {
+ throw new IOException("No data to read.");
+ }
+
+ return this.data[this.byteIndex++] & 0xff;
+ }
+}
diff --git a/src/main/java/com/encrox/instancedregions/chunkmap/ChunkWriter.java b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkWriter.java
new file mode 100755
index 0000000..398cb25
--- /dev/null
+++ b/src/main/java/com/encrox/instancedregions/chunkmap/ChunkWriter.java
@@ -0,0 +1,108 @@
+/**
+ * @author Aleksey Terzi
+ *
+ */
+
+package com.encrox.instancedregions.chunkmap;
+
+import java.io.IOException;
+
+public class ChunkWriter {
+ private byte[] data;
+ private int bitsPerBlock;
+ private int byteIndex;
+ private int bitIndex;
+ private long buffer;
+
+ public ChunkWriter(byte[] data) {
+ this.data = data;
+ }
+
+ public int getByteIndex() {
+ return this.byteIndex;
+ }
+
+ public void init() {
+ this.byteIndex = 0;
+ this.bitIndex = 0;
+ this.buffer = 0;
+ }
+
+ public void setBitsPerBlock(int bitsPerBlock) {
+ this.bitsPerBlock = bitsPerBlock;
+ }
+
+ public void save() throws IOException {
+ writeLong();
+ }
+
+ public void skip(int count) {
+ this.byteIndex += count;
+ this.bitIndex = 0;
+ }
+
+ public void writeBytes(byte[] source, int index, int length) throws IOException {
+ if(this.byteIndex + length > this.data.length) {
+ throw new IOException("No space to write.");
+ }
+
+ System.arraycopy(source, index, this.data, this.byteIndex, length);
+
+ this.byteIndex += length;
+ }
+
+ public void writeBlockBits(long bits) throws IOException {
+ if(this.bitIndex >= 64) {
+ writeLong();
+ this.bitIndex = 0;
+ }
+
+ int leftBits = 64 - this.bitIndex;
+
+ this.buffer |= bits << this.bitIndex;
+
+ if(leftBits >= this.bitsPerBlock) {
+ this.bitIndex += this.bitsPerBlock;
+ } else {
+ writeLong();
+
+ this.buffer = bits >>> leftBits;
+
+ this.bitIndex = this.bitsPerBlock - leftBits;
+ }
+ }
+
+ private void writeLong() throws IOException {
+ if(this.byteIndex + 7 >= this.data.length) {
+ throw new IOException("No space to write.");
+ }
+
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 56);
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 48);
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 40);
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 32);
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 24);
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 16);
+ this.data[this.byteIndex++] = (byte)(this.buffer >> 8);
+ this.data[this.byteIndex++] = (byte)this.buffer;
+
+ this.buffer = 0;
+ }
+
+ public void writeVarInt(int value) throws IOException {
+ while((value & ~0x7F) != 0) {
+ writeByte((value & 0x7F) | 0x80);
+ value >>>= 7;
+ }
+
+ writeByte(value);
+ }
+
+ public void writeByte(int value) throws IOException {
+ if(this.byteIndex >= this.data.length) {
+ throw new IOException("No space to write.");
+ }
+
+ this.data[this.byteIndex++] = (byte)value;
+ }
+}