Reworked lodBuilder. Make it faster.

This commit is contained in:
tom lee
2022-01-19 18:59:55 +08:00
parent 930113a6f9
commit 22e47b9734
6 changed files with 201 additions and 107 deletions
@@ -32,7 +32,6 @@ import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.util.ThreadMapUtil;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
@@ -163,14 +162,12 @@ public class LodBuilder
public boolean generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config, boolean override)
throws IllegalArgumentException
{
//config.distanceGenerationMode = DistanceGenerationMode.FULL;
//long executeTime = System.currentTimeMillis();
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
int startX;
int startZ;
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return false;
@@ -183,30 +180,27 @@ public class LodBuilder
// determine how many LODs to generate vertically
//VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
// generate the LODs
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte)0);
long[] data = new long[maxVerticalData*16*16];
for (int i = 0; i < 16*16; i++)
{
int subX = i/16;
int subZ = i%16;
writeVerticalData(data, i*maxVerticalData, maxVerticalData, chunk, config, subX, subZ);
}
if (!chunk.isLightCorrect()) return false;
region.isWriting++;
try {
LodRegion newRegion = lodDim.getRegionFromFile(region, (byte)0, region.getGenerationMode(), region.getVerticalQuality());
assert(region==newRegion);
// generate the LODs
int posX;
int posZ;
for (int i = 0; i < 16*16; i++)
{
startX = i/16;
startZ = i%16;
long[] data;
long[] dataToMergeVertical = createVerticalDataToMerge((byte)0, chunk, config, startX, startZ);
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData((byte)0));
if (data != null && data.length != 0)
{
posX = chunk.getChunkPosX() * 16 + startX;
posZ = chunk.getChunkPosZ() * 16 + startZ;
if (region.addVerticalData((byte)0, posX, posZ, data, override))
region.updateArea((byte)0, posX, posZ);
}
if (region.getMinDetailLevel()!= 0) {
LodRegion newRegion = lodDim.getRegionFromFile(region, (byte)0, region.getGenerationMode(), region.getVerticalQuality());
assert(region==newRegion);
}
if (region.addChunkOfData((byte)0, chunk.getMinX(), chunk.getMinZ(), 16, 16, data, maxVerticalData, true)) {
region.regenerateLodFromArea((byte)0, chunk.getMinX(), chunk.getMinZ(), 16, 16);
lodDim.regenDimensionBuffers = true;
}
} finally {
region.isWriting--;
@@ -216,81 +210,62 @@ public class LodBuilder
//executeTime = System.currentTimeMillis() - executeTime;
//if (executeTime > 0) ClientApi.LOGGER.info("generateLodNodeFromChunk level: " + detailLevel + " time ms: " + executeTime);
}
/** creates a vertical DataPoint */
private long[] createVerticalDataToMerge(byte detail, IChunkWrapper chunk, LodBuilderConfig config, int startX, int startZ)
private void writeVerticalData(long[] data, int dataOffset, int maxVerticalData,
IChunkWrapper chunk, LodBuilderConfig config, int chunkSubPosX, int chunkSubPosZ)
{
// equivalent to 2^detailLevel
int size = 1 << detail;
int totalVerticalData = (chunk.getHeight());
long[] dataToMerge = new long[totalVerticalData];
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail);
int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
int height;
int depth;
int color;
int light;
int lightSky;
int lightBlock;
int generation = config.distanceGenerationMode.complexity;
int xRel;
int zRel;
int xAbs;
int yAbs;
int zAbs;
boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
boolean isDefault;
int index;
int generation = config.distanceGenerationMode.complexity;
int count = 0;
// FIXME: This yAbs is just messy!
int x = chunk.getMinX() + chunkSubPosX;
int z = chunk.getMinZ() + chunkSubPosZ;
int y = chunk.getMaxY(x, z);
for (index = 0; index < size * size; index++)
{
xRel = startX + index % size;
zRel = startZ + index / size;
xAbs = chunk.getMinX() + xRel;
zAbs = chunk.getMinZ() + zRel;
//Calculate the height of the lod
yAbs = chunk.getMaxY(xRel,zRel) - MIN_WORLD_HEIGHT;
int count = 0;
boolean topBlock = true;
if (yAbs <= 0)
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
while (yAbs > 0)
{
height = determineHeightPointFrom(chunk, config, xAbs, yAbs, zAbs);
// If the lod is at the default height, it must be void data
if (height == 0)
break;
yAbs = height - 1;
// We search light on above air block
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs, count < this.config.client().graphics().quality().getVerticalQuality().maxConnectedLods && !hasCeiling);
if (hasCeiling && topBlock)
yAbs = depth;
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, hasCeiling, hasSkyLight, topBlock);
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
lightBlock = light & 0b1111;
lightSky = (light >> 4) & 0b1111;
isDefault = ((light >> 8)) == 1;
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
yAbs = depth - 1;
count++;
}
boolean topBlock = true;
if (y <= chunk.getMinBuildHeight())
data[dataOffset] = DataPointUtil.createVoidDataPoint(generation);
while (y > chunk.getMinBuildHeight()) {
int height = determineHeightPointFrom(chunk, config, x, y, z);
// If the lod is at the default height, it must be void data
if (height <= chunk.getMinBuildHeight())
break;
y = height - 1;
// We search light on above air block
int depth = determineBottomPointFrom(chunk, config, x, y, z,
count < this.config.client().graphics().quality().getVerticalQuality().maxConnectedLods
&& !hasCeiling);
if (hasCeiling && topBlock)
y = depth;
int light = getLightValue(chunk, x, y, z, hasCeiling, hasSkyLight, topBlock);
int color = generateLodColor(chunk, config, x, y, z);
int lightBlock = light & 0b1111;
int lightSky = (light >> 4) & 0b1111;
boolean isDefault = ((light >> 8)) == 1;
dataToMerge[count] = DataPointUtil.createDataPoint(height-chunk.getMinBuildHeight(), depth-chunk.getMinBuildHeight(),
color, lightSky, lightBlock, generation, isDefault);
topBlock = false;
y = depth - 1;
count++;
}
return dataToMerge;
long[] result = DataPointUtil.mergeMultiData(dataToMerge, totalVerticalData, maxVerticalData);
if (result.length != maxVerticalData) throw new ArrayIndexOutOfBoundsException();
System.arraycopy(result, 0, data, dataOffset, maxVerticalData);
}
/**
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
private int determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
{
short depth = 0;
int depth = chunk.getMinBuildHeight();
int colorOfBlock = 0;
if (strictEdge)
@@ -308,7 +283,7 @@ public class LodBuilder
}
}
for (int y = yAbs - 1; y >= 0; y--)
for (int y = yAbs - 1; y >= chunk.getMinBuildHeight(); y--)
{
if (!isLayerValidLodPoint(chunk, xAbs, y, zAbs)
@@ -322,21 +297,16 @@ public class LodBuilder
}
/** Find the highest valid point from the Top */
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
private int determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
//TODO find a way to skip bottom of the world
short height = 0;
if (config.useHeightmap)
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
else
int height = chunk.getMinBuildHeight();
for (int y = yAbs; y >= chunk.getMinBuildHeight(); y--)
{
for (int y = yAbs; y >= 0; y--)
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
height = (short) (y + 1);
break;
}
height = (y + 1);
break;
}
}
return height;
@@ -42,10 +42,16 @@ public interface LevelContainer
* @param data actual data to add in an array of long[] format.
* @param posX x position in the detail level
* @param posZ z position in the detail level
* @return true if correctly added, false otherwise
* @return true if correctly changed, false otherwise
*/
boolean addVerticalData(long[] data, int posX, int posZ, boolean override);
/**
* With this you can add a square of data to the level container
* @return true if anything changed, false otherwise
*/
boolean addChunkOfData(long[] data, int posX, int posZ, int widthX, int widthZ, boolean override);
/**
* With this you can add data to the level container
* @param data actual data to add in an array of long format.
@@ -115,4 +121,5 @@ public interface LevelContainer
* @return data as a String
*/
int getMaxNumberOfLods();
}
@@ -90,7 +90,6 @@ public class LodRegion {
/**
* Inserts the data point into the region.
* <p>
* TODO this will always return true unless it has
*
* @return true if the data was added successfully
*/
@@ -112,7 +111,6 @@ public class LodRegion {
/**
* Inserts the vertical data into the region.
* <p>
* TODO this will always return true unless it has
*
* @return true if the data was added successfully
*/
@@ -133,6 +131,31 @@ public class LodRegion {
}
return updated;
}
/**
* Inserts the vertical data into the region.
* <p>
*
* @return true if the data was added successfully
*/
public boolean addChunkOfData(byte detailLevel, int posX, int posZ, int widthX, int widthZ, long[] data, int verticalSize, boolean override) {
assert(isWriting!=0);
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
// The dataContainer could have null entries if the
// detailLevel changes.
if (this.dataContainer[detailLevel] == null)
return false;// this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
if (this.dataContainer[detailLevel].getVerticalSize() != verticalSize) throw new RuntimeException();
boolean updated = this.dataContainer[detailLevel].addChunkOfData(data, posX, posZ, widthX, widthZ, override);
if (updated) {
needRegenBuffer = 2;
needSaving = true;
}
return updated;
}
/**
* Get the dataPoint at the given relative position.
@@ -377,17 +400,53 @@ public class LodRegion {
update(up, LevelPosUtil.convert(detailLevel, posX, up), LevelPosUtil.convert(detailLevel, posZ, up));
}
}
public boolean regenerateLodFromArea(byte detailLevel, int posX, int posZ, int widthX, int widthZ) {
if (detailLevel >= LodUtil.REGION_DETAIL_LEVEL) return false;
if (detailLevel < minDetailLevel) {
byte startLevel = minDetailLevel;
int maxPosX = Math.floorDiv(posX+widthX-1, (1 << (minDetailLevel-startLevel)))+1;
int maxPosZ = Math.floorDiv(posZ+widthZ-1, (1 << (minDetailLevel-startLevel)))+1;
posX = Math.floorDiv(posX, (1 << (minDetailLevel-startLevel)));
posZ = Math.floorDiv(posZ, (1 << (minDetailLevel-startLevel)));
widthX = maxPosX-posX;
widthZ = maxPosZ-posZ;
detailLevel = minDetailLevel;
}
do {
int maxPosX = Math.floorDiv(posX+widthX-1, 2)+1;
int maxPosZ = Math.floorDiv(posZ+widthZ-1, 2)+1;
posX = Math.floorDiv(posX, 2);
posZ = Math.floorDiv(posZ, 2);
widthX = maxPosX-posX;
widthZ = maxPosZ-posZ;
detailLevel++;
chunkUpdate(detailLevel, posX, posZ, widthX, widthZ);
} while (detailLevel < LodUtil.REGION_DETAIL_LEVEL);
needRegenBuffer = 2;
return true;
}
/**
* Update the child at the given relative Pos
* <p>
* TODO could this be renamed mergeChildData?
* TODO make this return whether any value has changed
*/
private void update(byte detailLevel, int posX, int posZ) {
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
}
private void chunkUpdate(byte detailLevel, int posX, int posZ, int widthX,int widthZ) {
for (int ox=0; ox<widthX; ox++) {
for (int oz=0; oz<widthZ; oz++) {
update(detailLevel, posX+ox, posZ+oz);
}
}
}
/**
* Returns if data exists at the given relative Pos.
@@ -103,6 +103,24 @@ public class VerticalLevelContainer implements LevelContainer
return true;
}
@Override
public boolean addChunkOfData(long[] data, int posX, int posZ, int widthX, int widthZ, boolean override)
{
boolean anyChange = false;
if (posX+widthX > size || posZ+widthZ > size)
throw new IndexOutOfBoundsException("addChunkOfData param not inside valid range");
if (widthX*widthZ*verticalSize > data.length)
throw new IndexOutOfBoundsException("addChunkOfData data array not long enough to contain the data to be copied");
for (int ox=0; ox<widthX; ox++) {
anyChange = DataPointUtil.mergeTwoDataArray(
dataContainer, ((ox+posX)*size+posZ) * verticalSize,
data, ox*widthX*verticalSize,
widthZ, verticalSize, override);
}
return anyChange;
}
@Override
public boolean addSingleData(long data, int posX, int posZ)
{
@@ -1023,7 +1041,6 @@ public class VerticalLevelContainer implements LevelContainer
posZ);
}
private void mergeAndAddColorLightData(int sliceStart, int sliceEnd, int posZ, int posX, short[] inputPositionData, int[] inputVerticalData, int[] inputColorData, byte[] inputLightData, byte inputDetailLevel, int inputVerticalSize)
{
//STEP 5//
@@ -269,6 +269,45 @@ public class DataPointUtil
return genA-genB;
}
// Merge the newData data into the target data by comparing the Datapoint Priority.
// If target and newData are same priority, it will use the target one.
// If target and newData size are different, it will copy up to the smallest one.
// Return whether anything changed
public static boolean mergeTwoDataArray(long[] target, long[] newData, int verticalDataSize) {
boolean anyChange = false;
for (int i=0; i<target.length&&i<newData.length; i+=verticalDataSize) {
if (compareDatapointPriority(newData[i], target[i])>0) {
anyChange = true;
System.arraycopy(newData, i, target, i, verticalDataSize);
}
}
return anyChange;
}
// Same as above, but with args for buffer offset.
// Return whether anything changed
public static boolean mergeTwoDataArray(long[] target, int targetOffset, long[] newData, int newDataOffset, int dataLength, int verticalDataSize, boolean override) {
if (targetOffset + verticalDataSize*dataLength>target.length)
throw new ArrayIndexOutOfBoundsException("\"target\" array index out of bounds");
if (newDataOffset + verticalDataSize*dataLength>newData.length)
throw new ArrayIndexOutOfBoundsException("\"newData\" array index out of bounds");
boolean anyChange = false;
for (int o=0; o<(dataLength*verticalDataSize); o+=verticalDataSize) {
if (override) {
if (compareDatapointPriority(newData[o+newDataOffset], target[o+targetOffset])>=0) {
anyChange = true;
System.arraycopy(newData, o+newDataOffset, target, o+targetOffset, verticalDataSize);
}
} else {
if (compareDatapointPriority(newData[o+newDataOffset], target[o+targetOffset])>0) {
anyChange = true;
System.arraycopy(newData, o+newDataOffset, target, o+targetOffset, verticalDataSize);
}
}
}
return anyChange;
}
/**
* This method merge column of multiple data together
* @param dataToMerge one or more columns of data
@@ -29,9 +29,11 @@ import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
*/
public interface IChunkWrapper
{
int getHeight();
boolean isPositionInWater(int x, int y, int z);
default int getHeight() {
return getMaxBuildHeight()-getMinBuildHeight();
}
int getMinBuildHeight();
int getMaxBuildHeight();
int getHeightMapValue(int xRel, int zRel);