Continue making the structure of data
This commit is contained in:
@@ -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 {
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user