Merge branch '1.16.5' of gitlab.com:jeseibel/minecraft-lod-mod into 1.16.5

This commit is contained in:
James Seibel
2021-08-18 17:37:38 -05:00
21 changed files with 1624 additions and 2627 deletions
-21
View File
@@ -1,21 +0,0 @@
package com.seibel.lod;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import java.awt.*;
public class Main {
public static void main(String[] args){
LodRegion lodRegion = new LodRegion((byte) 0,new RegionPos(0,0));
lodRegion.setData((byte) 2,0,0, new LodDataPoint((short) 2,(short) 30, new Color(100,100,100)), (byte) 2,true);
try {
System.out.print("test ");
System.out.println(lodRegion.getData((byte) 6, 0, 0));
}catch (Exception e){
e.printStackTrace();
}
return;
}
}
@@ -21,15 +21,15 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import com.seibel.lod.objects.LevelPos;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import org.lwjgl.opengl.GL11;
import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodQuadTree;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodNodeRenderer;
@@ -101,7 +101,7 @@ public class LodNodeBufferBuilder
}
private LodQuadTreeDimension previousDimension = null;
private LodDimension previousDimension = null;
/**
@@ -113,7 +113,7 @@ public class LodNodeBufferBuilder
* After the buildable buffers have been generated they must be
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodNodeRenderer renderer, LodQuadTreeDimension lodDim,
public void generateLodBuffersAsync(LodNodeRenderer renderer, LodDimension lodDim,
BlockPos playerBlockPos, int numbChunksWide)
{
// only allow one generation process to happen at a time
@@ -191,14 +191,12 @@ public class LodNodeBufferBuilder
startBlockPos.getX(); // offset so the center LOD block is centered underneath the player
double yOffset = 0;
double zOffset = (LodUtil.CHUNK_WIDTH * j) + startBlockPos.getZ();
LodQuadTreeNode lod = lodDim.getLodFromCoordinates(new ChunkPos(chunkX, chunkZ), LodUtil.CHUNK_DETAIL_LEVEL);
if (lod == null || lod.complexity == DistanceGenerationMode.NONE)
if (!lodDim.doesDataExist(new LevelPos((byte) LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ)))
{
// 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)
if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
{
ChunkPos pos = new ChunkPos(chunkX, chunkZ);
@@ -304,38 +302,30 @@ public class LodNodeBufferBuilder
// determine detail level should this LOD be drawn at
int distance = (int) Math.sqrt(Math.pow((playerBlockPosRounded.getX() - lod.getCenter().getX()), 2) + Math.pow((playerBlockPosRounded.getZ() - lod.getCenter().getZ()), 2));
LodDetail detail = LodDetail.getDetailForDistance(LodConfig.CLIENT.maxDrawDetail.get(), distance, maxBlockDistance);
int distance = (int) Math.sqrt(Math.pow((playerBlockPosRounded.getX() - chunkX*16 + 8), 2) + Math.pow((playerBlockPosRounded.getZ() - chunkZ*16 + 8), 2));
int posX;
int posZ;
LevelPos levelPos;
LodDataPoint lodData;
LodDetail detail = LodDetail.getDetailForDistance(LodConfig.CLIENT.maxDrawDetail.get(), distance, 16*128);
for (int k = 0; k < detail.dataPointLengthCount * detail.dataPointLengthCount; k++)
{
// how much to offset this LOD by
int startX = detail.startX[k];
int startZ = detail.startZ[k];
// get the QuadTree location of this
LodQuadTree lodTree = lodDim.getLevelFromPos(
LodUtil.convertLevelPos((int) xOffset + startX, 0, LodUtil.CHUNK_DETAIL_LEVEL),
LodUtil.convertLevelPos((int) zOffset + startZ, 0, LodUtil.CHUNK_DETAIL_LEVEL),
LodUtil.CHUNK_DETAIL_LEVEL);
if (lodTree == null)
continue;
LodQuadTreeNode newLod = lodTree.getNodeAtPos(
LodUtil.convertLevelPos((int) xOffset + startX, 0, detail.detailLevel),
LodUtil.convertLevelPos((int) zOffset + startZ, 0, detail.detailLevel),
detail.detailLevel);
if (newLod != null)
{
// get the desired LodTemplate and
// add this LOD to the buffer
LodConfig.CLIENT.lodTemplate.get().
template.addLodToBuffer(currentBuffer, lodDim, newLod,
xOffset + startX, yOffset, zOffset + startZ, renderer.debugging, detail);
posX = (int) (xOffset + detail.startX[k]);
posZ = (int) (zOffset + detail.startZ[k]);
levelPos = new LevelPos((byte) 0, posX, posZ).convert((byte) detail.detailLevel);
if (lodDim.hasThisPositionBeenGenerated(levelPos)) {
lodData = lodDim.getData(levelPos);
}else {
lodData = lodDim.getData(levelPos);
}
// get the desired LodTemplate and
// add this LOD to the buffer
LodConfig.CLIENT.lodTemplate.get().
template.addLodToBuffer(currentBuffer, lodDim, lodData,
posX, yOffset, posZ, renderer.debugging, detail);
}
}
@@ -26,10 +26,10 @@ import java.util.concurrent.Executors;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LevelPos;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.LodQuadTreeWorld;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
@@ -72,47 +72,43 @@ public class LodNodeBuilder
}
public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world)
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER);
}
public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world, DistanceGenerationMode generationMode)
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world, DistanceGenerationMode generationMode)
{
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();
List<LodQuadTreeNode> nodeList = generateLodNodeFromChunk(chunk, new LodBuilderConfig(generationMode));
LodQuadTreeDimension lodDim;
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodQuadTreeDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dim);
}
for (LodQuadTreeNode node : nodeList)
{
lodDim.addNode(node);
}
generateLodNodeFromChunk(lodDim ,chunk, new LodBuilderConfig(generationMode));
}
catch (IllegalArgumentException | NullPointerException e)
{
@@ -123,47 +119,46 @@ public class LodNodeBuilder
}
});
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 List<LodQuadTreeNode> generateLodNodeFromChunk(IChunk chunk) throws IllegalArgumentException
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk) throws IllegalArgumentException
{
return generateLodNodeFromChunk(chunk, new LodBuilderConfig());
generateLodNodeFromChunk(lodDim, 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 List<LodQuadTreeNode> generateLodNodeFromChunk(IChunk chunk, LodBuilderConfig config)
public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
LodDetail detail = LodConfig.CLIENT.maxGenerationDetail.get();
List<LodQuadTreeNode> lodNodeList = new ArrayList<>();
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
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 = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
short height;
short depth;
if (!config.useHeightmap)
{
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
@@ -175,21 +170,22 @@ public class LodNodeBuilder
startZ, endX, endZ);
depth = 0;
}
lodNodeList.add(new LodQuadTreeNode((byte) detail.detailLevel,
LodUtil.convertLevelPos(chunk.getPos().getMinBlockX() + startX, 0, detail.detailLevel),
LodUtil.convertLevelPos(chunk.getPos().getMinBlockZ() + startZ, 0, detail.detailLevel),
new LodDataPoint(height, depth, color), config.distanceGenerationMode));
LevelPos levelPos = new LevelPos((byte)0 ,
chunk.getPos().x*16 + startX,
chunk.getPos().z*16 + startZ);
LodDataPoint data = new LodDataPoint(height, depth, color);
lodDim.addData(levelPos.convert((byte) detail.detailLevel),
data,
config.distanceGenerationMode,
true,
false);
}
return lodNodeList;
}
// =====================//
// constructor helpers //
// =====================//
/**
* Find the lowest valid point from the bottom.
*
@@ -202,14 +198,14 @@ public class LodNodeBuilder
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++)
@@ -217,7 +213,7 @@ public class LodNodeBuilder
if (isLayerValidLodPoint(chunkSections, section, y, x, z))
{
numberOfBlocksFound++;
if (numberOfBlocksFound >= numberOfBlocksRequired)
{
// we found
@@ -231,11 +227,11 @@ public class LodNodeBuilder
}
}
}
// we never found a valid LOD point
return -1;
}
/**
* Find the lowest valid point from the bottom.
*/
@@ -246,7 +242,7 @@ public class LodNodeBuilder
// doesn't have any info about how low they go
return 0;
}
/**
* Find the highest valid point from the Top
*
@@ -265,7 +261,7 @@ public class LodNodeBuilder
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++)
@@ -273,7 +269,7 @@ public class LodNodeBuilder
if (isLayerValidLodPoint(chunkSections, section, y, x, z))
{
numberOfBlocksFound++;
if (numberOfBlocksFound >= numberOfBlocksRequired)
{
// we found
@@ -287,11 +283,11 @@ public class LodNodeBuilder
}
}
}
// we never found a valid LOD point
return -1;
}
/**
* Find the highest point from the Top
*/
@@ -307,10 +303,10 @@ public class LodNodeBuilder
highest = newHeight;
}
}
return highest;
}
/**
* Generate the color for the given chunk using biome water color, foliage
* color, and grass color.
@@ -330,21 +326,21 @@ public class LodNodeBuilder
* material color
*/
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX,
int endZ)
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--)
{
@@ -354,19 +350,19 @@ public class LodNodeBuilder
{
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)
{
// I have no idea why I need to bit shift to the right, but
@@ -377,21 +373,21 @@ public class LodNodeBuilder
}
else
{
// the bit shift is equivalent to dividing by 4
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;
@@ -400,24 +396,24 @@ public class LodNodeBuilder
}
}
}
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())
{
@@ -429,7 +425,7 @@ public class LodNodeBuilder
{
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
}
// plant life
else if (blockState.getBlock() instanceof LeavesBlock)
{
@@ -443,84 +439,84 @@ public class LodNodeBuilder
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;
}
/**
* Returns a color int for the given biome.
*/
private int getColorForBiome(int x, int z, Biome biome)
{
int colorInt = 0;
switch (biome.getBiomeCategory())
{
case NETHER:
colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col;
break;
case THEEND:
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
break;
case BEACH:
case DESERT:
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
break;
case EXTREME_HILLS:
colorInt = Blocks.STONE.defaultMaterialColor().col;
break;
case MUSHROOM:
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
break;
case ICY:
colorInt = Blocks.SNOW.defaultMaterialColor().col;
break;
case MESA:
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
break;
case OCEAN:
case RIVER:
colorInt = biome.getWaterColor();
break;
case NONE:
case FOREST:
case TAIGA:
case JUNGLE:
case PLAINS:
case SAVANNA:
case SWAMP:
default:
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
break;
case NETHER:
colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col;
break;
case THEEND:
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
break;
case BEACH:
case DESERT:
colorInt = Blocks.SAND.defaultBlockState().materialColor.col;
break;
case EXTREME_HILLS:
colorInt = Blocks.STONE.defaultMaterialColor().col;
break;
case MUSHROOM:
colorInt = MaterialColor.COLOR_LIGHT_GRAY.col;
break;
case ICY:
colorInt = Blocks.SNOW.defaultMaterialColor().col;
break;
case MESA:
colorInt = Blocks.RED_SAND.defaultMaterialColor().col;
break;
case OCEAN:
case RIVER:
colorInt = biome.getWaterColor();
break;
case NONE:
case FOREST:
case TAIGA:
case JUNGLE:
case PLAINS:
case SAVANNA:
case SWAMP:
default:
Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z));
tmp = tmp.darker();
colorInt = LodUtil.colorToInt(tmp);
break;
}
return colorInt;
}
/**
* Is the layer between the given X, Z, and dataIndex values a valid LOD point?
*/
@@ -540,8 +536,8 @@ public class LodNodeBuilder
return true;
}
}
return false;
}
}
@@ -20,8 +20,8 @@ package com.seibel.lod.builders.lodNodeTemplates;
import java.awt.Color;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.renderer.BufferBuilder;
@@ -29,30 +29,30 @@ import net.minecraft.client.renderer.BufferBuilder;
/**
* This is the abstract class used to create different
* BufferBuilders.
*
*
* @author James Seibel
* @version 8-8-2021
*/
public abstract class AbstractLodNodeTemplate
{
public abstract void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail) ;
LodDimension lodDim, LodDataPoint lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail);
/** 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)
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 getBufferMemoryForSingleNode(int level);
/**
* Edit the given color as a HSV (Hue Saturation Value) color.
*/
@@ -61,9 +61,9 @@ public abstract class AbstractLodNodeTemplate
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[1] * saturationMultiplier, 1.0f),
LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f));
}
}
@@ -22,8 +22,8 @@ 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.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.renderer.BufferBuilder;
@@ -39,77 +39,77 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
{
public CubicLodNodeTemplate()
{
}
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail)
LodDimension lodDim, LodDataPoint lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail)
{
AxisAlignedBB bbox;
// add each LOD for the detail level
bbox = generateBoundingBox(
lod.getLodDataPoint().height,
lod.getLodDataPoint().depth,
lod.width,
lod.height,
lod.depth,
detail.dataPointWidth,
xOffset,
yOffset,
zOffset);
Color color = lod.getLodDataPoint().color;
Color color = lod.color;
if (LodConfig.CLIENT.debugMode.get())
{
color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detail.detailLevel];
}
if (bbox != null)
{
addBoundingBoxToBuffer(buffer, bbox, color);
}
}
/*
* @Override public void addLodToBuffer(BufferBuilder buffer,
* LodQuadTreeDimension lodDim, LodQuadTreeNode lod, double xOffset, double
* yOffset, double zOffset, boolean debugging) { AxisAlignedBB bbox;
*
*
* bbox = generateBoundingBox( lod.getLodDataPoint().height,
* lod.getLodDataPoint().depth, lod.width, xOffset, yOffset, zOffset);
*
*
* Color color = lod.getLodDataPoint().color;
*
*
* if (bbox != null) { addBoundingBoxToBuffer(buffer, bbox, 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)
{
@@ -119,20 +119,20 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
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());
@@ -143,7 +143,7 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
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());
@@ -154,7 +154,7 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
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());
@@ -166,7 +166,7 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
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());
}
@Override
public int getBufferMemoryForSingleNode(int detailLevel)
{
@@ -174,5 +174,5 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
// howManyPointsPerLodChunk
return (6 * 4 * (3 + 4));
}
}
@@ -18,8 +18,8 @@
package com.seibel.lod.builders.lodNodeTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.renderer.BufferBuilder;
@@ -36,9 +36,9 @@ public class DynamicLodNodeTemplate extends AbstractLodNodeTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail)
LodDimension lodDim, LodDataPoint lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail)
{
System.err.println("DynamicLodTemplate not implemented!");
}
@@ -18,8 +18,8 @@
package com.seibel.lod.builders.lodNodeTemplates;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.renderer.BufferBuilder;
@@ -34,9 +34,9 @@ public class TriangularLodNodeTemplate extends AbstractLodNodeTemplate
{
@Override
public void addLodToBuffer(BufferBuilder buffer,
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail)
LodDimension lodDim, LodDataPoint lod,
double xOffset, double yOffset, double zOffset,
boolean debugging, LodDetail detail)
{
System.err.println("DynamicLodTemplate not implemented!");
}
@@ -31,8 +31,7 @@ import com.seibel.lod.builders.LodNodeBufferBuilder;
import com.seibel.lod.builders.LodNodeBuilder;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodNodeRenderer;
import com.seibel.lod.util.LodThreadFactory;
@@ -69,165 +68,165 @@ import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
*
* @author James Seibel
* @version 7-4-2021
*/
public class LodNodeGenWorker implements IWorker
{
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
private boolean threadStarted = false;
private LodChunkGenThread thread;
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<>();
/** 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 LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer,
public LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer,
LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder,
LodQuadTreeDimension 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)
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)
if (newLodBufferBuilder == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodBufferBuilder");
if (newLodDimension == null)
if (newLodDimension == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
if (newServerWorld == null)
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)
{
if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
thread = new LodChunkGenThread(newPos, newLodRenderer,
newLodBuilder, newLodBufferBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
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();
// 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
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 LodQuadTreeDimension lodDim;
public final LodNodeBuilder lodNodeBuilder;
public final LodNodeRenderer lodRenderer;
private LodNodeBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodNodeRenderer newLodRenderer,
LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder,
LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodNodeBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final LodNodeBuilder lodNodeBuilder;
public final LodNodeRenderer lodRenderer;
private LodNodeBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodNodeRenderer newLodRenderer,
LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodNodeBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
try
{
// only generate LodChunks if they can
// be added to the current LodDimension
// 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;
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;
}
lodRenderer.regenerateLODsNextFrame();
// if (lodDim.getLodFromCoordinates(pos) != null)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// shows the pool size, active threads, queued tasks and completed tasks
// ClientProxy.LOGGER.info(genThreads.toString());
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}
catch (Exception e)
{
@@ -238,11 +237,11 @@ public class LodNodeGenWorker implements IWorker
// decrement how many threads are running
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1);
}
}// run
/**
* takes about 2-5 ms
*/
@@ -251,26 +250,26 @@ public class LodNodeGenWorker implements IWorker
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);
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
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++)
@@ -280,51 +279,51 @@ public class LodNodeGenWorker implements IWorker
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;
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
@@ -335,36 +334,29 @@ public class LodNodeGenWorker implements IWorker
}
}// z
}// x
chunk.setHeightmap(LodUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData());
List<LodQuadTreeNode> nodeList;
if (!inTheEnd)
{
nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false));
lodNodeBuilder.generateLodNodeFromChunk(lodDim, 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.
nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false));
lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false));
}
// long startTime = System.currentTimeMillis();
for(LodQuadTreeNode node : nodeList)
{
lodDim.addNode(node);
}
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}
/**
* takes about 10 - 20 ms
*/
@@ -374,11 +366,11 @@ public class LodNodeGenWorker implements IWorker
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
@@ -386,22 +378,19 @@ public class LodNodeGenWorker implements IWorker
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
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);
List<LodQuadTreeNode> nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig());
for(LodQuadTreeNode node : nodeList) {
lodDim.addNode(node);
}
lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.SURFACE));
}
/**
* takes about 15 - 20 ms
*
*
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
@@ -411,11 +400,11 @@ public class LodNodeGenWorker implements IWorker
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
@@ -423,8 +412,8 @@ public class LodNodeGenWorker implements IWorker
chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
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++)
@@ -432,7 +421,7 @@ public class LodNodeGenWorker implements IWorker
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
@@ -447,26 +436,26 @@ public class LodNodeGenWorker implements IWorker
}
}
}
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()))
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
continue;
try
{
configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
@@ -477,18 +466,18 @@ public class LodNodeGenWorker implements IWorker
// 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
// ( https://github.com/kostaskougios/cloning
// and
// https://github.com/EsotericSoftware/kryo )
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
catch(UnsupportedOperationException e)
@@ -496,45 +485,41 @@ public class LodNodeGenWorker implements IWorker
// This will happen when the LodServerWorld
// isn't able to return something that a feature
// generator needs
if (!allowUnstableFeatures)
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
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);
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
}
}
}
}
// generate a Lod like normal
List<LodQuadTreeNode> nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig());
for(LodQuadTreeNode node : nodeList) {
lodDim.addNode(node);
}
lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.FEATURES));
}
/**
* on pre generated chunks 0 - 1 ms
* on un generated chunks 0 - 50 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)
*/
@@ -542,31 +527,31 @@ public class LodNodeGenWorker implements IWorker
{
lodNodeBuilder.generateLodNodeAsync(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)
@@ -584,29 +569,29 @@ public class LodNodeGenWorker implements IWorker
// 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);
blacklist.add(state);
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
builder.whitelist(whitelist);
builder.blacklist(blacklist);
@@ -617,43 +602,43 @@ public class LodNodeGenWorker implements IWorker
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()
}
/**
* 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();
}
if (genThreads != null && !genThreads.isShutdown())
{
genThreads.shutdownNow();
}
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName()));
}
/*
* 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)
@@ -666,11 +651,11 @@ public class LodNodeGenWorker implements IWorker
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)
*/
}
@@ -74,6 +74,10 @@ public class LodConfig
/** this is multiplied by the default view distance
* to determine how far out to generate/render LODs */
public ForgeConfigSpec.IntValue lodChunkRadiusMultiplier;
public ForgeConfigSpec.IntValue lodQuality;
public ForgeConfigSpec.IntValue lodChunkRenderDistane;
public ForgeConfigSpec.DoubleValue brightnessMultiplier;
@@ -146,8 +150,8 @@ public class LodConfig
+ " " + LodDetail.HALF.toString() + ": render 64 LODs for each Chunk. \n"
+ " " + LodDetail.FULL.toString() + ": render 256 LODs for each Chunk. \n")
.defineEnum("lodGenerationQuality", LodDetail.DOUBLE);
lodChunkRadiusMultiplier = builder
.comment("\n\n"
+ " This is multiplied by the default view distance \n"
@@ -155,7 +159,7 @@ public class LodConfig
+ " A value of 2 means that there is 1 render distance worth \n"
+ " of LODs in each cardinal direction. \n")
.defineInRange("lodChunkRadiusMultiplier", 8, 2, 16);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are the amount of time it took \n"
@@ -17,6 +17,11 @@
*/
package com.seibel.lod.handlers;
import com.seibel.lod.objects.*;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
@@ -26,110 +31,110 @@ import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.objects.LodQuadTree;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
/**
* This object handles creating LodRegions
* from files and saving LodRegion objects
* to file.
*
*
* @author James Seibel
* @version 8-14-2021
*/
public class LodQuadTreeDimensionFileHandler
{
/** This is what separates each piece of data */
public static final char DATA_DELIMITER = ',';
private LodQuadTreeDimension loadedDimension = null;
public long regionLastWriteTime[][];
private File dimensionDataSaveFolder;
/** lod */
private static final String FILE_NAME_PREFIX = "lod";
/** .txt */
private static final String FILE_EXTENSION = ".txt";
/** .tmp <br>
* Added to the end of the file path when saving to prevent
* nulling a currently existing file. <br>
* After the file finishes saving it will end with
* FILE_EXTENSION. */
private static final String TMP_FILE_EXTENSION = ".tmp";
/** 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 = 3;
/** 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(new LodThreadFactory(this.getClass().getSimpleName()));
public LodQuadTreeDimensionFileHandler(File newSaveFolder, LodQuadTreeDimension 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 LodQuadTree region at the given coordinates.
* (null if the file doesn't exist)
*/
public LodQuadTree loadRegionFromFile(RegionPos regionPos)
{
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ);
if(FILE_EXTENSION == ".bin"){
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(fileName));
List<LodQuadTreeNode> dataList = (List<LodQuadTreeNode>) is.readObject();
//LodQuadTree region = (LodQuadTree) is.readObject();
is.close();
return new LodQuadTree(dataList, regionX, regionZ);
//return region;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return new LodQuadTree(new ArrayList<>(), regionX, regionZ);
//return null;
}
public class LodDimensionFileHandler {
/**
* This is what separates each piece of data
*/
public static final char DATA_DELIMITER = ',';
private LodDimension loadedDimension = null;
public long regionLastWriteTime[][];
private File dimensionDataSaveFolder;
/**
* lod
*/
private static final String FILE_NAME_PREFIX = "lod";
/**
* .txt
*/
private static final String FILE_EXTENSION = ".bin";
/**
* .tmp <br>
* Added to the end of the file path when saving to prevent
* nulling a currently existing file. <br>
* After the file finishes saving it will end with
* FILE_EXTENSION.
*/
private static final String TMP_FILE_EXTENSION = ".tmp";
/**
* 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 = 3;
/**
* 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(new LodThreadFactory(this.getClass().getSimpleName()));
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 region at the given coordinates.
* (null if the file doesn't exist)
*/
public LodRegion loadRegionFromFile(RegionPos regionPos) {
int regionX = regionPos.x;
int regionZ = regionPos.z;
String fileName = getFileNameAndPathForRegion(regionX, regionZ);
if (FILE_EXTENSION == ".bin") {
try {
System.out.println(fileName);
ObjectInputStream is = new ObjectInputStream(new FileInputStream(fileName));
LevelContainer levelContainer = (LevelContainer) is.readObject();
is.close();
return new LodRegion(levelContainer, regionPos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return null;
/*
if(FILE_EXTENSION == ".txt") {
File f = new File(fileName);
@@ -210,71 +215,65 @@ public class LodQuadTreeDimensionFileHandler
}
return null;
}
//==============//
// 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)
{
saveRegionToFile(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 saveRegionToFile(LodQuadTree region)
{
// convert to region coordinates
int x = region.getLodNodeData().posX;
int z = region.getLodNodeData().posZ;
*/
}
File oldFile = new File(getFileNameAndPathForRegion(x, z));
//==============//
// Save to File //
//==============//
if(FILE_EXTENSION == ".bin"){
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(getFileNameAndPathForRegion(x, z)));
os.writeObject(region.getNodeListWithMask(LodQuadTreeDimension.FULL_COMPLEXITY_MASK, false, true));
//os.writeObject(region);
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 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) {
saveRegionToFile(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 saveRegionToFile(LodRegion region) {
// convert to region coordinates
int x = region.regionPosX;
int z = region.regionPosZ;
// get minimum level
byte minDetailLevel = region.getMinDetailLevel();
File oldFile = new File(getFileNameAndPathForRegion(x, z));
if (!oldFile.getParentFile().exists())
oldFile.getParentFile().mkdirs();
if (FILE_EXTENSION == ".bin") {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(getFileNameAndPathForRegion(x, z)));
os.writeObject(region.getLevel((byte) 0));
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
if(FILE_EXTENSION == ".txt") {
try {
// make sure the file and folder exists
@@ -340,41 +339,33 @@ public class LodQuadTreeDimensionFileHandler
e.printStackTrace();
}
}
}
//================//
// 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.0.0.txt"
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data"
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
}
catch(IOException e)
{
return null;
}
}
*/
}
//================//
// 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>
* <p>
* example: "lod.0.0.txt"
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ) {
try {
// saveFolder is something like
// ".\Super Flat\DIM-1\data"
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
} catch (IOException e) {
return null;
}
}
}
@@ -0,0 +1,33 @@
package com.seibel.lod.objects;
import java.io.Serializable;
public class LevelContainer implements Serializable {
public final byte detailLevel;
public final byte[][][] colors;
public final short[][] height;
public final short[][] depth;
public final byte[][] generationType;
public final boolean[][] dataExistence;
public LevelContainer(byte detailLevel, byte[][][] colors, short[][] height, short[][] depth, byte[][] generationType, boolean[][] dataExistence){
this.detailLevel = detailLevel;
this.colors = colors;
this.height = height;
this.depth = depth;
this.generationType = generationType;
this.dataExistence = dataExistence;
}
/*
public LevelContainer(String data, byte detailLevel){
int size = detailLevel*detailLevel;
}
*/
}
@@ -0,0 +1,52 @@
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
public class LevelPos implements Cloneable{
public final byte detailLevel;
public final int posX;
public final int posZ;
public LevelPos(byte detailLevel, int posX, int posZ){
this.posX = posX;
this.posZ = posZ;
this.detailLevel = detailLevel;
}
public LevelPos convert( byte newDetailLevel){
if(newDetailLevel >= detailLevel) {
return new LevelPos(
newDetailLevel,
Math.floorDiv(posX, (int) Math.pow(2, newDetailLevel - detailLevel)),
Math.floorDiv(posZ, (int) Math.pow(2, newDetailLevel - detailLevel)));
}else{
return new LevelPos(
newDetailLevel,
posX * (int) Math.pow(2, detailLevel - newDetailLevel),
posZ * (int) Math.pow(2, detailLevel - newDetailLevel));
}
}
public LevelPos regionModule(){
return new LevelPos(
detailLevel,
Math.floorMod(posX, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel)),
Math.floorMod(posZ, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel)));
}
public RegionPos getRegionPos(){
return new RegionPos(
Math.floorDiv(posX, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel)),
Math.floorDiv(posZ, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel)));
}
public LevelPos clone(){
return new LevelPos(detailLevel,posX,posZ);
}
public String toString(){
String s = (detailLevel + " " + posX + " " + posZ);
return s;
}
}
@@ -21,7 +21,7 @@ import java.awt.*;
import java.io.Serializable;
import java.util.Objects;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import com.seibel.lod.util.LodUtil;
/**
@@ -34,7 +34,7 @@ import com.seibel.lod.util.LodUtil;
public class LodDataPoint implements Serializable
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER;
private static final char DATA_DELIMITER = LodDimensionFileHandler.DATA_DELIMITER;
/** this is how many pieces of data are exported when toData is called */
public static final int NUMBER_OF_DELIMITERS = 5;
@@ -0,0 +1,546 @@
/*
* 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.enums.DistanceGenerationMode;
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;
import java.io.File;
import java.io.IOException;
/**
* This object holds all loaded LOD regions
* for a given dimension.
*
* @author Leonardo Amato
* @author James Seibel
* @version 8-8-2021
*/
public class LodDimension
{
public final DimensionType dimension;
/** measured in regions */
private volatile int width;
/** measured in regions */
private volatile int halfWidth;
public volatile LodRegion regions[][];
public volatile boolean isRegionDirty[][];
private volatile RegionPos center;
private LodDimensionFileHandler fileHandler;
/**
* Creates the dimension centered at (0,0)
*
* @param newWidth in regions
*/
public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newWidth)
{
dimension = newDimension;
width = newWidth;
halfWidth = (int)Math.floor(width / 2);
if(newDimension != null && lodWorld != null)
{
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;
center = new RegionPos(0,0);
}
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset. <br><br>
*
* Synchronized to prevent multiple moves happening on top of each other.
*/
public synchronized void move(RegionPos regionOffset)
{
int xOffset = regionOffset.x;
int zOffset = regionOffset.z;
// 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
center.x += xOffset;
center.z += 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
center.x += xOffset;
center.z += 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(RegionPos regionPos)
{
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
if (!regionIsInRange(regionPos.x, regionPos.z))
// out of range
return null;
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = getRegionFromFile(regionPos);
if (regions[xIndex][zIndex] == null)
{
/**TODO the value is currently 0 but should be determinated by the distance of the player)*/
regions[xIndex][zIndex] = new LodRegion((byte) 0,regionPos);
}
}
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.regionPosX - center.x) + halfWidth;
int zIndex = (center.z - newRegion.regionPosZ) + halfWidth;
if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ))
// out of range
throw new ArrayIndexOutOfBoundsException();
regions[xIndex][zIndex] = newRegion;
}
/**
*this method creates all null regions
*/
public void initializeNullRegions()
{
int regionX;
int regionZ;
RegionPos regionPos;
LodRegion region;
for(int x = 0; x < regions.length; x++)
{
for(int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
regionPos = new RegionPos(regionX,regionZ);
region = getRegion(regionPos);
if (region == null)
{
// if no region exists, create it
region = new LodRegion((byte) 0,regionPos);
addOrOverwriteRegion(region);
}
}
}
}
/**
* 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 synchronized Boolean addData(LevelPos levelPos, LodDataPoint lodDataPoint, DistanceGenerationMode generationMode, boolean update, boolean dontSave)
{
// don't continue if the region can't be saved
RegionPos regionPos = levelPos.getRegionPos();
if (!regionIsInRange(regionPos.x, regionPos.z))
{
return false;
}
LodRegion region = getRegion(regionPos);
if (region == null)
{
// if no region exists, create it
region = new LodRegion((byte) 0,regionPos);
addOrOverwriteRegion(region);
}
boolean nodeAdded = region.setData(levelPos,lodDataPoint,(byte) generationMode.complexity,true);
// only save valid LODs to disk
if (!dontSave && fileHandler != null)
{
try
{
// mark the region as dirty so it will be saved to disk
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
}
catch(ArrayIndexOutOfBoundsException e)
{
// This method was probably called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
}
return nodeAdded;
}
/**
* Get the LodNodeData 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 synchronized LodDataPoint getData(ChunkPos chunkPos)
{
LevelPos levelPos = new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z);
return getData(levelPos);
}
/**
* Get the data point 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 synchronized LodDataPoint getData(LevelPos levelPos)
{
if (levelPos.detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + levelPos.detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodRegion region = getRegion(levelPos.getRegionPos());
if(region == null)
{
return null;
}
return region.getData(levelPos);
}
/**
* return true if and only if the node at that position exist
*/
public synchronized boolean hasThisPositionBeenGenerated(ChunkPos chunkPos)
{
LodRegion region = getRegion(LodUtil.convertGenericPosToRegionPos(chunkPos.x, chunkPos.z, LodUtil.CHUNK_DETAIL_LEVEL));
if(region == null)
{
return false;
}
return region.hasDataBeenGenerated(new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z));
}
/**
* return true if and only if the node at that position exist
*/
public synchronized boolean hasThisPositionBeenGenerated(LevelPos levelPos)
{
LodRegion region = getRegion(levelPos.getRegionPos());
if(region == null)
{
return false;
}
return region.hasDataBeenGenerated(levelPos);
}
/**
* return true if and only if the node at that position exist
*/
public synchronized boolean doesDataExist(LevelPos levelPos)
{
LodRegion region = getRegion(levelPos.getRegionPos());
if(region == null)
{
return false;
}
return region.doesDataExist(levelPos);
}
/**
* return true if and only if the node at that position exist
*/
public synchronized DistanceGenerationMode getGenerationMode(LevelPos levelPos)
{
LodRegion region = getRegion(levelPos.getRegionPos());
if(region == null)
{
return DistanceGenerationMode.NONE;
}
return region.getGenerationMode(levelPos);
}
/**
* Get the region at the given X and Z coordinates from the
* RegionFileHandler.
*/
public synchronized LodRegion getRegionFromFile(RegionPos regionPos)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(regionPos);
else
return null;
}
/**
* Save all dirty regions in this LodDimension to file.
*/
public void saveDirtyRegionsToFileAsync()
{
fileHandler.saveDirtyRegionsToFileAsync();
}
/**
* 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 - center.x) + halfWidth;
int zIndex = (regionZ - center.z) + halfWidth;
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
}
public int getCenterX()
{
return center.x;
}
public int getCenterZ()
{
return center.z;
}
/**
* TODO Double check that this method works as expected
*
* Returns how many non-null LodChunks
* are stored in this LodDimension.
*/
public int getNumberOfLods()
{
/**TODO **/
int numbLods = 0;
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;
halfWidth = (int)Math.floor(width / 2);
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 += "(" + center.x + "," + center.z + ")";
return s;
}
}
@@ -1,665 +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.Serializable;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
/**
* This object contains all data useful to render LodBlock in a region (32x32 chunk to 512x512 block)
* for every node it contains the border of said node, its size, its block position in the world,
* color, height and depth.
*
* @author Leonardo Amato
* @author James Seibel
* @version 8-7-2021
*/
public class LodQuadTree implements Serializable
{
// note:
// The term node correspond to a LodQuadTree object
/*
Example on how a LodQuadTreeRegion would be rendered (the number corresponds to the level of the LodNodeData)
.___.___._______._______________.
|6|6| 7 | | |
|6|6|___| 8 | |
| 7 | 7 | | |
|___|___|_______| 9 |
| | | |
| 8 | 8 | |
| | | |
|_______|_______|_______________|
| | |
| | |
| | |
| 9 | 9 |
| | |
| | |
| | |
|_______________|_______________|
*/
/** If true SetNodesAtLowerLevel will update the color and height of all higher level nodes */
public static boolean UPDATE_HIGHER_LEVEL = true;
//data useful to render
//if children are present then lodNodeData should be a combination of the lodData of the child. This can be
//turned off by deselecting the recursive update in all update method.
private LodQuadTreeNode lodNode;
/*
.____.____.
| NW | NE | |
|____|____| Z
| SW | SE | |
|____|____| V
-----X---->
North - negative z
South - positive z
West - negative x
east - positive x
*/
/** treeFull is true if and only if all child are not null */
private boolean treeFull;
private boolean treeEmpty;
/**
* The four child are based on the four diagonal cardinal directions.
* The first index is for North and South and the second index is for East and West. <br>
* Children should always be null for level 0.
*/
private final LodQuadTree[][] children;
//parent should always be null for level 9, and always not null for other levels.
private final LodQuadTree parent;
/**
* Constructor for level 0 without LodNodeData (region level constructor)
*
* @param regionPos indicate the region position of the node
*/
//maybe the use of useLevelCoordinate could be changed. I could use a builder to do all this work.
public LodQuadTree(RegionPos regionPos)
{
this(null, new LodQuadTreeNode(LodUtil.REGION_DETAIL_LEVEL, regionPos.x, regionPos.z));
}
/**
* Constructor for generic world without LOD data
*
* @param parent parent of this node
* @param level level of this note
* @param regionPos position of the node
*/
public LodQuadTree(LodQuadTree parent, byte level, RegionPos regionPos)
{
this(parent, new LodQuadTreeNode(level, regionPos.x, regionPos.z));
}
/**
* Constructor using a dataList
*
* @param dataList list of LodNodeData to put in this LodQuadTree
* @param regionX x region coordinate
* @param regionZ z region coordinate
*/
public LodQuadTree(List<LodQuadTreeNode> dataList, int regionX, int regionZ)
{
this(null, new LodQuadTreeNode(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ));
setNodesAtLowerLevel(dataList);
}
/**
* Constructor for a generic LodQuadTree using a LodNodeData
*
* @param newLodNode LodQuadTreeNode containing all the information of this node
*/
public LodQuadTree(LodQuadTree newParent, LodQuadTreeNode newLodNode)
{
parent = newParent;
lodNode = newLodNode;
children = new LodQuadTree[2][2];
treeEmpty = true;
treeFull = false;
}
/**
* @param dataList list of data to put in the node
*/
public void setNodesAtLowerLevel(List<LodQuadTreeNode> dataList)
{
for (LodQuadTreeNode lodQuadTreeNode : dataList)
{
this.setNodeAtLowerLevel(lodQuadTreeNode);
}
}
/**
* @param newLowerLodNode data to put in the node
* @return true only if the QuadTree has been changed
*/
public boolean setNodeAtLowerLevel(LodQuadTreeNode newLowerLodNode)
{
byte targetLevel = newLowerLodNode.detailLevel;
byte currentLevel = lodNode.detailLevel;
if (targetLevel >= currentLevel)
{
// we can't add a node that has a equal or higher
// detail level than this region
return false;
}
short widthRatio = (short) (lodNode.width / (2 * newLowerLodNode.width));
int WE = Math.abs(Math.floorDiv(newLowerLodNode.posX , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(newLowerLodNode.posZ , widthRatio) % 2);
if (getChild(NS, WE) == null)
{
// if this child doesn't exist, create an empty one
setChild(NS, WE);
}
LodQuadTree child = getChild(NS, WE);
if (lodNode.compareComplexity(newLowerLodNode) > 0)
{
// the node we want to introduce is less complex than the current node
// we don't want to override higher complexity with lower complexity
return false;
}
else
{
if (targetLevel == currentLevel - 1)
{
// we are at the level we want to add the newLowerLodNode
child.setLodNodeData(newLowerLodNode);
return true;
}
else
{
// recurse until we reach the level we want to add the newLowerLodNode
return child.setNodeAtLowerLevel(newLowerLodNode);
}
}
}
/**
* Gets the LodQuadTreeNode at the given chunkPos.
* Returns null if no such LodQuadTreeNode exists.
*/
public LodQuadTreeNode getNodeAtChunkPos(ChunkPos chunkPos)
{
return getNodeAtPos(chunkPos.x, chunkPos.z, LodUtil.CHUNK_DETAIL_LEVEL);
}
/**
* Gets the LodQuadTreeNode at the given generic pos and detailLevel.
* Returns null if no such LodQuadTreeNode exists.
*/
public LodQuadTreeNode getNodeAtPos(int posX, int posZ, int detailLevel)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getNodeAtChunkPos given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
byte currentDetailLevel = lodNode.detailLevel;
if (detailLevel == currentDetailLevel)
{
return lodNode;
}
else if (detailLevel < currentDetailLevel)
{
// the detail level we need is lower, go down a layer
short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, detailLevel)));
int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2);
if (getChild(NS, WE) == null)
{
return null;
}
LodQuadTree child = getChild(NS, WE);
return child.getNodeAtPos(posX, posZ, detailLevel);
}
else
{
// the detail level was higher than this region's
return null;
}
}
/**
* Gets the LodQuadTree at the given generic pos and detailLevel.
* Returns null if no such LodQuadTreeNode exists.
*/
public LodQuadTree getLevelAtPos(int posX, int posZ, int detailLevel)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getNodeAtChunkPos given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
byte currentDetailLevel = lodNode.detailLevel;
if (detailLevel == currentDetailLevel)
{
return this;
}
else if (detailLevel < currentDetailLevel)
{
// the detail level we need is lower, go down a layer
short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, detailLevel)));
int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2);
if (getChild(NS, WE) == null)
{
return null;
}
LodQuadTree child = getChild(NS, WE);
return child.getLevelAtPos(posX, posZ, detailLevel);
}
else
{
// the detail level was higher than this region's
return null;
}
}
/**
* Put a child with the given data into the given position.
*
* @param newLodNode data to put in the child
*/
public void setChild(LodQuadTreeNode newLodNode)
{
// the child must be 1 detail level lower than this region
if (newLodNode.detailLevel == lodNode.detailLevel - 1)
{
int WE = newLodNode.posX % lodNode.posX;
int NS = newLodNode.posZ % lodNode.posZ;
children[NS][WE] = new LodQuadTree(this, lodNode);
}
}
/**
* Put an empty child in the given position.
*
* @param NS North-South position
* @param WE West-East position
*/
public void setChild(int NS, int WE)
{
// TODO is this correctly converting to a regionPos?
int childRegionX = lodNode.posX * 2 + WE;
int childRegionZ = lodNode.posZ * 2 + NS;
children[NS][WE] = new LodQuadTree(this, (byte) (lodNode.detailLevel - 1), new RegionPos(childRegionX, childRegionZ));
}
/**
* Delete all the children
*/
public void deleteChildren()
{
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
children[NS][WE] = null;
}
}
}
/**
* Cut the tree at the given target level
*/
public void cutTreeAtLevel(byte targetLevel) {
if (targetLevel <= lodNode.detailLevel) {
deleteChildren();
} else {
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
if (getChild(NS, WE) != null) {
getChild(NS, WE).cutTreeAtLevel(targetLevel);
}
}
}
}
}
/**
* Update this region's data, specifically levelFull and lodNodeData.
*
* @param recursiveUpdate if recursive is true the update will rise up to the level 0
*/
private void updateRegion(boolean recursiveUpdate)
{
boolean isFull = true;
boolean isEmpty = true;
// determine if this region is empty or full
List<LodQuadTreeNode> dataList = new ArrayList<>();
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
if (getChild(NS,WE) != null)
{
dataList.add(getChild(NS,WE).getLodNodeData());
isEmpty = false;
}
else
{
isFull = false;
}
}
}
treeFull = isFull;
treeEmpty = isEmpty;
// update this regions
lodNode.combineData(dataList);
// update sub regions if requested
if (lodNode.detailLevel < LodUtil.REGION_DETAIL_LEVEL && recursiveUpdate)
{
this.parent.updateRegion(recursiveUpdate);
}
}
/**
* Returns nodes that match the given mask.
*
* @param complexityMask holds the DistanceGenerationModes to accept
* @param getOnlyDirty if true it will return only dirty nodes
* @param getOnlyLeaf if true it will return only leaf nodes
* @return list of nodes
*/
public List<LodQuadTreeNode> getNodeListWithMask(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty,
boolean getOnlyLeaf)
{
List<LodQuadTreeNode> nodeList = new ArrayList<>();
if (hasChildren())
{
//There is at least 1 child
// this detail level's node
if (!getOnlyLeaf && !(getOnlyDirty && !lodNode.isDirty())
&& complexityMask.contains(lodNode.complexity))
{
nodeList.add(lodNode);
}
// search the children for valid nodes
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
LodQuadTree child = children[NS][WE];
if (child != null)
{
nodeList.addAll(child.getNodeListWithMask(complexityMask, getOnlyDirty, getOnlyLeaf));
}
}
}
}
else
{
// This tree has no children
if (!(getOnlyDirty && !lodNode.isDirty()) && (complexityMask.contains(lodNode.complexity)))
{
nodeList.add(lodNode);
}
}
return nodeList;
}
/**
* This method will return all the nodes that can be rendered
*
* @param playerPos position of the player
* @param targetLevel minimum level that can be rendered
* @param maxDistance maximum distance from the player
* @param minDistance minimum distance from the player
* @return
*/
public List<LodQuadTreeNode> getNodeToRender(BlockPos playerPos, int targetLevel,
Set<DistanceGenerationMode> complexityMask, int maxDistance, int minDistance)
{
int x = playerPos.getX();
int z = playerPos.getZ();
List<Integer> distances = new ArrayList<>();
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2)));
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
List<LodQuadTreeNode> nodeList = new ArrayList<>();
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance)))
{
// TODO why is !isNodeFull() here? Becouse if a node is not full then at least one child is missing.
// if one child is missing then there would be a hole if you try to render all the other child
if (targetLevel == lodNode.detailLevel || !isNodeFull())
{
// we have either reached the right detail level or this tree isn't full
if (!lodNode.isVoidNode() && complexityMask.contains(lodNode.complexity))
{
// this node isn't void and has the complexity level we are looking for
nodeList.add(lodNode);
}
}
else
{
// look for the correct targetLevel
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
LodQuadTree child = getChild(NS, WE);
if (child != null)
{
nodeList.addAll(child.getNodeToRender(playerPos, targetLevel, complexityMask, maxDistance, minDistance));
}
}
}
}
}
return nodeList;
}
/**
* Returns nodes that should be generated. <br>
* A node is generated only if it has child, is higher than the target level, and in the distance range.
*/
public List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> getNodesToGenerate(BlockPos playerPos, byte targetLevel,
DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance)
{
int x = playerPos.getX();
int z = playerPos.getZ();
List<Integer> distances = new ArrayList<>();
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2)));
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> nodeList = new ArrayList<>();
// TODO what is the purpose of isCoordianteInLevel?
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) || isCoordinateInQuadTree(playerPos)))
{
// TODO shouldn't tagetLevel be != lodNode.detailLevel?
if(!hasChildren() || targetLevel == lodNode.detailLevel)
{
if (this.lodNode.complexity.compareTo(complexityToGenerate) <= 0 )
{
nodeList.add(new AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>(this.lodNode, min));
}
}
else
{
// check if there are nodes further down that need generation
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
if (getChild(NS, WE) == null)
{
setChild(NS, WE);
}
nodeList.addAll(getChild(NS, WE).getNodesToGenerate(playerPos, targetLevel, complexityToGenerate, maxDistance, minDistance));
}
}
}
}
return nodeList;
}
/**
* setter for lodNodeData, to maintain a correct relationship between worlds
* this method forces an update on all parent nodes.
*
* @param newLodQuadTreeNode data to set
*/
public void setLodNodeData(LodQuadTreeNode newLodQuadTreeNode)
{
if (this.lodNode == null)
{
this.lodNode = newLodQuadTreeNode;
}
else
{
this.lodNode.updateData(newLodQuadTreeNode);
}
//a recursive update is necessary to change the higher levels
if (parent != null && UPDATE_HIGHER_LEVEL)
{
parent.updateRegion(true);
}
}
/**
* Returns if the given BlockPos is within the boundary of
* this LodQuadTree.
*/
public boolean isCoordinateInQuadTree(BlockPos pos)
{
return (lodNode.getStart().getX() * lodNode.width <= pos.getX() &&
lodNode.getStart().getZ() * lodNode.width <= pos.getZ() &&
lodNode.getEnd().getX() * lodNode.width >= pos.getX() &&
lodNode.getEnd().getZ() * lodNode.width >= pos.getZ());
}
//================//
// simple getters //
//================//
public LodQuadTree getChild(int NS, int WE)
{
return children[NS][WE];
}
public LodQuadTreeNode getLodNodeData()
{
return lodNode;
}
public boolean isNodeFull()
{
return treeFull;
}
public boolean hasChildren()
{
return !treeEmpty;
}
public boolean isRenderable()
{
return (lodNode != null);
}
@Override
public String toString()
{
String s = lodNode.toString();
s += treeFull ? "Full and " : "";
s += treeEmpty ? "Empty " : "";
if (lodNode != null)
s += "detail: " + lodNode.detailLevel;
/*
if(hasChildren())
{
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
LodQuadTree child = children[NS][WE];
if (child != null) {
s += '\n' + child.toString();
}
}
}
}
*/
return s;
}
}
@@ -1,670 +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 java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.util.math.BlockPos;
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 Leonardo Amato
* @author James Seibel
* @version 8-8-2021
*/
public class LodQuadTreeDimension
{
/**TODO a dimension should support two different type of quadTree.
* The ones that are near from the player should always be saved and can be fully generated (even at block level)
* The ones that are far from the player should always be non-savable and at a high level
* If this is not done then you could see how heavy a fully generated 64 region dimension can get.
* IDEA : use a mask like the "isRegionDirty" to achieve this*/
public final DimensionType dimension;
/** measured in regions */
private volatile int width;
/** measured in regions */
private volatile int halfWidth;
/** */
public static final Set<DistanceGenerationMode> FULL_COMPLEXITY_MASK = new HashSet<DistanceGenerationMode>();
static
{
// I moved the setup here because eclipse was complaining
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.BIOME_ONLY);
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.SURFACE);
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.FEATURES);
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.SERVER);
}
public volatile LodQuadTree regions[][];
public volatile boolean isRegionDirty[][];
private volatile RegionPos center;
private LodQuadTreeDimensionFileHandler fileHandler;
/**
* Creates the dimension centered at (0,0)
*
* @param newWidth in regions
*/
public LodQuadTreeDimension(DimensionType newDimension, LodQuadTreeWorld lodWorld, int newWidth)
{
dimension = newDimension;
width = newWidth;
halfWidth = (int)Math.floor(width / 2);
if(newDimension != null && lodWorld != null)
{
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 LodQuadTreeDimensionFileHandler(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 LodQuadTree[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;
center = new RegionPos(0,0);
}
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset. <br><br>
*
* Synchronized to prevent multiple moves happening on top of each other.
*/
public synchronized void move(RegionPos regionOffset)
{
int xOffset = regionOffset.x;
int zOffset = regionOffset.z;
// 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
center.x += xOffset;
center.z += 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
center.x += xOffset;
center.z += 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 LodQuadTree getRegion(RegionPos regionPos)
{
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
if (!regionIsInRange(regionPos.x, regionPos.z))
// out of range
return null;
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = getRegionFromFile(regionPos);
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = new LodQuadTree(regionPos);
}
}
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(LodQuadTree newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.getLodNodeData().posX - center.x) + halfWidth;
int zIndex = (center.z - newRegion.getLodNodeData().posZ) + halfWidth;
if (!regionIsInRange(newRegion.getLodNodeData().posX, newRegion.getLodNodeData().posZ))
// out of range
throw new ArrayIndexOutOfBoundsException();
regions[xIndex][zIndex] = newRegion;
}
/**
*this method creates all null regions
*/
public void initializeNullRegions()
{
int regionX;
int regionZ;
RegionPos regionPos;
LodQuadTree region;
for(int x = 0; x < regions.length; x++)
{
for(int z = 0; z < regions.length; z++)
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
regionPos = new RegionPos(regionX,regionZ);
region = getRegion(regionPos);
if (region == null)
{
// if no region exists, create it
region = new LodQuadTree(regionPos);
addOrOverwriteRegion(region);
}
}
}
}
/**
* 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 Boolean addNode(LodQuadTreeNode lodNode)
{
// don't continue if the region can't be saved
RegionPos regionPos = LodUtil.convertGenericPosToRegionPos(lodNode.posX, lodNode.posZ, lodNode.detailLevel);
if (!regionIsInRange(regionPos.x, regionPos.z))
{
return false;
}
LodQuadTree region = getRegion(regionPos);
if (region == null)
{
// if no region exists, create it
region = new LodQuadTree(regionPos);
addOrOverwriteRegion(region);
}
boolean nodeAdded = region.setNodeAtLowerLevel(lodNode);
// only save valid LODs to disk
if (!lodNode.dontSave && fileHandler != null)
{
try
{
// mark the region as dirty so it will be saved to disk
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
}
catch(ArrayIndexOutOfBoundsException e)
{
// This method was probably called when the dimension was changing size.
// Hopefully this shouldn't be an issue.
}
}
return nodeAdded;
}
/**
* Get the LodNodeData 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 LodQuadTreeNode getLodFromCoordinates(ChunkPos chunkPos)
{
return getLodFromCoordinates(chunkPos, LodUtil.CHUNK_DETAIL_LEVEL);
}
/**
* Get the LodNodeData 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 LodQuadTreeNode getLodFromCoordinates(ChunkPos chunkPos, int detailLevel)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodQuadTree region = getRegion(LodUtil.convertGenericPosToRegionPos(chunkPos.x, chunkPos.z, LodUtil.CHUNK_DETAIL_LEVEL));
if(region == null)
{
return null;
}
return region.getNodeAtChunkPos(chunkPos);
}
/**
* Get the LodNodeData 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 LodQuadTreeNode getLodFromCoordinates(int posX, int posZ, int detailLevel)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodQuadTree region = getRegion(LodUtil.convertGenericPosToRegionPos(posX, posZ, detailLevel));
if(region == null)
{
return null;
}
return region.getNodeAtPos(posX, posZ, detailLevel);
}
/**
* Get the LodNodeData 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 LodQuadTree getLevelFromPos(int posX, int posZ, int detailLevel)
{
if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
LodQuadTree region = getRegion(LodUtil.convertGenericPosToRegionPos(posX, posZ, detailLevel));
if(region == null)
{
return null;
}
return region.getLevelAtPos(posX, posZ, detailLevel);
}
/**
* return true if and only if the node at that position exist
*/
public boolean hasThisPositionBeenGenerated(ChunkPos chunkPos, int level)
{
if (level > LodUtil.REGION_DETAIL_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + level + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
return getLodFromCoordinates(chunkPos, level).detailLevel == level;
}
/**
* method to get all the nodes that have to be rendered based on the position of the player
* @return list of nodes
*/
public List<LodQuadTreeNode> getNodesToRender(BlockPos playerPos, int detailLevel,
Set<DistanceGenerationMode> complexityMask, int maxDistance, int minDistance)
{
List<LodQuadTreeNode> listOfData = new ArrayList<>();
// go through every region we have stored
for(int i = 0; i < regions.length; i++)
{
for(int j = 0; j < regions.length; j++)
{
listOfData.addAll(regions[i][j].getNodeToRender(playerPos, detailLevel, complexityMask, maxDistance, minDistance));
}
}
return listOfData;
}
/**
* Returns all LodQuadTreeNodes that need to be generated based on the position of the player
* @return list of quadTrees
*/
public List<LodQuadTreeNode> getNodesToGenerate(BlockPos playerPos, byte level, DistanceGenerationMode complexity,
int maxDistance, int minDistance)
{
int regionX;
int regionZ;
LodQuadTree region;
RegionPos regionPos;
List<Map.Entry<LodQuadTreeNode,Integer>> listOfQuadTree = new ArrayList<>();
// go through every region we have stored
for(int xIndex = 0; xIndex < regions.length; xIndex++)
{
for(int zIndex = 0; zIndex < regions.length; zIndex++)
{
regionX = (xIndex + center.x) - halfWidth;
regionZ = (zIndex + center.z) - halfWidth;
regionPos = new RegionPos(regionX,regionZ);
region = getRegion(regionPos);
if (region == null)
{
region = new LodQuadTree(regionPos);
addOrOverwriteRegion(region);
}
listOfQuadTree.addAll(region.getNodesToGenerate(playerPos, level, complexity, maxDistance, minDistance));
}
}
// TODO why are we sorting the list?
Collections.sort(listOfQuadTree, Map.Entry.comparingByValue());
return listOfQuadTree.stream().map(entry -> entry.getKey()).collect(Collectors.toList());
}
/**
* @see LodQuadTree#getNodeListWithMask
*/
public List<LodQuadTreeNode> getNodesWithMask(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf)
{
List<LodQuadTreeNode> listOfNodes = new ArrayList<>();
int xIndex;
int zIndex;
LodQuadTree region;
// go through every region we have stored
for(int xRegion = 0; xRegion < regions.length; xRegion++)
{
for(int zRegion = 0; zRegion < regions.length; zRegion++)
{
xIndex = (xRegion + center.x) - halfWidth;
zIndex = (zRegion + center.z) - halfWidth;
region = getRegion(new RegionPos(xIndex,zIndex));
// Recursively add any children
if (region != null)
{
listOfNodes.addAll(region.getNodeListWithMask(complexityMask, getOnlyDirty, getOnlyLeaf));
}
}
}
return listOfNodes;
}
/**
* Get the region at the given X and Z coordinates from the
* RegionFileHandler.
*/
public LodQuadTree getRegionFromFile(RegionPos regionPos)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(regionPos);
else
return null;
}
/**
* Save all dirty regions in this LodDimension to file.
*/
public void saveDirtyRegionsToFileAsync()
{
fileHandler.saveDirtyRegionsToFileAsync();
}
/**
* 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 - center.x) + halfWidth;
int zIndex = (regionZ - center.z) + halfWidth;
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
}
public int getCenterX()
{
return center.x;
}
public int getCenterZ()
{
return center.z;
}
/**
* TODO Double check that this method works as expected
*
* Returns how many non-null LodChunks
* are stored in this LodDimension.
*/
public int getNumberOfLods()
{
int numbLods = 0;
for (LodQuadTree[] regions : regions)
{
if(regions == null)
continue;
for (LodQuadTree region : regions)
{
if(region == null)
continue;
for(LodQuadTreeNode node : region.getNodeListWithMask(FULL_COMPLEXITY_MASK,false,true))
{
if (node != null && !node.voidNode)
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;
halfWidth = (int)Math.floor(width / 2);
regions = new LodQuadTree[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 += "(" + center.x + "," + center.z + ")";
return s;
}
}
@@ -1,412 +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 java.io.Serializable;
import java.util.List;
import java.util.Objects;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.gen.Heightmap;
/**
* This object contains position, size,
* and color data for an LOD object.
*
* @author Leonardo Amato
* @author James Seibel
* @version 8-8-2021
*/
public class LodQuadTreeNode implements Serializable
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER;
/** If we ever have to use a heightmap for any reason, use this one. */
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
/** this is how many pieces of data are exported when toData is called */
public static final int NUMBER_OF_DELIMITERS = 10;
/** If this is set to true then toData will return
* the empty string */
public boolean dontSave = false;
/** X position relative to the Quad tree. */
public final int posX;
/** Z position relative to the Quad tree */
public final int posZ;
//Complexity indicate how the block was built. This is important because we could use
public DistanceGenerationMode complexity;
/** Indicates how complicated this node is. <br>
* Goes from 0 to 9; 0 being the deepest (block size) and 9 being the highest (region size) */
public final byte detailLevel;
/** Indicates the width in blocks of this node. <br>
* Goes from 1 to 512 */
public final short width;
/** holds the height, depth, and color data for this Node. */
private LodDataPoint lodDataPoint;
/** if true this node doesn't have any data */
public boolean voidNode;
/** if dirty is true, then this node have unsaved changes */
public boolean dirty;
/**TODO There should be a check for the level. Level must be positive, i could use runtime exception or simple if*/
/**TODO There should be a good way to create node that must not be saved
* For example loading a 64 region wide dimension that is fully generated is too much memory heavy.
* There should be a way to create Node that are approximated and at region level, so you could load those
* for far region, and then when you get closer you load the actual region from the file or you generate it.
* */
/**
* Creates and empty LodDataPoint
* This LodDataPoint only contains the position data
* @param detailLevel of the node
* @param posX position x in the level
* @param posZ position z in the level
*/
public LodQuadTreeNode(byte detailLevel, int posX, int posZ)
{
this.detailLevel = detailLevel;
this.posX = posX;
this.posZ = posZ;
width = (short) Math.pow(2, detailLevel);
lodDataPoint = new LodDataPoint();
complexity = DistanceGenerationMode.NONE;
dirty = true;
voidNode = true;
dontSave = true;
}
/**
* Constructor for a LodNodeData
* @param level
* @param posX
* @param posZ
* @param height
* @param depth
* @param color
* @param complexity
*/
public LodQuadTreeNode(byte level, int posX, int posZ, short height, short depth , Color color, DistanceGenerationMode complexity)
{
this(level, posX, posZ, new LodDataPoint(height,depth,color), complexity);
}
/**
* Constructor for a LodNodeData
* @param level
* @param posX
* @param posZ
* @param height
* @param depth
* @param color
* @param complexity
*/
public LodQuadTreeNode(byte level, int posX, int posZ, int height, int depth, Color color, DistanceGenerationMode complexity)
{
this(level, posX, posZ, new LodDataPoint(height,depth,color), complexity);
}
/**
* Constructor for a LodNodeData
* @param detailLevel level of this
* @param posX
* @param posZ
* @param lodDataPoint
* @param complexity
*/
public LodQuadTreeNode(byte detailLevel, int posX, int posZ, LodDataPoint lodDataPoint, DistanceGenerationMode complexity)
{
this.detailLevel = detailLevel;
this.posX = posX;
this.posZ = posZ;
width = (short) Math.pow(2, detailLevel);
this.lodDataPoint = lodDataPoint;
this.complexity = complexity;
dirty = true;
voidNode = false;
dontSave = false;
}
public BlockPos getStart(){
return new BlockPos(posX * width, 0, posZ * width);
}
public BlockPos getEnd(){
return new BlockPos(posX * (width + 1) - 1, 0, posZ * (width + 1) - 1);
}
public BlockPos getCenter(){
return new BlockPos(posX * width + width/2, 0, posZ * width + width/2);
}
/**
* @throws IllegalArgumentException if the data string doesn't have the correct number of delimited entries
*/
public LodQuadTreeNode(String data) throws IllegalArgumentException
{
// make sure there are the correct number of entries
// in the data string
int count = 0;
for(int i = 0; i < data.length(); i++)
if(data.charAt(i) == DATA_DELIMITER)
count++;
if(count != NUMBER_OF_DELIMITERS)
throw new IllegalArgumentException("LodQuadTreeNode constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + NUMBER_OF_DELIMITERS + ".");
// start reading the data string
int index = 0;
int lastIndex = 0;
index = data.indexOf(DATA_DELIMITER, 0);
this.detailLevel = (byte) Integer.parseInt(data.substring(0,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.posX = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.posZ = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.complexity = DistanceGenerationMode.valueOf(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
short height = (short) Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
short depth = (short) Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int red = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int green = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int blue = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int alpha = Integer.parseInt(data.substring(lastIndex+1,index));
Color color = new Color(red,green,blue,alpha);
lodDataPoint = new LodDataPoint(height,depth,color);
int isVoid = Integer.parseInt(data.substring(lastIndex+1,index));
this.voidNode = (isVoid == 1);
width = (short) Math.pow(2, detailLevel);
dirty = false;
dontSave = false;
}
//================//
// data processes //
//================//
/**
* Replaces the data in this object.
*/
public void updateData(LodQuadTreeNode lodQuadTreeNode)
{
if (lodQuadTreeNode == null)
return;
lodDataPoint = lodQuadTreeNode.lodDataPoint;
complexity = lodQuadTreeNode.complexity;
voidNode = lodQuadTreeNode.voidNode;
dontSave = lodQuadTreeNode.dontSave;
dirty = true;
}
/**
* Combines and averages the data from a list of LodQuadTreeNodes into this node.
*/
public void combineData(List<LodQuadTreeNode> dataList)
{
if(dataList.isEmpty())
{
lodDataPoint = new LodDataPoint();
}
else
{
// get the lowest height from the all the given LodQuadTreeNodes
short height = (short) (dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().height).sum() / dataList.size());
// get the highest depth
short depth = (short) (dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().depth).sum() / dataList.size());
// get the average color
int red = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getRed()).sum() / dataList.size();
int green = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getGreen()).sum() / dataList.size();
int blue = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getBlue()).sum() / dataList.size();
Color color = new Color(red,green,blue);
lodDataPoint = new LodDataPoint(height, depth, color);
// the new complexity is equal to the lowest complexity of the list
DistanceGenerationMode minComplexity = DistanceGenerationMode.SERVER;
for(LodQuadTreeNode node: dataList)
{
if (minComplexity.compareTo(node.complexity) > 0)
{
minComplexity = node.complexity;
}
}
complexity = minComplexity;
voidNode = lodDataPoint.isEmpty();
}
dirty = true;
dontSave = false;
}
//===================//
// basic comparisons //
//===================//
public int compareComplexity(LodQuadTreeNode other)
{
return this.complexity.compareTo(other.complexity);
}
public boolean equals(LodQuadTreeNode other)
{
return (this.complexity == other.complexity
&& this.detailLevel == other.detailLevel
&& this.posX == other.posX
&& this.posZ == other.posZ
&& this.lodDataPoint.equals(other.lodDataPoint)
&& this.complexity == other.complexity
&& this.voidNode == other.voidNode);
}
//=========================//
// basic setters / getters //
//=========================//
public LodDataPoint getLodDataPoint()
{
return lodDataPoint;
}
public void setLodDataPoint(LodDataPoint newLodDataPoint)
{
lodDataPoint = newLodDataPoint;
// update if this is node is currently void
voidNode = (lodDataPoint == null);
}
public boolean isVoidNode()
{
return voidNode;
}
public boolean isDirty()
{
return dirty;
}
//========//
// output //
//========//
@Override
public int hashCode()
{
return Objects.hash(this.complexity, this.detailLevel, this.posX, this.posZ, this.lodDataPoint, this.voidNode);
}
/**
* Outputs all data in a csv format
*/
public String toData()
{
if (dontSave)
return "";
String s = Integer.toString(detailLevel) + DATA_DELIMITER
+ Integer.toString(posX) + DATA_DELIMITER
+ Integer.toString(posZ) + DATA_DELIMITER
+ complexity.toString() + DATA_DELIMITER
+ Integer.toString((lodDataPoint.height)) + DATA_DELIMITER
+ Integer.toString((lodDataPoint.depth)) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getRed()) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getGreen()) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getBlue()) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getAlpha()) + DATA_DELIMITER
+ Integer.toString(voidNode ? 1 : 0);
return s;
}
@Override
public String toString()
{
return this.toData();
}
}
@@ -1,20 +1,24 @@
package com.seibel.lod.objects;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.util.LodUtil;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import java.awt.*;
import java.io.Serializable;
/**STANDARD TO FOLLOW
/**
* STANDARD TO FOLLOW
* every coordinate called posX or posZ is a relative coordinate and not and absolute coordinate
* if an array contain coordinate the order is the following
* 0 for x, 1 for z in 2D
* 0 for x, 1 for y, 2 for z in 3D
*/
public class LodRegion{
public class LodRegion implements Serializable {
//x coord,
private byte minLevelOfDetail;
private byte minDetailLevel;
private static final byte POSSIBLE_LOD = 10;
private int numberOfPoints;
@@ -32,186 +36,346 @@ public class LodRegion{
//a node with 1 is node
private byte[][][] generationType;
private int regionPosX;
private int regionPosZ;
private boolean[][][] dataExistence;
public LodRegion(byte minimumLevelOfDetail, RegionPos regionPos){
public final int regionPosX;
public final int regionPosZ;
public LodRegion(LevelContainer levelContainer, RegionPos regionPos) {
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
this.minDetailLevel = levelContainer.detailLevel;
//Array of matrices of arrays
colors = new byte[POSSIBLE_LOD][][][];
colors = new byte[POSSIBLE_LOD][][][];
//Arrays of matrices
height = new short[POSSIBLE_LOD][][];
depth = new short[POSSIBLE_LOD][][];
generationType = new byte[POSSIBLE_LOD][][];
height = new short[POSSIBLE_LOD][][];
depth = new short[POSSIBLE_LOD][][];
generationType = new byte[POSSIBLE_LOD][][];
dataExistence = new boolean[POSSIBLE_LOD][][];
colors[minDetailLevel] = levelContainer.colors;
height[minDetailLevel] = levelContainer.height;
depth[minDetailLevel] = levelContainer.depth;
generationType[minDetailLevel] = levelContainer.generationType;
dataExistence[minDetailLevel] = levelContainer.dataExistence;
//Initialize all the different matrices
for(byte lod = minimumLevelOfDetail; lod <= LodUtil.REGION_DETAIL_LEVEL; lod ++){
for (byte lod = (byte) (minDetailLevel + 1); lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) {
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod);
colors[lod] = new byte[size][size][3];
height[lod] = new short[size][size];
depth[lod] = new short[size][size];
generationType[lod] = new byte[size][size];
dataExistence[lod] = new boolean[size][size];
}
int sizeDiff = (int) Math.pow(2,LodUtil.REGION_DETAIL_LEVEL - (minDetailLevel + 1));
LevelPos levelPos;
for(int x = 0; x < sizeDiff; x++){
for(int z = 0; z < sizeDiff; z++){
levelPos = new LevelPos((byte) (minDetailLevel+1), x, z);
update(levelPos);
}
}
}
public LodRegion(byte minDetailLevel, RegionPos regionPos) {
this.minDetailLevel = minDetailLevel;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
//Array of matrices of arrays
colors = new byte[POSSIBLE_LOD][][][];
//Arrays of matrices
height = new short[POSSIBLE_LOD][][];
depth = new short[POSSIBLE_LOD][][];
generationType = new byte[POSSIBLE_LOD][][];
dataExistence = new boolean[POSSIBLE_LOD][][];
//Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) {
int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod);
colors[lod] = new byte[size][size][3];
height[lod] = new short[size][size];
depth[lod] = new short[size][size];
generationType[lod] = new byte[size][size];
dataExistence[lod] = new boolean[size][size];
}
}
/**
* This method can be used to insert data into the LodRegion
* @param lod
* @param posX
* @param posZ
*
* @param levelPos
* @param dataPoint
* @param generationType
* @param update
* @return
*/
public boolean setData(byte lod, int posX, int posZ, LodDataPoint dataPoint, byte generationType, boolean update){
return setData(lod, posX, posZ, (byte) (dataPoint.color.getRed() - 128), (byte) (dataPoint.color.getGreen() - 128), (byte) (dataPoint.color.getBlue() - 128), dataPoint.height, dataPoint.depth, generationType, update);
}
/**
* This method can be used to insert data into the LodRegion
* @param lod
* @param posX
* @param posZ
* @param red
* @param green
* @param blue
* @param height
* @param depth
* @param generationType
* @param update
* @return
*/
public boolean setData(byte lod, int posX, int posZ, byte red, byte green, byte blue, short height, short depth, byte generationType, boolean update){
posX = Math.floorMod(posX, (int) Math.pow(2,lod));
posZ = Math.floorMod(posZ, (int) Math.pow(2,lod));
if( (this.generationType[lod][posX][posZ] == 0) || (generationType < this.generationType[lod][posX][posZ]) ) {
public boolean setData(LevelPos levelPos, LodDataPoint dataPoint, byte generationType, boolean update) {
levelPos = levelPos.regionModule();
if ((this.generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] == 0) || (generationType >= this.generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ])) {
//update the number of node present
if (this.generationType[lod][posX][posZ] == 0) numberOfPoints++ ;
//if (this.generationType[lod][posX][posZ] == 0) numberOfPoints++;
//add the node data
this.colors[lod][posX][posZ][0] = red;
this.colors[lod][posX][posZ][1] = green;
this.colors[lod][posX][posZ][2] = blue;
this.height[lod][posX][posZ] = height;
this.depth[lod][posX][posZ] = depth;
this.generationType[lod][posX][posZ] = generationType;
//update all the higher level
int tempPosX = posX;
int tempPosZ = posZ;
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] = (byte) (dataPoint.color.getRed() - 128);
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] = (byte) (dataPoint.color.getGreen() - 128);
this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] = (byte) (dataPoint.color.getBlue() - 128);
this.height[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = dataPoint.height;
this.depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = dataPoint.depth;
this.generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = generationType;
this.dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = true;
//update could be stopped and a single big update could be done at the end
if(update) {
for (byte tempLod = (byte) (lod + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++) {
tempPosX = Math.floorDiv(tempPosX, 2);
tempPosZ = Math.floorDiv(tempPosZ, 2);
update(tempLod, tempPosX, tempPosZ);
LevelPos tempLevelPos = levelPos;
if (update) {
for (byte tempLod = (byte) (levelPos.detailLevel + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++) {
tempLevelPos = tempLevelPos.convert(tempLod);
update(tempLevelPos);
}
}
return true; //added
}else{
return false; //not added
return true;
} else {
return false;
}
}
public LodDataPoint getData(ChunkPos chunkPos) {
return getData(new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z));
}
/**
* This method will return the data in the position relative to the level of detail
*
* @param lod
* @param posX
* @param posZ
* @return the data at the relative pos and level
*/
public LodDataPoint getData(byte lod, int posX, int posZ){
public LodDataPoint getData(byte lod, BlockPos blockPos) {
int posX = Math.floorMod(blockPos.getX(), (int) Math.pow(2, lod));
int posZ = Math.floorMod(blockPos.getZ(), (int) Math.pow(2, lod));
return getData(new LevelPos(lod, posX, posZ));
}
/**
* This method will return the data in the position relative to the level of detail
*
* @param levelPos
* @return the data at the relative pos and level
*/
public LodDataPoint getData(LevelPos levelPos) {
levelPos = levelPos.regionModule();
return new LodDataPoint(
height[lod][posX][posZ],
depth[lod][posX][posZ],
new Color(colors[lod][posX][posZ][0] + 128,
colors[lod][posX][posZ][1] + 128,
colors[lod][posX][posZ][2] + 128
height[levelPos.detailLevel][levelPos.posX][levelPos.posZ],
depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ],
new Color(colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] + 128,
colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] + 128,
colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] + 128
)
);
}
/*
private void updateArea(byte lod, int posX, int posZ){
}
*/
private void update(byte lod, int posX, int posZ){
posX = Math.floorMod(posX, (int) Math.pow(2,lod));
posZ = Math.floorMod(posZ, (int) Math.pow(2,lod));
boolean[][] children = getChildren(lod, posX, posZ);
int numberOfChild = 0;
for(int x = 0; x <= 1; x++) {
/**TODO a method to update a whole area, to be used as a single big update*/
/**
* @param levelPos
*/
private void updateArea(LevelPos levelPos) {
/*
LevelPos tempLevelPos = levelPos;
int sizeDiff;
int startX;
int startZ;
for(int bottom = minLevelOfDetail + 1 ; bottom < levelPos.detailLevel ; bottom ++){
tempLevelPos = levelPos.convert(bottom);
startX = tempLevelPos.posX;
startZ = tempLevelPos.posZ;
sizeDiff = (int) Math.pow(2, levelPos.detailLevel - bottom);
for(int x = 0; x < sizeDiff; x++){
for(int z = 0; z < sizeDiff; z++) {
update(new LevelPos(bottom, startX+x, startZ+z));
}
}
}
*/
}
/**
* @param levelPos
*/
private void update(LevelPos levelPos) {
levelPos = levelPos.regionModule();
boolean[][] children = getChildren(levelPos);
int numberOfChildren = 0;
/**TODO add the ability to change how the heigth and depth are determinated (for example min or max)**/
byte minGenerationType = 10;
int tempRed = 0;
int tempGreen = 0;
int tempBlue = 0;
int tempHeight = 0;
int tempDepth = 0;
int newPosX;
int newPosZ;
byte newLod;
LevelPos childPos;
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
if(children[x][z]){
numberOfChild++;
newPosX = 2 * levelPos.posX + x;
newPosZ = 2 * levelPos.posZ + z;
newLod = (byte) (levelPos.detailLevel - 1);
childPos = new LevelPos(newLod, newPosX, newPosZ);
if (hasDataBeenGenerated(childPos)) {
numberOfChildren++;
tempRed += colors[newLod][newPosX][newPosZ][0];
tempGreen += colors[newLod][newPosX][newPosZ][1];
tempBlue += colors[newLod][newPosX][newPosZ][2];
tempHeight += height[newLod][newPosX][newPosZ];
tempDepth += depth[newLod][newPosX][newPosZ];
minGenerationType = (byte) Math.min(minGenerationType, generationType[newLod][newPosX][newPosZ]);
}
}
}
if(numberOfChild>0) {
//int minDepth = Integer.MAX_VALUE;
//int maxDepth = Integer.MIN_VALUE;
//int minHeight = Integer.MAX_VALUE;
//int maxHeight = Integer.MIN_VALUE;
byte minGenerationType = 0;
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
if (children[x][z]) {
int newPosX = 2 * posX + x;
int newPosZ = 2 * posZ + z;
for (int col = 0; col <= 2; col++) {
colors[lod][posX][posZ][col] += (byte) (colors[lod - 1][newPosX][newPosZ][col] / numberOfChild);
}
//TODO ability to change between mean, max and min.
height[lod][posX][posZ] += (short) (height[lod - 1][newPosX][newPosZ] / numberOfChild);
//minHeight = Math.min( height[lod - 1][newPosX][newPosZ] , maxHeight);
//maxHeight = Math.max( height[lod - 1][newPosX][newPosZ] , minHeight);
depth[lod][posX][posZ] += (short) (depth[lod - 1][newPosX][newPosZ] / numberOfChild);
//minDepth = Math.min( depth[lod - 1][newPosX][newPosZ] , maxDepth);
//maxDepth = Math.max( depth[lod - 1][newPosX][newPosZ] , minDepth);
minGenerationType = (byte) Math.max(minGenerationType, generationType[lod - 1][newPosX][newPosZ]);
}
}
}
//height[lod][posX][posZ] = minHeight;
//depth[lod][posX][posZ] = maxDepth;
generationType[lod][posX][posZ] = minGenerationType;
if (numberOfChildren > 0) {
colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] = (byte) (tempRed / numberOfChildren);
colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] = (byte) (tempGreen / numberOfChildren);
colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] = (byte) (tempBlue / numberOfChildren);
height[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = (short) (tempHeight / numberOfChildren);
depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = (short) (tempDepth / numberOfChildren);
generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = minGenerationType;
dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = true;
}
}
private boolean[][] getChildren(byte lod, int posX, int posZ){
posX = Math.floorMod(posX, (int) Math.pow(2,lod));
posZ = Math.floorMod(posZ, (int) Math.pow(2,lod));
/**
* @param levelPos
* @return
*/
private boolean[][] getChildren(LevelPos levelPos) {
levelPos = levelPos.regionModule();
boolean[][] children = new boolean[2][2];
int numberOfChild=0;
if(minLevelOfDetail == lod){
int numberOfChild = 0;
if (minDetailLevel == levelPos.detailLevel) {
return children;
}
for(int x = 0; x <= 1; x++) {
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
children[x][z] = (generationType[lod-1][2*posX+x][2*posZ+z] != 0);
children[x][z] = (dataExistence[levelPos.detailLevel - 1][2 * levelPos.posX + x][2 * levelPos.posZ + z]);
}
}
return children;
}
private void removeDetailLevel(byte lod, byte[][][] colors, short[][] height, short[][] depth, byte[][] generationType){
/**
* @param chunkPos
* @return
*/
public boolean doesDataExist(ChunkPos chunkPos) {
return doesDataExist(new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z));
}
private void addDetailLevel(byte lod, int posX, int posZ){
/**
* @param levelPos
* @return
*/
public boolean doesDataExist(LevelPos levelPos) {
levelPos = levelPos.regionModule();
return dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ];
}
/**
* @param levelPos
* @return
*/
public DistanceGenerationMode getGenerationMode(LevelPos levelPos) {
levelPos = levelPos.regionModule();
DistanceGenerationMode generationMode = DistanceGenerationMode.NONE;
switch (generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ]) {
case 0:
generationMode = DistanceGenerationMode.NONE;
break;
case 1:
generationMode = DistanceGenerationMode.BIOME_ONLY;
break;
case 2:
generationMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
break;
case 3:
generationMode = DistanceGenerationMode.SURFACE;
break;
case 4:
generationMode = DistanceGenerationMode.FEATURES;
break;
case 5:
generationMode = DistanceGenerationMode.SERVER;
break;
default:
generationMode = DistanceGenerationMode.NONE;
break;
}
return generationMode;
}
/**
* @param levelPos
* @return
*/
public boolean hasDataBeenGenerated(LevelPos levelPos) {
levelPos = levelPos.regionModule();
return (generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] != 0);
}
public byte getMinDetailLevel() {
return minDetailLevel;
}
/**
* This will be used to save a level
*
* @param lod
* @return
*/
public LevelContainer getLevel(byte lod) {
return new LevelContainer(lod, colors[lod], height[lod], depth[lod], generationType[lod], dataExistence[lod]);
}
/**
* @param levelContainer
*/
public void addLevel(LevelContainer levelContainer) {
if (levelContainer.detailLevel < minDetailLevel - 1) {
throw new IllegalArgumentException("addLevel requires a level that is at least the minimum level of the region -1 ");
}
if (levelContainer.detailLevel == minDetailLevel - 1) minDetailLevel = levelContainer.detailLevel;
colors[levelContainer.detailLevel] = levelContainer.colors;
height[levelContainer.detailLevel] = levelContainer.height;
depth[levelContainer.detailLevel] = levelContainer.depth;
generationType[levelContainer.detailLevel] = levelContainer.generationType;
dataExistence[levelContainer.detailLevel] = levelContainer.dataExistence;
}
/**
* @param lod
*/
public void removeDetailLevel(byte lod) {
for (byte tempLod = 0; tempLod <= lod; tempLod++) {
colors[tempLod] = new byte[0][0][0];
height[tempLod] = new short[0][0];
depth[tempLod] = new short[0][0];
generationType[tempLod] = new byte[0][0];
dataExistence[tempLod] = new boolean[0][0];
}
}
}
@@ -31,11 +31,11 @@ import net.minecraft.world.DimensionType;
* @author Leonardo Amato
* @version 8-17-2021
*/
public class LodQuadTreeWorld
public class LodWorld
{
private String worldName;
private Map<DimensionType, LodQuadTreeDimension> lodDimensions;
private Map<DimensionType, LodDimension> lodDimensions;
/** If true then the LOD world is setup and ready to use */
private boolean isWorldLoaded = false;
@@ -44,7 +44,7 @@ public class LodQuadTreeWorld
public LodQuadTreeWorld()
public LodWorld()
{
worldName = NO_WORLD_LOADED;
}
@@ -69,7 +69,7 @@ public class LodQuadTreeWorld
return;
worldName = newWorldName;
lodDimensions = new Hashtable<DimensionType, LodQuadTreeDimension>();
lodDimensions = new Hashtable<DimensionType, LodDimension>();
isWorldLoaded = true;
}
@@ -90,7 +90,7 @@ public class LodQuadTreeWorld
* Adds newStorage to this world, if a LodQuadTreeDimension
* already exists for the given dimension it is replaced.
*/
public void addLodDimension(LodQuadTreeDimension newStorage)
public void addLodDimension(LodDimension newStorage)
{
if (lodDimensions == null)
return;
@@ -101,7 +101,7 @@ public class LodQuadTreeWorld
/**
* Returns null if no LodQuadTreeDimension exists for the given dimension
*/
public LodQuadTreeDimension getLodDimension(DimensionType dimension)
public LodDimension getLodDimension(DimensionType dimension)
{
if (lodDimensions == null)
return null;
@@ -17,6 +17,8 @@
*/
package com.seibel.lod.proxy;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -29,8 +31,6 @@ import com.seibel.lod.enums.FogDrawOverride;
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.LodQuadTreeWorld;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.render.LodNodeRenderer;
import com.seibel.lod.util.LodUtil;
@@ -54,7 +54,7 @@ public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
private static LodQuadTreeWorld lodWorld = new LodQuadTreeWorld();
private static LodWorld lodWorld = new LodWorld();
private static LodNodeBuilder lodNodeBuilder = new LodNodeBuilder();
private static LodNodeBufferBuilder lodBufferBuilder = new LodNodeBufferBuilder(lodNodeBuilder);
private static LodNodeRenderer renderer = new LodNodeRenderer(lodBufferBuilder);
@@ -89,11 +89,11 @@ public class ClientProxy
{
if (mc == null || mc.player == null || !lodWorld.getIsWorldLoaded())
return;
viewDistanceChangedEvent();
LodQuadTreeDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
if (lodDim == null)
return;
@@ -142,14 +142,14 @@ public class ClientProxy
LodConfig.CLIENT.maxDrawDetail.set(LodDetail.FULL);
LodConfig.CLIENT.maxGenerationDetail.set(LodDetail.FULL);
LodConfig.CLIENT.lodChunkRadiusMultiplier.set(20);
LodConfig.CLIENT.lodChunkRadiusMultiplier.set(16);
LodConfig.CLIENT.fogDistance.set(FogDistance.FAR);
LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.NEVER_DRAW_FOG);
LodConfig.CLIENT.fogDrawOverride.set(FogDrawOverride.ALWAYS_DRAW_FOG_FANCY);
LodConfig.CLIENT.shadingMode.set(ShadingMode.DARKEN_SIDES);
// LodConfig.CLIENT.brightnessMultiplier.set(1.0);
// LodConfig.CLIENT.saturationMultiplier.set(1.0);
LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES);
LodConfig.CLIENT.allowUnstableFeatureGeneration.set(false);
LodConfig.CLIENT.numberOfWorldGenerationThreads.set(16);
@@ -224,7 +224,7 @@ public class ClientProxy
/**
* Re-centers the given LodDimension if it needs to be.
*/
private void playerMoveEvent(LodQuadTreeDimension lodDim)
private void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(mc.player.blockPosition());
@@ -237,15 +237,16 @@ public class ClientProxy
//LOGGER.info("offset: " + worldRegionOffset.x + "," + worldRegionOffset.z + "\t center: " + lodDim.getCenterX() + "," + lodDim.getCenterZ());
}
}
/**
* Re-sizes all LodDimensions if they needs to be.
*/
private void viewDistanceChangedEvent()
{
// calculate how wide the dimension(s) should be in regions
int chunksWide = (mc.options.renderDistance * 2) * LodConfig.CLIENT.lodChunkRadiusMultiplier.get();
//int chunksWide = (mc.options.renderDistance * 2) * LodConfig.CLIENT.lodChunkRadiusMultiplier.get();
int chunksWide = 8 * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + 1;
int newWidth = (int)Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
newWidth = (newWidth % 2 == 0) ? (newWidth += 1) : (newWidth += 2); // make sure we have a odd number of regions
@@ -262,13 +263,13 @@ public class ClientProxy
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
}
}
//================//
// public getters //
//================//
public static LodQuadTreeWorld getLodWorld()
public static LodWorld getLodWorld()
{
return lodWorld;
}
@@ -22,6 +22,7 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.HashSet;
import com.seibel.lod.objects.LevelPos;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.NVFogDistance;
@@ -35,8 +36,7 @@ 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.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarFogSettings;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodUtil;
@@ -146,7 +146,7 @@ public class LodNodeRenderer
* @param partialTicks how far into the current tick this method was called.
*/
@SuppressWarnings("deprecation")
public void drawLODs(LodQuadTreeDimension lodDim, float partialTicks, IProfiler newProfiler)
public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler)
{
if (lodDim == null)
{
@@ -213,10 +213,12 @@ public class LodNodeRenderer
// determine how far the game's render distance is currently set
farPlaneBlockDistance = mc.options.renderDistance * LodUtil.CHUNK_WIDTH;
//farPlaneBlockDistance = mc.options.renderDistance * LodUtil.CHUNK_WIDTH;
farPlaneBlockDistance = 8 * LodUtil.CHUNK_WIDTH;
// set how how far the LODs will go
int numbChunksWide = mc.options.renderDistance * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get();
//int numbChunksWide = mc.options.renderDistance * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get();
int numbChunksWide = 8 * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + 1;
// determine which LODs should not be rendered close to the player
HashSet<ChunkPos> chunkPosToSkip = getNearbyLodChunkPosToSkip(lodDim, player.blockPosition());
@@ -578,7 +580,7 @@ public class LodNodeRenderer
* setup the lighting to be used for the LODs
*/
@SuppressWarnings("deprecation")
private void setupLighting(LodQuadTreeDimension lodDimension, float partialTicks)
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;
@@ -771,7 +773,7 @@ public class LodNodeRenderer
* Get a HashSet of all ChunkPos within the normal render distance
* that should not be rendered.
*/
private HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodQuadTreeDimension lodDim, BlockPos playerPos)
private HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos)
{
int chunkRenderDist = mc.options.renderDistance;
int blockRenderDist = chunkRenderDist * 16;
@@ -786,11 +788,12 @@ public class LodNodeRenderer
{
for(int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
{
LodQuadTreeNode lod = lodDim.getLodFromCoordinates(new ChunkPos(x, z), 4);
if (lod != null)
LevelPos levelPos = new LevelPos((byte) 4, x, z);
if (lodDim.doesDataExist(levelPos))
{
short lodHighestPoint = lod.getLodDataPoint().height;
short lodHighestPoint = lodDim.getData(levelPos).height;
if (playerPos.getY() < lodHighestPoint)
{
// don't draw Lod's that are taller than the player