Continue making the structure of data

This commit is contained in:
TomTheFurry
2022-05-12 13:34:27 +08:00
parent d7962fbb4e
commit 4d352b012f
15 changed files with 1021 additions and 595 deletions
@@ -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;
}
}
@@ -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);
}
@@ -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<String> idMap;
protected CompleteDataContainer() {
@@ -16,8 +19,11 @@ public class CompleteDataContainer extends LodDataSource { // 1 chunk
}
@Override
public Function<ByteBuffer, ? extends LodDataSource> 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<String, Integer> idMap = new HashMap<String, Integer>();
@@ -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<DhSectionPos, LodDataSource> 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) {
@@ -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<String, Function<InputStream,? extends LodDataSource>>
public interface LodDataSource {
String REGISTER_STRING_FILTER_REGEX = "^[a-zA-Z0-9_]*$";
HashMap<String, Function<InputStream,? extends LodDataSource>>
dataSourceLoaderRegistry = new HashMap<String, Function<InputStream,? extends LodDataSource>>();
public static void registerDataSourceLoader(String name, int version, Function<InputStream,? extends LodDataSource> 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<InputStream,? extends LodDataSource> 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<InputStream,? extends LodDataSource> loader = dataSourceLoaderRegistry.get(dataSourceTypeNameVersion);
if (loader == null) {
@@ -31,7 +39,9 @@ public abstract class LodDataSource {
}
return loader.apply(data);
}
public abstract Function<ByteBuffer,? extends LodDataSource> getLatestLoader();
DataSourceLoader getLatestLoader();
public abstract <T> T[] getData(); //TODO & FIXME: What is T?
<T> T[] getData(); //TODO & FIXME: What is T?
DhSectionPos getSectionPos();
}
@@ -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<widthX; ox++) {
anyChange = DataPointUtil.mergeTwoDataArray(
dataContainer, ((ox+posX)* SECTION_SIZE +posZ) * verticalSize,
data, ox*widthX*verticalSize,
widthZ, verticalSize, override);
}
return anyChange;
}
public boolean copyChunkOfData(LodDataView 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.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<widthX; ox++) {
anyChange |= new LodDataView(dataContainer, widthX*verticalSize,
((ox+posX)* SECTION_SIZE +posZ) * verticalSize)
.mergeWith(data, verticalSize, override);
}
return anyChange;
}
public long getData(int posX, int posZ, int verticalIndex)
{
return dataContainer[posX * SECTION_SIZE * verticalSize + posZ * verticalSize + verticalIndex];
}
public long getSingleData(int posX, int posZ)
{
return dataContainer[posX * SECTION_SIZE * verticalSize + posZ * verticalSize];
}
public long[] getAllData(int posX, int posZ)
{
long[] result = new long[verticalSize];
int index = posX * SECTION_SIZE * verticalSize + posZ * verticalSize;
System.arraycopy(dataContainer, index, result, 0, verticalSize);
return result;
}
public LodDataView getVerticalDataView(int posX, int posZ) {
return new LodDataView(dataContainer, verticalSize, posX * SECTION_SIZE * verticalSize + posZ * verticalSize);
}
public int getVerticalSize()
{
return verticalSize;
}
public boolean doesItExist(int posX, int posZ)
{
return DataPointUtil.doesItExist(getSingleData(posX, posZ));
}
private long[] readDataVersion6(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException {
int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData;
byte[] data = new byte[x * Long.BYTES];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
inputData.readFully(data);
long[] result = new long[x];
bb.asLongBuffer().get(result);
patchVersion9Reorder(result);
patchHeightAndDepth(result,-yOffset);
return result;
}
private long[] readDataVersion7(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException {
int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData;
byte[] data = new byte[x * Long.BYTES];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
inputData.readFully(data);
long[] result = new long[x];
bb.asLongBuffer().get(result);
patchVersion9Reorder(result);
patchHeightAndDepth(result, 64 - yOffset);
return result;
}
private long[] readDataVersion8(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException {
int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData;
byte[] data = new byte[x * Long.BYTES];
short tempMinHeight = Short.reverseBytes(inputData.readShort());
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
inputData.readFully(data);
long[] result = new long[x];
bb.asLongBuffer().get(result);
patchVersion9Reorder(result);
if (tempMinHeight != yOffset) {
patchHeightAndDepth(result,tempMinHeight - yOffset);
}
return result;
}
private long[] readDataVersion9(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException {
int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData;
byte[] data = new byte[x * Long.BYTES];
short tempMinHeight = Short.reverseBytes(inputData.readShort());
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
inputData.readFully(data);
long[] result = new long[x];
bb.asLongBuffer().get(result);
if (tempMinHeight != yOffset) {
patchHeightAndDepth(result,tempMinHeight - yOffset);
}
return result;
}
private static void patchHeightAndDepth(long[] data, int offset) {
for (int i=0; i<data.length; i++) {
data[i] = DataPointUtil.shiftHeightAndDepth(data[i], (short)offset);
}
}
private static void patchVersion9Reorder(long[] data) {
for (int i=0; i<data.length; i++) {
data[i] = DataPointUtil.version9Reorder(data[i]);
}
}
private static final ThreadLocal<long[][]> 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<short[]> tLocalHeightAndDepth = new ThreadLocal<short[]>();
private static final ThreadLocal<int[]> tDataIndexCache = new ThreadLocal<int[]>();
/**
*
* 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> 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<RenderBuffer> referenceSlot) {
return false;
}
}
@@ -0,0 +1,5 @@
package com.seibel.lod.core.objects.a7.datatype.column;
public class ColumnRenderBuffer {
}
@@ -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<DhSectionPos> 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));
}
}
@@ -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<RenderBuffer> referenceSlot) {
return false;
}
}
@@ -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() {
}
@@ -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];
@@ -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<Integer, RenderContainerConstructor>
renderContainerLoaderRegistry = new TreeMap<Integer, RenderContainerConstructor>();
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<RenderBuffer> referenceSlot);
}
@@ -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:
* <pre>
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);
}
</pre>
*/
public interface RenderDataSource {
interface RenderContainerConstructor {
// Can return null as meaning the requirement is not met
RenderDataSource testAndConstruct(LodDataSource dataSource, DhSectionPos sectionPos);
}
SortedMap<Integer, RenderContainerConstructor>
renderContainerLoaderRegistry = new TreeMap<Integer, RenderContainerConstructor>();
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<RenderBuffer> referenceSlot);
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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> backState =
new AtomicReference<BackState>(BackState.Unused);
AtomicReference<FrontState> frontState =
new AtomicReference<FrontState>(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<CompletableFuture<Void>> 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<Void> 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<BoolType> 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<LodUtil.REGION_DETAIL_LEVEL &&
posToRender.contains((byte) (detailLevel+1), xAdj/2, zAdj/2))
adjDetail = (byte) (detailLevel+1);
else continue;
}
if (adjDetail < detailLevel-1 || adjDetail > 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);
}
}
}
@@ -1,5 +0,0 @@
package com.seibel.lod.core.objects.a7.render.column;
public class ColumnRenderBuffer {
}