diff --git a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
index 4cd72538a..a08aac698 100644
--- a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
+++ b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java
@@ -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;
diff --git a/src/main/java/com/seibel/lod/builders/LodBuilder.java b/src/main/java/com/seibel/lod/builders/LodBuilder.java
index 761d4e07d..b695e4945 100644
--- a/src/main/java/com/seibel/lod/builders/LodBuilder.java
+++ b/src/main/java/com/seibel/lod/builders/LodBuilder.java
@@ -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
+ * If true we look down from the top of the
+ * chunk until we find a non-invisible block, and then use
+ * its color. If false we generate the color immediately for
+ * each x and z.
+ * @param config_useBiomeColors
+ * If true use biome foliage, water, and grass colors,
+ * 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;
- }
-
diff --git a/src/main/java/com/seibel/lod/builders/LodBuilderConfig.java b/src/main/java/com/seibel/lod/builders/LodBuilderConfig.java
new file mode 100644
index 000000000..c1bf29bb9
--- /dev/null
+++ b/src/main/java/com/seibel/lod/builders/LodBuilderConfig.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java b/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java
deleted file mode 100644
index bfc48b77b..000000000
--- a/src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java
+++ /dev/null
@@ -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 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)
- */
-}
diff --git a/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java b/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java
new file mode 100644
index 000000000..99b97a57f
--- /dev/null
+++ b/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java
@@ -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 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 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 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 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>>> featuresForState = biome.biomeGenerationSettings.getFeatures();
+
+ for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++)
+ {
+ for(Supplier> 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 state : ((WeightedBlockStateProvider) config.stateProvider).weightedStates.field_220658_a)
+ provider.weightedStates.field_220658_a.add(state);
+
+ HashSet whitelist = new HashSet<>();
+ for(Block block : config.whitelist)
+ whitelist.add(block);
+
+ HashSet 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)
+ */
+}
diff --git a/src/main/java/com/seibel/lod/builders/LodServerWorld.java b/src/main/java/com/seibel/lod/builders/worldGeneration/LodServerWorld.java
similarity index 94%
rename from src/main/java/com/seibel/lod/builders/LodServerWorld.java
rename to src/main/java/com/seibel/lod/builders/worldGeneration/LodServerWorld.java
index 8e9e42878..add98028d 100644
--- a/src/main/java/com/seibel/lod/builders/LodServerWorld.java
+++ b/src/main/java/com/seibel/lod/builders/worldGeneration/LodServerWorld.java
@@ -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 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");
diff --git a/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java b/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java
new file mode 100644
index 000000000..e309e4ad1
--- /dev/null
+++ b/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java
@@ -0,0 +1,51 @@
+package com.seibel.lod.enums;
+
+/**
+ * BIOME_ONLY
+ * BIOME_ONLY_SIMULATE_HEIGHT
+ * SURFACE
+ * FEATURES
+ * SERVER
+ *
+ * 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;
+
+}
diff --git a/src/main/java/com/seibel/lod/handlers/LodConfig.java b/src/main/java/com/seibel/lod/handlers/LodConfig.java
index e5e4ce8b2..95380cff9 100644
--- a/src/main/java/com/seibel/lod/handlers/LodConfig.java
+++ b/src/main/java/com/seibel/lod/handlers/LodConfig.java
@@ -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;
- public ForgeConfigSpec.BooleanValue distanceBiomeOnlyGeneration;
+ public ForgeConfigSpec.EnumValue 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();
}
diff --git a/src/main/java/com/seibel/lod/objects/LodChunk.java b/src/main/java/com/seibel/lod/objects/LodChunk.java
index 375121ece..1a08d1792 100644
--- a/src/main/java/com/seibel/lod/objects/LodChunk.java
+++ b/src/main/java/com/seibel/lod/objects/LodChunk.java
@@ -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;
diff --git a/src/main/java/com/seibel/lod/proxy/ClientProxy.java b/src/main/java/com/seibel/lod/proxy/ClientProxy.java
index 413b056cf..df01917bd 100644
--- a/src/main/java/com/seibel/lod/proxy/ClientProxy.java
+++ b/src/main/java/com/seibel/lod/proxy/ClientProxy.java
@@ -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
diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg
index 44b72b133..8dcb70209 100644
--- a/src/main/resources/META-INF/accesstransformer.cfg
+++ b/src/main/resources/META-INF/accesstransformer.cfg
@@ -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
+
#=====================#