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 #