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;
}