Closes #85 (Sodium Overdraw incompatibility)

One minor issue: Sodium returns chunks differently than vanilla MC or Optifine. Specifically as of 1.16.5 (12-12-2021) it also returns one layer of chunks further than what is currently rendered.
This commit is contained in:
James Seibel
2021-12-12 17:42:23 -06:00
parent b385b018f1
commit 227f7d0a23
3 changed files with 149 additions and 46 deletions
@@ -19,8 +19,10 @@
package com.seibel.lod.core.handlers;
import java.util.HashSet;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
/**
* A singleton used to get variables from methods
@@ -35,7 +37,7 @@ import com.seibel.lod.core.objects.math.Mat4f;
* different MC versions.
*
* @author James Seibel
* @version 11-26-2021
* @version 12-12-2021
*/
public interface IReflectionHandler
{
@@ -45,14 +47,9 @@ public interface IReflectionHandler
/** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
boolean vivecraftPresent();
/**
* Modifies the projection matrix's clip planes.
* The projection matrix must be in column-major format.
*
* @param projectionMatrix The projection matrix to be modified.
* @param newNearClipPlane the new near clip plane value.
* @param newFarClipPlane the new far clip plane value.
* @return The modified matrix.
*/
Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
boolean sodiumPresent();
/** @returns a HashSet containing every chunk that is currently being rendered by Sodium. */
HashSet<AbstractChunkPosWrapper> getSodiumRenderedChunks();
}
@@ -20,39 +20,57 @@
package com.seibel.lod.core.handlers;
import java.lang.reflect.Field;
import java.util.HashSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.core.enums.rendering.FogDrawMode;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.util.SingletonHandler;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
/**
* A singleton used to get variables from methods
* where they are private or potentially absent.
* Specifically the fog setting in Optifine or the
* presence/absence of other mods.
* For example: the fog setting in Optifine or the
* presence/absence of Vivecraft.
*
* @author James Seibel
* @version 11-26-2021
* @version 12-12-2021
*/
public class ReflectionHandler implements IReflectionHandler
{
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName());
private static ReflectionHandler instance;
private final IWrapperFactory wrapperFactory;
public static ReflectionHandler instance;
private Field ofFogField = null;
private final Object mcOptionsObject;
private Boolean sodiumPresent = null;
private Field sodiumLoadedChunkPositionsField = null;
private Object sodiumWorldRendererInstance = null;
private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject)
{
wrapperFactory = SingletonHandler.get(IWrapperFactory.class);
mcOptionsObject = newMcOptionsObject;
setupFogField(optionFields);
if (sodiumPresent())
setupSodiumInteraction();
}
/**
@@ -75,6 +93,8 @@ public class ReflectionHandler implements IReflectionHandler
/** finds the Optifine fog type field */
private void setupFogField(Field[] optionFields)
{
@@ -155,41 +175,119 @@ public class ReflectionHandler implements IReflectionHandler
return false;
}
/**
* Modifies the projection matrix's clip planes.
* The projection matrix must be in column-major format.
*
* @param projectionMatrix The projection matrix to be modified.
* @param newNearClipPlane the new near clip plane value.
* @param newFarClipPlane the new far clip plane value.
* @return The modified matrix.
*/
@Override
public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane)
private void setupSodiumInteraction()
{
// find the matrix values.
float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
String errorMessagePrfix = ReflectionHandler.class.getSimpleName() + ": was unable to setup Sodium interaction. Error: ";
try
{
// TODO this was originally created before we had the Mat4f object,
// so this doesn't need to be done with reflection anymore.
// And should be moved to RenderUtil
// try getting the SodiumWorldRender class
Class<?> sodiumWorldRendererClass = Class.forName("me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer");
// get the fields of the projectionMatrix
Field[] fields = projectionMatrix.getClass().getDeclaredFields();
// bypass the security protections on the fields that encode near and far plane values.
fields[10].setAccessible(true);
fields[11].setAccessible(true);
// Change the values of the near and far plane.
fields[10].set(projectionMatrix, nearMatrixValue);
fields[11].set(projectionMatrix, farMatrixValue);
Field sodiumWorldRendererInstanceField = sodiumWorldRendererClass.getDeclaredField("instance");
sodiumWorldRendererInstanceField.setAccessible(true); // the field is private by default
try
{
// try getting the singleton from the static field
sodiumWorldRendererInstance = sodiumWorldRendererInstanceField.get(null);
try
{
// try getting the loadedChunkPosition field
Field loadedPosField = sodiumWorldRendererInstance.getClass().getDeclaredField("loadedChunkPositions");
loadedPosField.setAccessible(true);
sodiumLoadedChunkPositionsField = loadedPosField;
}
catch (IllegalArgumentException e)
{
LOGGER.info(errorMessagePrfix + " no loadedChunkPositions field.", e);
}
}
catch (IllegalArgumentException | IllegalAccessException e)
{
LOGGER.info(errorMessagePrfix + " no sodiumWorldRenderer instance.", e);
}
}
catch (Exception e)
catch (NoSuchFieldException | SecurityException | ClassNotFoundException e)
{
e.printStackTrace();
LOGGER.info(errorMessagePrfix + " no sodiumWorldRenderer class.", e);
}
return projectionMatrix;
}
@Override
public boolean sodiumPresent()
{
// we don't want to run a potentially expensive
// reflection search operation every time this method is called
if (sodiumPresent == null)
{
try
{
Class.forName("me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer");
sodiumPresent = true;
}
catch (ClassNotFoundException e)
{
sodiumPresent = false;
}
}
return sodiumPresent;
}
/**
* TODO: this returns chunks that aren't actually rendered.
* Specifically as of 1.16.5 (12-12-2021) it also returns one layer
* of chunks further than what is currently rendered.
*/
@Override
public HashSet<AbstractChunkPosWrapper> getSodiumRenderedChunks()
{
if (!sodiumPresent())
{
throw new IllegalStateException("[getSodiumRenderedChunks] can only be called if Sodium is installed.");
}
if (sodiumLoadedChunkPositionsField == null || sodiumWorldRendererInstance == null)
{
throw new IllegalStateException("[getSodiumRenderedChunks] was called either before the sodium setup was done, or the sodium setup failed.");
}
HashSet<AbstractChunkPosWrapper> loadedPos = new HashSet<>();
try
{
LongSet loadedChunkPositions = (LongSet) sodiumLoadedChunkPositionsField.get(sodiumWorldRendererInstance);
LongIterator iterator = loadedChunkPositions.iterator();
while (iterator.hasNext())
{
loadedPos.add(wrapperFactory.createChunkPos(iterator.nextLong()));
}
}
catch (IllegalArgumentException | IllegalAccessException e)
{
LOGGER.error("Unable to get sodium's rendered chunks" + e.getMessage(), e);
}
// // can be uncommented for debugging
// StringBuilder builder = new StringBuilder(loadedPos.size() * 4);
// for(AbstractChunkPosWrapper pos : loadedPos)
// {
// builder.append("(" + pos.getX() + "," + pos.getZ() + ") ");
// }
// ClientApi.LOGGER.info(builder.toString());
return loadedPos;
}
}
@@ -25,12 +25,13 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
import com.seibel.lod.forge.wrappers.chunk.ChunkPosWrapper;
/**
* This handles creating abstract wrapper objects.
*
* @author James Seibel
* @version 12-11-2021
* @version 12-12-2021
*/
public interface IWrapperFactory
{
@@ -39,6 +40,13 @@ public interface IWrapperFactory
AbstractChunkPosWrapper createChunkPos();
public default AbstractChunkPosWrapper createChunkPos(long xAndZPositionCombined)
{
int x = (int) (xAndZPositionCombined & Integer.MAX_VALUE);
int z = (int) (xAndZPositionCombined >> Long.SIZE / 2) & Integer.MAX_VALUE;
return new ChunkPosWrapper(x, z);
}
AbstractChunkPosWrapper createChunkPos(int x, int z);
AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);