()
{{
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
index 30d5c97c4..3b59af3ce 100644
--- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
@@ -24,7 +24,9 @@ import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.DropoffQuality;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.handlers.LodDimensionFileHandler;
@@ -37,13 +39,13 @@ 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.wrapperInterfaces.IWrapperFactory;
-import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+//FIXME: Race condition on lodDim move/resize!
/**
* This object holds all loaded LOD regions
@@ -61,7 +63,6 @@ public class LodDimension
{
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
- private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public final IDimensionTypeWrapper dimension;
@@ -76,11 +77,8 @@ public class LodDimension
public volatile LodRegion[][] regions;
/** stores if the region at the given x and z index needs to be saved to disk */
- private volatile boolean[][] isRegionDirty;
/** stores if the region at the given x and z index needs to be regenerated */
// Use int because I need Tri state:
- // 0: both buffer good. 1: the displaying buffer good. 2: both buffer bad.
- private volatile int[][] regenRegionBuffer;
/**
* if true that means there are regions in this dimension
@@ -92,10 +90,9 @@ public class LodDimension
private final RegionPos center;
- /** prevents the cutAndExpandThread from expanding at the same location multiple times */
- private volatile AbstractChunkPosWrapper lastExpandedChunk;
- /** prevents the cutAndExpandThread from cutting at the same location multiple times */
- private volatile AbstractChunkPosWrapper lastCutChunk;
+ private boolean isCutting = false;
+ private boolean isExpanding = false;
+
private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand"));
/**
@@ -104,8 +101,6 @@ public class LodDimension
*/
public LodDimension(IDimensionTypeWrapper newDimension, LodWorld lodWorld, int newWidth)
{
- lastCutChunk = null;
- lastExpandedChunk = null;
dimension = newDimension;
width = newWidth;
halfWidth = width / 2;
@@ -142,8 +137,6 @@ public class LodDimension
regions = new LodRegion[width][width];
- isRegionDirty = new boolean[width][width];
- regenRegionBuffer = new int[width][width];
center = new RegionPos(0, 0);
}
@@ -159,6 +152,8 @@ public class LodDimension
*/
public synchronized void move(RegionPos regionOffset)
{
+ ClientApi.LOGGER.info("LodDim MOVE. Offset: "+regionOffset);
+ saveDirtyRegionsToFile(false); //async add dirty regions to be saved.
int xOffset = regionOffset.x;
int zOffset = regionOffset.z;
@@ -170,7 +165,6 @@ public class LodDimension
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++) {
regions[x][z] = null;
- regenRegionBuffer[x][z] = 0;
}
// update the new center
center.x += xOffset;
@@ -188,14 +182,10 @@ public class LodDimension
{
for (int z = 0; z < width; z++)
{
- if (x + xOffset < width) {
+ if (x + xOffset < width)
regions[x][z] = regions[x + xOffset][z];
- regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z];
- }
- else {
+ else
regions[x][z] = null;
- regenRegionBuffer[x][z] = 0;
- }
}
}
}
@@ -206,14 +196,11 @@ public class LodDimension
{
for (int z = 0; z < width; z++)
{
- if (x + xOffset >= 0) {
+ if (x + xOffset >= 0)
regions[x][z] = regions[x + xOffset][z];
- regenRegionBuffer[x][z] = regenRegionBuffer[x + xOffset][z];
- }
- else {
+ else
regions[x][z] = null;
- regenRegionBuffer[x][z] = 0;
- }
+ }
}
}
@@ -226,14 +213,10 @@ public class LodDimension
{
for (int z = 0; z < width; z++)
{
- if (z + zOffset < width) {
+ if (z + zOffset < width)
regions[x][z] = regions[x][z + zOffset];
- regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset];
- }
- else {
+ else
regions[x][z] = null;
- regenRegionBuffer[x][z] = 0;
- }
}
}
}
@@ -244,15 +227,10 @@ public class LodDimension
{
for (int z = width - 1; z >= 0; z--)
{
- if (z + zOffset >= 0) {
+ if (z + zOffset >= 0)
regions[x][z] = regions[x][z + zOffset];
- regenRegionBuffer[x][z] = regenRegionBuffer[x][z + zOffset];
- }
- else {
+ else
regions[x][z] = null;
- regenRegionBuffer[x][z] = 0;
- }
- }
}
}
}
@@ -261,6 +239,7 @@ public class LodDimension
// update the new center
center.x += xOffset;
center.z += zOffset;
+ ClientApi.LOGGER.info("LodDim MOVE complete. Offset: "+regionOffset);
}
@@ -362,152 +341,109 @@ public class LodDimension
*/
public void cutRegionNodesAsync(int playerPosX, int playerPosZ)
{
- AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
-
- if (lastCutChunk == null)
- lastCutChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
-
+ if (isCutting) return;
+ isCutting = true;
// don't run the tree cutter multiple times
// for the same location
- if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ()) {
- lastCutChunk = newPlayerChunk;
+ Runnable thread = () -> {
+ //ClientApi.LOGGER.info("LodDim cut Region: " + playerPosX + "," + playerPosZ);
- Runnable thread = () -> {
-
- // go over every region in the dimension
- iterateWithSpiral((int x, int z) -> {
- int regionX;
- int regionZ;
- int minDistance;
- byte detail;
- byte minAllowedDetailLevel;
- regionX = (x + center.x) - halfWidth;
- regionZ = (z + center.z) - halfWidth;
-
- if (regions[x][z] != null) {
- // check what detail level this region should be
- // and cut it if it is higher then that
- minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ,
- playerPosX, playerPosZ);
- detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
- minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
-
- if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel) {
- regions[x][z].cutTree(minAllowedDetailLevel);
- regenRegionBuffer[x][z] = 2;
- regenDimensionBuffers = true;
- }
+ // go over every region in the dimension
+ iterateWithSpiral((int x, int z) -> {
+ int regionX;
+ int regionZ;
+ int minDistance;
+ byte detail;
+ byte minAllowedDetailLevel;
+ regionX = (x + center.x) - halfWidth;
+ regionZ = (z + center.z) - halfWidth;
+ LodRegion region = regions[x][z];
+ if (region != null) {
+ // check what detail level this region should be
+ // and cut it if it is higher then that
+ minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ,
+ playerPosX, playerPosZ);
+ detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
+ minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
+ if (region.getMinDetailLevel() < minAllowedDetailLevel) {
+ region.cutTree(minAllowedDetailLevel);
+ region.needRegenBuffer = 2;
+ regenDimensionBuffers = true;
}
- });
- };
- cutAndExpandThread.execute(thread);
- }
+ }
+ });
+ //ClientApi.LOGGER.info("LodDim cut Region complete: " + playerPosX + "," + playerPosZ);
+ isCutting = false;
+ };
+ cutAndExpandThread.execute(thread);
}
/** Either expands or loads all regions in the rendered LOD area */
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ) {
+
+ if (isExpanding) return;
+ isExpanding = true;
+
DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
- AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX),
- LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality();
-
- if (lastExpandedChunk == null)
- lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
-
+ DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getDropoffQuality();
+ if (dropoffQuality == DropoffQuality.AUTO)
+ dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ?
+ DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED;
+ int dropoffSwitch = dropoffQuality.fastModeSwitch;
// don't run the expander multiple times
// for the same location
- if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ()) {
- lastExpandedChunk = newPlayerChunk;
+ Runnable thread = () -> {
+ //ClientApi.LOGGER.info("LodDim expend Region: " + playerPosX + "," + playerPosZ);
- Runnable thread = () -> {
+ iterateWithSpiral((int x, int z) -> {
+ int regionX;
+ int regionZ;
+ LodRegion region;
+ int minDistance;
+ int maxDistance;
+ byte minDetail;
+ byte maxDetail;
+ regionX = (x + center.x) - halfWidth;
+ regionZ = (z + center.z) - halfWidth;
+ final RegionPos regionPos = new RegionPos(regionX, regionZ);
+ region = regions[x][z];
- iterateWithSpiral((int x, int z) -> {
- int regionX;
- int regionZ;
- LodRegion region;
- int minDistance;
- byte detail;
- byte levelToGen;
- regionX = (x + center.x) - halfWidth;
- regionZ = (z + center.z) - halfWidth;
- final RegionPos regionPos = new RegionPos(regionX, regionZ);
- region = regions[x][z];
-
- minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
- playerPosZ);
- detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
- levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
-
- // check that the region isn't null and at least this detail level
- if (region == null || region.getGenerationMode() != generationMode) {
- // First case, region has to be created
-
- // try to get the region from file
- regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
-
- // if there is no region file create an empty region
- if (regions[x][z] == null)
- regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
-
- regenRegionBuffer[x][z] = 2;
- regenDimensionBuffers = true;
- } else if (region.getMinDetailLevel() > levelToGen) {
- // Second case, the region exists at a higher detail level.
-
- // Expand the region by introducing the missing layer
- region.growTree(levelToGen);
- regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
- regenRegionBuffer[x][z] = 2;
- regenDimensionBuffers = true;
- }
- });
- };
-
- cutAndExpandThread.execute(thread);
- }
- }
-
- /**
- * Use addVerticalData when possible.
- * Add the given LOD to this dimension at the coordinate
- * stored in the LOD. If an LOD already exists at the given
- * coordinate it will be overwritten.
- */
- public Boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data, boolean dontSave)
- {
- int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
- int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
-
- // don't continue if the region can't be saved
- LodRegion region = getRegion(regionPosX, regionPosZ);
- if (region == null)
- return false;
-
- boolean nodeAdded = region.addData(detailLevel, posX, posZ, verticalIndex, data);
-
- // only save valid LODs to disk
- if (!dontSave && fileHandler != null)
- {
- try
- {
- // mark the region as dirty, so it will be saved to disk
- int xIndex = (regionPosX - center.x) + halfWidth;
- int zIndex = (regionPosZ - center.z) + halfWidth;
+ minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
+ playerPosZ);
+ maxDistance = LevelPosUtil.maxDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX,
+ playerPosZ);
+ minDetail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
+ maxDetail = DetailDistanceUtil.getTreeGenDetailFromDistance(maxDistance);
- isRegionDirty[xIndex][zIndex] = true;
- regenRegionBuffer[xIndex][zIndex] = 2;
- regenDimensionBuffers = true;
- }
- catch (ArrayIndexOutOfBoundsException e)
- {
- e.printStackTrace();
- // If this happens, the method was probably
- // called when the dimension was changing size.
- // Hopefully this shouldn't be an issue.
- }
- }
-
- return nodeAdded;
+ boolean updated = false;
+ if (region == null) {
+ region = getRegionFromFile(regionPos, minDetail, generationMode, verticalQuality);
+ regions[x][z] = region;
+ updated = true;
+ } else if (region.getGenerationMode().compareTo(generationMode) < 0 ||
+ region.getVerticalQuality() != verticalQuality ||
+ region.getMinDetailLevel() > minDetail) {
+ // The 'getRegionFromFile' will flush and save the region if it returns a new one
+ region = getRegionFromFile(regions[x][z], minDetail, generationMode, verticalQuality);
+ regions[x][z] = region;
+ updated = true;
+ } else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != maxDetail) {
+ region.lastMaxDetailLevel = maxDetail;
+ updated = true;
+ } else if (minDetail <= dropoffSwitch && region.lastMaxDetailLevel != region.getMinDetailLevel()) {
+ updated = true;
+ }
+ if (updated) {
+ region.needRegenBuffer = 2;
+ regenDimensionBuffers = true;
+ }
+ });
+ //ClientApi.LOGGER.info("LodDim expend Region complete: " + playerPosX + "," + playerPosZ);
+ isExpanding = false;
+ };
+
+ cutAndExpandThread.execute(thread);
}
/**
@@ -515,7 +451,7 @@ public class LodDimension
* stored in the LOD. If an LOD already exists at the given
* coordinate it will be overwritten.
*/
- public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data, boolean dontSave)
+ public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data)
{
int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
@@ -526,178 +462,38 @@ public class LodDimension
return false;
boolean nodeAdded = region.addVerticalData(detailLevel, posX, posZ, data);
-
- // only save valid LODs to disk
- if (!dontSave && fileHandler != null)
- {
- try
- {
- // mark the region as dirty, so it will be saved to disk
- int xIndex = (regionPosX - center.x) + halfWidth;
- int zIndex = (regionPosZ - center.z) + halfWidth;
-
- isRegionDirty[xIndex][zIndex] = true;
- regenRegionBuffer[xIndex][zIndex] = 2;
- regenDimensionBuffers = true;
- }
- catch (ArrayIndexOutOfBoundsException e)
- {
- e.printStackTrace();
- // If this happens, the method was probably
- // called when the dimension was changing size.
- // Hopefully this shouldn't be an issue.
- }
+ if (nodeAdded) {
+ region.needRegenBuffer = 2;
+ region.needSaving = true;
+ regenDimensionBuffers = true;
}
-
return nodeAdded;
}
/** marks the region at the given region position to have its buffer rebuilt */
public void markRegionBufferToRegen(int xRegion, int zRegion)
{
- int xIndex = (xRegion - center.x) + halfWidth;
- int zIndex = (zRegion - center.z) + halfWidth;
- regenRegionBuffer[xIndex][zIndex] = 2;
+ LodRegion r = getRegion(xRegion,zRegion);
+ if (r!=null) {
+ r.needRegenBuffer = 2;
+ regenDimensionBuffers = true;
+ }
}
/**
* Returns every position that need to be generated based on the position of the player
*/
- public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ)
+ public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ, GenerationPriority priority)
{
PosToGenerateContainer posToGenerate;
- LodRegion lodRegion;
- // all the following values are used for the spiral matrix visit
- // x and z are the matrix coord
- // dx and dz is the next move on the coordinate in the range -1 0 +1
- int x, z, dx, dz, t;
- x = 0;
- z = 0;
- dx = 0;
- dz = -1;
-
- // We can use two type of generation scheduling
- switch (CONFIG.client().worldGenerator().getGenerationPriority())
- {
- case NEAR_FIRST:
- //in the NEAR_FIRST generation scheduling we prioritize the nearest un-generated position to the player
- //the chunk position to generate will be stored in a posToGenerate object
- posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
-
- int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosX);
- int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosZ);
-
- int complexity;
- int xChunkToCheck;
- int zChunkToCheck;
- byte detailLevel;
- int posX;
- int posZ;
- long data;
- int numbChunksWide = (width) * 32;
- int circleLimit = Integer.MAX_VALUE;
-
- //posToGenerate is using an insertion sort algorithm which can become really fast if the
- //original data order is almost ordered. For this reason we explore the matrix of the position to generate
- //with a spiral matrix visit (a square spiral is almost ordered in the "nearest to farthest" order)
- for (int i = 0; i < numbChunksWide * numbChunksWide; i++)
- {
- //Firstly we check if the posToGenerate has been filled
- if (maxDataToGenerate == 0)
- {
- maxDataToGenerate--;
- //if it has been filled then we set a stop distance
- //the stop distance will be current distance (generically x) per square root of 2
- //this would guarantee a circular generation since (Math.abs(x) * 1.41f) is the
- //radius of a circle that inscribe a square
- circleLimit = (int) (Math.abs(x) * 1.41f);
- }
- //This second if check if we reached the circleLimit decided in the previous if
- //if so we stop
- else if (maxDataToGenerate < 0)
- {
- if (circleLimit < Math.abs(x) && circleLimit < Math.abs(z))
- break;
- }
-
-
- xChunkToCheck = x + playerChunkX;
- zChunkToCheck = z + playerChunkZ;
-
- //we get the lod region in which the chunk is present
- lodRegion = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck);
- if (lodRegion == null)
- continue;
-
- //Now we check if the current chunk has been generated with the correct complexity
- //if(lodRegion.isChunkPreGenerated(xChunkToCheck,zChunkToCheck))
- // complexity = DistanceGenerationMode.SERVER.complexity;
- //else
- complexity = CONFIG.client().worldGenerator().getDistanceGenerationMode().complexity;
-
-
- //we create the level position info of the chunk
- detailLevel = lodRegion.getMinDetailLevel();
- posX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, detailLevel);
- posZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, zChunkToCheck, detailLevel);
-
- data = getSingleData(detailLevel, posX, posZ);
-
- //we will generate the position only if the current generation complexity is lower than the target one.
- //an un-generated area will always have 0 generation
- if (DataPointUtil.getGenerationMode(data) < complexity)
- {
- posToGenerate.addPosToGenerate(detailLevel, posX, posZ);
- if (maxDataToGenerate >= 0)
- maxDataToGenerate--;
- }
-
- //with this code section we find the next chunk to check
- if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
- {
- t = dx;
- dx = -dz;
- dz = t;
- }
- x += dx;
- z += dz;
- }
- break;
-
- default:
- case FAR_FIRST:
- //in the FAR_FIRST generation we dedicate part of the generation process to the far region with really
- //low detail quality.
-
- posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
-
- int xRegion;
- int zRegion;
-
- for (int i = 0; i < width * width; i++)
- {
- xRegion = x + center.x;
- zRegion = z + center.z;
-
- //All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
- lodRegion = getRegion(xRegion, zRegion);
- if (lodRegion != null)
- lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ);
-
-
- //with this code section we find the next chunk to check
- if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
- {
- t = dx;
- dx = -dz;
- dz = t;
- }
- x += dx;
- z += dz;
- }
- break;
- }
- return posToGenerate;
+ posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
+ iterateWithSpiral((int x, int z) -> {
+ //All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
+ LodRegion lodRegion = regions[x][z];
+ if (lodRegion != null)
+ lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ, priority);
+ });
+ return posToGenerate;
}
/**
@@ -712,11 +508,16 @@ public class LodDimension
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority();
if (generationPriority == GenerationPriority.AUTO)
generationPriority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
+
+ DropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getDropoffQuality();
+ if (dropoffQuality == DropoffQuality.AUTO)
+ dropoffQuality = CONFIG.client().graphics().quality().getLodChunkRenderDistance() < 128 ?
+ DropoffQuality.SMOOTH_DROPOFF : DropoffQuality.PERFORMANCE_FOCUSED;
boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST;
if (region != null)
- region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel);
+ region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel, dropoffQuality);
}
/**
@@ -785,8 +586,9 @@ public class LodDimension
LodRegion region = getRegion(xRegion, zRegion);
if (region == null)
return;
- markRegionBufferToRegen(xRegion, zRegion);
region.clear(detailLevel, posX, posZ);
+ region.needRegenBuffer = 2;
+ regenDimensionBuffers = true;
}
/**
@@ -795,16 +597,12 @@ public class LodDimension
*/
public boolean getAndClearRegionNeedBufferRegen(int regionX, int regionZ)
{
- //FIXME: Use actual atomics on regenRegionBuffer
- //FIXME: Race condition on lodDim move/resize!
- int xIndex = (regionX - center.x) + halfWidth;
- int zIndex = (regionZ - center.z) + halfWidth;
-
- if (xIndex < 0 || xIndex >= width || zIndex < 0 || zIndex >= width)
- return false;
- int i = regenRegionBuffer[xIndex][zIndex];
+ //FIXME: Use actual atomics on needRegenBuffer
+ LodRegion region = getRegion(regionX, regionZ);
+ if (region == null) return false;
+ int i = region.needRegenBuffer;
if (i > 0) {
- regenRegionBuffer[xIndex][zIndex]--;
+ region.needRegenBuffer--;
return true;
}
return false;
@@ -825,11 +623,10 @@ public class LodDimension
int xRegion = LevelPosUtil.getRegion(detailLevel, posX);
int zRegion = LevelPosUtil.getRegion(detailLevel, posZ);
LodRegion region = getRegion(xRegion, zRegion);
- if (region == null)
- return;
- markRegionBufferToRegen(xRegion, zRegion);
-
+ if (region == null) return;
region.updateArea(detailLevel, posX, posZ);
+ region.needRegenBuffer = 2;
+ regenDimensionBuffers = true;
}
/** Returns true if a region exists at the given LevelPos */
@@ -846,13 +643,25 @@ public class LodDimension
public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel,
DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
- return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : null;
+ return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) :
+ new LodRegion(detailLevel, regionPos, generationMode, verticalQuality);
+ }
+ /**
+ * Loads the region at the given region from file,
+ * if a file exists for that region.
+ */
+ public LodRegion getRegionFromFile(LodRegion existingRegion, byte detailLevel,
+ DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
+ {
+ return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, existingRegion, generationMode, verticalQuality) :
+ new LodRegion(detailLevel, existingRegion.getRegionPos(), generationMode, verticalQuality);
}
/** Save all dirty regions in this LodDimension to file. */
- public void saveDirtyRegionsToFileAsync()
+ public void saveDirtyRegionsToFile(boolean blockUntilFinished)
{
- fileHandler.saveDirtyRegionsToFileAsync();
+ if (fileHandler == null) return;
+ fileHandler.saveDirtyRegionsToFile(blockUntilFinished);
}
@@ -907,13 +716,6 @@ public class LodDimension
halfWidth = width/ 2;
regions = new LodRegion[width][width];
- isRegionDirty = new boolean[width][width];
- regenRegionBuffer = new int[width][width];
-
- // populate isRegionDirty
- for (int i = 0; i < width; i++)
- for (int j = 0; j < width; j++)
- isRegionDirty[i][j] = false;
}
@@ -936,14 +738,4 @@ public class LodDimension
}
return stringBuilder.toString();
}
-
- public boolean GetIsRegionDirty(int i, int j)
- {
- return isRegionDirty[i][j];
- }
-
- public void SetIsRegionDirty(int i, int j, boolean val)
- {
- isRegionDirty[i][j] = val;
- }
}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java
index 02f98f2ba..b5dc6a9aa 100644
--- a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java
@@ -20,6 +20,8 @@
package com.seibel.lod.core.objects.lod;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.DropoffQuality;
+import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.enums.config.VerticalQuality;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.PosToRenderContainer;
@@ -29,8 +31,9 @@ import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
/**
- * This object holds all loaded LevelContainers acting as a quad tree
- * for a given region.
+ * This object holds all loaded LevelContainers acting as a quad tree for a
+ * given region.
+ *
*
* Coordinate Standard:
* Coordinate called posX or posZ are relative LevelPos coordinates
@@ -39,445 +42,371 @@ import com.seibel.lod.core.util.LodUtil;
* @author Leonardo Amato
* @version 10-10-2021
*/
-public class LodRegion
-{
+public class LodRegion {
/** Number of detail level supported by a region */
private static final byte POSSIBLE_LOD = 10;
-
-
+
/** Holds the lowest (least detailed) detail level in this region */
private byte minDetailLevel;
-
+ public byte lastMaxDetailLevel = LodUtil.REGION_DETAIL_LEVEL;
+
/**
* This holds all data for this region
*/
private final LevelContainer[] dataContainer;
-
+
/** This chunk Pos has been generated */
- //private final boolean[] preGeneratedChunkPos;
-
+ // private final boolean[] preGeneratedChunkPos;
+
/** the generation mode for this region */
private final DistanceGenerationMode generationMode;
/** the vertical quality of this region */
private final VerticalQuality verticalQuality;
-
+
/** this region's x RegionPos */
public final int regionPosX;
/** this region's z RegionPos */
public final int regionPosZ;
-
- public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
- {
+
+ public volatile int needRegenBuffer = 2;
+ public volatile boolean needSaving = false;
+
+ public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode,
+ VerticalQuality verticalQuality) {
this.minDetailLevel = minDetailLevel;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
this.verticalQuality = verticalQuality;
this.generationMode = generationMode;
dataContainer = new LevelContainer[POSSIBLE_LOD];
-
-
+
// Initialize all the different matrices
- for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
- {
+ for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) {
dataContainer[lod] = new VerticalLevelContainer(lod);
}
-
- boolean fileFound = false;
-
- /*
- preGeneratedChunkPos = new boolean[32 * 32];
- if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
- {
- File regionFileDirHead;
- File regionFileDirParent;
- // local world
-
- ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension());
-
- // provider needs a separate variable to prevent
- // the compiler from complaining
- StringBuilder string = new StringBuilder();
- try
- {
- ServerChunkProvider provider = serverWorld.getChunkSource();
-
- //System.out.println(provider.dataStorage.dataFolder);
- regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca");
- if (regionFileDirHead.exists())
- {
- regionFileDirParent = regionFileDirHead.getParentFile();
- //string.append(regionFileDirParent.toString());
- string.append(regionFileDirHead);
- RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true);
- for (int x = 0; x < 32; x++)
- {
- for (int z = 0; z < 32; z++)
- {
- preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z));
- }
- }
-
- string.append("region " + regionPosX + " " + regionPosZ + "\n");
- for (int x = 0; x < 32; x++)
- {
- for (int z = 0; z < 32; z++)
- {
- //regionFile.doesChunkExist()
- string.append(preGeneratedChunkPos[x * 32 + z] + "\t");
- }
- string.append("\n");
- }
- regionFile.close();
- }
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- System.out.println(string);
- }*/
-
}
-
-
- /** Return true if the chunk has been pregenerated in game */
- //public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
- //{
- // xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos);
- // zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos);
- // return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos];
- //}
-
+
/**
* Inserts the data point into the region.
*
* TODO this will always return true unless it has
+ *
* @return true if the data was added successfully
*/
- public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data)
- {
+ public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data) {
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)
- this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
-
+ return false;// this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
+
this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex);
-
+
return true;
}
-
+
/**
* Inserts the vertical data into the region.
*
* TODO this will always return true unless it has
+ *
* @return true if the data was added successfully
*/
- public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data)
- {
+ public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data) {
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)
- this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
-
+ return false;// this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
+
return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ);
}
-
+
/**
* Get the dataPoint at the given relative position.
- * @return the data at the relative pos and detail level,
- * 0 if the data doesn't exist.
+ *
+ * @return the data at the relative pos and detail level, 0 if the data doesn't
+ * exist.
*/
- public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
- {
+ public long getData(byte detailLevel, int posX, int posZ, int verticalIndex) {
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
}
-
+
/**
* Get the dataPoint at the given relative position.
- * @return the data at the relative pos and detail level,
- * 0 if the data doesn't exist.
+ *
+ * @return the data at the relative pos and detail level, 0 if the data doesn't
+ * exist.
*/
- public long getSingleData(byte detailLevel, int posX, int posZ)
- {
+ public long getSingleData(byte detailLevel, int posX, int posZ) {
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
return dataContainer[detailLevel].getSingleData(posX, posZ);
}
-
+
/**
* Clears the datapoint at the given relative position
*/
- public void clear(byte detailLevel, int posX, int posZ)
- {
+ public void clear(byte detailLevel, int posX, int posZ) {
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
dataContainer[detailLevel].clear(posX, posZ);
}
-
+
/**
- * This method will fill the posToGenerate array with all levelPos that
- * are render-able.
+ * This method will fill the posToGenerate array with all levelPos that are
+ * render-able.
*
- * TODO why don't we return the posToGenerate, it would make this easier to understand
+ * TODO why don't we return the posToGenerate, it would make this easier to
+ * understand
*/
- public void getPosToGenerate(PosToGenerateContainer posToGenerate,
- int playerBlockPosX, int playerBlockPosZ)
- {
- getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ);
-
+ public void getPosToGenerate(PosToGenerateContainer posToGenerate, int playerBlockPosX, int playerBlockPosZ,
+ GenerationPriority priority) {
+ getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ, priority);
+
}
-
+
/**
* A recursive method that fills the posToGenerate array with all levelPos that
* need to be generated.
*
- * TODO why don't we return the posToGenerate, it would make this easier to understand
+ * TODO why don't we return the posToGenerate, it would make this easier to
+ * understand
*/
- private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel,
- int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ)
- {
+ private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel, int childOffsetPosX,
+ int childOffsetPosZ, int playerPosX, int playerPosZ, GenerationPriority priority) {
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
-
+
// calculate what LevelPos are in range to generate
- int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
-
+ int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX,
+ playerPosZ);
+
// determine this child's levelPos
byte childDetailLevel = (byte) (detailLevel - 1);
int childPosX = childOffsetPosX * 2;
int childPosZ = childOffsetPosZ * 2;
-
- int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
-
- byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel;
- if (targetDetailLevel <= detailLevel)
- {
- if (targetDetailLevel == detailLevel)
- {
+
+
+ byte targetDetailLevel = DetailDistanceUtil.getGenerationDetailFromDistance(minDistance);
+ if (targetDetailLevel <= detailLevel) {
+ if (targetDetailLevel == detailLevel) {
if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ))
- posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size);
- }
- else
- {
- // we want at max one request per chunk (since the world generator creates chunks).
- // So for lod smaller than a chunk, only recurse down
- // the top right child
-
- if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
- {
- int ungeneratedChildren = 0;
-
- // make sure all children are generated to this detailLevel
+ posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size,
+ childOffsetPosZ + regionPosZ * size);
+ } else {
+ if (priority == GenerationPriority.FAR_FIRST && detailLevel >= posToGenerate.farMinDetail
+ && !doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ)) {
+ posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size,
+ childOffsetPosZ + regionPosZ * size);
+ } else if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) {
for (int x = 0; x <= 1; x++)
- {
for (int z = 0; z <= 1; z++)
- {
- if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
- {
- ungeneratedChildren++;
- posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
- }
- }
- }
-
- // only if all the children are correctly generated
- // should we go deeper
- if (ungeneratedChildren == 0)
- for (int x = 0; x <= 1; x++)
- for (int z = 0; z <= 1; z++)
- getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
- }
- else
- {
- // The detail Level is smaller than a chunk.
- // Only recurse down the top right child.
-
- if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
- {
- if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
- posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
- else
- getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
- }
+ getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX,
+ playerPosZ, priority);
+ } else {
+ // we want at max one request per chunk (since the world generator creates
+ // chunks).
+ // So for lod smaller than a chunk, only recurse down
+ // the top right child
+ getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ,
+ priority);
}
}
}
// we have gone beyond the target Detail level
// we can stop generating
-
+
}
-
-
+
/**
- * This method will fill the posToRender array with all levelPos that
- * are render-able.
+ * This method will fill the posToRender array with all levelPos that are
+ * render-able.
*
- * TODO why don't we return the posToRender, it would make this easier to understand
+ * TODO why don't we return the posToRender, it would make this easier to
+ * understand
*/
- public void getPosToRender(PosToRenderContainer posToRender,
- int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
- {
- getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
+ public void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ,
+ boolean requireCorrectDetailLevel, DropoffQuality dropoffQuality) {
+ int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, regionPosX, regionPosZ);
+ byte targetLevel = DetailDistanceUtil.getDrawDetailFromDistance(minDistance);
+ if (targetLevel <= dropoffQuality.fastModeSwitch) {
+ getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ,
+ requireCorrectDetailLevel);
+ } else {
+ getPosToRenderFlat(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, targetLevel, requireCorrectDetailLevel);
+ }
}
-
+
/**
- * This method will fill the posToRender array with all levelPos that
- * are render-able.
+ * This method will fill the posToRender array with all levelPos that are
+ * render-able.
*
- * TODO why don't we return the posToRender, it would make this easier to understand
- * TODO this needs some more comments, James was only able to figure out part of it
+ * TODO why don't we return the posToRender, it would make this easier to
+ * understand TODO this needs some more comments, James was only able to figure
+ * out part of it
*/
- private void getPosToRender(PosToRenderContainer posToRender,
- byte detailLevel, int posX, int posZ,
- int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
- {
+ private void getPosToRender(PosToRenderContainer posToRender, byte detailLevel, int posX, int posZ, int playerPosX,
+ int playerPosZ, boolean requireCorrectDetailLevel) {
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
-
+
byte desiredLevel;
int maxDistance;
int minDistance;
int childLevel;
-
-
+
// calculate the LevelPos that are in range
maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
-
- if (detailLevel == childLevel - 1)
+
+ if (detailLevel == childLevel - 1) {
+ posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
+ } else
+ // if (desiredLevel > detailLevel)
+ // {
+ // we have gone beyond the target Detail level
+ // we can stop generating
+ // } else
+ if (desiredLevel == detailLevel) {
+ posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
+ } else // case where (detailLevel > desiredLevel)
{
- posToRender.addPosToRender(detailLevel,
- posX + regionPosX * size,
- posZ + regionPosZ * size);
+ int childPosX = posX * 2;
+ int childPosZ = posZ * 2;
+ byte childDetailLevel = (byte) (detailLevel - 1);
+ int childrenCount = 0;
+
+ for (int x = 0; x <= 1; x++) {
+ for (int z = 0; z <= 1; z++) {
+ if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) {
+ if (!requireCorrectDetailLevel)
+ childrenCount++;
+ else
+ getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX,
+ playerPosZ, requireCorrectDetailLevel);
+ }
+ }
+ }
+
+ if (!requireCorrectDetailLevel) {
+ // If all the four children exist go deeper
+ if (childrenCount == 4) {
+ for (int x = 0; x <= 1; x++)
+ for (int z = 0; z <= 1; z++)
+ getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX,
+ playerPosZ, requireCorrectDetailLevel);
+ } else {
+ posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
+ }
+ }
}
- else
- //if (desiredLevel > detailLevel)
- //{
- // we have gone beyond the target Detail level
- // we can stop generating
- //} else
- if (desiredLevel == detailLevel)
- {
- posToRender.addPosToRender(detailLevel,
- posX + regionPosX * size,
- posZ + regionPosZ * size);
- }
- else //case where (detailLevel > desiredLevel)
- {
- int childPosX = posX * 2;
- int childPosZ = posZ * 2;
- byte childDetailLevel = (byte) (detailLevel - 1);
- int childrenCount = 0;
-
- for (int x = 0; x <= 1; x++)
- {
- for (int z = 0; z <= 1; z++)
- {
- if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
- {
- if (!requireCorrectDetailLevel)
- childrenCount++;
- else
- getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
- }
- }
- }
-
-
- if (!requireCorrectDetailLevel)
- {
- // If all the four children exist go deeper
- if (childrenCount == 4)
- {
- for (int x = 0; x <= 1; x++)
- for (int z = 0; z <= 1; z++)
- getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
- }
- else
- {
- posToRender.addPosToRender(detailLevel,
- posX + regionPosX * size,
- posZ + regionPosZ * size);
- }
- }
- }
}
-
-
+
+ /**
+ * This method will fill the posToRender array with all levelPos that are
+ * render-able. But the entire region try use the same detail level.
+ */
+ private void getPosToRenderFlat(PosToRenderContainer posToRender, byte detailLevel, int posX, int posZ, byte targetLevel, boolean requireCorrectDetailLevel) {
+ // equivalent to 2^(...)
+ int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+
+ if (detailLevel == targetLevel) {
+ posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
+ } else // case where (detailLevel > desiredLevel)
+ {
+ int childPosX = posX * 2;
+ int childPosZ = posZ * 2;
+ byte childDetailLevel = (byte) (detailLevel - 1);
+ int childrenCount = 0;
+
+ for (int x = 0; x <= 1; x++) {
+ for (int z = 0; z <= 1; z++) {
+ if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) {
+ if (!requireCorrectDetailLevel)
+ childrenCount++;
+ else
+ getPosToRenderFlat(posToRender, childDetailLevel, childPosX + x, childPosZ + z, targetLevel, requireCorrectDetailLevel);
+ }
+ }
+ }
+
+ if (!requireCorrectDetailLevel) {
+ // If all the four children exist go deeper
+ if (childrenCount == 4) {
+ for (int x = 0; x <= 1; x++)
+ for (int z = 0; z <= 1; z++)
+ getPosToRenderFlat(posToRender, childDetailLevel, childPosX + x, childPosZ + z, targetLevel, requireCorrectDetailLevel);
+ } else {
+ posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size);
+ }
+ }
+ }
+ }
+
/**
* Updates all children.
*
* TODO could this be renamed mergeArea?
*/
- public void updateArea(byte detailLevel, int posX, int posZ)
- {
+ public void updateArea(byte detailLevel, int posX, int posZ) {
int width;
int startX;
int startZ;
-
- // TODO what are each of these loops updating?
- for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++)
- {
+
+ // Update the level lower or equal to the detail level
+ for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++) {
startX = LevelPosUtil.convert(detailLevel, posX, down);
startZ = LevelPosUtil.convert(detailLevel, posZ, down);
width = 1 << (detailLevel - down);
-
+
for (int x = 0; x < width; x++)
for (int z = 0; z < width; z++)
update(down, startX + x, startZ + z);
}
-
-
- for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++)
- {
- update(up,
- LevelPosUtil.convert(detailLevel, posX, up),
- LevelPosUtil.convert(detailLevel, posZ, up));
+
+ // Update the level higher than the detail level
+ for (byte up = (byte) (Math.max(detailLevel, minDetailLevel) + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++) {
+ update(up, LevelPosUtil.convert(detailLevel, posX, up), LevelPosUtil.convert(detailLevel, posZ, up));
}
}
-
+
/**
* Update the child at the given relative Pos
*
* TODO could this be renamed mergeChildData?
*/
- private void update(byte detailLevel, int posX, int posZ)
- {
+ 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);
}
-
-
+
/**
* Returns if data exists at the given relative Pos.
*/
- public boolean doesDataExist(byte detailLevel, int posX, int posZ)
- {
+ public boolean doesDataExist(byte detailLevel, int posX, int posZ) {
if (detailLevel < minDetailLevel || dataContainer[detailLevel] == null)
return false;
-
+
posX = LevelPosUtil.getRegionModule(detailLevel, posX);
posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
-
+
return dataContainer[detailLevel].doesItExist(posX, posZ);
}
-
+
/**
* Gets the generation mode for the data point at the given relative pos.
*/
- public byte getGenerationMode(byte detailLevel, int posX, int posZ)
- {
+ public byte getGenerationMode(byte detailLevel, int posX, int posZ) {
if (dataContainer[detailLevel].doesItExist(posX, posZ))
// We take the bottom information always
// TODO what does that mean? bottom of what?
@@ -485,129 +414,115 @@ public class LodRegion
else
return DistanceGenerationMode.NONE.complexity;
}
-
+
/**
- * Returns the lowest (least detailed) detail level in this region
- * TODO is that right?
+ * Returns the lowest (least detailed) detail level in this region TODO is that
+ * right?
*/
- public byte getMinDetailLevel()
- {
+ public byte getMinDetailLevel() {
return minDetailLevel;
}
-
+
/**
* Returns the LevelContainer for the detailLevel
- * @throws IllegalArgumentException if the detailLevel is less than minDetailLevel
+ *
+ * @throws IllegalArgumentException if the detailLevel is less than
+ * minDetailLevel
*/
- public LevelContainer getLevel(byte detailLevel)
- {
+ public LevelContainer getLevel(byte detailLevel) {
if (detailLevel < minDetailLevel)
- throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]");
-
+ throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: ["
+ + minDetailLevel + "] level requested: [" + detailLevel + "]");
+
return dataContainer[detailLevel];
}
-
+
/**
- * Add the levelContainer to this Region, updating the minDetailLevel
- * if necessary.
- * @throws IllegalArgumentException if the LevelContainer's detailLevel
- * is 2 or more detail levels lower than the
- * minDetailLevel of this region.
+ * Add the levelContainer to this Region, updating the minDetailLevel if
+ * necessary.
+ *
+ * @throws IllegalArgumentException if the LevelContainer's detailLevel is 2 or
+ * more detail levels lower than the
+ * minDetailLevel of this region.
*/
- public void addLevelContainer(LevelContainer levelContainer)
- {
- if (levelContainer.getDetailLevel() < minDetailLevel - 1)
- {
- throw new IllegalArgumentException(
- "the LevelContainer's detailLevel was "
- + "[" + levelContainer.getDetailLevel() + "] but this region "
- + "only allows adding LevelContainers with a "
- + "detail level of [" + (minDetailLevel - 1) + "]");
+ public void addLevelContainer(LevelContainer levelContainer) {
+ if (levelContainer.getDetailLevel() < minDetailLevel - 1) {
+ throw new IllegalArgumentException("the LevelContainer's detailLevel was " + "["
+ + levelContainer.getDetailLevel() + "] but this region "
+ + "only allows adding LevelContainers with a " + "detail level of [" + (minDetailLevel - 1) + "]");
}
-
+
if (levelContainer.getDetailLevel() == minDetailLevel - 1)
minDetailLevel = levelContainer.getDetailLevel();
-
+
dataContainer[levelContainer.getDetailLevel()] = levelContainer;
}
-
+
// TODO James thinks cutTree and growTree (which he renamed to match cutTree)
// should have more descriptive names, to make sure the "Tree" portion isn't
// confused with Minecraft trees (the plant).
-
+
/**
- * Removes any dataContainers that are higher than
- * the given detailLevel
+ * Removes any dataContainers that are higher than the given detailLevel
*/
- public void cutTree(byte detailLevel)
- {
- if (detailLevel > minDetailLevel)
- {
+ public void cutTree(byte detailLevel) {
+ if (detailLevel > minDetailLevel) {
for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++)
dataContainer[detailLevelIndex] = null;
-
+
minDetailLevel = detailLevel;
}
}
-
+
/**
- * Make this region more detailed to the detailLevel given.
- * TODO is that correct?
+ * Make this region more detailed to the detailLevel given. TODO is that
+ * correct?
*/
- public void growTree(byte detailLevel)
- {
- if (detailLevel < minDetailLevel)
- {
- for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--)
- {
+ public void growTree(byte detailLevel) {
+ if (detailLevel < minDetailLevel) {
+ for (byte detailLevelIndex = (byte) (minDetailLevel
+ - 1); detailLevelIndex >= detailLevel; detailLevelIndex--) {
if (dataContainer[detailLevelIndex + 1] == null)
dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1));
-
+
dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand();
}
minDetailLevel = detailLevel;
}
}
-
+
/**
* return RegionPos of this lod region
*/
- public RegionPos getRegionPos()
- {
+ public RegionPos getRegionPos() {
return new RegionPos(regionPosX, regionPosZ);
}
-
+
/**
* Returns how many LODs are in this region
*/
- public int getNumberOfLods()
- {
+ public int getNumberOfLods() {
int count = 0;
for (LevelContainer container : dataContainer)
count += container.getMaxNumberOfLods();
-
+
return count;
}
-
- public VerticalQuality getVerticalQuality()
- {
+
+ public VerticalQuality getVerticalQuality() {
return verticalQuality;
}
-
- public DistanceGenerationMode getGenerationMode()
- {
+
+ public DistanceGenerationMode getGenerationMode() {
return generationMode;
}
-
- public int getMaxVerticalData(byte detailLevel)
- {
+
+ public int getMaxVerticalData(byte detailLevel) {
return dataContainer[detailLevel].getVerticalSize();
}
-
-
+
@Override
- public String toString()
- {
+ public String toString() {
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
}
}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java
index 9897d297d..69c21f23f 100644
--- a/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java
@@ -92,6 +92,7 @@ public class LodWorld
public void deselectWorld()
{
worldName = NO_WORLD_LOADED;
+ saveAllDimensions(true); // Make sure all dims are saved. This will block threads
lodDimensions = null;
isWorldLoaded = false;
}
@@ -106,7 +107,8 @@ public class LodWorld
if (lodDimensions == null)
return;
- lodDimensions.put(newDimension.dimension, newDimension);
+ LodDimension oldDim = lodDimensions.put(newDimension.dimension, newDimension);
+ if (oldDim != null) oldDim.saveDirtyRegionsToFile(true);
}
/**
@@ -129,7 +131,7 @@ public class LodWorld
if (lodDimensions == null)
return;
- saveAllDimensions();
+ saveAllDimensions(true); //block until saving is done
for (IDimensionTypeWrapper key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newRegionWidth);
@@ -138,7 +140,7 @@ public class LodWorld
/**
* Requests all dimensions save any dirty regions they may have.
*/
- public void saveAllDimensions()
+ public void saveAllDimensions(boolean isBlocking)
{
if (lodDimensions == null)
return;
@@ -147,8 +149,10 @@ public class LodWorld
// but that requires a LodDimension.hasDirtyRegions() method or something similar
ClientApi.LOGGER.info("Saving LODs");
- for (IDimensionTypeWrapper key : lodDimensions.keySet())
- lodDimensions.get(key).saveDirtyRegionsToFileAsync();
+ for (IDimensionTypeWrapper key : lodDimensions.keySet()) {
+ lodDimensions.get(key).saveDirtyRegionsToFile(isBlocking);
+ }
+ //FIXME: This should block until file is saved.
}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java
index f50754d02..6260cb418 100644
--- a/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java
+++ b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java
@@ -82,10 +82,29 @@ public class RegionPos
.offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
}
+ @Override
+ public boolean equals(Object o) {
+ // If the object is compared with itself then return true
+ if (o == this) {
+ return true;
+ }
+ // Check if o is an instance of RegionPos or not
+ if (!(o instanceof RegionPos)) {
+ return false;
+ }
+ RegionPos c = (RegionPos) o;
+ return c.x==x &&c.z==z;
+ }
+
@Override
public String toString()
{
return "(" + x + "," + z + ")";
}
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode((long)(x) << Integer.BYTES + z);
+ }
}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
index d0a280a49..1e580769b 100644
--- a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
+++ b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
@@ -210,14 +210,14 @@ public class VerticalLevelContainer implements LevelContainer
}
private static long[] downgradeVerticalSize(int oldVertSize, int newVertSize, long[] data) {
- long[] dataToMerge = new long[oldVertSize];
int size = data.length/oldVertSize;
+ long[] dataToMerge = new long[oldVertSize];
long[] newData = new long[size * newVertSize];
for (int i = 0; i < size; i++)
{
- System.arraycopy(oldVertSize, i * oldVertSize, dataToMerge, 0, oldVertSize);
- dataToMerge = DataPointUtil.mergeMultiData(dataToMerge, oldVertSize, newVertSize);
- System.arraycopy(dataToMerge, 0, newData, i * newVertSize, newVertSize);
+ System.arraycopy(data, i * oldVertSize, dataToMerge, 0, oldVertSize);
+ long[] tempBuffer = DataPointUtil.mergeMultiData(dataToMerge, oldVertSize, newVertSize);
+ System.arraycopy(tempBuffer, 0, newData, i * newVertSize, newVertSize);
}
return newData;
}
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
index 4bf6e94b5..14127a28d 100644
--- a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
@@ -21,6 +21,7 @@ package com.seibel.lod.core.objects.opengl;
import org.lwjgl.opengl.GL32;
+import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.enums.rendering.GLProxyContext;
import com.seibel.lod.core.render.GLProxy;
@@ -33,6 +34,7 @@ import com.seibel.lod.core.render.GLProxy;
*/
public class LodVertexBuffer implements AutoCloseable
{
+ public static int count = 0;
public int id;
public int vertexCount;
public final boolean isBufferStorage;
@@ -44,6 +46,8 @@ public class LodVertexBuffer implements AutoCloseable
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
this.id = GL32.glGenBuffers();
this.isBufferStorage = isBufferStorage;
+ count++;
+ //ClientApi.LOGGER.info("LodVertexBuffer Count: "+count);
}
@@ -52,8 +56,15 @@ public class LodVertexBuffer implements AutoCloseable
{
if (this.id >= 0)
{
- GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(this.id));
+ if (GLProxy.getInstance().getGlContext() == GLProxyContext.PROXY_WORKER) {
+ GL32.glDeleteBuffers(this.id);
+ } else {
+ final int id = this.id;
+ GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(id));
+ }
this.id = -1;
+ count--;
+ //ClientApi.LOGGER.info("LodVertexBuffer Count: "+count);
}
}
}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/render/GLProxy.java b/src/main/java/com/seibel/lod/core/render/GLProxy.java
index 1b9c34f05..99ceb1109 100644
--- a/src/main/java/com/seibel/lod/core/render/GLProxy.java
+++ b/src/main/java/com/seibel/lod/core/render/GLProxy.java
@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.PrintStream;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
@@ -39,6 +40,7 @@ import com.seibel.lod.core.api.ClientApi;
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.util.LodThreadFactory;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
@@ -65,7 +67,7 @@ public class GLProxy
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
- private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
+ private ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
@@ -423,6 +425,22 @@ public class GLProxy
}
}
+ public static void ensureAllGLJobCompleted() {
+ if (!hasInstance()) return;
+ ClientApi.LOGGER.info("Blocking until GL jobs finished!");
+ try {
+ instance.workerThread.shutdown();
+ boolean worked = instance.workerThread.awaitTermination(30, TimeUnit.SECONDS);
+ if (!worked)
+ ClientApi.LOGGER.error("GLWorkerThread shutdown timed out! Game may crash on exit due to cleanup failure!");
+ } catch (InterruptedException e) {
+ ClientApi.LOGGER.error("GLWorkerThread shutdown is interrupted! Game may crash on exit due to cleanup failure!");
+ e.printStackTrace();
+ } finally {
+ instance.workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
+ }
+ }
+
/**
* If called from a legacy OpenGL context this will
* set the fog end to infinity with a density of 0.
diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java
index 3d94d14a7..233737f92 100644
--- a/src/main/java/com/seibel/lod/core/render/LodRenderer.java
+++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java
@@ -49,6 +49,7 @@ import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
/**
* This is where all the magic happens.
@@ -59,21 +60,6 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
*/
public class LodRenderer
{
- public static class VanillaRenderedChunksList extends GridList {
- private static final long serialVersionUID = -5448501880911391315L;
-
- public final int centerX;
- public final int centerZ;
-
- public VanillaRenderedChunksList(int range, int centerX, int centerZ) {
- super(range);
- this.centerX = centerX;
- this.centerZ = centerZ;
- for (int i=0; i vanillaRenderedChunks;
public int vanillaRenderedChunksCenterX;
public int vanillaRenderedChunksCenterZ;
public int vanillaRenderedChunksRefreshTimer;
@@ -245,7 +231,7 @@ public class LodRenderer
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
// set the required open GL settings
- if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME)
+ if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME || CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_GENMODE_WIREFRAME)
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
else
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
@@ -304,14 +290,18 @@ public class LodRenderer
int lowRegionX = vbos.getCenterX() - vbos.gridCentreToEdge;
int lowRegionZ = vbos.getCenterY() - vbos.gridCentreToEdge;
int drawCall = 0;
- for (int regionX=lowRegionX; regionX 256) {
- vanillaRenderedChunks = new VanillaRenderedChunksList(
+ IWorldWrapper world = MC.getWrappedClientWorld();
+ if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight()) {
+ vanillaRenderedChunks = new MovableGridList(
chunkRenderDistance, chunkX, chunkZ);
return true;
}
- VanillaRenderedChunksList chunkList;
-
- if (recreateChunks) {
- vanillaRenderedChunks = new VanillaRenderedChunksList(chunkRenderDistance, chunkX, chunkZ);
- return true;
- } else {
- chunkList = vanillaRenderedChunks;
- chunkX = chunkList.centerX;
- chunkZ = chunkList.centerZ;
- chunkRenderDistance = (short) vanillaRenderedChunks.gridCentreToEdge;
- }
+ MovableGridList chunkList;
boolean anyChanged = false;
+ if (vanillaRenderedChunks == null || vanillaRenderedChunks.gridCentreToEdge != chunkRenderDistance ||
+ vanillaRenderedChunks.getCenterX()!=chunkX || vanillaRenderedChunks.getCenterY()!=chunkZ) {
+ chunkList = new MovableGridList(chunkRenderDistance, chunkX, chunkZ);
+ anyChanged = true;
+ } else {
+ chunkList = vanillaRenderedChunks;
+ }
+
LagSpikeCatcher getChunks = new LagSpikeCatcher();
- Set chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, previousPos);
+ Set chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, lastUpdatedPos);
getChunks.end("LodDrawSetup:UpdateStatus:UpdateVanillaChunks:getChunks");
for (AbstractChunkPosWrapper pos : chunkPosToSkip)
{
- int xIndex = (pos.getX() - chunkX) + (chunkRenderDistance + 1);
- int zIndex = (pos.getZ() - chunkZ) + (chunkRenderDistance + 1);
-
// sometimes we are given chunks that are outside the render distance,
// This prevents index out of bounds exceptions
- if (xIndex >= 0 && zIndex >= 0
- && xIndex < vanillaRenderedChunks.gridSize
- && zIndex < vanillaRenderedChunks.gridSize)
+ if (!chunkList.inRange(pos.getX(), pos.getZ())) continue;
+ Boolean oldBool = chunkList.swap(pos.getX(), pos.getZ(), true);
+ if (oldBool == null || !oldBool)
{
- if (!chunkList.get(chunkList.calculateOffset(xIndex, zIndex)))
- {
- chunkList.set(chunkList.calculateOffset(xIndex, zIndex), true);
- anyChanged = true;
- lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ());
- }
+ anyChanged = true;
+ lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ());
}
}
- vanillaRenderedChunks = chunkList;
+ if (anyChanged) vanillaRenderedChunks = chunkList;
return anyChanged;
}
@@ -552,7 +534,6 @@ public class LodRenderer
long newTime = System.currentTimeMillis();
AbstractBlockPosWrapper newPos = MC.getPlayerBlockPos();
boolean shouldUpdateChunks = false;
- boolean posUpdated = false;
boolean tryPartialGen = false;
boolean tryFullGen = false;
@@ -573,13 +554,11 @@ public class LodRenderer
// check if the player has moved
if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout) {
- if (previousPos == null
- || Math.abs(newPos.getX() - previousPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16
- || Math.abs(newPos.getZ() - previousPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16)
+ if (lastUpdatedPos == null
+ || Math.abs(newPos.getX() - lastUpdatedPos.getX()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16
+ || Math.abs(newPos.getZ() - lastUpdatedPos.getZ()) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance*16)
{
- tryPartialGen = true;
- previousPos = newPos;
- posUpdated = true;
+ shouldUpdateChunks = true;
}
prevPlayerPosTime = newTime;
}
@@ -598,14 +577,10 @@ public class LodRenderer
prevChunkTime = newTime;
}
-
- if (tryFullGen && !posUpdated) {
- previousPos = newPos;
- posUpdated = true;
- }
- shouldUpdateChunks |= posUpdated;
+ shouldUpdateChunks |= tryFullGen;
if (shouldUpdateChunks) {
- tryPartialGen |= updateVanillaRenderedChunks(lodDim, posUpdated);
+ lastUpdatedPos = newPos;
+ tryPartialGen |= updateVanillaRenderedChunks(lodDim);
}
if (tryFullGen) {
diff --git a/src/main/java/com/seibel/lod/core/util/BooleanMovableGridList.java b/src/main/java/com/seibel/lod/core/util/BooleanMovableGridList.java
new file mode 100644
index 000000000..2cdb5058f
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/BooleanMovableGridList.java
@@ -0,0 +1,248 @@
+package com.seibel.lod.core.util;
+
+/*Layout:
+ * 0,1,2,
+ * 3,4,5,
+ * 6,7,8
+ */
+
+public class BooleanMovableGridList {
+
+ private int centerX;
+ private int centerY;
+
+ public final int gridCentreToEdge;
+ public final int gridSize;
+ private boolean[] b;
+
+ public BooleanMovableGridList(int gridCentreToEdge, int centerX, int centerY) {
+ gridSize = gridCentreToEdge * 2 + 1;
+ this.gridCentreToEdge = gridCentreToEdge;
+ this.centerX = centerX;
+ this.centerY = centerY;
+ clear();
+ }
+
+ public void clear() {
+ b = new boolean[gridSize*gridSize];
+ }
+
+ public int getCenterX() {return centerX;}
+ public int getCenterY() {return centerY;}
+
+ private void assertIndex(int ix, int iy) {
+ if (ix<0 || ix>=gridSize || iy<0 || iy>=gridSize)
+ throw new IndexOutOfBoundsException("BooleanMovableGridList index position out of bound");
+ }
+
+ public boolean isInBound(int x, int y) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ return !(x<0 || x>=gridSize || y<0 || y>=gridSize);
+ }
+
+ // return onFail if x,y is outside of the grid
+ public boolean get(int x, int y) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ return _getDirect(x,y);
+ }
+
+ // return false if x,y is outside of the grid
+ public void set(int x, int y, boolean t) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ _setDirect(x,y,t);
+ }
+
+ // return onFail if x,y is outside of the grid
+ // Otherwise, return the new value (for chaining)
+ public boolean setAndGet(int x, int y, boolean t) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ _setDirect(x,y,t);
+ return t;
+ }
+ // return null if x,y is outside of the grid
+ // Otherwise, return the old value
+ public boolean swap(int x, int y, boolean t, boolean onFail) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ return _swapDirect(x,y, t);
+ }
+
+ private final boolean _getDirect(int x, int y) {
+ assertIndex(x,y);
+ return b[x + y * gridSize];
+ }
+ private final void _setDirect(int x, int y, boolean t) {
+ assertIndex(x,y);
+ b[x + y * gridSize] = t;
+ }
+ private final boolean _swapDirect(int x, int y, boolean t) {
+ assertIndex(x,y);
+ boolean r = b[x + y * gridSize];
+ b[x + y * gridSize] = t;
+ return r;
+ }
+
+ interface BoolTransformer {
+ boolean transform(boolean oldValue, int x, int y);
+ }
+
+ // Transform the list via the function. The data can still be accessed
+ // inside the function, and the returned value will not be applied
+ // until all elements have done the transform.
+ public void twoStageTransform(BoolTransformer transformer) {
+ boolean[] nb = new boolean[b.length];
+ int i=0;
+ for (int y=0; y {
+ if (v!=t) return v;
+ boolean r = false;
+ r |= (isInBound(x-1,y) ? get(x-1,y)==!t : false);
+ r |= (isInBound(x,y-1) ? get(x,y-1)==!t : false);
+ r |= (isInBound(x+1,y) ? get(x+1,y)==!t : false);
+ r |= (isInBound(x,y+1) ? get(x,y+1)==!t : false);
+ return r ? !t : t;
+ };
+ twoStageTransform(tran);
+ }
+ public void flipBorderCorner(boolean valueToBeFlipped) {
+ boolean t = valueToBeFlipped;
+ BoolTransformer tran = (v, x, y) -> {
+ if (v!=t) return v;
+ boolean r = false;
+ r |= (isInBound(x-1,y) ? get(x-1,y)==!t : false);
+ r |= (isInBound(x,y-1) ? get(x,y-1)==!t : false);
+ r |= (isInBound(x+1,y) ? get(x+1,y)==!t : false);
+ r |= (isInBound(x,y+1) ? get(x,y+1)==!t : false);
+ r |= (isInBound(x-1,y-1) ? get(x-1,y-1)==!t : false);
+ r |= (isInBound(x+1,y-1) ? get(x+1,y-1)==!t : false);
+ r |= (isInBound(x+1,y+1) ? get(x+1,y+1)==!t : false);
+ r |= (isInBound(x-1,y+1) ? get(x-1,y+1)==!t : false);
+ return r ? !t : t;
+ };
+ twoStageTransform(tran);
+ }
+ public void flipBorderCorner(boolean valueToBeFlipped, int range) {
+ boolean t = valueToBeFlipped;
+ BoolTransformer tran = (v, x, y) -> {
+ if (v!=t) return v;
+ boolean r = false;
+ for (int dx=-range;dx<=range;dx++)
+ for (int dy=-range;dy<=range;dy++)
+ r |= (isInBound(x+dx,y+dy) ? get(x+dx,y+dy)==!t : false);
+ return r ? !t : t;
+ };
+ twoStageTransform(tran);
+ }
+
+
+ // Return false if haven't changed. Return true if it did
+ public boolean move(int newCenterX, int newCenterY) {
+ return move(newCenterX, newCenterY, false);
+ }
+
+ // Return false if haven't changed. Return true if it did
+ public boolean move(int newCenterX, int newCenterY, boolean value) {
+ if (centerX == newCenterX && centerY == newCenterY) return false;
+ int deltaX = newCenterX - centerX;
+ int deltaY = newCenterY - centerY;
+
+ // if the x or z offset is equal to or greater than
+ // the total width, just delete the current data
+ // and update the centerX and/or centerZ
+ if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize)
+ {
+ clear();
+ // update the new center
+ centerX = newCenterX;
+ centerY = newCenterY;
+ return true;
+ }
+ centerX = newCenterX;
+ centerY = newCenterY;
+
+ // X
+ if (deltaX >= 0 && deltaY >= 0)
+ {
+ // move everything over to the left-up (as the center moves to the right-down)
+ for (int x = 0; x < gridSize; x++)
+ {
+ for (int y = 0; y < gridSize; y++)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else if (deltaX < 0 && deltaY >= 0)
+ {
+ // move everything over to the right-up (as the center moves to the left-down)
+ for (int x = gridSize - 1; x >= 0; x--)
+ {
+ for (int y = 0; y < gridSize; y++)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else if (deltaX >= 0 && deltaY < 0)
+ {
+ // move everything over to the left-down (as the center moves to the right-up)
+ for (int x = 0; x < gridSize; x++)
+ {
+ for (int y = gridSize - 1; y >= 0; y--)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else //if (deltaX < 0 && deltaY < 0)
+ {
+ // move everything over to the right-down (as the center moves to the left-up)
+ for (int x = gridSize - 1; x >= 0; x--)
+ {
+ for (int y = gridSize - 1; y >= 0; y--)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ return true;
+ }
+
+
+ @Override
+ public String toString() {
+ return "MovableGridList[" + centerX + "," + centerY + "] " + gridSize + "*" + gridSize + "[" + b.length + "]";
+ }
+
+ public String toDetailString() {
+ StringBuilder str = new StringBuilder("\n");
+ int i = 0;
+ str.append(toString());
+ str.append("\n");
+ for (boolean t : b) {
+ str.append(t ? "#" : ".");
+ i++;
+ if (i % gridSize == 0) {
+ str.append("\n");
+ } else {
+ //str.append(", ");
+ }
+ }
+ return str.toString();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java
index 507918280..d5644873c 100644
--- a/src/main/java/com/seibel/lod/core/util/LodUtil.java
+++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java
@@ -365,7 +365,7 @@ public class LodUtil
// get the chunks that are going to be rendered by Minecraft
- HashSet posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks();
+ HashSet posToSkip = MC_RENDER.getVanillaRenderedChunks();
// remove everything outside the skipRadius,
@@ -395,6 +395,7 @@ public class LodUtil
* @param z relative (to the matrix) z chunk to check
* @return true if and only if the chunk is a border of the renderable chunks
*/
+ @Deprecated
public static boolean isBorderChunk(boolean[][] vanillaRenderedChunks, int x, int z)
{
if (x < 0 || z < 0 || x >= vanillaRenderedChunks.length || z >= vanillaRenderedChunks[0].length)
@@ -411,6 +412,17 @@ public class LodUtil
}
return false;
}
+ public static boolean isBorderChunk(MovableGridList vanillaRenderedChunks, int chunkX, int chunkZ)
+ {
+ for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
+ {
+ int tempX = chunkX + lodDirection.getNormal().x;
+ int tempZ = chunkZ + lodDirection.getNormal().z;
+ Boolean b = vanillaRenderedChunks.get(tempX, tempZ);
+ if (b == null || !b) return true;
+ }
+ return false;
+ }
/** This is copied from Minecraft's MathHelper class */
diff --git a/src/main/java/com/seibel/lod/core/util/MovableGridList.java b/src/main/java/com/seibel/lod/core/util/MovableGridList.java
index 7a2d75968..79d6093f2 100644
--- a/src/main/java/com/seibel/lod/core/util/MovableGridList.java
+++ b/src/main/java/com/seibel/lod/core/util/MovableGridList.java
@@ -2,6 +2,7 @@ package com.seibel.lod.core.util;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
/*Layout:
* 0,1,2,
@@ -46,6 +47,14 @@ public class MovableGridList extends ArrayList implements List {
super.add(null);
}
}
+ public void clear(Consumer super T> d) {
+ super.forEach(d);
+ super.clear();
+ super.ensureCapacity(gridSize*gridSize);
+ for (int i=0; i extends ArrayList implements List {
return _getDirect(x,y);
}
+ // return false if x,y is outside of the grid
+ public boolean set(int x, int y, T t) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ return _setDirect(x,y, t);
+ }
+
// return null if x,y is outside of the grid
+ // Otherwise, return the new value (for chaining)
public T setAndGet(int x, int y, T t) {
x = x-centerX+gridCentreToEdge;
y = y-centerY+gridCentreToEdge;
return _setDirect(x,y, t) ? t : null;
}
+ // return null if x,y is outside of the grid
+ // Otherwise, return the old value
+ public T swap(int x, int y, T t) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ return _swapDirect(x,y, t);
+ }
+
+ public boolean inRange(int x, int y) {
+ x = x-centerX+gridCentreToEdge;
+ y = y-centerY+gridCentreToEdge;
+ return (x>=0 && x=0 && y=gridSize || y<0 || y>=gridSize) return null;
@@ -73,9 +103,14 @@ public class MovableGridList extends ArrayList implements List {
super.set(x + y * gridSize, t);
return true;
}
+ private final T _swapDirect(int x, int y, T t) {
+ if (x<0 || x>=gridSize || y<0 || y>=gridSize) return null;
+ return super.set(x + y * gridSize, t);
+ }
- public void move(int newCenterX, int newCenterY) {
- if (centerX == newCenterX && centerY == newCenterY) return;
+ // Return false if haven't changed. Return true if it did
+ public boolean move(int newCenterX, int newCenterY) {
+ if (centerX == newCenterX && centerY == newCenterY) return false;
int deltaX = newCenterX - centerX;
int deltaY = newCenterY - centerY;
@@ -88,8 +123,87 @@ public class MovableGridList extends ArrayList implements List {
// update the new center
centerX = newCenterX;
centerY = newCenterY;
+ return true;
+ }
+ centerX = newCenterX;
+ centerY = newCenterY;
+
+ // X
+ if (deltaX >= 0 && deltaY >= 0)
+ {
+ // move everything over to the left-up (as the center moves to the right-down)
+ for (int x = 0; x < gridSize; x++)
+ {
+ for (int y = 0; y < gridSize; y++)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else if (deltaX < 0 && deltaY >= 0)
+ {
+ // move everything over to the right-up (as the center moves to the left-down)
+ for (int x = gridSize - 1; x >= 0; x--)
+ {
+ for (int y = 0; y < gridSize; y++)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else if (deltaX >= 0 && deltaY < 0)
+ {
+ // move everything over to the left-down (as the center moves to the right-up)
+ for (int x = 0; x < gridSize; x++)
+ {
+ for (int y = gridSize - 1; y >= 0; y--)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ else //if (deltaX < 0 && deltaY < 0)
+ {
+ // move everything over to the right-down (as the center moves to the left-up)
+ for (int x = gridSize - 1; x >= 0; x--)
+ {
+ for (int y = gridSize - 1; y >= 0; y--)
+ {
+ _setDirect(x, y, _getDirect(x+deltaX, y+deltaY));
+ }
+ }
+ }
+ return true;
+ }
+
+ public void move(int newCenterX, int newCenterY, Consumer super T> d) {
+ if (centerX == newCenterX && centerY == newCenterY) return;
+ int deltaX = newCenterX - centerX;
+ int deltaY = newCenterY - centerY;
+
+ // if the x or z offset is equal to or greater than
+ // the total width, just delete the current data
+ // and update the centerX and/or centerZ
+ if (Math.abs(deltaX) >= gridSize || Math.abs(deltaY) >= gridSize)
+ {
+ clear(d);
+ // update the new center
+ centerX = newCenterX;
+ centerY = newCenterY;
return;
}
+ centerX = newCenterX;
+ centerY = newCenterY;
+
+ // Dealloc stuff
+ for (int x=0; x=gridSize || y-deltaY>=gridSize) {
+ d.accept(_getDirect(x,y));
+ }
+ }
+ }
// X
if (deltaX >= 0 && deltaY >= 0)
@@ -136,10 +250,8 @@ public class MovableGridList extends ArrayList implements List {
}
}
}
- centerX = newCenterX;
- centerY = newCenterY;
}
-
+
// TODO: This is unused but may be useful later on.
/*
@@ -154,6 +266,8 @@ public class MovableGridList extends ArrayList implements List {
public String toDetailString() {
StringBuilder str = new StringBuilder("\n");
int i = 0;
+ str.append(toString());
+ str.append("\n");
for (T t : this) {
str.append(t!=null ? t.toString() : "NULL");
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java
index 89f73401a..d37b3bcdf 100644
--- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java
@@ -55,4 +55,8 @@ public interface IChunkWrapper
boolean isWaterLogged(int x, int y, int z);
int getEmittedBrightness(int x, int y, int z);
+
+ default int getBlockLight(int x, int y, int z) {return -1;}
+
+ default int getSkyLight(int x, int y, int z) {return -1;}
}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
index 5f4f335c1..00da18d5c 100644
--- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
@@ -22,6 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces.config;
import com.seibel.lod.core.enums.config.BlocksToAvoid;
import com.seibel.lod.core.enums.config.BufferRebuildTimes;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.DropoffQuality;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.enums.config.GpuUploadMethod;
import com.seibel.lod.core.enums.config.HorizontalQuality;
@@ -54,6 +55,13 @@ public interface ILodConfigWrapperSingleton
IGraphics graphics();
IWorldGenerator worldGenerator();
IAdvanced advanced();
+
+
+ boolean OPTIONS_BUTTON_DEFAULT = true;
+ String OPTIONS_BUTTON_DESC = ""
+ + " Show the lod button in the options screen next to fov";
+ boolean getOptionsButton();
+ void setOptionsButton(boolean newOptionsButton);
//==================//
@@ -133,6 +141,21 @@ public interface ILodConfigWrapperSingleton
+ " Highest Quality: " + HorizontalQuality.HIGH;
HorizontalQuality getHorizontalQuality();
void setHorizontalQuality(HorizontalQuality newHorizontalQuality);
+
+ DropoffQuality DROPOFF_QUALITY_DEFAULT = DropoffQuality.AUTO;
+ String DROPOFF_QUALITY_DESC = ""
+ + " This determines how lod level drop off will be done. \n"
+ + "\n"
+ + " " + DropoffQuality.SMOOTH_DROPOFF + ": \n"
+ + " The lod level is calculated for each point, making the drop off a smooth circle. \n"
+ + " " + DropoffQuality.PERFORMANCE_FOCUSED + ": \n"
+ + " One detail level for an entire region. Minimize CPU usage and \n"
+ + " improve terrain refresh delay, especially for high Lod render distance. \n"
+ + " " + DropoffQuality.AUTO + ": \n"
+ + " Use "+ DropoffQuality.SMOOTH_DROPOFF + " for less then 128 Lod render distance, \n"
+ + " or "+ DropoffQuality.PERFORMANCE_FOCUSED +" otherwise. \n";
+ DropoffQuality getDropoffQuality();
+ void setDropoffQuality(DropoffQuality newDropoffQuality);
}
interface IFogQuality
@@ -274,7 +297,7 @@ public interface ILodConfigWrapperSingleton
+ " Higher settings will make terrain look good when looking backwards \n"
+ " when changing speeds quickly, but will increase upload times and GPU usage.";
int getBacksideCullingRange();
- void setBacksideCullingRange(int backsideCullingRange);
+ void setBacksideCullingRange(int newBacksideCullingRange);
boolean USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT = false;
String USE_EXTENDED_NEAR_CLIP_PLANE_DESC = ""
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
index 886e9603a..6ef47b760 100644
--- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
@@ -22,6 +22,7 @@ package com.seibel.lod.core.wrapperInterfaces.minecraft;
import java.awt.Color;
import java.util.HashSet;
+import com.seibel.lod.core.api.ModAccessorApi;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
@@ -29,6 +30,7 @@ import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.modAccessor.ISodiumAccessor;
/**
* Contains everything related to
@@ -77,18 +79,8 @@ public interface IMinecraftRenderWrapper
*/
public default HashSet getVanillaRenderedChunks()
{
- return getMaximumRenderedChunks();
- }
-
- /**
- * This method returns the ChunkPos of every chunk that
- * Sodium is going to render this frame.
- *
- * If not implemented this calls {@link #getMaximumRenderedChunks()}.
- */
- public default HashSet getSodiumRenderedChunks()
- {
- return getMaximumRenderedChunks();
+ ISodiumAccessor sodium = ModAccessorApi.get(ISodiumAccessor.class);
+ return sodium==null ? getMaximumRenderedChunks() : sodium.getNormalRenderedChunks();
}
/**
@@ -101,9 +93,6 @@ public interface IMinecraftRenderWrapper
IWrapperFactory factory = SingletonHandler.get(IWrapperFactory.class);
int chunkRenderDist = this.getRenderDistance();
- // if we have a odd render distance, we'll have a empty gap. This way we'll overlap by 1 instead,
- // which is preferable to having a hole in the world
- chunkRenderDist = chunkRenderDist % 2 == 0 ? chunkRenderDist : chunkRenderDist - 1;
AbstractChunkPosWrapper centerChunkPos = mcWrapper.getPlayerChunkPos();
int startChunkX = centerChunkPos.getX() - chunkRenderDist;
@@ -111,9 +100,9 @@ public interface IMinecraftRenderWrapper
// add every position within render distance
HashSet renderedPos = new HashSet();
- for (int chunkX = 0; chunkX < (chunkRenderDist * 2); chunkX++)
+ for (int chunkX = 0; chunkX < (chunkRenderDist * 2+1); chunkX++)
{
- for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2); chunkZ++)
+ for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2+1); chunkZ++)
{
renderedPos.add(factory.createChunkPos(startChunkX + chunkX, startChunkZ + chunkZ));
}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/IModAccessor.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/IModAccessor.java
new file mode 100644
index 000000000..dbf0b878e
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/IModAccessor.java
@@ -0,0 +1,5 @@
+package com.seibel.lod.core.wrapperInterfaces.modAccessor;
+
+public abstract interface IModAccessor {
+ String getModName();
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java
new file mode 100644
index 000000000..d2335c8e2
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/modAccessor/ISodiumAccessor.java
@@ -0,0 +1,9 @@
+package com.seibel.lod.core.wrapperInterfaces.modAccessor;
+
+import java.util.HashSet;
+
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+
+public interface ISodiumAccessor extends IModAccessor {
+ HashSet getNormalRenderedChunks();
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java
index ba6214e62..6726d1a56 100644
--- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java
@@ -23,6 +23,8 @@ import java.io.File;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
/**
* Can be either a Server world or a Client world.
@@ -60,5 +62,7 @@ public interface IWorldWrapper
/** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
File getSaveFolder() throws UnsupportedOperationException;
+ default IChunkWrapper tryGetChunk(AbstractChunkPosWrapper pos) {return null;}
+
}
diff --git a/src/main/resources/assets/lod/lang/en_us.json b/src/main/resources/assets/lod/lang/en_us.json
index 51724f6d2..d80c49345 100644
--- a/src/main/resources/assets/lod/lang/en_us.json
+++ b/src/main/resources/assets/lod/lang/en_us.json
@@ -1,8 +1,8 @@
{
"lod.title": "Distant Horizons",
"DistantHorizons.config.title": "Distant Horizons config",
- "DistantHorizons.config.ShowButton": "Show menu button",
- "DistantHorizons.config.ShowButton.@tooltip": "Show the custom button to the left of the fov button",
+ "DistantHorizons.config.optionsButton": "Show options button",
+ "DistantHorizons.config.optionsButton.@tooltip": "Show the custom button to the left of the fov button",
"DistantHorizons.config.client": "Client",
"DistantHorizons.config.client.graphics": "Graphics",
"DistantHorizons.config.client.graphics.quality": "Quality options",
@@ -16,6 +16,8 @@
"DistantHorizons.config.client.graphics.quality.horizontalScale.@tooltip": "This indicates how quickly fake chunks drop off in quality",
"DistantHorizons.config.client.graphics.quality.horizontalQuality": "Horizontal quality",
"DistantHorizons.config.client.graphics.quality.horizontalQuality.@tooltip": "This indicates the exponential base of the quadratic drop-off",
+ "DistantHorizons.config.client.graphics.quality.dropoffQuality": "Dropoff quality",
+ "DistantHorizons.config.client.graphics.quality.dropoffQuality.@tooltip": "This change how detail dropoff is done.",
"DistantHorizons.config.client.graphics.fogQuality": "Fog options",
"DistantHorizons.config.client.graphics.fogQuality.fogDistance": "Fog distance",
"DistantHorizons.config.client.graphics.fogQuality.fogDistance.@tooltip": "At what distance should Fog be drawn on the fake chunks?",
@@ -26,6 +28,7 @@
"DistantHorizons.config.client.graphics.fogQuality.disableVanillaFog": "Disable vanilla fog",
"DistantHorizons.config.client.graphics.fogQuality.disableVanillaFog.@tooltip": "If true disable Minecraft's fog. \nMay cause issues with other mods that edit fog. \nMay cause errors with other fog editing mods",
"DistantHorizons.config.client.graphics.cloudQuality": "Cloud options",
+ "DistantHorizons.config.client.graphics.cloudQuality.cloudWarning": "§l§nWARNING:§r§n Clouds are still experimental",
"DistantHorizons.config.client.graphics.cloudQuality.customClouds": "Custom cloud",
"DistantHorizons.config.client.graphics.cloudQuality.customClouds.@tooltip": "Do you want to use a custom way or rendering the clouds. \nIf you turn this off then the other settings here wont do anything \nThis will also make the clouds more dynamic",
"DistantHorizons.config.client.graphics.cloudQuality.fabulousClouds": "Fabulous clouds",
@@ -120,6 +123,8 @@
"DistantHorizons.config.enum.DebugMode.OFF": "Off",
"DistantHorizons.config.enum.DebugMode.SHOW_DETAIL": "Show detail",
"DistantHorizons.config.enum.DebugMode.SHOW_DETAIL_WIREFRAME": "Show detail with wireframe",
+ "DistantHorizons.config.enum.DebugMode.SHOW_GENMODE": "Show generation mode",
+ "DistantHorizons.config.enum.DebugMode.SHOW_GENMODE_WIREFRAME": "Show generation mode with wireframe",
"DistantHorizons.config.enum.GpuUploadMethod.AUTO": "Auto",
"DistantHorizons.config.enum.GpuUploadMethod.BUFFER_STORAGE": "Buffer storage",
"DistantHorizons.config.enum.GpuUploadMethod.SUB_DATA": "Sub data",
@@ -127,5 +132,8 @@
"DistantHorizons.config.enum.GpuUploadMethod.DATA": "Data",
"DistantHorizons.config.enum.BufferRebuildTimes.FREQUENT": "Frequent",
"DistantHorizons.config.enum.BufferRebuildTimes.NORMAL": "Normal",
- "DistantHorizons.config.enum.BufferRebuildTimes.RARE": "Rare"
+ "DistantHorizons.config.enum.BufferRebuildTimes.RARE": "Rare",
+ "DistantHorizons.config.enum.DropoffQuality.AUTO": "Auto",
+ "DistantHorizons.config.enum.DropoffQuality.SMOOTH_DROPOFF": "Smooth dropoff",
+ "DistantHorizons.config.enum.DropoffQuality.PERFORMANCE_FOCUSED": "Performance focused"
}
diff --git a/src/main/resources/assets/lod/textures/environment/clouds_small.png b/src/main/resources/assets/lod/textures/environment/clouds_small.png
new file mode 100644
index 000000000..973b20e6e
Binary files /dev/null and b/src/main/resources/assets/lod/textures/environment/clouds_small.png differ