Remove the old LOD objects

This commit is contained in:
James Seibel
2021-08-11 07:33:51 -05:00
parent 412f1bead0
commit d1417069d9
15 changed files with 2 additions and 4374 deletions
@@ -1,432 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import org.lwjgl.opengl.GL11;
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarBuffer;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.WorldWorkerManager;
/**
* This object is used to create NearFarBuffer objects.
*
* @author James Seibel
* @version 07-25-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 LodChunkBuilder lodChunkBuilder;
/** The buffers that are used to create LODs using near fog */
public volatile BufferBuilder buildableNearBuffer;
/** The buffers that are used to create LODs using far fog */
public volatile BufferBuilder buildableFarBuffer;
/** if this is true the LOD buffers are currently being
* regenerated. */
public volatile boolean generatingBuffers = false;
/** if this is true new LOD buffers have been generated
* and are waiting to be swapped with the drawable buffers*/
private volatile boolean switchBuffers = false;
/** This keeps track of how many chunk generation requests are on going.
* This is to prevent chunks from being generated for a long time in an area
* the player is no longer in. */
public volatile AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(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).
* I multiply by 8 to make sure there is always a buffer of chunk requests,
* to make sure the CPU is always busy and we can generate LODs as quickly as
* possible. */
public int maxChunkGenRequests = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8;
public LodBufferBuilder(LodChunkBuilder newLodBuilder)
{
mc = Minecraft.getInstance();
lodChunkBuilder = newLodBuilder;
}
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawable near and far buffers.
* <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(LodRenderer 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 / LodUtil.CHUNK_WIDTH) * LodUtil.CHUNK_WIDTH;
int playerZChunkOffset = ((int) playerZ / LodUtil.CHUNK_WIDTH) * LodUtil.CHUNK_WIDTH;
// this is where we will start drawing squares
// (exactly half the total width)
int startX = (-LodUtil.CHUNK_WIDTH * (numbChunksWide / 2)) + playerXChunkOffset;
int startZ = (-LodUtil.CHUNK_WIDTH * (numbChunksWide / 2)) + playerZChunkOffset;
Thread thread = new Thread(()->
{
// index of the chunk currently being added to the
// generation list
int chunkGenIndex = 0;
ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests];
// if we don't have a full number of chunks to generate in chunksToGen
// we can top it off from the reserve
ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests];
int minChunkDist = Integer.MAX_VALUE;
ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodUtil.CHUNK_WIDTH, (int)playerZ / LodUtil.CHUNK_WIDTH);
// generate our new buildable buffers
buildableNearBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
buildableFarBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT);
// x axis
for (int i = 0; i < numbChunksWide; i++)
{
// z axis
for (int j = 0; j < numbChunksWide; j++)
{
int chunkX = i + (startX / LodUtil.CHUNK_WIDTH);
int chunkZ = j + (startZ / LodUtil.CHUNK_WIDTH);
// skip any chunks that Minecraft is going to render
if(isCoordInCenterArea(i, j, (numbChunksWide / 2))
&& renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkX, chunkZ)))
{
continue;
}
LodDetail detail = LodConfig.CLIENT.maxDrawDetail.get();
// set where this square will be drawn in the world
double xOffset = (LodUtil.CHUNK_WIDTH * i) + // offset by the number of LOD blocks
startX + // offset so the center LOD block is centered underneath the player
8; //detail.offset; // truncation(?) correction
double yOffset = 0;
double zOffset = (LodUtil.CHUNK_WIDTH * j) + startZ + 8;//detail.offset;
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.get() < maxChunkGenRequests)
{
ChunkPos pos = new ChunkPos(chunkX, chunkZ);
// can be used for debugging
// if (chunksToGen == null)
// {
// chunkGenIndex = 0;
// chunksToGen = new ChunkPos[maxChunkGenRequests];
// }
//
// if (chunkGenIndex < maxChunkGenRequests)
// {
// chunksToGen[chunkGenIndex] = pos;
// chunkGenIndex++;
// }
// determine if this position is closer to the player
// than the previous
int newDistance = playerChunkPos.getChessboardDistance(pos);
// issue #40
// TODO optimize this code,
// using the purely optimized code above we can achieve close to
// 100% CPU utilization, this code generally achieves 40 - 50%
// after a certain point; and I'm sure there is a better data
// structure for this.
if (newDistance < minChunkDist)
{
// this chunk is closer, clear any previous
// positions and update the new minimum distance
minChunkDist = newDistance;
// move all the old chunks into the reserve
ChunkPos[] newReserve = new ChunkPos[maxChunkGenRequests];
int oldToGenIndex = 0;
int oldReserveIndex = 0;
for(int tmpIndex = 0; tmpIndex < newReserve.length; tmpIndex++)
{
// we don't check if the boundaries are good since
// the tmp array will always be the same length
// as chunksToGen and chunksToGenReserve
if (chunksToGen[oldToGenIndex] != null)
{
// add all the closest chunks...
newReserve[tmpIndex] = chunksToGen[oldToGenIndex];
oldToGenIndex++;
}
else if (chunksToGenReserve[oldReserveIndex] != null)
{
// ...then add all the previous reserve chunks
// (which are farther away)
newReserve[tmpIndex] = chunksToGenReserve[oldToGenIndex];
oldReserveIndex++;
}
else
{
// we have moved all the items from
// the old chunksToGen and reserve
break;
}
}
chunksToGenReserve = newReserve;
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++;
}
}
} // lod null and can generate more chunks
// don't render this null/empty chunk
continue;
} // lod null or empty
BufferBuilder currentBuffer = null;
if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2))
currentBuffer = buildableNearBuffer;
else
currentBuffer = buildableFarBuffer;
throw new UnsupportedOperationException("Not Implemented");
// 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);
}
}
// issue #19
// TODO add a way for a server side mod to generate chunks requested here
if(mc.hasSingleplayerServer())
{
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
// make sure we have as many chunks to generate as we are allowed
if (chunkGenIndex < maxChunkGenRequests)
{
for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++)
{
chunksToGen[i] = chunksToGenReserve[j];
}
}
// start chunk generation
for(ChunkPos chunkPos : chunksToGen)
{
if(chunkPos == null)
break;
// make sure we don't generate this chunk again
lodDim.addLod(new LodChunk(chunkPos));
numberOfChunksWaitingToGenerate.addAndGet(1);
LodChunkGenWorker genWorker = new LodChunkGenWorker(chunkPos, renderer, lodChunkBuilder, this, lodDim, serverWorld);
WorldWorkerManager.addWorker(genWorker);
}
}
// finish the buffer building
buildableNearBuffer.end();
buildableFarBuffer.end();
// mark that the buildable buffers as ready to swap
generatingBuffers = false;
switchBuffers = true;
});
genThread.execute(thread);
return;
}
//====================//
// generation helpers //
//====================//
/**
* Returns if the given coordinate is in the loaded area of the world.
* @param centerCoordinate the center of the loaded world
*/
private boolean isCoordInCenterArea(int i, int j, int centerCoordinate)
{
return (i >= centerCoordinate - mc.options.renderDistance
&& i <= centerCoordinate + mc.options.renderDistance)
&&
(j >= centerCoordinate - mc.options.renderDistance
&& j <= centerCoordinate + mc.options.renderDistance);
}
/**
* Find the coordinates that are in the center half of the given
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
*/
private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius)
{
int halfRadius = lodRadius / 2;
return (chunkX >= lodRadius - halfRadius
&& chunkX <= lodRadius + halfRadius)
&&
(chunkZ >= lodRadius - halfRadius
&& chunkZ <= lodRadius + halfRadius);
}
//===============================//
// BufferBuilder related methods //
//===============================//
/**
* Called from the LodRenderer to create the
* BufferBuilders at the right size.
*
* @param bufferMaxCapacity
*/
public void setupBuffers(int bufferMaxCapacity)
{
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
}
/**
* Swap the drawable and buildable buffers and return
* the old drawable buffers.
* @param drawableNearBuffer
* @param drawableFarBuffer
*/
public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer)
{
// swap the BufferBuilders
BufferBuilder tmp = buildableNearBuffer;
buildableNearBuffer = drawableNearBuffer;
drawableNearBuffer = tmp;
tmp = buildableFarBuffer;
buildableFarBuffer = drawableFarBuffer;
drawableFarBuffer = tmp;
// the buffers have been swapped
switchBuffers = false;
return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer);
}
/**
* If this is true the buildable near and far
* buffers have been generated and are ready to be
* sent to the LodRenderer.
*/
public boolean newBuffersAvaliable()
{
return switchBuffers;
}
}
@@ -1,551 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders;
import java.awt.Color;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.AbstractPlantBlock;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.BushBlock;
import net.minecraft.block.GrassBlock;
import net.minecraft.block.IGrowable;
import net.minecraft.block.LeavesBlock;
import net.minecraft.block.material.MaterialColor;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.biome.Biome;
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 8-9-2021
*/
public class LodChunkBuilder
{
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
/** Default size of any LOD regions we use */
public int regionWidth = 5;
public static final int CHUNK_DATA_WIDTH = LodUtil.CHUNK_WIDTH;
public static final int CHUNK_SECTION_HEIGHT = LodUtil.CHUNK_WIDTH;
// reminder now how to access biome info
// String name = biome.getRegistryName().getPath();
// String deepColdName = Biomes.DEEP_COLD_OCEAN.location().getPath();
// Biome deepCold = WorldGenRegistries.BIOME.get(Biomes.DEEP_COLD_OCEAN);
public LodChunkBuilder()
{
}
public void generateLodChunkAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodChunkAsync(chunk, new LodBuilderConfig(), lodWorld, world);
}
public void generateLodChunkAsync(IChunk chunk, LodBuilderConfig config, LodWorld lodWorld, IWorld world)
{
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
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.dimensionType();
LodChunk lod = generateLodFromChunk(chunk, config);
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
{
return generateLodFromChunk(chunk, new LodBuilderConfig());
}
/**
* 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, LodBuilderConfig config) throws IllegalArgumentException
{
if(chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodDetail detail = LodConfig.CLIENT.maxGenerationDetail.get();
LodDataPoint[][] dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
{
int startX = detail.startX[i];
int startZ = detail.startZ[i];
int endX = detail.endX[i];
int endZ = detail.endZ[i];
Color color;
color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
short height;
short depth;
if (!config.useHeightmap)
{
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
}
else
{
height = determineHeightPoint(chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
depth = 0;
}
int x = i / detail.dataPointLengthCount;
int z = i % detail.dataPointLengthCount;
dataPoints[x][z] = new LodDataPoint(height, depth, color);
}
return new LodChunk(chunk.getPos(), dataPoints, detail);
}
//=====================//
// constructor helpers //
//=====================//
/**
* Find the lowest valid point from the bottom.
* @param chunkSections
* @param startX
* @param startZ
* @param endX
* @param endZ
*/
private short determineBottomPointForArea(ChunkSection[] chunkSections,
int startX, int startZ, int endX, int endZ)
{
int numberOfBlocksRequired = ((endX-startX) * (endZ-startZ) / 2);
// search from the bottom up
for(int section = 0; section < CHUNK_DATA_WIDTH; section++)
{
for(int y = 0; y < CHUNK_SECTION_HEIGHT; y++)
{
int numberOfBlocksFound = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
if(isLayerValidLodPoint(chunkSections, section, y, x, z))
{
numberOfBlocksFound++;
if (numberOfBlocksFound >= numberOfBlocksRequired)
{
// 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 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
* @param chunkSections
* @param startX
* @param startZ
* @param endX
* @param endZ
*/
private short determineHeightPointForArea(ChunkSection[] chunkSections,
int startX, int startZ, int endX, int endZ)
{
int numberOfBlocksRequired = ((endX-startX) * (endZ-startZ) / 2);
// search from the top down
for(int section = chunkSections.length - 1; section >= 0; section--)
{
for(int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--)
{
int numberOfBlocksFound = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
if(isLayerValidLodPoint(chunkSections, section, y, x, z))
{
numberOfBlocksFound++;
if (numberOfBlocksFound >= numberOfBlocksRequired)
{
// we found
// enough blocks in this
// layer to count as an
// LOD point
return (short) (y + 1 + (section * CHUNK_SECTION_HEIGHT));
}
}
}
}
}
}
// we never found a valid LOD point
return -1;
}
/**
* Find the highest point from the Top
*/
private short determineHeightPoint(Heightmap heightmap,
int startX, int startZ, int endX, int endZ)
{
short highest = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
short newHeight = (short) heightmap.getFirstAvailable(x, z);
if (newHeight > highest)
highest = newHeight;
}
}
return highest;
}
/**
* Generate the color for the given chunk using biome
* water color, foliage color, and grass color.
*
* @param config_useSolidBlocksInColorGen <br>
* If true we look down from the top of the <br>
* chunk until we find a non-invisible block, and then use <br>
* its color. If false we generate the color immediately for <br>
* each x and z.
* @param config_useBiomeColors <br>
* If true use biome foliage, water, and grass colors, <br>
* otherwise only use the block's material color
*/
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
ChunkSection[] chunkSections = chunk.getSections();
int numbOfBlocks = 0;
int red = 0;
int green = 0;
int blue = 0;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
boolean foundBlock = false;
// go top down
for(int i = chunkSections.length - 1; !foundBlock && i >= 0; i--)
{
if( !foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen))
{
for(int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--)
{
int colorInt = 0;
BlockState blockState = null;
if (chunkSections[i] != null)
{
blockState = chunkSections[i].getBlockState(x, y, z);
colorInt = blockState.materialColor.col;
}
if(colorInt == 0 && config.useSolidBlocksInColorGen)
{
// skip air or invisible blocks
continue;
}
if (config.useBiomeColors)
{
// the bit shift is equivalent to dividing by 4
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2);
if (biome.getBiomeCategory() == Biome.Category.OCEAN ||
biome.getBiomeCategory() == Biome.Category.RIVER)
{
if (config.useSolidBlocksInColorGen &&
blockState != Blocks.WATER.defaultBlockState())
{
// non-water block
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
// water block
colorInt = biome.getWaterColor();
}
}
else if (biome.getBiomeCategory() == Biome.Category.EXTREME_HILLS)
{
colorInt = Blocks.STONE.defaultMaterialColor().col;
}
else if (biome.getBiomeCategory() == Biome.Category.ICY)
{
colorInt = LodUtil.colorToInt(Color.WHITE);
}
else if (biome.getBiomeCategory() == Biome.Category.THEEND)
{
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
}
else if (config.useSolidBlocksInColorGen)
{
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
}
}
else
{
// I have no idea why I need to bit shift to the right, but
// if I don't the biomes don't show up correctly.
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2);
colorInt = getColorForBlock(x,z, blockState, biome);
}
Color c = LodUtil.intToColor(colorInt);
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);
}
/**
* Returns a color int for a given block.
*/
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome)
{
int colorInt = 0;
// block special cases
if (blockState == Blocks.AIR.defaultBlockState())
{
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
}
else if (blockState == Blocks.MYCELIUM.defaultBlockState())
{
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
}
// plant life
else if (blockState.getBlock() instanceof LeavesBlock)
{
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
colorInt = LodUtil.colorToInt(leafColor);
}
else if (blockState.getBlock() instanceof GrassBlock ||
blockState.getBlock() instanceof AbstractPlantBlock ||
blockState.getBlock() instanceof BushBlock ||
blockState.getBlock() instanceof IGrowable)
{
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
}
// water
else if (blockState.getBlock() == Blocks.WATER)
{
colorInt = biome.getWaterColor();
}
// everything else
else
{
colorInt = blockState.materialColor.col;
}
return colorInt;
}
/**
* Is the layer between the given X, Z, and dataIndex
* values a valid LOD point?
*/
private boolean isLayerValidLodPoint(
ChunkSection[] chunkSections,
int sectionIndex, int y,
int x, int 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)
{
return true;
}
}
return false;
}
}
@@ -1,54 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import net.minecraft.client.renderer.BufferBuilder;
/**
* This is the abstract class used to create different
* BufferBuilders.
*
* @author James Seibel
* @version 06-16-2021
*/
public abstract class AbstractLodTemplate
{
public abstract void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
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.vertex(x, y, z).color(red, green, blue, alpha).endVertex();
}
/** Returns in bytes how much buffer memory is required
* for one LOD object */
public abstract int getBufferMemoryForSingleLod(LodDetail detail);
}
@@ -1,187 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
import java.awt.Color;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.enums.ShadingMode;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.util.math.AxisAlignedBB;
/**
* Builds LODs as rectangular prisms.
*
* @author James Seibel
* @version 8-8-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
public CubicLodTemplate()
{
}
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode 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
LodDetail detail = LodConfig.CLIENT.maxDrawDetail.get();
// returns null if the lod is empty at the given location
bbox = generateBoundingBox(
centerLod.getLodDataPoint().height,
centerLod.getLodDataPoint().depth,
detail.dataPointWidth,
xOffset - (centerLod.width / 2),
yOffset,
zOffset - (centerLod.width / 2));
if (bbox != null)
{
addBoundingBoxToBuffer(buffer, bbox, centerLod.getLodDataPoint().color);
}
}
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).move(xOffset, yOffset, zOffset);
}
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color c)
{
Color topColor = c;
Color northSouthColor = c;
Color eastWestColor = c;
Color bottomColor = c;
// darken the bottom and side colors if requested
if (LodConfig.CLIENT.shadingMode.get() == ShadingMode.DARKEN_SIDES)
{
// the side colors are different because
// when using fast lighting in Minecraft the north/south
// and east/west sides are different in a similar way
int northSouthDarkenAmount = 25;
int eastWestDarkenAmount = 50;
int bottomDarkenAmount = 75;
northSouthColor = new Color(Math.max(0, c.getRed() - northSouthDarkenAmount), Math.max(0, c.getGreen() - northSouthDarkenAmount), Math.max(0, c.getBlue() - northSouthDarkenAmount), c.getAlpha());
eastWestColor = new Color(Math.max(0, c.getRed() - eastWestDarkenAmount), Math.max(0, c.getGreen() - eastWestDarkenAmount), Math.max(0, c.getBlue() - eastWestDarkenAmount), c.getAlpha());
bottomColor = new Color(Math.max(0, c.getRed() - bottomDarkenAmount), Math.max(0, c.getGreen() - bottomDarkenAmount), Math.max(0, c.getBlue() - bottomDarkenAmount), c.getAlpha());
}
// apply the user specified saturation and brightness
float saturationMultiplier = LodConfig.CLIENT.saturationMultiplier.get().floatValue();
float brightnessMultiplier = LodConfig.CLIENT.brightnessMultiplier.get().floatValue();
topColor = applySaturationAndBrightnessMultipliers(topColor, saturationMultiplier, brightnessMultiplier);
northSouthColor = applySaturationAndBrightnessMultipliers(northSouthColor, saturationMultiplier, brightnessMultiplier);
bottomColor = applySaturationAndBrightnessMultipliers(bottomColor, saturationMultiplier, brightnessMultiplier);
// top (facing up)
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
// bottom (facing down)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
// south (facing -Z)
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
// north (facing +Z)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
// west (facing -X)
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
// east (facing +X)
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
}
/**
* Edit the given color as a HSV (Hue Saturation Value) color.
*/
private Color applySaturationAndBrightnessMultipliers(Color color, float saturationMultiplier, float brightnessMultiplier)
{
float[] hsv = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
return Color.getHSBColor(
hsv[0], // hue
LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f),
LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f));
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail)
{
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints))) * howManyPointsPerLodChunk
return (6 * 4 * (3 + 4)) * detail.dataPointLengthCount * detail.dataPointLengthCount;
}
}
@@ -1,51 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
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 06-16-2021
*/
public class DynamicLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
{
System.err.println("DynamicLodTemplate not implemented!");
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail) {
// TODO Auto-generated method stub
return 0;
}
}
@@ -1,49 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import net.minecraft.client.renderer.BufferBuilder;
/**
* TODO #21 TriangularLodTemplate
* Builds each LOD chunk as a singular rectangular prism.
*
* @author James Seibel
* @version 06-16-2021
*/
public class TriangularLodTemplate extends AbstractLodTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
double xOffset, double yOffset, double zOffset,
boolean debugging)
{
System.err.println("DynamicLodTemplate not implemented!");
}
@Override
public int getBufferMemoryForSingleLod(LodDetail detail) {
// TODO Auto-generated method stub
return 0;
}
}
@@ -1,646 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import com.seibel.lod.builders.LodBufferBuilder;
import com.seibel.lod.builders.LodBuilderConfig;
import com.seibel.lod.builders.LodChunkBuilder;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.LodUtil;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.WeightedList.Entry;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.palette.UpgradeData;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.ChunkGenerator;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider;
import net.minecraft.world.gen.feature.BlockClusterFeatureConfig;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.DecoratedFeatureConfig;
import net.minecraft.world.gen.feature.FeatureSpread;
import net.minecraft.world.gen.feature.FeatureSpreadConfig;
import net.minecraft.world.gen.feature.IceAndSnowFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.placement.ConfiguredPlacement;
import net.minecraft.world.gen.placement.DecoratedPlacementConfig;
import net.minecraft.world.gen.placement.IPlacementConfig;
import net.minecraft.world.gen.placement.NoiseDependant;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.server.ServerWorldLightManager;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 7-26-2021
*/
public class LodChunkGenWorker implements IWorker
{
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get());
private boolean threadStarted = false;
private LodChunkGenThread thread;
/** If a configured feature fails for whatever reason,
* add it to this list, this is to hopefully remove any
* features that could cause issues down the line. */
private static ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
public LodChunkGenWorker(ChunkPos newPos, LodRenderer newLodRenderer,
LodChunkBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
// just a few sanity checks
if (newPos == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
if (newLodRenderer == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null LodRenderer");
if (newLodBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
if (newLodBufferBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodBufferBuilder");
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newLodRenderer,
newLodBuilder, newLodBufferBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1);
if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
{
// if we are using SERVER generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
}
else
{
// Every other method can
// be done asynchronously
genThreads.execute(thread);
}
threadStarted = true;
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final LodChunkBuilder lodChunkBuilder;
public final LodRenderer lodRenderer;
private final LodBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodRenderer newLodRenderer,
LodChunkBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodChunkBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.x / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.z / LodUtil.REGION_WIDTH_IN_CHUNKS))
{
// long startTime = System.currentTimeMillis();
switch(LodConfig.CLIENT.distanceGenerationMode.get())
{
case NONE:
// don't generate
break;
case BIOME_ONLY:
case BIOME_ONLY_SIMULATE_HEIGHT:
// fastest
generateUsingBiomesOnly();
break;
case SURFACE:
// faster
generateUsingSurface();
break;
case FEATURES:
// fast
generateUsingFeatures();
break;
case SERVER:
// very slow
generateWithServer();
break;
default:
break;
}
lodRenderer.regenerateLODsNextFrame();
// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}// run
/**
* takes about 2-5 ms
*/
private void generateUsingBiomesOnly()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// generate fake height data for this LOD
int seaLevel = serverWorld.getSeaLevel();
boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
boolean inTheEnd = false;
// add fake heightmap data so our LODs aren't at height 0
Heightmap heightmap = new Heightmap(chunk, LodUtil.DEFAULT_HEIGHTMAP);
for(int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
{
for(int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
{
if (simulateHeight)
{
// TODO use the biomes around each block to smooth out the transition
// these heights are of course aren't super accurate,
// they are just to simulate height data where there isn't any
switch(chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
{
case NETHER:
heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
break;
case EXTREME_HILLS:
heightmap.setHeight(x, z, seaLevel + 30);
break;
case MESA:
heightmap.setHeight(x, z, seaLevel + 20);
break;
case JUNGLE:
heightmap.setHeight(x, z, seaLevel + 20);
break;
case BEACH:
heightmap.setHeight(x, z, seaLevel + 5);
break;
case NONE:
heightmap.setHeight(x, z, 0);
break;
case OCEAN:
case RIVER:
heightmap.setHeight(x, z, seaLevel);
break;
case THEEND:
inTheEnd = true;
break;
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
default:
heightmap.setHeight(x, z, seaLevel + 10);
break;
}// heightmap switch
}
else
{
// we aren't simulating height
// always use sea level
heightmap.setHeight(x, z, seaLevel);
}
}// z
}// x
chunk.setHeightmap(LodUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData());
LodChunk lod;
if (!inTheEnd)
{
lod = lodChunkBuilder.generateLodFromChunk(chunk, new LodBuilderConfig(true, true, false));
}
else
{
// if we are in the end, don't generate any chunks.
// Since we don't know where the islands are, everything
// generates the same and it looks really bad.
lod = new LodChunk(chunk.getPos().x, chunk.getPos().z);
}
lodDim.addLod(lod);
}
/**
* takes about 10 - 20 ms
*/
private void generateUsingSurface()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// this feature has proved to be thread safe
// so we will add it
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
LodChunk lod = lodChunkBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
}
/**
* takes about 15 - 20 ms
*
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
private void generateUsingFeatures()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// get all the biomes in the chunk
HashSet<Biome> biomes = new HashSet<>();
for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
{
for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2);
// Issue #35
// For some reason Jungle biomes cause incredible lag
// the features here must be interacting with each other
// in unpredictable ways (specifically tree feature generation).
// When generating Features my CPU usage generally hovers around 30 - 40%
// when generating Jungles it spikes to 100%.
if (biome.getBiomeCategory() != Biome.Category.JUNGLE)
{
// should probably use the heightmap here instead of seaLevel,
// but this seems to get the job done well enough
biomes.add(biome);
}
}
}
boolean allowUnstableFeatures = LodConfig.CLIENT.allowUnstableFeatureGeneration.get();
// generate all the features related to this chunk.
// this may or may not be thread safe
for (Biome biome : biomes)
{
List<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++)
{
for(Supplier<ConfiguredFeature<?, ?>> featureSupplier : featuresForState.get(featureStateToGenerate))
{
ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
if (!allowUnstableFeatures &&
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
continue;
try
{
configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
}
catch(ConcurrentModificationException e)
{
// This will happen. I'm not sure what to do about it
// except pray that it doesn't effect the normal world generation
// in any harmful way.
// Update: this can cause crashes and high CPU usage.
// Issue #35
// I tried cloning the config for each feature, but that
// path was blocked since I can't clone lambda methods.
// I tried using a deep cloning library and discovered
// the problem there.
// ( https://github.com/kostaskougios/cloning
// and
// https://github.com/EsotericSoftware/kryo )
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
catch(UnsupportedOperationException e)
{
// This will happen when the LodServerWorld
// isn't able to return something that a feature
// generator needs
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
catch(Exception e)
{
// I'm not sure what happened, print to the log
System.out.println();
System.out.println();
e.printStackTrace();
System.out.println();
System.out.println();
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
}
}
}
// generate a Lod like normal
LodChunk lod = lodChunkBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
}
/**
* on pre generated chunks 0 - 1 ms
* on un generated chunks 0 - 50 ms
* with the median seeming to hover around 15 - 30 ms
* and outliers in the 100 - 200 ms range
*
* Note this should not be multithreaded and does cause server/simulation lag
* (Higher lag for generating than loading)
*/
private void generateWithServer()
{
throw new UnsupportedOperationException("Not Implemented");
//lodChunkBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
}
//================//
// Unused methods //
//================//
// Sadly I wasn't able to get these to work,
// they are here for documentation purposes
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config)
{
IPlacementConfig placementConfig = null;
Class oldConfigClass = config.decorator.config().getClass();
if (oldConfigClass == FeatureSpreadConfig.class)
{
FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.config();
FeatureSpread oldSpread = oldPlacementConfig.count();
placementConfig = new FeatureSpreadConfig(oldSpread);
}
else if(oldConfigClass == DecoratedPlacementConfig.class)
{
DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config();
placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer());
}
else if(oldConfigClass == NoiseDependant.class)
{
NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config();
placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise);
}
else
{
// ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.config().getClass() + "\"");
return config;
}
ConfiguredPlacement<?> newPlacement = new ConfiguredPlacement(config.decorator.decorator, placementConfig);
return new DecoratedFeatureConfig(config.feature, newPlacement);
}
@SuppressWarnings("unused")
private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config)
{
WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
for(Entry<BlockState> state : ((WeightedBlockStateProvider) config.stateProvider).weightedList.entries)
provider.weightedList.entries.add(state);
HashSet<Block> whitelist = new HashSet<>();
for(Block block : config.whitelist)
whitelist.add(block);
HashSet<BlockState> blacklist = new HashSet<>();
for(BlockState state : config.blacklist)
blacklist.add(state);
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
builder.whitelist(whitelist);
builder.blacklist(blacklist);
builder.xspread(config.xspread);
builder.yspread(config.yspread);
builder.zspread(config.zspread);
if(config.canReplace) { builder.canReplace(); }
if(config.needWater) { builder.needWater(); }
if(config.project) { builder.noProjection(); }
builder.tries(config.tries);
return builder.build();
}
}
/**
* Stops the current genThreads if they are running
* and then recreates the Executer service. <br><br>
*
* This is done to clear any outstanding tasks
* that may exist after the player leaves their current world.
* If this isn't done unfinished tasks may be left in the queue
* preventing new LodChunks form being generated.
*/
public static void restartExecuterService()
{
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get());
}
/*
* 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)
*/
}
@@ -1,357 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.enums.LodDetail;
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 6-27-2021
*/
public class LodDimensionFileHandler
{
private LodDimension loadedDimension = null;
public long regionLastWriteTime[][];
private File dimensionDataSaveFolder;
/** lod */
private final String FILE_NAME_PREFIX = "lod";
/** .txt */
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 = 2;
/** 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, LodConfig.CLIENT.maxDrawDetail.get());
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, LodConfig.CLIENT.maxDrawDetail.get());
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, LodConfig.CLIENT.maxDrawDetail.get()));
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. <br><br>
*
* example: "lod.FULL.0.0.txt"
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ, LodDetail detail)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data"
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
FILE_NAME_PREFIX + "." + detail.toString() + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch(IOException e)
{
return null;
}
}
}
@@ -1,495 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.awt.Color;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import net.minecraft.util.math.ChunkPos;
/**
* This object contains position
* and color data for an LOD object.
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodChunk
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER;
/** alpha used when drawing chunks in debug mode */
private static final int DEBUG_ALPHA = 255; // 0 - 255
private static final Color DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
private static final Color DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
private static final Color INVISIBLE = new Color(0,0,0,0);
public LodDetail detail = LodDetail.SINGLE;
/** If this is set to true then toData will return
* the empty string */
public boolean dontSave = false;
// TODO store the DistanceGenerationMethod used for this chunk (so we can upgrade old chunks if we want to)
/** The x coordinate of the chunk. */
public int x = 0;
/** The z coordinate of the chunk. */
public int z = 0;
/** This stores the height and color for each data point in the LodChunk */
public LodDataPoint dataPoints[][];
/** If true then this LodChunk contains no data */
private boolean empty = false;
/**
* Create an empty, invisible, non-saving LodChunk at (0,0)
*/
public LodChunk()
{
dontSave = true;
empty = true;
x = 0;
z = 0;
detail = LodDetail.SINGLE;
dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
// fill with dummy data, to prevent null pointers
for(int i = 0; i < detail.dataPointLengthCount; i++)
for(int j = 0; j < detail.dataPointLengthCount; j++)
dataPoints[i][j] = new LodDataPoint();
}
/**
* Create an empty, invisible, non-saving LodChunk at the given ChunkPos
*/
public LodChunk(ChunkPos pos)
{
this();
x = pos.x;
z = pos.z;
}
/**
* Create an empty, invisible, non-saving LodChunk at the given ChunkPos
*/
public LodChunk(int newX, int newZ)
{
this();
x = newX;
z = newZ;
}
/**
* 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, LodDetail newDetail) throws IllegalArgumentException, NumberFormatException
{
/*
* data format:
* x, z, dataPoint[0][0], dataPoint[0][1], ...
* x, z, height, depth, rgb color data, height, depth, rgb....
*
* example:
* 5,8, 4, 0, 255,255,255, 4, 0, 255, 255, 255, ...
*/
dontSave = false;
// make sure there are the correct number of entries
// in the data string
int count = 0;
detail = newDetail;
for(int i = 0; i < data.length(); i++)
if(data.charAt(i) == DATA_DELIMITER)
count++;
if(count != detail.lodChunkStringDelimiterCount)
throw new IllegalArgumentException("LodChunk constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + detail.lodChunkStringDelimiterCount + ".");
dataPoints = new LodDataPoint[detail.dataPointLengthCount][detail.dataPointLengthCount];
// 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);
this.x = Integer.parseInt(data.substring(0,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.z = Integer.parseInt(data.substring(lastIndex+1,index));
// LodDataPoints
for(int blockX = 0; blockX < detail.dataPointLengthCount; blockX++)
{
for(int blockZ = 0; blockZ < detail.dataPointLengthCount; blockZ++)
{
// height
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int height = Short.parseShort(data.substring(lastIndex+1,index));
// depth
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int depth = Short.parseShort(data.substring(lastIndex+1,index));
// color
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;
}
}
dataPoints[blockX][blockZ] = new LodDataPoint((short)height, (short)depth, new Color(red, green, blue));
}
}
empty = determineIfEmtpy();
}
/**
* Create a LodChunk from the given values.
*/
public LodChunk(ChunkPos pos, LodDataPoint[][] newDataPoints, LodDetail newDetail)
{
x = pos.x;
z = pos.z;
dataPoints = newDataPoints;
dontSave = false;
detail = newDetail;
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()
{
for(LodDataPoint[] dataPointArray : dataPoints)
{
for(LodDataPoint dataPoint : dataPointArray)
{
if (dataPoint == null)
continue;
// we don't check the depth since the
// height should always be greater than or equal
// to the depth
if(dataPoint.height >= 0)
{
// the height is valid,
if (dataPoint.color == INVISIBLE)
continue;
// the color and height are valid
// this LodChunk isn't empty
return false;
}
}
}
// we checked everywhere, this LodChunk is empty
return true;
}
//========//
// output //
//========//
/**
* Returns the data point for the given relative block position.
*/
public LodDataPoint getDataPointForBlockPos(int blockX, int blockZ)
{
return dataPoints[blockX / detail.dataPointWidth][blockZ / detail.dataPointWidth];
}
public Color getColorForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).color;
}
public short getHeightForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).height;
}
public short getDepthForBlockPos(int blockX, int blockZ)
{
return getDataPointForBlockPos(blockX, blockZ).depth;
}
public Color getColor(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].color;
}
public short getHeight(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].height;
}
public short getDepth(int xIndex, int zIndex)
{
return dataPoints[xIndex][zIndex].depth;
}
public short calculateHighestPoint()
{
short highest = 0;
for(int x = 0; x < detail.dataPointLengthCount; x++)
{
for(int z = 0; z < detail.dataPointLengthCount; z++)
{
if (getHeight(x,z) > highest)
highest = getHeight(x,z);
}
}
return highest;
}
/**
* @param startX
* @param startZ
* @param endX
* @param endZ
* @return
*/
public short getAverageHeightOverArea(int startX, int startZ, int endX, int endZ)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getHeightForBlockPos(startX,startZ);
int average = 0;
for(int x = startX; x < endX; x++)
for(int z = startZ; z < endZ; z++)
average += getHeightForBlockPos(x,z);
return (short) (average / ((endX - startX) * (endZ - startZ)));
}
/**
* @param startX
* @param startZ
* @param endX
* @param endZ
* @return
*/
public short getAverageDepthOverArea(int startX, int startZ, int endX, int endZ)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getDepthForBlockPos(startX,startZ);
int average = 0;
for(int x = startX; x < endX; x++)
for(int z = startZ; z < endZ; z++)
average += getDepthForBlockPos(x,z);
return (short) (average / ((endX - startX) * (endZ - startZ)));
}
/**
* Determine the color of this LOD.
*/
public Color getAverageColorOverArea(int startX, int startZ, int endX, int endZ, boolean debugging)
{
if (startX == endX || startZ == endZ)
// we were given an area with 0 blocks in it
return getColorForBlockPos(startX,startZ);
int[] colorComponents = new int[3];
if (debugging)
{
// draw the squares as a black and white,
// like a checker board
// only return 1 color to prevent
// getting back gray
if ((startX + startZ) % 2 == 0)
return DEBUG_WHITE;
else
return DEBUG_BLACK;
}
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
colorComponents = addColorToColorAverages(colorComponents, getColorForBlockPos(x,z));
}
}
int numbPoints = ((endX - startX) * (endZ - startZ));
return new Color(colorComponents[0]/numbPoints, colorComponents[1]/numbPoints, colorComponents[2]/numbPoints);
}
private int[] addColorToColorAverages(int[] colorAverages, Color colorToAdd)
{
// convert the colorToAdd to an int array
float[] colorCompoments = new float[4];
colorCompoments = colorToAdd.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[rgbIndex] += (int) (colorCompoments[rgbIndex] * 255 + 0.5);
}
return colorAverages;
}
/**
* Outputs all data in a csv format
* with the given delimiter, if
* dontSave is false. <br><br>
*
* data format: <br>
* x, z, dataPoint[0][0], dataPoint[0][1], ... <br>
* x, z, height, depth, rgb color data, height, depth, rgb.... <br><br>
*
* example: <br>
* 5,8, 4, 0, 255,255,255, 4, 0, 255, 255, 255, ...
*/
public String toData()
{
if (dontSave)
return "";
String s = "";
s += Integer.toString(x) + DATA_DELIMITER + Integer.toString(z) + DATA_DELIMITER;
for (int i = 0; i < dataPoints.length; i++)
for (int j = 0; j < dataPoints[i].length; j++)
s += dataPoints[i][j].toData();
return s;
}
@Override
public String toString()
{
String s = "";
s += "x: " + x + " z: " + z + "\t";
return s;
}
}
@@ -1,433 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.io.File;
import java.io.IOException;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import com.seibel.lod.util.LodUtil;
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 06-27-2021
*/
public class LodDimension
{
public final DimensionType dimension;
private volatile int width;
private volatile int halfWidth;
public volatile LodRegion regions[][];
public volatile boolean isRegionDirty[][];
private volatile int centerX;
private volatile int centerZ;
private LodDimensionFileHandler fileHandler;
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newMaxWidth)
{
dimension = newDimension;
width = newMaxWidth;
try
{
Minecraft mc = Minecraft.getInstance();
File saveDir;
if(mc.hasSingleplayerServer())
{
// local world
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension);
// provider needs a separate variable to prevent
// the compiler from complaining
ServerChunkProvider provider = serverWorld.getChunkSource();
saveDir = new File(provider.dataStorage.dataFolder.getCanonicalFile().getPath() + File.separatorChar + "lod");
}
else
{
// connected to server
saveDir = new File(mc.gameDirectory.getCanonicalFile().getPath() +
File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.level));
}
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];
// populate isRegionDirty
for(int i = 0; i < width; i++)
for(int j = 0; j < width; j++)
isRegionDirty[i][j] = false;
centerX = 0;
centerZ = 0;
halfWidth = (int)Math.floor(width / 2);
}
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset.
*/
public synchronized void move(int xOffset, int zOffset)
{
// if the x or z offset is equal to or greater than
// the total size, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
{
for(int x = 0; x < width; x++)
{
for(int z = 0; z < width; z++)
{
regions[x][z] = null;
}
}
// update the new center
centerX += xOffset;
centerZ += zOffset;
return;
}
// X
if(xOffset > 0)
{
// move everything over to the left (as the center moves to the right)
for(int x = 0; x < width; x++)
{
for(int z = 0; z < width; z++)
{
if(x + xOffset < width)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
else
{
// move everything over to the right (as the center moves to the left)
for(int x = width - 1; x >= 0; x--)
{
for(int z = 0; z < width; z++)
{
if(x + xOffset >= 0)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
// Z
if(zOffset > 0)
{
// move everything up (as the center moves down)
for(int x = 0; x < width; x++)
{
for(int z = 0; z < width; z++)
{
if(z + zOffset < width)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
else
{
// move everything down (as the center moves up)
for(int x = 0; x < width; x++)
{
for(int z = width - 1; z >= 0; z--)
{
if(z + zOffset >= 0)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
// update the new center
centerX += xOffset;
centerZ += zOffset;
}
/**
* Gets the region at the given X and Z
* <br>
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodRegion getRegion(int regionX, int regionZ)
{
int xIndex = (regionX - centerX) + halfWidth;
int zIndex = (regionZ - centerZ) + halfWidth;
if (!regionIsInRange(regionX, regionZ))
// out of range
return null;
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = getRegionFromFile(regionX, regionZ);
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = new LodRegion(regionX, regionZ);
}
}
return regions[xIndex][zIndex];
}
/**
* Overwrite the LodRegion at the location of newRegion with newRegion.
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
*/
public void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.x - centerX) + halfWidth;
int zIndex = (centerZ - newRegion.z) + halfWidth;
if (!regionIsInRange(newRegion.x, newRegion.z))
// out of range
throw new ArrayIndexOutOfBoundsException();
regions[xIndex][zIndex] = newRegion;
}
/**
* Add the given LOD to this dimension at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinates it will be overwritten.
*/
public void addLod(LodChunk lod)
{
RegionPos pos = new RegionPos(new ChunkPos(lod.x, lod.z));
// don't continue if the region can't be saved
if (!regionIsInRange(pos.x, pos.z))
{
return;
}
LodRegion region = getRegion(pos.x, pos.z);
if (region == null)
{
// if no region exists, create it
region = new LodRegion(pos.x, pos.z);
addOrOverwriteRegion(region);
}
region.addLod(lod);
// only save valid LODs to disk
if (!(lod.isPlaceholder() || lod.dontSave) && 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();
}
}
/**
* Get the LodChunk at the given X and Z coordinates
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public LodChunk getLodFromCoordinates(int chunkX, int chunkZ)
{
RegionPos pos = new RegionPos(new ChunkPos(chunkX, chunkZ));
LodRegion region = getRegion(pos.x, pos.z);
if(region == null)
return null;
return region.getLod(chunkX, chunkZ);
}
/**
* Get the region at the given X and Z coordinates from the
* RegionFileHandler.
*/
public LodRegion getRegionFromFile(int regionX, int regionZ)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(regionX, regionZ);
else
return null;
}
/**
* Returns whether the region at the given X and Z coordinates
* is within the loaded range.
*/
public boolean regionIsInRange(int regionX, int regionZ)
{
int xIndex = (regionX - centerX) + halfWidth;
int zIndex = (regionZ - centerZ) + halfWidth;
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
}
public int getCenterX()
{
return centerX;
}
public int getCenterZ()
{
return centerZ;
}
/**
* 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()
{
if (regions != null)
{
// we want to get the length directly from the
// source to make sure it is in sync with region
// and isRegionDirty
return regions.length;
}
else
{
return width;
}
}
public void setRegionWidth(int newWidth)
{
width = newWidth;
regions = new LodRegion[width][width];
isRegionDirty = new boolean[width][width];
// populate isRegionDirty
for(int i = 0; i < width; i++)
for(int j = 0; j < width; j++)
isRegionDirty[i][j] = false;
}
@Override
public String toString()
{
String s = "";
s += "dim: " + dimension.toString() + "\t";
s += "(" + centerX + "," + centerZ + ")";
return s;
}
}
@@ -1,103 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
/**
* A LodRegion is a 32x32
* 2D array of LodChunk objects.
* Each LodRegion corresponds to
* one file in the file system.
*
* @author James Seibel
* @version 6-12-2021
*/
public class LodRegion
{
/** X coordinate of this region */
public final int x;
/** Z coordinate of this region */
public final int z;
private LodChunk chunks[][];
public LodRegion(int regionX, int regionZ)
{
x = regionX;
z = regionZ;
chunks = new LodChunk[LodUtil.REGION_WIDTH_IN_CHUNKS][LodUtil.REGION_WIDTH_IN_CHUNKS];
}
/**
* Add the given LOD to this region at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinates it will be overwritten.
*/
public void addLod(LodChunk lod)
{
// we use ABS since LODs can be negative, but if they are
// the region will negative first, therefore we don't have to
// store the LOD chunks at negative indexes since we search
// LOD the region first
int xIndex = Math.abs(lod.x % LodUtil.REGION_WIDTH_IN_CHUNKS);
int zIndex = Math.abs(lod.z % LodUtil.REGION_WIDTH_IN_CHUNKS);
chunks[xIndex][zIndex] = lod;
}
/**
* Get the LodChunk at the given X and Z coordinates
* in this region.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public LodChunk getLod(int chunkX, int chunkZ)
{
// since we add LOD's with ABS, we get them the same way
int arrayX = Math.abs(chunkX % LodUtil.REGION_WIDTH_IN_CHUNKS);
int arrayZ = Math.abs(chunkZ % LodUtil.REGION_WIDTH_IN_CHUNKS);
return chunks[arrayX][arrayZ];
}
/**
* Returns all LodChunks in this region
*/
public LodChunk[][] getAllLods()
{
return chunks;
}
@Override
public String toString()
{
String s = "";
s += "x: " + x + " z: " + z + "\t";
return s;
}
}
@@ -1,127 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -22,7 +22,6 @@ import org.apache.logging.log4j.Logger;
import com.seibel.lod.builders.LodNodeBufferBuilder;
import com.seibel.lod.builders.LodNodeBuilder;
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
@@ -206,7 +205,6 @@ public class ClientProxy
{
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
LodChunkGenWorker.restartExecuterService();
LodNodeGenWorker.restartExecuterService();
lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0);
@@ -1,885 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.render;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashSet;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.NVFogDistance;
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.FogDrawOverride;
import com.seibel.lod.enums.FogQuality;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.handlers.ReflectionHandler;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarBuffer;
import com.seibel.lod.objects.NearFarFogSettings;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.ClientPlayerEntity;
import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.FogRenderer;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.client.renderer.vertex.VertexBuffer;
import net.minecraft.client.renderer.vertex.VertexFormat;
import net.minecraft.potion.Effects;
import net.minecraft.profiler.IProfiler;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Matrix4f;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
/**
* This is where all the magic happens. <br>
* This is where LODs are draw to the world.
*
* @author James Seibel
* @version 07-4-2021
*/
public class LodRenderer
{
/** this is the light used when rendering the LODs,
* it should be something different than what is used by Minecraft */
private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2;
/**
* 64 MB by default is the maximum amount of memory that
* can be directly allocated. <br><br>
*
* I know there are commands to change that amount
* (specifically "-XX:MaxDirectMemorySize"), but
* I have no idea how to access that amount. <br>
* So I guess this will be the hard limit for now. <br><br>
*
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
*/
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
/** Does this computer's GPU support fancy fog? */
private static Boolean fancyFogAvailable = null;
/** If true the LODs colors will be replaced with
* a checkerboard, this can be used for debugging. */
public boolean debugging = false;
private Minecraft mc;
private GameRenderer gameRender;
private IProfiler profiler;
private float farPlaneDistance;
private ReflectionHandler reflectionHandler;
/** This is used to generate the buildable buffers */
private LodBufferBuilder lodBufferBuilder;
/** The buffers that are used to draw LODs using near fog */
private volatile BufferBuilder drawableNearBuffer;
/** The buffers that are used to draw LODs using far fog */
private volatile BufferBuilder drawableFarBuffer;
/** This is the VertexBuffer used to draw any LODs that use near fog */
private volatile VertexBuffer nearVbo;
/** This is the VertexBuffer used to draw any LODs that use far fog */
private volatile VertexBuffer farVbo;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
/** This is used to determine if the LODs should be regenerated */
private int previousChunkRenderDistance = 0;
/** This is used to determine if the LODs should be regenerated */
private int prevChunkX = 0;
/** This is used to determine if the LODs should be regenerated */
private int prevChunkZ = 0;
/** This is used to determine if the LODs should be regenerated */
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR;
/** if this is true the LOD buffers should be regenerated,
* provided they aren't already being regenerated. */
private volatile boolean regen = false;
/** This HashSet contains every chunk that Vanilla Minecraft
* is going to render */
public HashSet<ChunkPos> vanillaRenderedChunks = new HashSet<>();
public LodRenderer(LodBufferBuilder newLodBufferBuilder)
{
mc = Minecraft.getInstance();
gameRender = mc.gameRenderer;
reflectionHandler = new ReflectionHandler();
lodBufferBuilder = newLodBufferBuilder;
}
/**
* Besides drawing the LODs this method also starts
* the async process of generating the Buffers that hold those LODs.
*
* @param newDimension The dimension to draw, if null doesn't replace the current dimension.
* @param partialTicks how far into the current tick this method was called.
*/
@SuppressWarnings("deprecation")
public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler)
{
if (lodDim == null)
{
// if there aren't any loaded LodChunks
// don't try drawing anything
return;
}
//===============//
// initial setup //
//===============//
profiler = newProfiler;
profiler.push("LOD setup");
// only check the GPU capability's once
if (fancyFogAvailable == null)
{
// see if this GPU can run fancy fog
fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
if (!LodRenderer.fancyFogAvailable)
{
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available.");
}
}
ClientPlayerEntity player = mc.player;
// should LODs be regenerated?
if ((int)player.getX() / LodUtil.CHUNK_WIDTH != prevChunkX ||
(int)player.getZ() / LodUtil.CHUNK_WIDTH != prevChunkZ ||
previousChunkRenderDistance != mc.options.renderDistance ||
prevFogDistance != LodConfig.CLIENT.fogDistance.get())
{
// yes
regen = true;
prevChunkX = (int)player.getX() / LodUtil.CHUNK_WIDTH;
prevChunkZ = (int)player.getZ() / LodUtil.CHUNK_WIDTH;
prevFogDistance = LodConfig.CLIENT.fogDistance.get();
}
else
{
// nope, the player hasn't moved, the
// render distance hasn't changed, and
// the dimension is the same
}
// did the user change the debug setting?
if (LodConfig.CLIENT.debugMode.get() != debugging)
{
debugging = LodConfig.CLIENT.debugMode.get();
regen = true;
}
// determine how far the game's render distance is currently set
int renderDistWidth = mc.options.renderDistance;
farPlaneDistance = renderDistWidth * LodUtil.CHUNK_WIDTH;
// set how big the LODs will be and how far they will go
int totalLength = (int) farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2;
int numbChunksWide = (totalLength / LodUtil.CHUNK_WIDTH);
// determine which LODs should not be rendered close to the player
HashSet<ChunkPos> chunkPosToSkip = getNearbyLodChunkPosToSkip(lodDim, player.blockPosition());
// see if the chunks Minecraft is going to render are the
// same as last time
if (!vanillaRenderedChunks.containsAll(chunkPosToSkip))
{
regen = true;
vanillaRenderedChunks = chunkPosToSkip;
}
//=================//
// create the LODs //
//=================//
// only regenerate the LODs if:
// 1. we want to regenerate LODs
// 2. we aren't already regenerating the LODs
// 3. we aren't waiting for the build and draw buffers to swap
// (this is to prevent thread conflicts)
if (regen && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvaliable())
{
// this will mainly happen when the view distance is changed
if (drawableNearBuffer == null || drawableFarBuffer == null ||
previousChunkRenderDistance != mc.options.renderDistance)
setupBuffers(numbChunksWide);
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilder.generateLodBuffersAsync(this, lodDim, player.getX(), player.getZ(), numbChunksWide);
// the regen process has been started,
// it will be done when lodBufferBuilder.newBuffersAvaliable
// is true
regen = false;
}
// replace the buffers used to draw and build,
// this is only done when the createLodBufferGenerationThread
// has finished executing on a parallel thread.
if (lodBufferBuilder.newBuffersAvaliable())
{
swapBuffers();
}
//===========================//
// GL settings for rendering //
//===========================//
// set the required open GL settings
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glEnable(GL11.GL_CULL_FACE);
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
GL11.glEnable(GL11.GL_DEPTH_TEST);
// disable the lights Minecraft uses
GL11.glDisable(GL11.GL_LIGHT0);
GL11.glDisable(GL11.GL_LIGHT1);
// get the default projection matrix so we can
// reset it after drawing the LODs
float[] defaultProjMatrix = new float[16];
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix);
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
setupProjectionMatrix(partialTicks);
setupLighting(lodDim, partialTicks);
NearFarFogSettings fogSettings = determineFogSettings();
// determine the current fog settings so they can be
// reset after drawing the LODs
float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START);
float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END);
int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE);
int defaultFogDistance = GL11.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV);
//===========//
// rendering //
//===========//
profiler.popPush("LOD draw");
setupFog(fogSettings.near.distance, fogSettings.near.quality);
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
setupFog(fogSettings.far.distance, fogSettings.far.quality);
sendLodsToGpuAndDraw(farVbo, modelViewMatrix);
//=========//
// cleanup //
//=========//
profiler.popPush("LOD cleanup");
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
GL11.glEnable(GL11.GL_TEXTURE_2D);
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
// re-enable the lights Minecraft uses
GL11.glEnable(GL11.GL_LIGHT0);
GL11.glEnable(GL11.GL_LIGHT1);
RenderSystem.disableLighting();
// this can't be called until after the buffers are built
// because otherwise the buffers may be set to the wrong size
previousChunkRenderDistance = mc.options.renderDistance;
// reset the fog settings so the normal chunks
// will be drawn correctly
cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance);
// reset the projection matrix so anything drawn after
// the LODs will use the correct projection matrix
Matrix4f mvm = new Matrix4f(defaultProjMatrix);
mvm.transpose();
gameRender.resetProjectionMatrix(mvm);
// clear the depth buffer so anything drawn is drawn
// over the LODs
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
// end of internal LOD profiling
profiler.pop();
}
/**
* This is where the actual drawing happens.
*
* @param buffers the buffers sent to the GPU to draw
*/
private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix)
{
if (vbo == null)
return;
vbo.bind();
// 0L is the starting pointer
LOD_VERTEX_FORMAT.setupBufferState(0L);
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
VertexBuffer.unbind();
LOD_VERTEX_FORMAT.clearBufferState();
}
//=================//
// Setup Functions //
//=================//
@SuppressWarnings("deprecation")
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
{
if(fogQuality == FogQuality.OFF)
{
FogRenderer.setupNoFog();
RenderSystem.disableFog();
return;
}
if(fogDistance == FogDistance.NEAR_AND_FAR)
{
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
}
// determine the fog distance mode to use
int glFogDistanceMode;
if (fogQuality == FogQuality.FANCY)
{
// fancy fog (fragment distance based fog)
glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV;
}
else
{
// fast fog (frustum distance based fog)
glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
}
// the multipliers are percentages
// of the regular view distance.
if(fogDistance == FogDistance.NEAR)
{
// the reason that I wrote fogEnd then fogStart backwards
// is because we are using fog backwards to how
// it is normally used, with it hiding near objects
// instead of far objects.
if (fogQuality == FogQuality.FANCY)
{
RenderSystem.fogEnd(farPlaneDistance * 1.75f);
RenderSystem.fogStart(farPlaneDistance * 1.95f);
}
else if(fogQuality == FogQuality.FAST)
{
// for the far fog of the normal chunks
// to start right where the LODs' end use:
// end = 0.8f, start = 1.5f
RenderSystem.fogEnd(farPlaneDistance * 1.5f);
RenderSystem.fogStart(farPlaneDistance * 2.0f);
}
}
else if(fogDistance == FogDistance.FAR)
{
if (fogQuality == FogQuality.FANCY)
{
RenderSystem.fogStart(farPlaneDistance * 0.85f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
RenderSystem.fogEnd(farPlaneDistance * 1.0f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
}
else if(fogQuality == FogQuality.FAST)
{
RenderSystem.fogStart(farPlaneDistance * 0.5f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
RenderSystem.fogEnd(farPlaneDistance * 0.75f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
}
}
GL11.glEnable(GL11.GL_FOG);
RenderSystem.enableFog();
RenderSystem.setupNvFogDistance();
RenderSystem.fogMode(GlStateManager.FogMode.LINEAR);
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode);
}
/**
* Revert any changes that were made to the fog.
*/
@SuppressWarnings("deprecation")
private void cleanupFog(NearFarFogSettings fogSettings,
float defaultFogStartDist, float defaultFogEndDist,
int defaultFogMode, int defaultFogDistance)
{
RenderSystem.fogStart(defaultFogStartDist);
RenderSystem.fogEnd(defaultFogEndDist);
RenderSystem.fogMode(defaultFogMode);
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance);
// disable fog if Minecraft wasn't rendering fog
// but we were
if(!fogSettings.vanillaIsRenderingFog &&
(fogSettings.near.quality != FogQuality.OFF ||
fogSettings.far.quality != FogQuality.OFF))
{
GL11.glDisable(GL11.GL_FOG);
}
}
/**
* Create the model view matrix to move the LODs
* from object space into world space.
*/
private Matrix4f generateModelViewMatrix(float partialTicks)
{
// get all relevant camera info
ActiveRenderInfo renderInfo = mc.gameRenderer.getMainCamera();
Vector3d projectedView = renderInfo.getPosition();
// generate the model view matrix
MatrixStack matrixStack = new MatrixStack();
matrixStack.pushPose();
// translate and rotate to the current camera location
matrixStack.mulPose(Vector3f.XP.rotationDegrees(renderInfo.getXRot()));
matrixStack.mulPose(Vector3f.YP.rotationDegrees(renderInfo.getYRot() + 180));
matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z);
return matrixStack.last().pose();
}
/**
* create a new projection matrix and send it over to the GPU
* <br><br>
* A lot of this code is copied from renderLevel (line 567)
* in the GameRender class. The code copied is anything with
* a matrixStack and is responsible for making sure the LOD
* objects distort correctly relative to the rest of the world.
* Distortions are caused by: standing in a nether portal,
* nausea potion effect, walking bobbing.
*
* @param partialTicks how many ticks into the frame we are
*/
private void setupProjectionMatrix(float partialTicks)
{
// Note: if the LOD objects don't distort correctly
// compared to regular minecraft terrain, make sure
// all the transformations in renderWorld are here too
MatrixStack matrixStack = new MatrixStack();
matrixStack.pushPose();
gameRender.bobHurt(matrixStack, partialTicks);
if (this.mc.options.bobView) {
gameRender.bobView(matrixStack, partialTicks);
}
// potion and nausea effects
float f = MathHelper.lerp(partialTicks, this.mc.player.oPortalTime, this.mc.player.portalTime) * this.mc.options.screenEffectScale * this.mc.options.screenEffectScale;
if (f > 0.0F) {
int i = this.mc.player.hasEffect(Effects.CONFUSION) ? 7 : 20;
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
f1 = f1 * f1;
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_OF_TWO / 2.0F, MathHelper.SQRT_OF_TWO / 2.0F);
matrixStack.mulPose(vector3f.rotationDegrees((gameRender.tick + partialTicks) * i));
matrixStack.scale(1.0F / f1, 1.0F, 1.0F);
float f2 = -(gameRender.tick + partialTicks) * i;
matrixStack.mulPose(vector3f.rotationDegrees(f2));
}
// this projection matrix allows us to see past the normal
// world render distance
Matrix4f projectionMatrix =
Matrix4f.perspective(
getFov(partialTicks, true),
(float)this.mc.getWindow().getScreenWidth() / (float)this.mc.getWindow().getScreenHeight(),
// it is possible to see the near clip plane, but
// you have to be flying quickly in spectator mode through ungenerated
// terrain, so I don't think it is much of an issue.
LodConfig.CLIENT.lodChunkRadiusMultiplier.get(),
this.farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2);
// add the screen space distortions
projectionMatrix.multiply(matrixStack.last().pose());
gameRender.resetProjectionMatrix(projectionMatrix);
return;
}
/**
* setup the lighting to be used for the LODs
*/
@SuppressWarnings("deprecation")
private void setupLighting(LodDimension lodDimension, float partialTicks)
{
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f;
float gammaMultiplyer = (float)mc.options.gamma - 0.5f;
float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.3f);
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
// can be used for debugging
// if (partialTicks < 0.005)
// ClientProxy.LOGGER.debug(lightStrength);
ByteBuffer temp = ByteBuffer.allocateDirect(16);
temp.order(ByteOrder.nativeOrder());
GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
RenderSystem.enableLighting();
}
/**
* Create all buffers that will be used.
*/
private void setupBuffers(int numbChunksWide)
{
// calculate the max amount of memory needed (in bytes)
int bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
// if the required memory is greater than the
// MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier
// to fit.
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
{
int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodUtil.CHUNK_DETAIL_LEVEL);
ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high "
+ "and had to be lowered to fit memory constraints "
+ "from " + LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + " "
+ "to " + maxRadiusMultiplier);
LodConfig.CLIENT.lodChunkRadiusMultiplier.set(
maxRadiusMultiplier);
bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(maxRadiusMultiplier);
}
drawableNearBuffer = new BufferBuilder(bufferMemory);
drawableFarBuffer = new BufferBuilder(bufferMemory);
lodBufferBuilder.setupBuffers(bufferMemory);
}
//======================//
// Other Misc Functions //
//======================//
/**
* If this is called then the next time "drawLODs" is called
* the LODs will be regenerated; the same as if the player moved.
*/
public void regenerateLODsNextFrame()
{
regen = true;
}
/**
* Replace the current drawable buffers with the newly
* created buffers from the lodBufferBuilder.
*/
private void swapBuffers()
{
// replace the drawable buffers with
// the newly created buffers from the lodBufferBuilder
NearFarBuffer newBuffers = lodBufferBuilder.swapBuffers(drawableNearBuffer, drawableFarBuffer);
drawableNearBuffer = newBuffers.nearBuffer;
drawableFarBuffer = newBuffers.farBuffer;
// bind the buffers with their respective VBOs
if (nearVbo != null)
nearVbo.close();
nearVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
nearVbo.upload(drawableNearBuffer);
if (farVbo != null)
farVbo.close();
farVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
farVbo.upload(drawableFarBuffer);
}
private double getFov(float partialTicks, boolean useFovSetting)
{
return mc.gameRenderer.getFov(mc.gameRenderer.getMainCamera(), partialTicks, useFovSetting);
}
/**
* Return what fog settings should be used when rendering.
*/
private NearFarFogSettings determineFogSettings()
{
NearFarFogSettings fogSettings = new NearFarFogSettings();
FogQuality quality = reflectionHandler.getFogQuality();
FogDrawOverride override = LodConfig.CLIENT.fogDrawOverride.get();
if (quality == FogQuality.OFF)
fogSettings.vanillaIsRenderingFog = false;
else
fogSettings.vanillaIsRenderingFog = true;
// use any fog overrides the user may have set
switch(override)
{
case ALWAYS_DRAW_FOG_FANCY:
quality = FogQuality.FANCY;
break;
case NEVER_DRAW_FOG:
quality = FogQuality.OFF;
break;
case ALWAYS_DRAW_FOG_FAST:
quality = FogQuality.FAST;
break;
case USE_OPTIFINE_FOG_SETTING:
// don't override anything
break;
}
// only use fancy fog if the user's GPU can deliver
if (!fancyFogAvailable && quality == FogQuality.FANCY)
{
quality = FogQuality.FAST;
}
// how different distances are drawn depends on the quality set
switch(quality)
{
case FANCY:
fogSettings.near.quality = FogQuality.FANCY;
fogSettings.far.quality = FogQuality.FANCY;
switch(LodConfig.CLIENT.fogDistance.get())
{
case NEAR_AND_FAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.FAR;
break;
case NEAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.NEAR;
break;
case FAR:
fogSettings.near.distance = FogDistance.FAR;
fogSettings.far.distance = FogDistance.FAR;
break;
}
break;
case FAST:
fogSettings.near.quality = FogQuality.FAST;
fogSettings.far.quality = FogQuality.FAST;
// fast fog setting should only have one type of
// fog, since the LODs are separated into a near
// and far portion; and fast fog is rendered from the
// frustrum's perspective instead of the camera
switch(LodConfig.CLIENT.fogDistance.get())
{
case NEAR_AND_FAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.NEAR;
break;
case NEAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.NEAR;
break;
case FAR:
fogSettings.near.distance = FogDistance.FAR;
fogSettings.far.distance = FogDistance.FAR;
break;
}
break;
case OFF:
fogSettings.near.quality = FogQuality.OFF;
fogSettings.far.quality = FogQuality.OFF;
break;
}
return fogSettings;
}
/**
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
private HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int chunkRenderDist = mc.options.renderDistance;
int blockRenderDist = chunkRenderDist * 16;
ChunkPos centerChunk = new ChunkPos(playerPos);
// skip chunks that are already going to be rendered by Minecraft
HashSet<ChunkPos> posToSkip = getRenderedChunks();
// go through each chunk within the normal view distance
for(int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
{
for(int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
{
LodChunk lod = lodDim.getLodFromCoordinates(x, z);
if (lod != null)
{
short lodHighestPoint = lod.calculateHighestPoint();
if (playerPos.getY() < lodHighestPoint)
{
// don't draw Lod's that are taller than the player
// to prevent LODs being drawn on top of the player
posToSkip.add(new ChunkPos(x, z));
}
else if (blockRenderDist < Math.abs(playerPos.getY() - lodHighestPoint))
{
// draw Lod's that are lower than the player's view range
posToSkip.remove(new ChunkPos(x, z));
}
}
}
}
return posToSkip;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
*
* Note: This isn't perfect. It will return some chunks that are outside
* the clipping plane. (For example, if you are high above the ground some chunks
* will be incorrectly added, even though they are outside render range).
*/
public static HashSet<ChunkPos> getRenderedChunks()
{
HashSet<ChunkPos> loadedPos = new HashSet<>();
Minecraft mc = Minecraft.getInstance();
// Wow those are some long names!
// go through every RenderInfo to get the compiled chunks
for(WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : mc.levelRenderer.renderChunks)
{
if (!worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk().hasNoRenderableLayers())
{
// add the ChunkPos for every empty compiled chunk
BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin();
loadedPos.add(new ChunkPos(bpos.getX() / 16, bpos.getZ() / 16));
}
}
return loadedPos;
}
}
@@ -37,7 +37,7 @@ import net.minecraft.world.server.ServerWorld;
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 8-10-2021
* @version 8-11-2021
*/
public class LodUtil
{
@@ -77,7 +77,7 @@ public class LodUtil
public static final int REGION_WIDTH_IN_CHUNKS = 32;
/** If we ever have to use a heightmap for any reason, use this one. */
/** If we ever need to use a heightmap for any reason, use this one. */
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;