From 03ce4d9c421efce29107df49a1752ff601518043 Mon Sep 17 00:00:00 2001 From: Morippi Date: Fri, 9 Jul 2021 19:23:14 +0200 Subject: [PATCH] Several chages to converto to quadTree --- .../lod/builders/LodNodeBufferBuilder.java | 357 ++++++++ .../lod/objects/quadTree/LodNodeData.java | 1 - .../lod/objects/quadTree/LodQuadTree.java | 12 +- .../quadTree/LodQuadTreeDimension.java | 8 +- .../lod/objects/quadTree/QuadTreeImage.java | 32 +- .../com/seibel/lod/proxy/ClientProxy.java | 12 +- .../seibel/lod/render/LodNodeRenderer.java | 844 ++++++++++++++++++ 7 files changed, 1241 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java create mode 100644 src/main/java/com/seibel/lod/render/LodNodeRenderer.java diff --git a/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java new file mode 100644 index 000000000..52823b3ad --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java @@ -0,0 +1,357 @@ +package com.seibel.lod.builders; + +import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker; +import com.seibel.lod.handlers.LodConfig; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.NearFarBuffer; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.objects.quadTree.LodQuadTree; +import com.seibel.lod.objects.quadTree.LodQuadTreeDimension; +import com.seibel.lod.render.LodRenderer; +import com.seibel.lod.util.LodUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.biome.BiomeContainer; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.common.WorldWorkerManager; +import org.lwjgl.opengl.GL11; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * This object is used to create NearFarBuffer objects. + * + * @author James Seibel + * @version 06-19-2021 + */ +public class LodNodeBufferBuilder +{ + private Minecraft mc; + + /** This holds the thread used to generate new LODs off the main thread. */ + private ExecutorService genThread = Executors.newSingleThreadExecutor(); + + private LodChunkBuilder lodChunkBuilder; + + /** The buffers that are used to create LODs using near fog */ + public volatile BufferBuilder buildableNearBuffer; + /** The buffers that are used to create LODs using far fog */ + public volatile BufferBuilder buildableFarBuffer; + + /** if this is true the LOD buffers are currently being + * regenerated. */ + public volatile boolean generatingBuffers = false; + + /** if this is true new LOD buffers have been generated + * and are waiting to be swapped with the drawable buffers*/ + private volatile boolean switchBuffers = false; + + /** If this is greater than 0 no new chunk generation requests will be made + * this is to prevent chunks from being generated for a long time in an area + * the player is no longer in. */ + public volatile int numberOfChunksWaitingToGenerate = 0; + + /** how many chunks to generate outside of the player's + * view distance at one time. (or more specifically how + * many requests to make at one time) */ + public int maxChunkGenRequests = Runtime.getRuntime().availableProcessors(); + + + public LodNodeBufferBuilder(LodChunkBuilder newLodBuilder) + { + mc = Minecraft.getInstance(); + lodChunkBuilder = newLodBuilder; + } + + + private BiomeContainer biomeContainer = null; + private LodDimension previousDimension = null; + + + /** + * Create a thread to asynchronously generate LOD buffers + * centered around the given camera X and Z. + *
+ * This method will write to the drawableNearBuffers and drawableFarBuffers. + *
+ * After the buildable buffers have been generated they must be + * swapped with the drawable buffers in the LodRenderer to be drawn. + */ + public void generateLodBuffersAsync(LodRenderer renderer, LodQuadTreeDimension lodDim, + double playerX, double playerZ, int numbChunksWide) + { + // only allow one generation process to happen at a time + if (generatingBuffers) + return; + + if (buildableNearBuffer == null || buildableFarBuffer == null) + throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created."); + + if (previousDimension != lodDim) + { + biomeContainer = LodUtil.getServerWorldFromDimension(lodDim.dimension).getChunk(0, 0, ChunkStatus.EMPTY).getBiomes(); + previousDimension = lodDim; + } + + + + generatingBuffers = true; + + + + // this seemingly useless math is required, + // just using (int) playerX/Z doesn't work + int playerXChunkOffset = ((int) playerX / LodChunk.WIDTH) * LodChunk.WIDTH; + int playerZChunkOffset = ((int) playerZ / LodChunk.WIDTH) * LodChunk.WIDTH; + // this is where we will start drawing squares + // (exactly half the total width) + int startX = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerXChunkOffset; + int startZ = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerZChunkOffset; + + + Thread t = new Thread(()-> + { + // index of the chunk currently being added to the + // generation list + int chunkGenIndex = 0; + + ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests]; + // if we don't have a full number of chunks to generate in chunksToGen + // we can top it off from the reserve + ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests]; + int minChunkDist = Integer.MAX_VALUE; + ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodChunk.WIDTH, (int)playerZ / LodChunk.WIDTH); + + + // generate our new buildable buffers + buildableNearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT); + buildableFarBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT); + + // x axis + for (int i = 0; i < numbChunksWide; i++) + { + // z axis + for (int j = 0; j < numbChunksWide; j++) + { + int chunkX = i + (startX / LodChunk.WIDTH); + int chunkZ = j + (startZ / LodChunk.WIDTH); + + // skip any chunks that Minecraft is going to render + if(isCoordInCenterArea(i, j, (numbChunksWide / 2)) + && renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkX, chunkZ))) + { + continue; + } + + + // set where this square will be drawn in the world + double xOffset = (LodChunk.WIDTH * i) + // offset by the number of LOD blocks + startX; // offset so the center LOD block is centered underneath the player + double yOffset = 0; + double zOffset = (LodChunk.WIDTH * j) + startZ; + + LodChunk lod = lodDim.getLodFromCoordinates(chunkX, chunkZ, LodNodeData.CHUNK_LEVEL); + + if (lod == null || lod.isLodEmpty()) + { + // generate a new chunk if no chunk currently exists + // and we aren't waiting on any other chunks to generate + if (lod == null && numberOfChunksWaitingToGenerate < maxChunkGenRequests) + { + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + + // determine if this position is closer to the player + // than the previous + int newDistance = playerChunkPos.getChessboardDistance(pos); + + if (newDistance < minChunkDist) + { + // this chunk is closer, clear any previous + // positions and update the new minimum distance + minChunkDist = newDistance; + chunksToGenReserve = chunksToGen; + + chunkGenIndex = 0; + chunksToGen = new ChunkPos[maxChunkGenRequests]; + chunksToGen[chunkGenIndex] = pos; + chunkGenIndex++; + } + else if (newDistance <= minChunkDist) + { + // this chunk position is as close or closers than the + // minimum distance + if(chunkGenIndex < maxChunkGenRequests) + { + // we are still under the number of chunks to generate + // add this position to the list + chunksToGen[chunkGenIndex] = pos; + chunkGenIndex++; + } + } + + } + // don't render this null chunk + continue; + } + + + BufferBuilder currentBuffer = null; + if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2)) + currentBuffer = buildableNearBuffer; + else + currentBuffer = buildableFarBuffer; + + // get the desired LodTemplate and + // add this LOD to the buffer + LodConfig.CLIENT.lodTemplate.get(). + template.addLodToBuffer(currentBuffer, lodDim, lod, + xOffset, yOffset, zOffset, renderer.debugging); + } + } + + // TODO add a way for a server side mod to generate chunks requested here + if(mc.hasSingleplayerServer()) + { + ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension); + + // make sure we have as many chunks to generate as we are allowed + if (chunkGenIndex < maxChunkGenRequests) + { + for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++) + { + chunksToGen[i] = chunksToGenReserve[j]; + } + } + + // start chunk generation + for(ChunkPos chunkPos : chunksToGen) + { + if(chunkPos == null) + break; + + numberOfChunksWaitingToGenerate++; + + LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodChunkBuilder, this, lodDim, serverWorld, biomeContainer); + WorldWorkerManager.addWorker(genWorker); + } + } + + // finish the buffer building + buildableNearBuffer.end(); + buildableFarBuffer.end(); + + // mark that the buildable buffers as ready to swap + generatingBuffers = false; + switchBuffers = true; + }); + + genThread.execute(t); + + return; + } + + + + + + + + + + + //====================// + // generation helpers // + //====================// + + /** + * Returns if the given coordinate is in the loaded area of the world. + * @param centerCoordinate the center of the loaded world + */ + private boolean isCoordInCenterArea(int i, int j, int centerCoordinate) + { + return (i >= centerCoordinate - mc.options.renderDistance + && i <= centerCoordinate + mc.options.renderDistance) + && + (j >= centerCoordinate - mc.options.renderDistance + && j <= centerCoordinate + mc.options.renderDistance); + } + + + /** + * Find the coordinates that are in the center half of the given + * 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius). + */ + private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius) + { + int halfRadius = lodRadius / 2; + + return (chunkX >= lodRadius - halfRadius + && chunkX <= lodRadius + halfRadius) + && + (chunkZ >= lodRadius - halfRadius + && chunkZ <= lodRadius + halfRadius); + } + + + + + + //===============================// + // BufferBuilder related methods // + //===============================// + + + /** + * Called from the LodRenderer to create the + * BufferBuilders at the right size. + * + * @param bufferMaxCapacity + */ + public void setupBuffers(int bufferMaxCapacity) + { + buildableNearBuffer = new BufferBuilder(bufferMaxCapacity); + buildableFarBuffer = new BufferBuilder(bufferMaxCapacity); + } + + /** + * Swap the drawable and buildable buffers and return + * the old drawable buffers. + * @param drawableNearBuffer + * @param drawableFarBuffer + */ + public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer) + { + // swap the BufferBuilders + BufferBuilder tmp = buildableNearBuffer; + buildableNearBuffer = drawableNearBuffer; + drawableNearBuffer = tmp; + + tmp = buildableFarBuffer; + buildableFarBuffer = drawableFarBuffer; + drawableFarBuffer = tmp; + + + // the buffers have been swapped + switchBuffers = false; + + return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer); + } + + /** + * If this is true the buildable near and far + * buffers have been generated and are ready to be + * sent to the LodRenderer. + */ + public boolean newBuffersAvaliable() + { + return switchBuffers; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/objects/quadTree/LodNodeData.java b/src/main/java/com/seibel/lod/objects/quadTree/LodNodeData.java index d0b15f41c..7a818fa30 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/LodNodeData.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/LodNodeData.java @@ -204,7 +204,6 @@ public class LodNodeData { } - public int hashCode(){ return Objects.hash(this.real, this.level, this.posX, this.posZ, this.color, this.real, this.voidNode); } diff --git a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java index 83f806d41..625014f0e 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java @@ -171,10 +171,10 @@ public class LodQuadTree { return lodNodeData; } else if (targetLevel < currentLevel) { short widthRatio = (short) (lodNodeData.width / Math.pow(2, level)); - int NS = (posX / widthRatio) % lodNodeData.posX; - int WE = (posZ / widthRatio) % lodNodeData.posZ; + int NS = Math.abs((posX / widthRatio) % lodNodeData.posX); + int WE = Math.abs((posZ / widthRatio) % lodNodeData.posZ); if (getChild(NS, WE) == null) { - return lodNodeData; + return null; } LodQuadTree child = getChild(NS, WE); return child.getNodeAtLevelPosition(posX, posZ, level); @@ -184,6 +184,7 @@ public class LodQuadTree { } + public LodQuadTree getChild(int NS, int WE) { return children[NS][WE]; } @@ -439,9 +440,14 @@ public class LodQuadTree { return (lodNodeData != null); } + public LodNodeData getLodFromCoordinate(int x, int z, byte level){ + + } + public boolean isCoordinateInLevel(int x, int z){ return !(lodNodeData.startX > x || lodNodeData.startZ > z || lodNodeData.endX < x || lodNodeData.endZ < z); } + public String toString(){ String s = lodNodeData.toString(); return s; diff --git a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java index b0d050084..9f78ef4c3 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java @@ -297,15 +297,15 @@ public class LodQuadTreeDimension { */ public LodNodeData getLodFromCoordinates(int posX, int posZ, byte level) { - /*TODO */ - return null; + LodQuadTree region = getRegion(posX/(512/Math.pow(level,2)),posZ/(512/Math.pow(level,2))); + if(region == null) + return null; + return region.getLodFromCoordinate(posX, posZ, level); /* RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(chunkX, chunkZ)); LodQuadTree region = getRegion(pos.x, pos.z); - if(region == null) - return null; return region.getNode(chunkX, chunkZ); diff --git a/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java b/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java index 0929102de..0fdf86342 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java @@ -2,6 +2,7 @@ package com.seibel.lod.objects.quadTree; import com.seibel.lod.util.BiomeColorsUtils; import kaptainwutax.biomeutils.biome.Biome; +import kaptainwutax.biomeutils.source.EndBiomeSource; import kaptainwutax.biomeutils.source.OverworldBiomeSource; import kaptainwutax.mcutils.version.MCVersion; @@ -35,7 +36,7 @@ import javax.swing.Timer; @SuppressWarnings("serial") public class QuadTreeImage extends JPanel { - private static final int PREF_W = (int) 0.1*16*512; + private static final int PREF_W = 1000; private static final int PREF_H = PREF_W; private List drawables = new ArrayList<>(); @@ -74,9 +75,9 @@ public class QuadTreeImage extends JPanel { } private static void createAndShowGui() { - int playerX = 8*512; - int playerZ = (8*512); - LodQuadTreeDimension dim = new LodQuadTreeDimension(null, null, 16); + int playerX = 32*512; + int playerZ = (32*512); + LodQuadTreeDimension dim = new LodQuadTreeDimension(null, null, 64); System.out.println(dim.getRegion(0, 0)); dim.move(playerX/512,playerZ/512); System.out.println(dim.getCenterX()); @@ -92,16 +93,18 @@ public class QuadTreeImage extends JPanel { frame.setLocationByPlatform(true); frame.setVisible(true); List> listOfList = new ArrayList<>(); - OverworldBiomeSource biomeSource = new OverworldBiomeSource(MCVersion.v1_16_5, 100); + //OverworldBiomeSource biomeSource = new OverworldBiomeSource(MCVersion.v1_16_5, 1000); + EndBiomeSource biomeSource = new EndBiomeSource(MCVersion.v1_16_5, 1000); + int[] distances = {100000,8000,4000,2000,1000,500,250,100,50,25}; for (int i = 0; i <= (9 - 2); i++) { for (int j = 0; j < 1; j++) { int dist; - if (i == 9) { - dist = 30; + if (i == 0) { + dist = 2000; } else { - dist = 30; + dist = 32; } - List levelToGenerate = dim.getNodeToGenerate(playerX, playerZ, (byte) (9 - i), (int) (dist*Math.pow(9-i,2.5)), 0); + List levelToGenerate = dim.getNodeToGenerate(playerX, playerZ, (byte) (9 - i), distances[i], 0); for (LodQuadTree level : levelToGenerate) { Color color; int startX = level.getLodNodeData().startX; @@ -138,12 +141,15 @@ public class QuadTreeImage extends JPanel { } } } - List lodList = dim.getNodeToRender(playerX,playerZ,(byte) 8, 10000,4000); + List lodList = new ArrayList<>(); + lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 9, 100000,8000)); + lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 8, 8000,4000)); lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 7, 4000,2000)); lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 6, 2000,1000)); lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 5, 1000,500)); - lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 4, 500,0)); + lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 4, 500,250)); lodList.addAll(dim.getNodeToRender(playerX,playerZ,(byte) 3, 250,0)); + System.out.println(lodList.size()); //Collection lodList = dim.getNodes(false,false,false); // lodList.addAll(lodQuadTree.getNodeToRender(playerX, playerZ, (byte) 2, 100, 0)); // lodList.addAll(lodQuadTree.getNodeToRender(playerX, playerZ, (byte) 3, 200, 100)); @@ -171,7 +177,7 @@ public class QuadTreeImage extends JPanel { } else { if(drawCount==0) quadTreeImage.clearAll(); final List myDrawables = new ArrayList<>(); - double amp = 0.1; + double amp = 0.025; Collection lodList = listOfList.get(drawCount); for (LodNodeData data : lodList) { System.out.println(); @@ -196,7 +202,7 @@ public class QuadTreeImage extends JPanel { frame.printAll(g2d); g2d.dispose(); try { - ImageIO.write(img, "png", new File("Img" + drawCount+".png")); + ImageIO.write(img, "png", new File("ImgEnd" + drawCount+".png")); } catch (IOException ioException) { ioException.printStackTrace(); } diff --git a/src/main/java/com/seibel/lod/proxy/ClientProxy.java b/src/main/java/com/seibel/lod/proxy/ClientProxy.java index 02a450897..66ceb45bc 100644 --- a/src/main/java/com/seibel/lod/proxy/ClientProxy.java +++ b/src/main/java/com/seibel/lod/proxy/ClientProxy.java @@ -1,5 +1,9 @@ package com.seibel.lod.proxy; +import com.seibel.lod.builders.LodNodeBufferBuilder; +import com.seibel.lod.builders.LodNodeBuilder; +import com.seibel.lod.objects.quadTree.LodQuadTreeWorld; +import com.seibel.lod.render.LodNodeRenderer; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -31,10 +35,10 @@ public class ClientProxy { public static final Logger LOGGER = LogManager.getLogger("LOD"); - private static LodWorld lodWorld = new LodWorld(); - private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder(); - private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder); - private static LodRenderer renderer = new LodRenderer(lodBufferBuilder); + private static LodQuadTreeWorld lodWorld = new LodQuadTreeWorld(); + private static LodNodeBuilder lodChunkBuilder = new LodNodeBuilder(); + private static LodNodeBufferBuilder lodBufferBuilder = new LodNodeBufferBuilder(lodChunkBuilder); + private static LodNodeRenderer renderer = new LodNodeRenderer(lodBufferBuilder); Minecraft mc = Minecraft.getInstance(); diff --git a/src/main/java/com/seibel/lod/render/LodNodeRenderer.java b/src/main/java/com/seibel/lod/render/LodNodeRenderer.java new file mode 100644 index 000000000..71d297d4e --- /dev/null +++ b/src/main/java/com/seibel/lod/render/LodNodeRenderer.java @@ -0,0 +1,844 @@ +package com.seibel.lod.render; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.seibel.lod.builders.LodBufferBuilder; +import com.seibel.lod.builders.LodNodeBufferBuilder; +import com.seibel.lod.enums.FogDistance; +import com.seibel.lod.enums.FogDrawOverride; +import com.seibel.lod.enums.FogQuality; +import com.seibel.lod.handlers.LodConfig; +import com.seibel.lod.handlers.ReflectionHandler; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.NearFarBuffer; +import com.seibel.lod.objects.NearFarFogSettings; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.proxy.ClientProxy; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.player.ClientPlayerEntity; +import net.minecraft.client.renderer.*; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.renderer.vertex.VertexBuffer; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.potion.Effects; +import net.minecraft.profiler.IProfiler; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Matrix4f; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.util.math.vector.Vector3f; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.NVFogDistance; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.HashSet; + + +/** + * This is where all the magic happens.
+ * This is where LODs are draw to the world. + * + * @author James Seibel + * @version 07-4-2021 + */ +public class LodNodeRenderer +{ + /** this is the light used when rendering the LODs, + * it should be something different than what is used by Minecraft */ + private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2; + + /** + * 64 MB by default is the maximum amount of memory that + * can be directly allocated.

+ * + * I know there are commands to change that amount + * (specifically "-XX:MaxDirectMemorySize"), but + * I have no idea how to access that amount.
+ * So I guess this will be the hard limit for now.

+ * + * https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx + */ + public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024; + + /** Does this computer's GPU support fancy fog? */ + public static boolean fancyFogAvailable = false; + + + + /** If true the LODs colors will be replaced with + * a checkerboard, this can be used for debugging. */ + public boolean debugging = false; + + private Minecraft mc; + private GameRenderer gameRender; + private IProfiler profiler; + private float farPlaneDistance; + private ReflectionHandler reflectionHandler; + + + /** This is used to generate the buildable buffers */ + private LodNodeBufferBuilder lodNodeBufferBuilder; + + /** The buffers that are used to draw LODs using near fog */ + private volatile BufferBuilder drawableNearBuffer; + /** The buffers that are used to draw LODs using far fog */ + private volatile BufferBuilder drawableFarBuffer; + + /** This is the VertexBuffer used to draw any LODs that use near fog */ + private volatile VertexBuffer nearVbo; + /** This is the VertexBuffer used to draw any LODs that use far fog */ + private volatile VertexBuffer farVbo; + public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR; + + + /** This is used to determine if the LODs should be regenerated */ + private int previousChunkRenderDistance = 0; + /** This is used to determine if the LODs should be regenerated */ + private int prevChunkX = 0; + /** This is used to determine if the LODs should be regenerated */ + private int prevChunkZ = 0; + /** This is used to determine if the LODs should be regenerated */ + private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR; + + /** if this is true the LOD buffers should be regenerated, + * provided they aren't already being regenerated. */ + private volatile boolean regen = false; + + /** This HashSet contains every chunk that Vanilla Minecraft + * is going to render */ + public HashSet vanillaRenderedChunks = new HashSet<>(); + + + + public LodNodeRenderer(LodNodeBufferBuilder newLodNodeBufferBuilder) + { + mc = Minecraft.getInstance(); + gameRender = mc.gameRenderer; + + reflectionHandler = new ReflectionHandler(); + lodNodeBufferBuilder = newLodNodeBufferBuilder; + } + + + /** + * Besides drawing the LODs this method also starts + * the async process of generating the Buffers that hold those LODs. + * + * @param newDimension The dimension to draw, if null doesn't replace the current dimension. + * @param partialTicks how far into the current tick this method was called. + */ + public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler) + { + if (lodDim == null) + { + // if there aren't any loaded LodChunks + // don't try drawing anything + return; + } + + + + + + + //===============// + // initial setup // + //===============// + + profiler = newProfiler; + profiler.push("LOD setup"); + + ClientPlayerEntity player = mc.player; + + // should LODs be regenerated? + if ((int)player.getX() / LodChunk.WIDTH != prevChunkX || + (int)player.getZ() / LodChunk.WIDTH != prevChunkZ || + previousChunkRenderDistance != mc.options.renderDistance || + prevFogDistance != LodConfig.CLIENT.fogDistance.get()) + { + // yes + regen = true; + + prevChunkX = (int)player.getX() / LodChunk.WIDTH; + prevChunkZ = (int)player.getZ() / LodChunk.WIDTH; + prevFogDistance = LodConfig.CLIENT.fogDistance.get(); + } + else + { + // nope, the player hasn't moved, the + // render distance hasn't changed, and + // the dimension is the same + } + + // did the user change the debug setting? + if (LodConfig.CLIENT.debugMode.get() != debugging) + { + debugging = LodConfig.CLIENT.debugMode.get(); + regen = true; + } + + + // determine how far the game's render distance is currently set + int renderDistWidth = mc.options.renderDistance; + farPlaneDistance = renderDistWidth * LodChunk.WIDTH; + + // set how big the LODs will be and how far they will go + int totalLength = (int) farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 10; + int numbChunksWide = (totalLength / LodChunk.WIDTH); + + // determine which LODs should not be rendered close to the player + HashSet chunkPosToSkip = getNearbyLodChunkPosToSkip(lodDim, player.blockPosition()); + + // see if the chunks Minecraft is going to render are the + // same as last time + if (!vanillaRenderedChunks.containsAll(chunkPosToSkip)) + { + regen = true; + vanillaRenderedChunks = chunkPosToSkip; + } + + + + + + //=================// + // create the LODs // + //=================// + + // only regenerate the LODs if: + // 1. we want to regenerate LODs + // 2. we aren't already regenerating the LODs + // 3. we aren't waiting for the build and draw buffers to swap + // (this is to prevent thread conflicts) + if (regen && !lodNodeBufferBuilder.generatingBuffers && !lodNodeBufferBuilder.newBuffersAvaliable()) + { + // this will mainly happen when the view distance is changed + if (drawableNearBuffer == null || drawableFarBuffer == null || + previousChunkRenderDistance != mc.options.renderDistance) + setupBuffers(numbChunksWide); + + // generate the LODs on a separate thread to prevent stuttering or freezing + lodNodeBufferBuilder.generateLodBuffersAsync(this, lodDim, player.getX(), player.getZ(), numbChunksWide); + + // the regen process has been started, + // it will be done when lodBufferBuilder.newBuffersAvaliable + // is true + regen = false; + } + + // replace the buffers used to draw and build, + // this is only done when the createLodBufferGenerationThread + // has finished executing on a parallel thread. + if (lodBufferBuilder.newBuffersAvaliable()) + { + swapBuffers(); + } + + + + + + //===========================// + // GL settings for rendering // + //===========================// + + // set the required open GL settings + GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_COLOR_MATERIAL); + GL11.glEnable(GL11.GL_DEPTH_TEST); + + // disable the lights Minecraft uses + GL11.glDisable(GL11.GL_LIGHT0); + GL11.glDisable(GL11.GL_LIGHT1); + + // get the default projection matrix so we can + // reset it after drawing the LODs + float[] defaultProjMatrix = new float[16]; + GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix); + + Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks); + + setupProjectionMatrix(partialTicks); + setupLighting(lodDim, partialTicks); + + NearFarFogSettings fogSettings = determineFogSettings(); + + // determine the current fog settings so they can be + // reset after drawing the LODs + float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START); + float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END); + int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE); + int defaultFogDistance = GL11.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV); + + + + + + + //===========// + // rendering // + //===========// + profiler.popPush("LOD draw"); + + setupFog(fogSettings.near.distance, fogSettings.near.quality); + sendLodsToGpuAndDraw(nearVbo, modelViewMatrix); + + setupFog(fogSettings.far.distance, fogSettings.far.quality); + sendLodsToGpuAndDraw(farVbo, modelViewMatrix); + + + + + + //=========// + // cleanup // + //=========// + + profiler.popPush("LOD cleanup"); + + GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(LOD_GL_LIGHT_NUMBER); + // re-enable the lights Minecraft uses + GL11.glEnable(GL11.GL_LIGHT0); + GL11.glEnable(GL11.GL_LIGHT1); + RenderSystem.disableLighting(); + + // this can't be called until after the buffers are built + // because otherwise the buffers may be set to the wrong size + previousChunkRenderDistance = mc.options.renderDistance; + + // reset the fog settings so the normal chunks + // will be drawn correctly + cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance); + + // reset the projection matrix so anything drawn after + // the LODs will use the correct projection matrix + Matrix4f mvm = new Matrix4f(defaultProjMatrix); + mvm.transpose(); + gameRender.resetProjectionMatrix(mvm); + + // clear the depth buffer so anything drawn is drawn + // over the LODs + GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); + + + // end of internal LOD profiling + profiler.pop(); + } + + + + /** + * This is where the actual drawing happens. + * + * @param buffers the buffers sent to the GPU to draw + */ + private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix) + { + if (vbo == null) + return; + + vbo.bind(); + // 0L is the starting pointer + LOD_VERTEX_FORMAT.setupBufferState(0L); + + vbo.draw(modelViewMatrix, GL11.GL_QUADS); + + VertexBuffer.unbind(); + LOD_VERTEX_FORMAT.clearBufferState(); + } + + + + + + + + //=================// + // Setup Functions // + //=================// + + @SuppressWarnings("deprecation") + private void setupFog(FogDistance fogDistance, FogQuality fogQuality) + { + if(fogQuality == FogQuality.OFF) + { + FogRenderer.setupNoFog(); + RenderSystem.disableFog(); + return; + } + + if(fogDistance == FogDistance.NEAR_AND_FAR) + { + throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance."); + } + + + // determine the fog distance mode to use + int glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV; + if (fogQuality == FogQuality.FANCY) + { + // fancy fog (fragment distance based fog) + glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV; + } + else + { + // fast fog (frustum distance based fog) + glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV; + } + + + // the multipliers are percentages + // of the regular view distance. + if(fogDistance == FogDistance.NEAR) + { + // the reason that I wrote fogEnd then fogStart backwards + // is because we are using fog backwards to how + // it is normally used, with it hiding near objects + // instead of far objects. + + if (fogQuality == FogQuality.FANCY) + { + RenderSystem.fogEnd(farPlaneDistance * 1.75f); + RenderSystem.fogStart(farPlaneDistance * 1.95f); + } + else if(fogQuality == FogQuality.FAST) + { + // for the far fog of the normal chunks + // to start right where the LODs' end use: + // end = 0.8f, start = 1.5f + + RenderSystem.fogEnd(farPlaneDistance * 1.5f); + RenderSystem.fogStart(farPlaneDistance * 2.0f); + } + } + else if(fogDistance == FogDistance.FAR) + { + if (fogQuality == FogQuality.FANCY) + { + RenderSystem.fogStart(farPlaneDistance * 0.85f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + RenderSystem.fogEnd(farPlaneDistance * 1.0f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + } + else if(fogQuality == FogQuality.FAST) + { + RenderSystem.fogStart(farPlaneDistance * 0.5f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + RenderSystem.fogEnd(farPlaneDistance * 0.75f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + } + } + + + GL11.glEnable(GL11.GL_FOG); + RenderSystem.enableFog(); + RenderSystem.setupNvFogDistance(); + RenderSystem.fogMode(GlStateManager.FogMode.LINEAR); + GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode); + } + + /** + * Revert any changes that were made to the fog. + */ + private void cleanupFog(NearFarFogSettings fogSettings, + float defaultFogStartDist, float defaultFogEndDist, + int defaultFogMode, int defaultFogDistance) + { + RenderSystem.fogStart(defaultFogStartDist); + RenderSystem.fogEnd(defaultFogEndDist); + RenderSystem.fogMode(defaultFogMode); + GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance); + + // disable fog if Minecraft wasn't rendering fog + // but we were + if(!fogSettings.vanillaIsRenderingFog && + (fogSettings.near.quality != FogQuality.OFF || + fogSettings.far.quality != FogQuality.OFF)) + { + GL11.glDisable(GL11.GL_FOG); + } + } + + + /** + * Create the model view matrix to move the LODs + * from object space into world space. + */ + private Matrix4f generateModelViewMatrix(float partialTicks) + { + // get all relevant camera info + ActiveRenderInfo renderInfo = mc.gameRenderer.getMainCamera(); + Vector3d projectedView = renderInfo.getPosition(); + + + // generate the model view matrix + MatrixStack matrixStack = new MatrixStack(); + matrixStack.pushPose(); + // translate and rotate to the current camera location + matrixStack.mulPose(Vector3f.XP.rotationDegrees(renderInfo.getXRot())); + matrixStack.mulPose(Vector3f.YP.rotationDegrees(renderInfo.getYRot() + 180)); + matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z); + + return matrixStack.last().pose(); + } + + + /** + * create a new projection matrix and send it over to the GPU + *

+ * A lot of this code is copied from renderLevel (line 567) + * in the GameRender class. The code copied is anything with + * a matrixStack and is responsible for making sure the LOD + * objects distort correctly relative to the rest of the world. + * Distortions are caused by: standing in a nether portal, + * nausea potion effect, walking bobbing. + * + * @param partialTicks how many ticks into the frame we are + */ + private void setupProjectionMatrix(float partialTicks) + { + // Note: if the LOD objects don't distort correctly + // compared to regular minecraft terrain, make sure + // all the transformations in renderWorld are here too + + MatrixStack matrixStack = new MatrixStack(); + matrixStack.pushPose(); + + gameRender.bobHurt(matrixStack, partialTicks); + if (this.mc.options.bobView) { + gameRender.bobView(matrixStack, partialTicks); + } + + // potion and nausea effects + float f = MathHelper.lerp(partialTicks, this.mc.player.oPortalTime, this.mc.player.portalTime) * this.mc.options.screenEffectScale * this.mc.options.screenEffectScale; + if (f > 0.0F) { + int i = this.mc.player.hasEffect(Effects.CONFUSION) ? 7 : 20; + float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F; + f1 = f1 * f1; + Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_OF_TWO / 2.0F, MathHelper.SQRT_OF_TWO / 2.0F); + matrixStack.mulPose(vector3f.rotationDegrees((gameRender.tick + partialTicks) * i)); + matrixStack.scale(1.0F / f1, 1.0F, 1.0F); + float f2 = -(gameRender.tick + partialTicks) * i; + matrixStack.mulPose(vector3f.rotationDegrees(f2)); + } + + + + // this projection matrix allows us to see past the normal + // world render distance + Matrix4f projectionMatrix = + Matrix4f.perspective( + getFov(partialTicks, true), + (float)this.mc.getWindow().getScreenWidth() / (float)this.mc.getWindow().getScreenHeight(), + 0.5F, + this.farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2); + + // add the screen space distortions + projectionMatrix.multiply(matrixStack.last().pose()); + gameRender.resetProjectionMatrix(projectionMatrix); + return; + } + + + /** + * setup the lighting to be used for the LODs + */ + private void setupLighting(LodDimension lodDimension, float partialTicks) + { + float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f; + float gammaMultiplyer = (float)mc.options.gamma - 0.5f; + float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.2f); + + float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f}; + + // can be used for debugging +// if (partialTicks < 0.005) +// ClientProxy.LOGGER.debug(lightStrength); + + ByteBuffer temp = ByteBuffer.allocateDirect(16); + temp.order(ByteOrder.nativeOrder()); + GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip()); + GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting + + RenderSystem.enableLighting(); + } + + /** + * Create all buffers that will be used. + */ + private void setupBuffers(int numbChunksWide) + { + // calculate the max amount of memory needed (in bytes) + int bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + + // if the required memory is greater than the + // MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier + // to fit. + if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY) + { + int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodConfig.CLIENT.lodDetail.get()); + + ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high " + + "and had to be lowered to fit memory constraints " + + "from " + LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + " " + + "to " + maxRadiusMultiplier); + + LodConfig.CLIENT.lodChunkRadiusMultiplier.set( + maxRadiusMultiplier); + + bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(maxRadiusMultiplier); + } + + drawableNearBuffer = new BufferBuilder(bufferMemory); + drawableFarBuffer = new BufferBuilder(bufferMemory); + + lodNodeBufferBuilder.setupBuffers(bufferMemory); + } + + + + + + //======================// + // Other Misc Functions // + //======================// + + /** + * If this is called then the next time "drawLODs" is called + * the LODs will be regenerated; the same as if the player moved. + */ + public void regenerateLODsNextFrame() + { + regen = true; + } + + + /** + * Replace the current drawable buffers with the newly + * created buffers from the lodBufferBuilder. + */ + private void swapBuffers() + { + // replace the drawable buffers with + // the newly created buffers from the lodBufferBuilder + NearFarBuffer newBuffers = lodNodeBufferBuilder.swapBuffers(drawableNearBuffer, drawableFarBuffer); + drawableNearBuffer = newBuffers.nearBuffer; + drawableFarBuffer = newBuffers.farBuffer; + + + // bind the buffers with their respective VBOs + if (nearVbo != null) + nearVbo.close(); + + nearVbo = new VertexBuffer(LOD_VERTEX_FORMAT); + nearVbo.upload(drawableNearBuffer); + + + if (farVbo != null) + farVbo.close(); + + farVbo = new VertexBuffer(LOD_VERTEX_FORMAT); + farVbo.upload(drawableFarBuffer); + } + + + private double getFov(float partialTicks, boolean useFovSetting) + { + return mc.gameRenderer.getFov(mc.gameRenderer.getMainCamera(), partialTicks, useFovSetting); + } + + + /** + * Return what fog settings should be used when rendering. + */ + private NearFarFogSettings determineFogSettings() + { + NearFarFogSettings fogSettings = new NearFarFogSettings(); + + + FogQuality quality = reflectionHandler.getFogQuality(); + FogDrawOverride override = LodConfig.CLIENT.fogDrawOverride.get(); + + + if (quality == FogQuality.OFF) + fogSettings.vanillaIsRenderingFog = false; + else + fogSettings.vanillaIsRenderingFog = true; + + + // use any fog overrides the user may have set + switch(override) + { + case ALWAYS_DRAW_FOG_FANCY: + quality = FogQuality.FANCY; + break; + + case NEVER_DRAW_FOG: + quality = FogQuality.OFF; + break; + + case ALWAYS_DRAW_FOG_FAST: + quality = FogQuality.FAST; + break; + + case USE_OPTIFINE_FOG_SETTING: + // don't override anything + break; + } + + + // only use fancy fog if the user's GPU can deliver + if (!fancyFogAvailable && quality == FogQuality.FANCY) + { + quality = FogQuality.FAST; + } + + + // how different distances are drawn depends on the quality set + switch(quality) + { + case FANCY: + fogSettings.near.quality = FogQuality.FANCY; + fogSettings.far.quality = FogQuality.FANCY; + + switch(LodConfig.CLIENT.fogDistance.get()) + { + case NEAR_AND_FAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.FAR; + break; + + case NEAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.NEAR; + break; + + case FAR: + fogSettings.near.distance = FogDistance.FAR; + fogSettings.far.distance = FogDistance.FAR; + break; + } + break; + + case FAST: + fogSettings.near.quality = FogQuality.FAST; + fogSettings.far.quality = FogQuality.FAST; + + // fast fog setting should only have one type of + // fog, since the LODs are separated into a near + // and far portion; and fast fog is rendered from the + // frustrum's perspective instead of the camera + switch(LodConfig.CLIENT.fogDistance.get()) + { + case NEAR_AND_FAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.NEAR; + break; + + case NEAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.NEAR; + break; + + case FAR: + fogSettings.near.distance = FogDistance.FAR; + fogSettings.far.distance = FogDistance.FAR; + break; + } + break; + + case OFF: + + fogSettings.near.quality = FogQuality.OFF; + fogSettings.far.quality = FogQuality.OFF; + break; + + } + + + return fogSettings; + } + + + + /** + * Get a HashSet of all ChunkPos within the normal render distance + * that should not be rendered. + */ + private HashSet getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos) + { + int chunkRenderDist = mc.options.renderDistance; + int blockRenderDist = chunkRenderDist * 16; + ChunkPos centerChunk = new ChunkPos(playerPos); + + // skip chunks that are already going to be rendered by Minecraft + HashSet posToSkip = getRenderedChunks(); + + + // go through each chunk within the normal view distance + for(int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++) + { + for(int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++) + { + LodNodeData lod = lodDim.getLodFromCoordinates(x, z); + if (lod != null) + { + short lodHighestPoint = lod.height(); + + if (playerPos.getY() < lodHighestPoint) + { + // don't draw Lod's that are taller than the player + // to prevent LODs being drawn on top of the player + posToSkip.add(new ChunkPos(x, z)); + } + else if (blockRenderDist < Math.abs(playerPos.getY() - lodHighestPoint)) + { + // draw Lod's that are lower than the player's view range + posToSkip.remove(new ChunkPos(x, z)); + } + } + } + } + + return posToSkip; + } + + /** + * This method returns the ChunkPos of all chunks that Minecraft + * is going to render this frame.

+ * + * Note: This isn't perfect. It will return some chunks that are outside + * the clipping plane. (For example, if you are high above the ground some chunks + * will be incorrectly added, even though they are outside render range). + */ + public static HashSet getRenderedChunks() + { + HashSet loadedPos = new HashSet<>(); + + Minecraft mc = Minecraft.getInstance(); + + // Wow those are some long names! + + // go through every RenderInfo to get the compiled chunks + for(WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : mc.levelRenderer.renderChunks) + { + if (!worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk().hasNoRenderableLayers()) + { + // add the ChunkPos for every empty compiled chunk + BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin(); + + loadedPos.add(new ChunkPos(bpos.getX() / 16, bpos.getZ() / 16)); + } + } + + return loadedPos; + } + + +} \ No newline at end of file