Merge branch 'main' of https://gitlab.com/jeseibel/distant-horizons-core into NSizedMultiplayerTest

This commit is contained in:
s809
2024-11-12 12:07:22 +05:00
60 changed files with 931 additions and 1677 deletions
+2 -2
View File
@@ -1,10 +1,10 @@
# <img src="https://gitlab.com/jeseibel/distant-horizons-core/-/raw/main/_Misc%20Files%2Flogo%20files%2Fnew%2FSVG%2FDistant-Horizons-Core.svg" height="128px">
# <img src="https://gitlab.com/distant-horizons-team/distant-horizons-core/-/raw/main/_Misc%20Files/logo%20files/new/SVG/Distant-Horizons-Core.svg" height="128px">
This repo is for the Distant Horizons mod.
The purpose of this submodule is to isolate code that isn't tied to a specific version of minecraft. This prevents us from having duplicate code; reducing errors and helping us port to different versions faster and easier.
Check out the mod's main GitLab page here:
https://gitlab.com/jeseibel/distant-horizons
https://gitlab.com/distant-horizons-team/distant-horizons
## source code installation
@@ -25,7 +25,7 @@ package com.seibel.distanthorizons.api.enums.rendering;
* EXPONENTIAL_SQUARED <br>
*
* @author Leetom
* @version 2022-6-30
* @version 2024-11-09
* @since API 2.0.0
*/
public enum EDhApiFogFalloff
@@ -35,8 +35,17 @@ public enum EDhApiFogFalloff
// when removing items up the API major version
LINEAR,
EXPONENTIAL,
EXPONENTIAL_SQUARED,
LINEAR(0),
EXPONENTIAL(1),
EXPONENTIAL_SQUARED(2);
/**
* Stable version of {@link EDhApiFogFalloff#ordinal()}
* @since API 4.0.0
*/
public final int value;
EDhApiFogFalloff(int value) { this.value = value; }
}
@@ -31,28 +31,28 @@ package com.seibel.distanthorizons.api.enums.rendering;
* @version 2024-4-6
* @since API 2.0.0
*/
public enum EDhApiHeightFogMode
public enum EDhApiHeightFogDirection
{
// Reminder:
// when adding items up the API minor version
// when removing items up the API major version
ABOVE_CAMERA(true, true, false),
BELOW_CAMERA(true, false, true),
ABOVE_AND_BELOW_CAMERA(true, true, true),
ABOVE_SET_HEIGHT(false, true, false),
BELOW_SET_HEIGHT(false, false, true),
ABOVE_AND_BELOW_SET_HEIGHT(false, true, true);
ABOVE_CAMERA (true, true, false),
BELOW_CAMERA (true, false, true),
ABOVE_AND_BELOW_CAMERA (true, true, true),
ABOVE_SET_HEIGHT (false, true, false),
BELOW_SET_HEIGHT (false, false, true),
ABOVE_AND_BELOW_SET_HEIGHT (false, true, true);
public final boolean basedOnCamera;
public final boolean above;
public final boolean below;
public final boolean fogAppliesUp;
public final boolean fogAppliesDown;
EDhApiHeightFogMode(boolean basedOnCamera, boolean above, boolean below)
EDhApiHeightFogDirection(boolean basedOnCamera, boolean fogAppliesUp, boolean fogAppliesDown)
{
this.basedOnCamera = basedOnCamera;
this.above = above;
this.below = below;
this.fogAppliesUp = fogAppliesUp;
this.fogAppliesDown = fogAppliesDown;
}
}
@@ -20,10 +20,11 @@
package com.seibel.distanthorizons.api.enums.rendering;
/**
* BASIC <br>
* IGNORE_HEIGHT <br>
* ADDITION <br>
* SPHERICAL <br>
* CYLINDRICAL <br>
* <br>
* MAX <br>
* ADDITION <br>
* MULTIPLY <br>
* INVERSE_MULTIPLY <br>
* LIMITED_ADDITION <br>
@@ -37,14 +38,36 @@ package com.seibel.distanthorizons.api.enums.rendering;
*/
public enum EDhApiHeightFogMixMode
{
BASIC,
IGNORE_HEIGHT,
ADDITION,
MAX,
MULTIPLY,
INVERSE_MULTIPLY,
LIMITED_ADDITION,
MULTIPLY_ADDITION,
INVERSE_MULTIPLY_ADDITION,
AVERAGE,
/**
* Basic just means the fog will be based on the fragment depth
* not on any special height calculation IE spherical fog. <br><br>
*
* Not to be confused with {@link EDhApiHeightFogMixMode#CYLINDRICAL}
* which causes fog to only apply based on horizontal distance.
*/
SPHERICAL(0),
/**
* Fog is applied based on horizontal distance from the camera,
* IE cylindrical fog.
*/
CYLINDRICAL(1),
MAX(2),
ADDITION(3),
MULTIPLY(4),
INVERSE_MULTIPLY(5),
LIMITED_ADDITION(6),
MULTIPLY_ADDITION(7),
INVERSE_MULTIPLY_ADDITION(8),
AVERAGE(9);
/**
* Stable version of {@link EDhApiFogFalloff#ordinal()}
* @since API 4.0.0
*/
public final int value;
EDhApiHeightFogMixMode(int value) { this.value = value; }
}
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
@@ -42,11 +42,14 @@ public interface IDhApiHeightFogConfig extends IDhApiConfigGroup
/** Defines how the height fog mixes. */
IDhApiConfigValue<EDhApiHeightFogMixMode> heightFogMixMode();
/** Defines how the height fog is drawn relative to the camera or world. */
IDhApiConfigValue<EDhApiHeightFogMode> heightFogMode();
/**
* Defines which direction height fog is drawn relative to the world.
* @since API 4.0.0
*/
IDhApiConfigValue<EDhApiHeightFogDirection> heightFogDirection();
/**
* Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogMode()}
* Defines the height fog's base height if {@link IDhApiHeightFogConfig#heightFogDirection()}
* is set to use a specific height.
*/
IDhApiConfigValue<Double> heightFogBaseHeight();
@@ -39,6 +39,13 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
/** @since API 4.0.0 */
String getDimensionName();
/**
* Returns a string intended to uniquely identify this level.
*
* @since API 4.0.0
*/
String getDhIdentifier();
EDhApiLevelType getLevelType();
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
@@ -40,8 +40,8 @@ public class DhApiHeightFogConfig implements IDhApiHeightFogConfig
{ return new DhApiConfigValue<EDhApiHeightFogMixMode, EDhApiHeightFogMixMode>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMixMode); }
@Override
public IDhApiConfigValue<EDhApiHeightFogMode> heightFogMode()
{ return new DhApiConfigValue<EDhApiHeightFogMode, EDhApiHeightFogMode>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMode); }
public IDhApiConfigValue<EDhApiHeightFogDirection> heightFogDirection()
{ return new DhApiConfigValue<EDhApiHeightFogDirection, EDhApiHeightFogDirection>(Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection); }
@Override
public IDhApiConfigValue<Double> heightFogBaseHeight()
@@ -207,7 +207,7 @@ public class ClientApi
{
try
{
LOGGER.info("Unloading client level [" + level + "]-["+level.getDimensionName()+"].");
LOGGER.info("Unloading client level [" + level.getClass().getSimpleName() + "]-[" + level.getDhIdentifier() + "].");
if (level instanceof IServerKeyedClientLevel)
{
@@ -253,7 +253,7 @@ public class ClientApi
try
{
LOGGER.info("Loading client level [" + levelWrapper + "]-["+levelWrapper.getDimensionName()+"].");
LOGGER.info("Loading client level [" + levelWrapper + "]-[" + levelWrapper.getDhIdentifier() + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
@@ -312,28 +312,6 @@ public class ClientApi
// render events //
//===============//
public void rendererShutdownEvent()
{
LOGGER.info("Renderer shutting down.");
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.push("DH-RendererShutdown");
profiler.pop();
}
public void rendererStartupEvent()
{
LOGGER.info("Renderer starting up.");
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
profiler.push("DH-RendererStartup");
// make sure the GLProxy is created before the LodBufferBuilder needs it
GLProxy.getInstance();
profiler.pop();
}
public void clientTickEvent()
{
IProfilerWrapper profiler = MC_CLIENT.getProfiler();
@@ -101,7 +101,7 @@ public class ClientPluginChannelApi
}
else
{
LOGGER.info("Unloading non-keyed level: [" + clientLevel.getDimensionName() + "].");
LOGGER.info("Unloading non-keyed level: [" + clientLevel.getDhIdentifier() + "].");
this.levelUnloadHandler.accept(clientLevel);
}
@@ -22,12 +22,9 @@ package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhServerWorld;
import com.seibel.distanthorizons.core.world.IDhServerWorld;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -148,8 +145,13 @@ public class ServerApi
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
{
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
{
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
LOGGER.info("Player [${player.getName()}] joined.");
LOGGER.info("Player ["+player.getName()+"] joined.");
if (serverWorld != null)
{
serverWorld.addPlayer(player);
@@ -157,8 +159,13 @@ public class ServerApi
}
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
{
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
{
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
LOGGER.info("Player [${player.getName()}] disconnected.");
LOGGER.info("Player ["+player.getName()+"] disconnected.");
if (serverWorld != null)
{
serverWorld.removePlayer(player);
@@ -166,8 +173,13 @@ public class ServerApi
}
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
{
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
{
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
LOGGER.info("Player [${player.getName()}] changed level: [${originLevel.getKeyedLevelDimensionName()}] -> [${destinationLevel.getKeyedLevelDimensionName()}].");
LOGGER.info("Player ["+player.getName()+"] changed level: ["+originLevel.getKeyedLevelDimensionName()+"] -> ["+destinationLevel.getKeyedLevelDimensionName()+"].");
if (serverWorld != null)
{
serverWorld.changePlayerLevel(player, originLevel, destinationLevel);
@@ -176,6 +188,11 @@ public class ServerApi
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
{
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
{
return;
}
IDhServerWorld serverWorld = SharedApi.getIDhServerWorld();
if (serverWorld != null)
{
@@ -39,7 +39,6 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
import org.lwjgl.util.tinyfd.TinyFileDialogs;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.*;
@@ -549,40 +548,40 @@ public class Config
public static class HeightFog
{
public static ConfigEntry<EDhApiHeightFogMixMode> heightFogMixMode = new ConfigEntry.Builder<EDhApiHeightFogMixMode>()
.set(EDhApiHeightFogMixMode.BASIC)
.set(EDhApiHeightFogMixMode.SPHERICAL)
.comment(""
+ "How should height effect the fog thickness? \n"
+ "Note: height fog is combined with the other fog settings. \n"
+ "\n"
+ EDhApiHeightFogMixMode.BASIC + ": No special height fog effect. Fog is calculated based on camera distance \n"
+ EDhApiHeightFogMixMode.IGNORE_HEIGHT + ": Ignore height completely. Fog is only calculated with horizontal distance \n"
+ EDhApiHeightFogMixMode.ADDITION + ": heightFog + farFog \n"
+ EDhApiHeightFogMixMode.SPHERICAL + ": Fog is calculated based on camera distance. \n"
+ EDhApiHeightFogMixMode.CYLINDRICAL + ": Ignore height, fog is calculated based on horizontal distance. \n"
+ "\n"
+ EDhApiHeightFogMixMode.MAX + ": max(heightFog, farFog) \n"
+ EDhApiHeightFogMixMode.ADDITION + ": heightFog + farFog \n"
+ EDhApiHeightFogMixMode.MULTIPLY + ": heightFog * farFog \n"
+ EDhApiHeightFogMixMode.INVERSE_MULTIPLY + ": 1 - (1-heightFog) * (1-farFog) \n"
+ EDhApiHeightFogMixMode.LIMITED_ADDITION + ": farFog + max(farFog, heightFog) \n"
+ EDhApiHeightFogMixMode.MULTIPLY_ADDITION + ": farFog + farFog * heightFog \n"
+ EDhApiHeightFogMixMode.INVERSE_MULTIPLY_ADDITION + ": farFog + 1 - (1-heightFog) * (1-farFog) \n"
+ EDhApiHeightFogMixMode.AVERAGE + ": farFog*0.5 + heightFog*0.5 \n"
+ "\n"
+ "Note: height fog settings are ignored if '" + EDhApiHeightFogMixMode.BASIC + "' or '" + EDhApiHeightFogMixMode.IGNORE_HEIGHT + "' are selected.")
+ "\n")
.build();
public static ConfigEntry<EDhApiHeightFogMode> heightFogMode = new ConfigEntry.Builder<EDhApiHeightFogMode>()
.set(EDhApiHeightFogMode.ABOVE_AND_BELOW_CAMERA)
public static ConfigEntry<EDhApiHeightFogDirection> heightFogDirection = new ConfigEntry.Builder<EDhApiHeightFogDirection>()
.set(EDhApiHeightFogDirection.BELOW_SET_HEIGHT)
.comment(""
+ "Where should the height fog start? \n"
+ "\n"
+ EDhApiHeightFogMode.ABOVE_CAMERA + ": Height fog starts at the camera and goes towards the sky \n"
+ EDhApiHeightFogMode.BELOW_CAMERA + ": Height fog starts at the camera and goes towards the void \n"
+ EDhApiHeightFogMode.ABOVE_AND_BELOW_CAMERA + ": Height fog starts from the camera to goes towards both the sky and void \n"
+ EDhApiHeightFogMode.ABOVE_SET_HEIGHT + ": Height fog starts from a set height and goes towards the sky \n"
+ EDhApiHeightFogMode.BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards the void \n"
+ EDhApiHeightFogMode.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void")
+ EDhApiHeightFogDirection.ABOVE_CAMERA + ": Height fog starts at the camera and goes towards the sky \n"
+ EDhApiHeightFogDirection.BELOW_CAMERA + ": Height fog starts at the camera and goes towards the void \n"
+ EDhApiHeightFogDirection.ABOVE_AND_BELOW_CAMERA + ": Height fog starts from the camera to goes towards both the sky and void \n"
+ EDhApiHeightFogDirection.ABOVE_SET_HEIGHT + ": Height fog starts from a set height and goes towards the sky \n"
+ EDhApiHeightFogDirection.BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards the void \n"
+ EDhApiHeightFogDirection.ABOVE_AND_BELOW_SET_HEIGHT + ": Height fog starts from a set height and goes towards both the sky and void")
.build();
public static ConfigEntry<Double> heightFogBaseHeight = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(-4096.0, 70.0, 4096.0)
.setMinDefaultMax(-4096.0, 80.0, 4096.0)
.comment("If the height fog is calculated around a set height, what is that height position?")
.build();
@@ -596,7 +595,7 @@ public class Config
.build();
public static ConfigEntry<Double> heightFogEnd = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 1.0, FOG_RANGE_MAX)
.setMinDefaultMax(FOG_RANGE_MIN, 0.6, FOG_RANGE_MAX)
.comment(""
+ "Should the end of the height fog be offset? \n"
+ "\n"
@@ -605,7 +604,7 @@ public class Config
.build();
public static ConfigEntry<Double> heightFogMin = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(-5.0, 0.0, FOG_RANGE_MAX)
.setMinDefaultMax(0.0, 0.0, FOG_RANGE_MAX)
.comment(""
+ "What is the minimum fog thickness? \n"
+ "\n"
@@ -633,7 +632,7 @@ public class Config
.build();
public static ConfigEntry<Double> heightFogDensity = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(0.01, 2.5, 50.0)
.setMinDefaultMax(0.01, 20.0, 50.0)
.comment("What is the height fog's density?")
.build();
@@ -1268,6 +1267,19 @@ public class Config
+ "Expected Compression Ratio: 0.7\n"
+ "")
.build();
public static ConfigEntry<Boolean> recalculateChunkHeightmaps = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "True: Recalculate chunk height maps before chunks can be used by DH.\n"
+ " This can fix problems with worlds created by World Painter or \n"
+ " other external tools where the heightmap format may be incorrect. \n"
+ "False: Assume any height maps handled by Minecraft are correct. \n"
+ "\n"
+ "Fastest: False\n"
+ "Most Compatible: True\n"
+ "")
.build();
}
public static class MultiThreading
@@ -1531,11 +1543,11 @@ public class Config
public static ConfigEntry<String> levelKeyPrefix = new ConfigEntry.Builder<String>()
.setServersideShortName("levelKeyPrefix")
.set(getDefaultLevelKeyPrefix())
.set("")
.comment(""
+ "Prefix of the level keys sent to the clients.\n"
+ "If the mod is running behind a proxy, each backend should use a unique value (an empty string is allowed for one of the servers).\n"
+ "This value may be auto-generated if the mod is installed before the first start of the server.\n"
+ "If the mod is running behind a proxy, each backend should use a unique value.\n"
+ "If this value is empty, level key will be based on the server's seed hash.\n"
+ "")
.build();
@@ -1622,21 +1634,6 @@ public class Config
}
}
private static String getDefaultLevelKeyPrefix()
{
IMinecraftSharedWrapper mcWrapper = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
if (mcWrapper.isDedicatedServer())
{
return mcWrapper.isWorldNew()
? "server" + ThreadLocalRandom.current().nextInt(1, 1000)
: "";
}
else
{
return SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class).getUsername();
}
}
/** Guesses whether a dev environment is used based on the current folder path */
private static boolean isRunningInDevEnvironment()
{
@@ -19,9 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
@@ -30,7 +28,6 @@ import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.StatsMap;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.apache.logging.log4j.Logger;
import org.lwjgl.system.MemoryUtil;
@@ -46,9 +43,6 @@ import java.util.concurrent.*;
public class ColumnRenderBuffer implements AutoCloseable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
/** number of bytes a single quad takes */
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
@@ -59,110 +53,104 @@ public class ColumnRenderBuffer implements AutoCloseable
public final DhBlockPos pos;
public final DhBlockPos blockPos;
public boolean buffersUploaded = false;
private GLVertexBuffer[] vbos;
private GLVertexBuffer[] vbosTransparent;
private CompletableFuture<ColumnRenderBuffer> uploadFuture = null;
//==============//
// constructors //
//==============//
public ColumnRenderBuffer(DhBlockPos pos)
public ColumnRenderBuffer(DhBlockPos blockPos)
{
this.pos = pos;
this.blockPos = blockPos;
this.vbos = new GLVertexBuffer[0];
this.vbosTransparent = new GLVertexBuffer[0];
}
//==================//
// buffer uploading //
//==================//
/** Should be run on a DH thread. */
public void uploadBuffer(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod) throws InterruptedException
public synchronized CompletableFuture<ColumnRenderBuffer> makeAndUploadBuffersAsync(LodQuadBuilder builder, EDhApiGpuUploadMethod gpuUploadMethod)
{
LodUtil.assertTrue(DhApi.isDhThread(), "Buffer uploading needs to be done on a DH thread to prevent locking up any MC threads.");
if (this.uploadFuture != null)
{
return this.uploadFuture;
}
this.uploadFuture = new CompletableFuture<>();
// make the buffers
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers();
this.vbos = resizeBuffer(this.vbos, opaqueBuffers.size());
this.vbosTransparent = resizeBuffer(this.vbosTransparent, transparentBuffers.size());
// upload on MC's render thread
CompletableFuture<Void> uploadFuture = new CompletableFuture<>();
MC_CLIENT.executeOnRenderThread(() ->
GLProxy.getInstance().queueRunningOnRenderThread(() ->
{
try
{
this.uploadBuffers(builder, gpuUploadMethod);
uploadFuture.complete(null);
// skip this event if requested
if (Thread.interrupted() || this.uploadFuture.isCancelled())
{
throw new InterruptedException();
}
// upload on the render thread
uploadBuffersDirect(this.vbos, opaqueBuffers, gpuUploadMethod);
uploadBuffersDirect(this.vbosTransparent, transparentBuffers, gpuUploadMethod);
this.buffersUploaded = true;
// success
this.uploadFuture.complete(this);
this.uploadFuture = null;
}
catch (InterruptedException e)
catch (InterruptedException ignore)
{
throw new CompletionException(e);
this.uploadFuture.complete(this);
this.uploadFuture = null;
}
});
try
{
// wait for the upload to finish
uploadFuture.get(5_000, TimeUnit.MILLISECONDS);
}
catch (ExecutionException e)
{
LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
catch (TimeoutException e)
{
// timeouts can be ignored because it generally means the
// MC Render thread executor was closed
//LOGGER.warn("Error uploading builder ["+builder+"] synchronously. Error: "+e.getMessage(), e);
}
}
private void uploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method) throws InterruptedException
{
// uploading mapped buffers used to be done here,
// however due to a memory leak and complication with the previous code,
// now we only allow direct uploading.
// (There's also insufficient data to state whether mapped buffers are necessary
// for DH to upload without stuttering the main thread)
this.vbos = makeAndUploadBuffers(builder, method, this.vbos, builder.makeOpaqueVertexBuffers());
this.vbosTransparent = makeAndUploadBuffers(builder, method, this.vbosTransparent, builder.makeTransparentVertexBuffers());
this.buffersUploaded = true;
}
/** This resizes and returns the vbo array if necessary based on the amount of data needed for this area. */
private static GLVertexBuffer[] makeAndUploadBuffers(LodQuadBuilder builder, EDhApiGpuUploadMethod method, GLVertexBuffer[] vbos, ArrayList<ByteBuffer> buffers) throws InterruptedException
{
try
{
vbos = resizeBuffer(vbos, buffers.size());
uploadBuffersDirect(vbos, buffers, method);
}
finally
{
// all the buffers must be manually freed to prevent memory leaks
if (buffers != null)
catch (Exception e)
{
for (ByteBuffer buffer : buffers)
LOGGER.error("Unexpected issue uploading buffer ["+this.blockPos +"], error: ["+e.getMessage()+"].", e);
this.uploadFuture.completeExceptionally(e);
this.uploadFuture = null;
}
finally
{
// all the buffers must be manually freed to prevent memory leaks
for (ByteBuffer buffer : opaqueBuffers)
{
MemoryUtil.memFree(buffer);
}
for (ByteBuffer buffer : transparentBuffers)
{
MemoryUtil.memFree(buffer);
}
}
}
});
// return the array in case it was resized
return vbos;
return this.uploadFuture;
}
public static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
private static GLVertexBuffer[] resizeBuffer(GLVertexBuffer[] vbos, int newSize)
{
if (vbos.length == newSize)
{
@@ -228,8 +216,6 @@ public class ColumnRenderBuffer implements AutoCloseable
//========//
// render //
//========//
@@ -238,7 +224,7 @@ public class ColumnRenderBuffer implements AutoCloseable
public boolean renderOpaque(LodRenderer renderContext, DhApiRenderParam renderEventParam)
{
boolean hasRendered = false;
renderContext.setModelViewMatrixOffset(this.pos, renderEventParam);
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbos)
{
if (vbo == null)
@@ -266,7 +252,7 @@ public class ColumnRenderBuffer implements AutoCloseable
try
{
// can throw an IllegalStateException if the GL program was freed before it should've been
renderContext.setModelViewMatrixOffset(this.pos, renderEventParam);
renderContext.setModelViewMatrixOffset(this.blockPos, renderEventParam);
for (GLVertexBuffer vbo : this.vbosTransparent)
{
@@ -287,7 +273,7 @@ public class ColumnRenderBuffer implements AutoCloseable
}
catch (IllegalStateException e)
{
LOGGER.error("renderContext program doesn't exist for pos: "+this.pos, e);
LOGGER.error("renderContext program doesn't exist for pos: "+this.blockPos, e);
}
return hasRendered;
@@ -115,62 +115,17 @@ public class ColumnRenderBufferBuilder
LodQuadBuilder quadBuilder
)
{
// TODO put into a single future/thread so it can be easily canceled
ThreadPoolExecutor bufferUploaderExecutor = ThreadPoolUtil.getBufferUploaderExecutor();
if (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos)));
CompletableFuture<ColumnRenderBuffer> uploadFuture = buffer.makeAndUploadBuffersAsync(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
uploadFuture.whenComplete((uploadedBuffer, exception) ->
{
// one or more of the thread pools has been shut down
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
try
{
return CompletableFuture.supplyAsync(() ->
{
try
{
ColumnRenderBuffer buffer = new ColumnRenderBuffer(new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getMinY(), DhSectionPos.getMinCornerBlockZ(pos)));
try
{
buffer.uploadBuffer(quadBuilder, GLProxy.getInstance().getGpuUploadMethod());
if (buffer.buffersUploaded)
{
return buffer;
}
else
{
buffer.close();
return null;
}
}
catch (Exception e)
{
buffer.close();
throw e;
}
}
catch (InterruptedException e)
{
throw UncheckedInterruptedException.convert(e);
}
catch (Throwable e3)
{
LOGGER.error("LodNodeBufferBuilder was unable to upload buffer for pos ["+DhSectionPos.toString(pos)+"], error: [" + e3.getMessage() + "].", e3);
throw e3;
}
}, bufferUploaderExecutor);
}
catch (RejectedExecutionException ignore)
{
// shouldn't happen, but just in case
CompletableFuture<ColumnRenderBuffer> future = new CompletableFuture<>();
future.cancel(true);
return future;
}
// clean up if not uploaded
if (!uploadedBuffer.buffersUploaded)
{
uploadedBuffer.close();
}
});
return uploadFuture;
}
private static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.api.enums.config.EDhApiBlocksToAvoid;
import com.seibel.distanthorizons.api.enums.config.EDhApiVerticalQuality;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
@@ -40,12 +39,10 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrappe
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.Logger;
import java.text.ParseException;
import java.util.HashSet;
/**
@@ -236,10 +233,10 @@ public class FullDataToRenderDataTransformer
if (!brokenPos.contains(fullDataMapping.getPos()))
{
brokenPos.add(fullDataMapping.getPos());
String dimName = level.getLevelWrapper().getDimensionName();
String levelId = level.getLevelWrapper().getDhIdentifier();
LOGGER.warn("Unable to get data point with id ["+id+"] " +
"(Max possible ID: ["+fullDataMapping.getMaxValidId()+"]) " +
"for pos ["+fullDataMapping.getPos()+"] in dimension ["+dimName+"]. " +
"for pos ["+fullDataMapping.getPos()+"] in level ["+levelId+"]. " +
"Error: ["+e.getMessage()+"]. " +
"Further errors for this position won't be logged.");
}
@@ -141,7 +141,7 @@ public class LodDataBuilder
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{
LongArrayList longs = new LongArrayList(chunkWrapper.getHeight() / 4);
int lastY = chunkWrapper.getMaxBuildHeight();
int lastY = chunkWrapper.getExclusiveMaxBuildHeight();
IBiomeWrapper biome = chunkWrapper.getBiome(relBlockX, lastY, relBlockZ);
IBlockStateWrapper blockState = AIR;
int mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
@@ -149,7 +149,7 @@ public class LodDataBuilder
byte blockLight;
byte skyLight;
if (lastY < chunkWrapper.getMaxBuildHeight())
if (lastY < chunkWrapper.getExclusiveMaxBuildHeight())
{
// FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting
blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ);
@@ -167,7 +167,7 @@ public class LodDataBuilder
int y = chunkWrapper.getLightBlockingHeightMapValue(relBlockX, relBlockZ);
// go up until we reach open air or the world limit
IBlockStateWrapper topBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState);
while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight())
while (!topBlockState.isAir() && y < chunkWrapper.getExclusiveMaxBuildHeight())
{
try
{
@@ -180,7 +180,7 @@ public class LodDataBuilder
{
if (!getTopErrorLogged)
{
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getExclusiveMaxBuildHeight() + "] had issue getting block at pos [" + relBlockX + "," + y + "," + relBlockZ + "] error: " + e.getMessage(), e);
getTopErrorLogged = true;
}
@@ -208,7 +208,7 @@ public class LodDataBuilder
// check if this block is visible from any direction
|| blockVisible(chunkWrapper, relBlockX, y, relBlockZ))
{
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
biome = newBiome;
blockState = newBlockState;
@@ -219,7 +219,7 @@ public class LodDataBuilder
}
}
}
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getInclusiveMinBuildHeight(), blockLight, skyLight));
dataSource.setSingleColumn(longs,
relBlockX + chunkOffsetX,
@@ -289,7 +289,7 @@ public class LodDataBuilder
{
return true;
}
if (testBlockPos.getY() < chunkWrapper.getMinBuildHeight() || testBlockPos.getY() > chunkWrapper.getMaxBuildHeight())
if (testBlockPos.getY() < chunkWrapper.getInclusiveMinBuildHeight() || testBlockPos.getY() > chunkWrapper.getExclusiveMaxBuildHeight())
{
return true;
}
@@ -131,16 +131,16 @@ public class FullDataSourceProviderV2
DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showFullDataUpdateStatus);
String dimensionName = level.getLevelWrapper().getDimensionName();
String levelId = level.getLevelWrapper().getDhIdentifier();
// start migrating any legacy data sources present in the background
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX + "["+dimensionName+"]", Config.Common.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore) null);
this.migrationThreadPool = ThreadUtil.makeRateLimitedThreadPool(1, MIGRATION_THREAD_NAME_PREFIX + "["+levelId+"]", Config.Common.MultiThreading.runTimeRatioForUpdatePropagatorThreads.get(), Thread.MIN_PRIORITY, (Semaphore) null);
this.migrationThreadPool.execute(this::convertLegacyDataSources);
// update propagation doesn't need to be run on the server since only the highest detail level is needed
//if (SharedApi.getEnvironment() != EWorldEnvironment.SERVER_ONLY) // TODO
//{
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+dimensionName+"]");
this.updateQueueProcessor = ThreadUtil.makeSingleThreadPool("Parent Update Queue ["+levelId+"]");
this.updateQueueProcessor.execute(this::runUpdateQueue);
//}
//else
@@ -347,8 +347,8 @@ public class FullDataSourceProviderV2
private void convertLegacyDataSources()
{
String dimensionName = this.level.getLevelWrapper().getDimensionName();
LOGGER.info("Attempting to migrate data sources for: ["+dimensionName+"]-["+this.saveDir+"]...");
String levelId = this.level.getLevelWrapper().getDhIdentifier();
LOGGER.info("Attempting to migrate data sources for: ["+levelId+"]-["+this.saveDir+"]...");
@@ -367,7 +367,7 @@ public class FullDataSourceProviderV2
this.showMigrationStartMessage();
LOGGER.info("deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources...");
LOGGER.info("deleting [" + levelId + "] - ["+totalDeleteCount+"] unused data sources...");
this.legacyDeletionCount = totalDeleteCount;
ArrayList<String> unusedDataPosList = this.legacyFileHandler.repo.getUnusedDataSourcePositionStringList(50);
@@ -385,7 +385,7 @@ public class FullDataSourceProviderV2
long endStart = System.currentTimeMillis();
long deleteTime = endStart - startTime;
LOGGER.info("Deleting [" + dimensionName + "] - [" + unusedCount + "/" + totalDeleteCount + "] in ["+deleteTime+"]ms ...");
LOGGER.info("Deleting [" + levelId + "] - [" + unusedCount + "/" + totalDeleteCount + "] in ["+deleteTime+"]ms ...");
// a slight delay is added to prevent accidentally locking the database when deleting a lot of rows
@@ -398,7 +398,7 @@ public class FullDataSourceProviderV2
}
catch (InterruptedException ignore){}
}
LOGGER.info("Done deleting [" + dimensionName + "] - ["+totalDeleteCount+"] unused data sources.");
LOGGER.info("Done deleting [" + levelId + "] - ["+totalDeleteCount+"] unused data sources.");
}
@@ -423,7 +423,7 @@ public class FullDataSourceProviderV2
int progressCount = 0;
while (!legacyDataSourceList.isEmpty() && this.migrationThreadRunning.get())
{
LOGGER.info("Migrating [" + dimensionName + "] - [" + progressCount + "/" + totalMigrationCount + "]...");
LOGGER.info("Migrating [" + levelId + "] - [" + progressCount + "/" + totalMigrationCount + "]...");
ArrayList<CompletableFuture<Void>> updateFutureList = new ArrayList<>();
for (int i = 0; i < legacyDataSourceList.size() && this.migrationThreadRunning.get(); i++)
@@ -485,7 +485,7 @@ public class FullDataSourceProviderV2
}
catch (Exception e)
{
LOGGER.info("migration stopped due to error for: ["+dimensionName+"]-["+this.saveDir+"], error: ["+e.getMessage()+"].", e);
LOGGER.info("migration stopped due to error for: ["+levelId+"]-["+this.saveDir+"], error: ["+e.getMessage()+"].", e);
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
@@ -493,13 +493,13 @@ public class FullDataSourceProviderV2
{
if (this.migrationThreadRunning.get())
{
LOGGER.info("migration complete for: ["+dimensionName+"]-["+this.saveDir+"].");
LOGGER.info("migration complete for: ["+levelId+"]-["+this.saveDir+"].");
this.showMigrationEndMessage(true);
this.migrationCount = 0;
}
else
{
LOGGER.info("migration stopped for: ["+dimensionName+"]-["+this.saveDir+"].");
LOGGER.info("migration stopped for: ["+levelId+"]-["+this.saveDir+"].");
this.showMigrationEndMessage(false);
this.migrationStoppedWithError = true;
}
@@ -526,9 +526,9 @@ public class FullDataSourceProviderV2
}
this.migrationStartMessageQueued = true;
String dimName = this.level.getLevelWrapper().getDimensionName();
String levelId = this.level.getLevelWrapper().getDhIdentifier();
ClientApi.INSTANCE.showChatMessageNextFrame(
"Old Distant Horizons data is being migrated for ["+dimName+"]. \n" +
"Old Distant Horizons data is being migrated for ["+levelId+"]. \n" +
"While migrating LODs may load slowly \n" +
"and DH world gen will be disabled. \n" +
"You can see migration progress in the F3 menu."
@@ -537,16 +537,16 @@ public class FullDataSourceProviderV2
private void showMigrationEndMessage(boolean success)
{
String dimName = this.level.getLevelWrapper().getDimensionName();
String levelId = this.level.getLevelWrapper().getDhIdentifier();
if (success)
{
ClientApi.INSTANCE.showChatMessageNextFrame("Distant Horizons data migration for ["+dimName+"] completed.");
ClientApi.INSTANCE.showChatMessageNextFrame("Distant Horizons data migration for ["+levelId+"] completed.");
}
else
{
ClientApi.INSTANCE.showChatMessageNextFrame(
"Distant Horizons data migration for ["+dimName+"] stopped. \n" +
"Distant Horizons data migration for ["+levelId+"] stopped. \n" +
"Some data may not have been migrated."
);
}
@@ -148,7 +148,7 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im
{
boolean oldQueueExists = this.worldGenQueueRef.compareAndSet(null, newWorldGenQueue);
LodUtil.assertTrue(oldQueueExists, "previous world gen queue is still here!");
LOGGER.info("Set world gen queue for level [" + this.level.getLevelWrapper().getDimensionName() + "].");
LOGGER.info("Set world gen queue for level [" + this.level.getLevelWrapper().getDhIdentifier() + "].");
}
@Override
@@ -80,14 +80,14 @@ public class ClientOnlySaveStructure implements ISaveStructure
if (newLevelWrapper instanceof IServerKeyedClientLevel)
{
IServerKeyedClientLevel keyedClientLevel = (IServerKeyedClientLevel) newLevelWrapper;
LOGGER.info("Loading level [" + newLevelWrapper.getDimensionName() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
LOGGER.info("Loading level [" + newLevelWrapper.getDhIdentifier() + "] with key: [" + keyedClientLevel.getServerLevelKey() + "].");
// This world was identified by the server directly, so we can know for sure which folder to use.
saveFolder = getSaveFolderFromDimensionName(keyedClientLevel.getServerLevelKey());
saveFolder = getSaveFolderByLevelId(keyedClientLevel.getServerLevelKey());
}
else
{
// get the default folder
saveFolder = getSaveFolderFromDimensionName(levelWrapper.getDimensionName());
saveFolder = getSaveFolderByLevelId(levelWrapper.getDhIdentifier());
}
// Allow API users to override the save folder
@@ -160,7 +160,7 @@ public class ClientOnlySaveStructure implements ISaveStructure
}
private static File getSaveFolderFromDimensionName(String dimensionName)
private static File getSaveFolderByLevelId(String dimensionName)
{
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
+ SERVER_DATA_FOLDER_NAME + File.separatorChar
@@ -1,100 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.file.subDimMatching;
import com.seibel.distanthorizons.core.config.Config;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* Contains data used to compare different sub LodDimensions.
* Sub Dimensions are the different folders under a dimension.
* For example: "\Distant_Horizons_server_data\server_1\dim_the_nether\6fb97c01-e4c7-4634-87db-36b1e490e4c3"
* is a sub dimension for the server "server_1" in the nether.
*
* @author James Seibel
* @version 2022-12-17
*/
public class SubDimCompare implements Comparable<SubDimCompare>
{
/**
* the maximum distance in blocks a player can be away from the
* given dimension and still be considered in the same place.
*/
public static int MAX_SIMILAR_PLAYER_POS_DISTANCE_IN_BLOCKS = 3;
public int equalDataPoints = 0;
public int totalDataPoints = 0;
public int playerPosDist = 0;
public File folder = null;
public SubDimCompare(int newEqualDataPoints, int newTotalDataPoints, int newPlayerPosDistance, File newSubDimFolder)
{
this.equalDataPoints = newEqualDataPoints;
this.totalDataPoints = newTotalDataPoints;
this.playerPosDist = newPlayerPosDistance;
this.folder = newSubDimFolder;
}
/** returns a number between 0 (no equal datapoint) and 1 (totally equal) */
public double getPercentEqual()
{
// its possible the comparison didn't find any data points
if (this.totalDataPoints != 0)
{
return (double) this.equalDataPoints / (double) this.totalDataPoints;
}
else
{
return 0;
}
}
@Override
public int compareTo(@NotNull SubDimCompare other)
{
if (this.equalDataPoints != other.equalDataPoints)
{
// compare based on data points
return Double.compare(this.getPercentEqual(), other.getPercentEqual());
}
else
{
// break ties based on player position
return Integer.compare(this.playerPosDist, other.playerPosDist);
}
}
/** Returns true if this sub dimension is close enough to be considered a valid sub dimension */
public boolean isValidSubDim()
{
double minimumSimilarityRequired = 0.5;//Config.Client.Advanced.Multiplayer.multiverseSimilarityRequiredPercent.get();
return this.getPercentEqual() >= minimumSimilarityRequired
|| this.playerPosDist <= MAX_SIMILAR_PLAYER_POS_DISTANCE_IN_BLOCKS;
}
@Override
public String toString() { return this.equalDataPoints + "/" + this.totalDataPoints + ": " + this.getPercentEqual() + " playerPos: " + this.playerPosDist; }
}
@@ -1,386 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.file.subDimMatching;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.generation.DhLightingEngine;
import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Used to allow multiple levels using the same dimension type. <br/>
* This is specifically needed for servers running the Multiverse plugin (or similar).
*
* @author James Seibel
* @version 12-17-2022
*/
@Deprecated
public class SubDimensionLevelMatcher implements AutoCloseable
{
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
public static final Logger LOGGER = DhLoggerBuilder.getLogger();
//new ConfigBasedLogger(LogManager.getLogger(),
//() -> Config.Common.Logging.logFileSubDimEvent.get());
private final ExecutorService matcherThread = ThreadUtil.makeSingleThreadPool("Sub Dimension Matcher");
private SubDimensionPlayerData playerData = null;
private SubDimensionPlayerData firstSeenPlayerData = null;
/** If true the LodDimensionFileHelper is attempting to determine the folder for this dimension */
private final AtomicBoolean determiningWorldFolder = new AtomicBoolean(false);
private final IClientLevelWrapper currentClientLevel;
private volatile File foundLevelFile = null;
private final List<File> potentialLevelFolders;
private final File levelsFolder;
//=============//
// constructor //
//=============//
public SubDimensionLevelMatcher(IClientLevelWrapper targetLevel, File levelsFolder, List<File> potentialLevelFolders)
{
this.currentClientLevel = targetLevel;
this.potentialLevelFolders = potentialLevelFolders;
this.levelsFolder = levelsFolder;
if (potentialLevelFolders.size() == 0)
{
String newId = UUID.randomUUID().toString();
LOGGER.info("No potential level files found. Creating a new sub dimension with the ID ["+ StringUtil.shortenString(newId, 8)+"]...");
this.foundLevelFile = this.CreateSubDimFolder(newId);
}
}
//==============//
// level finder //
//==============//
public boolean isFindingLevel(ILevelWrapper level) { return Objects.equals(level, this.currentClientLevel); }
/** May return null if the level isn't known yet */
public File tryGetLevel()
{
this.tryGetLevelInternalAsync();
return this.foundLevelFile;
}
private void tryGetLevelInternalAsync()
{
if (this.foundLevelFile != null)
{
return;
}
// prevent multiple threads running at the same time
if (this.determiningWorldFolder.getAndSet(true))
{
return;
}
this.matcherThread.submit(() ->
{
try
{
// attempt to get the file handler
File saveDir = this.attemptToDetermineSubDimensionFolder();
if (saveDir != null)
{
this.foundLevelFile = saveDir;
}
}
catch (IOException e)
{
LOGGER.error("Unable to set the dimension file handler for level [" + this.currentClientLevel + "]. Error: ", e);
}
finally
{
// make sure we unlock this method
this.determiningWorldFolder.set(false);
}
});
}
/**
* Currently this method checks a single chunk (where the player is)
* and compares it against the same chunk position in the other dimension worlds to
* guess which world the player is in.
*
* @throws IOException if the folder doesn't exist or can't be accessed
*/
public File attemptToDetermineSubDimensionFolder() throws IOException
{
// Update PlayerData
SubDimensionPlayerData newPlayerData = SubDimensionPlayerData.tryGetPlayerData(MC_CLIENT);
if (newPlayerData != null)
{
if (this.firstSeenPlayerData == null)
{
this.firstSeenPlayerData = newPlayerData;
}
this.playerData = newPlayerData;
}
//================================//
// generate a LOD to test against //
//================================//
// attempt to get a chunk at the player's pos
IChunkWrapper newlyLoadedChunk = MC_CLIENT.getWrappedClientLevel().tryGetChunk(new DhChunkPos(this.playerData.playerBlockPos));
if (newlyLoadedChunk == null)
{
return null;
}
DhLightingEngine.INSTANCE.lightChunk(newlyLoadedChunk, new ArrayList<>(), MC_CLIENT.getWrappedClientLevel().hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT);
// build the chunk LOD
FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk);
// convert to a data source for easier comparing
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos));
newDataSource.update(newChunkSizedFullDataView);
//================================//
// test each known sub-dim folder //
//================================//
// log the start of this attempt
LOGGER.info("Attempting to determine sub-dimension for [" + MC_CLIENT.getWrappedClientLevel().getDimensionName() + "]");
LOGGER.info("Player block pos in dimension: [" + this.playerData.playerBlockPos.getX() + "," + this.playerData.playerBlockPos.getY() + "," + this.playerData.playerBlockPos.getZ() + "]");
LOGGER.info("Potential Sub Dimension folders: [" + this.potentialLevelFolders.size() + "]");
SubDimCompare mostSimilarSubDim = null;
for (File testLevelFolder : this.potentialLevelFolders)
{
LOGGER.info("Testing level folder: [" + StringUtil.shortenString(testLevelFolder.getName(), 8) + "]");
FullDataSourceV2 testFullDataSource = null;
try
{
// get the data source to compare against
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false, null))
{
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos)).join();
if (testFullDataSource == null)
{
continue;
}
}
// confirm both data sources have the same section pos
long newSectionChunkPos = DhSectionPos.convertToDetailLevel(newDataSource.getPos(), DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
long testSectionChunkPos = DhSectionPos.convertToDetailLevel(testFullDataSource.getPos(), DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
LodUtil.assertTrue(newSectionChunkPos == testSectionChunkPos, "data source positions don't match");
// compare the data sources
int equalDataPoints = 0;
int totalDataPointCount = 0;
for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{
for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
{
LongArrayList newColumn = newDataSource.get(x, z);
LongArrayList testColumn = testFullDataSource.get(x, z);
if (newColumn != null && testColumn != null)
{
// compare each data point in the column
FullDataPointIdMap newDataMap = newDataSource.mapping;
FullDataPointIdMap testDataMap = testFullDataSource.mapping;
// use min to prevent going out of bounds
int minColumnIndex = Math.min(newColumn.size(), testColumn.size());
for (int i = 0; i < minColumnIndex; i++)
{
long newDataPoint = newColumn.getLong(i);
long testDataPoint = testColumn.getLong(i);
int newId = FullDataPointUtil.getId(newDataPoint);
int testId = FullDataPointUtil.getId(testDataPoint);
// bottom Y
int newBottom = FullDataPointUtil.getBottomY(newDataPoint);
int testBottom = FullDataPointUtil.getBottomY(testDataPoint);
if (newBottom == testBottom)
{
equalDataPoints++;
}
totalDataPointCount++;
// height
int newHeight = FullDataPointUtil.getHeight(newDataPoint);
int testHeight = FullDataPointUtil.getHeight(testDataPoint);
if (newHeight == testHeight)
{
equalDataPoints++;
}
totalDataPointCount++;
// biome
IBiomeWrapper newBiome = newDataMap.getBiomeWrapper(newId);
IBiomeWrapper testBiome = testDataMap.getBiomeWrapper(testId);
if (newBiome.equals(testBiome))
{
equalDataPoints++;
}
totalDataPointCount++;
// block
IBlockStateWrapper newBlock = newDataMap.getBlockStateWrapper(newId);
IBlockStateWrapper testBlock = testDataMap.getBlockStateWrapper(testId);
if (newBlock.equals(testBlock))
{
equalDataPoints++;
}
totalDataPointCount++;
// ignore light values
// since we are using the DH lighting engine and only 1 chunk the values will never be the same
}
}
else if (newColumn != null)
{
// missing test column
totalDataPointCount += newColumn.size();
}
else
{
// new column present, test absent, can't compare
}
}
}
// get the player data for this dimension folder
SubDimensionPlayerData testPlayerData = new SubDimensionPlayerData(testLevelFolder);
LOGGER.info("Last known player pos: [" + testPlayerData.playerBlockPos.getX() + "," + testPlayerData.playerBlockPos.getY() + "," + testPlayerData.playerBlockPos.getZ() + "]");
// check if the block positions are close
int playerBlockDist = testPlayerData.playerBlockPos.getManhattanDistance(this.playerData.playerBlockPos);
LOGGER.info("Player block position distance between saved sub dimension and first seen is [" + playerBlockDist + "]");
// determine if this world is closer to the newly loaded world
SubDimCompare subDimCompare = new SubDimCompare(equalDataPoints, totalDataPointCount, playerBlockDist, testLevelFolder);
if (mostSimilarSubDim == null || subDimCompare.compareTo(mostSimilarSubDim) > 0)
{
mostSimilarSubDim = subDimCompare;
}
String subDimShortName = StringUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging
String equalPercent = StringUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5);
LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")");
}
catch (Exception e)
{
// this sub dimension isn't formatted correctly
// for now we are just assuming it is an unrelated file
LOGGER.warn("Error checking level: "+e.getMessage(), e);
}
finally
{
if (testFullDataSource != null)
{
try { testFullDataSource.close(); } catch (Exception ignore) {}
}
}
}
//================================//
// return the found sub dimension //
//================================//
// the first seen player data is no longer needed, the sub dimension has been determined
this.firstSeenPlayerData = null;
if (mostSimilarSubDim != null && mostSimilarSubDim.isValidSubDim())
{
// we found a sub dim folder that is similar, use it
LOGGER.info("Sub Dimension set to: [" + StringUtil.shortenString(mostSimilarSubDim.folder.getName(), 8) + "...] with an equality of [" + mostSimilarSubDim.getPercentEqual() + "]");
return mostSimilarSubDim.folder;
}
else
{
// no sub dim folder, create a new one
String newId = UUID.randomUUID().toString();
double highestEqualityPercent = mostSimilarSubDim != null ? mostSimilarSubDim.getPercentEqual() : 0;
String message = "No suitable sub dimension found. The highest equality was [" + StringUtil.shortenString(highestEqualityPercent + "", 5) + "]. Creating a new sub dimension with ID: " + StringUtil.shortenString(newId, 8) + "...";
LOGGER.info(message);
File folder = this.CreateSubDimFolder(newId);
folder.mkdirs();
return folder;
}
}
private File CreateSubDimFolder(String subDimId) { return new File(this.levelsFolder.getPath() + File.separatorChar + this.currentClientLevel.getDimensionName(), subDimId); }
@Override
public void close() { this.matcherThread.shutdownNow(); }
}
@@ -1,145 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.file.subDimMatching;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import org.jetbrains.annotations.Nullable;
import java.io.File;
/**
* Data container for any player data we can use to differentiate one dimension from another.
*
* @author James Seibel
* @version 2022-3-26
*/
public class SubDimensionPlayerData
{
public static final IWrapperFactory FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
public static final String PLAYER_DATA_FILE_NAME = "_playerData.toml";
public static final String PLAYER_BLOCK_POS_X_PATH = "playerBlockPosX";
public static final String PLAYER_BLOCK_POS_Y_PATH = "playerBlockPosY";
public static final String PLAYER_BLOCK_POS_Z_PATH = "playerBlockPosZ";
public DhBlockPos playerBlockPos;
// not implemented yet
public static final String WORLD_SPAWN_POS_X_PATH = "worldSpawnBlockPosX";
public static final String WORLD_SPAWN_POS_Y_PATH = "worldSpawnBlockPosY";
public static final String WORLD_SPAWN_POS_Z_PATH = "worldSpawnBlockPosZ";
/**
* The client world has access to a spawn point, so this should be possible to fill in.
* I'm not sure what this will look like for worlds that don't have a spawn point.
*/
public DhBlockPos worldSpawnPointBlockPos;
@Nullable
public static SubDimensionPlayerData tryGetPlayerData(IMinecraftClientWrapper mcClient)
{
if (!mcClient.playerExists())
{
return null;
}
try
{
return new SubDimensionPlayerData(mcClient);
}
catch (RuntimeException e)
{
// Player no longer exists due to concurrency. FIXME: Remember here is called not on main thread!!!
return null;
}
}
private SubDimensionPlayerData(IMinecraftClientWrapper mc)
{
this.updateData(mc);
}
public SubDimensionPlayerData(File dimensionFolder)
{
File file = getFileForDimensionFolder(dimensionFolder);
try (CommentedFileConfig toml = CommentedFileConfig.builder(file).build())
{
toml.load();
// get the player block pos if it is specified
if (toml.contains(PLAYER_BLOCK_POS_X_PATH)
&& toml.contains(PLAYER_BLOCK_POS_Y_PATH)
&& toml.contains(PLAYER_BLOCK_POS_Z_PATH))
{
int x = toml.getIntOrElse(PLAYER_BLOCK_POS_X_PATH, 0);
int y = toml.getIntOrElse(PLAYER_BLOCK_POS_Y_PATH, 0);
int z = toml.getIntOrElse(PLAYER_BLOCK_POS_Z_PATH, 0);
this.playerBlockPos = new DhBlockPos(x, y, z);
}
else
{
this.playerBlockPos = new DhBlockPos(0, 0, 0);
}
}
}
public static File getFileForDimensionFolder(File file) { return new File(file.getPath() + File.separatorChar + PLAYER_DATA_FILE_NAME); }
/** Should be called often to make sure this object is up to date with the player's info */
public void updateData(IMinecraftClientWrapper mc)
{
this.playerBlockPos = mc.getPlayerBlockPos();
if (this.playerBlockPos == null)
{
throw new RuntimeException("No player block pos!");
}
}
/** Writes everything from this object to the file given. */
public void toTomlFile(CommentedFileConfig toml)
{
// player block pos
toml.add(PLAYER_BLOCK_POS_X_PATH, this.playerBlockPos.getX());
toml.add(PLAYER_BLOCK_POS_Y_PATH, this.playerBlockPos.getY());
toml.add(PLAYER_BLOCK_POS_Z_PATH, this.playerBlockPos.getZ());
toml.save();
}
@Override
public String toString()
{
return "PlayerBlockPos: [" + this.playerBlockPos.getX() + "," + this.playerBlockPos.getY() + "," + this.playerBlockPos.getZ() + "]";
}
}
@@ -204,7 +204,7 @@ public class DhLightingEngine
IBlockStateWrapper previousBlockState = null;
int maxY = chunk.getMaxNonEmptyHeight();
int minY = chunk.getMinBuildHeight();
int minY = chunk.getInclusiveMinBuildHeight();
// get the adjacent chunk's sky lights
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) // relative block pos
@@ -322,7 +322,7 @@ public class DhLightingEngine
continue;
}
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getMaxBuildHeight())
if (relNeighbourBlockPos.getY() < neighbourChunk.getMinNonEmptyHeight() || relNeighbourBlockPos.getY() > neighbourChunk.getExclusiveMaxBuildHeight())
{
// the light pos is outside the chunk's min/max height,
// this can happen if given a chunk that hasn't finished generating
@@ -26,7 +26,11 @@ import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.generation.tasks.*;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.InProgressWorldGenTaskGroup;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTask;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenTaskGroup;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
@@ -191,7 +195,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
try
{
// loop until the generator is shutdown
while (!Thread.interrupted() && !DhApiWorldProxy.INSTANCE.getReadOnly())
while (!Thread.interrupted() && DhApiWorldProxy.INSTANCE.worldLoaded() && !DhApiWorldProxy.INSTANCE.getReadOnly())
{
this.generator.preGeneratorTaskStart();
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Objects;
@@ -46,27 +47,44 @@ public class JarUtils
@Nullable
public static File jarFile = null;
//=============//
// constructor //
//=============//
static
{
try
{
jarFile = new File(JarUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI()); // Always safe
// this will fail in development environments due to how the jars are compiled
// this may also fail in forge production
URI jarUri = JarUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI();
jarFile = new File(jarUri);
}
catch (Exception e)
catch (Exception eGetUri)
{
try
{
LOGGER.warn("Unable to get the jar file, trying backup method... Error: "+e.getMessage(), e);
jarFile = SingletonInjector.INSTANCE.get(IModChecker.class).modLocation(ModInfo.ID);
}
catch (Exception f)
catch (Exception eGetModLoc)
{
LOGGER.warn("Backup jar file getter failed. Error: "+f.getMessage(), f);
// only log if both methods fail since it isn't a problem unless both
// methods fail
LOGGER.warn("Unable to get jar file via URI or Mod Checker Location.");
LOGGER.warn("URI Error: ["+ eGetUri.getMessage()+"]", eGetUri);
LOGGER.warn("Mod Location Error: ["+ eGetModLoc.getMessage()+"]", eGetModLoc);
}
}
}
//=========//
// methods //
//=========//
/**
* Gets the URI of a resource
*
@@ -75,9 +93,7 @@ public class JarUtils
* @throws URISyntaxException If the file doesnt exist
*/
public static URI accessFileURI(String resource) throws URISyntaxException
{
return Objects.requireNonNull(JarUtils.class.getResource(resource)).toURI();
}
{ return Objects.requireNonNull(JarUtils.class.getResource(resource)).toURI(); }
/**
* Get a file within the mods resources
@@ -146,7 +162,7 @@ public class JarUtils
while ((bytesCount = fis.read(byteArray)) != -1)
{
digest.update(byteArray, 0, bytesCount);
} ;
}
//close the stream; We don't need it now.
fis.close();
@@ -174,7 +190,8 @@ public class JarUtils
/** Please use the EPlatform enum instead */
@Deprecated
public static OperatingSystem getOperatingSystem()
{ // Get the os and turn it into that enum
{
// Get the os and turn it into that enum
switch (EPlatform.get())
{
case WINDOWS:
@@ -114,7 +114,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
continue;
}
NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Fulfilled request group ["+entry.getKey()+"]");
NETWORK_LOGGER.debug("[" + this.serverLevelWrapper.getDhIdentifier() + "] Fulfilled request group [" + entry.getKey() + "]");
// Make this group unavailable for adding into
this.requestGroupByPos.remove(entry.getKey());
@@ -230,7 +230,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
FullDataSourceRequestMessage requestMessage = requestGroup.requestMessages.remove(msg.futureId);
if (requestGroup.requestMessages.isEmpty())
{
NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Cancelled request group ["+DhSectionPos.toString(requestMessage.sectionPos)+"].");
NETWORK_LOGGER.debug("[" + this.serverLevelWrapper.getDhIdentifier() + "] Cancelled request group [" + DhSectionPos.toString(requestMessage.sectionPos) + "].");
this.requestGroupByPos.remove(requestMessage.sectionPos);
this.serverside.fullDataFileHandler.removeRetrievalRequestIf(pos -> pos == requestMessage.sectionPos);
}
@@ -310,7 +310,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
{
DataSourceRequestGroup newGroup = new DataSourceRequestGroup();
this.tryFulfillDataSourceRequestGroup(newGroup, pos);
NETWORK_LOGGER.debug("["+this.serverLevelWrapper.getDimensionName()+"] Created request group for pos ["+DhSectionPos.toString(pos)+"].");
NETWORK_LOGGER.debug("[" + this.serverLevelWrapper.getDhIdentifier() + "] Created request group for pos [" + DhSectionPos.toString(pos) + "].");
return newGroup;
});
@@ -356,8 +356,8 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
new InvalidLevelException(
"Generation not allowed. " +
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
"player dimension: ["+message.getSession().serverPlayer.getLevel().getDimensionName()+"], " +
"handler dimension: ["+this.getLevelWrapper().getDimensionName()+"]"
"player dimension: [" + message.getSession().serverPlayer.getLevel().getDhIdentifier() + "], " +
"handler dimension: [" + this.getLevelWrapper().getDhIdentifier() + "]"
)
);
}
@@ -450,36 +450,44 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
LOGGER.warn("Unable to send FullDataPartialUpdateMessage - getNetworkCompressionExecutor() is null");
return this.getFullDataProvider().updateDataSourceAsync(data);
}
CompletableFuture.runAsync(() ->
try
{
Objects.requireNonNull(this.beaconBeamRepo);
try (FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos())))
CompletableFuture.runAsync(() ->
{
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
Objects.requireNonNull(this.beaconBeamRepo);
try (FullDataPayload payload = new FullDataPayload(data, this.beaconBeamRepo.getAllBeamsForPos(data.getPos())))
{
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
for (ServerPlayerState serverPlayerState : this.serverPlayerStateManager.getReadyPlayers())
{
continue;
}
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
&& distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
if (serverPlayerState.getServerPlayer().getLevel() != this.serverLevelWrapper)
{
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
});
continue;
}
if (!serverPlayerState.sessionConfig.isRealTimeUpdatesEnabled())
{
continue;
}
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer >= serverPlayerState.getServerPlayer().getViewDistance()
&& distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{
serverPlayerState.fullDataPayloadSender.sendInChunks(payload, () ->
{
serverPlayerState.networkSession.sendMessage(new FullDataPartialUpdateMessage(this.serverLevelWrapper, payload));
});
}
}
}
}
}, executor);
}, executor);
}
catch (RejectedExecutionException ignore)
{
// the executor was shut down, it should be back up shortly and able to accept new jobs
}
return this.getFullDataProvider().updateDataSourceAsync(data);
@@ -294,7 +294,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
@Override
public void addDebugMenuStringsToList(List<String> messageList)
{
String dimName = this.levelWrapper.getDimensionName();
String dimName = this.levelWrapper.getDhIdentifier();
boolean rendering = this.clientside.isRendering();
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
@@ -338,7 +338,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
//================//
@Override
public String toString() { return "DhClientLevel{"+this.getClientLevelWrapper().getDimensionName()+"}"; }
public String toString() { return "DhClientLevel{" + this.getClientLevelWrapper().getDhIdentifier() + "}"; }
@Override
public void close()
@@ -132,7 +132,7 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
public void addDebugMenuStringsToList(List<String> messageList)
{
// header
String dimName = this.serverLevelWrapper.getDimensionName();
String dimName = this.serverLevelWrapper.getDhIdentifier();
boolean rendering = this.clientside.isRendering();
messageList.add("["+dimName+"] rendering: "+(rendering ? "yes" : "no"));
@@ -78,7 +78,7 @@ public class DhServerLevel extends AbstractDhServerLevel
@Override
public void addDebugMenuStringsToList(List<String> messageList)
{
messageList.add("[" + this.serverLevelWrapper.getDimensionName() + "]");
messageList.add("[" + this.serverLevelWrapper.getDhIdentifier() + "]");
super.addDebugMenuStringsToList(messageList);
}
@@ -77,7 +77,6 @@ public class F3Screen
ThreadPoolExecutor updatePool = ThreadPoolUtil.getUpdatePropagatorExecutor();
ThreadPoolExecutor lodBuilderPool = ThreadPoolUtil.getChunkToLodBuilderExecutor();
ThreadPoolExecutor bufferBuilderPool = ThreadPoolUtil.getBufferBuilderExecutor();
ThreadPoolExecutor bufferUploaderPool = ThreadPoolUtil.getBufferUploaderExecutor();
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
Iterable<? extends IDhLevel> levelIterator = world.getAllLoadedLevels();
@@ -96,7 +95,6 @@ public class F3Screen
messageList.add(getThreadPoolStatString("Update Propagator", updatePool));
messageList.add(getThreadPoolStatString("LOD Builder", lodBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Builder", bufferBuilderPool));
messageList.add(getThreadPoolStatString("Buffer Uploader", bufferUploaderPool));
messageList.add("");
// chunk updates
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
@@ -134,7 +134,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
public synchronized boolean tick(DhBlockPos2D targetPos)
{
if (DhApiWorldProxy.INSTANCE.getReadOnly())
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
{
return false;
}
@@ -302,7 +302,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
public void addDebugMenuStringsToList(List<String> messageList)
{
messageList.add(this.getQueueName() + " [" + this.level.getClientLevelWrapper().getDimensionName() + "]");
messageList.add(this.getQueueName() + " [" + this.level.getClientLevelWrapper().getDhIdentifier() + "]");
messageList.add("Requests: " + this.finishedRequests + " / " + (this.getWaitingTaskCount() + this.finishedRequests.get()) + " (failed: " + this.failedRequests + ", rate limit: " + this.getRequestRateLimit() + ")");
}
@@ -17,7 +17,7 @@ public interface ILevelRelatedMessage
return this.getLevelName().equals(((IServerLevelWrapper) levelWrapper).getKeyedLevelDimensionName());
}
return this.getLevelName().equals(levelWrapper.getDimensionName());
return this.getLevelName().equals(levelWrapper.getDhIdentifier());
}
}
@@ -48,7 +48,7 @@ public class FullDataSourceRequestMessage extends AbstractTrackableMessage imple
public FullDataSourceRequestMessage() {}
public FullDataSourceRequestMessage(ILevelWrapper levelWrapper, long sectionPos, @Nullable Long clientTimestamp)
{
this.levelName = levelWrapper.getDimensionName();
this.levelName = levelWrapper.getDhIdentifier();
this.sectionPos = sectionPos;
this.clientTimestamp = clientTimestamp;
}
@@ -147,7 +147,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
catch (Exception e)
{
LOGGER.error("Quad Tree tick exception for dimension: " + this.level.getLevelWrapper().getDimensionName() + ", exception: " + e.getMessage(), e);
LOGGER.error("Quad Tree tick exception for level: [" + this.level.getLevelWrapper().getDhIdentifier() + "], error: [" + e.getMessage() + "].", e);
}
finally
{
@@ -274,10 +274,11 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
this.bufferUploadFuture = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder);
return this.bufferUploadFuture.thenCompose((buffer) ->
{
// needed to clean up the old data
ColumnRenderBuffer previousBuffer = this.renderBuffer;
// upload complete, clean up the old data if
this.renderBuffer = buffer;
// upload complete
this.renderBuffer = buffer.buffersUploaded ? buffer : null;
this.buildAndUploadRenderDataToGpuFuture = null;
this.bufferBuildFuture = null;
@@ -1,420 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.render.fog;
import com.seibel.distanthorizons.api.enums.rendering.*;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* This holds fog related settings and
* creates the fog related shader code.
*
* @author Leetom
* @author James Seibel
* @version 2022-11-24
*/
// TODO: Move lots out of here, there should be a listener hooked onto the config to update the shader
public class LodFogConfig
{
private static final IOptifineAccessor OPTIFINE = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class);
public static final boolean DEBUG_DUMP_GENERATED_CODE = false;
public final FogSettings farFogSetting;
public final FogSettings heightFogSetting;
public final EDhApiHeightFogMixMode heightFogMixMode;
public final EDhApiHeightFogMode heightFogMode;
public final float heightFogHeight;
// TODO: Move these out of here
public final int earthCurveRatio;
// Noise Values
public final boolean noiseEnable;
public final int noiseSteps;
public final float noiseIntensity;
public final int noiseDropoff;
public static LodFogConfig generateFogConfig() { return new LodFogConfig(); }
/** sets all fog options from the config */
private LodFogConfig()
{
// TODO: Move these out of here
this.earthCurveRatio = Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get();
this.noiseEnable = Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get();
this.noiseSteps = Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps.get();
this.noiseIntensity = Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity.get().floatValue();
this.noiseDropoff = Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get();
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get())
{
// fog should be drawn
this.farFogSetting = new FogSettings(
Config.Client.Advanced.Graphics.Fog.farFogStart.get(),
Config.Client.Advanced.Graphics.Fog.farFogEnd.get(),
Config.Client.Advanced.Graphics.Fog.farFogMin.get(),
Config.Client.Advanced.Graphics.Fog.farFogMax.get(),
Config.Client.Advanced.Graphics.Fog.farFogDensity.get(),
Config.Client.Advanced.Graphics.Fog.farFogFalloff.get()
);
this.heightFogMixMode = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMixMode.get();
if (this.heightFogMixMode == EDhApiHeightFogMixMode.IGNORE_HEIGHT
|| this.heightFogMixMode == EDhApiHeightFogMixMode.BASIC)
{
// basic fog mixing
this.heightFogSetting = null;
this.heightFogMode = null;
this.heightFogHeight = 0.f;
}
else
{
// advanced fog mixing
this.heightFogSetting = new FogSettings(
Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity.get(),
Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd.get(),
Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin.get(),
Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax.get(),
Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity.get(),
Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff.get()
);
this.heightFogMode = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMode.get();
if (this.heightFogMode.basedOnCamera)
{
this.heightFogHeight = 0.f;
}
else
{
this.heightFogHeight = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight.get().floatValue();
}
}
}
else
{
// fog disabled
this.farFogSetting = null;
this.heightFogMixMode = null;
this.heightFogMode = null;
this.heightFogSetting = null;
this.heightFogHeight = 0.f;
}
}
public StringBuilder loadAndProcessFragShader(String path, boolean absoluteFilePath)
{
StringBuilder stringBuilder = this.makeRuntimeDefine();
this.generateRuntimeShaderCode(Shader.loadFile(path, absoluteFilePath, stringBuilder));
if (DEBUG_DUMP_GENERATED_CODE)
{
try (FileOutputStream file = new FileOutputStream("debugGenerated.frag", false))
{
file.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
GLProxy.GL_LOGGER.info("Debug dumped generated code to debugGenerated.frag for {}", path);
}
catch (IOException e)
{
GLProxy.GL_LOGGER.warn("Failed to debug dump generated code to file for {}", path);
}
}
return stringBuilder;
}
/** Generates the necessary constants for a fragment shader */
private void generateRuntimeShaderCode(StringBuilder str)
{
str.append("// =======RUNTIME GENERATED CODE SECTION======== //\n");
// Generate method: float getNearFogThickness(float dist);
str.append("" +
"float getNearFogThickness(float dist) \n" +
"{ \n" +
" return linearFog(dist, uNearFogStart, uNearFogLength, 0.0, 1.0); \n" +
"} \n");
if (this.farFogSetting == null)
{
str.append("\n" +
"float getFarFogThickness(float dist) { return 0.0; } \n" +
"float getHeightFogThickness(float dist) { return 0.0; } \n" +
"float calculateFarFogDepth(float horizontal, float dist, float uNearFogStart) { return 0.0; } \n" +
"float calculateHeightFogDepth(float vertical, float realY) { return 0.0; } \n" +
"float mixFogThickness(float near, float far, float height) \n" +
"{ \n" +
" return 0.0; \n" +
"} \n\n");
}
else
{
// Generate method: float getFarFogThickness(float dist);
str.append("" +
"float getFarFogThickness(float dist) \n" +
"{ \n" +
getFarFogMethod(this.farFogSetting.fogType) + "\n" +
"} \n");
// Generate method: float getHeightFogThickness(float dist);
str.append("" +
"float getHeightFogThickness(float dist) \n" +
"{ \n" +
(this.heightFogSetting != null ? getHeightFogMethod(this.heightFogSetting.fogType) : " return 0.0;") + "\n" +
"} \n");
// Generate method: float calculateHeightFogDepth(float vertical, float realY);
str.append("" +
"float calculateHeightFogDepth(float vertical, float realY) \n" +
"{ \n" +
(this.heightFogSetting != null ? getHeightDepthMethod(this.heightFogMode, this.heightFogHeight) : " return 0.0;") + "\n" +
"} \n");
// Generate method: calculateFarFogDepth(float horizontal, float dist, float uNearFogStart);
str.append("" +
"float calculateFarFogDepth(float horizontal, float dist, float uNearFogStart) \n" +
"{ \n" +
" return " + (this.heightFogMixMode == EDhApiHeightFogMixMode.BASIC ?
"(dist - uNearFogStart)/(1.0 - uNearFogStart);" :
"(horizontal - uNearFogStart)/(1.0 - uNearFogStart);") +
"} \n");
// Generate method: float mixFogThickness(float near, float far, float height);
str.append("" +
"float mixFogThickness(float near, float far, float height) \n" +
"{ \n" +
getMixFogLine(this.heightFogMixMode) + "\n" +
"} \n");
}
}
//=================//
// shader creation //
// helper methods //
//=================//
private StringBuilder makeRuntimeDefine()
{
StringBuilder str = new StringBuilder();
str.append("// =======RUNTIME GENERATED DEFINE SECTION======== //\n");
str.append("#version 150 core\n");
FogSettings activeFarFogSetting = this.farFogSetting != null ? this.farFogSetting : FogSettings.EMPTY;
FogSettings activeHeightFogSetting = this.heightFogSetting != null ? this.heightFogSetting : FogSettings.EMPTY;
str.append("\n" +
"#define farFogStart " + activeFarFogSetting.start + "\n" +
"#define farFogLength " + (activeFarFogSetting.end - activeFarFogSetting.start) + "\n" +
"#define farFogMin " + activeFarFogSetting.min + "\n" +
"#define farFogRange " + (activeFarFogSetting.max - activeFarFogSetting.min) + "\n" +
"#define farFogDensity " + activeFarFogSetting.density + "\n" +
"\n" +
"#define heightFogStart " + activeHeightFogSetting.start + "\n" +
"#define heightFogLength " + (activeHeightFogSetting.end - activeHeightFogSetting.start) + "\n" +
"#define heightFogMin " + activeHeightFogSetting.min + "\n" +
"#define heightFogRange " + (activeHeightFogSetting.max - activeHeightFogSetting.min) + "\n" +
"#define heightFogDensity " + activeHeightFogSetting.density + "\n" +
"\n");
str.append("// =======RUNTIME END======== //\n");
return str;
}
private static String getFarFogMethod(EDhApiFogFalloff fogType)
{
switch (fogType)
{
case LINEAR:
return "return linearFog(dist, farFogStart, farFogLength, farFogMin, farFogRange);\n";
case EXPONENTIAL:
return "return exponentialFog(dist, farFogStart, farFogLength, farFogMin, farFogRange, farFogDensity);\n";
case EXPONENTIAL_SQUARED:
return "return exponentialSquaredFog(dist, farFogStart, farFogLength, farFogMin, farFogRange, farFogDensity);\n";
default:
throw new IllegalArgumentException("FogType [" + fogType + "] not implemented for [getFarFogMethod].");
}
}
private static String getHeightDepthMethod(EDhApiHeightFogMode heightMode, float heightFogHeight)
{
String str = "";
if (!heightMode.basedOnCamera)
{
str = " vertical = realY - (" + heightFogHeight + ");\n";
}
if (heightMode.below && heightMode.above)
{
str += " return abs(vertical);\n";
}
else if (heightMode.below)
{
str += " return -vertical;\n";
}
else if (heightMode.above)
{
str += " return vertical;\n";
}
else
{
str += " return 0;\n";
}
return str;
}
/**
* Returns the method call for the given fog type. <br>
* Example: <br>
* <code>" return linearFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange);"</code>
*/
private static String getHeightFogMethod(EDhApiFogFalloff fogType)
{
switch (fogType)
{
case LINEAR:
return " return linearFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange);\n";
case EXPONENTIAL:
return " return exponentialFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange, heightFogDensity);\n";
case EXPONENTIAL_SQUARED:
return " return exponentialSquaredFog(dist, heightFogStart, heightFogLength, heightFogMin, heightFogRange, heightFogDensity);\n";
default:
throw new IllegalArgumentException("FogType [" + fogType + "] not implemented for [getHeightFogMethod].");
}
}
/**
* creates a line in the format <br>
* <code>" return max(1.0-near, far);" </code>
*/
private static String getMixFogLine(EDhApiHeightFogMixMode heightFogMode)
{
String str = " return ";
switch (heightFogMode)
{
case BASIC:
case IGNORE_HEIGHT:
str += "near * far;\n";
break;
case ADDITION:
str += "near * (far + height);\n";
break;
case MAX:
str += "near * max(far, height);\n";
break;
case INVERSE_MULTIPLY:
str += "near * (1.0 - (1.0-far)*(1.0-height));\n";
break;
case MULTIPLY:
str += "near * far * height;\n";
break;
case LIMITED_ADDITION:
str += "near * (far + max(far, height));\n";
break;
case MULTIPLY_ADDITION:
str += "near * (far + far*height);\n";
break;
case INVERSE_MULTIPLY_ADDITION:
str += "near * (far + 1.0 - (1.0-far)*(1.0-height));\n";
break;
case AVERAGE:
str += "near * (far*0.5 + height*0.5);\n";
break;
default:
throw new IllegalArgumentException("FogType [" + heightFogMode + "] not implemented for [getMixFogMethod].");
}
return str;
}
//========================//
// default object methods //
//========================//
@Override
public boolean equals(Object other)
{
if (this == other)
{
return true;
}
else if (other == null || this.getClass() != other.getClass())
{
return false;
}
else
{
LodFogConfig that = (LodFogConfig) other;
return Float.compare(that.heightFogHeight, this.heightFogHeight) == 0 &&
Objects.equals(this.farFogSetting, that.farFogSetting) &&
Objects.equals(this.heightFogSetting, that.heightFogSetting) && this.heightFogMixMode == that.heightFogMixMode &&
this.heightFogMode == that.heightFogMode
// TODO: Move these out of here
&& this.earthCurveRatio == that.earthCurveRatio
&& this.noiseEnable == that.noiseEnable && this.noiseSteps == that.noiseSteps && this.noiseIntensity == that.noiseIntensity && this.noiseDropoff == that.noiseDropoff;
}
}
@Override
public int hashCode()
{
return Objects.hash(this.farFogSetting, this.heightFogSetting, this.heightFogMixMode, this.heightFogMode, this.heightFogHeight, this.earthCurveRatio, this.noiseEnable, this.noiseSteps, this.noiseIntensity, this.noiseDropoff);
}
}
@@ -238,16 +238,16 @@ public class GLProxy
Runnable runnable = this.renderThreadRunnableQueue.poll();
while(runnable != null)
{
runnable.run();
// only try running for 4ms (240 FPS) at a time to prevent random lag spikes
long currentTime = System.nanoTime();
long runDuration = currentTime - startTime;
// only try running for 4ms at a time to (hopefully) prevent random lag spikes
if (runDuration > 4_000_000)
{
break;
}
runnable.run();
runnable = this.renderThreadRunnableQueue.poll();
}
}
@@ -49,6 +49,8 @@ public class ShaderProgram
/** Stores the handle of the program. */
public final int id;
// TODO: A better way to set the fragData output name
/**
* Creates a shader program.
@@ -73,20 +75,20 @@ public class ShaderProgram
}
public ShaderProgram(List<Supplier<String>> vert, List<Supplier<String>> frag, String[] attributes)
public ShaderProgram(List<Supplier<String>> vertSupplierList, List<Supplier<String>> fragSupplierList, String[] attributes)
{
id = GL32.glCreateProgram();
this.id = GL32.glCreateProgram();
for (Supplier<String> v : vert)
for (Supplier<String> vertSupplier : vertSupplierList)
{
Shader vertShader = new Shader(GL32.GL_VERTEX_SHADER, v.get());
Shader vertShader = new Shader(GL32.GL_VERTEX_SHADER, vertSupplier.get());
GL32.glAttachShader(this.id, vertShader.id);
vertShader.free(); // important!
}
for (Supplier<String> f : frag)
for (Supplier<String> fragSupplier : fragSupplierList)
{
Shader fragShader = new Shader(GL32.GL_FRAGMENT_SHADER, f.get());
Shader fragShader = new Shader(GL32.GL_FRAGMENT_SHADER, fragSupplier.get());
GL32.glAttachShader(this.id, fragShader.id);
fragShader.free(); // important!
}
@@ -101,28 +103,22 @@ public class ShaderProgram
if (status != GL32.GL_TRUE)
{
String message = "Shader Link Error. Details: " + GL32.glGetProgramInfoLog(this.id);
free(); // important!
this.free(); // important!
throw new RuntimeException(message);
}
GL32.glUseProgram(id); // This HAVE to be a direct call to prevent calling the overloaded version
GL32.glUseProgram(this.id); // This HAVE to be a direct call to prevent calling the overloaded version
}
/** This will bind ShaderProgram */
public void bind()
{
GL32.glUseProgram(id);
}
/** This will unbind ShaderProgram */
public void unbind()
{
GL32.glUseProgram(0);
}
// REMEMBER to always free the resource!
public void free()
{
GL32.glDeleteProgram(id);
}
public void bind() { GL32.glUseProgram(this.id); }
public void unbind() { GL32.glUseProgram(0); }
public void free() { GL32.glDeleteProgram(this.id); }
/**
* WARNING: Slow native call! Cache it if possible!
@@ -139,12 +135,12 @@ public class ShaderProgram
if (i == -1) throw new RuntimeException("Attribute name not found: " + name);
return i;
}
// Same as above but without throwing errors.
// Return -1 if attribute doesn't exist or has been optimized out
/**
* Same as above but without throwing errors. <br>
* Returns -1 if the attribute doesn't exist or has been optimized out.
*/
public int tryGetAttributeLocation(CharSequence name)
{
return GL32.glGetAttribLocation(id, name);
}
{ return GL32.glGetAttribLocation(this.id, name); }
/**
* WARNING: Slow native call! Cache it if possible!
@@ -168,41 +164,34 @@ public class ShaderProgram
// Same as above but without throwing errors.
// Return -1 if uniform doesn't exist or has been optimized out
public int tryGetUniformLocation(CharSequence name)
{
return GL32.glGetUniformLocation(id, name);
}
{ return GL32.glGetUniformLocation(this.id, name); }
/** Requires ShaderProgram binded. */
public void setUniform(int location, boolean value)
{
// This use -1 for false as that equals all one set
GL32.glUniform1i(location, value ? 1 : 0);
}
/** Requires a bound ShaderProgram. */
public void setUniform(int location, boolean value) { GL32.glUniform1i(location, value ? 1 : 0); }
/** @see ShaderProgram#setUniform(int, boolean) */
public void trySetUniform(int location, boolean value) { if (location != -1) { this.setUniform(location, value); } }
/** Requires ShaderProgram binded. */
public void setUniform(int location, int value)
{
GL32.glUniform1i(location, value);
}
/** Requires a bound ShaderProgram. */
public void setUniform(int location, int value) { GL32.glUniform1i(location, value); }
/** @see ShaderProgram#setUniform(int, int) */
public void trySetUniform(int location, int value) { if (location != -1) { this.setUniform(location, value); } }
/** Requires ShaderProgram binded. */
public void setUniform(int location, float value)
{
GL32.glUniform1f(location, value);
}
/** Requires a bound ShaderProgram. */
public void setUniform(int location, float value) { GL32.glUniform1f(location, value); }
/** @see ShaderProgram#setUniform(int, float) */
public void trySetUniform(int location, float value) { if (location != -1) { this.setUniform(location, value); } }
/** Requires ShaderProgram binded. */
public void setUniform(int location, Vec3f value)
{
GL32.glUniform3f(location, value.x, value.y, value.z);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, DhApiVec3i value)
{
GL32.glUniform3i(location, value.x, value.y, value.z);
}
/** Requires a bound ShaderProgram. */
public void setUniform(int location, Vec3f value) { GL32.glUniform3f(location, value.x, value.y, value.z); }
/** @see ShaderProgram#setUniform(int, Vec3f) */
public void trySetUniform(int location, Vec3f value) { if (location != -1) { this.setUniform(location, value); } }
/** Requires ShaderProgram binded. */
/** Requires a bound ShaderProgram. */
public void setUniform(int location, DhApiVec3i value) { GL32.glUniform3i(location, value.x, value.y, value.z); }
/** @see ShaderProgram#setUniform(int, Mat4f) */
public void trySetUniform(int location, DhApiVec3i value) { if (location != -1) { this.setUniform(location, value); } }
/** Requires a bound ShaderProgram. */
public void setUniform(int location, Mat4f value)
{
try (MemoryStack stack = MemoryStack.stackPush())
@@ -212,14 +201,22 @@ public class ShaderProgram
GL32.glUniformMatrix4fv(location, false, buffer);
}
}
/** @see ShaderProgram#setUniform(int, Mat4f) */
public void trySetUniform(int location, Mat4f value) { if (location != -1) { this.setUniform(location, value); } }
/**
* Converts the color's RGBA values into values between 0 and 1.
* Requires ShaderProgram binded.
* Converts the color's RGBA values into values between 0 and 1. <br>
* Requires a bound ShaderProgram.
*/
public void setUniform(int location, Color value)
{
GL32.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
GL32.glUniform4f(location,
value.getRed() / 256.0f,
value.getGreen() / 256.0f,
value.getBlue() / 256.0f,
value.getAlpha() / 256.0f);
}
/** @see ShaderProgram#setUniform(int, Color) */
public void trySetUniform(int location, Color value) { if (location != -1) { this.setUniform(location, value); } }
}
@@ -44,7 +44,6 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRen
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogColorMode;
import com.seibel.distanthorizons.core.render.fog.LodFogConfig;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -104,8 +103,7 @@ public class LodRenderer
// The shader program
IDhApiShaderProgram lodRenderProgram = null;
LodFogConfig fogConfig;
private IDhApiShaderProgram lodRenderProgram = null;
public QuadElementBuffer quadIBO = null;
public boolean isSetupComplete = false;
@@ -634,22 +632,6 @@ public class LodRenderer
}
else
{
LodFogConfig newFogConfig = LodFogConfig.generateFogConfig(); // TODO use a config listener instead
if (this.fogConfig == null)
{
this.fogConfig = newFogConfig;
}
if (!this.fogConfig.equals(newFogConfig))
{
this.fogConfig = newFogConfig;
this.lodRenderProgram.free();
this.lodRenderProgram = new DhTerrainShaderProgram();
FogShader.INSTANCE.free();
FogShader.INSTANCE = new FogShader(newFogConfig);
}
this.lodRenderProgram.bind();
}
@@ -58,19 +58,23 @@ public class TestRenderer
public void init()
{
if (init) return;
if (this.init)
{
return;
}
logger.info("init");
init = true;
va = AbstractVertexAttribute.create();
va.bind();
this.init = true;
this.va = AbstractVertexAttribute.create();
this.va.bind();
// Pos
va.setVertexAttribute(0, 0, VertexPointer.addVec2Pointer(false));
this.va.setVertexAttribute(0, 0, VertexPointer.addVec2Pointer(false));
// Color
va.setVertexAttribute(0, 1, VertexPointer.addVec4Pointer(false));
va.completeAndCheck(Float.BYTES * 6);
basicShader = new ShaderProgram("shaders/test/vert.vert", "shaders/test/frag.frag",
this.va.setVertexAttribute(0, 1, VertexPointer.addVec4Pointer(false));
this.va.completeAndCheck(Float.BYTES * 6);
this.basicShader = new ShaderProgram("shaders/test/vert.vert", "shaders/test/frag.frag",
"fragColor", new String[]{"vPosition", "color"});
createBuffer();
this.createBuffer();
}
// Render a square with uv color
@@ -97,8 +101,8 @@ public class TestRenderer
private void createBuffer()
{
sharedContextBuffer = createTextingBuffer();
sameContextBuffer = createTextingBuffer();
this.sharedContextBuffer = createTextingBuffer();
this.sameContextBuffer = createTextingBuffer();
}
public void render()
@@ -106,7 +110,7 @@ public class TestRenderer
spamLogger.debug("rendering");
GLState state = new GLState();
init();
this.init();
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, MC_RENDER.getTargetFrameBuffer());
GL32.glViewport(0, 0, MC_RENDER.getTargetFrameBufferViewportWidth(), MC_RENDER.getTargetFrameBufferViewportHeight());
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
@@ -116,20 +120,20 @@ public class TestRenderer
GL32.glDisable(GL32.GL_BLEND);
//GL32.glDisable(GL32.GL_SCISSOR_TEST);
basicShader.bind();
va.bind();
this.basicShader.bind();
this.va.bind();
// Switch between the two buffers per second
if (System.currentTimeMillis() % 2000 < 1000)
{
sameContextBuffer.bind();
va.bindBufferToAllBindingPoints(sameContextBuffer.getId());
this.sameContextBuffer.bind();
this.va.bindBufferToAllBindingPoints(this.sameContextBuffer.getId());
spamLogger.debug("same context buffer");
}
else
{
sameContextBuffer.bind();
va.bindBufferToAllBindingPoints(sharedContextBuffer.getId());
this.sameContextBuffer.bind();
this.va.bindBufferToAllBindingPoints(this.sharedContextBuffer.getId());
spamLogger.debug("shared context buffer");
}
// Render the square
@@ -582,9 +582,23 @@ public class GenericObjectRenderer implements IDhApiCustomRenderRegister
shaderProgram.fillSharedDirectUniformData(renderEventParam, shading, boxGroup, camPos);
for (DhApiRenderableBox box : boxGroup)
for (int i = 0; i < boxGroup.size(); i++)
{
this.renderBox(shaderProgram, renderEventParam, boxGroup, box, camPos);
try
{
DhApiRenderableBox box = boxGroup.get(i);
if (box != null)
{
this.renderBox(shaderProgram, renderEventParam, boxGroup, box, camPos);
}
}
catch (IndexOutOfBoundsException e)
{
// Concurrency issue, the list was modified while rendering
// this can probably be ignored.
// However, if it does become a problem we can add locks to the box group.
break;
}
}
}
private void renderBox(
@@ -20,11 +20,11 @@
package com.seibel.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.fog.LodFogConfig;
import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
@@ -38,7 +38,7 @@ import java.awt.*;
public class FogShader extends AbstractShaderRenderer
{
public static FogShader INSTANCE = new FogShader(LodFogConfig.generateFogConfig());
public static final FogShader INSTANCE = new FogShader();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonInjector.INSTANCE.get(IVersionConstants.class);
@@ -46,21 +46,46 @@ public class FogShader extends AbstractShaderRenderer
public int frameBuffer;
private final LodFogConfig fogConfig;
private Mat4f inverseMvmProjMatrix;
// Uniforms
//==========//
// Uniforms //
//==========//
public int uDepthMap;
/** Inverted Model View Projection matrix */
public int uInvMvmProj;
// fog uniforms
public int uFogColor;
public int uFogScale;
public int uFogVerticalScale;
public int uNearFogStart;
public int uNearFogLength;
public int uFullFogMode;
/** Inverted Model View Projection matrix */
public int uInvMvmProj;
public int uDepthMap;
// far fog
public int uFarFogStart;
public int uFarFogLength;
public int uFarFogMin;
public int uFarFogRange;
public int uFarFogDensity;
// height fog
public int uHeightFogStart;
public int uHeightFogLength;
public int uHeightFogMin;
public int uHeightFogRange;
public int uHeightFogDensity;
public int uHeightFogEnabled;
public int uHeightFogFalloffType;
public int uHeightBasedOnCamera;
public int uHeightFogBaseHeight;
public int uHeightFogAppliesUp;
public int uHeightFogAppliesDown;
public int uUseSphericalFog;
public int uHeightFogMixingMode;
public int uCameraBlockYPos;
@@ -68,15 +93,13 @@ public class FogShader extends AbstractShaderRenderer
// constructor //
//=============//
public FogShader(LodFogConfig fogConfig) { this.fogConfig = fogConfig; }
public FogShader() { }
@Override
public void onInit()
{
this.shader = new ShaderProgram(
// TODO rename normal.vert to something like "postProcess.vert"
() -> Shader.loadFile("shaders/normal.vert", false, new StringBuilder()).toString(),
() -> this.fogConfig.loadAndProcessFragShader("shaders/fog/fog.frag", false).toString(),
"shaders/normal.vert", "shaders/fog/fog.frag",
"fragColor", new String[]{"vPosition"}
);
@@ -87,15 +110,35 @@ public class FogShader extends AbstractShaderRenderer
this.uInvMvmProj = this.shader.getUniformLocation("uInvMvmProj");
// Fog uniforms
this.uFogScale = this.shader.tryGetUniformLocation("uFogScale");
this.uFogVerticalScale = this.shader.tryGetUniformLocation("uFogVerticalScale");
this.uFogColor = this.shader.tryGetUniformLocation("uFogColor");
this.uFullFogMode = this.shader.tryGetUniformLocation("uFullFogMode");
this.uFogScale = this.shader.getUniformLocation("uFogScale");
this.uFogVerticalScale = this.shader.getUniformLocation("uFogVerticalScale");
this.uFogColor = this.shader.getUniformLocation("uFogColor");
this.uFullFogMode = this.shader.getUniformLocation("uFullFogMode");
// near fog
this.uNearFogStart = this.shader.tryGetUniformLocation("uNearFogStart");
this.uNearFogLength = this.shader.tryGetUniformLocation("uNearFogLength");
// fog config
this.uFarFogStart = this.shader.getUniformLocation("uFarFogStart");
this.uFarFogLength = this.shader.getUniformLocation("uFarFogLength");
this.uFarFogMin = this.shader.getUniformLocation("uFarFogMin");
this.uFarFogRange = this.shader.getUniformLocation("uFarFogRange");
this.uFarFogDensity = this.shader.getUniformLocation("uFarFogDensity");
// height fog
this.uHeightFogStart = this.shader.getUniformLocation("uHeightFogStart");
this.uHeightFogLength = this.shader.getUniformLocation("uHeightFogLength");
this.uHeightFogMin = this.shader.getUniformLocation("uHeightFogMin");
this.uHeightFogRange = this.shader.getUniformLocation("uHeightFogRange");
this.uHeightFogDensity = this.shader.getUniformLocation("uHeightFogDensity");
this.uHeightFogEnabled = this.shader.getUniformLocation("uHeightFogEnabled");
this.uHeightFogFalloffType = this.shader.getUniformLocation("uHeightFogFalloffType");
this.uHeightBasedOnCamera = this.shader.getUniformLocation("uHeightBasedOnCamera");
this.uHeightFogBaseHeight = this.shader.getUniformLocation("uHeightFogBaseHeight");
this.uHeightFogAppliesUp = this.shader.getUniformLocation("uHeightFogAppliesUp");
this.uHeightFogAppliesDown = this.shader.getUniformLocation("uHeightFogAppliesDown");
this.uUseSphericalFog = this.shader.getUniformLocation("uUseSphericalFog");
this.uHeightFogMixingMode = this.shader.getUniformLocation("uHeightFogMixingMode");
this.uCameraBlockYPos = this.shader.getUniformLocation("uCameraBlockYPos");
}
@@ -107,22 +150,66 @@ public class FogShader extends AbstractShaderRenderer
@Override
protected void onApplyUniforms(float partialTicks)
{
int lodDrawDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH;
if (this.inverseMvmProjMatrix != null)
{
this.shader.setUniform(this.uInvMvmProj, this.inverseMvmProjMatrix);
}
int lodDrawDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH;
// Fog
if (this.uFullFogMode != -1) this.shader.setUniform(this.uFullFogMode, MC_RENDER.isFogStateSpecial() ? 1 : 0);
if (this.uFogColor != -1) this.shader.setUniform(this.uFogColor, MC_RENDER.isFogStateSpecial() ? this.getSpecialFogColor(partialTicks) : this.getFogColor(partialTicks));
// Fog uniforms
this.shader.setUniform(this.uFogColor, MC_RENDER.isFogStateSpecial() ? this.getSpecialFogColor(partialTicks) : this.getFogColor(partialTicks));
this.shader.setUniform(this.uFogScale, 1.f / lodDrawDistance);
this.shader.setUniform(this.uFogVerticalScale, 1.f / MC.getWrappedClientLevel().getMaxHeight());
this.shader.setUniform(this.uFullFogMode, MC_RENDER.isFogStateSpecial() ? 1 : 0);
// fog config
float farFogStart = Config.Client.Advanced.Graphics.Fog.farFogStart.get().floatValue();
float farFogEnd = Config.Client.Advanced.Graphics.Fog.farFogEnd.get().floatValue();
float farFogMin = Config.Client.Advanced.Graphics.Fog.farFogMin.get().floatValue();
float farFogMax = Config.Client.Advanced.Graphics.Fog.farFogMax.get().floatValue();
float farFogDensity = Config.Client.Advanced.Graphics.Fog.farFogDensity.get().floatValue();
this.shader.setUniform(this.uFarFogStart, farFogStart);
this.shader.setUniform(this.uFarFogLength, farFogEnd - farFogStart);
this.shader.setUniform(this.uFarFogMin, farFogMin);
this.shader.setUniform(this.uFarFogRange, farFogMax - farFogMin);
this.shader.setUniform(this.uFarFogDensity, farFogDensity);
// height config
EDhApiHeightFogMixMode heightFogMixingMode = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMixMode.get();
boolean heightFogEnabled = heightFogMixingMode != EDhApiHeightFogMixMode.SPHERICAL && heightFogMixingMode != EDhApiHeightFogMixMode.CYLINDRICAL;
boolean useSphericalFog = heightFogMixingMode == EDhApiHeightFogMixMode.SPHERICAL;
EDhApiHeightFogDirection heightFogCameraDirection = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection.get();
float heightFogStart = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart.get().floatValue();
float heightFogEnd = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd.get().floatValue();
float heightFogMin = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin.get().floatValue();
float heightFogMax = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax.get().floatValue();
float heightFogDensity = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity.get().floatValue();
this.shader.setUniform(this.uHeightFogStart, heightFogStart);
this.shader.setUniform(this.uHeightFogLength, heightFogEnd - heightFogStart);
this.shader.setUniform(this.uHeightFogMin, heightFogMin);
this.shader.setUniform(this.uHeightFogRange, heightFogMax - heightFogMin);
this.shader.setUniform(this.uHeightFogDensity, heightFogDensity);
this.shader.setUniform(this.uHeightFogEnabled, heightFogEnabled);
this.shader.setUniform(this.uHeightFogFalloffType, Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff.get().value);
this.shader.setUniform(this.uHeightFogBaseHeight, Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight.get().floatValue());
this.shader.setUniform(this.uHeightBasedOnCamera, heightFogCameraDirection.basedOnCamera);
this.shader.setUniform(this.uHeightFogAppliesUp, heightFogCameraDirection.fogAppliesUp);
this.shader.setUniform(this.uHeightFogAppliesDown, heightFogCameraDirection.fogAppliesDown);
this.shader.setUniform(this.uUseSphericalFog, useSphericalFog);
this.shader.setUniform(this.uHeightFogMixingMode, heightFogMixingMode.value);
this.shader.setUniform(this.uCameraBlockYPos, (float)MC_RENDER.getCameraExactPosition().y);
float nearFogStart = (VERSION_CONSTANTS.isVanillaRenderedChunkSquare() ? (float) Math.sqrt(2.0) : 1.0f) / lodDrawDistance;
if (this.uNearFogStart != -1) this.shader.setUniform(this.uNearFogStart, nearFogStart);
if (this.uNearFogLength != -1) this.shader.setUniform(this.uNearFogLength, 0.0f);
if (this.uFogScale != -1) this.shader.setUniform(this.uFogScale, 1.f / lodDrawDistance);
if (this.uFogVerticalScale != -1) this.shader.setUniform(this.uFogVerticalScale, 1.f / MC.getWrappedClientLevel().getMaxHeight());
}
private Color getFogColor(float partialTicks)
{
@@ -23,18 +23,19 @@ import java.awt.*;
/**
* Handles the bit-wise math used when
* dealing with colors stored as integers.
* dealing with colors stored as integers. <br><br>
*
* Minecraft color format is: 0xAA BB GG RR <br>
* DH mod color format is: 0xAA RR GG BB <br>
* OpenGL RGBA format native order: 0xRR GG BB AA <br>
* OpenGL RGBA format Java Order: 0xAA BB GG RR <br>
*
* @author Cola
* @author Leonardo Amato
* @version 2023-5-15
*/
public class ColorUtil
{
//note: Minecraft color format is: 0xAA BB GG RR
//________ DH mod color format is: 0xAA RR GG BB
//OpenGL RGBA format native order: 0xRR GG BB AA
//_ OpenGL RGBA format Java Order: 0xAA BB GG RR
public static final int INVISIBLE = argbToInt(0, 0, 0, 0);
@@ -215,7 +216,7 @@ public class ColorUtil
}
public static Color toColorObjRGB(int color) { return new Color(getRed(color), getGreen(color), getBlue(color)); }
public static Color toColorObjRGBA(int color) { return new Color(getRed(color), getGreen(color), getBlue(color), getAlpha(color)); }
public static Color toColorObjARGB(int color) { return new Color(getRed(color), getGreen(color), getBlue(color), getAlpha(color)); }
public static int toColorInt(Color color) { return argbToInt(color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue()); }
@@ -189,14 +189,6 @@ public class RenderUtil
return false; //Level is not ready yet.
}
/* if (MC_RENDER.isFogStateSpecial())
{
// if the player is blind/under-water, don't render LODs,
// and don't change minecraft's fog
// which blindness relies on.
return false;
} */
if (MC_RENDER.getLightmapWrapper(levelWrapper) == null)
{
return false;
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.util.objects;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -30,45 +32,43 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class DummyRunExecutorService implements ExecutorService
/**
* This runs the given {@link Runnable}'s on the same
* thread as they're queued.
* This can be used to change MC threads to run on DH threads.
* This is done to prevent concurrent issues with world generation
* and reduce putting undue load on MC threads.
*/
public class RunOnThisThreadExecutorService implements ExecutorService
{
private boolean shutdownCalled = false;
@Override
public void execute(Runnable command)
{
command.run();
}
@Override
public void shutdown()
{
shutdownCalled = true;
}
public void execute(Runnable command) { command.run(); }
@Override
public void shutdown() { this.shutdownCalled = true; }
@Override
@NotNull
public List<Runnable> shutdownNow()
{
shutdownCalled = true;
return new ArrayList<Runnable>();
this.shutdownCalled = true;
return new ArrayList<>();
}
@Override
public boolean isShutdown()
{
return shutdownCalled;
}
public boolean isShutdown() { return this.shutdownCalled; }
@Override
public boolean isTerminated()
{
return shutdownCalled;
}
public boolean isTerminated() { return this.shutdownCalled; }
@Override
public boolean awaitTermination(long timeout, TimeUnit unit)
{
shutdownCalled = true;
this.shutdownCalled = true;
return true;
}
@@ -119,7 +119,7 @@ public class DummyRunExecutorService implements ExecutorService
List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
for (Callable<T> t : tasks)
{
futures.add(submit(t));
futures.add(this.submit(t));
}
return futures;
}
@@ -151,7 +151,7 @@ public class DummyRunExecutorService implements ExecutorService
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws ExecutionException
{
return invokeAny(tasks);
return this.invokeAny(tasks);
}
}
@@ -56,11 +56,6 @@ public class ThreadPoolUtil
@Nullable
public static ThreadPoolExecutor getWorldGenExecutor() { return worldGenThreadPool.executor; }
public static final String BUFFER_UPLOADER_THREAD_NAME = "Buffer Uploader";
private static ThreadPoolExecutor bufferUploaderThreadPool;
@Nullable
public static ThreadPoolExecutor getBufferUploaderExecutor() { return bufferUploaderThreadPool; }
public static final String CLEANUP_THREAD_NAME = "Cleanup";
private static ThreadPoolExecutor cleanupThreadPool;
@Nullable
@@ -118,7 +113,6 @@ public class ThreadPoolUtil
updatePropagatorThreadPool = new ConfigThreadPool(UPDATE_PROPAGATOR_THREAD_FACTORY, Config.Common.MultiThreading.numberOfUpdatePropagatorThreads, Config.Common.MultiThreading.runTimeRatioForUpdatePropagatorThreads, null);
worldGenThreadPool = new ConfigThreadPool(WORLD_GEN_THREAD_FACTORY, Config.Common.MultiThreading.numberOfWorldGenerationThreads, Config.Common.MultiThreading.runTimeRatioForWorldGenerationThreads, null);
networkCompressionThreadPool = new ConfigThreadPool(NETWORK_COMPRESSION_THREAD_FACTORY, Config.Common.MultiThreading.numberOfNetworkCompressionThreads, Config.Common.MultiThreading.runTimeRatioForNetworkCompressionThreads, null);
bufferUploaderThreadPool = ThreadUtil.makeSingleThreadPool(BUFFER_UPLOADER_THREAD_NAME);
cleanupThreadPool = ThreadUtil.makeSingleThreadPool(CLEANUP_THREAD_NAME);
beaconCullingThreadPool = ThreadUtil.makeSingleThreadPool(BEACON_CULLING_THREAD_NAME);
@@ -160,7 +154,6 @@ public class ThreadPoolUtil
updatePropagatorThreadPool.shutdownExecutorService();
worldGenThreadPool.shutdownExecutorService();
networkCompressionThreadPool.shutdownExecutorService();
bufferUploaderThreadPool.shutdown();
cleanupThreadPool.shutdown();
beaconCullingThreadPool.shutdown();
@@ -10,16 +10,27 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhServerLevel> extends AbstractDhWorld implements IDhServerWorld
{
protected final HashMap<ILevelWrapper, TDhServerLevel> dhLevelByLevelWrapper = new HashMap<>();
/**
* Concurrent since levels can be added/remove while other processing is happening.
* (Otherwise we may need to just put the logic in a lock.
*/
protected final ConcurrentHashMap<ILevelWrapper, TDhServerLevel> dhLevelByLevelWrapper = new ConcurrentHashMap<>();
public final LocalSaveStructure saveStructure = new LocalSaveStructure();
private final ServerPlayerStateManager serverPlayerStateManager;
//=============//
// constructor //
//=============//
public AbstractDhServerWorld(EWorldEnvironment worldEnvironment)
{
super(worldEnvironment);
@@ -31,7 +42,6 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
// player handling //
//=================//
@Override
public ServerPlayerStateManager getServerPlayerStateManager()
{
@@ -44,8 +54,10 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
ServerPlayerState playerState = this.serverPlayerStateManager.registerJoinedPlayer(serverPlayer);
this.getLevel(serverPlayer.getLevel()).addPlayer(serverPlayer);
for (TDhServerLevel level : (Iterable<? extends TDhServerLevel>) this.dhLevelByLevelWrapper.values().stream().distinct()::iterator)
Iterator<TDhServerLevel> it = this.dhLevelByLevelWrapper.values().stream().distinct().iterator();
while (it.hasNext())
{
TDhServerLevel level = it.next();
level.registerNetworkHandlers(playerState);
}
@@ -110,7 +122,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
{
for (TDhServerLevel level : this.dhLevelByLevelWrapper.values())
{
LOGGER.info("Unloading level [" + level.getLevelWrapper().getDimensionName() + "].");
LOGGER.info("Unloading level [" + level.getLevelWrapper().getDhIdentifier() + "].");
// level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
@@ -72,13 +72,13 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
public boolean worldLoaded() { return SharedApi.getAbstractDhWorld() != null; }
@Override
public void setReadOnly(boolean readOnly) { this.setReadOnly(readOnly, true); }
public void setReadOnly(boolean readOnly) throws IllegalStateException { this.setReadOnly(readOnly, true); }
/**
* Not part of the public API.
* Normal API users shouldn't be able to change the upcoming world state
* this is only here so DH can revert the readonly value after the world is unloaded
*/
public void setReadOnly(boolean readOnly, boolean throwIfWorldUnloaded)
public void setReadOnly(boolean readOnly, boolean throwIfWorldUnloaded) throws IllegalStateException
{
if (throwIfWorldUnloaded && SharedApi.getAbstractDhWorld() == null)
{
@@ -102,7 +102,7 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
}
@Override
public boolean getReadOnly()
public boolean getReadOnly() throws IllegalStateException
{
if (SharedApi.getAbstractDhWorld() == null)
{
@@ -120,7 +120,7 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
//================//
@Override
public IDhApiLevelWrapper getSinglePlayerLevel()
public IDhApiLevelWrapper getSinglePlayerLevel() throws IllegalStateException
{
if (SharedApi.getAbstractDhWorld() == null)
{
@@ -138,7 +138,7 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
@Override
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelWrappers()
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelWrappers() throws IllegalStateException
{
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
@@ -155,7 +155,7 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
}
@Override
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelsForDimensionType(IDhApiDimensionTypeWrapper dimensionTypeWrapper)
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelsForDimensionType(IDhApiDimensionTypeWrapper dimensionTypeWrapper) throws IllegalStateException
{
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
@@ -176,7 +176,7 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
}
@Override
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelsWithDimensionNameLike(String dimensionName)
public Iterable<IDhApiLevelWrapper> getAllLoadedLevelsWithDimensionNameLike(String dimensionName) throws IllegalStateException
{
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world == null)
@@ -190,7 +190,7 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
for (IDhLevel dhLevel : world.getAllLoadedLevels())
{
ILevelWrapper levelWrapper = dhLevel.getLevelWrapper();
String levelDimName = levelWrapper.getDimensionName().toLowerCase();
String levelDimName = levelWrapper.getDhIdentifier().toLowerCase();
if (levelDimName.contains(soughtDimName))
{
returnList.add(levelWrapper);
@@ -79,7 +79,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
LodUtil.assertTrue(serverLevelWrapper != null);
if (!clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()))
{
LodUtil.assertNotReach("tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: " + clientLevelWrapper.getDimensionName() + " ServerLevelWrapper dim: " + serverLevelWrapper.getDimensionName());
LodUtil.assertNotReach("tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: [" + clientLevelWrapper.getDhIdentifier() + "] ServerLevelWrapper dim: [" + serverLevelWrapper.getDhIdentifier() + "].");
}
@@ -148,7 +148,7 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
// close each level
for (DhClientServerLevel level : this.dhLevels)
{
LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionName());
LOGGER.info("Unloading level [" + level.getServerLevelWrapper().getDhIdentifier() + "].");
// level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
@@ -131,7 +131,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
for (DhClientLevel dhClientLevel : this.levels.values())
{
LOGGER.info("Unloading level [" + dhClientLevel.getLevelWrapper().getDimensionName() + "].");
LOGGER.info("Unloading level [" + dhClientLevel.getLevelWrapper().getDhIdentifier() + "].");
// level wrapper shouldn't be null, but just in case
IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper();
@@ -60,7 +60,7 @@ public class ChunkLightStorage
// constructor //
//=============//
public static ChunkLightStorage createSkyLightStorage(IChunkWrapper chunkWrapper) { return createSkyLightStorage(chunkWrapper.getMinBuildHeight(), chunkWrapper.getMaxBuildHeight()); }
public static ChunkLightStorage createSkyLightStorage(IChunkWrapper chunkWrapper) { return createSkyLightStorage(chunkWrapper.getInclusiveMinBuildHeight(), chunkWrapper.getExclusiveMaxBuildHeight()); }
public static ChunkLightStorage createSkyLightStorage(int minY, int maxY)
{
return new ChunkLightStorage(
@@ -68,7 +68,7 @@ public class ChunkLightStorage
// positions above should be lit but positions below should be unlit
LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT);
}
public static ChunkLightStorage createBlockLightStorage(IChunkWrapper chunkWrapper) { return createBlockLightStorage(chunkWrapper.getMinBuildHeight(), chunkWrapper.getMaxBuildHeight()); }
public static ChunkLightStorage createBlockLightStorage(IChunkWrapper chunkWrapper) { return createBlockLightStorage(chunkWrapper.getInclusiveMinBuildHeight(), chunkWrapper.getExclusiveMaxBuildHeight()); }
public static ChunkLightStorage createBlockLightStorage(int minY, int maxY)
{
return new ChunkLightStorage(
@@ -106,10 +106,18 @@ public class ChunkLightStorage
if (this.lightSections != null)
{
LightSection lightSection = this.lightSections[BitShiftUtil.divideByPowerOfTwo(y - this.minY, 4)];
if (lightSection != null)
int sectionIndex = BitShiftUtil.divideByPowerOfTwo(y - this.minY, 4);
try
{
return lightSection.get(x, y, z);
LightSection lightSection = this.lightSections[sectionIndex];
if (lightSection != null)
{
return lightSection.get(x, y, z);
}
}
catch (IndexOutOfBoundsException e)
{
throw new IndexOutOfBoundsException("Failed to get light at x:["+x+"], y:["+y+"], z:["+z+"], index:["+sectionIndex+"]. MinY:["+this.minY+"], maxY:["+this.maxY+"], section length:["+this.lightSections.length+"]. Original error: ["+e.getMessage()+"].");
}
}
@@ -126,7 +134,8 @@ public class ChunkLightStorage
//populate array if it doesn't exist.
if (this.lightSections == null)
{
this.lightSections = new LightSection[BitShiftUtil.divideByPowerOfTwo(this.maxY - this.minY, 4)];
int arrayLength = (this.maxY - this.minY) / 16;
this.lightSections = new LightSection[arrayLength];
}
int index = (y - this.minY) >> 4;
@@ -27,7 +27,6 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -47,18 +46,20 @@ public interface IChunkWrapper extends IBindable
DhChunkPos getChunkPos();
default int getHeight() { return this.getMaxBuildHeight() - this.getMinBuildHeight(); }
int getMinBuildHeight();
int getMaxBuildHeight();
default int getHeight() { return this.getExclusiveMaxBuildHeight() - this.getInclusiveMinBuildHeight(); }
/** inclusive (IE if returning -64 the min block can be placed at -64) */
int getInclusiveMinBuildHeight();
/** exclusive (IE if returning 320 the max block can be placed at 319) */
int getExclusiveMaxBuildHeight();
/**
* returns the Y level for the last non-empty section in this chunk,
* or {@link IChunkWrapper#getMinBuildHeight()} if this chunk is completely empty.
* or {@link IChunkWrapper#getInclusiveMinBuildHeight()} if this chunk is completely empty.
*/
int getMinNonEmptyHeight();
/**
* returns the Y level for the first non-empty section in this chunk,
* or {@link IChunkWrapper#getMaxBuildHeight()} if this chunk is completely empty.
* or {@link IChunkWrapper#getExclusiveMaxBuildHeight()} if this chunk is completely empty.
*/
int getMaxNonEmptyHeight();
@@ -99,7 +100,7 @@ public interface IChunkWrapper extends IBindable
default boolean blockPosInsideChunk(int x, int y, int z)
{
return (x >= this.getMinBlockX() && x <= this.getMaxBlockX()
&& y >= this.getMinBuildHeight() && y < this.getMaxBuildHeight()
&& y >= this.getInclusiveMinBuildHeight() && y < this.getExclusiveMaxBuildHeight()
&& z >= this.getMinBlockZ() && z <= this.getMaxBlockZ());
}
default boolean blockPosInsideChunk(DhBlockPos2D blockPos)
@@ -144,8 +145,8 @@ public interface IChunkWrapper extends IBindable
// FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the constructor
int minHeight = this.getMinBuildHeight();
int maxHeight = this.getMaxBuildHeight() + 1;
int minHeight = this.getInclusiveMinBuildHeight();
int maxHeight = this.getExclusiveMaxBuildHeight() + 1;
if (x < 0 || x >= LodUtil.CHUNK_WIDTH
|| z < 0 || z >= LodUtil.CHUNK_WIDTH
@@ -157,6 +158,27 @@ public interface IChunkWrapper extends IBindable
throw new IndexOutOfBoundsException(errorMessage);
}
}
/** used to prevent accidentally attempting to get/set values outside this chunk's boundaries */
default void throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(int x, int z) throws IndexOutOfBoundsException
{
if (!RUN_RELATIVE_POS_INDEX_VALIDATION)
{
return;
}
// FIXME +1 is to handle the fact that LodDataBuilder adds +1 to all block lighting calculations, also done in the constructor
int minHeight = this.getInclusiveMinBuildHeight();
int maxHeight = this.getExclusiveMaxBuildHeight() + 1;
if (x < 0 || x >= LodUtil.CHUNK_WIDTH
|| z < 0 || z >= LodUtil.CHUNK_WIDTH)
{
String errorMessage = "Relative position [" + x + "," + z + "] out of bounds. \n" +
"X/Z must be between 0 and 15 (inclusive).";
throw new IndexOutOfBoundsException(errorMessage);
}
}
/**
@@ -167,7 +189,7 @@ public interface IChunkWrapper extends IBindable
*/
default int relativeBlockPosToIndex(int xRel, int y, int zRel)
{
int yRel = y - this.getMinBuildHeight();
int yRel = y - this.getInclusiveMinBuildHeight();
return (zRel * LodUtil.CHUNK_WIDTH * this.getHeight()) + (yRel * LodUtil.CHUNK_WIDTH) + xRel;
}
@@ -183,7 +205,7 @@ public interface IChunkWrapper extends IBindable
index -= (zRel * LodUtil.CHUNK_WIDTH * this.getHeight());
final int y = index / LodUtil.CHUNK_WIDTH;
final int yRel = y + this.getMinBuildHeight();
final int yRel = y + this.getInclusiveMinBuildHeight();
final int xRel = index % LodUtil.CHUNK_WIDTH;
return new DhBlockPos(xRel, yRel, zRel);
@@ -25,6 +25,7 @@ import java.util.UUID;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
@@ -147,7 +148,11 @@ public interface IMinecraftClientWrapper extends IBindable
Object getOptionsObject();
/** Executes the given task on Minecraft's render thread. */
/**
* Executes the given task on Minecraft's render thread.
* @deprecated use {@link GLProxy#runningOnRenderThread()} instead
*/
@Deprecated
void executeOnRenderThread(Runnable runnable);
}
@@ -30,9 +30,6 @@ public interface IMinecraftSharedWrapper extends IBindable
File getInstallationDirectory();
/** @return true if this is the first time loading this world */
boolean isWorldNew();
int getPlayerCount();
}
@@ -36,7 +36,15 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
@Override
String getDimensionName();
long getHashedSeed();
/**
* A string intended to uniquely identify this level.
*/
@Override
default String getDhIdentifier() { return this.getDimensionName() + "_" + this.getHashedSeed(); }
@Override
boolean hasCeiling();
@@ -61,7 +69,7 @@ public interface ILevelWrapper extends IDhApiLevelWrapper, IBindable
/** Fired when the level is being unloaded. Doesn't unload the level. */
void onUnload();
// TODO I don't like the circular reference, can we merge the level wrapper and DhLevels?
// TODO James doesn't like this circular reference, can we merge the level wrapper and DhLevels?
@Deprecated
void setParentLevel(IDhLevel parentLevel);
@@ -32,7 +32,7 @@ public interface IServerLevelWrapper extends ILevelWrapper
default String getKeyedLevelDimensionName()
{
String dimensionName = this.getDimensionName();
String dimensionName = this.getDhIdentifier();
if (Config.Server.sendLevelKeys.get())
{
@@ -43,18 +43,22 @@ public interface IServerLevelWrapper extends ILevelWrapper
String cleanWorldFolderName = this.getMcSaveFolder().getParentFile().getName()
.replaceAll("[^" + LevelInitMessage.PART_ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_");
levelKeyPrefix += (!levelKeyPrefix.isEmpty() ? "_" : "") + cleanWorldFolderName;
levelKeyPrefix += (!levelKeyPrefix.isEmpty() ? "_" : "") + cleanWorldFolderName
+ "_" + this.getHashedSeed();
}
if (!levelKeyPrefix.isEmpty())
if (levelKeyPrefix.isEmpty())
{
String mainPart = "@" + dimensionName;
return levelKeyPrefix.substring(0, Math.min(
LevelInitMessage.MAX_LENGTH - mainPart.length(),
levelKeyPrefix.length()
)) + mainPart;
levelKeyPrefix = String.valueOf(this.getHashedSeed());
}
String mainPart = "@" + dimensionName;
return levelKeyPrefix.substring(0, Math.min(
LevelInitMessage.MAX_LENGTH - mainPart.length(),
levelKeyPrefix.length()
)) + mainPart;
}
return dimensionName;
@@ -269,10 +269,10 @@
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogMixMode":
"Height Fog Mix Mode",
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogMixMode.@tooltip":
"How the height should effect the fog thickness combined with the normal function? \n\nBASIC: No special height fog effect. Fog is calculated based on camera distance \nIGNORE_HEIGHT: Ignore height completely. Fog is calculated based on horizontal distance \nADDITION: heightFog + farFog \nMAX: max(heightFog, farFog) \nMULTIPLY: heightFog * farFog \nINVERSE_MULTIPLY: 1 - (1-heightFog) * (1-farFog) \nLIMITED_ADDITION: farFog + max(farFog, heightFog) \nMULTIPLY_ADDITION: farFog + farFog * heightFog \nINVERSE_MULTIPLY_ADDITION: farFog + 1 - (1-heightFog) * (1-farFog) \nAVERAGE: farFog*0.5 + heightFog*0.5 \n\nNote that for 'BASIC' mode and 'IGNORE_HEIGHT' mode, fog settings for height fog has no effect.\n",
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogMode":
"Height Fog Mode",
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogMode.@tooltip":
"Defines how height effects fog. \nWhen in doubt start with SPHERICAL, ADDITION, or MAX. \n\nSPHERICAL: Fog is calculated based on camera distance. \nCYLINDRICAL: Ignore height, fog is calculated based on horizontal distance. \nMAX: max(heightFog, farFog) \nADDITION: heightFog + farFog \nMULTIPLY: heightFog * farFog \nINVERSE_MULTIPLY: 1 - (1-heightFog) * (1-farFog) \nLIMITED_ADDITION: farFog + max(farFog, heightFog) \nMULTIPLY_ADDITION: farFog + farFog * heightFog \nINVERSE_MULTIPLY_ADDITION: farFog + 1 - (1-heightFog) * (1-farFog) \nAVERAGE: farFog*0.5 + heightFog*0.5",
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogDirection":
"Height Fog Direction",
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogDirection.@tooltip":
"Where should the height fog be located? \n\nABOVE_CAMERA: Height fog starts from camera to the sky \nBELOW_CAMERA: Height fog starts from camera to the void \nABOVE_AND_BELOW_CAMERA: Height fog starts from camera to both the sky and the void \nABOVE_SET_HEIGHT: Height fog starts from a set height to the sky \nBELOW_SET_HEIGHT: Height fog starts from a set height to the void \nABOVE_AND_BELOW_SET_HEIGHT: Height fog starts from a set height to both the sky and the void \n",
"distanthorizons.config.client.advanced.graphics.fog.heightFog.heightFogBaseHeight":
"Height Fog Base Height",
@@ -571,6 +571,10 @@
"Lossy World Compression",
"distanthorizons.config.common.lodBuilding.worldCompression.@tooltip":
"How should block data be compressed when creating LOD data? \nThis setting will only affect new or updated LOD data, \nany data already generated when this setting is changed will be \nunaffected until it is modified or re-loaded. \n\nMost Accurate: Merge Same Blocks \nHighest Compression: Visually Equal",
"distanthorizons.config.common.lodBuilding.recalculateChunkHeightmaps":
"Recalculate Chunk Heightmaps",
"distanthorizons.config.common.lodBuilding.recalculateChunkHeightmaps.@tooltip":
"True: Recalculate chunk height maps before chunks can be used by DH. This can fix problems with worlds created by external tools. \nFalse: Assume any height maps handled by Minecraft are correct. \n\nFastest: False\nMost Compatible: True",
"distanthorizons.config.common.lodBuilding.showMigrationChatWarning":
"Log Migration In Chat",
@@ -801,10 +805,10 @@
"distanthorizons.config.enum.EDhApiFogFalloff.EXPONENTIAL_SQUARED":
"Exponential squared",
"distanthorizons.config.enum.EDhApiHeightFogMixMode.BASIC":
"Basic",
"distanthorizons.config.enum.EDhApiHeightFogMixMode.IGNORE_HEIGHT":
"Ignore Height",
"distanthorizons.config.enum.EDhApiHeightFogMixMode.SPHERICAL":
"Spherical",
"distanthorizons.config.enum.EDhApiHeightFogMixMode.CYLINDRICAL":
"Cylindrical",
"distanthorizons.config.enum.EDhApiHeightFogMixMode.ADDITION":
"Addition",
"distanthorizons.config.enum.EDhApiHeightFogMixMode.MAX":
@@ -822,17 +826,17 @@
"distanthorizons.config.enum.EDhApiHeightFogMixMode.AVERAGE":
"Average",
"distanthorizons.config.enum.EDhApiHeightFogMode.ABOVE_CAMERA":
"distanthorizons.config.enum.EDhApiHeightFogDirection.ABOVE_CAMERA":
"Above Camera",
"distanthorizons.config.enum.EDhApiHeightFogMode.BELOW_CAMERA":
"distanthorizons.config.enum.EDhApiHeightFogDirection.BELOW_CAMERA":
"Below Camera",
"distanthorizons.config.enum.EDhApiHeightFogMode.ABOVE_AND_BELOW_CAMERA":
"distanthorizons.config.enum.EDhApiHeightFogDirection.ABOVE_AND_BELOW_CAMERA":
"Above And Below Camera",
"distanthorizons.config.enum.EDhApiHeightFogMode.ABOVE_SET_HEIGHT":
"distanthorizons.config.enum.EDhApiHeightFogDirection.ABOVE_SET_HEIGHT":
"Above Set Height",
"distanthorizons.config.enum.EDhApiHeightFogMode.BELOW_SET_HEIGHT":
"distanthorizons.config.enum.EDhApiHeightFogDirection.BELOW_SET_HEIGHT":
"Below Set Height",
"distanthorizons.config.enum.EDhApiHeightFogMode.ABOVE_AND_BELOW_SET_HEIGHT":
"distanthorizons.config.enum.EDhApiHeightFogDirection.ABOVE_AND_BELOW_SET_HEIGHT":
"Above And Below Set Height",
"distanthorizons.config.enum.EDhApiVanillaOverdraw.NEVER":

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+233 -60
View File
@@ -1,99 +1,120 @@
#version 150 core
in vec2 TexCoord;
out vec4 fragColor;
uniform sampler2D uDepthMap;
// inverted model view matrix and projection matrix
uniform mat4 uInvMvmProj;
// fog uniforms
uniform vec4 uFogColor;
uniform float uFogScale;
uniform float uFogVerticalScale;
uniform vec4 uFogColor;
uniform int uFullFogMode;
uniform int uFogFalloffType;
uniform float uNearFogStart;
uniform float uNearFogLength;
// fog config
uniform float uFarFogStart;
uniform float uFarFogLength;
uniform float uFarFogMin;
uniform float uFarFogRange;
uniform float uFarFogDensity;
// height fog config
uniform float uHeightFogStart;
uniform float uHeightFogLength;
uniform float uHeightFogMin;
uniform float uHeightFogRange;
uniform float uHeightFogDensity;
/* ========MARCO DEFINED BY RUNTIME CODE GEN=========
uniform bool uHeightFogEnabled;
uniform int uHeightFogFalloffType;
uniform bool uHeightBasedOnCamera;
uniform float uHeightFogBaseHeight;
uniform bool uHeightFogAppliesUp;
uniform bool uHeightFogAppliesDown;
uniform bool uUseSphericalFog;
uniform int uHeightFogMixingMode;
uniform float uCameraBlockYPos;
float farFogStart;
float farFogLength;
float farFogMin;
float farFogRange;
float farFogDensity;
float heightFogStart;
float heightFogLength;
float heightFogMin;
float heightFogRange;
float heightFogDensity;
*/
// method definitions
// ==== The below 5 methods will be run-time generated. ====
float getNearFogThickness(float dist);
float getFarFogThickness(float dist);
float getHeightFogThickness(float dist);
float calculateFarFogDepth(float horizontal, float dist, float nearFogStart);
float calculateHeightFogDepth(float vertical, float realY);
float mixFogThickness(float near, float far, float height);
// =========================================================
const vec3 MAGIC = vec3(0.06711056, 0.00583715, 52.9829189);
float InterleavedGradientNoise(const in vec2 pixel) {
float x = dot(pixel, MAGIC.xy);
return fract(MAGIC.z * fract(x));
}
vec3 calcViewPosition(float fragmentDepth) {
vec4 ndc = vec4(TexCoord.xy, fragmentDepth, 1.0);
ndc.xyz = ndc.xyz * 2.0 - 1.0;
vec4 eyeCoord = uInvMvmProj * ndc;
return eyeCoord.xyz / eyeCoord.w;
}
//====================//
// method definitions //
//====================//
float InterleavedGradientNoise(const in vec2 pixel);
vec3 calcViewPosition(float fragmentDepth);
float getFarFogThickness(float dist);
float getHeightFogThickness(float dist);
float calculateHeightFogDepth(float worldYPos);
float mixFogThickness(float far, float height);
//======//
// main //
//======//
/**
* Fragment shader for fog.
* This should be passed last so it applies above other affects like AO
*
* version: 2023-6-21
* This should be run last so it applies above other affects like Ambient Occlusioning
*/
void main()
void main()
{
float vertexYPos = 100.0f;
float fragmentDepth = texture(uDepthMap, TexCoord).r;
fragColor = vec4(uFogColor.rgb, 0.0);
// a fragment depth of "1" means the fragment wasn't drawn to,
// we only want to apply Fog to LODs, not to the sky outside the LODs
if (fragmentDepth < 1.0) {
if (uFullFogMode == 0) {
if (fragmentDepth < 1.0)
{
int fogMode = uFullFogMode;
if (fogMode == 0)
{
// render fog based on distance from the camera
vec3 vertexWorldPos = calcViewPosition(fragmentDepth);
float horizontalDist = length(vertexWorldPos.xz) * uFogScale;
float heightDist = calculateHeightFogDepth(vertexWorldPos.y, vertexYPos) * uFogVerticalScale;
float farDist = calculateFarFogDepth(horizontalDist, length(vertexWorldPos.xyz) * uFogScale, uNearFogStart);
float horizontalWorldDistance = length(vertexWorldPos.xz) * uFogScale;
float worldDistance = length(vertexWorldPos.xyz) * uFogScale;
float activeDistance = uUseSphericalFog ? worldDistance : horizontalWorldDistance;
float nearFogThickness = getNearFogThickness(horizontalDist);
float farFogThickness = getFarFogThickness(farDist);
float heightFogThickness = getHeightFogThickness(heightDist);
float mixedFogThickness = mixFogThickness(nearFogThickness, farFogThickness, heightFogThickness);
// far fog
float farFogThickness = getFarFogThickness(activeDistance);
// height fog
float heightFogDepth = calculateHeightFogDepth(vertexWorldPos.y);
float heightFogThickness = getHeightFogThickness(heightFogDepth);
// combined fog
float mixedFogThickness = mixFogThickness(farFogThickness, heightFogThickness);
fragColor.a = clamp(mixedFogThickness, 0.0, 1.0);
float dither = InterleavedGradientNoise(gl_FragCoord.xy) - 0.5;
fragColor.a += dither / 255.0;
// test
//fragColor.a = heightFogThickness;
// dither fog (to smooth out aliasing)
//float dither = InterleavedGradientNoise(gl_FragCoord.xy) - 0.5;
//fragColor.a += dither / 255.0;
}
else if (uFullFogMode == 1) {
else if (fogMode == 1)
{
// render everything with the fog color
fragColor.a = 1.0;
}
else {
else
{
// test code.
// this can be fired by manually changing the fullFogMode to a (normally)
@@ -108,22 +129,174 @@ void main()
}
}
// Are these still needed?
float linearFog(float x, float fogStart, float fogLength, float fogMin, float fogRange) {
x = clamp((x-fogStart)/fogLength, 0.0, 1.0);
return fogMin + fogRange * x;
//
// methods //
//
float InterleavedGradientNoise(const in vec2 pixel)
{
float x = dot(pixel, MAGIC.xy);
return fract(MAGIC.z * fract(x));
}
vec3 calcViewPosition(float fragmentDepth)
{
vec4 ndc = vec4(TexCoord.xy, fragmentDepth, 1.0);
ndc.xyz = ndc.xyz * 2.0 - 1.0;
vec4 eyeCoord = uInvMvmProj * ndc;
return eyeCoord.xyz / eyeCoord.w;
}
float linearFog(float worldDist, float fogStart, float fogLength, float fogMin, float fogRange)
{
worldDist = (worldDist - fogStart) / fogLength;
worldDist = clamp(worldDist, 0.0, 1.0);
return fogMin + fogRange * worldDist;
}
float exponentialFog(float x, float fogStart, float fogLength,
float fogMin, float fogRange, float fogDensity)
float fogMin, float fogRange, float fogDensity)
{
x = max((x-fogStart)/fogLength, 0.0) * fogDensity;
return fogMin + fogRange - fogRange/exp(x);
}
float exponentialSquaredFog(float x, float fogStart, float fogLength,
float fogMin, float fogRange, float fogDensity)
float fogMin, float fogRange, float fogDensity)
{
x = max((x-fogStart)/fogLength, 0.0) * fogDensity;
return fogMin + fogRange - fogRange/exp(x*x);
}
//
// generated methods //
//
float getFarFogThickness(float dist)
{
if (uFogFalloffType == 0) // LINEAR
{
return linearFog(dist, uFarFogStart, uFarFogLength, uFarFogMin, uFarFogRange);
}
else if (uFogFalloffType == 1) // EXPONENTIAL
{
return exponentialFog(dist, uFarFogStart, uFarFogLength, uFarFogMin, uFarFogRange, uFarFogDensity);
}
else // EXPONENTIAL_SQUARED
{
return exponentialSquaredFog(dist, uFarFogStart, uFarFogLength, uFarFogMin, uFarFogRange, uFarFogDensity);
}
}
float getHeightFogThickness(float dist)
{
if (!uHeightFogEnabled)
{
return 0.0;
}
if (uHeightFogFalloffType == 0) // LINEAR
{
return linearFog(dist, uHeightFogStart, uHeightFogLength, uHeightFogMin, uHeightFogRange);
}
else if (uHeightFogFalloffType == 1) // EXPONENTIAL
{
return exponentialFog(dist, uHeightFogStart, uHeightFogLength, uHeightFogMin, uHeightFogRange, uHeightFogDensity);
}
else // EXPONENTIAL_SQUARED
{
return exponentialSquaredFog(dist, uHeightFogStart, uHeightFogLength, uHeightFogMin, uHeightFogRange, uHeightFogDensity);
}
}
/** 1 = full fog, 0 = no fog */
float calculateHeightFogDepth(float worldYPos)
{
// worldYPos -65 - 384
//worldYPos = worldYPos * -1; // negative, fog below height; positive, fog above height
//return worldYPos * uFogVerticalScale; // "* uFogVerticalScale" is done to convert world position to a percent of the world height;
if (!uHeightFogEnabled)
{
// ignore the height
return 0.0;
}
if (!uHeightBasedOnCamera)
{
worldYPos -= (uHeightFogBaseHeight - uCameraBlockYPos);
}
if (uHeightFogAppliesDown && uHeightFogAppliesUp)
{
// TODO this aint right
return abs(worldYPos) * uFogVerticalScale;
}
else if (uHeightFogAppliesDown)
{
// apploy fog below given height
return -worldYPos * uFogVerticalScale;
}
else if (uHeightFogAppliesUp)
{
// apply fog above given height
return worldYPos * uFogVerticalScale;
}
else
{
// shouldn't happen,
return 0;
}
}
float mixFogThickness(float far, float height)
{
switch (uHeightFogMixingMode)
{
case 0: // BASIC
case 1: // IGNORE_HEIGHT
return far;
case 2: // MAX
return max(far, height);
case 3: // ADDITION
return (far + height);
case 4: // MULTIPLY
return far * height;
case 5: // INVERSE_MULTIPLY
return (1.0 - (1.0-far)*(1.0-height));
case 6: // LIMITED_ADDITION
return (far + max(far, height));
case 7: // MULTIPLY_ADDITION
return (far + far*height);
case 8: // INVERSE_MULTIPLY_ADDITION
return (far + 1.0 - (1.0-far)*(1.0-height));
case 9: // AVERAGE
return (far*0.5 + height*0.5);
}
// shouldn't happen, but default to BASIC / IGNORE_HEIGHT
// if an invalid option is selected
return far;
}
@@ -39,9 +39,10 @@ public class LevelWrapperTest implements IDhApiLevelWrapper
@Override
public IDhApiDimensionTypeWrapper getDimensionType() { return null; }
@Override
public String getDimensionName() { return null; }
@Override
public String getDhIdentifier() { return null; }
@Override
public EDhApiLevelType getLevelType() { return null; }