Close #28 (Optimize Distance Lod Generation)

This commit is contained in:
James Seibel
2021-06-27 22:00:43 -05:00
parent 46e65704d7
commit bb07e3db9a
11 changed files with 891 additions and 653 deletions
@@ -4,6 +4,7 @@ import java.util.concurrent.Executors;
import org.lwjgl.opengl.GL11;
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
@@ -4,7 +4,6 @@ import java.awt.Color;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.enums.ColorDirection;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
@@ -15,10 +14,12 @@ import com.seibel.lod.util.LodUtil;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.color.BlockColors;
import net.minecraft.block.FlowingFluidBlock;
import net.minecraft.block.GrassBlock;
import net.minecraft.block.LeavesBlock;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.chunk.ChunkSection;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.Heightmap;
@@ -29,12 +30,10 @@ import net.minecraft.world.gen.Heightmap;
* (specifically: Lod World, Dimension, Region, and Chunk objects)
*
* @author James Seibel
* @version 6-19-2021
* @version 6-27-2021
*/
public class LodBuilder
{
private static final Color INVISIBLE = new Color(0,0,0,0);
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
/** Default size of any LOD regions we use */
@@ -52,18 +51,15 @@ public class LodBuilder
}
public void generateLodChunkAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
generateLodChunkAsync(chunk, new LodBuilderConfig(), lodWorld, world);
}
public void generateLodChunkAsync(IChunk chunk, LodBuilderConfig config, LodWorld lodWorld, IWorld world)
{
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
return;
// is this chunk from the same world as the lodWorld?
if (!lodWorld.getWorldName().equals(LodUtil.getWorldID(world)))
// we are not in the same world anymore
// don't add this LOD
return;
// don't try to create an LOD object
// if for some reason we aren't
@@ -77,7 +73,7 @@ public class LodBuilder
{
DimensionType dim = world.getDimensionType();
LodChunk lod = generateLodFromChunk(chunk);
LodChunk lod = generateLodFromChunk(chunk, config);
LodDimension lodDim;
@@ -117,7 +113,7 @@ public class LodBuilder
*/
public LodChunk generateLodFromChunk(IChunk chunk) throws IllegalArgumentException
{
return generateLodFromChunk(chunk, false);
return generateLodFromChunk(chunk, new LodBuilderConfig());
}
/**
@@ -126,7 +122,7 @@ public class LodBuilder
* @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/
public LodChunk generateLodFromChunk(IChunk chunk, boolean useHeightmap) throws IllegalArgumentException
public LodChunk generateLodFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException
{
if(chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
@@ -142,20 +138,22 @@ public class LodBuilder
int endX = detail.endX[i];
int endZ = detail.endZ[i];
Color color;
Color color = generateLodColorForAreaInDirection(chunk, ColorDirection.TOP, startX, startZ, endX, endZ);
color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
short height;
short depth;
if (!useHeightmap)
if (!config.useHeightmap)
{
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
}
else
{
height = determineHeightPoint(chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE), startX, startZ, endX, endZ);
height = determineHeightPoint(chunk.getHeightmap(LodChunk.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
depth = 0;
}
@@ -304,38 +302,156 @@ public class LodBuilder
}
/**
* Generate the color for the given chunk in the given ColorDirection.
* NOTE: only vertical is currently implemented for area,
* the horizontal colors will always be the same regardless of the area.
* Generate the color for the given chunk using biome
* water color, foliage color, and grass color.
*
* @param config_useSolidBlocksInColorGen <br>
* If true we look down from the top of the <br>
* chunk until we find a non-invisible block, and then use <br>
* its color. If false we generate the color immediately for <br>
* each x and z.
* @param config_useBiomeColors <br>
* If true use biome foliage, water, and grass colors, <br>
* otherwise use the
*/
private Color generateLodColorForAreaInDirection(IChunk chunk, ColorDirection colorDir, int startX, int startZ, int endX, int endZ)
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
{
Minecraft mc = Minecraft.getInstance();
BlockColors bc = mc.getBlockColors();
ChunkSection[] chunkSections = chunk.getSections();
switch (colorDir)
int numbOfBlocks = 0;
int red = 0;
int green = 0;
int blue = 0;
for(int x = startX; x < endX; x++)
{
case TOP:
return generateLodColorVerticalOverArea(chunk, colorDir, bc, startX, startZ, endX, endZ);
case BOTTOM:
return generateLodColorVerticalOverArea(chunk, colorDir, bc, startX, startZ, endX, endZ);
case NORTH:
return generateLodColorHorizontal(chunk, colorDir, bc);
case SOUTH:
return generateLodColorHorizontal(chunk, colorDir, bc);
case EAST:
return generateLodColorHorizontal(chunk, colorDir, bc);
case WEST:
return generateLodColorHorizontal(chunk, colorDir, bc);
for(int z = startZ; z < endZ; z++)
{
boolean foundBlock = false;
// go top down
for(int i = chunkSections.length - 1; !foundBlock && i >= 0; i--)
{
if( !foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen))
{
for(int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--)
{
int colorInt = 0;
BlockState blockState = null;
if (chunkSections[i] != null)
{
blockState = chunkSections[i].getBlockState(x, y, z);
colorInt = blockState.materialColor.colorValue;
}
if(colorInt == 0 && config.useSolidBlocksInColorGen)
{
// skip air or invisible blocks
continue;
}
if (config.useBiomeColors)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, y + i * chunkSections.length, z);
if (biome.getCategory() == Biome.Category.OCEAN ||
biome.getCategory() == Biome.Category.RIVER)
{
colorInt = biome.getWaterColor();
}
else if (biome.getCategory() == Biome.Category.EXTREME_HILLS)
{
colorInt = Blocks.STONE.getMaterialColor().colorValue;
}
else if (biome.getCategory() == Biome.Category.ICY)
{
colorInt = LodUtil.colorToInt(Color.WHITE);
}
else if (biome.getCategory() == Biome.Category.THEEND)
{
colorInt = Blocks.END_STONE.getDefaultState().materialColor.colorValue;
}
else if (config.useSolidBlocksInColorGen)
{
colorInt = getColorForBlock(x, z, blockState, biome);
}
else
{
colorInt = biome.getGrassColor(x, z);
}
}
else
{
Biome biome = chunk.getBiomes().getNoiseBiome(x, y + i * chunkSections.length, z);
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;
}
}
}
}
}
return INVISIBLE;
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;
if (blockState == Blocks.AIR.getDefaultState())
{
colorInt = biome.getGrassColor(x, z);
}
else if (blockState.getBlock() instanceof LeavesBlock)
{
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
colorInt = LodUtil.colorToInt(leafColor);
}
else if (blockState.getBlock() instanceof GrassBlock)
{
colorInt = biome.getGrassColor(x, z);
}
else if (blockState.getBlock() instanceof FlowingFluidBlock)
{
colorInt = biome.getWaterColor();
}
else
{
colorInt = blockState.materialColor.colorValue;
}
return colorInt;
}
@@ -366,311 +482,7 @@ public class LodBuilder
return false;
}
/**
* Generates the color of the top or bottom of the given chunk.
*
* @throws IllegalArgumentException if given a ColorDirection other than TOP or BOTTOM
*/
private Color generateLodColorVerticalOverArea(
IChunk chunk, ColorDirection colorDir, BlockColors bc,
int startX, int startZ, int endX, int endZ)
{
if(colorDir != ColorDirection.TOP && colorDir != ColorDirection.BOTTOM)
{
throw new IllegalArgumentException("generateLodColorVertical only accepts the ColorDirection TOP or BOTTOM");
}
ChunkSection[] chunkSections = chunk.getSections();
int numbOfBlocks = 0;
int red = 0;
int green = 0;
int blue = 0;
boolean goTopDown = (colorDir == ColorDirection.TOP);
// either go top down or bottom up
int dataStart = goTopDown? chunkSections.length - 1 : 0;
int dataMax = chunkSections.length;
int dataMin = 0;
int dataIncrement = goTopDown? -1 : 1;
int topStart = goTopDown? CHUNK_SECTION_HEIGHT - 1 : 0;
int topMax = CHUNK_SECTION_HEIGHT;
int topMin = 0;
int topIncrement = goTopDown? -1 : 1;
for(int x = startX; x < endX; x++)
{
for(int z = startZ; z < endZ; z++)
{
boolean foundBlock = false;
for(int i = dataStart; !foundBlock && i >= dataMin && i < dataMax; i += dataIncrement)
{
if(!foundBlock && chunkSections[i] != null)
{
for(int y = topStart; !foundBlock && y >= topMin && y < topMax; y += topIncrement)
{
int ci;
ci = chunkSections[i].getBlockState(x, y, z).materialColor.colorValue;
if(ci == 0)
{
// skip air or invisible blocks
continue;
}
Color c = LodUtil.intToColor(ci);
red += c.getRed();
green += c.getGreen();
blue += c.getBlue();
numbOfBlocks++;
// we found a valid block, skip to the
// next x and z
foundBlock = true;
}
}
}
}
}
if(numbOfBlocks == 0)
numbOfBlocks = 1;
red /= numbOfBlocks;
green /= numbOfBlocks;
blue /= numbOfBlocks;
return new Color(red, green, blue);
}
/*
* unused variation that can be used with only the heightmap,
* although it just returns the foliage color, so it shouldn't
* be used normally.
Heightmap heightmap = chunk.getHeightmap(Heightmap.Type.WORLD_SURFACE_WG);
int numbOfBlocks = CHUNK_DATA_WIDTH * CHUNK_DATA_WIDTH;
int red = 0;
int green = 0;
int blue = 0;
for(int x = 0; x < CHUNK_DATA_WIDTH; x++)
{
Biome biome = chunk.getBiomes().getNoiseBiome(x,z, heightmap.getHeight(x, z));
Color c = intToColor(biome.getFoliageColor());
red += c.getRed();
green += c.getGreen();
blue += c.getBlue();
}
}
red /= numbOfBlocks;
green /= numbOfBlocks;
blue /= numbOfBlocks;
return new Color(red, green, blue);
*/
/**
* Generates the color for the sides of the given chunk.
*/
private Color generateLodColorHorizontal(
IChunk chunk, ColorDirection colorDir, BlockColors bc)
{
if(colorDir != ColorDirection.NORTH && colorDir != ColorDirection.SOUTH && colorDir != ColorDirection.EAST && colorDir != ColorDirection.WEST)
{
throw new IllegalArgumentException("generateLodColorHorizontal only accepts the ColorDirection N (North), S (South), E (East), or W (West)");
}
ChunkSection[] chunkSections = chunk.getSections();
int numbOfBlocks = 0;
int red = 0;
int green = 0;
int blue = 0;
// these don't change since the over direction doesn't matter
int overStart = 0;
int overIncrement = 1;
// determine which direction is "in"
int inStart = 0;
int inIncrement = 1;
switch (colorDir)
{
case NORTH:
inStart = 0;
inIncrement = 1;
break;
case SOUTH:
inStart = CHUNK_DATA_WIDTH - 1;
inIncrement = -1;
break;
case EAST:
inStart = 0;
inIncrement = 1;
break;
case WEST:
inStart = CHUNK_DATA_WIDTH - 1;
inIncrement = -1;
break;
default:
// we were given an invalid position, return invisible.
// this shouldn't happen and is mostly here to make the
// compiler happy
return INVISIBLE;
}
for (int section = 0; section < chunkSections.length; section++)
{
if (chunkSections[section] == null)
continue;
for (int y = 0; y < CHUNK_SECTION_HEIGHT; y++)
{
boolean foundBlock = false;
// over moves "over" the side of the chunk
// in moves "into" the chunk until it finds a block
for (int over = overStart; !foundBlock && over >= 0 && over < CHUNK_DATA_WIDTH; over += overIncrement)
{
for (int in = inStart; !foundBlock && in >= 0 && in < CHUNK_DATA_WIDTH; in += inIncrement)
{
int x = -1;
int z = -1;
// determine which should be X and Z
switch(colorDir)
{
case NORTH:
x = over;
z = in;
break;
case SOUTH:
x = over;
z = in;
break;
case EAST:
x = in;
z = over;
break;
case WEST:
x = in;
z = over;
break;
default:
// here to make the compiler happy
break;
}
// if this block is buried, under other blocks
// don't add it
if(!isBlockPosVisible(chunkSections[section], x,y,z))
{
// go to the next "over" block location,
// don't look at the next "in" location,
// since the next "in" location will more than
// likely still be covered
in = CHUNK_DATA_WIDTH + 2;
continue;
}
int ci;
ci = chunkSections[section].getBlockState(x, y, z).getMaterial().getColor().colorValue;
if (ci == 0) {
// skip air or invisible blocks
continue;
}
Color c = LodUtil.intToColor(ci);
red += c.getRed();
green += c.getGreen();
blue += c.getBlue();
numbOfBlocks++;
// we found a valid block, skip to the
// next x and z
foundBlock = true;
}
}
}
}
// if we didn't find any blocks return invisible
if(numbOfBlocks == 0)
return INVISIBLE;
red /= numbOfBlocks;
green /= numbOfBlocks;
blue /= numbOfBlocks;
return new Color(red, green, blue);
}
private static BlockState airState = Blocks.AIR.getDefaultState();
/**
* returns true if the block at the given coordinates is open to
* air on at least one side.
*/
private boolean isBlockPosVisible(ChunkSection chunkSection, int x, int y, int z)
{
/*
// above
if (y+1 < CHUNK_SECTION_HEIGHT) // don't go over the top
if (chunkSection.getBlockState(x, y+1, z).getBlock() == (Blocks.AIR))
return true;
// below
if (y-1 >= 0) // don't go below the bottom
if (chunkSection.getBlockState(x, y-1, z).getBlock() == (Blocks.AIR))
return true;
*/
// north
if (z-1 > 0)
if (chunkSection.getBlockState(x, y, z-1) == airState)
return true;
// south
if (z+1 < LodChunk.WIDTH)
if (chunkSection.getBlockState(x, y, z+1) == airState)
return true;
// east
if (x+1 <= LodChunk.WIDTH)
if (chunkSection.getBlockState(x+1, y, z) == airState)
return true;
// west
if (x-1 >= 0)
if (chunkSection.getBlockState(x-1, y, z) == airState)
return true;
return false;
}
@@ -0,0 +1,41 @@
package com.seibel.lod.builders;
/**
* This is used to easily configure how LodChunks are generated.
* Generally this will only be used if we want to generate a
* LodChunk using a incomplete Chunk, otherwise the defaults
* work best for a fully generated chunk (IE has correct surface blocks).
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodBuilderConfig
{
public boolean useHeightmap;
public boolean useBiomeColors;
public boolean useSolidBlocksInColorGen;
/** default settings for a normal chunk
* useHeightmap = false
* useBiomeColors = false
* useSolidBlocksInColorGen = true
*/
public LodBuilderConfig()
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
}
/**
* @param newUseHeightmap
* @param newUseBiomeColors
* @param newUseSolidBlocksInBiomeColor
*/
public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
{
useHeightmap = newUseHeightmap;
useBiomeColors = newUseBiomeColors;
useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
}
}
@@ -1,272 +0,0 @@
package com.seibel.lod.builders;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.palette.UpgradeData;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.ChunkGenerator;
import net.minecraft.world.gen.feature.BaseTreeFeatureConfig;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.Features;
import net.minecraft.world.gen.feature.IceAndSnowFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.feature.TreeFeature;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.server.ServerWorldLightManager;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 6-23-2021
*/
public class LodChunkGenWorker implements IWorker
{
public static final ExecutorService genThreads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private boolean threadStarted = false;
private LodChunkGenThread thread;
public LodChunkGenWorker(ChunkPos newPos, LodRenderer newLodRenderer,
LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld,
BiomeContainer newBiomeContainer)
{
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newLodRenderer,
newLodBuilder, newLodBufferBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
// make sure we don't generate this chunk again
thread.lodDim.addLod(new LodChunk(thread.pos));
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate--;
if (LodConfig.CLIENT.distanceBiomeOnlyGeneration.get())
{
// if we are using biome only generation
// that can be done asynchronously
genThreads.execute(thread);
}
else
{
// if we are using normal generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
}
threadStarted = true;
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final LodBuilder lodBuilder;
public final LodRenderer lodRenderer;
private LodBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodRenderer newLodRenderer,
LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE))
{
// long startTime = System.currentTimeMillis();
if (LodConfig.CLIENT.distanceBiomeOnlyGeneration.get())
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
ChunkGenerator chunkGen = serverWorld.getWorld().getChunkProvider().getChunkGenerator();
ChunkStatus.EMPTY.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
for(IChunk c : chunkList)
((ChunkPrimer)c).setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
ChunkStatus.NOISE.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
ChunkStatus.SURFACE.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
LodServerWorld lodWorld = new LodServerWorld(chunk, serverWorld);
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.field_236558_a_);
snowFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos(), null);
TreeFeature treeFeature = new TreeFeature(BaseTreeFeatureConfig.CODEC);
treeFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos(), Features.SPRUCE.getConfig());
treeFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos(), Features.OAK.getConfig());
treeFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos(), Features.PINE.getConfig());
try
{
// TODO fix concurrent exception
ConfiguredFeature<?, ?> configFeature = new ConfiguredFeature(Features.PATCH_TALL_GRASS.feature, Features.PATCH_TALL_GRASS.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_TALL_GRASS_2.feature, Features.PATCH_TALL_GRASS_2.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_TAIGA_GRASS.feature, Features.PATCH_TAIGA_GRASS.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_PLAIN.feature, Features.PATCH_GRASS_PLAIN.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_FOREST.feature, Features.PATCH_GRASS_FOREST.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_BADLANDS.feature, Features.PATCH_GRASS_BADLANDS.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_SAVANNA.feature, Features.PATCH_GRASS_SAVANNA.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_NORMAL.feature, Features.PATCH_GRASS_NORMAL.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_TAIGA_2.feature, Features.PATCH_GRASS_TAIGA_2.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
configFeature = new ConfiguredFeature(Features.PATCH_GRASS_TAIGA.feature, Features.PATCH_GRASS_TAIGA.config);
configFeature.generate(lodWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
}
catch(Exception e)
{
//e.printStackTrace();
// System.out.println();
}
LodChunk lod = lodBuilder.generateLodFromChunk(chunk, false);
lodDim.addLod(lod);
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " h:" + lod.getHeight(0, 0) + " c:" + lod.getColor(0, 0));
// Biome biome = ;// = worldGenRegion.getBiome(pos.asBlockPos());
// LodDataPoint[][] details = new LodDataPoint[1][1];
// Color color;
// if (biome.getCategory() == Biome.Category.OCEAN)
// {
// color = LodUtil.intToColor(biome.getWaterColor());
// }
// else if (biome.getCategory() == Biome.Category.ICY ||
// biome.getCategory() == Biome.Category.EXTREME_HILLS)
// {
// color = Color.WHITE;
// }
// else
// {
// color = LodUtil.intToColor(biome.getFoliageColor());
// }
//
//
// details[0][0] = new LodDataPoint(serverWorld.getSeaLevel(), 0, color);
// LodChunk lod = new LodChunk(pos, details , LodDetail.SINGLE);
// lodDim.addLod(lod);
}
else
{
lodBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
}
lodRenderer.regenerateLODsNextFrame();
// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}// run
}
/*
* performance/generation tests related to
* serverWorld.getChunk(x, z, ChunkStatus. *** )
true/false is whether they generated blocks or not
the time is how long it took to generate
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
ChunkStatus.BIOMES 1 - 10 ms false (no height)
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
ChunkStatus.FEATURES 7 - 25 ms true
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
ChunkStatus.LIGHT 20 - 40 ms true
ChunkStatus.FULL 30 - 50 ms true
ChunkStatus.SPAWN 50 - 80 ms true
At this point I would suggest using FEATURES, as it generates snow and trees
(and any other object that is needed to make biomes distinct)
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
*/
}
@@ -0,0 +1,556 @@
package com.seibel.lod.builders.worldGeneration;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
import com.seibel.lod.builders.LodBufferBuilder;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.builders.LodBuilderConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.render.LodRenderer;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.WeightedList.Entry;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.palette.UpgradeData;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeContainer;
import net.minecraft.world.chunk.ChunkPrimer;
import net.minecraft.world.chunk.ChunkStatus;
import net.minecraft.world.chunk.IChunk;
import net.minecraft.world.gen.ChunkGenerator;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider;
import net.minecraft.world.gen.feature.BlockClusterFeatureConfig;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.DecoratedFeatureConfig;
import net.minecraft.world.gen.feature.FeatureSpread;
import net.minecraft.world.gen.feature.FeatureSpreadConfig;
import net.minecraft.world.gen.feature.IceAndSnowFeature;
import net.minecraft.world.gen.feature.NoFeatureConfig;
import net.minecraft.world.gen.placement.ConfiguredPlacement;
import net.minecraft.world.gen.placement.DecoratedPlacementConfig;
import net.minecraft.world.gen.placement.IPlacementConfig;
import net.minecraft.world.gen.placement.NoiseDependant;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.server.ServerWorldLightManager;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 6-27-2021
*/
public class LodChunkGenWorker implements IWorker
{
public static final ExecutorService genThreads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private boolean threadStarted = false;
private LodChunkGenThread thread;
public LodChunkGenWorker(ChunkPos newPos, LodRenderer newLodRenderer,
LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld,
BiomeContainer newBiomeContainer)
{
if (newServerWorld == null)
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld");
thread = new LodChunkGenThread(newPos, newLodRenderer,
newLodBuilder, newLodBufferBuilder,
newLodDimension, newServerWorld);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
// make sure we don't generate this chunk again
thread.lodDim.addLod(new LodChunk(thread.pos));
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate--;
if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
{
// if we are using SERVER generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
}
else
{
// Every other method can
// be done asynchronously
genThreads.execute(thread);
}
threadStarted = true;
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private class LodChunkGenThread implements Runnable
{
public final ServerWorld serverWorld;
public final LodDimension lodDim;
public final LodBuilder lodBuilder;
public final LodRenderer lodRenderer;
private LodBufferBuilder lodBufferBuilder;
private ChunkPos pos;
public LodChunkGenThread(ChunkPos newPos, LodRenderer newLodRenderer,
LodBuilder newLodBuilder, LodBufferBuilder newLodBufferBuilder,
LodDimension newLodDimension, ServerWorld newServerWorld)
{
pos = newPos;
lodRenderer = newLodRenderer;
lodBuilder = newLodBuilder;
lodBufferBuilder = newLodBufferBuilder;
lodDim = newLodDimension;
serverWorld = newServerWorld;
}
@Override
public void run()
{
// only generate LodChunks if they can
// be added to the current LodDimension
if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE))
{
// long startTime = System.currentTimeMillis();
switch(LodConfig.CLIENT.distanceGenerationMode.get())
{
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.x, pos.z) != null)
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
// else
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
}// if in range
}// run
/**
* takes about 2-5 ms
*/
private void generateUsingBiomesOnly()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
ChunkGenerator chunkGen = serverWorld.getWorld().getChunkProvider().getChunkGenerator();
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// generate fake height data for this LOD
int seaLevel = serverWorld.getSeaLevel();
boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
boolean inTheEnd = false;
// add fake heightmap data so our LODs aren't at height 0
Heightmap heightmap = new Heightmap(chunk, LodChunk.DEFAULT_HEIGHTMAP);
for(int x = 0; x < LodChunk.WIDTH && !inTheEnd; x++)
{
for(int z = 0; z < LodChunk.WIDTH && !inTheEnd; z++)
{
if (simulateHeight)
{
// TODO use the biomes around each block to smooth out the transition
// these heights are of course aren't super accurate,
// they are just to simulate height data where there isn't any
switch(chunk.getBiomes().getNoiseBiome(x, seaLevel, z).getCategory())
{
case NETHER:
heightmap.set(x, z, serverWorld.getHeight() / 2);
break;
case EXTREME_HILLS:
heightmap.set(x, z, seaLevel + 30);
break;
case MESA:
heightmap.set(x, z, seaLevel + 20);
break;
case JUNGLE:
heightmap.set(x, z, seaLevel + 20);
break;
case BEACH:
heightmap.set(x, z, seaLevel + 5);
break;
case NONE:
heightmap.set(x, z, 0);
break;
case OCEAN:
case RIVER:
heightmap.set(x, z, seaLevel);
break;
case THEEND:
inTheEnd = true;
break;
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
default:
heightmap.set(x, z, seaLevel + 10);
break;
}// heightmap switch
}
else
{
// we aren't simulating height
// always use sea level
heightmap.set(x, z, seaLevel);
}
}// z
}// x
chunk.setHeightmap(LodChunk.DEFAULT_HEIGHTMAP, heightmap.getDataArray());
LodChunk lod;
if (!inTheEnd)
{
lod = lodBuilder.generateLodFromChunk(chunk, new LodBuilderConfig(true, true, false));
}
else
{
// if we are in the end, don't generate any chunks.
// Since we don't know where the islands are, everything
// generates the same and it looks really bad.
lod = new LodChunk(chunk.getPos().x, chunk.getPos().z);
}
lodDim.addLod(lod);
}
/**
* takes about 10 - 20 ms
*/
private void generateUsingSurface()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
LodServerWorld lodServerWorld = new LodServerWorld(chunk);
ChunkGenerator chunkGen = serverWorld.getWorld().getChunkProvider().getChunkGenerator();
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
ChunkStatus.NOISE.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
ChunkStatus.SURFACE.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
// this feature has proved to be thread safe
// so we will add it
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.field_236558_a_);
snowFeature.generate(lodServerWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos(), null);
LodChunk lod = lodBuilder.generateLodFromChunk(chunk, new LodBuilderConfig(false, true, true));
lodDim.addLod(lod);
}
/**
* takes about 15 - 20 ms
*
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
private void generateUsingFeatures()
{
List<IChunk> chunkList = new LinkedList<>();
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
chunkList.add(chunk);
LodServerWorld lodServerWorld = new LodServerWorld(chunk);
ChunkGenerator chunkGen = serverWorld.getWorld().getChunkProvider().getChunkGenerator();
// generate the terrain (this is thread safe)
ChunkStatus.EMPTY.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
// override the chunk status so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
ChunkStatus.BIOMES.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
ChunkStatus.NOISE.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
ChunkStatus.SURFACE.doGenerationWork(serverWorld, chunkGen, serverWorld.getStructureTemplateManager(), (ServerWorldLightManager) serverWorld.getLightManager(), null, chunkList);
// get all the biomes in the chunk
HashSet<Biome> biomes = new HashSet<>();
for (int x = 0; x < LodChunk.WIDTH; x++)
{
for (int z = 0; z < LodChunk.WIDTH; z++)
{
// should probably use the heightmap here instead of seaLevel,
// but this seems to get the job done well enough
biomes.add(chunk.getBiomes().getNoiseBiome(x, serverWorld.getSeaLevel(), z));
}
}
// 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.biomeGenerationSettings.getFeatures();
for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++)
{
for(Supplier<ConfiguredFeature<?, ?>> featureSupplier : featuresForState.get(featureStateToGenerate))
{
ConfiguredFeature<?, ?> configuredfeature = featureSupplier.get();
/*
// clone any items that aren't thread safe to prevent
// them from causing issues
if(configuredfeature.config.getClass() == BlockClusterFeatureConfig.class)
{
config = cloneBlockClusterFeatureConfig((BlockClusterFeatureConfig) configuredfeature.config);
}
else if (configuredfeature.config.getClass() == DecoratedFeatureConfig.class)
{
config = cloneDecoratedFeatureConfig((DecoratedFeatureConfig) configuredfeature.config);
}
*/
try
{
configuredfeature.generate(lodServerWorld, chunkGen, serverWorld.rand, chunk.getPos().asBlockPos());
}
catch(ConcurrentModificationException e)
{
// This will happen. I'm not sure what to do about it
// except pray that it doesn't effect the normal world generation
// in any harmful way
// 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 )
}
catch(UnsupportedOperationException e)
{
// This will happen when the LodServerWorld
// isn't able to return something that a feature
// generator needs
}
catch(Exception e)
{
// I'm not sure what happened, print to the log
System.out.println();
System.out.println();
e.printStackTrace();
System.out.println();
//ClientProxy.LOGGER.error("error class: \"" + configuredfeature.config.getClass() + "\"");
System.out.println();
}
}
}
}
// generate a Lod like normal
LodChunk lod = lodBuilder.generateLodFromChunk(chunk);
lodDim.addLod(lod);
}
/**
* on pre generated chunks 0 - 1 ms
* on un generated chunks 0 - 50 ms
* with the median seeming to hover around 15 - 30 ms
* and outliers in the 100 - 200 ms range
*
* Note this should not be multithreaded and does cause server/simulation lag
* (Higher lag for generating than loading)
*/
private void generateWithServer()
{
lodBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
}
//================//
// Unused methods //
//================//
// Sadly I wasn't able to get these to work,
// they are here for documentation purposes
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config)
{
IPlacementConfig placementConfig = null;
Class oldConfigClass = config.decorator.func_242877_b().getClass();
if (oldConfigClass == FeatureSpreadConfig.class)
{
FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.func_242877_b();
FeatureSpread oldSpread = oldPlacementConfig.func_242799_a();
placementConfig = new FeatureSpreadConfig(oldSpread);
}
else if(oldConfigClass == DecoratedPlacementConfig.class)
{
DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.func_242877_b();
placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.getInner(), oldPlacementConfig.getOuter());
}
else if(oldConfigClass == NoiseDependant.class)
{
NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.func_242877_b();
placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise);
}
else
{
// ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.func_242877_b().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).weightedStates.field_220658_a)
provider.weightedStates.field_220658_a.add(state);
HashSet<Block> whitelist = new HashSet<>();
for(Block block : config.whitelist)
whitelist.add(block);
HashSet<BlockState> blacklist = new HashSet<>();
for(BlockState state : config.blacklist)
blacklist.add(state);
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
builder.whitelist(whitelist);
builder.blacklist(blacklist);
builder.xSpread(config.xSpread);
builder.ySpread(config.ySpread);
builder.zSpread(config.zSpread);
if(config.isReplaceable) { builder.replaceable(); }
if(config.requiresWater) { builder.requiresWater(); }
if(config.field_227298_k_) { builder.func_227317_b_(); }
builder.tries(config.tryCount);
return builder.build();
}
}
/*
* performance/generation tests related to
* serverWorld.getChunk(x, z, ChunkStatus. *** )
true/false is whether they generated blocks or not
the time is how long it took to generate
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
ChunkStatus.BIOMES 1 - 10 ms false (no height)
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
ChunkStatus.FEATURES 7 - 25 ms true
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
ChunkStatus.LIGHT 20 - 40 ms true
ChunkStatus.FULL 30 - 50 ms true
ChunkStatus.SPAWN 50 - 80 ms true
At this point I would suggest using FEATURES, as it generates snow and trees
(and any other object that is needed to make biomes distinct)
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
*/
}
@@ -1,4 +1,4 @@
package com.seibel.lod.builders;
package com.seibel.lod.builders.worldGeneration;
import java.util.HashMap;
import java.util.List;
@@ -45,27 +45,23 @@ import net.minecraft.world.storage.IWorldInfo;
/**
* This is used when generating features.
* This is a fake ServerWorld used when generating features.
* This allows us to keep each LodChunk generation independent
* of the actual ServerWorld, allowing us
* to multithread generation.
*
* @author James Seibel
* @version 6-23-2021
* @version 6-27-2021
*/
public class LodServerWorld implements ISeedReader {
public HashMap<Heightmap.Type, Heightmap> heightmaps = new HashMap<>();
public IChunk chunk;
private Random rand;
private ServerWorld serverWorld;
public LodServerWorld(IChunk newChunk, ServerWorld newServerWorld)
public LodServerWorld(IChunk newChunk)
{
chunk = newChunk;
rand = newServerWorld.rand;
serverWorld = newServerWorld;
}
@@ -80,7 +76,7 @@ public class LodServerWorld implements ISeedReader {
z = z % LodChunk.WIDTH;
z = (z < 0) ? z + 16 : z;
return chunk.getHeightmap(Type.WORLD_SURFACE_WG).getHeight(x, z);
return chunk.getHeightmap(LodChunk.DEFAULT_HEIGHTMAP).getHeight(x, z);
}
@Override
@@ -126,6 +122,11 @@ public class LodServerWorld implements ISeedReader {
return EmptyTickList.get();
}
@Override
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull) {
return chunk;
}
@@ -207,11 +208,6 @@ public class LodServerWorld implements ISeedReader {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull) {
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public int getSkylightSubtracted() {
throw new UnsupportedOperationException("Not Implemented");
@@ -0,0 +1,51 @@
package com.seibel.lod.enums;
/**
* BIOME_ONLY <br>
* BIOME_ONLY_SIMULATE_HEIGHT <br>
* SURFACE <br>
* FEATURES <br>
* SERVER <br><br>
*
* In order of fastest to slowest.
*
* @author James Seibel
* @version 6-27-2021
*/
public enum DistanceGenerationMode
{
/** Only generate the biomes and use biome
* grass/foliage color, water color, or ice color
* to generate the color.
* Doesn't generate height, everything is shown at sea level.
* Multithreaded - Fastest (2-5 ms) */
BIOME_ONLY,
/**
* Same as BIOME_ONLY, except instead
* of always using sea level as the LOD height
* different biome types (mountain, ocean, forest, etc.)
* use predetermined heights to simulate having height data.
*/
BIOME_ONLY_SIMULATE_HEIGHT,
/** Generate the world surface,
* this does NOT include caves, trees,
* or structures.
* Multithreaded - Faster (10-20 ms) */
SURFACE,
/** Generate everything except structures.
* NOTE: This may cause world generation bugs or instability,
* since some features cause concurrentModification exceptions.
* Multithreaded - Fast (15-20 ms) */
FEATURES,
/** Ask the server to generate/load each chunk.
* This is the most compatible, but causes server/simulation lag.
* This will also show player made structures if you
* are adding the mod to a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) */
SERVER;
}
@@ -9,6 +9,7 @@ import org.apache.logging.log4j.LogManager;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.enums.LodTemplate;
@@ -21,7 +22,7 @@ import net.minecraftforge.fml.config.ModConfig;
/**
*
* @author James Seibel
* @version 6-19-2021
* @version 6-27-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
@@ -38,7 +39,7 @@ public class LodConfig
public ForgeConfigSpec.EnumValue<LodDetail> lodDetail;
public ForgeConfigSpec.BooleanValue distanceBiomeOnlyGeneration;
public ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
/** this is multiplied by the default view distance
* to determine how far out to generate/render LODs */
@@ -49,21 +50,21 @@ public class LodConfig
builder.comment(ModInfo.MODNAME + " configuration settings").push("client");
drawLODs = builder
.comment("\n"
.comment("\n\n"
+ " If false LODs will not be drawn, \n"
+ " however they will still be generated \n"
+ " and saved to file for later use.")
.define("drawLODs", true);
fogDistance = builder
.comment("\n"
.comment("\n\n"
+ " At what distance should Fog be drawn on the LODs? \n"
+ " If the fog cuts off ubruptly or you are using Optifine's \"fast\" \n"
+ " fog option set this to " + FogDistance.NEAR.toString() + " or " + FogDistance.FAR.toString() + ".")
.defineEnum("fogDistance", FogDistance.NEAR_AND_FAR);
debugMode = builder
.comment("\n"
.comment("\n\n"
+ " If false the LODs will draw with their normal world colors. \n"
+ " If true they will draw as a black and white checkerboard. \n"
+ " This can be used for debugging or imagining you are playing a \n"
@@ -71,7 +72,7 @@ public class LodConfig
.define("drawCheckerBoard", false);
lodTemplate = builder
.comment("\n"
.comment("\n\n"
+ " How should the LODs be drawn? \n"
+ " " + LodTemplate.CUBIC.toString() + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ " " + LodTemplate.TRIANGULAR.toString() + ": LOD Chunks smoothly transition between other. \n"
@@ -80,28 +81,64 @@ public class LodConfig
.defineEnum("lodTemplate", LodTemplate.CUBIC);
lodDetail = builder
.comment("\n"
.comment("\n\n"
+ " How detailed should the LODs be? \n"
+ " " + LodDetail.SINGLE.toString() + ": render 1 LOD for each Chunk. \n"
+ " " + LodDetail.DOUBLE.toString() + ": render 4 LODs for each Chunk.")
.defineEnum("lodGeometryQuality", LodDetail.QUAD);
lodChunkRadiusMultiplier = builder
.comment("\n"
.comment("\n\n"
+ " This is multiplied by the default view distance \n"
+ " to determine how far out to generate/render LODs. \n"
+ " A value of 2 means that there is 1 render distance worth \n"
+ " of LODs in each cardinal direction. ")
.defineInRange("lodChunkRadiusMultiplier", 6, 2, 32);
distanceBiomeOnlyGeneration = builder
.comment("\n"
+ " If true LODs generated outside the normal view distance \n"
+ " will be created using a simpler faster method \n"
+ " at the cost of visual quality. \n"
+ " Nearby chunks will still use the full quality method \n"
+ " and will overwrite the lower quality ones. ")
.define("distanceBiomeOnlyGeneration", false);
distanceGenerationMode = builder
.comment("\n\n"
+ " Note: The times listed here are based on the developer's \n"
+ " PC, and are included to show the speed difference \n"
+ " between options. Your mileage may vary. \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY.toString() + " \n"
+ " Only generate the biomes and use biome \n"
+ " grass/foliage color, water color, or ice color \n"
+ " to generate the color. \n"
+ " Doesn't generate height, everything is shown at sea level. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT.toString() + " \n"
+ " Same as BIOME_ONLY, except instead \n"
+ " of always using sea level as the LOD height \n"
+ " different biome types (mountain, ocean, forest, etc.) \n"
+ " use predetermined heights to simulate having height data. \n"
+ " Multithreaded - Fastest (2-5 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SURFACE.toString() + " \n"
+ " Generate the world surface, \n"
+ " this does NOT include caves, trees, \n"
+ " or structures. \n"
+ " Multithreaded - Faster (10-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.FEATURES.toString() + " \n"
+ " Generate everything except structures. \n"
+ " WARNING: This may cause world generation bugs or instability, \n"
+ " since some features cause concurrentModification exceptions. \n"
+ " Multithreaded - Fast (15-20 ms) \n"
+ "\n"
+ " " + DistanceGenerationMode.SERVER.toString() + " \n"
+ " Ask the server to generate/load each chunk. \n"
+ " This is the most compatible, but causes server/simulation lag. \n"
+ " This will also show player made structures if you \n"
+ " are adding the mod to a pre-existing world. \n"
+ " Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) \n")
.defineEnum("distanceBiomeOnlyGeneration", DistanceGenerationMode.FEATURES);
builder.pop();
}
@@ -6,13 +6,14 @@ import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.gen.Heightmap;
/**
* This object contains position
* and color data for an LOD object.
*
* @author James Seibel
* @version 6-19-2021
* @version 6-27-2021
*/
public class LodChunk
{
@@ -28,12 +29,18 @@ public class LodChunk
private static final Color DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
private static final Color INVISIBLE = new Color(0,0,0,0);
/** 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;
public LodDetail detail = LodDetail.SINGLE;
/** If this is set to true then toData will return
* the empty string */
public boolean dontSave = false;
// TODO store the DistanceGenerationMethod used for this chunk (so we can upgrade old chunks if we want to)
/** The x coordinate of the chunk. */
public int x = 0;
@@ -25,7 +25,7 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
* and is the starting point for most of this program.
*
* @author James_Seibel
* @version 05-31-2021
* @version 06-27-2021
*/
public class ClientProxy
{
@@ -98,6 +98,7 @@ public class ClientProxy
// LodConfig.CLIENT.lodDetail.set(LodDetail.DOUBLE);
// LodConfig.CLIENT.lodColorStyle.set(LodColorStyle.INDIVIDUAL_SIDES);
// LodConfig.CLIENT.lodChunkRadiusMultiplier.set(12);
// LodConfig.CLIENT.distanceGenerationMode.set(DistanceGenerationMode.FEATURES);
// Note to self:
// if "unspecified" shows up in the pie chart, it is
@@ -31,6 +31,14 @@ public net.minecraft.world.chunk.ChunkPrimer field_201661_i # sections
public net.minecraft.world.server.ChunkManager field_219269_w # templateManager
public net.minecraft.world.server.ChunkManager field_219256_j # lightManager
public net.minecraft.world.gen.feature.template.TemplateManager field_186240_a # templates
public net.minecraft.world.biome.Biome field_242424_k # biomeGenerationSettings
public net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider field_227406_b_ # weightedStates
public net.minecraft.world.gen.placement.ConfiguredPlacement field_215096_a # decorator
public net.minecraft.world.gen.placement.ConfiguredPlacement field_215097_b # config
public net.minecraft.util.WeightedList field_220658_a # weightedEntries
public net.minecraft.world.gen.feature.FeatureSpread field_242250_b # base
public net.minecraft.world.gen.feature.FeatureSpread field_242251_c # spread
#=====================#