From 227f7d0a23051e4969c9131bd68014629000c7b9 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 12 Dec 2021 17:42:23 -0600 Subject: [PATCH] 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. --- .../lod/core/handlers/IReflectionHandler.java | 21 +-- .../lod/core/handlers/ReflectionHandler.java | 164 ++++++++++++++---- .../wrapperInterfaces/IWrapperFactory.java | 10 +- 3 files changed, 149 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java b/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java index 6e7aa1c2c..e1da9c7dc 100644 --- a/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java +++ b/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java @@ -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 getSodiumRenderedChunks(); } diff --git a/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java b/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java index a096bb71e..c41b6b35c 100644 --- a/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java +++ b/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java @@ -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 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 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; } } diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java index 15ab5ee46..965d82e58 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java @@ -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);