Compare commits

...

22 Commits

Author SHA1 Message Date
James Seibel 81f11269f8 Fix a spelling mistake 2021-02-28 22:25:20 -06:00
James Seibel c6f06a6fd4 Update the readme to reflect the state of this branch 2021-02-28 22:02:43 -06:00
James Seibel 9704ad80f1 Remove a lingering reference to ASMHelper 2021-02-24 15:17:24 -06:00
James Seibel 026d9a425e Remove optifine from the home directory 2021-02-24 15:15:59 -06:00
James Seibel 85bff28758 Update the reflectionHandler to better handle missing optifine 2021-02-24 15:03:31 -06:00
James Seibel 213060d473 Fix issue #1 (LODs not being rendered at full distance)
This was fixed by using the pigeon hole principle to balance the number of rows each CPU needs to generate buffers for; while also preventing any rows from being missed (the cause of issue #1).
2021-02-24 13:31:24 -06:00
James Seibel ca400ccf59 Add java doc comments to variables in LodRenderer 2021-02-24 13:28:18 -06:00
James Seibel 9dc6d17d7b Fix issue #5 (teleporting not regenerating LODs) 2021-02-23 21:42:49 -06:00
James Seibel 15d3af3c66 Add a comment for issue #5 2021-02-23 15:29:25 -06:00
James Seibel d9d1c4dfaf Fix issue #2
The center of the LodDimension was accidentally being added to the region coordinate, causing the wrong region to be selected at times.
2021-02-23 15:20:12 -06:00
James Seibel 1c2974cf6a Add comment for issue #4 2021-02-23 15:18:21 -06:00
James Seibel c353335968 Clean up LodChunk toString 2021-02-23 15:15:49 -06:00
James Seibel 2ee76a413f Refactor, comment, and clean up everything
This adds comments to almost every class, removes a few classes that weren't being used, improves the names of a number of methods, and does a bunch of random polishing/cleaning.
2021-02-23 08:19:37 -06:00
James Seibel 7b074fc155 Remove the clear buffers and remove a completed TODO
The clear buffers are no longer needed since we are now using BufferBuilders instead of ByteBuffers (which also gives us a nice performance boost as well).
2021-02-22 14:32:33 -06:00
James Seibel 28220f62ad Update version number 2021-02-21 16:56:02 -06:00
James Seibel cc7e4359ec Make Builder objects to improve organization 2021-02-21 16:18:18 -06:00
James Seibel 72a292aeb6 Improve file organization 2021-02-21 13:54:00 -06:00
James Seibel ca8f63cf7d Replace ByteBuffers with BufferBuilders 2021-02-21 13:23:28 -06:00
James Seibel 4f518a0701 Implement async buffer generation 2021-02-21 09:14:02 -06:00
James Seibel b81253af67 Merge branch 'master' of gitlab.com:jeseibel/minecraft-lod-mod 2021-02-21 08:48:39 -06:00
James Seibel fd3d4e92af Update to the newest version of OptifineDevTweaker 2021-02-21 08:40:04 -06:00
James Seibel 7418a196a9 Fix a few typos in the Readme 2021-02-21 08:39:20 -06:00
27 changed files with 1102 additions and 1046 deletions
Binary file not shown.
+14 -5
View File
@@ -1,7 +1,16 @@
This program is an attempt to create Level Of Details (LODs) in Minecraft. This program is an attempt to implement a Level Of Detail system for Minecraft.
The purpose is to increase the maximum view distance in game With the purpose of increasing the maximum view distance in game without harming performance.
Used in congunction with: Forge version: 1.12.2-14.23.5.2847
Notes:
This version will run in eclipse perfectly fine, but will not work correctly
in retail Minecraft. Specifically the core mod doesn't work in retail minecraft,
which means that LODs will be drawn on top of normal terrain.
This version also doesn't work with optifine at all, since it breaks the core mod.
Used in conjunction with:
https://gitlab.com/jeseibel/minecraft-lod-core-mod https://gitlab.com/jeseibel/minecraft-lod-core-mod
@@ -21,11 +30,11 @@ Step 3: run the command: "./gradlew eclipse"
Step 4: Import project Step 4: Import project
Step 5: Create a system variable called "JAVA_MC_HOME" with the location of the JDK 1.8.0_251 (This is needed for gradle to work correctly) Step 5: Create a system variable called "JAVA_MC_HOME" with the location of the JDK 1.8.0_251 (This is needed for gradle to work correctly)
And make sure it is used in the build.gradle file. And make sure it is used in the gradle.bat file.
Step 6: Import the lodcore and lodcore_source jar files into the referenced libraries. Step 6: Import the lodcore and lodcore_source jar files into the referenced libraries.
Step 6: Make sure the eclipse has the JDK 1.8.0_251 installed. (This is needed so that eclipse can run minecraft) Step 7: Make sure the eclipse has the JDK 1.8.0_251 installed. (This is needed so that eclipse can run minecraft)
Other commands: Other commands:
Binary file not shown.
-1
View File
@@ -1 +0,0 @@
include ":ASMHelper"
@@ -16,6 +16,10 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
/** /**
* Initialize and setup the Mod.
* <br>
* If you are looking for the real start of the mod
* check out the ClientProxy.
* *
* @author James Seibel * @author James Seibel
* @version 02-07-2021 * @version 02-07-2021
@@ -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<NearFarBuffer>
{
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);
}
}
@@ -0,0 +1,133 @@
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 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.storage.ExtendedBlockStorage;
/**
* 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)
{
Minecraft mc = Minecraft.getMinecraft();
// 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;
int dimId = chunk.getWorld().provider.getDimension();
World world = mc.getIntegratedServer().getWorld(dimId);
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(dimId) == null)
{
DimensionType dim = DimensionType.getById(dimId);
lodDim = new LodDimension(dim, regionWidth);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dimId);
}
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)
{
ExtendedBlockStorage[] blockStorage = chunk.getBlockStorageArray();
for(ExtendedBlockStorage e : blockStorage)
{
if(e != null && !e.isEmpty())
{
return true;
}
}
return false;
}
}
@@ -1,4 +1,4 @@
package com.backsun.lod.util; package com.backsun.lod.handlers;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@@ -23,9 +23,9 @@ import net.minecraft.world.storage.ISaveHandler;
* @author James Seibel * @author James Seibel
* @version 01-30-2021 * @version 01-30-2021
*/ */
public class LodFileHandler public class LodDimensionFileHandler
{ {
private LodDimension loadedRegion = null; private LodDimension loadedDimension = null;
public long regionLastWriteTime[][]; public long regionLastWriteTime[][];
// String s = Minecraft.getMinecraftDir().getCanonicalPath() + "/saves/" + world.getSaveHandler().getSaveDirectoryName() + "/data/AA/World" + world.provider.dimensionId + ".dat"; // String s = Minecraft.getMinecraftDir().getCanonicalPath() + "/saves/" + world.getSaveHandler().getSaveDirectoryName() + "/data/AA/World" + world.provider.dimensionId + ".dat";
@@ -36,19 +36,17 @@ public class LodFileHandler
private final String FILE_EXTENSION = ".txt"; private final String FILE_EXTENSION = ".txt";
private ExecutorService fileWritingThreadPool = Executors.newFixedThreadPool(1); private ExecutorService fileWritingThreadPool = Executors.newFixedThreadPool(1);
/** Is true if the readyToReadAndWrite is false */
private boolean waitingToSaveRegions = false;
public LodFileHandler(ISaveHandler newSaveHandler, LodDimension newLoadedRegion) public LodDimensionFileHandler(ISaveHandler newSaveHandler, LodDimension newLoadedDimension)
{ {
saveHandler = newSaveHandler; saveHandler = newSaveHandler;
loadedRegion = newLoadedRegion; loadedDimension = newLoadedDimension;
// these two variable are used in sync with the LodDimension // these two variable are used in sync with the LodDimension
regionLastWriteTime = new long[loadedRegion.getWidth()][loadedRegion.getWidth()]; regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
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++)
regionLastWriteTime[i][j] = -1; regionLastWriteTime[i][j] = -1;
if (saveHandler != null && saveHandler.getWorldDirectory() != null) if (saveHandler != null && saveHandler.getWorldDirectory() != null)
@@ -141,8 +139,10 @@ public class LodFileHandler
// Save to File // // Save to File //
//==============// //==============//
/**
public synchronized void saveDirtyRegionsToFile() * Save all dirty regions in this LodDimension to file.
*/
public synchronized void saveDirtyRegionsToFileAsync()
{ {
// we don't currently support reading or writing // we don't currently support reading or writing
// files when connected to a server // files when connected to a server
@@ -150,64 +150,31 @@ public class LodFileHandler
return; return;
if (!readyToReadAndWrite()) if (!readyToReadAndWrite())
{
// we aren't ready to read and write yet // we aren't ready to read and write yet
if(!waitingToSaveRegions)
{
waitingToSaveRegions = true;
// retry until we are able to read and write
// then wake up the fileWritingThreadPool
Thread retryReady = new Thread(() ->
{
try
{
// check once every so often so see
// if anything has changed so we can
// start reading and writing files
while(!readyToReadAndWrite())
{
this.wait(1000);
// get the save handler again, if for some
// reason the original handler was null
saveHandler = Minecraft.getMinecraft().getIntegratedServer().getWorld(0).getSaveHandler();
save_dir = getWorldSaveDirectory();
}
// we can start writing files now
fileWritingThreadPool.execute(saveDirtyRegionsThread);
waitingToSaveRegions = false;
}
catch (InterruptedException e)
{ /* should never be called */}
});
retryReady.run();
}
return; return;
}
fileWritingThreadPool.execute(saveDirtyRegionsThread); fileWritingThreadPool.execute(saveDirtyRegionsThread);
} }
private Thread saveDirtyRegionsThread = new Thread(() -> 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]); saveRegionToDisk(loadedDimension.regions[i][j]);
loadedRegion.isRegionDirty[i][j] = false; loadedDimension.isRegionDirty[i][j] = false;
} }
} }
} }
waitingToSaveRegions = false;
}); });
/**
* Save a specific region to disk.<br>
* Note: it will save to the LodDimension that this
* handler is associated with.
*/
private void saveRegionToDisk(LodRegion region) private void saveRegionToDisk(LodRegion region)
{ {
if (!readyToReadAndWrite() || region == null) if (!readyToReadAndWrite() || region == null)
@@ -258,15 +225,13 @@ public class LodFileHandler
* Return the name of the file that should contain the * Return the name of the file that should contain the
* region at the given x and z. <br> * region at the given x and z. <br>
* Returns null if this object isn't ready to read and write. * Returns null if this object isn't ready to read and write.
* @param regionX
* @param regionZ
*/ */
private String getFileNameForRegion(int regionX, int regionZ) private String getFileNameForRegion(int regionX, int regionZ)
{ {
if (!readyToReadAndWrite()) if (!readyToReadAndWrite())
return null; return null;
return save_dir + "\\lod_data\\DIM" + loadedRegion.dimension.getId() + "\\" + return save_dir + "\\lod_data\\DIM" + loadedDimension.dimension.getId() + "\\" +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION; FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
} }
@@ -274,6 +239,8 @@ public class LodFileHandler
/** /**
* Returns if this FileHandler is ready to read * Returns if this FileHandler is ready to read
* and write files. * and write files.
* <br>
* This returns true when the world save directory is known.
*/ */
public boolean readyToReadAndWrite() public boolean readyToReadAndWrite()
{ {
@@ -306,6 +273,8 @@ public class LodFileHandler
/** /**
* Gets the canonical path to the world save folder.
* <br>
* Returns null if there was an IO Exception * Returns null if there was an IO Exception
*/ */
private String getWorldSaveDirectory() private String getWorldSaveDirectory()
@@ -1,4 +1,4 @@
package com.backsun.lod.util; package com.backsun.lod.handlers;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@@ -11,7 +11,8 @@ import net.minecraft.client.Minecraft;
/** /**
* This object is used to get variables from methods * This object is used to get variables from methods
* where they are private. * where they are private. Specifically the fog setting
* in Optifine.
* *
* @author James Seibel * @author James Seibel
* @version 09-21-2020 * @version 09-21-2020
@@ -136,7 +137,7 @@ public class ReflectionHandler
// either optifine isn't installed, // either optifine isn't installed,
// the variable name was changed, or // the variable name was changed, or
// the setup method wasn't called yet. // the setup method wasn't called yet.
return FogQuality.OFF; return FogQuality.FANCY;
} }
int returnNum = 0; int returnNum = 0;
@@ -3,7 +3,7 @@ package com.backsun.lod.objects;
import java.awt.Color; import java.awt.Color;
import com.backsun.lod.util.enums.ColorDirection; import com.backsun.lod.util.enums.ColorDirection;
import com.backsun.lod.util.enums.LodLocation; import com.backsun.lod.util.enums.LodCorner;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@@ -73,7 +73,7 @@ public class LodChunk
//==============// //==============//
/** /**
* Create an empty LodChunk * Create an empty invisible LodChunk at (0,0)
*/ */
public LodChunk() public LodChunk()
{ {
@@ -145,7 +145,7 @@ public class LodChunk
// top // top
top = new short[4]; top = new short[4];
for(LodLocation loc : LodLocation.values()) for(LodCorner loc : LodCorner.values())
{ {
lastIndex = index; lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1); index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
@@ -156,7 +156,7 @@ public class LodChunk
// bottom // bottom
bottom = new short[4]; bottom = new short[4];
for(LodLocation loc : LodLocation.values()) for(LodCorner loc : LodCorner.values())
{ {
lastIndex = index; lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex + 1); index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
@@ -202,11 +202,11 @@ public class LodChunk
} }
/** /**
* Illegal argument is thrown if either the * Creates a LodChunk for a chunk in the given world. <br>
* chunk or world is null. The reason the world * Note: The world is required to determine each block's color
* can't be null is because it's required to determine *
* a block's color.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/ */
public LodChunk(Chunk chunk, World world) throws IllegalArgumentException public LodChunk(Chunk chunk, World world) throws IllegalArgumentException
{ {
@@ -228,16 +228,16 @@ public class LodChunk
colors = new Color[6]; colors = new Color[6];
// generate the top and bottom points of this LOD // generate the top and bottom points of this LOD
for(LodLocation loc : LodLocation.values()) for(LodCorner loc : LodCorner.values())
{ {
top[loc.value] = generateLodSection(chunk, true, loc); top[loc.value] = generateLodCorner(chunk, SectionGenerationMode.GENERATE_TOP, loc);
bottom[loc.value] = generateLodSection(chunk, false, loc); bottom[loc.value] = generateLodCorner(chunk, SectionGenerationMode.GENERATE_BOTTOM, loc);
} }
// determine the average color for each direction // determine the average color for each direction
for(ColorDirection dir : ColorDirection.values()) for(ColorDirection dir : ColorDirection.values())
{ {
colors[dir.value] = generateLodColorSection(chunk, world, dir); colors[dir.value] = generateLodColor(chunk, world, dir);
} }
} }
@@ -253,15 +253,17 @@ public class LodChunk
/** /**
* Generate the height for the given LodLocation, either the top or bottom.
* <br><br>
* If invalid/null/empty chunks are given * If invalid/null/empty chunks are given
* crashes may occur. * crashes may occur.
*/ */
public short generateLodSection(Chunk chunk, boolean getTopSection, LodLocation lodLoc) private short generateLodCorner(Chunk chunk, SectionGenerationMode generationMode, LodCorner lodLoc)
{ {
// should have a length of 16 // should have a length of 16
// (each storage is 16x16x16 and the // (each storage is 16x16x16 and the
// world height is 256) // world height is 256)
ExtendedBlockStorage[] data = chunk.getBlockStorageArray(); ExtendedBlockStorage[] blockStorage = chunk.getBlockStorageArray();
@@ -313,21 +315,29 @@ public class LodChunk
} }
if(getTopSection) if(generationMode == SectionGenerationMode.GENERATE_TOP)
return determineTopPoint(data, startX, endX, startZ, endZ); return determineTopPoint(blockStorage, startX, endX, startZ, endZ);
else else
return determineBottomPoint(data, startX, endX, startZ, endZ); return determineBottomPoint(blockStorage, startX, endX, startZ, endZ);
}
/** GENERATE_TOP, GENERATE_BOTTOM */
private enum SectionGenerationMode
{
GENERATE_TOP,
GENERATE_BOTTOM;
} }
private short determineBottomPoint(ExtendedBlockStorage[] data, int startX, int endX, int startZ, int endZ) /**
* Find the lowest valid point from the bottom.
*/
private short determineBottomPoint(ExtendedBlockStorage[] blockStorage, int startX, int endX, int startZ, int endZ)
{ {
// search from the bottom up // search from the bottom up
for(int i = 0; i < data.length; i++) for(int i = 0; i < blockStorage.length; i++)
{ {
for(int y = 0; y < CHUNK_DATA_HEIGHT; y++) for(int y = 0; y < CHUNK_DATA_HEIGHT; y++)
{ {
if(isLayerValidLodPoint(blockStorage, startX, endX, startZ, endZ, i, y))
if(isLayerValidLodPoint(data, startX, endX, startZ, endZ, i, y))
{ {
// we found // we found
// enough blocks in this // enough blocks in this
@@ -335,23 +345,24 @@ public class LodChunk
// LOD point // LOD point
return (short) (y + (i * CHUNK_DATA_HEIGHT)); return (short) (y + (i * CHUNK_DATA_HEIGHT));
} }
}
} // y }
} // data
// we never found a valid LOD point // we never found a valid LOD point
return -1; return -1;
} }
private short determineTopPoint(ExtendedBlockStorage[] data, int startX, int endX, int startZ, int endZ) /**
* Find the highest valid point from the Top
*/
private short determineTopPoint(ExtendedBlockStorage[] blockStorage, int startX, int endX, int startZ, int endZ)
{ {
// search from the top down // search from the top down
for(int i = data.length - 1; i >= 0; i--) for(int i = blockStorage.length - 1; i >= 0; i--)
{ {
for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--) for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
{ {
if(isLayerValidLodPoint(data, startX, endX, startZ, endZ, i, y)) if(isLayerValidLodPoint(blockStorage, startX, endX, startZ, endZ, i, y))
{ {
// we found // we found
// enough blocks in this // enough blocks in this
@@ -359,10 +370,8 @@ public class LodChunk
// LOD point // LOD point
return (short) (y + (i * CHUNK_DATA_HEIGHT)); return (short) (y + (i * CHUNK_DATA_HEIGHT));
} }
} // y }
} // data }
// we never found a valid LOD point // we never found a valid LOD point
return -1; return -1;
@@ -373,7 +382,7 @@ public class LodChunk
* values a valid LOD point? * values a valid LOD point?
*/ */
private boolean isLayerValidLodPoint( private boolean isLayerValidLodPoint(
ExtendedBlockStorage[] data, ExtendedBlockStorage[] blockStorage,
int startX, int endX, int startX, int endX,
int startZ, int endZ, int startZ, int endZ,
int dataIndex, int y) int dataIndex, int y)
@@ -385,7 +394,7 @@ public class LodChunk
{ {
for(int z = startZ; z < endZ; z++) for(int z = startZ; z < endZ; z++)
{ {
if(data[dataIndex] == null) if(blockStorage[dataIndex] == null)
{ {
// this section doesn't have any blocks, // this section doesn't have any blocks,
// it is not a valid section // it is not a valid section
@@ -393,7 +402,7 @@ public class LodChunk
} }
else else
{ {
if(data[dataIndex].get(x, y, z) != null && Block.getIdFromBlock(data[dataIndex].get(x, y, z).getBlock()) != airBlockId) if(blockStorage[dataIndex].get(x, y, z) != null && Block.getIdFromBlock(blockStorage[dataIndex].get(x, y, z).getBlock()) != airBlockId)
{ {
// we found a valid block in // we found a valid block in
// in this layer // in this layer
@@ -412,9 +421,11 @@ public class LodChunk
return false; return false;
} }
/**
* Generate the color of the given ColorDirection at the given chunk
private Color generateLodColorSection(Chunk chunk, World world, ColorDirection colorDir) * in the given world.
*/
private Color generateLodColor(Chunk chunk, World world, ColorDirection colorDir)
{ {
Minecraft mc = Minecraft.getMinecraft(); Minecraft mc = Minecraft.getMinecraft();
BlockColors bc = mc.getBlockColors(); BlockColors bc = mc.getBlockColors();
@@ -441,11 +452,18 @@ 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) private Color generateLodColorVertical(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc)
{ {
ExtendedBlockStorage[] data = chunk.getBlockStorageArray(); if(colorDir != ColorDirection.TOP && colorDir != ColorDirection.BOTTOM)
{
throw new IllegalArgumentException("generateLodColorVertical only accepts the ColorDirection TOP or BOTTOM");
}
ExtendedBlockStorage[] blockStorage = chunk.getBlockStorageArray();
int numbOfBlocks = 0; int numbOfBlocks = 0;
int red = 0; int red = 0;
@@ -456,8 +474,8 @@ public class LodChunk
// either go top down or bottom up // either go top down or bottom up
int dataStart = goTopDown? data.length - 1 : 0; int dataStart = goTopDown? blockStorage.length - 1 : 0;
int dataMax = data.length; int dataMax = blockStorage.length;
int dataMin = 0; int dataMin = 0;
int dataIncrement = goTopDown? -1 : 1; int dataIncrement = goTopDown? -1 : 1;
@@ -474,16 +492,16 @@ public class LodChunk
for(int di = dataStart; !foundBlock && di >= dataMin && di < dataMax; di += dataIncrement) for(int di = dataStart; !foundBlock && di >= dataMin && di < dataMax; di += dataIncrement)
{ {
if(!foundBlock && data[di] != null) if(!foundBlock && blockStorage[di] != null)
{ {
for(int y = topStart; !foundBlock && y >= topMin && y < topMax; y += topIncrement) for(int y = topStart; !foundBlock && y >= topMin && y < topMax; y += topIncrement)
{ {
int ci; int ci;
if(Block.getIdFromBlock(data[di].get(x, y, z).getBlock()) == waterBlockId) if(Block.getIdFromBlock(blockStorage[di].get(x, y, z).getBlock()) == waterBlockId)
// this is a special case since getColor on water generally returns white // this is a special case since getColor on water generally returns white
ci = waterColor; ci = waterColor;
else else
ci = bc.getColor(data[di].get(x, y, z), world, new BlockPos(x,y,z)); ci = bc.getColor(blockStorage[di].get(x, y, z), world, new BlockPos(x,y,z));
if(ci == 0) if(ci == 0)
{ {
@@ -520,9 +538,19 @@ public class LodChunk
return new Color(red, green, blue); 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) private Color generateLodColorHorizontal(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc)
{ {
ExtendedBlockStorage[] data = chunk.getBlockStorageArray(); 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)");
}
ExtendedBlockStorage[] blockStorage = chunk.getBlockStorageArray();
int numbOfBlocks = 0; int numbOfBlocks = 0;
int red = 0; int red = 0;
@@ -563,9 +591,9 @@ public class LodChunk
} }
for (int di = 0; di < data.length; di++) for (int di = 0; di < blockStorage.length; di++)
{ {
if (data[di] != null) if (blockStorage[di] != null)
{ {
for (int y = 0; y < CHUNK_DATA_HEIGHT; y++) for (int y = 0; y < CHUNK_DATA_HEIGHT; y++)
{ {
@@ -607,11 +635,11 @@ public class LodChunk
} }
int ci; int ci;
if(Block.getIdFromBlock(data[di].get(x, y, z).getBlock()) == waterBlockId) if(Block.getIdFromBlock(blockStorage[di].get(x, y, z).getBlock()) == waterBlockId)
// this is a special case since getColor on water generally returns white // this is a special case since getColor on water generally returns white
ci = waterColor; ci = waterColor;
else else
ci = bc.getColor(data[di].get(x, y, z), world, new BlockPos(x,y,z)); ci = bc.getColor(blockStorage[di].get(x, y, z), world, new BlockPos(x,y,z));
if (ci == 0) { if (ci == 0) {
// skip air or invisible blocks // skip air or invisible blocks
@@ -671,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;
}
@@ -681,7 +733,6 @@ public class LodChunk
//========// //========//
/** /**
* Outputs all data in csv format * Outputs all data in csv format
* with the given delimiter. * with the given delimiter.
@@ -727,28 +778,7 @@ public class LodChunk
s += "x: " + x + " z: " + z + "\t"; s += "x: " + x + " z: " + z + "\t";
// s += "top: "; s += "(" + colors[ColorDirection.TOP.value].getRed() + ", " + colors[ColorDirection.TOP.value].getGreen() + ", " + colors[ColorDirection.TOP.value].getBlue() + ")";
// 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() + "), ";
return s; return s;
} }
@@ -1,6 +1,6 @@
package com.backsun.lod.objects; package com.backsun.lod.objects;
import com.backsun.lod.util.LodFileHandler; import com.backsun.lod.handlers.LodDimensionFileHandler;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.world.DimensionType; import net.minecraft.world.DimensionType;
@@ -10,13 +10,13 @@ import net.minecraft.world.DimensionType;
* for a given dimension. * for a given dimension.
* *
* @author James Seibel * @author James Seibel
* @version 01-31-2021 * @version 02-23-2021
*/ */
public class LodDimension public class LodDimension
{ {
public final DimensionType dimension; 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; private volatile int halfWidth;
public LodRegion regions[][]; public LodRegion regions[][];
@@ -25,7 +25,8 @@ public class LodDimension
private int centerX; private int centerX;
private int centerZ; private int centerZ;
private LodFileHandler rfHandler; private LodDimensionFileHandler fileHandler;
public LodDimension(DimensionType newDimension, int newMaxWidth) public LodDimension(DimensionType newDimension, int newMaxWidth)
{ {
@@ -33,7 +34,7 @@ public class LodDimension
width = newMaxWidth; width = newMaxWidth;
// dimension 0 works here since we are just looking for the save handler anyway // dimension 0 works here since we are just looking for the save handler anyway
rfHandler = new LodFileHandler(Minecraft.getMinecraft().getIntegratedServer().getWorld(0).getSaveHandler(), this); fileHandler = new LodDimensionFileHandler(Minecraft.getMinecraft().getIntegratedServer().getWorld(0).getSaveHandler(), this);
regions = new LodRegion[width][width]; regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width]; isRegionDirty = new boolean[width][width];
@@ -50,7 +51,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) public void move(int xOffset, int zOffset)
{ {
// if the x or z offset is equal to or greater than // if the x or z offset is equal to or greater than
@@ -144,22 +148,16 @@ public class LodDimension
} }
public int getCenterX()
{
return centerX;
}
public int getCenterZ()
{
return centerZ;
}
/**
* Gets the region at the given X and Z
* <br>
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodRegion getRegion(int regionX, int regionZ) public LodRegion getRegion(int regionX, int regionZ)
{ {
int xIndex = (regionX - centerX) + halfWidth; int xIndex = (regionX - centerX) + halfWidth;
@@ -201,11 +199,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) public void addLod(LodChunk lod)
{ {
int regionX = (lod.x + centerX) / LodRegion.SIZE; int regionX = lod.x / LodRegion.SIZE;
int regionZ = (lod.z + centerZ) / LodRegion.SIZE; int regionZ = lod.z / LodRegion.SIZE;
// prevent issues if X/Z is negative and less than 16 // prevent issues if X/Z is negative and less than 16
if (lod.x < 0) if (lod.x < 0)
@@ -236,20 +238,20 @@ public class LodDimension
int xIndex = (regionX - centerX) + halfWidth; int xIndex = (regionX - centerX) + halfWidth;
int zIndex = (regionZ - centerZ) + halfWidth; int zIndex = (regionZ - centerZ) + halfWidth;
isRegionDirty[xIndex][zIndex] = true; isRegionDirty[xIndex][zIndex] = true;
fileHandler.saveDirtyRegionsToFileAsync();
rfHandler.saveDirtyRegionsToFile();
} }
/** /**
* Returns null if the LodChunk isn't loaded * Get the LodChunk at the given X and Z coordinates
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/ */
public LodChunk getLodFromCoordinates(int chunkX, int chunkZ) public LodChunk getLodFromCoordinates(int chunkX, int chunkZ)
{ {
// (chunkX + centerX) % width int regionX = chunkX / LodRegion.SIZE;
int regionX = (chunkX + centerX) / LodRegion.SIZE; int regionZ = chunkZ / LodRegion.SIZE;
int regionZ = (chunkZ + centerZ) / LodRegion.SIZE;
// prevent issues if chunkX/Z is negative and less than width // prevent issues if chunkX/Z is negative and less than width
if (chunkX < 0) if (chunkX < 0)
@@ -271,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) public LodRegion getRegionFromFile(int regionX, int regionZ)
{ {
return rfHandler.loadRegionFromFile(regionX, regionZ); return fileHandler.loadRegionFromFile(regionX, regionZ);
} }
@@ -293,6 +297,22 @@ public class LodDimension
public int getCenterX()
{
return centerX;
}
public int getCenterZ()
{
return centerZ;
}
public int getWidth() public int getWidth()
{ {
return width; return width;
@@ -311,6 +331,18 @@ public class LodDimension
for(int j = 0; j < width; j++) for(int j = 0; j < width; j++)
isRegionDirty[i][j] = false; isRegionDirty[i][j] = false;
} }
@Override
public String toString()
{
String s = "";
s += "dim: " + dimension.getName() + "\t";
s += "(" + centerX + "," + centerZ + ")";
return s;
}
} }
@@ -7,7 +7,7 @@ package com.backsun.lod.objects;
* one file in the file system. * one file in the file system.
* *
* @author James Seibel * @author James Seibel
* @version 1-20-2021 * @version 1-22-2021
*/ */
public class LodRegion 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) public void addLod(LodChunk lod)
{ {
// we use ABS since LODs can be negative, but if they are // we use ABS since LODs can be negative, but if they are
@@ -43,6 +48,13 @@ public class LodRegion
chunks[xIndex][zIndex] = lod; chunks[xIndex][zIndex] = lod;
} }
/**
* Get the LodChunk at the given X and Z coordinates
* in this region.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public LodChunk getLod(int chunkX, int chunkZ) public LodChunk getLod(int chunkX, int chunkZ)
{ {
// since we add LOD's with ABS, we get them the same way // 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() public LodChunk[][] getAllLods()
{ {
return chunks; return chunks;
@@ -1,14 +1,13 @@
package com.backsun.lod.objects; package com.backsun.lod.objects;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map;
/** /**
* This stores all LODs for a given world. * This stores all LODs for a given world.
* *
* @author James Seibel * @author James Seibel
* @version 01-31-2021 * @version 02-22-2021
*/ */
public class LodWorld public class LodWorld
{ {
@@ -17,7 +16,7 @@ public class LodWorld
/** /**
* Key = Dimension id (as an int) * Key = Dimension id (as an int)
*/ */
private Dictionary<Integer, LodDimension> lodDimensions; private Map<Integer, LodDimension> lodDimensions;
public LodWorld(String newWorldName) public LodWorld(String newWorldName)
@@ -38,12 +37,27 @@ public class LodWorld
return lodDimensions.get(dimensionId); return lodDimensions.get(dimensionId);
} }
/**
* Resizes the max width in regions that each LodDimension
* should use.
*/
public void resizeDimensionRegionWidth(int newWidth) public void resizeDimensionRegionWidth(int newWidth)
{ {
Enumeration<Integer> keys = lodDimensions.keys(); for(Integer key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newWidth);
}
while(keys.hasMoreElements())
lodDimensions.get(keys.nextElement()).setRegionWidth(newWidth);
@Override
public String toString()
{
String s = "";
s += worldName + "\t - dimensions: ";
for(Integer key : lodDimensions.keySet())
s += lodDimensions.get(key).dimension.getName() + ", ";
return s;
} }
} }
@@ -1,6 +1,6 @@
package com.backsun.lod.renderer; package com.backsun.lod.objects;
import java.nio.ByteBuffer; import net.minecraft.client.renderer.BufferBuilder;
/** /**
* This object is just a replacement for an array * This object is just a replacement for an array
@@ -8,16 +8,16 @@ import java.nio.ByteBuffer;
* and BuildBufferThread. * and BuildBufferThread.
* *
* @author James Seibel * @author James Seibel
* @version 02-13-2021 * @version 02-21-2021
*/ */
public class NearFarBuffer public class NearFarBuffer
{ {
public ByteBuffer nearBuffer; public BufferBuilder nearBuffer;
public ByteBuffer farBuffer; public BufferBuilder farBuffer;
NearFarBuffer(ByteBuffer newNearBuffer, ByteBuffer newFarBuffer) public NearFarBuffer(BufferBuilder newNearBuffer, BufferBuilder newFarBuffer)
{ {
nearBuffer = newNearBuffer; nearBuffer = newNearBuffer;
farBuffer = newFarBuffer; farBuffer = newFarBuffer;
@@ -1,25 +1,18 @@
package com.backsun.lod.proxy; package com.backsun.lod.proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import com.backsun.lod.builders.LodBuilder;
import com.backsun.lod.objects.LodChunk; import com.backsun.lod.objects.LodChunk;
import com.backsun.lod.objects.LodDimension; import com.backsun.lod.objects.LodDimension;
import com.backsun.lod.objects.LodRegion; import com.backsun.lod.objects.LodRegion;
import com.backsun.lod.objects.LodWorld; import com.backsun.lod.objects.LodWorld;
import com.backsun.lod.renderer.LodRenderer; import com.backsun.lod.renderer.LodRenderer;
import com.backsun.lod.util.LodConfig; import com.backsun.lod.util.LodConfig;
import com.backsun.lod.util.LodFileHandler;
import com.backsun.lodCore.util.RenderGlobalHook; import com.backsun.lodCore.util.RenderGlobalHook;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.WorldClient; import net.minecraft.client.multiplayer.WorldClient;
import net.minecraft.world.DimensionType;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import net.minecraftforge.client.event.RenderWorldLastEvent; import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.event.terraingen.PopulateChunkEvent; import net.minecraftforge.event.terraingen.PopulateChunkEvent;
import net.minecraftforge.event.world.ChunkEvent; import net.minecraftforge.event.world.ChunkEvent;
@@ -29,23 +22,21 @@ import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
// Minecraft.getMinecraft().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 * @author James_Seibel
* @version 01-31-2021 * @version 02-23-2021
*/ */
public class ClientProxy extends CommonProxy public class ClientProxy extends CommonProxy
{ {
private LodRenderer renderer; private LodRenderer renderer;
private LodWorld lodWorld; private LodWorld lodWorld;
private ExecutorService lodGenThreadPool = Executors.newFixedThreadPool(1); private LodBuilder lodBuilder;
/** Default size of any LOD regions we use */
private int regionWidth = 5;
public ClientProxy() public ClientProxy()
{ {
lodBuilder = new LodBuilder();
} }
@@ -67,13 +58,17 @@ public class ClientProxy extends CommonProxy
GL11.glDisable(GL11.GL_STENCIL_TEST); 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) public void renderLods(float partialTicks)
{ {
int newWidth = Math.max(4, (Minecraft.getMinecraft().gameSettings.renderDistanceChunks * LodChunk.WIDTH * 2) / LodRegion.SIZE); int newWidth = Math.max(4, (Minecraft.getMinecraft().gameSettings.renderDistanceChunks * LodChunk.WIDTH * 2) / LodRegion.SIZE);
if (lodWorld != null && regionWidth != newWidth) if (lodWorld != null && lodBuilder.regionWidth != newWidth)
{ {
lodWorld.resizeDimensionRegionWidth(newWidth); lodWorld.resizeDimensionRegionWidth(newWidth);
regionWidth = newWidth; lodBuilder.regionWidth = newWidth;
// skip this frame, hopefully the lodWorld // skip this frame, hopefully the lodWorld
// should have everything set up by then // should have everything set up by then
@@ -101,7 +96,6 @@ public class ClientProxy extends CommonProxy
lodDim.move(xOffset, zOffset); lodDim.move(xOffset, zOffset);
} }
// we wait to create the renderer until the first frame // we wait to create the renderer until the first frame
// to make sure that the EntityRenderer has // to make sure that the EntityRenderer has
// been created, that way we can get the fovModifer // been created, that way we can get the fovModifer
@@ -120,9 +114,6 @@ public class ClientProxy extends CommonProxy
//===============// //===============//
// update events // // update events //
//===============// //===============//
@@ -130,7 +121,7 @@ public class ClientProxy extends CommonProxy
@SubscribeEvent @SubscribeEvent
public void chunkLoadEvent(ChunkEvent event) public void chunkLoadEvent(ChunkEvent event)
{ {
generateLodChunk(event.getChunk()); lodWorld = lodBuilder.generateLodChunkAsync(event.getChunk());
} }
/** /**
@@ -146,111 +137,12 @@ public class ClientProxy extends CommonProxy
if(world != null) if(world != null)
{ {
generateLodChunk(world.getChunkFromChunkCoords(event.getChunkX(), event.getChunkZ())); lodWorld = lodBuilder.generateLodChunkAsync(world.getChunkFromChunkCoords(event.getChunkX(), event.getChunkZ()));
} }
} }
} }
/*
*
Use this for generating chunks and maybe determining if they are loaded at all?
Could I create my own chunk generator and multithread it? It wouldn't save to the world, but could I save it for LODs?
chunk = Minecraft.getMinecraft().getIntegratedServer().getWorld(0).getChunkProvider().chunkGenerator.generateChunk(chunk.x, chunk.z);
System.out.println(chunk.x + " " + chunk.z + "\tloaded: " + chunk.isLoaded() + "\tpop: " + chunk.isPopulated() + "\tter pop: " + chunk.isTerrainPopulated());
*/
private void generateLodChunk(Chunk chunk)
{
Minecraft mc = Minecraft.getMinecraft();
// 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;
int dimId = chunk.getWorld().provider.getDimension();
World world = mc.getIntegratedServer().getWorld(dimId);
if (world == null)
return;
Thread thread = new Thread(() ->
{
try
{
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(dimId) == null)
{
DimensionType dim = DimensionType.getById(dimId);
lodDim = new LodDimension(dim, regionWidth);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dimId);
}
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 whether the given chunk
* has any data in it.
*/
private boolean isValidChunk(Chunk chunk)
{
ExtendedBlockStorage[] data = chunk.getBlockStorageArray();
for(ExtendedBlockStorage e : data)
{
if(e != null && !e.isEmpty())
{
return true;
}
}
return false;
}
} }
@@ -1,7 +1,7 @@
package com.backsun.lod.proxy; package com.backsun.lod.proxy;
/** /**
* This is used by the server. * This handles any events sent to the server.
* *
* @author James_Seibel * @author James_Seibel
* @version 08-31-2020 * @version 08-31-2020
@@ -1,333 +0,0 @@
package com.backsun.lod.renderer;
import java.awt.Color;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.Callable;
import com.backsun.lod.util.enums.FogDistance;
import net.minecraft.client.renderer.GLAllocation;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.client.renderer.vertex.VertexFormatElement;
import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.MathHelper;
/**
*
*
* @author James Seibel
* @version 02-13-2021
*/
public class BuildBufferThread implements Callable<NearFarBuffer>
{
public ByteBuffer nearBuffer;
public ByteBuffer farBuffer;
public FogDistance distanceMode;
public AxisAlignedBB[][] lods;
public Color[][] colors;
private int start = 0;
private int end = -1;
private int vertexCount = 0;
private VertexFormat vertexFormat = null;
private int vertexFormatIndex = 0;
private VertexFormatElement vertexFormatElement = null;
BuildBufferThread()
{
vertexCount = 0;
vertexFormat = DefaultVertexFormats.POSITION_COLOR;
vertexFormatIndex = 0;
vertexFormatElement = vertexFormat.getElement(vertexFormatIndex);
}
BuildBufferThread(ByteBuffer newNearByteBuffer, ByteBuffer newFarByteBuffer, AxisAlignedBB[][] newLods, Color[][] newColors, FogDistance newDistanceMode, int threadNumber, int totalThreads)
{
setNewData(newNearByteBuffer, newFarByteBuffer, distanceMode, newLods, newColors, threadNumber, totalThreads);
vertexCount = 0;
vertexFormat = DefaultVertexFormats.POSITION_COLOR;
vertexFormatIndex = 0;
vertexFormatElement = vertexFormat.getElement(vertexFormatIndex);
}
public void setNewData(ByteBuffer newNearByteBuffer, ByteBuffer newFarByteBuffer, FogDistance newDistanceMode, AxisAlignedBB[][] newLods, Color[][] newColors, int threadNumber, int totalThreads)
{
vertexCount = 0;
vertexFormatIndex = 0;
nearBuffer = newNearByteBuffer;
farBuffer = newFarByteBuffer;
distanceMode = newDistanceMode;
lods = newLods;
colors = newColors;
int numbChunksWide = lods.length;
int rowsToRender = numbChunksWide / totalThreads;
start = threadNumber * rowsToRender;
end = (threadNumber + 1) * rowsToRender;
}
@Override
public NearFarBuffer call()
{
int numbChunksWide = lods.length;
ByteBuffer currentBuffer;
AxisAlignedBB bb;
int red;
int green;
int blue;
int alpha;
if (distanceMode == FogDistance.NEAR)
{
currentBuffer = nearBuffer;
}
else // if (distanceMode == FogDistance.FAR)
{
currentBuffer = farBuffer;
}
// x axis
for (int i = start; i < end; 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();
// choose which buffer to add these LODs too
if (distanceMode == FogDistance.NEAR_AND_FAR)
{
if (RenderUtil.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
return new NearFarBuffer(nearBuffer, farBuffer);
}
private void addPosAndColor(ByteBuffer buffer, double x, double y, double z, int red, int green, int blue, int alpha)
{
addPos(buffer, x, y, z);
addColor(buffer, red, green, blue, alpha);
endVertex();
}
private void addPos(ByteBuffer byteBuffer, double x, double y, double z)
{
int i = this.vertexCount * this.vertexFormat.getNextOffset() + this.vertexFormat.getOffset(this.vertexFormatIndex);
switch (this.vertexFormatElement.getType())
{
case FLOAT: // This is the one currently used
byteBuffer.putFloat(i, (float)(x));
byteBuffer.putFloat(i + 4, (float)(y));
byteBuffer.putFloat(i + 8, (float)(z));
break;
case UINT:
case INT:
byteBuffer.putInt(i, Float.floatToRawIntBits((float)(x)));
byteBuffer.putInt(i + 4, Float.floatToRawIntBits((float)(y)));
byteBuffer.putInt(i + 8, Float.floatToRawIntBits((float)(z)));
break;
case USHORT:
case SHORT:
byteBuffer.putShort(i, (short)((int)(x)));
byteBuffer.putShort(i + 2, (short)((int)(y)));
byteBuffer.putShort(i + 4, (short)((int)(z)));
break;
case UBYTE:
case BYTE:
byteBuffer.put(i, (byte)((int)(x)));
byteBuffer.put(i + 1, (byte)((int)(y)));
byteBuffer.put(i + 2, (byte)((int)(z)));
}
nextVertexFormatIndex();
}
private void addColor(ByteBuffer byteBuffer, int red, int green, int blue, int alpha)
{
int i = this.vertexCount * this.vertexFormat.getNextOffset() + this.vertexFormat.getOffset(this.vertexFormatIndex);
switch (this.vertexFormatElement.getType())
{
case FLOAT:
byteBuffer.putFloat(i, red / 255.0F);
byteBuffer.putFloat(i + 4, green / 255.0F);
byteBuffer.putFloat(i + 8, blue / 255.0F);
byteBuffer.putFloat(i + 12, alpha / 255.0F);
break;
case UINT:
case INT:
byteBuffer.putFloat(i, red);
byteBuffer.putFloat(i + 4, green);
byteBuffer.putFloat(i + 8, blue);
byteBuffer.putFloat(i + 12, alpha);
break;
case USHORT:
case SHORT:
byteBuffer.putShort(i, (short)red);
byteBuffer.putShort(i + 2, (short)green);
byteBuffer.putShort(i + 4, (short)blue);
byteBuffer.putShort(i + 6, (short)alpha);
break;
case UBYTE:
case BYTE:
if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN)
{
// this is the one used currently
byteBuffer.put(i, (byte)red);
byteBuffer.put(i + 1, (byte)green);
byteBuffer.put(i + 2, (byte)blue);
byteBuffer.put(i + 3, (byte)alpha);
}
else
{
byteBuffer.put(i, (byte)alpha);
byteBuffer.put(i + 1, (byte)blue);
byteBuffer.put(i + 2, (byte)green);
byteBuffer.put(i + 3, (byte)red);
}
}
nextVertexFormatIndex();
}
private void nextVertexFormatIndex()
{
++this.vertexFormatIndex;
this.vertexFormatIndex %= this.vertexFormat.getElementCount();
this.vertexFormatElement = this.vertexFormat.getElement(this.vertexFormatIndex);
if (this.vertexFormatElement.getUsage() == VertexFormatElement.EnumUsage.PADDING)
{
this.nextVertexFormatIndex();
}
}
private void endVertex()
{
++this.vertexCount;
growBuffer(this.vertexFormat.getNextOffset());
}
private void growBuffer(int p_181670_1_)
{
//if (MathHelper.roundUp(p_181670_1_, 4) / 4 > this.rawIntBuffer.remaining() || this.vertexCount * this.vertexFormat.getNextOffset() + p_181670_1_ > this.byteBuffer.capacity())
if (this.vertexCount * this.vertexFormat.getNextOffset() + p_181670_1_ > nearBuffer.capacity())
{
int i = nearBuffer.capacity();
int j = i + MathHelper.roundUp(p_181670_1_, 2097152);
// int k = this.rawIntBuffer.position();
ByteBuffer directBytebuffer = GLAllocation.createDirectByteBuffer(j);
nearBuffer.position(0);
directBytebuffer.put(nearBuffer);
directBytebuffer.rewind();
nearBuffer = directBytebuffer;
// this.rawFloatBuffer = buffer.asFloatBuffer().asReadOnlyBuffer();
// this.rawIntBuffer = buffer.asIntBuffer();
// this.rawIntBuffer.position(k);
// this.rawShortBuffer = buffer.asShortBuffer();
// this.rawShortBuffer.position(k << 1);
}
}
}
@@ -15,30 +15,36 @@ import java.util.concurrent.Future;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.Project; import org.lwjgl.util.glu.Project;
import com.backsun.lod.builders.BuildBufferThread;
import com.backsun.lod.handlers.ReflectionHandler;
import com.backsun.lod.objects.LodChunk; import com.backsun.lod.objects.LodChunk;
import com.backsun.lod.objects.LodDimension; import com.backsun.lod.objects.LodDimension;
import com.backsun.lod.objects.NearFarBuffer;
import com.backsun.lod.util.LodConfig; 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.ColorDirection;
import com.backsun.lod.util.enums.FogDistance; import com.backsun.lod.util.enums.FogDistance;
import com.backsun.lod.util.enums.FogQuality; import com.backsun.lod.util.enums.FogQuality;
import com.backsun.lod.util.enums.LodLocation; import com.backsun.lod.util.enums.LodCorner;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.EntityPlayerSP;
import net.minecraft.client.renderer.BufferBuilder; import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats; import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.MathHelper;
/** /**
* @author James Seibel * @author James Seibel
* @version 2-13-2021 * @version 2-24-2021
*/ */
public class LodRenderer 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 /** If true the LODs colors will be replaced with
* a checkerboard, this can be used for debugging. */ * a checkerboard, this can be used for debugging. */
public boolean debugging = false; public boolean debugging = false;
@@ -52,31 +58,35 @@ public class LodRenderer
private Tessellator tessellator; private Tessellator tessellator;
private BufferBuilder bufferBuilder; 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; private ReflectionHandler reflectionHandler;
public LodDimension lodDimension = null; public LodDimension lodDimension = null;
/** Total number of CPU cores available to the Java VM */
private int maxNumbThreads = Runtime.getRuntime().availableProcessors(); private int maxNumbThreads = Runtime.getRuntime().availableProcessors();
/** How many threads should be used for building the render buffer. */ /** How many threads should be used for building render buffers */
private int numbBufferThreads = maxNumbThreads; private int numbBufferThreads = maxNumbThreads;
/** This stores all the BuildBufferThread objects for each CPU core */
private ArrayList<BuildBufferThread> bufferThreads = new ArrayList<BuildBufferThread>(); private ArrayList<BuildBufferThread> bufferThreads = new ArrayList<BuildBufferThread>();
private volatile ByteBuffer[] nearBuffers = new ByteBuffer[maxNumbThreads]; /** The buffers that are used to draw LODs using near fog */
private volatile ByteBuffer[] farBuffers = new ByteBuffer[maxNumbThreads]; 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); private ExecutorService bufferThreadPool = Executors.newFixedThreadPool(maxNumbThreads);
/* /** This holds the thread used to generate new LODs off the main thread. */
* this is the maximum number of bytes a buffer private ExecutorService genThread = Executors.newSingleThreadExecutor();
* would ever have to hold at once (this prevents the buffer
* from having to resize and thus save performance)
*/
private int bufferMaxCapacity = 0;
/** This is used to determine if the LODs should be regenerated */ /** This is used to determine if the LODs should be regenerated */
private int previousChunkRenderDistance = 0; private int previousChunkRenderDistance = 0;
@@ -87,8 +97,17 @@ public class LodRenderer
/** This is used to determine if the LODs should be regenerated */ /** This is used to determine if the LODs should be regenerated */
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR; 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; 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;
@@ -98,23 +117,22 @@ public class LodRenderer
mc = Minecraft.getMinecraft(); mc = Minecraft.getMinecraft();
// for some reason "Tessellator.getInstance()" won't work here, we have to create a new one // 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(); bufferBuilder = tessellator.getBuffer();
reflectionHandler = new ReflectionHandler(); reflectionHandler = new ReflectionHandler();
} }
/**
* 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) public void drawLODs(LodDimension newDimension, float partialTicks)
{ {
if (reflectionHandler.fovMethod == null)
{
// don't continue if we can't get the
// user's FOV
return;
}
if (reflectionHandler.fovMethod == null) if (reflectionHandler.fovMethod == null)
{ {
// we aren't able to get the user's // we aren't able to get the user's
@@ -122,29 +140,7 @@ public class LodRenderer
return; return;
} }
// should the LODs be regenerated? if (lodDimension == null && newDimension == null)
if ((int)Minecraft.getMinecraft().player.posX / LodChunk.WIDTH != prevChunkX ||
(int)Minecraft.getMinecraft().player.posZ / LodChunk.WIDTH != prevChunkZ ||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks ||
prevFogDistance != LodConfig.fogDistance ||
lodDimension != newDimension)
{
regen = true;
prevChunkX = (int)Minecraft.getMinecraft().player.posX / LodChunk.WIDTH;
prevChunkZ = (int)Minecraft.getMinecraft().player.posZ / LodChunk.WIDTH;
prevFogDistance = LodConfig.fogDistance;
}
else
{
// nope, the player hasn't moved, the
// render distance hasn't changed, and
// the dimension is the same
regen = false;
}
lodDimension = newDimension;
if (lodDimension == null)
{ {
// if there aren't any loaded LodChunks // if there aren't any loaded LodChunks
// don't try drawing anything // don't try drawing anything
@@ -155,13 +151,43 @@ public class LodRenderer
//===============//
// initial setup //
//===============//
// used for debugging and viewing how long different processes take // used for debugging and viewing how long different processes take
mc.mcProfiler.endSection(); mc.mcProfiler.endSection();
mc.mcProfiler.startSection("LOD"); mc.mcProfiler.startSection("LOD");
mc.mcProfiler.startSection("LOD setup"); mc.mcProfiler.startSection("LOD setup");
@SuppressWarnings("unused")
long startTime = System.nanoTime(); EntityPlayerSP player = mc.player;
// should LODs be regenerated?
if ((int)player.posX / LodChunk.WIDTH != prevChunkX ||
(int)player.posZ / LodChunk.WIDTH != prevChunkZ ||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks ||
prevFogDistance != LodConfig.fogDistance ||
lodDimension != newDimension)
{
// yes
regen = true;
prevChunkX = (int)player.posX / LodChunk.WIDTH;
prevChunkZ = (int)player.posZ / LodChunk.WIDTH;
prevFogDistance = LodConfig.fogDistance;
}
else
{
// nope, the player hasn't moved, the
// render distance hasn't changed, and
// the dimension is the same
regen = false;
}
if (newDimension != null)
lodDimension = newDimension;
if (LodConfig.drawCheckerBoard) if (LodConfig.drawCheckerBoard)
{ {
if (debugging != LodConfig.drawCheckerBoard) if (debugging != LodConfig.drawCheckerBoard)
@@ -176,28 +202,12 @@ 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 // get the camera location
Entity player = mc.player;
double cameraX = player.lastTickPosX + (player.posX - player.lastTickPosX) * partialTicks; double cameraX = player.lastTickPosX + (player.posX - player.lastTickPosX) * partialTicks;
double cameraY = player.lastTickPosY + (player.posY - player.lastTickPosY) * partialTicks; double cameraY = player.lastTickPosY + (player.posY - player.lastTickPosY) * partialTicks;
double cameraZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialTicks; double cameraZ = player.lastTickPosZ + (player.posZ - player.lastTickPosZ) * partialTicks;
// determine how far the game's render distance is currently set // determine how far the game's render distance is currently set
int renderDistWidth = mc.gameSettings.renderDistanceChunks; int renderDistWidth = mc.gameSettings.renderDistanceChunks;
farPlaneDistance = renderDistWidth * LodChunk.WIDTH; farPlaneDistance = renderDistWidth * LodChunk.WIDTH;
@@ -206,20 +216,6 @@ public class LodRenderer
int totalLength = (int) farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2; int totalLength = (int) farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2;
int numbChunksWide = (totalLength / LodChunk.WIDTH); 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];
@@ -229,89 +225,39 @@ public class LodRenderer
// create the LODs // // create the LODs //
//=================// //=================//
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)
{ {
mc.mcProfiler.endStartSection("LOD generation"); mc.mcProfiler.endStartSection("LOD generation");
regenerating = true;
// x axis // this will only be called once, unless the numbBufferThreads changes
for (int i = 0; i < numbChunksWide; i++) 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.posX, player.posZ, 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)
{ {
// z axis swapBuffers();
for (int j = 0; j < numbChunksWide; j++) switchBuffers = false;
{
// 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); // new LodChunk(); //
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;
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);
}
}
}
@@ -320,18 +266,17 @@ public class LodRenderer
//===========================// //===========================//
// set the required open GL settings // set the required open GL settings
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glLineWidth(2.0f); GL11.glLineWidth(2.0f);
GL11.glDisable(GL11.GL_TEXTURE_2D); GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_CULL_FACE); GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); // move the LODs so they are in the correct place relative to the camera
GlStateManager.translate(-cameraX, -cameraY, -cameraZ); GlStateManager.translate(-cameraX, -cameraY, -cameraZ);
setProjectionMatrix(partialTicks); setProjectionMatrix(partialTicks);
setupLighting(partialTicks); setupLighting(partialTicks);
setupBufferThreads(lodArray);
@@ -342,30 +287,31 @@ public class LodRenderer
// rendering // // rendering //
//===========// //===========//
mc.mcProfiler.endStartSection("LOD build buffer");
if (regen)
generateLodBuffers(lodArray, colorArray, LodConfig.fogDistance);
switch(LodConfig.fogDistance) switch(LodConfig.fogDistance)
{ {
case NEAR_AND_FAR: case NEAR_AND_FAR:
mc.mcProfiler.endStartSection("LOD draw setup"); // when drawing NEAR_AND_FAR fog we need 2 draw
setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality()); // calls since fog can only go in one direction at a time
sendLodsToGpuAndDraw(nearBuffers);
mc.mcProfiler.endStartSection("LOD draw setup"); mc.mcProfiler.endStartSection("LOD draw");
setupFog(FogDistance.FAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(farBuffers);
break;
case NEAR:
mc.mcProfiler.endStartSection("LOD draw setup");
setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality()); setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(nearBuffers); sendLodsToGpuAndDraw(drawableNearBuffers);
break;
case FAR: mc.mcProfiler.endStartSection("LOD draw");
mc.mcProfiler.endStartSection("LOD draw setup");
setupFog(FogDistance.FAR, reflectionHandler.getFogQuality()); setupFog(FogDistance.FAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(farBuffers); sendLodsToGpuAndDraw(drawableFarBuffers);
break;
case NEAR:
mc.mcProfiler.endStartSection("LOD draw");
setupFog(FogDistance.NEAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(drawableNearBuffers);
break;
case FAR:
mc.mcProfiler.endStartSection("LOD draw");
setupFog(FogDistance.FAR, reflectionHandler.getFogQuality());
sendLodsToGpuAndDraw(drawableFarBuffers);
break; break;
} }
@@ -379,14 +325,13 @@ public class LodRenderer
mc.mcProfiler.endStartSection("LOD cleanup"); mc.mcProfiler.endStartSection("LOD cleanup");
// this must be done otherwise other parts of the screen may be drawn with a fog effect // this must be done otherwise other parts of the screen may be drawn with a fog effect
// IE the GUI // IE the GUI
GlStateManager.disableFog(); GlStateManager.disableFog();
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(GL11.GL_LIGHT1); GL11.glDisable(LOD_GL_LIGHT_NUMBER);
GL11.glDisable(GL11.GL_COLOR_MATERIAL); GL11.glDisable(GL11.GL_COLOR_MATERIAL);
// change the perspective matrix back to prevent incompatibilities // change the perspective matrix back to prevent incompatibilities
@@ -398,114 +343,36 @@ public class LodRenderer
previousChunkRenderDistance = mc.gameSettings.renderDistanceChunks; 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 // end of profiler tracking
mc.mcProfiler.endSection(); mc.mcProfiler.endSection();
} }
/** /**
* draw an array of cubes (or squares) with the given colors. * This is where the actual drawing happens.
* @param lods bounding boxes to draw *
* @param colors color of each box to draw * @param buffers the buffers sent to the GPU to draw
*/ */
private void generateLodBuffers(AxisAlignedBB[][] lods, Color[][] colors, FogDistance fogDistance) private void sendLodsToGpuAndDraw(BufferBuilder[] buffers)
{
List<Future<NearFarBuffer>> 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] = ByteBuffer.allocateDirect(bufferMaxCapacity);
nearBuffers[i].order(ByteOrder.LITTLE_ENDIAN);
farBuffers[i] = 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].clear();
nearBuffers[i].put(clearBytes);
nearBuffers[i].clear();
farBuffers[i].clear();
farBuffers[i].put(clearBytes);
farBuffers[i].clear();
}
int pos = bufferBuilder.getByteBuffer().position();
nearBuffers[i].position(pos);
farBuffers[i].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;
}
catch(CancellationException | ExecutionException| InterruptedException e)
{
// this should never happen, but just in case
e.printStackTrace();
}
}
}
private void sendLodsToGpuAndDraw(ByteBuffer[] buffers)
{ {
for(int i = 0; i < numbBufferThreads; i++) for(int i = 0; i < numbBufferThreads; i++)
{
if (shouldDrawBuffer[i])
{ {
int pos = bufferBuilder.getByteBuffer().position(); int pos = bufferBuilder.getByteBuffer().position();
buffers[i].position(pos); buffers[i].getByteBuffer().position(pos);
bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR); bufferBuilder.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_COLOR);
bufferBuilder.getByteBuffer().clear(); bufferBuilder.getByteBuffer().clear();
bufferBuilder.putBulkData(buffers[i]); // replace the data in bufferBuilder with the data from the given buffer
bufferBuilder.putBulkData(buffers[i].getByteBuffer());
mc.mcProfiler.endStartSection("LOD draw");
tessellator.draw(); tessellator.draw();
mc.mcProfiler.endStartSection("LOD draw setup");
bufferBuilder.getByteBuffer().clear(); // this is required otherwise nothing is drawn bufferBuilder.getByteBuffer().clear(); // this is required otherwise nothing is drawn
} }
} }
}
@@ -527,7 +394,7 @@ public class LodRenderer
if(fogDistance == FogDistance.NEAR_AND_FAR) 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 // the multipliers are percentages
@@ -577,7 +444,6 @@ public class LodRenderer
/** /**
* create a new projection matrix and send it over to the GPU * create a new projection matrix and send it over to the GPU
* @param partialTicks how many ticks into the frame we are * @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 setProjectionMatrix(float partialTicks) private void setProjectionMatrix(float partialTicks)
{ {
@@ -612,33 +478,59 @@ public class LodRenderer
ByteBuffer temp = ByteBuffer.allocateDirect(16); ByteBuffer temp = ByteBuffer.allocateDirect(16);
temp.order(ByteOrder.nativeOrder()); temp.order(ByteOrder.nativeOrder());
GL11.glLight(GL11.GL_LIGHT1, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip()); GL11.glLight(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
GL11.glEnable(GL11.GL_LIGHT1); // Enable the above lighting GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
GlStateManager.enableLighting(); GlStateManager.enableLighting();
} }
/**
private void setupBufferThreads(AxisAlignedBB[][] lods) * create the BuildBufferThreads
*/
private void setupBufferThreads()
{ {
if (numbBufferThreads != bufferThreads.size())
{
bufferMaxCapacity = (lods.length * lods.length * (6 * 4 * ((3 * 4) + (4 * 4)))) / numbBufferThreads;
clearBytes = new byte[bufferMaxCapacity];
bufferThreads.clear(); bufferThreads.clear();
for(int i = 0; i < numbBufferThreads; i++) for(int i = 0; i < numbBufferThreads; i++)
bufferThreads.add(new BuildBufferThread()); bufferThreads.add(new BuildBufferThread());
regen = true; }
for(int i = 0; i < maxNumbThreads; i++) /**
* Create all buffers that will be used.
*/
private void setupBuffers(int numbChunksWide)
{ {
nearBuffers[i] = ByteBuffer.allocateDirect(bufferMaxCapacity); drawableNearBuffers = new BufferBuilder[numbBufferThreads];
nearBuffers[i].order(ByteOrder.LITTLE_ENDIAN); drawableFarBuffers = new BufferBuilder[numbBufferThreads];
farBuffers[i] = ByteBuffer.allocateDirect(bufferMaxCapacity); buildableNearBuffers = new BufferBuilder[numbBufferThreads];
farBuffers[i].order(ByteOrder.LITTLE_ENDIAN); 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++)
{
// 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);
buildableNearBuffers[i] = new BufferBuilder(bufferMaxCapacity);
buildableFarBuffers[i] = new BufferBuilder(bufferMaxCapacity);
} }
} }
@@ -646,22 +538,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) if (heightPoints[LodCorner.NE.value] != -1)
return heightPoints[LodLocation.NE.value]; return heightPoints[LodCorner.NE.value];
if (heightPoints[LodLocation.NW.value] != -1) if (heightPoints[LodCorner.NW.value] != -1)
return heightPoints[LodLocation.NW.value]; return heightPoints[LodCorner.NW.value];
if (heightPoints[LodLocation.SE.value] != -1) if (heightPoints[LodCorner.SE.value] != -1)
return heightPoints[LodLocation.NE.value]; return heightPoints[LodCorner.NE.value];
return heightPoints[LodLocation.NE.value]; return heightPoints[LodCorner.NE.value];
} }
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This thread will write to the drawableNearBuffers and drawableFarBuffers.
* <br>
* 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.fogDistance);
regenerating = false;
switchBuffers = true;
});
return t;
}
/**
* draw an array of boxes with the given colors.
* <br><br>
* 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<Future<NearFarBuffer>> bufferFutures = new ArrayList<>();
ArrayList<BuildBufferThread> 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;
}
} }
@@ -1,45 +0,0 @@
package com.backsun.lod.renderer;
import net.minecraft.client.Minecraft;
/**
* This holds miscellaneous helper code
* to be used in the rendering process.
*
* @author James Seibel
* @version 2-13-2021
*/
public class RenderUtil
{
/**
* Returns if the given coordinate is in the loaded area of the world.
* @param centerCoordinate the center of the loaded world
*/
public static boolean isCoordinateInLoadedArea(int i, int j, int centerCoordinate)
{
Minecraft mc = Minecraft.getMinecraft();
return (i >= centerCoordinate - mc.gameSettings.renderDistanceChunks
&& i <= centerCoordinate + mc.gameSettings.renderDistanceChunks)
&&
(j >= centerCoordinate - mc.gameSettings.renderDistanceChunks
&& j <= centerCoordinate + mc.gameSettings.renderDistanceChunks);
}
/**
* 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).
*/
public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
{
int halfRadius = lodRadius / 2;
return (i >= lodRadius - halfRadius
&& i <= lodRadius + halfRadius)
&&
(j >= lodRadius - halfRadius
&& j <= lodRadius + halfRadius);
}
}
@@ -9,6 +9,7 @@ import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
/** /**
* This is linked to Forge's mod config GUI.
* *
* @author James Seibel * @author James Seibel
* @version 02-14-2021 * @version 02-14-2021
@@ -1,6 +1,7 @@
package com.backsun.lod.util; package com.backsun.lod.util;
/** /**
* This holds meta information about the mod.
* *
* @author James Seibel * @author James Seibel
* @version 04-16-2020 * @version 04-16-2020
@@ -1,10 +1,10 @@
package com.backsun.lod.util.enums; package com.backsun.lod.util.enums;
/** /**
* TOP, N, S, E, W, BOTTOM
*
* @author James Seibel * @author James Seibel
* @version 10-17-2020 * @version 10-17-2020
*
* TOP, N, S, E, W, BOTTOM
*/ */
public enum ColorDirection public enum ColorDirection
{ {
@@ -1,32 +0,0 @@
package com.backsun.lod.util.enums;
/**
* @author James Seibel
* @version 1-23-2021
*/
public enum DrawMode
{
/** Draw the LOD objects in groups.
* <br>
* <br>
* Fancy fog: render the center and outside LOD
* objects in 2 different groups.
* <br>
* Fast fog: render all LOD objects at one time.
*/
BATCH(0),
/** Draw each LOD objects separately.
* <br>
* <br>
* Not suggested normally since draw calls are GPU expensive.
*/
INDIVIDUAL(5);
public final int value;
private DrawMode(int newValue)
{
value = newValue;
}
}
@@ -8,9 +8,9 @@ package com.backsun.lod.util.enums;
*/ */
public enum FogDistance public enum FogDistance
{ {
/** valid for both fast and fancy fog qualities. */ /** good for fast or fancy fog qualities. */
NEAR, NEAR,
/** valid for both fast and fancy fog qualities. */ /** good for fast or fancy fog qualities. */
FAR, FAR,
/** only looks good if the fog quality is set to Fancy. */ /** only looks good if the fog quality is set to Fancy. */
NEAR_AND_FAR; NEAR_AND_FAR;
@@ -1,13 +1,12 @@
package com.backsun.lod.util.enums; package com.backsun.lod.util.enums;
/** /**
* NE, SE, SW, NW
* *
* @author James Seibel * @author James Seibel
* @version 1-20-2020 * @version 1-20-2020
*
* NE, SE, SW, NW
*/ */
public enum LodLocation public enum LodCorner
{ {
// used for position // used for position
@@ -22,7 +21,7 @@ public enum LodLocation
public final int value; public final int value;
private LodLocation(int newValue) private LodCorner(int newValue)
{ {
value = newValue; value = newValue;
} }
+1 -1
View File
@@ -3,7 +3,7 @@
"modid": "lod", "modid": "lod",
"name": "Level Of Details", "name": "Level Of Details",
"description": "Generates and renders simplified chunks beyond the normal view distance, at a low performance cost.", "description": "Generates and renders simplified chunks beyond the normal view distance, at a low performance cost.",
"version": "0.1", "version": "1.0",
"mcversion": "1.12.2", "mcversion": "1.12.2",
"url": "", "url": "",
"updateUrl": "", "updateUrl": "",