This commit is contained in:
James Seibel
2022-01-10 18:08:06 -06:00
33 changed files with 1427 additions and 946 deletions
+1
View File
@@ -0,0 +1 @@
<mxfile host="app.diagrams.net" modified="2022-01-04T16:15:44.618Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62" etag="-Vdo5wDmcYQvA_9MRiBE" version="16.0.0" type="google"><diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">7V1db5s6GP41kbaLTHxDLpt063aWHfWs2zrtzgtOwhnBOcZpm/36Y2M7CRg60mGoNEuVGt44Bvw876eNGbmzzcMVBtv1BxTDdORY8cPIvRw5jmPZDv3HJHsuCQIhWOEk5iL7KLhJfkIhtIR0l8QwLzUkCKUk2ZaFC5RlcEFKMoAxui83W6K0fNYtWEFFcLMAqSq9TWKy5tLICY/ytzBZreWZ7WDCv9kA2VjcSb4GMbo/EbmvR+4MI0T4p83DDKZs8OS43L7b36bzH8HVX//k/4HP0/ef/v4y5p29Oecnh1vAMCNP7prcfA4vxsv3wY+3YTb+mb27Dq/GchhyspcDBmM6fuIQYbJGK5SB9PVROsVol8WQdWvRo2ObOUJbKrSp8F9IyF6QAewIoqI12aTi25xg9APOUIowlcRwCXYpvbEpzOILBjkVZiiDXPImSVNxKn6t7AIr2P5iYES7HO3wAj4yGq4vCArwCpLHGk4O+FPFgWgDCd7TH2KYApLclS8PCAavDu0OP71GCb1wxxLa5kqqCV1z/KjcBb8D8atTrNWOXlmu51Ga+1HgWGGp28NpZLf8fpVu6YeTuzuKCmKdQTJpCvol2eBkmrQkk+f2QiYvsp5MplJHftAzffw/kT6cFS3oE9i90Cd0PB22KLLakYliAfYnzbasQa6DbtIJ3IF0JwZvjuJbhGngUeVhfp9sUlCwY4kyIinJ+APSZJXRzwtKB0jd3PQOYpLQuORCfEEYEaeLdZLGc7BHO4ZxTsDihzyarhFOftJuwdFvAkwEY52g1OKG/VJQE8OctrmWTLQrog/godRwDnIiBAuUpmCbJ98Pt7GhcCTZFBGCNiXms9uBD0/gvspUGWt6Fe8nqXF/jNtsaXvWJzFbFDSTu8SOs6kQKFQYOVMWhSaQhaEXTD/3W6jwgo4MUeIcYUaW1IZURJIrKVySRqbkW7BIstW8aHPpHSUfxUgwEaK/XaaF1VoncQyzwhQSQACHlOEnVIdeqD+lf3RAZ9Yrf+TTC5/RY/t4TP9Yc0xmKKP3ApICXUj5cg8ZZ9pR4REtU7kgsQ/aQe9ogz6sgb6CcpoU6HGUZXJhPwniDQWr8FsC00+FlxrbCu6uirtbg3EKvsP0GuUJSRDrH/O2FewHg9d3Wmq2LnSjBsWm3a9R/IIp9Uuj3/oIELQ07fr0e1Ln5S+TDcxypjLG0/fo6d22dLBlNapzPniqRRjzqtQyWeXcFMzhHUzzWSEyJuF8k9BQNHkuLt92nRoOzJzRhZUy5GdM/ajLxydsOMoMHzrmQ9sYQRsfPNuEgPrgDaNhQ0BPVXYTAvZJgGhoex+5CqjPoK7YNDcyei71Rr+TcnVzPa++iuhblXKkVZnt+kX7wPcqhOFX0GnpUI7haVLxeJRg0ooO04qD+ThEEGpacShrl0yMdAbdexmvNq0o4skbuGCuWSQXH9AdM+BXOInnCR1PJyjUfn7SkspWhSngjtt4pHM9UtNMW3MGUk8XbRGnOvVgIs7O4K1JKGrh1RZxNs0m7LYxIHBOg84YUj+RFjpPFWKL8q/83zcRid4hegaj9x0To6b61K/eR6qX4MygkZjhxCCcqMlO+uWEbzVyIvuEoUlIO4f8UFweDvOmmsRiRwzmWjB31TJUv5jbVlNYkEPyEWYxxHTs36Rglb8wFl8nFQJvaCrYdYsOBBWuYAYxYJG14YJ+LkyGDgn9usmHggrgDl4mmOwPRQEDfsdlAHvw2K+mjohYkRi+BRlN2fGIndq1j8U0U1LUU1L0K6tgA9tuxw1taxL9unDB1Ija6n7T7MWZJUBdNSJfXZbEzX6KQGwsvibU3aEjv6Ap2Wfe3sCuCfbB636BGuSV53uMa+9vtjDyWtLBtxxdfFDrP2y28GTYCzBY3icfjzEG4RyD0PSs1HOZAAxVgzAWt8qgMfBrhb/tBKE+f6DmfeOi+lO4g2s29WPQ14V+zYLEntFXY/8y+t8M+trQt62hc4Cw3vkL+C9PZoELFnzfEzMf1D0N3KFzglBNBRkNtqJkckmHlBMgp2EhMUvBNPHAHzwUVNeCnYaCRx6wQTEs0MOCaOiIMKxfP7pgyBkK9JESWkOHhWF9UpCyUxsK9EGB4cvDdesDzZxPZ1WfljGfrkmfqGmuny8MrqwA5cHg18Onb/wTVTSIT6vHL40l6J4pQzuDoO4JAbFS+PokRXhh0O8e/cngfqBpqSBF/8tJYmDQ7x59t/XDZNrQb1odSNGfyYTAQK8Bem/wYkDTfkUU+rlMBAz0GqCXVdnhng1qXBpiPL529CdDx3th03qw3Hh83eh7ztDxXvjI8wDG4+uE3h882Gvy+Lnx+HqhD4cO9iaBguofvC+NXEjf05785+5L41U3LRY5etO+NNX2bmhVCKNhX5pIrRs8upehWWd6PpVbP0JyeDPKrwyMvs0uIzWuYFNKG/AgY8rySkMxsdT8zJHxOr/yOlwFn+/K00n9cgO+8WWCFyn8COIE7XLDiv5YMfiC1Kgu/TQTjx3B23bFqa55x0lTesnnHc1Gx3pgH3xrGds2GcapGkQtMwxNL2ryJuUA0ZfHT34lTv15qlti+uIdF2230JTvfdKaqkzUusccxdMdTSfM9pn9Jiqtt890K2+G6nBvHLMF9+84Iq5Mv5106Io/bEsNQKiy802PjLbr1faxUzHvrRc46FN3WRkx6v4kdRf69Hz1XTLHxJ18OKy+As/fhE1Vy+luuYTYhGVDGOogGtxQuyYu+y1DzTVqAENND48vLOc52/G17+7r/wE=</diagram></mxfile>
@@ -43,6 +43,9 @@ public class ApiShared
/** Used to determine if the LODs should be regenerated */
public static int previousLodRenderDistance = 0;
/** Signal whether a world is shutting down */
public static volatile boolean isShuttingDown = false;
private ApiShared()
@@ -19,20 +19,28 @@
package com.seibel.lod.core.api;
import java.util.HashSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DetailDistanceUtil;
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.chunk.IChunkWrapper;
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 holds the methods that should be called
@@ -69,14 +77,27 @@ public class ClientApi
}
private HashSet<AbstractChunkPosWrapper> toBeLoaded = null;
// TODO: Fix it
@Deprecated
public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world)
{
//EventApi.INSTANCE.chunkLoadEvent(chunk, world.getDimensionType());
//ClientApi.LOGGER.info("ChunkLoadEvent called for "+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"), new RuntimeException());
if (toBeLoaded == null) toBeLoaded = new HashSet<AbstractChunkPosWrapper>();
//toBeLoaded.add(WRAPPER_FACTORY.createChunkPos(chunk.getChunkPosX(), chunk.getChunkPosZ()));
}
private HashSet<AbstractChunkPosWrapper> lastFrame = new HashSet<AbstractChunkPosWrapper>();
public void renderLods(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks)
{
// comment out when creating a release
applyConfigOverrides();
// clear any out of date objects
MC.clearFrameObjectCache();
@@ -85,8 +106,19 @@ public class ClientApi
// only run the first time setup once
if (!firstTimeSetupComplete)
firstFrameSetup();
IWorldWrapper world = MC.getWrappedClientWorld();
HashSet<AbstractChunkPosWrapper> chunks = MC_RENDER.getVanillaRenderedChunks();
if (chunks != null) {
for (AbstractChunkPosWrapper pos : chunks) {
if (lastFrame.contains(pos)) continue;
IChunkWrapper chunk = world.tryGetChunk(pos);
if (chunk == null) continue;
ApiShared.lodBuilder.generateLodNodeDirect(chunk, ApiShared.lodWorld,
world.getDimensionType(), DistanceGenerationMode.FULL, true);
}
}
lastFrame = chunks;
if (!MC.playerExists() || ApiShared.lodWorld.getIsWorldNotLoaded())
return;
@@ -98,6 +130,8 @@ public class ClientApi
EVENT_API.viewDistanceChangedEvent();
EVENT_API.playerMoveEvent(lodDim);
lodDim.cutRegionNodesAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ());
@@ -23,9 +23,10 @@ import org.lwjgl.glfw.GLFW;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.WorldType;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderer;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
@@ -64,8 +65,12 @@ public class EventApi
{
}
@Deprecated
public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
//ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL, true);
}
//=============//
@@ -80,8 +85,8 @@ public class EventApi
LodDimension lodDim = ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension());
if (lodDim == null)
return;
if (ApiShared.isShuttingDown) return;
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
}
@@ -92,19 +97,22 @@ public class EventApi
// world events //
//==============//
public void chunkLoadEvent(IChunkWrapper chunk, IDimensionTypeWrapper dimType)
{
ApiShared.lodBuilder.generateLodNodeAsync(chunk, ApiShared.lodWorld, dimType, DistanceGenerationMode.FULL);
}
public void worldSaveEvent()
{
ApiShared.lodWorld.saveAllDimensions();
ApiShared.lodWorld.saveAllDimensions(false); // Do an async save.
}
private boolean isCurrentlyOnSinglePlayerServer = false;
/** This is also called when a new dimension loads */
public void worldLoadEvent(IWorldWrapper world)
{
ClientApi.LOGGER.info("WorldLoadEvent called here for "+ (world.getWorldType() == WorldType.ClientWorld ?
"clientLevel" : "serverLevel"), new RuntimeException());
// Always ignore ServerWorld event
if (world.getWorldType() == WorldType.ServerWorld) return;
isCurrentlyOnSinglePlayerServer = MC.hasSinglePlayerServer();
ApiShared.isShuttingDown = false;
DataPointUtil.WORLD_HEIGHT = world.getHeight();
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
@@ -120,17 +128,22 @@ public class EventApi
}
/** This is also called when the user disconnects from a server+ */
public void worldUnloadEvent()
public void worldUnloadEvent(IWorldWrapper world)
{
ClientApi.LOGGER.info("WorldUnloadEvent called here for "+ (world.getWorldType() == WorldType.ClientWorld ? "clientLevel" : "serverLevel"), new RuntimeException());
// If it's single player, ignore the client side world unload event
// Note: using this as often API call unload event AFTER setting MC to not be in a singlePlayerServer
if (isCurrentlyOnSinglePlayerServer && world.getWorldType() == WorldType.ClientWorld) return;
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
// ClientApi.renderer.markForCleanup();
// ClientApi.renderer.destroyBuffers();
new Thread(() -> checkIfDisconnectedFromServer()).start();
checkIfDisconnectedFromServer();
//new Thread(() -> checkIfDisconnectedFromServer()).start();
}
private void checkIfDisconnectedFromServer()
{
/*
try
{
// world unloading events are called before disconnecting from the server,
@@ -141,10 +154,10 @@ public class EventApi
{
// this should never happen, but just in case
e.printStackTrace();
}
}*/
if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer()))
//if (MC.getWrappedClientWorld() == null || (!MC.connectedToServer() && !MC.hasSinglePlayerServer()))
{
// the player just left the server
@@ -152,16 +165,17 @@ public class EventApi
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
ApiShared.isShuttingDown = true;
LodWorldGenerator.INSTANCE.restartExecutorService();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
ApiShared.lodWorld.deselectWorld();
ApiShared.lodWorld.deselectWorld(); // This force a save
// prevent issues related to the buffer builder
// breaking or retaining previous data when changing worlds.
ClientApi.renderer.destroyBuffers();
ClientApi.renderer.requestCleanup();
GLProxy.ensureAllGLJobCompleted();
recalculateWidths = true;
// TODO: Check if after the refactoring, is this still needed
ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
@@ -205,6 +219,7 @@ public class EventApi
}
}
// NOTE: This is being called from Render Thread.
/** Re-centers the given LodDimension if it needs to be. */
public void playerMoveEvent(LodDimension lodDim)
{
@@ -213,7 +228,6 @@ public class EventApi
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
ApiShared.lodWorld.saveAllDimensions();
lodDim.move(worldRegionOffset);
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
@@ -236,12 +250,10 @@ public class EventApi
// do the dimensions need to change in size?
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
ApiShared.lodWorld.saveAllDimensions();
// update the dimensions to fit the new width
ApiShared.lodWorld.resizeDimensionRegionWidth(newWidth);
ApiShared.lodBuilder.defaultDimensionWidthInRegions = newWidth;
ClientApi.renderer.setupBuffers(ApiShared.lodWorld.getLodDimension(MC.getCurrentDimension()));
ClientApi.renderer.setupBuffers();
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
@@ -0,0 +1,53 @@
package com.seibel.lod.core.api;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.core.wrapperInterfaces.modAccessor.IModAccessor;
/**
* This class takes care of dependency injection
* for mods (for mod compatibility support).
*
* (Basically the same as SingletonHandler, except
* it can return null which means that mod aren't
* loaded in the game, or it haven't been implemented
* for that build.)
*/
public class ModAccessorApi {
private static final Map<Class<? extends IModAccessor>, IModAccessor> singletons = new HashMap<Class<? extends IModAccessor>, IModAccessor>();
public static void bind(Class<? extends IModAccessor> interfaceClass, IModAccessor modAccessor) throws IllegalStateException
{
// make sure we haven't already bound this singleton
if (singletons.containsKey(interfaceClass))
{
throw new IllegalStateException("The modAccessor [" + interfaceClass.getSimpleName() + "] has already been bound.");
}
// make sure the given singleton implements the interface
boolean singletonImplementsInterface = false;
for (Class<?> singletonInterface : modAccessor.getClass().getInterfaces())
{
if (singletonInterface.equals(interfaceClass))
{
singletonImplementsInterface = true;
break;
}
}
if (!singletonImplementsInterface)
{
throw new IllegalStateException("The singleton [" + interfaceClass.getSimpleName() + "] doesn't implement the interface [" + interfaceClass.getSimpleName() + "].");
}
ClientApi.LOGGER.info("DistantHorizon: Registored mod comatibility accessor for "+modAccessor.getModName());
singletons.put(interfaceClass, modAccessor);
}
@SuppressWarnings("unchecked")
public static <T extends IModAccessor> T get(Class<T> objectClass) throws ClassCastException
{
IModAccessor modAccessor = singletons.get(objectClass);
return modAccessor==null ? null : (T) modAccessor;
}
}
@@ -54,7 +54,12 @@ public class CubicLodTemplate
int color;
if (debugging != DebugMode.OFF)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
{
if (debugging == DebugMode.SHOW_DETAIL || debugging == DebugMode.SHOW_DETAIL_WIREFRAME)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
else ///if (debugging == DebugMode.SHOW_GENMODE || debugging == DebugMode.SHOW_GENMODE_WIREFRAME)
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[DataPointUtil.getGenerationMode(data)].getRGB();
}
else
color = DataPointUtil.getColor(data);
@@ -78,7 +78,7 @@ public class LodBufferBuilderFactory
public void end(String source) {
timer = System.nanoTime() - timer;
if (timer> 16000000) { //16 ms
ClientApi.LOGGER.info("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
ClientApi.LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
}
}
@@ -280,7 +280,9 @@ public class LodBufferBuilderFactory
vboY = buildableCenterBlockY;
vboZ = buildableCenterBlockZ;
buildableBuffers.move(playerRegionX, playerRegionZ);
buildableVbos.move(playerRegionX, playerRegionZ);
buildableVbos.move(playerRegionX, playerRegionZ, (bs) -> {
if (bs!=null) for (LodVertexBuffer b : bs) if (b!=null) b.close();
});
setsToRender.move(playerRegionX, playerRegionZ);
vertexOptimizerCache.move(playerRegionX, playerRegionZ);
}
@@ -370,7 +372,7 @@ public class LodBufferBuilderFactory
long buildTime = endTime - startTime;
long executeTime = executeEnd - executeStart;
if (enableLogging)
ClientApi.LOGGER.info("Thread Build("+nodeToRenderThreads.size()+"/"+(lodDim.getWidth()*lodDim.getWidth())+ (fullRegen ? "FULL" : "")+") time: " + buildTime + " ms" + '\n' +
ClientApi.LOGGER.debug("Thread Build("+nodeToRenderThreads.size()+"/"+(lodDim.getWidth()*lodDim.getWidth())+ (fullRegen ? "FULL" : "")+") time: " + buildTime + " ms" + '\n' +
"thread execute time: " + executeTime + " ms");
//================================//
@@ -398,7 +400,7 @@ public class LodBufferBuilderFactory
uploadBuffers(posToUpload);
long uploadTime = System.currentTimeMillis() - startUploadTime;
if (enableLogging)
ClientApi.LOGGER.info("Thread Upload time: " + uploadTime + " ms");
ClientApi.LOGGER.debug("Thread Upload time: " + uploadTime + " ms");
}
catch (Exception e)
{
@@ -459,21 +461,22 @@ public class LodBufferBuilderFactory
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ;
// Currently fixing below
// FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide!
//We don't want to render this fake block if
//The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
//
//The block is in the player chunk or in a chunk adjacent to the player
//if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance))
//{
// continue;
//}
// TODO: In the future, We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide!
// We don't want to render this fake block if
// The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
// The block is in the player chunk or in a chunk adjacent to the player
if(detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL &&
isThisPositionGoingToBeRendered(LevelPosUtil.getChunkPos(detailLevel, posX),
LevelPosUtil.getChunkPos(detailLevel, posZ)))
{
continue;
}
//we check if the block to render is not in player chunk
boolean posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0);
// We extract the adj data in the four cardinal direction
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass
// to avoid having a "darker border" underground
@@ -556,31 +559,20 @@ public class LodBufferBuilderFactory
return true;
}
// Will be removed in a1.7
@Deprecated
private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, int chunkPosX, int chunkPosZ, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
private boolean isThisPositionGoingToBeRendered(int chunkX, int chunkZ){
MovableGridList<Boolean> chunkGrid = ClientApi.renderer.vanillaRenderedChunks;
Boolean isRendered = chunkGrid.get(chunkX, chunkZ);
// skip any chunks that Minecraft is going to render
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - chunkPosX;
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - chunkPosZ;
if (isRendered == null || !isRendered) return false;
// check if the chunk is on the border
boolean isItBorderPos;
if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER)
isItBorderPos = LodUtil.isBorderChunk(vanillaRenderedChunks, chunkXdist + gameChunkRenderDistance + 1, chunkZdist + gameChunkRenderDistance + 1);
return !LodUtil.isBorderChunk(ClientApi.renderer.vanillaRenderedChunks, chunkX, chunkZ);
else
isItBorderPos = false;
//boolean smallRenderDistance = gameChunkRenderDistance <= LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW;
// get the positions that will be rendered
return (gameChunkRenderDistance >= Math.abs(chunkXdist)
&& gameChunkRenderDistance >= Math.abs(chunkZdist)
&& detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
&& vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
&& (!isItBorderPos);
return true;
}
@@ -838,7 +830,7 @@ public class LodBufferBuilderFactory
private boolean swapBuffers() {
bufferLock.lock();
ClientApi.LOGGER.info("Lod Swap Buffers");
ClientApi.LOGGER.debug("Lod Swap Buffers");
{
boolean shouldRegenBuff = true;
try
@@ -90,10 +90,10 @@ public class LodBuilder
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim)
{
generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL);
generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL, false);
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode)
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode, boolean override)
{
if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
return;
@@ -130,7 +130,7 @@ public class LodBuilder
{
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override);
//}
//catch (IllegalArgumentException | NullPointerException e)
//{
@@ -142,6 +142,52 @@ public class LodBuilder
});
lodGenThreadPool.execute(thread);
}
public void generateLodNodeDirect(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode, boolean override)
{
if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
return;
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null)
return;
//noinspection GrazieInspection
try
{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
return;
// don't try to generate LODs if the user isn't in the world anymore
// (this happens a lot when the user leaves a world/server)
if (!MC.hasSinglePlayerServer() && !MC.connectedToServer())
return;
// make sure the dimension exists
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dim);
}
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override);
}
catch (IllegalArgumentException | NullPointerException e)
{
e.printStackTrace();
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
}
/**
* Creates a LodNode for a chunk in the given world.
@@ -149,14 +195,14 @@ public class LodBuilder
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk) throws IllegalArgumentException
{
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(), false);
}
/**
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config)
public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config, boolean override)
throws IllegalArgumentException
{
//long executeTime = System.currentTimeMillis();
@@ -176,6 +222,7 @@ public class LodBuilder
return;
// determine how many LODs to generate horizontally
byte minDetailLevel = region.getMinDetailLevel();
HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
@@ -201,12 +248,16 @@ public class LodBuilder
//lodDim.clear(detailLevel, posX, posZ);
if (data != null && data.length != 0)
{
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, minDetailLevel);
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, minDetailLevel);
long oldData = lodDim.getSingleData(minDetailLevel, posX, posZ);
if (override || !DataPointUtil.doesItExist(oldData) ||
DataPointUtil.getGenerationMode(oldData)<config.distanceGenerationMode.complexity) {
lodDim.addVerticalData(minDetailLevel, posX, posZ, data);
lodDim.updateData(minDetailLevel, posX, posZ);
}
}
}
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getChunkPosX(), chunk.getChunkPosZ());
//executeTime = System.currentTimeMillis() - executeTime;
//if (executeTime > 0) ClientApi.LOGGER.info("generateLodNodeFromChunk level: " + detailLevel + " time ms: " + executeTime);
}
@@ -394,7 +445,6 @@ public class LodBuilder
// 1 means the lighting is a guess
int isDefault = 0;
IWorldWrapper world = MC.getWrappedServerWorld();
int blockBrightness = chunk.getEmittedBrightness(x, y, z);
// get the air block above or below this block
@@ -403,12 +453,24 @@ public class LodBuilder
else
y++;
blockLight = chunk.getBlockLight(x, y, z);
if (hasSkyLight)
skyLight = chunk.getSkyLight(x, y, z);
else
skyLight = 0;
if (blockLight != -1 && skyLight != -1) {
blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4) + (isDefault << 8);
}
IWorldWrapper world = MC.getWrappedServerWorld();
if (world != null)
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(x,y,z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
@@ -424,11 +486,10 @@ public class LodBuilder
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
skyLight = 6;
}
else
skyLight = 0;
skyLight = 6;
}
}
else
@@ -437,8 +498,7 @@ public class LodBuilder
if (world==null)
{
blockLight = 0;
skyLight = 12;
isDefault = 1;
skyLight = 6;
}
else
{
@@ -461,11 +521,10 @@ public class LodBuilder
// lets just take a guess
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
skyLight = 6;
}
else
skyLight = 0;
skyLight = 6;
}
}
}
@@ -23,11 +23,14 @@ import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
import com.seibel.lod.core.enums.config.GenerationPriority;
import com.seibel.lod.core.objects.PosToGenerateContainer;
import com.seibel.lod.core.objects.lod.LodDimension;
import com.seibel.lod.core.util.LevelPosUtil;
@@ -96,12 +99,21 @@ public class LodWorldGenerator
// TODO: Rename the config option
if (CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration()) {
if (experimentalWorldGenerator == null) {
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
try {
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
if (experimentalWorldGenerator == null) CONFIG.client().worldGenerator().setAllowUnstableFeatureGeneration(false);
} catch (RuntimeException e) {
// Exception may happen if world got unloaded unorderly
e.printStackTrace();
}
}
} else {
if (experimentalWorldGenerator != null) {
experimentalWorldGenerator.stop();
try {
experimentalWorldGenerator.stop();
} catch (RuntimeException e) {
e.printStackTrace();
}
experimentalWorldGenerator = null;
}
}
@@ -115,6 +127,10 @@ public class LodWorldGenerator
// This is fine currently since DistanceGenerationMode doesn't care about the detail level for now.
// However, If that was to be changed, This will need to be fixed.
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
final GenerationPriority priority;
if (CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO)
priority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
else priority = CONFIG.client().worldGenerator().getGenerationPriority();
if (mode != DistanceGenerationMode.NONE
&& !generatorThreadRunning
@@ -152,9 +168,7 @@ public class LodWorldGenerator
IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
maxChunkGenRequests,
playerPosX,
playerPosZ);
maxChunkGenRequests, playerPosX, playerPosZ, priority);
byte detailLevel;
int posX;
@@ -192,7 +206,9 @@ public class LodWorldGenerator
// add the far positions
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
// But if priority is NEAR_FIRST, we only do that if near pos has ran out.
if ((nearIndex >= posToGenerate.getNumberOfNearPos() || priority != GenerationPriority.NEAR_FIRST) &&
farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
{
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
posX = posToGenerate.getNthPosX(farIndex, false);
@@ -364,10 +380,21 @@ public class LodWorldGenerator
if (genSubThreads != null && !genSubThreads.isShutdown())
{
genSubThreads.shutdownNow();
ClientApi.LOGGER.info("Blocking until generator sub threads terminated!!");
try {
mainGenThread.shutdownNow();
genSubThreads.shutdownNow();
boolean worked = genSubThreads.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
ClientApi.LOGGER.error("Generator sub threads timed out! May cause crash on game exit due to cleanup failure.");
} catch (InterruptedException e) {
ClientApi.LOGGER.error("Generator sub threads shutdown is interrupted! May cause crash on game exit due to cleanup failure.");
e.printStackTrace();
} finally {
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2022 Tom Lee (TomTheFurry)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.enums.config;
/**
* AUTO <br>
* SMOOTH_DROPOFF <br>
* PERFORMANCE_FOCUSED <br>
* <br>
* Determines how lod level drop off should be done
*
* @author Tom Lee
* @version 7-1-2022
*/
public enum DropoffQuality {
/** SMOOTH_DROPOFF when <128 lod view distance, or PERFORMANCE_FOCUSED otherwise */
AUTO(-1),
SMOOTH_DROPOFF(10),
PERFORMANCE_FOCUSED(0);
public final int fastModeSwitch;
DropoffQuality(int fastModeSwitch) {
this.fastModeSwitch = fastModeSwitch;
}
}
@@ -34,7 +34,13 @@ public enum DebugMode
SHOW_DETAIL,
/** LOD colors are based on their detail, and draws in wireframe. */
SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME,
/** LOD colors are based on their gen mode. */
SHOW_GENMODE,
/** LOD colors are based on their gen mode, and draws in wireframe. */
SHOW_GENMODE_WIREFRAME;
/** used when cycling through the different modes */
private DebugMode next;
@@ -43,7 +49,9 @@ public enum DebugMode
{
OFF.next = SHOW_DETAIL;
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
SHOW_DETAIL_WIREFRAME.next = OFF;
SHOW_DETAIL_WIREFRAME.next = SHOW_GENMODE;
SHOW_GENMODE.next = SHOW_GENMODE_WIREFRAME;
SHOW_GENMODE_WIREFRAME.next = OFF;
}
/** returns the next debug mode */
@@ -21,11 +21,15 @@ package com.seibel.lod.core.handlers;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.time.Duration;
import java.time.Instant;
import java.util.ConcurrentModificationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
@@ -39,7 +43,7 @@ import com.seibel.lod.core.objects.lod.RegionPos;
import com.seibel.lod.core.objects.lod.VerticalLevelContainer;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.ThreadMapUtil;
/**
* This object handles creating LodRegions
@@ -84,9 +88,10 @@ public class LodDimensionFileHandler
* Allow saving asynchronously, but never try to save multiple regions
* at a time
*/
private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private AtomicBoolean isFileWritingThreadRunning = new AtomicBoolean(false);
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
private ConcurrentHashMap<RegionPos, LodRegion> regionToSave = new ConcurrentHashMap<RegionPos, LodRegion>();
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension)
@@ -105,19 +110,39 @@ public class LodDimensionFileHandler
//================//
// read from file //
//================//
/**
* Returns the LodRegion at the given coordinates.
* Returns a new LodRegion at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality);
// Get one from the region hot cache
LodRegion region = regionToSave.get(regionPos);
if (region!=null && region.getMinDetailLevel()<=detailLevel &&
region.getGenerationMode().compareTo(generationMode)>=0 &&
region.getVerticalQuality().compareTo(verticalQuality)>=0)
return region; // The current hot cache to-be-saved region match our requirement.
region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL+1), regionPos, generationMode, verticalQuality);
return loadRegionFromFile(detailLevel, region, generationMode, verticalQuality);
}
/**
* Returns the LodRegion that is filled at the given coordinates.
* Returns an empty region if the file doesn't exist.
*/
public LodRegion loadRegionFromFile(byte detailLevel, LodRegion region, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
if (region.getGenerationMode().compareTo(generationMode)<0 || region.getVerticalQuality().compareTo(verticalQuality)<0) {
regionToSave.put(region.getRegionPos(), region); //FIXME: The hashMap key should prob be a {regionPos,VertQual} pair.
region = new LodRegion((byte) (LodUtil.REGION_DETAIL_LEVEL+1), region.getRegionPos(), generationMode, verticalQuality);
}
int regionX = region.regionPosX;
int regionZ = region.regionPosZ;
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
for (byte tempDetailLevel = (byte) (region.getMinDetailLevel()-1); tempDetailLevel >= detailLevel; tempDetailLevel--)
{
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, generationMode, verticalQuality);
if (file == null) {
region.addLevelContainer(new VerticalLevelContainer(tempDetailLevel));
@@ -198,33 +223,96 @@ public class LodDimensionFileHandler
// Save to File //
//==============//
/** Save all dirty regions in this LodDimension to file */
public void saveDirtyRegionsToFileAsync()
{
fileWritingThreadPool.execute(saveDirtyRegionsThread);
public void addRegionsToSave(LodRegion r) {
regionToSave.put(r.getRegionPos(), r);
}
private final Thread saveDirtyRegionsThread = new Thread(() ->
/** Save all dirty regions in this LodDimension to file */
public void saveDirtyRegionsToFile(boolean blockUntilFinished)
{
try
for (int i = 0; i < lodDimension.getWidth(); i++)
{
for (int i = 0; i < lodDimension.getWidth(); i++)
for (int j = 0; j < lodDimension.getWidth(); j++)
{
for (int j = 0; j < lodDimension.getWidth(); j++)
LodRegion r = lodDimension.getRegionByArrayIndex(i, j);
if (r != null && r.needSaving)
{
if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null)
{
saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j));
lodDimension.SetIsRegionDirty(i, j, false);
}
r.needSaving = false;
regionToSave.put(r.getRegionPos(), r);
}
}
}
catch (Exception e)
{
e.printStackTrace();
trySaveRegionsToBeSaved();
if (blockUntilFinished) {
ClientApi.LOGGER.info("Blocking until lod file save finishes!");
try {
fileWritingThreadPool.shutdown();
boolean worked = fileWritingThreadPool.awaitTermination(30, TimeUnit.SECONDS);
if (!worked)
ClientApi.LOGGER.error("File writing timed out! File data may not be saved correctly and may cause corruptions!!!");
} catch (InterruptedException e) {
ClientApi.LOGGER.error("File writing wait is interrupted! File data may not be saved correctly and may cause corruptions!!!");
e.printStackTrace();
} finally {
fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
}
}
});
}
public void trySaveRegionsToBeSaved() {
if (regionToSave.isEmpty()) return;
// Use Memory order Acquire to acquire any memory changes on getting this boolean
// (Corresponding call is the this::writerMain(...)::...setRelease(false);)
//boolean haventStarted = !isFileWritingThreadRunning.compareAndExchangeAcquire(false, true);
// The above needs java 9!
boolean haventStarted = isFileWritingThreadRunning.compareAndSet(false, true);
if (haventStarted) {
// We acquired the atomic lock.
fileWritingThreadPool.execute(this::writerMain);
}
}
private void writerMain() {
// Use Memory order Relaxed as no additional memory changes needed to be visible.
// (This is just a safety checks)
// boolean isStarted = isFileWritingThreadRunning.getPlain();
// The above needs java 9!
boolean isStarted = isFileWritingThreadRunning.get();
if (!isStarted) throw new ConcurrentModificationException("WriterMain Triggered but the thead state is not started!?");
ClientApi.LOGGER.info("Lod File Writer started. To-be-written-regions: "+regionToSave.size());
Instant start = Instant.now();
// Note: Since regionToSave is a ConcurrentHashMap, and the .values() return one that support concurrency,
// this for loop should be safe and loop until all values are gone.
while (!regionToSave.isEmpty()) {
for (LodRegion r : regionToSave.values()) {
//Check if the data has been swapped out right under me. Otherwise remove it from the entry
if (!regionToSave.remove(r.getRegionPos(), r)) continue;
try
{
Instant i = Instant.now();
ClientApi.LOGGER.info("Lod: Saving Region "+r.getRegionPos());
saveRegionToFile(r);
Instant j = Instant.now();
Duration d = Duration.between(i, j);
ClientApi.LOGGER.info("Lod: Region "+r.getRegionPos()+" save finish. Took "+d);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
Instant end = Instant.now();
ClientApi.LOGGER.info("Lod File Writer completed. Took "+Duration.between(start, end));
// Use Memory order Release to release any memory changes on setting this boolean
// (Corresponding call is the this::saveRegions(...)::...compareAndExchangeAcquire(false, true);)
// isFileWritingThreadRunning.setRelease(false);
// The above needs java 9!
isFileWritingThreadRunning.set(false);
}
/**
* Save a specific region to disk.<br>
@@ -240,7 +328,7 @@ public class LodDimensionFileHandler
{
// Get the old file
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] detail "+detailLevel+" to file.");
boolean isFileFullyGened = false;
// make sure the file and folder exists
@@ -366,14 +454,14 @@ public class LodDimensionFileHandler
// Return null if no file found
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, DistanceGenerationMode targetGenMode, VerticalQuality targetVertQuality) {
DistanceGenerationMode genMode = targetGenMode;
DistanceGenerationMode genMode = DistanceGenerationMode.FULL;
// Search from least GenMode to max GenMode, than least vertQuality to max vertQuality
do {
File file = getRegionFile(regionX, regionZ, genMode, detailLevel, targetVertQuality);
if (file.exists()) return file; // Found target file.
targetGenMode = DistanceGenerationMode.next(targetGenMode);
if (targetGenMode == null) { // Failed to find any files for this vertQuality. Try next one up.
targetGenMode = genMode;
genMode = DistanceGenerationMode.previous(genMode);
if (genMode==null || genMode==DistanceGenerationMode.previous(targetGenMode)) { // Failed to find any files for this vertQuality. Try next one up.
genMode = DistanceGenerationMode.FULL;
targetVertQuality = VerticalQuality.next(targetVertQuality);
}
} while (targetVertQuality != null);
@@ -31,7 +31,7 @@ public class PosToGenerateContainer
{
private final int playerPosX;
private final int playerPosZ;
private final byte farMinDetail;
public final byte farMinDetail;
private int nearSize;
private int farSize;
@@ -152,6 +152,7 @@ public class VertexOptimizer
* This is a map from Direction to the relative normal vector
* we are using this since I'm not sure if the getNormal create new object at every call
*/
// FIXME: No. It doesn't. Just remove this.
@SuppressWarnings("serial")
public static final Map<LodDirection, Vec3i> DIRECTION_NORMAL_MAP = new HashMap<LodDirection, Vec3i>()
{{
@@ -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;
}
}
@@ -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. <Br><Br>
* This object holds all loaded LevelContainers acting as a quad tree for a
* given region. <Br>
* <Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
@@ -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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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
* <p>
* 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();
}
}
@@ -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.
}
@@ -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);
}
}
@@ -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;
}
@@ -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);
}
}
}
@@ -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.
@@ -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. <br>
@@ -59,21 +60,6 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
*/
public class LodRenderer
{
public static class VanillaRenderedChunksList extends GridList<Boolean> {
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<gridSize*gridSize; i++) {
add(i, false);
}
}
}
public static class LagSpikeCatcher {
long timer = System.nanoTime();
@@ -109,7 +95,7 @@ public class LodRenderer
LodRenderProgram shaderProgram = null;
/** This is used to determine if the LODs should be regenerated */
private AbstractBlockPosWrapper previousPos = null;
private AbstractBlockPosWrapper lastUpdatedPos = null;
// these variables are used to determine if the buffers should be rebuilt
private int prevRenderDistance = 0;
@@ -133,7 +119,7 @@ public class LodRenderer
* This HashSet contains every chunk that Vanilla Minecraft
* is going to render
*/
public VanillaRenderedChunksList vanillaRenderedChunks;
public MovableGridList<Boolean> 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<vbos.gridSize; regionX++) {
for (int regionZ=lowRegionZ; regionZ<vbos.gridSize; regionZ++) {
int vCount0 = 0;
for (int regionX=lowRegionX; regionX<lowRegionX+vbos.gridSize; regionX++) {
for (int regionZ=lowRegionZ; regionZ<lowRegionZ+vbos.gridSize; regionZ++) {
if (vbos.get(regionX, regionZ) == null) continue;
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
MC_RENDER.getLookAtVector(), regionX, regionZ)) {
for (LodVertexBuffer vbo : vbos.get(regionX, regionZ)) {
if (vbo == null) continue;
if (vbo.vertexCount == 0) continue;
if (vbo.vertexCount == 0) {
vCount0++;
continue;
}
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
shaderProgram.bindVertexBuffer(vbo.id);
drawCall++;
@@ -322,8 +312,8 @@ public class LodRenderer
}
}
//if (drawCall!=0)
// ClientApi.LOGGER.info("DrawCall Count: "+drawCall);
//if (drawCall==0)
// ClientApi.LOGGER.info("DrawCall Count: "+drawCall+"("+vCount0+")");
//================//
// render cleanup //
@@ -378,7 +368,7 @@ public class LodRenderer
}
/** Create all buffers that will be used. */
public void setupBuffers(LodDimension lodDim)
public void setupBuffers()
{
lodBufferBuilderFactory.allBuffersRequireReset = true;
}
@@ -498,52 +488,44 @@ public class LodRenderer
}
// returns whether anything changed
private boolean updateVanillaRenderedChunks(LodDimension lodDim, boolean recreateChunks) {
short chunkRenderDistance = (short) MC_RENDER.getRenderDistance();
int chunkX = Math.floorDiv(previousPos.getX(), 16);
int chunkZ = Math.floorDiv(previousPos.getZ(), 16);
private boolean updateVanillaRenderedChunks(LodDimension lodDim) {
int chunkRenderDistance = MC_RENDER.getRenderDistance()+2;
int chunkX = Math.floorDiv(lastUpdatedPos.getX(), 16);
int chunkZ = Math.floorDiv(lastUpdatedPos.getZ(), 16);
// if the player is high enough, draw all LODs
if (previousPos.getY() > 256) {
vanillaRenderedChunks = new VanillaRenderedChunksList(
IWorldWrapper world = MC.getWrappedClientWorld();
if (lastUpdatedPos.getY() > world.getHeight()-world.getMinHeight()) {
vanillaRenderedChunks = new MovableGridList<Boolean>(
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<Boolean> chunkList;
boolean anyChanged = false;
if (vanillaRenderedChunks == null || vanillaRenderedChunks.gridCentreToEdge != chunkRenderDistance ||
vanillaRenderedChunks.getCenterX()!=chunkX || vanillaRenderedChunks.getCenterY()!=chunkZ) {
chunkList = new MovableGridList<Boolean>(chunkRenderDistance, chunkX, chunkZ);
anyChanged = true;
} else {
chunkList = vanillaRenderedChunks;
}
LagSpikeCatcher getChunks = new LagSpikeCatcher();
Set<AbstractChunkPosWrapper> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, previousPos);
Set<AbstractChunkPosWrapper> 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) {
@@ -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<gridSize; y++) {
for (int x=0; x<gridSize; x++) {
nb[i] = transformer.transform(b[i],
x+centerX-gridCentreToEdge, y+centerY-gridCentreToEdge);
i++;
}
}
b = nb;
}
public void flipBorder(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);
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();
}
}
@@ -365,7 +365,7 @@ public class LodUtil
// get the chunks that are going to be rendered by Minecraft
HashSet<AbstractChunkPosWrapper> posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks();
HashSet<AbstractChunkPosWrapper> 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<Boolean> 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 */
@@ -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<T> extends ArrayList<T> implements List<T> {
super.add(null);
}
}
public void clear(Consumer<? super T> d) {
super.forEach(d);
super.clear();
super.ensureCapacity(gridSize*gridSize);
for (int i=0; i<gridSize*gridSize; i++) {
super.add(null);
}
}
public int getCenterX() {return centerX;}
public int getCenterY() {return centerY;}
@@ -57,12 +66,33 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
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<gridSize && y>=0 && y<gridSize);
}
private final T _getDirect(int x, int y) {
if (x<0 || x>=gridSize || y<0 || y>=gridSize) return null;
@@ -73,9 +103,14 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
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<T> extends ArrayList<T> implements List<T> {
// 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; x++) {
for (int y=0; y<gridSize; y++) {
if (x-deltaX<0 || y-deltaY<0 ||
x-deltaX>=gridSize || y-deltaY>=gridSize) {
d.accept(_getDirect(x,y));
}
}
}
// X
if (deltaX >= 0 && deltaY >= 0)
@@ -136,10 +250,8 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
}
}
}
centerX = newCenterX;
centerY = newCenterY;
}
// TODO: This is unused but may be useful later on.
/*
@@ -154,6 +266,8 @@ public class MovableGridList<T> extends ArrayList<T> implements List<T> {
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");
@@ -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;}
}
@@ -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 = ""
@@ -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<AbstractChunkPosWrapper> getVanillaRenderedChunks()
{
return getMaximumRenderedChunks();
}
/**
* This method returns the ChunkPos of every chunk that
* Sodium is going to render this frame.
* <br>
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
*/
public default HashSet<AbstractChunkPosWrapper> 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<AbstractChunkPosWrapper> renderedPos = new HashSet<AbstractChunkPosWrapper>();
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));
}
@@ -0,0 +1,5 @@
package com.seibel.lod.core.wrapperInterfaces.modAccessor;
public abstract interface IModAccessor {
String getModName();
}
@@ -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<AbstractChunkPosWrapper> getNormalRenderedChunks();
}
@@ -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;}
}
+11 -3
View File
@@ -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"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB