From a0bd9648dc6fb77e044f65880e802a485a5e90d9 Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 18 Aug 2021 19:46:56 +0200 Subject: [PATCH] Conversion to new quad tree LodRegion --- src/main/java/com/seibel/lod/Main.java | 21 - .../lod/builders/LodNodeBufferBuilder.java | 64 +- .../seibel/lod/builders/LodNodeBuilder.java | 256 ++++--- .../AbstractLodNodeTemplate.java | 32 +- .../CubicLodNodeTemplate.java | 66 +- .../DynamicLodNodeTemplate.java | 10 +- .../TriangularLodNodeTemplate.java | 10 +- .../worldGeneration/LodNodeGenWorker.java | 547 +++++++------- ...dler.java => LodDimensionFileHandler.java} | 139 ++-- .../seibel/lod/objects/LevelContainer.java | 33 + .../java/com/seibel/lod/objects/LevelPos.java | 52 ++ .../com/seibel/lod/objects/LodDataPoint.java | 4 +- .../com/seibel/lod/objects/LodDimension.java | 546 ++++++++++++++ .../com/seibel/lod/objects/LodQuadTree.java | 665 ----------------- .../lod/objects/LodQuadTreeDimension.java | 670 ------------------ .../seibel/lod/objects/LodQuadTreeNode.java | 412 ----------- .../com/seibel/lod/objects/LodRegion.java | 329 ++++++--- .../{LodQuadTreeWorld.java => LodWorld.java} | 12 +- .../com/seibel/lod/proxy/ClientProxy.java | 12 +- .../seibel/lod/render/LodNodeRenderer.java | 19 +- 20 files changed, 1416 insertions(+), 2483 deletions(-) delete mode 100644 src/main/java/com/seibel/lod/Main.java rename src/main/java/com/seibel/lod/handlers/{LodQuadTreeDimensionFileHandler.java => LodDimensionFileHandler.java} (89%) create mode 100644 src/main/java/com/seibel/lod/objects/LevelContainer.java create mode 100644 src/main/java/com/seibel/lod/objects/LevelPos.java create mode 100644 src/main/java/com/seibel/lod/objects/LodDimension.java delete mode 100644 src/main/java/com/seibel/lod/objects/LodQuadTree.java delete mode 100644 src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java delete mode 100644 src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java rename src/main/java/com/seibel/lod/objects/{LodQuadTreeWorld.java => LodWorld.java} (90%) diff --git a/src/main/java/com/seibel/lod/Main.java b/src/main/java/com/seibel/lod/Main.java deleted file mode 100644 index 51b33264a..000000000 --- a/src/main/java/com/seibel/lod/Main.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.seibel.lod; - -import com.seibel.lod.objects.LodDataPoint; -import com.seibel.lod.objects.LodRegion; -import com.seibel.lod.objects.RegionPos; - -import java.awt.*; - -public class Main { - public static void main(String[] args){ - LodRegion lodRegion = new LodRegion((byte) 0,new RegionPos(0,0)); - lodRegion.setData((byte) 2,0,0, new LodDataPoint((short) 2,(short) 30, new Color(100,100,100)), (byte) 2,true); - try { - System.out.print("test "); - System.out.println(lodRegion.getData((byte) 6, 0, 0)); - }catch (Exception e){ - e.printStackTrace(); - } - return; - } -} diff --git a/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java index d6c0aba2b..8e28bb331 100644 --- a/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java @@ -21,15 +21,15 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; +import com.seibel.lod.objects.LevelPos; +import com.seibel.lod.objects.LodDataPoint; +import com.seibel.lod.objects.LodDimension; import org.lwjgl.opengl.GL11; import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker; import com.seibel.lod.enums.DistanceGenerationMode; import com.seibel.lod.enums.LodDetail; import com.seibel.lod.handlers.LodConfig; -import com.seibel.lod.objects.LodQuadTree; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; import com.seibel.lod.objects.RegionPos; import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.render.LodNodeRenderer; @@ -101,7 +101,7 @@ public class LodNodeBufferBuilder } - private LodQuadTreeDimension previousDimension = null; + private LodDimension previousDimension = null; /** @@ -113,7 +113,7 @@ public class LodNodeBufferBuilder * After the buildable buffers have been generated they must be * swapped with the drawable buffers in the LodRenderer to be drawn. */ - public void generateLodBuffersAsync(LodNodeRenderer renderer, LodQuadTreeDimension lodDim, + public void generateLodBuffersAsync(LodNodeRenderer renderer, LodDimension lodDim, BlockPos playerBlockPos, int numbChunksWide) { // only allow one generation process to happen at a time @@ -191,14 +191,12 @@ public class LodNodeBufferBuilder startBlockPos.getX(); // offset so the center LOD block is centered underneath the player double yOffset = 0; double zOffset = (LodUtil.CHUNK_WIDTH * j) + startBlockPos.getZ(); - - LodQuadTreeNode lod = lodDim.getLodFromCoordinates(new ChunkPos(chunkX, chunkZ), LodUtil.CHUNK_DETAIL_LEVEL); - - if (lod == null || lod.complexity == DistanceGenerationMode.NONE) + + if (!lodDim.doesDataExist(new LevelPos((byte) LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ))) { // generate a new chunk if no chunk currently exists // and we aren't waiting on any other chunks to generate - if (lod == null && numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests) + if (numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests) { ChunkPos pos = new ChunkPos(chunkX, chunkZ); @@ -304,38 +302,30 @@ public class LodNodeBufferBuilder // determine detail level should this LOD be drawn at - int distance = (int) Math.sqrt(Math.pow((playerBlockPosRounded.getX() - lod.getCenter().getX()), 2) + Math.pow((playerBlockPosRounded.getZ() - lod.getCenter().getZ()), 2)); + int distance = (int) Math.sqrt(Math.pow((playerBlockPosRounded.getX() - chunkX*16 + 8), 2) + Math.pow((playerBlockPosRounded.getZ() - chunkZ*16 + 8), 2)); + int posX; + int posZ; + LevelPos levelPos; + LodDataPoint lodData; LodDetail detail = LodDetail.getDetailForDistance(LodConfig.CLIENT.maxDrawDetail.get(), distance, maxBlockDistance); - - + for (int k = 0; k < detail.dataPointLengthCount * detail.dataPointLengthCount; k++) { // how much to offset this LOD by - int startX = detail.startX[k]; - int startZ = detail.startZ[k]; - - // get the QuadTree location of this - LodQuadTree lodTree = lodDim.getLevelFromPos( - LodUtil.convertLevelPos((int) xOffset + startX, 0, LodUtil.CHUNK_DETAIL_LEVEL), - LodUtil.convertLevelPos((int) zOffset + startZ, 0, LodUtil.CHUNK_DETAIL_LEVEL), - LodUtil.CHUNK_DETAIL_LEVEL); - - if (lodTree == null) - continue; - - LodQuadTreeNode newLod = lodTree.getNodeAtPos( - LodUtil.convertLevelPos((int) xOffset + startX, 0, detail.detailLevel), - LodUtil.convertLevelPos((int) zOffset + startZ, 0, detail.detailLevel), - detail.detailLevel); - - if (newLod != null) - { - // get the desired LodTemplate and - // add this LOD to the buffer - LodConfig.CLIENT.lodTemplate.get(). - template.addLodToBuffer(currentBuffer, lodDim, newLod, - xOffset + startX, yOffset, zOffset + startZ, renderer.debugging, detail); + posX = (int) (xOffset + detail.startX[k]); + posZ = (int) (zOffset + detail.startZ[k]); + levelPos = new LevelPos((byte) 0, posX, posZ).convert((byte) detail.detailLevel); + if (lodDim.hasThisPositionBeenGenerated(levelPos)) { + lodData = lodDim.getData(levelPos); + }else { + lodData = lodDim.getData(levelPos); } + // get the desired LodTemplate and + // add this LOD to the buffer + LodConfig.CLIENT.lodTemplate.get(). + template.addLodToBuffer(currentBuffer, lodDim, lodData, + posX, yOffset, posZ, renderer.debugging, detail); + } } diff --git a/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java b/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java index 16bad492b..ed45d736a 100644 --- a/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java @@ -26,10 +26,10 @@ import java.util.concurrent.Executors; import com.seibel.lod.enums.DistanceGenerationMode; import com.seibel.lod.enums.LodDetail; import com.seibel.lod.handlers.LodConfig; +import com.seibel.lod.objects.LevelPos; import com.seibel.lod.objects.LodDataPoint; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; -import com.seibel.lod.objects.LodQuadTreeWorld; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodWorld; import com.seibel.lod.util.LodThreadFactory; import com.seibel.lod.util.LodUtil; @@ -72,47 +72,43 @@ public class LodNodeBuilder } - public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world) + public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world) { generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER); } - - public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world, DistanceGenerationMode generationMode) + + + public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world, DistanceGenerationMode generationMode) { if (lodWorld == null || !lodWorld.getIsWorldLoaded()) return; - + // don't try to create an LOD object // if for some reason we aren't // given a valid chunk object if (chunk == null) return; - + Thread thread = new Thread(() -> { try { DimensionType dim = world.dimensionType(); - - List nodeList = generateLodNodeFromChunk(chunk, new LodBuilderConfig(generationMode)); - - LodQuadTreeDimension lodDim; - + + + LodDimension lodDim; + if (lodWorld.getLodDimension(dim) == null) { - lodDim = new LodQuadTreeDimension(dim, lodWorld, defaultDimensionWidthInRegions); + lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions); lodWorld.addLodDimension(lodDim); } else { lodDim = lodWorld.getLodDimension(dim); } - - for (LodQuadTreeNode node : nodeList) - { - lodDim.addNode(node); - - } + + generateLodNodeFromChunk(lodDim ,chunk, new LodBuilderConfig(generationMode)); } catch (IllegalArgumentException | NullPointerException e) { @@ -123,47 +119,46 @@ public class LodNodeBuilder } }); lodGenThreadPool.execute(thread); - + return; } - + /** * Creates a LodChunk for a chunk in the given world. * * @throws IllegalArgumentException thrown if either the chunk or world is null. */ - public List generateLodNodeFromChunk(IChunk chunk) throws IllegalArgumentException + public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk) throws IllegalArgumentException { - return generateLodNodeFromChunk(chunk, new LodBuilderConfig()); + generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig()); } - + /** * Creates a LodChunk for a chunk in the given world. * * @throws IllegalArgumentException thrown if either the chunk or world is null. */ - public List generateLodNodeFromChunk(IChunk chunk, LodBuilderConfig config) + public void generateLodNodeFromChunk(LodDimension lodDim, IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException { LodDetail detail = LodConfig.CLIENT.maxGenerationDetail.get(); - List lodNodeList = new ArrayList<>(); - + if (chunk == null) throw new IllegalArgumentException("generateLodFromChunk given a null chunk"); - - + + for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++) { int startX = detail.startX[i]; int startZ = detail.startZ[i]; int endX = detail.endX[i]; int endZ = detail.endZ[i]; - + Color color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ); - + short height; short depth; - + if (!config.useHeightmap) { height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ); @@ -175,21 +170,22 @@ public class LodNodeBuilder startZ, endX, endZ); depth = 0; } - - lodNodeList.add(new LodQuadTreeNode((byte) detail.detailLevel, - LodUtil.convertLevelPos(chunk.getPos().getMinBlockX() + startX, 0, detail.detailLevel), - LodUtil.convertLevelPos(chunk.getPos().getMinBlockZ() + startZ, 0, detail.detailLevel), - new LodDataPoint(height, depth, color), config.distanceGenerationMode)); - + LevelPos levelPos = new LevelPos((byte)0 , + chunk.getPos().x*16 + startX, + chunk.getPos().z*16 + startZ); + LodDataPoint data = new LodDataPoint(height, depth, color); + lodDim.addData(levelPos.convert((byte) detail.detailLevel), + data, + config.distanceGenerationMode, + true, + false); } - - return lodNodeList; } - + // =====================// // constructor helpers // // =====================// - + /** * Find the lowest valid point from the bottom. * @@ -202,14 +198,14 @@ public class LodNodeBuilder private short determineBottomPointForArea(ChunkSection[] chunkSections, int startX, int startZ, int endX, int endZ) { int numberOfBlocksRequired = ((endX - startX) * (endZ - startZ) / 2); - + // search from the bottom up for (int section = 0; section < CHUNK_DATA_WIDTH; section++) { for (int y = 0; y < CHUNK_SECTION_HEIGHT; y++) { int numberOfBlocksFound = 0; - + for (int x = startX; x < endX; x++) { for (int z = startZ; z < endZ; z++) @@ -217,7 +213,7 @@ public class LodNodeBuilder if (isLayerValidLodPoint(chunkSections, section, y, x, z)) { numberOfBlocksFound++; - + if (numberOfBlocksFound >= numberOfBlocksRequired) { // we found @@ -231,11 +227,11 @@ public class LodNodeBuilder } } } - + // we never found a valid LOD point return -1; } - + /** * Find the lowest valid point from the bottom. */ @@ -246,7 +242,7 @@ public class LodNodeBuilder // doesn't have any info about how low they go return 0; } - + /** * Find the highest valid point from the Top * @@ -265,7 +261,7 @@ public class LodNodeBuilder for (int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--) { int numberOfBlocksFound = 0; - + for (int x = startX; x < endX; x++) { for (int z = startZ; z < endZ; z++) @@ -273,7 +269,7 @@ public class LodNodeBuilder if (isLayerValidLodPoint(chunkSections, section, y, x, z)) { numberOfBlocksFound++; - + if (numberOfBlocksFound >= numberOfBlocksRequired) { // we found @@ -287,11 +283,11 @@ public class LodNodeBuilder } } } - + // we never found a valid LOD point return -1; } - + /** * Find the highest point from the Top */ @@ -307,10 +303,10 @@ public class LodNodeBuilder highest = newHeight; } } - + return highest; } - + /** * Generate the color for the given chunk using biome water color, foliage * color, and grass color. @@ -330,21 +326,21 @@ public class LodNodeBuilder * material color */ private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, - int endZ) + int endZ) { ChunkSection[] chunkSections = chunk.getSections(); - + int numbOfBlocks = 0; int red = 0; int green = 0; int blue = 0; - + for (int x = startX; x < endX; x++) { for (int z = startZ; z < endZ; z++) { boolean foundBlock = false; - + // go top down for (int i = chunkSections.length - 1; !foundBlock && i >= 0; i--) { @@ -354,19 +350,19 @@ public class LodNodeBuilder { int colorInt = 0; BlockState blockState = null; - + if (chunkSections[i] != null) { blockState = chunkSections[i].getBlockState(x, y, z); colorInt = blockState.materialColor.col; } - + if (colorInt == 0 && config.useSolidBlocksInColorGen) { // skip air or invisible blocks continue; } - + if (config.useBiomeColors) { // I have no idea why I need to bit shift to the right, but @@ -377,21 +373,21 @@ public class LodNodeBuilder } else { - + // the bit shift is equivalent to dividing by 4 Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2); colorInt = getColorForBlock(x, z, blockState, biome); } - + Color c = LodUtil.intToColor(colorInt); - + red += c.getRed(); green += c.getGreen(); blue += c.getBlue(); - + numbOfBlocks++; - + // we found a valid block, skip to the // next x and z foundBlock = true; @@ -400,24 +396,24 @@ public class LodNodeBuilder } } } - + if (numbOfBlocks == 0) numbOfBlocks = 1; - + red /= numbOfBlocks; green /= numbOfBlocks; blue /= numbOfBlocks; - + return new Color(red, green, blue); } - + /** * Returns a color int for a given block. */ private int getColorForBlock(int x, int z, BlockState blockState, Biome biome) { int colorInt = 0; - + // block special cases if (blockState == Blocks.AIR.defaultBlockState()) { @@ -429,7 +425,7 @@ public class LodNodeBuilder { colorInt = MaterialColor.COLOR_LIGHT_GRAY.col; } - + // plant life else if (blockState.getBlock() instanceof LeavesBlock) { @@ -443,84 +439,84 @@ public class LodNodeBuilder tmp = tmp.darker(); colorInt = LodUtil.colorToInt(tmp); } - + // water else if (blockState.getBlock() == Blocks.WATER) { colorInt = biome.getWaterColor(); } - + // everything else else { colorInt = blockState.materialColor.col; } - + return colorInt; } - + /** * Returns a color int for the given biome. */ private int getColorForBiome(int x, int z, Biome biome) { int colorInt = 0; - + switch (biome.getBiomeCategory()) { - - case NETHER: - colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col; - break; - - case THEEND: - colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col; - break; - - case BEACH: - case DESERT: - colorInt = Blocks.SAND.defaultBlockState().materialColor.col; - break; - - case EXTREME_HILLS: - colorInt = Blocks.STONE.defaultMaterialColor().col; - break; - - case MUSHROOM: - colorInt = MaterialColor.COLOR_LIGHT_GRAY.col; - break; - - case ICY: - colorInt = Blocks.SNOW.defaultMaterialColor().col; - break; - - case MESA: - colorInt = Blocks.RED_SAND.defaultMaterialColor().col; - break; - - case OCEAN: - case RIVER: - colorInt = biome.getWaterColor(); - break; - - case NONE: - case FOREST: - case TAIGA: - case JUNGLE: - case PLAINS: - case SAVANNA: - case SWAMP: - default: - Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z)); - tmp = tmp.darker(); - colorInt = LodUtil.colorToInt(tmp); - break; - + + case NETHER: + colorInt = Blocks.BEDROCK.defaultBlockState().materialColor.col; + break; + + case THEEND: + colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col; + break; + + case BEACH: + case DESERT: + colorInt = Blocks.SAND.defaultBlockState().materialColor.col; + break; + + case EXTREME_HILLS: + colorInt = Blocks.STONE.defaultMaterialColor().col; + break; + + case MUSHROOM: + colorInt = MaterialColor.COLOR_LIGHT_GRAY.col; + break; + + case ICY: + colorInt = Blocks.SNOW.defaultMaterialColor().col; + break; + + case MESA: + colorInt = Blocks.RED_SAND.defaultMaterialColor().col; + break; + + case OCEAN: + case RIVER: + colorInt = biome.getWaterColor(); + break; + + case NONE: + case FOREST: + case TAIGA: + case JUNGLE: + case PLAINS: + case SAVANNA: + case SWAMP: + default: + Color tmp = LodUtil.intToColor(biome.getGrassColor(x, z)); + tmp = tmp.darker(); + colorInt = LodUtil.colorToInt(tmp); + break; + } - + return colorInt; } - + /** * Is the layer between the given X, Z, and dataIndex values a valid LOD point? */ @@ -540,8 +536,8 @@ public class LodNodeBuilder return true; } } - + return false; } - + } diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java index a5c04c35f..6afa15339 100644 --- a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java @@ -20,8 +20,8 @@ package com.seibel.lod.builders.lodNodeTemplates; import java.awt.Color; import com.seibel.lod.enums.LodDetail; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodDataPoint; +import com.seibel.lod.objects.LodDimension; import com.seibel.lod.util.LodUtil; import net.minecraft.client.renderer.BufferBuilder; @@ -29,30 +29,30 @@ import net.minecraft.client.renderer.BufferBuilder; /** * This is the abstract class used to create different * BufferBuilders. - * + * * @author James Seibel * @version 8-8-2021 */ public abstract class AbstractLodNodeTemplate { public abstract void addLodToBuffer(BufferBuilder buffer, - LodQuadTreeDimension lodDim, LodQuadTreeNode lod, - double xOffset, double yOffset, double zOffset, - boolean debugging, LodDetail detail) ; - + LodDimension lodDim, LodDataPoint lod, + double xOffset, double yOffset, double zOffset, + boolean debugging, LodDetail detail); + /** add the given position and color to the buffer */ - protected void addPosAndColor(BufferBuilder buffer, - double x, double y, double z, - int red, int green, int blue, int alpha) + protected void addPosAndColor(BufferBuilder buffer, + double x, double y, double z, + int red, int green, int blue, int alpha) { buffer.vertex(x, y, z).color(red, green, blue, alpha).endVertex(); } - + /** Returns in bytes how much buffer memory is required * for one LOD object */ public abstract int getBufferMemoryForSingleNode(int level); - - + + /** * Edit the given color as a HSV (Hue Saturation Value) color. */ @@ -61,9 +61,9 @@ public abstract class AbstractLodNodeTemplate float[] hsv = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); return Color.getHSBColor( hsv[0], // hue - LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f), + LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f), LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f)); } - - + + } diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java index ab2f5fc67..4832e553c 100644 --- a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java @@ -22,8 +22,8 @@ import java.awt.Color; import com.seibel.lod.enums.LodDetail; import com.seibel.lod.enums.ShadingMode; import com.seibel.lod.handlers.LodConfig; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodDataPoint; +import com.seibel.lod.objects.LodDimension; import com.seibel.lod.util.LodUtil; import net.minecraft.client.renderer.BufferBuilder; @@ -39,77 +39,77 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate { public CubicLodNodeTemplate() { - + } - + @Override public void addLodToBuffer(BufferBuilder buffer, - LodQuadTreeDimension lodDim, LodQuadTreeNode lod, - double xOffset, double yOffset, double zOffset, - boolean debugging, LodDetail detail) + LodDimension lodDim, LodDataPoint lod, + double xOffset, double yOffset, double zOffset, + boolean debugging, LodDetail detail) { AxisAlignedBB bbox; - + // add each LOD for the detail level bbox = generateBoundingBox( - lod.getLodDataPoint().height, - lod.getLodDataPoint().depth, - lod.width, + lod.height, + lod.depth, + detail.dataPointWidth, xOffset, yOffset, zOffset); - - Color color = lod.getLodDataPoint().color; + + Color color = lod.color; if (LodConfig.CLIENT.debugMode.get()) { color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detail.detailLevel]; } - + if (bbox != null) { addBoundingBoxToBuffer(buffer, bbox, color); } - + } - + /* * @Override public void addLodToBuffer(BufferBuilder buffer, * LodQuadTreeDimension lodDim, LodQuadTreeNode lod, double xOffset, double * yOffset, double zOffset, boolean debugging) { AxisAlignedBB bbox; - * + * * bbox = generateBoundingBox( lod.getLodDataPoint().height, * lod.getLodDataPoint().depth, lod.width, xOffset, yOffset, zOffset); - * + * * Color color = lod.getLodDataPoint().color; - * + * * if (bbox != null) { addBoundingBoxToBuffer(buffer, bbox, color); } - * + * * } */ - + private AxisAlignedBB generateBoundingBox(int height, int depth, int width, double xOffset, double yOffset, double zOffset) { // don't add an LOD if it is empty if (height == -1 && depth == -1) return null; - + if (depth == height) { // if the top and bottom points are at the same height // render this LOD as 1 block thick height++; } - + return new AxisAlignedBB(0, depth, 0, width, height, width).move(xOffset, yOffset, zOffset); } - + private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color c) { Color topColor = c; Color northSouthColor = c; Color eastWestColor = c; Color bottomColor = c; - + // darken the bottom and side colors if requested if (LodConfig.CLIENT.shadingMode.get() == ShadingMode.DARKEN_SIDES) { @@ -119,20 +119,20 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate int northSouthDarkenAmount = 25; int eastWestDarkenAmount = 50; int bottomDarkenAmount = 75; - + northSouthColor = new Color(Math.max(0, c.getRed() - northSouthDarkenAmount), Math.max(0, c.getGreen() - northSouthDarkenAmount), Math.max(0, c.getBlue() - northSouthDarkenAmount), c.getAlpha()); eastWestColor = new Color(Math.max(0, c.getRed() - eastWestDarkenAmount), Math.max(0, c.getGreen() - eastWestDarkenAmount), Math.max(0, c.getBlue() - eastWestDarkenAmount), c.getAlpha()); bottomColor = new Color(Math.max(0, c.getRed() - bottomDarkenAmount), Math.max(0, c.getGreen() - bottomDarkenAmount), Math.max(0, c.getBlue() - bottomDarkenAmount), c.getAlpha()); } - + // apply the user specified saturation and brightness float saturationMultiplier = LodConfig.CLIENT.saturationMultiplier.get().floatValue(); float brightnessMultiplier = LodConfig.CLIENT.brightnessMultiplier.get().floatValue(); - + topColor = applySaturationAndBrightnessMultipliers(topColor, saturationMultiplier, brightnessMultiplier); northSouthColor = applySaturationAndBrightnessMultipliers(northSouthColor, saturationMultiplier, brightnessMultiplier); bottomColor = applySaturationAndBrightnessMultipliers(bottomColor, saturationMultiplier, brightnessMultiplier); - + // top (facing up) addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha()); addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha()); @@ -143,7 +143,7 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha()); addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha()); addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha()); - + // south (facing -Z) addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); @@ -154,7 +154,7 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); - + // west (facing -X) addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); @@ -166,7 +166,7 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); } - + @Override public int getBufferMemoryForSingleNode(int detailLevel) { @@ -174,5 +174,5 @@ public class CubicLodNodeTemplate extends AbstractLodNodeTemplate // howManyPointsPerLodChunk return (6 * 4 * (3 + 4)); } - + } diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java index 4291edcba..e022f9e48 100644 --- a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java @@ -18,8 +18,8 @@ package com.seibel.lod.builders.lodNodeTemplates; import com.seibel.lod.enums.LodDetail; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodDataPoint; +import com.seibel.lod.objects.LodDimension; import net.minecraft.client.renderer.BufferBuilder; @@ -36,9 +36,9 @@ public class DynamicLodNodeTemplate extends AbstractLodNodeTemplate { @Override public void addLodToBuffer(BufferBuilder buffer, - LodQuadTreeDimension lodDim, LodQuadTreeNode lod, - double xOffset, double yOffset, double zOffset, - boolean debugging, LodDetail detail) + LodDimension lodDim, LodDataPoint lod, + double xOffset, double yOffset, double zOffset, + boolean debugging, LodDetail detail) { System.err.println("DynamicLodTemplate not implemented!"); } diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java index 028a1d230..cd1b0cd61 100644 --- a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java @@ -18,8 +18,8 @@ package com.seibel.lod.builders.lodNodeTemplates; import com.seibel.lod.enums.LodDetail; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodDataPoint; +import com.seibel.lod.objects.LodDimension; import net.minecraft.client.renderer.BufferBuilder; @@ -34,9 +34,9 @@ public class TriangularLodNodeTemplate extends AbstractLodNodeTemplate { @Override public void addLodToBuffer(BufferBuilder buffer, - LodQuadTreeDimension lodDim, LodQuadTreeNode lod, - double xOffset, double yOffset, double zOffset, - boolean debugging, LodDetail detail) + LodDimension lodDim, LodDataPoint lod, + double xOffset, double yOffset, double zOffset, + boolean debugging, LodDetail detail) { System.err.println("DynamicLodTemplate not implemented!"); } diff --git a/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java b/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java index 6067ec50d..3ebc59774 100644 --- a/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java +++ b/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java @@ -31,8 +31,7 @@ import com.seibel.lod.builders.LodNodeBufferBuilder; import com.seibel.lod.builders.LodNodeBuilder; import com.seibel.lod.enums.DistanceGenerationMode; import com.seibel.lod.handlers.LodConfig; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodDimension; import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.render.LodNodeRenderer; import com.seibel.lod.util.LodThreadFactory; @@ -69,165 +68,165 @@ import net.minecraftforge.common.WorldWorkerManager.IWorker; /** * This is used to generate a LodChunk at a given ChunkPos. - * + * * @author James Seibel * @version 7-4-2021 */ public class LodNodeGenWorker implements IWorker { - public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName())); + public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName())); - private boolean threadStarted = false; - private LodChunkGenThread thread; + private boolean threadStarted = false; + private LodChunkGenThread thread; - /** If a configured feature fails for whatever reason, - * add it to this list, this is to hopefully remove any - * features that could cause issues down the line. */ - private static ConcurrentHashMap> configuredFeaturesToAvoid = new ConcurrentHashMap<>(); + /** If a configured feature fails for whatever reason, + * add it to this list, this is to hopefully remove any + * features that could cause issues down the line. */ + private static ConcurrentHashMap> configuredFeaturesToAvoid = new ConcurrentHashMap<>(); - public LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer, + public LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer, LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder, - LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld) - { - // just a few sanity checks - if (newPos == null) - throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos"); - - if (newLodRenderer == null) - throw new IllegalArgumentException("LodChunkGenWorker must have a non-null LodRenderer"); - - if (newLodBuilder == null) + LodDimension newLodDimension, ServerWorld newServerWorld) + { + // just a few sanity checks + if (newPos == null) + throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos"); + + if (newLodRenderer == null) + throw new IllegalArgumentException("LodChunkGenWorker must have a non-null LodRenderer"); + + if (newLodBuilder == null) throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder"); - - if (newLodBufferBuilder == null) + + if (newLodBufferBuilder == null) throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodBufferBuilder"); - - if (newLodDimension == null) + + if (newLodDimension == null) throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension"); - if (newServerWorld == null) + if (newServerWorld == null) throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld"); - - - - thread = new LodChunkGenThread(newPos, newLodRenderer, - newLodBuilder, newLodBufferBuilder, - newLodDimension, newServerWorld); - } - - @Override - public boolean doWork() - { - if (!threadStarted) - { - if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER) + + + + thread = new LodChunkGenThread(newPos, newLodRenderer, + newLodBuilder, newLodBufferBuilder, + newLodDimension, newServerWorld); + } + + @Override + public boolean doWork() + { + if (!threadStarted) + { + if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER) { - // if we are using SERVER generation that has to be done - // synchronously to prevent crashing and harmful - // interactions with the normal world generator - thread.run(); + // if we are using SERVER generation that has to be done + // synchronously to prevent crashing and harmful + // interactions with the normal world generator + thread.run(); } - else - { - // Every other method can - // be done asynchronously - genThreads.execute(thread); - } - - threadStarted = true; - - // useful for debugging + else + { + // Every other method can + // be done asynchronously + genThreads.execute(thread); + } + + threadStarted = true; + + // useful for debugging // ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods()); // ClientProxy.LOGGER.info(genThreads.toString()); - } - - return false; - } - - @Override - public boolean hasWork() - { - return !threadStarted; - } - - - - - private class LodChunkGenThread implements Runnable - { - public final ServerWorld serverWorld; - public final LodQuadTreeDimension lodDim; - public final LodNodeBuilder lodNodeBuilder; - public final LodNodeRenderer lodRenderer; - private LodNodeBufferBuilder lodBufferBuilder; - - private ChunkPos pos; - - public LodChunkGenThread(ChunkPos newPos, LodNodeRenderer newLodRenderer, - LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder, - LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld) - { - pos = newPos; - lodRenderer = newLodRenderer; - lodNodeBuilder = newLodBuilder; - lodBufferBuilder = newLodBufferBuilder; - lodDim = newLodDimension; - serverWorld = newServerWorld; - } - + } + + return false; + } + + @Override + public boolean hasWork() + { + return !threadStarted; + } + + + + + private class LodChunkGenThread implements Runnable + { + public final ServerWorld serverWorld; + public final LodDimension lodDim; + public final LodNodeBuilder lodNodeBuilder; + public final LodNodeRenderer lodRenderer; + private LodNodeBufferBuilder lodBufferBuilder; + + private ChunkPos pos; + + public LodChunkGenThread(ChunkPos newPos, LodNodeRenderer newLodRenderer, + LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder, + LodDimension newLodDimension, ServerWorld newServerWorld) + { + pos = newPos; + lodRenderer = newLodRenderer; + lodNodeBuilder = newLodBuilder; + lodBufferBuilder = newLodBufferBuilder; + lodDim = newLodDimension; + serverWorld = newServerWorld; + } + @Override public void run() { try { // only generate LodChunks if they can - // be added to the current LodDimension + // be added to the current LodDimension if (lodDim.regionIsInRange(pos.x / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.z / LodUtil.REGION_WIDTH_IN_CHUNKS)) { // long startTime = System.currentTimeMillis(); - + switch(LodConfig.CLIENT.distanceGenerationMode.get()) { - case NONE: - // don't generate - break; - case BIOME_ONLY: - case BIOME_ONLY_SIMULATE_HEIGHT: - // fastest - generateUsingBiomesOnly(); - break; - case SURFACE: - // faster - generateUsingSurface(); - break; - case FEATURES: - // fast - generateUsingFeatures(); - break; - case SERVER: - // very slow - generateWithServer(); - break; + case NONE: + // don't generate + break; + case BIOME_ONLY: + case BIOME_ONLY_SIMULATE_HEIGHT: + // fastest + generateUsingBiomesOnly(); + break; + case SURFACE: + // faster + generateUsingSurface(); + break; + case FEATURES: + // fast + generateUsingFeatures(); + break; + case SERVER: + // very slow + generateWithServer(); + break; } - + lodRenderer.regenerateLODsNextFrame(); - - + + // if (lodDim.getLodFromCoordinates(pos) != null) // ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); // else // ClientProxy.LOGGER.info(pos.x + " " + pos.z); - + // shows the pool size, active threads, queued tasks and completed tasks // ClientProxy.LOGGER.info(genThreads.toString()); - + // long endTime = System.currentTimeMillis(); // System.out.println(endTime - startTime); - + }// if in range - + } catch (Exception e) { @@ -238,11 +237,11 @@ public class LodNodeGenWorker implements IWorker // decrement how many threads are running thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1); } - + }// run - - - + + + /** * takes about 2-5 ms */ @@ -251,26 +250,26 @@ public class LodNodeGenWorker implements IWorker List chunkList = new LinkedList<>(); ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); chunkList.add(chunk); - + ServerChunkProvider chunkSource = serverWorld.getChunkSource(); ChunkGenerator chunkGen = chunkSource.generator; - + // generate the terrain (this is thread safe) ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); // override the chunk status so we can run the next generator stage chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk); chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); - - - - + + + + // generate fake height data for this LOD int seaLevel = serverWorld.getSeaLevel(); - + boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT; boolean inTheEnd = false; - + // add fake heightmap data so our LODs aren't at height 0 Heightmap heightmap = new Heightmap(chunk, LodUtil.DEFAULT_HEIGHTMAP); for(int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++) @@ -280,51 +279,51 @@ public class LodNodeGenWorker implements IWorker if (simulateHeight) { // TODO use the biomes around each block to smooth out the transition - + // these heights are of course aren't super accurate, // they are just to simulate height data where there isn't any switch(chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory()) { - case NETHER: - heightmap.setHeight(x, z, serverWorld.getHeight() / 2); - break; - - case EXTREME_HILLS: - heightmap.setHeight(x, z, seaLevel + 30); - break; - case MESA: - heightmap.setHeight(x, z, seaLevel + 20); - break; - case JUNGLE: - heightmap.setHeight(x, z, seaLevel + 20); - break; - case BEACH: - heightmap.setHeight(x, z, seaLevel + 5); - break; - case NONE: - heightmap.setHeight(x, z, 0); - break; - - case OCEAN: - case RIVER: - heightmap.setHeight(x, z, seaLevel); - break; - - case THEEND: - inTheEnd = true; - break; - - // DESERT - // FOREST - // ICY - // MUSHROOM - // SAVANNA - // SWAMP - // TAIGA - // PLAINS - default: - heightmap.setHeight(x, z, seaLevel + 10); - break; + case NETHER: + heightmap.setHeight(x, z, serverWorld.getHeight() / 2); + break; + + case EXTREME_HILLS: + heightmap.setHeight(x, z, seaLevel + 30); + break; + case MESA: + heightmap.setHeight(x, z, seaLevel + 20); + break; + case JUNGLE: + heightmap.setHeight(x, z, seaLevel + 20); + break; + case BEACH: + heightmap.setHeight(x, z, seaLevel + 5); + break; + case NONE: + heightmap.setHeight(x, z, 0); + break; + + case OCEAN: + case RIVER: + heightmap.setHeight(x, z, seaLevel); + break; + + case THEEND: + inTheEnd = true; + break; + + // DESERT + // FOREST + // ICY + // MUSHROOM + // SAVANNA + // SWAMP + // TAIGA + // PLAINS + default: + heightmap.setHeight(x, z, seaLevel + 10); + break; }// heightmap switch } else @@ -335,36 +334,29 @@ public class LodNodeGenWorker implements IWorker } }// z }// x - + chunk.setHeightmap(LodUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData()); - - - List nodeList; + + if (!inTheEnd) { - nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false)); } else { // if we are in the end, don't generate any chunks. // Since we don't know where the islands are, everything // generates the same and it looks really bad. - nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(true, true, false)); } - - + + // long startTime = System.currentTimeMillis(); - - for(LodQuadTreeNode node : nodeList) - { - lodDim.addNode(node); - } - // long endTime = System.currentTimeMillis(); // System.out.println(endTime - startTime); } - - + + /** * takes about 10 - 20 ms */ @@ -374,11 +366,11 @@ public class LodNodeGenWorker implements IWorker ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); chunkList.add(chunk); LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk); - + ServerChunkProvider chunkSource = serverWorld.getChunkSource(); ChunkGenerator chunkGen = chunkSource.generator; - - + + // generate the terrain (this is thread safe) ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); // override the chunk status so we can run the next generator stage @@ -386,22 +378,19 @@ public class LodNodeGenWorker implements IWorker chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk); ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); - + // this feature has proved to be thread safe // so we will add it IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC); snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null); - List nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig()); - for(LodQuadTreeNode node : nodeList) { - lodDim.addNode(node); - } + lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.SURFACE)); } - - + + /** * takes about 15 - 20 ms - * + * * Causes concurrentModification Exceptions, * which could cause instability or world generation bugs */ @@ -411,11 +400,11 @@ public class LodNodeGenWorker implements IWorker ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); chunkList.add(chunk); LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk); - + ServerChunkProvider chunkSource = serverWorld.getChunkSource(); ChunkGenerator chunkGen = chunkSource.generator; - - + + // generate the terrain (this is thread safe) ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); // override the chunk status so we can run the next generator stage @@ -423,8 +412,8 @@ public class LodNodeGenWorker implements IWorker chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk); ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); - - + + // get all the biomes in the chunk HashSet biomes = new HashSet<>(); for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) @@ -432,7 +421,7 @@ public class LodNodeGenWorker implements IWorker for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) { Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2); - + // Issue #35 // For some reason Jungle biomes cause incredible lag // the features here must be interacting with each other @@ -447,26 +436,26 @@ public class LodNodeGenWorker implements IWorker } } } - + boolean allowUnstableFeatures = LodConfig.CLIENT.allowUnstableFeatureGeneration.get(); - + // generate all the features related to this chunk. // this may or may not be thread safe for (Biome biome : biomes) { List>>> featuresForState = biome.generationSettings.features(); - + for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++) { for(Supplier> featureSupplier : featuresForState.get(featureStateToGenerate)) { ConfiguredFeature configuredFeature = featureSupplier.get(); - + if (!allowUnstableFeatures && - configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode())) + configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode())) continue; - - + + try { configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition()); @@ -477,18 +466,18 @@ public class LodNodeGenWorker implements IWorker // except pray that it doesn't effect the normal world generation // in any harmful way. // Update: this can cause crashes and high CPU usage. - + // Issue #35 // I tried cloning the config for each feature, but that // path was blocked since I can't clone lambda methods. // I tried using a deep cloning library and discovered // the problem there. - // ( https://github.com/kostaskougios/cloning + // ( https://github.com/kostaskougios/cloning // and // https://github.com/EsotericSoftware/kryo ) - + if (!allowUnstableFeatures) - configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); // ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); } catch(UnsupportedOperationException e) @@ -496,45 +485,41 @@ public class LodNodeGenWorker implements IWorker // This will happen when the LodServerWorld // isn't able to return something that a feature // generator needs - + if (!allowUnstableFeatures) - configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); // ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); } catch(Exception e) { // I'm not sure what happened, print to the log - + System.out.println(); System.out.println(); e.printStackTrace(); System.out.println(); System.out.println(); - + if (!allowUnstableFeatures) - configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); // ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); } } } } - + // generate a Lod like normal - List nodeList = lodNodeBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig()); - - for(LodQuadTreeNode node : nodeList) { - lodDim.addNode(node); - } + lodNodeBuilder.generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(DistanceGenerationMode.FEATURES)); } - - + + /** * on pre generated chunks 0 - 1 ms - * on un generated chunks 0 - 50 ms + * on un generated chunks 0 - 50 ms * with the median seeming to hover around 15 - 30 ms * and outliers in the 100 - 200 ms range - * + * * Note this should not be multithreaded and does cause server/simulation lag * (Higher lag for generating than loading) */ @@ -542,31 +527,31 @@ public class LodNodeGenWorker implements IWorker { lodNodeBuilder.generateLodNodeAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld); } - - - - - - + + + + + + //================// // Unused methods // //================// - + // Sadly I wasn't able to get these to work, // they are here for documentation purposes - + @SuppressWarnings({ "rawtypes", "unchecked", "unused" }) private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config) { IPlacementConfig placementConfig = null; - + Class oldConfigClass = config.decorator.config().getClass(); - + if (oldConfigClass == FeatureSpreadConfig.class) { FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.config(); FeatureSpread oldSpread = oldPlacementConfig.count(); - + placementConfig = new FeatureSpreadConfig(oldSpread); } else if(oldConfigClass == DecoratedPlacementConfig.class) @@ -584,29 +569,29 @@ public class LodNodeGenWorker implements IWorker // ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.config().getClass() + "\""); return config; } - - + + ConfiguredPlacement newPlacement = new ConfiguredPlacement(config.decorator.decorator, placementConfig); return new DecoratedFeatureConfig(config.feature, newPlacement); } - - + + @SuppressWarnings("unused") private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config) { WeightedBlockStateProvider provider = new WeightedBlockStateProvider(); for(Entry state : ((WeightedBlockStateProvider) config.stateProvider).weightedList.entries) provider.weightedList.entries.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); - - + blacklist.add(state); + + BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer); builder.whitelist(whitelist); builder.blacklist(blacklist); @@ -617,43 +602,43 @@ public class LodNodeGenWorker implements IWorker if(config.needWater) { builder.needWater(); } if(config.project) { builder.noProjection(); } builder.tries(config.tries); - - + + return builder.build(); } - - } - - - /** - * Stops the current genThreads if they are running - * and then recreates the Executer service.

- * - * This is done to clear any outstanding tasks - * that may exist after the player leaves their current world. - * If this isn't done unfinished tasks may be left in the queue - * preventing new LodChunks form being generated. - */ - public static void restartExecuterService() + + } + + + /** + * Stops the current genThreads if they are running + * and then recreates the Executer service.

+ * + * This is done to clear any outstanding tasks + * that may exist after the player leaves their current world. + * If this isn't done unfinished tasks may be left in the queue + * preventing new LodChunks form being generated. + */ + public static void restartExecuterService() { - if (genThreads != null && !genThreads.isShutdown()) - { - genThreads.shutdownNow(); - } + if (genThreads != null && !genThreads.isShutdown()) + { + genThreads.shutdownNow(); + } genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get(), new LodThreadFactory(LodNodeGenWorker.class.getSimpleName())); } - - - - - + + + + + /* * performance/generation tests related to * serverWorld.getChunk(x, z, ChunkStatus. *** ) - + true/false is whether they generated blocks or not the time is how long it took to generate - + ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P) ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks) ChunkStatus.BIOMES 1 - 10 ms false (no height) @@ -666,11 +651,11 @@ public class LodNodeGenWorker implements IWorker ChunkStatus.LIGHT 20 - 40 ms true ChunkStatus.FULL 30 - 50 ms true ChunkStatus.SPAWN 50 - 80 ms true - - + + At this point I would suggest using FEATURES, as it generates snow and trees (and any other object that is needed to make biomes distinct) - + Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much) */ } diff --git a/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java b/src/main/java/com/seibel/lod/handlers/LodDimensionFileHandler.java similarity index 89% rename from src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java rename to src/main/java/com/seibel/lod/handlers/LodDimensionFileHandler.java index b838c0586..365bc1f1c 100644 --- a/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java +++ b/src/main/java/com/seibel/lod/handlers/LodDimensionFileHandler.java @@ -17,6 +17,13 @@ */ package com.seibel.lod.handlers; +import com.seibel.lod.objects.LevelContainer; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodRegion; +import com.seibel.lod.objects.RegionPos; +import com.seibel.lod.proxy.ClientProxy; +import com.seibel.lod.util.LodThreadFactory; + import java.io.*; import java.nio.file.Files; import java.nio.file.StandardCopyOption; @@ -26,63 +33,56 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import com.seibel.lod.objects.LodQuadTree; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; -import com.seibel.lod.objects.RegionPos; -import com.seibel.lod.proxy.ClientProxy; -import com.seibel.lod.util.LodThreadFactory; - /** * This object handles creating LodRegions * from files and saving LodRegion objects * to file. - * + * * @author James Seibel * @version 8-14-2021 */ -public class LodQuadTreeDimensionFileHandler +public class LodDimensionFileHandler { /** This is what separates each piece of data */ public static final char DATA_DELIMITER = ','; - - - private LodQuadTreeDimension loadedDimension = null; + + + private LodDimension loadedDimension = null; public long regionLastWriteTime[][]; - + private File dimensionDataSaveFolder; - + /** lod */ private static final String FILE_NAME_PREFIX = "lod"; /** .txt */ - private static final String FILE_EXTENSION = ".txt"; + private static final String FILE_EXTENSION = ".bin"; /** .tmp
* Added to the end of the file path when saving to prevent * nulling a currently existing file.
* After the file finishes saving it will end with * FILE_EXTENSION. */ private static final String TMP_FILE_EXTENSION = ".tmp"; - + /** This is the file version currently accepted by this * file handler, older versions (smaller numbers) will be deleted and overwritten, * newer versions (larger numbers) will be ignored and won't be read. */ public static final int LOD_SAVE_FILE_VERSION = 3; - + /** This is the string written before the file version */ private static final String LOD_FILE_VERSION_PREFIX = "lod_save_file_version"; - + /** Allow saving asynchronously, but never try to save multiple regions * at a time */ private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName())); - - - public LodQuadTreeDimensionFileHandler(File newSaveFolder, LodQuadTreeDimension newLoadedDimension) + + + public LodDimensionFileHandler(File newSaveFolder, LodDimension newLoadedDimension) { if (newSaveFolder == null) throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to."); - + dimensionDataSaveFolder = newSaveFolder; - + loadedDimension = newLoadedDimension; // these two variable are used in sync with the LodDimension regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()]; @@ -90,35 +90,36 @@ public class LodQuadTreeDimensionFileHandler for(int j = 0; j < loadedDimension.getWidth(); j++) regionLastWriteTime[i][j] = -1; } - - - - - + + + + + //================// // read from file // //================// - - - + + + /** - * Return the LodQuadTree region at the given coordinates. + * Return the LodRegion region at the given coordinates. * (null if the file doesn't exist) */ - public LodQuadTree loadRegionFromFile(RegionPos regionPos) + public LodRegion loadRegionFromFile(RegionPos regionPos) { int regionX = regionPos.x; int regionZ = regionPos.z; - + String fileName = getFileNameAndPathForRegion(regionX, regionZ); if(FILE_EXTENSION == ".bin"){ try { + System.out.println(fileName); ObjectInputStream is = new ObjectInputStream(new FileInputStream(fileName)); - List dataList = (List) is.readObject(); - //LodQuadTree region = (LodQuadTree) is.readObject(); + + + LodRegion region = (LodRegion) is.readObject(); is.close(); - return new LodQuadTree(dataList, regionX, regionZ); - //return region; + return region; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { @@ -126,10 +127,9 @@ public class LodQuadTreeDimensionFileHandler } catch (ClassNotFoundException e) { e.printStackTrace(); } - return new LodQuadTree(new ArrayList<>(), regionX, regionZ); - //return null; } - + return null; + /* if(FILE_EXTENSION == ".txt") { File f = new File(fileName); @@ -210,19 +210,20 @@ public class LodQuadTreeDimensionFileHandler } return null; + */ } - - - - - - - - + + + + + + + + //==============// // Save to File // //==============// - + /** * Save all dirty regions in this LodDimension to file. */ @@ -230,7 +231,7 @@ public class LodQuadTreeDimensionFileHandler { fileWritingThreadPool.execute(saveDirtyRegionsThread); } - + private Thread saveDirtyRegionsThread = new Thread(() -> { for(int i = 0; i < loadedDimension.getWidth(); i++) @@ -245,7 +246,7 @@ public class LodQuadTreeDimensionFileHandler } } }); - + /** * Save a specific region to disk.
* Note:
@@ -254,20 +255,20 @@ public class LodQuadTreeDimensionFileHandler * 2. This will save to the LodDimension that this * handler is associated with. */ - private void saveRegionToFile(LodQuadTree region) + private void saveRegionToFile(LodRegion region) { // convert to region coordinates - int x = region.getLodNodeData().posX; - int z = region.getLodNodeData().posZ; + int x = region.regionPosX; + int z = region.regionPosZ; File oldFile = new File(getFileNameAndPathForRegion(x, z)); - + if (!oldFile.getParentFile().exists()) + oldFile.getParentFile().mkdirs(); if(FILE_EXTENSION == ".bin"){ try { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(getFileNameAndPathForRegion(x, z))); - os.writeObject(region.getNodeListWithMask(LodQuadTreeDimension.FULL_COMPLEXITY_MASK, false, true)); - //os.writeObject(region); + os.writeObject(region); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); @@ -275,6 +276,7 @@ public class LodQuadTreeDimensionFileHandler e.printStackTrace(); } } + /* if(FILE_EXTENSION == ".txt") { try { // make sure the file and folder exists @@ -340,19 +342,20 @@ public class LodQuadTreeDimensionFileHandler e.printStackTrace(); } } + */ } - - - - - - - + + + + + + + //================// // helper methods // //================// - - + + /** * Return the name of the file that should contain the * region at the given x and z.
@@ -376,5 +379,5 @@ public class LodQuadTreeDimensionFileHandler return null; } } - + } diff --git a/src/main/java/com/seibel/lod/objects/LevelContainer.java b/src/main/java/com/seibel/lod/objects/LevelContainer.java new file mode 100644 index 000000000..92a5b2fa6 --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LevelContainer.java @@ -0,0 +1,33 @@ +package com.seibel.lod.objects; + +import java.io.Serializable; + +public class LevelContainer implements Serializable { + + public final byte detailLevel; + + public final byte[][][] colors; + + public final short[][] height; + + public final short[][] depth; + + public final byte[][] generationType; + + public final boolean[][] dataExistence; + + public LevelContainer(byte detailLevel, byte[][][] colors, short[][] height, short[][] depth, byte[][] generationType, boolean[][] dataExistence){ + this.detailLevel = detailLevel; + this.colors = colors; + this.height = height; + this.depth = depth; + this.generationType = generationType; + this.dataExistence = dataExistence; + } +/* + public LevelContainer(String data, byte detailLevel){ + int size = detailLevel*detailLevel; + } + + */ +} diff --git a/src/main/java/com/seibel/lod/objects/LevelPos.java b/src/main/java/com/seibel/lod/objects/LevelPos.java new file mode 100644 index 000000000..ef5d8fea8 --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LevelPos.java @@ -0,0 +1,52 @@ +package com.seibel.lod.objects; + +import com.seibel.lod.util.LodUtil; + +public class LevelPos implements Cloneable{ + public final byte detailLevel; + public final int posX; + public final int posZ; + + public LevelPos(byte detailLevel, int posX, int posZ){ + this.posX = posX; + this.posZ = posZ; + this.detailLevel = detailLevel; + } + + public LevelPos convert( byte newDetailLevel){ + if(newDetailLevel >= detailLevel) { + return new LevelPos( + newDetailLevel, + Math.floorDiv(posX, (int) Math.pow(2, newDetailLevel - detailLevel)), + Math.floorDiv(posZ, (int) Math.pow(2, newDetailLevel - detailLevel))); + }else{ + return new LevelPos( + newDetailLevel, + posX * (int) Math.pow(2, detailLevel - newDetailLevel), + posZ * (int) Math.pow(2, detailLevel - newDetailLevel)); + } + } + + public LevelPos regionModule(){ + return new LevelPos( + detailLevel, + Math.floorMod(posX, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel)), + Math.floorMod(posZ, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel))); + } + + public RegionPos getRegionPos(){ + return new RegionPos( + Math.floorDiv(posX, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel)), + Math.floorDiv(posZ, (int) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - detailLevel))); + } + + + public LevelPos clone(){ + return new LevelPos(detailLevel,posX,posZ); + } + + public String toString(){ + String s = (detailLevel + " " + posX + " " + posZ); + return s; + } +} diff --git a/src/main/java/com/seibel/lod/objects/LodDataPoint.java b/src/main/java/com/seibel/lod/objects/LodDataPoint.java index fe698c55d..dcdff72fe 100644 --- a/src/main/java/com/seibel/lod/objects/LodDataPoint.java +++ b/src/main/java/com/seibel/lod/objects/LodDataPoint.java @@ -21,7 +21,7 @@ import java.awt.*; import java.io.Serializable; import java.util.Objects; -import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler; +import com.seibel.lod.handlers.LodDimensionFileHandler; import com.seibel.lod.util.LodUtil; /** @@ -34,7 +34,7 @@ import com.seibel.lod.util.LodUtil; public class LodDataPoint implements Serializable { /** This is what separates each piece of data in the toData method */ - private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER; + private static final char DATA_DELIMITER = LodDimensionFileHandler.DATA_DELIMITER; /** this is how many pieces of data are exported when toData is called */ public static final int NUMBER_OF_DELIMITERS = 5; diff --git a/src/main/java/com/seibel/lod/objects/LodDimension.java b/src/main/java/com/seibel/lod/objects/LodDimension.java new file mode 100644 index 000000000..fab22071a --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LodDimension.java @@ -0,0 +1,546 @@ +/* + * This file is part of the LOD Mod, licensed under the GNU GPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.seibel.lod.objects; + +import com.seibel.lod.enums.DistanceGenerationMode; +import com.seibel.lod.handlers.LodDimensionFileHandler; +import com.seibel.lod.util.LodUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.DimensionType; +import net.minecraft.world.server.ServerChunkProvider; +import net.minecraft.world.server.ServerWorld; + +import java.io.File; +import java.io.IOException; + +/** + * This object holds all loaded LOD regions + * for a given dimension. + * + * @author Leonardo Amato + * @author James Seibel + * @version 8-8-2021 + */ +public class LodDimension +{ + + public final DimensionType dimension; + + /** measured in regions */ + private volatile int width; + /** measured in regions */ + private volatile int halfWidth; + + + public volatile LodRegion regions[][]; + public volatile boolean isRegionDirty[][]; + + private volatile RegionPos center; + + private LodDimensionFileHandler fileHandler; + + + /** + * Creates the dimension centered at (0,0) + * + * @param newWidth in regions + */ + public LodDimension(DimensionType newDimension, LodWorld lodWorld, int newWidth) + { + dimension = newDimension; + width = newWidth; + halfWidth = (int)Math.floor(width / 2); + + if(newDimension != null && lodWorld != null) + { + try + { + Minecraft mc = Minecraft.getInstance(); + + File saveDir; + if (mc.hasSingleplayerServer()) + { + // local world + + ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension); + + // provider needs a separate variable to prevent + // the compiler from complaining + ServerChunkProvider provider = serverWorld.getChunkSource(); + saveDir = new File(provider.dataStorage.dataFolder.getCanonicalFile().getPath() + File.separatorChar + "lod"); + } + else + { + // connected to server + + saveDir = new File(mc.gameDirectory.getCanonicalFile().getPath() + + File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.level)); + } + + fileHandler = new LodDimensionFileHandler(saveDir, this); + } + catch (IOException e) + { + // the file handler wasn't able to be created + // we won't be able to read or write any files + } + } + + + + regions = new LodRegion[width][width]; + isRegionDirty = new boolean[width][width]; + + // populate isRegionDirty + for(int i = 0; i < width; i++) + for(int j = 0; j < width; j++) + isRegionDirty[i][j] = false; + + center = new RegionPos(0,0); + } + + + /** + * Move the center of this LodDimension and move all owned + * regions over by the given x and z offset.

+ * + * Synchronized to prevent multiple moves happening on top of each other. + */ + public synchronized void move(RegionPos regionOffset) + { + int xOffset = regionOffset.x; + int zOffset = regionOffset.z; + + // if the x or z offset is equal to or greater than + // the total size, just delete the current data + // and update the centerX and/or centerZ + if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width) + { + for(int x = 0; x < width; x++) + { + for(int z = 0; z < width; z++) + { + regions[x][z] = null; + } + } + + // update the new center + center.x += xOffset; + center.z += zOffset; + + return; + } + + + // X + if(xOffset > 0) + { + // move everything over to the left (as the center moves to the right) + for(int x = 0; x < width; x++) + { + for(int z = 0; z < width; z++) + { + if(x + xOffset < width) + regions[x][z] = regions[x + xOffset][z]; + else + regions[x][z] = null; + } + } + } + else + { + // move everything over to the right (as the center moves to the left) + for(int x = width - 1; x >= 0; x--) + { + for(int z = 0; z < width; z++) + { + if(x + xOffset >= 0) + regions[x][z] = regions[x + xOffset][z]; + else + regions[x][z] = null; + } + } + } + + + + // Z + if(zOffset > 0) + { + // move everything up (as the center moves down) + for(int x = 0; x < width; x++) + { + for(int z = 0; z < width; z++) + { + if(z + zOffset < width) + regions[x][z] = regions[x][z + zOffset]; + else + regions[x][z] = null; + } + } + } + else + { + // move everything down (as the center moves up) + for(int x = 0; x < width; x++) + { + for(int z = width - 1; z >= 0; z--) + { + if(z + zOffset >= 0) + regions[x][z] = regions[x][z + zOffset]; + else + regions[x][z] = null; + } + } + } + + + + // update the new center + center.x += xOffset; + center.z += zOffset; + } + + + + + + + /** + * Gets the region at the given X and Z + *
+ * Returns null if the region doesn't exist + * or is outside the loaded area. + */ + public LodRegion getRegion(RegionPos regionPos) + { + int xIndex = (regionPos.x - center.x) + halfWidth; + int zIndex = (regionPos.z - center.z) + halfWidth; + + if (!regionIsInRange(regionPos.x, regionPos.z)) + // out of range + return null; + + if (regions[xIndex][zIndex] == null) + { + + regions[xIndex][zIndex] = getRegionFromFile(regionPos); + if (regions[xIndex][zIndex] == null) + { + /**TODO the value is currently 0 but should be determinated by the distance of the player)*/ + regions[xIndex][zIndex] = new LodRegion((byte) 0,regionPos); + } + } + + return regions[xIndex][zIndex]; + } + + /** + * Overwrite the LodRegion at the location of newRegion with newRegion. + * + * @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension. + */ + public void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException + { + int xIndex = (newRegion.regionPosX - center.x) + halfWidth; + int zIndex = (center.z - newRegion.regionPosZ) + halfWidth; + + if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ)) + // out of range + throw new ArrayIndexOutOfBoundsException(); + + regions[xIndex][zIndex] = newRegion; + } + + + /** + *this method creates all null regions + */ + public void initializeNullRegions() + { + int regionX; + int regionZ; + RegionPos regionPos; + LodRegion region; + + for(int x = 0; x < regions.length; x++) + { + for(int z = 0; z < regions.length; z++) + { + regionX = (x + center.x) - halfWidth; + regionZ = (z + center.z) - halfWidth; + regionPos = new RegionPos(regionX,regionZ); + region = getRegion(regionPos); + + if (region == null) + { + // if no region exists, create it + region = new LodRegion((byte) 0,regionPos); + addOrOverwriteRegion(region); + } + } + } + } + + + /** + * Add the given LOD to this dimension at the coordinate + * stored in the LOD. If an LOD already exists at the given + * coordinates it will be overwritten. + */ + public synchronized Boolean addData(LevelPos levelPos, LodDataPoint lodDataPoint, DistanceGenerationMode generationMode, boolean update, boolean dontSave) + { + // don't continue if the region can't be saved + RegionPos regionPos = levelPos.getRegionPos(); + if (!regionIsInRange(regionPos.x, regionPos.z)) + { + return false; + } + + LodRegion region = getRegion(regionPos); + + if (region == null) + { + // if no region exists, create it + region = new LodRegion((byte) 0,regionPos); + addOrOverwriteRegion(region); + } + boolean nodeAdded = region.setData(levelPos,lodDataPoint,(byte) generationMode.complexity,true); + // only save valid LODs to disk + if (!dontSave && fileHandler != null) + { + try + { + // mark the region as dirty so it will be saved to disk + int xIndex = (regionPos.x - center.x) + halfWidth; + int zIndex = (regionPos.z - center.z) + halfWidth; + isRegionDirty[xIndex][zIndex] = true; + } + catch(ArrayIndexOutOfBoundsException e) + { + // This method was probably called when the dimension was changing size. + // Hopefully this shouldn't be an issue. + } + } + return nodeAdded; + } + + + /** + * Get the LodNodeData at the given X and Z coordinates + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ + public synchronized LodDataPoint getData(ChunkPos chunkPos) + { + LevelPos levelPos = new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z); + return getData(levelPos); + } + + /** + * Get the data point at the given X and Z coordinates + * in this dimension. + *
+ * Returns null if the LodChunk doesn't exist or + * is outside the loaded area. + */ + public synchronized LodDataPoint getData(LevelPos levelPos) + { + if (levelPos.detailLevel > LodUtil.REGION_DETAIL_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + levelPos.detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); + + LodRegion region = getRegion(levelPos.getRegionPos()); + + + if(region == null) + { + return null; + } + + return region.getData(levelPos); + } + + /** + * return true if and only if the node at that position exist + */ + public synchronized boolean hasThisPositionBeenGenerated(ChunkPos chunkPos) + { + LodRegion region = getRegion(LodUtil.convertGenericPosToRegionPos(chunkPos.x, chunkPos.z, LodUtil.CHUNK_DETAIL_LEVEL)); + + if(region == null) + { + return false; + } + + return region.hasDataBeenGenerated(new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z)); + } + + /** + * return true if and only if the node at that position exist + */ + + public synchronized boolean hasThisPositionBeenGenerated(LevelPos levelPos) + { + LodRegion region = getRegion(levelPos.getRegionPos()); + + if(region == null) + { + return false; + } + + return region.hasDataBeenGenerated(levelPos); + } + + /** + * return true if and only if the node at that position exist + */ + public synchronized boolean doesDataExist(LevelPos levelPos) + { + LodRegion region = getRegion(levelPos.getRegionPos()); + + if(region == null) + { + return false; + } + + return region.doesDataExist(levelPos); + } + + /** + * return true if and only if the node at that position exist + */ + public synchronized DistanceGenerationMode getGenerationMode(LevelPos levelPos) + { + LodRegion region = getRegion(levelPos.getRegionPos()); + + if(region == null) + { + return DistanceGenerationMode.NONE; + } + + return region.getGenerationMode(levelPos); + } + + /** + * Get the region at the given X and Z coordinates from the + * RegionFileHandler. + */ + public synchronized LodRegion getRegionFromFile(RegionPos regionPos) + { + if (fileHandler != null) + return fileHandler.loadRegionFromFile(regionPos); + else + return null; + } + + /** + * Save all dirty regions in this LodDimension to file. + */ + public void saveDirtyRegionsToFileAsync() + { + fileHandler.saveDirtyRegionsToFileAsync(); + } + + + /** + * Returns whether the region at the given X and Z coordinates + * is within the loaded range. + */ + public boolean regionIsInRange(int regionX, int regionZ) + { + int xIndex = (regionX - center.x) + halfWidth; + int zIndex = (regionZ - center.z) + halfWidth; + + return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width; + } + + + + + + + + public int getCenterX() + { + return center.x; + } + + public int getCenterZ() + { + return center.z; + } + + + /** + * TODO Double check that this method works as expected + * + * Returns how many non-null LodChunks + * are stored in this LodDimension. + */ + public int getNumberOfLods() + { + /**TODO **/ + int numbLods = 0; + return numbLods; + } + + + public int getWidth() + { + if (regions != null) + { + // we want to get the length directly from the + // source to make sure it is in sync with region + // and isRegionDirty + return regions.length; + } + else + { + return width; + } + } + + public void setRegionWidth(int newWidth) + { + width = newWidth; + halfWidth = (int)Math.floor(width / 2); + + regions = new LodRegion[width][width]; + isRegionDirty = new boolean[width][width]; + + // populate isRegionDirty + for(int i = 0; i < width; i++) + for(int j = 0; j < width; j++) + isRegionDirty[i][j] = false; + } + + + @Override + public String toString() + { + String s = ""; + + s += "dim: " + dimension.toString() + "\t"; + s += "(" + center.x + "," + center.z + ")"; + + return s; + } +} diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTree.java b/src/main/java/com/seibel/lod/objects/LodQuadTree.java deleted file mode 100644 index feb316f20..000000000 --- a/src/main/java/com/seibel/lod/objects/LodQuadTree.java +++ /dev/null @@ -1,665 +0,0 @@ -/* - * This file is part of the LOD Mod, licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.seibel.lod.objects; - -import java.io.Serializable; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import com.seibel.lod.enums.DistanceGenerationMode; -import com.seibel.lod.util.LodUtil; - -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; - -/** - * This object contains all data useful to render LodBlock in a region (32x32 chunk to 512x512 block) - * for every node it contains the border of said node, its size, its block position in the world, - * color, height and depth. - * - * @author Leonardo Amato - * @author James Seibel - * @version 8-7-2021 - */ -public class LodQuadTree implements Serializable -{ - // note: - // The term node correspond to a LodQuadTree object - - /* - Example on how a LodQuadTreeRegion would be rendered (the number corresponds to the level of the LodNodeData) - .___.___._______._______________. - |6|6| 7 | | | - |6|6|___| 8 | | - | 7 | 7 | | | - |___|___|_______| 9 | - | | | | - | 8 | 8 | | - | | | | - |_______|_______|_______________| - | | | - | | | - | | | - | 9 | 9 | - | | | - | | | - | | | - |_______________|_______________| - */ - - - /** If true SetNodesAtLowerLevel will update the color and height of all higher level nodes */ - public static boolean UPDATE_HIGHER_LEVEL = true; - - //data useful to render - //if children are present then lodNodeData should be a combination of the lodData of the child. This can be - //turned off by deselecting the recursive update in all update method. - private LodQuadTreeNode lodNode; - - /* - .____.____. - | NW | NE | | - |____|____| Z - | SW | SE | | - |____|____| V - -----X----> - - North - negative z - South - positive z - West - negative x - east - positive x - */ - - - /** treeFull is true if and only if all child are not null */ - private boolean treeFull; - private boolean treeEmpty; - - /** - * The four child are based on the four diagonal cardinal directions. - * The first index is for North and South and the second index is for East and West.
- * Children should always be null for level 0. - */ - private final LodQuadTree[][] children; - - //parent should always be null for level 9, and always not null for other levels. - private final LodQuadTree parent; - - - - - - /** - * Constructor for level 0 without LodNodeData (region level constructor) - * - * @param regionPos indicate the region position of the node - */ - //maybe the use of useLevelCoordinate could be changed. I could use a builder to do all this work. - public LodQuadTree(RegionPos regionPos) - { - this(null, new LodQuadTreeNode(LodUtil.REGION_DETAIL_LEVEL, regionPos.x, regionPos.z)); - } - - /** - * Constructor for generic world without LOD data - * - * @param parent parent of this node - * @param level level of this note - * @param regionPos position of the node - */ - public LodQuadTree(LodQuadTree parent, byte level, RegionPos regionPos) - { - this(parent, new LodQuadTreeNode(level, regionPos.x, regionPos.z)); - } - - /** - * Constructor using a dataList - * - * @param dataList list of LodNodeData to put in this LodQuadTree - * @param regionX x region coordinate - * @param regionZ z region coordinate - */ - public LodQuadTree(List dataList, int regionX, int regionZ) - { - this(null, new LodQuadTreeNode(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ)); - setNodesAtLowerLevel(dataList); - } - - /** - * Constructor for a generic LodQuadTree using a LodNodeData - * - * @param newLodNode LodQuadTreeNode containing all the information of this node - */ - public LodQuadTree(LodQuadTree newParent, LodQuadTreeNode newLodNode) - { - parent = newParent; - lodNode = newLodNode; - children = new LodQuadTree[2][2]; - treeEmpty = true; - treeFull = false; - } - - - /** - * @param dataList list of data to put in the node - */ - public void setNodesAtLowerLevel(List dataList) - { - for (LodQuadTreeNode lodQuadTreeNode : dataList) - { - this.setNodeAtLowerLevel(lodQuadTreeNode); - } - } - - /** - * @param newLowerLodNode data to put in the node - * @return true only if the QuadTree has been changed - */ - public boolean setNodeAtLowerLevel(LodQuadTreeNode newLowerLodNode) - { - byte targetLevel = newLowerLodNode.detailLevel; - byte currentLevel = lodNode.detailLevel; - - if (targetLevel >= currentLevel) - { - // we can't add a node that has a equal or higher - // detail level than this region - return false; - } - - short widthRatio = (short) (lodNode.width / (2 * newLowerLodNode.width)); - int WE = Math.abs(Math.floorDiv(newLowerLodNode.posX , widthRatio) % 2); - int NS = Math.abs(Math.floorDiv(newLowerLodNode.posZ , widthRatio) % 2); - - if (getChild(NS, WE) == null) - { - // if this child doesn't exist, create an empty one - setChild(NS, WE); - } - - LodQuadTree child = getChild(NS, WE); - if (lodNode.compareComplexity(newLowerLodNode) > 0) - { - // the node we want to introduce is less complex than the current node - // we don't want to override higher complexity with lower complexity - return false; - } - else - { - if (targetLevel == currentLevel - 1) - { - // we are at the level we want to add the newLowerLodNode - child.setLodNodeData(newLowerLodNode); - return true; - } - else - { - // recurse until we reach the level we want to add the newLowerLodNode - return child.setNodeAtLowerLevel(newLowerLodNode); - } - } - - } - - /** - * Gets the LodQuadTreeNode at the given chunkPos. - * Returns null if no such LodQuadTreeNode exists. - */ - public LodQuadTreeNode getNodeAtChunkPos(ChunkPos chunkPos) - { - return getNodeAtPos(chunkPos.x, chunkPos.z, LodUtil.CHUNK_DETAIL_LEVEL); - } - - /** - * Gets the LodQuadTreeNode at the given generic pos and detailLevel. - * Returns null if no such LodQuadTreeNode exists. - */ - public LodQuadTreeNode getNodeAtPos(int posX, int posZ, int detailLevel) - { - if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) - throw new IllegalArgumentException("getNodeAtChunkPos given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - - byte currentDetailLevel = lodNode.detailLevel; - if (detailLevel == currentDetailLevel) - { - return lodNode; - } - else if (detailLevel < currentDetailLevel) - { - // the detail level we need is lower, go down a layer - short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, detailLevel))); - int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2); - int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2); - if (getChild(NS, WE) == null) - { - return null; - } - LodQuadTree child = getChild(NS, WE); - return child.getNodeAtPos(posX, posZ, detailLevel); - } - else - { - // the detail level was higher than this region's - return null; - } - - } - - /** - * Gets the LodQuadTree at the given generic pos and detailLevel. - * Returns null if no such LodQuadTreeNode exists. - */ - public LodQuadTree getLevelAtPos(int posX, int posZ, int detailLevel) - { - if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) - throw new IllegalArgumentException("getNodeAtChunkPos given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - - byte currentDetailLevel = lodNode.detailLevel; - if (detailLevel == currentDetailLevel) - { - return this; - } - else if (detailLevel < currentDetailLevel) - { - // the detail level we need is lower, go down a layer - short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, detailLevel))); - int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2); - int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2); - if (getChild(NS, WE) == null) - { - return null; - } - LodQuadTree child = getChild(NS, WE); - return child.getLevelAtPos(posX, posZ, detailLevel); - } - else - { - // the detail level was higher than this region's - return null; - } - - } - - /** - * Put a child with the given data into the given position. - * - * @param newLodNode data to put in the child - */ - public void setChild(LodQuadTreeNode newLodNode) - { - // the child must be 1 detail level lower than this region - if (newLodNode.detailLevel == lodNode.detailLevel - 1) - { - int WE = newLodNode.posX % lodNode.posX; - int NS = newLodNode.posZ % lodNode.posZ; - children[NS][WE] = new LodQuadTree(this, lodNode); - } - } - - /** - * Put an empty child in the given position. - * - * @param NS North-South position - * @param WE West-East position - */ - public void setChild(int NS, int WE) - { - // TODO is this correctly converting to a regionPos? - int childRegionX = lodNode.posX * 2 + WE; - int childRegionZ = lodNode.posZ * 2 + NS; - - children[NS][WE] = new LodQuadTree(this, (byte) (lodNode.detailLevel - 1), new RegionPos(childRegionX, childRegionZ)); - } - - /** - * Delete all the children - */ - public void deleteChildren() - { - for (int NS = 0; NS <= 1; NS++) { - for (int WE = 0; WE <= 1; WE++) { - children[NS][WE] = null; - } - } - } - - /** - * Cut the tree at the given target level - */ - public void cutTreeAtLevel(byte targetLevel) { - if (targetLevel <= lodNode.detailLevel) { - deleteChildren(); - } else { - for (int NS = 0; NS <= 1; NS++) { - for (int WE = 0; WE <= 1; WE++) { - if (getChild(NS, WE) != null) { - getChild(NS, WE).cutTreeAtLevel(targetLevel); - } - } - } - } - } - - /** - * Update this region's data, specifically levelFull and lodNodeData. - * - * @param recursiveUpdate if recursive is true the update will rise up to the level 0 - */ - private void updateRegion(boolean recursiveUpdate) - { - boolean isFull = true; - boolean isEmpty = true; - - // determine if this region is empty or full - List dataList = new ArrayList<>(); - for (int NS = 0; NS <= 1; NS++) - { - for (int WE = 0; WE <= 1; WE++) - { - if (getChild(NS,WE) != null) - { - dataList.add(getChild(NS,WE).getLodNodeData()); - isEmpty = false; - } - else - { - isFull = false; - } - } - } - - treeFull = isFull; - treeEmpty = isEmpty; - - // update this regions - lodNode.combineData(dataList); - - // update sub regions if requested - if (lodNode.detailLevel < LodUtil.REGION_DETAIL_LEVEL && recursiveUpdate) - { - this.parent.updateRegion(recursiveUpdate); - } - } - - /** - * Returns nodes that match the given mask. - * - * @param complexityMask holds the DistanceGenerationModes to accept - * @param getOnlyDirty if true it will return only dirty nodes - * @param getOnlyLeaf if true it will return only leaf nodes - * @return list of nodes - */ - public List getNodeListWithMask(Set complexityMask, boolean getOnlyDirty, - boolean getOnlyLeaf) - { - List nodeList = new ArrayList<>(); - - if (hasChildren()) - { - //There is at least 1 child - - // this detail level's node - if (!getOnlyLeaf && !(getOnlyDirty && !lodNode.isDirty()) - && complexityMask.contains(lodNode.complexity)) - { - nodeList.add(lodNode); - } - - // search the children for valid nodes - for (int NS = 0; NS <= 1; NS++) - { - for (int WE = 0; WE <= 1; WE++) - { - LodQuadTree child = children[NS][WE]; - - if (child != null) - { - nodeList.addAll(child.getNodeListWithMask(complexityMask, getOnlyDirty, getOnlyLeaf)); - } - } - } - } - else - { - // This tree has no children - - if (!(getOnlyDirty && !lodNode.isDirty()) && (complexityMask.contains(lodNode.complexity))) - { - nodeList.add(lodNode); - } - } - - return nodeList; - } - - /** - * This method will return all the nodes that can be rendered - * - * @param playerPos position of the player - * @param targetLevel minimum level that can be rendered - * @param maxDistance maximum distance from the player - * @param minDistance minimum distance from the player - * @return - */ - public List getNodeToRender(BlockPos playerPos, int targetLevel, - Set complexityMask, int maxDistance, int minDistance) - { - int x = playerPos.getX(); - int z = playerPos.getZ(); - - List distances = new ArrayList<>(); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2))); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2))); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2))); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2))); - - int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt(); - int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt(); - List nodeList = new ArrayList<>(); - - - if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance))) - { - // TODO why is !isNodeFull() here? Becouse if a node is not full then at least one child is missing. - // if one child is missing then there would be a hole if you try to render all the other child - if (targetLevel == lodNode.detailLevel || !isNodeFull()) - { - // we have either reached the right detail level or this tree isn't full - - if (!lodNode.isVoidNode() && complexityMask.contains(lodNode.complexity)) - { - // this node isn't void and has the complexity level we are looking for - nodeList.add(lodNode); - } - } - else - { - // look for the correct targetLevel - for (int NS = 0; NS <= 1; NS++) - { - for (int WE = 0; WE <= 1; WE++) - { - LodQuadTree child = getChild(NS, WE); - if (child != null) - { - nodeList.addAll(child.getNodeToRender(playerPos, targetLevel, complexityMask, maxDistance, minDistance)); - } - } - } - } - } - return nodeList; - } - - - /** - * Returns nodes that should be generated.
- * A node is generated only if it has child, is higher than the target level, and in the distance range. - */ - public List> getNodesToGenerate(BlockPos playerPos, byte targetLevel, - DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) - { - int x = playerPos.getX(); - int z = playerPos.getZ(); - - List distances = new ArrayList<>(); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2))); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStart().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2))); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getStart().getZ(), 2))); - distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEnd().getX(), 2) + Math.pow(z - lodNode.getEnd().getZ(), 2))); - - int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt(); - int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt(); - List> nodeList = new ArrayList<>(); - - - // TODO what is the purpose of isCoordianteInLevel? - if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) || isCoordinateInQuadTree(playerPos))) - { - // TODO shouldn't tagetLevel be != lodNode.detailLevel? - if(!hasChildren() || targetLevel == lodNode.detailLevel) - { - if (this.lodNode.complexity.compareTo(complexityToGenerate) <= 0 ) - { - nodeList.add(new AbstractMap.SimpleEntry(this.lodNode, min)); - } - } - else - { - // check if there are nodes further down that need generation - - for (int NS = 0; NS <= 1; NS++) - { - for (int WE = 0; WE <= 1; WE++) - { - if (getChild(NS, WE) == null) - { - setChild(NS, WE); - } - - nodeList.addAll(getChild(NS, WE).getNodesToGenerate(playerPos, targetLevel, complexityToGenerate, maxDistance, minDistance)); - } - } - } - } - - return nodeList; - } - - /** - * setter for lodNodeData, to maintain a correct relationship between worlds - * this method forces an update on all parent nodes. - * - * @param newLodQuadTreeNode data to set - */ - public void setLodNodeData(LodQuadTreeNode newLodQuadTreeNode) - { - if (this.lodNode == null) - { - this.lodNode = newLodQuadTreeNode; - } - else - { - this.lodNode.updateData(newLodQuadTreeNode); - } - - //a recursive update is necessary to change the higher levels - if (parent != null && UPDATE_HIGHER_LEVEL) - { - parent.updateRegion(true); - } - } - - /** - * Returns if the given BlockPos is within the boundary of - * this LodQuadTree. - */ - public boolean isCoordinateInQuadTree(BlockPos pos) - { - return (lodNode.getStart().getX() * lodNode.width <= pos.getX() && - lodNode.getStart().getZ() * lodNode.width <= pos.getZ() && - lodNode.getEnd().getX() * lodNode.width >= pos.getX() && - lodNode.getEnd().getZ() * lodNode.width >= pos.getZ()); - } - - - - //================// - // simple getters // - //================// - - public LodQuadTree getChild(int NS, int WE) - { - return children[NS][WE]; - } - - public LodQuadTreeNode getLodNodeData() - { - return lodNode; - } - - public boolean isNodeFull() - { - return treeFull; - } - - public boolean hasChildren() - { - return !treeEmpty; - } - - public boolean isRenderable() - { - return (lodNode != null); - } - - - @Override - public String toString() - { - String s = lodNode.toString(); - - s += treeFull ? "Full and " : ""; - s += treeEmpty ? "Empty " : ""; - - if (lodNode != null) - s += "detail: " + lodNode.detailLevel; - - /* - if(hasChildren()) - { - for (int NS = 0; NS <= 1; NS++) - { - for (int WE = 0; WE <= 1; WE++) - { - LodQuadTree child = children[NS][WE]; - if (child != null) { - s += '\n' + child.toString(); - } - } - } - } - */ - - return s; - } -} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java deleted file mode 100644 index 38eab6a3c..000000000 --- a/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java +++ /dev/null @@ -1,670 +0,0 @@ -/* - * This file is part of the LOD Mod, licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.seibel.lod.objects; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import com.seibel.lod.enums.DistanceGenerationMode; -import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler; -import com.seibel.lod.util.LodUtil; - -import net.minecraft.client.Minecraft; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraft.world.DimensionType; -import net.minecraft.world.server.ServerChunkProvider; -import net.minecraft.world.server.ServerWorld; - -/** - * This object holds all loaded LOD regions - * for a given dimension. - * - * @author Leonardo Amato - * @author James Seibel - * @version 8-8-2021 - */ -public class LodQuadTreeDimension -{ - /**TODO a dimension should support two different type of quadTree. - * The ones that are near from the player should always be saved and can be fully generated (even at block level) - * The ones that are far from the player should always be non-savable and at a high level - * If this is not done then you could see how heavy a fully generated 64 region dimension can get. - * IDEA : use a mask like the "isRegionDirty" to achieve this*/ - - public final DimensionType dimension; - - /** measured in regions */ - private volatile int width; - /** measured in regions */ - private volatile int halfWidth; - - /** */ - public static final Set FULL_COMPLEXITY_MASK = new HashSet(); - static - { - // I moved the setup here because eclipse was complaining - FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.BIOME_ONLY); - FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT); - FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.SURFACE); - FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.FEATURES); - FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.SERVER); - } - - - public volatile LodQuadTree regions[][]; - public volatile boolean isRegionDirty[][]; - - private volatile RegionPos center; - - private LodQuadTreeDimensionFileHandler fileHandler; - - - /** - * Creates the dimension centered at (0,0) - * - * @param newWidth in regions - */ - public LodQuadTreeDimension(DimensionType newDimension, LodQuadTreeWorld lodWorld, int newWidth) - { - dimension = newDimension; - width = newWidth; - halfWidth = (int)Math.floor(width / 2); - - if(newDimension != null && lodWorld != null) - { - try - { - Minecraft mc = Minecraft.getInstance(); - - File saveDir; - if (mc.hasSingleplayerServer()) - { - // local world - - ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension); - - // provider needs a separate variable to prevent - // the compiler from complaining - ServerChunkProvider provider = serverWorld.getChunkSource(); - saveDir = new File(provider.dataStorage.dataFolder.getCanonicalFile().getPath() + File.separatorChar + "lod"); - } - else - { - // connected to server - - saveDir = new File(mc.gameDirectory.getCanonicalFile().getPath() + - File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.level)); - } - - fileHandler = new LodQuadTreeDimensionFileHandler(saveDir, this); - } - catch (IOException e) - { - // the file handler wasn't able to be created - // we won't be able to read or write any files - } - } - - - - regions = new LodQuadTree[width][width]; - isRegionDirty = new boolean[width][width]; - - // populate isRegionDirty - for(int i = 0; i < width; i++) - for(int j = 0; j < width; j++) - isRegionDirty[i][j] = false; - - center = new RegionPos(0,0); - } - - - /** - * Move the center of this LodDimension and move all owned - * regions over by the given x and z offset.

- * - * Synchronized to prevent multiple moves happening on top of each other. - */ - public synchronized void move(RegionPos regionOffset) - { - int xOffset = regionOffset.x; - int zOffset = regionOffset.z; - - // if the x or z offset is equal to or greater than - // the total size, just delete the current data - // and update the centerX and/or centerZ - if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width) - { - for(int x = 0; x < width; x++) - { - for(int z = 0; z < width; z++) - { - regions[x][z] = null; - } - } - - // update the new center - center.x += xOffset; - center.z += zOffset; - - return; - } - - - // X - if(xOffset > 0) - { - // move everything over to the left (as the center moves to the right) - for(int x = 0; x < width; x++) - { - for(int z = 0; z < width; z++) - { - if(x + xOffset < width) - regions[x][z] = regions[x + xOffset][z]; - else - regions[x][z] = null; - } - } - } - else - { - // move everything over to the right (as the center moves to the left) - for(int x = width - 1; x >= 0; x--) - { - for(int z = 0; z < width; z++) - { - if(x + xOffset >= 0) - regions[x][z] = regions[x + xOffset][z]; - else - regions[x][z] = null; - } - } - } - - - - // Z - if(zOffset > 0) - { - // move everything up (as the center moves down) - for(int x = 0; x < width; x++) - { - for(int z = 0; z < width; z++) - { - if(z + zOffset < width) - regions[x][z] = regions[x][z + zOffset]; - else - regions[x][z] = null; - } - } - } - else - { - // move everything down (as the center moves up) - for(int x = 0; x < width; x++) - { - for(int z = width - 1; z >= 0; z--) - { - if(z + zOffset >= 0) - regions[x][z] = regions[x][z + zOffset]; - else - regions[x][z] = null; - } - } - } - - - - // update the new center - center.x += xOffset; - center.z += zOffset; - } - - - - - - - /** - * Gets the region at the given X and Z - *
- * Returns null if the region doesn't exist - * or is outside the loaded area. - */ - public LodQuadTree getRegion(RegionPos regionPos) - { - int xIndex = (regionPos.x - center.x) + halfWidth; - int zIndex = (regionPos.z - center.z) + halfWidth; - - if (!regionIsInRange(regionPos.x, regionPos.z)) - // out of range - return null; - - if (regions[xIndex][zIndex] == null) - { - regions[xIndex][zIndex] = getRegionFromFile(regionPos); - if (regions[xIndex][zIndex] == null) - { - regions[xIndex][zIndex] = new LodQuadTree(regionPos); - } - } - - return regions[xIndex][zIndex]; - } - - /** - * Overwrite the LodRegion at the location of newRegion with newRegion. - * - * @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension. - */ - public void addOrOverwriteRegion(LodQuadTree newRegion) throws ArrayIndexOutOfBoundsException - { - int xIndex = (newRegion.getLodNodeData().posX - center.x) + halfWidth; - int zIndex = (center.z - newRegion.getLodNodeData().posZ) + halfWidth; - - if (!regionIsInRange(newRegion.getLodNodeData().posX, newRegion.getLodNodeData().posZ)) - // out of range - throw new ArrayIndexOutOfBoundsException(); - - regions[xIndex][zIndex] = newRegion; - } - - - /** - *this method creates all null regions - */ - public void initializeNullRegions() - { - int regionX; - int regionZ; - RegionPos regionPos; - LodQuadTree region; - - for(int x = 0; x < regions.length; x++) - { - for(int z = 0; z < regions.length; z++) - { - regionX = (x + center.x) - halfWidth; - regionZ = (z + center.z) - halfWidth; - regionPos = new RegionPos(regionX,regionZ); - region = getRegion(regionPos); - - if (region == null) - { - // if no region exists, create it - region = new LodQuadTree(regionPos); - addOrOverwriteRegion(region); - } - } - } - } - - - /** - * Add the given LOD to this dimension at the coordinate - * stored in the LOD. If an LOD already exists at the given - * coordinates it will be overwritten. - */ - public Boolean addNode(LodQuadTreeNode lodNode) - { - // don't continue if the region can't be saved - RegionPos regionPos = LodUtil.convertGenericPosToRegionPos(lodNode.posX, lodNode.posZ, lodNode.detailLevel); - if (!regionIsInRange(regionPos.x, regionPos.z)) - { - return false; - } - - LodQuadTree region = getRegion(regionPos); - - if (region == null) - { - // if no region exists, create it - region = new LodQuadTree(regionPos); - addOrOverwriteRegion(region); - } - boolean nodeAdded = region.setNodeAtLowerLevel(lodNode); - - // only save valid LODs to disk - if (!lodNode.dontSave && fileHandler != null) - { - try - { - // mark the region as dirty so it will be saved to disk - int xIndex = (regionPos.x - center.x) + halfWidth; - int zIndex = (regionPos.z - center.z) + halfWidth; - isRegionDirty[xIndex][zIndex] = true; - } - catch(ArrayIndexOutOfBoundsException e) - { - // This method was probably called when the dimension was changing size. - // Hopefully this shouldn't be an issue. - } - } - return nodeAdded; - } - - - /** - * Get the LodNodeData at the given X and Z coordinates - * in this dimension. - *
- * Returns null if the LodChunk doesn't exist or - * is outside the loaded area. - */ - public LodQuadTreeNode getLodFromCoordinates(ChunkPos chunkPos) - { - return getLodFromCoordinates(chunkPos, LodUtil.CHUNK_DETAIL_LEVEL); - } - - /** - * Get the LodNodeData at the given X and Z coordinates - * in this dimension. - *
- * Returns null if the LodChunk doesn't exist or - * is outside the loaded area. - */ - public LodQuadTreeNode getLodFromCoordinates(ChunkPos chunkPos, int detailLevel) - { - if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) - throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - LodQuadTree region = getRegion(LodUtil.convertGenericPosToRegionPos(chunkPos.x, chunkPos.z, LodUtil.CHUNK_DETAIL_LEVEL)); - - if(region == null) - { - return null; - } - - return region.getNodeAtChunkPos(chunkPos); - } - - /** - * Get the LodNodeData at the given X and Z coordinates - * in this dimension. - *
- * Returns null if the LodChunk doesn't exist or - * is outside the loaded area. - */ - public LodQuadTreeNode getLodFromCoordinates(int posX, int posZ, int detailLevel) - { - if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) - throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - LodQuadTree region = getRegion(LodUtil.convertGenericPosToRegionPos(posX, posZ, detailLevel)); - - if(region == null) - { - return null; - } - - return region.getNodeAtPos(posX, posZ, detailLevel); - } - - /** - * Get the LodNodeData at the given X and Z coordinates - * in this dimension. - *
- * Returns null if the LodChunk doesn't exist or - * is outside the loaded area. - */ - public LodQuadTree getLevelFromPos(int posX, int posZ, int detailLevel) - { - if (detailLevel > LodUtil.REGION_DETAIL_LEVEL) - throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - LodQuadTree region = getRegion(LodUtil.convertGenericPosToRegionPos(posX, posZ, detailLevel)); - - if(region == null) - { - return null; - } - - return region.getLevelAtPos(posX, posZ, detailLevel); - } - - /** - * return true if and only if the node at that position exist - */ - public boolean hasThisPositionBeenGenerated(ChunkPos chunkPos, int level) - { - if (level > LodUtil.REGION_DETAIL_LEVEL) - throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + level + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max."); - - return getLodFromCoordinates(chunkPos, level).detailLevel == level; - } - - /** - * method to get all the nodes that have to be rendered based on the position of the player - * @return list of nodes - */ - public List getNodesToRender(BlockPos playerPos, int detailLevel, - Set complexityMask, int maxDistance, int minDistance) - { - List listOfData = new ArrayList<>(); - - // go through every region we have stored - for(int i = 0; i < regions.length; i++) - { - for(int j = 0; j < regions.length; j++) - { - listOfData.addAll(regions[i][j].getNodeToRender(playerPos, detailLevel, complexityMask, maxDistance, minDistance)); - } - } - - return listOfData; - } - - /** - * Returns all LodQuadTreeNodes that need to be generated based on the position of the player - * @return list of quadTrees - */ - public List getNodesToGenerate(BlockPos playerPos, byte level, DistanceGenerationMode complexity, - int maxDistance, int minDistance) - { - int regionX; - int regionZ; - LodQuadTree region; - RegionPos regionPos; - List> listOfQuadTree = new ArrayList<>(); - - // go through every region we have stored - for(int xIndex = 0; xIndex < regions.length; xIndex++) - { - for(int zIndex = 0; zIndex < regions.length; zIndex++) - { - regionX = (xIndex + center.x) - halfWidth; - regionZ = (zIndex + center.z) - halfWidth; - regionPos = new RegionPos(regionX,regionZ); - region = getRegion(regionPos); - - if (region == null) - { - region = new LodQuadTree(regionPos); - addOrOverwriteRegion(region); - } - - listOfQuadTree.addAll(region.getNodesToGenerate(playerPos, level, complexity, maxDistance, minDistance)); - } - } - - // TODO why are we sorting the list? - Collections.sort(listOfQuadTree, Map.Entry.comparingByValue()); - return listOfQuadTree.stream().map(entry -> entry.getKey()).collect(Collectors.toList()); - } - - /** - * @see LodQuadTree#getNodeListWithMask - */ - public List getNodesWithMask(Set complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf) - { - List listOfNodes = new ArrayList<>(); - - int xIndex; - int zIndex; - LodQuadTree region; - - // go through every region we have stored - for(int xRegion = 0; xRegion < regions.length; xRegion++) - { - for(int zRegion = 0; zRegion < regions.length; zRegion++) - { - xIndex = (xRegion + center.x) - halfWidth; - zIndex = (zRegion + center.z) - halfWidth; - region = getRegion(new RegionPos(xIndex,zIndex)); - - // Recursively add any children - if (region != null) - { - listOfNodes.addAll(region.getNodeListWithMask(complexityMask, getOnlyDirty, getOnlyLeaf)); - } - } - } - - return listOfNodes; - } - - /** - * Get the region at the given X and Z coordinates from the - * RegionFileHandler. - */ - public LodQuadTree getRegionFromFile(RegionPos regionPos) - { - if (fileHandler != null) - return fileHandler.loadRegionFromFile(regionPos); - else - return null; - } - - /** - * Save all dirty regions in this LodDimension to file. - */ - public void saveDirtyRegionsToFileAsync() - { - fileHandler.saveDirtyRegionsToFileAsync(); - } - - - /** - * Returns whether the region at the given X and Z coordinates - * is within the loaded range. - */ - public boolean regionIsInRange(int regionX, int regionZ) - { - int xIndex = (regionX - center.x) + halfWidth; - int zIndex = (regionZ - center.z) + halfWidth; - - return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width; - } - - - - - - - - public int getCenterX() - { - return center.x; - } - - public int getCenterZ() - { - return center.z; - } - - - /** - * TODO Double check that this method works as expected - * - * Returns how many non-null LodChunks - * are stored in this LodDimension. - */ - public int getNumberOfLods() - { - int numbLods = 0; - for (LodQuadTree[] regions : regions) - { - if(regions == null) - continue; - - for (LodQuadTree region : regions) - { - if(region == null) - continue; - - for(LodQuadTreeNode node : region.getNodeListWithMask(FULL_COMPLEXITY_MASK,false,true)) - { - if (node != null && !node.voidNode) - numbLods++; - } - } - } - - return numbLods; - } - - - public int getWidth() - { - if (regions != null) - { - // we want to get the length directly from the - // source to make sure it is in sync with region - // and isRegionDirty - return regions.length; - } - else - { - return width; - } - } - - public void setRegionWidth(int newWidth) - { - width = newWidth; - halfWidth = (int)Math.floor(width / 2); - - regions = new LodQuadTree[width][width]; - isRegionDirty = new boolean[width][width]; - - // populate isRegionDirty - for(int i = 0; i < width; i++) - for(int j = 0; j < width; j++) - isRegionDirty[i][j] = false; - } - - - @Override - public String toString() - { - String s = ""; - - s += "dim: " + dimension.toString() + "\t"; - s += "(" + center.x + "," + center.z + ")"; - - return s; - } -} diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java deleted file mode 100644 index c5fef38a6..000000000 --- a/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * This file is part of the LOD Mod, licensed under the GNU GPL v3 License. - * - * Copyright (C) 2020 James Seibel - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package com.seibel.lod.objects; - -import java.awt.Color; -import java.io.Serializable; -import java.util.List; -import java.util.Objects; - -import com.seibel.lod.enums.DistanceGenerationMode; -import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler; - -import net.minecraft.util.math.BlockPos; -import net.minecraft.world.gen.Heightmap; - -/** - * This object contains position, size, - * and color data for an LOD object. - * - * @author Leonardo Amato - * @author James Seibel - * @version 8-8-2021 - */ -public class LodQuadTreeNode implements Serializable -{ - /** This is what separates each piece of data in the toData method */ - private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER; - - /** If we ever have to use a heightmap for any reason, use this one. */ - public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG; - - /** this is how many pieces of data are exported when toData is called */ - public static final int NUMBER_OF_DELIMITERS = 10; - - - - /** If this is set to true then toData will return - * the empty string */ - public boolean dontSave = false; - - /** X position relative to the Quad tree. */ - public final int posX; - /** Z position relative to the Quad tree */ - public final int posZ; - - //Complexity indicate how the block was built. This is important because we could use - public DistanceGenerationMode complexity; - - /** Indicates how complicated this node is.
- * Goes from 0 to 9; 0 being the deepest (block size) and 9 being the highest (region size) */ - public final byte detailLevel; - - /** Indicates the width in blocks of this node.
- * Goes from 1 to 512 */ - public final short width; - - - /** holds the height, depth, and color data for this Node. */ - private LodDataPoint lodDataPoint; - - /** if true this node doesn't have any data */ - public boolean voidNode; - /** if dirty is true, then this node have unsaved changes */ - public boolean dirty; - - - /**TODO There should be a check for the level. Level must be positive, i could use runtime exception or simple if*/ - /**TODO There should be a good way to create node that must not be saved - * For example loading a 64 region wide dimension that is fully generated is too much memory heavy. - * There should be a way to create Node that are approximated and at region level, so you could load those - * for far region, and then when you get closer you load the actual region from the file or you generate it. - * */ - - - - /** - * Creates and empty LodDataPoint - * This LodDataPoint only contains the position data - * @param detailLevel of the node - * @param posX position x in the level - * @param posZ position z in the level - */ - public LodQuadTreeNode(byte detailLevel, int posX, int posZ) - { - this.detailLevel = detailLevel; - - this.posX = posX; - this.posZ = posZ; - - width = (short) Math.pow(2, detailLevel); - - lodDataPoint = new LodDataPoint(); - - complexity = DistanceGenerationMode.NONE; - - dirty = true; - voidNode = true; - dontSave = true; - } - - /** - * Constructor for a LodNodeData - * @param level - * @param posX - * @param posZ - * @param height - * @param depth - * @param color - * @param complexity - */ - public LodQuadTreeNode(byte level, int posX, int posZ, short height, short depth , Color color, DistanceGenerationMode complexity) - { - this(level, posX, posZ, new LodDataPoint(height,depth,color), complexity); - } - - /** - * Constructor for a LodNodeData - * @param level - * @param posX - * @param posZ - * @param height - * @param depth - * @param color - * @param complexity - */ - public LodQuadTreeNode(byte level, int posX, int posZ, int height, int depth, Color color, DistanceGenerationMode complexity) - { - this(level, posX, posZ, new LodDataPoint(height,depth,color), complexity); - } - - /** - * Constructor for a LodNodeData - * @param detailLevel level of this - * @param posX - * @param posZ - * @param lodDataPoint - * @param complexity - */ - public LodQuadTreeNode(byte detailLevel, int posX, int posZ, LodDataPoint lodDataPoint, DistanceGenerationMode complexity) - { - this.detailLevel = detailLevel; - - this.posX = posX; - this.posZ = posZ; - - width = (short) Math.pow(2, detailLevel); - - - this.lodDataPoint = lodDataPoint; - this.complexity = complexity; - - dirty = true; - voidNode = false; - dontSave = false; - } - - public BlockPos getStart(){ - return new BlockPos(posX * width, 0, posZ * width); - } - - public BlockPos getEnd(){ - return new BlockPos(posX * (width + 1) - 1, 0, posZ * (width + 1) - 1); - } - - public BlockPos getCenter(){ - return new BlockPos(posX * width + width/2, 0, posZ * width + width/2); - } - - /** - * @throws IllegalArgumentException if the data string doesn't have the correct number of delimited entries - */ - public LodQuadTreeNode(String data) throws IllegalArgumentException - { - // make sure there are the correct number of entries - // in the data string - int count = 0; - - for(int i = 0; i < data.length(); i++) - if(data.charAt(i) == DATA_DELIMITER) - count++; - - if(count != NUMBER_OF_DELIMITERS) - throw new IllegalArgumentException("LodQuadTreeNode constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + NUMBER_OF_DELIMITERS + "."); - - - // start reading the data string - - int index = 0; - int lastIndex = 0; - - index = data.indexOf(DATA_DELIMITER, 0); - this.detailLevel = (byte) Integer.parseInt(data.substring(0,index)); - - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - this.posX = Integer.parseInt(data.substring(lastIndex+1,index)); - - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - this.posZ = Integer.parseInt(data.substring(lastIndex+1,index)); - - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - this.complexity = DistanceGenerationMode.valueOf(data.substring(lastIndex+1,index)); - - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - short height = (short) Integer.parseInt(data.substring(lastIndex+1,index)); - - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - short depth = (short) Integer.parseInt(data.substring(lastIndex+1,index)); - - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - int red = Integer.parseInt(data.substring(lastIndex+1,index)); - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - int green = Integer.parseInt(data.substring(lastIndex+1,index)); - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - int blue = Integer.parseInt(data.substring(lastIndex+1,index)); - lastIndex = index; - index = data.indexOf(DATA_DELIMITER, lastIndex+1); - int alpha = Integer.parseInt(data.substring(lastIndex+1,index)); - Color color = new Color(red,green,blue,alpha); - lodDataPoint = new LodDataPoint(height,depth,color); - - int isVoid = Integer.parseInt(data.substring(lastIndex+1,index)); - this.voidNode = (isVoid == 1); - - - width = (short) Math.pow(2, detailLevel); - - dirty = false; - dontSave = false; - } - - - - //================// - // data processes // - //================// - - /** - * Replaces the data in this object. - */ - public void updateData(LodQuadTreeNode lodQuadTreeNode) - { - if (lodQuadTreeNode == null) - return; - - lodDataPoint = lodQuadTreeNode.lodDataPoint; - complexity = lodQuadTreeNode.complexity; - voidNode = lodQuadTreeNode.voidNode; - dontSave = lodQuadTreeNode.dontSave; - - dirty = true; - } - - /** - * Combines and averages the data from a list of LodQuadTreeNodes into this node. - */ - public void combineData(List dataList) - { - if(dataList.isEmpty()) - { - lodDataPoint = new LodDataPoint(); - } - else - { - // get the lowest height from the all the given LodQuadTreeNodes - short height = (short) (dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().height).sum() / dataList.size()); - // get the highest depth - short depth = (short) (dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().depth).sum() / dataList.size()); - - // get the average color - int red = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getRed()).sum() / dataList.size(); - int green = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getGreen()).sum() / dataList.size(); - int blue = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getBlue()).sum() / dataList.size(); - Color color = new Color(red,green,blue); - - lodDataPoint = new LodDataPoint(height, depth, color); - - - // the new complexity is equal to the lowest complexity of the list - DistanceGenerationMode minComplexity = DistanceGenerationMode.SERVER; - for(LodQuadTreeNode node: dataList) - { - if (minComplexity.compareTo(node.complexity) > 0) - { - minComplexity = node.complexity; - } - } - - complexity = minComplexity; - - voidNode = lodDataPoint.isEmpty(); - } - - dirty = true; - dontSave = false; - } - - - - //===================// - // basic comparisons // - //===================// - - public int compareComplexity(LodQuadTreeNode other) - { - return this.complexity.compareTo(other.complexity); - } - - public boolean equals(LodQuadTreeNode other) - { - return (this.complexity == other.complexity - && this.detailLevel == other.detailLevel - && this.posX == other.posX - && this.posZ == other.posZ - && this.lodDataPoint.equals(other.lodDataPoint) - && this.complexity == other.complexity - && this.voidNode == other.voidNode); - } - - - - //=========================// - // basic setters / getters // - //=========================// - - public LodDataPoint getLodDataPoint() - { - return lodDataPoint; - } - - public void setLodDataPoint(LodDataPoint newLodDataPoint) - { - lodDataPoint = newLodDataPoint; - - // update if this is node is currently void - voidNode = (lodDataPoint == null); - } - - public boolean isVoidNode() - { - return voidNode; - } - - public boolean isDirty() - { - return dirty; - } - - - - - //========// - // output // - //========// - - @Override - public int hashCode() - { - return Objects.hash(this.complexity, this.detailLevel, this.posX, this.posZ, this.lodDataPoint, this.voidNode); - } - - /** - * Outputs all data in a csv format - */ - public String toData() - { - if (dontSave) - return ""; - - String s = Integer.toString(detailLevel) + DATA_DELIMITER - + Integer.toString(posX) + DATA_DELIMITER - + Integer.toString(posZ) + DATA_DELIMITER - + complexity.toString() + DATA_DELIMITER - + Integer.toString((lodDataPoint.height)) + DATA_DELIMITER - + Integer.toString((lodDataPoint.depth)) + DATA_DELIMITER - + Integer.toString(lodDataPoint.color.getRed()) + DATA_DELIMITER - + Integer.toString(lodDataPoint.color.getGreen()) + DATA_DELIMITER - + Integer.toString(lodDataPoint.color.getBlue()) + DATA_DELIMITER - + Integer.toString(lodDataPoint.color.getAlpha()) + DATA_DELIMITER - + Integer.toString(voidNode ? 1 : 0); - return s; - } - - @Override - public String toString() - { - return this.toData(); - } - -} diff --git a/src/main/java/com/seibel/lod/objects/LodRegion.java b/src/main/java/com/seibel/lod/objects/LodRegion.java index 561fb6e29..a5162a607 100644 --- a/src/main/java/com/seibel/lod/objects/LodRegion.java +++ b/src/main/java/com/seibel/lod/objects/LodRegion.java @@ -1,18 +1,22 @@ package com.seibel.lod.objects; +import com.seibel.lod.enums.DistanceGenerationMode; import com.seibel.lod.util.LodUtil; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; import java.awt.*; import java.io.Serializable; -/**STANDARD TO FOLLOW +/** + * STANDARD TO FOLLOW * every coordinate called posX or posZ is a relative coordinate and not and absolute coordinate * if an array contain coordinate the order is the following * 0 for x, 1 for z in 2D * 0 for x, 1 for y, 2 for z in 3D */ -public class LodRegion{ +public class LodRegion implements Serializable { //x coord, private byte minLevelOfDetail; private static final byte POSSIBLE_LOD = 10; @@ -32,186 +36,277 @@ public class LodRegion{ //a node with 1 is node private byte[][][] generationType; - private int regionPosX; - private int regionPosZ; + private boolean[][][] dataExistence; - public LodRegion(byte minimumLevelOfDetail, RegionPos regionPos){ + public final int regionPosX; + public final int regionPosZ; + + public LodRegion(byte minimumLevelOfDetail, RegionPos regionPos) { this.regionPosX = regionPos.x; this.regionPosZ = regionPos.z; //Array of matrices of arrays - colors = new byte[POSSIBLE_LOD][][][]; + colors = new byte[POSSIBLE_LOD][][][]; //Arrays of matrices - height = new short[POSSIBLE_LOD][][]; - depth = new short[POSSIBLE_LOD][][]; - generationType = new byte[POSSIBLE_LOD][][]; + height = new short[POSSIBLE_LOD][][]; + depth = new short[POSSIBLE_LOD][][]; + generationType = new byte[POSSIBLE_LOD][][]; + dataExistence = new boolean[POSSIBLE_LOD][][]; //Initialize all the different matrices - for(byte lod = minimumLevelOfDetail; lod <= LodUtil.REGION_DETAIL_LEVEL; lod ++){ + for (byte lod = minimumLevelOfDetail; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++) { int size = (short) Math.pow(2, LodUtil.REGION_DETAIL_LEVEL - lod); colors[lod] = new byte[size][size][3]; height[lod] = new short[size][size]; depth[lod] = new short[size][size]; generationType[lod] = new byte[size][size]; + dataExistence[lod] = new boolean[size][size]; } } /** * This method can be used to insert data into the LodRegion - * @param lod - * @param posX - * @param posZ + * + * @param levelPos * @param dataPoint * @param generationType * @param update * @return */ - public boolean setData(byte lod, int posX, int posZ, LodDataPoint dataPoint, byte generationType, boolean update){ - return setData(lod, posX, posZ, (byte) (dataPoint.color.getRed() - 128), (byte) (dataPoint.color.getGreen() - 128), (byte) (dataPoint.color.getBlue() - 128), dataPoint.height, dataPoint.depth, generationType, update); - } - - /** - * This method can be used to insert data into the LodRegion - * @param lod - * @param posX - * @param posZ - * @param red - * @param green - * @param blue - * @param height - * @param depth - * @param generationType - * @param update - * @return - */ - public boolean setData(byte lod, int posX, int posZ, byte red, byte green, byte blue, short height, short depth, byte generationType, boolean update){ - posX = Math.floorMod(posX, (int) Math.pow(2,lod)); - posZ = Math.floorMod(posZ, (int) Math.pow(2,lod)); - if( (this.generationType[lod][posX][posZ] == 0) || (generationType < this.generationType[lod][posX][posZ]) ) { + public boolean setData(LevelPos levelPos, LodDataPoint dataPoint, byte generationType, boolean update) { + levelPos = levelPos.regionModule(); + if ((this.generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] == 0) || (generationType >= this.generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ])) { //update the number of node present - if (this.generationType[lod][posX][posZ] == 0) numberOfPoints++ ; + //if (this.generationType[lod][posX][posZ] == 0) numberOfPoints++; //add the node data - this.colors[lod][posX][posZ][0] = red; - this.colors[lod][posX][posZ][1] = green; - this.colors[lod][posX][posZ][2] = blue; - this.height[lod][posX][posZ] = height; - this.depth[lod][posX][posZ] = depth; - this.generationType[lod][posX][posZ] = generationType; - - //update all the higher level - int tempPosX = posX; - int tempPosZ = posZ; + this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] = (byte) (dataPoint.color.getRed() - 128); + this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] = (byte) (dataPoint.color.getGreen() - 128); + this.colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] = (byte) (dataPoint.color.getBlue() - 128); + this.height[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = dataPoint.height; + this.depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = dataPoint.depth; + this.generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = generationType; + this.dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = true; //update could be stopped and a single big update could be done at the end - if(update) { - for (byte tempLod = (byte) (lod + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++) { - tempPosX = Math.floorDiv(tempPosX, 2); - tempPosZ = Math.floorDiv(tempPosZ, 2); - update(tempLod, tempPosX, tempPosZ); + LevelPos tempLevelPos = levelPos; + if (update) { + for (byte tempLod = (byte) (levelPos.detailLevel + 1); tempLod <= LodUtil.REGION_DETAIL_LEVEL; tempLod++) { + tempLevelPos = tempLevelPos.convert(tempLod); + update(tempLevelPos); } } - return true; //added - }else{ - return false; //not added + return true; + } else { + return false; } } + + public LodDataPoint getData(ChunkPos chunkPos) { + return getData(new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z)); + } + /** * This method will return the data in the position relative to the level of detail + * * @param lod - * @param posX - * @param posZ * @return the data at the relative pos and level */ - public LodDataPoint getData(byte lod, int posX, int posZ){ + public LodDataPoint getData(byte lod, BlockPos blockPos) { + int posX = Math.floorMod(blockPos.getX(), (int) Math.pow(2, lod)); + int posZ = Math.floorMod(blockPos.getZ(), (int) Math.pow(2, lod)); + return getData(new LevelPos(lod, posX, posZ)); + } + + /** + * This method will return the data in the position relative to the level of detail + * + * @param levelPos + * @return the data at the relative pos and level + */ + public LodDataPoint getData(LevelPos levelPos) { + levelPos = levelPos.regionModule(); return new LodDataPoint( - height[lod][posX][posZ], - depth[lod][posX][posZ], - new Color(colors[lod][posX][posZ][0] + 128, - colors[lod][posX][posZ][1] + 128, - colors[lod][posX][posZ][2] + 128 + height[levelPos.detailLevel][levelPos.posX][levelPos.posZ], + depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ], + new Color(colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] + 128, + colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] + 128, + colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] + 128 ) ); } -/* - private void updateArea(byte lod, int posX, int posZ){ - } -*/ - private void update(byte lod, int posX, int posZ){ - posX = Math.floorMod(posX, (int) Math.pow(2,lod)); - posZ = Math.floorMod(posZ, (int) Math.pow(2,lod)); - boolean[][] children = getChildren(lod, posX, posZ); - int numberOfChild = 0; - for(int x = 0; x <= 1; x++) { + /**TODO a method to update a whole area, to be used as a single big update*/ + /** + * + * @param levelPos + */ + private void updateArea(LevelPos levelPos) { + /* + LevelPos tempLevelPos = levelPos; + int sizeDiff; + int startX; + int startZ; + for(int bottom = minLevelOfDetail + 1 ; bottom < levelPos.detailLevel ; bottom ++){ + tempLevelPos = levelPos.convert(bottom); + startX = tempLevelPos.posX; + startZ = tempLevelPos.posZ; + sizeDiff = (int) Math.pow(2, levelPos.detailLevel - bottom); + for(int x = 0; x < sizeDiff; x++){ + for(int z = 0; z < sizeDiff; z++) { + update(new LevelPos(bottom, startX+x, startZ+z)); + } + } + + } + + */ + } + + /** + * + * @param levelPos + */ + private void update(LevelPos levelPos) { + + levelPos = levelPos.regionModule(); + boolean[][] children = getChildren(levelPos); + int numberOfChildren = 0; + + /**TODO add the ability to change how the heigth and depth are determinated (for example min or max)**/ + byte minGenerationType = 10; + int tempRed = 0; + int tempGreen = 0; + int tempBlue = 0; + int tempHeight = 0; + int tempDepth = 0; + int newPosX; + int newPosZ; + byte newLod; + LevelPos childPos; + for (int x = 0; x <= 1; x++) { for (int z = 0; z <= 1; z++) { - if(children[x][z]){ - numberOfChild++; + newPosX = 2 * levelPos.posX + x; + newPosZ = 2 * levelPos.posZ + z; + newLod = (byte) (levelPos.detailLevel - 1); + childPos = new LevelPos(newLod, newPosX, newPosZ); + if (hasDataBeenGenerated(childPos)) { + numberOfChildren++; + + tempRed += colors[newLod][newPosX][newPosZ][0]; + tempGreen += colors[newLod][newPosX][newPosZ][1]; + tempBlue += colors[newLod][newPosX][newPosZ][2]; + tempHeight += height[newLod][newPosX][newPosZ]; + tempDepth += depth[newLod][newPosX][newPosZ]; + minGenerationType = (byte) Math.min(minGenerationType, generationType[newLod][newPosX][newPosZ]); } } } - if(numberOfChild>0) { - - //int minDepth = Integer.MAX_VALUE; - //int maxDepth = Integer.MIN_VALUE; - //int minHeight = Integer.MAX_VALUE; - //int maxHeight = Integer.MIN_VALUE; - - byte minGenerationType = 0; - for (int x = 0; x <= 1; x++) { - for (int z = 0; z <= 1; z++) { - if (children[x][z]) { - int newPosX = 2 * posX + x; - int newPosZ = 2 * posZ + z; - for (int col = 0; col <= 2; col++) { - colors[lod][posX][posZ][col] += (byte) (colors[lod - 1][newPosX][newPosZ][col] / numberOfChild); - } - - //TODO ability to change between mean, max and min. - - height[lod][posX][posZ] += (short) (height[lod - 1][newPosX][newPosZ] / numberOfChild); - //minHeight = Math.min( height[lod - 1][newPosX][newPosZ] , maxHeight); - //maxHeight = Math.max( height[lod - 1][newPosX][newPosZ] , minHeight); - - depth[lod][posX][posZ] += (short) (depth[lod - 1][newPosX][newPosZ] / numberOfChild); - //minDepth = Math.min( depth[lod - 1][newPosX][newPosZ] , maxDepth); - //maxDepth = Math.max( depth[lod - 1][newPosX][newPosZ] , minDepth); - - minGenerationType = (byte) Math.max(minGenerationType, generationType[lod - 1][newPosX][newPosZ]); - } - } - } - //height[lod][posX][posZ] = minHeight; - //depth[lod][posX][posZ] = maxDepth; - generationType[lod][posX][posZ] = minGenerationType; + if (numberOfChildren > 0) { + colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][0] = (byte) (tempRed / numberOfChildren); + colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][1] = (byte) (tempGreen / numberOfChildren); + colors[levelPos.detailLevel][levelPos.posX][levelPos.posZ][2] = (byte) (tempBlue / numberOfChildren); + height[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = (short) (tempHeight / numberOfChildren); + depth[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = (short) (tempDepth / numberOfChildren); + generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = minGenerationType; + dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ] = true; } } - private boolean[][] getChildren(byte lod, int posX, int posZ){ - posX = Math.floorMod(posX, (int) Math.pow(2,lod)); - posZ = Math.floorMod(posZ, (int) Math.pow(2,lod)); + private boolean[][] getChildren(LevelPos levelPos) { + levelPos = levelPos.regionModule(); boolean[][] children = new boolean[2][2]; - int numberOfChild=0; - if(minLevelOfDetail == lod){ + int numberOfChild = 0; + if (minLevelOfDetail == levelPos.detailLevel) { return children; } - for(int x = 0; x <= 1; x++) { + for (int x = 0; x <= 1; x++) { for (int z = 0; z <= 1; z++) { - children[x][z] = (generationType[lod-1][2*posX+x][2*posZ+z] != 0); + children[x][z] = (dataExistence[levelPos.detailLevel - 1][2 * levelPos.posX + x][2 * levelPos.posZ + z]); } } return children; } - private void removeDetailLevel(byte lod, byte[][][] colors, short[][] height, short[][] depth, byte[][] generationType){ + public boolean doesDataExist(ChunkPos chunkPos) { + return doesDataExist(new LevelPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPos.x, chunkPos.z)); } - private void addDetailLevel(byte lod, int posX, int posZ){ + public boolean doesDataExist(LevelPos levelPos) { + levelPos = levelPos.regionModule(); + return dataExistence[levelPos.detailLevel][levelPos.posX][levelPos.posZ]; + } + + public DistanceGenerationMode getGenerationMode(LevelPos levelPos) { + levelPos = levelPos.regionModule(); + DistanceGenerationMode generationMode = DistanceGenerationMode.NONE; + switch(generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ]){ + case 0: + generationMode = DistanceGenerationMode.NONE; + break; + case 1: + generationMode = DistanceGenerationMode.BIOME_ONLY; + break; + case 2: + generationMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT; + break; + case 3: + generationMode = DistanceGenerationMode.SURFACE; + break; + case 4: + generationMode = DistanceGenerationMode.FEATURES; + break; + case 5: + generationMode = DistanceGenerationMode.SERVER; + break; + default: + generationMode = DistanceGenerationMode.NONE; + break; + + } + return generationMode; + } + + public boolean hasDataBeenGenerated(LevelPos levelPos) { + levelPos = levelPos.regionModule(); + return (generationType[levelPos.detailLevel][levelPos.posX][levelPos.posZ] != 0); + } + /** + * This will be used to save a level + * + * @param lod + * @return + */ + public LevelContainer getLevel(byte lod) { + return new LevelContainer(lod, colors[lod], height[lod], depth[lod], generationType[lod], dataExistence[lod]); + } + + public void addLevel(byte lod, LevelContainer levelContainer) { + if (lod < minLevelOfDetail - 1) { + throw new IllegalArgumentException("addLevel requires a level that is at least the minimum level of the region -1 "); + } + if (lod == minLevelOfDetail - 1) minLevelOfDetail = lod; + colors[lod] = levelContainer.colors; + height[lod] = levelContainer.height; + depth[lod] = levelContainer.depth; + generationType[lod] = levelContainer.generationType; + dataExistence[lod] = levelContainer.dataExistence; + + } + + public void removeDetailLevel(byte lod) { + for (byte tempLod = 0; tempLod <= lod; tempLod++) { + colors[tempLod] = new byte[0][0][0]; + height[tempLod] = new short[0][0]; + depth[tempLod] = new short[0][0]; + generationType[tempLod] = new byte[0][0]; + dataExistence[tempLod] = new boolean[0][0]; + } } } diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeWorld.java b/src/main/java/com/seibel/lod/objects/LodWorld.java similarity index 90% rename from src/main/java/com/seibel/lod/objects/LodQuadTreeWorld.java rename to src/main/java/com/seibel/lod/objects/LodWorld.java index d70609a72..422718c2e 100644 --- a/src/main/java/com/seibel/lod/objects/LodQuadTreeWorld.java +++ b/src/main/java/com/seibel/lod/objects/LodWorld.java @@ -31,11 +31,11 @@ import net.minecraft.world.DimensionType; * @author Leonardo Amato * @version 8-17-2021 */ -public class LodQuadTreeWorld +public class LodWorld { private String worldName; - private Map lodDimensions; + private Map lodDimensions; /** If true then the LOD world is setup and ready to use */ private boolean isWorldLoaded = false; @@ -44,7 +44,7 @@ public class LodQuadTreeWorld - public LodQuadTreeWorld() + public LodWorld() { worldName = NO_WORLD_LOADED; } @@ -69,7 +69,7 @@ public class LodQuadTreeWorld return; worldName = newWorldName; - lodDimensions = new Hashtable(); + lodDimensions = new Hashtable(); isWorldLoaded = true; } @@ -90,7 +90,7 @@ public class LodQuadTreeWorld * Adds newStorage to this world, if a LodQuadTreeDimension * already exists for the given dimension it is replaced. */ - public void addLodDimension(LodQuadTreeDimension newStorage) + public void addLodDimension(LodDimension newStorage) { if (lodDimensions == null) return; @@ -101,7 +101,7 @@ public class LodQuadTreeWorld /** * Returns null if no LodQuadTreeDimension exists for the given dimension */ - public LodQuadTreeDimension getLodDimension(DimensionType dimension) + public LodDimension getLodDimension(DimensionType dimension) { if (lodDimensions == null) return null; diff --git a/src/main/java/com/seibel/lod/proxy/ClientProxy.java b/src/main/java/com/seibel/lod/proxy/ClientProxy.java index 5b6fa67f5..c25be2459 100644 --- a/src/main/java/com/seibel/lod/proxy/ClientProxy.java +++ b/src/main/java/com/seibel/lod/proxy/ClientProxy.java @@ -17,6 +17,8 @@ */ package com.seibel.lod.proxy; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodWorld; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -29,8 +31,6 @@ import com.seibel.lod.enums.FogDrawOverride; import com.seibel.lod.enums.LodDetail; import com.seibel.lod.enums.ShadingMode; import com.seibel.lod.handlers.LodConfig; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeWorld; import com.seibel.lod.objects.RegionPos; import com.seibel.lod.render.LodNodeRenderer; import com.seibel.lod.util.LodUtil; @@ -54,7 +54,7 @@ public class ClientProxy { public static final Logger LOGGER = LogManager.getLogger("LOD"); - private static LodQuadTreeWorld lodWorld = new LodQuadTreeWorld(); + private static LodWorld lodWorld = new LodWorld(); private static LodNodeBuilder lodNodeBuilder = new LodNodeBuilder(); private static LodNodeBufferBuilder lodBufferBuilder = new LodNodeBufferBuilder(lodNodeBuilder); private static LodNodeRenderer renderer = new LodNodeRenderer(lodBufferBuilder); @@ -93,7 +93,7 @@ public class ClientProxy viewDistanceChangedEvent(); - LodQuadTreeDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType()); + LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType()); if (lodDim == null) return; @@ -224,7 +224,7 @@ public class ClientProxy /** * Re-centers the given LodDimension if it needs to be. */ - private void playerMoveEvent(LodQuadTreeDimension lodDim) + private void playerMoveEvent(LodDimension lodDim) { // make sure the dimension is centered RegionPos playerRegionPos = new RegionPos(mc.player.blockPosition()); @@ -267,7 +267,7 @@ public class ClientProxy // public getters // //================// - public static LodQuadTreeWorld getLodWorld() + public static LodWorld getLodWorld() { return lodWorld; } diff --git a/src/main/java/com/seibel/lod/render/LodNodeRenderer.java b/src/main/java/com/seibel/lod/render/LodNodeRenderer.java index b0012da1c..4605d76f9 100644 --- a/src/main/java/com/seibel/lod/render/LodNodeRenderer.java +++ b/src/main/java/com/seibel/lod/render/LodNodeRenderer.java @@ -22,6 +22,7 @@ import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.HashSet; +import com.seibel.lod.objects.LevelPos; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.NVFogDistance; @@ -35,8 +36,7 @@ import com.seibel.lod.enums.FogDrawOverride; import com.seibel.lod.enums.FogQuality; import com.seibel.lod.handlers.LodConfig; import com.seibel.lod.handlers.ReflectionHandler; -import com.seibel.lod.objects.LodQuadTreeDimension; -import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodDimension; import com.seibel.lod.objects.NearFarFogSettings; import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.util.LodUtil; @@ -146,7 +146,7 @@ public class LodNodeRenderer * @param partialTicks how far into the current tick this method was called. */ @SuppressWarnings("deprecation") - public void drawLODs(LodQuadTreeDimension lodDim, float partialTicks, IProfiler newProfiler) + public void drawLODs(LodDimension lodDim, float partialTicks, IProfiler newProfiler) { if (lodDim == null) { @@ -585,7 +585,7 @@ public class LodNodeRenderer * setup the lighting to be used for the LODs */ @SuppressWarnings("deprecation") - private void setupLighting(LodQuadTreeDimension lodDimension, float partialTicks) + private void setupLighting(LodDimension lodDimension, float partialTicks) { float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f; float gammaMultiplyer = (float)mc.options.gamma - 0.5f; @@ -778,7 +778,7 @@ public class LodNodeRenderer * Get a HashSet of all ChunkPos within the normal render distance * that should not be rendered. */ - private HashSet getNearbyLodChunkPosToSkip(LodQuadTreeDimension lodDim, BlockPos playerPos) + private HashSet getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPos playerPos) { int chunkRenderDist = mc.options.renderDistance; int blockRenderDist = chunkRenderDist * 16; @@ -793,11 +793,12 @@ public class LodNodeRenderer { for(int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++) { - LodQuadTreeNode lod = lodDim.getLodFromCoordinates(new ChunkPos(x, z), 4); - if (lod != null) + + LevelPos levelPos = new LevelPos((byte) 4, x, z); + if (lodDim.doesDataExist(levelPos)) { - short lodHighestPoint = lod.getLodDataPoint().height; - + short lodHighestPoint = lodDim.getData(levelPos).height; + if (playerPos.getY() < lodHighestPoint) { // don't draw Lod's that are taller than the player