diff --git a/build.gradle b/build.gradle index ae9b5e24e..530592861 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,11 @@ buildscript { maven { url = 'https://files.minecraftforge.net/maven' } jcenter() mavenCentral() + // potential replacement in case of problems: + // https://dist.creeper.host/Sponge/maven maven { url = 'https://repo.spongepowered.org/maven/' } - // potential replacement in case of problems: - // https://dist.creeper.host/Sponge/maven + // used to download and compile dependencies from git repos + maven { url 'https://jitpack.io' } } dependencies { classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '3.+', changing: true @@ -116,26 +118,47 @@ minecraft { // Include resources generated by data generators. sourceSets.main.resources { srcDir 'src/generated/resources' } +// this is required so that we can use +// jitpack in the dependencies section below +repositories { + mavenCentral() + // used to download and compile dependencies from git repos + maven { url 'https://jitpack.io' } +} + dependencies { // Specify the version of Minecraft to use, If this is any group other then 'net.minecraft' it is assumed // that the dep is a ForgeGradle 'patcher' dependency. And it's patches will be applied. // The userdev artifact is a special name and will get all sorts of transformations applied to it. minecraft 'net.minecraftforge:forge:1.16.5-36.1.0' - - // these were added to hopefully allow for cloning - // configuredFeatures to allow for safe - // multi threaded feature generation. Sadly I couldn't find - // a way to duplicate lambda functions (which features use) - // so for now I'm not sure what to do. - //implementation 'io.github.kostaskougios:cloning:1.10.3' - // - //implementation ('com.esotericsoftware:kryo:5.1.1') { - // exclude group: "org.objenesis" - //} - //implementation 'org.objenesis:objenesis:3.2' - - + implementation ('com.github.KaptainWutax:BiomeUtils:-SNAPSHOT'){ + transitive = false + } + implementation ('com.github.KaptainWutax:SeedUtils:-SNAPSHOT'){ + transitive = false + } + implementation ('com.github.KaptainWutax:MCUtils:-SNAPSHOT'){ + transitive = false + } + // not working + implementation ('com.github.KaptainWutax:NoiseUtils:-SNAPSHOT'){ + transitive = false + } + + // these were added to hopefully allow for cloning + // configuredFeatures to allow for safe + // multi threaded feature generation. Sadly I couldn't find + // a way to duplicate lambda functions (which features use) + // so for now I'm not sure what to do. + //implementation 'io.github.kostaskougios:cloning:1.10.3' + // + //implementation ('com.esotericsoftware:kryo:5.1.1') { + // exclude group: "org.objenesis" + //} + //implementation 'org.objenesis:objenesis:3.2' + + // You may put jars on which you depend on in ./libs or you may define them like so.. // compile "some.group:artifact:version:classifier" // compile "some.group:artifact:version" diff --git a/src/main/java/com/seibel/lod/LodMain.java b/src/main/java/com/seibel/lod/LodMain.java index c4177f297..d4b133041 100644 --- a/src/main/java/com/seibel/lod/LodMain.java +++ b/src/main/java/com/seibel/lod/LodMain.java @@ -47,7 +47,6 @@ public class LodMain public static ClientProxy client_proxy; - @SuppressWarnings("deprecation") private void init(final FMLCommonSetupEvent event) { ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.clientSpec); diff --git a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java index f6a0c9b77..01d8d98fe 100644 --- a/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodBufferBuilder.java @@ -275,11 +275,12 @@ public class LodBufferBuilder else currentBuffer = buildableFarBuffer; + throw new UnsupportedOperationException("Not Implemented"); // get the desired LodTemplate and // add this LOD to the buffer - LodConfig.CLIENT.lodTemplate.get(). - template.addLodToBuffer(currentBuffer, lodDim, lod, - xOffset, yOffset, zOffset, renderer.debugging); +// LodConfig.CLIENT.lodTemplate.get(). +// template.addLodToBuffer(currentBuffer, lodDim, lod, +// xOffset, yOffset, zOffset, renderer.debugging); } } diff --git a/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java new file mode 100644 index 000000000..9c7bce2e0 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java @@ -0,0 +1,445 @@ +/* + * 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.builders; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +import org.lwjgl.opengl.GL11; + +import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker; +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.NearFarBuffer; +import com.seibel.lod.proxy.ClientProxy; +import com.seibel.lod.render.LodNodeRenderer; +import com.seibel.lod.util.LodUtil; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.biome.BiomeContainer; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.server.ServerWorld; +import net.minecraftforge.common.WorldWorkerManager; + +/** + * This object is used to create NearFarBuffer objects. + * + * @author James Seibel + * @version 06-19-2021 + */ +public class LodNodeBufferBuilder +{ + private Minecraft mc; + + /** This holds the thread used to generate new LODs off the main thread. */ + private ExecutorService genThread = Executors.newSingleThreadExecutor(); + + private LodNodeBuilder LodQuadTreeNodeBuilder; + + /** The buffers that are used to create LODs using near fog */ + public volatile BufferBuilder buildableNearBuffer; + /** The buffers that are used to create LODs using far fog */ + public volatile BufferBuilder buildableFarBuffer; + + /** if this is true the LOD buffers are currently being + * regenerated. */ + public volatile boolean generatingBuffers = false; + + /** if this is true new LOD buffers have been generated + * and are waiting to be swapped with the drawable buffers*/ + private volatile boolean switchBuffers = false; + + /** This keeps track of how many chunk generation requests are on going. + * This is to prevent chunks from being generated for a long time in an area + * the player is no longer in. */ + public volatile AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0); + + + + /** how many chunks to generate outside of the player's + * view distance at one time. (or more specifically how + * many requests to make at one time). + * I multiply by 8 to make sure there is always a buffer of chunk requests, + * to make sure the CPU is always busy and we can generate LODs as quickly as + * possible. */ + public int maxChunkGenRequests = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8; + + + public LodNodeBufferBuilder(LodNodeBuilder newLodBuilder) + { + mc = Minecraft.getInstance(); + LodQuadTreeNodeBuilder = newLodBuilder; + } + + + private BiomeContainer biomeContainer = null; + private LodQuadTreeDimension previousDimension = null; + + + /** + * Create a thread to asynchronously generate LOD buffers + * centered around the given camera X and Z. + *
+ * This method will write to the drawable near and far buffers. + *
+ * 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, + double playerX, double playerZ, int numbChunksWide) + { + // only allow one generation process to happen at a time + if (generatingBuffers) + return; + + if (buildableNearBuffer == null || buildableFarBuffer == null) + throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created."); + + if (previousDimension != lodDim) + { + biomeContainer = LodUtil.getServerWorldFromDimension(lodDim.dimension).getChunk(0, 0, ChunkStatus.EMPTY).getBiomes(); + previousDimension = lodDim; + } + + + + generatingBuffers = true; + + + + // this seemingly useless math is required, + // just using (int) playerX/Z doesn't work + int playerXChunkOffset = ((int) playerX / LodQuadTreeNode.CHUNK_WIDTH) * LodQuadTreeNode.CHUNK_WIDTH; + int playerZChunkOffset = ((int) playerZ / LodQuadTreeNode.CHUNK_WIDTH) * LodQuadTreeNode.CHUNK_WIDTH; + // this is where we will start drawing squares + // (exactly half the total width) + int startX = (-LodQuadTreeNode.CHUNK_WIDTH * (numbChunksWide / 2)) + playerXChunkOffset; + int startZ = (-LodQuadTreeNode.CHUNK_WIDTH * (numbChunksWide / 2)) + playerZChunkOffset; + + + Thread thread = new Thread(()-> + { + // index of the chunk currently being added to the + // generation list + int chunkGenIndex = 0; + + ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests]; + // if we don't have a full number of chunks to generate in chunksToGen + // we can top it off from the reserve + ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests]; + int minChunkDist = Integer.MAX_VALUE; + ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodQuadTreeNode.CHUNK_WIDTH, (int)playerZ / LodQuadTreeNode.CHUNK_WIDTH); + + + // generate our new buildable buffers + buildableNearBuffer.begin(GL11.GL_QUADS, LodNodeRenderer.LOD_VERTEX_FORMAT); + buildableFarBuffer.begin(GL11.GL_QUADS, LodNodeRenderer.LOD_VERTEX_FORMAT); + + // x axis + for (int i = 0; i < numbChunksWide; i++) + { + // z axis + for (int j = 0; j < numbChunksWide; j++) + { + int chunkX = i + (startX / LodQuadTreeNode.CHUNK_WIDTH); + int chunkZ = j + (startZ / LodQuadTreeNode.CHUNK_WIDTH); + + // skip any chunks that Minecraft is going to render + if(isCoordInCenterArea(i, j, (numbChunksWide / 2)) + && renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkX, chunkZ))) + { + continue; + } + + + // set where this square will be drawn in the world + double xOffset = (LodQuadTreeNode.CHUNK_WIDTH * i) + // offset by the number of LOD blocks + startX; // offset so the center LOD block is centered underneath the player + double yOffset = 0; + double zOffset = (LodQuadTreeNode.CHUNK_WIDTH * j) + startZ; + + LodQuadTreeNode lod = lodDim.getLodFromCoordinates(chunkX, chunkZ, LodQuadTreeNode.CHUNK_LEVEL); + + if (lod == null || lod.getComplexity() == DistanceGenerationMode.NONE) + { + // 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) + { + ChunkPos pos = new ChunkPos(chunkX, chunkZ); + + + // can be used for debugging +// if (chunksToGen == null) +// { +// chunkGenIndex = 0; +// chunksToGen = new ChunkPos[maxChunkGenRequests]; +// } +// +// if (chunkGenIndex < maxChunkGenRequests) +// { +// chunksToGen[chunkGenIndex] = pos; +// chunkGenIndex++; +// } + + + + + // determine if this position is closer to the player + // than the previous + int newDistance = playerChunkPos.getChessboardDistance(pos); + + // issue #40 + // TODO optimize this code, + // using the purely optimized code above we can achieve close to + // 100% CPU utilization, this code generally achieves 40 - 50% + // after a certain point; and I'm sure there is a better data + // structure for this. + if (newDistance < minChunkDist) + { + // this chunk is closer, clear any previous + // positions and update the new minimum distance + minChunkDist = newDistance; + chunksToGenReserve = chunksToGen; + + // move all the old chunks into the reserve + ChunkPos[] newReserve = new ChunkPos[maxChunkGenRequests]; + int oldToGenIndex = 0; + int oldReserveIndex = 0; + for(int tmpIndex = 0; tmpIndex < newReserve.length; tmpIndex++) + { + // we don't check if the boundaries are good since + // the tmp array will always be the same length + // as chunksToGen and chunksToGenReserve + + if (chunksToGen[oldToGenIndex] != null) + { + // add all the closest chunks... + newReserve[tmpIndex] = chunksToGen[oldToGenIndex]; + oldToGenIndex++; + } + else if (chunksToGenReserve[oldReserveIndex] != null) + { + // ...then add all the previous reserve chunks + // (which are farther away) + newReserve[tmpIndex] = chunksToGenReserve[oldToGenIndex]; + oldReserveIndex++; + } + else + { + // we have moved all the items from + // the old chunksToGen and reserve + break; + } + } + chunksToGenReserve = newReserve; + + + + chunkGenIndex = 0; + chunksToGen = new ChunkPos[maxChunkGenRequests]; + chunksToGen[chunkGenIndex] = pos; + chunkGenIndex++; + } + else if (newDistance <= minChunkDist) + { + // this chunk position is as close or closers than the + // minimum distance + if(chunkGenIndex < maxChunkGenRequests) + { + // we are still under the number of chunks to generate + // add this position to the list + chunksToGen[chunkGenIndex] = pos; + chunkGenIndex++; + } + } + } // lod null and can generate more chunks + + // don't render this null/empty chunk + continue; + + } // lod null or empty + + + BufferBuilder currentBuffer = null; + if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2)) + currentBuffer = buildableNearBuffer; + else + currentBuffer = buildableFarBuffer; + + // get the desired LodTemplate and + // add this LOD to the buffer + LodConfig.CLIENT.lodTemplate.get(). + template.addLodToBuffer(currentBuffer, lodDim, lod, + xOffset, yOffset, zOffset, renderer.debugging); + } + } + + ClientProxy.LOGGER.info(numberOfChunksWaitingToGenerate.get()); + + // issue #19 + // TODO add a way for a server side mod to generate chunks requested here + if(mc.hasSingleplayerServer()) + { + ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension); + + // make sure we have as many chunks to generate as we are allowed + if (chunkGenIndex < maxChunkGenRequests) + { + for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++) + { + chunksToGen[i] = chunksToGenReserve[j]; + } + } + + // start chunk generation + for(ChunkPos chunkPos : chunksToGen) + { + if(chunkPos == null) + break; + + // make sure we don't generate this chunk again + lodDim.addNode(new LodQuadTreeNode(chunkPos)); + + numberOfChunksWaitingToGenerate.addAndGet(1); + + LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, renderer, LodQuadTreeNodeBuilder, this, lodDim, serverWorld, biomeContainer); + WorldWorkerManager.addWorker(genWorker); + } + } + + // finish the buffer building + buildableNearBuffer.end(); + buildableFarBuffer.end(); + + // mark that the buildable buffers as ready to swap + generatingBuffers = false; + switchBuffers = true; + }); + + genThread.execute(thread); + + return; + } + + + + + + + + + + + //====================// + // generation helpers // + //====================// + + /** + * Returns if the given coordinate is in the loaded area of the world. + * @param centerCoordinate the center of the loaded world + */ + private boolean isCoordInCenterArea(int i, int j, int centerCoordinate) + { + return (i >= centerCoordinate - mc.options.renderDistance + && i <= centerCoordinate + mc.options.renderDistance) + && + (j >= centerCoordinate - mc.options.renderDistance + && j <= centerCoordinate + mc.options.renderDistance); + } + + + /** + * Find the coordinates that are in the center half of the given + * 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius). + */ + private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius) + { + int halfRadius = lodRadius / 2; + + return (chunkX >= lodRadius - halfRadius + && chunkX <= lodRadius + halfRadius) + && + (chunkZ >= lodRadius - halfRadius + && chunkZ <= lodRadius + halfRadius); + } + + + + + + //===============================// + // BufferBuilder related methods // + //===============================// + + + /** + * Called from the LodRenderer to create the + * BufferBuilders at the right size. + * + * @param bufferMaxCapacity + */ + public void setupBuffers(int bufferMaxCapacity) + { + buildableNearBuffer = new BufferBuilder(bufferMaxCapacity); + buildableFarBuffer = new BufferBuilder(bufferMaxCapacity); + } + + /** + * Swap the drawable and buildable buffers and return + * the old drawable buffers. + * @param drawableNearBuffer + * @param drawableFarBuffer + */ + public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer) + { + // swap the BufferBuilders + BufferBuilder tmp = buildableNearBuffer; + buildableNearBuffer = drawableNearBuffer; + drawableNearBuffer = tmp; + + tmp = buildableFarBuffer; + buildableFarBuffer = drawableFarBuffer; + drawableFarBuffer = tmp; + + + // the buffers have been swapped + switchBuffers = false; + + return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer); + } + + /** + * If this is true the buildable near and far + * buffers have been generated and are ready to be + * sent to the LodRenderer. + */ + public boolean newBuffersAvaliable() + { + return switchBuffers; + } + + + + +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java b/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java new file mode 100644 index 000000000..423ee70b7 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/LodNodeBuilder.java @@ -0,0 +1,468 @@ +/* + * 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.builders; + +import java.awt.Color; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import com.seibel.lod.enums.DistanceGenerationMode; +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.util.LodUtil; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.FlowingFluidBlock; +import net.minecraft.block.GrassBlock; +import net.minecraft.block.LeavesBlock; +import net.minecraft.world.DimensionType; +import net.minecraft.world.IWorld; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.chunk.ChunkSection; +import net.minecraft.world.chunk.IChunk; +import net.minecraft.world.gen.Heightmap; + +public class LodNodeBuilder { + private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(); + private long seed; + private DimensionType dimension; + + public static final int CHUNK_DATA_WIDTH = LodQuadTreeNode.CHUNK_WIDTH; + public static final int CHUNK_SECTION_HEIGHT = CHUNK_DATA_WIDTH; + public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG; + + /** + * Default size of any LOD regions we use + */ + public int regionWidth = 5; + + + /** + * fast biome calculator + */ + //private BiomeSource biomeSource; + //Biome biome=biomeSource.getBiome(x,y,z); // here y is always 0 no matter what you pass + + public LodNodeBuilder() { + + } +/* + public setApproxGenerator(long seed){ + //Dimension.OVERWORLD; + //Dimension.END; + //Dimension.NETHER; + biomeSource = BiomeSource.of(Dimension.OVERWORLD ,MCVersion.v1_16_4, seed); + } + public void generateLodNodeAsync(List dataList){ + Thread thread = new Thread(() ->{ + for(LodNodeData data : dataList){ + + } + }); + thread.setPriority(4); + lodGenThreadPool.execute(thread); + + return; + } + */ + + public void generateLodNodeAsync(IChunk chunk, LodQuadTreeWorld lodWorld, IWorld world) { + if (lodWorld == null || !lodWorld.getIsWorldLoaded()) { + System.out.println("This case?"); + 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(); + + LodQuadTreeNode node = generateLodNodeFromChunk(chunk); + + LodQuadTreeDimension lodDim; + + if (lodWorld.getLodDimension(dim) == null) + { + //System.out.println("Adding"); + lodDim = new LodQuadTreeDimension(dim, lodWorld, regionWidth); + lodWorld.addLodDimension(lodDim); + } + else + { + //System.out.println("Not adding"); + lodDim = lodWorld.getLodDimension(dim); + } + + lodDim.addNode(node); + } catch (IllegalArgumentException | NullPointerException e) { + e.printStackTrace(); + // if the world changes while LODs are being generated + // they will throw errors as they try to access things that no longer + // exist. + } + }); + lodGenThreadPool.execute(thread); + //System.out.println("Is this ENDING?"); + + return; + } + + + /** + * Creates a LodChunk for a chunk in the given world. + * + * @throws IllegalArgumentException thrown if either the chunk or world is null. + */ + public LodQuadTreeNode generateLodNodeFromChunk(IChunk chunk) throws IllegalArgumentException { + return generateLodNodeFromChunk(chunk, new LodBuilderConfig()); + } + + /** + * Creates a LodChunk for a chunk in the given world. + * + * @throws IllegalArgumentException thrown if either the chunk or world is null. + * @return + */ + public LodQuadTreeNode generateLodNodeFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException { + if (chunk == null) + throw new IllegalArgumentException("generateLodFromChunk given a null chunk"); + + // TODO startX/Z and endX/Z are relative coordinates + // getMin/Max appears to return world block coordinates + int startX = 0; //chunk.getPos().getMinBlockX(); + int startZ = 0; //chunk.getPos().getMinBlockZ(); + int endX = 15; //chunk.getPos().getMaxBlockX(); + int endZ = 15; //chunk.getPos().getMaxBlockZ(); + + Color color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ); + + short height; + short depth; + + /**TODO I HAVE TO RE-ADD THE HEIGHTMAP**/ + height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ); + depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ); + + + return new LodQuadTreeNode(LodQuadTreeNode.CHUNK_LEVEL, chunk.getPos().x, chunk.getPos().z, new LodDataPoint(height, depth, color) , DistanceGenerationMode.SERVER); + } + + + //=====================// + // constructor helpers // + //=====================// + + + /** + * Find the lowest valid point from the bottom. + * + * @param chunkSections + * @param startX + * @param startZ + * @param endX + * @param endZ + */ + 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++) { + if (isLayerValidLodPoint(chunkSections, section, y, x, z)) { + numberOfBlocksFound++; + + if (numberOfBlocksFound >= numberOfBlocksRequired) { + // we found + // enough blocks in this + // layer to count as an + // LOD point + return (short) (y + (section * CHUNK_SECTION_HEIGHT)); + } + } + } + } + } + } + + // we never found a valid LOD point + return -1; + } + + /** + * Find the lowest valid point from the bottom. + */ + @SuppressWarnings("unused") + private short determineBottomPoint(Heightmap heightmap) { + // the heightmap only shows how high the blocks go, it + // doesn't have any info about how low they go + return 0; + } + + + /** + * Find the highest valid point from the Top + * + * @param chunkSections + * @param startX + * @param startZ + * @param endX + * @param endZ + */ + private short determineHeightPointForArea(ChunkSection[] chunkSections, + int startX, int startZ, int endX, int endZ) { + int numberOfBlocksRequired = ((endX - startX) * (endZ - startZ) / 2); + // search from the top down + for (int section = chunkSections.length - 1; section >= 0; section--) { + 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++) { + if (isLayerValidLodPoint(chunkSections, section, y, x, z)) { + numberOfBlocksFound++; + + if (numberOfBlocksFound >= numberOfBlocksRequired) { + // we found + // enough blocks in this + // layer to count as an + // LOD point + return (short) (y + (section * CHUNK_SECTION_HEIGHT)); + } + } + } + } + } + } + + // we never found a valid LOD point + return -1; + } + + /** + * Find the highest point from the Top + */ + private short determineHeightPoint(Heightmap heightmap, + int startX, int startZ, int endX, int endZ) + { + short highest = 0; + for (int x = startX; x < endX; x++) { + for (int z = startZ; z < endZ; z++) { + short newHeight = (short) heightmap.getFirstAvailable(x, z); + if (newHeight > highest) + highest = newHeight; + } + } + + return highest; + } + + /** + * Generate the color for the given chunk using biome + * water color, foliage color, and grass color. + * + * @param config_useSolidBlocksInColorGen
+ * If true we look down from the top of the
+ * chunk until we find a non-invisible block, and then use
+ * its color. If false we generate the color immediately for
+ * each x and z. + * @param config_useBiomeColors
+ * If true use biome foliage, water, and grass colors,
+ * otherwise only use the block's material color + */ + private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, 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--) + { + if( !foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen)) + { + for(int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--) + { + int colorInt = 0; + BlockState blockState = null; + + if (chunkSections[i] != null) + { + blockState = chunkSections[i].getBlockState(x, y, z); + colorInt = blockState.materialColor.col; + } + + if(colorInt == 0 && config.useSolidBlocksInColorGen) + { + // skip air or invisible blocks + continue; + } + + if (config.useBiomeColors) + { + // the bit shift is equivalent to dividing by 4 + Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2); + + if (biome.getBiomeCategory() == Biome.Category.OCEAN || + biome.getBiomeCategory() == Biome.Category.RIVER) + { + if (config.useSolidBlocksInColorGen && + blockState != Blocks.WATER.defaultBlockState()) + { + // non-water block + colorInt = getColorForBlock(x, z, blockState, biome); + } + else + { + // water block + colorInt = biome.getWaterColor(); + } + } + else if (biome.getBiomeCategory() == Biome.Category.EXTREME_HILLS) + { + colorInt = Blocks.STONE.defaultMaterialColor().col; + } + else if (biome.getBiomeCategory() == Biome.Category.ICY) + { + colorInt = LodUtil.colorToInt(Color.WHITE); + } + else if (biome.getBiomeCategory() == Biome.Category.THEEND) + { + colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col; + } + else if (config.useSolidBlocksInColorGen) + { + colorInt = getColorForBlock(x, z, blockState, biome); + } + else + { + colorInt = biome.getGrassColor(x, z); + } + } + else + { + // I have no idea why I need to bit shift to the right, but + // if I don't the biomes don't show up correctly. + 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; + } + } + } + } + } + + if(numbOfBlocks == 0) + numbOfBlocks = 1; + + red /= numbOfBlocks; + green /= numbOfBlocks; + blue /= numbOfBlocks; + + return new Color(red, green, blue); + } + + + /** + * Returns a color int for a given block. + */ + private int getColorForBlock(int x, int z, BlockState blockState, Biome biome) + { + int colorInt = 0; + + if (blockState == Blocks.AIR.defaultBlockState()) { + colorInt = biome.getGrassColor(x, z); + } else if (blockState.getBlock() instanceof LeavesBlock) { + Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker(); + colorInt = LodUtil.colorToInt(leafColor); + } else if (blockState.getBlock() instanceof GrassBlock) { + colorInt = biome.getGrassColor(x, z); + } else if (blockState.getBlock() instanceof FlowingFluidBlock) { + colorInt = biome.getWaterColor(); + } else { + colorInt = blockState.materialColor.col; + } + + return colorInt; + } + + + /** + * Is the layer between the given X, Z, and dataIndex + * values a valid LOD point? + */ + private boolean isLayerValidLodPoint( + ChunkSection[] chunkSections, + int sectionIndex, int y, + int x, int z) + { + if (chunkSections[sectionIndex] == null) + { + // this section doesn't have any blocks, + // it is not a valid section + return false; + } + else + { + if (chunkSections[sectionIndex].getBlockState(x, y, z) != null && + chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR) + { + 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 new file mode 100644 index 000000000..adf2f61f5 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java @@ -0,0 +1,53 @@ +/* + * 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.builders.lodNodeTemplates; + +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; + +import net.minecraft.client.renderer.BufferBuilder; + +/** + * This is the abstract class used to create different + * BufferBuilders. + * + * @author James Seibel + * @version 06-16-2021 + */ +public abstract class AbstractLodNodeTemplate +{ + public abstract void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodQuadTreeNode lod, + double xOffset, double yOffset, double zOffset, + boolean debugging); + + /** 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) + { + 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 getBufferMemoryForSingleLod(int level); + + + +} diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java new file mode 100644 index 000000000..d10e0df9b --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java @@ -0,0 +1,183 @@ +/* + * 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.builders.lodNodeTemplates; +import java.awt.Color; + +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.util.LodUtil; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.util.math.AxisAlignedBB; + + +/** + * Builds LODs as rectangular prisms. + * + * @author James Seibel + * @version 06-16-2021 + */ +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) { + AxisAlignedBB bbox; + + // Add this LOD to the BufferBuilder + int halfWidth = lod.width / 2; + int startX = lod.startX; + int startZ = lod.startZ; + + // returns null if the lod is empty at the given location + bbox = generateBoundingBox( + lod.lodDataPoint.height, + lod.lodDataPoint.depth, + lod.width, + xOffset - halfWidth + startX, + yOffset, + zOffset - halfWidth + startZ); + + if (bbox != null) { + addBoundingBoxToBuffer(buffer, bbox, lod.lodDataPoint.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) + { + // the side colors are different because + // when using fast lighting in Minecraft the north/south + // and east/west sides are different in a similar way + 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()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha()); + // bottom (facing down) + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha()); + 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()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); + // north (facing +Z) + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha()); + 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()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); + // east (facing +X) + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha()); + 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()); + } + + + /** + * Edit the given color as a HSV (Hue Saturation Value) color. + */ + private Color applySaturationAndBrightnessMultipliers(Color color, float saturationMultiplier, float brightnessMultiplier) + { + 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[2] * brightnessMultiplier, 1.0f)); + } + + + @Override + public int getBufferMemoryForSingleLod(int detailLevel) // TODO maybe multiply by how many would be per chunk? + { + // (sidesOnACube * pointsInASquare * (positionPoints + colorPoints))) * 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 new file mode 100644 index 000000000..3ea42d1b1 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java @@ -0,0 +1,51 @@ +/* + * 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.builders.lodNodeTemplates; + +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; + +import net.minecraft.client.renderer.BufferBuilder; + +/** + * TODO DynamicLodTemplate + * Chunks smoothly transition between + * each other, unless a neighboring chunk + * is at a significantly different height. + * + * @author James Seibel + * @version 06-16-2021 + */ +public class DynamicLodNodeTemplate extends AbstractLodNodeTemplate +{ + @Override + public void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodQuadTreeNode lod, + double xOffset, double yOffset, double zOffset, + boolean debugging) + { + System.err.println("DynamicLodTemplate not implemented!"); + } + + @Override + public int getBufferMemoryForSingleLod(int detailLevel) + { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java new file mode 100644 index 000000000..5c4562d31 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java @@ -0,0 +1,49 @@ +/* + * 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.builders.lodNodeTemplates; + +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; + +import net.minecraft.client.renderer.BufferBuilder; + +/** + * TODO #21 TriangularLodTemplate + * Builds each LOD chunk as a singular rectangular prism. + * + * @author James Seibel + * @version 06-16-2021 + */ +public class TriangularLodNodeTemplate extends AbstractLodNodeTemplate +{ + @Override + public void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodQuadTreeNode lod, + double xOffset, double yOffset, double zOffset, + boolean debugging) + { + System.err.println("DynamicLodTemplate not implemented!"); + } + + @Override + public int getBufferMemoryForSingleLod(int detailLevel) + { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/src/main/java/com/seibel/lod/builders/lodTemplates/AbstractLodTemplate.java b/src/main/java/com/seibel/lod/builders/lodTemplates/AbstractLodTemplate.java index 8478178d3..81cdf17d3 100644 --- a/src/main/java/com/seibel/lod/builders/lodTemplates/AbstractLodTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodTemplates/AbstractLodTemplate.java @@ -18,8 +18,8 @@ package com.seibel.lod.builders.lodTemplates; import com.seibel.lod.enums.LodDetail; -import com.seibel.lod.objects.LodChunk; -import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; import net.minecraft.client.renderer.BufferBuilder; @@ -32,10 +32,10 @@ import net.minecraft.client.renderer.BufferBuilder; */ public abstract class AbstractLodTemplate { - public abstract void addLodToBuffer(BufferBuilder buffer, - LodDimension lodDim, LodChunk lod, - double xOffset, double yOffset, double zOffset, - boolean debugging); + public abstract void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod, + double xOffset, double yOffset, double zOffset, + boolean debugging); /** add the given position and color to the buffer */ protected void addPosAndColor(BufferBuilder buffer, diff --git a/src/main/java/com/seibel/lod/builders/lodTemplates/CubicLodTemplate.java b/src/main/java/com/seibel/lod/builders/lodTemplates/CubicLodTemplate.java index 237b432f3..da26823c7 100644 --- a/src/main/java/com/seibel/lod/builders/lodTemplates/CubicLodTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodTemplates/CubicLodTemplate.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.LodChunk; -import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; import com.seibel.lod.util.LodUtil; import net.minecraft.client.renderer.BufferBuilder; @@ -46,8 +46,8 @@ public class CubicLodTemplate extends AbstractLodTemplate @Override public void addLodToBuffer(BufferBuilder buffer, - LodDimension lodDim, LodChunk centerLod, - double xOffset, double yOffset, double zOffset, + LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod, + double xOffset, double yOffset, double zOffset, boolean debugging) { AxisAlignedBB bbox; @@ -56,28 +56,18 @@ public class CubicLodTemplate extends AbstractLodTemplate // using the quality setting set by the config LodDetail detail = LodConfig.CLIENT.lodDetail.get(); - int halfWidth = detail.dataPointWidth / 2; + // returns null if the lod is empty at the given location + bbox = generateBoundingBox( + centerLod.lodDataPoint.height, + centerLod.lodDataPoint.depth, + detail.dataPointWidth, + xOffset - (centerLod.width / 2), + yOffset, + zOffset - (centerLod.width / 2)); - for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++) + if (bbox != null) { - int startX = detail.startX[i]; - int startZ = detail.startZ[i]; - int endX = detail.endX[i]; - int endZ = detail.endZ[i]; - - // returns null if the lod is empty at the given location - bbox = generateBoundingBox( - centerLod.getAverageHeightOverArea(startX, startZ, endX, endZ), - centerLod.getAverageDepthOverArea(startX, startZ, endX, endZ), - detail.dataPointWidth, - xOffset - (halfWidth / 2) + detail.startX[i], - yOffset, - zOffset - (halfWidth / 2) + detail.startZ[i]); - - if (bbox != null) - { - addBoundingBoxToBuffer(buffer, bbox, centerLod.getAverageColorOverArea(startX, startZ, endX, endZ, debugging)); - } + addBoundingBoxToBuffer(buffer, bbox, centerLod.lodDataPoint.color); } } diff --git a/src/main/java/com/seibel/lod/builders/lodTemplates/DynamicLodTemplate.java b/src/main/java/com/seibel/lod/builders/lodTemplates/DynamicLodTemplate.java index cd43c1c5c..81a6b15ce 100644 --- a/src/main/java/com/seibel/lod/builders/lodTemplates/DynamicLodTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodTemplates/DynamicLodTemplate.java @@ -18,8 +18,8 @@ package com.seibel.lod.builders.lodTemplates; import com.seibel.lod.enums.LodDetail; -import com.seibel.lod.objects.LodChunk; -import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; import net.minecraft.client.renderer.BufferBuilder; @@ -36,9 +36,9 @@ public class DynamicLodTemplate extends AbstractLodTemplate { @Override public void addLodToBuffer(BufferBuilder buffer, - LodDimension lodDim, LodChunk lod, - double xOffset, double yOffset, double zOffset, - boolean debugging) + LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod, + double xOffset, double yOffset, double zOffset, + boolean debugging) { System.err.println("DynamicLodTemplate not implemented!"); } diff --git a/src/main/java/com/seibel/lod/builders/lodTemplates/TriangularLodTemplate.java b/src/main/java/com/seibel/lod/builders/lodTemplates/TriangularLodTemplate.java index 4070d0a79..d95822666 100644 --- a/src/main/java/com/seibel/lod/builders/lodTemplates/TriangularLodTemplate.java +++ b/src/main/java/com/seibel/lod/builders/lodTemplates/TriangularLodTemplate.java @@ -18,8 +18,8 @@ package com.seibel.lod.builders.lodTemplates; import com.seibel.lod.enums.LodDetail; -import com.seibel.lod.objects.LodChunk; -import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; import net.minecraft.client.renderer.BufferBuilder; @@ -34,9 +34,9 @@ public class TriangularLodTemplate extends AbstractLodTemplate { @Override public void addLodToBuffer(BufferBuilder buffer, - LodDimension lodDim, LodChunk lod, - double xOffset, double yOffset, double zOffset, - boolean debugging) + LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod, + double xOffset, double yOffset, double zOffset, + boolean debugging) { System.err.println("DynamicLodTemplate not implemented!"); } diff --git a/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java b/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java index a72e76c72..80cf029f5 100644 --- a/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java +++ b/src/main/java/com/seibel/lod/builders/worldGeneration/LodChunkGenWorker.java @@ -34,7 +34,6 @@ import com.seibel.lod.handlers.LodConfig; import com.seibel.lod.objects.LodChunk; import com.seibel.lod.objects.LodDimension; import com.seibel.lod.objects.LodRegion; -import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.render.LodRenderer; import net.minecraft.block.Block; @@ -188,6 +187,9 @@ public class LodChunkGenWorker implements IWorker switch(LodConfig.CLIENT.distanceGenerationMode.get()) { + case NONE: + // don't generate + break; case BIOME_ONLY: case BIOME_ONLY_SIMULATE_HEIGHT: // fastest @@ -205,6 +207,8 @@ public class LodChunkGenWorker implements IWorker // very slow generateWithServer(); break; + default: + break; } lodRenderer.regenerateLODsNextFrame(); @@ -505,7 +509,8 @@ public class LodChunkGenWorker implements IWorker */ private void generateWithServer() { - lodChunkBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld); + throw new UnsupportedOperationException("Not Implemented"); + //lodChunkBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld); } diff --git a/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java b/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java new file mode 100644 index 000000000..b707f4c51 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java @@ -0,0 +1,645 @@ +/* + * 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.builders.worldGeneration; + +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +import com.seibel.lod.builders.LodBuilderConfig; +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.LodChunk; +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeNode; +import com.seibel.lod.objects.LodRegion; +import com.seibel.lod.render.LodNodeRenderer; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.util.WeightedList.Entry; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.palette.UpgradeData; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.BiomeContainer; +import net.minecraft.world.chunk.ChunkPrimer; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.IChunk; +import net.minecraft.world.gen.ChunkGenerator; +import net.minecraft.world.gen.Heightmap; +import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider; +import net.minecraft.world.gen.feature.BlockClusterFeatureConfig; +import net.minecraft.world.gen.feature.ConfiguredFeature; +import net.minecraft.world.gen.feature.DecoratedFeatureConfig; +import net.minecraft.world.gen.feature.FeatureSpread; +import net.minecraft.world.gen.feature.FeatureSpreadConfig; +import net.minecraft.world.gen.feature.IceAndSnowFeature; +import net.minecraft.world.gen.feature.NoFeatureConfig; +import net.minecraft.world.gen.placement.ConfiguredPlacement; +import net.minecraft.world.gen.placement.DecoratedPlacementConfig; +import net.minecraft.world.gen.placement.IPlacementConfig; +import net.minecraft.world.gen.placement.NoiseDependant; +import net.minecraft.world.server.ServerChunkProvider; +import net.minecraft.world.server.ServerWorld; +import net.minecraft.world.server.ServerWorldLightManager; +import net.minecraftforge.common.WorldWorkerManager.IWorker; + +/** + * This is used to generate a LodChunk at a given ChunkPos. + * + * @author James Seibel + * @version 7-4-2021 + */ +public class LodNodeGenWorker implements IWorker +{ + public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get()); + + 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<>(); + + + + public LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer, + LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder, + LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld, + BiomeContainer newBiomeContainer) + { + // 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) + throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodBufferBuilder"); + + if (newLodDimension == null) + throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension"); + + 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) + { + thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1); + + if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER) + { + // if we are using SERVER generation that has to be done + // synchronously to prevent crashing and harmful + // interactions with the normal world generator + thread.run(); + } + else + { + // Every other method can + // be done asynchronously + genThreads.execute(thread); + } + + threadStarted = true; + + // useful for debugging +// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods()); +// 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 lodChunkBuilder; + 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; + lodChunkBuilder = newLodBuilder; + lodBufferBuilder = newLodBufferBuilder; + lodDim = newLodDimension; + serverWorld = newServerWorld; + } + + @Override + public void run() + { + // only generate LodChunks if they can + // be added to the current LodDimension + if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE)) + { +// long startTime = System.currentTimeMillis(); + + switch(LodConfig.CLIENT.distanceGenerationMode.get()) + { + case 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.x, pos.z) != null) +// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); +// else +// ClientProxy.LOGGER.info(pos.x + " " + pos.z); + +// long endTime = System.currentTimeMillis(); +// System.out.println(endTime - startTime); + + }// if in range + + }// run + + + + /** + * takes about 2-5 ms + */ + private void generateUsingBiomesOnly() + { + List chunkList = new LinkedList<>(); + ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); + chunkList.add(chunk); + + 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); + ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + + + // generate fake height data for this LOD + int seaLevel = serverWorld.getSeaLevel(); + + boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT; + boolean inTheEnd = false; + + // add fake heightmap data so our LODs aren't at height 0 + Heightmap heightmap = new Heightmap(chunk, LodChunk.DEFAULT_HEIGHTMAP); + for(int x = 0; x < LodChunk.WIDTH && !inTheEnd; x++) + { + for(int z = 0; z < LodChunk.WIDTH && !inTheEnd; z++) + { + if (simulateHeight) + { + // TODO use the biomes around each block to smooth out the transition + + // these heights are of course aren't super accurate, + // they are just to simulate height data where there isn't any + switch(chunk.getBiomes().getNoiseBiome(x >> 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; + }// heightmap switch + } + else + { + // we aren't simulating height + // always use sea level + heightmap.setHeight(x, z, seaLevel); + } + }// z + }// x + + chunk.setHeightmap(LodChunk.DEFAULT_HEIGHTMAP, heightmap.getRawData()); + + + LodQuadTreeNode lod; + if (!inTheEnd) + { + lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + } + else + { + // if we are in the end, don't generate any chunks. + // Since we don't know where the islands are, everything + // generates the same and it looks really bad. + lod = new LodQuadTreeNode(LodQuadTreeNode.CHUNK_LEVEL,chunk.getPos().x, chunk.getPos().z); + } + lodDim.addNode(lod); + } + + + /** + * takes about 10 - 20 ms + */ + private void generateUsingSurface() + { + List chunkList = new LinkedList<>(); + ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); + chunkList.add(chunk); + LodServerWorld lodServerWorld = new LodServerWorld(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 + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + 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); + + LodQuadTreeNode lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + lodDim.addNode(lod); + } + + + /** + * takes about 15 - 20 ms + * + * Causes concurrentModification Exceptions, + * which could cause instability or world generation bugs + */ + private void generateUsingFeatures() + { + List chunkList = new LinkedList<>(); + ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); + chunkList.add(chunk); + LodServerWorld lodServerWorld = new LodServerWorld(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 + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + 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 < LodChunk.WIDTH; x++) + { + for (int z = 0; z < LodChunk.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 + // in unpredictable ways (specifically tree feature generation). + // When generating Features my CPU usage generally hovers around 30 - 40% + // when generating Jungles it spikes to 100%. + if (biome.getBiomeCategory() != Biome.Category.JUNGLE) + { + // should probably use the heightmap here instead of seaLevel, + // but this seems to get the job done well enough + biomes.add(biome); + } + } + } + + 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())) + continue; + + + try + { + configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition()); + } + catch(ConcurrentModificationException e) + { + // This will happen. I'm not sure what to do about it + // except pray that it doesn't effect the normal world generation + // in any harmful way. + // 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 + // and + // https://github.com/EsotericSoftware/kryo ) + + if (!allowUnstableFeatures) + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); +// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); + } + catch(UnsupportedOperationException e) + { + // This will happen when the LodServerWorld + // isn't able to return something that a feature + // generator needs + + if (!allowUnstableFeatures) + 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); +// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); + } + } + } + } + + // generate a Lod like normal + + LodQuadTreeNode lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + lodDim.addNode(lod); + } + + + /** + * on pre generated chunks 0 - 1 ms + * on un generated chunks 0 - 50 ms + * with the median seeming to hover around 15 - 30 ms + * and outliers in the 100 - 200 ms range + * + * Note this should not be multithreaded and does cause server/simulation lag + * (Higher lag for generating than loading) + */ + private void generateWithServer() { + //lodChunkBuilder.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) + { + DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config(); + placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer()); + } + else if(oldConfigClass == NoiseDependant.class) + { + NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config(); + placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise); + } + else + { +// 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); + + + BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer); + builder.whitelist(whitelist); + builder.blacklist(blacklist); + builder.xspread(config.xspread); + builder.yspread(config.yspread); + builder.zspread(config.zspread); + if(config.canReplace) { builder.canReplace(); } + 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() + { + if (genThreads != null && !genThreads.isShutdown()) + { + genThreads.shutdownNow(); + } + genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get()); + } + + + + + + /* + * performance/generation tests related to + * serverWorld.getChunk(x, z, ChunkStatus. *** ) + + true/false is whether they generated blocks or not + the time is how long it took to generate + + ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P) + ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks) + ChunkStatus.BIOMES 1 - 10 ms false (no height) + ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone) + ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass) + ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass) + ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass) + ChunkStatus.FEATURES 7 - 25 ms true + ChunkStatus.HEIGHTMAPS 20 - 40 ms true + ChunkStatus.LIGHT 20 - 40 ms true + ChunkStatus.FULL 30 - 50 ms true + ChunkStatus.SPAWN 50 - 80 ms true + + + At this point I would suggest using FEATURES, as it generates snow and trees + (and any other object that is needed to make biomes distinct) + + Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much) + */ +} diff --git a/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java b/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java index 0fe0dd122..c06253254 100644 --- a/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java +++ b/src/main/java/com/seibel/lod/enums/DistanceGenerationMode.java @@ -31,12 +31,15 @@ package com.seibel.lod.enums; */ public enum DistanceGenerationMode { + /** No generation has be used*/ + NONE(0), + /** Only generate the biomes and use biome * grass/foliage color, water color, or ice color * to generate the color. * Doesn't generate height, everything is shown at sea level. * Multithreaded - Fastest (2-5 ms) */ - BIOME_ONLY, + BIOME_ONLY(1), /** * Same as BIOME_ONLY, except instead @@ -44,25 +47,37 @@ public enum DistanceGenerationMode * different biome types (mountain, ocean, forest, etc.) * use predetermined heights to simulate having height data. */ - BIOME_ONLY_SIMULATE_HEIGHT, + BIOME_ONLY_SIMULATE_HEIGHT(2), /** Generate the world surface, * this does NOT include caves, trees, * or structures. * Multithreaded - Faster (10-20 ms) */ - SURFACE, + SURFACE(3), /** Generate everything except structures. * NOTE: This may cause world generation bugs or instability, * since some features cause concurrentModification exceptions. * Multithreaded - Fast (15-20 ms) */ - FEATURES, + FEATURES(4), /** Ask the server to generate/load each chunk. * This is the most compatible, but causes server/simulation lag. * This will also show player made structures if you * are adding the mod to a pre-existing world. * Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) */ - SERVER; - + SERVER(5); + + public final int complexity; + + DistanceGenerationMode(int complexity) { + this.complexity = complexity; + } + + + /* + public int compareTo(DistanceGenerationMode other){ + return Integer.compare(complexity, other.complexity); + ) + */ } diff --git a/src/main/java/com/seibel/lod/enums/LodTemplate.java b/src/main/java/com/seibel/lod/enums/LodTemplate.java index f45473522..790beecad 100644 --- a/src/main/java/com/seibel/lod/enums/LodTemplate.java +++ b/src/main/java/com/seibel/lod/enums/LodTemplate.java @@ -17,16 +17,16 @@ */ package com.seibel.lod.enums; -import com.seibel.lod.builders.lodTemplates.AbstractLodTemplate; -import com.seibel.lod.builders.lodTemplates.CubicLodTemplate; -import com.seibel.lod.builders.lodTemplates.DynamicLodTemplate; -import com.seibel.lod.builders.lodTemplates.TriangularLodTemplate; +import com.seibel.lod.builders.lodNodeTemplates.AbstractLodNodeTemplate; +import com.seibel.lod.builders.lodNodeTemplates.CubicLodNodeTemplate; +import com.seibel.lod.builders.lodNodeTemplates.DynamicLodNodeTemplate; +import com.seibel.lod.builders.lodNodeTemplates.TriangularLodNodeTemplate; /** * Cubic, Triangular, Dynamic * * @author James Seibel - * @version 06-16-2021 + * @version 8-4-2021 */ public enum LodTemplate { @@ -34,28 +34,28 @@ public enum LodTemplate /** Chunks are rendered as * rectangular prisms. */ - CUBIC(new CubicLodTemplate()), + CUBIC(new CubicLodNodeTemplate()), /** Chunks smoothly transition between * each other. */ - TRIANGULAR(new TriangularLodTemplate()), + TRIANGULAR(new TriangularLodNodeTemplate()), /** Chunks smoothly transition between * each other, unless a neighboring chunk * is at a significantly different height. */ - DYNAMIC(new DynamicLodTemplate()); + DYNAMIC(new DynamicLodNodeTemplate()); - public final AbstractLodTemplate template; + public final AbstractLodNodeTemplate template; - private LodTemplate(AbstractLodTemplate newTemplate) + private LodTemplate(AbstractLodNodeTemplate newTemplate) { template = newTemplate; } - public int getBufferMemoryForSingleLod(LodDetail detail) + public int getBufferMemoryForSingleLod(int detailLevel) { - return template.getBufferMemoryForSingleLod(detail); + return template.getBufferMemoryForSingleLod(detailLevel); } } diff --git a/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java b/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java new file mode 100644 index 000000000..59e239442 --- /dev/null +++ b/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java @@ -0,0 +1,357 @@ +/* + * 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.handlers; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +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.proxy.ClientProxy; + +/** + * This object handles creating LodRegions + * from files and saving LodRegion objects + * to file. + * + * @author James Seibel + * @version 6-27-2021 + */ +public class LodQuadTreeDimensionFileHandler { + /** This is what separates each piece of data */ + public static final char DATA_DELIMITER = ','; + + + private LodQuadTreeDimension loadedDimension = null; + public long regionLastWriteTime[][]; + + private File dimensionDataSaveFolder; + + /** lod */ + private final String FILE_NAME_PREFIX = "lod"; + /** .txt */ + private final String FILE_EXTENSION = ".txt"; + + /** 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(); + + + public LodQuadTreeDimensionFileHandler(File newSaveFolder, LodQuadTreeDimension newLoadedDimension) + { + if (newSaveFolder == null) + throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to."); + + dimensionDataSaveFolder = newSaveFolder; + + loadedDimension = newLoadedDimension; + // these two variable are used in sync with the LodDimension + regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()]; + for(int i = 0; i < loadedDimension.getWidth(); i++) + for(int j = 0; j < loadedDimension.getWidth(); j++) + regionLastWriteTime[i][j] = -1; + } + + + + + + //================// + // read from file // + //================// + + + + /** + * Return the LodQuadTree region at the given coordinates. + * (null if the file doesn't exist) + */ + public LodQuadTree loadRegionFromFile(int regionX, int regionZ) + { + + String fileName = getFileNameAndPathForRegion(regionX, regionZ); + + File f = new File(fileName); + + if (!f.exists()) + { + // there wasn't a file, don't + // return anything + return null; + } + + List dataList = new ArrayList<>(); + try + { + BufferedReader br = new BufferedReader(new FileReader(f)); + String s = br.readLine(); + int fileVersion = -1; + + if(s != null && !s.isEmpty()) + { + // try to get the file version + try + { + fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim()); + } + catch(NumberFormatException | StringIndexOutOfBoundsException e) + { + // this file doesn't have a version + // keep the version as -1 + fileVersion = -1; + } + + // check if this file can be read by this file handler + if(fileVersion < LOD_SAVE_FILE_VERSION) + { + // the file we are reading is an older version, + // close the reader and delete the file. + br.close(); + f.delete(); + ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion + + ", version requested: " + LOD_SAVE_FILE_VERSION + + " File was been deleted."); + + return null; + } + else if(fileVersion > LOD_SAVE_FILE_VERSION) + { + // the file we are reading is a newer version, + // close the reader and ignore the file, we don't + // want to accidently delete anything the user may want. + br.close(); + ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion + + ", version requested: " + LOD_SAVE_FILE_VERSION + + " this region will not be written to in order to protect the newer file."); + + return null; + } + } + else + { + // there is no data in this file + br.close(); + return null; + } + + + // this file is a readable version, begin reading the file + s = br.readLine(); + + while(s != null && !s.isEmpty()) + { + try + { + dataList.add(new LodQuadTreeNode(s)); + } + catch(IllegalArgumentException e) + { + // we were unable to create this chunk + // for whatever reason. + // skip to the next chunk + ClientProxy.LOGGER.warn(e.getMessage()); + } + + s = br.readLine(); + } + + br.close(); + } + catch (IOException e) + { + // the buffered reader encountered a + // problem reading the file + return null; + } + return new LodQuadTree(dataList,regionX, regionZ); + } + + + + + + + + + //==============// + // Save to File // + //==============// + + /** + * Save all dirty regions in this LodDimension to file. + */ + public void saveDirtyRegionsToFileAsync() + { + fileWritingThreadPool.execute(saveDirtyRegionsThread); + } + + private Thread saveDirtyRegionsThread = new Thread(() -> + { + for(int i = 0; i < loadedDimension.getWidth(); i++) + { + for(int j = 0; j < loadedDimension.getWidth(); j++) + { + if(loadedDimension.isRegionDirty[i][j] && loadedDimension.regions[i][j] != null) + { + saveRegionToDisk(loadedDimension.regions[i][j]); + loadedDimension.isRegionDirty[i][j] = false; + } + } + } + }); + + /** + * Save a specific region to disk.
+ * Note:
+ * 1. If a file already exists for a newer version + * the file won't be written.
+ * 2. This will save to the LodDimension that this + * handler is associated with. + */ + private void saveRegionToDisk(LodQuadTree region) + { + // convert chunk coordinates to region + // coordinates + int x = region.getLodNodeData().posX; + int z = region.getLodNodeData().posX; + + File f = new File(getFileNameAndPathForRegion(x, z)); + + try + { + // make sure the file and folder exists + if (!f.exists()) + { + // the file doesn't exist, + // create it and the folder if need be + if(!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + f.createNewFile(); + } + else + { + // the file exists, make sure it + // is the correct version. + // (to make sure we don't overwrite a newer + // version file if it exists) + + BufferedReader br = new BufferedReader(new FileReader(f)); + String s = br.readLine(); + int fileVersion = LOD_SAVE_FILE_VERSION; + + if(s != null && !s.isEmpty()) + { + // try to get the file version + try + { + fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim()); + } + catch(NumberFormatException | StringIndexOutOfBoundsException e) + { + // this file doesn't have a correctly formated version + // just overwrite the file + } + } + br.close(); + + // check if this file can be written to by the file handler + if(fileVersion <= LOD_SAVE_FILE_VERSION) + { + // we are good to continue and overwrite the old file + } + else //if(fileVersion > LOD_SAVE_FILE_VERSION) + { + // the file we are reading is a newer version, + // don't write anything, we don't want to accidently + // delete anything the user may want. + return; + } + } + + FileWriter fw = new FileWriter(f); + + // add the version of this file + fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n"); + + // add each LodChunk to the file + for (LodQuadTreeNode lodQuadTreeNode : Collections.unmodifiableList(region.getNodeList(LodQuadTreeDimension.FULL_COMPLEXITY_MASK , true, true))) + { + fw.write(lodQuadTreeNode.toData() + "\n"); + lodQuadTreeNode.dirty = false; + } + fw.close(); + } + catch(Exception e) + { + ClientProxy.LOGGER.error("LOD file write error: " + e.getMessage()); + } + } + + + + + + + + //================// + // helper methods // + //================// + + + /** + * Return the name of the file that should contain the + * region at the given x and z.
+ * Returns null if this object isn't ready to read and write.

+ * + * example: "lod.FULL.0.0.txt" + */ + private String getFileNameAndPathForRegion(int regionX, int regionZ) + { + try + { + // saveFolder is something like + // ".\Super Flat\DIM-1\data" + // or + // ".\Super Flat\data" + return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar + + FILE_NAME_PREFIX + regionX + "." + regionZ + FILE_EXTENSION; + } + catch(IOException e) + { + return null; + } + } + +} diff --git a/src/main/java/com/seibel/lod/objects/LodDataPoint.java b/src/main/java/com/seibel/lod/objects/LodDataPoint.java index cf69c7650..46d64a8ea 100644 --- a/src/main/java/com/seibel/lod/objects/LodDataPoint.java +++ b/src/main/java/com/seibel/lod/objects/LodDataPoint.java @@ -18,6 +18,7 @@ package com.seibel.lod.objects; import java.awt.Color; +import java.util.Objects; import com.seibel.lod.handlers.LodDimensionFileHandler; @@ -72,8 +73,17 @@ public class LodDataPoint depth = (short) newDepth; color = newColor; } - - + + public int hashCode(){ + return Objects.hash(this.height, this.depth, this.color); + } + + public boolean equals(LodDataPoint other){ + return (this.height == other.height + && this.depth == other.depth + && this.color == other.color); + } + /** * Outputs all data in a csv format * with the given delimiter. diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTree.java b/src/main/java/com/seibel/lod/objects/LodQuadTree.java new file mode 100644 index 000000000..4a0fbd3ab --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LodQuadTree.java @@ -0,0 +1,470 @@ +/* + * 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.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import com.seibel.lod.enums.DistanceGenerationMode; + +/** + * This object contains all data useful to render LodBlock in a region (32x32 chunk o 512x512 block) + * for every node it contains the border of the block, the size, the position at it's level, the color, the height and the depth. + */ +public class LodQuadTree { + //notes + //The term node correspond to a LodQuadTree object + + + /* + Example on how it will be rendered (the number correspond to the level in the LodNodeData) + .___.___._______._______________. + |6|6| 7 | | | + |6|6|___| 8 | | + | 7 | 7 | | | + |___|___|_______| 9 | + | | | | + | 8 | 8 | | + | | | | + |_______|_______|_______________| + | | | + | | | + | | | + | 9 | 9 | + | | | + | | | + | | | + |_______________|_______________| + */ + //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 + */ + + + //level completed is true if and only if all child are not null + private boolean nodeFull; + private boolean nodeEmpty; + + //the four child based on the four diagonal cardinal direction + //the first index is for N and S and the second index is for W and S + //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 regionX indicate the x region position of the node + * @param regionZ indicate the z 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(int regionX, int regionZ) { + this(null, new LodQuadTreeNode(LodQuadTreeNode.REGION_LEVEL, regionX, regionZ)); + } + + /** + * Constructor for generic level without LodNodeData + * + * @param parent parent of this node + * @param level level of this note + * @param posX position x in the level + * @param posZ position z in the level + */ + public LodQuadTree(LodQuadTree parent, byte level, int posX, int posZ) { + this(parent, new LodQuadTreeNode(level, posX, posZ)); + } + + /** + * Constructor for generic level via the LodNodeData + * + * @param lodNode object containing all the information of this node + */ + public LodQuadTree(LodQuadTree parent, LodQuadTreeNode lodNode) { + this.parent = parent; + this.lodNode = lodNode; + this.children = new LodQuadTree[2][2]; + this.nodeEmpty = true; + this.nodeFull = false; + } + + /** + * 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(LodQuadTreeNode.REGION_LEVEL, regionX, regionZ)); + this.setNodesAtLowerLevel(dataList, true); + } + + + /** + * @param dataList list of data to put in the node + * @param updateHigherLevel will update the color and height of higher level only if true + */ + public void setNodesAtLowerLevel(List dataList, boolean updateHigherLevel) { + for (LodQuadTreeNode lodQuadTreeNode : dataList) { + //this is slow, you could set update to false and use an only top down update method. + this.setNodeAtLowerLevel(lodQuadTreeNode, updateHigherLevel); + } + } + + /** + * @param newLodNode data to put in the node + * @param updateHigherLevel will update the color and height of higher level only if true + * @return true only if the QuadTree has been changed + */ + public boolean setNodeAtLowerLevel(LodQuadTreeNode newLodNode, boolean updateHigherLevel) { + //check if we try to introduce a level that is higher or equal than the current one + byte targetLevel = newLodNode.detailLevel; + byte currentLevel = lodNode.detailLevel; + if (targetLevel < currentLevel) { + int posX = newLodNode.posX; + int posZ = newLodNode.posZ; + short widthRatio = (short) (lodNode.width / (2 * newLodNode.width)); + int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2); + int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2); + if (getChild(NS, WE) == null) { + setChild(NS, WE); + } + LodQuadTree child = getChild(NS, WE); + if (lodNode.compareComplexity(newLodNode) > 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) { + child.setLodNodeData(newLodNode, true); + return true; + } else { + return child.setNodeAtLowerLevel(newLodNode, updateHigherLevel); + } + } + } else { + return false; + } + + } + + /** + * @param posX + * @param posZ + * @param targetLevel + * @return + */ + public LodQuadTreeNode getNodeAtLevelPosition(int posX, int posZ, int targetLevel) + { + if (targetLevel > LodQuadTreeNode.REGION_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + targetLevel + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max."); + + byte currentLevel = lodNode.detailLevel; + if (targetLevel == currentLevel) + { + return lodNode; + } + else if (targetLevel < currentLevel) + { + short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, targetLevel))); + 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.getNodeAtLevelPosition(posX, posZ, targetLevel); + } + else + { + return null; + } + + } + + + public LodQuadTree getChild(int NS, int WE) { + return children[NS][WE]; + } + + /** + * setChild will put a child with given data in the given position + * + * @param newLodNode data to put in the child + * @param NS North-South position + * @param WE West-East position + */ + public void setChild(LodQuadTreeNode newLodNode, int NS, int WE) { + if (newLodNode.detailLevel == lodNode.detailLevel - 1) { + children[NS][WE] = new LodQuadTree(this, lodNode); + } + } + + /** + * setChild will put a child with given data in the given position + * + * @param newLodNode data to put in the child + */ + public void setChild(LodQuadTreeNode newLodNode) { + 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); + } + } + + /** + * setChild will put a child in the given position + * + * @param NS North-South position + * @param WE West-East position + */ + public void setChild(int NS, int WE) { + int childX = lodNode.posX * 2 + WE; + int childZ = lodNode.posZ * 2 + NS; + children[NS][WE] = new LodQuadTree(this, (byte) (lodNode.detailLevel - 1), childX, childZ); + } + + /** + * Update level update the level data such as levelFull and lodNodeData. + * + * @param recursiveUpdate if recursive is true the update will rise up to the level 0 + */ + private void updateLevel(boolean recursiveUpdate) { + boolean isFull = true; + boolean isEmpty = true; + 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; + } + } + } + nodeFull = isFull; + nodeEmpty = isEmpty; + lodNode.combineData(dataList); + if (lodNode.detailLevel < 9 && recursiveUpdate) { + this.parent.updateLevel(true); + } + } + + /** + * + * method to get certain nodes from the LodQuadTree + * + * @param complexityMask set of complexity 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 getNodeList(Set complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf) { + List nodeList = new ArrayList<>(); + if (isThereAnyChild()) { + //There is at least 1 child + if (!getOnlyLeaf + && !(getOnlyDirty && !lodNode.isDirty()) + && complexityMask.contains(lodNode.getComplexity())) { + nodeList.add(lodNode); + } + 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.getNodeList(complexityMask, getOnlyDirty, getOnlyLeaf)); + } + } + } + } else { + //There are no children + if (!(getOnlyDirty && !lodNode.isDirty()) + && (complexityMask.contains(lodNode.getComplexity()))){ + nodeList.add(lodNode); + } + } + + return nodeList; + } + + /** + * This method will return all the nodes that can be rendered based on the data given + * + * @param x position of the player + * @param z 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(int x, int z, int targetLevel, Set complexityMask, int maxDistance, int minDistance) + { + List distances = new ArrayList<>(); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2))); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2))); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2))); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 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) /*|| isCoordinateInLevel(x, z)*/)) { + if (targetLevel == lodNode.detailLevel || !isNodeFull()) { + if (!lodNode.isVoidNode() && complexityMask.contains(lodNode.getComplexity())) { + nodeList.add(lodNode); + } + } else { + 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(x, z, targetLevel, complexityMask, maxDistance, minDistance)); + } + } + } + } + } + return nodeList; + } + + + /** + * Nodes that can be generated in the approximated version + * A level is generated only if it has child and is higher than the target level and in the distance range + * @param x + * @param z + * @param targetLevel + * @param complexityToGenerate + * @param maxDistance + * @param minDistance + * @return + */ + public List> getNodesToGenerate(int x, int z, byte targetLevel, DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) { + + List distances = new ArrayList<>(); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2))); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2))); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2))); + distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 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) || isCoordinateInLevel(x, z))) { + if(!isThereAnyChild() || targetLevel == lodNode.detailLevel){ + if (this.lodNode.getComplexity().compareTo(complexityToGenerate) <= 0 ) { + nodeList.add(new AbstractMap.SimpleEntry(this.lodNode, min)); + } + }else { + 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(x, z, targetLevel, complexityToGenerate, maxDistance, minDistance)); + } + } + } + } + return nodeList; + } + + + /** + * simple getter for lodNodeData + * + * @return lodNodeData + */ + public LodQuadTreeNode getLodNodeData() { + return lodNode; + } + + /** + * setter for lodNodeData, to maintain a correct relationship between level this method force update on all parent + * + * @param newLodQuadTreeNode data to set + * @param updateHigherLevel if true it will update all the upper levels. + */ + public void setLodNodeData(LodQuadTreeNode newLodQuadTreeNode, boolean updateHigherLevel) { + if (this.lodNode == null) { + this.lodNode = newLodQuadTreeNode; + } else { + this.lodNode.update(newLodQuadTreeNode); + } + //a recursive update is necessary to change higher level + if (parent != null && updateHigherLevel) parent.updateLevel(true); + } + + public boolean isNodeFull() { + return nodeFull; + } + + public boolean isThereAnyChild() { + return !nodeEmpty; + } + + public boolean isRenderable() { + return (lodNode != null); + } + + + public boolean isCoordinateInLevel(int x, int z){ + return (lodNode.getStartX() * lodNode.width <= x && lodNode.getStartZ() * lodNode.width <= z && lodNode.getEndX() * lodNode.width >= x && lodNode.getEndZ() * lodNode.width >= z); + } + + @Override + public String toString(){ + String s = lodNode.toString(); + return s; + /* + if(isThereAnyChild()){ + 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 new file mode 100644 index 000000000..dce794919 --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java @@ -0,0 +1,572 @@ +/* + * 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.ChunkPos; +import net.minecraft.world.DimensionType; +import net.minecraft.world.server.ServerChunkProvider; +import net.minecraft.world.server.ServerWorld; + +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; + + private volatile int width; + private volatile int halfWidth; + public long seed; + + 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 int centerX; + private volatile int centerZ; + + private LodQuadTreeDimensionFileHandler fileHandler; + + + + + public LodQuadTreeDimension(DimensionType newDimension, LodQuadTreeWorld lodWorld, int newMaxWidth) + { + dimension = newDimension; + width = newMaxWidth; + if(newDimension != null && lodWorld != null) { + try { + Minecraft mc = Minecraft.getInstance(); + + File saveDir; + if (mc.hasSingleplayerServer()) { + // local world + + ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension); + seed = serverWorld.getSeed(); + // 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; + + centerX = 0; + centerZ = 0; + + halfWidth = (int)Math.floor(width / 2); + } + + + /** + * Move the center of this LodDimension and move all owned + * regions over by the given x and z offset. + */ + public synchronized void move(int xOffset, int zOffset) + { + // 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 + centerX += xOffset; + centerZ += 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 + centerX += xOffset; + centerZ += 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(int regionX, int regionZ) + { + int xIndex = (regionX - centerX) + halfWidth; + int zIndex = (regionZ - centerZ) + halfWidth; + + if (!regionIsInRange(regionX, regionZ)) + // out of range + return null; + + if (regions[xIndex][zIndex] == null) + { + regions[xIndex][zIndex] = getRegionFromFile(regionX, regionZ); + if (regions[xIndex][zIndex] == null) + { + regions[xIndex][zIndex] = new LodQuadTree(regionX, regionZ); + } + } + + 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 - centerX) + halfWidth; + int zIndex = (centerZ - newRegion.getLodNodeData().posZ) + halfWidth; + + if (!regionIsInRange(newRegion.getLodNodeData().posX, newRegion.getLodNodeData().posZ)) + // out of range + throw new ArrayIndexOutOfBoundsException(); + + regions[xIndex][zIndex] = newRegion; + } + + + /** + *this method create all the regions that are null + */ + public void initializeNullRegions(){ + int n = regions.length; + int xIndex; + int zIndex; + LodQuadTree region; + for(int xRegion=0; xRegion + * 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 > LodQuadTreeNode.REGION_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max."); + + LodQuadTree region = getRegion( + (Math.floorDiv(posX, (int) (LodQuadTreeNode.REGION_WIDTH/Math.pow(detailLevel,2)))), + (Math.floorDiv(posZ, (int) (LodQuadTreeNode.REGION_WIDTH/Math.pow(detailLevel,2))))); + + if(region == null) + { + //System.out.println("THIS CASE"); + return null; + } + + return region.getNodeAtLevelPosition(posX, posZ, detailLevel); + + /* + RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(chunkX, chunkZ)); + + LodQuadTree region = getRegion(pos.x, pos.z); + + return region.getNode(chunkX, chunkZ); + */ + } + + /** + * return true if and only if the node at that position exist + */ + public boolean hasThisPositionBeenGenerated(int posX, int posZ, int level) + { + if (level > LodQuadTreeNode.REGION_LEVEL) + throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + level + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max."); + + return getLodFromCoordinates(posX,posZ,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 getNodeToRender(int x, int z, int level, Set complexityMask, int maxDistance, int minDistance) + { + int n = regions.length; + List listOfData = new ArrayList<>(); + for(int i=0; i getNodesToGenerate(int x, int z, byte level, DistanceGenerationMode complexity, int maxDistance, int minDistance) + { + int n = regions.length; + int xIndex; + int zIndex; + LodQuadTree region; + List> listOfQuadTree = new ArrayList<>(); + for(int xRegion=0; xRegion entry.getKey()).collect(Collectors.toList()); + } + + /** + * getNodes + * @return list of quadTrees + */ + public List getNodes(Set complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf){ + int n = regions.length; + List listOfNodes = new ArrayList<>(); + int xIndex; + int zIndex; + LodQuadTree region; + for(int xRegion=0; xRegion= 0 && xIndex < width && zIndex >= 0 && zIndex < width; + } + + + + + + + + public int getCenterX() + { + return centerX; + } + + public int getCenterZ() + { + return centerZ; + } + + + /** + * TODO THIS METHOD HAVE TO BE CHANGES. IS NOT THE SAME AS NUMER OF CHUNK + * Is it good now? - James + * + * 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.getNodeList(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; + + 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 += "(" + centerX + "," + centerZ + ")"; + + return s; + } +} diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java new file mode 100644 index 000000000..048a21cdf --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java @@ -0,0 +1,424 @@ +/* + * 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.util.List; +import java.util.Objects; + +import com.seibel.lod.enums.DistanceGenerationMode; +import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler; + +import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.gen.Heightmap; + +public class LodQuadTreeNode +{ + /** This is what separates each piece of data in the toData method */ + private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER; + + /** alpha used when drawing chunks in debug mode */ + private static final int DEBUG_ALPHA = 255; // 0 - 255 + @SuppressWarnings("unused") + private static final Color DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA); + @SuppressWarnings("unused") + private static final Color DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA); + @SuppressWarnings("unused") + private static final Color INVISIBLE = new Color(0,0,0,0); + + + /** If we ever have to use a heightmap for any reason, use this one. */ + public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG; + + + /** If this is set to true then toData will return + * the empty string */ + public boolean dontSave = false; + + + + /** this is how many pieces of data are exported when toData is called */ + public static final int NUMBER_OF_DELIMITERS = 10; + + + //Complexity indicate how the block was built. This is important because we could use + public DistanceGenerationMode complexity; + + //level height goes from 0 to 9 with 0 the deepest (block size) and 9 the highest (region size) + public final byte detailLevel; + public static final byte REGION_LEVEL = 9; //at level 9 we reach the dimension of a single region + public static final byte CHUNK_LEVEL = 4; //at level 4 we reach the dimension of a single chunk + public static final byte BLOCK_LEVEL = 0; //at level 0 we reach the dimension of a single block + + //indicate the width in block of this node (goes from 1 to 512) + public final short width; + public static final short REGION_WIDTH = 512; //at level 9 we reach the dimension of a single region + public static final short CHUNK_WIDTH = 16; //at level 4 we reach the dimension of a single chunk + public static final short BLOCK_WIDTH = 1; //at level 0 we reach the dimension of a single block + + //this 2 values indicate the position of the LOD in the relative Level + //this will be useful in the generation process + public final int posX; + public final int posZ; + + //these 4 value indicate the corner of the LOD block + //they can be named SW, SE, NW, NE as the cardinal direction. + //the start values should always be smaller than the end values. + //All this value could be calculated from level, posx and posz + //so they could be removed and replaced with just a getter + public final int startX; + public final int startZ; + public final int endX; + public final int endZ; + //these 2 value indicate the center of the LodNode in real coordinate. This + //can be used to calculate the distance from the player + public final int centerX; + public final int centerZ; + + public LodDataPoint lodDataPoint; + + //void node is used + 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(ChunkPos pos) + { + this(CHUNK_LEVEL, pos.x, pos.z); + } + + /** + * 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); + + startX = posX * width; + startZ = posZ * width; + endX = startX + width - 1; + endZ = startZ + width - 1; + + centerX = startX + width/2; + centerZ = startZ + width/2; + + 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); + + startX = posX * width; + startZ = posZ * width; + + endX = startX + width - 1; + endZ = startZ + width - 1; + centerX = startX + width/2; + centerZ = startZ + width/2; + + this.lodDataPoint = lodDataPoint; + this.complexity = complexity; + + dirty = true; + voidNode = false; + dontSave = false; + } + + public LodQuadTreeNode(String data) + { + 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 r = Integer.parseInt(data.substring(lastIndex+1,index)); + lastIndex = index; + index = data.indexOf(DATA_DELIMITER, lastIndex+1); + int g = Integer.parseInt(data.substring(lastIndex+1,index)); + lastIndex = index; + index = data.indexOf(DATA_DELIMITER, lastIndex+1); + int b = Integer.parseInt(data.substring(lastIndex+1,index)); + lastIndex = index; + index = data.indexOf(DATA_DELIMITER, lastIndex+1); + int a = Integer.parseInt(data.substring(lastIndex+1,index)); + Color color = new Color(r,g,b,a); + lodDataPoint = new LodDataPoint(height,depth,color); + + int val = Integer.parseInt(data.substring(lastIndex+1,index)); + this.voidNode = (val == 1); + + + width = (short) Math.pow(2, detailLevel); + + startX = posX * width; + startZ = posZ * width; + endX = startX + width - 1; + endZ = startZ + width - 1; + + centerX = startX + width/2; + centerZ = startZ + width/2; + + dirty = false; + dontSave = false; + } + + public void update(LodQuadTreeNode lodQuadTreeNode){ + this.lodDataPoint = lodQuadTreeNode.lodDataPoint; + this.complexity = lodQuadTreeNode.complexity; + this.voidNode = lodQuadTreeNode.voidNode; + dirty = true; + dontSave = false; + } + + public LodDataPoint getLodDataPoint(){ + return lodDataPoint; + } + + public void combineData(List dataList){ + if(dataList.isEmpty()){ + lodDataPoint = new LodDataPoint(); + }else { + short height = (short) dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().height).min().getAsInt(); + short depth = (short) dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().depth).max().getAsInt(); + 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 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 = dataList.stream().filter(x -> !x.voidNode).count() == 0; + } + dirty = true; + dontSave = false; + } + + + @Override + public int hashCode(){ + return Objects.hash(this.complexity, this.detailLevel, this.posX, this.posZ, this.lodDataPoint, this.voidNode); + } + + 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); + } + + + /** + * 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; + int val = voidNode ? 1 : 0; + s += Integer.toString(val) + DATA_DELIMITER; + return s; + } + + + + @Override + public String toString() + { + return this.toData(); + } + + + // These getters should be used + + public byte getDetailLevel() { + return detailLevel; + } + + public DistanceGenerationMode getComplexity() { + return complexity; + } + + public short getWidth() { + return width; + } + + public int getPosX() { + return posX; + } + + public int getPosZ() { + return posZ; + } + + public int getStartX() { + return startX; + } + + public int getStartZ() { + return startZ; + } + + public int getEndX() { + return endX; + } + + public int getEndZ() { + return endZ; + } + + public int getCenterX() { + return centerX; + } + + public int getCenterZ() { + return centerZ; + } + + public boolean isVoidNode() { + return voidNode; + } + + public boolean isDirty() { + return dirty; + } +} diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeWorld.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeWorld.java new file mode 100644 index 000000000..3167e2918 --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LodQuadTreeWorld.java @@ -0,0 +1,117 @@ +/* + * 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.util.Hashtable; +import java.util.Map; + +import net.minecraft.world.DimensionType; + +/** + * This stores all LODs for a given world. + * + */ +public class LodQuadTreeWorld +{ + + private String worldName; + + private Map lodDimensions; + /** If true then the LOD world is setup and ready to use */ + private boolean isWorldLoaded = false; + + public static final String NO_WORLD_LOADED = "No world loaded"; + + public LodQuadTreeWorld() { + worldName = NO_WORLD_LOADED; + } + + /** + * Set up the LodWorld with the given newWorldName.
+ * This should be done whenever loading a new world. + * @param newWorldName name of the world + */ + public void selectWorld(String newWorldName) { + if (newWorldName.isEmpty()) { + deselectWorld(); + return; + } + + if (worldName.equals(newWorldName)) + // don't recreate everything if we + // didn't actually change worlds + return; + + worldName = newWorldName; + lodDimensions = new Hashtable(); + isWorldLoaded = true; + } + + /** + * Set the worldName to "No world loaded" + * and clear the lodDimensions Map.
+ * This should be done whenever unloaded a world. + */ + public void deselectWorld() { + worldName = NO_WORLD_LOADED; + lodDimensions = null; + isWorldLoaded = false; + } + + + public void addLodDimension(LodQuadTreeDimension newStorage) { + if (lodDimensions == null) + throw new IllegalStateException("LodWorld hasn't been given a world yet."); + + lodDimensions.put(newStorage.dimension, newStorage); + } + + public LodQuadTreeDimension getLodDimension(DimensionType dimension) { + if (lodDimensions == null) { + throw new IllegalStateException("LodWorld hasn't been given a world yet."); + } + return lodDimensions.get(dimension); + } + + /** + * Resizes the max width in regions that each LodDimension + * should use. + */ + public void resizeDimensionRegionWidth(int newWidth) { + if (lodDimensions == null) + throw new IllegalStateException("LodWorld hasn't been given a world yet."); + + for (DimensionType key : lodDimensions.keySet()) + lodDimensions.get(key).setRegionWidth(newWidth); + } + + + public boolean getIsWorldLoaded() { + return isWorldLoaded; + } + + public String getWorldName() { + return worldName; + } + + @Override + public String toString() { + return "World name: " + worldName; + } +} + diff --git a/src/main/java/com/seibel/lod/objects/QuadTreeImage.java b/src/main/java/com/seibel/lod/objects/QuadTreeImage.java new file mode 100644 index 000000000..8d61f2449 --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/QuadTreeImage.java @@ -0,0 +1,354 @@ +/* + * 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.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +import com.seibel.lod.enums.DistanceGenerationMode; +import com.seibel.lod.util.BiomeColorsUtils; + +import kaptainwutax.biomeutils.source.OverworldBiomeSource; +import kaptainwutax.mcutils.version.MCVersion; + +@SuppressWarnings("serial") +public class QuadTreeImage extends JPanel +{ + private static final int PREF_W = 1024; + private static final int PREF_H = PREF_W; + private List drawables = new ArrayList<>(); + + public QuadTreeImage() { + setBackground(Color.white); + } + + public void addMyDrawable(MyDrawable myDrawable) { + drawables.add(myDrawable); + repaint(); + } + + @Override + // make it bigger + public Dimension getPreferredSize() { + if (isPreferredSizeSet()) { + return super.getPreferredSize(); + } + return new Dimension(PREF_W, PREF_H); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + for (MyDrawable myDrawable : drawables) { + myDrawable.draw(g2); + } + } + + public void clearAll() { + drawables.clear(); + repaint(); + } + + private static void createAndShowGui() { + + final QuadTreeImage quadTreeImage = new QuadTreeImage(); + + + JFrame frame = new JFrame("DrawChit"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.getContentPane().add(quadTreeImage); + frame.pack(); + frame.setLocationByPlatform(true); + frame.setVisible(true); + List> listOfList = new ArrayList<>(); + OverworldBiomeSource biomeSource = new OverworldBiomeSource(MCVersion.v1_16_5, 1000); + //EndBiomeSource biomeSource = new EndBiomeSource(MCVersion.v1_16_5, 1000); + int sizeOfTheWorld = 32; + + LodQuadTreeDimension dim = new LodQuadTreeDimension(null, null, sizeOfTheWorld); + + //SIMULATING A PLAYER MOVING, + int[] playerXs = {0, 100, 200, 300, 400, 1000}; + int[] playerZs = {0, 100, 200, 300, 400, 500}; + for (int pos = 0; pos < 1; pos++) { + int playerX = 0 + playerXs[pos]; //2097152 + int playerZ = 0 + playerZs[pos]/2; + + //int sizeOfTheWorld=512; //TRY THIS TO SEE A 250'000 BLOCK RENDER DISTANCE + dim.move(Math.floorDiv(playerX, 512), Math.floorDiv(playerZ, 512)); +/* + System.out.println(dim.getRegion(0, 0)); + System.out.println(dim.getCenterX()); + System.out.println(dim.getCenterZ()); + System.out.println(dim.getWidth()); + + System.out.println("GETTING LOD FROM COORDINATE BEFORE GENERETION"); + System.out.println(dim.getLodFromCoordinates(-6, -6)); +*/ + + DistanceGenerationMode[] complexities = {DistanceGenerationMode.BIOME_ONLY, DistanceGenerationMode.BIOME_ONLY, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT, DistanceGenerationMode.SURFACE, DistanceGenerationMode.SURFACE, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES}; + int[] distances = {1000000, 8000, 4000, 2000, 1000, 500, 250, 100, 50, 25}; + for (int i = 0; i <= (9-2); i++) { + List levelToGenerate = dim.getNodesToGenerate(playerX, playerZ, (byte) (9 - i), complexities[i], distances[i]*2, 0); + //System.out.println(levelToGenerate); + for (LodQuadTreeNode node : levelToGenerate) { + Color color; + int startX = node.startX; + int startZ = node.startZ; + int endX = node.endX; + int endZ = node.endZ; + int centerX = node.centerX; + int centerZ = node.centerZ; + int width = node.width; + byte otherLevel = LodQuadTreeNode.BLOCK_LEVEL; + int otherWidth = LodQuadTreeNode.BLOCK_WIDTH; + + List posXs = new ArrayList<>(); + List posZs = new ArrayList<>(); + posXs.add(Math.floorDiv(startX, otherWidth)); + posXs.add(Math.floorDiv(centerX, otherWidth)); + posZs.add(Math.floorDiv(startZ, otherWidth)); + posZs.add(Math.floorDiv(centerZ, otherWidth)); + + for (Integer posXI : posXs) { + for (Integer posZI : posZs) { + int posX = posXI.intValue(); + int posZ = posZI.intValue(); + color = BiomeColorsUtils.getColorFromBiomeManual(biomeSource.getBiome(posX, 0, posZ)); + LodQuadTreeNode newNode = new LodQuadTreeNode(otherLevel, posX, posZ, new LodDataPoint(0, 0, color), complexities[i]); + if (dim.addNode(newNode)) { + } + } + } + } + + //Set complexityMask = new HashSet<>(); + //complexityMask.add(DistanceGenerationMode.SERVER); + //complexityMask.add(DistanceGenerationMode.FEATURES); + //complexityMask.add(DistanceGenerationMode.SURFACE); + //complexityMask.add(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT); + //complexityMask.add(DistanceGenerationMode.BIOME_ONLY); + Set complexityMask = LodQuadTreeDimension.FULL_COMPLEXITY_MASK; + + List lodList = new ArrayList<>(); + //The min and max distances should increase quadratically + //int[] distances2 = {100000, 8000, 4000, 2000, 1000, 500, 250, 0}; + int[] distances2 = {0, 250, 500, 1000, 2000, 4000, 8000, 100000}; + for (int h = 0; h <= (9 - 3); h++) { + lodList.addAll(dim.getNodeToRender(playerX, playerZ, (byte) (3+h), complexityMask, distances2[h+1], distances2[h])); + } + System.out.println("Number of node to render " + lodList.size()); + listOfList.add(lodList); + System.out.println("Number of list " + listOfList.size()); + /* + List lodList = dim.getNodes(complexityMask, false, false); //USE THIS TO SEE AL THE LODS + listOfList.add(lodList); + */ + + } + + } + System.out.println("GETTING LOD FROM COORDINATE AFTER GENERETION"); + System.out.println(dim.getLodFromCoordinates(0, 100, (byte) 1)); + //FROM THIS POINT ON THE CODE JUST CREATE THE IMAGE + + int timerDelay = 1000; + System.out.println("STARTING"); + System.out.println(dim.getWidth()); + System.out.println(dim.getCenterX()); + int xOffset = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startX).min().getAsInt()).min().getAsInt(); + int zOffset = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startZ).min().getAsInt()).min().getAsInt(); + int maxX = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startX).max().getAsInt()).min().getAsInt(); + int maxZ = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startZ).max().getAsInt()).min().getAsInt(); + int maxSize = Math.max(maxX - xOffset, maxZ - zOffset) / 512; + System.out.println(xOffset); + System.out.println(zOffset); + new Timer(timerDelay, new ActionListener() { + private int drawCount = 0; + + @Override + public void actionPerformed(ActionEvent e) { + if (drawCount >= listOfList.size()) { + drawCount = 0; + } else { + if (drawCount == 0) quadTreeImage.clearAll(); + final List myDrawables = new ArrayList<>(); + double amp = ((double) 2) / ((double) sizeOfTheWorld); + Collection lodList = listOfList.get(drawCount); + for (LodQuadTreeNode data : lodList) { + Color colorOfComplexity = Color.black; + switch (data.complexity){ + case NONE: + colorOfComplexity = Color.black; + break; + case BIOME_ONLY: + colorOfComplexity = Color.red; + break; + case BIOME_ONLY_SIMULATE_HEIGHT: + colorOfComplexity = Color.yellow; + break; + case SURFACE: + colorOfComplexity = Color.blue; + break; + case FEATURES: + colorOfComplexity = Color.cyan; + break; + case SERVER: + colorOfComplexity = Color.green; + break; + } + myDrawables.add(new MyDrawable(new Rectangle2D.Double( + ((data.startX - xOffset) * amp), + ((data.startZ - zOffset) * amp), + data.width * amp, + data.width * amp), + data.lodDataPoint.color, new BasicStroke(1))); + } + myDrawables.add(new MyDrawable(new Rectangle2D.Double( + (playerXs[0] - xOffset) * amp, + (playerZs[0] - zOffset) * amp, + 20, + 20), + Color.yellow, new BasicStroke(1))); + for (int k = 0; k < myDrawables.size(); k++) { + quadTreeImage.addMyDrawable(myDrawables.get(k)); + } + /* + BufferedImage img = new BufferedImage(frame.getWidth(), frame.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = img.createGraphics(); + frame.printAll(g2d); + g2d.dispose(); + try { + ImageIO.write(img, "png", new File("ImgEnd" + drawCount + ".png")); + } catch (IOException ioException) { + ioException.printStackTrace(); + } + */ + drawCount++; + } + } + }).start(); + } + + public static void main(String[] args) { + /* + LodQuadTreeDimension dim2 = new LodQuadTreeDimension(null, null, 1); + dim2.move(10000000,10000000); + List levelToGenerate = dim2.getNodesToGenerate(10000000, 10000000, (byte) 7,DistanceGenerationMode.SERVER, (int) 10000, 0); + System.out.println(levelToGenerate); + dim2.addNode(new LodQuadTreeNode((byte) 0,0,0,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER)); + dim2.addNode(new LodQuadTreeNode((byte) 0,256,0,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER)); + dim2.addNode(new LodQuadTreeNode((byte) 0,0,256,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER)); + dim2.addNode(new LodQuadTreeNode((byte) 0,256,256,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER)); + levelToGenerate = dim2.getNodesToGenerate(10000000, 10000000, (byte) 7,DistanceGenerationMode.SERVER, (int) 10000, 0); + System.out.println(levelToGenerate); + + */ + /* + System.out.println(DistanceGenerationMode.SERVER.compareTo(DistanceGenerationMode.SERVER)); + System.out.println(DistanceGenerationMode.NONE.compareTo(DistanceGenerationMode.SERVER)); + System.out.println(DistanceGenerationMode.SERVER.compareTo(DistanceGenerationMode.NONE)); + System.out.println(DistanceGenerationMode.BIOME_ONLY.compareTo(DistanceGenerationMode.SURFACE)); + System.out.println(DistanceGenerationMode.SURFACE.compareTo(DistanceGenerationMode.BIOME_ONLY)); + System.out.println(DistanceGenerationMode.BIOME_ONLY.compareTo(DistanceGenerationMode.BIOME_ONLY)); + System.out.println(DistanceGenerationMode.BIOME_ONLY.compareTo(DistanceGenerationMode.NONE)); + System.out.println(DistanceGenerationMode.NONE.compareTo(DistanceGenerationMode.BIOME_ONLY)); + + */ + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + createAndShowGui(); + } + }); + + + + } +} + +class MyDrawable { + private Shape shape; + private Color color; + private Stroke stroke; + + public MyDrawable(Shape shape, Color color, Stroke stroke) { + this.shape = shape; + this.color = color; + this.stroke = stroke; + } + + public Shape getShape() { + return shape; + } + + public Color getColor() { + return color; + } + + public Stroke getStroke() { + return stroke; + } + + public void draw(Graphics2D g2) { + Color oldColor = g2.getColor(); + Stroke oldStroke = g2.getStroke(); + + g2.setColor(color); + g2.fill(shape); + //g2.setStroke(stroke); + g2.draw(shape); + + g2.setColor(oldColor); + g2.setStroke(oldStroke); + } + + public void fill(Graphics2D g2) { + Color oldColor = g2.getColor(); + Stroke oldStroke = g2.getStroke(); + + g2.setColor(color); + g2.setStroke(stroke); + g2.fill(shape); + + g2.setColor(oldColor); + g2.setStroke(oldStroke); + } + +} diff --git a/src/main/java/com/seibel/lod/proxy/ClientProxy.java b/src/main/java/com/seibel/lod/proxy/ClientProxy.java index 88a617ea7..1900f8a28 100644 --- a/src/main/java/com/seibel/lod/proxy/ClientProxy.java +++ b/src/main/java/com/seibel/lod/proxy/ClientProxy.java @@ -20,17 +20,18 @@ package com.seibel.lod.proxy; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import com.seibel.lod.builders.LodBufferBuilder; -import com.seibel.lod.builders.LodChunkBuilder; +import com.seibel.lod.builders.LodNodeBufferBuilder; +import com.seibel.lod.builders.LodNodeBuilder; import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker; +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.LodChunk; -import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodQuadTreeDimension; +import com.seibel.lod.objects.LodQuadTreeWorld; import com.seibel.lod.objects.LodRegion; -import com.seibel.lod.objects.LodWorld; -import com.seibel.lod.render.LodRenderer; +import com.seibel.lod.render.LodNodeRenderer; import com.seibel.lod.util.LodUtil; import net.minecraft.client.Minecraft; @@ -51,10 +52,16 @@ public class ClientProxy { public static final Logger LOGGER = LogManager.getLogger("LOD"); - private static LodWorld lodWorld = new LodWorld(); - private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder(); - private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder); - private static LodRenderer renderer = new LodRenderer(lodBufferBuilder); +// private static LodWorld lodWorld = new LodWorld(); +// private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder(); +// private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder); +// private static LodRenderer renderer = new LodRenderer(lodBufferBuilder); + + private static LodQuadTreeWorld lodWorld = new LodQuadTreeWorld(); + private static LodNodeBuilder lodNodeBuilder = new LodNodeBuilder(); + private static LodNodeBufferBuilder lodBufferBuilder = new LodNodeBufferBuilder(lodNodeBuilder); + private static LodNodeRenderer renderer = new LodNodeRenderer(lodBufferBuilder); + Minecraft mc = Minecraft.getInstance(); @@ -85,17 +92,17 @@ public class ClientProxy // TODO is this logic good? (mc.options.renderDistance * LodChunk.WIDTH * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()) / LodRegion.SIZE ); - if (lodChunkBuilder.regionWidth != newWidth) + if (lodNodeBuilder.regionWidth != newWidth) { lodWorld.resizeDimensionRegionWidth(newWidth); - lodChunkBuilder.regionWidth = newWidth; + lodNodeBuilder.regionWidth = newWidth; // skip this frame, hopefully the lodWorld // should have everything set up by then return; } - LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType()); + LodQuadTreeDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType()); if (lodDim == null) return; @@ -155,7 +162,8 @@ public class ClientProxy @SubscribeEvent public void chunkLoadEvent(ChunkEvent.Load event) { - lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld()); + //lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld()); + lodNodeBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld()); } @@ -179,6 +187,7 @@ public class ClientProxy // if this isn't done unfinished tasks may be left in the queue // preventing new LodChunks form being generated LodChunkGenWorker.restartExecuterService(); + LodNodeGenWorker.restartExecuterService(); lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0); // the player has disconnected from a server @@ -197,7 +206,8 @@ public class ClientProxy event.getClass() == BlockEvent.PortalSpawnEvent.class) { // recreate the LOD where the blocks were changed - lodChunkBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld()); + //lodChunkBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld()); + lodNodeBuilder.generateLodNodeAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld()); } } @@ -208,17 +218,17 @@ public class ClientProxy // public getters // //================// - public static LodWorld getLodWorld() + public static LodQuadTreeWorld getLodWorld() { return lodWorld; } - public static LodChunkBuilder getLodBuilder() + public static LodNodeBuilder getLodBuilder() { - return lodChunkBuilder; + return lodNodeBuilder; } - public static LodRenderer getRenderer() + public static LodNodeRenderer getRenderer() { return renderer; } diff --git a/src/main/java/com/seibel/lod/render/LodNodeRenderer.java b/src/main/java/com/seibel/lod/render/LodNodeRenderer.java new file mode 100644 index 000000000..b6de59696 --- /dev/null +++ b/src/main/java/com/seibel/lod/render/LodNodeRenderer.java @@ -0,0 +1,886 @@ +/* + * 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.render; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.HashSet; + +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.NVFogDistance; + +import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.seibel.lod.builders.LodNodeBufferBuilder; +import com.seibel.lod.enums.FogDistance; +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.NearFarBuffer; +import com.seibel.lod.objects.NearFarFogSettings; +import com.seibel.lod.proxy.ClientProxy; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.player.ClientPlayerEntity; +import net.minecraft.client.renderer.ActiveRenderInfo; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.WorldRenderer; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.renderer.vertex.VertexBuffer; +import net.minecraft.client.renderer.vertex.VertexFormat; +import net.minecraft.potion.Effects; +import net.minecraft.profiler.IProfiler; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.vector.Matrix4f; +import net.minecraft.util.math.vector.Vector3d; +import net.minecraft.util.math.vector.Vector3f; + + +/** + * This is where all the magic happens.
+ * This is where LODs are draw to the world. + * + * @author James Seibel + * @version 07-4-2021 + */ +public class LodNodeRenderer +{ + /** this is the light used when rendering the LODs, + * it should be something different than what is used by Minecraft */ + private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2; + + /** + * 64 MB by default is the maximum amount of memory that + * can be directly allocated.

+ * + * I know there are commands to change that amount + * (specifically "-XX:MaxDirectMemorySize"), but + * I have no idea how to access that amount.
+ * So I guess this will be the hard limit for now.

+ * + * https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx + */ + public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024; + + /** Does this computer's GPU support fancy fog? */ + private static Boolean fancyFogAvailable = null; + + + + /** If true the LODs colors will be replaced with + * a checkerboard, this can be used for debugging. */ + public boolean debugging = false; + + private Minecraft mc; + private GameRenderer gameRender; + private IProfiler profiler; + private float farPlaneDistance; + private ReflectionHandler reflectionHandler; + + + /** This is used to generate the buildable buffers */ + private LodNodeBufferBuilder lodNodeBufferBuilder; + + /** The buffers that are used to draw LODs using near fog */ + private volatile BufferBuilder drawableNearBuffer; + /** The buffers that are used to draw LODs using far fog */ + private volatile BufferBuilder drawableFarBuffer; + + /** This is the VertexBuffer used to draw any LODs that use near fog */ + private volatile VertexBuffer nearVbo; + /** This is the VertexBuffer used to draw any LODs that use far fog */ + private volatile VertexBuffer farVbo; + public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR; + + + /** This is used to determine if the LODs should be regenerated */ + private int previousChunkRenderDistance = 0; + /** This is used to determine if the LODs should be regenerated */ + private int prevChunkX = 0; + /** This is used to determine if the LODs should be regenerated */ + private int prevChunkZ = 0; + /** This is used to determine if the LODs should be regenerated */ + private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR; + + /** if this is true the LOD buffers should be regenerated, + * provided they aren't already being regenerated. */ + private volatile boolean regen = false; + + /** This HashSet contains every chunk that Vanilla Minecraft + * is going to render */ + public HashSet vanillaRenderedChunks = new HashSet<>(); + + + + public LodNodeRenderer(LodNodeBufferBuilder newLodNodeBufferBuilder) + { + mc = Minecraft.getInstance(); + gameRender = mc.gameRenderer; + + reflectionHandler = new ReflectionHandler(); + lodNodeBufferBuilder = newLodNodeBufferBuilder; + } + + + /** + * Besides drawing the LODs this method also starts + * the async process of generating the Buffers that hold those LODs. + * + * @param newDim The dimension to draw, if null doesn't replace the current dimension. + * @param partialTicks how far into the current tick this method was called. + */ + @SuppressWarnings("deprecation") + public void drawLODs(LodQuadTreeDimension lodDim, float partialTicks, IProfiler newProfiler) + { + if (lodDim == null) + { + // if there aren't any loaded LodChunks + // don't try drawing anything + return; + } + + + + + + + //===============// + // initial setup // + //===============// + + profiler = newProfiler; + profiler.push("LOD setup"); + + + // only check the GPU capability's once + if (fancyFogAvailable == null) + { + // see if this GPU can run fancy fog + fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance; + + if (!fancyFogAvailable) + { + ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available."); + } + } + + + ClientPlayerEntity player = mc.player; + + // should LODs be regenerated? + if ((int)player.getX() / LodQuadTreeNode.CHUNK_WIDTH != prevChunkX || + (int)player.getZ() / LodQuadTreeNode.CHUNK_WIDTH != prevChunkZ || + previousChunkRenderDistance != mc.options.renderDistance || + prevFogDistance != LodConfig.CLIENT.fogDistance.get()) + { + // yes + regen = true; + + prevChunkX = (int)player.getX() / LodQuadTreeNode.CHUNK_WIDTH; + prevChunkZ = (int)player.getZ() / LodQuadTreeNode.CHUNK_WIDTH; + prevFogDistance = LodConfig.CLIENT.fogDistance.get(); + } + else + { + // nope, the player hasn't moved, the + // render distance hasn't changed, and + // the dimension is the same + } + + // did the user change the debug setting? + if (LodConfig.CLIENT.debugMode.get() != debugging) + { + debugging = LodConfig.CLIENT.debugMode.get(); + regen = true; + } + + + // determine how far the game's render distance is currently set + int renderDistWidth = mc.options.renderDistance; + farPlaneDistance = renderDistWidth * LodQuadTreeNode.CHUNK_WIDTH; + + // set how big the LODs will be and how far they will go + int totalLength = (int) farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 10; + int numbChunksWide = (totalLength / LodQuadTreeNode.CHUNK_WIDTH); + + // determine which LODs should not be rendered close to the player + HashSet chunkPosToSkip = getNearbyLodChunkPosToSkip(lodDim, player.blockPosition()); + + // see if the chunks Minecraft is going to render are the + // same as last time + if (!vanillaRenderedChunks.containsAll(chunkPosToSkip)) + { + regen = true; + vanillaRenderedChunks = chunkPosToSkip; + } + + + + + + //=================// + // create the LODs // + //=================// + + // only regenerate the LODs if: + // 1. we want to regenerate LODs + // 2. we aren't already regenerating the LODs + // 3. we aren't waiting for the build and draw buffers to swap + // (this is to prevent thread conflicts) + if (regen && !lodNodeBufferBuilder.generatingBuffers && !lodNodeBufferBuilder.newBuffersAvaliable()) + { + // this will mainly happen when the view distance is changed + if (drawableNearBuffer == null || drawableFarBuffer == null || + previousChunkRenderDistance != mc.options.renderDistance) + setupBuffers(numbChunksWide); + + // generate the LODs on a separate thread to prevent stuttering or freezing + lodNodeBufferBuilder.generateLodBuffersAsync(this, lodDim, player.getX(), player.getZ(), numbChunksWide); + + // the regen process has been started, + // it will be done when lodBufferBuilder.newBuffersAvaliable + // is true + regen = false; + } + + // replace the buffers used to draw and build, + // this is only done when the createLodBufferGenerationThread + // has finished executing on a parallel thread. + if (lodNodeBufferBuilder.newBuffersAvaliable()) + { + swapBuffers(); + } + + + + + + //===========================// + // GL settings for rendering // + //===========================// + + // set the required open GL settings + GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glDisable(GL11.GL_TEXTURE_2D); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_COLOR_MATERIAL); + GL11.glEnable(GL11.GL_DEPTH_TEST); + + // disable the lights Minecraft uses + GL11.glDisable(GL11.GL_LIGHT0); + GL11.glDisable(GL11.GL_LIGHT1); + + // get the default projection matrix so we can + // reset it after drawing the LODs + float[] defaultProjMatrix = new float[16]; + GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix); + + Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks); + + setupProjectionMatrix(partialTicks); + setupLighting(lodDim, partialTicks); + + NearFarFogSettings fogSettings = determineFogSettings(); + + // determine the current fog settings so they can be + // reset after drawing the LODs + float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START); + float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END); + int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE); + int defaultFogDistance = GL11.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV); + + + + + + + //===========// + // rendering // + //===========// + profiler.popPush("LOD draw"); + + setupFog(fogSettings.near.distance, fogSettings.near.quality); + sendLodsToGpuAndDraw(nearVbo, modelViewMatrix); + + setupFog(fogSettings.far.distance, fogSettings.far.quality); + sendLodsToGpuAndDraw(farVbo, modelViewMatrix); + + + + + + //=========// + // cleanup // + //=========// + + profiler.popPush("LOD cleanup"); + + GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL); + GL11.glEnable(GL11.GL_TEXTURE_2D); + GL11.glDisable(LOD_GL_LIGHT_NUMBER); + // re-enable the lights Minecraft uses + GL11.glEnable(GL11.GL_LIGHT0); + GL11.glEnable(GL11.GL_LIGHT1); + RenderSystem.disableLighting(); + + // this can't be called until after the buffers are built + // because otherwise the buffers may be set to the wrong size + previousChunkRenderDistance = mc.options.renderDistance; + + // reset the fog settings so the normal chunks + // will be drawn correctly + cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance); + + // reset the projection matrix so anything drawn after + // the LODs will use the correct projection matrix + Matrix4f mvm = new Matrix4f(defaultProjMatrix); + mvm.transpose(); + gameRender.resetProjectionMatrix(mvm); + + // clear the depth buffer so anything drawn is drawn + // over the LODs + GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); + + + // end of internal LOD profiling + profiler.pop(); + } + + + + /** + * This is where the actual drawing happens. + * + * @param buffers the buffers sent to the GPU to draw + */ + private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix) + { + if (vbo == null) + return; + + vbo.bind(); + // 0L is the starting pointer + LOD_VERTEX_FORMAT.setupBufferState(0L); + + vbo.draw(modelViewMatrix, GL11.GL_QUADS); + + VertexBuffer.unbind(); + LOD_VERTEX_FORMAT.clearBufferState(); + } + + + + + + + + //=================// + // Setup Functions // + //=================// + + @SuppressWarnings("deprecation") + private void setupFog(FogDistance fogDistance, FogQuality fogQuality) + { + if(fogQuality == FogQuality.OFF) + { + FogRenderer.setupNoFog(); + RenderSystem.disableFog(); + return; + } + + if(fogDistance == FogDistance.NEAR_AND_FAR) + { + throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance."); + } + + + // determine the fog distance mode to use + int glFogDistanceMode; + if (fogQuality == FogQuality.FANCY) + { + // fancy fog (fragment distance based fog) + glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV; + } + else + { + // fast fog (frustum distance based fog) + glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV; + } + + + // the multipliers are percentages + // of the regular view distance. + if(fogDistance == FogDistance.NEAR) + { + // the reason that I wrote fogEnd then fogStart backwards + // is because we are using fog backwards to how + // it is normally used, with it hiding near objects + // instead of far objects. + + if (fogQuality == FogQuality.FANCY) + { + RenderSystem.fogEnd(farPlaneDistance * 1.75f); + RenderSystem.fogStart(farPlaneDistance * 1.95f); + } + else if(fogQuality == FogQuality.FAST) + { + // for the far fog of the normal chunks + // to start right where the LODs' end use: + // end = 0.8f, start = 1.5f + + RenderSystem.fogEnd(farPlaneDistance * 1.5f); + RenderSystem.fogStart(farPlaneDistance * 2.0f); + } + } + else if(fogDistance == FogDistance.FAR) + { + if (fogQuality == FogQuality.FANCY) + { + RenderSystem.fogStart(farPlaneDistance * 0.85f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + RenderSystem.fogEnd(farPlaneDistance * 1.0f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + } + else if(fogQuality == FogQuality.FAST) + { + RenderSystem.fogStart(farPlaneDistance * 0.5f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + RenderSystem.fogEnd(farPlaneDistance * 0.75f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + } + } + + + GL11.glEnable(GL11.GL_FOG); + RenderSystem.enableFog(); + RenderSystem.setupNvFogDistance(); + RenderSystem.fogMode(GlStateManager.FogMode.LINEAR); + GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode); + } + + /** + * Revert any changes that were made to the fog. + */ + @SuppressWarnings("deprecation") + private void cleanupFog(NearFarFogSettings fogSettings, + float defaultFogStartDist, float defaultFogEndDist, + int defaultFogMode, int defaultFogDistance) + { + RenderSystem.fogStart(defaultFogStartDist); + RenderSystem.fogEnd(defaultFogEndDist); + RenderSystem.fogMode(defaultFogMode); + GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance); + + // disable fog if Minecraft wasn't rendering fog + // but we were + if(!fogSettings.vanillaIsRenderingFog && + (fogSettings.near.quality != FogQuality.OFF || + fogSettings.far.quality != FogQuality.OFF)) + { + GL11.glDisable(GL11.GL_FOG); + } + } + + + /** + * Create the model view matrix to move the LODs + * from object space into world space. + */ + private Matrix4f generateModelViewMatrix(float partialTicks) + { + // get all relevant camera info + ActiveRenderInfo renderInfo = mc.gameRenderer.getMainCamera(); + Vector3d projectedView = renderInfo.getPosition(); + + + // generate the model view matrix + MatrixStack matrixStack = new MatrixStack(); + matrixStack.pushPose(); + // translate and rotate to the current camera location + matrixStack.mulPose(Vector3f.XP.rotationDegrees(renderInfo.getXRot())); + matrixStack.mulPose(Vector3f.YP.rotationDegrees(renderInfo.getYRot() + 180)); + matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z); + + return matrixStack.last().pose(); + } + + + /** + * create a new projection matrix and send it over to the GPU + *

+ * A lot of this code is copied from renderLevel (line 567) + * in the GameRender class. The code copied is anything with + * a matrixStack and is responsible for making sure the LOD + * objects distort correctly relative to the rest of the world. + * Distortions are caused by: standing in a nether portal, + * nausea potion effect, walking bobbing. + * + * @param partialTicks how many ticks into the frame we are + */ + private void setupProjectionMatrix(float partialTicks) + { + // Note: if the LOD objects don't distort correctly + // compared to regular minecraft terrain, make sure + // all the transformations in renderWorld are here too + + MatrixStack matrixStack = new MatrixStack(); + matrixStack.pushPose(); + + gameRender.bobHurt(matrixStack, partialTicks); + if (this.mc.options.bobView) { + gameRender.bobView(matrixStack, partialTicks); + } + + // potion and nausea effects + float f = MathHelper.lerp(partialTicks, this.mc.player.oPortalTime, this.mc.player.portalTime) * this.mc.options.screenEffectScale * this.mc.options.screenEffectScale; + if (f > 0.0F) { + int i = this.mc.player.hasEffect(Effects.CONFUSION) ? 7 : 20; + float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F; + f1 = f1 * f1; + Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_OF_TWO / 2.0F, MathHelper.SQRT_OF_TWO / 2.0F); + matrixStack.mulPose(vector3f.rotationDegrees((gameRender.tick + partialTicks) * i)); + matrixStack.scale(1.0F / f1, 1.0F, 1.0F); + float f2 = -(gameRender.tick + partialTicks) * i; + matrixStack.mulPose(vector3f.rotationDegrees(f2)); + } + + + + // this projection matrix allows us to see past the normal + // world render distance + Matrix4f projectionMatrix = + Matrix4f.perspective( + getFov(partialTicks, true), + (float)this.mc.getWindow().getScreenWidth() / (float)this.mc.getWindow().getScreenHeight(), + // it is possible to see the near clip plane, but + // you have to be flying quickly in spectator mode through ungenerated + // terrain, so I don't think it is much of an issue. + LodConfig.CLIENT.lodChunkRadiusMultiplier.get(), + this.farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2); + + // add the screen space distortions + projectionMatrix.multiply(matrixStack.last().pose()); + gameRender.resetProjectionMatrix(projectionMatrix); + return; + } + + + /** + * setup the lighting to be used for the LODs + */ + @SuppressWarnings("deprecation") + private void setupLighting(LodQuadTreeDimension lodDimension, float partialTicks) + { + float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f; + float gammaMultiplyer = (float)mc.options.gamma - 0.5f; + float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.3f); + + float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f}; + + // can be used for debugging +// if (partialTicks < 0.005) +// ClientProxy.LOGGER.debug(lightStrength); + + ByteBuffer temp = ByteBuffer.allocateDirect(16); + temp.order(ByteOrder.nativeOrder()); + GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip()); + GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting + + RenderSystem.enableLighting(); + } + + /** + * Create all buffers that will be used. + */ + private void setupBuffers(int numbChunksWide) + { + // calculate the max amount of memory needed (in bytes) + int bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(LodConfig.CLIENT.lodChunkRadiusMultiplier.get()); + + // if the required memory is greater than the + // MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier + // to fit. + if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY) + { + int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodQuadTreeNode.CHUNK_LEVEL); + + ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high " + + "and had to be lowered to fit memory constraints " + + "from " + LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + " " + + "to " + maxRadiusMultiplier); + + LodConfig.CLIENT.lodChunkRadiusMultiplier.set( + maxRadiusMultiplier); + + bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(maxRadiusMultiplier); + } + + drawableNearBuffer = new BufferBuilder(bufferMemory); + drawableFarBuffer = new BufferBuilder(bufferMemory); + + lodNodeBufferBuilder.setupBuffers(bufferMemory); + } + + + + + + //======================// + // Other Misc Functions // + //======================// + + /** + * If this is called then the next time "drawLODs" is called + * the LODs will be regenerated; the same as if the player moved. + */ + public void regenerateLODsNextFrame() + { + regen = true; + } + + + /** + * Replace the current drawable buffers with the newly + * created buffers from the lodBufferBuilder. + */ + private void swapBuffers() + { + // replace the drawable buffers with + // the newly created buffers from the lodBufferBuilder + NearFarBuffer newBuffers = lodNodeBufferBuilder.swapBuffers(drawableNearBuffer, drawableFarBuffer); + drawableNearBuffer = newBuffers.nearBuffer; + drawableFarBuffer = newBuffers.farBuffer; + + + // bind the buffers with their respective VBOs + if (nearVbo != null) + nearVbo.close(); + + nearVbo = new VertexBuffer(LOD_VERTEX_FORMAT); + nearVbo.upload(drawableNearBuffer); + + + if (farVbo != null) + farVbo.close(); + + farVbo = new VertexBuffer(LOD_VERTEX_FORMAT); + farVbo.upload(drawableFarBuffer); + } + + + private double getFov(float partialTicks, boolean useFovSetting) + { + return mc.gameRenderer.getFov(mc.gameRenderer.getMainCamera(), partialTicks, useFovSetting); + } + + + /** + * Return what fog settings should be used when rendering. + */ + private NearFarFogSettings determineFogSettings() + { + NearFarFogSettings fogSettings = new NearFarFogSettings(); + + + FogQuality quality = reflectionHandler.getFogQuality(); + FogDrawOverride override = LodConfig.CLIENT.fogDrawOverride.get(); + + + if (quality == FogQuality.OFF) + fogSettings.vanillaIsRenderingFog = false; + else + fogSettings.vanillaIsRenderingFog = true; + + + // use any fog overrides the user may have set + switch(override) + { + case ALWAYS_DRAW_FOG_FANCY: + quality = FogQuality.FANCY; + break; + + case NEVER_DRAW_FOG: + quality = FogQuality.OFF; + break; + + case ALWAYS_DRAW_FOG_FAST: + quality = FogQuality.FAST; + break; + + case USE_OPTIFINE_FOG_SETTING: + // don't override anything + break; + } + + + // only use fancy fog if the user's GPU can deliver + if (!fancyFogAvailable && quality == FogQuality.FANCY) + { + quality = FogQuality.FAST; + } + + + // how different distances are drawn depends on the quality set + switch(quality) + { + case FANCY: + fogSettings.near.quality = FogQuality.FANCY; + fogSettings.far.quality = FogQuality.FANCY; + + switch(LodConfig.CLIENT.fogDistance.get()) + { + case NEAR_AND_FAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.FAR; + break; + + case NEAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.NEAR; + break; + + case FAR: + fogSettings.near.distance = FogDistance.FAR; + fogSettings.far.distance = FogDistance.FAR; + break; + } + break; + + case FAST: + fogSettings.near.quality = FogQuality.FAST; + fogSettings.far.quality = FogQuality.FAST; + + // fast fog setting should only have one type of + // fog, since the LODs are separated into a near + // and far portion; and fast fog is rendered from the + // frustrum's perspective instead of the camera + switch(LodConfig.CLIENT.fogDistance.get()) + { + case NEAR_AND_FAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.NEAR; + break; + + case NEAR: + fogSettings.near.distance = FogDistance.NEAR; + fogSettings.far.distance = FogDistance.NEAR; + break; + + case FAR: + fogSettings.near.distance = FogDistance.FAR; + fogSettings.far.distance = FogDistance.FAR; + break; + } + break; + + case OFF: + + fogSettings.near.quality = FogQuality.OFF; + fogSettings.far.quality = FogQuality.OFF; + break; + + } + + + return fogSettings; + } + + + + /** + * Get a HashSet of all ChunkPos within the normal render distance + * that should not be rendered. + */ + private HashSet getNearbyLodChunkPosToSkip(LodQuadTreeDimension lodDim, BlockPos playerPos) + { + int chunkRenderDist = mc.options.renderDistance; + int blockRenderDist = chunkRenderDist * 16; + ChunkPos centerChunk = new ChunkPos(playerPos); + + // skip chunks that are already going to be rendered by Minecraft + HashSet posToSkip = getRenderedChunks(); + + + // go through each chunk within the normal view distance + for(int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++) + { + for(int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++) + { + LodQuadTreeNode lod = lodDim.getLodFromCoordinates(x, z, 4); + if (lod != null) + { + short lodHighestPoint = lod.lodDataPoint.height; + + if (playerPos.getY() < lodHighestPoint) + { + // don't draw Lod's that are taller than the player + // to prevent LODs being drawn on top of the player + posToSkip.add(new ChunkPos(x, z)); + } + else if (blockRenderDist < Math.abs(playerPos.getY() - lodHighestPoint)) + { + // draw Lod's that are lower than the player's view range + posToSkip.remove(new ChunkPos(x, z)); + } + } + } + } + + return posToSkip; + } + + /** + * This method returns the ChunkPos of all chunks that Minecraft + * is going to render this frame.

+ * + * Note: This isn't perfect. It will return some chunks that are outside + * the clipping plane. (For example, if you are high above the ground some chunks + * will be incorrectly added, even though they are outside render range). + */ + public static HashSet getRenderedChunks() + { + HashSet loadedPos = new HashSet<>(); + + Minecraft mc = Minecraft.getInstance(); + + // Wow those are some long names! + + // go through every RenderInfo to get the compiled chunks + for(WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : mc.levelRenderer.renderChunks) + { + if (!worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk().hasNoRenderableLayers()) + { + // add the ChunkPos for every empty compiled chunk + BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin(); + + loadedPos.add(new ChunkPos(bpos.getX() / 16, bpos.getZ() / 16)); + } + } + + return loadedPos; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/render/LodRenderer.java b/src/main/java/com/seibel/lod/render/LodRenderer.java index 3a8777c26..f42211c8c 100644 --- a/src/main/java/com/seibel/lod/render/LodRenderer.java +++ b/src/main/java/com/seibel/lod/render/LodRenderer.java @@ -37,6 +37,7 @@ import com.seibel.lod.handlers.LodConfig; import com.seibel.lod.handlers.ReflectionHandler; import com.seibel.lod.objects.LodChunk; import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodQuadTreeNode; import com.seibel.lod.objects.NearFarBuffer; import com.seibel.lod.objects.NearFarFogSettings; import com.seibel.lod.proxy.ClientProxy; @@ -622,7 +623,7 @@ public class LodRenderer // to fit. if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY) { - int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodConfig.CLIENT.lodDetail.get()); + int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodQuadTreeNode.CHUNK_LEVEL); ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high " + "and had to be lowered to fit memory constraints " diff --git a/src/main/java/com/seibel/lod/render/RenderUtil.java b/src/main/java/com/seibel/lod/render/RenderUtil.java index 57a8e8485..a5ce197fc 100644 --- a/src/main/java/com/seibel/lod/render/RenderUtil.java +++ b/src/main/java/com/seibel/lod/render/RenderUtil.java @@ -17,9 +17,9 @@ */ package com.seibel.lod.render; -import com.seibel.lod.enums.LodDetail; import com.seibel.lod.enums.LodTemplate; import com.seibel.lod.handlers.LodConfig; +import com.seibel.lod.objects.LodQuadTreeNode; import net.minecraft.client.Minecraft; import net.minecraft.util.math.ChunkPos; @@ -94,16 +94,16 @@ public class RenderUtil // calculate the max amount of buffer memory needed (in bytes) return numbChunksWide * numbChunksWide * LodConfig.CLIENT.lodTemplate.get(). - getBufferMemoryForSingleLod(LodConfig.CLIENT.lodDetail.get()); + getBufferMemoryForSingleLod(LodQuadTreeNode.CHUNK_LEVEL); } /** * Returns the maxViewDistanceMultiplier for the given LodTemplate * at the given LodDetail level. */ - public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, LodDetail lodDetail) + public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, int detailLevel) { - int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod(lodDetail); + int maxNumberOfLods = LodNodeRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod(detailLevel); int numbLodsWide = (int) Math.sqrt(maxNumberOfLods); return numbLodsWide / (2 * mc.options.renderDistance); diff --git a/src/main/java/com/seibel/lod/util/BiomeColorsUtils.java b/src/main/java/com/seibel/lod/util/BiomeColorsUtils.java new file mode 100644 index 000000000..f08d7540a --- /dev/null +++ b/src/main/java/com/seibel/lod/util/BiomeColorsUtils.java @@ -0,0 +1,719 @@ +/* + * 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.util; + +import net.minecraft.block.Blocks; +import net.minecraft.world.biome.*; + +import java.awt.*; + +public class BiomeColorsUtils { + //public static OverworldBiomeSource overworldBiomeSource = new OverworldBiomeSource(MCVersion.v1_16_4, 64971835648254); + + public static Color getColorFromBiome(Biome biome,double x, double y){ + int color = 0; + switch(biome.getBiomeCategory()) { + case BEACH: + case DESERT: + color = Blocks.SAND.defaultMaterialColor().col; + break; + case EXTREME_HILLS: + color = Blocks.SNOW.defaultMaterialColor().col; + break; + case NONE: + break; + case FOREST: + case JUNGLE: + case TAIGA: + color = biome.getFoliageColor(); + break; + case MUSHROOM: + color = Blocks.MYCELIUM.defaultMaterialColor().col; + break; + case PLAINS: + case SAVANNA: + color = biome.getGrassColor(x,y); + break; + case OCEAN: + case RIVER: + case SWAMP: + color = biome.getWaterColor(); + break; + case ICY: + color = Blocks.PACKED_ICE.defaultMaterialColor().col; + break; + case THEEND: + color = Blocks.END_STONE.defaultMaterialColor().col; + break; + case NETHER: + color = Blocks.NETHERRACK.defaultMaterialColor().col; + break; + case MESA: + color = Blocks.RED_SAND.defaultMaterialColor().col; + break; + default: + color = 0; + } + return new Color(color); + } + + public static Color getColorFromBiome(Biome biome){ + int color = 0; + switch(biome.getBiomeCategory()) { + case BEACH: + case DESERT: + color = Blocks.SAND.defaultMaterialColor().col; + break; + case EXTREME_HILLS: + color = Blocks.SNOW.defaultMaterialColor().col; + break; + case FOREST: + case SAVANNA: + case JUNGLE: + case TAIGA: + color = biome.getFoliageColor(); + break; + case MUSHROOM: + color = Blocks.MYCELIUM.defaultMaterialColor().col; + break; + case PLAINS: + color = Blocks.GRASS_BLOCK.defaultMaterialColor().col; + break; + case OCEAN: + case RIVER: + case SWAMP: + color = biome.getWaterColor(); + break; + case ICY: + color = Blocks.PACKED_ICE.defaultMaterialColor().col; + break; + case THEEND: + color = Blocks.END_STONE.defaultMaterialColor().col; + break; + case NETHER: + color = Blocks.NETHERRACK.defaultMaterialColor().col; + break; + case MESA: + color = Blocks.RED_SAND.defaultMaterialColor().col; + break; + case NONE: + default: + color = 0; + } + return new Color(color); + } + + /** + * methods that gives the ChunkBase color of biomes + * @param biome to check + * @return color of the biome + */ + public static Color getColorFromIdRealistic(kaptainwutax.biomeutils.biome.Biome biome){ + Biome.Builder builder = new Biome.Builder(); + int color = 0; + switch(biome.getCategory()) { + case BEACH: + case DESERT: + color = Blocks.SAND.defaultMaterialColor().col; + break; + case EXTREME_HILLS: + color = Blocks.SNOW.defaultMaterialColor().col; + break; + case FOREST: + builder.biomeCategory(Biome.Category.FOREST); + color = builder.build().getFoliageColor(); + break; + case SAVANNA: + builder.biomeCategory(Biome.Category.SAVANNA); + color = builder.build().getFoliageColor(); + break; + case JUNGLE: + builder.biomeCategory(Biome.Category.JUNGLE); + color = builder.build().getFoliageColor(); + break; + case TAIGA: + builder.biomeCategory(Biome.Category.TAIGA); + color = builder.build().getFoliageColor(); + break; + case MUSHROOM: + color = Blocks.MYCELIUM.defaultMaterialColor().col; + break; + case PLAINS: + color = Blocks.GRASS_BLOCK.defaultMaterialColor().col; + break; + case OCEAN: + builder.biomeCategory(Biome.Category.OCEAN); + color = builder.build().getWaterColor(); + break; + case RIVER: + builder.biomeCategory(Biome.Category.RIVER); + color = builder.build().getWaterColor(); + break; + case SWAMP: + builder.biomeCategory(Biome.Category.SWAMP); + color = builder.build().getWaterColor(); + break; + case ICY: + color = Blocks.PACKED_ICE.defaultMaterialColor().col; + break; + case THE_END: + color = Blocks.END_STONE.defaultMaterialColor().col; + break; + case NETHER: + color = Blocks.NETHERRACK.defaultMaterialColor().col; + break; + case BADLANDS_PLATEAU: + case MESA: + color = Blocks.RED_SAND.defaultMaterialColor().col; + break; + case NONE: + default: + color = 0; + } + return new Color(color); + } + + /** + * methods that gives the ChunkBase color of biomes + * @param biome to check + * @return color of the biome + */ + public static Color getColorFromBiomeBlock(kaptainwutax.biomeutils.biome.Biome biome){ + int color = 0; + switch(biome.getCategory()) { + case BEACH: + case DESERT: + color = Blocks.SAND.defaultMaterialColor().col; + break; + case EXTREME_HILLS: + color = Blocks.SNOW.defaultMaterialColor().col; + break; + case FOREST: + color = Blocks.OAK_LEAVES.defaultMaterialColor().col; + break; + case SAVANNA: + color = Blocks.ACACIA_LEAVES.defaultMaterialColor().col; + break; + case JUNGLE: + color = Blocks.JUNGLE_LEAVES.defaultMaterialColor().col; + break; + case TAIGA: + color = Blocks.SPRUCE_LEAVES.defaultMaterialColor().col; + break; + case MUSHROOM: + color = Blocks.MYCELIUM.defaultMaterialColor().col; + break; + case PLAINS: + color = Blocks.GRASS_BLOCK.defaultMaterialColor().col; + break; + case OCEAN: + case RIVER: + color = Blocks.WATER.defaultMaterialColor().col; + case SWAMP: + color = Blocks.LILY_PAD.defaultMaterialColor().col; + break; + case ICY: + color = Blocks.PACKED_ICE.defaultMaterialColor().col; + break; + case THE_END: + color = Blocks.END_STONE.defaultMaterialColor().col; + break; + case NETHER: + color = Blocks.NETHERRACK.defaultMaterialColor().col; + break; + case BADLANDS_PLATEAU: + case MESA: + color = Blocks.RED_SAND.defaultMaterialColor().col; + break; + case NONE: + default: + color = 0; + } + return new Color(color); + } + + /** + * methods that gives the ChunkBase color of biomes + * @param biome to check + * @return color of the biome + */ + public static Color getColorFromBiomeManual(kaptainwutax.biomeutils.biome.Biome biome){ + Color color; + switch(biome.getCategory()) { + case BEACH: + case DESERT: + color = new Color(220,214,170); + break; + case EXTREME_HILLS: + color = new Color(81,129,60); + break; + case FOREST: + color = new Color(81,129,60); + break; + case SAVANNA: + color = new Color(119,113,53); + break; + case JUNGLE: + color = new Color(41,141,4); + break; + case TAIGA: + color = new Color(70,95,68); + break; + case MUSHROOM: + color = new Color(123,105,109); + break; + case PLAINS: + color = new Color(96,125,59); + break; + case OCEAN: + case RIVER: + color = new Color(54,73,229); + break; + case SWAMP: + color = new Color(83,86,67); + break; + case ICY: + color = new Color(199,217,254); + break; + case THE_END: + color = new Color(100,100,0); + break; + case NETHER: + color = new Color(100,0,0); + break; + case BADLANDS_PLATEAU: + case MESA: + color = new Color(188,103,39); + break; + case NONE: + color = new Color(96,125,59); + break; + default: + color = new Color(0,0,0,0); + } + return color; + } + + /** + * methods that gives the ChunkBase color of biomes + * @param biomeId id of the biome + * @return color of the biome + */ + public static Color getColorFromIdCB(int biomeId){ + int red=0; + int green=0; + int blue=0; + switch(biomeId) { + case 0: + red = 0; + green = 0; + blue = 112; + break; + case 1: + red = 141; + green = 179; + blue = 96; + break; + case 2: + red = 250; + green = 148; + blue = 24; + break; + case 3: + red = 96; + green = 96; + blue = 96; + break; + case 4: + red = 5; + green = 102; + blue = 33; + break; + case 5: + red = 11; + green = 2; + blue = 89; + break; + case 6: + red = 7; + green = 249; + blue = 178; + break; + case 7: + red = 0; + green = 0; + blue = 255; + break; + case 8: + red = 255; + green = 0; + blue = 0; + break; + case 9: + red = 128; + green = 128; + blue = 255; + break; + case 10: + red = 112; + green = 112; + blue = 214; + break; + case 11: + red = 160; + green = 160; + blue = 255; + break; + case 12: + red = 255; + green = 255; + blue = 255; + break; + case 13: + red = 160; + green = 160; + blue = 160; + break; + case 14: + red = 255; + green = 0; + blue = 255; + break; + case 15: + red = 160; + green = 0; + blue = 255; + break; + case 16: + red = 250; + green = 222; + blue = 85; + break; + case 17: + red = 210; + green = 95; + blue = 18; + break; + case 18: + red = 34; + green = 85; + blue = 28; + break; + case 19: + red = 22; + green = 57; + blue = 51; + break; + case 20: + red = 114; + green = 120; + blue = 154; + break; + case 21: + red = 83; + green = 123; + blue = 9; + break; + case 22: + red = 44; + green = 66; + blue = 5; + break; + case 23: + red = 98; + green = 139; + blue = 23; + break; + case 24: + red = 0; + green = 0; + blue = 48; + break; + case 25: + red = 162; + green = 162; + blue = 132; + break; + case 26: + red = 250; + green = 240; + blue = 192; + break; + case 27: + red = 48; + green = 116; + blue = 68; + break; + case 28: + red = 31; + green = 5; + blue = 50; + break; + case 29: + red = 64; + green = 81; + blue = 26; + break; + case 30: + red = 49; + green = 85; + blue = 74; + break; + case 31: + red = 36; + green = 63; + blue = 54; + break; + case 32: + red = 89; + green = 102; + blue = 81; + break; + case 33: + red = 69; + green = 7; + blue = 62; + break; + case 34: + red = 80; + green = 112; + blue = 80; + break; + case 35: + red = 189; + green = 18; + blue = 95; + break; + case 36: + red = 167; + green = 157; + blue = 100; + break; + case 37: + red = 217; + green = 69; + blue = 21; + break; + case 38: + red = 17; + green = 151; + blue = 101; + break; + case 39: + red = 202; + green = 140; + blue = 101; + break; + case 40: + red = 128; + green = 128; + blue = 255; + break; + case 41: + red = 128; + green = 128; + blue = 255; + break; + case 42: + red = 128; + green = 128; + blue = 255; + break; + case 43: + red = 128; + green = 128; + blue = 255; + break; + case 44: + red = 0; + green = 0; + blue = 172; + break; + case 45: + red = 0; + green = 0; + blue = 144; + break; + case 46: + red = 32; + green = 32; + blue = 112; + break; + case 47: + red = 0; + green = 0; + blue = 80; + break; + case 48: + red = 0; + green = 0; + blue = 64; + break; + case 49: + red = 32; + green = 32; + blue = 56; + break; + case 50: + red = 64; + green = 64; + blue = 144; + break; + case 127: + red = 0; + green = 0; + blue = 0; + break; + case 129: + red = 181; + green = 219; + blue = 136; + break; + case 130: + red = 255; + green = 188; + blue = 64; + break; + case 131: + red = 136; + green = 136; + blue = 136; + break; + case 132: + red = 45; + green = 142; + blue = 73; + break; + case 133: + red = 51; + green = 142; + blue = 19; + break; + case 134: + red = 47; + green = 255; + blue = 18; + break; + case 140: + red = 180; + green = 20; + blue = 220; + break; + case 149: + red = 123; + green = 13; + blue = 49; + break; + case 151: + red = 138; + green = 179; + blue = 63; + break; + case 155: + red = 88; + green = 156; + blue = 108; + break; + case 156: + red = 71; + green = 15; + blue = 90; + break; + case 157: + red = 104; + green = 121; + blue = 66; + break; + case 158: + red = 89; + green = 125; + blue = 114; + break; + case 160: + red = 129; + green = 142; + blue = 121; + break; + case 161: + red = 109; + green = 119; + blue = 102; + break; + case 162: + red = 120; + green = 52; + blue = 120; + break; + case 163: + red = 229; + green = 218; + blue = 135; + break; + case 164: + red = 207; + green = 197; + blue = 140; + break; + case 165: + red = 255; + green = 109; + blue = 61; + break; + case 166: + red = 216; + green = 191; + blue = 141; + break; + case 167: + red = 242; + green = 180; + blue = 141; + break; + case 168: + red = 118; + green = 142; + blue = 20; + break; + case 169: + red = 59; + green = 71; + blue = 10; + break; + case 170: + red = 82; + green = 41; + blue = 33; + break; + case 171: + red = 221; + green = 8; + blue = 8; + break; + case 172: + red = 73; + green = 144; + blue = 123; + break; + default: + red = 255; + green = 0; + blue = 0; + } + return new Color(red, green, blue); + } +} diff --git a/src/main/java/com/seibel/lod/util/LodUtil.java b/src/main/java/com/seibel/lod/util/LodUtil.java index fb87b381d..003d77f93 100644 --- a/src/main/java/com/seibel/lod/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/util/LodUtil.java @@ -20,7 +20,6 @@ package com.seibel.lod.util; import java.awt.Color; import java.io.File; -import com.seibel.lod.objects.LodRegion; import com.seibel.lod.objects.RegionPos; import net.minecraft.client.Minecraft; @@ -96,8 +95,8 @@ public class LodUtil public static RegionPos convertChunkPosToRegionPos(ChunkPos pos) { RegionPos rPos = new RegionPos(); - rPos.x = pos.x / LodRegion.SIZE; - rPos.z = pos.z / LodRegion.SIZE; + rPos.x = pos.x / 512; + rPos.z = pos.z / 512; // prevent issues if X/Z is negative and less than 16 if (pos.x < 0) @@ -109,6 +108,9 @@ public class LodUtil rPos.z = (Math.abs(rPos.z) * -1) - 1; } + //rPos.x = (Math.floorDiv(pos.x, (int) (512/Math.pow(LodQuadTreeNode.CHUNK_LEVEL,2)))); + //rPos.z = (Math.floorDiv(pos.z, (int) (512/Math.pow(LodQuadTreeNode.CHUNK_LEVEL,2)))); + return rPos; }