From cfda8c9655da27d4b8489c93a51ae910c421cb3a Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 26 Feb 2021 10:31:34 -0600 Subject: [PATCH] Bring over improvements and changes from the master branch --- src/main/java/com/backsun/lod/LodMain.java | 4 + .../lod/builders/BuildBufferThread.java | 223 +++++ .../com/backsun/lod/builders/LodBuilder.java | 132 +++ .../LodDimensionFileHandler.java} | 142 ++- .../lod/handlers/ReflectionHandler.java | 99 +++ .../com/backsun/lod/objects/LodChunk.java | 115 ++- .../com/backsun/lod/objects/LodDimension.java | 101 ++- .../com/backsun/lod/objects/LodRegion.java | 18 +- .../com/backsun/lod/objects/LodWorld.java | 32 +- .../backsun/lod/objects/NearFarBuffer.java | 25 + .../com/backsun/lod/proxy/ClientProxy.java | 148 +--- .../com/backsun/lod/proxy/CommonProxy.java | 12 + .../com/backsun/lod/renderer/LodRenderer.java | 837 ++++++++++-------- .../java/com/backsun/lod/util/LodUtils.java | 55 ++ .../java/com/backsun/lod/util/Reference.java | 3 +- .../lod/util/enums/ColorDirection.java | 4 +- .../backsun/lod/util/enums/FogDistance.java | 4 +- .../com/backsun/lod/util/enums/LodCorner.java | 28 + .../resources/META-INF/accesstransformer.cfg | 5 +- 19 files changed, 1379 insertions(+), 608 deletions(-) create mode 100644 src/main/java/com/backsun/lod/builders/BuildBufferThread.java create mode 100644 src/main/java/com/backsun/lod/builders/LodBuilder.java rename src/main/java/com/backsun/lod/{util/LodFileHandler.java => handlers/LodDimensionFileHandler.java} (52%) create mode 100644 src/main/java/com/backsun/lod/handlers/ReflectionHandler.java create mode 100644 src/main/java/com/backsun/lod/objects/NearFarBuffer.java create mode 100644 src/main/java/com/backsun/lod/proxy/CommonProxy.java create mode 100644 src/main/java/com/backsun/lod/util/LodUtils.java create mode 100644 src/main/java/com/backsun/lod/util/enums/LodCorner.java diff --git a/src/main/java/com/backsun/lod/LodMain.java b/src/main/java/com/backsun/lod/LodMain.java index b381c7d21..ec8974c20 100644 --- a/src/main/java/com/backsun/lod/LodMain.java +++ b/src/main/java/com/backsun/lod/LodMain.java @@ -15,6 +15,10 @@ import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; /** + * Initialize and setup the Mod. + *
+ * If you are looking for the real start of the mod + * check out the ClientProxy. * * @author James Seibel * @version 02-07-2021 diff --git a/src/main/java/com/backsun/lod/builders/BuildBufferThread.java b/src/main/java/com/backsun/lod/builders/BuildBufferThread.java new file mode 100644 index 000000000..f4a1be419 --- /dev/null +++ b/src/main/java/com/backsun/lod/builders/BuildBufferThread.java @@ -0,0 +1,223 @@ +package com.backsun.lod.builders; +import java.awt.Color; +import java.util.concurrent.Callable; + +import org.lwjgl.opengl.GL11; + +import com.backsun.lod.objects.NearFarBuffer; +import com.backsun.lod.util.enums.FogDistance; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.math.AxisAlignedBB; + +/** + * This object is used to create NearFarBuffer objects + * in a thread independent way, so multiple of these objects can be + * created and executed in parallel to populate BufferBuilders. + * + * @author James Seibel + * @version 02-23-2021 + */ +public class BuildBufferThread implements Callable +{ + public BufferBuilder nearBuffer; + public BufferBuilder farBuffer; + public FogDistance distanceMode; + public AxisAlignedBB[][] lods; + public Color[][] colors; + + private int startLodIndex = 0; + private int endLodIndex = -1; + + + + public BuildBufferThread() + { + + } + + public BuildBufferThread(BufferBuilder newNearBufferBuilder, + BufferBuilder newFarBufferBuilder, AxisAlignedBB[][] newLods, + Color[][] newColors, FogDistance newDistanceMode, int newStartingIndex, + int numberOfRowsToGenerate) + { + setNewData(newNearBufferBuilder, newFarBufferBuilder, distanceMode, + newLods, newColors, newStartingIndex, numberOfRowsToGenerate); + } + + public void setNewData(BufferBuilder newNearBufferBuilder, + BufferBuilder newFarBufferBuilder, FogDistance newDistanceMode, + AxisAlignedBB[][] newLods, Color[][] newColors, + int newStartingIndex, int numberOfRowsToGenerate) + { + nearBuffer = newNearBufferBuilder; + farBuffer = newFarBufferBuilder; + distanceMode = newDistanceMode; + lods = newLods; + colors = newColors; + + startLodIndex = newStartingIndex; + endLodIndex = newStartingIndex + numberOfRowsToGenerate; + } + + @Override + public NearFarBuffer call() + { + nearBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); + farBuffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); + + int numbChunksWide = lods.length; + + BufferBuilder currentBuffer; + AxisAlignedBB bb; + int red; + int green; + int blue; + int alpha; + + // this is done if the FogDistance is either + // NEAR or FAR, if it is NEAR_AND_FAR + // the buffer is determined for each LOD + if (distanceMode == FogDistance.NEAR) + { + currentBuffer = nearBuffer; + } + else // if (distanceMode == FogDistance.FAR) + { + currentBuffer = farBuffer; + } + + + // x axis + for (int i = startLodIndex; i < endLodIndex; i++) + { + // z axis + for (int j = 0; j < numbChunksWide; j++) + { + if (lods[i][j] == null || colors[i][j] == null) + continue; + + bb = lods[i][j]; + + // get the color of this LOD object + red = colors[i][j].getRed(); + green = colors[i][j].getGreen(); + blue = colors[i][j].getBlue(); + alpha = colors[i][j].getAlpha(); + + + if (distanceMode == FogDistance.NEAR_AND_FAR) + { + if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2)) + currentBuffer = nearBuffer; + else + currentBuffer = farBuffer; + } + + + if (bb.minY != bb.maxY) + { + // top (facing up) + addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha); + // bottom (facing down) + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha); + + // south (facing -Z) + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha); + // north (facing +Z) + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha); + + // west (facing -X) + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.maxY, bb.minZ, red, green, blue, alpha); + // east (facing +X) + addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.maxY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha); + } + else + { + // render this LOD as one block thick + + // top (facing up) + addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.minZ, red, green, blue, alpha); + // bottom (facing down) + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha); + + // south (facing -Z) + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha); + // north (facing +Z) + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha); + + // west (facing -X) + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.minX, bb.minY+1, bb.minZ, red, green, blue, alpha); + // east (facing +X) + addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.minZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY+1, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.maxZ, red, green, blue, alpha); + addPosAndColor(currentBuffer, bb.maxX, bb.minY, bb.minZ, red, green, blue, alpha); + } + + } // z axis + } // x axis + + nearBuffer.finishDrawing(); + farBuffer.finishDrawing(); + + return new NearFarBuffer(nearBuffer, farBuffer); + } + + private void addPosAndColor(BufferBuilder buffer, double x, double y, double z, int red, int green, int blue, int alpha) + { + buffer.pos(x, y, z).color(red, green, blue, alpha).endVertex(); + } + + + + /** + * 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); + } + +} \ No newline at end of file diff --git a/src/main/java/com/backsun/lod/builders/LodBuilder.java b/src/main/java/com/backsun/lod/builders/LodBuilder.java new file mode 100644 index 000000000..cce1ddd5e --- /dev/null +++ b/src/main/java/com/backsun/lod/builders/LodBuilder.java @@ -0,0 +1,132 @@ +package com.backsun.lod.builders; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.backsun.lod.handlers.LodDimensionFileHandler; +import com.backsun.lod.objects.LodChunk; +import com.backsun.lod.objects.LodDimension; +import com.backsun.lod.objects.LodWorld; +import com.backsun.lod.util.LodUtils; + +import net.minecraft.world.DimensionType; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.ChunkSection; + +/** + * This object is in charge of creating Lod + * related objects. + * (specifically: Lod World, Dimension, Region, and Chunk objects) + * + * @author James Seibel + * @version 2-22-2021 + */ +public class LodBuilder +{ + private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(); + public volatile LodWorld lodWorld; + + /** Default size of any LOD regions we use */ + public int regionWidth = 5; + + public LodBuilder() + { + + } + + + + /** + * Returns LodWorld so that it can be passed + * to the LodRenderer. + */ + public LodWorld generateLodChunkAsync(Chunk chunk) + { + // don't try to create an LOD object + // if for some reason we aren't + // given a valid chunk object + // (Minecraft often gives back empty + // or null chunks in this method) + if (chunk == null || !isValidChunk(chunk)) + return lodWorld; + + + DimensionType dim = chunk.getWorld().getDimensionType(); + World world = LodUtils.getServerWorldFromDimension(dim); + + + if (world == null) + return lodWorld; + + Thread thread = new Thread(() -> + { + try + { + LodChunk lod = new LodChunk(chunk, world); + LodDimension lodDim; + + if (lodWorld == null) + { + lodWorld = new LodWorld(LodDimensionFileHandler.getWorldName()); + } + else + { + // if we have a lodWorld make sure + // it is for this minecraft world + if (!lodWorld.worldName.equals(LodDimensionFileHandler.getWorldName())) + { + // this lodWorld isn't for this minecraft world + // delete it so we can get a new one + lodWorld = null; + + // skip this frame + // we'll get this set up next time + return; + } + } + + + if (lodWorld.getLodDimension(dim) == null) + { + lodDim = new LodDimension(dim, regionWidth); + lodWorld.addLodDimension(lodDim); + } + else + { + lodDim = lodWorld.getLodDimension(dim); + } + + lodDim.addLod(lod); + } + catch(IllegalArgumentException | NullPointerException e) + { + // if the world changes while LODs are being generated + // they will throw errors as they try to access things that no longer + // exist. + } + }); + lodGenThreadPool.execute(thread); + + return lodWorld; + } + + /** + * Return whether the given chunk + * has any data in it. + */ + public boolean isValidChunk(Chunk chunk) + { + ChunkSection[] blockStorage = chunk.getSections(); + + for(ChunkSection section : blockStorage) + { + if(section != null && !section.isEmpty()) + { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/com/backsun/lod/util/LodFileHandler.java b/src/main/java/com/backsun/lod/handlers/LodDimensionFileHandler.java similarity index 52% rename from src/main/java/com/backsun/lod/util/LodFileHandler.java rename to src/main/java/com/backsun/lod/handlers/LodDimensionFileHandler.java index 392756097..d17e6da25 100644 --- a/src/main/java/com/backsun/lod/util/LodFileHandler.java +++ b/src/main/java/com/backsun/lod/handlers/LodDimensionFileHandler.java @@ -1,4 +1,4 @@ -package com.backsun.lod.util; +package com.backsun.lod.handlers; import java.io.BufferedReader; import java.io.File; @@ -11,8 +11,10 @@ import java.util.concurrent.Executors; import com.backsun.lod.objects.LodChunk; import com.backsun.lod.objects.LodDimension; import com.backsun.lod.objects.LodRegion; +import com.backsun.lod.util.LodUtils; import net.minecraft.client.Minecraft; +import net.minecraft.world.server.ServerWorld; /** * This object handles creating LodRegions @@ -22,26 +24,29 @@ import net.minecraft.client.Minecraft; * @author James Seibel * @version 01-30-2021 */ -public class LodFileHandler +public class LodDimensionFileHandler { - // TODO this object needs to be changed to use NBT data instead of writing to files - private static final boolean IMPLEMENTED = false; - - private Minecraft mc = Minecraft.getInstance(); - - private LodDimension loadedRegion = null; + private LodDimension loadedDimension = null; public long regionLastWriteTime[][]; - private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(); + private File saveFolder; + private String saveDir; + + private final String FILE_NAME_PREFIX = "lod"; + private final String FILE_EXTENSION = ".txt"; + + private ExecutorService fileWritingThreadPool = Executors.newFixedThreadPool(1); - public LodFileHandler(LodDimension newLoadedRegion) + public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension) { - loadedRegion = newLoadedRegion; + saveFolder = newSaveFolder; + + loadedDimension = newLoadedDimension; // these two variable are used in sync with the LodDimension - regionLastWriteTime = new long[loadedRegion.getWidth()][loadedRegion.getWidth()]; - for(int i = 0; i < loadedRegion.getWidth(); i++) - for(int j = 0; j < loadedRegion.getWidth(); j++) + regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()]; + for(int i = 0; i < loadedDimension.getWidth(); i++) + for(int j = 0; j < loadedDimension.getWidth(); j++) regionLastWriteTime[i][j] = -1; } @@ -61,15 +66,15 @@ public class LodFileHandler */ public LodRegion loadRegionFromFile(int regionX, int regionZ) { - if (!IMPLEMENTED) - return null; - // we don't currently support reading or writing // files when connected to a server - if (!mc.isIntegratedServerRunning()) + if (!Minecraft.getInstance().isIntegratedServerRunning()) return null; - String fileName = ""; + if (!readyToReadAndWrite()) + return null; + + String fileName = getFileNameForRegion(regionX, regionZ); File f = new File(fileName); @@ -131,47 +136,53 @@ public class LodFileHandler // Save to File // //==============// - - public synchronized void saveDirtyRegionsToFile() + /** + * Save all dirty regions in this LodDimension to file. + */ + public synchronized void saveDirtyRegionsToFileAsync() { - if (!IMPLEMENTED) - return; - // we don't currently support reading or writing // files when connected to a server - if (!mc.isIntegratedServerRunning()) + if (!Minecraft.getInstance().isIntegratedServerRunning()) + return; + + if (!readyToReadAndWrite()) + // we aren't ready to read and write yet return; fileWritingThreadPool.execute(saveDirtyRegionsThread); } private Thread saveDirtyRegionsThread = new Thread(() -> { - for(int i = 0; i < loadedRegion.getWidth(); i++) + for(int i = 0; i < loadedDimension.getWidth(); i++) { - for(int j = 0; j < loadedRegion.getWidth(); j++) + for(int j = 0; j < loadedDimension.getWidth(); j++) { - if(loadedRegion.isRegionDirty[i][j]) + if(loadedDimension.isRegionDirty[i][j]) { - saveRegionToDisk(loadedRegion.regions[i][j]); - loadedRegion.isRegionDirty[i][j] = false; + saveRegionToDisk(loadedDimension.regions[i][j]); + loadedDimension.isRegionDirty[i][j] = false; } } } }); - + /** + * Save a specific region to disk.
+ * Note: it will save to the LodDimension that this + * handler is associated with. + */ private void saveRegionToDisk(LodRegion region) { - if (!IMPLEMENTED) + if (!readyToReadAndWrite() || region == null) return; - if (region == null) - return; + // convert chunk coordinates to region + // coordinates + int x = region.x; + int z = region.z; -// int x = region.x; -// int z = region.z; - - File f = new File(""); + File f = new File(getFileNameForRegion(x, z)); try { @@ -207,6 +218,33 @@ public class LodFileHandler //================// + /** + * Return the name of the file that should contain the + * region at the given x and z.
+ * Returns null if this object isn't ready to read and write. + */ + private String getFileNameForRegion(int regionX, int regionZ) + { + if (!readyToReadAndWrite()) + return null; + + return saveDir + "\\lod_data\\DIM" + loadedDimension.dimension.toString() + "\\" + + FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION; + } + + + /** + * Returns if this FileHandler is ready to read + * and write files. + *
+ * This returns true when the world save directory is known. + */ + public boolean readyToReadAndWrite() + { + return saveFolder != null; + } + + /** @@ -220,7 +258,11 @@ public class LodFileHandler if(mc.isIntegratedServerRunning()) { - return mc.getIntegratedServer().getName(); + ServerWorld world = LodUtils.getFirstValidServerWorld(); + if(world != null) + return world.getServer().getDataDirectory().toString(); + + return ""; } else { @@ -229,4 +271,26 @@ public class LodFileHandler } + + /** + * Gets the canonical path to the world save folder. + *
+ * Returns null if there was an IO Exception + */ + private String getWorldSaveDirectory() + { + ServerWorld world = LodUtils.getFirstValidServerWorld(); + + try + { + if(world != null) + return world.getServer().getDataDirectory().getCanonicalPath(); + } + catch (IOException e) + { + e.printStackTrace(); + } + + return null; + } } diff --git a/src/main/java/com/backsun/lod/handlers/ReflectionHandler.java b/src/main/java/com/backsun/lod/handlers/ReflectionHandler.java new file mode 100644 index 000000000..a13c254f4 --- /dev/null +++ b/src/main/java/com/backsun/lod/handlers/ReflectionHandler.java @@ -0,0 +1,99 @@ +package com.backsun.lod.handlers; + +import java.lang.reflect.Field; + +import com.backsun.lod.util.enums.FogQuality; + +import net.minecraft.client.Minecraft; + +/** + * This object is used to get variables from methods + * where they are private. Specifically the fog setting + * in Optifine. + * + * @author James Seibel + * @version 09-21-2020 + */ +public class ReflectionHandler +{ + private Minecraft mc = Minecraft.getInstance(); + public Field ofFogField = null; + + + public ReflectionHandler() + { + setupFogField(); + } + + + + + /** + * Similar to setupFovMethod. + */ + private void setupFogField() + { + // get every variable from the entity renderer + Field[] vars = mc.gameSettings.getClass().getDeclaredFields(); + + // try and find the ofFogType variable in gameSettings + for(Field f : vars) + { + if(f.getName().equals("ofFogType")) + { + ofFogField = f; + return; + } + } + + // we didn't find the field, + // either optifine isn't installed, or + // optifine changed the name of the variable + ofFogField = null; + } + + + + + + /** + * Get what type of fog optifine is currently set to render. + */ + public FogQuality getFogQuality() + { + if (ofFogField == null) + { + // either optifine isn't installed, + // the variable name was changed, or + // the setup method wasn't called yet. + return FogQuality.FANCY; + } + + int returnNum = 0; + + try + { + returnNum = (int)ofFogField.get(mc.gameSettings); + } + catch (IllegalArgumentException | IllegalAccessException e) + { + e.printStackTrace(); + } + + switch (returnNum) + { + case 0: + return FogQuality.FAST; + case 1: + return FogQuality.FAST; + case 2: + return FogQuality.FANCY; + case 3: + return FogQuality.OFF; + + default: + return FogQuality.FAST; + } + } + +} diff --git a/src/main/java/com/backsun/lod/objects/LodChunk.java b/src/main/java/com/backsun/lod/objects/LodChunk.java index 85aae9b1e..69c88815a 100644 --- a/src/main/java/com/backsun/lod/objects/LodChunk.java +++ b/src/main/java/com/backsun/lod/objects/LodChunk.java @@ -3,6 +3,7 @@ package com.backsun.lod.objects; import java.awt.Color; import com.backsun.lod.util.enums.ColorDirection; +import com.backsun.lod.util.enums.LodCorner; import com.backsun.lod.util.enums.LodLocation; import net.minecraft.block.Blocks; @@ -71,7 +72,7 @@ public class LodChunk //==============// /** - * Create an empty LodChunk + * Create an empty invisible LodChunk at (0,0) */ public LodChunk() { @@ -200,11 +201,11 @@ public class LodChunk } /** - * Illegal argument is thrown if either the - * chunk or world is null. The reason the world - * can't be null is because it's required to determine - * a block's color. - * @throws IllegalArgumentException + * Creates a LodChunk for a chunk in the given world.
+ * Note: The world is required to determine each block's color + * + * @throws IllegalArgumentException + * thrown if either the chunk or world is null. */ public LodChunk(Chunk chunk, World world) throws IllegalArgumentException { @@ -251,6 +252,8 @@ public class LodChunk /** + * Generate the height for the given LodLocation, either the top or bottom. + *

* If invalid/null/empty chunks are given * crashes may occur. */ @@ -316,7 +319,16 @@ public class LodChunk else return determineBottomPoint(chunkSections, startX, endX, startZ, endZ); } + /** GENERATE_TOP, GENERATE_BOTTOM */ + private enum SectionGenerationMode + { + GENERATE_TOP, + GENERATE_BOTTOM; + } + /** + * Find the lowest valid point from the bottom. + */ private short determineBottomPoint(ChunkSection[] chunkSections, int startX, int endX, int startZ, int endZ) { // search from the bottom up @@ -333,15 +345,16 @@ public class LodChunk // LOD point return (short) (y + (i * CHUNK_DATA_HEIGHT)); } - - } // y - } // data - + } + } // we never found a valid LOD point return -1; } + /** + * Find the highest valid point from the Top + */ private short determineTopPoint(ChunkSection[] chunkSections, int startX, int endX, int startZ, int endZ) { // search from the top down @@ -357,10 +370,8 @@ public class LodChunk // LOD point return (short) (y + (i * CHUNK_DATA_HEIGHT)); } - } // y - } // data - - + } + } // we never found a valid LOD point return -1; @@ -410,9 +421,11 @@ public class LodChunk return false; } - - - private Color generateLodColorSection(Chunk chunk, World world, ColorDirection colorDir) + /** + * Generate the color of the given ColorDirection at the given chunk + * in the given world. + */ + private Color generateLodColorSection(Chunk chunk, World world, ColorDirection colorDir) { Minecraft mc = Minecraft.getInstance(); BlockColors bc = mc.getBlockColors(); @@ -439,10 +452,17 @@ public class LodChunk } /** - * Only accepts TOP and BOTTOM as ColorPositions + * Generates the color of the top or bottom of a given chunk in the given world. + * + * @throws IllegalArgumentException if given a ColorDirection other than TOP or BOTTOM */ private Color generateLodColorVertical(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc) { + if(colorDir != ColorDirection.TOP && colorDir != ColorDirection.BOTTOM) + { + throw new IllegalArgumentException("generateLodColorVertical only accepts the ColorDirection TOP or BOTTOM"); + } + ChunkSection[] chunkSections = chunk.getSections(); int numbOfBlocks = 0; @@ -517,9 +537,19 @@ public class LodChunk return new Color(red, green, blue); } - + + /** + * Generates the color of the side of a given chunk in the given world for the given ColorDirection. + * + * @throws IllegalArgumentException if given a ColorDirection other than N, S, W, E (North, South, East, West) + */ private Color generateLodColorHorizontal(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc) { + if(colorDir != ColorDirection.N && colorDir != ColorDirection.S && colorDir != ColorDirection.E && colorDir != ColorDirection.W) + { + throw new IllegalArgumentException("generateLodColorHorizontal only accepts the ColorDirection N (North), S (South), E (East), or W (West)"); + } + ChunkSection[] chunkSections = chunk.getSections(); int numbOfBlocks = 0; @@ -669,6 +699,30 @@ public class LodChunk + //================// + // misc functions // + //================// + + /** + * If this LOD is either invisible from every + * direction or doesn't have a valid height + * it is empty. + */ + public boolean isLodEmpty() + { + for(LodCorner corner : LodCorner.values()) + if(top[corner.value] != -1 || bottom[corner.value] != -1) + // at least one corner is valid + return false; + + Color invisible = new Color(0,0,0,0); + for(ColorDirection dir : ColorDirection.values()) + if(!colors[dir.value].equals(invisible)) + // at least one direction has a non-invisible color + return false; + + return true; + } @@ -725,28 +779,7 @@ public class LodChunk s += "x: " + x + " z: " + z + "\t"; -// s += "top: "; -// for(int i = 0; i < top.length; i++) -// { -// s += top[i] + " "; -// } -// s += "\t"; - -// s += "bottom: "; -// for(int i = 0; i < bottom.length; i++) -// { -// s += bottom[i] + " "; -// } -// s += "\t"; - -// s += "colors "; -// for(int i = 0; i < colors.length; i++) -// { -// if(colors[i] != null) -// s += "(" + colors[i].getRed() + ", " + colors[i].getGreen() + ", " + colors[i].getBlue() + "), "; -// } - - s += "(" + colors[ColorDirection.TOP.value].getRed() + ", " + colors[ColorDirection.TOP.value].getGreen() + ", " + colors[ColorDirection.TOP.value].getBlue() + "), "; + s += "(" + colors[ColorDirection.TOP.value].getRed() + ", " + colors[ColorDirection.TOP.value].getGreen() + ", " + colors[ColorDirection.TOP.value].getBlue() + ")"; return s; } diff --git a/src/main/java/com/backsun/lod/objects/LodDimension.java b/src/main/java/com/backsun/lod/objects/LodDimension.java index dd9cf78c0..18cb1df10 100644 --- a/src/main/java/com/backsun/lod/objects/LodDimension.java +++ b/src/main/java/com/backsun/lod/objects/LodDimension.java @@ -1,21 +1,23 @@ package com.backsun.lod.objects; -import com.backsun.lod.util.LodFileHandler; +import com.backsun.lod.handlers.LodDimensionFileHandler; +import com.backsun.lod.util.LodUtils; import net.minecraft.world.DimensionType; +import net.minecraft.world.server.ServerChunkProvider; /** * This object holds all loaded LOD regions * for a given dimension. * * @author James Seibel - * @version 01-31-2021 + * @version 02-23-2021 */ public class LodDimension { public final DimensionType dimension; - private volatile int width; // if this ever changes make sure to update the halfWidth too + private volatile int width; private volatile int halfWidth; public LodRegion regions[][]; @@ -24,15 +26,16 @@ public class LodDimension private int centerX; private int centerZ; - private LodFileHandler rfHandler; + private LodDimensionFileHandler fileHandler; + public LodDimension(DimensionType newDimension, int newMaxWidth) { dimension = newDimension; width = newMaxWidth; - // dimension 0 works here since we are just looking for the save handler anyway - rfHandler = new LodFileHandler(this); + ServerChunkProvider provider = LodUtils.getServerWorldFromDimension(newDimension).getChunkProvider(); + fileHandler = new LodDimensionFileHandler(provider.getSavedData().folder, this); regions = new LodRegion[width][width]; isRegionDirty = new boolean[width][width]; @@ -49,7 +52,10 @@ public class LodDimension } - + /** + * Move the center of this LodDimension and move all owned + * regions over by the given x and z offset. + */ public void move(int xOffset, int zOffset) { // if the x or z offset is equal to or greater than @@ -143,22 +149,16 @@ public class LodDimension } - public int getCenterX() - { - return centerX; - } - - public int getCenterZ() - { - return centerZ; - } - - - + /** + * Gets the region at the given X and Z + *
+ * Returns null if the region doesn't exist + * or is outside the loaded area. + */ public LodRegion getRegion(int regionX, int regionZ) { int xIndex = (regionX - centerX) + halfWidth; @@ -200,11 +200,15 @@ public class LodDimension - + /** + * Add the given LOD to this dimension at the coordinate + * stored in the LOD. If an LOD already exists at the given + * coordinates it will be overwritten. + */ public void addLod(LodChunk lod) { - int regionX = (lod.x + centerX) / LodRegion.SIZE; - int regionZ = (lod.z + centerZ) / LodRegion.SIZE; + int regionX = lod.x / LodRegion.SIZE; + int regionZ = lod.z / LodRegion.SIZE; // prevent issues if X/Z is negative and less than 16 if (lod.x < 0) @@ -235,20 +239,20 @@ public class LodDimension int xIndex = (regionX - centerX) + halfWidth; int zIndex = (regionZ - centerZ) + halfWidth; isRegionDirty[xIndex][zIndex] = true; - - - - rfHandler.saveDirtyRegionsToFile(); + fileHandler.saveDirtyRegionsToFileAsync(); } /** - * Returns null if the LodChunk isn't loaded + * Get the LodChunk at the given X and Z coordinates + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. */ public LodChunk getLodFromCoordinates(int chunkX, int chunkZ) { - // (chunkX + centerX) % width - int regionX = (chunkX + centerX) / LodRegion.SIZE; - int regionZ = (chunkZ + centerZ) / LodRegion.SIZE; + int regionX = chunkX / LodRegion.SIZE; + int regionZ = chunkZ / LodRegion.SIZE; // prevent issues if chunkX/Z is negative and less than width if (chunkX < 0) @@ -262,7 +266,6 @@ public class LodDimension LodRegion region = getRegion(regionX, regionZ); - // TODO fix small render distances sometimes not having all regions loaded if(region == null) return null; @@ -270,11 +273,13 @@ public class LodDimension } - - + /** + * Get the region at the given X and Z coordinates from the + * RegionFileHandler. + */ public LodRegion getRegionFromFile(int regionX, int regionZ) { - return rfHandler.loadRegionFromFile(regionX, regionZ); + return fileHandler.loadRegionFromFile(regionX, regionZ); } @@ -292,6 +297,22 @@ public class LodDimension + + + + + public int getCenterX() + { + return centerX; + } + + public int getCenterZ() + { + return centerZ; + } + + + public int getWidth() { return width; @@ -310,6 +331,18 @@ public class LodDimension for(int j = 0; j < width; j++) isRegionDirty[i][j] = false; } + + + @Override + public String toString() + { + String s = ""; + + s += "dim: " + dimension.toString() + "\t"; + s += "(" + centerX + "," + centerZ + ")"; + + return s; + } } diff --git a/src/main/java/com/backsun/lod/objects/LodRegion.java b/src/main/java/com/backsun/lod/objects/LodRegion.java index 7e826ddec..009bb46bf 100644 --- a/src/main/java/com/backsun/lod/objects/LodRegion.java +++ b/src/main/java/com/backsun/lod/objects/LodRegion.java @@ -7,7 +7,7 @@ package com.backsun.lod.objects; * one file in the file system. * * @author James Seibel - * @version 1-20-2021 + * @version 1-22-2021 */ public class LodRegion { @@ -31,6 +31,11 @@ public class LodRegion } + /** + * Add the given LOD to this region at the coordinate + * stored in the LOD. If an LOD already exists at the given + * coordinates it will be overwritten. + */ public void addLod(LodChunk lod) { // we use ABS since LODs can be negative, but if they are @@ -43,6 +48,13 @@ public class LodRegion chunks[xIndex][zIndex] = lod; } + /** + * Get the LodChunk at the given X and Z coordinates + * in this region. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ public LodChunk getLod(int chunkX, int chunkZ) { // since we add LOD's with ABS, we get them the same way @@ -56,7 +68,9 @@ public class LodRegion } - + /** + * Returns all LodChunks in this region + */ public LodChunk[][] getAllLods() { return chunks; diff --git a/src/main/java/com/backsun/lod/objects/LodWorld.java b/src/main/java/com/backsun/lod/objects/LodWorld.java index fe82437ca..381ee5c74 100644 --- a/src/main/java/com/backsun/lod/objects/LodWorld.java +++ b/src/main/java/com/backsun/lod/objects/LodWorld.java @@ -1,8 +1,7 @@ package com.backsun.lod.objects; -import java.util.Dictionary; -import java.util.Enumeration; import java.util.Hashtable; +import java.util.Map; import net.minecraft.world.DimensionType; @@ -10,7 +9,7 @@ import net.minecraft.world.DimensionType; * This stores all LODs for a given world. * * @author James Seibel - * @version 01-31-2021 + * @version 02-22-2021 */ public class LodWorld { @@ -19,13 +18,13 @@ public class LodWorld /** * Key = Dimension id (as an int) */ - private Dictionary lodDimensions; + private Map lodDimensions; public LodWorld(String newWorldName) { worldName = newWorldName; - lodDimensions = new Hashtable<>(); + lodDimensions = new Hashtable(); } @@ -40,12 +39,27 @@ public class LodWorld return lodDimensions.get(dimension); } - + /** + * Resizes the max width in regions that each LodDimension + * should use. + */ public void resizeDimensionRegionWidth(int newWidth) { - Enumeration keys = lodDimensions.keys(); + for(DimensionType key : lodDimensions.keySet()) + lodDimensions.get(key).setRegionWidth(newWidth); + } + + + + @Override + public String toString() + { + String s = ""; - while(keys.hasMoreElements()) - lodDimensions.get(keys.nextElement()).setRegionWidth(newWidth); + s += worldName + "\t - dimensions: "; + for(DimensionType key : lodDimensions.keySet()) + s += lodDimensions.get(key).dimension.toString() + ", "; + + return s; } } diff --git a/src/main/java/com/backsun/lod/objects/NearFarBuffer.java b/src/main/java/com/backsun/lod/objects/NearFarBuffer.java new file mode 100644 index 000000000..060a53bb5 --- /dev/null +++ b/src/main/java/com/backsun/lod/objects/NearFarBuffer.java @@ -0,0 +1,25 @@ +package com.backsun.lod.objects; + +import net.minecraft.client.renderer.BufferBuilder; + +/** + * This object is just a replacement for an array + * to make things easier to understand in the LodRenderer + * and BuildBufferThread. + * + * @author James Seibel + * @version 02-21-2021 + */ +public class NearFarBuffer +{ + public BufferBuilder nearBuffer; + + public BufferBuilder farBuffer; + + + public NearFarBuffer(BufferBuilder newNearBuffer, BufferBuilder newFarBuffer) + { + nearBuffer = newNearBuffer; + farBuffer = newFarBuffer; + } +} diff --git a/src/main/java/com/backsun/lod/proxy/ClientProxy.java b/src/main/java/com/backsun/lod/proxy/ClientProxy.java index a54806f5d..78371a7ba 100644 --- a/src/main/java/com/backsun/lod/proxy/ClientProxy.java +++ b/src/main/java/com/backsun/lod/proxy/ClientProxy.java @@ -1,10 +1,8 @@ package com.backsun.lod.proxy; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - import org.lwjgl.opengl.GL11; +import com.backsun.lod.builders.LodBuilder; import com.backsun.lod.objects.LodChunk; import com.backsun.lod.objects.LodDimension; import com.backsun.lod.objects.LodRegion; @@ -12,39 +10,33 @@ import com.backsun.lod.objects.LodWorld; import com.backsun.lod.renderer.LodRenderer; import com.backsun.lod.renderer.RenderGlobalHook; import com.backsun.lod.util.LodConfig; -import com.backsun.lod.util.LodFileHandler; import net.minecraft.client.Minecraft; -import net.minecraft.world.DimensionType; -import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; -import net.minecraft.world.chunk.ChunkSection; import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; //TODO Find a way to replace getIntegratedServer so this mod could be used on non-local worlds. -// Minecraft.getInstance().getIntegratedServer() +// Minecraft.getMinecraft().getIntegratedServer() /** - * This is used by the client. + * This handles all events sent to the client, + * and is the starting point for most of this program. * * @author James_Seibel - * @version 01-31-2021 + * @version 02-23-2021 */ public class ClientProxy { private LodRenderer renderer; private LodWorld lodWorld; - private ExecutorService lodGenThreadPool = Executors.newFixedThreadPool(1); + private LodBuilder lodBuilder; Minecraft mc = Minecraft.getInstance(); - /** Default size of any LOD regions we use */ - private int regionWidth = 5; - public ClientProxy() { - + lodBuilder = new LodBuilder(); } @@ -66,13 +58,17 @@ public class ClientProxy GL11.glDisable(GL11.GL_STENCIL_TEST); } + /** + * Do any setup that is required to draw LODs + * and then tell the LodRenderer to draw. + */ public void renderLods(float partialTicks) { int newWidth = Math.max(4, (mc.gameSettings.renderDistanceChunks * LodChunk.WIDTH * 2) / LodRegion.SIZE); - if (lodWorld != null && regionWidth != newWidth) + if (lodWorld != null && lodBuilder.regionWidth != newWidth) { lodWorld.resizeDimensionRegionWidth(newWidth); - regionWidth = newWidth; + lodBuilder.regionWidth = newWidth; // skip this frame, hopefully the lodWorld // should have everything set up by then @@ -86,8 +82,6 @@ public class ClientProxy if (lodDim == null) return; - mc.getProfiler().endSection(); - mc.getProfiler().startSection("LOD"); double playerX = mc.player.getPosX(); double playerZ = mc.player.getPosZ(); @@ -100,7 +94,6 @@ public class ClientProxy lodDim.move(xOffset, zOffset); } - // we wait to create the renderer until the first frame // to make sure that the EntityRenderer has // been created, that way we can get the fovModifer @@ -113,118 +106,41 @@ public class ClientProxy { renderer.drawLODs(lodDim, partialTicks, mc.getProfiler()); } - - // end of profiler tracking - mc.getProfiler().endSection(); } - - - //===============// // update events // //===============// @SubscribeEvent - public void chunkLoadEvent(ChunkEvent.Load event) + public void chunkLoadEvent(ChunkEvent event) { - if (mc != null && event != null) - { - World world = mc.world; - - if(world != null) - { - generateLodChunk((Chunk)event.getChunk()); - } - } - } - - private void generateLodChunk(Chunk chunk) - { - // don't try to create an LOD object - // if for some reason we aren't - // given a valid chunk object - // (Minecraft often gives back empty - // or null chunks in this method) - if (chunk == null || chunk.getWorld() == null || !isValidChunk(chunk)) - return; - - Thread thread = new Thread(() -> - { - try - { - DimensionType dim = chunk.getWorldForge().getDimensionType(); - World world = chunk.getWorld(); - LodChunk lod = new LodChunk(chunk, world); - LodDimension lodDim; - - if (lodWorld == null) - { - lodWorld = new LodWorld(LodFileHandler.getWorldName()); - } - else - { - // if we have a lodWorld make sure - // it is for this minecraft world - if (!lodWorld.worldName.equals(LodFileHandler.getWorldName())) - { - // this lodWorld isn't for this minecraft world - // delete it so we can get a new one - lodWorld = null; - - // skip this frame - // we'll get this set up next time - return; - } - } - - - if (lodWorld.getLodDimension(dim) == null) - { - lodDim = new LodDimension(dim, regionWidth); - lodWorld.addLodDimension(lodDim); - } - else - { - lodDim = lodWorld.getLodDimension(dim); - } - - lodDim.addLod(lod); - } - catch(IllegalArgumentException | NullPointerException e) - { - // if the world changes while LODs are being generated - // they will throw errors as they try to access things that no longer - // exist. - } - - }); - - lodGenThreadPool.execute(thread); + lodWorld = lodBuilder.generateLodChunkAsync((Chunk) event.getChunk()); } /** - * Return whether the given chunk - * has any data in it. + * this event is called whenever a chunk is created for the first time. */ - private boolean isValidChunk(Chunk chunk) - { - ChunkSection[] sections = chunk.getSections(); - - for(ChunkSection section : sections) - { - if(section != null && !section.isEmpty()) - { - return true; - } - } - - return false; - } +// @SubscribeEvent +// public void onChunkPopulate(PopulateChunkEvent event) +// { +// Minecraft mc = Minecraft.getMinecraft(); +// if (mc != null && event != null) +// { +// WorldClient world = mc.world; +// +// if(world != null) +// { +// lodWorld = lodBuilder.generateLodChunkAsync(world.getChunkFromChunkCoords(event.getChunkX(), event.getChunkZ())); +// } +// } +// } + + } diff --git a/src/main/java/com/backsun/lod/proxy/CommonProxy.java b/src/main/java/com/backsun/lod/proxy/CommonProxy.java new file mode 100644 index 000000000..5362c2e93 --- /dev/null +++ b/src/main/java/com/backsun/lod/proxy/CommonProxy.java @@ -0,0 +1,12 @@ +package com.backsun.lod.proxy; + +/** + * This handles any events sent to the server. + * + * @author James_Seibel + * @version 08-31-2020 + */ +public class CommonProxy +{ + +} diff --git a/src/main/java/com/backsun/lod/renderer/LodRenderer.java b/src/main/java/com/backsun/lod/renderer/LodRenderer.java index 5bec14306..05997048a 100644 --- a/src/main/java/com/backsun/lod/renderer/LodRenderer.java +++ b/src/main/java/com/backsun/lod/renderer/LodRenderer.java @@ -14,17 +14,20 @@ import java.util.concurrent.Future; import org.lwjgl.opengl.GL11; +import com.backsun.lod.builders.BuildBufferThread; +import com.backsun.lod.handlers.ReflectionHandler; import com.backsun.lod.objects.LodChunk; import com.backsun.lod.objects.LodDimension; +import com.backsun.lod.objects.NearFarBuffer; import com.backsun.lod.util.LodConfig; -import com.backsun.lod.util.ReflectionHandler; import com.backsun.lod.util.enums.ColorDirection; import com.backsun.lod.util.enums.FogDistance; import com.backsun.lod.util.enums.FogQuality; -import com.backsun.lod.util.enums.LodLocation; +import com.backsun.lod.util.enums.LodCorner; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.player.ClientPlayerEntity; import net.minecraft.client.renderer.ActiveRenderInfo; import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.GameRenderer; @@ -34,14 +37,17 @@ import net.minecraft.profiler.IProfiler; import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.vector.Matrix4f; import net.minecraft.util.math.vector.Vector3d; -import net.minecraft.world.chunk.Chunk; /** * @author James Seibel - * @version 2-13-2021 + * @version 2-24-2021 */ public class LodRenderer { + /** 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; + /** If true the LODs colors will be replaced with * a checkerboard, this can be used for debugging. */ public boolean debugging = false; @@ -57,31 +63,35 @@ public class LodRenderer private Tessellator tessellator; private BufferBuilder bufferBuilder; - /** - * This is an array of 0's used to clear old - * ByteBuffers when they need to be rebuilt. - */ - byte[] clearBytes; - private ReflectionHandler reflectionHandler; public LodDimension lodDimension = null; - + /** Total number of CPU cores available to the Java VM */ private int maxNumbThreads = Runtime.getRuntime().availableProcessors(); - /** How many threads should be used for building the render buffer. */ - private int numbBufferThreads = 1; + /** How many threads should be used for building render buffers */ + private int numbBufferThreads = maxNumbThreads; + /** This stores all the BuildBufferThread objects for each CPU core */ private ArrayList bufferThreads = new ArrayList(); - private volatile BufferBuilder[] nearBuffers = new BufferBuilder[maxNumbThreads]; - private volatile BufferBuilder[] farBuffers = new BufferBuilder[maxNumbThreads]; + /** The buffers that are used to draw LODs using near fog */ + private volatile BufferBuilder[] drawableNearBuffers = null; + /** The buffers that are used to draw LODs using far fog */ + private volatile BufferBuilder[] drawableFarBuffers = null; + + /** The buffers that are used to create LODs using near fog */ + private volatile BufferBuilder[] buildableNearBuffers = null; + /** The buffers that are used to create LODs using far fog */ + private volatile BufferBuilder[] buildableFarBuffers = null; + + /** If we have more CPU cores than LOD rows to draw this tells + * which drawable buffers will and won't be used. */ + private boolean[] shouldDrawBuffer = new boolean[maxNumbThreads]; + + /** This holds the threads used to generate the LOD buffers */ private ExecutorService bufferThreadPool = Executors.newFixedThreadPool(maxNumbThreads); - /* - * this is the maximum number of bytes a buffer - * would ever have to hold at once (this prevents the buffer - * from having to resize and thus save performance) - */ - private int bufferMaxCapacity = 0; + /** This holds the thread used to generate new LODs off the main thread. */ + private ExecutorService genThread = Executors.newSingleThreadExecutor(); /** This is used to determine if the LODs should be regenerated */ private int previousChunkRenderDistance = 0; @@ -92,10 +102,18 @@ public class LodRenderer /** This is used to determine if the LODs should be regenerated */ private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR; - /** if this is true the LODs should be regenerated */ + /** if this is true the LOD buffers should be regenerated, + * provided they aren't already being regenerated. */ private boolean regen = false; - + /** if this is true the LOD buffers are currently being + * regenerated. */ private volatile boolean regenerating = 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; + + + @@ -105,29 +123,57 @@ public class LodRenderer gameRender = mc.gameRenderer; // for some reason "Tessellator.getInstance()" won't work here, we have to create a new one - tessellator = new Tessellator(2097152); + tessellator = new Tessellator(2097152); // the number here is what is used by the default Tessellator bufferBuilder = tessellator.getBuffer(); reflectionHandler = new ReflectionHandler(); } - private ExecutorService genThread = Executors.newSingleThreadExecutor(); - private ExecutorService loadQueueThread = Executors.newSingleThreadExecutor(); - + /** + * 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 newDimension, float partialTicks, IProfiler newProfiler) - { - // should the LODs be regenerated? - if ((int)mc.player.getPosX() / LodChunk.WIDTH != prevChunkX || - (int)mc.player.getPosZ() / LodChunk.WIDTH != prevChunkZ || + { + if (lodDimension == null && newDimension == null) + { + // if there aren't any loaded LodChunks + // don't try drawing anything + return; + } + + + + + + //===============// + // initial setup // + //===============// + + + // used for debugging and viewing how long different processes take + mc.getProfiler().endSection(); + mc.getProfiler().startSection("LOD"); + mc.getProfiler().startSection("LOD setup"); + + ClientPlayerEntity player = mc.player; + + // should LODs be regenerated? + if ((int)player.getPosX() / LodChunk.WIDTH != prevChunkX || + (int)player.getPosZ() / LodChunk.WIDTH != prevChunkZ || previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks || prevFogDistance != LodConfig.COMMON.fogDistance.get() || lodDimension != newDimension) { + // yes regen = true; - prevChunkX = (int)mc.player.getPosX() / LodChunk.WIDTH; - prevChunkZ = (int)mc.player.getPosZ() / LodChunk.WIDTH; + prevChunkX = (int)player.getPosX() / LodChunk.WIDTH; + prevChunkZ = (int)player.getPosZ() / LodChunk.WIDTH; prevFogDistance = LodConfig.COMMON.fogDistance.get(); } else @@ -147,16 +193,6 @@ public class LodRenderer return; } - - - - - - // used for debugging and viewing how long different processes take - profiler.startSection("LOD_setup"); - - @SuppressWarnings("unused") - long startTime = System.nanoTime(); if (LodConfig.COMMON.drawCheckerBoard.get()) { if (debugging != LodConfig.COMMON.drawCheckerBoard.get()) @@ -171,18 +207,6 @@ public class LodRenderer } - // color setup - int alpha = 255; // 0 - 255 - - Color red = new Color(255, 0, 0, alpha); - Color black = new Color(0, 0, 0, alpha); - Color white = new Color(255, 255, 255, alpha); - @SuppressWarnings("unused") - Color invisible = new Color(0,0,0,0); - @SuppressWarnings("unused") - Color error = new Color(255, 0, 225, alpha); // bright pink - - // get the camera location ActiveRenderInfo renderInfo = mc.gameRenderer.getActiveRenderInfo(); @@ -202,21 +226,8 @@ public class LodRenderer int totalLength = (int) farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2; int numbChunksWide = (totalLength / LodChunk.WIDTH); - // this seemingly useless math is required, - // just using (int) camera doesn't work - int playerXChunkOffset = ((int) cameraX / LodChunk.WIDTH) * LodChunk.WIDTH; - int playerZChunkOffset = ((int) cameraZ / LodChunk.WIDTH) * LodChunk.WIDTH; - // this 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; - // this is where we store the LOD objects - AxisAlignedBB lodArray[][] = new AxisAlignedBB[numbChunksWide][numbChunksWide]; - // this is where we store the color for each LOD object - Color colorArray[][] = new Color[numbChunksWide][numbChunksWide]; - @@ -224,117 +235,36 @@ public class LodRenderer // create the LODs // //=================// - - profiler.endStartSection("LOD_generation"); - - if (regen) + // 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 && !regenerating && !switchBuffers) { - Thread t = new Thread(()-> - { - int numbChunksGen = 0; - int maxChunkGen = 640; - - // x axis - for (int i = 0; i < numbChunksWide; i++) - { - // z axis - for (int j = 0; j < numbChunksWide; j++) - { - // skip the middle - // (As the player moves some chunks will overlap or be missing, - // this is just how chunk loading/unloading works. This can hopefully - // be hidden with careful use of fog) - int middle = (numbChunksWide / 2); - if (RenderUtil.isCoordinateInLoadedArea(i, j, middle)) - { - 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; - - int chunkX = i + (startX / LodChunk.WIDTH); - int chunkZ = j + (startZ / LodChunk.WIDTH); - - LodChunk lod = lodDimension.getLodFromCoordinates(chunkX, chunkZ); - if (lod == null) - { - // note: for some reason if any color or lod object are set here - // it causes the game to use 100% gpu, all of it undefined in the debug menu - // and drop to ~6 fps. - // colorArray[i][j] = null; - // lodArray[i][j] = null; - - // TODO this partially works, but not fully - if (numbChunksGen < maxChunkGen) - { -// if (lod == null) -// { -// LodChunk placeholder = new LodChunk(); -// placeholder.x = chunkX; -// placeholder.z = chunkZ; -// placeholder.colors[ColorDirection.TOP.value] = error; -// lodDimension.addLod(placeholder); -// } - - // Thread loadT = new Thread(()-> { - Chunk newChunk = mc.world.getChunk(chunkX, chunkZ); - lodDimension.addLod(new LodChunk(newChunk, newChunk.getWorld())); - System.out.println(chunkX + "," + chunkZ + "\t" + numbChunksGen); - // }); - // loadQueueThread.execute(loadT); - numbChunksGen++; - } - - continue; - } - - Color c = new Color( - (lod.colors[ColorDirection.TOP.value].getRed()), - (lod.colors[ColorDirection.TOP.value].getGreen()), - (lod.colors[ColorDirection.TOP.value].getBlue()), - lod.colors[ColorDirection.TOP.value].getAlpha()); - - - - if (!debugging) - { - // add the color to the array - colorArray[i][j] = c; - } - else - { - // if debugging draw the squares as a black and white checker board - if ((chunkX + chunkZ) % 2 == 0) - c = white; - else - c = black; - // draw the first square as red - if (i == 0 && j == 0) - c = red; - - colorArray[i][j] = c; - } - - - // add the new box to the array - int topPoint = getLodHeightPoint(lod.top); - int bottomPoint = getLodHeightPoint(lod.bottom); - - // don't draw an LOD if it is empty - if (topPoint == -1 && bottomPoint == -1) - continue; - - lodArray[i][j] = new AxisAlignedBB(0, bottomPoint, 0, LodChunk.WIDTH, topPoint, LodChunk.WIDTH).offset(xOffset, yOffset, zOffset); - } - } - }); - t.run(); -// genThread.execute(t); + mc.getProfiler().endStartSection("LOD generation"); + regenerating = true; + + // this will only be called once, unless the numbBufferThreads changes + if (numbBufferThreads != bufferThreads.size()) + setupBufferThreads(); + + // this will mainly happen when the view distance is changed + if (drawableNearBuffers == null || drawableFarBuffers == null || + previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks) + setupBuffers(numbChunksWide); + + // generate the LODs on a separate thread to prevent stuttering or freezing + genThread.execute(createLodBufferGenerationThread(player.getPosX(), player.getPosZ(), numbChunksWide)); + } + + // replace the buffers used to draw and build, + // this is only done when the createLodBufferGenerationThread + // has finished executing on a parallel thread. + if (switchBuffers) + { + swapBuffers(); + switchBuffers = false; } @@ -345,14 +275,14 @@ public class LodRenderer // GL settings for rendering // //===========================// - profiler.endStartSection("LOD_setup"); // set the required open GL settings - 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.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); +// 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.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); RenderSystem.pushMatrix(); RenderSystem.rotatef(renderInfo.getPitch(), 1, 0, 0); // Fixes camera rotation. @@ -361,8 +291,6 @@ public class LodRenderer // setupProjectionMatrix(partialTicks); // setupLighting(partialTicks); - setupBufferThreads(lodArray); - @@ -377,34 +305,31 @@ public class LodRenderer // rendering // //===========// - profiler.endStartSection("LOD build buffer"); - if (regen) - generateLodBuffers(lodArray, colorArray, LodConfig.COMMON.fogDistance.get()); - - profiler.endStartSection("LOD draw"); - switch(LodConfig.COMMON.fogDistance.get()) { case NEAR_AND_FAR: - profiler.startSection("LOD draw near"); - setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality()); - sendLodsToGpuAndDraw(nearBuffers); - profiler.endSection(); + // when drawing NEAR_AND_FAR fog we need 2 draw + // calls since fog can only go in one direction at a time -// profiler.startSection("LOD draw far"); -// setupFog(FogDistance.FAR, reflectionHandler.getFogQuality()); -// sendLodsToGpuAndDraw(farBuffers); -// profiler.endSection(); - break; - case NEAR: - profiler.endStartSection("LOD draw near"); + mc.getProfiler().endStartSection("LOD draw"); setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality()); - sendLodsToGpuAndDraw(nearBuffers); - break; - case FAR: - profiler.endStartSection("LOD draw far"); + sendLodsToGpuAndDraw(drawableNearBuffers); + + mc.getProfiler().endStartSection("LOD draw"); setupFog(FogDistance.FAR, reflectionHandler.getFogQuality()); - sendLodsToGpuAndDraw(farBuffers); + sendLodsToGpuAndDraw(drawableFarBuffers); + break; + + case NEAR: + mc.getProfiler().endStartSection("LOD draw"); + setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality()); + sendLodsToGpuAndDraw(drawableNearBuffers); + break; + + case FAR: + mc.getProfiler().endStartSection("LOD draw"); + setupFog(FogDistance.FAR, reflectionHandler.getFogQuality()); + sendLodsToGpuAndDraw(drawableFarBuffers); break; } @@ -415,18 +340,16 @@ public class LodRenderer // cleanup // //=========// - profiler.endStartSection("LOD_cleanup"); - + mc.getProfiler().endStartSection("LOD cleanup"); // this must be done otherwise other parts of the screen may be drawn with a fog effect // IE the GUI RenderSystem.disableFog(); - GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); - GL11.glEnable(GL11.GL_TEXTURE_2D); - GL11.glDisable(GL11.GL_LIGHT2); - GL11.glDisable(GL11.GL_COLOR_MATERIAL); - GL11.glDisable(GL11.GL_CULL_FACE); +// GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); +// GL11.glEnable(GL11.GL_TEXTURE_2D); +// GL11.glDisable(LOD_GL_LIGHT_NUMBER); +// GL11.glDisable(GL11.GL_COLOR_MATERIAL); // undo any projection matrix changes we did to prevent other renders // from being corrupted @@ -438,122 +361,34 @@ public class LodRenderer previousChunkRenderDistance = mc.gameSettings.renderDistanceChunks; - // This is about how long this whole process should take - // 16 ms = 60 hz - @SuppressWarnings("unused") - long endTime = System.nanoTime(); + // end of profiler tracking + mc.getProfiler().endSection(); } - - - - - - - - - - - - - - - - - /** - * draw an array of cubes (or squares) with the given colors. - * @param lods bounding boxes to draw - * @param colors color of each box to draw + * This is where the actual drawing happens. + * + * @param buffers the buffers sent to the GPU to draw */ - private void generateLodBuffers(AxisAlignedBB[][] lods, Color[][] colors, FogDistance fogDistance) - { - List> bufferFutures = new ArrayList<>(); - // TODO this should change based on whether we are using near/far or both fog settings - bufferMaxCapacity = (lods.length * lods.length * (6 * 4 * ((3 * 4) + (4 * 4)))) / numbBufferThreads; - - for(int i = 0; i < numbBufferThreads; i++) - { - if (nearBuffers[i] == null || previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks) - { - nearBuffers[i] = new BufferBuilder(bufferMaxCapacity); //ByteBuffer.allocateDirect(bufferMaxCapacity); -// nearBuffers[i].order(ByteOrder.LITTLE_ENDIAN); - - farBuffers[i] = new BufferBuilder(bufferMaxCapacity); //ByteBuffer.allocateDirect(bufferMaxCapacity); -// farBuffers[i].order(ByteOrder.LITTLE_ENDIAN); - - clearBytes = new byte[bufferMaxCapacity]; - } - - if (regen) - { - // this is the best way I could find to - // overwrite the old data - // (which needs to be done otherwise old - // LODs may be drawn) - nearBuffers[i].byteBuffer.clear(); - nearBuffers[i].byteBuffer.put(clearBytes); - nearBuffers[i].byteBuffer.clear(); - - farBuffers[i].byteBuffer.clear(); - farBuffers[i].byteBuffer.put(clearBytes); - farBuffers[i].byteBuffer.clear(); - } - -// int pos = bufferBuilder.byteBuffer.position(); -// nearBuffers[i].byteBuffer.position(pos); -// farBuffers[i].byteBuffer.position(pos); - - bufferThreads.get(i).setNewData(nearBuffers[i], farBuffers[i], fogDistance, lods, colors, i, numbBufferThreads); - } - - try - { - bufferFutures = bufferThreadPool.invokeAll(bufferThreads); - } - catch (InterruptedException e) - { - // this should never happen, but just in case - e.printStackTrace(); - } - - for(int i = 0; i < numbBufferThreads; i++) - { - try - { - nearBuffers[i] = bufferFutures.get(i).get().nearBuffer; - farBuffers[i] = bufferFutures.get(i).get().farBuffer; - - nearBuffers[i].finishDrawing(); - farBuffers[i].finishDrawing(); - } - catch(CancellationException | ExecutionException| InterruptedException e) - { - // this should never happen, but just in case - e.printStackTrace(); - } - } - - } - - private void sendLodsToGpuAndDraw(BufferBuilder[] nearBuffers) + private void sendLodsToGpuAndDraw(BufferBuilder[] buffers) { for(int i = 0; i < numbBufferThreads; i++) { - profiler.startSection("LOD setup"); -// int pos = bufferBuilder.byteBuffer.position(); -// nearBuffers[i].byteBuffer.position(pos); - - bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); - bufferBuilder.byteBuffer.clear(); - bufferBuilder.putBulkData(nearBuffers[i].byteBuffer); - - profiler.endStartSection("LOD draw"); - tessellator.draw(); - - bufferBuilder.byteBuffer.clear(); // this is required otherwise nothing is drawn - profiler.endSection(); + if (shouldDrawBuffer[i]) + { + int pos = bufferBuilder.byteBuffer.position(); + buffers[i].byteBuffer.position(pos); + + bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); + bufferBuilder.byteBuffer.clear(); + // replace the data in bufferBuilder with the data from the given buffer + bufferBuilder.putBulkData(buffers[i].byteBuffer); + + tessellator.draw(); + + bufferBuilder.byteBuffer.clear(); // this is required otherwise nothing is drawn + } } } @@ -567,7 +402,6 @@ public class LodRenderer // Setup Functions // //=================// - @SuppressWarnings("deprecation") private void setupFog(FogDistance fogDistance, FogQuality fogQuality) { if(fogQuality == FogQuality.OFF) @@ -578,7 +412,7 @@ public class LodRenderer if(fogDistance == FogDistance.NEAR_AND_FAR) { - throw new IllegalArgumentException("setupFog only accepts NEAR or FAR fog distances."); + throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance."); } // the multipliers are percentages @@ -620,16 +454,13 @@ public class LodRenderer } } - RenderSystem.fogDensity(0.1f); RenderSystem.enableFog(); } - /** * create a new projection matrix and send it over to the GPU * @param partialTicks how many ticks into the frame we are - * @return true if the matrix was successfully created and sent to the GPU, false otherwise */ private void setupProjectionMatrix(float partialTicks) { @@ -660,42 +491,69 @@ public class LodRenderer { GL11.glEnable(GL11.GL_COLOR_MATERIAL); // set the color to be used as the material (this allows lighting to be enabled) + // FIXME // this isn't perfect right now, but it looks pretty good at 50% brightness - float sunBrightness = mc.world.getSunBrightness(partialTicks); + float sunBrightness = mc.world.getSunBrightness(partialTicks); // * mc.world.provider.getSunBrightnessFactor(partialTicks); float skyHasLight = 1.0f; //mc.world.provider.hasSkyLight()? 1.0f : 0.15f; - float gammaMultiplyer = ((float)mc.gameSettings.gamma * 0.5f + 0.5f); + float gammaMultiplyer = (float) mc.gameSettings.gamma * 0.5f + 0.5f; float lightStrength = sunBrightness * skyHasLight * gammaMultiplyer; float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f}; - + ByteBuffer temp = ByteBuffer.allocateDirect(16); temp.order(ByteOrder.nativeOrder()); - GL11.glLightfv(GL11.GL_LIGHT2, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip()); - GL11.glEnable(GL11.GL_LIGHT2); // Enable the above lighting + 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(); } - - private void setupBufferThreads(AxisAlignedBB[][] lods) + /** + * create the BuildBufferThreads + */ + private void setupBufferThreads() { - if (numbBufferThreads != bufferThreads.size()) + bufferThreads.clear(); + for(int i = 0; i < numbBufferThreads; i++) + bufferThreads.add(new BuildBufferThread()); + } + + /** + * Create all buffers that will be used. + */ + private void setupBuffers(int numbChunksWide) + { + drawableNearBuffers = new BufferBuilder[numbBufferThreads]; + drawableFarBuffers = new BufferBuilder[numbBufferThreads]; + + buildableNearBuffers = new BufferBuilder[numbBufferThreads]; + buildableFarBuffers = new BufferBuilder[numbBufferThreads]; + + + // calculate how many chunks wide, at most + // any thread will have to generate + int biggestWidth = -1; + int[] loads = calculateCpuLoadBalance(numbChunksWide, numbBufferThreads); + for(int i : loads) + if (i > biggestWidth) + biggestWidth = i; + + + // calculate the max amount of storage needed (in bytes) + // by any singular buffer + // NOTE: most buffers won't use the full amount, but this should prevent + // them from needing to allocate more memory (which is a slow progress) + int bufferMaxCapacity = (numbChunksWide * biggestWidth * (6 * 4 * ((3 * 4) + (4 * 4)))); + + for(int i = 0; i < numbBufferThreads; i++) { - bufferMaxCapacity = (lods.length * lods.length * (6 * 4 * ((3 * 4) + (4 * 4)))) / numbBufferThreads; - clearBytes = new byte[bufferMaxCapacity]; + // TODO complain or do something when memory is too low + // currently the VM will just crash and complain there is no more memory + // issue #4 + drawableNearBuffers[i] = new BufferBuilder(bufferMaxCapacity); + drawableFarBuffers[i] = new BufferBuilder(bufferMaxCapacity); - bufferThreads.clear(); - for(int i = 0; i < numbBufferThreads; i++) - bufferThreads.add(new BuildBufferThread()); - regen = true; - - for(int i = 0; i < maxNumbThreads; i++) - { - nearBuffers[i] = new BufferBuilder(bufferMaxCapacity); //ByteBuffer.allocateDirect(bufferMaxCapacity); -// nearBuffers[i].order(ByteOrder.LITTLE_ENDIAN); - - farBuffers[i] = new BufferBuilder(bufferMaxCapacity); //ByteBuffer.allocateDirect(bufferMaxCapacity); -// farBuffers[i].order(ByteOrder.LITTLE_ENDIAN); - } + buildableNearBuffers[i] = new BufferBuilder(bufferMaxCapacity); + buildableFarBuffers[i] = new BufferBuilder(bufferMaxCapacity); } } @@ -704,18 +562,275 @@ public class LodRenderer + + //======================// + // Other Misc Functions // + //======================// + + /** - * Returns -1 if there are no valid points + * @Returns -1 if there are no valid points */ - private int getLodHeightPoint(short[] heightPoints) + private int getValidHeightPoint(short[] heightPoints) { - if (heightPoints[LodLocation.NE.value] != -1) - return heightPoints[LodLocation.NE.value]; - if (heightPoints[LodLocation.NW.value] != -1) - return heightPoints[LodLocation.NW.value]; - if (heightPoints[LodLocation.SE.value] != -1) - return heightPoints[LodLocation.NE.value]; - return heightPoints[LodLocation.NE.value]; + if (heightPoints[LodCorner.NE.value] != -1) + return heightPoints[LodCorner.NE.value]; + if (heightPoints[LodCorner.NW.value] != -1) + return heightPoints[LodCorner.NW.value]; + if (heightPoints[LodCorner.SE.value] != -1) + return heightPoints[LodCorner.NE.value]; + return heightPoints[LodCorner.NE.value]; + } + + + /** + * Create a thread to asynchronously generate LOD buffers + * centered around the given camera X and Z. + *
+ * This thread will write to the drawableNearBuffers and drawableFarBuffers. + *
+ * After the buildable buffers have been generated they must be + * swapped with the drawable buffers to be drawn. + */ + private Thread createLodBufferGenerationThread(double playerX, double playerZ, + int numbChunksWide) + { + // this is where we store the points for each LOD object + AxisAlignedBB lodArray[][] = new AxisAlignedBB[numbChunksWide][numbChunksWide]; + // this is where we store the color for each LOD object + Color colorArray[][] = new Color[numbChunksWide][numbChunksWide]; + + int alpha = 255; // 0 - 255 + Color red = new Color(255, 0, 0, alpha); + Color black = new Color(0, 0, 0, alpha); + Color white = new Color(255, 255, 255, alpha); + @SuppressWarnings("unused") + Color invisible = new Color(0,0,0,0); + @SuppressWarnings("unused") + Color error = new Color(255, 0, 225, alpha); // bright pink + + // this seemingly useless math is required, + // just using (int) camera 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(()-> + { + // x axis + for (int i = 0; i < numbChunksWide; i++) + { + // z axis + for (int j = 0; j < numbChunksWide; j++) + { + // skip the middle + // (As the player moves some chunks will overlap or be missing, + // this is just how chunk loading/unloading works. This can hopefully + // be hidden with careful use of fog) + int middle = (numbChunksWide / 2); + if (isCoordInCenterArea(i, j, middle)) + { + 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; + + int chunkX = i + (startX / LodChunk.WIDTH); + int chunkZ = j + (startZ / LodChunk.WIDTH); + + LodChunk lod = lodDimension.getLodFromCoordinates(chunkX, chunkZ); + if (lod == null) + { + // note: for some reason if any color or lod objects are set here + // it causes the game to use 100% gpu; + // undefined in the debug menu + // and drop to ~6 fps. + colorArray[i][j] = null; + lodArray[i][j] = null; + + continue; + } + + Color c = new Color( + (lod.colors[ColorDirection.TOP.value].getRed()), + (lod.colors[ColorDirection.TOP.value].getGreen()), + (lod.colors[ColorDirection.TOP.value].getBlue()), + lod.colors[ColorDirection.TOP.value].getAlpha()); + + if (!debugging) + { + // add the color to the array + colorArray[i][j] = c; + } + else + { + // if debugging draw the squares as a black and white checker board + if ((chunkX + chunkZ) % 2 == 0) + c = white; + else + c = black; + // draw the first square as red + if (i == 0 && j == 0) + c = red; + + colorArray[i][j] = c; + } + + + // add the new box to the array + int topPoint = getValidHeightPoint(lod.top); + int bottomPoint = getValidHeightPoint(lod.bottom); + + // don't draw an LOD if it is empty + if (topPoint == -1 && bottomPoint == -1) + continue; + + lodArray[i][j] = new AxisAlignedBB(0, bottomPoint, 0, LodChunk.WIDTH, topPoint, LodChunk.WIDTH).offset(xOffset, yOffset, zOffset); + } + } + + generateLodBuffers(lodArray, colorArray, LodConfig.COMMON.fogDistance.get()); + + regenerating = false; + switchBuffers = true; + }); + return t; + } + + /** + * draw an array of boxes with the given colors. + *

+ * Currently only one color per box is supported. + * + * @param lods bounding boxes to draw + * @param colors color of each box to draw + */ + private void generateLodBuffers(AxisAlignedBB[][] lods, Color[][] colors, FogDistance fogDistance) + { + List> bufferFutures = new ArrayList<>(); + ArrayList threadsToRun = new ArrayList<>(); + + int indexToStart = 0; + int[] threadLoads = calculateCpuLoadBalance(lods.length, numbBufferThreads); + + // update the information that the bufferThreads are using + for(int i = 0; i < numbBufferThreads; i++) + { + // if we have more threads than LOD rows to generate + // don't send the threads to the CPU + if (threadLoads[i] != 0) + { + // update this thread with the latest information + bufferThreads.get(i). + setNewData(buildableNearBuffers[i], buildableFarBuffers[i], + fogDistance, lods, colors, indexToStart, threadLoads[i]); + indexToStart += threadLoads[i]; + + // add this thread to the list of threads we are going to run + threadsToRun.add(bufferThreads.get(i)); + + shouldDrawBuffer[i] = true; + } + else + { + shouldDrawBuffer[i] = false; + } + } + + // run all the bufferThreads and get their results + try + { + bufferFutures = bufferThreadPool.invokeAll(threadsToRun); + } + catch (InterruptedException e) + { + // this should never happen, but just in case + e.printStackTrace(); + } + + // update our buildable buffers + for(int i = 0; i < numbBufferThreads; i++) + { + // only replace buffers that actually generated something + if (threadLoads[i] != 0) + { + try + { + buildableNearBuffers[i] = bufferFutures.get(i).get().nearBuffer; + buildableFarBuffers[i] = bufferFutures.get(i).get().farBuffer; + } + catch(CancellationException | ExecutionException| InterruptedException e) + { + // this should never happen, but just in case + e.printStackTrace(); + } + } + } + } + + + /** + * Swap buildable and drawable buffers. + */ + private void swapBuffers() + { + for(int i = 0; i < buildableNearBuffers.length; i++) + { + try + { + BufferBuilder tmp = buildableNearBuffers[i]; + buildableNearBuffers[i] = drawableNearBuffers[i]; + drawableNearBuffers[i] = tmp; + + tmp = buildableFarBuffers[i]; + buildableFarBuffers[i] = drawableFarBuffers[i]; + drawableFarBuffers[i] = tmp; + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + + + /** + * 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.gameSettings.renderDistanceChunks + && i <= centerCoordinate + mc.gameSettings.renderDistanceChunks) + && + (j >= centerCoordinate - mc.gameSettings.renderDistanceChunks + && j <= centerCoordinate + mc.gameSettings.renderDistanceChunks); + } + + + /** + * This is a simple implementation of the pigeon hole + * principle to try and give each BuildBufferThread a balanced load. + * + * @returns an array of ints where each int is how many rows + * that BuildBufferThread should generate + */ + private int[] calculateCpuLoadBalance(int numbOfItems, int numbOfThreads) + { + int[] cpuLoad = new int[numbOfThreads]; + + for(int i = 0; i < numbOfItems; i++) + cpuLoad[i % numbOfThreads]++; + + return cpuLoad; } diff --git a/src/main/java/com/backsun/lod/util/LodUtils.java b/src/main/java/com/backsun/lod/util/LodUtils.java new file mode 100644 index 000000000..5a44e9187 --- /dev/null +++ b/src/main/java/com/backsun/lod/util/LodUtils.java @@ -0,0 +1,55 @@ +package com.backsun.lod.util; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.DimensionType; +import net.minecraft.world.server.ServerWorld; + +/** + * This class holds methods that may be used in multiple places. + * + * @author James Seibel + * @version 02-26-2021 + */ +public class LodUtils +{ + private static Minecraft mc = Minecraft.getInstance(); + + + + /** + * Gets the first valid ServerWorld. + * + * @return null if there are no ServerWorlds + */ + public static ServerWorld getFirstValidServerWorld() + { + Iterable worlds = mc.getIntegratedServer().getWorlds(); + + for (ServerWorld world : worlds) + return world; + + return null; + } + + /** + * Gets the ServerWorld for the relevant dimension. + * + * @return null if there is no ServerWorld for the given dimension + */ + public static ServerWorld getServerWorldFromDimension(DimensionType dimension) + { + Iterable worlds = mc.getIntegratedServer().getWorlds(); + ServerWorld returnWorld = null; + + for (ServerWorld world : worlds) + { + if(world.getDimensionType() == dimension) + { + returnWorld = world; + break; + } + } + + return returnWorld; + } +} diff --git a/src/main/java/com/backsun/lod/util/Reference.java b/src/main/java/com/backsun/lod/util/Reference.java index fdb79e37b..2a2ba46c8 100644 --- a/src/main/java/com/backsun/lod/util/Reference.java +++ b/src/main/java/com/backsun/lod/util/Reference.java @@ -1,6 +1,7 @@ package com.backsun.lod.util; /** + * This holds meta information about the mod. * * @author James Seibel * @version 04-16-2020 @@ -14,7 +15,7 @@ public class Reference /** the mod's version */ public static final String VERSION = "1.0"; /** the version of minecraft this mod is built for */ - public static final String ACCEPTED_VERSIONS = "[1.12.2]"; + public static final String ACCEPTED_VERSIONS = "[1.16.4]"; /** where the client proxy class is */ public static final String CLIENT_PROXY_CLASS = "com.backsun.lod.proxy.ClientProxy"; diff --git a/src/main/java/com/backsun/lod/util/enums/ColorDirection.java b/src/main/java/com/backsun/lod/util/enums/ColorDirection.java index 49a188951..9d4aa782f 100644 --- a/src/main/java/com/backsun/lod/util/enums/ColorDirection.java +++ b/src/main/java/com/backsun/lod/util/enums/ColorDirection.java @@ -1,10 +1,10 @@ package com.backsun.lod.util.enums; /** + * TOP, N, S, E, W, BOTTOM + * * @author James Seibel * @version 10-17-2020 - * - * TOP, N, S, E, W, BOTTOM */ public enum ColorDirection { diff --git a/src/main/java/com/backsun/lod/util/enums/FogDistance.java b/src/main/java/com/backsun/lod/util/enums/FogDistance.java index a3046993d..87dd8c3f0 100644 --- a/src/main/java/com/backsun/lod/util/enums/FogDistance.java +++ b/src/main/java/com/backsun/lod/util/enums/FogDistance.java @@ -8,9 +8,9 @@ package com.backsun.lod.util.enums; */ public enum FogDistance { - /** valid for both fast and fancy fog qualities. */ + /** good for fast or fancy fog qualities. */ NEAR, - /** valid for both fast and fancy fog qualities. */ + /** good for fast or fancy fog qualities. */ FAR, /** only looks good if the fog quality is set to Fancy. */ NEAR_AND_FAR; diff --git a/src/main/java/com/backsun/lod/util/enums/LodCorner.java b/src/main/java/com/backsun/lod/util/enums/LodCorner.java new file mode 100644 index 000000000..ac686f31f --- /dev/null +++ b/src/main/java/com/backsun/lod/util/enums/LodCorner.java @@ -0,0 +1,28 @@ +package com.backsun.lod.util.enums; + +/** + * NE, SE, SW, NW + * + * @author James Seibel + * @version 1-20-2020 + */ +public enum LodCorner +{ + // used for position + + /** -Z, +X */ + NE(0), + /** +Z, +X */ + SE(1), + /** +Z, -X */ + SW(2), + /** -Z, -X */ + NW(3); + + public final int value; + + private LodCorner(int newValue) + { + value = newValue; + } +} diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index c0aa56039..d2a35063c 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,4 +1,4 @@ -# Note: to update code in eclipse run the "eclipse" commad in graldew +# Note: to update code in eclipse run the "eclipse" command in graldew # make public the method getFOVModifier @@ -16,6 +16,9 @@ public net.minecraft.client.renderer.GameRenderer field_228376_w_ # cameraYaw # make public the cameraPitch in the GameRenderer public net.minecraft.client.renderer.GameRenderer field_228377_x_ # cameraPitch +# make public the folder in the DimensionSavedDataManager +public net.minecraft.world.storage.DimensionSavedDataManager field_215759_d # folder + #=====================# # Examples from Forge #