Add optional DhApiChunk validation for world gen
This commit is contained in:
+12
@@ -96,6 +96,18 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
|
||||
*/
|
||||
boolean isBusy();
|
||||
|
||||
/**
|
||||
* Only used if {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}. <Br>
|
||||
* If true DH will run additional validation on the {@link DhApiChunk}'s returned. <Br>
|
||||
* This should be disabled during release but should be enabled during development to help spot issues with your data format.
|
||||
*
|
||||
* @see #getReturnType()
|
||||
* @see DhApiChunk
|
||||
* @see EDhApiWorldGeneratorReturnType#API_CHUNKS
|
||||
* @since API 3.0.0
|
||||
*/
|
||||
default boolean runApiChunkValidation() { return true; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -116,32 +116,19 @@ public class DhApiChunk
|
||||
*/
|
||||
public void setDataPoints(int relX, int relZ, List<DhApiTerrainDataPoint> dataPoints) throws IndexOutOfBoundsException, IllegalArgumentException
|
||||
{
|
||||
//================//
|
||||
// validate input //
|
||||
//================//
|
||||
//==================//
|
||||
// basic validation //
|
||||
//==================//
|
||||
|
||||
// heavier validation is done in the world generator if requested
|
||||
|
||||
int internalArrayIndex = (relZ << 4) | relX;
|
||||
throwIfRelativePosOutOfBounds(relX, relZ);
|
||||
|
||||
// ignore empty inputs
|
||||
if (dataPoints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check that each datapoint is valid
|
||||
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
|
||||
{
|
||||
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
|
||||
if (dataPoint == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
|
||||
}
|
||||
|
||||
if (dataPoint.detailLevel != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("DhApiTerrainDataPoints has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
|
||||
}
|
||||
// we don't allow null columns
|
||||
throw new IllegalArgumentException("Null columns aren't allowed. If you want to remove all data from a column please clear the list or pass in an empty list.");
|
||||
}
|
||||
|
||||
|
||||
@@ -150,20 +137,13 @@ public class DhApiChunk
|
||||
// set datapoints //
|
||||
//================//
|
||||
|
||||
// order doesn't need to be checked if there is 0 or 1 items
|
||||
if (dataPoints.size() > 1)
|
||||
List<DhApiTerrainDataPoint> column = this.dataPoints.get(internalArrayIndex);
|
||||
if (column == null)
|
||||
{
|
||||
// DH expects datapoints to be in a top-down order
|
||||
DhApiTerrainDataPoint first = dataPoints.get(0);
|
||||
DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1);
|
||||
if (first.bottomYBlockPos < last.bottomYBlockPos)
|
||||
{
|
||||
// flip the array if it's in bottom-up order
|
||||
Collections.reverse(dataPoints);
|
||||
}
|
||||
column = new ArrayList<>();
|
||||
this.dataPoints.set(internalArrayIndex, column);
|
||||
}
|
||||
|
||||
this.dataPoints.set(internalArrayIndex, dataPoints);
|
||||
column.addAll(dataPoints);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+76
-1
@@ -19,6 +19,7 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.dataObjects.transformers;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import com.seibel.distanthorizons.api.enums.config.EDhApiWorldCompressionMode;
|
||||
@@ -34,6 +35,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.DataCorruptedException;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
@@ -295,7 +297,7 @@ public class LodDataBuilder
|
||||
|
||||
|
||||
/** @throws ClassCastException if an API user returns the wrong object type(s) */
|
||||
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk) throws ClassCastException, DataCorruptedException
|
||||
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk, boolean runAdditionalValidation) throws ClassCastException, DataCorruptedException, IllegalArgumentException
|
||||
{
|
||||
// get the section position
|
||||
int sectionPosX = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosX);
|
||||
@@ -312,6 +314,10 @@ public class LodDataBuilder
|
||||
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
|
||||
{
|
||||
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ);
|
||||
if (runAdditionalValidation)
|
||||
{
|
||||
validateOrThrowDataColumn(columnDataPoints);
|
||||
}
|
||||
|
||||
|
||||
// this null check does 2 nice things at the same time:
|
||||
@@ -352,6 +358,75 @@ public class LodDataBuilder
|
||||
}
|
||||
return dataSource;
|
||||
}
|
||||
private static void validateOrThrowDataColumn(List<DhApiTerrainDataPoint> dataPoints) throws IllegalArgumentException
|
||||
{
|
||||
// order doesn't need to be checked if there is 0 or 1 items
|
||||
if (dataPoints.size() > 1)
|
||||
{
|
||||
// DH expects datapoints to be in a top-down order
|
||||
DhApiTerrainDataPoint first = dataPoints.get(0);
|
||||
DhApiTerrainDataPoint last = dataPoints.get(dataPoints.size() - 1);
|
||||
if (first.bottomYBlockPos < last.bottomYBlockPos)
|
||||
{
|
||||
// flip the array if it's in bottom-up order
|
||||
Collections.reverse(dataPoints);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// check that each datapoint is valid
|
||||
int lastBottomYPos = Integer.MIN_VALUE;
|
||||
for (int i = 0; i < dataPoints.size(); i++) // standard for-loop used instead of an enhanced for-loop to slightly reduce GC overhead due to iterator allocation
|
||||
{
|
||||
DhApiTerrainDataPoint dataPoint = dataPoints.get(i);
|
||||
|
||||
if (dataPoint == null)
|
||||
{
|
||||
throw new IllegalArgumentException("Datapoint: ["+i+"] is null DhApiTerrainDataPoints are not allowed. If you want to represent empty terrain, please use AIR.");
|
||||
}
|
||||
|
||||
if (dataPoint.detailLevel != 0)
|
||||
{
|
||||
throw new IllegalArgumentException("Datapoint: ["+i+"] has the wrong detail level ["+dataPoint.detailLevel+"], all data points must be block sized; IE their detail level must be [0].");
|
||||
}
|
||||
|
||||
|
||||
|
||||
int bottomYPos = dataPoint.bottomYBlockPos;
|
||||
int topYPos = dataPoint.topYBlockPos;
|
||||
int height = (dataPoint.topYBlockPos - dataPoint.bottomYBlockPos);
|
||||
|
||||
// is the datapoint right side up?
|
||||
if (bottomYPos > topYPos)
|
||||
{
|
||||
throw new IllegalArgumentException("Datapoint: ["+i+"] is upside down. Top Pos: ["+topYPos+"], bottom pos: ["+bottomYPos+"].");
|
||||
}
|
||||
// valid height?
|
||||
if (height <= 0 || height >= RenderDataPointUtil.MAX_WORLD_Y_SIZE)
|
||||
{
|
||||
throw new IllegalArgumentException("Datapoint: ["+i+"] has invalid height. Height must be in the range [1 - "+RenderDataPointUtil.MAX_WORLD_Y_SIZE+"] (inclusive).");
|
||||
}
|
||||
|
||||
// is this datapoint overlapping the last one?
|
||||
if (lastBottomYPos > topYPos)
|
||||
{
|
||||
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] is overlapping with the last datapoint, this top Y: ["+topYPos+"], lastBottomYPos: ["+lastBottomYPos+"].");
|
||||
}
|
||||
// is there a gap between the last datapoint?
|
||||
if (topYPos != lastBottomYPos
|
||||
&& lastBottomYPos != Integer.MIN_VALUE)
|
||||
{
|
||||
throw new IllegalArgumentException("DhApiTerrainDataPoint ["+i+"] has a gap between it and index ["+(i-1)+"]. Empty spaces should be filled by air, otherwise DH's downsampling won't calculate lighting correctly.");
|
||||
}
|
||||
|
||||
|
||||
lastBottomYPos = bottomYPos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -469,10 +469,10 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
||||
{
|
||||
try
|
||||
{
|
||||
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints);
|
||||
FullDataSourceV2 dataSource = LodDataBuilder.createFromApiChunkData(dataPoints, this.generator.runApiChunkValidation());
|
||||
chunkDataConsumer.accept(dataSource);
|
||||
}
|
||||
catch (DataCorruptedException e)
|
||||
catch (DataCorruptedException | IllegalArgumentException e)
|
||||
{
|
||||
LOGGER.error("World generator returned a corrupt chunk. Error: [" + e.getMessage() + "]. World generator disabled.", e);
|
||||
Config.Client.Advanced.WorldGenerator.enableDistantGeneration.set(false);
|
||||
|
||||
Reference in New Issue
Block a user