Compare commits
80 Commits
1.16.4-a1
...
1.16.4-a1.2
| Author | SHA1 | Date | |
|---|---|---|---|
| bf6db89a4b | |||
| 38c644739a | |||
| a0fe977976 | |||
| 739d6d5856 | |||
| 84125735a1 | |||
| 884f6811a1 | |||
| 402abb0963 | |||
| 2f7f489e14 | |||
| 4de5c287fc | |||
| ccb58024a1 | |||
| 0381d56511 | |||
| 7bbd3fd815 | |||
| 4e243252e9 | |||
| b488d21a14 | |||
| 3e8dbf7ac7 | |||
| 695b73f9d3 | |||
| 6e37bce38a | |||
| 06232f65b2 | |||
| bebe4b7436 | |||
| 7d9f04d54c | |||
| 70336acc75 | |||
| e1ef08a783 | |||
| 648a70097a | |||
| 280892f5f4 | |||
| 3c57b124d5 | |||
| 04d6bae479 | |||
| 98096a5db7 | |||
| 7e4f3a3185 | |||
| 9fe6be10f6 | |||
| 76b356d38e | |||
| 811e24ee5e | |||
| 5002db15d6 | |||
| a3357c1193 | |||
| af36224a57 | |||
| 630ff361d3 | |||
| 34c6f29a18 | |||
| 909718e491 | |||
| 8a3d199247 | |||
| fa13c981b7 | |||
| 815b00c3ca | |||
| ae9144a6c4 | |||
| 359fde3b6b | |||
| 92fa904cc6 | |||
| 2583ae34d4 | |||
| e36b3394f4 | |||
| 7af38df92c | |||
| 51add24110 | |||
| 9e5aac3bf7 | |||
| 5738a5b7cd | |||
| e1216966a3 | |||
| 222c0de7f1 | |||
| b4f1fb6d28 | |||
| a32082ad20 | |||
| eeb5fb6c3c | |||
| cb50f24c86 | |||
| 5ca5764c0e | |||
| fccd1db045 | |||
| 6c515350bc | |||
| 50aee9dfb2 | |||
| 0649504770 | |||
| 29068f9550 | |||
| 18c08ccd88 | |||
| b71d6a5e3f | |||
| 8f619f3fa1 | |||
| eab16ff20a | |||
| d913ed9621 | |||
| a649cf179f | |||
| cf5de39250 | |||
| bab3cd9656 | |||
| cafd4f0c47 | |||
| e20833225f | |||
| 14a06c220b | |||
| e5a5ba327e | |||
| fedc8f7b66 | |||
| 7a3497d44c | |||
| 873034f7e4 | |||
| a151054b48 | |||
| 14c69971f6 | |||
| edc3858699 | |||
| bdaf33b80b |
@@ -2,6 +2,10 @@ This mod adds a Level Of Detail (LOD) system to Minecraft.
|
||||
This implementation renders simplified chunks outside the normal render distance
|
||||
allowing for an increased view distance without harming performance.
|
||||
|
||||
Or in other words: this mod let's you see farther without turning your game into a slide show.
|
||||
If you want to see a quick demo, check out the video I made here:
|
||||
https://youtu.be/v61iOYZQWCs
|
||||
|
||||
|
||||
Forge version: 1.16.4-35.1.4
|
||||
|
||||
|
||||
Binary file not shown.
+4
-2
@@ -3,7 +3,9 @@ buildscript {
|
||||
maven { url = 'https://files.minecraftforge.net/maven' }
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url='https://dist.creeper.host/Sponge/maven' }
|
||||
maven { url = 'https://repo.spongepowered.org/maven/' }
|
||||
// potential replacement in case of problems:
|
||||
// https://dist.creeper.host/Sponge/maven
|
||||
}
|
||||
dependencies {
|
||||
classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true
|
||||
@@ -16,7 +18,7 @@ apply plugin: 'org.spongepowered.mixin'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
version = 'a1'
|
||||
version = 'a1.2'
|
||||
group = 'com.backsun.lod'
|
||||
archivesBaseName = 'lod_1.16.4'
|
||||
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
package com.backsun.lod.builders;
|
||||
import java.awt.Color;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.backsun.lod.objects.NearFarBuffer;
|
||||
import com.backsun.lod.renderer.LodRenderer;
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
|
||||
/**
|
||||
* This object is used to create NearFarBuffer objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-27-2021
|
||||
*/
|
||||
public class LodBufferBuilder
|
||||
{
|
||||
public BufferBuilder nearBuffer;
|
||||
public BufferBuilder farBuffer;
|
||||
public FogDistance distanceMode;
|
||||
public AxisAlignedBB[][] lods;
|
||||
public Color[][] colors;
|
||||
|
||||
|
||||
|
||||
public LodBufferBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public NearFarBuffer createBuffers(
|
||||
BufferBuilder newNearBufferBuilder, BufferBuilder newFarBufferBuilder,
|
||||
FogDistance newDistanceMode,
|
||||
AxisAlignedBB[][] newLods, Color[][] newColors)
|
||||
{
|
||||
nearBuffer = newNearBufferBuilder;
|
||||
farBuffer = newFarBufferBuilder;
|
||||
distanceMode = newDistanceMode;
|
||||
lods = newLods;
|
||||
colors = newColors;
|
||||
|
||||
|
||||
nearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
|
||||
farBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
|
||||
|
||||
int numbChunksWide = lods.length;
|
||||
|
||||
BufferBuilder currentBuffer;
|
||||
AxisAlignedBB bb;
|
||||
int red;
|
||||
int green;
|
||||
int blue;
|
||||
int alpha;
|
||||
|
||||
|
||||
// x axis
|
||||
for (int i = 0; i < numbChunksWide; 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 (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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.backsun.lod.builders;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.backsun.lod.handlers.LodDimensionFileHandler;
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.LodWorld;
|
||||
import com.backsun.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if (lodWorld != null)
|
||||
// is this chunk from the same world as the lodWorld?
|
||||
if (!lodWorld.worldName.equals(LodDimensionFileHandler.getCurrentWorldID()))
|
||||
// we are not in the same world anymore,
|
||||
// remove the old world so it can be recreated later
|
||||
lodWorld = null;
|
||||
|
||||
// don't try to create an LOD object
|
||||
// if for some reason we aren't
|
||||
// given a valid chunk object
|
||||
// (Minecraft often gives back empty
|
||||
// or null chunks in this method)
|
||||
if (chunk == null || !isValidChunk(chunk))
|
||||
return lodWorld;
|
||||
|
||||
|
||||
DimensionType dim = chunk.getWorld().getDimensionType();
|
||||
ServerWorld world = LodUtils.getServerWorldFromDimension(dim);
|
||||
|
||||
|
||||
if (world == null)
|
||||
return lodWorld;
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
LodChunk lod = new LodChunk(chunk, world);
|
||||
LodDimension lodDim;
|
||||
|
||||
if (lodWorld == null)
|
||||
{
|
||||
lodWorld = new LodWorld(LodDimensionFileHandler.getCurrentWorldID());
|
||||
}
|
||||
|
||||
|
||||
if (lodWorld.getLodDimension(dim) == null)
|
||||
{
|
||||
lodDim = new LodDimension(dim, regionWidth);
|
||||
lodWorld.addLodDimension(lodDim);
|
||||
}
|
||||
else
|
||||
{
|
||||
lodDim = lodWorld.getLodDimension(dim);
|
||||
}
|
||||
|
||||
lodDim.addLod(lod);
|
||||
}
|
||||
catch(IllegalArgumentException | NullPointerException e)
|
||||
{
|
||||
// if the world changes while LODs are being generated
|
||||
// they will throw errors as they try to access things that no longer
|
||||
// exist.
|
||||
}
|
||||
});
|
||||
lodGenThreadPool.execute(thread);
|
||||
|
||||
return lodWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given chunk
|
||||
* has any data in it.
|
||||
*/
|
||||
public boolean isValidChunk(Chunk chunk)
|
||||
{
|
||||
ChunkSection[] blockStorage = chunk.getSections();
|
||||
|
||||
for(ChunkSection section : blockStorage)
|
||||
{
|
||||
if(section != null && !section.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
package com.backsun.lod.handlers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.LodRegion;
|
||||
import com.backsun.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This object handles creating LodRegions
|
||||
* from files and saving LodRegion objects
|
||||
* to file.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 01-30-2021
|
||||
*/
|
||||
public class LodDimensionFileHandler
|
||||
{
|
||||
private LodDimension loadedDimension = null;
|
||||
public long regionLastWriteTime[][];
|
||||
|
||||
private File dimensionDataSaveFolder;
|
||||
|
||||
private final String FILE_NAME_PREFIX = "lod";
|
||||
private final String FILE_EXTENSION = ".txt";
|
||||
|
||||
private ExecutorService fileWritingThreadPool = Executors.newFixedThreadPool(1);
|
||||
|
||||
|
||||
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension)
|
||||
{
|
||||
dimensionDataSaveFolder = newSaveFolder;
|
||||
|
||||
loadedDimension = newLoadedDimension;
|
||||
// these two variable are used in sync with the LodDimension
|
||||
regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
|
||||
for(int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
for(int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
regionLastWriteTime[i][j] = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// read from file //
|
||||
//================//
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the LodRegion at the given coordinates.
|
||||
* (null if the file doesn't exist)
|
||||
*/
|
||||
public LodRegion loadRegionFromFile(int regionX, int regionZ)
|
||||
{
|
||||
// we don't currently support reading or writing
|
||||
// files when connected to a server
|
||||
if (!Minecraft.getInstance().isIntegratedServerRunning())
|
||||
return null;
|
||||
|
||||
if (!readyToReadAndWrite())
|
||||
return null;
|
||||
|
||||
String fileName = getFileNameForRegion(regionX, regionZ);
|
||||
|
||||
File f = new File(fileName);
|
||||
|
||||
if (!f.exists())
|
||||
{
|
||||
// there wasn't a file, don't
|
||||
// return anything
|
||||
return null;
|
||||
}
|
||||
|
||||
LodRegion region = new LodRegion(regionX, regionZ);
|
||||
|
||||
try
|
||||
{
|
||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
String s = br.readLine();
|
||||
|
||||
while(s != null && !s.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
// convert each line into an LOD object and add it to the region
|
||||
LodChunk lod = new LodChunk(s);
|
||||
|
||||
region.addLod(lod);
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
// we were unable to create this chunk
|
||||
// for whatever reason.
|
||||
// skip to the next chunk
|
||||
}
|
||||
|
||||
s = br.readLine();
|
||||
}
|
||||
|
||||
br.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// File not found
|
||||
|
||||
// or the buffered reader encountered a
|
||||
// problem reading the file
|
||||
return null;
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// Save to File //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Save all dirty regions in this LodDimension to file.
|
||||
*/
|
||||
public synchronized void saveDirtyRegionsToFileAsync()
|
||||
{
|
||||
// we don't currently support reading or writing
|
||||
// files when connected to a server
|
||||
if (!Minecraft.getInstance().isIntegratedServerRunning())
|
||||
return;
|
||||
|
||||
if (!readyToReadAndWrite())
|
||||
// we aren't ready to read and write yet
|
||||
return;
|
||||
|
||||
fileWritingThreadPool.execute(saveDirtyRegionsThread);
|
||||
}
|
||||
private Thread saveDirtyRegionsThread = new Thread(() ->
|
||||
{
|
||||
for(int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
{
|
||||
for(int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
{
|
||||
if(loadedDimension.isRegionDirty[i][j])
|
||||
{
|
||||
saveRegionToDisk(loadedDimension.regions[i][j]);
|
||||
loadedDimension.isRegionDirty[i][j] = 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)
|
||||
{
|
||||
if (!readyToReadAndWrite() || region == null)
|
||||
return;
|
||||
|
||||
// convert chunk coordinates to region
|
||||
// coordinates
|
||||
int x = region.x;
|
||||
int z = region.z;
|
||||
|
||||
File f = new File(getFileNameForRegion(x, z));
|
||||
|
||||
try
|
||||
{
|
||||
// make sure the file and folder exists
|
||||
if (!f.exists())
|
||||
if(!f.getParentFile().exists())
|
||||
f.getParentFile().mkdirs();
|
||||
f.createNewFile();
|
||||
|
||||
FileWriter fw = new FileWriter(f);
|
||||
|
||||
for(LodChunk[] chunkArray : region.getAllLods())
|
||||
for(LodChunk chunk : chunkArray)
|
||||
if(chunk != null)
|
||||
fw.write(chunk.toData() + "\n");
|
||||
|
||||
fw.close();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
System.err.println("LOD file write error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of the file that should contain the
|
||||
* region at the given x and z. <br>
|
||||
* Returns null if this object isn't ready to read and write.
|
||||
*/
|
||||
private String getFileNameForRegion(int regionX, int regionZ)
|
||||
{
|
||||
if (!readyToReadAndWrite())
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// saveFolder is something like
|
||||
// ".\Super Flat\DIM-1\data"
|
||||
// or
|
||||
// ".\Super Flat\data"
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + "\\lod\\" +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns if this FileHandler is ready to read
|
||||
* and write files.
|
||||
* <br>
|
||||
* This returns true when the world save directory is known.
|
||||
*/
|
||||
public boolean readyToReadAndWrite()
|
||||
{
|
||||
return dimensionDataSaveFolder != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* If on single player this will return the name of the user's
|
||||
* world, if in multiplayer it will return the server name
|
||||
* and game version.
|
||||
*/
|
||||
public static String getCurrentWorldID()
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
ServerWorld serverWorld = LodUtils.getFirstValidServerWorld();
|
||||
if (serverWorld == null)
|
||||
return "";
|
||||
|
||||
ServerChunkProvider provider = serverWorld.getChunkProvider();
|
||||
if(provider != null)
|
||||
return provider.getSavedData().folder.toString();
|
||||
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return mc.getCurrentServerData().serverName + "_version_" + mc.getCurrentServerData().gameVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package com.backsun.lod.mixin;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.backsun.lod.renderer.RenderGlobalHook;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
|
||||
@Mixin(WorldRenderer.class)
|
||||
public class MixinWorldRenderer
|
||||
{
|
||||
@Inject(at = @At("HEAD"), method = "renderBlockLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V", cancellable = false)
|
||||
private void renderBlockLayer(RenderType blockLayerIn, MatrixStack matrixStackIn, double xIn, double yIn, double zIn, CallbackInfo callback)
|
||||
{
|
||||
RenderGlobalHook.startRenderingStencil(blockLayerIn);
|
||||
}
|
||||
}
|
||||
@@ -1,776 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import com.backsun.lod.util.enums.ColorDirection;
|
||||
import com.backsun.lod.util.enums.LodCorner;
|
||||
import com.backsun.lod.util.enums.LodLocation;
|
||||
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.color.BlockColors;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
|
||||
/**
|
||||
* This object contains position
|
||||
* and color data for an LOD object.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-13-2021
|
||||
*/
|
||||
public class LodChunk
|
||||
{
|
||||
/** how many different pieces of data are in one line */
|
||||
private static final int DATA_DELIMITER_COUNT = 28;
|
||||
|
||||
/** This is what separates each piece of data in the toData method */
|
||||
public static final char DATA_DELIMITER = ',';
|
||||
|
||||
public static final int WIDTH = 16;
|
||||
|
||||
private static final int CHUNK_DATA_WIDTH = WIDTH;
|
||||
private static final int CHUNK_DATA_HEIGHT = WIDTH;
|
||||
|
||||
/**
|
||||
* This is how many blocks are
|
||||
* required at a specific y-value
|
||||
* to constitute a LOD point
|
||||
*/
|
||||
private final int LOD_BLOCK_REQ = 16;
|
||||
// the max number of blocks per layer = 64 (8*8)
|
||||
// since each layer is 1/4 the chunk
|
||||
|
||||
|
||||
/** The x coordinate of the chunk. */
|
||||
public int x;
|
||||
/** The z coordinate of the chunk. */
|
||||
public int z;
|
||||
|
||||
// each short is the height of
|
||||
// that 8th of the chunk.
|
||||
public short top[];
|
||||
public short bottom[];
|
||||
|
||||
/** The average color of each 6 cardinal directions */
|
||||
public Color colors[];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// constructors //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Create an empty invisible LodChunk at (0,0)
|
||||
*/
|
||||
public LodChunk()
|
||||
{
|
||||
x = 0;
|
||||
z = 0;
|
||||
|
||||
top = new short[4];
|
||||
bottom = new short[4];
|
||||
colors = new Color[6];
|
||||
|
||||
// by default have the colors invisible
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
colors[dir.value] = new Color(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an LodChunk from the string
|
||||
* generated by the toData method.
|
||||
*
|
||||
* @throws IllegalArgumentException if the data isn't valid to create a LodChunk
|
||||
* @throws NumberFormatException if the data can't be converted into an int at any point
|
||||
*/
|
||||
public LodChunk(String data) throws IllegalArgumentException, NumberFormatException
|
||||
{
|
||||
/*
|
||||
* data format:
|
||||
* x, z, top data, bottom data, rgb color data
|
||||
*
|
||||
* example:
|
||||
* 5,8, 4,4,4,4, 0,0,0,0, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255,
|
||||
*/
|
||||
|
||||
// make sure there are the correct number of entries
|
||||
// in the data string (28)
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < data.length(); i++)
|
||||
{
|
||||
if(data.charAt(i) == DATA_DELIMITER)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if(count != DATA_DELIMITER_COUNT)
|
||||
{
|
||||
throw new IllegalArgumentException("LodChunk constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + DATA_DELIMITER_COUNT + ".");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// index we will use when going through the String
|
||||
int index = 0;
|
||||
int lastIndex = 0;
|
||||
|
||||
|
||||
|
||||
// x and z position
|
||||
index = data.indexOf(DATA_DELIMITER, 0);
|
||||
x = Integer.parseInt(data.substring(0,index));
|
||||
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
z = Integer.parseInt(data.substring(lastIndex+1,index));
|
||||
|
||||
|
||||
|
||||
// top
|
||||
top = new short[4];
|
||||
for(LodLocation loc : LodLocation.values())
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
top[loc.value] = Short.parseShort(data.substring(lastIndex+1,index));
|
||||
}
|
||||
|
||||
|
||||
// bottom
|
||||
bottom = new short[4];
|
||||
for(LodLocation loc : LodLocation.values())
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
bottom[loc.value] = Short.parseShort(data.substring(lastIndex+1,index));
|
||||
}
|
||||
|
||||
|
||||
// color
|
||||
colors = new Color[6];
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
// get r,g,b
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
String raw = "";
|
||||
switch(i)
|
||||
{
|
||||
case 0:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
red = Short.parseShort(raw);
|
||||
break;
|
||||
case 1:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
green = Short.parseShort(raw);
|
||||
break;
|
||||
case 2:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
blue = Short.parseShort(raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
colors[dir.value] = new Color(red, green, blue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world. <br>
|
||||
* Note: The world is required to determine each block's color
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* thrown if either the chunk or world is null.
|
||||
*/
|
||||
public LodChunk(Chunk chunk, World world) throws IllegalArgumentException
|
||||
{
|
||||
if(chunk == null)
|
||||
{
|
||||
throw new IllegalArgumentException("LodChunk constructor given a null chunk");
|
||||
}
|
||||
if(world == null)
|
||||
{
|
||||
throw new IllegalArgumentException("LodChunk constructor given a null world");
|
||||
}
|
||||
|
||||
|
||||
x = chunk.getPos().x;
|
||||
z = chunk.getPos().z;
|
||||
|
||||
top = new short[4];
|
||||
bottom = new short[4];
|
||||
colors = new Color[6];
|
||||
|
||||
// generate the top and bottom points of this LOD
|
||||
for(LodLocation loc : LodLocation.values())
|
||||
{
|
||||
top[loc.value] = generateLodCorner(chunk, SectionGenerationMode.GENERATE_TOP, loc);
|
||||
bottom[loc.value] = generateLodCorner(chunk, SectionGenerationMode.GENERATE_BOTTOM, loc);
|
||||
}
|
||||
|
||||
// determine the average color for each direction
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
colors[dir.value] = generateLodColorForDirection(chunk, world, dir);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// constructor helpers //
|
||||
//=====================//
|
||||
|
||||
|
||||
/**
|
||||
* Generate the height for the given LodLocation, either the top or bottom.
|
||||
* <br><br>
|
||||
* If invalid/null/empty chunks are given
|
||||
* crashes may occur.
|
||||
*/
|
||||
public short generateLodCorner(Chunk chunk, SectionGenerationMode generationMode, LodLocation lodLoc)
|
||||
{
|
||||
// should have a length of 16
|
||||
// (each storage is 16x16x16 and the
|
||||
// world height is 256)
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
|
||||
|
||||
int startX = 0;
|
||||
int endX = 0;
|
||||
|
||||
int startZ = 0;
|
||||
int endZ = 0;
|
||||
|
||||
// determine where we should look in this
|
||||
// chunk
|
||||
switch(lodLoc)
|
||||
{
|
||||
case NE:
|
||||
// -N
|
||||
startZ = 0;
|
||||
endZ = (CHUNK_DATA_WIDTH / 2) - 1;
|
||||
// +E
|
||||
startX = CHUNK_DATA_WIDTH / 2;
|
||||
endX = CHUNK_DATA_WIDTH - 1;
|
||||
break;
|
||||
|
||||
case SE:
|
||||
// +S
|
||||
startZ = CHUNK_DATA_WIDTH / 2;
|
||||
endZ = CHUNK_DATA_WIDTH;
|
||||
// +E
|
||||
startX = CHUNK_DATA_WIDTH / 2;
|
||||
endX = CHUNK_DATA_WIDTH;
|
||||
break;
|
||||
|
||||
case SW:
|
||||
// +S
|
||||
startZ = CHUNK_DATA_WIDTH / 2;
|
||||
endZ = CHUNK_DATA_WIDTH;
|
||||
// -W
|
||||
startX = 0;
|
||||
endX = (CHUNK_DATA_WIDTH / 2) - 1;
|
||||
break;
|
||||
|
||||
case NW:
|
||||
// -N
|
||||
startZ = 0;
|
||||
endZ = CHUNK_DATA_WIDTH / 2;
|
||||
// -W
|
||||
startX = 0;
|
||||
endX = CHUNK_DATA_WIDTH / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if(generationMode == SectionGenerationMode.GENERATE_TOP)
|
||||
return determineTopPoint(chunkSections, startX, endX, startZ, endZ);
|
||||
else
|
||||
return determineBottomPoint(chunkSections, startX, endX, startZ, endZ);
|
||||
}
|
||||
/** GENERATE_TOP, GENERATE_BOTTOM */
|
||||
private enum SectionGenerationMode
|
||||
{
|
||||
GENERATE_TOP,
|
||||
GENERATE_BOTTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
private short determineBottomPoint(ChunkSection[] chunkSections, int startX, int endX, int startZ, int endZ)
|
||||
{
|
||||
// search from the bottom up
|
||||
for(int i = 0; i < chunkSections.length; i++)
|
||||
{
|
||||
for(int y = 0; y < CHUNK_DATA_HEIGHT; y++)
|
||||
{
|
||||
|
||||
if(isLayerValidLodPoint(chunkSections, startX, endX, startZ, endZ, i, y))
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (i * CHUNK_DATA_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the highest valid point from the Top
|
||||
*/
|
||||
private short determineTopPoint(ChunkSection[] chunkSections, int startX, int endX, int startZ, int endZ)
|
||||
{
|
||||
// search from the top down
|
||||
for(int i = chunkSections.length - 1; i >= 0; i--)
|
||||
{
|
||||
for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
|
||||
{
|
||||
if(isLayerValidLodPoint(chunkSections, startX, endX, startZ, endZ, i, y))
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (i * CHUNK_DATA_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the layer between the given X, Z, and dataIndex
|
||||
* values a valid LOD point?
|
||||
*/
|
||||
private boolean isLayerValidLodPoint(
|
||||
ChunkSection[] chunkSections,
|
||||
int startX, int endX,
|
||||
int startZ, int endZ,
|
||||
int sectionIndex, int y)
|
||||
{
|
||||
// search through this layer
|
||||
int layerBlocks = 0;
|
||||
|
||||
for(int x = startX; x < endX; x++)
|
||||
{
|
||||
for(int z = startZ; z < endZ; z++)
|
||||
{
|
||||
if(chunkSections[sectionIndex] == null)
|
||||
{
|
||||
// this section doesn't have any blocks,
|
||||
// it is not a valid section
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(chunkSections[sectionIndex].getBlockState(x, y, z) != null && chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR)
|
||||
{
|
||||
// we found a valid block in
|
||||
// in this layer
|
||||
layerBlocks++;
|
||||
|
||||
if(layerBlocks >= LOD_BLOCK_REQ)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // z
|
||||
} // x
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the color of the given ColorDirection at the given chunk
|
||||
* in the given world.
|
||||
*/
|
||||
private Color generateLodColorForDirection(Chunk chunk, World world, ColorDirection colorDir)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
BlockColors bc = mc.getBlockColors();
|
||||
|
||||
switch (colorDir)
|
||||
{
|
||||
case TOP:
|
||||
return generateLodColorVertical(chunk, colorDir, world, bc);
|
||||
case BOTTOM:
|
||||
return generateLodColorVertical(chunk, colorDir, world, bc);
|
||||
|
||||
case N:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
case S:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
|
||||
case E:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
case W:
|
||||
return generateLodColorHorizontal(chunk, colorDir, world, bc);
|
||||
}
|
||||
|
||||
return new Color(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the color of the top or bottom of a given chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException if given a ColorDirection other than TOP or BOTTOM
|
||||
*/
|
||||
private Color generateLodColorVertical(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc)
|
||||
{
|
||||
if(colorDir != ColorDirection.TOP && colorDir != ColorDirection.BOTTOM)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodColorVertical only accepts the ColorDirection TOP or BOTTOM");
|
||||
}
|
||||
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
boolean goTopDown = (colorDir == ColorDirection.TOP);
|
||||
|
||||
|
||||
// either go top down or bottom up
|
||||
int dataStart = goTopDown? chunkSections.length - 1 : 0;
|
||||
int dataMax = chunkSections.length;
|
||||
int dataMin = 0;
|
||||
int dataIncrement = goTopDown? -1 : 1;
|
||||
|
||||
int topStart = goTopDown? CHUNK_DATA_HEIGHT - 1 : 0;
|
||||
int topMax = CHUNK_DATA_HEIGHT;
|
||||
int topMin = 0;
|
||||
int topIncrement = goTopDown? -1 : 1;
|
||||
|
||||
for(int x = 0; x < CHUNK_DATA_WIDTH; x++)
|
||||
{
|
||||
for(int z = 0; z < CHUNK_DATA_WIDTH; z++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
for(int i = dataStart; !foundBlock && i >= dataMin && i < dataMax; i += dataIncrement)
|
||||
{
|
||||
if(!foundBlock && chunkSections[i] != null)
|
||||
{
|
||||
for(int y = topStart; !foundBlock && y >= topMin && y < topMax; y += topIncrement)
|
||||
{
|
||||
int ci;
|
||||
ci = chunkSections[i].getBlockState(x, y, z).materialColor.colorValue;
|
||||
|
||||
if(ci == 0)
|
||||
{
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = intToColor(ci);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the color of the side of a given chunk in the given world for the given ColorDirection.
|
||||
*
|
||||
* @throws IllegalArgumentException if given a ColorDirection other than N, S, W, E (North, South, East, West)
|
||||
*/
|
||||
private Color generateLodColorHorizontal(Chunk chunk, ColorDirection colorDir, World world, BlockColors bc)
|
||||
{
|
||||
if(colorDir != ColorDirection.N && colorDir != ColorDirection.S && colorDir != ColorDirection.E && colorDir != ColorDirection.W)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodColorHorizontal only accepts the ColorDirection N (North), S (South), E (East), or W (West)");
|
||||
}
|
||||
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
|
||||
// these don't change since the over direction doesn't matter
|
||||
int overStart = 0;
|
||||
int overIncrement = 1;
|
||||
|
||||
// determine which direction is "in"
|
||||
int inStart = 0;
|
||||
int inIncrement = 1;
|
||||
switch (colorDir)
|
||||
{
|
||||
case N:
|
||||
inStart = 0;
|
||||
inIncrement = 1;
|
||||
break;
|
||||
case S:
|
||||
inStart = CHUNK_DATA_WIDTH - 1;
|
||||
inIncrement = -1;
|
||||
break;
|
||||
case E:
|
||||
inStart = 0;
|
||||
inIncrement = 1;
|
||||
break;
|
||||
case W:
|
||||
inStart = CHUNK_DATA_WIDTH - 1;
|
||||
inIncrement = -1;
|
||||
break;
|
||||
default:
|
||||
// we were given an invalid position, return invisible.
|
||||
// this shouldn't happen and is mostly here to make the
|
||||
// compiler happy
|
||||
return new Color(0,0,0,0);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < chunkSections.length; i++)
|
||||
{
|
||||
if (chunkSections[i] != null)
|
||||
{
|
||||
for (int y = 0; y < CHUNK_DATA_HEIGHT; y++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
// over moves "over" the side of the chunk
|
||||
// in moves "into" the chunk until it finds a block
|
||||
|
||||
for (int over = overStart; !foundBlock && over >= 0 && over < CHUNK_DATA_WIDTH; over += overIncrement)
|
||||
{
|
||||
for (int in = inStart; !foundBlock && in >= 0 && in < CHUNK_DATA_WIDTH; in += inIncrement)
|
||||
{
|
||||
int x = -1;
|
||||
int z = -1;
|
||||
|
||||
// determine which should be X and Z
|
||||
switch(colorDir)
|
||||
{
|
||||
case N:
|
||||
x = over;
|
||||
z = in;
|
||||
break;
|
||||
case S:
|
||||
x = over;
|
||||
z = in;
|
||||
break;
|
||||
case E:
|
||||
x = in;
|
||||
z = over;
|
||||
break;
|
||||
case W:
|
||||
x = in;
|
||||
z = over;
|
||||
break;
|
||||
default:
|
||||
// this will never happen, it would have
|
||||
// been caught by the switch before the loops
|
||||
break;
|
||||
}
|
||||
|
||||
int ci;
|
||||
ci = chunkSections[i].getBlockState(x, y, z).getMaterial().getColor().colorValue;
|
||||
|
||||
if (ci == 0) {
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = intToColor(ci);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a BlockColors int into a Color object.
|
||||
*/
|
||||
private Color intToColor(int num)
|
||||
{
|
||||
int filter = 0b11111111;
|
||||
|
||||
int red = (num >> 16 ) & filter;
|
||||
int green = (num >> 8 ) & filter;
|
||||
int blue = num & filter;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Color into a BlockColors object.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private int colorToInt(Color color)
|
||||
{
|
||||
return color.getRGB();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// output //
|
||||
//========//
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Outputs all data in csv format
|
||||
* with the given delimiter.
|
||||
* <br>
|
||||
* Exports data in the form:
|
||||
* <br>
|
||||
* x, z, top data, bottom data, rgb color data
|
||||
*
|
||||
* <br>
|
||||
* example output:
|
||||
* <br>
|
||||
* 5,8, 4,4,4,4, 0,0,0,0 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255,
|
||||
*/
|
||||
public String toData()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += Integer.toString(x) + DATA_DELIMITER + Integer.toString(z) + DATA_DELIMITER;
|
||||
|
||||
for(int i = 0; i < top.length; i++)
|
||||
{
|
||||
s += Short.toString(top[i]) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
for(int i = 0; i < bottom.length; i++)
|
||||
{
|
||||
s += Short.toString(bottom[i]) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
for(int i = 0; i < colors.length; i++)
|
||||
{
|
||||
s += Integer.toString(colors[i].getRed()) + DATA_DELIMITER + Integer.toString(colors[i].getGreen()) + DATA_DELIMITER + Integer.toString(colors[i].getBlue()) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += "x: " + x + " z: " + z + "\t";
|
||||
|
||||
s += "(" + colors[ColorDirection.TOP.value].getRed() + ", " + colors[ColorDirection.TOP.value].getGreen() + ", " + colors[ColorDirection.TOP.value].getBlue() + ")";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.backsun.lod.objects;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
|
||||
/**
|
||||
* This stores all LODs for a given world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-22-2021
|
||||
*/
|
||||
public class LodWorld
|
||||
{
|
||||
public String worldName;
|
||||
|
||||
/**
|
||||
* Key = Dimension id (as an int)
|
||||
*/
|
||||
private Map<DimensionType, LodDimension> lodDimensions;
|
||||
|
||||
|
||||
public LodWorld(String newWorldName)
|
||||
{
|
||||
worldName = newWorldName;
|
||||
lodDimensions = new Hashtable<DimensionType, LodDimension>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void addLodDimension(LodDimension newStorage)
|
||||
{
|
||||
lodDimensions.put(newStorage.dimension, newStorage);
|
||||
}
|
||||
|
||||
public LodDimension getLodDimension(DimensionType dimension)
|
||||
{
|
||||
return lodDimensions.get(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the max width in regions that each LodDimension
|
||||
* should use.
|
||||
*/
|
||||
public void resizeDimensionRegionWidth(int newWidth)
|
||||
{
|
||||
for(DimensionType key : lodDimensions.keySet())
|
||||
lodDimensions.get(key).setRegionWidth(newWidth);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += worldName + "\t - dimensions: ";
|
||||
for(DimensionType key : lodDimensions.keySet())
|
||||
s += lodDimensions.get(key).dimension.toString() + ", ";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package com.backsun.lod.proxy;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.backsun.lod.builders.LodBuilder;
|
||||
import com.backsun.lod.handlers.LodDimensionFileHandler;
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.LodRegion;
|
||||
import com.backsun.lod.objects.LodWorld;
|
||||
import com.backsun.lod.renderer.LodRenderer;
|
||||
import com.backsun.lod.renderer.RenderGlobalHook;
|
||||
import com.backsun.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import net.minecraftforge.client.event.RenderWorldLastEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
//TODO Find a way to replace getIntegratedServer so this mod could be used on non-local worlds.
|
||||
// Minecraft.getMinecraft().getIntegratedServer()
|
||||
|
||||
/**
|
||||
* This handles all events sent to the client,
|
||||
* and is the starting point for most of this program.
|
||||
*
|
||||
* @author James_Seibel
|
||||
* @version 02-23-2021
|
||||
*/
|
||||
public class ClientProxy
|
||||
{
|
||||
private LodRenderer renderer;
|
||||
private LodWorld lodWorld;
|
||||
private LodBuilder lodBuilder;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
public ClientProxy()
|
||||
{
|
||||
lodBuilder = new LodBuilder();
|
||||
renderer = new LodRenderer();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// render event //
|
||||
//==============//
|
||||
|
||||
@SubscribeEvent
|
||||
public void renderWorldLast(RenderWorldLastEvent event)
|
||||
{
|
||||
RenderGlobalHook.endRenderingStencil();
|
||||
GL11.glStencilFunc(GL11.GL_EQUAL, 0, 0xFF);
|
||||
|
||||
if (LodConfig.CLIENT.drawLODs.get())
|
||||
renderLods(event.getPartialTicks());
|
||||
|
||||
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)
|
||||
{
|
||||
// update the
|
||||
int newWidth = Math.max(4, (mc.gameSettings.renderDistanceChunks * LodChunk.WIDTH * 2) / LodRegion.SIZE);
|
||||
if (lodWorld != null && lodBuilder.regionWidth != newWidth)
|
||||
{
|
||||
lodWorld.resizeDimensionRegionWidth(newWidth);
|
||||
lodBuilder.regionWidth = newWidth;
|
||||
|
||||
// skip this frame, hopefully the lodWorld
|
||||
// should have everything set up by then
|
||||
return;
|
||||
}
|
||||
|
||||
// are we still in the same world?
|
||||
if (!lodWorld.worldName.equals(LodDimensionFileHandler.getCurrentWorldID()))
|
||||
// no, don't render the wrong world
|
||||
return;
|
||||
|
||||
|
||||
if (mc == null || mc.player == null || lodWorld == null)
|
||||
return;
|
||||
|
||||
LodDimension lodDim = lodWorld.getLodDimension(mc.player.world.getDimensionType());
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
|
||||
|
||||
// offset the regions
|
||||
double playerX = mc.player.getPosX();
|
||||
double playerZ = mc.player.getPosZ();
|
||||
|
||||
int xOffset = ((int)playerX / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterX();
|
||||
int zOffset = ((int)playerZ / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterZ();
|
||||
|
||||
if (xOffset != 0 || zOffset != 0)
|
||||
{
|
||||
lodDim.move(xOffset, zOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
renderer.drawLODs(lodDim, partialTicks, mc.getProfiler());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// lod creation events //
|
||||
//=====================//
|
||||
|
||||
@SubscribeEvent
|
||||
public void chunkLoadEvent(ChunkEvent.Load event)
|
||||
{
|
||||
if (event.getChunk().getClass() == Chunk.class)
|
||||
lodWorld = lodBuilder.generateLodChunkAsync((Chunk) event.getChunk());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.backsun.lod.renderer;
|
||||
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
|
||||
/**
|
||||
* This code is used for drawing
|
||||
* to the stencil buffer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-17-2021
|
||||
*/
|
||||
public class RenderGlobalHook
|
||||
{
|
||||
/**
|
||||
* this variable should be the same as the method name below.
|
||||
* It is used when transforming the RenderGlobal class'
|
||||
* renderBlockLayer method.
|
||||
*/
|
||||
public static final String START_STENCIL_METHOD_NAME = "startRenderingStencil";
|
||||
|
||||
/**
|
||||
* This method tells OpenGL to start drawing everything to the stencil.
|
||||
* This is done to prevent LODs from being rendered on top of the world.
|
||||
* <br><br>
|
||||
* Called in the order (as of minecraft 1.16.4): <br>
|
||||
* RenderType.getSolid() <br>
|
||||
* RenderType.getCutoutMipped() <br>
|
||||
* RenderType.getCutout() <br>
|
||||
* RenderType.getTranslucent() <br>
|
||||
* RenderType.getTripwire() <br>
|
||||
*/
|
||||
public static void startRenderingStencil(RenderType blockLayerIn)
|
||||
{
|
||||
// we only enable drawing to the stencil once since
|
||||
// we want to skip the rendering of the out of world fog
|
||||
// but catch everything else
|
||||
if (blockLayerIn == RenderType.getSolid())
|
||||
{
|
||||
// solid is the first layer rendered
|
||||
// clear the buffer so we can start fresh.
|
||||
// if this isn't cleared first we will have overlap with the fog
|
||||
// outside the world
|
||||
GL11.glClearStencil(0);
|
||||
GL11.glStencilMask(0x11111111);
|
||||
GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
GL11.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 0x11111111);
|
||||
GL11.glStencilMask(0b11111111);
|
||||
GL11.glStencilOp(GL11.GL_KEEP, // this doesn't mater since GL_ALWAYS is being used
|
||||
GL11.GL_KEEP, // stencil test passes
|
||||
GL11.GL_REPLACE); // stencil + depth pass
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* this variable should be the same as the method name below.
|
||||
* It is used when transforming the RenderGlobal class'
|
||||
* renderBlockLayer method.
|
||||
*/
|
||||
public static final String END_STENCIL_METHOD_NAME = "endRenderingStencil";
|
||||
|
||||
/**
|
||||
* Currently this method isn't used in any transformations since we end
|
||||
* the stencil drawing in the ClientProxy right before we draw the LODs.
|
||||
*/
|
||||
public static void endRenderingStencil(RenderType blockLayerIn)
|
||||
{
|
||||
GL11.glStencilOp(GL11.GL_KEEP, // this doesn't mater since GL_ALWAYS is being used
|
||||
GL11.GL_KEEP, // stencil test passes
|
||||
GL11.GL_KEEP); // stencil + depth pass
|
||||
}
|
||||
|
||||
public static void endRenderingStencil()
|
||||
{
|
||||
endRenderingStencil(null);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package com.backsun.lod.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.backsun.lod.ModInfo;
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-27-2021
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public class LodConfig
|
||||
{
|
||||
public static class Client
|
||||
{
|
||||
public ForgeConfigSpec.BooleanValue drawLODs;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
|
||||
|
||||
public ForgeConfigSpec.BooleanValue drawCheckerBoard;
|
||||
|
||||
|
||||
Client(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment(ModInfo.MODNAME + " configuration settings").push("client");
|
||||
|
||||
drawLODs = builder
|
||||
.comment("If false LODs will not be drawn, \n"
|
||||
+ "however they will still be generated \n"
|
||||
+ "and saved to file for later use.")
|
||||
.define("drawLODs", true);
|
||||
|
||||
fogDistance = builder
|
||||
.comment("\n"
|
||||
+ "At what distance should Fog be drawn on the LODs? \n"
|
||||
+ "If fog cuts off ubruptly or you are using Optifine's \"fast\" \n"
|
||||
+ "fog option set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ".")
|
||||
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
|
||||
|
||||
drawCheckerBoard = builder
|
||||
.comment("\n"
|
||||
+ "If false the LODs will draw with their normal world colors. \n"
|
||||
+ "If true they will draw as a black and white checkerboard. \n"
|
||||
+ "This can be used for debugging or imagining you are playing a \n"
|
||||
+ "giant game of chess ;)")
|
||||
.define("drawCheckerBoard", false);
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@link Path} to the configuration file of this mod
|
||||
*/
|
||||
private static final Path CONFIG_PATH =
|
||||
Paths.get("config", ModInfo.MODID + ".toml");
|
||||
|
||||
public static final ForgeConfigSpec clientSpec;
|
||||
public static final Client CLIENT;
|
||||
static {
|
||||
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
|
||||
clientSpec = specPair.getRight();
|
||||
CLIENT = specPair.getLeft();
|
||||
|
||||
// setup the config file
|
||||
CommentedFileConfig config = CommentedFileConfig.builder(CONFIG_PATH)
|
||||
.writingMode(WritingMode.REPLACE)
|
||||
.build();
|
||||
config.load();
|
||||
config.save();
|
||||
clientSpec.setConfig(config);
|
||||
}
|
||||
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoad(final ModConfig.Loading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onFileChange(final ModConfig.Reloading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Forge config just got changed on the file system!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package com.backsun.lod.util;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.server.integrated.IntegratedServer;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This class holds methods that may be used in multiple places.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-26-2021
|
||||
*/
|
||||
public class LodUtils
|
||||
{
|
||||
private static Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the first valid ServerWorld.
|
||||
*
|
||||
* @return null if there are no ServerWorlds
|
||||
*/
|
||||
public static ServerWorld getFirstValidServerWorld()
|
||||
{
|
||||
if (mc.getIntegratedServer() == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = mc.getIntegratedServer().getWorlds();
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
return world;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ServerWorld for the relevant dimension.
|
||||
*
|
||||
* @return null if there is no ServerWorld for the given dimension
|
||||
*/
|
||||
public static ServerWorld getServerWorldFromDimension(DimensionType dimension)
|
||||
{
|
||||
IntegratedServer server = mc.getIntegratedServer();
|
||||
if (server == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = server.getWorlds();
|
||||
ServerWorld returnWorld = null;
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
{
|
||||
if(world.getDimensionType() == dimension)
|
||||
{
|
||||
returnWorld = world;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return returnWorld;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 1-20-2020
|
||||
*
|
||||
* NE, SE, SW, NW
|
||||
*/
|
||||
public enum LodLocation
|
||||
{
|
||||
// used for position
|
||||
|
||||
/** -Z, +X */
|
||||
NE(0),
|
||||
/** +Z, +X */
|
||||
SE(1),
|
||||
/** +Z, -X */
|
||||
SW(2),
|
||||
/** -Z, -X */
|
||||
NW(3);
|
||||
|
||||
public final int value;
|
||||
|
||||
private LodLocation(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
package com.backsun.lod;
|
||||
package com.seibel.lod;
|
||||
|
||||
import com.backsun.lod.proxy.ClientProxy;
|
||||
import com.backsun.lod.util.LodConfig;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
package com.backsun.lod;
|
||||
package com.seibel.lod;
|
||||
|
||||
/**
|
||||
* This file is similar to mcmod.info
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-17-2021
|
||||
* @version 05-31-2021
|
||||
*/
|
||||
public final class ModInfo
|
||||
{
|
||||
public static final String MODID = "lod";
|
||||
public static final String MODNAME = "Levels of Detail";
|
||||
public static final String MODAPI = "LodAPI";
|
||||
public static final String VERSION = "1.0";
|
||||
public static final String VERSION = "a1.2";
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
package com.seibel.lod.builders;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.NearFarBuffer;
|
||||
import com.seibel.lod.render.LodRender;
|
||||
import com.seibel.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraftforge.common.WorldWorkerManager;
|
||||
|
||||
/**
|
||||
* This object is used to create NearFarBuffer objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-06-2021
|
||||
*/
|
||||
public class LodBufferBuilder
|
||||
{
|
||||
private Minecraft mc;
|
||||
|
||||
/** This holds the thread used to generate new LODs off the main thread. */
|
||||
private ExecutorService genThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
private LodBuilder lodBuilder;
|
||||
|
||||
/** The buffers that are used to create LODs using near fog */
|
||||
public volatile BufferBuilder buildableNearBuffer;
|
||||
/** The buffers that are used to create LODs using far fog */
|
||||
public volatile BufferBuilder buildableFarBuffer;
|
||||
|
||||
/** if this is true the LOD buffers are currently being
|
||||
* regenerated. */
|
||||
public volatile boolean generatingBuffers = false;
|
||||
|
||||
/** if this is true new LOD buffers have been generated
|
||||
* and are waiting to be swapped with the drawable buffers*/
|
||||
private volatile boolean switchBuffers = false;
|
||||
|
||||
/** If this is greater than 0 no new chunk generation requests will be made
|
||||
* this is to prevent chunks from being generated for a long time in an area
|
||||
* the player is no longer in. */
|
||||
public int numberOfChunksWaitingToGenerate = 0;
|
||||
|
||||
/** how many chunks to generate outside of the player's
|
||||
* view distance at one time. (or more specifically how
|
||||
* many requests to make at one time) */
|
||||
public int maxChunkGenRequests = 8;
|
||||
|
||||
|
||||
public LodBufferBuilder(LodBuilder newLodBuilder)
|
||||
{
|
||||
mc = Minecraft.getInstance();
|
||||
lodBuilder = newLodBuilder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Create a thread to asynchronously generate LOD buffers
|
||||
* centered around the given camera X and Z.
|
||||
* <br>
|
||||
* This method will write to the drawableNearBuffers and drawableFarBuffers.
|
||||
* <br>
|
||||
* After the buildable buffers have been generated they must be
|
||||
* swapped with the drawable buffers in the LodRenderer to be drawn.
|
||||
*/
|
||||
public void generateLodBuffersAsync(LodRender renderer, LodDimension lodDim,
|
||||
double playerX, double playerZ, int numbChunksWide)
|
||||
{
|
||||
// only allow one generation process to happen at a time
|
||||
if (generatingBuffers)
|
||||
return;
|
||||
|
||||
if (buildableNearBuffer == null || buildableFarBuffer == null)
|
||||
throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created.");
|
||||
|
||||
|
||||
|
||||
generatingBuffers = true;
|
||||
|
||||
|
||||
|
||||
// this seemingly useless math is required,
|
||||
// just using (int) playerX/Z doesn't work
|
||||
int playerXChunkOffset = ((int) playerX / LodChunk.WIDTH) * LodChunk.WIDTH;
|
||||
int playerZChunkOffset = ((int) playerZ / LodChunk.WIDTH) * LodChunk.WIDTH;
|
||||
// this is where we will start drawing squares
|
||||
// (exactly half the total width)
|
||||
int startX = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerXChunkOffset;
|
||||
int startZ = (-LodChunk.WIDTH * (numbChunksWide / 2)) + playerZChunkOffset;
|
||||
|
||||
|
||||
|
||||
Thread t = new Thread(()->
|
||||
{
|
||||
// index of the chunk currently being added to the
|
||||
// generation list
|
||||
int chunkGenIndex = 0;
|
||||
|
||||
ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests];
|
||||
int minChunkDist = Integer.MAX_VALUE;
|
||||
ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodChunk.WIDTH, (int)playerZ / LodChunk.WIDTH);
|
||||
|
||||
|
||||
// generate our new buildable buffers
|
||||
buildableNearBuffer.begin(GL11.GL_QUADS, LodRender.LOD_VERTEX_FORMAT);
|
||||
buildableFarBuffer.begin(GL11.GL_QUADS, LodRender.LOD_VERTEX_FORMAT);
|
||||
|
||||
// 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 = lodDim.getLodFromCoordinates(chunkX, chunkZ);
|
||||
|
||||
if (lod == null || lod.isLodEmpty())
|
||||
{
|
||||
// generate a new chunk if no chunk currently exists
|
||||
// and we aren't waiting on any other chunks to generate
|
||||
if (lod == null && numberOfChunksWaitingToGenerate == 0)
|
||||
{
|
||||
ChunkPos pos = new ChunkPos(chunkX, chunkZ);
|
||||
|
||||
// determine if this position is closer to the player
|
||||
// than the previous
|
||||
int newDistance = playerChunkPos.getChessboardDistance(pos);
|
||||
|
||||
if (newDistance < minChunkDist)
|
||||
{
|
||||
// this chunk is closer, clear any previous
|
||||
// positions and update the new minimum distance
|
||||
minChunkDist = newDistance;
|
||||
|
||||
chunkGenIndex = 0;
|
||||
chunksToGen = new ChunkPos[maxChunkGenRequests];
|
||||
chunksToGen[chunkGenIndex] = pos;
|
||||
chunkGenIndex++;
|
||||
}
|
||||
else if (newDistance <= minChunkDist)
|
||||
{
|
||||
// this chunk position is as close or closers than the
|
||||
// minimum distance
|
||||
if(chunkGenIndex < maxChunkGenRequests)
|
||||
{
|
||||
// we are still under the number of chunks to generate
|
||||
// add this position to the list
|
||||
chunksToGen[chunkGenIndex] = pos;
|
||||
chunkGenIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// don't render this null chunk
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
BufferBuilder currentBuffer = null;
|
||||
if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2))
|
||||
currentBuffer = buildableNearBuffer;
|
||||
else
|
||||
currentBuffer = buildableFarBuffer;
|
||||
|
||||
// get the desired LodTemplate and
|
||||
// add this LOD to the buffer
|
||||
LodConfig.CLIENT.lodTemplate.get().template.
|
||||
addLodToBuffer(currentBuffer, lodDim, lod,
|
||||
xOffset, yOffset, zOffset, renderer.debugging);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO add a way for a server side mod to generate chunks requested here
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
// start chunk generation
|
||||
for(ChunkPos chunkPos : chunksToGen)
|
||||
{
|
||||
if(chunkPos == null)
|
||||
break;
|
||||
|
||||
numberOfChunksWaitingToGenerate++;
|
||||
|
||||
LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodBuilder, this, lodDim);
|
||||
WorldWorkerManager.addWorker(genWorker);
|
||||
}
|
||||
}
|
||||
|
||||
// finish the buffer building
|
||||
buildableNearBuffer.finishDrawing();
|
||||
buildableFarBuffer.finishDrawing();
|
||||
|
||||
// mark that the buildable buffers as ready to swap
|
||||
generatingBuffers = false;
|
||||
switchBuffers = true;
|
||||
});
|
||||
|
||||
genThread.execute(t);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//====================//
|
||||
// generation helpers //
|
||||
//====================//
|
||||
|
||||
/**
|
||||
* Returns if the given coordinate is in the loaded area of the world.
|
||||
* @param centerCoordinate the center of the loaded world
|
||||
*/
|
||||
private boolean isCoordInCenterArea(int i, int j, int centerCoordinate)
|
||||
{
|
||||
return (i >= centerCoordinate - mc.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).
|
||||
*/
|
||||
private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius)
|
||||
{
|
||||
int halfRadius = lodRadius / 2;
|
||||
|
||||
return (chunkX >= lodRadius - halfRadius
|
||||
&& chunkX <= lodRadius + halfRadius)
|
||||
&&
|
||||
(chunkZ >= lodRadius - halfRadius
|
||||
&& chunkZ <= lodRadius + halfRadius);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===============================//
|
||||
// BufferBuilder related methods //
|
||||
//===============================//
|
||||
|
||||
|
||||
/**
|
||||
* Called from the LodRenderer to create the
|
||||
* BufferBuilders at the right size.
|
||||
*
|
||||
* @param bufferMaxCapacity
|
||||
*/
|
||||
public void setupBuffers(int bufferMaxCapacity)
|
||||
{
|
||||
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the drawable and buildable buffers and return
|
||||
* the old drawable buffers.
|
||||
* @param drawableNearBuffer
|
||||
* @param drawableFarBuffer
|
||||
*/
|
||||
public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer)
|
||||
{
|
||||
// swap the BufferBuilders
|
||||
BufferBuilder tmp = buildableNearBuffer;
|
||||
buildableNearBuffer = drawableNearBuffer;
|
||||
drawableNearBuffer = tmp;
|
||||
|
||||
tmp = buildableFarBuffer;
|
||||
buildableFarBuffer = drawableFarBuffer;
|
||||
drawableFarBuffer = tmp;
|
||||
|
||||
|
||||
// the buffers have been swapped
|
||||
switchBuffers = false;
|
||||
|
||||
return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is true the buildable near and far
|
||||
* buffers have been generated and are ready to be
|
||||
* sent to the LodRenderer.
|
||||
*/
|
||||
public boolean newBuffersAvaliable()
|
||||
{
|
||||
return switchBuffers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,663 @@
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.enums.ColorDirection;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodWorld;
|
||||
import com.seibel.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.color.BlockColors;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
|
||||
/**
|
||||
* This object is in charge of creating Lod
|
||||
* related objects.
|
||||
* (specifically: Lod World, Dimension, Region, and Chunk objects)
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 5-29-2021
|
||||
*/
|
||||
public class LodBuilder
|
||||
{
|
||||
private static final Color INVISIBLE = new Color(0,0,0,0);
|
||||
|
||||
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
|
||||
|
||||
/** Default size of any LOD regions we use */
|
||||
public int regionWidth = 5;
|
||||
|
||||
|
||||
public static final int CHUNK_DATA_WIDTH = LodChunk.WIDTH;
|
||||
public static final int CHUNK_SECTION_HEIGHT = LodChunk.WIDTH;
|
||||
|
||||
/**
|
||||
* This is how many blocks are
|
||||
* required at a specific y-value
|
||||
* to constitute a LOD point
|
||||
*/
|
||||
private final int LOD_BLOCK_REQ = 32;
|
||||
// the max number of blocks per layer = 64 (8*8)
|
||||
|
||||
|
||||
public LodBuilder()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void generateLodChunkAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
|
||||
{
|
||||
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
|
||||
return;
|
||||
|
||||
// is this chunk from the same world as the lodWorld?
|
||||
if (!lodWorld.getWorldName().equals(LodUtils.getWorldID(world)))
|
||||
// we are not in the same world anymore
|
||||
// don't add this LOD
|
||||
return;
|
||||
|
||||
|
||||
// don't try to create an LOD object
|
||||
// if for some reason we aren't
|
||||
// given a valid chunk object
|
||||
if (chunk == null)
|
||||
return;
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
DimensionType dim = world.getDimensionType();
|
||||
|
||||
LodChunk lod = generateLodFromChunk(chunk);
|
||||
|
||||
LodDimension lodDim;
|
||||
|
||||
if (lodWorld.getLodDimension(dim) == null)
|
||||
{
|
||||
lodDim = new LodDimension(dim, lodWorld, regionWidth);
|
||||
lodWorld.addLodDimension(lodDim);
|
||||
}
|
||||
else
|
||||
{
|
||||
lodDim = lodWorld.getLodDimension(dim);
|
||||
}
|
||||
|
||||
lodDim.addLod(lod);
|
||||
}
|
||||
catch(IllegalArgumentException | NullPointerException e)
|
||||
{
|
||||
// if the world changes while LODs are being generated
|
||||
// they will throw errors as they try to access things that no longer
|
||||
// exist.
|
||||
}
|
||||
});
|
||||
lodGenThreadPool.execute(thread);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* thrown if either the chunk or world is null.
|
||||
*/
|
||||
public LodChunk generateLodFromChunk(IChunk chunk) throws IllegalArgumentException
|
||||
{
|
||||
if(chunk == null)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
|
||||
}
|
||||
|
||||
Color[] colors = new Color[ColorDirection.values().length];
|
||||
|
||||
|
||||
short height = determineTopPoint(chunk.getSections());
|
||||
short depth = determineBottomPoint(chunk.getSections());
|
||||
|
||||
|
||||
// determine the average color for each direction
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
colors[dir.value] = generateLodColorForDirection(chunk, dir);
|
||||
}
|
||||
|
||||
// check to see if there are any invisible sides
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
// if there are any invisible sides
|
||||
// replace them with the top side
|
||||
// (this is done to make sure oceans and other totally
|
||||
// flat locations have the correct side colors)
|
||||
if (dir == ColorDirection.BOTTOM || dir == ColorDirection.TOP)
|
||||
continue;
|
||||
if (colors[dir.value] == INVISIBLE)
|
||||
colors[dir.value] = colors[ColorDirection.TOP.value];
|
||||
}
|
||||
|
||||
return new LodChunk(chunk.getPos(), height, depth, colors);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=====================//
|
||||
// constructor helpers //
|
||||
//=====================//
|
||||
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
private short determineBottomPoint(ChunkSection[] chunkSections)
|
||||
{
|
||||
// search from the bottom up
|
||||
for(int i = 0; i < CHUNK_DATA_WIDTH; i++)
|
||||
{
|
||||
for(int y = 0; y < CHUNK_SECTION_HEIGHT; y++)
|
||||
{
|
||||
if(isLayerValidLodPoint(chunkSections, i, y))
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (i * CHUNK_SECTION_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private short determineBottomPoint(Heightmap heightmap)
|
||||
{
|
||||
// the heightmap only shows how high the blocks go, it
|
||||
// doesn't have any info about how low they go
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Find the highest valid point from the Top
|
||||
*/
|
||||
private short determineTopPoint(ChunkSection[] chunkSections)
|
||||
{
|
||||
// search from the top down
|
||||
for(int section = chunkSections.length - 1; section >= 0; section--)
|
||||
{
|
||||
for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
|
||||
{
|
||||
if(isLayerValidLodPoint(chunkSections, section, y))
|
||||
{
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (section * CHUNK_SECTION_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the highest point from the Top
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private short determineTopPoint(Heightmap heightmap, int endZ)
|
||||
{
|
||||
short highest = 0;
|
||||
for(int x = 0; x < LodChunk.WIDTH; x++)
|
||||
{
|
||||
for(int z = 0; z < LodChunk.WIDTH; z++)
|
||||
{
|
||||
short newHeight = (short) heightmap.getHeight(x, z);
|
||||
if (newHeight > highest)
|
||||
highest = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return highest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the color for the given chunk in the given ColorDirection.
|
||||
*/
|
||||
private Color generateLodColorForDirection(IChunk chunk, ColorDirection colorDir)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
BlockColors bc = mc.getBlockColors();
|
||||
|
||||
switch (colorDir)
|
||||
{
|
||||
case TOP:
|
||||
return generateLodColorVertical(chunk, colorDir, bc);
|
||||
case BOTTOM:
|
||||
return generateLodColorVertical(chunk, colorDir, bc);
|
||||
|
||||
case NORTH:
|
||||
return generateLodColorHorizontal(chunk, colorDir, bc);
|
||||
case SOUTH:
|
||||
return generateLodColorHorizontal(chunk, colorDir, bc);
|
||||
|
||||
case EAST:
|
||||
return generateLodColorHorizontal(chunk, colorDir, bc);
|
||||
case WEST:
|
||||
return generateLodColorHorizontal(chunk, colorDir, bc);
|
||||
}
|
||||
|
||||
return INVISIBLE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Is the layer between the given X, Z, and dataIndex
|
||||
* values a valid LOD point?
|
||||
*/
|
||||
private boolean isLayerValidLodPoint(
|
||||
ChunkSection[] chunkSections,
|
||||
int sectionIndex, int y)
|
||||
{
|
||||
// search through this layer
|
||||
int layerBlocks = 0;
|
||||
|
||||
for(int x = 0; x < LodChunk.WIDTH; x++)
|
||||
{
|
||||
for(int z = 0; z < LodChunk.WIDTH; z++)
|
||||
{
|
||||
if(chunkSections[sectionIndex] == null)
|
||||
{
|
||||
// this section doesn't have any blocks,
|
||||
// it is not a valid section
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(chunkSections[sectionIndex].getBlockState(x, y, z) != null &&
|
||||
chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR)
|
||||
{
|
||||
// we found a valid block in
|
||||
// in this layer
|
||||
layerBlocks++;
|
||||
|
||||
if(layerBlocks >= LOD_BLOCK_REQ)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // z
|
||||
} // x
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the color of the top or bottom of the given chunk.
|
||||
*
|
||||
* @throws IllegalArgumentException if given a ColorDirection other than TOP or BOTTOM
|
||||
*/
|
||||
private Color generateLodColorVertical(IChunk chunk, ColorDirection colorDir, BlockColors bc)
|
||||
{
|
||||
if(colorDir != ColorDirection.TOP && colorDir != ColorDirection.BOTTOM)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodColorVertical only accepts the ColorDirection TOP or BOTTOM");
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
boolean goTopDown = (colorDir == ColorDirection.TOP);
|
||||
|
||||
|
||||
// either go top down or bottom up
|
||||
int dataStart = goTopDown? chunkSections.length - 1 : 0;
|
||||
int dataMax = chunkSections.length;
|
||||
int dataMin = 0;
|
||||
int dataIncrement = goTopDown? -1 : 1;
|
||||
|
||||
int topStart = goTopDown? CHUNK_SECTION_HEIGHT - 1 : 0;
|
||||
int topMax = CHUNK_SECTION_HEIGHT;
|
||||
int topMin = 0;
|
||||
int topIncrement = goTopDown? -1 : 1;
|
||||
|
||||
for(int x = 0; x < CHUNK_DATA_WIDTH; x++)
|
||||
{
|
||||
for(int z = 0; z < CHUNK_DATA_WIDTH; z++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
for(int i = dataStart; !foundBlock && i >= dataMin && i < dataMax; i += dataIncrement)
|
||||
{
|
||||
if(!foundBlock && chunkSections[i] != null)
|
||||
{
|
||||
for(int y = topStart; !foundBlock && y >= topMin && y < topMax; y += topIncrement)
|
||||
{
|
||||
int ci;
|
||||
ci = chunkSections[i].getBlockState(x, y, z).materialColor.colorValue;
|
||||
|
||||
if(ci == 0)
|
||||
{
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = intToColor(ci);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/*
|
||||
* unused variation that can be used with only the heightmap,
|
||||
* although it just returns the foliage color, so it shouldn't
|
||||
* be used normally.
|
||||
|
||||
Heightmap heightmap = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE_WG);
|
||||
|
||||
int numbOfBlocks = CHUNK_DATA_WIDTH * CHUNK_DATA_WIDTH;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
for(int x = 0; x < CHUNK_DATA_WIDTH; x++)
|
||||
{
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x,z, heightmap.getHeight(x, z));
|
||||
Color c = intToColor(biome.getFoliageColor());
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
}
|
||||
}
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Generates the color for the sides of the given chunk.
|
||||
*/
|
||||
private Color generateLodColorHorizontal(IChunk chunk, ColorDirection colorDir, BlockColors bc)
|
||||
{
|
||||
if(colorDir != ColorDirection.NORTH && colorDir != ColorDirection.SOUTH && colorDir != ColorDirection.EAST && colorDir != ColorDirection.WEST)
|
||||
{
|
||||
throw new IllegalArgumentException("generateLodColorHorizontal only accepts the ColorDirection N (North), S (South), E (East), or W (West)");
|
||||
}
|
||||
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
|
||||
// these don't change since the over direction doesn't matter
|
||||
int overStart = 0;
|
||||
int overIncrement = 1;
|
||||
|
||||
// determine which direction is "in"
|
||||
int inStart = 0;
|
||||
int inIncrement = 1;
|
||||
switch (colorDir)
|
||||
{
|
||||
case NORTH:
|
||||
inStart = 0;
|
||||
inIncrement = 1;
|
||||
break;
|
||||
case SOUTH:
|
||||
inStart = CHUNK_DATA_WIDTH - 1;
|
||||
inIncrement = -1;
|
||||
break;
|
||||
case EAST:
|
||||
inStart = 0;
|
||||
inIncrement = 1;
|
||||
break;
|
||||
case WEST:
|
||||
inStart = CHUNK_DATA_WIDTH - 1;
|
||||
inIncrement = -1;
|
||||
break;
|
||||
default:
|
||||
// we were given an invalid position, return invisible.
|
||||
// this shouldn't happen and is mostly here to make the
|
||||
// compiler happy
|
||||
return INVISIBLE;
|
||||
}
|
||||
|
||||
|
||||
for (int section = 0; section < chunkSections.length; section++)
|
||||
{
|
||||
if (chunkSections[section] == null)
|
||||
continue;
|
||||
|
||||
for (int y = 0; y < CHUNK_SECTION_HEIGHT; y++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
// over moves "over" the side of the chunk
|
||||
// in moves "into" the chunk until it finds a block
|
||||
for (int over = overStart; !foundBlock && over >= 0 && over < CHUNK_DATA_WIDTH; over += overIncrement)
|
||||
{
|
||||
for (int in = inStart; !foundBlock && in >= 0 && in < CHUNK_DATA_WIDTH; in += inIncrement)
|
||||
{
|
||||
int x = -1;
|
||||
int z = -1;
|
||||
|
||||
// determine which should be X and Z
|
||||
switch(colorDir)
|
||||
{
|
||||
case NORTH:
|
||||
x = over;
|
||||
z = in;
|
||||
break;
|
||||
case SOUTH:
|
||||
x = over;
|
||||
z = in;
|
||||
break;
|
||||
case EAST:
|
||||
x = in;
|
||||
z = over;
|
||||
break;
|
||||
case WEST:
|
||||
x = in;
|
||||
z = over;
|
||||
break;
|
||||
default:
|
||||
// here to make the compiler happy
|
||||
break;
|
||||
}
|
||||
|
||||
// if this block is buried, under other blocks
|
||||
// don't add it
|
||||
if(!isBlockPosVisible(chunkSections[section], x,y,z))
|
||||
{
|
||||
// go to the next "over" block location,
|
||||
// don't look at the next "in" location,
|
||||
// since the next "in" location will more than
|
||||
// likely still be covered
|
||||
in = CHUNK_DATA_WIDTH + 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int ci;
|
||||
ci = chunkSections[section].getBlockState(x, y, z).getMaterial().getColor().colorValue;
|
||||
|
||||
if (ci == 0) {
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
Color c = intToColor(ci);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// if we didn't find any blocks return invisible
|
||||
if(numbOfBlocks == 0)
|
||||
return INVISIBLE;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static BlockState airState = Blocks.AIR.getDefaultState();
|
||||
|
||||
/**
|
||||
* returns true if the block at the given coordinates is open to
|
||||
* air on at least one side.
|
||||
*/
|
||||
private boolean isBlockPosVisible(ChunkSection chunkSection, int x, int y, int z)
|
||||
{
|
||||
/*
|
||||
// above
|
||||
if (y+1 < CHUNK_SECTION_HEIGHT) // don't go over the top
|
||||
if (chunkSection.getBlockState(x, y+1, z).getBlock() == (Blocks.AIR))
|
||||
return true;
|
||||
// below
|
||||
if (y-1 >= 0) // don't go below the bottom
|
||||
if (chunkSection.getBlockState(x, y-1, z).getBlock() == (Blocks.AIR))
|
||||
return true;
|
||||
*/
|
||||
|
||||
// north
|
||||
if (z-1 > 0)
|
||||
if (chunkSection.getBlockState(x, y, z-1) == airState)
|
||||
return true;
|
||||
// south
|
||||
if (z+1 < LodChunk.WIDTH)
|
||||
if (chunkSection.getBlockState(x, y, z+1) == airState)
|
||||
return true;
|
||||
|
||||
// east
|
||||
if (x+1 <= LodChunk.WIDTH)
|
||||
if (chunkSection.getBlockState(x+1, y, z) == airState)
|
||||
return true;
|
||||
// west
|
||||
if (x-1 >= 0)
|
||||
if (chunkSection.getBlockState(x-1, y, z) == airState)
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Convert a BlockColors int into a Color object.
|
||||
*/
|
||||
private Color intToColor(int num)
|
||||
{
|
||||
int filter = 0b11111111;
|
||||
|
||||
int red = (num >> 16 ) & filter;
|
||||
int green = (num >> 8 ) & filter;
|
||||
int blue = num & filter;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Color into a BlockColors object.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private int colorToInt(Color color)
|
||||
{
|
||||
return color.getRGB();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.seibel.lod.builders;
|
||||
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.render.LodRender;
|
||||
import com.seibel.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.WorldWorkerManager.IWorker;
|
||||
|
||||
/**
|
||||
* This is used to generate a LodChunk at a given ChunkPos.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 03-31-2021
|
||||
*/
|
||||
public class LodChunkGenWorker implements IWorker
|
||||
{
|
||||
private ServerWorld serverWorld;
|
||||
private ChunkPos pos;
|
||||
private LodDimension lodDim;
|
||||
private LodBuilder lodBuilder;
|
||||
private LodBufferBuilder lodBufferBuilder;
|
||||
private LodRender lodRender;
|
||||
|
||||
public LodChunkGenWorker(ChunkPos newPos, LodRender newLodRenderer,
|
||||
LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
|
||||
LodDimension newLodDimension)
|
||||
{
|
||||
serverWorld = LodUtils.getServerWorldFromDimension(newLodDimension.dimension);
|
||||
if (serverWorld == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld");
|
||||
|
||||
pos = newPos;
|
||||
lodDim = newLodDimension;
|
||||
lodBuilder = newLodBuilder;
|
||||
lodBufferBuilder = newLodBufferBuilder;
|
||||
lodRender = newLodRenderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doWork()
|
||||
{
|
||||
if (pos != null)
|
||||
{
|
||||
int x = pos.x;
|
||||
int z = pos.z;
|
||||
|
||||
// only generate LodChunks if they can
|
||||
// be added to the current LodDimension
|
||||
if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE))
|
||||
{
|
||||
IChunk chunk;// = serverWorld.getChunk(x, z, ChunkStatus.EMPTY, true);
|
||||
|
||||
//long startTime = System.currentTimeMillis();
|
||||
chunk = serverWorld.getChunk(x, z, ChunkStatus.FEATURES);
|
||||
//long endTime = System.currentTimeMillis();
|
||||
//System.out.println(endTime - startTime + "\t" + lodBuilder.hasBlockData(chunk));
|
||||
|
||||
|
||||
lodBuilder.generateLodChunkAsync(chunk, ClientProxy.getLodWorld(), serverWorld);
|
||||
// this is called so that the new LOD chunk is drawn
|
||||
// after it is generated
|
||||
lodRender.regenerateLODsNextFrame();
|
||||
|
||||
|
||||
// useful for debugging
|
||||
// ClientProxy.LOGGER.info(lodDim.getNumberOfLods());
|
||||
|
||||
// if (lodDim.getLodFromCoordinates(x, z) != null)
|
||||
// ClientProxy.LOGGER.info(x + " " + z + " Success!");
|
||||
// else
|
||||
// ClientProxy.LOGGER.info(x + " " + z);
|
||||
}
|
||||
// can be used for debugging
|
||||
//else
|
||||
//{
|
||||
// System.out.println("Out of range " + x + " " + z);
|
||||
//}
|
||||
|
||||
lodBufferBuilder.numberOfChunksWaitingToGenerate--;
|
||||
|
||||
pos = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasWork()
|
||||
{
|
||||
return pos != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* performance/generation tests related to
|
||||
* serverWorld.getChunk(x, z, ChunkStatus. *** )
|
||||
|
||||
true/false is whether they generated blocks or not
|
||||
the time is how long it took to generate
|
||||
|
||||
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
|
||||
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
|
||||
ChunkStatus.BIOMES 1 - 10 ms false (no height)
|
||||
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
|
||||
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.FEATURES 7 - 25 ms true
|
||||
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
|
||||
ChunkStatus.LIGHT 20 - 40 ms true
|
||||
ChunkStatus.FULL 30 - 50 ms true
|
||||
ChunkStatus.SPAWN 50 - 80 ms true
|
||||
|
||||
|
||||
At this point I would suggest using FEATURES, as it generates snow and trees
|
||||
(and any other object that is needed to make biomes distinct)
|
||||
|
||||
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* This is the abstract class used to create different
|
||||
* BufferBuilders.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-07-2021
|
||||
*/
|
||||
public abstract class AbstractLodTemplate
|
||||
{
|
||||
/** alpha used when drawing chunks in debug mode */
|
||||
protected int debugAlpha = 255; // 0 - 255
|
||||
protected Color debugBlack = new Color(0, 0, 0, debugAlpha);
|
||||
protected Color debugWhite = new Color(255, 255, 255, debugAlpha);
|
||||
|
||||
|
||||
public abstract void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging);
|
||||
|
||||
/** add the given position and color to the buffer */
|
||||
protected 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import com.seibel.lod.enums.ColorDirection;
|
||||
import com.seibel.lod.enums.RelativeChunkPos;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
|
||||
/**
|
||||
* Builds LODs as rectangular prisms.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-31-2021
|
||||
*/
|
||||
public class CubicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
|
||||
public CubicLodTemplate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk centerLod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
AxisAlignedBB bbox;
|
||||
|
||||
// Add this LOD to the BufferBuilder
|
||||
// using the quality setting set by the config
|
||||
switch(LodConfig.CLIENT.lodDetail.get())
|
||||
{
|
||||
// add a single LOD object for this chunk
|
||||
case SINGLE:
|
||||
|
||||
// returns null if the lod is empty at the given location
|
||||
bbox = generateBoundingBox(centerLod.getHeight(), centerLod.getDepth(), LodChunk.WIDTH, xOffset, yOffset, zOffset);
|
||||
|
||||
if (bbox != null)
|
||||
{
|
||||
addBoundingBoxToBuffer(buffer, bbox, generateLodColors(centerLod, false));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
// add 4 LOD objects for this chunk
|
||||
case DOUBLE:
|
||||
/*
|
||||
* This method generates LODs using the LodChunks that
|
||||
* are adjacent to create an average quarter and thus
|
||||
* smooth the transition between chunks.
|
||||
*/
|
||||
|
||||
// get the adjacent LodChunks
|
||||
LodChunk[] lods = new LodChunk[RelativeChunkPos.values().length];
|
||||
for(RelativeChunkPos pos : RelativeChunkPos.values())
|
||||
lods[pos.index] = lodDim.getLodFromCoordinates(centerLod.x + pos.x, centerLod.z + pos.z);
|
||||
|
||||
|
||||
int halfWidth = LodChunk.WIDTH / 2;
|
||||
|
||||
// use the adjacent chunks to generate quarter sections
|
||||
for(EnumSet<RelativeChunkPos> set : RelativeChunkPos.CORNERS)
|
||||
{
|
||||
int x = 0;
|
||||
int z = 0;
|
||||
|
||||
// Weight the center LodChunk by this amount
|
||||
// when taking the average.
|
||||
// this should be between 3 and 6;
|
||||
// if set to 1 (no extra weight)
|
||||
// then the chunks don't appear to be averaged.
|
||||
int centerWeight = 3;
|
||||
|
||||
// how many LodChunks adjacent to the center
|
||||
// are valid?
|
||||
int validPoints = centerWeight;
|
||||
|
||||
int avgHeight = centerLod.getHeight() * centerWeight;
|
||||
int avgDepth = centerLod.getDepth() * centerWeight;
|
||||
|
||||
int[][] colorAverages = new int[ColorDirection.values().length][3];
|
||||
Color[] colorToAdd = generateLodColors(centerLod, debugging);
|
||||
for(int i = 0; i < centerWeight; i++)
|
||||
colorAverages = addColorToColorAverages(colorAverages, colorToAdd);
|
||||
|
||||
for(RelativeChunkPos cornerPos : set)
|
||||
{
|
||||
// set the x and y location based on which
|
||||
// corner we are working on
|
||||
if (RelativeChunkPos.DIAGONAL.contains(cornerPos))
|
||||
{
|
||||
x = Math.min(cornerPos.x, 0) * halfWidth;
|
||||
z = Math.min(cornerPos.z, 0) * halfWidth;
|
||||
}
|
||||
|
||||
LodChunk cornerLod = lods[cornerPos.index];
|
||||
if (cornerLod != null && !cornerLod.isLodEmpty())
|
||||
{
|
||||
validPoints++;
|
||||
|
||||
avgHeight += cornerLod.getHeight();
|
||||
avgDepth += cornerLod.getDepth();
|
||||
|
||||
// only generate average colors if we aren't debugging
|
||||
// (this is to prevent everything from becoming grey)
|
||||
if (!debugging)
|
||||
colorToAdd = generateLodColors(cornerLod, debugging);
|
||||
else
|
||||
colorToAdd = generateLodColors(centerLod, debugging);
|
||||
// add to the running color average
|
||||
colorAverages = addColorToColorAverages(colorAverages, colorToAdd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// convert the heights into actual averages
|
||||
avgHeight /= validPoints;
|
||||
avgDepth /= validPoints;
|
||||
// calculate the average colors
|
||||
Color[] colors = new Color[ColorDirection.values().length];
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
for(int rgbIndex = 0; rgbIndex < 3; rgbIndex++)
|
||||
colorAverages[dir.value][rgbIndex] /= validPoints;
|
||||
colors[dir.value] = new Color(colorAverages[dir.value][0], colorAverages[dir.value][1], colorAverages[dir.value][2]);
|
||||
}
|
||||
|
||||
|
||||
// returns null if the lod is empty at the given location
|
||||
bbox = generateBoundingBox(avgHeight, avgDepth, halfWidth, xOffset - (halfWidth / 2) + x + 12, yOffset, zOffset - (halfWidth / 2) + z + 12);
|
||||
|
||||
if (bbox != null)
|
||||
{
|
||||
addBoundingBoxToBuffer(buffer, bbox, colors);
|
||||
}
|
||||
}
|
||||
break;
|
||||
} // case
|
||||
}
|
||||
|
||||
|
||||
private int[][] addColorToColorAverages(int[][] colorAverages, Color[] colorToAdd)
|
||||
{
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
// convert the colorToAdd to an int array
|
||||
float[] colorCompoments = new float[4];
|
||||
colorCompoments = colorToAdd[dir.value].getColorComponents(colorCompoments);
|
||||
|
||||
// add each color component to the array
|
||||
for(int rgbIndex = 0; rgbIndex < 3; rgbIndex++)
|
||||
{
|
||||
// * 255 + 0.5 taken from the Color java class
|
||||
colorAverages[dir.value][rgbIndex] += (int) (colorCompoments[rgbIndex] * 255 + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
return colorAverages;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private AxisAlignedBB generateBoundingBox(int height, int depth, int width, double xOffset, double yOffset, double zOffset)
|
||||
{
|
||||
// don't add an LOD if it is empty
|
||||
if (height == -1 && depth == -1)
|
||||
return null;
|
||||
|
||||
if (depth == height)
|
||||
{
|
||||
// if the top and bottom points are at the same height
|
||||
// render this LOD as 1 block thick
|
||||
height++;
|
||||
}
|
||||
|
||||
return new AxisAlignedBB(0, depth, 0, width, height, width).offset(xOffset, yOffset, zOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color[] c)
|
||||
{
|
||||
// top (facing up)
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha());
|
||||
// bottom (facing down)
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha());
|
||||
|
||||
// south (facing -Z)
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha());
|
||||
// north (facing +Z)
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha());
|
||||
|
||||
// west (facing -X)
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha());
|
||||
// east (facing +X)
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Determine the color for each side of this LOD.
|
||||
*/
|
||||
private Color[] generateLodColors(LodChunk lod, boolean debugging)
|
||||
{
|
||||
Color[] colors = new Color[ColorDirection.values().length];
|
||||
|
||||
if (!debugging)
|
||||
{
|
||||
// if NOT debugging, look to the config to determine
|
||||
// how this LOD should be colored
|
||||
switch (LodConfig.CLIENT.lodColorStyle.get())
|
||||
{
|
||||
case TOP:
|
||||
// only add the top's color to the array
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
colors[dir.value] = lod.getColor(ColorDirection.TOP);
|
||||
break;
|
||||
|
||||
case INDIVIDUAL_SIDES:
|
||||
// add each direction's color to the array
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
colors[dir.value] = lod.getColor(dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if debugging draw the squares as a black and white checker board
|
||||
if ((lod.x + lod.z) % 2 == 0)
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
// have each direction be the same
|
||||
// color if debugging
|
||||
colors[dir.value] = debugWhite;
|
||||
else
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
colors[dir.value] = debugBlack;
|
||||
}
|
||||
|
||||
return colors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* TODO DynamicLodTemplate
|
||||
* Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-07-2021
|
||||
*/
|
||||
public class DynamicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* TODO TriangularLodTemplate
|
||||
* Builds each LOD chunk as a singular rectangular prism.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-07-2021
|
||||
*/
|
||||
public class TriangularLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* TOP, N, S, E, W, BOTTOM
|
||||
* TOP, NORTH, SOUTH, EAST, WEST, BOTTOM
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 10-17-2020
|
||||
@@ -13,14 +13,14 @@ public enum ColorDirection
|
||||
TOP(0),
|
||||
|
||||
/** -Z */
|
||||
N(1),
|
||||
NORTH(1),
|
||||
/** +Z */
|
||||
S(2),
|
||||
SOUTH(2),
|
||||
|
||||
/** +X */
|
||||
E(3),
|
||||
EAST(3),
|
||||
/** -X */
|
||||
W(4),
|
||||
WEST(4),
|
||||
|
||||
/** -Y */
|
||||
BOTTOM(5);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* Near, far, or NEAR_AND_FAR.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* fast, fancy, or off
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* top, individual_sides
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-08-2021
|
||||
*/
|
||||
public enum LodColorStyle
|
||||
{
|
||||
/** Use the color from the top of the LOD chunk
|
||||
* for all sides */
|
||||
TOP,
|
||||
|
||||
/** For each side of the LOD use the color corresponding
|
||||
* to that side */
|
||||
INDIVIDUAL_SIDES;
|
||||
}
|
||||
+1
-3
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.util.enums;
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* NE, SE, SW, NW
|
||||
@@ -8,8 +8,6 @@ package com.backsun.lod.util.enums;
|
||||
*/
|
||||
public enum LodCorner
|
||||
{
|
||||
// used for position
|
||||
|
||||
/** -Z, +X */
|
||||
NE(0),
|
||||
/** +Z, +X */
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
/**
|
||||
* single, quad
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-29-2021
|
||||
*/
|
||||
public enum LodDetail
|
||||
{
|
||||
/** render 1 LOD for each chunk */
|
||||
SINGLE(1),
|
||||
|
||||
/** render 4 LODs for each chunk */
|
||||
DOUBLE(2);
|
||||
|
||||
/** How many data points wide the related
|
||||
* LodChunk object should contain */
|
||||
public final int value;
|
||||
|
||||
private LodDetail(int newValue)
|
||||
{
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
import com.seibel.lod.builders.lodTemplates.AbstractLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.CubicLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.DynamicLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.TriangularLodTemplate;
|
||||
|
||||
/**
|
||||
* Cubic, Triangular, Dynamic
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-07-2021
|
||||
*/
|
||||
public enum LodTemplate
|
||||
{
|
||||
// used for position
|
||||
|
||||
/** Chunks are rendered as
|
||||
* rectangular prisms. */
|
||||
CUBIC(new CubicLodTemplate()),
|
||||
|
||||
/** Chunks smoothly transition between
|
||||
* each other. */
|
||||
TRIANGULAR(new TriangularLodTemplate()),
|
||||
|
||||
/** Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height. */
|
||||
DYNAMIC(new DynamicLodTemplate());
|
||||
|
||||
|
||||
public final AbstractLodTemplate template;
|
||||
|
||||
private LodTemplate(AbstractLodTemplate newTemplate)
|
||||
{
|
||||
template = newTemplate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* NE, SE, SW, NW <br>
|
||||
* NORTH, SOUTH, EAST, WEST, <br>
|
||||
* CENTER
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-30-2021
|
||||
*/
|
||||
public enum RelativeChunkPos
|
||||
{
|
||||
/** +X, -Z */
|
||||
NE(0, 1,-1),
|
||||
/** +X, +Z */
|
||||
SE(1, 1,1),
|
||||
/** -X, +Z */
|
||||
SW(2, -1,1),
|
||||
/** -X, -Z */
|
||||
NW(3, -1,-1),
|
||||
|
||||
/** -Z */
|
||||
NORTH(4, 0,-1),
|
||||
/** +Z */
|
||||
SOUTH(5, 0,1),
|
||||
|
||||
/** +X */
|
||||
EAST(6, 1,0),
|
||||
/** -X */
|
||||
WEST(7, -1,0),
|
||||
|
||||
CENTER(8, 0,0);
|
||||
|
||||
/** index used when referencing objects in an array */
|
||||
public final int index;
|
||||
|
||||
/** position relative to X CENTER */
|
||||
public final int x;
|
||||
/** position relative to Z CENTER */
|
||||
public final int z;
|
||||
|
||||
/** NORTH, SOUTH, EAST, WEST */
|
||||
public static EnumSet<RelativeChunkPos> ADJACENT = EnumSet.of(NORTH, SOUTH, EAST, WEST);
|
||||
/** NE, NW, SE, SW */
|
||||
public static EnumSet<RelativeChunkPos> DIAGONAL = EnumSet.of(NE, NW, SE, SW);
|
||||
|
||||
|
||||
public static EnumSet<RelativeChunkPos> NW_CORNER = EnumSet.of(WEST, NW, NORTH);
|
||||
public static EnumSet<RelativeChunkPos> NE_CORNER = EnumSet.of(EAST, NE, NORTH);
|
||||
public static EnumSet<RelativeChunkPos> SW_CORNER = EnumSet.of(WEST, SW, SOUTH);
|
||||
public static EnumSet<RelativeChunkPos> SE_CORNER = EnumSet.of(EAST, SE, SOUTH);
|
||||
|
||||
/** NW_CORNER, NE_CORNER, SW_CORNER, SE_CORNER <br>
|
||||
* Contains the 3 points surrounding a corner. */
|
||||
public static ArrayList<EnumSet<RelativeChunkPos>> CORNERS = new ArrayList<EnumSet<RelativeChunkPos>>( Arrays.asList(NW_CORNER, NE_CORNER, SW_CORNER, SE_CORNER) );
|
||||
|
||||
private RelativeChunkPos(int newIndex, int newX, int newZ)
|
||||
{
|
||||
index = newIndex;
|
||||
x = newX;
|
||||
z = newZ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
package com.seibel.lod.handlers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
|
||||
/**
|
||||
* This object handles creating LodRegions
|
||||
* from files and saving LodRegion objects
|
||||
* to file.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-31-2021
|
||||
*/
|
||||
public class LodDimensionFileHandler
|
||||
{
|
||||
private LodDimension loadedDimension = null;
|
||||
public long regionLastWriteTime[][];
|
||||
|
||||
private File dimensionDataSaveFolder;
|
||||
|
||||
private final String FILE_NAME_PREFIX = "lod";
|
||||
private final String FILE_EXTENSION = ".txt";
|
||||
|
||||
/** This is the file version currently accepted by this
|
||||
* file handler, older versions (smaller numbers) will be deleted and overwritten,
|
||||
* newer versions (larger numbers) will be ignored and won't be read. */
|
||||
public static final int LOD_SAVE_FILE_VERSION = 1;
|
||||
/** This is the string written before the file version */
|
||||
private static final String LOD_FILE_VERSION_PREFIX = "lod_save_file_version";
|
||||
|
||||
/** Allow saving asynchronously, but never try to save multiple regions
|
||||
* at a time */
|
||||
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor();
|
||||
|
||||
|
||||
public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension)
|
||||
{
|
||||
if (newSaveFolder == null)
|
||||
throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to.");
|
||||
|
||||
dimensionDataSaveFolder = newSaveFolder;
|
||||
|
||||
loadedDimension = newLoadedDimension;
|
||||
// these two variable are used in sync with the LodDimension
|
||||
regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
|
||||
for(int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
for(int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
regionLastWriteTime[i][j] = -1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// read from file //
|
||||
//================//
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return the LodRegion at the given coordinates.
|
||||
* (null if the file doesn't exist)
|
||||
*/
|
||||
public LodRegion loadRegionFromFile(int regionX, int regionZ)
|
||||
{
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ);
|
||||
|
||||
File f = new File(fileName);
|
||||
|
||||
if (!f.exists())
|
||||
{
|
||||
// there wasn't a file, don't
|
||||
// return anything
|
||||
return null;
|
||||
}
|
||||
|
||||
LodRegion region = new LodRegion(regionX, regionZ);
|
||||
|
||||
try
|
||||
{
|
||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
String s = br.readLine();
|
||||
int fileVersion = -1;
|
||||
|
||||
if(s != null && !s.isEmpty())
|
||||
{
|
||||
// try to get the file version
|
||||
try
|
||||
{
|
||||
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
|
||||
}
|
||||
catch(NumberFormatException | StringIndexOutOfBoundsException e)
|
||||
{
|
||||
// this file doesn't have a version
|
||||
// keep the version as -1
|
||||
fileVersion = -1;
|
||||
}
|
||||
|
||||
// check if this file can be read by this file handler
|
||||
if(fileVersion < LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is an older version,
|
||||
// close the reader and delete the file.
|
||||
br.close();
|
||||
f.delete();
|
||||
ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
|
||||
", version requested: " + LOD_SAVE_FILE_VERSION +
|
||||
" File was been deleted.");
|
||||
|
||||
return null;
|
||||
}
|
||||
else if(fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// close the reader and ignore the file, we don't
|
||||
// want to accidently delete anything the user may want.
|
||||
br.close();
|
||||
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
|
||||
", version requested: " + LOD_SAVE_FILE_VERSION +
|
||||
" this region will not be written to in order to protect the newer file.");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// there is no data in this file
|
||||
br.close();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// this file is a readable version, begin reading the file
|
||||
s = br.readLine();
|
||||
|
||||
while(s != null && !s.isEmpty())
|
||||
{
|
||||
try
|
||||
{
|
||||
// convert each line into an LOD object and add it to the region
|
||||
LodChunk lod = new LodChunk(s);
|
||||
|
||||
region.addLod(lod);
|
||||
}
|
||||
catch(IllegalArgumentException e)
|
||||
{
|
||||
// we were unable to create this chunk
|
||||
// for whatever reason.
|
||||
// skip to the next chunk
|
||||
ClientProxy.LOGGER.warn(e.getMessage());
|
||||
}
|
||||
|
||||
s = br.readLine();
|
||||
}
|
||||
|
||||
br.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// the buffered reader encountered a
|
||||
// problem reading the file
|
||||
return null;
|
||||
}
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// Save to File //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Save all dirty regions in this LodDimension to file.
|
||||
*/
|
||||
public void saveDirtyRegionsToFileAsync()
|
||||
{
|
||||
fileWritingThreadPool.execute(saveDirtyRegionsThread);
|
||||
}
|
||||
|
||||
private Thread saveDirtyRegionsThread = new Thread(() ->
|
||||
{
|
||||
for(int i = 0; i < loadedDimension.getWidth(); i++)
|
||||
{
|
||||
for(int j = 0; j < loadedDimension.getWidth(); j++)
|
||||
{
|
||||
if(loadedDimension.isRegionDirty[i][j] && loadedDimension.regions[i][j] != null)
|
||||
{
|
||||
saveRegionToDisk(loadedDimension.regions[i][j]);
|
||||
loadedDimension.isRegionDirty[i][j] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Save a specific region to disk.<br>
|
||||
* Note: <br>
|
||||
* 1. If a file already exists for a newer version
|
||||
* the file won't be written.<br>
|
||||
* 2. This will save to the LodDimension that this
|
||||
* handler is associated with.
|
||||
*/
|
||||
private void saveRegionToDisk(LodRegion region)
|
||||
{
|
||||
// convert chunk coordinates to region
|
||||
// coordinates
|
||||
int x = region.x;
|
||||
int z = region.z;
|
||||
|
||||
File f = new File(getFileNameAndPathForRegion(x, z));
|
||||
|
||||
try
|
||||
{
|
||||
// make sure the file and folder exists
|
||||
if (!f.exists())
|
||||
{
|
||||
// the file doesn't exist,
|
||||
// create it and the folder if need be
|
||||
if(!f.getParentFile().exists())
|
||||
f.getParentFile().mkdirs();
|
||||
f.createNewFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
// the file exists, make sure it
|
||||
// is the correct version.
|
||||
// (to make sure we don't overwrite a newer
|
||||
// version file if it exists)
|
||||
|
||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
String s = br.readLine();
|
||||
int fileVersion = LOD_SAVE_FILE_VERSION;
|
||||
|
||||
if(s != null && !s.isEmpty())
|
||||
{
|
||||
// try to get the file version
|
||||
try
|
||||
{
|
||||
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
|
||||
}
|
||||
catch(NumberFormatException | StringIndexOutOfBoundsException e)
|
||||
{
|
||||
// this file doesn't have a correctly formated version
|
||||
// just overwrite the file
|
||||
}
|
||||
}
|
||||
br.close();
|
||||
|
||||
// check if this file can be written to by the file handler
|
||||
if(fileVersion <= LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// we are good to continue and overwrite the old file
|
||||
}
|
||||
else //if(fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// don't write anything, we don't want to accidently
|
||||
// delete anything the user may want.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
FileWriter fw = new FileWriter(f);
|
||||
|
||||
// add the version of this file
|
||||
fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n");
|
||||
|
||||
// add each LodChunk to the file
|
||||
for(LodChunk[] chunkArray : region.getAllLods())
|
||||
for(LodChunk chunk : chunkArray)
|
||||
if(chunk != null && !chunk.isPlaceholder())
|
||||
fw.write(chunk.toData() + "\n");
|
||||
|
||||
fw.close();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
ClientProxy.LOGGER.error("LOD file write error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of the file that should contain the
|
||||
* region at the given x and z. <br>
|
||||
* Returns null if this object isn't ready to read and write.
|
||||
*/
|
||||
private String getFileNameAndPathForRegion(int regionX, int regionZ)
|
||||
{
|
||||
try
|
||||
{
|
||||
// saveFolder is something like
|
||||
// ".\Super Flat\DIM-1\data"
|
||||
// or
|
||||
// ".\Super Flat\data"
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + "\\" +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.backsun.lod.handlers;
|
||||
package com.seibel.lod.handlers;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.backsun.lod.util.enums.FogQuality;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.seibel.lod.mixin;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.seibel.lod.LodMain;
|
||||
import com.seibel.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
|
||||
/**
|
||||
* This class is used to mix in my rendering code
|
||||
* before Minecraft starts rendering blocks.
|
||||
* If this wasn't done the LODs would render on top
|
||||
* of the normal terrain.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-29-2021
|
||||
*/
|
||||
@Mixin(WorldRenderer.class)
|
||||
public class MixinWorldRenderer
|
||||
{
|
||||
private static float previousPartialTicks = 0;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "renderSky(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V", cancellable = false)
|
||||
private void renderSky(MatrixStack matrixStackIn, float partialTicks, CallbackInfo callback)
|
||||
{
|
||||
// get the partial ticks since renderBlockLayer doesn't
|
||||
// have access to them
|
||||
previousPartialTicks = partialTicks;
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "renderBlockLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/matrix/MatrixStack;DDD)V", cancellable = false)
|
||||
private void renderBlockLayer(RenderType renderType, MatrixStack matrixStackIn, double xIn, double yIn, double zIn, CallbackInfo callback)
|
||||
{
|
||||
// only render if LODs are enabled and
|
||||
// only render before solid blocks
|
||||
if (LodConfig.CLIENT.drawLODs.get() && renderType.equals(RenderType.getSolid()))
|
||||
LodMain.client_proxy.renderLods(previousPartialTicks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import com.seibel.lod.enums.ColorDirection;
|
||||
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
|
||||
/**
|
||||
* This object contains position
|
||||
* and color data for an LOD object.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-29-2021
|
||||
*/
|
||||
public class LodChunk
|
||||
{
|
||||
/** how many different pieces of data are in one line */
|
||||
private static final int DATA_DELIMITER_COUNT = 22;
|
||||
|
||||
/** This is what separates each piece of data in the toData method */
|
||||
public static final char DATA_DELIMITER = ',';
|
||||
|
||||
/** Width of a Minecraft Chunk */
|
||||
public static final int WIDTH = 16;
|
||||
|
||||
private static final Color INVISIBLE = new Color(0,0,0,0);
|
||||
|
||||
|
||||
|
||||
/** The x coordinate of the chunk. */
|
||||
public int x = 0;
|
||||
/** The z coordinate of the chunk. */
|
||||
public int z = 0;
|
||||
|
||||
/*
|
||||
* The reason we are only using 1 height and depth point
|
||||
* instead of multiple is because:
|
||||
* 1. more points would drastically increase the amount of
|
||||
* memory and disk space needed
|
||||
* 2. more height/depth points require more color points,
|
||||
* which can get out of hand quickly
|
||||
* 3. with the increased disk space reading/writing would
|
||||
* take far too long
|
||||
* 4. the increased resolution is generally not worth it,
|
||||
* 4 LODs per chunk is the limit of diminishing returns.
|
||||
* And some of that could potentially be faked through
|
||||
* smart LodTemplates
|
||||
*/
|
||||
private short height;
|
||||
private short depth;
|
||||
|
||||
/** The average color for the 6 cardinal directions */
|
||||
public Color colors[];
|
||||
|
||||
/** If true then this LodChunk contains no data */
|
||||
private boolean empty = false;
|
||||
|
||||
|
||||
/**
|
||||
* Create an empty invisible LodChunk at (0,0)
|
||||
*/
|
||||
public LodChunk()
|
||||
{
|
||||
empty = true;
|
||||
|
||||
x = 0;
|
||||
z = 0;
|
||||
|
||||
height = 0;
|
||||
depth = 0;
|
||||
colors = new Color[ColorDirection.values().length];
|
||||
|
||||
// by default have the colors invisible
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
colors[dir.value] = INVISIBLE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an LodChunk from the string
|
||||
* generated by the toData method.
|
||||
*
|
||||
* @throws IllegalArgumentException if the data isn't valid to create a LodChunk
|
||||
* @throws NumberFormatException if any piece of data can't be converted at any point
|
||||
*/
|
||||
public LodChunk(String data) throws IllegalArgumentException, NumberFormatException
|
||||
{
|
||||
/*
|
||||
* data format:
|
||||
* x, z, height, depth, rgb color data
|
||||
*
|
||||
* example:
|
||||
* 5,8, 4, 0, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255,
|
||||
*/
|
||||
|
||||
// make sure there are the correct number of entries
|
||||
// in the data string (28)
|
||||
int count = 0;
|
||||
|
||||
for(int i = 0; i < data.length(); i++)
|
||||
if(data.charAt(i) == DATA_DELIMITER)
|
||||
count++;
|
||||
|
||||
if(count != DATA_DELIMITER_COUNT)
|
||||
throw new IllegalArgumentException("LodChunk constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + DATA_DELIMITER_COUNT + ".");
|
||||
|
||||
|
||||
|
||||
// index we will use when going through the String
|
||||
int index = 0;
|
||||
int lastIndex = 0;
|
||||
|
||||
|
||||
|
||||
// x and z position
|
||||
index = data.indexOf(DATA_DELIMITER, 0);
|
||||
x = Integer.parseInt(data.substring(0,index));
|
||||
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
|
||||
z = Integer.parseInt(data.substring(lastIndex+1,index));
|
||||
|
||||
// height
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
|
||||
height = Short.parseShort(data.substring(lastIndex+1,index));
|
||||
|
||||
// depth
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
|
||||
depth = Short.parseShort(data.substring(lastIndex+1,index));
|
||||
|
||||
|
||||
|
||||
// color
|
||||
colors = new Color[6];
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
{
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
// get r,g,b
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
lastIndex = index;
|
||||
index = data.indexOf(DATA_DELIMITER, lastIndex + 1);
|
||||
|
||||
String raw = "";
|
||||
switch(i)
|
||||
{
|
||||
case 0:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
red = Short.parseShort(raw);
|
||||
break;
|
||||
case 1:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
green = Short.parseShort(raw);
|
||||
break;
|
||||
case 2:
|
||||
raw = data.substring(lastIndex+1,index);
|
||||
blue = Short.parseShort(raw);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
colors[dir.value] = new Color(red, green, blue);
|
||||
}
|
||||
|
||||
|
||||
// after going through all this
|
||||
// is this LOD empty?
|
||||
empty = determineIfEmtpy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a LodChunk from the given values.
|
||||
*/
|
||||
public LodChunk(ChunkPos pos, short newHeight, short newDepth, Color[] newColors)
|
||||
{
|
||||
x = pos.x;
|
||||
z = pos.z;
|
||||
|
||||
height = newHeight;
|
||||
depth = newDepth;
|
||||
colors = newColors;
|
||||
|
||||
empty = determineIfEmtpy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// misc functions //
|
||||
//================//
|
||||
|
||||
/**
|
||||
* Returns true if this LodChunk is an emptyPlaceholder
|
||||
*/
|
||||
public boolean isPlaceholder()
|
||||
{
|
||||
return empty;
|
||||
}
|
||||
|
||||
public boolean isLodEmpty()
|
||||
{
|
||||
return empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this LOD is either invisible
|
||||
* from every direction or doesn't have a valid height.
|
||||
*/
|
||||
private boolean determineIfEmtpy()
|
||||
{
|
||||
// we don't check the depth since the
|
||||
// height should always be greater than or equal
|
||||
// to the depth
|
||||
if(height >= 0)
|
||||
{
|
||||
// the height is valid,
|
||||
// do we have at least 1 non-invisible color?
|
||||
|
||||
for(ColorDirection dir : ColorDirection.values())
|
||||
if(!colors[dir.value].equals(INVISIBLE))
|
||||
// at least one direction has a non-invisible color
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// the height is negative,
|
||||
// this LodChunk is empty
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//========//
|
||||
// output //
|
||||
//========//
|
||||
|
||||
/** Returns the color for the given direction */
|
||||
public Color getColor(ColorDirection dir)
|
||||
{
|
||||
return colors[dir.value];
|
||||
}
|
||||
|
||||
public short getHeight()
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
public short getDepth()
|
||||
{
|
||||
return depth;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Outputs all data in csv format
|
||||
* with the given delimiter.
|
||||
* <br>
|
||||
* Exports data in the form:
|
||||
* <br>
|
||||
* x, z, height, depth, rgb color data
|
||||
*
|
||||
* <br>
|
||||
* example output:
|
||||
* <br>
|
||||
* 5,8, 4, 0, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255, 255,255,255,
|
||||
*/
|
||||
public String toData()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += Integer.toString(x) + DATA_DELIMITER + Integer.toString(z) + DATA_DELIMITER;
|
||||
|
||||
s += Short.toString(height) + DATA_DELIMITER;
|
||||
|
||||
s += Short.toString(depth) + DATA_DELIMITER;
|
||||
|
||||
for(int i = 0; i < colors.length; i++)
|
||||
{
|
||||
s += Integer.toString(colors[i].getRed()) + DATA_DELIMITER + Integer.toString(colors[i].getGreen()) + DATA_DELIMITER + Integer.toString(colors[i].getBlue()) + DATA_DELIMITER;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String s = "";
|
||||
|
||||
s += "x: " + x + " z: " + z + "\t";
|
||||
|
||||
s += "(" + colors[ColorDirection.TOP.value].getRed() + ", " + colors[ColorDirection.TOP.value].getGreen() + ", " + colors[ColorDirection.TOP.value].getBlue() + ")";
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
+99
-43
@@ -1,17 +1,23 @@
|
||||
package com.backsun.lod.objects;
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import com.backsun.lod.handlers.LodDimensionFileHandler;
|
||||
import com.backsun.lod.util.LodUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import com.seibel.lod.handlers.LodDimensionFileHandler;
|
||||
import com.seibel.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This object holds all loaded LOD regions
|
||||
* for a given dimension.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-23-2021
|
||||
* @version 03-19-2021
|
||||
*/
|
||||
public class LodDimension
|
||||
{
|
||||
@@ -29,13 +35,42 @@ public class LodDimension
|
||||
private LodDimensionFileHandler fileHandler;
|
||||
|
||||
|
||||
public LodDimension(DimensionType newDimension, int newMaxWidth)
|
||||
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newMaxWidth)
|
||||
{
|
||||
dimension = newDimension;
|
||||
width = newMaxWidth;
|
||||
|
||||
ServerChunkProvider provider = LodUtils.getServerWorldFromDimension(newDimension).getChunkProvider();
|
||||
fileHandler = new LodDimensionFileHandler(provider.getSavedData().folder, this);
|
||||
try
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
File saveDir;
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
// local world
|
||||
|
||||
ServerWorld serverWorld = LodUtils.getServerWorldFromDimension(newDimension);
|
||||
// provider needs a separate variable to prevent
|
||||
// the compiler from complaining
|
||||
ServerChunkProvider provider = serverWorld.getChunkProvider();
|
||||
saveDir = new File(provider.getSavedData().folder.getCanonicalFile() + "\\lod");
|
||||
}
|
||||
else
|
||||
{
|
||||
// connected to server
|
||||
|
||||
saveDir = new File(mc.gameDir.getCanonicalFile() +
|
||||
"\\lod server data\\" + LodUtils.getDimensionIDFromWorld(mc.world));
|
||||
}
|
||||
|
||||
fileHandler = new LodDimensionFileHandler(saveDir, this);
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
// the file handler wasn't able to be created
|
||||
// we won't be able to read or write any files
|
||||
}
|
||||
|
||||
|
||||
regions = new LodRegion[width][width];
|
||||
isRegionDirty = new boolean[width][width];
|
||||
@@ -207,39 +242,34 @@ public class LodDimension
|
||||
*/
|
||||
public void addLod(LodChunk lod)
|
||||
{
|
||||
int regionX = lod.x / LodRegion.SIZE;
|
||||
int regionZ = lod.z / LodRegion.SIZE;
|
||||
|
||||
// prevent issues if X/Z is negative and less than 16
|
||||
if (lod.x < 0)
|
||||
{
|
||||
regionX = (Math.abs(regionX) * -1) - 1;
|
||||
}
|
||||
if (lod.z < 0)
|
||||
{
|
||||
regionZ = (Math.abs(regionZ) * -1) - 1;
|
||||
}
|
||||
RegionPos pos = LodUtils.convertChunkPosToRegionPos(new ChunkPos(lod.x, lod.z));
|
||||
|
||||
// don't continue if the region can't be saved
|
||||
if (!regionIsInRange(regionX, regionZ))
|
||||
if (!regionIsInRange(pos.x, pos.z))
|
||||
{
|
||||
return;
|
||||
|
||||
LodRegion region = getRegion(regionX, regionZ);
|
||||
}
|
||||
|
||||
LodRegion region = getRegion(pos.x, pos.z);
|
||||
|
||||
if (region == null)
|
||||
{
|
||||
// if no region exists, create it
|
||||
region = new LodRegion(regionX, regionZ);
|
||||
region = new LodRegion(pos.x, pos.z);
|
||||
setRegion(region);
|
||||
}
|
||||
|
||||
region.addLod(lod);
|
||||
|
||||
// mark the region as dirty so it will be saved to disk
|
||||
int xIndex = (regionX - centerX) + halfWidth;
|
||||
int zIndex = (regionZ - centerZ) + halfWidth;
|
||||
isRegionDirty[xIndex][zIndex] = true;
|
||||
fileHandler.saveDirtyRegionsToFileAsync();
|
||||
// don't save empty place holders to disk
|
||||
if (!lod.isPlaceholder() && fileHandler != null)
|
||||
{
|
||||
// mark the region as dirty so it will be saved to disk
|
||||
int xIndex = (pos.x - centerX) + halfWidth;
|
||||
int zIndex = (pos.z - centerZ) + halfWidth;
|
||||
isRegionDirty[xIndex][zIndex] = true;
|
||||
fileHandler.saveDirtyRegionsToFileAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,20 +281,9 @@ public class LodDimension
|
||||
*/
|
||||
public LodChunk getLodFromCoordinates(int chunkX, int chunkZ)
|
||||
{
|
||||
int regionX = chunkX / LodRegion.SIZE;
|
||||
int regionZ = chunkZ / LodRegion.SIZE;
|
||||
RegionPos pos = LodUtils.convertChunkPosToRegionPos(new ChunkPos(chunkX, chunkZ));
|
||||
|
||||
// prevent issues if chunkX/Z is negative and less than width
|
||||
if (chunkX < 0)
|
||||
{
|
||||
regionX = (Math.abs(regionX) * -1) - 1;
|
||||
}
|
||||
if (chunkZ < 0)
|
||||
{
|
||||
regionZ = (Math.abs(regionZ) * -1) - 1;
|
||||
}
|
||||
|
||||
LodRegion region = getRegion(regionX, regionZ);
|
||||
LodRegion region = getRegion(pos.x, pos.z);
|
||||
|
||||
if(region == null)
|
||||
return null;
|
||||
@@ -279,7 +298,10 @@ public class LodDimension
|
||||
*/
|
||||
public LodRegion getRegionFromFile(int regionX, int regionZ)
|
||||
{
|
||||
return fileHandler.loadRegionFromFile(regionX, regionZ);
|
||||
if (fileHandler != null)
|
||||
return fileHandler.loadRegionFromFile(regionX, regionZ);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +309,7 @@ public class LodDimension
|
||||
* Returns whether the region at the given X and Z coordinates
|
||||
* is within the loaded range.
|
||||
*/
|
||||
private boolean regionIsInRange(int regionX, int regionZ)
|
||||
public boolean regionIsInRange(int regionX, int regionZ)
|
||||
{
|
||||
int xIndex = (regionX - centerX) + halfWidth;
|
||||
int zIndex = (regionZ - centerZ) + halfWidth;
|
||||
@@ -312,6 +334,40 @@ public class LodDimension
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns how many non-null LodChunks
|
||||
* are stored in this LodDimension.
|
||||
*/
|
||||
public int getNumberOfLods()
|
||||
{
|
||||
int numbLods = 0;
|
||||
for (LodRegion[] regions : regions)
|
||||
{
|
||||
if(regions == null)
|
||||
continue;
|
||||
|
||||
for (LodRegion region : regions)
|
||||
{
|
||||
if(region == null)
|
||||
continue;
|
||||
|
||||
for(LodChunk[] lods : region.getAllLods())
|
||||
{
|
||||
if(lods == null)
|
||||
continue;
|
||||
|
||||
for(LodChunk lod : lods)
|
||||
{
|
||||
if (lod != null)
|
||||
numbLods++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return numbLods;
|
||||
}
|
||||
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.objects;
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
/**
|
||||
* A LodRegion is the a 32x32
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
|
||||
/**
|
||||
* This stores all LODs for a given world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 04-01-2021
|
||||
*/
|
||||
public class LodWorld
|
||||
{
|
||||
private String worldName;
|
||||
|
||||
private Map<DimensionType, LodDimension> lodDimensions;
|
||||
/** If true then the LOD world is setup and ready to use */
|
||||
private boolean isWorldLoaded = false;
|
||||
|
||||
public static final String NO_WORLD_LOADED = "No world loaded";
|
||||
|
||||
public LodWorld()
|
||||
{
|
||||
worldName = NO_WORLD_LOADED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the LodWorld with the given newWorldName. <br>
|
||||
* This should be done whenever loading a new world.
|
||||
* @param newWorldName
|
||||
*/
|
||||
public void selectWorld(String newWorldName)
|
||||
{
|
||||
if(newWorldName.isEmpty())
|
||||
{
|
||||
deselectWorld();
|
||||
return;
|
||||
}
|
||||
|
||||
if (worldName.equals(newWorldName))
|
||||
// don't recreate everything if we
|
||||
// didn't actually change worlds
|
||||
return;
|
||||
|
||||
worldName = newWorldName;
|
||||
lodDimensions = new Hashtable<DimensionType, LodDimension>();
|
||||
isWorldLoaded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the worldName to "No world loaded"
|
||||
* and clear the lodDimensions Map. <br>
|
||||
* This should be done whenever unloaded a world.
|
||||
*/
|
||||
public void deselectWorld()
|
||||
{
|
||||
worldName = NO_WORLD_LOADED;
|
||||
lodDimensions = null;
|
||||
isWorldLoaded = false;
|
||||
}
|
||||
|
||||
|
||||
public void addLodDimension(LodDimension newStorage)
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
|
||||
|
||||
lodDimensions.put(newStorage.dimension, newStorage);
|
||||
}
|
||||
|
||||
public LodDimension getLodDimension(DimensionType dimension)
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
|
||||
|
||||
return lodDimensions.get(dimension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the max width in regions that each LodDimension
|
||||
* should use.
|
||||
*/
|
||||
public void resizeDimensionRegionWidth(int newWidth)
|
||||
{
|
||||
if (lodDimensions == null)
|
||||
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
|
||||
|
||||
for(DimensionType key : lodDimensions.keySet())
|
||||
lodDimensions.get(key).setRegionWidth(newWidth);
|
||||
}
|
||||
|
||||
|
||||
public boolean getIsWorldLoaded()
|
||||
{
|
||||
return isWorldLoaded;
|
||||
}
|
||||
|
||||
public String getWorldName()
|
||||
{
|
||||
return worldName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "World name: " + worldName;
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.objects;
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
@@ -8,7 +8,7 @@ import net.minecraft.client.renderer.BufferBuilder;
|
||||
* and BuildBufferThread.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 02-21-2021
|
||||
* @version 03-25-2021
|
||||
*/
|
||||
public class NearFarBuffer
|
||||
{
|
||||
@@ -16,7 +16,10 @@ public class NearFarBuffer
|
||||
|
||||
public BufferBuilder farBuffer;
|
||||
|
||||
|
||||
/**
|
||||
* @param newNearBuffer
|
||||
* @param newFarBuffer
|
||||
*/
|
||||
public NearFarBuffer(BufferBuilder newNearBuffer, BufferBuilder newFarBuffer)
|
||||
{
|
||||
nearBuffer = newNearBuffer;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package com.backsun.lod.objects;
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
|
||||
/**
|
||||
* This object is just a replacement for an array
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
/**
|
||||
* This object is similar to ChunkPos or BlockPos.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 03-19-2021
|
||||
*/
|
||||
public class RegionPos
|
||||
{
|
||||
public int x;
|
||||
public int z;
|
||||
|
||||
|
||||
/**
|
||||
* Default Constructor <br>
|
||||
*
|
||||
* Sets x and z to 0
|
||||
*/
|
||||
public RegionPos()
|
||||
{
|
||||
x = 0;
|
||||
z = 0;
|
||||
}
|
||||
|
||||
public RegionPos(int newX, int newZ)
|
||||
{
|
||||
x = newX;
|
||||
z = newZ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.seibel.lod.proxy;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.seibel.lod.builders.LodBufferBuilder;
|
||||
import com.seibel.lod.builders.LodBuilder;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.objects.LodWorld;
|
||||
import com.seibel.lod.render.LodRender;
|
||||
import com.seibel.lod.util.LodConfig;
|
||||
import com.seibel.lod.util.LodUtils;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
/**
|
||||
* This handles all events sent to the client,
|
||||
* and is the starting point for most of this program.
|
||||
*
|
||||
* @author James_Seibel
|
||||
* @version 05-31-2021
|
||||
*/
|
||||
public class ClientProxy
|
||||
{
|
||||
public static final Logger LOGGER = LogManager.getLogger("LOD");
|
||||
|
||||
private static LodWorld lodWorld = new LodWorld();
|
||||
private static LodBuilder lodBuilder = new LodBuilder();
|
||||
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodBuilder);
|
||||
private static LodRender renderer = new LodRender(lodBufferBuilder);
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
public ClientProxy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// render event //
|
||||
//==============//
|
||||
|
||||
/**
|
||||
* Do any setup that is required to draw LODs
|
||||
* and then tell the LodRenderer to draw.
|
||||
*/
|
||||
public void renderLods(float partialTicks)
|
||||
{
|
||||
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
|
||||
return;
|
||||
|
||||
// update each regions' width to match the new render distance
|
||||
int newWidth = Math.max(4,
|
||||
// TODO is this logic good?
|
||||
(mc.gameSettings.renderDistanceChunks * LodChunk.WIDTH * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()) / LodRegion.SIZE
|
||||
);
|
||||
if (lodBuilder.regionWidth != newWidth)
|
||||
{
|
||||
lodWorld.resizeDimensionRegionWidth(newWidth);
|
||||
lodBuilder.regionWidth = newWidth;
|
||||
|
||||
// skip this frame, hopefully the lodWorld
|
||||
// should have everything set up by then
|
||||
return;
|
||||
}
|
||||
|
||||
LodDimension lodDim = lodWorld.getLodDimension(mc.player.world.getDimensionType());
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
|
||||
// offset the regions
|
||||
double playerX = mc.player.getPosX();
|
||||
double playerZ = mc.player.getPosZ();
|
||||
|
||||
int xOffset = ((int)playerX / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterX();
|
||||
int zOffset = ((int)playerZ / (LodChunk.WIDTH * LodRegion.SIZE)) - lodDim.getCenterZ();
|
||||
|
||||
if (xOffset != 0 || zOffset != 0)
|
||||
{
|
||||
lodDim.move(xOffset, zOffset);
|
||||
}
|
||||
|
||||
|
||||
// TODO for testing
|
||||
// LodConfig.CLIENT.debugMode.set(false);
|
||||
// LodConfig.CLIENT.lodDetail.set(LodDetail.DOUBLE);
|
||||
// LodConfig.CLIENT.lodColorStyle.set(LodColorStyle.INDIVIDUAL_SIDES);
|
||||
// LodConfig.CLIENT.lodChunkRadiusMultiplier.set(12);
|
||||
|
||||
// Note to self:
|
||||
// if "unspecified" shows up in the pie chart, it is
|
||||
// possibly because the amount of time between sections
|
||||
// is too small for the profile to measure
|
||||
IProfiler profiler = mc.getProfiler();
|
||||
profiler.endSection(); // get out of "terrain"
|
||||
profiler.startSection("LOD");
|
||||
|
||||
renderer.drawLODs(lodDim, partialTicks, mc.getProfiler());
|
||||
|
||||
profiler.endSection(); // end LOD
|
||||
profiler.startSection("terrain"); // restart terrain
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// forge events //
|
||||
//==============//
|
||||
|
||||
@SubscribeEvent
|
||||
public void chunkLoadEvent(ChunkEvent.Load event)
|
||||
{
|
||||
lodBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld());
|
||||
}
|
||||
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldLoadEvent(WorldEvent.Load event)
|
||||
{
|
||||
// the player just loaded a new world/dimension
|
||||
lodWorld.selectWorld(LodUtils.getWorldID(event.getWorld()));
|
||||
// make sure the correct LODs are being rendered
|
||||
// (if this isn't done the previous world's LODs may be drawn)
|
||||
renderer.regenerateLODsNextFrame();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void worldUnloadEvent(WorldEvent.Unload event)
|
||||
{
|
||||
// the player just unloaded a world/dimension
|
||||
|
||||
if(mc.getConnection().getWorld() == null)
|
||||
{
|
||||
lodBufferBuilder.numberOfChunksWaitingToGenerate = 0;
|
||||
// the player has disconnected from a server
|
||||
lodWorld.deselectWorld();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SubscribeEvent
|
||||
public void blockChangeEvent(BlockEvent event)
|
||||
{
|
||||
if (event.getClass() == BlockEvent.BreakEvent.class ||
|
||||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
|
||||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
|
||||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
|
||||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
|
||||
{
|
||||
// recreate the LOD where the blocks were changed
|
||||
lodBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// public getters //
|
||||
//================//
|
||||
|
||||
public static LodWorld getLodWorld()
|
||||
{
|
||||
return lodWorld;
|
||||
}
|
||||
|
||||
public static LodBuilder getLodBuilder()
|
||||
{
|
||||
return lodBuilder;
|
||||
}
|
||||
|
||||
public static LodRender getRenderer()
|
||||
{
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
+163
-334
@@ -1,28 +1,23 @@
|
||||
package com.backsun.lod.renderer;
|
||||
package com.seibel.lod.render;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.backsun.lod.builders.LodBufferBuilder;
|
||||
import com.backsun.lod.handlers.ReflectionHandler;
|
||||
import com.backsun.lod.objects.LodChunk;
|
||||
import com.backsun.lod.objects.LodDimension;
|
||||
import com.backsun.lod.objects.NearFarBuffer;
|
||||
import com.backsun.lod.objects.NearFarFogSetting;
|
||||
import com.backsun.lod.util.LodConfig;
|
||||
import com.backsun.lod.util.enums.ColorDirection;
|
||||
import com.backsun.lod.util.enums.FogDistance;
|
||||
import com.backsun.lod.util.enums.FogQuality;
|
||||
import com.backsun.lod.util.enums.LodCorner;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.seibel.lod.builders.LodBufferBuilder;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
import com.seibel.lod.handlers.ReflectionHandler;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.NearFarBuffer;
|
||||
import com.seibel.lod.objects.NearFarFogSetting;
|
||||
import com.seibel.lod.util.LodConfig;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.player.ClientPlayerEntity;
|
||||
@@ -35,7 +30,6 @@ import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.potion.Effects;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
@@ -43,13 +37,13 @@ import net.minecraft.util.math.vector.Vector3f;
|
||||
|
||||
|
||||
/**
|
||||
* This is where all the magic happens.
|
||||
* This is where all the magic happens. <br>
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2-27-2021
|
||||
* @version 05-08-2021
|
||||
*/
|
||||
public class LodRenderer
|
||||
public class LodRender
|
||||
{
|
||||
/** this is the light used when rendering the LODs,
|
||||
* it should be something different than what is used by Minecraft */
|
||||
@@ -63,36 +57,23 @@ public class LodRenderer
|
||||
private GameRenderer gameRender;
|
||||
private IProfiler profiler;
|
||||
private float farPlaneDistance;
|
||||
/** this is the radius of the LODs */
|
||||
private static final int LOD_CHUNK_DISTANCE_RADIUS = 6;
|
||||
|
||||
private ReflectionHandler reflectionHandler;
|
||||
|
||||
public LodDimension lodDimension = null;
|
||||
|
||||
|
||||
/** This is used to generate the buildable buffers */
|
||||
private LodBufferBuilder lodBufferBuilder = null;
|
||||
private LodBufferBuilder lodBufferBuilder;
|
||||
|
||||
/** The buffers that are used to draw LODs using near fog */
|
||||
private volatile BufferBuilder drawableNearBuffer = null;
|
||||
private volatile BufferBuilder drawableNearBuffer;
|
||||
/** The buffers that are used to draw LODs using far fog */
|
||||
private volatile BufferBuilder drawableFarBuffer = null;
|
||||
|
||||
/** The buffers that are used to create LODs using near fog */
|
||||
private volatile BufferBuilder buildableNearBuffer = null;
|
||||
/** The buffers that are used to create LODs using far fog */
|
||||
private volatile BufferBuilder buildableFarBuffer = null;
|
||||
private volatile BufferBuilder drawableFarBuffer;
|
||||
|
||||
/** This is the VertexBuffer used to draw any LODs that use near fog */
|
||||
private volatile VertexBuffer nearVbo = null;
|
||||
private volatile VertexBuffer nearVbo;
|
||||
/** This is the VertexBuffer used to draw any LODs that use far fog */
|
||||
private volatile VertexBuffer farVbo = null;
|
||||
private volatile VertexBuffer farVbo;
|
||||
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
|
||||
|
||||
/** This holds the thread used to generate new LODs off the main thread. */
|
||||
private ExecutorService genThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int previousChunkRenderDistance = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
@@ -105,24 +86,18 @@ public class LodRenderer
|
||||
/** if this is true the LOD buffers should be regenerated,
|
||||
* provided they aren't already being regenerated. */
|
||||
private boolean regen = false;
|
||||
/** if this is true the LOD buffers are currently being
|
||||
* regenerated. */
|
||||
private volatile boolean regenerating = false;
|
||||
/** if this is true new LOD buffers have been generated
|
||||
* and are waiting to be swapped with the drawable buffers*/
|
||||
private volatile boolean switchBuffers = false;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public LodRenderer()
|
||||
public LodRender(LodBufferBuilder newLodBufferBuilder)
|
||||
{
|
||||
mc = Minecraft.getInstance();
|
||||
gameRender = mc.gameRenderer;
|
||||
|
||||
reflectionHandler = new ReflectionHandler();
|
||||
lodBufferBuilder = newLodBufferBuilder;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,9 +108,10 @@ public class LodRenderer
|
||||
* @param newDimension The dimension to draw, if null doesn't replace the current dimension.
|
||||
* @param partialTicks how far into the current tick this method was called.
|
||||
*/
|
||||
public void drawLODs(LodDimension newDimension, float partialTicks, IProfiler newProfiler)
|
||||
@SuppressWarnings("deprecation")
|
||||
public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler)
|
||||
{
|
||||
if (lodDimension == null && newDimension == null)
|
||||
if (lodDim == null)
|
||||
{
|
||||
// if there aren't any loaded LodChunks
|
||||
// don't try drawing anything
|
||||
@@ -151,11 +127,7 @@ public class LodRenderer
|
||||
// initial setup //
|
||||
//===============//
|
||||
|
||||
|
||||
// used for debugging and viewing how long different processes take
|
||||
profiler = newProfiler;
|
||||
profiler.endSection();
|
||||
profiler.startSection("LOD");
|
||||
profiler.startSection("LOD setup");
|
||||
|
||||
ClientPlayerEntity player = mc.player;
|
||||
@@ -164,8 +136,7 @@ public class LodRenderer
|
||||
if ((int)player.getPosX() / LodChunk.WIDTH != prevChunkX ||
|
||||
(int)player.getPosZ() / LodChunk.WIDTH != prevChunkZ ||
|
||||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks ||
|
||||
prevFogDistance != LodConfig.CLIENT.fogDistance.get() ||
|
||||
lodDimension != newDimension)
|
||||
prevFogDistance != LodConfig.CLIENT.fogDistance.get())
|
||||
{
|
||||
// yes
|
||||
regen = true;
|
||||
@@ -181,42 +152,25 @@ public class LodRenderer
|
||||
// the dimension is the same
|
||||
}
|
||||
|
||||
lodDimension = newDimension;
|
||||
if (lodDimension == null)
|
||||
// did the user change the debug setting?
|
||||
if (LodConfig.CLIENT.debugMode.get() != debugging)
|
||||
{
|
||||
// if there aren't any loaded LodChunks
|
||||
// don't try drawing anything
|
||||
return;
|
||||
debugging = LodConfig.CLIENT.debugMode.get();
|
||||
regen = true;
|
||||
}
|
||||
|
||||
if (LodConfig.CLIENT.drawCheckerBoard.get())
|
||||
{
|
||||
if (debugging != LodConfig.CLIENT.drawCheckerBoard.get())
|
||||
regen = true;
|
||||
debugging = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (debugging != LodConfig.CLIENT.drawCheckerBoard.get())
|
||||
regen = true;
|
||||
debugging = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// determine how far the game's render distance is currently set
|
||||
int renderDistWidth = mc.gameSettings.renderDistanceChunks;
|
||||
farPlaneDistance = renderDistWidth * LodChunk.WIDTH;
|
||||
|
||||
// set how big the LODs will be and how far they will go
|
||||
int totalLength = (int) farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2;
|
||||
int totalLength = (int) farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2;
|
||||
int numbChunksWide = (totalLength / LodChunk.WIDTH);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// create the LODs //
|
||||
//=================//
|
||||
@@ -226,32 +180,28 @@ public class LodRenderer
|
||||
// 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)
|
||||
if (regen && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvaliable())
|
||||
{
|
||||
regenerating = true;
|
||||
|
||||
if (lodBufferBuilder == null)
|
||||
lodBufferBuilder = new LodBufferBuilder();
|
||||
|
||||
// this will mainly happen when the view distance is changed
|
||||
if (drawableNearBuffer == null || drawableFarBuffer == null ||
|
||||
previousChunkRenderDistance != mc.gameSettings.renderDistanceChunks)
|
||||
setupBuffers(numbChunksWide);
|
||||
|
||||
// generate the LODs on a separate thread to prevent stuttering or freezing
|
||||
generateLodBuffersAsync(player.getPosX(), player.getPosZ(), numbChunksWide);
|
||||
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, player.getPosX(), player.getPosZ(), numbChunksWide);
|
||||
|
||||
// the regen process has been started
|
||||
// the regen process has been started,
|
||||
// it will be done when lodBufferBuilder.newBuffersAvaliable
|
||||
// is true
|
||||
regen = false;
|
||||
}
|
||||
|
||||
// replace the buffers used to draw and build,
|
||||
// this is only done when the createLodBufferGenerationThread
|
||||
// has finished executing on a parallel thread.
|
||||
if (switchBuffers)
|
||||
if (lodBufferBuilder.newBuffersAvaliable())
|
||||
{
|
||||
swapBuffers();
|
||||
switchBuffers = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -270,13 +220,27 @@ public class LodRenderer
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
// disable the lights Minecraft uses
|
||||
GL11.glDisable(GL11.GL_LIGHT0);
|
||||
GL11.glDisable(GL11.GL_LIGHT1);
|
||||
|
||||
// get the default projection matrix so we can
|
||||
// reset it after drawing the LODs
|
||||
float[] defaultProjMatrix = new float[16];
|
||||
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix);
|
||||
|
||||
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
|
||||
|
||||
setupProjectionMatrix(partialTicks);
|
||||
setupLighting(partialTicks);
|
||||
setupLighting(lodDim, partialTicks);
|
||||
|
||||
NearFarFogSetting fogSetting = determineFogSettings();
|
||||
|
||||
|
||||
// determine the current fog settings so they can be
|
||||
// reset after drawing the LODs
|
||||
float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START);
|
||||
float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END);
|
||||
int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE);
|
||||
|
||||
|
||||
|
||||
@@ -287,6 +251,7 @@ public class LodRenderer
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
profiler.endStartSection("LOD draw");
|
||||
|
||||
setupFog(fogSetting.nearFogSetting, reflectionHandler.getFogQuality());
|
||||
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
|
||||
@@ -296,73 +261,48 @@ public class LodRenderer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
|
||||
profiler.endStartSection("LOD cleanup");
|
||||
|
||||
// this must be done otherwise other parts of the screen may be drawn with a fog effect
|
||||
// IE the GUI
|
||||
FogRenderer.resetFog();
|
||||
RenderSystem.disableFog();
|
||||
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
|
||||
GL11.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
// re-enable the lights Minecraft uses
|
||||
GL11.glEnable(GL11.GL_LIGHT0);
|
||||
GL11.glEnable(GL11.GL_LIGHT1);
|
||||
RenderSystem.disableLighting();
|
||||
|
||||
// this can't be called until after the buffers are built
|
||||
// because otherwise the buffers may be set to the wrong size
|
||||
previousChunkRenderDistance = mc.gameSettings.renderDistanceChunks;
|
||||
|
||||
// reset the fog settings so the normal chunks
|
||||
// will be drawn correctly
|
||||
RenderSystem.fogStart(defaultFogStartDist);
|
||||
RenderSystem.fogEnd(defaultFogEndDist);
|
||||
RenderSystem.fogMode(defaultFogMode);
|
||||
|
||||
// end of profiler tracking
|
||||
// reset the projection matrix so anything drawn after
|
||||
// the LODs will use the correct projection matrix
|
||||
Matrix4f mvm = new Matrix4f(defaultProjMatrix);
|
||||
mvm.transpose();
|
||||
gameRender.resetProjectionMatrix(mvm);
|
||||
|
||||
// clear the depth buffer so anything drawn is drawn
|
||||
// over the LODs
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
|
||||
// end of internal LOD profiling
|
||||
profiler.endSection();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the model view matrix to move the LODs
|
||||
* from object space into world space.
|
||||
*/
|
||||
private Matrix4f generateModelViewMatrix(float partialTicks)
|
||||
{
|
||||
// get all relevant camera info
|
||||
ActiveRenderInfo renderInfo = mc.gameRenderer.getActiveRenderInfo();
|
||||
Vector3d projectedView = renderInfo.getProjectedView();
|
||||
double cameraX = projectedView.x;
|
||||
double cameraY = projectedView.y;
|
||||
double cameraZ = projectedView.z;
|
||||
|
||||
|
||||
// generate the model view matrix
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.push();
|
||||
// translate and rotate to the current camera location
|
||||
matrixStack.rotate(Vector3f.XP.rotationDegrees(renderInfo.getPitch()));
|
||||
matrixStack.rotate(Vector3f.YP.rotationDegrees(renderInfo.getYaw() + 180));
|
||||
matrixStack.translate(-cameraX, -cameraY, -cameraZ);
|
||||
|
||||
|
||||
// this isn't a great solution to nausea or portal spinning
|
||||
// but i'm not sure what to do otherwise
|
||||
float f = 0.23f * MathHelper.lerp(partialTicks, this.mc.player.prevTimeInPortal, this.mc.player.timeInPortal) * this.mc.gameSettings.screenEffectScale * this.mc.gameSettings.screenEffectScale;
|
||||
if (f > 0.0F) {
|
||||
int i = this.mc.player.isPotionActive(Effects.NAUSEA) ? 7 : 20;
|
||||
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
|
||||
f1 = f1 * f1;
|
||||
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_2 / 2.0F, MathHelper.SQRT_2 / 2.0F);
|
||||
matrixStack.rotate(vector3f.rotationDegrees((mc.gameRenderer.rendererUpdateCount + partialTicks) * i));
|
||||
matrixStack.scale(1.01F / f1, 1.0F, 1.0F);
|
||||
float f2 = -(mc.gameRenderer.rendererUpdateCount + partialTicks) * i;
|
||||
matrixStack.rotate(vector3f.rotationDegrees(f2));
|
||||
}
|
||||
|
||||
|
||||
return matrixStack.getLast().getMatrix();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is where the actual drawing happens.
|
||||
@@ -374,15 +314,12 @@ public class LodRenderer
|
||||
if (vbo == null)
|
||||
return;
|
||||
|
||||
profiler.endStartSection("LOD draw setup");
|
||||
vbo.bindBuffer();
|
||||
// 0L is the starting pointer, and the value doesn't appear to matter
|
||||
// 0L is the starting pointer
|
||||
LOD_VERTEX_FORMAT.setupBufferState(0L);
|
||||
|
||||
profiler.endStartSection("LOD draw");
|
||||
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
|
||||
|
||||
profiler.endStartSection("LOD draw cleanup");
|
||||
VertexBuffer.unbindBuffer();
|
||||
LOD_VERTEX_FORMAT.clearBufferState();
|
||||
}
|
||||
@@ -424,8 +361,8 @@ public class LodRenderer
|
||||
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogEnd(farPlaneDistance * 0.3f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.35f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.75f);
|
||||
RenderSystem.fogStart(farPlaneDistance * 1.95f);
|
||||
}
|
||||
else if(fogQuality == FogQuality.FAST)
|
||||
{
|
||||
@@ -441,13 +378,13 @@ public class LodRenderer
|
||||
{
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.78f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.0f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.85f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.0f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
}
|
||||
else if(fogQuality == FogQuality.FAST)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.5f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogEnd(farPlaneDistance * 0.75f * LOD_CHUNK_DISTANCE_RADIUS);
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.5f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
RenderSystem.fogEnd(farPlaneDistance * 0.75f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,21 +393,82 @@ public class LodRenderer
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the model view matrix to move the LODs
|
||||
* from object space into world space.
|
||||
*/
|
||||
private Matrix4f generateModelViewMatrix(float partialTicks)
|
||||
{
|
||||
// get all relevant camera info
|
||||
ActiveRenderInfo renderInfo = mc.gameRenderer.getActiveRenderInfo();
|
||||
Vector3d projectedView = renderInfo.getProjectedView();
|
||||
|
||||
|
||||
// generate the model view matrix
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.push();
|
||||
// translate and rotate to the current camera location
|
||||
matrixStack.rotate(Vector3f.XP.rotationDegrees(renderInfo.getPitch()));
|
||||
matrixStack.rotate(Vector3f.YP.rotationDegrees(renderInfo.getYaw() + 180));
|
||||
matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z);
|
||||
|
||||
return matrixStack.getLast().getMatrix();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create a new projection matrix and send it over to the GPU
|
||||
* <br><br>
|
||||
* A lot of this code is copied from renderWorld (line 578)
|
||||
* in the GameRender class. The code copied is anything with
|
||||
* a matrixStack and is responsible for making sure the LOD
|
||||
* objects distort correctly relative to the rest of the world.
|
||||
* Distortions are caused by: standing in a nether portal,
|
||||
* nausea potion effect, walking bobbing.
|
||||
*
|
||||
* @param partialTicks how many ticks into the frame we are
|
||||
*/
|
||||
private void setupProjectionMatrix(float partialTicks)
|
||||
{
|
||||
// Note: if the LOD objects don't distort correctly
|
||||
// compared to regular minecraft terrain, make sure
|
||||
// all the transformations in renderWorld are here too
|
||||
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.push();
|
||||
|
||||
gameRender.hurtCameraEffect(matrixStack, partialTicks);
|
||||
if (this.mc.gameSettings.viewBobbing) {
|
||||
gameRender.applyBobbing(matrixStack, partialTicks);
|
||||
}
|
||||
|
||||
// potion and nausea effects
|
||||
float f = MathHelper.lerp(partialTicks, mc.player.prevTimeInPortal, mc.player.timeInPortal) * mc.gameSettings.screenEffectScale * mc.gameSettings.screenEffectScale;
|
||||
if (f > 0.0F) {
|
||||
int i = this.mc.player.isPotionActive(Effects.NAUSEA) ? 7 : 20;
|
||||
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
|
||||
f1 = f1 * f1;
|
||||
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_2 / 2.0F, MathHelper.SQRT_2 / 2.0F);
|
||||
matrixStack.rotate(vector3f.rotationDegrees((gameRender.rendererUpdateCount + partialTicks) * i));
|
||||
matrixStack.scale(1.0F / f1, 1.0F, 1.0F);
|
||||
float f2 = -(gameRender.rendererUpdateCount + partialTicks) * i;
|
||||
matrixStack.rotate(vector3f.rotationDegrees(f2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this projection matrix allows us to see past the normal
|
||||
// world render distance
|
||||
Matrix4f projectionMatrix =
|
||||
Matrix4f.perspective(
|
||||
getFov(partialTicks, true),
|
||||
(float)this.mc.getMainWindow().getFramebufferWidth() / (float)this.mc.getMainWindow().getFramebufferHeight(),
|
||||
0.5F,
|
||||
this.farPlaneDistance * LOD_CHUNK_DISTANCE_RADIUS * 2);
|
||||
this.farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2);
|
||||
|
||||
// add the screen space distortions
|
||||
projectionMatrix.mul(matrixStack.getLast().getMatrix());
|
||||
gameRender.resetProjectionMatrix(projectionMatrix);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -478,11 +476,12 @@ public class LodRenderer
|
||||
/**
|
||||
* setup the lighting to be used for the LODs
|
||||
*/
|
||||
private void setupLighting(float partialTicks)
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setupLighting(LodDimension lodDimension, float partialTicks)
|
||||
{
|
||||
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.world.getSunBrightness(partialTicks) : 0.2f;
|
||||
float gammaMultiplyer = (float)mc.gameSettings.gamma - 0.5f;
|
||||
float lightStrength = sunBrightness - 0.7f + (gammaMultiplyer * 0.2f);
|
||||
float lightStrength = sunBrightness - 0.4f + (gammaMultiplyer * 0.2f);
|
||||
|
||||
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
|
||||
|
||||
@@ -494,6 +493,7 @@ public class LodRenderer
|
||||
RenderSystem.enableLighting();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create all buffers that will be used.
|
||||
*/
|
||||
@@ -510,11 +510,11 @@ public class LodRenderer
|
||||
drawableNearBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
drawableFarBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
|
||||
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
lodBufferBuilder.setupBuffers(bufferMaxCapacity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -524,183 +524,27 @@ public class LodRenderer
|
||||
// Other Misc Functions //
|
||||
//======================//
|
||||
|
||||
|
||||
/**
|
||||
* @Returns -1 if there are no valid points
|
||||
* If this is called then the next time "drawLODs" is called
|
||||
* the LODs will be regenerated; the same as if the player moved.
|
||||
*/
|
||||
private int getValidHeightPoint(short[] heightPoints)
|
||||
public void regenerateLODsNextFrame()
|
||||
{
|
||||
if (heightPoints[LodCorner.NE.value] != -1)
|
||||
return heightPoints[LodCorner.NE.value];
|
||||
if (heightPoints[LodCorner.NW.value] != -1)
|
||||
return heightPoints[LodCorner.NW.value];
|
||||
if (heightPoints[LodCorner.SE.value] != -1)
|
||||
return heightPoints[LodCorner.NE.value];
|
||||
return heightPoints[LodCorner.NE.value];
|
||||
regen = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 void generateLodBuffersAsync(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;
|
||||
|
||||
// This is something I would like to have done someday
|
||||
// but currently world generation is too slow to have it
|
||||
// here, maybe it could be put in a loop
|
||||
// that happens every tick for a specific number of chunks?
|
||||
// LodChunk tmpLod = new LodChunk();
|
||||
// tmpLod.x = chunkX;
|
||||
// tmpLod.z = chunkZ;
|
||||
// lodDimension.addLod(tmpLod);
|
||||
//
|
||||
// world.getChunkProvider().getChunk(chunkX, chunkZ, true);
|
||||
// System.out.println(chunkX + "," + chunkZ);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// generate our new buildable buffers
|
||||
NearFarBuffer nearFarBuffers = lodBufferBuilder.createBuffers(
|
||||
buildableNearBuffer, buildableFarBuffer,
|
||||
LodConfig.CLIENT.fogDistance.get(), lodArray, colorArray);
|
||||
|
||||
// update our buffers
|
||||
buildableNearBuffer = nearFarBuffers.nearBuffer;
|
||||
buildableFarBuffer = nearFarBuffers.farBuffer;
|
||||
|
||||
// mark the buildable buffers as ready to swap
|
||||
regenerating = false;
|
||||
switchBuffers = true;
|
||||
});
|
||||
|
||||
genThread.execute(t);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Swap buildable and drawable buffers.
|
||||
* Replace the current drawable buffers with the newly
|
||||
* created buffers from the lodBufferBuilder.
|
||||
*/
|
||||
private void swapBuffers()
|
||||
{
|
||||
// swap the BufferBuilders
|
||||
BufferBuilder tmp = buildableNearBuffer;
|
||||
buildableNearBuffer = drawableNearBuffer;
|
||||
drawableNearBuffer = tmp;
|
||||
|
||||
tmp = buildableFarBuffer;
|
||||
buildableFarBuffer = drawableFarBuffer;
|
||||
drawableFarBuffer = tmp;
|
||||
// replace the drawable buffers with
|
||||
// the newly created buffers from the lodBufferBuilder
|
||||
NearFarBuffer newBuffers = lodBufferBuilder.swapBuffers(drawableNearBuffer, drawableFarBuffer);
|
||||
drawableNearBuffer = newBuffers.nearBuffer;
|
||||
drawableFarBuffer = newBuffers.farBuffer;
|
||||
|
||||
|
||||
// bind the buffers with their respective VBOs
|
||||
@@ -719,21 +563,7 @@ public class LodRenderer
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
||||
public double getFov(float partialTicks, boolean useFovSetting)
|
||||
private double getFov(float partialTicks, boolean useFovSetting)
|
||||
{
|
||||
return mc.gameRenderer.getFOVModifier(mc.gameRenderer.getActiveRenderInfo(), partialTicks, useFovSetting);
|
||||
}
|
||||
@@ -748,7 +578,6 @@ public class LodRenderer
|
||||
{
|
||||
NearFarFogSetting fogSetting = new NearFarFogSetting();
|
||||
|
||||
LodConfig.CLIENT.fogDistance.get();
|
||||
switch(reflectionHandler.getFogQuality())
|
||||
{
|
||||
case FANCY:
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.renderer;
|
||||
package com.seibel.lod.render;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.electronwill.nightconfig.core.io.WritingMode;
|
||||
import com.seibel.lod.ModInfo;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.LodColorStyle;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.enums.LodTemplate;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 05-31-2021
|
||||
*/
|
||||
@Mod.EventBusSubscriber
|
||||
public class LodConfig
|
||||
{
|
||||
public static class Client
|
||||
{
|
||||
public ForgeConfigSpec.BooleanValue drawLODs;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
|
||||
|
||||
public ForgeConfigSpec.BooleanValue debugMode;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<LodDetail> lodDetail;
|
||||
|
||||
public ForgeConfigSpec.EnumValue<LodColorStyle> lodColorStyle;
|
||||
|
||||
/** this is multiplied by the default view distance
|
||||
* to determine how far out to generate/render LODs */
|
||||
public ForgeConfigSpec.IntValue lodChunkRadiusMultiplier;
|
||||
|
||||
Client(ForgeConfigSpec.Builder builder)
|
||||
{
|
||||
builder.comment(ModInfo.MODNAME + " configuration settings").push("client");
|
||||
|
||||
drawLODs = builder
|
||||
.comment("\n"
|
||||
+ " If false LODs will not be drawn, \n"
|
||||
+ " however they will still be generated \n"
|
||||
+ " and saved to file for later use.")
|
||||
.define("drawLODs", true);
|
||||
|
||||
fogDistance = builder
|
||||
.comment("\n"
|
||||
+ " At what distance should Fog be drawn on the LODs? \n"
|
||||
+ " If the fog cuts off ubruptly or you are using Optifine's \"fast\" \n"
|
||||
+ " fog option set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ".")
|
||||
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
|
||||
|
||||
debugMode = builder
|
||||
.comment("\n"
|
||||
+ " If false the LODs will draw with their normal world colors. \n"
|
||||
+ " If true they will draw as a black and white checkerboard. \n"
|
||||
+ " This can be used for debugging or imagining you are playing a \n"
|
||||
+ " giant game of chess ;)")
|
||||
.define("drawCheckerBoard", false);
|
||||
|
||||
lodTemplate = builder
|
||||
.comment("\n"
|
||||
+ " How should the LODs be drawn? \n"
|
||||
+ " " + LodTemplate.CUBIC.toString() + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
|
||||
+ " " + LodTemplate.TRIANGULAR.toString() + ": LOD Chunks smoothly transition between other. \n"
|
||||
+ " " + LodTemplate.DYNAMIC.toString() + ": LOD Chunks smoothly transition between other, "
|
||||
+ " " + " unless a neighboring chunk is at a significantly different height. ")
|
||||
.defineEnum("lodTemplate", LodTemplate.CUBIC);
|
||||
|
||||
lodDetail = builder
|
||||
.comment("\n"
|
||||
+ " How detailed should the LODs be? \n"
|
||||
+ " " + LodDetail.SINGLE.toString() + ": render 1 LOD for each Chunk. \n"
|
||||
+ " " + LodDetail.DOUBLE.toString() + ": render 4 LODs for each Chunk.")
|
||||
.defineEnum("lodGeometryQuality", LodDetail.SINGLE);
|
||||
|
||||
lodColorStyle = builder
|
||||
.comment("\n"
|
||||
+ " How should the LODs be colored? \n"
|
||||
+ " " + LodColorStyle.TOP.toString() + ": Use the color from the top of the LOD chunk for all sides. \n"
|
||||
+ " " + LodColorStyle.INDIVIDUAL_SIDES.toString() + ": For each side of the LOD use the color corresponding to that side. ")
|
||||
.defineEnum("lodColorStyle", LodColorStyle.TOP);
|
||||
|
||||
lodChunkRadiusMultiplier = builder
|
||||
.comment("\n"
|
||||
+ " This is multiplied by the default view distance \n"
|
||||
+ " to determine how far out to generate/render LODs. \n"
|
||||
+ " A value of 2 means that there is 1 render distance worth \n"
|
||||
+ " of LODs in each cardinal direction.")
|
||||
.defineInRange("lodChunkRadiusMultiplier", 6, 2, 1023);
|
||||
|
||||
builder.pop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* {@link Path} to the configuration file of this mod
|
||||
*/
|
||||
private static final Path CONFIG_PATH =
|
||||
Paths.get("config", ModInfo.MODID + ".toml");
|
||||
|
||||
public static final ForgeConfigSpec clientSpec;
|
||||
public static final Client CLIENT;
|
||||
static {
|
||||
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
|
||||
clientSpec = specPair.getRight();
|
||||
CLIENT = specPair.getLeft();
|
||||
|
||||
// setup the config file
|
||||
CommentedFileConfig config = CommentedFileConfig.builder(CONFIG_PATH)
|
||||
.writingMode(WritingMode.REPLACE)
|
||||
.build();
|
||||
config.load();
|
||||
config.save();
|
||||
clientSpec.setConfig(config);
|
||||
}
|
||||
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoad(final ModConfig.Loading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Loaded forge config file {}", configEvent.getConfig().getFileName());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onFileChange(final ModConfig.Reloading configEvent)
|
||||
{
|
||||
LogManager.getLogger().debug(ModInfo.MODNAME, "Forge config just got changed on the file system!");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
import net.minecraft.server.integrated.IntegratedServer;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
/**
|
||||
* This class holds methods that may be used in multiple places.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 04-01-2021
|
||||
*/
|
||||
public class LodUtils
|
||||
{
|
||||
private static Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the first valid ServerWorld.
|
||||
*
|
||||
* @return null if there are no ServerWorlds
|
||||
*/
|
||||
public static ServerWorld getFirstValidServerWorld()
|
||||
{
|
||||
if (mc.getIntegratedServer() == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = mc.getIntegratedServer().getWorlds();
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
return world;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ServerWorld for the relevant dimension.
|
||||
*
|
||||
* @return null if there is no ServerWorld for the given dimension
|
||||
*/
|
||||
public static ServerWorld getServerWorldFromDimension(DimensionType dimension)
|
||||
{
|
||||
IntegratedServer server = mc.getIntegratedServer();
|
||||
if (server == null)
|
||||
return null;
|
||||
|
||||
Iterable<ServerWorld> worlds = server.getWorlds();
|
||||
ServerWorld returnWorld = null;
|
||||
|
||||
for (ServerWorld world : worlds)
|
||||
{
|
||||
if(world.getDimensionType() == dimension)
|
||||
{
|
||||
returnWorld = world;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return returnWorld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given ChunkPos into a RegionPos.
|
||||
*/
|
||||
public static RegionPos convertChunkPosToRegionPos(ChunkPos pos)
|
||||
{
|
||||
RegionPos rPos = new RegionPos();
|
||||
rPos.x = pos.x / LodRegion.SIZE;
|
||||
rPos.z = pos.z / LodRegion.SIZE;
|
||||
|
||||
// prevent issues if X/Z is negative and less than 16
|
||||
if (pos.x < 0)
|
||||
{
|
||||
rPos.x = (Math.abs(rPos.x) * -1) - 1;
|
||||
}
|
||||
if (pos.z < 0)
|
||||
{
|
||||
rPos.z = (Math.abs(rPos.z) * -1) - 1;
|
||||
}
|
||||
|
||||
return rPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the given chunk
|
||||
* has any data in it.
|
||||
*/
|
||||
public static boolean chunkHasBlockData(IChunk chunk)
|
||||
{
|
||||
ChunkSection[] blockStorage = chunk.getSections();
|
||||
|
||||
for(ChunkSection section : blockStorage)
|
||||
{
|
||||
if(section != null && !section.isEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String getCurrentDimensionID()
|
||||
{
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
// this will return the world save location
|
||||
// and the dimension folder
|
||||
|
||||
if(mc.world == null)
|
||||
return "";
|
||||
|
||||
ServerWorld serverWorld = LodUtils.getServerWorldFromDimension(mc.world.getDimensionType());
|
||||
if(serverWorld == null)
|
||||
return "";
|
||||
|
||||
ServerChunkProvider provider = serverWorld.getChunkProvider();
|
||||
if(provider == null)
|
||||
return "";
|
||||
|
||||
return provider.getSavedData().folder.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData server = mc.getCurrentServerData();
|
||||
return server.serverName + ", IP " +
|
||||
server.serverIP + ", GameVersion " +
|
||||
server.gameVersion.getString() + "\\"
|
||||
+ "dim_" + mc.world.getDimensionType().getEffects().getPath() + "\\";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If on single player this will return the name of the user's
|
||||
* world and the dimensional save folder, if in multiplayer
|
||||
* it will return the server name, game version, and dimension.<br>
|
||||
* <br>
|
||||
* This can be used to determine where to save files for a given
|
||||
* dimension.
|
||||
*/
|
||||
public static String getDimensionIDFromWorld(IWorld world)
|
||||
{
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
// this will return the world save location
|
||||
// and the dimension folder
|
||||
|
||||
ServerWorld serverWorld = LodUtils.getServerWorldFromDimension(world.getDimensionType());
|
||||
if(serverWorld == null)
|
||||
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerWorld for the dimension " + world.getDimensionType().getEffects().getPath());
|
||||
|
||||
ServerChunkProvider provider = serverWorld.getChunkProvider();
|
||||
if(provider == null)
|
||||
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + world.getDimensionType().getEffects().getPath());
|
||||
|
||||
return provider.getSavedData().folder.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData server = mc.getCurrentServerData();
|
||||
return server.serverName + ", IP " +
|
||||
server.serverIP + ", GameVersion " +
|
||||
server.gameVersion.getString() + "\\"
|
||||
+ "dim_" + world.getDimensionType().getEffects().getPath() + "\\";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If on single player this will return the name of the user's
|
||||
* world, if in multiplayer it will return the server name
|
||||
* and game version.
|
||||
*/
|
||||
public static String getWorldID(IWorld world)
|
||||
{
|
||||
if(mc.isIntegratedServerRunning())
|
||||
{
|
||||
// chop off the dimension ID as it is not needed/wanted
|
||||
String dimId = getDimensionIDFromWorld(world);
|
||||
|
||||
// get the world name
|
||||
int saveIndex = dimId.indexOf("saves") + 1 + "saves".length();
|
||||
int slashIndex = dimId.indexOf('\\', saveIndex);
|
||||
dimId = dimId.substring(saveIndex, slashIndex);
|
||||
return dimId;
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerData server = mc.getCurrentServerData();
|
||||
return server.serverName + ", IP " +
|
||||
server.serverIP + ", GameVersion " +
|
||||
server.gameVersion.getString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.backsun.lod.util;
|
||||
package com.seibel.lod.util;
|
||||
|
||||
/**
|
||||
* This holds meta information about the mod.
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
package com.backsun.lod.util;
|
||||
package com.seibel.lod.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import com.backsun.lod.util.enums.FogQuality;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
@@ -1,34 +1,23 @@
|
||||
# Note: to update code in eclipse run the "eclipse" command in graldew
|
||||
|
||||
|
||||
# make public the method getFOVModifier
|
||||
# used when creating the projection matrix
|
||||
public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D # getFOVModifier
|
||||
public net.minecraft.client.renderer.GameRenderer field_78529_t # rendererUpdateCount
|
||||
public net.minecraft.client.renderer.GameRenderer func_228380_a_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # hurtCameraEffect
|
||||
public net.minecraft.client.renderer.GameRenderer func_228383_b_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # applyBobbing
|
||||
|
||||
# make public the byteBuffer in BufferBuilder
|
||||
# used when accessing built byteBuffers
|
||||
public net.minecraft.client.renderer.BufferBuilder field_179001_a # byteBuffer
|
||||
|
||||
# make public the cameraZoom in the GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_78503_V # cameraZoom
|
||||
|
||||
# make public the cameraYaw in the GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_228376_w_ # cameraYaw
|
||||
|
||||
# make public the cameraPitch in the GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_228377_x_ # cameraPitch
|
||||
|
||||
# make public the folder in the DimensionSavedDataManager
|
||||
# used when determining where to save files too
|
||||
public net.minecraft.world.storage.DimensionSavedDataManager field_215759_d # folder
|
||||
|
||||
# make public the ambiantLight field in DimensionType
|
||||
public net.minecraft.world.DimensionType field_236017_x_ # ambientLight
|
||||
|
||||
# make public the renderUpdateCount in GameRenderer
|
||||
public net.minecraft.client.renderer.GameRenderer field_78529_t # rendererUpdateCount
|
||||
|
||||
# make public the materialColor in AbstractBlockState
|
||||
# used when generating LodChunks
|
||||
public net.minecraft.block.AbstractBlock$AbstractBlockState field_235704_h_ # materialColor
|
||||
|
||||
|
||||
|
||||
#=====================#
|
||||
# Examples from Forge #
|
||||
#=====================#
|
||||
|
||||
@@ -24,7 +24,7 @@ modId="lod" #mandatory
|
||||
#// The version number of the mod - there's a few well known ${} variables useable here or just hardcode it
|
||||
#//${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata
|
||||
#// see the associated build.gradle script for how to populate this completely automatically during a build
|
||||
version="a1" #mandatory
|
||||
version="a1.2" #mandatory
|
||||
|
||||
#// A display name for the mod
|
||||
displayName="Levels of Detail" #mandatory
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"required": true,
|
||||
"package": "com.backsun.lod.mixin",
|
||||
"package": "com.seibel.lod.mixin",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"refmap": "lod.refmap.json",
|
||||
"mixins": [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"modid": "lod",
|
||||
"name": "Level Of Details",
|
||||
"description": "Generates and renders simplified chunks beyond the normal view distance, at a low performance cost.",
|
||||
"version": "a1",
|
||||
"version": "a1.2",
|
||||
"mcversion": "1.16.4",
|
||||
"url": "",
|
||||
"updateUrl": "",
|
||||
|
||||
Reference in New Issue
Block a user