From bb07e3db9af8e24e362ddd570e52fd50b61d5007 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 27 Jun 2021 22:00:43 -0500 Subject: [PATCH] Close #28 (Optimize Distance Lod Generation) --- .../seibel/lod/builders/LodBufferBuilder.java | 1 + .../com/seibel/lod/builders/LodBuilder.java | 510 +++++----------- .../seibel/lod/builders/LodBuilderConfig.java | 41 ++ .../lod/builders/LodChunkGenWorker.java | 272 --------- .../worldGeneration/LodChunkGenWorker.java | 556 ++++++++++++++++++ .../{ => worldGeneration}/LodServerWorld.java | 24 +- .../lod/enums/DistanceGenerationMode.java | 51 ++ .../com/seibel/lod/handlers/LodConfig.java | 69 ++- .../java/com/seibel/lod/objects/LodChunk.java | 9 +- .../com/seibel/lod/proxy/ClientProxy.java | 3 +- .../resources/META-INF/accesstransformer.cfg | 8 + 11 files changed, 891 insertions(+), 653 deletions(-) create mode 100644 src/main/java/com/seibel/lod/builders/LodBuilderConfig.java delete mode 100644 src/main/java/com/seibel/lod/builders/LodChunkGenWorker.java create mode 100644 src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java rename src/main/java/com/seibel/lod/builders/{ => worldGeneration}/LodServerWorld.java (94%) create mode 100644 src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java 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 + #=====================#