Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
+29
-37
@@ -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;}
|
||||
}
|
||||
|
||||
+24
-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 = ""
|
||||
|
||||
+6
-17
@@ -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;}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user