From 4d352b012fc3d3c5a06561bcbf0d7a818918a769 Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Thu, 12 May 2022 13:34:27 +0800 Subject: [PATCH] Continue making the structure of data --- .../lod/core/objects/a7/LodSection.java | 26 +- .../lod/core/objects/a7/RenderDataSource.java | 3 +- .../a7/data/CompleteDataContainer.java | 17 +- .../core/objects/a7/data/DataFileHandler.java | 13 +- .../core/objects/a7/data/LodDataSource.java | 24 +- .../a7/datatype/column/ColumnDatatype.java | 849 ++++++++++++++++++ .../datatype/column/ColumnRenderBuffer.java | 5 + .../lod/core/objects/a7/pos/DhSectionPos.java | 50 +- .../a7/render/ColumnRenderContainer.java | 60 -- .../a7/render/EmptyRenderContainer.java | 12 +- .../a7/render/RenderBufferHandler.java | 4 +- .../objects/a7/render/RenderContainer.java | 66 -- .../objects/a7/render/RenderDataSource.java | 58 ++ .../core/objects/a7/render/RenderSection.java | 424 --------- .../a7/render/column/ColumnRenderBuffer.java | 5 - 15 files changed, 1021 insertions(+), 595 deletions(-) create mode 100644 src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java create mode 100644 src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java delete mode 100644 src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java delete mode 100644 src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java create mode 100644 src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java delete mode 100644 src/main/java/com/seibel/lod/core/objects/a7/render/RenderSection.java delete mode 100644 src/main/java/com/seibel/lod/core/objects/a7/render/column/ColumnRenderBuffer.java diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java index ce9e80652..f83d17989 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodSection.java @@ -1,7 +1,7 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; -import com.seibel.lod.core.objects.a7.render.RenderContainer; +import com.seibel.lod.core.objects.a7.render.RenderDataSource; import com.seibel.lod.core.util.LodUtil; public class LodSection { @@ -15,33 +15,39 @@ public class LodSection { public byte childCount = 0; // TODO: Should I provide a way to change the render source? - private RenderContainer renderContainer; + private RenderDataSource renderDataSource; + private boolean isLoaded = false; // Create sub region - public LodSection(DhSectionPos pos, RenderDataSource renderSource) { + public LodSection(DhSectionPos pos, com.seibel.lod.core.objects.a7.RenderDataSource renderSource) { this.pos = pos; - this.renderContainer = renderSource.createRenderData(pos); + this.renderDataSource = renderSource.createRenderData(pos); } public void load() { LodUtil.assertTrue(!isLoaded()); - renderContainer.load(); + renderDataSource.load(); + isLoaded = true; } public void unload() { LodUtil.assertTrue(isLoaded()); - renderContainer.unload(); + renderDataSource.unload(); + isLoaded = false; } public void dispose() { - if (renderContainer != null) renderContainer.dispose(); + if (renderDataSource != null) { + if (isLoaded()) renderDataSource.unload(); + renderDataSource.dispose(); + } } public boolean isLoaded() { - return renderContainer != null && renderContainer.isLoaded(); + return renderDataSource != null && isLoaded; } - public RenderContainer getRenderContainer() { - return renderContainer; + public RenderDataSource getRenderContainer() { + return renderDataSource; } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java index 6aed9808b..f5f892502 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataSource.java @@ -1,7 +1,6 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; -import com.seibel.lod.core.objects.a7.render.RenderContainer; public interface RenderDataSource { /** @@ -9,5 +8,5 @@ public interface RenderDataSource { * @param pos The section position. * @return The render container. If there are no data, returns EmptyRenderContainer. */ - RenderContainer createRenderData(DhSectionPos pos); + com.seibel.lod.core.objects.a7.render.RenderDataSource createRenderData(DhSectionPos pos); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/CompleteDataContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/data/CompleteDataContainer.java index 6d8d51c62..c672ecf4d 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/CompleteDataContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/CompleteDataContainer.java @@ -1,14 +1,17 @@ package com.seibel.lod.core.objects.a7.data; import com.seibel.lod.core.objects.a7.IdMappingUtil; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper; +import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.function.Function; -public class CompleteDataContainer extends LodDataSource { // 1 chunk +public class CompleteDataContainer implements LodDataSource { // 1 chunk + private DhSectionPos sectionPos; ArrayList idMap; protected CompleteDataContainer() { @@ -16,8 +19,11 @@ public class CompleteDataContainer extends LodDataSource { // 1 chunk } @Override - public Function getLatestLoader() { - return null; + public DataSourceLoader getLatestLoader() { + return (DhSectionPos sectionPos, InputStream inputStream) -> { + //TODO: Implement + return null; + }; } @Override @@ -25,6 +31,11 @@ public class CompleteDataContainer extends LodDataSource { // 1 chunk return null; } + @Override + public DhSectionPos getSectionPos() { + return sectionPos; + } + public static CompleteDataContainer createNewFromChunk(IChunkWrapper chunk) { CompleteDataContainer dataContainer = new CompleteDataContainer(); HashMap idMap = new HashMap(); diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java index e54756299..a4bdc1c89 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java @@ -1,16 +1,15 @@ package com.seibel.lod.core.objects.a7.data; -import com.seibel.lod.core.objects.a7.RenderDataSource; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.a7.render.EmptyRenderContainer; -import com.seibel.lod.core.objects.a7.render.RenderContainer; +import com.seibel.lod.core.objects.a7.render.RenderDataSource; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.HashMap; -public class DataFileHandler implements RenderDataSource { +public class DataFileHandler implements com.seibel.lod.core.objects.a7.RenderDataSource { public final File folder; private final HashMap dataSourceCache; @@ -20,11 +19,11 @@ public class DataFileHandler implements RenderDataSource { } @Override - public RenderContainer createRenderData(DhSectionPos pos) { + public RenderDataSource createRenderData(DhSectionPos pos) { LodDataSource dataSource = getDataSource(pos); - RenderContainer renderContainer = RenderContainer.tryConstruct(dataSource, pos); - if (renderContainer == null) renderContainer = EmptyRenderContainer.INSTANCE; - return renderContainer; + RenderDataSource renderDataSource = RenderDataSource.tryConstruct(dataSource, pos); + if (renderDataSource == null) renderDataSource = EmptyRenderContainer.INSTANCE; + return renderDataSource; } private LodDataSource getDataSource(DhSectionPos pos) { diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java index 32c9ab09f..3198b6f68 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/LodDataSource.java @@ -1,16 +1,24 @@ package com.seibel.lod.core.objects.a7.data; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.a7.render.RenderDataSource; + import java.io.InputStream; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.function.Function; -public abstract class LodDataSource { - private static final String REGISTER_STRING_FILTER_REGEX = "^[a-zA-Z0-9_]*$"; - public static final HashMap> +public interface LodDataSource { + String REGISTER_STRING_FILTER_REGEX = "^[a-zA-Z0-9_]*$"; + HashMap> dataSourceLoaderRegistry = new HashMap>(); - public static void registerDataSourceLoader(String name, int version, Function loader) { + interface DataSourceLoader { + // Can return null as meaning the requirement is not met + LodDataSource loadData(DhSectionPos sectionPos, InputStream data); + } + + static void registerDataSourceLoader(String name, int version, Function loader) { if (name == null || loader == null || name.isEmpty()) { throw new IllegalArgumentException("Name and loader must be non-null, and not empty"); } @@ -23,7 +31,7 @@ public abstract class LodDataSource { dataSourceLoaderRegistry.put(name+"$"+version, loader); } - public static LodDataSource loadData(String dataSourceTypeNameVersion, InputStream data) { + static LodDataSource loadData(String dataSourceTypeNameVersion, InputStream data) { Function loader = dataSourceLoaderRegistry.get(dataSourceTypeNameVersion); if (loader == null) { @@ -31,7 +39,9 @@ public abstract class LodDataSource { } return loader.apply(data); } - public abstract Function getLatestLoader(); + DataSourceLoader getLatestLoader(); - public abstract T[] getData(); //TODO & FIXME: What is T? + T[] getData(); //TODO & FIXME: What is T? + + DhSectionPos getSectionPos(); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java new file mode 100644 index 000000000..4a8fb13e4 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java @@ -0,0 +1,849 @@ +package com.seibel.lod.core.objects.a7.datatype.column; + +import com.seibel.lod.core.objects.LodDataView; +import com.seibel.lod.core.objects.a7.RenderDataContainer; +import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.a7.render.RenderDataSource; +import com.seibel.lod.core.objects.opengl.RenderBuffer; +import com.seibel.lod.core.util.DataPointUtil; +import com.seibel.lod.core.util.DetailDistanceUtil; +import com.seibel.lod.core.util.LodUtil; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +public class ColumnDatatype implements LodDataSource, RenderDataSource { + public static final boolean DO_SAFETY_CHECKS = true; + public static final int SECTION_SIZE = DhSectionPos.DATA_WIDTH_PER_SECTION; + public static final int LATEST_VERSION = 9; + public final int AIR_LODS_SIZE = 16; + public final int AIR_SECTION_SIZE = SECTION_SIZE/AIR_LODS_SIZE; + public final int verticalSize; + public final DhSectionPos sectionPos; + + public final long[] dataContainer; + public final int[] airDataContainer; + + /** + * Constructor of the RenderDataContainer + * @param maxVerticalSize the maximum vertical size of the container + */ + public ColumnDatatype(DhSectionPos sectionPos, int maxVerticalSize) { + verticalSize = maxVerticalSize; + dataContainer = new long[SECTION_SIZE * SECTION_SIZE * verticalSize]; + airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; + this.sectionPos = sectionPos; + } + + // Load from data stream with maxVerticalSize loaded from the data stream + public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version) throws IOException { + this.sectionPos = sectionPos; + byte detailLevel = inputData.readByte(); + if (sectionPos.detail != detailLevel) { + throw new IOException("Invalid data: detail level does not match"); + } + verticalSize = inputData.readByte() & 0b01111111; + switch (version) { + case 6: + dataContainer = readDataVersion6(inputData, verticalSize, sectionPos.yOffset); + break; + case 7: + dataContainer = readDataVersion7(inputData, verticalSize, sectionPos.yOffset); + break; + case 8: + dataContainer = readDataVersion8(inputData, verticalSize, sectionPos.yOffset); + break; + case 9: + dataContainer = readDataVersion9(inputData, verticalSize, sectionPos.yOffset); + break; + default: + throw new IOException("Invalid Data: The version of the data is not supported"); + } + airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; + } + + // Load from data stream with new maxVerticalSize + public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version, int maxVerticalSize) throws IOException { + verticalSize = maxVerticalSize; + this.sectionPos = sectionPos; + byte detailLevel = inputData.readByte(); + if (sectionPos.detail != detailLevel) { + throw new IOException("Invalid data: detail level does not match"); + } + int fileMaxVerticalSize = inputData.readByte() & 0b01111111; + long[] fileDataContainer = null; + switch (version) { + case 6: + fileDataContainer = readDataVersion6(inputData, fileMaxVerticalSize, sectionPos.yOffset); + break; + case 7: + fileDataContainer = readDataVersion7(inputData, fileMaxVerticalSize, sectionPos.yOffset); + break; + case 8: + fileDataContainer = readDataVersion8(inputData, fileMaxVerticalSize, sectionPos.yOffset); + break; + case 9: + fileDataContainer = readDataVersion9(inputData, fileMaxVerticalSize, sectionPos.yOffset); + break; + default: + throw new IOException("Invalid Data: The version of the data is not supported"); + } + dataContainer = DataPointUtil.changeMaxVertSize(fileDataContainer, fileMaxVerticalSize, verticalSize); + airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; + } + + // Copy constructor + public ColumnDatatype(DhSectionPos sectionPos, LodDataSource dataSource, int maxVerticalData) { + verticalSize = maxVerticalData; + this.sectionPos = sectionPos; + dataContainer = new long[SECTION_SIZE * SECTION_SIZE * verticalSize]; + airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; + DhSectionPos sourcePos = dataSource.getSectionPos(); + if (!sourcePos.overlaps(sectionPos)) { + throw new IllegalArgumentException("The source section does not overlap with new target position"); + } + if (sourcePos.detail > sectionPos.detail) { + throw new IllegalArgumentException("The source section has higher detail than new target detail"); + } + if (sourcePos.yOffset != sectionPos.yOffset) { + throw new IllegalArgumentException("Different yOffset is not yet supported"); // TODO: is this needed? + } + + if (sourcePos.equals(sectionPos)) { + //TODO: Simple full copy. + } else { + //TODO: Downsample copy. + } + } + + /** + * This method will clear all data at relative section position + * @param posX + * @param posZ + */ + public void clear(int posX, int posZ) + { + for (int verticalIndex = 0; verticalIndex < verticalSize; verticalIndex++) + dataContainer[posX * SECTION_SIZE * verticalSize + posZ * verticalSize + verticalIndex] = DataPointUtil.EMPTY_DATA; + } + + /** + * This method will add the data given in input at the relative position and vertical index + * @param data + * @param posX + * @param posZ + * @param verticalIndex + * @return + */ + public boolean addData(long data, int posX, int posZ, int verticalIndex) + { + dataContainer[posX * SECTION_SIZE * verticalSize + posZ * verticalSize + verticalIndex] = data; + return true; + } + + /** + * This section will fill the data given in input at the given position + * @param data + * @param posX + * @param posZ + */ + private void forceWriteVerticalData(long[] data, int posX, int posZ) + { + int index = posX * SECTION_SIZE * verticalSize + posZ * verticalSize; + if (verticalSize >= 0) System.arraycopy(data, 0, dataContainer, index + 0, verticalSize); + } + + /** + * This methods will add the data in the given position if certain condition are satisfied + * @param data + * @param posX + * @param posZ + * @param override if override is true we can override data created with same generation mode + * @return + */ + public boolean addVerticalData(long[] data, int posX, int posZ, boolean override) + { + int index = posX * SECTION_SIZE * verticalSize + posZ * verticalSize; + int compare = DataPointUtil.compareDatapointPriority(data[0], dataContainer[index]); + if (override) { + if (compare<0) return false; + } else { + if (compare<=0) return false; + } + forceWriteVerticalData(data, posX, posZ); + return true; + } + + public boolean copyVerticalData(LodDataView data, int posX, int posZ, boolean override) { + if (DO_SAFETY_CHECKS) { + if (data.size() != verticalSize) + throw new IllegalArgumentException("data size not the same as vertical size"); + if (posX < 0 || posX >= SECTION_SIZE) + throw new IllegalArgumentException("X position is out of bounds"); + if (posZ < 0 || posZ >= SECTION_SIZE) + throw new IllegalArgumentException("Z position is out of bounds"); + } + int index = posX * SECTION_SIZE * verticalSize + posZ * verticalSize; + int compare = DataPointUtil.compareDatapointPriority(data.get(0), dataContainer[index]); + if (override) { + if (compare<0) return false; + } else { + if (compare<=0) return false; + } + data.copyTo(dataContainer, index); + return true; + } + + public boolean addChunkOfData(long[] data, int posX, int posZ, int widthX, int widthZ, boolean override) + { + boolean anyChange = false; + if (posX+widthX > SECTION_SIZE || posZ+widthZ > SECTION_SIZE) + throw new IndexOutOfBoundsException("addChunkOfData param not inside valid range"); + if (widthX*widthZ*verticalSize != data.length) + throw new IndexOutOfBoundsException("addChunkOfData data array not sized correctly to contain the data to be copied"); + if (posX<0 || posZ<0 || widthX<0 || widthZ<0) + throw new IndexOutOfBoundsException("addChunkOfData param is negative"); + + for (int ox=0; ox SECTION_SIZE || posZ+widthZ > SECTION_SIZE) + throw new IndexOutOfBoundsException("addChunkOfData param not inside valid range"); + if (widthX*widthZ*verticalSize != data.size()) + throw new IndexOutOfBoundsException("addChunkOfData data array not sized correctly to contain the data to be copied"); + if (posX<0 || posZ<0 || widthX<0 || widthZ<0) + throw new IndexOutOfBoundsException("addChunkOfData param is negative"); + + for (int ox=0; ox tLocalVerticalUpdateArrays = ThreadLocal.withInitial(() -> + { + return new long[LodUtil.DETAIL_OPTIONS - 1][]; + }); + + public void updateData(RenderDataContainer lowerRenderContainer, int posX, int posZ) + { + //We reset the array + long[][] verticalUpdateArrays = tLocalVerticalUpdateArrays.get(); + long[] dataToMerge = verticalUpdateArrays[sectionPos.detail-1]; + int arrayLength = DetailDistanceUtil.getMaxVerticalData(sectionPos.detail-1) * 4; + if (dataToMerge == null || dataToMerge.length != arrayLength) { + dataToMerge = new long[arrayLength]; + verticalUpdateArrays[sectionPos.detail-1] = dataToMerge; + } else Arrays.fill(dataToMerge, 0); + + //int lowerMaxVertical = dataToMerge.length / 4; + int lowerSectionSize = lowerRenderContainer.getSECTION_SIZE(); + int childPosStartX = Math.floorMod(2 * posX, lowerSectionSize); + int childPosEndX = Math.floorMod(2 * posX + 1, lowerSectionSize); + int childPosStartZ = Math.floorMod(2 * posZ, lowerSectionSize); + int childPosEndZ = Math.floorMod(2 * posZ + 1, lowerSectionSize); + + long[] data; + boolean anyDataExist = false; + + mergeAndAddDataFromOtherContainer(posX, posZ, lowerRenderContainer, childPosStartX, childPosEndX, childPosStartZ, childPosEndZ); + /* + TODO remove this old code when we are sure that this works + for (int x = 0; x <= 1; x++) + { + for (int z = 0; z <= 1; z++) + { + childPosX = 2 * posX + x; + childPosZ = 2 * posZ + z; + if (lowerLevelContainer.doesItExist(childPosX, childPosZ)) anyDataExist = true; + for (int verticalIndex = 0; verticalIndex < lowerMaxVertical; verticalIndex++) + dataToMerge[(z * 2 + x) * lowerMaxVertical + verticalIndex] = lowerLevelContainer.getData(childPosX, childPosZ, verticalIndex); + } + } + if (!anyDataExist) + throw new RuntimeException("Update data called but no child datapoint exist!"); + + if ((!DataPointUtil.doesItExist(data[0])) && anyDataExist) + throw new RuntimeException("Update data called but higher level datapoint doesn't exist even though child data does exist!"); + + //FIXME: Disabled check if genMode for old data is already invalid due to having genMode 0. + if (DataPointUtil.getGenerationMode(data[0]) != DataPointUtil.getGenerationMode(lowerLevelContainer.getSingleData(posX*2, posZ*2))) + throw new RuntimeException("Update data called but higher level datapoint does not have the same GenerationMode as the top left corner child datapoint!"); + + forceWriteVerticalData(data, posX, posZ);*/ + } + + public boolean writeData(DataOutputStream output) throws IOException { + output.writeByte(sectionPos.detail); + output.writeByte((byte) verticalSize); + // FIXME: yOffset is a int, but we only are writing a short. + output.writeByte((byte) (sectionPos.yOffset & 0xFF)); + output.writeByte((byte) ((sectionPos.yOffset >> 8) & 0xFF)); + boolean allGenerated = true; + int x = SECTION_SIZE * SECTION_SIZE; + for (int i = 0; i < x; i++) + { + for (int j = 0; j < verticalSize; j++) + { + long current = dataContainer[i * verticalSize + j]; + output.writeLong(Long.reverseBytes(current)); + } + if (!DataPointUtil.doesItExist(dataContainer[i])) + allGenerated = false; + } + return allGenerated; + } + + public String toString() + { + String LINE_DELIMITER = "\n"; + String DATA_DELIMITER = " "; + String SUBDATA_DELIMITER = ","; + StringBuilder stringBuilder = new StringBuilder(); + int size = sectionPos.getWidth().value; + stringBuilder.append(sectionPos); + stringBuilder.append(LINE_DELIMITER); + for (int z = 0; z < size; z++) + { + for (int x = 0; x < size; x++) + { + for (int y = 0; y < verticalSize; y++) { + //Converting the dataToHex + stringBuilder.append(Long.toHexString(getData(x,z,y))); + if (y != verticalSize-1) stringBuilder.append(SUBDATA_DELIMITER); + } + if (x != size-1) stringBuilder.append(DATA_DELIMITER); + } + if (z != size-1) stringBuilder.append(LINE_DELIMITER); + } + return stringBuilder.toString(); + } + + public int getMaxNumberOfLods() + { + return SECTION_SIZE * SECTION_SIZE * getVerticalSize(); + } + + public long getRoughRamUsage() + { + return (long) dataContainer.length * Long.BYTES; + } + + + + private static final ThreadLocal tLocalHeightAndDepth = new ThreadLocal(); + private static final ThreadLocal tDataIndexCache = new ThreadLocal(); + /** + * + * This method merge column of multiple data together + */ + // TODO: Make this operate on a out param array, to allow skipping copy array on use + public void mergeAndAddDataFromOtherContainer(int mergeInX, int mergeInZ, RenderDataContainer lowerDataContainer, int mergeFromX, int mergeToX, int mergeFromZ, int mergeToZ) + { + int outBaseIndex = mergeInX * SECTION_SIZE * verticalSize + mergeInZ*verticalSize; + int inputVerticalSize = lowerDataContainer.verticalSize; + int inputSectionSize = lowerDataContainer.SECTION_SIZE; + int mergeFromToX; + int xSize = (mergeFromX - mergeToX + 1); + int zSize = (mergeFromZ - mergeToZ + 1); + //size indicate how many position we are merging in one position + + // We initialize the arrays that are going to be used + int heightAndDepthLength = (DataPointUtil.MAX_WORLD_Y_SIZE / 2 + 16) * 2; + short[] heightAndDepth = tLocalHeightAndDepth.get(); + if (heightAndDepth==null || heightAndDepth.length != heightAndDepthLength) { + heightAndDepth = new short[heightAndDepthLength]; + tLocalHeightAndDepth.set(heightAndDepth); + } + int dataPointLength = verticalSize; + + int firstIndex = mergeFromX*SECTION_SIZE*inputVerticalSize + mergeFromZ * inputVerticalSize; + byte genMode = DataPointUtil.getGenerationMode(lowerDataContainer.dataContainer[firstIndex]); + if (genMode == 0) genMode = 1; // FIXME: Hack to make the version 10 genMode never be 0. + boolean allEmpty = true; + boolean allVoid = true; + boolean limited = false; + boolean allDefault; + long singleData; + + + short depth; + short height; + int count = 0; + int i; + int ii; + + //We collect the indexes of the data, ordered by the depth + int dataIndex = 0; + int x; + int z; + int y; + for (x = mergeFromX; x <= mergeToX; x++) + { + for (z = mergeFromZ; z <= mergeToZ; z++) + { + if (x == mergeFromX && z == mergeFromZ) + { + for (y = 0; y < inputVerticalSize; y++) + { + dataIndex = x * inputSectionSize * inputVerticalSize + z * inputVerticalSize + y; + singleData = lowerDataContainer.dataContainer[dataIndex]; + if (DataPointUtil.doesItExist(singleData)) + { + //genMode = Math.min(genMode, getGenerationMode(singleData)); + allEmpty = false; + if (!DataPointUtil.isVoid(singleData)) + { + allVoid = false; + count++; + heightAndDepth[dataIndex * 2] = DataPointUtil.getHeight(singleData); + heightAndDepth[dataIndex * 2 + 1] = DataPointUtil.getDepth(singleData); + } + } + else + break; + } + } + else + { + for (y = 0; y < inputVerticalSize; y++) + { + dataIndex = x * inputSectionSize * inputVerticalSize + z * inputVerticalSize + y; + singleData = lowerDataContainer.dataContainer[dataIndex]; + if (DataPointUtil.doesItExist(singleData)) + { + //genMode = Math.min(genMode, getGenerationMode(singleData)); + allEmpty = false; + if (!DataPointUtil.isVoid(singleData)) + { + allVoid = false; + depth = DataPointUtil.getDepth(singleData); + height = DataPointUtil.getHeight(singleData); + + int botPos = -1; + int topPos = -1; + //values fall in between and possibly require extension of array + boolean botExtend = false; + boolean topExtend = false; + for (i = 0; i < count; i++) + { + if (depth < heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1]) + { + botPos = i; + break; + } + else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth >= heightAndDepth[(i + 1) * 2]) || i + 1 == count)) + { + botPos = i; + botExtend = true; + break; + } + } + for (i = 0; i < count; i++) + { + if (height <= heightAndDepth[i * 2] && height > heightAndDepth[i * 2 + 1]) + { + topPos = i; + break; + } + else if (height <= heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count)) + { + topPos = i; + topExtend = true; + break; + } + } + if (topPos == -1) + { + if (botPos == -1) + { + //whole block falls above + DataPointUtil.extendArray(heightAndDepth, 2, 0, 1, count); + heightAndDepth[0] = height; + heightAndDepth[1] = depth; + count++; + } + else if (!botExtend) + { + //only top falls above extending it there, while bottom is inside existing + DataPointUtil.shrinkArray(heightAndDepth, 2, 0, botPos, count); + heightAndDepth[0] = height; + count -= botPos; + } + else + { + //top falls between some blocks, extending those as well + DataPointUtil.shrinkArray(heightAndDepth, 2, 0, botPos, count); + heightAndDepth[0] = height; + heightAndDepth[1] = depth; + count -= botPos; + } + } + else if (!topExtend) + { + if (!botExtend) + //both top and bottom are within some exiting blocks, possibly merging them + heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; + else + //top falls between some blocks, extending it there + heightAndDepth[topPos * 2 + 1] = depth; + DataPointUtil.shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + } + else + { + if (!botExtend) + { + //only top is within some exiting block, extending it + topPos++; //to make it easier + heightAndDepth[topPos * 2] = height; + heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; + DataPointUtil.shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + } + else + { + //both top and bottom are outside existing blocks + DataPointUtil.shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + DataPointUtil.extendArray(heightAndDepth, 2, topPos + 1, 1, count); + count++; + heightAndDepth[topPos * 2 + 2] = height; + heightAndDepth[topPos * 2 + 3] = depth; + } + } + } + } + else + break; + } + } + } + } + //We check if there is any data that's not empty or void + if (allEmpty) + return; + if (allVoid) + { + dataContainer[outBaseIndex] = DataPointUtil.createVoidDataPoint(genMode); + return; + } + + //we limit the vertical portion to maxVerticalData + int j = 0; + while (count > verticalSize) + { + limited = true; + ii = DataPointUtil.MAX_WORLD_Y_SIZE; + for (i = 0; i < count - 1; i++) + { + if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii) + { + ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2]; + j = i; + } + } + heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1]; + for (i = j + 1; i < count - 1; i++) + { + heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2]; + heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1]; + } + //System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1); + count--; + } + int yOut; + //As standard the vertical lods are ordered from top to bottom + if (!limited && xSize*zSize == 1) + { + for (yOut = 0; yOut < count; yOut++) + dataIndex = mergeFromX*inputSectionSize*inputVerticalSize + mergeFromZ*inputVerticalSize + yOut; + dataContainer[outBaseIndex + yOut] = lowerDataContainer.dataContainer[dataIndex]; + } + else + { + + //We want to efficiently memorize indexes + int[] dataIndexesCache = tDataIndexCache.get(); + if (dataIndexesCache==null || dataIndexesCache.length != xSize*zSize) { + dataIndexesCache = new int[xSize*zSize]; + tDataIndexCache.set(dataIndexesCache); + } + Arrays.fill(dataIndexesCache,0); + + //For each lod height-depth value we have found we now want to generate the rest of the data + //by merging all lods at lower level that are contained inside the new ones + for (yOut = 0; yOut < count; yOut++) + { + //We firstly collect height and depth data + //this will be added to each realtive long DataPoint + height = heightAndDepth[yOut * 2]; + depth = heightAndDepth[yOut * 2 + 1]; + + //if both height and depth are at 0 then we finished + if ((depth == 0 && height == 0) || yOut >= heightAndDepth.length / 2) + break; + + //We initialize data useful for the merge + int numberOfChildren = 0; + allEmpty = true; + allVoid = true; + + //We initialize all the new values that we are going to put in the dataPoint + int tempAlpha = 0; + int tempRed = 0; + int tempGreen = 0; + int tempBlue = 0; + int tempLightBlock = 0; + int tempLightSky = 0; + long data = 0; + + int index; + + //For each position that we want to merge + for(x = mergeFromX; x <= mergeToX; x++) + { + for (z = mergeFromZ; z <= mergeToZ; z++) + { + index = x * xSize + z; + //we scan the lods in the position from top to bottom + while (dataIndexesCache[index] < inputVerticalSize) + { + y = dataIndexesCache[index]; + dataIndex = x * inputSectionSize * inputVerticalSize + z * inputVerticalSize + y; + + singleData = lowerDataContainer.dataContainer[dataIndex]; + if (DataPointUtil.doesItExist(singleData) && !DataPointUtil.isVoid(singleData)) + { + dataIndexesCache[index]++; + if ((depth <= DataPointUtil.getDepth(singleData) && DataPointUtil.getDepth(singleData) < height) + || (depth < DataPointUtil.getHeight(singleData) && DataPointUtil.getHeight(singleData) <= height)) + { + data = singleData; + break; + } + } + else + break; + } + if (!DataPointUtil.doesItExist(data)) + { + data = DataPointUtil.createVoidDataPoint(genMode); + } + + if (DataPointUtil.doesItExist(data)) + { + allEmpty = false; + if (!DataPointUtil.isVoid(data)) + { + numberOfChildren++; + allVoid = false; + tempAlpha = Math.max(DataPointUtil.getAlpha(data), tempAlpha); + tempRed += DataPointUtil.getRed(data) * DataPointUtil.getRed(data); + tempGreen += DataPointUtil.getGreen(data) * DataPointUtil.getGreen(data); + tempBlue += DataPointUtil.getBlue(data) * DataPointUtil.getBlue(data); + tempLightBlock += DataPointUtil.getLightBlock(data); + tempLightSky += DataPointUtil.getLightSky(data); + } + } + } + } + + if (allEmpty) + //no child has been initialized + dataContainer[outBaseIndex + yOut] = DataPointUtil.EMPTY_DATA; + else if (allVoid) + //all the children are void + dataContainer[outBaseIndex + yOut] = DataPointUtil.createVoidDataPoint(genMode); + else + { + //we have at least 1 child + if (xSize*zSize != 1) + { + tempRed = tempRed / numberOfChildren; + tempGreen = tempGreen / numberOfChildren; + tempBlue = tempBlue / numberOfChildren; + tempLightBlock = tempLightBlock / numberOfChildren; + tempLightSky = tempLightSky / numberOfChildren; + } + //data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault); + //if (j > 0 && getColor(data) == getColor(dataPoint[j])) + //{ + // add simplification at the end due to color + //} + dataContainer[outBaseIndex + yOut] = DataPointUtil.createDataPoint((int) Math.sqrt(tempAlpha), (int) Math.sqrt(tempRed), (int) Math.sqrt(tempGreen), (int) Math.sqrt(tempBlue), height, depth, tempLightSky, tempLightBlock, genMode); + } + } + } + } + + public static LodDataSource load(DhSectionPos pos, InputStream is, int version) { + try (DataInputStream dis = new DataInputStream(is)) { + return new ColumnDatatype(pos, dis, version); + } catch (IOException e) { + //FIXME: Log error + return null; + } + } + + public static RenderDataSource loadByCasting(LodDataSource dataSource, DhSectionPos sectionPos) { + if (dataSource instanceof ColumnDatatype) { + return (RenderDataSource) dataSource; + } + return null; + } + public static RenderDataSource loadByCopying(LodDataSource dataSource, DhSectionPos sectionPos) { + ColumnDatatype columns = new ColumnDatatype(sectionPos, dataSource, + DetailDistanceUtil.getMaxVerticalData(sectionPos.detail)); + + return null; + } + static { + RenderDataSource.registorLoader(ColumnDatatype::loadByCasting, 100); + } + + @Override + public DataSourceLoader getLatestLoader() { + return (DhSectionPos sectionPos, InputStream is) -> load(sectionPos, is, LATEST_VERSION); + } + + @Override + public T[] getData() { + return null; + } + + @Override + public DhSectionPos getSectionPos() { + return sectionPos; + } + + @Override + public void load() { + } + + @Override + public void unload() { + } + + @Override + public void dispose() { + } + + @Override + public boolean trySwapRenderBuffer(AtomicReference referenceSlot) { + return false; + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java new file mode 100644 index 000000000..d06458b01 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnRenderBuffer.java @@ -0,0 +1,5 @@ +package com.seibel.lod.core.objects.a7.datatype.column; + +public class ColumnRenderBuffer { + +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java index 88f1dd758..dc3ffdde2 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java @@ -1,8 +1,6 @@ package com.seibel.lod.core.objects.a7.pos; import com.seibel.lod.core.enums.LodDirection; -import com.seibel.lod.core.objects.a7.DHLevel; -import org.lwjgl.system.CallbackI; import java.util.function.Consumer; @@ -13,12 +11,25 @@ public class DhSectionPos { public final byte detail; public final int x; public final int z; + public final int yOffset; public DhSectionPos(byte detail, int x, int z) { this.detail = detail; this.x = x; this.z = z; + this.yOffset = 0; } + public DhSectionPos(byte detail, int x, int z, int yOffset) { + this.detail = detail; + this.x = x; + this.z = z; + this.yOffset = yOffset; + } + + public DhSectionPos withOffset(int yOffset) { + return new DhSectionPos(detail, x, z, yOffset); + } + public DhLodPos getCenter() { return new DhLodPos(detail, x * DATA_WIDTH_PER_SECTION + DATA_WIDTH_PER_SECTION / 2, z * DATA_WIDTH_PER_SECTION + DATA_WIDTH_PER_SECTION / 2); @@ -39,7 +50,7 @@ public class DhSectionPos { public DhSectionPos getChild(int child0to3){ if (child0to3 < 0 || child0to3 > 3) throw new IllegalArgumentException("child0to3 must be between 0 and 3"); if (detail == 0) throw new IllegalStateException("detail must be greater than 0"); - return new DhSectionPos((byte) (detail - 1), x * 2 + (child0to3 & 1), z * 2 + (child0to3 & 2) / 2); + return new DhSectionPos((byte) (detail - 1), x * 2 + (child0to3 & 1), z * 2 + (child0to3 & 2) / 2, yOffset); } public void forEachChild(Consumer callback){ @@ -49,10 +60,39 @@ public class DhSectionPos { } public DhSectionPos getParent(){ - return new DhSectionPos((byte) (detail + 1), x / 2, z / 2); + return new DhSectionPos((byte) (detail + 1), x / 2, z / 2, yOffset); } public DhSectionPos getAdjacent(LodDirection dir) { - return new DhSectionPos(detail, x + dir.getNormal().x, z + dir.getNormal().z); + return new DhSectionPos(detail, x + dir.getNormal().x, z + dir.getNormal().z, yOffset); + } + + public DhSectionPos convertUpwardsTo(byte newDetail){ + if (detail == newDetail) return this; + if (detail > newDetail) return new DhSectionPos(newDetail, x >> (detail - newDetail), z >> (detail - newDetail), yOffset); + throw new IllegalArgumentException("newDetail must be greater than detail"); + } + + /** + * NOTE: This equals() does not consider yOffset! + */ + + public boolean equals(Object o){ + if (o == this) return true; + if (!(o instanceof DhSectionPos)) return false; + DhSectionPos other = (DhSectionPos) o; + return detail == other.detail && x == other.x && z == other.z; + } + + /** + * NOTE: This does not consider yOffset! + */ + public boolean overlaps(DhSectionPos other){ + if (this.equals(other)) + return true; + else if (detail < other.detail) + return other.equals(this.convertUpwardsTo(other.detail)); + else + return this.equals(other.convertUpwardsTo(detail)); } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java deleted file mode 100644 index 14f484961..000000000 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/ColumnRenderContainer.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.seibel.lod.core.objects.a7.render; - -import com.seibel.lod.core.objects.a7.LodSection; -import com.seibel.lod.core.objects.a7.RenderDataContainer; -import com.seibel.lod.core.objects.a7.data.LodDataSource; -import com.seibel.lod.core.objects.a7.pos.DhSectionPos; -import com.seibel.lod.core.objects.opengl.RenderBuffer; -import com.seibel.lod.core.objects.opengl.RenderRegion; -import com.seibel.lod.core.util.LodUtil; - -import java.util.concurrent.atomic.AtomicReference; - -public class ColumnRenderContainer extends RenderContainer { - public static final int columnWidth = DhSectionPos.DATA_WIDTH_PER_SECTION; - public static final int columnCount = LodUtil.pow2(DhSectionPos.DATA_WIDTH_PER_SECTION); - - public RenderDataContainer dataContainer = null; - - public final int maxColumnHeight; - public final int minWorldHeight; - - public static RenderContainer testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos) { - ColumnRenderContainer container = new ColumnRenderContainer(10, -100); //FIXME: Use actual config value - container.startFillData(dataSource); - return container; - } - static { - RenderContainer.registorLoader(ColumnRenderContainer::testAndConstruct, 0); - } - - public ColumnRenderContainer(int maxColumnHeight, int minWorldHeight) { - this.maxColumnHeight = maxColumnHeight; - //columnData = new long[columnCount * maxColumnHeight]; - this.minWorldHeight = minWorldHeight; - } - - private void startFillData(LodDataSource dataSource) { - //TODO - } - - @Override - public void notifyLoad() { - - } - - @Override - public void notifyUnload() { - - } - - @Override - public void notifyDispose() { - - } - - @Override - public boolean trySwapRenderBuffer(AtomicReference referenceSlot) { - return false; - } -} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java index cf63d8d5c..e1d62345d 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/EmptyRenderContainer.java @@ -1,24 +1,28 @@ package com.seibel.lod.core.objects.a7.render; +import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.opengl.RenderBuffer; import java.util.concurrent.atomic.AtomicReference; -public class EmptyRenderContainer extends RenderContainer { +public class EmptyRenderContainer implements RenderDataSource { public static final EmptyRenderContainer INSTANCE = new EmptyRenderContainer(); + // NOTE: No register() needed since this should never be loaded from a actual data. + @Override - public void notifyLoad() { + public void load() { } @Override - public void notifyUnload() { + public void unload() { } @Override - public void notifyDispose() { + public void dispose() { } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java index cc12d1111..6b03e4396 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java @@ -46,7 +46,7 @@ public class RenderBufferHandler { // If this fails, there may be concurrent modification of the quad tree // (as this update() should be called from the same thread that calls update() on the quad tree) LodUtil.assertTrue(section != null); - RenderContainer container = section.getRenderContainer(); + RenderDataSource container = section.getRenderContainer(); // Update self's render buffer state boolean shouldRender = section.isLoaded(); @@ -60,7 +60,7 @@ public class RenderBufferHandler { } // Update children's render buffer state - boolean shouldHaveChildren = !container.isLoaded(); + boolean shouldHaveChildren = !section.isLoaded(); if (shouldHaveChildren) { if (children == null) { RenderBufferNode[] childs = new RenderBufferNode[4]; diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java deleted file mode 100644 index 9062b75ce..000000000 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderContainer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.seibel.lod.core.objects.a7.render; - -import com.seibel.lod.core.objects.a7.LodSection; -import com.seibel.lod.core.objects.a7.data.LodDataSource; -import com.seibel.lod.core.objects.a7.pos.DhSectionPos; -import com.seibel.lod.core.objects.opengl.RenderBuffer; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicReference; - -public abstract class RenderContainer { - interface RenderContainerConstructor { - // Can return null as meaning the requirement is not met - RenderContainer testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos); - } - public static final SortedMap - renderContainerLoaderRegistry = new TreeMap(); - public static void registorLoader(RenderContainerConstructor func, int priority) { - if (func == null) { - throw new IllegalArgumentException("loader must be non-null"); - } - renderContainerLoaderRegistry.put(priority, func); - } - - public static RenderContainer tryConstruct(LodDataSource dataSource, DhSectionPos pos) { - for (RenderContainerConstructor func : renderContainerLoaderRegistry.values()) { - RenderContainer container = func.testAndConstruct(dataSource, pos); - if (container != null) { - return container; - } - } - return null; - } - - private boolean isLoaded = false; - public final void load() { - isLoaded = true; - notifyLoad(); - } - public final void unload() { - isLoaded = false; - notifyUnload(); - } - public final void dispose() { - if (isLoaded) { - unload(); - } - notifyDispose(); - } - - public final boolean isLoaded() { - return isLoaded; - } - - protected abstract void notifyLoad(); // notify the container that it is now loaded and therefore may be rendered - protected abstract void notifyUnload(); // notify the container that it is now unloaded and therefore will not be rendered - protected abstract void notifyDispose(); // notify the container that the parent lodSection is now disposed - - /** - * Try and swap in new render buffer for this section. Note that before this call, there should be no other - * places storing or referencing the render buffer. - * @param referenceSlot The slot for swapping in the new buffer. - * @return True if the swap was successful. False if swap is not needed or if it is in progress. - */ - public abstract boolean trySwapRenderBuffer(AtomicReference referenceSlot); -} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java new file mode 100644 index 000000000..24f3c7bb4 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java @@ -0,0 +1,58 @@ +package com.seibel.lod.core.objects.a7.render; + +import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; +import com.seibel.lod.core.objects.opengl.RenderBuffer; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Example on how to register a loader: + *
+     public static RenderDataSource testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos) {
+        ColumnRenderContainer container = new ColumnRenderContainer(10, -100);
+        container.startFillData(dataSource);
+        return container;
+     }
+     static {
+        RenderDataSource.registorLoader(ColumnRenderContainer::testAndConstruct, 0);
+     }
+ 
+ */ +public interface RenderDataSource { + interface RenderContainerConstructor { + // Can return null as meaning the requirement is not met + RenderDataSource testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos); + } + SortedMap + renderContainerLoaderRegistry = new TreeMap(); + static void registorLoader(RenderContainerConstructor func, int priority) { + if (func == null) { + throw new IllegalArgumentException("loader must be non-null"); + } + renderContainerLoaderRegistry.put(priority, func); + } + + static RenderDataSource tryConstruct(LodDataSource dataSource, DhSectionPos pos) { + for (RenderContainerConstructor func : renderContainerLoaderRegistry.values()) { + RenderDataSource container = func.testAndConstruct(dataSource, pos); + if (container != null) { + return container; + } + } + return null; + } + void load(); // notify the container that it is now loaded and therefore may be rendered + void unload(); // notify the container that it is now unloaded and therefore will not be rendered + void dispose(); // notify the container that the parent lodSection is now disposed (can be in loaded or unloaded state) + + /** + * Try and swap in new render buffer for this section. Note that before this call, there should be no other + * places storing or referencing the render buffer. + * @param referenceSlot The slot for swapping in the new buffer. + * @return True if the swap was successful. False if swap is not needed or if it is in progress. + */ + boolean trySwapRenderBuffer(AtomicReference referenceSlot); + +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderSection.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderSection.java deleted file mode 100644 index f8b5be3a3..000000000 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderSection.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * This file is part of the Distant Horizons mod (formerly the LOD Mod), - * licensed under the GNU LGPL v3 License. - * - * Copyright (C) 2020-2022 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.seibel.lod.core.objects.a7.render; - -import com.seibel.lod.core.api.internal.ClientApi; -import com.seibel.lod.core.builders.lodBuilding.LodBuilder; -import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.CubicLodTemplate; -import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder; -import com.seibel.lod.core.enums.LodDirection; -import com.seibel.lod.core.enums.config.GpuUploadMethod; -import com.seibel.lod.core.enums.rendering.DebugMode; -import com.seibel.lod.core.enums.rendering.GLProxyContext; -import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler; -import com.seibel.lod.core.objects.*; -import com.seibel.lod.core.objects.a7.LodQuadTree; -import com.seibel.lod.core.objects.a7.LodSection; -import com.seibel.lod.core.objects.lod.LodDimension; -import com.seibel.lod.core.objects.lod.LodRegion; -import com.seibel.lod.core.objects.math.Vec3d; -import com.seibel.lod.core.objects.math.Vec3f; -import com.seibel.lod.core.objects.opengl.RenderBuffer; -import com.seibel.lod.core.objects.opengl.SimpleRenderBuffer; -import com.seibel.lod.core.render.GLProxy; -import com.seibel.lod.core.render.LodRenderProgram; -import com.seibel.lod.core.render.RenderUtil; -import com.seibel.lod.core.util.DataPointUtil; -import com.seibel.lod.core.util.LevelPosUtil; -import com.seibel.lod.core.util.LodUtil; -import com.seibel.lod.core.util.StatsMap; -import com.seibel.lod.core.util.gridList.PosArrayGridList; -import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton; - -import java.util.ConcurrentModificationException; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import static com.seibel.lod.core.render.LodRenderer.EVENT_LOGGER; - -public class RenderSection implements AutoCloseable -{ - private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); - - /** stores if the region at the given x and z index needs to be regenerated */ - // Use int because I need Tri state: - private final AtomicInteger needRegen = new AtomicInteger(2); - - private enum BackState { - Unused, - Building, - Complete, - } - private enum FrontState { - Unused, - Rendering, - Invalidated, - } - - final LodSection lodSection; - final LodQuadTree quadTree; - RenderBuffer renderBufferBack = null; - AtomicReference backState = - new AtomicReference(BackState.Unused); - AtomicReference frontState = - new AtomicReference(FrontState.Unused); - RenderBuffer renderBufferFront = null; - - public RenderSection(LodSection lodSection, LodQuadTree quadTree) { - this.lodSection = lodSection; - this.quadTree = quadTree; - } - - public void setNeedRegen() { - needRegen.set(2); - } - - public Optional> updateStatus(Executor bufferUploader, Executor bufferBuilder, boolean alwaysRegen, int playerPosX, int playerPosZ, boolean doCaveCulling) { - if (alwaysRegen) setNeedRegen(); - - BackState state = backState.get(); - if (state != BackState.Unused) { - EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: BackState is {}", lodSection, state); - return Optional.empty(); - } - if (needRegen.get() == 0) { - EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: Region doesn't need regen", lodSection); - return Optional.empty(); - } - if (!backState.compareAndSet(BackState.Unused, BackState.Building)) { - EVENT_LOGGER.trace("{}: UpdateStatus rejected. Cause: CAS on BackState failed: ", backState.get()); - return Optional.empty(); - } - needRegen.decrementAndGet(); - return Optional.of(startBuid(bufferUploader, bufferBuilder, lodSection, playerPosX, playerPosZ, doCaveCulling)); - } - - public boolean render(LodDimension renderDim, - Vec3d cameraPos, DHBlockPos cameraBlockPos, Vec3f cameraDir, - boolean enableDirectionalCulling, LodRenderProgram program) { - if (!frontState.compareAndSet(FrontState.Unused, FrontState.Rendering)) return false; - try { - if (renderDim != lodDim) return false; - if (enableDirectionalCulling && - !RenderUtil.isRegionInViewFrustum(cameraBlockPos, - cameraDir, regionPos.x, regionPos.z)) return false; - BackState state = backState.get(); - if (state == BackState.Complete) { - if (renderBufferBack != null) { - EVENT_LOGGER.debug("RenderRegion swap @ {}", regionPos); - boolean shouldKeep = renderBufferFront != null && renderBufferFront.onSwapToBack(); - RenderBuffer temp = shouldKeep ? renderBufferFront : null; - renderBufferFront = renderBufferBack; - renderBufferBack = temp; - if (renderBufferFront != null) renderBufferFront.onSwapToFront(); - } - if (!backState.compareAndSet(BackState.Complete, BackState.Unused)) { - EVENT_LOGGER.error("RenderRegion.render() got illegal state on swapping buffer!"); - } - } - if (renderBufferFront == null) return false; - program.setModelPos(new Vec3f( - (float) ((regionPos.x * LodUtil.REGION_WIDTH) - cameraPos.x), - (float) (LodBuilder.MIN_WORLD_HEIGHT - cameraPos.y), - (float) ((regionPos.z * LodUtil.REGION_WIDTH) - cameraPos.z))); - - return renderBufferFront.render(program); - } finally { - frontState.compareAndSet(FrontState.Rendering, FrontState.Unused); - } - - } - - private void recreateBuffer(LodQuadBuilder builder) { - if (renderBufferBack != null) throw new RuntimeException("Assert Error"); - boolean useSimpleBuffer = (builder.getCurrentNeededVertexBufferCount() <= 6) || true; - renderBufferBack = useSimpleBuffer ? - new SimpleRenderBuffer() - : null; //new ComplexRenderRegion(regPos); - } - - private CompletableFuture startBuid(Executor bufferUploader, Executor bufferBuilder, LodSection section, int playerPosX, int playerPosZ, boolean doCaveCulling) { - EVENT_LOGGER.trace("RenderSection startBuild @ {}", section); - RenderContainer[] adjSections = new RenderContainer[4]; - try { - if (renderBufferBack != null) renderBufferBack.onReuse(); - for (LodDirection dir : LodDirection.ADJ_DIRECTIONS) { - adjSections[dir.ordinal() - 2] = quadTree.getSection(section.pos.getAdjacent(dir)).renderContainer; - - } - } catch (Throwable t) { - setNeedRegen(); - if (!backState.compareAndSet(BackState.Building, BackState.Unused)) { - EVENT_LOGGER.error("\"Lod Builder Starter\"" - + " encountered error on catching exceptions and fallback on starting build task: ", - new ConcurrentModificationException("RenderRegion Illegal State")); - } - throw t; - } - return CompletableFuture.supplyAsync(() -> { - try { - EVENT_LOGGER.trace("RenderRegion start QuadBuild @ {}", regionPos); - int skyLightCullingBelow = CONFIG.client().graphics().advancedGraphics().getCaveCullingHeight(); - // FIXME: Clamp also to the max world height. - skyLightCullingBelow = Math.max(skyLightCullingBelow, LodBuilder.MIN_WORLD_HEIGHT); - LodQuadBuilder builder = new LodQuadBuilder(doCaveCulling, skyLightCullingBelow); - Runnable buildRun = ()->{ - makeLodRenderData(builder, region, adjRegions, playerPosX, playerPosZ); - }; - if (renderBufferBack != null) - renderBufferBack.build(buildRun); - else - buildRun.run(); - EVENT_LOGGER.trace("RenderRegion end QuadBuild @ {}", regionPos); - return builder; - } catch (Throwable e3) { - EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3); - throw e3; - } - }, bufferBuilder) - - .thenAcceptAsync((builder) -> { - try { - EVENT_LOGGER.trace("RenderRegion start Upload @ {}", regionPos); - GLProxy glProxy = GLProxy.getInstance(); - GpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod(); - GLProxyContext oldContext = glProxy.getGlContext(); - glProxy.setGlContext(GLProxyContext.LOD_BUILDER); - try { - if (renderBufferBack == null) recreateBuffer(builder); - if (!renderBufferBack.tryUploadBuffers(builder, method)) { - renderBufferBack = null; - recreateBuffer(builder); - if (!renderBufferBack.tryUploadBuffers(builder, method)) { - throw new RuntimeException("Newly created renderBuffer " - + "is still returning false on tryUploadBuffers!"); - } - } - } finally { - glProxy.setGlContext(oldContext); - } - EVENT_LOGGER.trace("RenderRegion end Upload @ {}", regionPos); - } catch (Throwable e3) { - EVENT_LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); - throw e3; - } - }, bufferUploader).handle((v, e) -> { - if (e != null) { - setNeedRegen(); - if (!backState.compareAndSet(BackState.Building, BackState.Unused)) { - EVENT_LOGGER.error("\"LodNodeBufferBuilder\"" - + " encountered error on exit: ", - new ConcurrentModificationException("RenderRegion Illegal State")); - } - } else { - if (!backState.compareAndSet(BackState.Building, BackState.Complete)) { - EVENT_LOGGER.error("\"LodNodeBufferBuilder\"" - + " encountered error on exit: ", - new ConcurrentModificationException("RenderRegion Illegal State")); - } - } - return (Void) null; - }); - } - - private static final int ADJACENT8[][] = { - {-1,-1}, - {-1, 0}, - {-1, 1}, - { 0,-1}, - //{ 0, 0}, - { 0, 1}, - { 1,-1}, - { 1, 0}, - { 1, 1} - }; - - private static void makeLodRenderData(LodQuadBuilder quadBuilder, LodRegion region, LodRegion[] adjRegions, int playerX, - int playerZ) { - byte minDetail = region.getMinDetailLevel(); - - // Variable initialization - DebugMode debugMode = CONFIG.client().advanced().debugging().getDebugMode(); - - // We ask the lod dimension which block we have to render given the player - // position - PosToRenderContainer posToRender = new PosToRenderContainer(minDetail, region.regionPosX, region.regionPosZ); - region.getPosToRender(posToRender, playerX, playerZ); - posToRender.sort(); - PosArrayGridList chunkGrid = ClientApi.renderer.vanillaChunks; - - for (int index = 0; index < posToRender.getNumberOfPos(); index++) { - - byte detailLevel = posToRender.getNthDetailLevel(index); - int posX = posToRender.getNthPosX(index); - int posZ = posToRender.getNthPosZ(index); - - // TODO: In the future, We don't need to ignore rendered chunks! Just build it - // and leave it for the renderer to decide! - // We don't want to render this fake block if - // The block is inside the render distance with, is not bigger than a chunk and - // is positioned in a chunk set as vanilla rendered - - // The block is in the player chunk or in a chunk adjacent to the player - if (detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL) { - int chunkX = LevelPosUtil.getChunkPos(detailLevel, posX); - int chunkZ = LevelPosUtil.getChunkPos(detailLevel, posZ); - // skip any chunks that Minecraft is going to render - if (chunkGrid != null && chunkGrid.get(chunkX, chunkZ) != null) continue; - } - LodDataView posData = region.getDataView(detailLevel, posX, posZ); - if (posData == null || posData.size() == 0 || !DataPointUtil.doesItExist(posData.get(0)) - || DataPointUtil.isVoid(posData.get(0))) - continue; - - LodDataView[][] adjData = new LodDataView[4][]; - boolean[] adjUseBlack = new boolean[4]; - - // We extract the adj data in the four cardinal direction - - // we first reset the adjShadeDisabled. This is used to disable the shade on the - // border when we have transparent block like water or glass - // to avoid having a "darker border" underground - // Arrays.fill(adjShadeDisabled, false); - - // We check every adj block in each direction - - // If the adj block is rendered in the same region and with same detail - // and is positioned in a place that is not going to be rendered by vanilla game - // then we can set this position as adj - // We avoid cases where the adjPosition is in player chunk while the position is - // not - // to always have a wall underwater - for (LodDirection lodDirection : LodDirection.ADJ_DIRECTIONS) { - try { - int xAdj = posX + lodDirection.getNormal().x; - int zAdj = posZ + lodDirection.getNormal().z; - int chunkXAdj = LevelPosUtil.getChunkPos(detailLevel, xAdj); - int chunkZAdj = LevelPosUtil.getChunkPos(detailLevel, zAdj); - if (chunkGrid != null && chunkGrid.get(chunkXAdj, chunkZAdj)!=null) { - adjUseBlack[lodDirection.ordinal()-2] = true; - } - - boolean isCrossRegionBoundary = LevelPosUtil.getRegion(detailLevel, xAdj) != region.regionPosX || - LevelPosUtil.getRegion(detailLevel, zAdj) != region.regionPosZ; - - LodRegion adjRegion; - byte adjDetail; - int childXAdj = xAdj*2 + (lodDirection.getNormal().x<0 ? 1 : 0); - int childZAdj = zAdj*2 + (lodDirection.getNormal().z<0 ? 1 : 0); - - //we check if the detail of the adjPos is equal to the correct one (region border fix) - //or if the detail is wrong by 1 value (region+circle border fix) - if (isCrossRegionBoundary) { - //we compute at which detail that position should be rendered - adjRegion = adjRegions[lodDirection.ordinal()-2]; - if(adjRegion == null) continue; - adjDetail = adjRegion.getRenderDetailLevelAt(playerX, playerZ, detailLevel, xAdj, zAdj); - } else { - adjRegion = region; - if (posToRender.contains(detailLevel, xAdj, zAdj)) adjDetail = detailLevel; - else if (detailLevel>0 && - posToRender.contains((byte) (detailLevel-1), childXAdj, childZAdj)) - adjDetail = (byte) (detailLevel-1); - else if (detailLevel detailLevel+1) { - continue; - } - - if (adjDetail == detailLevel || adjDetail > detailLevel) { - adjData[lodDirection.ordinal() - 2] = new LodDataView[1]; - adjData[lodDirection.ordinal() - 2][0] = adjRegion.getDataView(adjDetail, - LevelPosUtil.convert(detailLevel, xAdj, adjDetail), - LevelPosUtil.convert(detailLevel, zAdj, adjDetail)); - } else { - adjData[lodDirection.ordinal() - 2] = new LodDataView[2]; - adjData[lodDirection.ordinal() - 2][0] = adjRegion.getDataView(adjDetail, - childXAdj, childZAdj); - adjData[lodDirection.ordinal() - 2][1] = adjRegion.getDataView(adjDetail, - childXAdj + (lodDirection.getAxis()==LodDirection.Axis.X ? 0 : 1), - childZAdj + (lodDirection.getAxis()==LodDirection.Axis.Z ? 0 : 1)); - } - } catch (RuntimeException e) { - EVENT_LOGGER.warn("Failed to get adj data for [{}:{},{}] at [{}]", detailLevel, posX, posZ, lodDirection); - EVENT_LOGGER.warn("Detail exception: ", e); - } - } - - // We render every vertical lod present in this position - // We only stop when we find a block that is void or non-existing block - for (int i = 0; i < posData.size(); i++) { - long data = posData.get(i); - // If the data is not renderable (Void or non-existing) we stop since there is - // no data left in this position - if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data)) - break; - - long adjDataTop = i - 1 >= 0 ? posData.get(i - 1) : DataPointUtil.EMPTY_DATA; - long adjDataBot = i + 1 < posData.size() ? posData.get(i + 1) : DataPointUtil.EMPTY_DATA; - - // We send the call to create the vertices - CubicLodTemplate.addLodToBuffer(data, adjDataTop, adjDataBot, adjData, adjUseBlack, detailLevel, - LevelPosUtil.getRegionModule(detailLevel, posX), - LevelPosUtil.getRegionModule(detailLevel, posZ), quadBuilder, debugMode); - } - - } // for pos to in list to render - // the thread executed successfully - // Merge all quads - quadBuilder.mergeQuads(); - } - - - @Override - public void close() - { - if (renderBufferBack != null) renderBufferBack.close(); - while (frontState.get() != FrontState.Invalidated && !frontState.compareAndSet(FrontState.Unused, FrontState.Invalidated)) { - Thread.yield(); //FIXME: If on java 9, use Thread.onSpinWait(); - } - if (renderBufferFront != null) renderBufferFront.close(); - } - - public void debugDumpStats(StatsMap statsMap) - { - statsMap.incStat("RenderRegions"); - RenderBuffer front = renderBufferFront; - if (front!=null) { - statsMap.incStat("FrontBuffers"); - front.debugDumpStats(statsMap); - } - - RenderBuffer back = renderBufferBack; - if (back!=null) { - statsMap.incStat("BackBuffers"); - back.debugDumpStats(statsMap); - } - - - } -} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/column/ColumnRenderBuffer.java b/src/main/java/com/seibel/lod/core/objects/a7/render/column/ColumnRenderBuffer.java deleted file mode 100644 index 3eb30a8b1..000000000 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/column/ColumnRenderBuffer.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.seibel.lod.core.objects.a7.render.column; - -public class ColumnRenderBuffer { - -}