future : futuresBuffer)
+ {
+ // the future will be false if its thread failed
+ if (!future.get())
+ {
+ ClientApi.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
+ break;
+ }
+ }
+ //long executeEnd = System.currentTimeMillis();
+
+
+ //long endTime = System.currentTimeMillis();
+ //long buildTime = endTime - startTime;
+ //long executeTime = executeEnd - executeStart;
+
+// ClientProxy.LOGGER.info("Thread Build time: " + buildTime + " ms" + '\n' +
+// "thread execute time: " + executeTime + " ms");
+
+ // mark that the buildable buffers as ready to swap
+ switchVbos = true;
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" ran into trouble: ");
+ e.printStackTrace();
+ }
+ finally
+ {
+ try
+ {
+ // clean up any potentially open resources
+ if (buildableBuffers != null)
+ closeBuffers(fullRegen, lodDim);
+
+ // upload the new buffers
+ uploadBuffers(fullRegen, lodDim);
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.warn("\"LodNodeBufferBuilder.generateLodBuffersAsync\" was unable to upload the buffers to the GPU: " + e.getMessage());
+ e.printStackTrace();
+ }
+
+ // regardless of whether we were able to successfully create
+ // the buffers, we are done generating.
+ generatingBuffers = false;
+ bufferLock.unlock();
+ }
+ }
+
+ private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, AbstractChunkPosWrapper playerChunkPos, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
+
+
+ // skip any chunks that Minecraft is going to render
+ int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.getX();
+ int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.getZ();
+
+ // check if the chunk is on the border
+ boolean isItBorderPos;
+ if (CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw() == VanillaOverdraw.BORDER)
+ isItBorderPos = LodUtil.isBorderChunk(vanillaRenderedChunks, chunkXdist + gameChunkRenderDistance + 1, chunkZdist + gameChunkRenderDistance + 1);
+ else
+ isItBorderPos = false;
+
+
+ //boolean smallRenderDistance = gameChunkRenderDistance <= LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW;
+
+ // get the positions that will be rendered
+
+ return (gameChunkRenderDistance >= Math.abs(chunkXdist)
+ && gameChunkRenderDistance >= Math.abs(chunkZdist)
+ && detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
+ && vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1])
+ && (!isItBorderPos);
+ }
+
+
+
+
+
+
+ //===============================//
+ // BufferBuilder related methods //
+ //===============================//
+
+ /**
+ * Called from the LodRenderer to create the
+ * BufferBuilders.
+ *
+ * May have to wait for the bufferLock to open.
+ */
+ public void setupBuffers(LodDimension lodDimension)
+ {
+ try
+ {
+ bufferLock.lock();
+
+ int numbRegionsWide = lodDimension.getWidth();
+ long regionMemoryRequired;
+ int numberOfBuffers;
+
+ GLProxy glProxy = GLProxy.getInstance();
+ GLProxyContext oldContext = glProxy.getGlContext();
+ glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
+
+
+ previousRegionWidth = numbRegionsWide;
+ numberOfBuffersPerRegion = new int[numbRegionsWide][numbRegionsWide];
+ buildableBuffers = new LodBufferBuilder[numbRegionsWide][numbRegionsWide][];
+
+ buildableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][];
+ drawableVbos = new LodVertexBuffer[numbRegionsWide][numbRegionsWide][];
+
+ if (glProxy.bufferStorageSupported)
+ {
+ buildableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
+ drawableStorageBufferIds = new int[numbRegionsWide][numbRegionsWide][];
+ }
+
+ for (int x = 0; x < numbRegionsWide; x++)
+ {
+ for (int z = 0; z < numbRegionsWide; z++)
+ {
+ regionMemoryRequired = DEFAULT_MEMORY_ALLOCATION;
+
+ // if the memory required is greater than the max buffer
+ // capacity, divide the memory across multiple buffers
+ if (regionMemoryRequired > LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY)
+ {
+ numberOfBuffers = (int) regionMemoryRequired / LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY + 1;
+
+ // TODO shouldn't this be determined with regionMemoryRequired?
+ // always allocating the max memory is a bit expensive isn't it?
+ regionMemoryRequired = LodUtil.MAX_ALLOCATABLE_DIRECT_MEMORY;
+ numberOfBuffersPerRegion[x][z] = numberOfBuffers;
+ buildableBuffers[x][z] = new LodBufferBuilder[numberOfBuffers];
+ buildableVbos[x][z] = new LodVertexBuffer[numberOfBuffers];
+ drawableVbos[x][z] = new LodVertexBuffer[numberOfBuffers];
+
+ if (glProxy.bufferStorageSupported)
+ {
+ buildableStorageBufferIds[x][z] = new int[numberOfBuffers];
+ drawableStorageBufferIds[x][z] = new int[numberOfBuffers];
+ }
+ }
+ else
+ {
+ // we only need one buffer for this region
+ numberOfBuffersPerRegion[x][z] = 1;
+ buildableBuffers[x][z] = new LodBufferBuilder[1];
+ buildableVbos[x][z] = new LodVertexBuffer[1];
+ drawableVbos[x][z] = new LodVertexBuffer[1];
+
+ if (glProxy.bufferStorageSupported)
+ {
+ buildableStorageBufferIds[x][z] = new int[1];
+ drawableStorageBufferIds[x][z] = new int[1];
+ }
+ }
+
+
+ for (int i = 0; i < numberOfBuffersPerRegion[x][z]; i++)
+ {
+ buildableBuffers[x][z][i] = new LodBufferBuilder((int) regionMemoryRequired);
+
+ buildableVbos[x][z][i] = new LodVertexBuffer();
+ drawableVbos[x][z][i] = new LodVertexBuffer();
+
+
+ // create the initial mapped buffers (system memory)
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
+ GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
+ GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+
+
+ if (glProxy.bufferStorageSupported)
+ {
+ // create the buffer storage (GPU memory)
+ buildableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
+ GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); // the 0 flag means to create the storage in the GPUs memory
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+
+ drawableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
+ GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0);
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+ }
+ }
+ }
+ }
+
+ glProxy.setGlContext(oldContext);
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.info("setupBuffers ran into trouble: " + e.getMessage(), e);
+ }
+ finally
+ {
+ // this shouldn't normally happen, but just in case it sill prevent deadlock
+ bufferLock.unlock();
+ }
+ }
+
+
+
+ /**
+ * Sets the buffers and Vbos to null, forcing them to be recreated
+ * and destroys any bound OpenGL objects.
+ *
+ * May have to wait for the bufferLock to open.
+ */
+ public void destroyBuffers()
+ {
+ try
+ {
+ bufferLock.lock();
+
+
+ // destroy the buffer storages if they aren't already
+ if (buildableStorageBufferIds != null)
+ {
+ for (int x = 0; x < buildableStorageBufferIds.length; x++)
+ {
+ for (int z = 0; z < buildableStorageBufferIds.length; z++)
+ {
+ for (int i = 0; i < buildableStorageBufferIds[x][z].length; i++)
+ {
+ int buildableId = buildableStorageBufferIds[x][z][i];
+ int drawableId = drawableStorageBufferIds[x][z][i];
+
+ // make sure the buffers are deleted in a openGL context
+ GLProxy.getInstance().recordOpenGlCall(() ->
+ {
+ GL15.glDeleteBuffers(buildableId);
+ GL15.glDeleteBuffers(drawableId);
+ });
+ }
+ }
+ }
+ }
+
+ buildableStorageBufferIds = null;
+ drawableStorageBufferIds = null;
+
+
+
+
+ // destroy the VBOs if they aren't already
+ if (buildableVbos != null)
+ {
+ for (int i = 0; i < buildableVbos.length; i++)
+ {
+ for (int j = 0; j < buildableVbos.length; j++)
+ {
+ for (int k = 0; k < buildableVbos[i][j].length; k++)
+ {
+ int buildableId;
+ int drawableId;
+
+ // variables passed into a lambda expression
+ // need to be effectively final, so we have
+ // to use an else statement here
+ if (buildableVbos[i][j][k] != null)
+ buildableId = buildableVbos[i][j][k].id;
+ else
+ buildableId = 0;
+
+ if (drawableVbos[i][j][k] != null)
+ drawableId = drawableVbos[i][j][k].id;
+ else
+ drawableId = 0;
+
+
+ GLProxy.getInstance().recordOpenGlCall(() ->
+ {
+ if (buildableId != 0)
+ GL15.glDeleteBuffers(buildableId);
+ if (drawableId != 0)
+ GL15.glDeleteBuffers(drawableId);
+ });
+ }
+ }
+ }
+ }
+
+ buildableVbos = null;
+ drawableVbos = null;
+
+
+ // these don't contain any OpenGL objects, so
+ // they don't require any special clean-up
+ buildableBuffers = null;
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.info("destroyBuffers ran into trouble: " + e.getMessage(), e);
+ }
+ finally
+ {
+ // this shouldn't normally happen, but just in case it sill prevent deadlock
+ bufferLock.unlock();
+ }
+ }
+
+ /** Calls begin on each of the buildable BufferBuilders. */
+ private void startBuffers(boolean fullRegen, LodDimension lodDim)
+ {
+ for (int x = 0; x < buildableBuffers.length; x++)
+ {
+ for (int z = 0; z < buildableBuffers.length; z++)
+ {
+ if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
+ {
+ for (int i = 0; i < buildableBuffers[x][z].length; i++)
+ {
+ // for some reason BufferBuilder.vertexCounts
+ // isn't reset unless this is called, which can cause
+ // a false indexOutOfBoundsException
+ buildableBuffers[x][z][i].discard();
+
+ buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
+ }
+ }
+ }
+ }
+ }
+
+ /** Calls end on each of the buildable BufferBuilders. */
+ private void closeBuffers(boolean fullRegen, LodDimension lodDim)
+ {
+ for (int x = 0; x < buildableBuffers.length; x++)
+ for (int z = 0; z < buildableBuffers.length; z++)
+ for (int i = 0; i < buildableBuffers[x][z].length; i++)
+ if (buildableBuffers[x][z][i] != null && buildableBuffers[x][z][i].building() && (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z)))
+ buildableBuffers[x][z][i].end();
+ }
+
+
+ /** Upload all buildableBuffers to the GPU. */
+ private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
+ {
+ GLProxy glProxy = GLProxy.getInstance();
+ long fence = 0;
+
+ try
+ {
+ // make sure we are uploading to the builder context,
+ // this helps prevent interference (IE stuttering) with the Minecraft context.
+ glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
+
+ // determine the upload method
+ GpuUploadMethod uploadMethod = CONFIG.client().advanced().buffers().getGpuUploadMethod();
+ if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
+ {
+ // if buffer storage isn't supported
+ // default to SUB_DATA
+ CONFIG.client().advanced().buffers().setGpuUploadMethod(GpuUploadMethod.SUB_DATA);
+ uploadMethod = GpuUploadMethod.SUB_DATA;
+ }
+
+ // determine the upload timeout
+ int uploadTimeoutInMS = CONFIG.client().advanced().buffers().getGpuUploadTimeoutInMilliseconds();
+
+ // James has no idea if this does anything helpful,
+ // but in theory it should prevent OpenGL from drawing and
+ // writing to a buffer at the same time.
+ GL45.glMemoryBarrier(GL45.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
+ fence = GL45.glFenceSync(GL45.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+
+
+
+ // actually upload the buffers
+ for (int x = 0; x < buildableVbos.length; x++)
+ {
+ for (int z = 0; z < buildableVbos.length; z++)
+ {
+ if (fullRegen || lodDim.doesRegionNeedBufferRegen(x, z))
+ {
+ for (int i = 0; i < buildableBuffers[x][z].length; i++)
+ {
+ ByteBuffer uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
+ vboUpload(x,z,i, uploadBuffer, true, uploadMethod);
+ lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
+
+
+ // upload buffers over an extended period of time
+ // to hopefully prevent stuttering.
+ if (uploadTimeoutInMS != 0)
+ Thread.sleep(uploadTimeoutInMS);
+ GL15.glFinish();
+ }
+ }
+ }
+ }
+
+ // make sure all of the uploads finish before continuing
+ GL45.glClientWaitSync(fence, GL45.GL_SYNC_FLUSH_COMMANDS_BIT, 5L * 1000000000); // wait up to 5 seconds
+ }
+ catch (Exception e)
+ {
+ // this doesn't appear to be necessary anymore, but just in case.
+ ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
+ e.printStackTrace();
+ }
+ finally
+ {
+ GL15.glFinish();
+ if (fence != 0)
+ GL45.glDeleteSync(fence);
+
+ // close the context so it can be re-used later.
+ // I'm guessing we can't just leave it because the executor service
+ // does something that invalidates the OpenGL context.
+ glProxy.setGlContext(GLProxyContext.NONE);
+ }
+ }
+
+ /** Uploads the uploadBuffer so the GPU can use it. */
+ private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer,
+ boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
+ {
+ // get the vbos, buffers, ids, etc.
+ int storageBufferId = 0;
+ if (buildableStorageBufferIds != null)
+ storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex];
+
+ LodVertexBuffer vbo = buildableVbos[xIndex][zIndex][iIndex];
+
+
+
+
+ // this shouldn't happen, but just to be safe
+ if (vbo.id != -1 && GLProxy.getInstance().getGlContext() == GLProxyContext.LOD_BUILDER)
+ {
+ // this is how many points will be rendered
+ vbo.vertexCount = (uploadBuffer.capacity() / ((Float.BYTES * 3) + (Byte.BYTES * 4))); // TODO make this change with the LodTemplate
+
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
+ try
+ {
+ // if possible use the faster buffer storage route
+ if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0)
+ {
+ // get a pointer to the buffer in system memory
+ ByteBuffer vboBuffer = GL30.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
+ if (vboBuffer == null)
+ {
+ int previousCapacity = uploadBuffer.capacity();
+
+ // only expand the buffers if the uploadBuffer actually
+ // has something in it and expansion is allowed
+ if (previousCapacity != 0 && allowBufferExpansion)
+ {
+ // the buffer(s) aren't big enough, expand them.
+ // This does cause lag/stuttering, so it should be avoided!
+
+ // expand the buffer in system memory
+ GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW);
+ GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
+
+ // un-bind the system memory buffer
+ GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+
+ // expand the buffer storage
+ GL15.glDeleteBuffers(storageBufferId);
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, storageBufferId);
+ GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), 0);
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+
+
+ // recursively try to upload into the newly created buffer storage
+ // but don't recurse again if that fails
+ // (we don't want an infinitely expanding buffer!)
+ vboUpload(xIndex,zIndex,iIndex, uploadBuffer, false, uploadMethod);
+ }
+ }
+ else
+ {
+ // upload the buffer into system memory...
+ vboBuffer.put(uploadBuffer);
+ GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
+
+ // ...then upload into GPU memory
+ // (uploading into GPU memory directly can only be done
+ // through the glCopyBufferSubData/glCopyNamed... methods)
+ GL45.glCopyNamedBufferSubData(vbo.id, storageBufferId, 0, 0, uploadBuffer.capacity());
+ }
+ }
+ else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
+ {
+ // no stuttering but high GPU usage
+ // stores everything in system memory instead of GPU memory
+ // making rendering much slower.
+ // Unless the user is running integrated graphics,
+ // in that case this will actually work better than SUB_DATA.
+
+
+ ByteBuffer vboBuffer;
+
+ // map buffer range is better since it can be explicitly unsynchronized
+ if (GLProxy.getInstance().mapBufferRangeSupported)
+ vboBuffer = GL30.glMapBufferRange(GL30.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT);
+ else
+ vboBuffer = GL15.glMapBuffer(GL30.GL_ARRAY_BUFFER, uploadBuffer.capacity());
+
+
+ if (vboBuffer == null)
+ {
+ GL15.glBufferData(GL45.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
+ GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
+ }
+ else
+ {
+ vboBuffer.put(uploadBuffer);
+ }
+ }
+ else if (uploadMethod == GpuUploadMethod.DATA)
+ {
+ // hybrid bufferData //
+ // high stutter, low GPU usage
+ // But simplest/most compatible
+
+ GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.capacity(), GL15.GL_STATIC_DRAW);
+ GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15.GL_STATIC_DRAW);
+ }
+ else
+ {
+ // hybrid subData/bufferData //
+ // less stutter, low GPU usage
+
+ long size = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
+ if (size < uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER)
+ {
+ GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
+ }
+ GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
+ }
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.error("vboUpload failed: " + e.getClass().getSimpleName());
+ e.printStackTrace();
+ }
+ finally
+ {
+ if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
+ GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
+
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+ }
+
+ }//if vbo exists and in correct GL context
+ }//vboUpload
+
+ /** Get the newly created VBOs */
+ public VertexBuffersAndOffset getVertexBuffers()
+ {
+ // don't wait for the lock to open,
+ // since this is called on the main render thread
+ if (bufferLock.tryLock())
+ {
+ try
+ {
+ LodVertexBuffer[][][] tmpVbo = drawableVbos;
+ drawableVbos = buildableVbos;
+ buildableVbos = tmpVbo;
+
+ int[][][] tmpStorage = drawableStorageBufferIds;
+ drawableStorageBufferIds = buildableStorageBufferIds;
+ buildableStorageBufferIds = tmpStorage;
+
+ drawableCenterChunkPos = buildableCenterChunkPos;
+
+ // the vbos have been swapped
+ switchVbos = false;
+ }
+ catch (Exception e)
+ {
+ // this shouldn't normally happen, but just in case it sill prevent deadlock
+ ClientApi.LOGGER.info("getVertexBuffers ran into trouble: " + e.getMessage(), e);
+ }
+ finally
+ {
+ bufferLock.unlock();
+ }
+ }
+
+ return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPos);
+ }
+
+ /** A simple container to pass multiple objects back in the getVertexBuffers method. */
+ public static class VertexBuffersAndOffset
+ {
+ public final LodVertexBuffer[][][] vbos;
+ public final int[][][] storageBufferIds;
+ public final AbstractChunkPosWrapper drawableCenterChunkPos;
+
+ public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, AbstractChunkPosWrapper newDrawableCenterChunkPos)
+ {
+ vbos = newVbos;
+ storageBufferIds = newStorageBufferIds;
+ drawableCenterChunkPos = newDrawableCenterChunkPos;
+ }
+ }
+
+ /**
+ * If this is true the buildable near and far
+ * buffers have been generated and are ready to be
+ * sent to the LodRenderer.
+ */
+ public boolean newBuffersAvailable()
+ {
+ return switchVbos;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java
new file mode 100644
index 000000000..69bb57b10
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.bufferBuilding.lodTemplates;
+
+import java.util.Map;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.objects.VertexOptimizer;
+import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+/**
+ * This is the abstract class used to create different
+ * BufferBuilders.
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public abstract class AbstractLodTemplate
+{
+ /** Uploads the given LOD to the buffer. */
+ public abstract void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData,
+ byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled);
+
+ /** add the given position and color to the buffer */
+ protected void addPosAndColor(LodBufferBuilder buffer,
+ float x, float y, float z,
+ int color)
+ {
+ // TODO re-add transparency by replacing the 255 with "ColorUtil.getAlpha(color)"
+ buffer.vertex(x, y, z).color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255).endVertex();
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java
new file mode 100644
index 000000000..aaf016b05
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java
@@ -0,0 +1,142 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.bufferBuilding.lodTemplates;
+
+import java.util.Map;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.objects.VertexOptimizer;
+import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+/**
+ * Builds LODs as rectangular prisms.
+ * @author James Seibel
+ * @version 11-8-2021
+ */
+public class CubicLodTemplate extends AbstractLodTemplate
+{
+
+ public CubicLodTemplate()
+ {
+
+ }
+
+ @Override
+ public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData,
+ byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
+ {
+ if (vertexOptimizer == null)
+ return;
+
+ // equivalent to 2^detailLevel
+ int blockWidth = 1 << detailLevel;
+
+ int color;
+ if (debugging != DebugMode.OFF)
+ color = LodUtil.DEBUG_DETAIL_LEVEL_COLORS[detailLevel].getRGB();
+ else
+ color = DataPointUtil.getColor(data);
+
+
+ generateBoundingBox(
+ vertexOptimizer,
+ DataPointUtil.getHeight(data),
+ DataPointUtil.getDepth(data),
+ blockWidth,
+ posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
+ bufferCenterBlockPos,
+ adjData,
+ color,
+ DataPointUtil.getLightSkyAlt(data),
+ DataPointUtil.getLightBlock(data),
+ adjShadeDisabled);
+
+ addBoundingBoxToBuffer(buffer, vertexOptimizer);
+ }
+
+ private void generateBoundingBox(VertexOptimizer vertexOptimizer,
+ int height, int depth, int width,
+ double xOffset, double yOffset, double zOffset,
+ AbstractBlockPosWrapper bufferCenterBlockPos,
+ Map adjData,
+ int color,
+ int skyLight,
+ int blockLight,
+ boolean[] adjShadeDisabled)
+ {
+ // don't add an LOD if it is empty
+ if (height == -1 && depth == -1)
+ return;
+
+ if (depth == height)
+ // if the top and bottom points are at the same height
+ // render this LOD as 1 block thick
+ height++;
+
+ // offset the AABB by its x/z position in the world since
+ // it uses doubles to specify its location, unlike the model view matrix
+ // which only uses floats
+ double x = -bufferCenterBlockPos.getX();
+ double z = -bufferCenterBlockPos.getZ();
+ vertexOptimizer.reset();
+ vertexOptimizer.setColor(color, adjShadeDisabled);
+ vertexOptimizer.setLights(skyLight, blockLight);
+ vertexOptimizer.setWidth(width, height - depth, width);
+ vertexOptimizer.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
+ vertexOptimizer.setUpCulling(32, bufferCenterBlockPos);
+ vertexOptimizer.setAdjData(adjData);
+ }
+
+ private void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
+ {
+ int color;
+ int skyLight;
+ int blockLight;
+ for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS)
+ {
+ if(vertexOptimizer.isCulled(lodDirection))
+ continue;
+
+ int verticalFaceIndex = 0;
+ while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex))
+ {
+ for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++)
+ {
+ color = vertexOptimizer.getColor(lodDirection);
+ skyLight = vertexOptimizer.getSkyLight(lodDirection, verticalFaceIndex);
+ blockLight = vertexOptimizer.getBlockLight();
+ color = ColorUtil.applyLightValue(color, skyLight, blockLight);
+ addPosAndColor(buffer,
+ vertexOptimizer.getX(lodDirection, vertexIndex),
+ vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + DataPointUtil.VERTICAL_OFFSET,
+ vertexOptimizer.getZ(lodDirection, vertexIndex),
+ color);
+ }
+ verticalFaceIndex++;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java
new file mode 100644
index 000000000..a1c302b00
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.bufferBuilding.lodTemplates;
+
+import java.util.Map;
+
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.objects.VertexOptimizer;
+import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+/**
+ * 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 DynamicLodTemplate extends AbstractLodTemplate
+{
+ @Override
+ public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData,
+ byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
+ {
+ ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java
new file mode 100644
index 000000000..489869645
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.bufferBuilding.lodTemplates;
+
+import java.util.Map;
+
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.objects.VertexOptimizer;
+import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+/**
+ * TODO #21 TriangularLodTemplate
+ * Builds each LOD chunk as a singular rectangular prism.
+ * @author James Seibel
+ * @version 06-16-2021
+ */
+public class TriangularLodTemplate extends AbstractLodTemplate
+{
+ @Override
+ public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map adjData,
+ byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
+ {
+ ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java
new file mode 100644
index 000000000..671abcadc
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java
@@ -0,0 +1,538 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.lodBuilding;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.HorizontalResolution;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.objects.lod.LodRegion;
+import com.seibel.lod.core.objects.lod.LodWorld;
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.util.DetailDistanceUtil;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodThreadFactory;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.util.ThreadMapUtil;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+
+/**
+ * This object is in charge of creating Lod related objects.
+ *
+ * @author Cola
+ * @author Leonardo Amato
+ * @author James Seibel
+ * @version 10-22-2021
+ */
+@SuppressWarnings("GrazieInspection") public class LodBuilder
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
+ private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+ /** If no blocks are found in the area in determineBottomPointForArea return this */
+ public static final short DEFAULT_DEPTH = 0;
+ /** If no blocks are found in the area in determineHeightPointForArea return this */
+ public static final short DEFAULT_HEIGHT = 0;
+ /** Minecraft's max light value */
+ public static final short DEFAULT_MAX_LIGHT = 15;
+
+
+ private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
+ private final ILodConfigWrapperSingleton config = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+
+
+
+ /**
+ * How wide LodDimensions should be in regions
+ * Is automatically set before the first frame in ClientProxy.
+ */
+ public int defaultDimensionWidthInRegions = 0;
+
+ //public static final boolean useExperimentalLighting = true;
+
+
+
+
+ public LodBuilder()
+ {
+
+ }
+
+ public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim)
+ {
+ generateLodNodeAsync(chunk, lodWorld, dim, DistanceGenerationMode.FULL);
+ }
+
+ public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, DistanceGenerationMode generationMode)
+ {
+ if (lodWorld == null || lodWorld.getIsWorldNotLoaded())
+ 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(() ->
+ {
+ //noinspection GrazieInspection
+ try
+ {
+ // we need a loaded client world in order to
+ // get the textures for blocks
+ if (MC.getWrappedClientWorld() == null)
+ return;
+
+ // don't try to generate LODs if the user isn't in the world anymore
+ // (this happens a lot when the user leaves a world/server)
+ if (!MC.hasSinglePlayerServer() && !MC.connectedToServer())
+ return;
+
+ // make sure the dimension exists
+ LodDimension lodDim;
+ if (lodWorld.getLodDimension(dim) == null)
+ {
+ lodDim = new LodDimension(dim, lodWorld, defaultDimensionWidthInRegions);
+ lodWorld.addLodDimension(lodDim);
+ }
+ else
+ {
+ lodDim = lodWorld.getLodDimension(dim);
+ }
+ generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode));
+ }
+ 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);
+ }
+
+ /**
+ * Creates a LodNode for a chunk in the given world.
+ * @throws IllegalArgumentException thrown if either the chunk or world is null.
+ */
+ public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk) throws IllegalArgumentException
+ {
+ generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig());
+ }
+
+ /**
+ * Creates a LodNode for a chunk in the given world.
+ * @throws IllegalArgumentException thrown if either the chunk or world is null.
+ */
+ public void generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config)
+ throws IllegalArgumentException
+ {
+ if (chunk == null)
+ throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
+
+ int startX;
+ int startZ;
+
+
+ LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
+ if (region == null)
+ return;
+
+ // this happens if a LOD is generated after the user leaves the world.
+ if (MC.getWrappedClientWorld() == null)
+ return;
+
+ // determine how many LODs to generate horizontally
+ byte minDetailLevel = region.getMinDetailLevel();
+ HorizontalResolution detail = DetailDistanceUtil.getLodGenDetail(minDetailLevel);
+
+
+ // determine how many LODs to generate vertically
+ //VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
+ byte detailLevel = detail.detailLevel;
+
+
+ // generate the LODs
+ int posX;
+ int posZ;
+ for (int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
+ {
+ startX = detail.startX[i];
+ startZ = detail.startZ[i];
+
+ long[] data;
+ long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
+ data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
+
+
+ //lodDim.clear(detailLevel, posX, posZ);
+ if (data != null && data.length != 0)
+ {
+ posX = LevelPosUtil.convert((byte) 0, chunk.getPos().getX() * 16 + startX, detail.detailLevel);
+ posZ = LevelPosUtil.convert((byte) 0, chunk.getPos().getZ() * 16 + startZ, detail.detailLevel);
+ lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
+ }
+ }
+ lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().getX(), chunk.getPos().getZ());
+ }
+
+ /** creates a vertical DataPoint */
+ private long[] createVerticalDataToMerge(HorizontalResolution detail, IChunkWrapper chunk, LodBuilderConfig config, int startX, int startZ)
+ {
+ // equivalent to 2^detailLevel
+ int size = 1 << detail.detailLevel;
+
+ long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
+ int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
+
+ AbstractChunkPosWrapper chunkPos = chunk.getPos();
+ int height;
+ int depth;
+ int color;
+ int light;
+ int lightSky;
+ int lightBlock;
+ int generation = config.distanceGenerationMode.complexity;
+
+ int xRel;
+ int zRel;
+ int xAbs;
+ int yAbs;
+ int zAbs;
+ boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
+ boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
+ boolean isDefault;
+ AbstractBlockPosWrapper blockPos = FACTORY.createBlockPos();
+ int index;
+
+ for (index = 0; index < size * size; index++)
+ {
+ xRel = startX + index % size;
+ zRel = startZ + index / size;
+ xAbs = chunkPos.getMinBlockX() + xRel;
+ zAbs = chunkPos.getMinBlockZ() + zRel;
+
+ //Calculate the height of the lod
+ yAbs = DataPointUtil.WORLD_HEIGHT - DataPointUtil.VERTICAL_OFFSET + 1;
+ int count = 0;
+ boolean topBlock = true;
+ while (yAbs > 0)
+ {
+ height = determineHeightPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
+
+ // If the lod is at the default height, it must be void data
+ if (height == DEFAULT_HEIGHT)
+ {
+ if (topBlock)
+ dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
+ break;
+ }
+
+ yAbs = height - 1;
+ // We search light on above air block
+ depth = determineBottomPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
+ if (hasCeiling && topBlock)
+ {
+ yAbs = depth;
+ blockPos.set(xAbs, yAbs, zAbs);
+ light = getLightValue(chunk, blockPos, true, hasSkyLight, true);
+ color = generateLodColor(chunk, config, xAbs, yAbs, zAbs, blockPos);
+ blockPos.set(xAbs, yAbs - 1, zAbs);
+ }
+ else
+ {
+ blockPos.set(xAbs, yAbs, zAbs);
+ light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock);
+ color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
+ blockPos.set(xAbs, yAbs + 1, zAbs);
+ }
+ lightBlock = light & 0b1111;
+ lightSky = (light >> 4) & 0b1111;
+ isDefault = ((light >> 8)) == 1;
+
+ dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height - DataPointUtil.VERTICAL_OFFSET, depth - DataPointUtil.VERTICAL_OFFSET, color, lightSky, lightBlock, generation, isDefault);
+ topBlock = false;
+ yAbs = depth - 1;
+ count++;
+ }
+ }
+ return dataToMerge;
+ }
+
+ /**
+ * Find the lowest valid point from the bottom.
+ * Used when creating a vertical LOD.
+ */
+ private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos)
+ {
+ short depth = DEFAULT_DEPTH;
+
+ for (int y = yAbs; y >= 0; y--)
+ {
+ blockPos.set(xAbs, y, zAbs);
+ if (!isLayerValidLodPoint(chunk, blockPos))
+ {
+ depth = (short) (y + 1);
+ break;
+ }
+ }
+ return depth;
+ }
+
+ /** Find the highest valid point from the Top */
+ private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos)
+ {
+ short height = DEFAULT_HEIGHT;
+ if (config.useHeightmap)
+ height = (short) chunk.getHeightMapValue(xAbs, zAbs);
+ else
+ {
+ for (int y = yAbs; y >= 0; y--)
+ {
+ blockPos.set(xAbs, y, zAbs);
+ if (isLayerValidLodPoint(chunk, blockPos))
+ {
+ height = (short) (y + 1);
+ break;
+ }
+ }
+ }
+ return height;
+ }
+
+
+
+ // =====================//
+ // constructor helpers //
+ // =====================//
+
+ /**
+ * Generate the color for the given chunk using biome water color, foliage
+ * color, and grass color.
+ */
+ private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xRel, int yAbs, int zRel, AbstractBlockPosWrapper blockPos)
+ {
+ int colorInt;
+ if (builderConfig.useBiomeColors)
+ {
+ // 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.
+ colorInt = chunk.getBiome(xRel, yAbs, zRel).getColorForBiome(xRel, zRel);
+ }
+ else
+ {
+ blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs, chunk.getPos().getMinBlockZ() + zRel);
+ colorInt = getColorForBlock(chunk, blockPos);
+
+ // if we are skipping non-full and non-solid blocks that means we ignore
+ // snow, flowers, etc. Get the above block so we can still get the color
+ // of the snow, flower, etc. that may be above this block
+ int aboveColorInt = 0;
+ if (config.client().worldGenerator().getBlocksToAvoid().nonFull || config.client().worldGenerator().getBlocksToAvoid().noCollision)
+ {
+ blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs + 1, chunk.getPos().getMinBlockZ() + zRel);
+ aboveColorInt = getColorForBlock(chunk, blockPos);
+ }
+
+ //if (colorInt == 0 && yAbs > 0)
+ // if this block is invisible, check the block below it
+ // colorInt = generateLodColor(chunk, config, xRel, yAbs - 1, zRel, blockPos);
+
+ // override this block's color if there was a block above this
+ // and we were avoiding non-full/non-solid blocks
+ if (aboveColorInt != 0)
+ colorInt = aboveColorInt;
+ }
+
+ return colorInt;
+ }
+
+ /** Gets the light value for the given block position */
+ private int getLightValue(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
+ {
+ int skyLight = 0;
+ int blockLight;
+ // 1 means the lighting is a guess
+ int isDefault = 0;
+
+ IWorldWrapper world = MC.getWrappedServerWorld();
+
+ int blockBrightness = chunk.getEmittedBrightness(blockPos);
+ // get the air block above or below this block
+ if (hasCeiling && topBlock)
+ blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
+ else
+ blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
+
+
+
+ if (world != null)
+ {
+ // server world sky light (always accurate)
+ blockLight = world.getBlockLight(blockPos);
+ if (topBlock && !hasCeiling && hasSkyLight)
+ skyLight = DEFAULT_MAX_LIGHT;
+ else
+ {
+ if (hasSkyLight)
+ skyLight = world.getSkyLight(blockPos);
+ //else
+ // skyLight = 0;
+ }
+ if (!topBlock && skyLight == 15)
+ {
+ // we are on predicted terrain, and we don't know what the light here is,
+ // lets just take a guess
+ if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
+ {
+ skyLight = 12;
+ isDefault = 1;
+ }
+ else
+ skyLight = 0;
+ }
+ }
+ else
+ {
+ world = MC.getWrappedClientWorld();
+ if (world==null)
+ {
+ blockLight = 0;
+ skyLight = 12;
+ isDefault = 1;
+ }
+ else
+ {
+ // client world sky light (almost never accurate)
+ blockLight = world.getBlockLight(blockPos);
+ // estimate what the lighting should be
+ if (hasSkyLight || !hasCeiling)
+ {
+ if (topBlock)
+ skyLight = DEFAULT_MAX_LIGHT;
+ else
+ {
+ if (hasSkyLight)
+ skyLight = world.getSkyLight(blockPos);
+ //else
+ // skyLight = 0;
+ if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
+ {
+ // we don't know what the light here is,
+ // lets just take a guess
+ if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
+ {
+ skyLight = 12;
+ isDefault = 1;
+ }
+ else
+ skyLight = 0;
+ }
+ }
+ }
+ }
+ }
+
+ blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
+
+ return blockLight + (skyLight << 4) + (isDefault << 8);
+ }
+
+ /** Returns a color int for the given block. */
+ private int getColorForBlock(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos)
+ {
+ int colorOfBlock;
+ int colorInt;
+
+ int xRel = blockPos.getX() - chunk.getPos().getMinBlockX();
+ int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ();
+ //int x = blockPos.getX();
+ int y = blockPos.getY();
+ //int z = blockPos.getZ();
+
+ IBlockColorWrapper blockColorWrapper;
+ IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(blockPos);
+
+ if (chunk.isWaterLogged(blockPos))
+ blockColorWrapper = BLOCK_COLOR.getWaterColor();
+ else
+ blockColorWrapper = chunk.getBlockColorWrapper(blockPos);
+
+ if (blockShapeWrapper.isToAvoid())
+ return 0;
+
+ colorOfBlock = blockColorWrapper.getColor();
+
+
+ if (blockColorWrapper.hasTint())
+ {
+ IBiomeWrapper biome = chunk.getBiome(xRel, y, zRel);
+ int tintValue;
+ if (blockColorWrapper.hasGrassTint())
+ // grass and green plants
+ tintValue = biome.getGrassTint(0,0);
+ else if (blockColorWrapper.hasFolliageTint())
+ tintValue = biome.getFolliageTint();
+ else
+ //we can reintroduce this with the wrappers
+ tintValue = biome.getWaterTint();
+
+ colorInt = ColorUtil.multiplyRGBcolors(tintValue | 0xFF000000, colorOfBlock);
+ }
+ else
+ colorInt = colorOfBlock;
+ return colorInt;
+ }
+
+
+ /** Is the block at the given blockPos a valid LOD point? */
+ private boolean isLayerValidLodPoint(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos)
+ {
+ if (chunk.isWaterLogged(blockPos))
+ return true;
+
+ boolean nonFullAvoidance = config.client().worldGenerator().getBlocksToAvoid().nonFull;
+ boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision;
+
+ IBlockShapeWrapper block = chunk.getBlockShapeWrapper(blockPos);
+ return !block.isToAvoid()
+ && !(nonFullAvoidance && block.isNonFull())
+ && !(noCollisionAvoidance && block.hasNoCollision());
+
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java
new file mode 100644
index 000000000..e88a5995b
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java
@@ -0,0 +1,95 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.lodBuilding;
+
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+
+/**
+ * This is used to easily configure how LodChunks are generated.
+ * Generally this will only be used if we want to generate a
+ * LodChunk using an incomplete Chunk, otherwise the defaults
+ * work best for a fully generated chunk (IE has correct surface blocks).
+ * @author James Seibel
+ * @version 8-14-2021
+ */
+public class LodBuilderConfig
+{
+ /** default: false */
+ public boolean useHeightmap;
+ /** default: false */
+ public boolean useBiomeColors;
+ /** default: true */
+ public boolean useSolidBlocksInColorGen;
+ /** default: server */
+ public DistanceGenerationMode distanceGenerationMode;
+
+ /**
+ * default settings for a normal chunk
+ * useHeightmap = false
+ * useBiomeColors = false
+ * useSolidBlocksInColorGen = true
+ * generationMode = Server
+ */
+ public LodBuilderConfig()
+ {
+ useHeightmap = false;
+ useBiomeColors = false;
+ useSolidBlocksInColorGen = true;
+ distanceGenerationMode = DistanceGenerationMode.FULL;
+ }
+
+ /**
+ * @param newUseHeightmap default = false
+ * @param newUseBiomeColors default = false
+ * @param newUseSolidBlocksInBiomeColor default = true
+ * @param newDistanceGenerationMode default = Server
+ */
+ public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors,
+ boolean newUseSolidBlocksInBiomeColor, DistanceGenerationMode newDistanceGenerationMode)
+ {
+ useHeightmap = newUseHeightmap;
+ useBiomeColors = newUseBiomeColors;
+ useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
+ distanceGenerationMode = newDistanceGenerationMode;
+ }
+
+ /**
+ * @param newUseHeightmap default = false
+ * @param newUseBiomeColors default = false
+ * @param newUseSolidBlocksInBiomeColor default = true
+ */
+ public LodBuilderConfig(boolean newUseHeightmap, boolean newUseBiomeColors, boolean newUseSolidBlocksInBiomeColor)
+ {
+ this();
+ useHeightmap = newUseHeightmap;
+ useBiomeColors = newUseBiomeColors;
+ useSolidBlocksInColorGen = newUseSolidBlocksInBiomeColor;
+ distanceGenerationMode = newUseHeightmap ? DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT : DistanceGenerationMode.BIOME_ONLY;
+ }
+
+ /**
+ * @param newDistanceGenerationMode default = Server
+ */
+ public LodBuilderConfig(DistanceGenerationMode newDistanceGenerationMode)
+ {
+ this();
+ distanceGenerationMode = newDistanceGenerationMode;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java
new file mode 100644
index 000000000..77eb213b2
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java
@@ -0,0 +1,212 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.worldGeneration;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
+
+/**
+ * This is used to generate a LodChunk at a given ChunkPos.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class LodGenWorker
+{
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+ private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+ public static ExecutorService genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
+
+ private final LodChunkGenThread thread;
+
+
+
+ public LodGenWorker(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
+ LodBuilder newLodBuilder,
+ LodDimension newLodDimension, IWorldWrapper serverWorld)
+ {
+ // just a few sanity checks
+ if (newPos == null)
+ throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
+
+ if (newLodBuilder == null)
+ throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
+
+ if (newLodDimension == null)
+ throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
+
+ if (serverWorld == null)
+ throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
+
+
+
+ thread = new LodChunkGenThread(newPos, newGenerationMode,
+ newLodBuilder,
+ newLodDimension, serverWorld);
+ }
+
+ public void queueWork()
+ {
+ if (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL)
+ {
+ // if we are using FULL generation there is no reason
+ // to queue up a bunch of generation requests,
+ // because MC's internal server (as of 1.16.5) only
+ // responds with a single thread. And we don't
+ // want to cause more lag than necessary or queue up
+ // requests that may end up being unneeded.
+ thread.run();
+ }
+ else
+ {
+ // Every other method can
+ // be done asynchronously
+ genThreads.execute(thread);
+ }
+
+ // useful for debugging
+// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
+// ClientProxy.LOGGER.info(genThreads.toString());
+ }
+
+
+
+
+ private static class LodChunkGenThread implements Runnable
+ {
+ private final AbstractWorldGeneratorWrapper worldGenWrapper;
+
+ public final LodDimension lodDim;
+ public final DistanceGenerationMode generationMode;
+
+ private final AbstractChunkPosWrapper pos;
+
+ public LodChunkGenThread(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
+ LodBuilder newLodBuilder,
+ LodDimension newLodDimension, IWorldWrapper worldWrapper)
+ {
+ worldGenWrapper = FACTORY.createWorldGenerator(newLodBuilder, newLodDimension, worldWrapper);
+
+ pos = newPos;
+ generationMode = newGenerationMode;
+ lodDim = newLodDimension;
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ // only generate LodChunks if they can
+ // be added to the current LodDimension
+
+ if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
+ {
+ switch (generationMode)
+ {
+ case NONE:
+ // don't generate
+ break;
+ case BIOME_ONLY:
+ case BIOME_ONLY_SIMULATE_HEIGHT:
+ // fastest
+ worldGenWrapper.generateBiomesOnly(pos, generationMode);
+ break;
+ case SURFACE:
+ // faster
+ worldGenWrapper.generateSurface(pos);
+ break;
+ case FEATURES:
+ // fast
+ worldGenWrapper.generateFeatures(pos);
+ break;
+ case FULL:
+ // very slow
+ worldGenWrapper.generateFull(pos);
+ break;
+ }
+
+
+// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
+// if (dataExistence)
+// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
+// else
+// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
+
+ // shows the pool size, active threads, queued tasks and completed tasks
+// ClientProxy.LOGGER.info(genThreads.toString());
+
+// long endTime = System.currentTimeMillis();
+// System.out.println(endTime - startTime);
+
+ }// if in range
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage());
+ e.printStackTrace();
+ }
+ finally
+ {
+ // decrement how many threads are running
+ LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
+
+ // this position is no longer being generated
+ LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
+ }
+ }// run
+
+
+ }
+
+
+ /**
+ * Stops the current genThreads if they are running
+ * and then recreates the Executor 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 restartExecutorService()
+ {
+ if (genThreads != null && !genThreads.isShutdown())
+ {
+ genThreads.shutdownNow();
+ }
+ genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java
new file mode 100644
index 000000000..e97498c7c
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java
@@ -0,0 +1,209 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.builders.worldGeneration;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.objects.PosToGenerateContainer;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.render.LodRenderer;
+import com.seibel.lod.core.util.DetailDistanceUtil;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodThreadFactory;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+
+/**
+ * A singleton that handles all long distance LOD world generation.
+ * @author Leonardo Amato
+ * @author James Seibel
+ * @version 9-25-2021
+ */
+public class LodWorldGenerator
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+ private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+
+ /** This holds the thread used to create LOD generation requests off the main thread. */
+ private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
+
+ /** we only want to queue up one generator thread at a time */
+ private boolean generatorThreadRunning = false;
+
+ /**
+ * How many chunks to generate outside 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;
+
+ /**
+ * This keeps track of how many chunk generation requests are on going. This is
+ * to limit how many chunks are queued at once. To prevent chunks from being
+ * generated for a long time in an area the player is no longer in.
+ */
+ public final AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
+
+ public final Set positionsWaitingToBeGenerated = new HashSet<>();
+
+ /**
+ * Singleton copy of this object
+ */
+ public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
+
+
+
+ private LodWorldGenerator()
+ {
+
+ }
+
+ /**
+ * Queues up LodNodeGenWorkers for the given lodDimension.
+ * @param renderer needed so the LodNodeGenWorkers can flag that the
+ * buffers need to be rebuilt.
+ */
+ public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
+ {
+ if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE
+ && !generatorThreadRunning
+ && MC.hasSinglePlayerServer())
+ {
+ // the thread is now running, don't queue up another thread
+ generatorThreadRunning = true;
+
+ // just in case the config changed
+ maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8;
+
+ Thread generatorThread = new Thread(() ->
+ {
+ try
+ {
+ // round the player's block position down to the nearest chunk BlockPos
+ int playerPosX = MC.getPlayerBlockPos().getX();
+ int playerPosZ = MC.getPlayerBlockPos().getZ();
+
+
+ //=======================================//
+ // fill in positionsWaitingToBeGenerated //
+ //=======================================//
+
+ IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
+
+ PosToGenerateContainer posToGenerate = lodDim.getPosToGenerate(
+ maxChunkGenRequests,
+ playerPosX,
+ playerPosZ);
+
+
+ byte detailLevel;
+ int posX;
+ int posZ;
+ int nearIndex = 0;
+ int farIndex = 0;
+
+ for (int i = 0; i < posToGenerate.getNumberOfPos(); i++)
+ {
+ // I wish there was a way to compress this code, but I'm not aware of
+ // an easy way to do so.
+
+ // add the near positions
+ if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
+ {
+ detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
+ posX = posToGenerate.getNthPosX(nearIndex, true);
+ posZ = posToGenerate.getNthPosZ(nearIndex, true);
+ nearIndex++;
+
+ AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
+
+ // prevent generating the same chunk multiple times
+ if (positionsWaitingToBeGenerated.contains(chunkPos))
+ continue;
+
+ // don't add more to the generation queue then allowed
+ if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
+ break;
+
+ positionsWaitingToBeGenerated.add(chunkPos);
+ numberOfChunksWaitingToGenerate.addAndGet(1);
+ LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
+ genWorker.queueWork();
+ }
+
+
+ // add the far positions
+ if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
+ {
+ detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
+ posX = posToGenerate.getNthPosX(farIndex, false);
+ posZ = posToGenerate.getNthPosZ(farIndex, false);
+ farIndex++;
+
+ AbstractChunkPosWrapper chunkPos = WRAPPER_FACTORY.createChunkPos(LevelPosUtil.getChunkPos(detailLevel, posX), LevelPosUtil.getChunkPos(detailLevel, posZ));
+
+ // don't add more to the generation queue then allowed
+ if (numberOfChunksWaitingToGenerate.get() >= maxChunkGenRequests)
+ continue;
+ //break;
+
+ // prevent generating the same chunk multiple times
+ if (positionsWaitingToBeGenerated.contains(chunkPos))
+ continue;
+
+ positionsWaitingToBeGenerated.add(chunkPos);
+ numberOfChunksWaitingToGenerate.addAndGet(1);
+ LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
+ genWorker.queueWork();
+ }
+ }
+
+ }
+ catch (Exception e)
+ {
+ // this shouldn't ever happen, but just in case
+ e.printStackTrace();
+ }
+ finally
+ {
+ generatorThreadRunning = false;
+ }
+ });
+
+ mainGenThread.execute(generatorThread);
+ } // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
+ } // queueGenerationRequests
+
+}
diff --git a/src/main/java/com/seibel/lod/core/dataFormat/BlockDataFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/BlockDataFormat.java
new file mode 100644
index 000000000..d8becb6f5
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/dataFormat/BlockDataFormat.java
@@ -0,0 +1,5 @@
+package com.seibel.lod.core.dataFormat;
+
+public class BlockDataFormat
+{
+}
diff --git a/src/main/java/com/seibel/lod/core/dataFormat/ColorFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/ColorFormat.java
new file mode 100644
index 000000000..750fcde64
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/dataFormat/ColorFormat.java
@@ -0,0 +1,5 @@
+package com.seibel.lod.core.dataFormat;
+
+public class ColorFormat
+{
+}
diff --git a/src/main/java/com/seibel/lod/core/dataFormat/DepthHeightFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/DepthHeightFormat.java
new file mode 100644
index 000000000..3573abec2
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/dataFormat/DepthHeightFormat.java
@@ -0,0 +1,5 @@
+package com.seibel.lod.core.dataFormat;
+
+public class DepthHeightFormat
+{
+}
diff --git a/src/main/java/com/seibel/lod/core/dataFormat/LightFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/LightFormat.java
new file mode 100644
index 000000000..188a76cdb
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/dataFormat/LightFormat.java
@@ -0,0 +1,5 @@
+package com.seibel.lod.core.dataFormat;
+
+public class LightFormat
+{
+}
diff --git a/src/main/java/com/seibel/lod/core/dataFormat/PositionDataFormat.java b/src/main/java/com/seibel/lod/core/dataFormat/PositionDataFormat.java
new file mode 100644
index 000000000..8f8940ebe
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/dataFormat/PositionDataFormat.java
@@ -0,0 +1,5 @@
+package com.seibel.lod.core.dataFormat;
+
+public class PositionDataFormat
+{
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/LodDirection.java b/src/main/java/com/seibel/lod/core/enums/LodDirection.java
new file mode 100644
index 000000000..2467ed14b
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/LodDirection.java
@@ -0,0 +1,529 @@
+package com.seibel.lod.core.enums;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import com.seibel.lod.core.objects.math.Vec3i;
+
+/**
+ * A (almost) exact copy of Minecraft's
+ * Direction enum.
+ *
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public enum LodDirection
+{
+ DOWN(0, 1, -1, "down", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.Y, new Vec3i(0, -1, 0)),
+ UP(1, 0, -1, "up", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Y, new Vec3i(0, 1, 0)),
+ NORTH(2, 3, 2, "north", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.Z, new Vec3i(0, 0, -1)),
+ SOUTH(3, 2, 0, "south", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.Z, new Vec3i(0, 0, 1)),
+ WEST(4, 5, 1, "west", LodDirection.AxisDirection.NEGATIVE, LodDirection.Axis.X, new Vec3i(-1, 0, 0)),
+ EAST(5, 4, 3, "east", LodDirection.AxisDirection.POSITIVE, LodDirection.Axis.X, new Vec3i(1, 0, 0));
+
+// private final int data3d;
+// private final int oppositeIndex;
+// private final int data2d;
+
+ private final String name;
+ private final LodDirection.Axis axis;
+ private final LodDirection.AxisDirection axisDirection;
+ private final Vec3i normal;
+ private static final LodDirection[] VALUES = values();
+
+ private static final Map BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(LodDirection::getName, (p_199787_0_) ->
+ {
+ return p_199787_0_;
+ }));
+
+// private static final LodDirection[] BY_3D_DATA = Arrays.stream(VALUES).sorted(Comparator.comparingInt((p_199790_0_) ->
+// {
+// return p_199790_0_.data3d;
+// })).toArray((p_199788_0_) ->
+// {
+// return new LodDirection[p_199788_0_];
+// });
+//
+// private static final LodDirection[] BY_2D_DATA = Arrays.stream(VALUES).filter((p_199786_0_) ->
+// {
+// return p_199786_0_.getAxis().isHorizontal();
+// }).sorted(Comparator.comparingInt((p_199789_0_) ->
+// {
+// return p_199789_0_.data2d;
+// })).toArray((p_199791_0_) ->
+// {
+// return new LodDirection[p_199791_0_];
+// });
+
+// private static final Long2ObjectMap BY_NORMAL = Arrays.stream(VALUES).collect(Collectors.toMap((p_218385_0_) ->
+// {
+// return (new BlockPos(p_218385_0_.getNormal())).asLong();
+// }, (p_218384_0_) ->
+// {
+// return p_218384_0_;
+// }, (p_218386_0_, p_218386_1_) ->
+// {
+// throw new IllegalArgumentException("Duplicate keys");
+// }, Long2ObjectOpenHashMap::new));
+
+
+
+ LodDirection(int p_i46016_3_, int p_i46016_4_, int p_i46016_5_, String p_i46016_6_, LodDirection.AxisDirection p_i46016_7_, LodDirection.Axis p_i46016_8_, Vec3i p_i46016_9_)
+ {
+// this.data3d = p_i46016_3_;
+// this.data2d = p_i46016_5_;
+// this.oppositeIndex = p_i46016_4_;
+ this.name = p_i46016_6_;
+ this.axis = p_i46016_8_;
+ this.axisDirection = p_i46016_7_;
+ this.normal = p_i46016_9_;
+ }
+
+
+
+
+// public static LodDirection[] orderedByNearest(Entity p_196054_0_)
+// {
+// float f = p_196054_0_.getViewXRot(1.0F) * ((float) Math.PI / 180F);
+// float f1 = -p_196054_0_.getViewYRot(1.0F) * ((float) Math.PI / 180F);
+// float f2 = MathHelper.sin(f);
+// float f3 = MathHelper.cos(f);
+// float f4 = MathHelper.sin(f1);
+// float f5 = MathHelper.cos(f1);
+// boolean flag = f4 > 0.0F;
+// boolean flag1 = f2 < 0.0F;
+// boolean flag2 = f5 > 0.0F;
+// float f6 = flag ? f4 : -f4;
+// float f7 = flag1 ? -f2 : f2;
+// float f8 = flag2 ? f5 : -f5;
+// float f9 = f6 * f3;
+// float f10 = f8 * f3;
+// LodDirection lodDirection = flag ? EAST : WEST;
+// LodDirection direction1 = flag1 ? UP : DOWN;
+// LodDirection direction2 = flag2 ? SOUTH : NORTH;
+// if (f6 > f8)
+// {
+// if (f7 > f9)
+// {
+// return makeDirectionArray(direction1, lodDirection, direction2);
+// }
+// else
+// {
+// return f10 > f7 ? makeDirectionArray(lodDirection, direction2, direction1) : makeDirectionArray(lodDirection, direction1, direction2);
+// }
+// }
+// else if (f7 > f10)
+// {
+// return makeDirectionArray(direction1, direction2, lodDirection);
+// }
+// else
+// {
+// return f9 > f7 ? makeDirectionArray(direction2, lodDirection, direction1) : makeDirectionArray(direction2, direction1, lodDirection);
+// }
+// }
+
+// private static LodDirection[] makeDirectionArray(LodDirection p_196053_0_, LodDirection p_196053_1_, LodDirection p_196053_2_)
+// {
+// return new LodDirection[] { p_196053_0_, p_196053_1_, p_196053_2_, p_196053_2_.getOpposite(), p_196053_1_.getOpposite(), p_196053_0_.getOpposite() };
+// }
+
+// public static LodDirection rotate(Mat4f p_229385_0_, LodDirection p_229385_1_)
+// {
+// Vec3i Vec3i = p_229385_1_.getNormal();
+// Vector4f vector4f = new Vector4f(Vec3i.getX(), Vec3i.getY(), Vec3i.getZ(), 0.0F);
+// vector4f.transform(p_229385_0_);
+// return getNearest(vector4f.x(), vector4f.y(), vector4f.z());
+// }
+
+// public Quaternion getRotation()
+// {
+// Quaternion quaternion = Vector3f.XP.rotationDegrees(90.0F);
+// switch (this)
+// {
+// case DOWN:
+// return Vector3f.XP.rotationDegrees(180.0F);
+// case UP:
+// return Quaternion.ONE.copy();
+// case NORTH:
+// quaternion.mul(Vector3f.ZP.rotationDegrees(180.0F));
+// return quaternion;
+// case SOUTH:
+// return quaternion;
+// case WEST:
+// quaternion.mul(Vector3f.ZP.rotationDegrees(90.0F));
+// return quaternion;
+// case EAST:
+// default:
+// quaternion.mul(Vector3f.ZP.rotationDegrees(-90.0F));
+// return quaternion;
+// }
+// }
+
+// public int get3DDataValue()
+// {
+// return this.data3d;
+// }
+//
+// public int get2DDataValue()
+// {
+// return this.data2d;
+// }
+
+ public LodDirection.AxisDirection getAxisDirection()
+ {
+ return this.axisDirection;
+ }
+
+// public LodDirection getOpposite()
+// {
+// return from3DDataValue(this.oppositeIndex);
+// }
+
+ public LodDirection getClockWise()
+ {
+ switch (this)
+ {
+ case NORTH:
+ return EAST;
+ case SOUTH:
+ return WEST;
+ case WEST:
+ return NORTH;
+ case EAST:
+ return SOUTH;
+ default:
+ throw new IllegalStateException("Unable to get Y-rotated facing of " + this);
+ }
+ }
+
+ public LodDirection getCounterClockWise()
+ {
+ switch (this)
+ {
+ case NORTH:
+ return WEST;
+ case SOUTH:
+ return EAST;
+ case WEST:
+ return SOUTH;
+ case EAST:
+ return NORTH;
+ default:
+ throw new IllegalStateException("Unable to get CCW facing of " + this);
+ }
+ }
+
+ public String getName()
+ {
+ return this.name;
+ }
+
+ public LodDirection.Axis getAxis()
+ {
+ return this.axis;
+ }
+
+ public static LodDirection byName(String name)
+ {
+ return name == null ? null : BY_NAME.get(name.toLowerCase(Locale.ROOT));
+ }
+
+// public static LodDirection from3DDataValue(int p_82600_0_)
+// {
+// return BY_3D_DATA[MathHelper.abs(p_82600_0_ % BY_3D_DATA.length)];
+// }
+//
+// public static LodDirection from2DDataValue(int p_176731_0_)
+// {
+// return BY_2D_DATA[MathHelper.abs(p_176731_0_ % BY_2D_DATA.length)];
+// }
+
+// @Nullable
+// public static LodDirection fromNormal(int p_218383_0_, int p_218383_1_, int p_218383_2_)
+// {
+// return BY_NORMAL.get(BlockPos.asLong(p_218383_0_, p_218383_1_, p_218383_2_));
+// }
+
+// public static LodDirection fromYRot(double p_176733_0_)
+// {
+// return from2DDataValue(MathHelper.floor(p_176733_0_ / 90.0D + 0.5D) & 3);
+// }
+
+ public static LodDirection fromAxisAndDirection(LodDirection.Axis p_211699_0_, LodDirection.AxisDirection p_211699_1_)
+ {
+ switch (p_211699_0_)
+ {
+ case X:
+ return p_211699_1_ == LodDirection.AxisDirection.POSITIVE ? EAST : WEST;
+ case Y:
+ return p_211699_1_ == LodDirection.AxisDirection.POSITIVE ? UP : DOWN;
+ case Z:
+ default:
+ return p_211699_1_ == LodDirection.AxisDirection.POSITIVE ? SOUTH : NORTH;
+ }
+ }
+
+// public float toYRot()
+// {
+// return (this.data2d & 3) * 90;
+// }
+
+// public static LodDirection getRandom(Random p_239631_0_)
+// {
+// return Util.getRandom(VALUES, p_239631_0_);
+// }
+
+// public static LodDirection getNearest(double p_210769_0_, double p_210769_2_, double p_210769_4_)
+// {
+// return getNearest((float) p_210769_0_, (float) p_210769_2_, (float) p_210769_4_);
+// }
+
+// public static LodDirection getNearest(float p_176737_0_, float p_176737_1_, float p_176737_2_)
+// {
+// LodDirection lodDirection = NORTH;
+// float f = Float.MIN_VALUE;
+//
+// for (LodDirection direction1 : VALUES)
+// {
+// float f1 = p_176737_0_ * direction1.normal.x + p_176737_1_ * direction1.normal.y + p_176737_2_ * direction1.normal.z;
+// if (f1 > f)
+// {
+// f = f1;
+// lodDirection = direction1;
+// }
+// }
+//
+// return lodDirection;
+// }
+
+ public static LodDirection get(LodDirection.AxisDirection p_181076_0_, LodDirection.Axis p_181076_1_)
+ {
+ for (LodDirection lodDirection : VALUES)
+ {
+ if (lodDirection.getAxisDirection() == p_181076_0_ && lodDirection.getAxis() == p_181076_1_)
+ {
+ return lodDirection;
+ }
+ }
+
+ throw new IllegalArgumentException("No such direction: " + p_181076_0_ + " " + p_181076_1_);
+ }
+
+ public Vec3i getNormal()
+ {
+ return this.normal;
+ }
+
+// public boolean isFacingAngle(float p_243532_1_)
+// {
+// float f = p_243532_1_ * ((float) Math.PI / 180F);
+// float f1 = -MathHelper.sin(f);
+// float f2 = MathHelper.cos(f);
+// return this.normal.getX() * f1 + this.normal.getZ() * f2 > 0.0F;
+// }
+
+ public enum Axis implements Predicate
+ {
+ X("x")
+ {
+ @Override
+ public int choose(int x, int y, int z)
+ {
+ return x;
+ }
+
+ @Override
+ public double choose(double x, double y, double z)
+ {
+ return x;
+ }
+ },
+ Y("y")
+ {
+ @Override
+ public int choose(int x, int y, int z)
+ {
+ return y;
+ }
+
+ @Override
+ public double choose(double x, double y, double z)
+ {
+ return y;
+ }
+ },
+ Z("z")
+ {
+ @Override
+ public int choose(int x, int y, int z)
+ {
+ return z;
+ }
+
+ @Override
+ public double choose(double x, double y, double z)
+ {
+ return z;
+ }
+ };
+
+ private static final LodDirection.Axis[] VALUES = values();
+
+ private static final Map BY_NAME = Arrays.stream(VALUES).collect(Collectors.toMap(LodDirection.Axis::getName, (p_199785_0_) ->
+ {
+ return p_199785_0_;
+ }));
+ private final String name;
+
+ Axis(String name)
+ {
+ this.name = name;
+ }
+
+ public static LodDirection.Axis byName(String name)
+ {
+ return BY_NAME.get(name.toLowerCase(Locale.ROOT));
+ }
+
+ public String getName()
+ {
+ return this.name;
+ }
+
+ public boolean isVertical()
+ {
+ return this == Y;
+ }
+
+ public boolean isHorizontal()
+ {
+ return this == X || this == Z;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.name;
+ }
+
+// public static LodDirection.Axis getRandom(Random p_239634_0_)
+// {
+// return Util.getRandom(VALUES, p_239634_0_);
+// }
+
+ @Override
+ public boolean test(LodDirection p_test_1_)
+ {
+ return p_test_1_ != null && p_test_1_.getAxis() == this;
+ }
+
+// public LodDirection.Plane getPlane()
+// {
+// switch (this)
+// {
+// case X:
+// case Z:
+// return LodDirection.Plane.HORIZONTAL;
+// case Y:
+// return LodDirection.Plane.VERTICAL;
+// default:
+// throw new Error("Someone's been tampering with the universe!");
+// }
+// }
+
+ public abstract int choose(int p_196052_1_, int p_196052_2_, int p_196052_3_);
+
+ public abstract double choose(double p_196051_1_, double p_196051_3_, double p_196051_5_);
+ }
+
+ public enum AxisDirection
+ {
+ POSITIVE(1, "Towards positive"),
+ NEGATIVE(-1, "Towards negative");
+
+ private final int step;
+ private final String name;
+
+ AxisDirection(int newStep, String newName)
+ {
+ this.step = newStep;
+ this.name = newName;
+ }
+
+ public int getStep()
+ {
+ return this.step;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.name;
+ }
+
+ public LodDirection.AxisDirection opposite()
+ {
+ return this == POSITIVE ? NEGATIVE : POSITIVE;
+ }
+ }
+
+// public static enum Plane implements Iterable, Predicate
+// {
+// HORIZONTAL(new LodDirection[] { LodDirection.NORTH, LodDirection.EAST, LodDirection.SOUTH, LodDirection.WEST }, new LodDirection.Axis[] { LodDirection.Axis.X, LodDirection.Axis.Z }),
+// VERTICAL(new LodDirection[] { LodDirection.UP, LodDirection.DOWN }, new LodDirection.Axis[] { LodDirection.Axis.Y });
+//
+// private final LodDirection[] faces;
+// private final LodDirection.Axis[] axis;
+//
+// private Plane(LodDirection[] p_i49393_3_, LodDirection.Axis[] p_i49393_4_)
+// {
+// this.faces = p_i49393_3_;
+// this.axis = p_i49393_4_;
+// }
+//
+// public LodDirection getRandomDirection(Random p_179518_1_)
+// {
+// return Util.getRandom(this.faces, p_179518_1_);
+// }
+//
+// public LodDirection.Axis getRandomAxis(Random p_244803_1_)
+// {
+// return Util.getRandom(this.axis, p_244803_1_);
+// }
+//
+// @Override
+// public boolean test(@Nullable LodDirection p_test_1_)
+// {
+// return p_test_1_ != null && p_test_1_.getAxis().getPlane() == this;
+// }
+//
+// @Override
+// public Iterator iterator()
+// {
+// return Iterators.forArray(this.faces);
+// }
+//
+// public Stream stream()
+// {
+// return Arrays.stream(this.faces);
+// }
+// }
+
+
+
+
+ public String getSerializedName()
+ {
+ return this.name;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.name;
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/WorldType.java b/src/main/java/com/seibel/lod/core/enums/WorldType.java
new file mode 100644
index 000000000..a36b06194
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/WorldType.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums;
+
+/**
+ * ServerWorld, ClientWorld, Unknown
+ *
+ * @author James Seibel
+ * @version 11-12-2021
+ */
+public enum WorldType
+{
+ ServerWorld,
+ ClientWorld,
+ Unknown
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java b/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java
new file mode 100644
index 000000000..bdfcaf363
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java
@@ -0,0 +1,47 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * heightmap
+ * multi_lod
+ *
+ * @author Leonardo Amato
+ * @version 11-16-2021
+ */
+public enum BlocksToAvoid
+{
+ NONE(false, false),
+
+ NON_FULL(true, false),
+
+ NO_COLLISION(false, true),
+
+ BOTH(true, true);
+
+ public final boolean nonFull;
+ public final boolean noCollision;
+
+ BlocksToAvoid(boolean nonFull, boolean noCollision)
+ {
+ this.nonFull = nonFull;
+ this.noCollision = noCollision;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java b/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java
new file mode 100644
index 000000000..3c5db0390
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * FREQUENT
+ * NORMAL
+ * RARE
+ *
+ * Determines how fast the buffers need to be regenerated
+ *
+ * @author Leonardo Amato
+ * @version 9-25-2021
+ */
+public enum BufferRebuildTimes
+{
+ FREQUENT(1000, 500, 2500, 1),
+
+ NORMAL(2000, 1000, 5000, 4),
+
+ RARE(5000, 2000, 10000, 16);
+
+ public final int playerMoveTimeout;
+ public final int renderedChunkTimeout;
+ public final int chunkChangeTimeout;
+ public final int playerMoveDistance;
+
+ BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout, int playerMoveDistance)
+ {
+ this.playerMoveTimeout = playerMoveTimeout;
+ this.renderedChunkTimeout = renderedChunkTimeout;
+ this.chunkChangeTimeout = chunkChangeTimeout;
+ this.playerMoveDistance = playerMoveDistance;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java b/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java
new file mode 100644
index 000000000..712de3cc3
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java
@@ -0,0 +1,95 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * NONE
+ * BIOME_ONLY
+ * BIOME_ONLY_SIMULATE_HEIGHT
+ * SURFACE
+ * FEATURES
+ * SERVER
+ *
+ * In order of fastest to slowest.
+ *
+ * @author James Seibel
+ * @author Leonardo Amato
+ * @version 8-7-2021
+ */
+public enum DistanceGenerationMode
+{
+ /**
+ * Don't generate anything
+ */
+ NONE((byte) 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((byte) 1),
+
+ /**
+ * Same as BIOME_ONLY, except instead
+ * of always using sea level as the LOD height
+ * different biome types (mountain, ocean, forest, etc.)
+ * use predetermined heights to simulate having height data.
+ */
+ BIOME_ONLY_SIMULATE_HEIGHT((byte) 2),
+
+ /**
+ * Generate the world surface,
+ * this does NOT include caves, trees,
+ * or structures.
+ * Multithreaded - Faster (10-20 ms)
+ */
+ SURFACE((byte) 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((byte) 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 on a pre-existing world.
+ * Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
+ */
+ FULL((byte) 5);
+
+
+ /**
+ * The higher the number the more complete the generation is.
+ */
+ public final byte complexity;
+
+ DistanceGenerationMode(byte complexity)
+ {
+ this.complexity = complexity;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java b/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java
new file mode 100644
index 000000000..af2089170
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * AUTO
+ * Near_First
+ * Far_First
+ *
+ * Determines which LODs should have priority when generating
+ * outside the normal view distance.
+ *
+ * @author Leonardo Amato
+ * @version 12-1-2021
+ */
+public enum GenerationPriority
+{
+ /** NEAR_FIRST when connected to servers and FAR_FIRST when on single player */
+ AUTO,
+
+ NEAR_FIRST,
+
+ FAR_FIRST
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java b/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java
new file mode 100644
index 000000000..4d36d676a
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java
@@ -0,0 +1,59 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * Auto, Buffer_Storage, Sub_Data, Buffer_Mapping, Data
+ *
+ * @author James Seibel
+ * @version 12-1-2021
+ */
+public enum GpuUploadMethod
+{
+ /** Picks the best option based on the GPU the user has. */
+ AUTO,
+
+ /**
+ * Default for NVIDIA if OpenGL 4.5 is supported.
+ * Fast rendering, no stuttering.
+ */
+ BUFFER_STORAGE,
+
+ /**
+ * Backup option for NVIDIA.
+ * Fast rendering but may stutter when uploading.
+ */
+ SUB_DATA,
+
+ /**
+ * Default option for AMD/Intel.
+ * May end up storing buffers in System memory.
+ * Fast rending if in GPU memory, slow if in system memory,
+ * but won't stutter when uploading.
+ */
+ BUFFER_MAPPING,
+
+ /**
+ * Backup option for AMD/Intel.
+ * Fast rendering but may stutter when uploading.
+ */
+ DATA,
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java b/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java
new file mode 100644
index 000000000..e91ed0f68
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java
@@ -0,0 +1,53 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * Lowest
+ * Low
+ * Medium
+ * High
+ *
+ * this indicates the base of the quadratic function we use for the quality drop off
+ *
+ * @author Leonardo Amato
+ * @version 9-29-2021
+ */
+public enum HorizontalQuality
+{
+ /** 1.0 AKA Linear */
+ LOWEST(1.0f),
+
+ /** exponent 1.5 */
+ LOW(1.5f),
+
+ /** exponent 2.0 */
+ MEDIUM(2.0f),
+
+ /** exponent 2.2 */
+ HIGH(2.2f);
+
+ public final double quadraticBase;
+
+ HorizontalQuality(double distanceUnit)
+ {
+ this.quadraticBase = distanceUnit;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java b/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java
new file mode 100644
index 000000000..794b67f71
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java
@@ -0,0 +1,175 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * chunk
+ * half_chunk
+ * four_blocks
+ * two_blocks
+ * block
+ *
+ * @author James Seibel
+ * @author Leonardo Amato
+ * @version 9-25-2021
+ */
+public enum HorizontalResolution
+{
+ /** render 256 LODs for each chunk */
+ BLOCK(16, 0),
+
+ /** render 64 LODs for each chunk */
+ TWO_BLOCKS(8, 1),
+
+ /** render 16 LODs for each chunk */
+ FOUR_BLOCKS(4, 2),
+
+ /** render 4 LODs for each chunk */
+ HALF_CHUNK(2, 3),
+
+ /** render 1 LOD for each chunk */
+ CHUNK(1, 4);
+
+
+
+
+
+ /**
+ * How many DataPoints should
+ * be drawn per side, per LodChunk
+ */
+ public final int dataPointLengthCount;
+
+ /** How wide each LOD DataPoint is */
+ public final int dataPointWidth;
+
+ /**
+ * This is the same as detailLevel in LodQuadTreeNode,
+ * lowest is 0 highest is 9
+ */
+ public final byte detailLevel;
+
+ /* Start/End X/Z give the block positions
+ * for each individual dataPoint in a LodChunk */
+ public final int[] startX;
+ public final int[] startZ;
+
+ public final int[] endX;
+ public final int[] endZ;
+
+
+ /**
+ * 1st dimension: LodDetail.detailLevel
+ * 2nd dimension: An array of all LodDetails that are less than or
+ * equal to that detailLevel
+ */
+ private static HorizontalResolution[][] lowerDetailArrays;
+
+
+
+
+ HorizontalResolution(int newLengthCount, int newDetailLevel)
+ {
+ detailLevel = (byte) newDetailLevel;
+ dataPointLengthCount = newLengthCount;
+ dataPointWidth = 16 / dataPointLengthCount;
+
+ startX = new int[dataPointLengthCount * dataPointLengthCount];
+ endX = new int[dataPointLengthCount * dataPointLengthCount];
+
+ startZ = new int[dataPointLengthCount * dataPointLengthCount];
+ endZ = new int[dataPointLengthCount * dataPointLengthCount];
+
+
+ int index = 0;
+ for (int x = 0; x < newLengthCount; x++)
+ {
+ for (int z = 0; z < newLengthCount; z++)
+ {
+ startX[index] = x * dataPointWidth;
+ startZ[index] = z * dataPointWidth;
+
+ endX[index] = (x * dataPointWidth) + dataPointWidth;
+ endZ[index] = (z * dataPointWidth) + dataPointWidth;
+
+ index++;
+ }
+ }
+
+ }// constructor
+
+
+
+
+
+
+ /**
+ * Returns an array of all LodDetails that have a detail level
+ * that is less than or equal to the given LodDetail
+ */
+ public static HorizontalResolution[] getSelfAndLowerDetails(HorizontalResolution detail)
+ {
+ if (lowerDetailArrays == null)
+ {
+ // run first time setup
+ lowerDetailArrays = new HorizontalResolution[HorizontalResolution.values().length][];
+
+ // go through each LodDetail
+ for (HorizontalResolution currentDetail : HorizontalResolution.values())
+ {
+ ArrayList lowerDetails = new ArrayList<>();
+
+ // find the details lower than currentDetail
+ for (HorizontalResolution compareDetail : HorizontalResolution.values())
+ {
+ if (currentDetail.detailLevel <= compareDetail.detailLevel)
+ {
+ lowerDetails.add(compareDetail);
+ }
+ }
+
+ // have the highest detail item first in the list
+ Collections.sort(lowerDetails);
+ Collections.reverse(lowerDetails);
+
+ lowerDetailArrays[currentDetail.detailLevel] = lowerDetails.toArray(new HorizontalResolution[lowerDetails.size()]);
+ }
+ }
+
+ return lowerDetailArrays[detail.detailLevel];
+ }
+
+ /** Returns what detail level should be used at a given distance and maxDistance. */
+ public static HorizontalResolution getDetailForDistance(HorizontalResolution maxDetailLevel, int distance, int maxDistance)
+ {
+ HorizontalResolution[] lowerDetails = getSelfAndLowerDetails(maxDetailLevel);
+ int distanceBetweenDetails = maxDistance / lowerDetails.length;
+ int index = LodUtil.clamp(0, distance / distanceBetweenDetails, lowerDetails.length - 1);
+
+ return lowerDetails[index];
+
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java b/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java
new file mode 100644
index 000000000..e710de006
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * Low
+ * Medium
+ * High
+ *
+ * this is a quality scale for the detail drop-off
+ *
+ * @author Leonardo Amato
+ * @version 9-25-2021
+ */
+public enum HorizontalScale
+{
+ /** Lods are 2D with heightMap */
+ LOW(64),
+
+ /** Lods expand in three dimension */
+ MEDIUM(128),
+
+ /** Lods expand in three dimension */
+ HIGH(256);
+
+ public final int distanceUnit;
+
+ HorizontalScale(int distanceUnit)
+ {
+ this.distanceUnit = distanceUnit;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java b/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java
new file mode 100644
index 000000000..397699c7d
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java
@@ -0,0 +1,62 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
+import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
+import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
+import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.TriangularLodTemplate;
+
+/**
+ * Cubic, Triangular, Dynamic
+ *
+ * @author James Seibel
+ * @version 10-10-2021
+ */
+public enum LodTemplate
+{
+ /**
+ * LODs are rendered as
+ * rectangular prisms.
+ */
+ CUBIC(new CubicLodTemplate()),
+
+ /**
+ * LODs smoothly transition between
+ * each other.
+ */
+ TRIANGULAR(new TriangularLodTemplate()),
+
+ /**
+ * LODs smoothly transition between
+ * each other, unless a neighboring LOD
+ * is at a significantly different height.
+ */
+ DYNAMIC(new DynamicLodTemplate());
+
+
+ public final AbstractLodTemplate template;
+
+ LodTemplate(AbstractLodTemplate newTemplate)
+ {
+ template = newTemplate;
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java b/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java
new file mode 100644
index 000000000..1971d51bb
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java
@@ -0,0 +1,22 @@
+package com.seibel.lod.core.enums.config;
+
+/**
+ * NONE, GAME_SHADING
+ *
+ * @author James Seibel
+ * @version 7-25-2020
+ */
+public enum ShadingMode
+{
+ /**
+ * LODs will have darker sides and bottoms to simulate
+ * Minecraft's fast lighting.
+ */
+ GAME_SHADING,
+
+ /**
+ * LODs will use ambient occlusion to mimic Minecarft's
+ * Fancy lighting.
+ */
+ AMBIENT_OCCLUSION
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java b/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java
new file mode 100644
index 000000000..e28552893
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * None, Dynamic, Always
+ *
+ *
+ * This represents how far the LODs should overlap with
+ * the vanilla Minecraft terrain.
+ *
+ * @author James Seibel
+ * @version 10-11-2021
+ */
+public enum VanillaOverdraw
+{
+ /** Never draw LODs where a minecraft chunk could be. */
+ NEVER,
+
+ /** Draw LODs over the farther minecraft chunks. */
+ DYNAMIC,
+
+ /** Draw LODs over all minecraft chunks. */
+ ALWAYS,
+
+ /** Draw LODs over border chunks. */
+ BORDER,
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java b/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java
new file mode 100644
index 000000000..9d82f9486
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java
@@ -0,0 +1,80 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.config;
+
+/**
+ * heightmap
+ * multi_lod
+ *
+ * @author Leonardo Amato
+ * @version 10-07-2021
+ */
+public enum VerticalQuality
+{
+ LOW(
+ new int[] { 2,
+ 2,
+ 2,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1 }
+ ),
+
+ MEDIUM(
+ new int[] { 4,
+ 4,
+ 2,
+ 2,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1 }
+ ),
+
+ HIGH(
+ new int[] {
+ 8,
+ 8,
+ 4,
+ 4,
+ 2,
+ 2,
+ 2,
+ 1,
+ 1,
+ 1,
+ 1 }
+ );
+
+ public final int[] maxVerticalData;
+
+ VerticalQuality(int[] maxVerticalData)
+ {
+ this.maxVerticalData = maxVerticalData;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java
new file mode 100644
index 000000000..cdc02fa1f
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java
@@ -0,0 +1,54 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.rendering;
+
+/**
+ * off, detail, detail wireframe
+ *
+ * @author James Seibel
+ * @version 8-28-2021
+ */
+public enum DebugMode
+{
+ /** LODs are rendered normally */
+ OFF,
+
+ /** LOD colors are based on their detail */
+ SHOW_DETAIL,
+
+ /** LOD colors are based on their detail, and draws in wireframe. */
+ SHOW_DETAIL_WIREFRAME;
+
+ /** used when cycling through the different modes */
+ private DebugMode next;
+
+ static
+ {
+ OFF.next = SHOW_DETAIL;
+ SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME;
+ SHOW_DETAIL_WIREFRAME.next = OFF;
+ }
+
+ /** returns the next debug mode */
+ public DebugMode getNext()
+ {
+ return this.next;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/FogColorMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/FogColorMode.java
new file mode 100644
index 000000000..14b68004c
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/rendering/FogColorMode.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.rendering;
+
+/**
+ * USE_DEFAULT_FOG_COLOR,
+ * USE_SKY_COLOR,
+ *
+ * @author James Seibel
+ * @version 11-27-2021
+ */
+public enum FogColorMode
+{
+ /** Fog uses Minecraft's fog color. */
+ USE_WORLD_FOG_COLOR,
+
+ /**
+ * Replicates the effect of the clear sky mod.
+ * Making the fog blend in with the sky better
+ * https://www.curseforge.com/minecraft/mc-mods/clear-skies
+ * https://www.curseforge.com/minecraft/mc-mods/clear-skies-forge-port
+ * For it to look good you need one of those mods
+ */
+ USE_SKY_COLOR,
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java b/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java
new file mode 100644
index 000000000..a73443ca4
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.rendering;
+
+/**
+ * NEAR, FAR, or NEAR_AND_FAR.
+ *
+ * @author James Seibel
+ * @version 11-26-2021
+ */
+public enum FogDistance
+{
+ NEAR,
+ FAR,
+ NEAR_AND_FAR
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawMode.java b/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawMode.java
new file mode 100644
index 000000000..bf05d7dbe
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawMode.java
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.rendering;
+
+/**
+ * USE_OPTIFINE_FOG_SETTING,
+ * FOG_ENABLED,
+ * FOG_DISABLED
+ *
+ * @author James Seibel
+ * @version 11-27-2021
+ */
+public enum FogDrawMode
+{
+ /**
+ * Use whatever Fog setting optifine is using.
+ * If optifine isn't installed this defaults to ALWAYS_DRAW_FOG.
+ */
+ USE_OPTIFINE_SETTING,
+
+ FOG_ENABLED,
+ FOG_DISABLED
+}
diff --git a/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java b/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java
new file mode 100644
index 000000000..b83bcc6c3
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.enums.rendering;
+
+/**
+ * Minecraft, Lod_Builder, None
+ *
+ * @author James Seibel
+ * @version 10-1-2021
+ */
+public enum GLProxyContext
+{
+ /** Minecraft's render thread */
+ MINECRAFT,
+
+ /** The context we send buffers to the GPU on */
+ LOD_BUILDER,
+
+ /** A context that can be used for miscellaneous tasks, owned by the GLProxy */
+ PROXY_WORKER,
+
+ /** used to un-bind threads */
+ NONE,
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java b/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java
new file mode 100644
index 000000000..81cb0068e
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java
@@ -0,0 +1,65 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.handlers;
+
+/**
+ *
+ * @author Cola
+ * @author Leonardo Amato
+ * @version 11-12-2021
+ */
+public class ChunkFileLoader
+{
+ // TODO
+// public static IChunk getChunkFromFile(ChunkPos pos)
+// {
+// LevelWrapper clientLevel = MinecraftWrapper.INSTANCE.getWrappedClientLevel();
+// if (clientLevel == null)
+// return null;
+// WorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(clientLevel.getDimensionType());
+// try
+// {
+// File file = new File(serverWorld.getSaveFolder().getParent() + File.separatorChar + "region", "r." + (pos.x >> 5) + "." + (pos.z >> 5) + ".mca");
+// if(!file.exists())
+// return null;
+// IChunk loadedChunk = ChunkSerializer.read(
+// serverWorld,
+// serverWorld.getStructureManager(),
+// serverWorld.getPoiManager(),
+// pos,
+// serverWorld.getChunkSource().chunkMap.read(pos)
+// );
+// boolean emptyChunk = true;
+// for(int i = 0; i < 16; i++){
+// for(int j = 0; j < 16; j++){
+// emptyChunk &= loadedChunk.isYSpaceEmpty(i,j);
+// }
+// }
+// if(emptyChunk)
+// return null;
+// else
+// return loadedChunk;
+// }
+// catch (Exception e)
+// {
+// return null;
+// }
+// }
+}
diff --git a/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java b/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java
new file mode 100644
index 000000000..6e7aa1c2c
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java
@@ -0,0 +1,58 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.handlers;
+
+import com.seibel.lod.core.enums.rendering.FogDrawMode;
+import com.seibel.lod.core.objects.math.Mat4f;
+
+/**
+ * A singleton used to get variables from methods
+ * where they are private or potentially absent.
+ * Specifically the fog setting used by Optifine or the
+ * presence/absence of other mods.
+ *
+ * This interface doesn't necessarily have to exist, but
+ * it makes using the singleton handler more uniform (always
+ * passing in interfaces), and it may be needed in the future if
+ * we find that reflection handlers need to be different for
+ * different MC versions.
+ *
+ * @author James Seibel
+ * @version 11-26-2021
+ */
+public interface IReflectionHandler
+{
+ /** @returns Whether Optifine is set to render fog or not. */
+ FogDrawMode getFogDrawMode();
+
+ /** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
+ boolean vivecraftPresent();
+
+ /**
+ * Modifies the projection matrix's clip planes.
+ * The projection matrix must be in column-major format.
+ *
+ * @param projectionMatrix The projection matrix to be modified.
+ * @param newNearClipPlane the new near clip plane value.
+ * @param newFarClipPlane the new far clip plane value.
+ * @return The modified matrix.
+ */
+ Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
+}
diff --git a/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java
new file mode 100644
index 000000000..f8e5e1f1e
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java
@@ -0,0 +1,485 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.handlers;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
+import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
+
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.VerticalQuality;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.objects.lod.LodRegion;
+import com.seibel.lod.core.objects.lod.RegionPos;
+import com.seibel.lod.core.objects.lod.VerticalLevelContainer;
+import com.seibel.lod.core.util.LodThreadFactory;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.ThreadMapUtil;
+
+/**
+ * This object handles creating LodRegions
+ * from files and saving LodRegion objects
+ * to file.
+ *
+ * @author James Seibel
+ * @author Cola
+ * @version 9-25-2021
+ */
+public class LodDimensionFileHandler
+{
+ /** This is the dimension that owns this file handler */
+ private LodDimension lodDimension;
+
+ private final File dimensionDataSaveFolder;
+
+ /** lod */
+ private static final String FILE_NAME_PREFIX = "lod";
+ /** .txt */
+ private static final String FILE_EXTENSION = ".xz";
+ /** detail- */
+ private static final String DETAIL_FOLDER_NAME_PREFIX = "detail-";
+
+ /**
+ * .tmp
+ * Added to the end of the file path when saving to prevent
+ * nulling a currently existing file.
+ * After the file finishes saving it will end with
+ * FILE_EXTENSION.
+ */
+ private static final String TMP_FILE_EXTENSION = ".tmp";
+
+ /**
+ * This is the file version currently accepted by this
+ * file handler, older versions (smaller numbers) will be deleted and overwritten,
+ * newer versions (larger numbers) will be ignored and won't be read.
+ */
+ public static final int LOD_SAVE_FILE_VERSION = 7;
+
+ /**
+ * Allow saving asynchronously, but never try to save multiple regions
+ * at a time
+ */
+ private final ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
+
+
+
+
+ public LodDimensionFileHandler(File newSaveFolder, LodDimension newLodDimension)
+ {
+ if (newSaveFolder == null)
+ throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to.");
+
+ dimensionDataSaveFolder = newSaveFolder;
+ lodDimension = newLodDimension;
+ }
+
+
+
+ //================//
+ // read from file //
+ //================//
+
+ /**
+ * Returns the LodRegion at the given coordinates.
+ * Returns an empty region if the file doesn't exist.
+ */
+ public LodRegion loadRegionFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
+ {
+ int regionX = regionPos.x;
+ int regionZ = regionPos.z;
+ LodRegion region = new LodRegion(LodUtil.REGION_DETAIL_LEVEL, regionPos, generationMode, verticalQuality);
+
+ for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
+ {
+ String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
+
+ try
+ {
+ // if the fileName was null that means the folder is inaccessible
+ // for some reason
+ if (fileName == null)
+ throw new IllegalArgumentException("Unable to read region [" + regionX + ", " + regionZ + "] file, no fileName.");
+
+ File file = new File(fileName);
+ if (!file.exists())
+ {
+ //there is no file for current gen mode
+ //search others above current from the most to the least detailed
+ VerticalQuality tempVerticalQuality = VerticalQuality.HIGH;
+ do {
+ DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL;
+ do {
+ fileName = getFileNameAndPathForRegion(regionX, regionZ, tempGenMode, tempDetailLevel, verticalQuality);
+ if (fileName != null)
+ {
+ file = new File(fileName);
+ if (file.exists())
+ break;
+ }
+ //decrease gen mode
+ if (tempGenMode == DistanceGenerationMode.FULL)
+ tempGenMode = DistanceGenerationMode.FEATURES;
+ else if (tempGenMode == DistanceGenerationMode.FEATURES)
+ tempGenMode = DistanceGenerationMode.SURFACE;
+ else if (tempGenMode == DistanceGenerationMode.SURFACE)
+ tempGenMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
+ else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT)
+ tempGenMode = DistanceGenerationMode.BIOME_ONLY;
+ else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY)
+ tempGenMode = DistanceGenerationMode.NONE;
+ } while (tempGenMode != generationMode);
+ if (fileName != null)
+ {
+ file = new File(fileName);
+ if (file.exists())
+ break;
+ }
+ if (tempVerticalQuality == VerticalQuality.HIGH)
+ tempVerticalQuality = VerticalQuality.MEDIUM;
+ else if (tempVerticalQuality == VerticalQuality.MEDIUM)
+ tempVerticalQuality = VerticalQuality.LOW;
+ } while (tempVerticalQuality != verticalQuality);
+ if (!file.exists())
+ //there wasn't a file, don't return anything
+ continue;
+ }
+
+
+
+ // don't try parsing empty files
+ long dataSize = file.length();
+ dataSize -= 1;
+ if (dataSize > 0)
+ {
+ try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
+ {
+ int fileVersion;
+ fileVersion = inputStream.read();
+
+ // check if this file can be read by this file handler
+ if (fileVersion < 6)
+ {
+ // the file we are reading is an older version,
+ // close the reader and delete the file.
+ inputStream.close();
+ file.delete();
+ ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
+ + " version found: " + fileVersion
+ + ", version requested: " + LOD_SAVE_FILE_VERSION
+ + ". File was been deleted.");
+
+ break;
+ }
+ 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 accidentally delete anything the user may want.
+ inputStream.close();
+ ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
+ + " version found: " + fileVersion
+ + ", version requested: " + LOD_SAVE_FILE_VERSION
+ + " this region will not be written to in order to protect the newer file.");
+
+ break;
+ }
+ else if (fileVersion == 6)
+ {
+ //this is old, but readable version
+ byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
+ inputStream.read(data);
+ inputStream.close();
+ // add the data to our region
+ region.addLevelContainer(new VerticalLevelContainer(data, 6));
+ } else
+ {
+ // this file is a readable version,
+ // read the file
+ byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
+ inputStream.read(data);
+ inputStream.close();
+ // add the data to our region
+ region.addLevelContainer(new VerticalLevelContainer(data, LOD_SAVE_FILE_VERSION));
+ }
+ }
+ catch (IOException ioEx)
+ {
+ ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
+ ioEx.printStackTrace();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ // the buffered reader encountered a
+ // problem reading the file
+ ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: ");
+ e.printStackTrace();
+ }
+ }// for each detail level
+
+ if (region.getMinDetailLevel() >= detailLevel)
+ region.growTree(detailLevel);
+
+ return region;
+ }
+
+
+ //==============//
+ // Save to File //
+ //==============//
+
+ /** Save all dirty regions in this LodDimension to file */
+ public void saveDirtyRegionsToFileAsync()
+ {
+ fileWritingThreadPool.execute(saveDirtyRegionsThread);
+ }
+
+ private final Thread saveDirtyRegionsThread = new Thread(() ->
+ {
+ try
+ {
+ for (int i = 0; i < lodDimension.getWidth(); i++)
+ {
+ for (int j = 0; j < lodDimension.getWidth(); j++)
+ {
+ if (lodDimension.GetIsRegionDirty(i, j) && lodDimension.getRegionByArrayIndex(i, j) != null)
+ {
+ saveRegionToFile(lodDimension.getRegionByArrayIndex(i, j));
+ lodDimension.SetIsRegionDirty(i, j, false);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ });
+
+ /**
+ * 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 saveRegionToFile(LodRegion region)
+ {
+ for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
+ {
+ String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
+
+ // if the fileName was null that means the folder is inaccessible
+ // for some reason
+ if (fileName == null)
+ {
+ ClientApi.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible.");
+ return;
+ }
+ File oldFile = new File(fileName);
+ //ClientProxy.LOGGER.info("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
+ byte[] temp = region.getLevel(detailLevel).toDataString();
+
+ try
+ {
+ // make sure the file and folder exists
+ if (!oldFile.exists())
+ {
+ // the file doesn't exist,
+ // create it and the folder if need be
+ if (!oldFile.getParentFile().exists())
+ oldFile.getParentFile().mkdirs();
+ oldFile.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)
+ int fileVersion = LOD_SAVE_FILE_VERSION;
+ int isFull = 0;
+ try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
+ {
+ fileVersion = inputStream.read();
+ inputStream.skip(1);
+ isFull = inputStream.read() & 0b10000000;
+ inputStream.close();
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+
+ // check if this file can be written to by the file handler
+ if (fileVersion > LOD_SAVE_FILE_VERSION)
+ {
+ // the file we are reading is a newer version,
+ // don't write anything, we don't want to accidentally
+ // delete anything the user may want.
+ return;
+ }
+ if ((temp[1] & 0b10000000) != 0b10000000 && isFull == 0b10000000)
+ {
+ // existing file is complete while new one is only partially generate
+ // this can happen is for some reason loading failed
+ // this doesn't fix the bug, but at least protects old data
+ ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + fileName + "]");
+ return;
+ }
+ // if we got this far then we are good
+ // to overwrite the old file
+ }
+ // the old file is good, now create a new temporary save file
+ File newFile = new File(fileName + TMP_FILE_EXTENSION);
+ try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
+ {
+ // add the version of this file
+ outputStream.write(LOD_SAVE_FILE_VERSION);
+
+ // add each LodChunk to the file
+ outputStream.write(temp);
+ outputStream.close();
+
+ // overwrite the old file with the new one
+ Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
+ e.printStackTrace();
+ }
+ }
+ }
+
+
+ public void saveRegionFile (byte[] regionFile, RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
+ {
+ int regionX = regionPos.x;
+ int regionZ = regionPos.z;
+ String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
+
+ if (fileName != null)
+ {
+ File oldFile = new File(fileName);
+ File newFile = new File(fileName + TMP_FILE_EXTENSION);
+ try (OutputStream os = new FileOutputStream(newFile))
+ {
+ os.write(regionFile);
+ Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ os.close();
+ }
+ catch (IOException ioEx)
+ {
+ ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
+ ioEx.printStackTrace();
+ }
+ }
+ }
+
+ public byte[] getRegionFile (RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
+ {
+ int regionX = regionPos.x;
+ int regionZ = regionPos.z;
+ String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
+ if (fileName != null)
+ {
+ File file = new File(fileName);
+ try (InputStream is = new FileInputStream(file))
+ {
+ byte[] data = ThreadMapUtil.getSaveContainer(detailLevel);
+ is.read(data);
+ is.close();
+ return Arrays.copyOf(data, (int) file.length());
+ }
+ catch (IOException ioEx)
+ {
+ ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
+ ioEx.printStackTrace();
+ }
+ }
+ return new byte[0];
+ }
+
+
+ //================//
+ // helper methods //
+ //================//
+
+ public int getHashFromFile(RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
+ {
+ int regionX = regionPos.x;
+ int regionZ = regionPos.z;
+ String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
+ if (fileName == null)
+ return 0;
+
+ File file = new File(fileName);
+ return file.hashCode();
+ }
+
+
+ /**
+ * Return the name of the file that should contain the
+ * region at the given x and z.
+ * Returns null if this object isn't available to read and write.
+ *
+ * example: "lod.0.0.txt"
+ *
+ * Returns null if there is an IO or security Exception.
+ */
+ private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
+ {
+ try
+ {
+ // saveFolder is something like
+ // ".\Super Flat\DIM-1\data\"
+ // or
+ // ".\Super Flat\data\"
+ return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
+ verticalQuality + File.separatorChar +
+ generationMode.toString() + File.separatorChar +
+ DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
+ FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
+ }
+ catch (IOException | SecurityException e)
+ {
+ ClientApi.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java b/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java
new file mode 100644
index 000000000..a096bb71e
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java
@@ -0,0 +1,195 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.handlers;
+
+import java.lang.reflect.Field;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.seibel.lod.core.ModInfo;
+import com.seibel.lod.core.enums.rendering.FogDrawMode;
+import com.seibel.lod.core.objects.math.Mat4f;
+
+/**
+ * A singleton used to get variables from methods
+ * where they are private or potentially absent.
+ * Specifically the fog setting in Optifine or the
+ * presence/absence of other mods.
+ *
+ * @author James Seibel
+ * @version 11-26-2021
+ */
+public class ReflectionHandler implements IReflectionHandler
+{
+ private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName());
+
+ private static ReflectionHandler instance;
+
+ private Field ofFogField = null;
+ private final Object mcOptionsObject;
+
+
+
+ private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject)
+ {
+ mcOptionsObject = newMcOptionsObject;
+
+ setupFogField(optionFields);
+ }
+
+ /**
+ * @param optionFields the fields that should contain "ofFogType"
+ * @param newMcOptionsObject the object instance that contains "ofFogType"
+ * @return the ReflectionHandler just created
+ * @throws IllegalStateException if a ReflectionHandler already exists
+ */
+ public static ReflectionHandler createSingleton(Field[] optionFields, Object newMcOptionsObject) throws IllegalStateException
+ {
+ if (instance != null)
+ {
+ throw new IllegalStateException();
+ }
+
+ instance = new ReflectionHandler(optionFields, newMcOptionsObject);
+ return instance;
+ }
+
+
+
+
+ /** finds the Optifine fog type field */
+ private void setupFogField(Field[] optionFields)
+ {
+ // try and find the ofFogType variable in gameSettings
+ for (Field field : optionFields)
+ {
+ if (field.getName().equals("ofFogType"))
+ {
+ ofFogField = field;
+ return;
+ }
+ }
+
+ // we didn't find the field,
+ // either optifine isn't installed, or
+ // optifine changed the name of the variable
+ LOGGER.info(ReflectionHandler.class.getSimpleName() + ": unable to find the Optifine fog field. If Optifine isn't installed this can be ignored.");
+ }
+
+
+ /**
+ * Get what type of fog optifine is currently set to render.
+ * @return the fog quality
+ */
+ @Override
+ public FogDrawMode getFogDrawMode()
+ {
+ if (ofFogField == null)
+ {
+ // either optifine isn't installed,
+ // the variable name was changed, or
+ // the setup method wasn't called yet.
+ return FogDrawMode.FOG_ENABLED;
+ }
+
+ int returnNum = 0;
+
+ try
+ {
+ returnNum = (int) ofFogField.get(mcOptionsObject);
+ }
+ catch (IllegalArgumentException | IllegalAccessException e)
+ {
+ e.printStackTrace();
+ }
+
+ switch (returnNum)
+ {
+ default:
+ case 0:
+ // optifine's "default" option,
+ // it should never be called in this case
+
+ // normal options
+ case 1: // fast
+ case 2: // fancy
+ return FogDrawMode.FOG_ENABLED;
+ case 3: // off
+ return FogDrawMode.FOG_DISABLED;
+ }
+ }
+
+
+
+ /** Detect if Vivecraft is present. Attempts to find the "VRRenderer" class. */
+ @Override
+ public boolean vivecraftPresent()
+ {
+ try
+ {
+ Class.forName("org.vivecraft.provider.VRRenderer");
+ return true;
+ }
+ catch (ClassNotFoundException ignored)
+ {
+ LOGGER.info(ReflectionHandler.class.getSimpleName() + ": Vivecraft not detected.");
+ }
+ return false;
+ }
+
+ /**
+ * Modifies the projection matrix's clip planes.
+ * The projection matrix must be in column-major format.
+ *
+ * @param projectionMatrix The projection matrix to be modified.
+ * @param newNearClipPlane the new near clip plane value.
+ * @param newFarClipPlane the new far clip plane value.
+ * @return The modified matrix.
+ */
+ @Override
+ public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane)
+ {
+ // find the matrix values.
+ float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
+ float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
+
+ try
+ {
+ // TODO this was originally created before we had the Mat4f object,
+ // so this doesn't need to be done with reflection anymore.
+ // And should be moved to RenderUtil
+
+ // get the fields of the projectionMatrix
+ Field[] fields = projectionMatrix.getClass().getDeclaredFields();
+ // bypass the security protections on the fields that encode near and far plane values.
+ fields[10].setAccessible(true);
+ fields[11].setAccessible(true);
+ // Change the values of the near and far plane.
+ fields[10].set(projectionMatrix, nearMatrixValue);
+ fields[11].set(projectionMatrix, farMatrixValue);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ return projectionMatrix;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java b/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java
new file mode 100644
index 000000000..977c3533d
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java
@@ -0,0 +1,41 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects;
+
+/**
+ * Used when setting up configuration fields.
+ *
+ * @author James Seibel
+ * @version 11-14-2021
+ * @param The data type this object is storing
+ */
+public class MinDefaultMax
+{
+ public final T minValue;
+ public final T defaultValue;
+ public final T maxValue;
+
+ public MinDefaultMax(T newMinValue, T newDefaultValue, T newMaxValue)
+ {
+ minValue = newMinValue;
+ defaultValue = newDefaultValue;
+ maxValue = newMaxValue;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java
new file mode 100644
index 000000000..730c9d6cb
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java
@@ -0,0 +1,209 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects;
+
+import com.seibel.lod.core.util.LevelPosUtil;
+
+/**
+ * Holds the levelPos that need to be generated.
+ *
+ * @author Leonardo Amato
+ * @version 9-27-2021
+ */
+public class PosToGenerateContainer
+{
+ private final int playerPosX;
+ private final int playerPosZ;
+ private final byte farMinDetail;
+ private int nearSize;
+ private int farSize;
+
+ // TODO what is the format of these two arrays? [detailLevel][4-children]?
+ private final int[][] nearPosToGenerate;
+ private final int[][] farPosToGenerate;
+
+
+
+
+ public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ)
+ {
+ this.playerPosX = playerPosX;
+ this.playerPosZ = playerPosZ;
+ this.farMinDetail = farMinDetail;
+ nearSize = 0;
+ farSize = 0;
+ nearPosToGenerate = new int[maxDataToGenerate][4];
+ farPosToGenerate = new int[maxDataToGenerate][4];
+ }
+
+
+
+ // TODO what is going on in this method?
+ public void addPosToGenerate(byte detailLevel, int posX, int posZ)
+ {
+ int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ);
+ int index;
+
+ if (detailLevel >= farMinDetail)
+ {
+ // We are introducing a position in the far array
+
+ if (farSize < farPosToGenerate.length)
+ farSize++;
+
+ index = farSize - 1;
+ while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0)
+ {
+ farPosToGenerate[index][0] = farPosToGenerate[index - 1][0];
+ farPosToGenerate[index][1] = farPosToGenerate[index - 1][1];
+ farPosToGenerate[index][2] = farPosToGenerate[index - 1][2];
+ farPosToGenerate[index][3] = farPosToGenerate[index - 1][3];
+ index--;
+ }
+
+
+ if (index != farSize - 1 || farSize != farPosToGenerate.length)
+ {
+ farPosToGenerate[index][0] = detailLevel + 1;
+ farPosToGenerate[index][1] = posX;
+ farPosToGenerate[index][2] = posZ;
+ farPosToGenerate[index][3] = distance;
+ }
+ }
+ else
+ {
+ //We are introducing a position in the near array
+
+ if (nearSize < nearPosToGenerate.length)
+ nearSize++;
+
+ index = nearSize - 1;
+ while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0)
+ {
+ nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0];
+ nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1];
+ nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2];
+ nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3];
+ index--;
+ }
+
+
+ if (index != nearSize - 1 || nearSize != nearPosToGenerate.length)
+ {
+ nearPosToGenerate[index][0] = detailLevel + 1;
+ nearPosToGenerate[index][1] = posX;
+ nearPosToGenerate[index][2] = posZ;
+ nearPosToGenerate[index][3] = distance;
+ }
+ }
+ }
+
+
+
+ public int getNumberOfPos()
+ {
+ return nearSize + farSize;
+ }
+
+ public int getNumberOfNearPos()
+ {
+ return nearSize;
+ }
+
+ public int getNumberOfFarPos()
+ {
+ return farSize;
+ }
+
+ // TODO what does getNth mean? could the name be more descriptive or is it just a index?
+ public int getNthDetail(int n, boolean near)
+ {
+ if (near)
+ return nearPosToGenerate[n][0];
+ else
+ return farPosToGenerate[n][0];
+ }
+
+ public int getNthPosX(int n, boolean near)
+ {
+ if (near)
+ return nearPosToGenerate[n][1];
+ else
+ return farPosToGenerate[n][1];
+ }
+
+ public int getNthPosZ(int n, boolean near)
+ {
+ if (near)
+ return nearPosToGenerate[n][2];
+ else
+ return farPosToGenerate[n][2];
+ }
+
+ public int getNthGeneration(int n, boolean near)
+ {
+ if (near)
+ return nearPosToGenerate[n][3];
+ else
+ return farPosToGenerate[n][3];
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.append('\n');
+ builder.append('\n');
+ builder.append('\n');
+ builder.append("near pos to generate");
+ builder.append('\n');
+ for (int[] ints : nearPosToGenerate)
+ {
+ if (ints[0] == 0)
+ break;
+ builder.append(ints[0] - 1);
+ builder.append(" ");
+ builder.append(ints[1]);
+ builder.append(" ");
+ builder.append(ints[2]);
+ builder.append(" ");
+ builder.append(ints[3]);
+ builder.append('\n');
+ }
+ builder.append('\n');
+
+ builder.append("far pos to generate");
+ builder.append('\n');
+ for (int[] ints : farPosToGenerate)
+ {
+ if (ints[0] == 0)
+ break;
+ builder.append(ints[0] - 1);
+ builder.append(" ");
+ builder.append(ints[1]);
+ builder.append(" ");
+ builder.append(ints[2]);
+ builder.append(" ");
+ builder.append(ints[3]);
+ builder.append('\n');
+ }
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java b/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java
new file mode 100644
index 000000000..5a42e7212
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java
@@ -0,0 +1,147 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects;
+
+import java.util.Arrays;
+
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * Holds a levelPos that needs to be rendered.
+ *
+ * @author Leonardo Amato
+ * @version 9-18-2021
+ */
+public class PosToRenderContainer
+{
+ public byte minDetail;
+ private int regionPosX;
+ private int regionPosZ;
+ private int numberOfPosToRender;
+ private int[] posToRender;
+ private byte[][] population;
+
+ public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
+ {
+ this.minDetail = minDetail;
+ this.numberOfPosToRender = 0;
+ this.regionPosX = regionPosX;
+ this.regionPosZ = regionPosZ;
+ int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
+ posToRender = new int[size * size * 3];
+ population = new byte[size][size];
+ }
+
+ public void addPosToRender(byte detailLevel, int posX, int posZ)
+ {
+ // When rapidly changing dimensions the bufferBuilder can cause this,
+ // James isn't sure why, but this will prevent an exception at
+ // the very least (while stilling logging the problem).
+ if (numberOfPosToRender >= posToRender.length)
+ {
+ // This is might be due to dimensions having a different width
+ // when first loading in
+ ClientApi.LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
+ numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go
+ return;
+ }
+
+ //if(numberOfPosToRender >= posToRender.length)
+ // posToRender = Arrays.copyOf(posToRender, posToRender.length*2);
+ posToRender[numberOfPosToRender * 3] = detailLevel;
+ posToRender[numberOfPosToRender * 3 + 1] = posX;
+ posToRender[numberOfPosToRender * 3 + 2] = posZ;
+ numberOfPosToRender++;
+ population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
+ [LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] = (byte) (detailLevel + 1);
+ }
+
+ public boolean contains(byte detailLevel, int posX, int posZ)
+ {
+ if (LevelPosUtil.getRegion(detailLevel, posX) == regionPosX && LevelPosUtil.getRegion(detailLevel, posZ) == regionPosZ)
+ return (population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
+ [LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] == (detailLevel + 1));
+ else
+ return false;
+ }
+
+ public void clear(byte minDetail, int regionPosX, int regionPosZ)
+ {
+ this.numberOfPosToRender = 0;
+ this.regionPosX = regionPosX;
+ this.regionPosZ = regionPosZ;
+ if (this.minDetail == minDetail)
+ {
+ Arrays.fill(posToRender, 0);
+ for (byte[] bytes : population)
+ Arrays.fill(bytes, (byte) 0);
+ }
+ else
+ {
+ this.minDetail = minDetail;
+ int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
+ posToRender = new int[size * size * 3];
+ population = new byte[size][size];
+ }
+ }
+
+ public int getNumberOfPos()
+ {
+ return numberOfPosToRender;
+ }
+
+ public byte getNthDetailLevel(int n)
+ {
+ return (byte) posToRender[n * 3];
+ }
+
+ public int getNthPosX(int n)
+ {
+ return posToRender[n * 3 + 1];
+ }
+
+ public int getNthPosZ(int n)
+ {
+ return posToRender[n * 3 + 2];
+ }
+
+ @Override
+ public String toString()
+ {
+
+ StringBuilder builder = new StringBuilder();
+ builder.append("To render ");
+ builder.append(numberOfPosToRender);
+ builder.append('\n');
+ for (int i = 0; i < numberOfPosToRender; i++)
+ {
+ builder.append(posToRender[i * 3]);
+ builder.append(" ");
+ builder.append(posToRender[i * 3 + 1]);
+ builder.append(" ");
+ builder.append(posToRender[i * 3 + 2]);
+ builder.append('\n');
+ }
+ builder.append('\n');
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java
new file mode 100644
index 000000000..4c13f247b
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/VertexOptimizer.java
@@ -0,0 +1,626 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.objects.math.Vec3i;
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+
+/**
+ * This class handles all the vertex optimization that's needed for a column of lods. W
+ * @author Leonardo Amato
+ * @version 10-2-2021
+ */
+public class VertexOptimizer
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+
+ public static final int ADJACENT_HEIGHT_INDEX = 0;
+ public static final int ADJACENT_DEPTH_INDEX = 1;
+
+ public static final int X = 0;
+ public static final int Y = 1;
+ public static final int Z = 2;
+
+ public static final int MIN = 0;
+ public static final int MAX = 1;
+
+ public static final int VOID_FACE = 0;
+
+ /** The six cardinal directions */
+ public static final LodDirection[] DIRECTIONS = new LodDirection[] {
+ LodDirection.UP,
+ LodDirection.DOWN,
+ LodDirection.WEST,
+ LodDirection.EAST,
+ LodDirection.NORTH,
+ LodDirection.SOUTH };
+
+ /** North, South, East, West */
+ public static final LodDirection[] ADJ_DIRECTIONS = new LodDirection[] {
+ LodDirection.EAST,
+ LodDirection.WEST,
+ LodDirection.SOUTH,
+ LodDirection.NORTH };
+
+ /** All the faces and vertices of a cube. This is used to extract the vertex from the column */
+ @SuppressWarnings("serial")
+ public static final Map DIRECTION_VERTEX_MAP = new HashMap()
+ {{
+ put(LodDirection.UP, new int[][] {
+ { 0, 1, 0 }, // 0
+ { 0, 1, 1 }, // 1
+ { 1, 1, 1 }, // 2
+
+ { 0, 1, 0 }, // 0
+ { 1, 1, 1 }, // 2
+ { 1, 1, 0 } // 3
+ });
+ put(LodDirection.DOWN, new int[][] {
+ { 1, 0, 0 }, // 0
+ { 1, 0, 1 }, // 1
+ { 0, 0, 1 }, // 2
+
+ { 1, 0, 0 }, // 0
+ { 0, 0, 1 }, // 2
+ { 0, 0, 0 } // 3
+ });
+ put(LodDirection.EAST, new int[][] {
+ { 1, 1, 0 }, // 0
+ { 1, 1, 1 }, // 1
+ { 1, 0, 1 }, // 2
+
+ { 1, 1, 0 }, // 0
+ { 1, 0, 1 }, // 2
+ { 1, 0, 0 } }); // 3
+ put(LodDirection.WEST, new int[][] {
+ { 0, 0, 0 }, // 0
+ { 0, 0, 1 }, // 1
+ { 0, 1, 1 }, // 2
+
+ { 0, 0, 0 }, // 0
+ { 0, 1, 1 }, // 2
+ { 0, 1, 0 } // 3
+ });
+ put(LodDirection.SOUTH, new int[][] {
+ { 1, 0, 1 }, // 0
+ { 1, 1, 1 }, // 1
+ { 0, 1, 1 }, // 2
+
+ { 1, 0, 1 }, // 0
+ { 0, 1, 1 }, // 2
+ { 0, 0, 1 } // 3
+ });
+ put(LodDirection.NORTH, new int[][] {
+ { 0, 0, 0 }, // 0
+ { 0, 1, 0 }, // 1
+ { 1, 1, 0 }, // 2
+
+ { 0, 0, 0 }, // 0
+ { 1, 1, 0 }, // 2
+ { 1, 0, 0 } // 3
+ });
+ }};
+
+
+ /**
+ * This indicates which position is invariable in the DIRECTION_VERTEX_MAP.
+ * Is used to extract the vertex
+ */
+ @SuppressWarnings("serial")
+ public static final Map FACE_DIRECTION = new HashMap()
+ {{
+ put(LodDirection.UP, new int[] { Y, MAX });
+ put(LodDirection.DOWN, new int[] { Y, MIN });
+ put(LodDirection.EAST, new int[] { X, MAX });
+ put(LodDirection.WEST, new int[] { X, MIN });
+ put(LodDirection.SOUTH, new int[] { Z, MAX });
+ put(LodDirection.NORTH, new int[] { Z, MIN });
+ }};
+
+
+ /**
+ * This is a map from Direction to the relative normal vector
+ * we are using this since I'm not sure if the getNormal create new object at every call
+ */
+ @SuppressWarnings("serial")
+ public static final Map DIRECTION_NORMAL_MAP = new HashMap()
+ {{
+ put(LodDirection.UP, LodDirection.UP.getNormal());
+ put(LodDirection.DOWN, LodDirection.DOWN.getNormal());
+ put(LodDirection.EAST, LodDirection.EAST.getNormal());
+ put(LodDirection.WEST, LodDirection.WEST.getNormal());
+ put(LodDirection.SOUTH, LodDirection.SOUTH.getNormal());
+ put(LodDirection.NORTH, LodDirection.NORTH.getNormal());
+ }};
+
+ /** We use this index for all array that are going to */
+ @SuppressWarnings("serial")
+ public static final Map DIRECTION_INDEX = new HashMap()
+ {{
+ put(LodDirection.UP, 0);
+ put(LodDirection.DOWN, 1);
+ put(LodDirection.EAST, 2);
+ put(LodDirection.WEST, 3);
+ put(LodDirection.SOUTH, 4);
+ put(LodDirection.NORTH, 5);
+ }};
+
+ @SuppressWarnings("serial")
+ public static final Map ADJ_DIRECTION_INDEX = new HashMap()
+ {{
+ put(LodDirection.EAST, 0);
+ put(LodDirection.WEST, 1);
+ put(LodDirection.SOUTH, 2);
+ put(LodDirection.NORTH, 3);
+ }};
+ /** holds the box's x, y, z offset */
+ public final int[] boxOffset;
+ /** holds the box's x, y, z width */
+ public final int[] boxWidth;
+
+ /** Holds each direction's color */
+ public final int[] colorMap;
+ /** The original color (before shading) of this box */
+ public int color;
+ /**
+ *
+ */
+ public final Map adjHeight;
+ public final Map adjDepth;
+ public final Map skyLights;
+ public byte blockLight;
+
+ /** Holds if the given direction should be culled or not */
+ public final boolean[] culling;
+
+
+ /** creates an empty box */
+ @SuppressWarnings("serial")
+ public VertexOptimizer()
+ {
+ boxOffset = new int[3];
+ boxWidth = new int[3];
+
+ colorMap = new int[6];
+ skyLights = new HashMap()
+ {{
+ put(LodDirection.UP, new byte[1]);
+ put(LodDirection.DOWN, new byte[1]);
+ put(LodDirection.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ }};
+ adjHeight = new HashMap()
+ {{
+ put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ }};
+ adjDepth = new HashMap()
+ {{
+ put(LodDirection.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
+ }};
+
+ culling = new boolean[6];
+ }
+
+ /** Set the light of the columns */
+ public void setLights(int skyLight, int blockLight)
+ {
+ this.blockLight = (byte) blockLight;
+ skyLights.get(LodDirection.UP)[0] = (byte) skyLight;
+ }
+
+ /**
+ * Set the color of the columns
+ * @param color color to add
+ * @param adjShadeDisabled this array indicates which face does not need shading
+ */
+ public void setColor(int color, boolean[] adjShadeDisabled)
+ {
+ this.color = color;
+ for (LodDirection lodDirection : DIRECTIONS)
+ {
+ if (!adjShadeDisabled[DIRECTION_INDEX.get(lodDirection)])
+ colorMap[DIRECTION_INDEX.get(lodDirection)] = ColorUtil.applyShade(color, MC.getShade(lodDirection));
+ else
+ colorMap[DIRECTION_INDEX.get(lodDirection)] = color;
+ }
+ }
+
+ /**
+ * @param lodDirection of the face of which we want to get the color
+ * @return color of the face
+ */
+ public int getColor(LodDirection lodDirection)
+ {
+ if (CONFIG.client().advanced().debugging().getDebugMode() != DebugMode.SHOW_DETAIL)
+ return colorMap[DIRECTION_INDEX.get(lodDirection)];
+ else
+ return ColorUtil.applyShade(color, MC.getShade(lodDirection));
+ }
+
+ /**
+ */
+ public byte getSkyLight(LodDirection lodDirection, int verticalIndex)
+ {
+ if(lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
+ return skyLights.get(lodDirection)[0];
+ else
+ return skyLights.get(lodDirection)[verticalIndex];
+ }
+
+ /**
+ */
+ public int getBlockLight()
+ {
+ return blockLight;
+ }
+ /** clears this box, resetting everything to default values */
+ public void reset()
+ {
+ Arrays.fill(boxWidth, 0);
+ Arrays.fill(boxOffset, 0);
+ Arrays.fill(colorMap, 0);
+ blockLight = 0;
+ for (LodDirection lodDirection : ADJ_DIRECTIONS)
+ {
+ for (int i = 0; i < adjHeight.get(lodDirection).length; i++)
+ {
+ adjHeight.get(lodDirection)[i] = VOID_FACE;
+ adjDepth.get(lodDirection)[i] = VOID_FACE;
+ skyLights.get(lodDirection)[i] = 0;
+ }
+ }
+ }
+
+ /** determine which faces should be culled */
+ public void setUpCulling(int cullingDistance, AbstractBlockPosWrapper playerPos)
+ {
+ for (LodDirection lodDirection : DIRECTIONS)
+ {
+ if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.WEST || lodDirection == LodDirection.NORTH)
+ culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) > getFacePos(lodDirection) + cullingDistance;
+
+ else if (lodDirection == LodDirection.UP || lodDirection == LodDirection.EAST || lodDirection == LodDirection.SOUTH)
+ culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) < getFacePos(lodDirection) - cullingDistance;
+
+ culling[DIRECTION_INDEX.get(lodDirection)] = false;
+ }
+ }
+
+ /**
+ * @param lodDirection direction that we want to check if it's culled
+ * @return true if and only if the face of the direction is culled
+ */
+ public boolean isCulled(LodDirection lodDirection)
+ {
+ return culling[DIRECTION_INDEX.get(lodDirection)];
+ }
+
+
+ /**
+ * This method create all the shared face culling based on the adjacent data
+ * @param adjData data adjacent to the column we are going to render
+ */
+ public void setAdjData(Map adjData)
+ {
+ int height;
+ int depth;
+ int minY = getMinY();
+ int maxY = getMaxY();
+ long singleAdjDataPoint;
+
+ /* TODO implement attached vertical face culling
+ //Up direction case
+ if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
+ {
+ height = DataPointUtil.getHeight(singleAdjDataPoint);
+ depth = DataPointUtil.getDepth(singleAdjDataPoint);
+ }*/
+ //Down direction case
+ singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0];
+ if(DataPointUtil.doesItExist(singleAdjDataPoint))
+ skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
+ else
+ skyLights.get(LodDirection.DOWN)[0] = skyLights.get(LodDirection.UP)[0];
+ //other sided
+ //TODO clean some similar cases
+ for (LodDirection lodDirection : ADJ_DIRECTIONS)
+ {
+ if (isCulled(lodDirection))
+ continue;
+
+ long[] dataPoint = adjData.get(lodDirection);
+ if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
+ {
+ adjHeight.get(lodDirection)[0] = maxY;
+ adjDepth.get(lodDirection)[0] = minY;
+ adjHeight.get(lodDirection)[1] = VOID_FACE;
+ adjDepth.get(lodDirection)[1] = VOID_FACE;
+ skyLights.get(lodDirection)[0] = 15; //in void set full skylight
+ continue;
+ }
+
+ int i;
+ int faceToDraw = 0;
+ boolean firstFace = true;
+ boolean toFinish = false;
+ int toFinishIndex = 0;
+ boolean allAbove = true;
+ for (i = 0; i < dataPoint.length; i++)
+ {
+ singleAdjDataPoint = dataPoint[i];
+
+ if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
+ break;
+
+ height = DataPointUtil.getHeight(singleAdjDataPoint);
+ depth = DataPointUtil.getDepth(singleAdjDataPoint);
+
+ if (depth <= maxY)
+ {
+ allAbove = false;
+ if (height < minY)
+ {
+ // the adj data is lower than the current data
+
+ if (firstFace)
+ {
+ adjHeight.get(lodDirection)[0] = getMaxY();
+ adjDepth.get(lodDirection)[0] = getMinY();
+ skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
+ }
+ else
+ {
+ adjDepth.get(lodDirection)[faceToDraw] = getMinY();
+ skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
+ }
+ faceToDraw++;
+ toFinish = false;
+
+ // break since all the other data will be lower
+ break;
+ }
+ else if (depth <= minY)
+ {
+ if (height >= maxY)
+ {
+ // the adj data is inside the current data
+ // don't draw the face
+ adjHeight.get(lodDirection)[0] = VOID_FACE;
+ adjDepth.get(lodDirection)[0] = VOID_FACE;
+ }
+ else // height < maxY
+ {
+ // the adj data intersects the lower part of the current data
+ // if this is the only face, use the maxY and break,
+ // if there was another face we finish the last one and break
+ if (firstFace)
+ {
+ adjHeight.get(lodDirection)[0] = getMaxY();
+ adjDepth.get(lodDirection)[0] = height;
+ skyLights.get(lodDirection)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint); //skyLights.get(Direction.UP)[0];
+ }
+ else
+ {
+ adjDepth.get(lodDirection)[faceToDraw] = height;
+ skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
+ }
+ toFinish = false;
+ faceToDraw++;
+ }
+ break;
+ }
+ else if (height >= maxY)//depth > minY &&
+ {
+ // the adj data intersects the higher part of the current data
+ // we start the creation of a new face
+ adjHeight.get(lodDirection)[faceToDraw] = depth;
+ //skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
+ firstFace = false;
+ toFinish = true;
+ toFinishIndex = i + 1;
+ }
+ else
+ {
+ // if (depth > minY && height < maxY)
+
+ // the adj data is contained in the current data
+ if (firstFace)
+ {
+ adjHeight.get(lodDirection)[0] = getMaxY();
+ }
+
+ adjDepth.get(lodDirection)[faceToDraw] = height;
+ skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
+ faceToDraw++;
+ adjHeight.get(lodDirection)[faceToDraw] = depth;
+ firstFace = false;
+ toFinish = true;
+ toFinishIndex = i + 1;
+ }
+ }
+ }
+
+ if (allAbove)
+ {
+ adjHeight.get(lodDirection)[0] = getMaxY();
+ adjDepth.get(lodDirection)[0] = getMinY();
+ skyLights.get(lodDirection)[0] = skyLights.get(LodDirection.UP)[0];
+ faceToDraw++;
+ }
+ else if (toFinish)
+ {
+ adjDepth.get(lodDirection)[faceToDraw] = minY;
+ if(toFinishIndex < dataPoint.length)
+ {
+ singleAdjDataPoint = dataPoint[toFinishIndex];
+ if (DataPointUtil.doesItExist(singleAdjDataPoint))
+ skyLights.get(lodDirection)[faceToDraw] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
+ else
+ skyLights.get(lodDirection)[faceToDraw] = skyLights.get(LodDirection.UP)[0];
+ }
+ faceToDraw++;
+ }
+
+ adjHeight.get(lodDirection)[faceToDraw] = VOID_FACE;
+ adjDepth.get(lodDirection)[faceToDraw] = VOID_FACE;
+ }
+ }
+
+ /** We use this method to set the various width of the column */
+ public void setWidth(int xWidth, int yWidth, int zWidth)
+ {
+ boxWidth[X] = xWidth;
+ boxWidth[Y] = yWidth;
+ boxWidth[Z] = zWidth;
+ }
+
+ /** We use this method to set the various offset of the column */
+ public void setOffset(int xOffset, int yOffset, int zOffset)
+ {
+ boxOffset[X] = xOffset;
+ boxOffset[Y] = yOffset;
+ boxOffset[Z] = zOffset;
+ }
+
+ /**
+ * This method return the position of a face in the axis of the face
+ * This is useful for the face culling
+ * @param lodDirection that we want to check
+ * @return position in the axis of the face
+ */
+ public int getFacePos(LodDirection lodDirection)
+ {
+ return boxOffset[FACE_DIRECTION.get(lodDirection)[0]] + boxWidth[FACE_DIRECTION.get(lodDirection)[0]] * FACE_DIRECTION.get(lodDirection)[1];
+ }
+
+ /**
+ * returns true if the given direction should be rendered.
+ */
+ public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
+ {
+ if (lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
+ return adjIndex == 0;
+ return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE);
+ }
+
+
+ /**
+ * @param lodDirection direction of the face we want to render
+ * @param vertexIndex index of the vertex of the face (0-1-2-3)
+ * @return position x of the relative vertex
+ */
+ public int getX(LodDirection lodDirection, int vertexIndex)
+ {
+ return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][X];
+ }
+
+ /**
+ * @param lodDirection direction of the face we want to render
+ * @param vertexIndex index of the vertex of the face (0-1-2-3)
+ * @return position y of the relative vertex
+ */
+ public int getY(LodDirection lodDirection, int vertexIndex)
+ {
+ return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y];
+ }
+
+ /**
+ * @param lodDirection direction of the face we want to render
+ * @param vertexIndex index of the vertex of the face (0-1-2-3)
+ * @param adjIndex, index of the n-th culled face of this direction
+ * @return position x of the relative vertex
+ */
+ public int getY(LodDirection lodDirection, int vertexIndex, int adjIndex)
+ {
+ if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.UP)
+ return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y];
+ else
+ {
+ // this could probably be cleaned up more,
+ // but it still works
+ if (1 - DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
+ return adjHeight.get(lodDirection)[adjIndex];
+ else
+ return adjDepth.get(lodDirection)[adjIndex];
+ }
+ }
+
+ /**
+ * @param lodDirection direction of the face we want to render
+ * @param vertexIndex index of the vertex of the face (0-1-2-3)
+ * @return position z of the relative vertex
+ */
+ public int getZ(LodDirection lodDirection, int vertexIndex)
+ {
+ return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(lodDirection)[vertexIndex][Z];
+ }
+
+ public int getMinX()
+ {
+ return boxOffset[X];
+ }
+
+ public int getMaxX()
+ {
+ return boxOffset[X] + boxWidth[X];
+ }
+
+ public int getMinY()
+ {
+ return boxOffset[Y];
+ }
+
+ public int getMaxY()
+ {
+ return boxOffset[Y] + boxWidth[Y];
+ }
+
+ public int getMinZ()
+ {
+ return boxOffset[Z];
+ }
+
+ public int getMaxZ()
+ {
+ return boxOffset[Z] + boxWidth[Z];
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java
new file mode 100644
index 000000000..b314fbcf7
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java
@@ -0,0 +1,115 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.lod;
+
+/**
+ * A level container is a quad tree level
+ */
+public interface LevelContainer
+{
+ /**
+ * With this you can add data to the level container
+ * @param data actual data to add in an array of long format.
+ * @param posX x position in the detail level
+ * @param posZ z position in the detail level
+ * @param index z position in the detail level
+ * @return true if correctly added, false otherwise
+ */
+ boolean addData(long data, int posX, int posZ, int index);
+
+ /**
+ * With this you can add data to the level container
+ * @param data actual data to add in an array of long[] format.
+ * @param posX x position in the detail level
+ * @param posZ z position in the detail level
+ * @return true if correctly added, false otherwise
+ */
+ boolean addVerticalData(long[] data, int posX, int posZ);
+
+ /**
+ * With this you can add data to the level container
+ * @param data actual data to add in an array of long format.
+ * @param posX x position in the detail level
+ * @param posZ z position in the detail level
+ * @return true if correctly added, false otherwise
+ */
+ boolean addSingleData(long data, int posX, int posZ);
+
+ /**
+ * With this you can get data from the level container
+ * @param posX x position in the detail level
+ * @param posZ z position in the detail level
+ * @return the data in long array format
+ */
+ long getData(int posX, int posZ, int index);
+
+ /**
+ * With this you can get data from the level container
+ * @param posX x position in the detail level
+ * @param posZ z position in the detail level
+ * @return the data in long array format
+ */
+ long getSingleData(int posX, int posZ);
+
+ /**
+ * @param posX x position in the detail level
+ * @param posZ z position in the detail level
+ * @return true only if the data exist
+ */
+ boolean doesItExist(int posX, int posZ);
+
+ /**
+ * @return return the detailLevel of this level container
+ */
+ byte getDetailLevel();
+
+
+ int getMaxVerticalData();
+
+ /** Clears the dataPoint at the given array index */
+ void clear(int posX, int posZ);
+
+ /**
+ * This return a level container with detail level lower than the current level.
+ * The new level container may use information of this level.
+ * @return the new level container
+ */
+ LevelContainer expand();
+
+ /**
+ * @param lowerLevelContainer lower level where we extract the data
+ * @param posX x position in the detail level to update
+ * @param posZ z position in the detail level to update
+ */
+ void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
+
+ /**
+ * This will give the data to save in the file
+ * @return data as a String
+ */
+ byte[] toDataString();
+
+
+ /**
+ * This will give the data to save in the file
+ * @return data as a String
+ */
+ int getMaxNumberOfLods();
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
new file mode 100644
index 000000000..06c8fd921
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
@@ -0,0 +1,913 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.lod;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.GenerationPriority;
+import com.seibel.lod.core.enums.config.VerticalQuality;
+import com.seibel.lod.core.handlers.LodDimensionFileHandler;
+import com.seibel.lod.core.objects.PosToGenerateContainer;
+import com.seibel.lod.core.objects.PosToRenderContainer;
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.util.DetailDistanceUtil;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodThreadFactory;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+
+
+
+/**
+ * This object holds all loaded LOD regions
+ * for a given dimension.
+ *
+ * Coordinate Standard:
+ * Coordinate called posX or posZ are relative LevelPos coordinates
+ * unless stated otherwise.
+ *
+ * @author Leonardo Amato
+ * @author James Seibel
+ * @version 11-12-2021
+ */
+public class LodDimension
+{
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+ public final IDimensionTypeWrapper dimension;
+
+ /** measured in regions */
+ private volatile int width;
+ /** measured in regions */
+ private volatile int halfWidth;
+
+ // these three variables are private to force use of the getWidth() method
+ // which is a safer way to get the width then directly asking the arrays
+ /** stores all the regions in this dimension */
+ public volatile LodRegion[][] regions;
+
+ /** stores if the region at the given x and z index needs to be saved to disk */
+ private volatile boolean[][] isRegionDirty;
+ /** stores if the region at the given x and z index needs to be regenerated */
+ private volatile boolean[][] regenRegionBuffer;
+ /** stores if the buffer size at the given x and z index needs to be changed */
+ private volatile boolean[][] recreateRegionBuffer;
+
+ /**
+ * if true that means there are regions in this dimension
+ * that need to have their buffers rebuilt.
+ */
+ public volatile boolean regenDimensionBuffers = false;
+
+ private LodDimensionFileHandler fileHandler;
+
+ private final RegionPos center;
+
+ /** prevents the cutAndExpandThread from expanding at the same location multiple times */
+ private volatile AbstractChunkPosWrapper lastExpandedChunk;
+ /** prevents the cutAndExpandThread from cutting at the same location multiple times */
+ private volatile AbstractChunkPosWrapper lastCutChunk;
+ private final ExecutorService cutAndExpandThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " - Cut and Expand"));
+
+ /**
+ * Creates the dimension centered at (0,0)
+ * @param newWidth in regions
+ */
+ public LodDimension(IDimensionTypeWrapper newDimension, LodWorld lodWorld, int newWidth)
+ {
+ lastCutChunk = null;
+ lastExpandedChunk = null;
+ dimension = newDimension;
+ width = newWidth;
+ halfWidth = width / 2;
+
+ if (newDimension != null && lodWorld != null)
+ {
+ try
+ {
+ // determine the save folder
+ File saveDir;
+ if (MC.hasSinglePlayerServer())
+ {
+ // local world
+
+ IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(newDimension);
+ saveDir = new File(serverWorld.getSaveFolder().getCanonicalFile().getPath() + File.separatorChar + "lod");
+ }
+ else
+ {
+ // connected to server
+
+ saveDir = new File(MC.getGameDirectory().getCanonicalFile().getPath() +
+ File.separatorChar + "Distant_Horizons_server_data" + File.separatorChar + MC.getCurrentDimensionId());
+ }
+
+ fileHandler = new LodDimensionFileHandler(saveDir, this);
+ }
+ catch (IOException e)
+ {
+ // the file handler wasn't able to be created
+ // we won't be able to read or write any files
+ }
+ }
+
+
+ regions = new LodRegion[width][width];
+ isRegionDirty = new boolean[width][width];
+ regenRegionBuffer = new boolean[width][width];
+ recreateRegionBuffer = new boolean[width][width];
+
+ center = new RegionPos(0, 0);
+ }
+
+
+ /**
+ * Move the center of this LodDimension and move all owned
+ * regions over by the given x and z offset.
+ *
+ * Synchronized to prevent multiple moves happening on top of each other.
+ */
+ public synchronized void move(RegionPos regionOffset)
+ {
+ int xOffset = regionOffset.x;
+ int zOffset = regionOffset.z;
+
+ // if the x or z offset is equal to or greater than
+ // the total width, just delete the current data
+ // and update the centerX and/or centerZ
+ if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
+ {
+ for (int x = 0; x < width; x++)
+ for (int z = 0; z < width; z++)
+ regions[x][z] = null;
+
+ // update the new center
+ center.x += xOffset;
+ center.z += zOffset;
+
+ return;
+ }
+
+
+ // X
+ if (xOffset > 0)
+ {
+ // move everything over to the left (as the center moves to the right)
+ for (int x = 0; x < width; x++)
+ {
+ for (int z = 0; z < width; z++)
+ {
+ if (x + xOffset < width)
+ regions[x][z] = regions[x + xOffset][z];
+ else
+ regions[x][z] = null;
+ }
+ }
+ }
+ else
+ {
+ // move everything over to the right (as the center moves to the left)
+ for (int x = width - 1; x >= 0; x--)
+ {
+ for (int z = 0; z < width; z++)
+ {
+ if (x + xOffset >= 0)
+ regions[x][z] = regions[x + xOffset][z];
+ else
+ regions[x][z] = null;
+ }
+ }
+ }
+
+
+ // Z
+ if (zOffset > 0)
+ {
+ // move everything up (as the center moves down)
+ for (int x = 0; x < width; x++)
+ {
+ for (int z = 0; z < width; z++)
+ {
+ if (z + zOffset < width)
+ regions[x][z] = regions[x][z + zOffset];
+ else
+ regions[x][z] = null;
+ }
+ }
+ }
+ else
+ {
+ // move everything down (as the center moves up)
+ for (int x = 0; x < width; x++)
+ {
+ for (int z = width - 1; z >= 0; z--)
+ {
+ if (z + zOffset >= 0)
+ regions[x][z] = regions[x][z + zOffset];
+ else
+ regions[x][z] = null;
+ }
+ }
+ }
+
+
+ // update the new center
+ center.x += xOffset;
+ center.z += zOffset;
+ }
+
+
+ /**
+ * Gets the region at the given LevelPos
+ *
+ * Returns null if the region doesn't exist
+ * or is outside the loaded area.
+ */
+ public LodRegion getRegion(byte detailLevel, int levelPosX, int levelPosZ)
+ {
+ int xRegion = LevelPosUtil.getRegion(detailLevel, levelPosX);
+ int zRegion = LevelPosUtil.getRegion(detailLevel, levelPosZ);
+ int xIndex = (xRegion - center.x) + halfWidth;
+ int zIndex = (zRegion - center.z) + halfWidth;
+
+ if (!regionIsInRange(xRegion, zRegion))
+ return null;
+ // throw new ArrayIndexOutOfBoundsException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " out of range");
+ else if (regions[xIndex][zIndex] == null)
+ return null;
+ else if (regions[xIndex][zIndex].getMinDetailLevel() > detailLevel)
+ return null;
+ //throw new InvalidParameterException("Region for level pos " + LevelPosUtil.toString(detailLevel, posX, posZ) + " currently only reach level " + regions[xIndex][zIndex].getMinDetailLevel());
+
+ return regions[xIndex][zIndex];
+ }
+
+ /**
+ * Gets the region at the given X and Z
+ *
+ * Returns null if the region doesn't exist
+ * or is outside the loaded area.
+ */
+ public LodRegion getRegion(int regionPosX, int regionPosZ)
+ {
+ int xIndex = (regionPosX - center.x) + halfWidth;
+ int zIndex = (regionPosZ - center.z) + halfWidth;
+
+ if (!regionIsInRange(regionPosX, regionPosZ))
+ return null;
+ //throw new ArrayIndexOutOfBoundsException("Region " + regionPosX + " " + regionPosZ + " out of range");
+
+ return regions[xIndex][zIndex];
+ }
+
+ /** Useful when iterating over every region. */
+ public LodRegion getRegionByArrayIndex(int xIndex, int zIndex)
+ {
+ 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 synchronized void addOrOverwriteRegion(LodRegion newRegion) throws ArrayIndexOutOfBoundsException
+ {
+ int xIndex = (newRegion.regionPosX - center.x) + halfWidth;
+ int zIndex = (newRegion.regionPosZ - center.z) + halfWidth;
+
+ if (!regionIsInRange(newRegion.regionPosX, newRegion.regionPosZ))
+ // out of range
+ throw new ArrayIndexOutOfBoundsException("Region " + newRegion.regionPosX + ", " + newRegion.regionPosZ + " out of range");
+
+ regions[xIndex][zIndex] = newRegion;
+ }
+
+
+ /**
+ * Deletes nodes that are a higher detail then necessary, freeing
+ * up memory.
+ */
+ public void cutRegionNodesAsync(int playerPosX, int playerPosZ)
+ {
+ AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
+
+ if (lastCutChunk == null)
+ lastCutChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
+
+ // don't run the tree cutter multiple times
+ // for the same location
+ if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ())
+ {
+ lastCutChunk = newPlayerChunk;
+
+ Thread thread = new Thread(() ->
+ {
+ int regionX;
+ int regionZ;
+ int minDistance;
+ byte detail;
+ byte minAllowedDetailLevel;
+
+ // go over every region in the dimension
+ for (int x = 0; x < regions.length; x++)
+ {
+ for (int z = 0; z < regions.length; z++)
+ {
+ regionX = (x + center.x) - halfWidth;
+ regionZ = (z + center.z) - halfWidth;
+
+ if (regions[x][z] != null)
+ {
+ // check what detail level this region should be
+ // and cut it if it is higher then that
+ minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
+ detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
+ minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
+
+ if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel)
+ {
+ regions[x][z].cutTree(minAllowedDetailLevel);
+ recreateRegionBuffer[x][z] = true;
+ }
+ }
+ }// region z
+ }// region z
+ });
+
+ cutAndExpandThread.execute(thread);
+ }
+ }
+
+ /** Either expands or loads all regions in the rendered LOD area */
+ public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ)
+ {
+ DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
+ AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
+ VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality();
+
+
+ if (lastExpandedChunk == null)
+ lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
+
+ // don't run the expander multiple times
+ // for the same location
+ if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ())
+ {
+ lastExpandedChunk = newPlayerChunk;
+
+ Thread thread = new Thread(() ->
+ {
+ int regionX;
+ int regionZ;
+ LodRegion region;
+ int minDistance;
+ byte detail;
+ byte levelToGen;
+
+ for (int x = 0; x < regions.length; x++)
+ {
+ for (int z = 0; z < regions.length; z++)
+ {
+ regionX = (x + center.x) - halfWidth;
+ regionZ = (z + center.z) - halfWidth;
+ final RegionPos regionPos = new RegionPos(regionX, regionZ);
+ region = regions[x][z];
+
+ minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
+ detail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance);
+ levelToGen = DetailDistanceUtil.getLodGenDetail(detail).detailLevel;
+
+ // check that the region isn't null and at least this detail level
+ if (region == null || region.getGenerationMode() != generationMode)
+ {
+ // First case, region has to be created
+
+ // try to get the region from file
+ regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
+
+ // if there is no region file create an empty region
+ if (regions[x][z] == null)
+ regions[x][z] = new LodRegion(levelToGen, regionPos, generationMode, verticalQuality);
+
+ regenRegionBuffer[x][z] = true;
+ regenDimensionBuffers = true;
+ recreateRegionBuffer[x][z] = true;
+ }
+ else if (region.getMinDetailLevel() > levelToGen)
+ {
+ // Second case, the region exists at a higher detail level.
+
+ // Expand the region by introducing the missing layer
+ region.growTree(levelToGen);
+ regions[x][z] = getRegionFromFile(regionPos, levelToGen, generationMode, verticalQuality);
+ recreateRegionBuffer[x][z] = true;
+ }
+ }
+ }
+ });
+
+ cutAndExpandThread.execute(thread);
+ }
+ }
+
+ /**
+ * Use addVerticalData when possible.
+ * Add the given LOD to this dimension at the coordinate
+ * stored in the LOD. If an LOD already exists at the given
+ * coordinate it will be overwritten.
+ */
+ public Boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data, boolean dontSave)
+ {
+ int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
+ int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
+
+ // don't continue if the region can't be saved
+ LodRegion region = getRegion(regionPosX, regionPosZ);
+ if (region == null)
+ return false;
+
+ boolean nodeAdded = region.addData(detailLevel, posX, posZ, verticalIndex, data);
+
+ // only save valid LODs to disk
+ if (!dontSave && fileHandler != null)
+ {
+ try
+ {
+ // mark the region as dirty, so it will be saved to disk
+ int xIndex = (regionPosX - center.x) + halfWidth;
+ int zIndex = (regionPosZ - center.z) + halfWidth;
+
+ isRegionDirty[xIndex][zIndex] = true;
+ regenRegionBuffer[xIndex][zIndex] = true;
+ regenDimensionBuffers = true;
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ e.printStackTrace();
+ // If this happens, the method was probably
+ // called when the dimension was changing size.
+ // Hopefully this shouldn't be an issue.
+ }
+ }
+
+ return nodeAdded;
+ }
+
+ /**
+ * Add whole column of LODs to this dimension at the coordinate
+ * stored in the LOD. If an LOD already exists at the given
+ * coordinate it will be overwritten.
+ */
+ public Boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data, boolean dontSave)
+ {
+ int regionPosX = LevelPosUtil.getRegion(detailLevel, posX);
+ int regionPosZ = LevelPosUtil.getRegion(detailLevel, posZ);
+
+ // don't continue if the region can't be saved
+ LodRegion region = getRegion(regionPosX, regionPosZ);
+ if (region == null)
+ return false;
+
+ boolean nodeAdded = region.addVerticalData(detailLevel, posX, posZ, data);
+
+ // only save valid LODs to disk
+ if (!dontSave && fileHandler != null)
+ {
+ try
+ {
+ // mark the region as dirty, so it will be saved to disk
+ int xIndex = (regionPosX - center.x) + halfWidth;
+ int zIndex = (regionPosZ - center.z) + halfWidth;
+
+ isRegionDirty[xIndex][zIndex] = true;
+ regenRegionBuffer[xIndex][zIndex] = true;
+ regenDimensionBuffers = true;
+ }
+ catch (ArrayIndexOutOfBoundsException e)
+ {
+ e.printStackTrace();
+ // If this happens, the method was probably
+ // called when the dimension was changing size.
+ // Hopefully this shouldn't be an issue.
+ }
+ }
+
+ return nodeAdded;
+ }
+
+ /** marks the region at the given region position to have its buffer rebuilt */
+ public void markRegionBufferToRegen(int xRegion, int zRegion)
+ {
+ int xIndex = (xRegion - center.x) + halfWidth;
+ int zIndex = (zRegion - center.z) + halfWidth;
+ regenRegionBuffer[xIndex][zIndex] = true;
+ }
+
+ /**
+ * Returns every position that need to be generated based on the position of the player
+ */
+ public PosToGenerateContainer getPosToGenerate(int maxDataToGenerate, int playerBlockPosX, int playerBlockPosZ)
+ {
+ PosToGenerateContainer posToGenerate;
+ LodRegion lodRegion;
+ // all the following values are used for the spiral matrix visit
+ // x and z are the matrix coord
+ // dx and dz is the next move on the coordinate in the range -1 0 +1
+ int x, z, dx, dz, t;
+ x = 0;
+ z = 0;
+ dx = 0;
+ dz = -1;
+
+ // We can use two type of generation scheduling
+ switch (CONFIG.client().worldGenerator().getGenerationPriority())
+ {
+ default:
+ case NEAR_FIRST:
+ //in the NEAR_FIRST generation scheduling we prioritize the nearest un-generated position to the player
+ //the chunk position to generate will be stored in a posToGenerate object
+ posToGenerate = new PosToGenerateContainer((byte) 10, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
+
+ int playerChunkX = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosX);
+ int playerChunkZ = LevelPosUtil.getChunkPos(LodUtil.BLOCK_DETAIL_LEVEL, playerBlockPosZ);
+
+ int complexity;
+ int xChunkToCheck;
+ int zChunkToCheck;
+ byte detailLevel;
+ int posX;
+ int posZ;
+ long data;
+ int numbChunksWide = (width) * 32;
+ int circleLimit = Integer.MAX_VALUE;
+
+ //posToGenerate is using an insertion sort algorithm which can become really fast if the
+ //original data order is almost ordered. For this reason we explore the matrix of the position to generate
+ //with a spiral matrix visit (a square spiral is almost ordered in the "nearest to farthest" order)
+ for (int i = 0; i < numbChunksWide * numbChunksWide; i++)
+ {
+ //Firstly we check if the posToGenerate has been filled
+ if (maxDataToGenerate == 0)
+ {
+ maxDataToGenerate--;
+ //if it has been filled then we set a stop distance
+ //the stop distance will be current distance (generically x) per square root of 2
+ //this would guarantee a circular generation since (Math.abs(x) * 1.41f) is the
+ //radius of a circle that inscribe a square
+ circleLimit = (int) (Math.abs(x) * 1.41f);
+ }
+ //This second if check if we reached the circleLimit decided in the previous if
+ //if so we stop
+ else if (maxDataToGenerate < 0)
+ {
+ if (circleLimit < Math.abs(x) && circleLimit < Math.abs(z))
+ break;
+ }
+
+
+ xChunkToCheck = x + playerChunkX;
+ zChunkToCheck = z + playerChunkZ;
+
+ //we get the lod region in which the chunk is present
+ lodRegion = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, zChunkToCheck);
+ if (lodRegion == null)
+ continue;
+
+ //Now we check if the current chunk has been generated with the correct complexity
+ //if(lodRegion.isChunkPreGenerated(xChunkToCheck,zChunkToCheck))
+ // complexity = DistanceGenerationMode.SERVER.complexity;
+ //else
+ complexity = CONFIG.client().worldGenerator().getDistanceGenerationMode().complexity;
+
+
+ //we create the level position info of the chunk
+ detailLevel = lodRegion.getMinDetailLevel();
+ posX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, xChunkToCheck, detailLevel);
+ posZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, zChunkToCheck, detailLevel);
+
+ data = getSingleData(detailLevel, posX, posZ);
+
+ //we will generate the position only if the current generation complexity is lower than the target one.
+ //an un-generated area will always have 0 generation
+ if (DataPointUtil.getGenerationMode(data) < complexity)
+ {
+ posToGenerate.addPosToGenerate(detailLevel, posX, posZ);
+ if (maxDataToGenerate >= 0)
+ maxDataToGenerate--;
+ }
+
+ //with this code section we find the next chunk to check
+ if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
+ {
+ t = dx;
+ dx = -dz;
+ dz = t;
+ }
+ x += dx;
+ z += dz;
+ }
+ break;
+
+
+ case FAR_FIRST:
+ //in the FAR_FIRST generation we dedicate part of the generation process to the far region with really
+ //low detail quality.
+
+ posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ);
+
+ int xRegion;
+ int zRegion;
+
+ for (int i = 0; i < width * width; i++)
+ {
+ xRegion = x + center.x;
+ zRegion = z + center.z;
+
+ //All of this is handled directly by the region, which scan every pos from top to bottom of the quad tree
+ lodRegion = getRegion(xRegion, zRegion);
+ if (lodRegion != null)
+ lodRegion.getPosToGenerate(posToGenerate, playerBlockPosX, playerBlockPosZ);
+
+
+ //with this code section we find the next chunk to check
+ if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z)))
+ {
+ t = dx;
+ dx = -dz;
+ dz = t;
+ }
+ x += dx;
+ z += dz;
+ }
+ break;
+ }
+ return posToGenerate;
+ }
+
+ /**
+ * Fills the posToRender with the position to render for the regionPos given in input
+ */
+ public void getPosToRender(PosToRenderContainer posToRender, RegionPos regionPos, int playerPosX,
+ int playerPosZ)
+ {
+ LodRegion region = getRegion(regionPos.x, regionPos.z);
+
+ // use FAR_FIRST on local worlds and NEAR_FIRST on servers
+ GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO && MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
+ boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST;
+
+ if (region != null)
+ region.getPosToRender(posToRender, playerPosX, playerPosZ, requireCorrectDetailLevel);
+ }
+
+ /**
+ * Determines how many vertical LODs could be used
+ * for the given region at the given detail level
+ */
+ public int getMaxVerticalData(byte detailLevel, int posX, int posZ)
+ {
+ if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
+ throw new IllegalArgumentException("getMaxVerticalData given a level of [" + detailLevel + "] when [" + LodUtil.REGION_DETAIL_LEVEL + "] is the max.");
+
+ LodRegion region = getRegion(detailLevel, posX, posZ);
+ if (region == null)
+ return 0;
+
+ return region.getMaxVerticalData(detailLevel);
+ }
+
+ /**
+ * Get the data point at the given X and Z coordinates
+ * in this dimension.
+ *
+ * Returns null if the LodChunk doesn't exist or
+ * is outside the loaded area.
+ */
+ public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
+ {
+ if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
+ throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
+
+ LodRegion region = getRegion(detailLevel, posX, posZ);
+ if (region == null)
+ return DataPointUtil.EMPTY_DATA;
+
+ return region.getData(detailLevel, posX, posZ, verticalIndex);
+ }
+
+
+ /**
+ * Get the data point at the given X and Z coordinates
+ * in this dimension.
+ *
+ * Returns null if the LodChunk doesn't exist or
+ * is outside the loaded area.
+ */
+ public long getSingleData(byte detailLevel, int posX, int posZ)
+ {
+ if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
+ throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
+
+ LodRegion region = getRegion(detailLevel, posX, posZ);
+ if (region == null)
+ return DataPointUtil.EMPTY_DATA;
+
+ return region.getSingleData(detailLevel, posX, posZ);
+ }
+
+ /** Clears the given region */
+ public void clear(byte detailLevel, int posX, int posZ)
+ {
+ if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
+ throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
+
+ LodRegion region = getRegion(detailLevel, posX, posZ);
+ if (region == null)
+ return;
+
+ region.clear(detailLevel, posX, posZ);
+ }
+
+ /**
+ * Returns if the buffer at the given array index needs
+ * to have its buffer regenerated.
+ */
+ public boolean doesRegionNeedBufferRegen(int xIndex, int zIndex)
+ {
+ return regenRegionBuffer[xIndex][zIndex] || recreateRegionBuffer[xIndex][zIndex];
+ }
+
+
+ /**
+ * Sets if the buffer at the given array index needs
+ * to have its buffer regenerated.
+ */
+ public void setRegenRegionBufferByArrayIndex(int xIndex, int zIndex, boolean newRegen)
+ {
+ regenRegionBuffer[xIndex][zIndex] = newRegen;
+ }
+
+ /**
+ * Get the data point at the given LevelPos
+ * in this dimension.
+ *
+ * Returns null if the LodChunk doesn't exist or
+ * is outside the loaded area.
+ */
+ public void updateData(byte detailLevel, int posX, int posZ)
+ {
+ if (detailLevel > LodUtil.REGION_DETAIL_LEVEL)
+ throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodUtil.REGION_DETAIL_LEVEL + "\" is the max.");
+
+ LodRegion region = getRegion(detailLevel, posX, posZ);
+ if (region == null)
+ return;
+
+ region.updateArea(detailLevel, posX, posZ);
+ }
+
+ /** Returns true if a region exists at the given LevelPos */
+ public boolean doesDataExist(byte detailLevel, int posX, int posZ)
+ {
+ LodRegion region = getRegion(detailLevel, posX, posZ);
+ return region != null && region.doesDataExist(detailLevel, posX, posZ);
+ }
+
+ /**
+ * Loads the region at the given RegionPos from file,
+ * if a file exists for that region.
+ */
+ public LodRegion getRegionFromFile(RegionPos regionPos, byte detailLevel,
+ DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
+ {
+ return fileHandler != null ? fileHandler.loadRegionFromFile(detailLevel, regionPos, generationMode, verticalQuality) : null;
+ }
+
+ /** Save all dirty regions in this LodDimension to file. */
+ public void saveDirtyRegionsToFileAsync()
+ {
+ fileHandler.saveDirtyRegionsToFileAsync();
+ }
+
+
+ /** Return true if the chunk has been pregenerated in game */
+ //public boolean isChunkPreGenerated(int xChunkPosWrapper, int zChunkPosWrapper)
+ //{
+ //
+ // LodRegion region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPosWrapper, zChunkPosWrapper);
+ // if (region == null)
+ // return false;
+ //
+ // return region.isChunkPreGenerated(xChunkPosWrapper, zChunkPosWrapper);
+ //}
+
+ /**
+ * Returns whether the region at the given RegionPos
+ * is within the loaded range.
+ */
+ public boolean regionIsInRange(int regionX, int regionZ)
+ {
+ int xIndex = (regionX - center.x) + halfWidth;
+ int zIndex = (regionZ - center.z) + halfWidth;
+
+ return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
+ }
+
+ /** Returns the dimension's center region position X value */
+ public int getCenterRegionPosX()
+ {
+ return center.x;
+ }
+
+ /** Returns the dimension's center region position Z value */
+ public int getCenterRegionPosZ()
+ {
+ return center.z;
+ }
+
+ /** returns the width of the dimension in regions */
+ public int getWidth()
+ {
+ // we want to get the length directly from the
+ // source to make sure it is in sync with region
+ // and isRegionDirty
+ return regions != null ? regions.length : width;
+ }
+
+ /** Update the width of this dimension, in regions */
+ public void setRegionWidth(int newWidth)
+ {
+ width = newWidth;
+ halfWidth = width/ 2;
+
+ regions = new LodRegion[width][width];
+ isRegionDirty = new boolean[width][width];
+ regenRegionBuffer = new boolean[width][width];
+ recreateRegionBuffer = 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()
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append("Dimension : \n");
+ for (LodRegion[] lodRegions : regions)
+ {
+ for (LodRegion region : lodRegions)
+ {
+ if (region == null)
+ stringBuilder.append("n");
+ else
+ stringBuilder.append(region.getMinDetailLevel());
+ stringBuilder.append("\t");
+ }
+ stringBuilder.append("\n");
+ }
+ return stringBuilder.toString();
+ }
+
+ public boolean GetIsRegionDirty(int i, int j)
+ {
+ return isRegionDirty[i][j];
+ }
+
+ public void SetIsRegionDirty(int i, int j, boolean val)
+ {
+ isRegionDirty[i][j] = val;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java
new file mode 100644
index 000000000..7acd6b8cf
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java
@@ -0,0 +1,611 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.lod;
+
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.VerticalQuality;
+import com.seibel.lod.core.objects.PosToGenerateContainer;
+import com.seibel.lod.core.objects.PosToRenderContainer;
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.util.DetailDistanceUtil;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * This object holds all loaded LevelContainers acting as a quad tree
+ * for a given region.
+ *
+ * Coordinate Standard:
+ * Coordinate called posX or posZ are relative LevelPos coordinates
+ * unless stated otherwise.
+ *
+ * @author Leonardo Amato
+ * @version 10-10-2021
+ */
+public class LodRegion
+{
+ /** Number of detail level supported by a region */
+ private static final byte POSSIBLE_LOD = 10;
+
+
+ /** Holds the lowest (least detailed) detail level in this region */
+ private byte minDetailLevel;
+
+ /**
+ * This holds all data for this region
+ */
+ private final LevelContainer[] dataContainer;
+
+ /** This chunk Pos has been generated */
+ //private final boolean[] preGeneratedChunkPos;
+
+ /** the generation mode for this region */
+ private final DistanceGenerationMode generationMode;
+ /** the vertical quality of this region */
+ private final VerticalQuality verticalQuality;
+
+ /** this region's x RegionPos */
+ public final int regionPosX;
+ /** this region's z RegionPos */
+ public final int regionPosZ;
+
+ public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
+ {
+ this.minDetailLevel = minDetailLevel;
+ this.regionPosX = regionPos.x;
+ this.regionPosZ = regionPos.z;
+ this.verticalQuality = verticalQuality;
+ this.generationMode = generationMode;
+ dataContainer = new LevelContainer[POSSIBLE_LOD];
+
+
+ // Initialize all the different matrices
+ for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
+ {
+ dataContainer[lod] = new VerticalLevelContainer(lod);
+ }
+
+ boolean fileFound = false;
+
+ /*
+ preGeneratedChunkPos = new boolean[32 * 32];
+ if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
+ {
+ File regionFileDirHead;
+ File regionFileDirParent;
+ // local world
+
+ ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension());
+
+ // provider needs a separate variable to prevent
+ // the compiler from complaining
+ StringBuilder string = new StringBuilder();
+ try
+ {
+ ServerChunkProvider provider = serverWorld.getChunkSource();
+
+ //System.out.println(provider.dataStorage.dataFolder);
+ regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca");
+ if (regionFileDirHead.exists())
+ {
+ regionFileDirParent = regionFileDirHead.getParentFile();
+ //string.append(regionFileDirParent.toString());
+ string.append(regionFileDirHead);
+ RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true);
+ for (int x = 0; x < 32; x++)
+ {
+ for (int z = 0; z < 32; z++)
+ {
+ preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z));
+ }
+ }
+
+ string.append("region " + regionPosX + " " + regionPosZ + "\n");
+ for (int x = 0; x < 32; x++)
+ {
+ for (int z = 0; z < 32; z++)
+ {
+ //regionFile.doesChunkExist()
+ string.append(preGeneratedChunkPos[x * 32 + z] + "\t");
+ }
+ string.append("\n");
+ }
+ regionFile.close();
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ System.out.println(string);
+ }*/
+
+ }
+
+
+ /** Return true if the chunk has been pregenerated in game */
+ //public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
+ //{
+ // xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos);
+ // zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos);
+ // return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos];
+ //}
+
+ /**
+ * Inserts the data point into the region.
+ *
+ * TODO this will always return true unless it has
+ * @return true if the data was added successfully
+ */
+ public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data)
+ {
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+
+ // The dataContainer could have null entries if the
+ // detailLevel changes.
+ if (this.dataContainer[detailLevel] == null)
+ {
+ this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
+ }
+
+ this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex);
+
+ return true;
+ }
+
+ /**
+ * Inserts the vertical data into the region.
+ *
+ * TODO this will always return true unless it has
+ * @return true if the data was added successfully
+ */
+ public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data)
+ {
+ //position is already relative
+ //posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ //posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+
+ // The dataContainer could have null entries if the
+ // detailLevel changes.
+ if (this.dataContainer[detailLevel] == null)
+ this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel);
+
+ return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ);
+ }
+
+ /**
+ * Get the dataPoint at the given relative position.
+ * @return the data at the relative pos and detail level,
+ * 0 if the data doesn't exist.
+ */
+ public long getData(byte detailLevel, int posX, int posZ, int verticalIndex)
+ {
+ return dataContainer[detailLevel].getData(posX, posZ, verticalIndex);
+ }
+
+ /**
+ * Get the dataPoint at the given relative position.
+ * @return the data at the relative pos and detail level,
+ * 0 if the data doesn't exist.
+ */
+ public long getSingleData(byte detailLevel, int posX, int posZ)
+ {
+ return dataContainer[detailLevel].getSingleData(posX, posZ);
+ }
+
+ /**
+ * Clears the datapoint at the given relative position
+ */
+ public void clear(byte detailLevel, int posX, int posZ)
+ {
+ dataContainer[detailLevel].clear(posX, posZ);
+ }
+
+ /**
+ * This method will fill the posToGenerate array with all levelPos that
+ * are render-able.
+ *
+ * TODO why don't we return the posToGenerate, it would make this easier to understand
+ */
+ public void getPosToGenerate(PosToGenerateContainer posToGenerate,
+ int playerBlockPosX, int playerBlockPosZ)
+ {
+ getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ);
+
+ }
+
+ /**
+ * A recursive method that fills the posToGenerate array with all levelPos that
+ * need to be generated.
+ *
+ * TODO why don't we return the posToGenerate, it would make this easier to understand
+ */
+ private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel,
+ int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ)
+ {
+ // equivalent to 2^(...)
+ int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+
+ // calculate what LevelPos are in range to generate
+ int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
+
+ // determine this child's levelPos
+ byte childDetailLevel = (byte) (detailLevel - 1);
+ int childPosX = childOffsetPosX * 2;
+ int childPosZ = childOffsetPosZ * 2;
+
+ int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel);
+
+ byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel;
+ if (targetDetailLevel <= detailLevel)
+ {
+ if (targetDetailLevel == detailLevel)
+ {
+ if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ))
+ posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size);
+ }
+ else
+ {
+ // we want at max one request per chunk (since the world generator creates chunks).
+ // So for lod smaller than a chunk, only recurse down
+ // the top right child
+
+ if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL)
+ {
+ int ungeneratedChildren = 0;
+
+ // make sure all children are generated to this detailLevel
+ for (int x = 0; x <= 1; x++)
+ {
+ for (int z = 0; z <= 1; z++)
+ {
+ if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
+ {
+ ungeneratedChildren++;
+ posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize);
+ }
+ }
+ }
+
+ // only if all the children are correctly generated
+ // should we go deeper
+ if (ungeneratedChildren == 0)
+ for (int x = 0; x <= 1; x++)
+ for (int z = 0; z <= 1; z++)
+ getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ);
+ }
+ else
+ {
+ // The detail Level is smaller than a chunk.
+ // Only recurse down the top right child.
+
+ if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel))
+ {
+ if (!doesDataExist(childDetailLevel, childPosX, childPosZ))
+ posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize);
+ else
+ getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ);
+ }
+ }
+ }
+ }
+ // we have gone beyond the target Detail level
+ // we can stop generating
+
+ }
+
+
+ /**
+ * This method will fill the posToRender array with all levelPos that
+ * are render-able.
+ *
+ * TODO why don't we return the posToRender, it would make this easier to understand
+ */
+ public void getPosToRender(PosToRenderContainer posToRender,
+ int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
+ {
+ getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel);
+ }
+
+ /**
+ * This method will fill the posToRender array with all levelPos that
+ * are render-able.
+ *
+ * TODO why don't we return the posToRender, it would make this easier to understand
+ * TODO this needs some more comments, James was only able to figure out part of it
+ */
+ private void getPosToRender(PosToRenderContainer posToRender,
+ byte detailLevel, int posX, int posZ,
+ int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel)
+ {
+ // equivalent to 2^(...)
+ int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+
+ byte desiredLevel;
+ int maxDistance;
+ int minDistance;
+ int childLevel;
+
+
+ // calculate the LevelPos that are in range
+ maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
+ desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance));
+ minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ);
+ childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance));
+
+ if (detailLevel == childLevel - 1)
+ {
+ posToRender.addPosToRender(detailLevel,
+ posX + regionPosX * size,
+ posZ + regionPosZ * size);
+ }
+ else
+ //if (desiredLevel > detailLevel)
+ //{
+ // we have gone beyond the target Detail level
+ // we can stop generating
+ //} else
+ if (desiredLevel == detailLevel)
+ {
+ posToRender.addPosToRender(detailLevel,
+ posX + regionPosX * size,
+ posZ + regionPosZ * size);
+ }
+ else //case where (detailLevel > desiredLevel)
+ {
+ int childPosX = posX * 2;
+ int childPosZ = posZ * 2;
+ byte childDetailLevel = (byte) (detailLevel - 1);
+ int childrenCount = 0;
+
+ for (int x = 0; x <= 1; x++)
+ {
+ for (int z = 0; z <= 1; z++)
+ {
+ if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
+ {
+ if (!requireCorrectDetailLevel)
+ childrenCount++;
+ else
+ getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
+ }
+ }
+ }
+
+
+ if (!requireCorrectDetailLevel)
+ {
+ // If all the four children exist go deeper
+ if (childrenCount == 4)
+ {
+ for (int x = 0; x <= 1; x++)
+ for (int z = 0; z <= 1; z++)
+ getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
+ }
+ else
+ {
+ posToRender.addPosToRender(detailLevel,
+ posX + regionPosX * size,
+ posZ + regionPosZ * size);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Updates all children.
+ *
+ * TODO could this be renamed mergeArea?
+ */
+ public void updateArea(byte detailLevel, int posX, int posZ)
+ {
+ int width;
+ int startX;
+ int startZ;
+
+ // TODO what are each of these loops updating?
+ for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++)
+ {
+ startX = LevelPosUtil.convert(detailLevel, posX, down);
+ startZ = LevelPosUtil.convert(detailLevel, posZ, down);
+ width = 1 << (detailLevel - down);
+
+ for (int x = 0; x < width; x++)
+ for (int z = 0; z < width; z++)
+ update(down, startX + x, startZ + z);
+ }
+
+
+ for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++)
+ {
+ update(up,
+ LevelPosUtil.convert(detailLevel, posX, up),
+ LevelPosUtil.convert(detailLevel, posZ, up));
+ }
+ }
+
+ /**
+ * Update the child at the given relative Pos
+ *
+ * TODO could this be renamed mergeChildData?
+ */
+ private void update(byte detailLevel, int posX, int posZ)
+ {
+ dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ);
+ }
+
+
+ /**
+ * Returns if data exists at the given relative Pos.
+ */
+ public boolean doesDataExist(byte detailLevel, int posX, int posZ)
+ {
+ if (detailLevel < minDetailLevel)
+ return false;
+
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+
+ if (dataContainer[detailLevel] == null)
+ return false;
+
+ return dataContainer[detailLevel].doesItExist(posX, posZ);
+ }
+
+ /**
+ * Gets the generation mode for the data point at the given relative pos.
+ */
+ public byte getGenerationMode(byte detailLevel, int posX, int posZ)
+ {
+ if (dataContainer[detailLevel].doesItExist(posX, posZ))
+ // We take the bottom information always
+ // TODO what does that mean? bottom of what?
+ return DataPointUtil.getGenerationMode(dataContainer[detailLevel].getSingleData(posX, posZ));
+ else
+ return DistanceGenerationMode.NONE.complexity;
+ }
+
+ /**
+ * Returns the lowest (least detailed) detail level in this region
+ * TODO is that right?
+ */
+ public byte getMinDetailLevel()
+ {
+ return minDetailLevel;
+ }
+
+ /**
+ * Returns the LevelContainer for the detailLevel
+ * @throws IllegalArgumentException if the detailLevel is less than minDetailLevel
+ */
+ public LevelContainer getLevel(byte detailLevel)
+ {
+ if (detailLevel < minDetailLevel)
+ throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]");
+
+ return dataContainer[detailLevel];
+ }
+
+ /**
+ * Add the levelContainer to this Region, updating the minDetailLevel
+ * if necessary.
+ * @throws IllegalArgumentException if the LevelContainer's detailLevel
+ * is 2 or more detail levels lower than the
+ * minDetailLevel of this region.
+ */
+ public void addLevelContainer(LevelContainer levelContainer)
+ {
+ if (levelContainer.getDetailLevel() < minDetailLevel - 1)
+ {
+ throw new IllegalArgumentException(
+ "the LevelContainer's detailLevel was "
+ + "[" + levelContainer.getDetailLevel() + "] but this region "
+ + "only allows adding LevelContainers with a "
+ + "detail level of [" + (minDetailLevel - 1) + "]");
+ }
+
+ if (levelContainer.getDetailLevel() == minDetailLevel - 1)
+ minDetailLevel = levelContainer.getDetailLevel();
+
+ dataContainer[levelContainer.getDetailLevel()] = levelContainer;
+ }
+
+ // TODO James thinks cutTree and growTree (which he renamed to match cutTree)
+ // should have more descriptive names, to make sure the "Tree" portion isn't
+ // confused with Minecraft trees (the plant).
+
+ /**
+ * Removes any dataContainers that are higher than
+ * the given detailLevel
+ */
+ public void cutTree(byte detailLevel)
+ {
+ if (detailLevel > minDetailLevel)
+ {
+ for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++)
+ dataContainer[detailLevelIndex] = null;
+
+ minDetailLevel = detailLevel;
+ }
+ }
+
+ /**
+ * Make this region more detailed to the detailLevel given.
+ * TODO is that correct?
+ */
+ public void growTree(byte detailLevel)
+ {
+ if (detailLevel < minDetailLevel)
+ {
+ for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--)
+ {
+ if (dataContainer[detailLevelIndex + 1] == null)
+ dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1));
+
+ dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand();
+ }
+ minDetailLevel = detailLevel;
+ }
+ }
+
+ /**
+ * return RegionPos of this lod region
+ */
+ public RegionPos getRegionPos()
+ {
+ return new RegionPos(regionPosX, regionPosZ);
+ }
+
+ /**
+ * Returns how many LODs are in this region
+ */
+ public int getNumberOfLods()
+ {
+ int count = 0;
+ for (LevelContainer container : dataContainer)
+ count += container.getMaxNumberOfLods();
+
+ return count;
+ }
+
+ public VerticalQuality getVerticalQuality()
+ {
+ return verticalQuality;
+ }
+
+ public DistanceGenerationMode getGenerationMode()
+ {
+ return generationMode;
+ }
+
+ public int getMaxVerticalData(byte detailLevel)
+ {
+ return dataContainer[detailLevel].getMaxVerticalData();
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java
new file mode 100644
index 000000000..9897d297d
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java
@@ -0,0 +1,171 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.lod;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+
+/**
+ * This stores all LODs for a given world.
+ *
+ * @author James Seibel
+ * @author Leonardo Amato
+ * @version 9-27-2021
+ */
+public class LodWorld
+{
+ /** name of this world */
+ private String worldName;
+
+ /** dimensions in this world */
+ private Map lodDimensions;
+
+ /** If true then the LOD world is setup and ready to use */
+ private boolean isWorldLoaded = false;
+
+ /** the name given to the world if it isn't loaded */
+ public static final String NO_WORLD_LOADED = "No world loaded";
+
+
+
+ public LodWorld()
+ {
+ worldName = NO_WORLD_LOADED;
+ }
+
+
+
+ /**
+ * Set up the LodWorld with the given newWorldName.
+ * This should be done whenever loading a new world.
+ *
+ * Note a System.gc() call may be in order after calling this
+ * since a lot of LOD data is now homeless.
+ * @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.
+ *
+ * Note a System.gc() call may be in order after calling this
+ * since a lot of LOD data is now homeless.
+ */
+ public void deselectWorld()
+ {
+ worldName = NO_WORLD_LOADED;
+ lodDimensions = null;
+ isWorldLoaded = false;
+ }
+
+
+ /**
+ * Adds newDimension to this world, if a LodDimension
+ * already exists for the given dimension it is replaced.
+ */
+ public void addLodDimension(LodDimension newDimension)
+ {
+ if (lodDimensions == null)
+ return;
+
+ lodDimensions.put(newDimension.dimension, newDimension);
+ }
+
+ /**
+ * Returns null if no LodDimension exists for the given dimension
+ */
+ public LodDimension getLodDimension(IDimensionTypeWrapper dimType)
+ {
+ if (lodDimensions == null)
+ return null;
+
+ return lodDimensions.get(dimType);
+ }
+
+ /**
+ * Resizes the max width in regions that each LodDimension
+ * should use.
+ */
+ public void resizeDimensionRegionWidth(int newRegionWidth)
+ {
+ if (lodDimensions == null)
+ return;
+
+ saveAllDimensions();
+
+ for (IDimensionTypeWrapper key : lodDimensions.keySet())
+ lodDimensions.get(key).setRegionWidth(newRegionWidth);
+ }
+
+ /**
+ * Requests all dimensions save any dirty regions they may have.
+ */
+ public void saveAllDimensions()
+ {
+ if (lodDimensions == null)
+ return;
+
+ // TODO we should only print this if lods were actually saved to file
+ // but that requires a LodDimension.hasDirtyRegions() method or something similar
+ ClientApi.LOGGER.info("Saving LODs");
+
+ for (IDimensionTypeWrapper key : lodDimensions.keySet())
+ lodDimensions.get(key).saveDirtyRegionsToFileAsync();
+ }
+
+
+ public boolean getIsWorldNotLoaded()
+ {
+ return !isWorldLoaded;
+ }
+
+ public String getWorldName()
+ {
+ return worldName;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "World name: " + worldName;
+ }
+}
+
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java
new file mode 100644
index 000000000..f50754d02
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java
@@ -0,0 +1,91 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.lod;
+
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+
+/**
+ * This object is similar to ChunkPos or BlockPos.
+ *
+ * @author James Seibel
+ * @version 8-21-2021
+ */
+public class RegionPos
+{
+ private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+
+ public int x;
+ public int z;
+
+
+ /** Sets x and z to 0 */
+ public RegionPos()
+ {
+ x = 0;
+ z = 0;
+ }
+
+ /** simple constructor that sets x and z to new x and z. */
+ public RegionPos(int newX, int newZ)
+ {
+ x = newX;
+ z = newZ;
+ }
+
+ /** Converts from a BlockPos to a RegionPos */
+ public RegionPos(AbstractBlockPosWrapper pos)
+ {
+ this(WRAPPER_FACTORY.createChunkPos(pos));
+ }
+
+ /** Converts from a ChunkPos to a RegionPos */
+ public RegionPos(AbstractChunkPosWrapper pos)
+ {
+ x = Math.floorDiv(pos.getX(), LodUtil.REGION_WIDTH_IN_CHUNKS);
+ z = Math.floorDiv(pos.getZ(), LodUtil.REGION_WIDTH_IN_CHUNKS);
+ }
+
+ /** Returns the ChunkPos at the center of this region */
+ public AbstractChunkPosWrapper chunkPos()
+ {
+ return WRAPPER_FACTORY.createChunkPos(
+ (x * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2,
+ (z * LodUtil.REGION_WIDTH_IN_CHUNKS) + LodUtil.REGION_WIDTH_IN_CHUNKS / 2);
+ }
+
+ /** Returns the BlockPos at the center of this region */
+ public AbstractBlockPosWrapper blockPos()
+ {
+ return chunkPos().getWorldPosition()
+ .offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return "(" + x + "," + z + ")";
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
new file mode 100644
index 000000000..f813a9a86
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
@@ -0,0 +1,289 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.lod;
+
+import com.seibel.lod.core.util.DataPointUtil;
+import com.seibel.lod.core.util.DetailDistanceUtil;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.ThreadMapUtil;
+
+/**
+ *
+ * @author Leonardo Amato
+ * @version ??
+ */
+public class VerticalLevelContainer implements LevelContainer
+{
+
+ public final byte detailLevel;
+ public final int size;
+ public final int maxVerticalData;
+
+ public final long[] dataContainer;
+
+ public VerticalLevelContainer(byte detailLevel)
+ {
+ this.detailLevel = detailLevel;
+ size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ maxVerticalData = DetailDistanceUtil.getMaxVerticalData(detailLevel);
+ dataContainer = new long[size * size * DetailDistanceUtil.getMaxVerticalData(detailLevel)];
+ }
+
+ @Override
+ public byte getDetailLevel()
+ {
+ return detailLevel;
+ }
+
+ @Override
+ public void clear(int posX, int posZ)
+ {
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+ for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++)
+ {
+ dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = DataPointUtil.EMPTY_DATA;
+ }
+ }
+
+ @Override
+ public boolean addData(long data, int posX, int posZ, int verticalIndex)
+ {
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+ dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data;
+ return true;
+ }
+
+ @Override
+ public boolean addVerticalData(long[] data, int posX, int posZ)
+ {
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+ for (int verticalIndex = 0; verticalIndex < maxVerticalData; verticalIndex++)
+ dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex] = data[verticalIndex];
+ return true;
+ }
+
+ @Override
+ public boolean addSingleData(long data, int posX, int posZ)
+ {
+ return addData(data, posX, posZ, 0);
+ }
+
+ @Override
+ public long getData(int posX, int posZ, int verticalIndex)
+ {
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+ return dataContainer[posX * size * maxVerticalData + posZ * maxVerticalData + verticalIndex];
+ }
+
+ @Override
+ public long getSingleData(int posX, int posZ)
+ {
+ return getData(posX, posZ, 0);
+ }
+
+ @Override
+ public int getMaxVerticalData()
+ {
+ return maxVerticalData;
+ }
+
+ public int getSize()
+ {
+ return size;
+ }
+
+ @Override
+ public boolean doesItExist(int posX, int posZ)
+ {
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+ return DataPointUtil.doesItExist(getSingleData(posX, posZ));
+ }
+
+ public VerticalLevelContainer(byte[] inputData, int version)
+ {
+ int tempMaxVerticalData;
+ int tempIndex;
+ int index = 0;
+ long newData;
+ detailLevel = inputData[index];
+ index++;
+ tempMaxVerticalData = inputData[index] & 0b01111111;
+ index++;
+ size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ int x = size * size * tempMaxVerticalData;
+ long[] tempDataContainer = new long[x];
+
+ if (version == 6)
+ {
+ for (int i = 0; i < x; i++)
+ {
+ newData = 0;
+ for (tempIndex = 0; tempIndex < 8; tempIndex++)
+ newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
+ index += 8;
+
+ newData = DataPointUtil.createDataPoint(
+ DataPointUtil.getAlpha(newData),
+ DataPointUtil.getRed(newData),
+ DataPointUtil.getGreen(newData),
+ DataPointUtil.getBlue(newData),
+ DataPointUtil.getHeight(newData) - DataPointUtil.VERTICAL_OFFSET,
+ DataPointUtil.getDepth(newData) - DataPointUtil.VERTICAL_OFFSET,
+ DataPointUtil.getLightSky(newData),
+ DataPointUtil.getLightBlock(newData),
+ DataPointUtil.getGenerationMode(newData),
+ DataPointUtil.getFlag(newData)
+ );
+ tempDataContainer[i] = newData;
+ }
+ }
+ else //if (version == 7)
+ {
+ for (int i = 0; i < x; i++)
+ {
+ newData = 0;
+ for (tempIndex = 0; tempIndex < 8; tempIndex++)
+ newData += (((long) inputData[index + tempIndex]) & 0xff) << (8 * tempIndex);
+ index += 8;
+ tempDataContainer[i] = newData;
+ }
+ }
+
+ if (tempMaxVerticalData > DetailDistanceUtil.getMaxVerticalData(detailLevel))
+ {
+ int tempMaxVerticalData2 = DetailDistanceUtil.getMaxVerticalData(detailLevel);
+ long[] dataToMerge = new long[tempMaxVerticalData];
+ long[] tempDataContainer2 = new long[size * size * tempMaxVerticalData2];
+ for (int i = 0; i < size * size; i++)
+ {
+ System.arraycopy(tempDataContainer, i * tempMaxVerticalData, dataToMerge, 0, tempMaxVerticalData);
+ dataToMerge = DataPointUtil.mergeMultiData(dataToMerge, tempMaxVerticalData, tempMaxVerticalData2);
+ System.arraycopy(dataToMerge, 0, tempDataContainer2, i * tempMaxVerticalData2, tempMaxVerticalData2);
+ }
+ maxVerticalData = tempMaxVerticalData2;
+ this.dataContainer = tempDataContainer2;
+ }
+ else
+ {
+ maxVerticalData = tempMaxVerticalData;
+ this.dataContainer = tempDataContainer;
+ }
+ }
+
+ @Override
+ public LevelContainer expand()
+ {
+ return new VerticalLevelContainer((byte) (getDetailLevel() - 1));
+ }
+
+ @Override
+ public void updateData(LevelContainer lowerLevelContainer, int posX, int posZ)
+ {
+ //We reset the array
+ long[] dataToMerge = ThreadMapUtil.getVerticalUpdateArray(detailLevel);
+
+ int lowerMaxVertical = dataToMerge.length / 4;
+ int childPosX;
+ int childPosZ;
+ long[] data;
+ posX = LevelPosUtil.getRegionModule(detailLevel, posX);
+ posZ = LevelPosUtil.getRegionModule(detailLevel, posZ);
+ for (int x = 0; x <= 1; x++)
+ {
+ for (int z = 0; z <= 1; z++)
+ {
+ childPosX = 2 * posX + x;
+ childPosZ = 2 * posZ + z;
+ for (int verticalIndex = 0; verticalIndex < lowerMaxVertical; verticalIndex++)
+ dataToMerge[(z * 2 + x) * lowerMaxVertical + verticalIndex] = lowerLevelContainer.getData(childPosX, childPosZ, verticalIndex);
+ }
+ }
+ data = DataPointUtil.mergeMultiData(dataToMerge, lowerMaxVertical, getMaxVerticalData());
+
+ addVerticalData(data, posX, posZ);
+ }
+
+ @Override
+ public byte[] toDataString()
+ {
+ int index = 0;
+ int x = size * size;
+ int tempIndex;
+ long current;
+ boolean allGenerated = true;
+ byte[] tempData = ThreadMapUtil.getSaveContainer(detailLevel);
+
+ tempData[index] = detailLevel;
+ index++;
+ tempData[index] = (byte) maxVerticalData;
+ index++;
+ int j;
+ for (int i = 0; i < x; i++)
+ {
+ for (j = 0; j < maxVerticalData; j++)
+ {
+ current = dataContainer[i * maxVerticalData + j];
+ for (tempIndex = 0; tempIndex < 8; tempIndex++)
+ tempData[index + tempIndex] = (byte) (current >>> (8 * tempIndex));
+ index += 8;
+ }
+ if(!DataPointUtil.doesItExist(dataContainer[i]))
+ allGenerated = false;
+ }
+ if (allGenerated)
+ tempData[1] |= 0b10000000;
+ return tempData;
+ }
+
+ @Override
+ @SuppressWarnings("unused")
+ public String toString()
+ {
+ /*
+ StringBuilder stringBuilder = new StringBuilder();
+ int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ stringBuilder.append(detailLevel);
+ stringBuilder.append(DATA_DELIMITER);
+ for (int x = 0; x < size; x++)
+ {
+ for (int z = 0; z < size; z++)
+ {
+ //Converting the dataToHex
+ stringBuilder.append(Long.toHexString(dataContainer[x][z][0]));
+ stringBuilder.append(DATA_DELIMITER);
+ }
+ }
+ return stringBuilder.toString();
+ */
+ return " ";
+ }
+
+ @Override
+ public int getMaxNumberOfLods()
+ {
+ return size * size * getMaxVerticalData();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java b/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java
new file mode 100644
index 000000000..88588131e
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java
@@ -0,0 +1,543 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.math;
+
+import java.nio.FloatBuffer;
+
+/**
+ * A (almost) exact copy of Minecraft's 1.16.5
+ * implementation of a 4x4 float matrix.
+ *
+ * @author James Seibel
+ * @version 11-11-2021
+ */
+public class Mat4f
+{
+ private float m00;
+ private float m01;
+ private float m02;
+ private float m03;
+ private float m10;
+ private float m11;
+ private float m12;
+ private float m13;
+ private float m20;
+ private float m21;
+ private float m22;
+ private float m23;
+ private float m30;
+ private float m31;
+ private float m32;
+ private float m33;
+
+
+ public Mat4f()
+ {
+
+ }
+
+ public Mat4f(Mat4f sourceMatrix)
+ {
+ this.m00 = sourceMatrix.m00;
+ this.m01 = sourceMatrix.m01;
+ this.m02 = sourceMatrix.m02;
+ this.m03 = sourceMatrix.m03;
+ this.m10 = sourceMatrix.m10;
+ this.m11 = sourceMatrix.m11;
+ this.m12 = sourceMatrix.m12;
+ this.m13 = sourceMatrix.m13;
+ this.m20 = sourceMatrix.m20;
+ this.m21 = sourceMatrix.m21;
+ this.m22 = sourceMatrix.m22;
+ this.m23 = sourceMatrix.m23;
+ this.m30 = sourceMatrix.m30;
+ this.m31 = sourceMatrix.m31;
+ this.m32 = sourceMatrix.m32;
+ this.m33 = sourceMatrix.m33;
+ }
+
+ /* Quaternions are not currently needed/implemented
+ public Matrix4float(Quaternion p_i48104_1_)
+ {
+ float f = p_i48104_1_.i();
+ float f1 = p_i48104_1_.j();
+ float f2 = p_i48104_1_.k();
+ float f3 = p_i48104_1_.r();
+ float f4 = 2.0F * f * f;
+ float f5 = 2.0F * f1 * f1;
+ float f6 = 2.0F * f2 * f2;
+ this.m00 = 1.0F - f5 - f6;
+ this.m11 = 1.0F - f6 - f4;
+ this.m22 = 1.0F - f4 - f5;
+ this.m33 = 1.0F;
+ float f7 = f * f1;
+ float f8 = f1 * f2;
+ float f9 = f2 * f;
+ float f10 = f * f3;
+ float f11 = f1 * f3;
+ float f12 = f2 * f3;
+ this.m10 = 2.0F * (f7 + f12);
+ this.m01 = 2.0F * (f7 - f12);
+ this.m20 = 2.0F * (f9 - f11);
+ this.m02 = 2.0F * (f9 + f11);
+ this.m21 = 2.0F * (f8 + f10);
+ this.m12 = 2.0F * (f8 - f10);
+ }
+ */
+
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj != null && this.getClass() == obj.getClass())
+ {
+ Mat4f otherMatrix = (Mat4f) obj;
+ return Float.compare(otherMatrix.m00, this.m00) == 0
+ && Float.compare(otherMatrix.m01, this.m01) == 0
+ && Float.compare(otherMatrix.m02, this.m02) == 0
+ && Float.compare(otherMatrix.m03, this.m03) == 0
+ && Float.compare(otherMatrix.m10, this.m10) == 0
+ && Float.compare(otherMatrix.m11, this.m11) == 0
+ && Float.compare(otherMatrix.m12, this.m12) == 0
+ && Float.compare(otherMatrix.m13, this.m13) == 0
+ && Float.compare(otherMatrix.m20, this.m20) == 0
+ && Float.compare(otherMatrix.m21, this.m21) == 0
+ && Float.compare(otherMatrix.m22, this.m22) == 0
+ && Float.compare(otherMatrix.m23, this.m23) == 0
+ && Float.compare(otherMatrix.m30, this.m30) == 0
+ && Float.compare(otherMatrix.m31, this.m31) == 0
+ && Float.compare(otherMatrix.m32, this.m32) == 0
+ && Float.compare(otherMatrix.m33, this.m33) == 0;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int i = this.m00 != 0.0F ? Float.floatToIntBits(this.m00) : 0;
+ i = 31 * i + (this.m01 != 0.0F ? Float.floatToIntBits(this.m01) : 0);
+ i = 31 * i + (this.m02 != 0.0F ? Float.floatToIntBits(this.m02) : 0);
+ i = 31 * i + (this.m03 != 0.0F ? Float.floatToIntBits(this.m03) : 0);
+ i = 31 * i + (this.m10 != 0.0F ? Float.floatToIntBits(this.m10) : 0);
+ i = 31 * i + (this.m11 != 0.0F ? Float.floatToIntBits(this.m11) : 0);
+ i = 31 * i + (this.m12 != 0.0F ? Float.floatToIntBits(this.m12) : 0);
+ i = 31 * i + (this.m13 != 0.0F ? Float.floatToIntBits(this.m13) : 0);
+ i = 31 * i + (this.m20 != 0.0F ? Float.floatToIntBits(this.m20) : 0);
+ i = 31 * i + (this.m21 != 0.0F ? Float.floatToIntBits(this.m21) : 0);
+ i = 31 * i + (this.m22 != 0.0F ? Float.floatToIntBits(this.m22) : 0);
+ i = 31 * i + (this.m23 != 0.0F ? Float.floatToIntBits(this.m23) : 0);
+ i = 31 * i + (this.m30 != 0.0F ? Float.floatToIntBits(this.m30) : 0);
+ i = 31 * i + (this.m31 != 0.0F ? Float.floatToIntBits(this.m31) : 0);
+ i = 31 * i + (this.m32 != 0.0F ? Float.floatToIntBits(this.m32) : 0);
+ return 31 * i + (this.m33 != 0.0F ? Float.floatToIntBits(this.m33) : 0);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return "Matrix4f:\n" +
+ this.m00 + " " + this.m01 + " " + this.m02 + " " + this.m03 + "\n" +
+ this.m10 + " " + this.m11 + " " + this.m12 + " " + this.m13 + "\n" +
+ this.m20 + " " + this.m21 + " " + this.m22 + " " + this.m23 + "\n" +
+ this.m30 + " " + this.m31 + " " + this.m32 + " " + this.m33 + "\n";
+ }
+
+
+ public void store(FloatBuffer floatBuffer)
+ {
+ floatBuffer.put(bufferIndex(0, 0), this.m00);
+ floatBuffer.put(bufferIndex(0, 1), this.m01);
+ floatBuffer.put(bufferIndex(0, 2), this.m02);
+ floatBuffer.put(bufferIndex(0, 3), this.m03);
+ floatBuffer.put(bufferIndex(1, 0), this.m10);
+ floatBuffer.put(bufferIndex(1, 1), this.m11);
+ floatBuffer.put(bufferIndex(1, 2), this.m12);
+ floatBuffer.put(bufferIndex(1, 3), this.m13);
+ floatBuffer.put(bufferIndex(2, 0), this.m20);
+ floatBuffer.put(bufferIndex(2, 1), this.m21);
+ floatBuffer.put(bufferIndex(2, 2), this.m22);
+ floatBuffer.put(bufferIndex(2, 3), this.m23);
+ floatBuffer.put(bufferIndex(3, 0), this.m30);
+ floatBuffer.put(bufferIndex(3, 1), this.m31);
+ floatBuffer.put(bufferIndex(3, 2), this.m32);
+ floatBuffer.put(bufferIndex(3, 3), this.m33);
+ }
+
+ private static int bufferIndex(int xIndex, int zIndex)
+ {
+ return (zIndex * 4) + xIndex;
+ }
+
+
+ public void setIdentity()
+ {
+ this.m00 = 1.0F;
+ this.m01 = 0.0F;
+ this.m02 = 0.0F;
+ this.m03 = 0.0F;
+ this.m10 = 0.0F;
+ this.m11 = 1.0F;
+ this.m12 = 0.0F;
+ this.m13 = 0.0F;
+ this.m20 = 0.0F;
+ this.m21 = 0.0F;
+ this.m22 = 1.0F;
+ this.m23 = 0.0F;
+ this.m30 = 0.0F;
+ this.m31 = 0.0F;
+ this.m32 = 0.0F;
+ this.m33 = 1.0F;
+ }
+
+ /** adjugate and determinate */
+ public float adjugateAndDet()
+ {
+ float f = this.m00 * this.m11 - this.m01 * this.m10;
+ float f1 = this.m00 * this.m12 - this.m02 * this.m10;
+ float f2 = this.m00 * this.m13 - this.m03 * this.m10;
+ float f3 = this.m01 * this.m12 - this.m02 * this.m11;
+ float f4 = this.m01 * this.m13 - this.m03 * this.m11;
+ float f5 = this.m02 * this.m13 - this.m03 * this.m12;
+ float f6 = this.m20 * this.m31 - this.m21 * this.m30;
+ float f7 = this.m20 * this.m32 - this.m22 * this.m30;
+ float f8 = this.m20 * this.m33 - this.m23 * this.m30;
+ float f9 = this.m21 * this.m32 - this.m22 * this.m31;
+ float f10 = this.m21 * this.m33 - this.m23 * this.m31;
+ float f11 = this.m22 * this.m33 - this.m23 * this.m32;
+ float f12 = this.m11 * f11 - this.m12 * f10 + this.m13 * f9;
+ float f13 = -this.m10 * f11 + this.m12 * f8 - this.m13 * f7;
+ float f14 = this.m10 * f10 - this.m11 * f8 + this.m13 * f6;
+ float f15 = -this.m10 * f9 + this.m11 * f7 - this.m12 * f6;
+ float f16 = -this.m01 * f11 + this.m02 * f10 - this.m03 * f9;
+ float f17 = this.m00 * f11 - this.m02 * f8 + this.m03 * f7;
+ float f18 = -this.m00 * f10 + this.m01 * f8 - this.m03 * f6;
+ float f19 = this.m00 * f9 - this.m01 * f7 + this.m02 * f6;
+ float f20 = this.m31 * f5 - this.m32 * f4 + this.m33 * f3;
+ float f21 = -this.m30 * f5 + this.m32 * f2 - this.m33 * f1;
+ float f22 = this.m30 * f4 - this.m31 * f2 + this.m33 * f;
+ float f23 = -this.m30 * f3 + this.m31 * f1 - this.m32 * f;
+ float f24 = -this.m21 * f5 + this.m22 * f4 - this.m23 * f3;
+ float f25 = this.m20 * f5 - this.m22 * f2 + this.m23 * f1;
+ float f26 = -this.m20 * f4 + this.m21 * f2 - this.m23 * f;
+ float f27 = this.m20 * f3 - this.m21 * f1 + this.m22 * f;
+ this.m00 = f12;
+ this.m10 = f13;
+ this.m20 = f14;
+ this.m30 = f15;
+ this.m01 = f16;
+ this.m11 = f17;
+ this.m21 = f18;
+ this.m31 = f19;
+ this.m02 = f20;
+ this.m12 = f21;
+ this.m22 = f22;
+ this.m32 = f23;
+ this.m03 = f24;
+ this.m13 = f25;
+ this.m23 = f26;
+ this.m33 = f27;
+ return f * f11 - f1 * f10 + f2 * f9 + f3 * f8 - f4 * f7 + f5 * f6;
+ }
+
+ public void transpose()
+ {
+ float f = this.m10;
+ this.m10 = this.m01;
+ this.m01 = f;
+ f = this.m20;
+ this.m20 = this.m02;
+ this.m02 = f;
+ f = this.m21;
+ this.m21 = this.m12;
+ this.m12 = f;
+ f = this.m30;
+ this.m30 = this.m03;
+ this.m03 = f;
+ f = this.m31;
+ this.m31 = this.m13;
+ this.m13 = f;
+ f = this.m32;
+ this.m32 = this.m23;
+ this.m23 = f;
+ }
+
+ public boolean invert()
+ {
+ float det = this.adjugateAndDet();
+ if (Math.abs(det) > 1.0E-6F)
+ {
+ this.multiply(det);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public void multiply(Mat4f multMatrix)
+ {
+ float f = this.m00 * multMatrix.m00 + this.m01 * multMatrix.m10 + this.m02 * multMatrix.m20 + this.m03 * multMatrix.m30;
+ float f1 = this.m00 * multMatrix.m01 + this.m01 * multMatrix.m11 + this.m02 * multMatrix.m21 + this.m03 * multMatrix.m31;
+ float f2 = this.m00 * multMatrix.m02 + this.m01 * multMatrix.m12 + this.m02 * multMatrix.m22 + this.m03 * multMatrix.m32;
+ float f3 = this.m00 * multMatrix.m03 + this.m01 * multMatrix.m13 + this.m02 * multMatrix.m23 + this.m03 * multMatrix.m33;
+ float f4 = this.m10 * multMatrix.m00 + this.m11 * multMatrix.m10 + this.m12 * multMatrix.m20 + this.m13 * multMatrix.m30;
+ float f5 = this.m10 * multMatrix.m01 + this.m11 * multMatrix.m11 + this.m12 * multMatrix.m21 + this.m13 * multMatrix.m31;
+ float f6 = this.m10 * multMatrix.m02 + this.m11 * multMatrix.m12 + this.m12 * multMatrix.m22 + this.m13 * multMatrix.m32;
+ float f7 = this.m10 * multMatrix.m03 + this.m11 * multMatrix.m13 + this.m12 * multMatrix.m23 + this.m13 * multMatrix.m33;
+ float f8 = this.m20 * multMatrix.m00 + this.m21 * multMatrix.m10 + this.m22 * multMatrix.m20 + this.m23 * multMatrix.m30;
+ float f9 = this.m20 * multMatrix.m01 + this.m21 * multMatrix.m11 + this.m22 * multMatrix.m21 + this.m23 * multMatrix.m31;
+ float f10 = this.m20 * multMatrix.m02 + this.m21 * multMatrix.m12 + this.m22 * multMatrix.m22 + this.m23 * multMatrix.m32;
+ float f11 = this.m20 * multMatrix.m03 + this.m21 * multMatrix.m13 + this.m22 * multMatrix.m23 + this.m23 * multMatrix.m33;
+ float f12 = this.m30 * multMatrix.m00 + this.m31 * multMatrix.m10 + this.m32 * multMatrix.m20 + this.m33 * multMatrix.m30;
+ float f13 = this.m30 * multMatrix.m01 + this.m31 * multMatrix.m11 + this.m32 * multMatrix.m21 + this.m33 * multMatrix.m31;
+ float f14 = this.m30 * multMatrix.m02 + this.m31 * multMatrix.m12 + this.m32 * multMatrix.m22 + this.m33 * multMatrix.m32;
+ float f15 = this.m30 * multMatrix.m03 + this.m31 * multMatrix.m13 + this.m32 * multMatrix.m23 + this.m33 * multMatrix.m33;
+ this.m00 = f;
+ this.m01 = f1;
+ this.m02 = f2;
+ this.m03 = f3;
+ this.m10 = f4;
+ this.m11 = f5;
+ this.m12 = f6;
+ this.m13 = f7;
+ this.m20 = f8;
+ this.m21 = f9;
+ this.m22 = f10;
+ this.m23 = f11;
+ this.m30 = f12;
+ this.m31 = f13;
+ this.m32 = f14;
+ this.m33 = f15;
+ }
+
+ /* Quaternions aren't currently needed/implemented
+ public void multiply(Quaternion p_226596_1_)
+ {
+ this.multiply(new Matrix4f(p_226596_1_));
+ }
+ */
+
+ public void multiply(float scalar)
+ {
+ this.m00 *= scalar;
+ this.m01 *= scalar;
+ this.m02 *= scalar;
+ this.m03 *= scalar;
+ this.m10 *= scalar;
+ this.m11 *= scalar;
+ this.m12 *= scalar;
+ this.m13 *= scalar;
+ this.m20 *= scalar;
+ this.m21 *= scalar;
+ this.m22 *= scalar;
+ this.m23 *= scalar;
+ this.m30 *= scalar;
+ this.m31 *= scalar;
+ this.m32 *= scalar;
+ this.m33 *= scalar;
+ }
+
+ public static Mat4f perspective(double fov, float widthHeightRatio, float nearClipPlane, float farClipPlane)
+ {
+ float f = (float) (1.0D / Math.tan(fov * ((float) Math.PI / 180F) / 2.0D));
+ Mat4f matrix = new Mat4f();
+ matrix.m00 = f / widthHeightRatio;
+ matrix.m11 = f;
+ matrix.m22 = (farClipPlane + nearClipPlane) / (nearClipPlane - farClipPlane);
+ matrix.m32 = -1.0F;
+ matrix.m23 = 2.0F * farClipPlane * nearClipPlane / (nearClipPlane - farClipPlane);
+ return matrix;
+ }
+
+
+ /* not currently needed/implemented
+ * Also the parameter names should be double checked as they may be incorrect
+ public static Matrix4Float orthographic(float left, float right, float top, float bottom)
+ {
+ Matrix4Float matrix4f = new Matrix4Float();
+ matrix4f.m00 = 2.0F / left;
+ matrix4f.m11 = 2.0F / right;
+ float f = bottom - top;
+ matrix4f.m22 = -2.0F / f;
+ matrix4f.m33 = 1.0F;
+ matrix4f.m03 = -1.0F;
+ matrix4f.m13 = -1.0F;
+ matrix4f.m23 = -(bottom + top) / f;
+ return matrix4f;
+ }
+ */
+
+ /**
+ * TODO: what kind of translation is this?
+ * and how is this different from "multiplyTranslationMatrix"?
+ */
+ public void translate(Vec3f vec)
+ {
+ this.m03 += vec.x;
+ this.m13 += vec.y;
+ this.m23 += vec.z;
+ }
+
+ /** originally "translate" from Minecraft's MatrixStack */
+ public void multiplyTranslationMatrix(double x, double y, double z)
+ {
+ multiply(createTranslateMatrix((float)x, (float)y, (float)z));
+ }
+
+ public Mat4f copy()
+ {
+ return new Mat4f(this);
+ }
+
+ public static Mat4f createScaleMatrix(float x, float y, float z)
+ {
+ Mat4f matrix = new Mat4f();
+ matrix.m00 = x;
+ matrix.m11 = y;
+ matrix.m22 = z;
+ matrix.m33 = 1.0F;
+ return matrix;
+ }
+
+ public static Mat4f createTranslateMatrix(float x, float y, float z)
+ {
+ Mat4f matrix = new Mat4f();
+ matrix.m00 = 1.0F;
+ matrix.m11 = 1.0F;
+ matrix.m22 = 1.0F;
+ matrix.m33 = 1.0F;
+ matrix.m03 = x;
+ matrix.m13 = y;
+ matrix.m23 = z;
+ return matrix;
+ }
+
+
+ // Forge start
+ public Mat4f(float[] values)
+ {
+ m00 = values[0];
+ m01 = values[1];
+ m02 = values[2];
+ m03 = values[3];
+ m10 = values[4];
+ m11 = values[5];
+ m12 = values[6];
+ m13 = values[7];
+ m20 = values[8];
+ m21 = values[9];
+ m22 = values[10];
+ m23 = values[11];
+ m30 = values[12];
+ m31 = values[13];
+ m32 = values[14];
+ m33 = values[15];
+ }
+
+ public Mat4f(FloatBuffer buffer)
+ {
+ this(buffer.array());
+ }
+
+ public void set(Mat4f mat)
+ {
+ this.m00 = mat.m00;
+ this.m01 = mat.m01;
+ this.m02 = mat.m02;
+ this.m03 = mat.m03;
+ this.m10 = mat.m10;
+ this.m11 = mat.m11;
+ this.m12 = mat.m12;
+ this.m13 = mat.m13;
+ this.m20 = mat.m20;
+ this.m21 = mat.m21;
+ this.m22 = mat.m22;
+ this.m23 = mat.m23;
+ this.m30 = mat.m30;
+ this.m31 = mat.m31;
+ this.m32 = mat.m32;
+ this.m33 = mat.m33;
+ }
+
+ public void add(Mat4f other)
+ {
+ m00 += other.m00;
+ m01 += other.m01;
+ m02 += other.m02;
+ m03 += other.m03;
+ m10 += other.m10;
+ m11 += other.m11;
+ m12 += other.m12;
+ m13 += other.m13;
+ m20 += other.m20;
+ m21 += other.m21;
+ m22 += other.m22;
+ m23 += other.m23;
+ m30 += other.m30;
+ m31 += other.m31;
+ m32 += other.m32;
+ m33 += other.m33;
+ }
+
+ public void multiplyBackward(Mat4f other)
+ {
+ Mat4f copy = other.copy();
+ copy.multiply(this);
+ this.set(copy);
+ }
+
+ public void setTranslation(float x, float y, float z)
+ {
+ this.m00 = 1.0F;
+ this.m11 = 1.0F;
+ this.m22 = 1.0F;
+ this.m33 = 1.0F;
+ this.m03 = x;
+ this.m13 = y;
+ this.m23 = z;
+ }
+
+ /**
+ * Changes the values that store the clipping planes.
+ * Formula for calculating matrix values is the same that OpenGL uses when making matrices.
+ *
+ * @param nearClip New near clipping plane value.
+ * @param farClip New far clipping plane value.
+ */
+ public void setClipPlanes(float nearClip,float farClip)
+ {
+ //convert to matrix values, formula copied from a textbook / openGL specification.
+ float matNearClip = -((farClip + nearClip) / (farClip - nearClip));
+ float matFarClip = -((2 * farClip * nearClip) / (farClip - nearClip));
+ //set new values for the clip planes.
+ this.m22 = matNearClip;
+ this.m23 = matFarClip;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java b/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java
new file mode 100644
index 000000000..c2ba8aa24
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java
@@ -0,0 +1,257 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.math;
+
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * This is closer to MC's implementation of a
+ * 3 element float vector than a 3 element double
+ * vector. Hopefully that shouldn't cause any issues.
+ *
+ * @author James Seibel
+ * @version 11-18-2021
+ */
+public class Vec3d
+{
+ public static Vec3d XNeg = new Vec3d(-1.0F, 0.0F, 0.0F);
+ public static Vec3d XPos = new Vec3d(1.0F, 0.0F, 0.0F);
+ public static Vec3d YNeg = new Vec3d(0.0F, -1.0F, 0.0F);
+ public static Vec3d YPos = new Vec3d(0.0F, 1.0F, 0.0F);
+ public static Vec3d ZNeg = new Vec3d(0.0F, 0.0F, -1.0F);
+ public static Vec3d ZPos = new Vec3d(0.0F, 0.0F, 1.0F);
+
+ public static final Vec3d ZERO_VECTOR = new Vec3d(0.0D, 0.0D, 0.0D);
+
+ public double x;
+ public double y;
+ public double z;
+
+
+
+ public Vec3d()
+ {
+
+ }
+
+ public Vec3d(double x, double y, double z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj != null && this.getClass() == obj.getClass())
+ {
+ Vec3d Vec3f = (Vec3d) obj;
+ if (Double.compare(Vec3f.x, this.x) != 0)
+ {
+ return false;
+ }
+ else if (Double.compare(Vec3f.y, this.y) != 0)
+ {
+ return false;
+ }
+ else
+ {
+ return Double.compare(Vec3f.z, this.z) == 0;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ long longVal = Double.doubleToLongBits(this.x);
+
+ int intVal = (int) (longVal ^ longVal >>> 32);
+ longVal = Double.doubleToLongBits(this.y);
+ intVal = 31 * intVal + (int) (longVal ^ longVal >>> 32);
+ longVal = Double.doubleToLongBits(this.z);
+
+ return 31 * intVal + (int) (longVal ^ longVal >>> 32);
+ }
+
+ public void mul(double scalar)
+ {
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ }
+
+ public void mul(double x, double y, double z)
+ {
+ this.x *= x;
+ this.y *= y;
+ this.z *= z;
+ }
+
+ public void clamp(double min, double max)
+ {
+ this.x = LodUtil.clamp(min, this.x, max);
+ this.y = LodUtil.clamp(min, this.y, max);
+ this.z = LodUtil.clamp(min, this.z, max);
+ }
+
+ public void set(double x, double y, double z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public void add(double x, double y, double z)
+ {
+ this.x += x;
+ this.y += y;
+ this.z += z;
+ }
+
+ public void add(Vec3d vector)
+ {
+ this.x += vector.x;
+ this.y += vector.y;
+ this.z += vector.z;
+ }
+
+ public void subtract(Vec3d vector)
+ {
+ this.x -= vector.x;
+ this.y -= vector.y;
+ this.z -= vector.z;
+ }
+
+ public double dotProduct(Vec3d vector)
+ {
+ return this.x * vector.x + this.y * vector.y + this.z * vector.z;
+ }
+
+ public Vec3d normalize()
+ {
+ double value = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
+ return value < 1.0E-4D ? ZERO_VECTOR : new Vec3d(this.x / value, this.y / value, this.z / value);
+ }
+
+ public void crossProduct(Vec3d vector)
+ {
+ double f = this.x;
+ double f1 = this.y;
+ double f2 = this.z;
+ double f3 = vector.x;
+ double f4 = vector.y;
+ double f5 = vector.z;
+ this.x = f1 * f5 - f2 * f4;
+ this.y = f2 * f3 - f * f5;
+ this.z = f * f4 - f1 * f3;
+ }
+
+ /* Matrix3f is not currently needed/implemented
+ public void transform(Matrix3f p_229188_1_)
+ {
+ double f = this.x;
+ double f1 = this.y;
+ double f2 = this.z;
+ this.x = p_229188_1_.m00 * f + p_229188_1_.m01 * f1 + p_229188_1_.m02 * f2;
+ this.y = p_229188_1_.m10 * f + p_229188_1_.m11 * f1 + p_229188_1_.m12 * f2;
+ this.z = p_229188_1_.m20 * f + p_229188_1_.m21 * f1 + p_229188_1_.m22 * f2;
+ }
+ */
+
+ /* Quaternions are not currently needed/implemented
+ public void transform(Quaternion p_214905_1_)
+ {
+ Quaternion quaternion = new Quaternion(p_214905_1_);
+ quaternion.mul(new Quaternion(this.x(), this.y(), this.z(), 0.0F));
+ Quaternion quaternion1 = new Quaternion(p_214905_1_);
+ quaternion1.conj();
+ quaternion.mul(quaternion1);
+ this.set(quaternion.i(), quaternion.j(), quaternion.k());
+ }
+ */
+
+ /* not currently needed
+ * percent may actually be partial ticks (which is available when rendering)
+ public void linearInterp(Vec3f resultingVector, double percent)
+ {
+ double f = 1.0F - percent;
+ this.x = this.x * f + resultingVector.x * percent;
+ this.y = this.y * f + resultingVector.y * percent;
+ this.z = this.z * f + resultingVector.z * percent;
+ }
+ */
+
+ /* Quaternions are not currently needed/implemented
+ public Quaternion rotation(double p_229193_1_)
+ {
+ return new Quaternion(this, p_229193_1_, false);
+ }
+
+
+ @OnlyIn(Dist.CLIENT)
+ public Quaternion rotationDegrees(double p_229187_1_)
+ {
+ return new Quaternion(this, p_229187_1_, true);
+ }
+ */
+
+ public Vec3d copy()
+ {
+ return new Vec3d(this.x, this.y, this.z);
+ }
+
+ /* not currently needed/implemented
+ public void map(double2doubleFunction p_229191_1_)
+ {
+ this.x = p_229191_1_.get(this.x);
+ this.y = p_229191_1_.get(this.y);
+ this.z = p_229191_1_.get(this.z);
+ }
+ */
+
+ @Override
+ public String toString()
+ {
+ return "[" + this.x + ", " + this.y + ", " + this.z + "]";
+ }
+
+ // Forge start
+ public Vec3d(double[] values)
+ {
+ set(values);
+ }
+
+ public void set(double[] values)
+ {
+ this.x = values[0];
+ this.y = values[1];
+ this.z = values[2];
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java b/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java
new file mode 100644
index 000000000..fe25d8889
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java
@@ -0,0 +1,261 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.math;
+
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * A (almost) exact copy of Minecraft's 1.16.5
+ * implementation of a 3 element float vector.
+ *
+ * @author James Seibel
+ * @version 11-11-2021
+ */
+public class Vec3f
+{
+ public static Vec3f XNeg = new Vec3f(-1.0F, 0.0F, 0.0F);
+ public static Vec3f XPos = new Vec3f(1.0F, 0.0F, 0.0F);
+ public static Vec3f YNeg = new Vec3f(0.0F, -1.0F, 0.0F);
+ public static Vec3f YPos = new Vec3f(0.0F, 1.0F, 0.0F);
+ public static Vec3f ZNeg = new Vec3f(0.0F, 0.0F, -1.0F);
+ public static Vec3f ZPos = new Vec3f(0.0F, 0.0F, 1.0F);
+
+
+ public float x;
+ public float y;
+ public float z;
+
+
+
+ public Vec3f()
+ {
+
+ }
+
+ public Vec3f(float x, float y, float z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj != null && this.getClass() == obj.getClass())
+ {
+ Vec3f Vec3f = (Vec3f) obj;
+ if (Float.compare(Vec3f.x, this.x) != 0)
+ {
+ return false;
+ }
+ else if (Float.compare(Vec3f.y, this.y) != 0)
+ {
+ return false;
+ }
+ else
+ {
+ return Float.compare(Vec3f.z, this.z) == 0;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int i = Float.floatToIntBits(this.x);
+ i = 31 * i + Float.floatToIntBits(this.y);
+ return 31 * i + Float.floatToIntBits(this.z);
+ }
+
+ public void mul(float scalar)
+ {
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ }
+
+ public void mul(float x, float y, float z)
+ {
+ this.x *= x;
+ this.y *= y;
+ this.z *= z;
+ }
+
+ public void clamp(float min, float max)
+ {
+ this.x = LodUtil.clamp(min, this.x, max);
+ this.y = LodUtil.clamp(min, this.y, max);
+ this.z = LodUtil.clamp(min, this.z, max);
+ }
+
+ public void set(float x, float y, float z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public void add(float x, float y, float z)
+ {
+ this.x += x;
+ this.y += y;
+ this.z += z;
+ }
+
+ public void add(Vec3f vector)
+ {
+ this.x += vector.x;
+ this.y += vector.y;
+ this.z += vector.z;
+ }
+
+ public void subtract(Vec3f vector)
+ {
+ this.x -= vector.x;
+ this.y -= vector.y;
+ this.z -= vector.z;
+ }
+
+ public float dotProduct(Vec3f vector)
+ {
+ return this.x * vector.x + this.y * vector.y + this.z * vector.z;
+ }
+
+ public boolean normalize()
+ {
+ float squaredSum = this.x * this.x + this.y * this.y + this.z * this.z;
+ if (squaredSum < 1.0E-5D)
+ {
+ return false;
+ }
+ else
+ {
+ float f1 = LodUtil.fastInvSqrt(squaredSum);
+ this.x *= f1;
+ this.y *= f1;
+ this.z *= f1;
+ return true;
+ }
+ }
+
+ public void crossProduct(Vec3f vector)
+ {
+ float f = this.x;
+ float f1 = this.y;
+ float f2 = this.z;
+ float f3 = vector.x;
+ float f4 = vector.y;
+ float f5 = vector.z;
+ this.x = f1 * f5 - f2 * f4;
+ this.y = f2 * f3 - f * f5;
+ this.z = f * f4 - f1 * f3;
+ }
+
+ /* Matrix3f is not currently needed/implemented
+ public void transform(Matrix3f p_229188_1_)
+ {
+ float f = this.x;
+ float f1 = this.y;
+ float f2 = this.z;
+ this.x = p_229188_1_.m00 * f + p_229188_1_.m01 * f1 + p_229188_1_.m02 * f2;
+ this.y = p_229188_1_.m10 * f + p_229188_1_.m11 * f1 + p_229188_1_.m12 * f2;
+ this.z = p_229188_1_.m20 * f + p_229188_1_.m21 * f1 + p_229188_1_.m22 * f2;
+ }
+ */
+
+ /* Quaternions are not currently needed/implemented
+ public void transform(Quaternion p_214905_1_)
+ {
+ Quaternion quaternion = new Quaternion(p_214905_1_);
+ quaternion.mul(new Quaternion(this.x(), this.y(), this.z(), 0.0F));
+ Quaternion quaternion1 = new Quaternion(p_214905_1_);
+ quaternion1.conj();
+ quaternion.mul(quaternion1);
+ this.set(quaternion.i(), quaternion.j(), quaternion.k());
+ }
+ */
+
+ /* not currently needed
+ * percent may actually be partial ticks (which is available when rendering)
+ public void linearInterp(Vec3f resultingVector, float percent)
+ {
+ float f = 1.0F - percent;
+ this.x = this.x * f + resultingVector.x * percent;
+ this.y = this.y * f + resultingVector.y * percent;
+ this.z = this.z * f + resultingVector.z * percent;
+ }
+ */
+
+ /* Quaternions are not currently needed/implemented
+ public Quaternion rotation(float p_229193_1_)
+ {
+ return new Quaternion(this, p_229193_1_, false);
+ }
+
+
+ @OnlyIn(Dist.CLIENT)
+ public Quaternion rotationDegrees(float p_229187_1_)
+ {
+ return new Quaternion(this, p_229187_1_, true);
+ }
+ */
+
+ public Vec3f copy()
+ {
+ return new Vec3f(this.x, this.y, this.z);
+ }
+
+ /* not currently needed/implemented
+ public void map(Float2FloatFunction p_229191_1_)
+ {
+ this.x = p_229191_1_.get(this.x);
+ this.y = p_229191_1_.get(this.y);
+ this.z = p_229191_1_.get(this.z);
+ }
+ */
+
+ @Override
+ public String toString()
+ {
+ return "[" + this.x + ", " + this.y + ", " + this.z + "]";
+ }
+
+ // Forge start
+ public Vec3f(float[] values)
+ {
+ set(values);
+ }
+
+ public void set(float[] values)
+ {
+ this.x = values[0];
+ this.y = values[1];
+ this.z = values[2];
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java b/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java
new file mode 100644
index 000000000..a65a404f0
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java
@@ -0,0 +1,203 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.math;
+
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * A (almost) exact copy of Minecraft's 1.16.5
+ * implementation of a 3 element integer vector.
+ *
+ * @author James Seibel
+ * @version 11-11-2021
+ */
+public class Vec3i
+{
+ public static Vec3i XNeg = new Vec3i(-1, 0, 0);
+ public static Vec3i XPos = new Vec3i(1, 0, 0);
+ public static Vec3i YNeg = new Vec3i(0, -1, 0);
+ public static Vec3i YPos = new Vec3i(0, 1, 0);
+ public static Vec3i ZNeg = new Vec3i(0, 0, -1);
+ public static Vec3i ZPos = new Vec3i(0, 0, 1);
+
+
+ public int x;
+ public int y;
+ public int z;
+
+
+
+ public Vec3i()
+ {
+
+ }
+
+ public Vec3i(int x, int y, int z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj != null && this.getClass() == obj.getClass())
+ {
+ Vec3i Vec3f = (Vec3i) obj;
+ if (Float.compare(Vec3f.x, this.x) != 0)
+ {
+ return false;
+ }
+ else if (Float.compare(Vec3f.y, this.y) != 0)
+ {
+ return false;
+ }
+ else
+ {
+ return Float.compare(Vec3f.z, this.z) == 0;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int i = Float.floatToIntBits(this.x);
+ i = 31 * i + Float.floatToIntBits(this.y);
+ return 31 * i + Float.floatToIntBits(this.z);
+ }
+
+ public void mul(float scalar)
+ {
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ }
+
+ public void mul(float x, float y, float z)
+ {
+ this.x *= x;
+ this.y *= y;
+ this.z *= z;
+ }
+
+ public void clamp(int min, int max)
+ {
+ this.x = LodUtil.clamp(min, this.x, max);
+ this.y = LodUtil.clamp(min, this.y, max);
+ this.z = LodUtil.clamp(min, this.z, max);
+ }
+
+ public void set(int x, int y, int z)
+ {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public void add(int x, int y, int z)
+ {
+ this.x += x;
+ this.y += y;
+ this.z += z;
+ }
+
+ public void add(Vec3i vector)
+ {
+ this.x += vector.x;
+ this.y += vector.y;
+ this.z += vector.z;
+ }
+
+ public void subtract(Vec3i vector)
+ {
+ this.x -= vector.x;
+ this.y -= vector.y;
+ this.z -= vector.z;
+ }
+
+ public double distSqr(double x, double y, double z, boolean centerOfBlock)
+ {
+ double offset = centerOfBlock ? 0.5 : 0.0;
+ double xAdd = this.x + offset - x;
+ double yAdd = this.y + offset - y;
+ double zAdd = this.z + offset - z;
+ return (xAdd * xAdd) + (yAdd * yAdd) + (zAdd * zAdd);
+ }
+
+ public int distManhattan(Vec3i otherVec)
+ {
+ float xSub = Math.abs(otherVec.x - this.x);
+ float ySub = Math.abs(otherVec.y - this.y);
+ float zSub = Math.abs(otherVec.z - this.z);
+ return (int) (xSub + ySub + zSub);
+ }
+
+ /** inner product */
+ public float dotProduct(Vec3i vector)
+ {
+ return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z);
+ }
+
+ /** Cross product */
+ public Vec3i cross(Vec3i otherVec)
+ {
+ return new Vec3i(
+ (this.y * otherVec.z) - (this.z * otherVec.y),
+ (this.z * otherVec.x) - (this.x * otherVec.z),
+ (this.x * otherVec.y) - (this.y * otherVec.x));
+ }
+
+ public Vec3i copy()
+ {
+ return new Vec3i(this.x, this.y, this.z);
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return "[" + this.x + ", " + this.y + ", " + this.z + "]";
+ }
+
+
+ // Forge start
+ public Vec3i(int[] values)
+ {
+ set(values);
+ }
+
+ public void set(int[] values)
+ {
+ this.x = values[0];
+ this.y = values[1];
+ this.z = values[2];
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java b/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java
new file mode 100644
index 000000000..116ed0f0c
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.opengl;
+
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A (almost) exact copy of MC's
+ * DefaultVertexFormats class.
+ *
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public class DefaultLodVertexFormats
+{
+ public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 3);
+ public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4);
+ public static final LodVertexFormatElement ELEMENT_UV = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.FLOAT, 2);
+ public static final LodVertexFormatElement ELEMENT_LIGHT_MAP_UV = new LodVertexFormatElement(1, LodVertexFormatElement.DataType.SHORT, 2);
+ public static final LodVertexFormatElement ELEMENT_NORMAL = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 3);
+ public static final LodVertexFormatElement ELEMENT_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1);
+
+
+ public static final LodVertexFormat POSITION = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).build());
+ public static final LodVertexFormat POSITION_COLOR = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).build());
+ public static final LodVertexFormat POSITION_COLOR_LIGHTMAP = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_LIGHT_MAP_UV).build());
+ public static final LodVertexFormat POSITION_TEX = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_UV).build());
+ public static final LodVertexFormat POSITION_COLOR_TEX = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).build());
+ public static final LodVertexFormat POSITION_COLOR_TEX_LIGHTMAP = new LodVertexFormat(ImmutableList.builder().add(ELEMENT_POSITION).add(ELEMENT_COLOR).add(ELEMENT_UV).add(ELEMENT_LIGHT_MAP_UV).build());
+
+}
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java
new file mode 100644
index 000000000..29d764092
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java
@@ -0,0 +1,543 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.opengl;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+
+/**
+ * A (almost) exact copy of Minecraft's
+ * BufferBuilder object.
+ * Which allows for creating and filling
+ * OpenGL buffers.
+ *
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public class LodBufferBuilder
+{
+ private static final Logger LOGGER = LogManager.getLogger();
+ public ByteBuffer buffer;
+
+ private final List vertexCounts = Lists.newArrayList();
+ private int lastRenderedCountIndex = 0;
+ private int totalRenderedBytes = 0;
+ private int nextElementByte = 0;
+ private int totalUploadedBytes = 0;
+ private int vertices;
+ private LodVertexFormatElement currentElement;
+ private int elementIndex;
+ private int mode;
+ private LodVertexFormat format;
+ private boolean building;
+
+
+
+
+ public LodBufferBuilder(int bufferSizeInBytes)
+ {
+ this.buffer = allocateByteBuffer(bufferSizeInBytes * 4);
+ }
+
+
+
+ /** originally from MC's GLAllocation class */
+ private ByteBuffer allocateByteBuffer(int bufferSizeInBytes)
+ {
+ return ByteBuffer.allocateDirect(bufferSizeInBytes).order(ByteOrder.nativeOrder());
+ }
+ /** originally from MC's GLAllocation class */
+ @SuppressWarnings("unused")
+ private FloatBuffer allocateFloatBuffer(int bufferSizeInBytes)
+ {
+ return allocateByteBuffer(bufferSizeInBytes).asFloatBuffer();
+ }
+
+
+
+ /** make sure the buffer doesn't overflow when inserting new elements */
+ private void ensureVertexCapacity()
+ {
+ this.ensureCapacity(this.format.getVertexSize());
+ }
+ private void ensureCapacity(int vertexSizeInBytes)
+ {
+ if (this.nextElementByte + vertexSizeInBytes > this.buffer.capacity())
+ {
+ int i = this.buffer.capacity();
+ int j = i + roundUp(vertexSizeInBytes);
+ //LOGGER.debug("Needed to grow BufferBuilder buffer: Old size {} bytes, new size {} bytes.", i, j);
+ ByteBuffer bytebuffer = allocateByteBuffer(j);
+ this.buffer.position(0);
+ bytebuffer.put(this.buffer);
+ bytebuffer.rewind();
+ this.buffer = bytebuffer;
+ }
+ }
+ private static int roundUp(int vertexSizeInBytes)
+ {
+ int i = 2097152; // 2 ^ 21
+ if (vertexSizeInBytes == 0)
+ {
+ return i;
+ }
+ else
+ {
+ if (vertexSizeInBytes < 0)
+ {
+ i *= -1;
+ }
+
+ int j = vertexSizeInBytes % i;
+ return j == 0 ? vertexSizeInBytes : vertexSizeInBytes + i - j;
+ }
+ }
+
+
+ /* not currently needed sortQuads()
+ // the x,y,z location is a blockPos
+ public void sortQuads(float x, float y, float z)
+ {
+ ((Buffer) this.buffer).clear();
+ FloatBuffer floatbuffer = this.buffer.asFloatBuffer();
+ int i = this.vertices / 4;
+ float[] afloat = new float[i];
+
+ for (int j = 0; j < i; ++j)
+ {
+ afloat[j] = getQuadDistanceFromPlayer(floatbuffer, x, y, z, this.format.getIntegerSize(), this.totalRenderedBytes / 4 + j * this.format.getVertexSize());
+ }
+
+ int[] aint = new int[i];
+
+ for (int k = 0; k < aint.length; aint[k] = k++)
+ {
+ }
+
+ IntArrays.mergeSort(aint, (p_227830_1_, p_227830_2_) ->
+ {
+ return Floats.compare(afloat[p_227830_2_], afloat[p_227830_1_]);
+ });
+ BitSet bitset = new BitSet();
+ FloatBuffer floatbuffer1 = allocateFloatBuffer(this.format.getIntegerSize() * 4);
+
+ for (int l = bitset.nextClearBit(0); l < aint.length; l = bitset.nextClearBit(l + 1))
+ {
+ int i1 = aint[l];
+ if (i1 != l)
+ {
+ this.limitToVertex(floatbuffer, i1);
+ ((Buffer) floatbuffer1).clear();
+ floatbuffer1.put(floatbuffer);
+ int j1 = i1;
+
+ for (int k1 = aint[i1]; j1 != l; k1 = aint[k1])
+ {
+ this.limitToVertex(floatbuffer, k1);
+ FloatBuffer floatbuffer2 = floatbuffer.slice();
+ this.limitToVertex(floatbuffer, j1);
+ floatbuffer.put(floatbuffer2);
+ bitset.set(j1);
+ j1 = k1;
+ }
+
+ this.limitToVertex(floatbuffer, l);
+ ((Buffer) floatbuffer1).flip();
+ floatbuffer.put(floatbuffer1);
+ }
+
+ bitset.set(l);
+ }
+ }
+
+
+ private void limitToVertex(FloatBuffer p_227829_1_, int p_227829_2_)
+ {
+ int i = this.format.getIntegerSize() * 4;
+ ((Buffer) p_227829_1_).limit(this.totalRenderedBytes / 4 + (p_227829_2_ + 1) * i);
+ ((Buffer) p_227829_1_).position(this.totalRenderedBytes / 4 + p_227829_2_ * i);
+ }
+ */
+
+ /* not curerntly needed getState()
+ public LodBufferBuilder.State getState()
+ {
+ ((Buffer) this.buffer).limit(this.nextElementByte);
+ ((Buffer) this.buffer).position(this.totalRenderedBytes);
+ ByteBuffer bytebuffer = ByteBuffer.allocate(this.vertices * this.format.getVertexSize());
+ bytebuffer.put(this.buffer);
+ ((Buffer) this.buffer).clear();
+ return new LodBufferBuilder.State(bytebuffer, this.format);
+ }
+ */
+
+ /* not currently needed getQuadDistanceFromPlayer()
+ private static float getQuadDistanceFromPlayer(FloatBuffer floatBuffer, float x, float y, float z, int p_181665_4_, int p_181665_5_)
+ {
+ float f = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 0);
+ float f1 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 1);
+ float f2 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 0 + 2);
+ float f3 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 0);
+ float f4 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 1);
+ float f5 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 1 + 2);
+ float f6 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 0);
+ float f7 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 1);
+ float f8 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 2 + 2);
+ float f9 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 0);
+ float f10 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 1);
+ float f11 = floatBuffer.get(p_181665_5_ + p_181665_4_ * 3 + 2);
+ float f12 = (f + f3 + f6 + f9) * 0.25F - x;
+ float f13 = (f1 + f4 + f7 + f10) * 0.25F - y;
+ float f14 = (f2 + f5 + f8 + f11) * 0.25F - z;
+ return f12 * f12 + f13 * f13 + f14 * f14;
+ }
+ */
+
+ /* not currently needed restoreState()
+ public void restoreState(LodBufferBuilder.State bufferState)
+ {
+ ((Buffer) bufferState.data).clear();
+ int i = bufferState.data.capacity();
+ this.ensureCapacity(i);
+ ((Buffer) this.buffer).limit(this.buffer.capacity());
+ ((Buffer) this.buffer).position(this.totalRenderedBytes);
+ this.buffer.put(bufferState.data);
+ ((Buffer) this.buffer).clear();
+ LodVertexFormat LodVertexFormat = bufferState.format;
+ this.switchFormat(LodVertexFormat);
+ this.vertices = i / LodVertexFormat.getVertexSize();
+ this.nextElementByte = this.totalRenderedBytes + this.vertices * LodVertexFormat.getVertexSize();
+ }
+ */
+
+
+
+
+
+ private void switchFormat(LodVertexFormat newFormat)
+ {
+ format = newFormat;
+ }
+
+
+
+
+ //========================================//
+ // methods for actually building a buffer //
+ //========================================//
+
+ /**
+ * @param openGlLodVertexFormat GL11.GL_QUADS, GL11.GL_TRIANGLES, etc.
+ * @param LodVertexFormat
+ */
+ public void begin(int openGlLodVertexFormat, LodVertexFormat LodVertexFormat)
+ {
+ if (this.building)
+ {
+ throw new IllegalStateException("Already building!");
+ }
+ else
+ {
+ this.building = true;
+ this.mode = openGlLodVertexFormat;
+ this.switchFormat(LodVertexFormat);
+ this.currentElement = LodVertexFormat.getElements().get(0);
+ this.elementIndex = 0;
+ this.buffer.clear();
+ }
+ }
+
+ public void end()
+ {
+ if (!this.building)
+ {
+ throw new IllegalStateException("Not building!");
+ }
+ else
+ {
+ this.building = false;
+ this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode));
+ this.totalRenderedBytes += this.vertices * this.format.getVertexSize();
+ this.vertices = 0;
+ this.currentElement = null;
+ this.elementIndex = 0;
+ }
+ }
+
+ public void putByte(int index, byte newByte)
+ {
+ this.buffer.put(this.nextElementByte + index, newByte);
+ }
+
+ public void putShort(int index, short newShort)
+ {
+ this.buffer.putShort(this.nextElementByte + index, newShort);
+ }
+
+ public void putFloat(int index, float newFloat)
+ {
+ this.buffer.putFloat(this.nextElementByte + index, newFloat);
+ }
+
+ public void endVertex()
+ {
+ if (this.elementIndex != 0)
+ {
+ throw new IllegalStateException("Not filled all elements of the vertex");
+ }
+ else
+ {
+ ++this.vertices;
+ this.ensureVertexCapacity();
+ }
+ }
+
+ public void nextElement()
+ {
+ ImmutableList immutablelist = this.format.getElements();
+ this.elementIndex = (this.elementIndex + 1) % immutablelist.size();
+ this.nextElementByte += this.currentElement.getByteSize();
+ this.currentElement = immutablelist.get(this.elementIndex);
+// if (LodVertexFormatelement.getUsage() == LodVertexFormatElement.Usage.PADDING)
+// {
+// this.nextElement();
+// }
+
+// if (this.defaultColorSet && this.currentElement.getUsage() == LodVertexFormatElement.Usage.COLOR)
+// {
+// color(this.defaultR, this.defaultG, this.defaultB, this.defaultA);
+// }
+
+ }
+
+ public LodBufferBuilder color(int red, int green, int blue, int alpha)
+ {
+ LodVertexFormatElement LodVertexFormatelement = this.currentElement();
+ if (LodVertexFormatelement.getType() != LodVertexFormatElement.DataType.UBYTE)
+ {
+ throw new IllegalStateException("Color must be stored as a UBYTE");
+ }
+ else
+ {
+ this.putByte(0, (byte) red);
+ this.putByte(1, (byte) green);
+ this.putByte(2, (byte) blue);
+ this.putByte(3, (byte) alpha);
+ this.nextElement();
+ return this;
+ }
+ }
+
+ public LodBufferBuilder vertex(float x, float y, float z)
+ {
+ if (this.currentElement().getType() != LodVertexFormatElement.DataType.FLOAT)
+ {
+ throw new IllegalStateException("Position verticies must be stored as a FLOAT");
+ }
+ else
+ {
+ this.putFloat(0, x);
+ this.putFloat(4, y);
+ this.putFloat(8, z);
+ this.nextElement();
+ return this;
+ }
+ }
+
+
+
+
+
+
+ /* not currently needed fullVertex()
+ * TODO James isn't sure about these names
+ public void vertex(float blockPosX, float blockPosY, float blockPosZ,
+ float red, float green, float blue, float alpha,
+ float textureU, float textureV,
+ int p_225588_10_, int p_225588_11_,
+ float p_225588_12_, float p_225588_13_, float p_225588_14_)
+ {
+ if (this.defaultColorSet)
+ {
+ throw new IllegalStateException();
+ }
+ else if (this.fastFormat)
+ {
+ this.putFloat(0, blockPosX);
+ this.putFloat(4, blockPosY);
+ this.putFloat(8, blockPosZ);
+ this.putByte(12, (byte) ((int) (red * 255.0F)));
+ this.putByte(13, (byte) ((int) (green * 255.0F)));
+ this.putByte(14, (byte) ((int) (blue * 255.0F)));
+ this.putByte(15, (byte) ((int) (alpha * 255.0F)));
+ this.putFloat(16, textureU);
+ this.putFloat(20, textureV);
+ int i;
+ if (this.fullFormat)
+ {
+ this.putShort(24, (short) (p_225588_10_ & '\uffff'));
+ this.putShort(26, (short) (p_225588_10_ >> 16 & '\uffff'));
+ i = 28;
+ }
+ else
+ {
+ i = 24;
+ }
+
+ this.putShort(i + 0, (short) (p_225588_11_ & '\uffff'));
+ this.putShort(i + 2, (short) (p_225588_11_ >> 16 & '\uffff'));
+ this.putByte(i + 4, IVertexConsumer.normalIntValue(p_225588_12_));
+ this.putByte(i + 5, IVertexConsumer.normalIntValue(p_225588_13_));
+ this.putByte(i + 6, IVertexConsumer.normalIntValue(p_225588_14_));
+ this.nextElementByte += i + 8;
+ this.endVertex();
+ }
+ else
+ {
+ super.vertex(blockPosX, blockPosY, blockPosZ, red, green, blue, alpha, textureU, textureV, p_225588_10_, p_225588_11_, p_225588_12_, p_225588_13_, p_225588_14_);
+ }
+ }
+ */
+
+ /**
+ * James isn't sure what the difference between
+ * using this method and just directly getting the buffer would be.
+ * But this was what was being used before, so it will stay for now.
+ *
+ * If anyone figures out what is special about this, please replace this comment.
+ */
+ public ByteBuffer getCleanedByteBuffer()
+ {
+ LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
+ this.buffer.position(this.totalUploadedBytes);
+ this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize();
+ this.buffer.limit(this.totalUploadedBytes);
+ if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
+ {
+ this.clear();
+ }
+
+ ByteBuffer bytebuffer = this.buffer.slice();
+ bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
+ this.buffer.clear();
+ return bytebuffer; // the original method also returned bufferbuilder$drawstate
+ }
+
+
+ public void clear()
+ {
+ if (this.totalRenderedBytes != this.totalUploadedBytes)
+ {
+ LOGGER.warn("Bytes mismatch " + this.totalRenderedBytes + " " + this.totalUploadedBytes);
+ }
+
+ this.discard();
+ }
+
+ public void discard()
+ {
+ this.totalRenderedBytes = 0;
+ this.totalUploadedBytes = 0;
+ this.nextElementByte = 0;
+ this.vertexCounts.clear();
+ this.lastRenderedCountIndex = 0;
+ }
+
+ public LodVertexFormatElement currentElement()
+ {
+ if (this.currentElement == null)
+ {
+ throw new IllegalStateException("BufferBuilder not started");
+ }
+ else
+ {
+ return this.currentElement;
+ }
+ }
+
+ public boolean building()
+ {
+ return this.building;
+ }
+
+
+
+
+
+ //==================//
+ // internal classes //
+ //==================//
+
+
+ public static final class DrawState
+ {
+ private final LodVertexFormat format;
+ private final int vertexCount;
+ private final int mode;
+
+ private DrawState(LodVertexFormat p_i225905_1_, int p_i225905_2_, int p_i225905_3_)
+ {
+ this.format = p_i225905_1_;
+ this.vertexCount = p_i225905_2_;
+ this.mode = p_i225905_3_;
+ }
+
+ public LodVertexFormat format()
+ {
+ return this.format;
+ }
+
+ public int vertexCount()
+ {
+ return this.vertexCount;
+ }
+
+ public int mode()
+ {
+ return this.mode;
+ }
+ }
+
+
+
+ // Forge added methods
+ public void putBulkData(ByteBuffer buffer)
+ {
+ ensureCapacity(buffer.limit() + this.format.getVertexSize());
+ this.buffer.position(this.vertices * this.format.getVertexSize());
+ this.buffer.put(buffer);
+ this.vertices += buffer.limit() / this.format.getVertexSize();
+ this.nextElementByte += buffer.limit();
+ }
+
+ public LodVertexFormat getLodVertexFormat()
+ {
+ return this.format;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
new file mode 100644
index 000000000..a933e84ba
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
@@ -0,0 +1,57 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.opengl;
+
+import org.lwjgl.opengl.GL15;
+
+import com.seibel.lod.core.enums.rendering.GLProxyContext;
+import com.seibel.lod.core.render.GLProxy;
+
+/**
+ * This is a container for a OpenGL
+ * VBO (Vertex Buffer Object).
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class LodVertexBuffer implements AutoCloseable
+{
+ public int id;
+ public int vertexCount;
+
+ public LodVertexBuffer()
+ {
+ if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
+ throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
+
+ this.id = GL15.glGenBuffers();
+ }
+
+
+ @Override
+ public void close()
+ {
+ if (this.id >= 0)
+ {
+ GLProxy.getInstance().recordOpenGlCall(() -> GL15.glDeleteBuffers(this.id));
+ this.id = -1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java
new file mode 100644
index 000000000..a35cee4e9
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java
@@ -0,0 +1,186 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.opengl;
+
+import java.util.stream.Collectors;
+
+import com.google.common.collect.ImmutableList;
+
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+
+/**
+ * This is used to represent a single vertex
+ * stored in GPU memory,
+ *
+ * A (almost) exact copy of Minecraft's
+ * VertexFormat class, several methods
+ * were commented out since we didn't need them.
+ *
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public class LodVertexFormat
+{
+ private final ImmutableList elements;
+ private final IntList offsets = new IntArrayList();
+ private final int vertexSize;
+
+ public LodVertexFormat(ImmutableList elementList)
+ {
+ this.elements = elementList;
+ int i = 0;
+
+ for (LodVertexFormatElement LodVertexFormatElement : elementList)
+ {
+ this.offsets.add(i);
+ i += LodVertexFormatElement.getByteSize();
+ }
+
+ this.vertexSize = i;
+ }
+
+ public int getIntegerSize()
+ {
+ return this.getVertexSize() / 4;
+ }
+
+ public int getVertexSize()
+ {
+ return this.vertexSize;
+ }
+
+ public ImmutableList getElements()
+ {
+ return this.elements;
+ }
+
+
+ // Forge added method
+ public int getOffset(int index)
+ {
+ return offsets.getInt(index);
+ }
+
+
+
+ @Override
+ public String toString()
+ {
+ return "format: " + this.elements.size() + " elements: " + this.elements.stream().map(Object::toString).collect(Collectors.joining(" "));
+ }
+
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj != null && this.getClass() == obj.getClass())
+ {
+ LodVertexFormat vertexformat = (LodVertexFormat) obj;
+ return this.vertexSize == vertexformat.vertexSize && this.elements.equals(vertexformat.elements);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return this.elements.hashCode();
+ }
+
+
+
+
+
+
+
+ /* not currently needed setupBufferState()
+ public void setupBufferState(long p_227892_1_)
+ {
+ if (!RenderSystem.isOnRenderThread())
+ {
+ RenderSystem.recordRenderCall(() ->
+ {
+ this.setupBufferState(p_227892_1_);
+ });
+ }
+ else
+ {
+ int i = this.getVertexSize();
+ List list = this.getElements();
+
+ for (int j = 0; j < list.size(); ++j)
+ {
+ list.get(j).setupBufferState(p_227892_1_ + this.offsets.getInt(j), i);
+ }
+
+ }
+ }
+ */
+
+ /* not currently needed clearBufferState()
+ public void clearBufferState()
+ {
+ if (!RenderSystem.isOnRenderThread())
+ {
+ RenderSystem.recordRenderCall(this::clearBufferState);
+ }
+ else
+ {
+ for (LodVertexFormatElement LodVertexFormatElement : this.getElements())
+ {
+ LodVertexFormatElement.clearBufferState();
+ }
+
+ }
+ }
+ */
+
+
+ /* not currently needed has-Position/Normal/Color/UV
+ public boolean hasPosition()
+ {
+ return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.POSITION);
+ }
+
+ public boolean hasNormal()
+ {
+ return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.NORMAL);
+ }
+
+ public boolean hasColor()
+ {
+ return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.COLOR);
+ }
+
+ public boolean hasUV(int which)
+ {
+ return elements.stream().anyMatch(e -> e.getUsage() == LodVertexFormatElement.Usage.UV && e.getIndex() == which);
+ }
+ */
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java
new file mode 100644
index 000000000..a85ca82cf
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java
@@ -0,0 +1,161 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.opengl;
+
+import org.lwjgl.opengl.GL11;
+
+/**
+ * This object is used to build LodVertexFormats.
+ *
+ * A (almost) exact copy of Minecraft's
+ * VertexFormatElement class.
+ * A number of things were removed from the original
+ * object since we didn't need them, specifically "usage".
+ *
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public class LodVertexFormatElement
+{
+ private final LodVertexFormatElement.DataType dataType;
+ /** James isn't sure what index is for */
+ private final int index;
+ private final int count;
+ private final int byteSize;
+
+ public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount)
+ {
+ this.dataType = newType;
+ this.index = newIndex;
+ this.count = newCount;
+ this.byteSize = newType.getSize() * this.count;
+ }
+
+ public final LodVertexFormatElement.DataType getType()
+ {
+ return this.dataType;
+ }
+
+ public final int getIndex()
+ {
+ return this.index;
+ }
+
+ public final int getByteSize()
+ {
+ return this.byteSize;
+ }
+
+ // added by Forge
+ public int getElementCount()
+ {
+ return count;
+ }
+
+
+
+ public enum DataType
+ {
+ FLOAT(4, "Float", GL11.GL_FLOAT),
+ UBYTE(1, "Unsigned Byte", GL11.GL_UNSIGNED_BYTE),
+ BYTE(1, "Byte", GL11.GL_BYTE),
+ USHORT(2, "Unsigned Short", GL11.GL_UNSIGNED_SHORT),
+ SHORT(2, "Short", GL11.GL_SHORT),
+ UINT(4, "Unsigned Int", GL11.GL_UNSIGNED_INT),
+ INT(4, "Int", GL11.GL_INT);
+
+ private final int size;
+ private final String name;
+ private final int glType;
+
+ DataType(int sizeInBytes, String newName, int openGlDataType)
+ {
+ this.size = sizeInBytes;
+ this.name = newName;
+ this.glType = openGlDataType;
+ }
+
+ public int getSize()
+ {
+ return this.size;
+ }
+
+ public String getName()
+ {
+ return this.name;
+ }
+
+ public int getGlType()
+ {
+ return this.glType;
+ }
+ }
+
+
+
+
+ @Override
+ public int hashCode()
+ {
+ int i = this.dataType.hashCode();
+ i = 31 * i + this.index;
+ return 31 * i + this.count;
+ }
+
+ @Override
+ public String toString()
+ {
+ return this.count + "," + this.dataType.getName();
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj != null && this.getClass() == obj.getClass())
+ {
+ LodVertexFormatElement LodVertexFormatElement = (LodVertexFormatElement) obj;
+ if (this.count != LodVertexFormatElement.count)
+ {
+ return false;
+ }
+ else if (this.index != LodVertexFormatElement.index)
+ {
+ return false;
+ }
+ else if (this.dataType != LodVertexFormatElement.dataType)
+ {
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/objects/rending/LodFogConfig.java b/src/main/java/com/seibel/lod/core/objects/rending/LodFogConfig.java
new file mode 100644
index 000000000..0fcdba0db
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/objects/rending/LodFogConfig.java
@@ -0,0 +1,43 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.objects.rending;
+
+import com.seibel.lod.core.enums.rendering.FogDistance;
+import com.seibel.lod.core.enums.rendering.FogDrawMode;
+
+/**
+ * This object is just a replacement for an array
+ * to make things easier to understand in the LodRenderer.
+ *
+ * @author James Seibel
+ * @version 11-26-2021
+ */
+public class LodFogConfig
+{
+ public FogDrawMode fogDrawMode;
+ public FogDistance fogDistance;
+
+
+ public float nearFogStart = 0;
+ public float nearFogEnd = 0;
+
+ public float farFogStart = 0;
+ public float farFogEnd = 0;
+}
diff --git a/src/main/java/com/seibel/lod/core/render/GLProxy.java b/src/main/java/com/seibel/lod/core/render/GLProxy.java
new file mode 100644
index 000000000..3c3109e22
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/render/GLProxy.java
@@ -0,0 +1,434 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.render;
+
+import java.io.File;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import org.lwjgl.glfw.GLFW;
+import org.lwjgl.opengl.GL;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL15;
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL30;
+import org.lwjgl.opengl.GLCapabilities;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.seibel.lod.core.ModInfo;
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.enums.config.GpuUploadMethod;
+import com.seibel.lod.core.enums.rendering.GLProxyContext;
+import com.seibel.lod.core.render.shader.LodShader;
+import com.seibel.lod.core.render.shader.LodShaderProgram;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+
+/**
+ * A singleton that holds references to different openGL contexts
+ * and GPU capabilities.
+ *
+ *
+ * Helpful OpenGL resources:
+ *
+ * https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf
+ * https://learnopengl.com/Advanced-OpenGL/Advanced-Data
+ * https://www.slideshare.net/CassEveritt/approaching-zero-driver-overhead
+ *
+ * https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one
+ * https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer
+ *
+ * @author James Seibel
+ * @version 12-1-2021
+ */
+public class GLProxy
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+
+ private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
+
+
+ private static GLProxy instance = null;
+
+ /** Minecraft's GLFW window */
+ public final long minecraftGlContext;
+ /** Minecraft's GL capabilities */
+ public final GLCapabilities minecraftGlCapabilities;
+
+ /** the LodBuilder's GLFW window */
+ public final long lodBuilderGlContext;
+ /** the LodBuilder's GL capabilities */
+ public final GLCapabilities lodBuilderGlCapabilities;
+
+ /** the proxyWorker's GLFW window */
+ public final long proxyWorkerGlContext;
+ /** the proxyWorker's GL capabilities */
+ public final GLCapabilities proxyWorkerGlCapabilities;
+
+
+
+ /** This program contains all shaders required when rendering LODs */
+ public LodShaderProgram lodShaderProgram;
+ /** This is the VAO that is used when rendering */
+ public final int vertexArrayObjectId;
+
+
+ /** Requires OpenGL 4.5, and offers the best buffer uploading */
+ public final boolean bufferStorageSupported;
+
+ /** Requires OpenGL 3.0 */
+ public final boolean mapBufferRangeSupported;
+
+
+
+
+ private GLProxy()
+ {
+ ClientApi.LOGGER.error("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
+
+ // getting Minecraft's context has to be done on the render thread,
+ // where the GL context is
+ if (GLFW.glfwGetCurrentContext() == 0L)
+ throw new IllegalStateException(GLProxy.class.getSimpleName() + " was created outside the render thread!");
+
+
+
+ //============================//
+ // create the builder context //
+ //============================//
+
+ // get Minecraft's context
+ minecraftGlContext = GLFW.glfwGetCurrentContext();
+ minecraftGlCapabilities = GL.getCapabilities();
+
+
+ // context creation setup
+ GLFW.glfwDefaultWindowHints();
+ // make the context window invisible
+ GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
+ // by default the context should get the highest available OpenGL version
+ // but this can be explicitly set for testing
+// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4);
+// GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 5);
+
+
+ // create the LodBuilder context
+ lodBuilderGlContext = GLFW.glfwCreateWindow(64, 48, "LOD Builder Window", 0L, minecraftGlContext);
+ GLFW.glfwMakeContextCurrent(lodBuilderGlContext);
+ lodBuilderGlCapabilities = GL.createCapabilities();
+
+
+ // create the proxyWorker's context
+ proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext);
+ GLFW.glfwMakeContextCurrent(proxyWorkerGlContext);
+ proxyWorkerGlCapabilities = GL.createCapabilities();
+
+
+
+
+
+
+ //==================================//
+ // get any GPU related capabilities //
+ //==================================//
+
+ setGlContext(GLProxyContext.LOD_BUILDER);
+
+ ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
+
+ // crash the game if the GPU doesn't support OpenGL 2.0
+ if (!minecraftGlCapabilities.OpenGL20)
+ {
+ // Note: as of MC 1.17 this shouldn't happen since MC
+ // requires OpenGL 3.3, but just in case.
+ String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 2.0 or greater.";
+ MC.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 2.0 or greater."));
+ }
+
+
+
+ // get specific capabilities
+ bufferStorageSupported = lodBuilderGlCapabilities.glBufferStorage != 0;
+ mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0;
+
+ // display the capabilities
+ if (!bufferStorageSupported)
+ {
+ String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5";
+ ClientApi.LOGGER.error("This GPU doesn't support Buffer Storage (OpenGL 4.5), falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance.");
+ }
+
+
+ // if using AUTO gpuUpload
+ // determine a good default for the GPU
+ if (CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.AUTO)
+ {
+ GpuUploadMethod uploadMethod;
+ String vendor = GL15.glGetString(GL15.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
+ if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
+ {
+ // NVIDIA card
+
+ if (bufferStorageSupported)
+ {
+ uploadMethod = GpuUploadMethod.BUFFER_STORAGE;
+ }
+ else
+ {
+ uploadMethod = GpuUploadMethod.SUB_DATA;
+ }
+ }
+ else
+ {
+ // AMD or Intel card
+
+ if (mapBufferRangeSupported)
+ {
+ uploadMethod = GpuUploadMethod.BUFFER_MAPPING;
+ }
+ else
+ {
+ uploadMethod = GpuUploadMethod.DATA;
+ }
+ }
+
+ CONFIG.client().advanced().buffers().setGpuUploadMethod(uploadMethod);
+ ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Upload method set to [" + uploadMethod + "].");
+ }
+
+
+
+
+ //==============//
+ // shader setup //
+ //==============//
+
+ setGlContext(GLProxyContext.MINECRAFT);
+
+ createShaderProgram();
+
+ // Note: VAO objects can not be shared between contexts,
+ // this must be created on minecraft's render context to work correctly
+ vertexArrayObjectId = GL30.glGenVertexArrays();
+
+
+
+
+
+ //==========//
+ // clean up //
+ //==========//
+
+ // Since this is created on the render thread, make sure the Minecraft context is used in the end
+ setGlContext(GLProxyContext.MINECRAFT);
+
+
+ // GLProxy creation success
+ ClientApi.LOGGER.error(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
+ }
+
+ /** Creates all required shaders */
+ public void createShaderProgram()
+ {
+ LodShader vertexShader = null;
+ LodShader fragmentShader = null;
+
+ try
+ {
+ // get the shaders from the resource folder
+ vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "shaders" + File.separator + "standard.vert", false);
+ fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders" + File.separator + "flat_shaded.frag", false);
+
+ // this can be used when testing shaders,
+ // since we can't hot swap the files in the resource folder
+// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/standard.vert", true);
+// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.frag", true);
+
+
+ // create the shaders
+
+ lodShaderProgram = new LodShaderProgram();
+
+ // Attach the compiled shaders to the program
+ lodShaderProgram.attachShader(vertexShader);
+ lodShaderProgram.attachShader(fragmentShader);
+
+ // activate the fragment shader output
+ GL30.glBindFragDataLocation(lodShaderProgram.id, 0, "fragColor");
+
+ // attach the shader program to the OpenGL context
+ lodShaderProgram.link();
+
+ // after the shaders have been attached to the program
+ // we don't need their OpenGL references anymore
+ GL20.glDeleteShader(vertexShader.id);
+ GL20.glDeleteShader(fragmentShader.id);
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.error("Unable to compile shaders. Error: " + e.getMessage());
+ }
+ }
+
+
+ /**
+ * A wrapper function to make switching contexts easier.
+ * Does nothing if the calling thread is already using newContext.
+ */
+ public void setGlContext(GLProxyContext newContext)
+ {
+ GLProxyContext currentContext = getGlContext();
+
+ // we don't have to change the context, we are already there.
+ if (currentContext == newContext)
+ return;
+
+
+ long contextPointer;
+ GLCapabilities newGlCapabilities = null;
+
+ // get the pointer(s) for this context
+ switch (newContext)
+ {
+ case LOD_BUILDER:
+ contextPointer = lodBuilderGlContext;
+ newGlCapabilities = lodBuilderGlCapabilities;
+ break;
+
+ case MINECRAFT:
+ contextPointer = minecraftGlContext;
+ newGlCapabilities = minecraftGlCapabilities;
+ break;
+
+ case PROXY_WORKER:
+ contextPointer = proxyWorkerGlContext;
+ newGlCapabilities = proxyWorkerGlCapabilities;
+ break;
+
+ default: // default should never happen, it is just here to make the compiler happy
+ case NONE:
+ // 0L is equivalent to null
+ contextPointer = 0L;
+ break;
+ }
+
+ GLFW.glfwMakeContextCurrent(contextPointer);
+ GL.setCapabilities(newGlCapabilities);
+ }
+
+ /** Returns this thread's OpenGL context. */
+ public GLProxyContext getGlContext()
+ {
+ long currentContext = GLFW.glfwGetCurrentContext();
+
+
+ if (currentContext == lodBuilderGlContext)
+ return GLProxyContext.LOD_BUILDER;
+ else if (currentContext == minecraftGlContext)
+ return GLProxyContext.MINECRAFT;
+ else if (currentContext == proxyWorkerGlContext)
+ return GLProxyContext.PROXY_WORKER;
+ else if (currentContext == 0L)
+ return GLProxyContext.NONE;
+ else
+ // hopefully this shouldn't happen
+ throw new IllegalStateException(Thread.currentThread().getName() +
+ " has a unknown OpenGl context: [" + currentContext + "]. "
+ + "Minecraft context [" + minecraftGlContext + "], "
+ + "LodBuilder context [" + lodBuilderGlContext + "], "
+ + "ProxyWorker context [" + proxyWorkerGlContext + "], "
+ + "no context [0].");
+ }
+
+
+ public static GLProxy getInstance()
+ {
+ if (instance == null)
+ instance = new GLProxy();
+
+ return instance;
+ }
+
+
+
+
+
+
+
+
+ /**
+ * Asynchronously calls the given runnable on proxy's OpenGL context.
+ * Useful for creating/destroying OpenGL objects in a thread
+ * that doesn't normally have access to a OpenGL context.
+ * No rendering can be done through this method.
+ */
+ public void recordOpenGlCall(Runnable renderCall)
+ {
+ workerThread.execute(new Thread(() -> { runnableContainer(renderCall); }));
+ }
+ private void runnableContainer(Runnable renderCall)
+ {
+ try
+ {
+ // set up the context...
+ setGlContext(GLProxyContext.PROXY_WORKER);
+ // ...run the actual code...
+ renderCall.run();
+ }
+ catch (Exception e)
+ {
+ ClientApi.LOGGER.error(Thread.currentThread().getName() + " ran into a issue: " + e.getMessage());
+ e.printStackTrace();
+ }
+ finally
+ {
+ // ...and make sure the context is released when the thread finishes
+ setGlContext(GLProxyContext.NONE);
+ }
+ }
+
+ /**
+ * If called from a legacy OpenGL context this will
+ * set the fog end to infinity with a density of 0.
+ * Effectively removing the fog.
+ *
+ * This only works with Legacy OpenGL because James hasn't
+ * looking into a way for it to work with Modern OpenGL.
+ */
+ public void disableLegacyFog()
+ {
+ // make sure this is a legacy OpenGL context
+ if (minecraftGlCapabilities.glFogf != 0)
+ {
+ // glFogf should only have an address if the current OpenGL
+ // context can call it, and it should only be able to call it in
+ // legacy OpenGL contexts; since it is disabled in Modern
+ // OpenGL.
+
+ GL11.glFogf(GL11.GL_FOG_START, 0.0f);
+ GL11.glFogf(GL11.GL_FOG_END, Float.MAX_VALUE);
+ GL11.glFogf(GL11.GL_FOG_DENSITY, 0.0f);
+ }
+ }
+
+
+}
diff --git a/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/src/main/java/com/seibel/lod/core/render/LodRenderer.java
new file mode 100644
index 000000000..2bb298b48
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/render/LodRenderer.java
@@ -0,0 +1,777 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.render;
+
+import java.awt.Color;
+import java.util.HashSet;
+
+import org.lwjgl.opengl.GL15;
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL30;
+
+import com.seibel.lod.core.api.ApiShared;
+import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory;
+import com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.VertexBuffersAndOffset;
+import com.seibel.lod.core.enums.config.GpuUploadMethod;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.enums.rendering.FogColorMode;
+import com.seibel.lod.core.enums.rendering.FogDistance;
+import com.seibel.lod.core.enums.rendering.FogDrawMode;
+import com.seibel.lod.core.handlers.IReflectionHandler;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.objects.lod.RegionPos;
+import com.seibel.lod.core.objects.math.Mat4f;
+import com.seibel.lod.core.objects.math.Vec3d;
+import com.seibel.lod.core.objects.math.Vec3f;
+import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
+import com.seibel.lod.core.objects.rending.LodFogConfig;
+import com.seibel.lod.core.render.shader.LodShaderProgram;
+import com.seibel.lod.core.util.DetailDistanceUtil;
+import com.seibel.lod.core.util.LevelPosUtil;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
+
+/**
+ * This is where all the magic happens.
+ * This is where LODs are draw to the world.
+ *
+ * @author James Seibel
+ * @version 11-27-2021
+ */
+public class LodRenderer
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+ private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
+ private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+
+ /**
+ * If true the LODs colors will be replaced with
+ * a checkerboard, this can be used for debugging.
+ */
+ public DebugMode previousDebugMode = DebugMode.OFF;
+
+ private int farPlaneBlockDistance;
+
+
+ /** This is used to generate the buildable buffers */
+ private final LodBufferBuilderFactory lodBufferBuilderFactory;
+
+ /** Each VertexBuffer represents 1 region */
+ private LodVertexBuffer[][][] vbos;
+ /**
+ * the OpenGL IDs for the vbos of the same indices.
+ * These have to be separate because we can't override the
+ * buffers in the VBOs (and we don't want to)
+ */
+ @SuppressWarnings("unused")
+ private int[][][] storageBufferIds;
+
+ private AbstractChunkPosWrapper vbosCenter = FACTORY.createChunkPos();
+
+
+ /** This is used to determine if the LODs should be regenerated */
+ private int[] previousPos = new int[] { 0, 0, 0 };
+
+ // these variables are used to determine if the buffers should be rebuilt
+ private float prevSkyBrightness = 0;
+ private double prevBrightness = 0;
+ private int prevRenderDistance = 0;
+ private long prevPlayerPosTime = 0;
+ private long prevVanillaChunkTime = 0;
+ private long prevChunkTime = 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 partialRegen = false;
+ private volatile boolean fullRegen = true;
+
+ /**
+ * This HashSet contains every chunk that Vanilla Minecraft
+ * is going to render
+ */
+ public boolean[][] vanillaRenderedChunks;
+ public boolean vanillaRenderedChunksChanged;
+ public boolean vanillaRenderedChunksEmptySkip = false;
+ public int vanillaBlockRenderedDistance;
+
+
+
+
+ public LodRenderer(LodBufferBuilderFactory newLodNodeBufferBuilder)
+ {
+ lodBufferBuilderFactory = newLodNodeBufferBuilder;
+ }
+
+
+
+
+
+
+
+ /**
+ * Besides drawing the LODs this method also starts
+ * the async process of generating the Buffers that hold those LODs.
+ * @param lodDim The dimension to draw, if null doesn't replace the current dimension.
+ * @param mcModelViewMatrix This matrix stack should come straight from MC's renderChunkLayer (or future equivalent) method
+ * @param mcProjectionMatrix
+ * @param partialTicks how far into the current tick this method was called.
+ */
+ public void drawLODs(LodDimension lodDim, Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler)
+ {
+ //=================================//
+ // determine if LODs should render //
+ //=================================//
+
+ if (lodDim == null)
+ {
+ // if there aren't any loaded LodChunks
+ // don't try drawing anything
+ return;
+ }
+
+ if (MC_RENDER.playerHasBlindnessEffect())
+ {
+ // if the player is blind, don't render LODs,
+ // and don't change minecraft's fog
+ // which blindness relies on.
+ return;
+ }
+
+ if (CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
+ GLProxy.getInstance().disableLegacyFog();
+
+
+
+
+ // TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
+ // starting here...
+ determineIfLodsShouldRegenerate(lodDim, partialTicks);
+
+ //=================//
+ // 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 ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable())
+ {
+ // generate the LODs on a separate thread to prevent stuttering or freezing
+ lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos(), true);
+
+ // the regen process has been started,
+ // it will be done when lodBufferBuilder.newBuffersAvailable()
+ // is true
+ fullRegen = false;
+ partialRegen = false;
+ }
+
+ // TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
+ // ...ending here
+
+ if (lodBufferBuilderFactory.newBuffersAvailable())
+ {
+ swapBuffers();
+ }
+
+
+
+
+
+ //===============//
+ // initial setup //
+ //===============//
+
+ profiler.push("LOD setup");
+
+ GLProxy glProxy = GLProxy.getInstance();
+
+
+
+ // set the required open GL settings
+
+ if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME)
+ GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE);
+ else
+ GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
+
+ GL15.glEnable(GL15.GL_CULL_FACE);
+ GL15.glEnable(GL15.GL_DEPTH_TEST);
+
+ // enable transparent rendering
+ GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA);
+ GL15.glEnable(GL15.GL_BLEND);
+
+ // get MC's shader program
+ int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM);
+
+
+ Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks);
+ vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
+ // required for setupFog and setupProjectionMatrix
+ if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
+ farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
+ else
+ farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH;
+
+
+ Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance);
+
+ LodFogConfig fogSettings = determineFogConfig();
+
+
+
+
+
+
+ if (vbos != null)
+ {
+ //==============//
+ // shader setup //
+ //==============//
+
+ // can be used when testing shaders
+ // glProxy.createShaderProgram();
+
+
+ LodShaderProgram shaderProgram = glProxy.lodShaderProgram;
+ shaderProgram.use();
+
+
+ // determine the VertexArrayObject's element positions
+ int posAttrib = shaderProgram.getAttributeLocation("vPosition");
+ shaderProgram.enableVertexAttribute(posAttrib);
+ int colAttrib = shaderProgram.getAttributeLocation("color");
+ shaderProgram.enableVertexAttribute(colAttrib);
+
+
+ // global uniforms
+ int mvmUniform = shaderProgram.getUniformLocation("modelViewMatrix");
+ shaderProgram.setUniform(mvmUniform, modelViewMatrix);
+ int projUniform = shaderProgram.getUniformLocation("projectionMatrix");
+ shaderProgram.setUniform(projUniform, projectionMatrix);
+ int cameraUniform = shaderProgram.getUniformLocation("cameraPos");
+ shaderProgram.setUniform(cameraUniform, getTranslatedCameraPos());
+ int fogColorUniform = shaderProgram.getUniformLocation("fogColor");
+ shaderProgram.setUniform(fogColorUniform, getFogColor());
+
+
+ // region dependent uniforms
+ int fogEnabledUniform = shaderProgram.getUniformLocation("fogEnabled");
+ int nearFogEnabledUniform = shaderProgram.getUniformLocation("nearFogEnabled");
+ int farFogEnabledUniform = shaderProgram.getUniformLocation("farFogEnabled");
+ // near
+ int nearFogStartUniform = shaderProgram.getUniformLocation("nearFogStart");
+ int nearFogEndUniform = shaderProgram.getUniformLocation("nearFogEnd");
+ // far
+ int farFogStartUniform = shaderProgram.getUniformLocation("farFogStart");
+ int farFogEndUniform = shaderProgram.getUniformLocation("farFogEnd");
+
+
+
+
+
+ //===========//
+ // rendering //
+ //===========//
+
+ profiler.popPush("LOD draw");
+
+ boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
+ boolean renderBufferStorage = CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE && glProxy.bufferStorageSupported;
+
+ // where the center of the buffers is (needed when culling regions)
+ RegionPos vboCenterRegionPos = new RegionPos(vbosCenter);
+ RegionPos vboPos = new RegionPos();
+
+
+ // render each of the buffers
+ for (int x = 0; x < vbos.length; x++)
+ {
+ for (int z = 0; z < vbos.length; z++)
+ {
+ vboPos.x = x + vboCenterRegionPos.x - (lodDim.getWidth() / 2);
+ vboPos.z = z + vboCenterRegionPos.z - (lodDim.getWidth() / 2);
+
+ if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(), MC_RENDER.getLookAtVector(), vboPos.blockPos()))
+ {
+ // fog may be different from region to region
+ applyFog(shaderProgram,
+ fogSettings, fogEnabledUniform, nearFogEnabledUniform, farFogEnabledUniform,
+ nearFogStartUniform, nearFogEndUniform, farFogStartUniform, farFogEndUniform);
+
+
+ // actual rendering
+ int bufferId = 0;
+ for (int i = 0; i < vbos[x][z].length; i++)
+ {
+ bufferId = (storageBufferIds != null && renderBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
+ drawArrays(bufferId, vbos[x][z][i].vertexCount, posAttrib, colAttrib);
+ }
+
+ }
+ }
+ }
+
+
+
+ //================//
+ // render cleanup //
+ //================//
+
+ // if this cleanup isn't done MC may crash
+ // when trying to render its own terrain
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+ GL30.glBindVertexArray(0);
+
+ GL20.glDisableVertexAttribArray(posAttrib);
+ GL20.glDisableVertexAttribArray(colAttrib);
+ }
+
+
+
+
+
+ //=========//
+ // cleanup //
+ //=========//
+
+ profiler.popPush("LOD cleanup");
+
+ GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
+ GL15.glDisable(GL15.GL_BLEND); // TODO: what should this be reset to?
+
+ GL20.glUseProgram(currentProgram);
+
+ // clear the depth buffer so everything is drawn
+ // over the LODs
+ GL15.glClear(GL15.GL_DEPTH_BUFFER_BIT);
+
+
+
+ // end of internal LOD profiling
+ profiler.pop();
+ }
+
+
+
+
+
+
+
+
+ /** This is where the actual drawing happens. */
+ private void drawArrays(int glBufferId, int vertexCount, int posAttrib, int colAttrib)
+ {
+ if (glBufferId == 0)
+ return;
+
+ // can be used to check for OpenGL errors
+// int error = GL15.glGetError();
+// ClientProxy.LOGGER.info(Integer.toHexString(error));
+
+
+ // bind the buffer we are going to draw
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, glBufferId);
+ GL30.glBindVertexArray(GLProxy.getInstance().vertexArrayObjectId);
+
+ // let OpenGL know how our buffer is set up
+ int vertexByteCount = (Float.BYTES * 3) + (Byte.BYTES * 4);
+ GL20.glEnableVertexAttribArray(posAttrib);
+ GL20.glVertexAttribPointer(posAttrib, 3, GL15.GL_FLOAT, false, vertexByteCount, 0);
+ GL20.glEnableVertexAttribArray(colAttrib);
+ GL20.glVertexAttribPointer(colAttrib, 4, GL15.GL_UNSIGNED_BYTE, true, vertexByteCount, Float.BYTES * 3);
+
+
+ // draw the LODs
+ GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, vertexCount);
+ }
+
+
+
+
+
+
+ //=================//
+ // Setup Functions //
+ //=================//
+
+
+ /** Create all buffers that will be used. */
+ public void setupBuffers(LodDimension lodDim)
+ {
+ lodBufferBuilderFactory.setupBuffers(lodDim);
+ }
+
+
+
+
+ /** Return what fog settings should be used when rendering. */
+ private LodFogConfig determineFogConfig()
+ {
+ LodFogConfig fogConfig = new LodFogConfig();
+
+
+ fogConfig.fogDrawMode = CONFIG.client().graphics().fogQuality().getFogDrawMode();
+ if (fogConfig.fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
+ fogConfig.fogDrawMode = REFLECTION_HANDLER.getFogDrawMode();
+
+
+ // how different distances are drawn depends on the quality set
+ fogConfig.fogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
+
+
+
+
+
+ // far fog //
+
+ if (CONFIG.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
+ fogConfig.farFogStart = farPlaneBlockDistance * 1.6f * 0.9f;
+ else
+ // for more realistic fog when using FAR
+ fogConfig.farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f * 1.6f);
+
+ fogConfig.farFogEnd = farPlaneBlockDistance * 1.6f;
+
+
+ // near fog //
+
+ // the reason that I wrote fogEnd then fogStart backwards
+ // is because we are using fog backwards to how
+ // it is normally used, hiding near objects
+ // instead of far objects.
+ fogConfig.nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
+ fogConfig.nearFogStart = vanillaBlockRenderedDistance * 1.6f;
+
+
+ return fogConfig;
+ }
+
+ private Color getFogColor()
+ {
+ Color fogColor;
+
+ if (CONFIG.client().graphics().fogQuality().getFogColorMode() == FogColorMode.USE_SKY_COLOR)
+ fogColor = MC_RENDER.getSkyColor();
+ else
+ fogColor = MC_RENDER.getFogColor();
+
+ return fogColor;
+ }
+
+ /**
+ * Translate the camera relative to the LodDimension's center,
+ * this is done since all LOD buffers are created in world space
+ * instead of object space.
+ * (since AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
+ * accuracy vs the model view matrix, which only uses floats)
+ */
+ private Mat4f translateModelViewMatrix(Mat4f mcModelViewMatrix, float partialTicks)
+ {
+ // get all relevant camera info
+ Vec3d projectedView = MC_RENDER.getCameraExactPosition();
+
+ // translate the camera relative to the regions' center
+ // (AxisAlignedBoundingBoxes (LODs) use doubles and thus have a higher
+ // accuracy vs the model view matrix, which only uses floats)
+ AbstractBlockPosWrapper bufferPos = vbosCenter.getWorldPosition();
+ double xDiff = projectedView.x - bufferPos.getX();
+ double zDiff = projectedView.z - bufferPos.getZ();
+ mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff);
+
+ return mcModelViewMatrix;
+ }
+
+ /**
+ * Similar to translateModelViewMatrix (above),
+ * but for the camera position
+ */
+ private Vec3f getTranslatedCameraPos()
+ {
+ AbstractBlockPosWrapper worldCenter = vbosCenter.getWorldPosition();
+ Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
+ return new Vec3f((float)cameraPos.x - worldCenter.getX(), (float)cameraPos.y, (float)cameraPos.z - worldCenter.getZ());
+ }
+
+ /**
+ * create and return a new projection matrix based on MC's projection matrix
+ * @param currentProjectionMatrix this is Minecraft's current projection matrix
+ * @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
+ */
+ private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance)
+ {
+ //Create a copy of the current matrix, so the current matrix isn't modified.
+ Mat4f lodProj = currentProjectionMatrix.copy();
+
+ //Set new far and near clip plane values.
+ lodProj.setClipPlanes(
+ CONFIG.client().graphics().advancedGraphics().getUseExtendedNearClipPlane() ? vanillaBlockRenderedDistance / 5 : 1,
+ farPlaneBlockDistance * LodUtil.CHUNK_WIDTH / 2);
+
+ return lodProj;
+ }
+
+ private void applyFog(LodShaderProgram shaderProgram,
+ LodFogConfig fogSettings, int fogEnabledUniform, int nearFogEnabledUniform, int farFogEnabledUniform,
+ int nearFogStartUniform, int nearFogEndUniform, int farFogStartUniform, int farFogEndUniform)
+ {
+ if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED)
+ {
+ shaderProgram.setUniform(fogEnabledUniform, true);
+ shaderProgram.setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
+ shaderProgram.setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
+
+ // near
+ shaderProgram.setUniform(nearFogStartUniform, fogSettings.nearFogStart);
+ shaderProgram.setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
+ // far
+ shaderProgram.setUniform(farFogStartUniform, fogSettings.farFogStart);
+ shaderProgram.setUniform(farFogEndUniform, fogSettings.farFogEnd);
+ }
+ else
+ {
+ shaderProgram.setUniform(fogEnabledUniform, false);
+ }
+ }
+
+
+
+
+
+ //======================//
+ // 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()
+ {
+ fullRegen = true;
+ }
+
+ /**
+ * Replace the current Vertex Buffers with the newly
+ * created buffers from the lodBufferBuilder.
+ *
+ * For some reason this has to be called after the frame has been rendered,
+ * otherwise visual stuttering/rubber banding may happen. I'm not sure why...
+ */
+ private void swapBuffers()
+ {
+ // replace the drawable buffers with
+ // the newly created buffers from the lodBufferBuilder
+ VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers();
+ vbos = result.vbos;
+ storageBufferIds = result.storageBufferIds;
+ vbosCenter = result.drawableCenterChunkPos;
+ }
+
+ /** Calls the BufferBuilder's destroyBuffers method. */
+ public void destroyBuffers()
+ {
+ lodBufferBuilderFactory.destroyBuffers();
+ }
+
+
+
+ /** Determines if the LODs should have a fullRegen or partialRegen */
+ private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks)
+ {
+ short chunkRenderDistance = (short) MC_RENDER.getRenderDistance();
+ int vanillaRenderedChunksWidth = chunkRenderDistance * 2 + 2;
+
+ //=============//
+ // full regens //
+ //=============//
+
+ // check if the view distance changed
+ if (ApiShared.previousLodRenderDistance != CONFIG.client().graphics().quality().getLodChunkRenderDistance()
+ || chunkRenderDistance != prevRenderDistance
+ || prevFogDistance != CONFIG.client().graphics().fogQuality().getFogDistance())
+ {
+
+ vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
+ DetailDistanceUtil.updateSettings();
+ fullRegen = true;
+ previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getZ(), MC.getPlayerChunkPos().getZ());
+ prevFogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
+ prevRenderDistance = chunkRenderDistance;
+ }
+
+ // did the user change the debug setting?
+ if (CONFIG.client().advanced().debugging().getDebugMode() != previousDebugMode)
+ {
+ previousDebugMode = CONFIG.client().advanced().debugging().getDebugMode();
+ fullRegen = true;
+ }
+
+
+ long newTime = System.currentTimeMillis();
+
+ // check if the player has moved
+ if (newTime - prevPlayerPosTime > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveTimeout)
+ {
+ if (LevelPosUtil.getDetailLevel(previousPos) == 0
+ || Math.abs(MC.getPlayerChunkPos().getX() - LevelPosUtil.getPosX(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance
+ || Math.abs(MC.getPlayerChunkPos().getZ() - LevelPosUtil.getPosZ(previousPos)) > CONFIG.client().advanced().buffers().getRebuildTimes().playerMoveDistance)
+ {
+ vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
+ fullRegen = true;
+ previousPos = LevelPosUtil.createLevelPos((byte) 4, MC.getPlayerChunkPos().getX(), MC.getPlayerChunkPos().getZ());
+ }
+ prevPlayerPosTime = newTime;
+ }
+
+
+
+ // determine how far the lighting has to
+ // change in order to rebuild the buffers
+
+ // the max brightness is 1 and the minimum is 0.2
+ float skyBrightness = lodDim.dimension.hasSkyLight() ? MC.getSkyDarken(partialTicks) : 0.2f;
+ float minLightingDifference;
+ switch (CONFIG.client().advanced().buffers().getRebuildTimes())
+ {
+ case FREQUENT:
+ minLightingDifference = 0.025f;
+ break;
+ case NORMAL:
+ minLightingDifference = 0.05f;
+ break;
+ default:
+ case RARE:
+ minLightingDifference = 0.1f;
+ break;
+ }
+
+ // check if the lighting changed
+ if (Math.abs(skyBrightness - prevSkyBrightness) > minLightingDifference
+ // make sure the lighting gets to the max/minimum value
+ // (just in case the minLightingDifference is too large to notice the change)
+ || (skyBrightness == 1.0f && prevSkyBrightness != 1.0f) // noon
+ || (skyBrightness == 0.2f && prevSkyBrightness != 0.2f) // midnight
+ || MC_RENDER.getGamma() != prevBrightness)
+ {
+ fullRegen = true;
+ prevBrightness = MC_RENDER.getGamma();
+ prevSkyBrightness = skyBrightness;
+ }
+
+ /*if (lightMap != lastLightMap)
+ {
+ fullRegen = true;
+ lastLightMap = lightMap;
+ }*/
+
+ //================//
+ // partial regens //
+ //================//
+
+
+ // check if the vanilla rendered chunks changed
+ if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
+ {
+ if (vanillaRenderedChunksChanged)
+ {
+ partialRegen = true;
+ vanillaRenderedChunksChanged = false;
+ }
+ prevVanillaChunkTime = newTime;
+ }
+
+
+ // check if there is any newly generated terrain to show
+ if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
+ {
+ if (lodDim.regenDimensionBuffers)
+ {
+ partialRegen = true;
+ lodDim.regenDimensionBuffers = false;
+ }
+ prevChunkTime = newTime;
+ }
+
+
+
+ //==============//
+ // LOD skipping //
+ //==============//
+
+ // determine which LODs should not be rendered close to the player
+ HashSet chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, MC.getPlayerBlockPos());
+ int xIndex;
+ int zIndex;
+ for (AbstractChunkPosWrapper pos : chunkPosToSkip)
+ {
+ vanillaRenderedChunksEmptySkip = false;
+
+ xIndex = (pos.getX() - MC.getPlayerChunkPos().getX()) + (chunkRenderDistance + 1);
+ zIndex = (pos.getZ() - MC.getPlayerChunkPos().getZ()) + (chunkRenderDistance + 1);
+
+ // sometimes we are given chunks that are outside the render distance,
+ // This prevents index out of bounds exceptions
+ if (xIndex >= 0 && zIndex >= 0
+ && xIndex < vanillaRenderedChunks.length
+ && zIndex < vanillaRenderedChunks.length)
+ {
+ if (!vanillaRenderedChunks[xIndex][zIndex])
+ {
+ vanillaRenderedChunks[xIndex][zIndex] = true;
+ vanillaRenderedChunksChanged = true;
+ lodDim.markRegionBufferToRegen(pos.getRegionX(), pos.getRegionZ());
+ }
+ }
+ }
+
+
+ // if the player is high enough, draw all LODs
+ if (chunkPosToSkip.isEmpty() && MC.getPlayerBlockPos().getY() > 256 && !vanillaRenderedChunksEmptySkip)
+ {
+ vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
+ vanillaRenderedChunksChanged = true;
+ vanillaRenderedChunksEmptySkip = true;
+ }
+
+ vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/render/RenderUtil.java b/src/main/java/com/seibel/lod/core/render/RenderUtil.java
new file mode 100644
index 000000000..15130a691
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/render/RenderUtil.java
@@ -0,0 +1,124 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.render;
+
+import com.seibel.lod.core.objects.math.Vec3f;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
+
+/**
+ * This holds miscellaneous helper code
+ * to be used in the rendering process.
+ *
+ * @author James Seibel
+ * @version 10-19-2021
+ */
+public class RenderUtil
+{
+ private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
+
+
+ /**
+ * Returns if the given ChunkPos is in the loaded area of the world.
+ * @param center the center of the loaded world (probably the player's ChunkPos)
+ */
+ public static boolean isChunkPosInLoadedArea(AbstractChunkPosWrapper pos, AbstractChunkPosWrapper center)
+ {
+ return (pos.getX() >= center.getX() - MC_RENDER.getRenderDistance()
+ && pos.getX() <= center.getX() + MC_RENDER.getRenderDistance())
+ &&
+ (pos.getZ() >= center.getZ() - MC_RENDER.getRenderDistance()
+ && pos.getZ() <= center.getZ() + MC_RENDER.getRenderDistance());
+ }
+
+ /**
+ * Returns if the given coordinate is in the loaded area of the world.
+ * @param centerCoordinate the center of the loaded world
+ */
+ public static boolean isCoordinateInLoadedArea(int x, int z, int centerCoordinate)
+ {
+ return (x >= centerCoordinate - MC_RENDER.getRenderDistance()
+ && x <= centerCoordinate + MC_RENDER.getRenderDistance())
+ &&
+ (z >= centerCoordinate - MC_RENDER.getRenderDistance()
+ && z <= centerCoordinate + MC_RENDER.getRenderDistance());
+ }
+
+
+ /**
+ * 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).
+ */
+ public static boolean isCoordinateInNearFogArea(int i, int j, int lodRadius)
+ {
+ int halfRadius = lodRadius / 2;
+
+ return (i >= lodRadius - halfRadius
+ && i <= lodRadius + halfRadius)
+ &&
+ (j >= lodRadius - halfRadius
+ && j <= lodRadius + halfRadius);
+ }
+
+
+ /**
+ * Returns true if one of the region's 4 corners is in front
+ * of the camera.
+ */
+ public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, AbstractBlockPosWrapper vboCenterPos)
+ {
+ // convert the vbo position into a direction vector
+ // starting from the player's position
+ Vec3f vboVec = new Vec3f(vboCenterPos.getX(), 0, vboCenterPos.getZ());
+ Vec3f playerVec = new Vec3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
+
+ vboVec.subtract(playerVec);
+
+
+ int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
+
+ // calculate the 4 corners
+ Vec3f vboSeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth);
+ Vec3f vboSwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z + halfRegionWidth);
+ Vec3f vboNwVec = new Vec3f(vboVec.x - halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth);
+ Vec3f vboNeVec = new Vec3f(vboVec.x + halfRegionWidth, vboVec.y, vboVec.z - halfRegionWidth);
+
+ // if any corner is visible, this region should be rendered
+ return isNormalizedVectorInViewFrustum(vboSeVec, cameraDir) ||
+ isNormalizedVectorInViewFrustum(vboSwVec, cameraDir) ||
+ isNormalizedVectorInViewFrustum(vboNwVec, cameraDir) ||
+ isNormalizedVectorInViewFrustum(vboNeVec, cameraDir);
+ }
+
+ /**
+ * Currently takes the dot product of the two vectors,
+ * but in the future could do more complicated frustum culling tests.
+ */
+ private static boolean isNormalizedVectorInViewFrustum(Vec3f objectVector, Vec3f cameraDir)
+ {
+ // the -0.1 is to offer a slight buffer, so we are
+ // more likely to render LODs and thus, hopefully prevent
+ // flickering or odd disappearances
+ return objectVector.dotProduct(cameraDir) > -0.1;
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/render/shader/LodShader.java b/src/main/java/com/seibel/lod/core/render/shader/LodShader.java
new file mode 100644
index 000000000..d5689a0a1
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/render/shader/LodShader.java
@@ -0,0 +1,116 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.render.shader;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.lwjgl.opengl.GL20;
+
+import com.seibel.lod.core.api.ClientApi;
+
+/**
+ * This object holds a OpenGL reference to a shader
+ * and allows for reading in and compiling a shader file.
+ *
+ * @author James Seibel
+ * @version 11-8-2021
+ */
+public class LodShader
+{
+ /** OpenGL shader ID */
+ public final int id;
+
+
+
+ /** Creates a shader with specified type. */
+ public LodShader(int type)
+ {
+ id = GL20.glCreateShader(type);
+ }
+
+
+
+ /**
+ * Loads a shader from file.
+ *
+ * @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
+ * @param path File path of the shader
+ * @param absoluteFilePath If false the file path is relative to the resource jar folder.
+ * @throws Exception if the shader fails to compile
+ */
+ public static LodShader loadShader(int type, String path, boolean absoluteFilePath) throws Exception
+ {
+ StringBuilder stringBuilder = new StringBuilder();
+
+ try
+ {
+ // open the file
+ InputStream in = absoluteFilePath ? new FileInputStream(path) : LodShader.class.getClassLoader().getResourceAsStream(path);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+
+ // read in the file
+ String line;
+ while ((line = reader.readLine()) != null)
+ stringBuilder.append(line).append("\n");
+ }
+ catch (IOException e)
+ {
+ ClientApi.LOGGER.error("Unable to load shader from file [" + path + "]. Error: " + e.getMessage());
+ }
+ CharSequence shaderFileSource = stringBuilder.toString();
+
+ return createShader(type, shaderFileSource);
+ }
+
+ /**
+ * Creates a shader with the specified type and source.
+ *
+ * @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
+ * @param source Source of the shader
+ * @throws Exception if the shader fails to compile
+ */
+ public static LodShader createShader(int type, CharSequence source) throws Exception
+ {
+ LodShader shader = new LodShader(type);
+ GL20.glShaderSource(shader.id, source);
+ shader.compile();
+
+ return shader;
+ }
+
+ /**
+ * Compiles the shader and checks its status afterwards.
+ * @throws Exception if the shader fails to compile
+ */
+ public void compile() throws Exception
+ {
+ GL20.glCompileShader(id);
+
+ // check if the shader compiled
+ int status = GL20.glGetShaderi(id, GL20.GL_COMPILE_STATUS);
+ if (status != GL20.GL_TRUE)
+ throw new Exception(GL20.glGetShaderInfoLog(id));
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java b/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java
new file mode 100644
index 000000000..f5478f607
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java
@@ -0,0 +1,203 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.render.shader;
+
+import java.awt.Color;
+import java.nio.FloatBuffer;
+
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.system.MemoryStack;
+
+import com.seibel.lod.core.objects.math.Mat4f;
+import com.seibel.lod.core.objects.math.Vec3d;
+import com.seibel.lod.core.objects.math.Vec3f;
+
+
+/**
+ * This object holds the reference to a OpenGL shader program
+ * and contains a few methods that can be used with OpenGL shader programs.
+ * The reason for many of these simple wrapper methods is as reminders of what
+ * can (and needs to be) done with a shader program.
+ *
+ * @author James Seibel
+ * @version 11-26-2021
+ */
+public class LodShaderProgram
+{
+ /** Stores the handle of the program. */
+ public final int id;
+
+ /** Creates a shader program. */
+ public LodShaderProgram()
+ {
+ id = GL20.glCreateProgram();
+ }
+
+
+
+ /** Calls GL20.glUseProgram(this.id) */
+ public void use()
+ {
+ GL20.glUseProgram(id);
+ }
+
+ /**
+ * Calls GL20.glAttachShader(this.id, shader.id)
+ *
+ * @param shader Shader to get attached
+ */
+ public void attachShader(LodShader shader)
+ {
+ GL20.glAttachShader(this.id, shader.id);
+ }
+
+
+ /**
+ * Links the shader program to the current OpenGL context.
+ * @throws Exception Exception if the program failed to link
+ */
+ public void link() throws Exception
+ {
+ GL20.glLinkProgram(this.id);
+ checkLinkStatus();
+ }
+
+ /**
+ * Checks if the program was linked successfully.
+ * @throws Exception if the program failed to link
+ */
+ public void checkLinkStatus() throws Exception
+ {
+ int status = GL20.glGetProgrami(this.id, GL20.GL_LINK_STATUS);
+ if (status != GL20.GL_TRUE)
+ throw new Exception(GL20.glGetProgramInfoLog(this.id));
+ }
+
+
+
+
+ /**
+ * Gets the location of an attribute variable with specified name.
+ * Calls GL20.glGetAttribLocation(id, name)
+ *
+ * @param name Attribute name
+ *
+ * @return Location of the attribute
+ */
+ public int getAttributeLocation(CharSequence name)
+ {
+ return GL20.glGetAttribLocation(id, name);
+ }
+
+ /**
+ * Calls GL20.glEnableVertexAttribArray(location)
+ *
+ * @param location Location of the vertex attribute
+ */
+ public void enableVertexAttribute(int location)
+ {
+ GL20.glEnableVertexAttribArray(location);
+ }
+
+ /**
+ * Calls GL20.glDisableVertexAttribArray(location)
+ *
+ * @param location Location of the vertex attribute
+ */
+ public void disableVertexAttribute(int location)
+ {
+ GL20.glDisableVertexAttribArray(location);
+ }
+
+ /**
+ * Sets the vertex attribute pointer.
+ * Calls GL20.glVertexAttribPointer(...)
+ *
+ * @param location Location of the vertex attribute
+ * @param size Number of values per vertex
+ * @param stride Offset between consecutive generic vertex attributes in
+ * bytes
+ * @param offset Offset of the first component of the first generic vertex
+ * attribute in bytes
+ */
+ public void pointVertexAttribute(int location, int size, int stride, int offset)
+ {
+ GL20.glVertexAttribPointer(location, size, GL20.GL_FLOAT, false, stride, offset);
+ }
+
+ /**
+ * Gets the location of a uniform variable with specified name.
+ * Calls GL20.glGetUniformLocation(id, name)
+ *
+ * @param name Uniform name
+ *
+ * @return -1 = error value, 0 = first value, 1 = second value, etc.
+ */
+ public int getUniformLocation(CharSequence name)
+ {
+ return GL20.glGetUniformLocation(id, name);
+ }
+
+
+
+ public void setUniform(int location, boolean value)
+ {
+ GL20.glUniform1i(location, value ? 1 : 0);
+ }
+
+ public void setUniform(int location, int value)
+ {
+ GL20.glUniform1i(location, value);
+ }
+
+ public void setUniform(int location, float value)
+ {
+ GL20.glUniform1f(location, value);
+ }
+
+ public void setUniform(int location, Vec3f value)
+ {
+ GL20.glUniform3f(location, value.x, value.y, value.z);
+ }
+
+ public void setUniform(int location, Vec3d value)
+ {
+ GL20.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
+ }
+
+ public void setUniform(int location, Mat4f value)
+ {
+ try (MemoryStack stack = MemoryStack.stackPush())
+ {
+ FloatBuffer buffer = stack.mallocFloat(4 * 4);
+ value.store(buffer);
+ GL20.glUniformMatrix4fv(location, false, buffer);
+ }
+ }
+
+ /** Converts the color's RGBA values into values between 0 and 1. */
+ public void setUniform(int location, Color value)
+ {
+ GL20.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
+ }
+
+
+
+}
diff --git a/src/main/java/com/seibel/lod/core/util/ColorUtil.java b/src/main/java/com/seibel/lod/core/util/ColorUtil.java
new file mode 100644
index 000000000..1933246d2
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/ColorUtil.java
@@ -0,0 +1,122 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import java.awt.Color;
+
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+
+/**
+ *
+ * @author Cola
+ * @author Leonardo Amato
+ * @version 11-13-2021
+ */
+public class ColorUtil
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+
+
+ public static int rgbToInt(int red, int green, int blue)
+ {
+ return (0xFF << 24) | (red << 16) | (green << 8) | blue;
+ }
+
+ public static int rgbToInt(int alpha, int red, int green, int blue)
+ {
+ return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ }
+
+ /** Returns a value between 0 and 255 */
+ public static int getAlpha(int color)
+ {
+ return (color >> 24) & 0xFF;
+ }
+
+ /** Returns a value between 0 and 255 */
+ public static int getRed(int color)
+ {
+ return (color >> 16) & 0xFF;
+ }
+
+ /** Returns a value between 0 and 255 */
+ public static int getGreen(int color)
+ {
+ return (color >> 8) & 0xFF;
+ }
+
+ /** Returns a value between 0 and 255 */
+ public static int getBlue(int color)
+ {
+ return color & 0xFF;
+ }
+
+ public static int applyShade(int color, int shade)
+ {
+ if (shade < 0)
+ return (getAlpha(color) << 24) | (Math.max(getRed(color) + shade, 0) << 16) | (Math.max(getGreen(color) + shade, 0) << 8) | Math.max(getBlue(color) + shade, 0);
+ else
+ return (getAlpha(color) << 24) | (Math.min(getRed(color) + shade, 255) << 16) | (Math.min(getGreen(color) + shade, 255) << 8) | Math.min(getBlue(color) + shade, 255);
+ }
+
+ public static int applyShade(int color, float shade)
+ {
+ if (shade < 1)
+ return (getAlpha(color) << 24) | ((int) Math.max(getRed(color) * shade, 0) << 16) | ((int) Math.max(getGreen(color) * shade, 0) << 8) | (int) Math.max(getBlue(color) * shade, 0);
+ else
+ return (getAlpha(color) << 24) | ((int) Math.min(getRed(color) * shade, 255) << 16) | ((int) Math.min(getGreen(color) * shade, 255) << 8) | (int) Math.min(getBlue(color) * shade, 255);
+ }
+
+ /** This method apply the lightmap to the color to use */
+ public static int applyLightValue(int color, int skyLight, int blockLight)
+ {
+ int lightColor = MC.getColorIntFromLightMap(blockLight, skyLight);
+ int red = ColorUtil.getBlue(lightColor);
+ int green = ColorUtil.getGreen(lightColor);
+ int blue = ColorUtil.getRed(lightColor);
+
+ return ColorUtil.multiplyRGBcolors(color, ColorUtil.rgbToInt(red, green, blue));
+ }
+
+ /** Edit the given color as an HSV (Hue Saturation Value) color */
+ public static int applySaturationAndBrightnessMultipliers(int color, float saturationMultiplier, float brightnessMultiplier)
+ {
+ float[] hsv = Color.RGBtoHSB(getRed(color), getGreen(color), getBlue(color), 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)).getRGB();
+ }
+
+ /** Multiply 2 RGB colors */
+ public static int multiplyRGBcolors(int color1, int color2)
+ {
+ return ((getAlpha(color1) * getAlpha(color2) / 255) << 24) | ((getRed(color1) * getRed(color2) / 255) << 16) | ((getGreen(color1) * getGreen(color2) / 255) << 8) | (getBlue(color1) * getBlue(color2) / 255);
+ }
+
+ @SuppressWarnings("unused")
+ public static String toString(int color)
+ {
+ return Integer.toHexString(getAlpha(color)) + " " +
+ Integer.toHexString(getRed(color)) + " " +
+ Integer.toHexString(getGreen(color)) + " " +
+ Integer.toHexString(getBlue(color));
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/util/DataPointUtil.java b/src/main/java/com/seibel/lod/core/util/DataPointUtil.java
new file mode 100644
index 000000000..81b11c287
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/DataPointUtil.java
@@ -0,0 +1,521 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactory.skyLightPlayer;
+
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+
+/**
+ *
+ * @author Leonardo Amato
+ * @version ??
+ */
+public class DataPointUtil
+{
+ /*
+ |a |a |a |a |r |r |r |r |
+
+ |r |r |r |r |g |g |g |g |
+
+ |g |g |g |g |b |b |b |b |
+
+ |b |b |b |b |h |h |h |h |
+
+ |h |h |h |h |h |h |d |d |
+
+ |d |d |d |d |d |d |d |d |
+
+ |bl |bl |bl |bl |sl |sl |sl |sl |
+
+ |l |l |f |g |g |g |v |e |
+
+
+ */
+
+ // Reminder: bytes have range of [-128, 127].
+ // When converting to or from an int a 128 should be added or removed.
+ // If there is a bug with color then it's probably caused by this.
+
+ //To be used in the future for negative value
+ //public final static int MIN_DEPTH = -64;
+ //public final static int MIN_HEIGHT = -64;
+ public final static int EMPTY_DATA = 0;
+ public static final short VERTICAL_OFFSET = -64;
+ public static int WORLD_HEIGHT = 1024;
+
+ public final static int ALPHA_DOWNSIZE_SHIFT = 4;
+
+ //public final static int BLUE_COLOR_SHIFT = 0;
+ //public final static int GREEN_COLOR_SHIFT = 8;
+ //public final static int RED_COLOR_SHIFT = 16;
+ //public final static int ALPHA_COLOR_SHIFT = 24;
+
+ public final static int BLUE_SHIFT = 36;
+ public final static int GREEN_SHIFT = BLUE_SHIFT + 8;
+ public final static int RED_SHIFT = BLUE_SHIFT + 16;
+ public final static int ALPHA_SHIFT = BLUE_SHIFT + 24;
+
+ public final static int COLOR_SHIFT = 36;
+
+ public final static int HEIGHT_SHIFT = 26;
+ public final static int DEPTH_SHIFT = 16;
+ public final static int BLOCK_LIGHT_SHIFT = 12;
+ public final static int SKY_LIGHT_SHIFT = 8;
+ //public final static int LIGHTS_SHIFT = SKY_LIGHT_SHIFT;
+ //public final static int VERTICAL_INDEX_SHIFT = 6;
+ public final static int FLAG_SHIFT = 5;
+ public final static int GEN_TYPE_SHIFT = 2;
+ public final static int VOID_SHIFT = 1;
+ public final static int EXISTENCE_SHIFT = 0;
+
+ public final static long ALPHA_MASK = 0b1111;
+ public final static long RED_MASK = 0b1111_1111;
+ public final static long GREEN_MASK = 0b1111_1111;
+ public final static long BLUE_MASK = 0b1111_1111;
+ public final static long COLOR_MASK = 0b11111111_11111111_11111111;
+ public final static long HEIGHT_MASK = 0b11_1111_1111;
+ public final static long DEPTH_MASK = 0b11_1111_1111;
+ //public final static long LIGHTS_MASK = 0b1111_1111;
+ public final static long BLOCK_LIGHT_MASK = 0b1111;
+ public final static long SKY_LIGHT_MASK = 0b1111;
+ //public final static long VERTICAL_INDEX_MASK = 0b11;
+ public final static long FLAG_MASK = 0b1;
+ public final static long GEN_TYPE_MASK = 0b111;
+ public final static long VOID_MASK = 1;
+ public final static long EXISTENCE_MASK = 1;
+
+
+ public static long createVoidDataPoint(int generationMode)
+ {
+ long dataPoint = 0;
+ dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
+ dataPoint += VOID_MASK << VOID_SHIFT;
+ dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
+ return dataPoint;
+ }
+
+ public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode, boolean flag)
+ {
+ return createDataPoint(
+ ColorUtil.getAlpha(color),
+ ColorUtil.getRed(color),
+ ColorUtil.getGreen(color),
+ ColorUtil.getBlue(color),
+ height, depth, lightSky, lightBlock, generationMode, flag);
+ }
+
+ public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode, boolean flag)
+ {
+ long dataPoint = 0;
+ dataPoint += (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT;
+ dataPoint += (red & RED_MASK) << RED_SHIFT;
+ dataPoint += (green & GREEN_MASK) << GREEN_SHIFT;
+ dataPoint += (blue & BLUE_MASK) << BLUE_SHIFT;
+ dataPoint += (height & HEIGHT_MASK) << HEIGHT_SHIFT;
+ dataPoint += (depth & DEPTH_MASK) << DEPTH_SHIFT;
+ dataPoint += (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT;
+ dataPoint += (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT;
+ dataPoint += (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT;
+ if (flag) dataPoint += FLAG_MASK << FLAG_SHIFT;
+ dataPoint += EXISTENCE_MASK << EXISTENCE_SHIFT;
+
+ return dataPoint;
+ }
+
+ public static short getHeight(long dataPoint)
+ {
+ return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
+ }
+
+ public static short getDepth(long dataPoint)
+ {
+ return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK);
+ }
+
+ public static short getAlpha(long dataPoint)
+ {
+ return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111);
+ }
+
+ public static short getRed(long dataPoint)
+ {
+ return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK);
+ }
+
+ public static short getGreen(long dataPoint)
+ {
+ return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK);
+ }
+
+ public static short getBlue(long dataPoint)
+ {
+ return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK);
+ }
+
+ public static byte getLightSky(long dataPoint)
+ {
+ return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
+ }
+
+ public static byte getLightSkyAlt(long dataPoint)
+ {
+ if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1)
+ return 0;
+ else
+ return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
+ }
+
+ public static byte getLightBlock(long dataPoint)
+ {
+ return (byte) ((dataPoint >>> BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
+ }
+
+ public static boolean getFlag(long dataPoint)
+ {
+ return ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1;
+ }
+
+ public static byte getGenerationMode(long dataPoint)
+ {
+ return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
+ }
+
+
+ public static boolean isVoid(long dataPoint)
+ {
+ return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
+ }
+
+ public static boolean doesItExist(long dataPoint)
+ {
+ return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
+ }
+
+ public static int getColor(long dataPoint)
+ {
+ return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
+ }
+
+ /** This is used to convert a dataPoint to string (useful for the print function) */
+ @SuppressWarnings("unused")
+ public static String toString(long dataPoint)
+ {
+ return getHeight(dataPoint) + " " +
+ getDepth(dataPoint) + " " +
+ getAlpha(dataPoint) + " " +
+ getRed(dataPoint) + " " +
+ getBlue(dataPoint) + " " +
+ getGreen(dataPoint) + " " +
+ getLightBlock(dataPoint) + " " +
+ getLightSky(dataPoint) + " " +
+ getGenerationMode(dataPoint) + " " +
+ isVoid(dataPoint) + " " +
+ doesItExist(dataPoint) + '\n';
+ }
+
+ public static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize)
+ {
+ start *= packetSize;
+ length *= packetSize;
+ arraySize *= packetSize;
+ for (int i = 0; i < arraySize - start; i++)
+ {
+ array[start + i] = array[start + length + i];
+ //remove comment to not leave garbage at the end
+ //array[start + packetSize + i] = 0;
+ }
+ }
+
+ public static void extendArray(short[] array, int packetSize, int start, int length, int arraySize)
+ {
+ start *= packetSize;
+ length *= packetSize;
+ arraySize *= packetSize;
+ for (int i = arraySize - start - 1; i >= 0; i--)
+ {
+ array[start + length + i] = array[start + i];
+ array[start + i] = 0;
+ }
+ }
+
+ /**
+ * This method merge column of multiple data together
+ * @param dataToMerge one or more columns of data
+ * @param inputVerticalData vertical size of an input data
+ * @param maxVerticalData max vertical size of the merged data
+ * @return one column of correctly parsed data
+ */
+ public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
+ {
+ int size = dataToMerge.length / inputVerticalData;
+
+ // We initialize the arrays that are going to be used
+ short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2);
+ long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
+
+
+ int genMode = DistanceGenerationMode.FULL.complexity;
+ boolean allEmpty = true;
+ boolean allVoid = true;
+ boolean allDefault;
+ long singleData;
+
+
+ short depth;
+ short height;
+ int count = 0;
+ int i;
+ int ii;
+ int dataIndex;
+ //We collect the indexes of the data, ordered by the depth
+ for (int index = 0; index < size; index++)
+ {
+ for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
+ {
+ singleData = dataToMerge[index * inputVerticalData + dataIndex];
+ if (doesItExist(singleData))
+ {
+ genMode = Math.min(genMode, getGenerationMode(singleData));
+ allEmpty = false;
+ if (!isVoid(singleData))
+ {
+ allVoid = false;
+ depth = getDepth(singleData);
+ height = getHeight(singleData);
+
+ int botPos = -1;
+ int topPos = -1;
+ //values fall in between and possibly require extension of array
+ boolean botExtend = false;
+ boolean topExtend = false;
+ for (i = 0; i < count; i++)
+ {
+ if (depth <= heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1])
+ {
+ botPos = i;
+ break;
+ }
+ else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
+ {
+ botPos = i;
+ botExtend = true;
+ break;
+ }
+ }
+ for (i = 0; i < count; i++)
+ {
+ if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
+ {
+ topPos = i;
+ break;
+ }
+ else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
+ {
+ topPos = i;
+ topExtend = true;
+ break;
+ }
+ }
+ if (topPos == -1)
+ {
+ if (botPos == -1)
+ {
+ //whole block falls above
+ extendArray(heightAndDepth, 2, 0, 1, count);
+ heightAndDepth[0] = height;
+ heightAndDepth[1] = depth;
+ count++;
+ }
+ else if (!botExtend)
+ {
+ //only top falls above extending it there, while bottom is inside existing
+ shrinkArray(heightAndDepth, 2, 0, botPos, count);
+ heightAndDepth[0] = height;
+ count -= botPos;
+ }
+ else
+ {
+ //top falls between some blocks, extending those as well
+ shrinkArray(heightAndDepth, 2, 0, botPos, count);
+ heightAndDepth[0] = height;
+ heightAndDepth[1] = depth;
+ count -= botPos;
+ }
+ }
+ else if (!topExtend)
+ {
+ if (!botExtend)
+ //both top and bottom are within some exiting blocks, possibly merging them
+ heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
+ else
+ //top falls between some blocks, extending it there
+ heightAndDepth[topPos * 2 + 1] = depth;
+ shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
+ count -= botPos - topPos;
+ }
+ else
+ {
+ if (!botExtend)
+ {
+ //only top is within some exiting block, extending it
+ topPos++; //to make it easier
+ heightAndDepth[topPos * 2] = height;
+ heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
+ shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
+ count -= botPos - topPos;
+ }
+ else
+ {
+ //both top and bottom are outside existing blocks
+ shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
+ count -= botPos - topPos;
+ extendArray(heightAndDepth, 2, topPos + 1, 1, count);
+ count++;
+ heightAndDepth[topPos * 2 + 2] = height;
+ heightAndDepth[topPos * 2 + 3] = depth;
+ }
+ }
+ }
+ }
+ else
+ break;
+ }
+ }
+
+ //We check if there is any data that's not empty or void
+ if (allEmpty)
+ return dataPoint;
+ if (allVoid)
+ {
+ dataPoint[0] = createVoidDataPoint(genMode);
+ return dataPoint;
+ }
+
+ //we limit the vertical portion to maxVerticalData
+ int j = 0;
+ while (count > maxVerticalData)
+ {
+ ii = WORLD_HEIGHT - VERTICAL_OFFSET;
+ for (i = 0; i < count - 1; i++)
+ {
+ if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
+ {
+ ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2];
+ j = i;
+ }
+ }
+ heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
+ for (i = j + 1; i < count - 1; i++)
+ {
+ heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2];
+ heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
+ }
+ //System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1);
+ count--;
+ }
+ //As standard the vertical lods are ordered from top to bottom
+ for (j = count - 1; j >= 0; j--)
+ {
+ height = heightAndDepth[j * 2];
+ depth = heightAndDepth[j * 2 + 1];
+
+ if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
+ break;
+
+ int numberOfChildren = 0;
+ int tempAlpha = 0;
+ int tempRed = 0;
+ int tempGreen = 0;
+ int tempBlue = 0;
+ int tempLightBlock = 0;
+ int tempLightSky = 0;
+ byte tempGenMode = DistanceGenerationMode.FULL.complexity;
+ allEmpty = true;
+ allVoid = true;
+ allDefault = true;
+ long data = 0;
+
+ for (int index = 0; index < size; index++)
+ {
+ for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
+ {
+ singleData = dataToMerge[index * inputVerticalData + dataIndex];
+ if (doesItExist(singleData) && !isVoid(singleData))
+ {
+
+ if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
+ || (depth <= getHeight(singleData) && getHeight(singleData) <= height))
+ {
+ if (getHeight(singleData) > getHeight(data))
+ data = singleData;
+ }
+ }
+ else
+ break;
+ }
+ if (!doesItExist(data))
+ {
+ singleData = dataToMerge[index * inputVerticalData];
+ data = createVoidDataPoint(getGenerationMode(singleData));
+ }
+
+ if (doesItExist(data))
+ {
+ allEmpty = false;
+ if (!isVoid(data))
+ {
+ numberOfChildren++;
+ allVoid = false;
+ tempAlpha += getAlpha(data);
+ tempRed += getRed(data);
+ tempGreen += getGreen(data);
+ tempBlue += getBlue(data);
+ tempLightBlock += getLightBlock(data);
+ tempLightSky += getLightSky(data);
+ if (!getFlag(data)) allDefault = false;
+ }
+ tempGenMode = (byte) Math.min(tempGenMode, getGenerationMode(data));
+ }
+ else
+ tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
+ }
+
+ if (allEmpty)
+ //no child has been initialized
+ dataPoint[j] = EMPTY_DATA;
+ else if (allVoid)
+ //all the children are void
+ dataPoint[j] = createVoidDataPoint(tempGenMode);
+ else
+ {
+ //we have at least 1 child
+ tempAlpha = tempAlpha / numberOfChildren;
+ tempRed = tempRed / numberOfChildren;
+ tempGreen = tempGreen / numberOfChildren;
+ tempBlue = tempBlue / numberOfChildren;
+ tempLightBlock = tempLightBlock / numberOfChildren;
+ tempLightSky = tempLightSky / numberOfChildren;
+ dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
+ }
+ }
+ return dataPoint;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java b/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java
new file mode 100644
index 000000000..67793fa3f
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java
@@ -0,0 +1,174 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.HorizontalQuality;
+import com.seibel.lod.core.enums.config.HorizontalResolution;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
+
+/**
+ *
+ * @author Leonardo Amato
+ * @version ??
+ */
+public class DetailDistanceUtil
+{
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+ private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
+
+ private static final double genMultiplier = 1.0;
+ private static final double treeGenMultiplier = 1.0;
+ private static final double treeCutMultiplier = 1.0;
+ private static byte minGenDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel;
+ private static byte minDrawDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel;
+ private static final int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
+ private static final int minDistance = 0;
+ private static int minDetailDistance = (int) (MC_RENDER.getRenderDistance()*16 * 1.42f);
+ private static int maxDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 16 * 2;
+
+
+ private static final HorizontalResolution[] lodGenDetails = {
+ HorizontalResolution.BLOCK,
+ HorizontalResolution.TWO_BLOCKS,
+ HorizontalResolution.FOUR_BLOCKS,
+ HorizontalResolution.HALF_CHUNK,
+ HorizontalResolution.CHUNK,
+ HorizontalResolution.CHUNK,
+ HorizontalResolution.CHUNK,
+ HorizontalResolution.CHUNK,
+ HorizontalResolution.CHUNK,
+ HorizontalResolution.CHUNK,
+ HorizontalResolution.CHUNK };
+
+
+
+ public static void updateSettings()
+ {
+ minDetailDistance = (int) (MC_RENDER.getRenderDistance()*16 * 1.42f);
+ minGenDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel;
+ minDrawDetail = (byte) Math.max(CONFIG.client().graphics().quality().getDrawResolution().detailLevel, CONFIG.client().graphics().quality().getDrawResolution().detailLevel);
+ maxDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 16 * 8;
+ }
+
+ public static int baseDistanceFunction(int detail)
+ {
+ if (detail <= minGenDetail)
+ return minDistance;
+ if (detail >= maxDetail)
+ return maxDistance;
+
+ if (CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality())
+ return detail * 0x10000; //if you want more you are doing wrong
+
+ int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16;
+ if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST)
+ return (detail * distanceUnit);
+ else
+ {
+ double base = CONFIG.client().graphics().quality().getHorizontalQuality().quadraticBase;
+ return (int) (Math.pow(base, detail) * distanceUnit);
+ }
+ }
+
+ public static int getDrawDistanceFromDetail(int detail)
+ {
+ return baseDistanceFunction(detail);
+ }
+
+ public static byte baseInverseFunction(int distance, byte minDetail, boolean useRenderMinDistance)
+ {
+ int detail;
+ if (distance == 0
+ || (distance < minDetailDistance && useRenderMinDistance)
+ || CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality())
+ return minDetail;
+ int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16;
+ if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST)
+ detail = (byte) distance / distanceUnit;
+ else
+ {
+ double base = CONFIG.client().graphics().quality().getHorizontalQuality().quadraticBase;
+ double logBase = Math.log(base);
+ //noinspection IntegerDivisionInFloatingPointContext
+ detail = (byte) (Math.log(distance / distanceUnit) / logBase);
+ }
+ return (byte) LodUtil.clamp(minDetail, detail, maxDetail - 1);
+ }
+
+ public static byte getDrawDetailFromDistance(int distance)
+ {
+ return baseInverseFunction(distance, minDrawDetail, false);
+ }
+
+ public static byte getGenerationDetailFromDistance(int distance)
+ {
+ return baseInverseFunction((int) (distance * genMultiplier), minGenDetail, true);
+ }
+
+ public static byte getTreeCutDetailFromDistance(int distance)
+ {
+ return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail, true);
+ }
+
+ public static byte getTreeGenDetailFromDistance(int distance)
+ {
+ return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true);
+ }
+
+ public static DistanceGenerationMode getDistanceGenerationMode(int detail)
+ {
+ return CONFIG.client().worldGenerator().getDistanceGenerationMode();
+ }
+
+ public static byte getLodDrawDetail(byte detail)
+ {
+ detail += minDrawDetail;
+ if (detail > 10)
+ detail = 10;
+ return detail;
+ }
+
+ public static HorizontalResolution getLodGenDetail(int detail)
+ {
+ if (detail < minGenDetail)
+ return lodGenDetails[minGenDetail];
+ else
+ return lodGenDetails[detail];
+ }
+
+
+ public static byte getCutLodDetail(int detail)
+ {
+ if (detail < minGenDetail)
+ return lodGenDetails[minGenDetail].detailLevel;
+ else if (detail == maxDetail)
+ return LodUtil.REGION_DETAIL_LEVEL;
+ else
+ return lodGenDetails[detail].detailLevel;
+ }
+
+ public static int getMaxVerticalData(int detail)
+ {
+ return CONFIG.client().graphics().quality().getVerticalQuality().maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java b/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java
new file mode 100644
index 000000000..2dfe29440
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java
@@ -0,0 +1,269 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+/**
+ *
+ * @author Leonardo Amato
+ * @version ??
+ */
+public class LevelPosUtil
+{
+ public static int[] convert(int[] levelPos, byte newDetailLevel)
+ {
+ return convert(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos), newDetailLevel);
+ }
+
+ public static int[] convert(byte detailLevel, int posX, int posZ, byte newDetailLevel)
+ {
+ int width;
+ if (newDetailLevel >= detailLevel)
+ {
+ width = 1 << (newDetailLevel - detailLevel);
+ return createLevelPos(
+ newDetailLevel,
+ Math.floorDiv(posX, width),
+ Math.floorDiv(posZ, width));
+ }
+ else
+ {
+ width = 1 << (detailLevel - newDetailLevel);
+ return createLevelPos(
+ newDetailLevel,
+ posX * width,
+ posZ * width);
+ }
+ }
+
+ public static int[] createLevelPos(byte detailLevel, int posX, int posZ)
+ {
+ return new int[] { detailLevel, posX, posZ };
+ }
+
+ public static int convert(byte detailLevel, int pos, byte newDetailLevel)
+ {
+ int width;
+ if (newDetailLevel >= detailLevel)
+ {
+ width = 1 << (newDetailLevel - detailLevel);
+ return Math.floorDiv(pos, width);
+ }
+ else
+ {
+ width = 1 << (detailLevel - newDetailLevel);
+ return pos * width;
+ }
+ }
+
+ public static int getRegion(byte detailLevel, int pos)
+ {
+ return Math.floorDiv(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
+ }
+
+ public static int getRegionModule(byte detailLevel, int pos)
+ {
+ return Math.floorMod(pos, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
+ }
+
+ public static byte getDetailLevel(int[] levelPos)
+ {
+ return (byte) levelPos[0];
+ }
+
+ public static int getPosX(int[] levelPos)
+ {
+ return levelPos[1];
+ }
+
+ public static int getPosZ(int[] levelPos)
+ {
+ return levelPos[2];
+ }
+
+ public static int getDistance(int[] levelPos)
+ {
+ return levelPos[3];
+ }
+
+ public static int[] getRegionModule(int[] levelPos)
+ {
+ return getRegionModule(getDetailLevel(levelPos), getPosX(levelPos), getPosZ(levelPos));
+ }
+
+ public static int[] getRegionModule(byte detailLevel, int posX, int posZ)
+ {
+ int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ return createLevelPos(
+ detailLevel,
+ Math.floorMod(posX, width),
+ Math.floorMod(posZ, width));
+ }
+
+ public static int[] applyOffset(int[] levelPos, int xOffset, int zOffset)
+ {
+ return createLevelPos(
+ getDetailLevel(levelPos),
+ getPosX(levelPos) + xOffset,
+ getPosZ(levelPos) + zOffset);
+ }
+
+ public static int[] applyLevelOffset(int[] levelPos, byte detailOffset, int xOffset, int zOffset)
+ {
+ return createLevelPos(
+ getDetailLevel(levelPos),
+ getPosX(levelPos) + xOffset * (1 << detailOffset),
+ getPosZ(levelPos) + zOffset * (1 << detailOffset));
+ }
+
+ public static int getRegionPosX(int[] levelPos)
+ {
+ int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
+ return Math.floorDiv(getPosX(levelPos), width);
+ }
+
+ public static int getRegionPosZ(int[] levelPos)
+ {
+ int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - getDetailLevel(levelPos));
+ return Math.floorDiv(getPosZ(levelPos), width);
+ }
+
+ public static int getChunkPos(byte detailLevel, int pos)
+ {
+ return convert(detailLevel, pos, LodUtil.CHUNK_DETAIL_LEVEL);
+ }
+
+ public static int myPow2(int x)
+ {
+ return x*x;
+ }
+
+ public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
+ {
+ int width = 1 << detailLevel;
+
+ int startPosX = posX * width;
+ int startPosZ = posZ * width;
+ int endPosX = myPow2(playerPosX - startPosX - width);
+ int endPosZ = myPow2(playerPosZ - startPosZ - width);
+ startPosX = myPow2(playerPosX - startPosX);
+ startPosZ = myPow2(playerPosZ - startPosZ);
+
+ int maxDistance = (int) Math.sqrt(startPosX + startPosZ);
+ maxDistance = Math.max(maxDistance, (int) Math.sqrt(startPosX + endPosZ));
+ maxDistance = Math.max(maxDistance, (int) Math.sqrt(endPosX + startPosZ));
+ maxDistance = Math.max(maxDistance, (int) Math.sqrt(endPosX + endPosZ));
+
+ return maxDistance;
+ }
+
+ public static int maxDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
+ {
+ int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ int newPosX = xRegion * width + posX;
+ int newPosZ = zRegion * width + posZ;
+ return maxDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
+ }
+
+
+ public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ)
+ {
+ int width = 1 << detailLevel;
+
+ int startPosX = posX * width;
+ int startPosZ = posZ * width;
+ int endPosX = startPosX + width;
+ int endPosZ = startPosZ + width;
+
+ boolean inXArea = playerPosX >= startPosX && playerPosX <= endPosX;
+ boolean inZArea = playerPosZ >= startPosZ && playerPosZ <= endPosZ;
+ if (inXArea && inZArea)
+ return 0;
+ else if (inXArea)
+ {
+ return Math.min(
+ Math.abs(playerPosZ - startPosZ),
+ Math.abs(playerPosZ - endPosZ)
+ );
+ }
+ else if (inZArea)
+ {
+ return Math.min(
+ Math.abs(playerPosX - startPosX),
+ Math.abs(playerPosX - endPosX)
+ );
+ }
+ else
+ {
+ startPosX = myPow2(playerPosX - startPosX);
+ startPosZ = myPow2(playerPosZ - startPosZ);
+ endPosX = myPow2(playerPosX - endPosX);
+ endPosZ = myPow2(playerPosZ - endPosZ);
+
+ int minDistance = (int) Math.sqrt(startPosX + startPosZ);
+ minDistance = Math.min(minDistance, (int) Math.sqrt(startPosX + endPosZ));
+ minDistance = Math.min(minDistance, (int) Math.sqrt(endPosX + startPosZ));
+ minDistance = Math.min(minDistance, (int) Math.sqrt(endPosX + endPosZ));
+ return minDistance;
+ }
+ }
+
+ public static int minDistance(byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, int xRegion, int zRegion)
+ {
+ int width = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ int newPosX = xRegion * width + posX;
+ int newPosZ = zRegion * width + posZ;
+ return minDistance(detailLevel, newPosX, newPosZ, playerPosX, playerPosZ);
+ }
+
+ public static int compareDistance(int firstDistance, int secondDistance)
+ {
+ return Integer.compare(
+ firstDistance,
+ secondDistance);
+ }
+
+
+ public static int compareLevelAndDistance(byte firstDetail, int firstDistance, byte secondDetail, int secondDistance)
+ {
+ int compareResult = Integer.compare(
+ secondDetail,
+ firstDetail);
+ if (compareResult == 0)
+ {
+ compareResult = Integer.compare(
+ firstDistance,
+ secondDistance);
+ }
+ return compareResult;
+ }
+
+ @SuppressWarnings("unused")
+ public static String toString(int[] levelPos)
+ {
+ return (getDetailLevel(levelPos) + " "
+ + getPosX(levelPos) + " "
+ + getPosZ(levelPos));
+ }
+
+ public static String toString(byte detailLevel, int posX, int posZ)
+ {
+ return (detailLevel + " " + posX + " " + posZ);
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java b/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java
new file mode 100644
index 000000000..c12e8e0ff
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Just a simple ThreadFactory to name ExecutorService
+ * threads, which can be helpful when debugging.
+ * @author James Seibel
+ * @version 8-15-2021
+ */
+public class LodThreadFactory implements ThreadFactory
+{
+ public final String threadName;
+
+
+ public LodThreadFactory(String newThreadName)
+ {
+ threadName = newThreadName + " Thread";
+ }
+
+ @Override
+ public Thread newThread(Runnable r)
+ {
+ return new Thread(r, threadName);
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java
new file mode 100644
index 000000000..f87c85f0a
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java
@@ -0,0 +1,427 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.HashSet;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.enums.config.HorizontalResolution;
+import com.seibel.lod.core.enums.config.VanillaOverdraw;
+import com.seibel.lod.core.objects.VertexOptimizer;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.objects.lod.RegionPos;
+import com.seibel.lod.core.objects.opengl.DefaultLodVertexFormats;
+import com.seibel.lod.core.objects.opengl.LodVertexFormat;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+
+/**
+ * This class holds methods and constants that may be used in multiple places.
+ *
+ * @author James Seibel
+ * @version 11-13-2021
+ */
+public class LodUtil
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+ private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+ private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
+
+ /**
+ * Vanilla render distances less than or equal to this will not allow partial
+ * overdraw. The VanillaOverdraw will either be ALWAYS or NEVER.
+ */
+ public static final int MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW = 4;
+
+ /**
+ * Vanilla render distances less than or equal to this will cause the overdraw to
+ * run at a smaller fraction of the vanilla render distance.
+ */
+ public static final int MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW = 11;
+
+
+
+
+ /** The maximum number of LODs that can be rendered vertically */
+ public static final int MAX_NUMBER_OF_VERTICAL_LODS = 32;
+
+ /**
+ * alpha used when drawing chunks in debug mode
+ */
+ public static final int DEBUG_ALPHA = 255; // 0 - 255
+ public static final Color COLOR_DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
+ public static final Color COLOR_DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
+ public static final Color COLOR_INVISIBLE = new Color(0, 0, 0, 0);
+
+ public static final int CEILED_DIMENSION_MAX_RENDER_DISTANCE = 64; // 0 - 255
+
+ /**
+ * In order of nearest to farthest:
+ * Red, Orange, Yellow, Green, Cyan, Blue, Magenta, white, gray, black
+ */
+ public static final Color[] DEBUG_DETAIL_LEVEL_COLORS = new Color[] { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.CYAN, Color.BLUE, Color.MAGENTA, Color.WHITE, Color.GRAY, Color.BLACK };
+
+
+ public static final byte DETAIL_OPTIONS = 10;
+
+ /** 512 blocks wide */
+ public static final byte REGION_DETAIL_LEVEL = DETAIL_OPTIONS - 1;
+ /** 16 blocks wide */
+ public static final byte CHUNK_DETAIL_LEVEL = 4;
+ /** 1 block wide */
+ public static final byte BLOCK_DETAIL_LEVEL = 0;
+
+ public static final short MAX_VERTICAL_DATA = 4;
+
+ /**
+ * measured in Blocks
+ * detail level max - 1
+ */
+ public static final short REGION_WIDTH = 1 << REGION_DETAIL_LEVEL;
+ /**
+ * measured in Blocks
+ * detail level 4
+ */
+ public static final short CHUNK_WIDTH = 16;
+ /**
+ * measured in Blocks
+ * detail level 0
+ */
+ public static final short BLOCK_WIDTH = 1;
+
+
+ /** number of chunks wide */
+ public static final int REGION_WIDTH_IN_CHUNKS = REGION_WIDTH / CHUNK_WIDTH;
+
+
+ /**
+ * This regex finds any characters that are invalid for use in a windows
+ * (and by extension mac and linux) file path
+ */
+ public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
+
+ /**
+ * 64 MB by default is the maximum amount of memory that
+ * can be directly allocated.
+ *
+ * James knows there are commands to change that amount
+ * (specifically "-XX:MaxDirectMemorySize"), but
+ * He has no idea how to access that amount.
+ * So for now this will be the hard limit.
+ *
+ * https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
+ */
+ public static final int MAX_ALLOCATABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
+
+ /** the format of data stored in the GPU buffers */
+ public static final LodVertexFormat LOD_VERTEX_FORMAT = DefaultLodVertexFormats.POSITION_COLOR;
+
+
+
+
+
+ /**
+ * Gets the ServerWorld for the relevant dimension.
+ * @return null if there is no ServerWorld for the given dimension
+ */
+ public static IWorldWrapper getServerWorldFromDimension(IDimensionTypeWrapper newDimension)
+ {
+ if(!MC.hasSinglePlayerServer())
+ return null;
+
+ Iterable worlds = MC.getAllServerWorlds();
+ IWorldWrapper returnWorld = null;
+
+ for (IWorldWrapper world : worlds)
+ {
+ if (world.getDimensionType() == newDimension)
+ {
+ returnWorld = world;
+ break;
+ }
+ }
+
+ return returnWorld;
+ }
+
+ /** Convert a 2D absolute position into a quad tree relative position. */
+ public static RegionPos convertGenericPosToRegionPos(int x, int z, int detailLevel)
+ {
+ int relativePosX = Math.floorDiv(x, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
+ int relativePosZ = Math.floorDiv(z, 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel));
+
+ return new RegionPos(relativePosX, relativePosZ);
+ }
+
+ /** Convert a 2D absolute position into a quad tree relative position. */
+ public static int convertLevelPos(int pos, int currentDetailLevel, int targetDetailLevel)
+ {
+ return pos / (1 << (targetDetailLevel - currentDetailLevel));
+ }
+
+
+ /**
+ * If on single player this will return the name of the user's
+ * world, if in multiplayer it will return the server name, IP,
+ * and game version.
+ */
+ public static String getWorldID(IWorldWrapper world)
+ {
+ if (MC.hasSinglePlayerServer())
+ {
+ // chop off the dimension ID as it is not needed/wanted
+ String dimId = getDimensionIDFromWorld(world);
+
+ // get the world name
+ int saveIndex = dimId.indexOf("saves") + 1 + "saves".length();
+ int slashIndex = dimId.indexOf(File.separatorChar, saveIndex);
+ dimId = dimId.substring(saveIndex, slashIndex);
+ return dimId;
+ }
+ else
+ {
+ return getServerId();
+ }
+ }
+
+
+ /**
+ * If on single player this will return the name of the user's
+ * world and the dimensional save folder, if in multiplayer
+ * it will return the server name, ip, game version, and dimension.
+ *
+ * This can be used to determine where to save files for a given
+ * dimension.
+ */
+ public static String getDimensionIDFromWorld(IWorldWrapper world)
+ {
+ if (MC.hasSinglePlayerServer())
+ {
+ // this will return the world save location
+ // and the dimension folder
+
+ IWorldWrapper serverWorld = LodUtil.getServerWorldFromDimension(world.getDimensionType());
+ if (serverWorld == null)
+ throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the WorldWrapper for the dimension " + world.getDimensionType().getDimensionName());
+
+ return serverWorld.getSaveFolder().toString();
+ }
+ else
+ {
+ return getServerId() + File.separatorChar + "dim_" + world.getDimensionType().getDimensionName() + File.separatorChar;
+ }
+ }
+
+ /** returns the server name, IP and game version. */
+ public static String getServerId()
+ {
+ String serverName = MC.getCurrentServerName().replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
+ String serverIp = MC.getCurrentServerIp().replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
+ String serverMcVersion = MC.getCurrentServerVersion().replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
+
+ return serverName + ", IP " + serverIp + ", GameVersion " + serverMcVersion;
+ }
+
+
+ /** Convert a BlockColors int into a Color object */
+ public static Color intToColor(int num)
+ {
+ int filter = 0b11111111;
+
+ int red = (num >> 16) & filter;
+ int green = (num >> 8) & filter;
+ int blue = num & filter;
+
+ return new Color(red, green, blue);
+ }
+
+ /** Convert a Color into a BlockColors object. */
+ public static int colorToInt(Color color)
+ {
+ return color.getRGB();
+ }
+
+
+ /**
+ * Clamps the given value between the min and max values.
+ * May behave strangely if min > max.
+ */
+ public static int clamp(int min, int value, int max)
+ {
+ return Math.min(max, Math.max(value, min));
+ }
+
+ /**
+ * Clamps the given value between the min and max values.
+ * May behave strangely if min > max.
+ */
+ public static float clamp(float min, float value, float max)
+ {
+ return Math.min(max, Math.max(value, min));
+ }
+
+ /**
+ * Clamps the given value between the min and max values.
+ * May behave strangely if min > max.
+ */
+ public static double clamp(double min, double value, double max)
+ {
+ return Math.min(max, Math.max(value, min));
+ }
+
+ /**
+ * Get a HashSet of all ChunkPos within the normal render distance
+ * that should not be rendered.
+ */
+ public static HashSet getNearbyLodChunkPosToSkip(LodDimension lodDim, AbstractBlockPosWrapper blockPosWrapper)
+ {
+ int chunkRenderDist = MC_RENDER.getRenderDistance();
+ AbstractChunkPosWrapper centerChunk = FACTORY.createChunkPos(blockPosWrapper);
+
+ int skipRadius;
+ VanillaOverdraw overdraw = CONFIG.client().graphics().advancedGraphics().getVanillaOverdraw();
+ HorizontalResolution drawRes = CONFIG.client().graphics().quality().getDrawResolution();
+
+ // apply distance based rules for dynamic overdraw
+ if (overdraw == VanillaOverdraw.DYNAMIC
+ && chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW)
+ {
+ // The vanilla render distance isn't far enough
+ // for partial skipping to make sense...
+ if (!lodDim.dimension.hasCeiling() && (drawRes == HorizontalResolution.BLOCK))
+ {
+ // ...and the dimension is open, so we don't have to worry about
+ // LODs rendering on top of the player,
+ // and the user is using a high horizontal resolution,
+ // so the overdraw shouldn't be noticeable
+ overdraw = VanillaOverdraw.ALWAYS;
+ }
+ else
+ {
+ // ...but we are underground, so we don't want
+ // LODs rendering on top of the player,
+ // Or the user is using a LOW horizontal resolution
+ // and overdraw would be very noticeable.
+ overdraw = VanillaOverdraw.NEVER;
+ }
+ }
+
+
+ // determine the skipping type based
+ // on the overdraw type
+ switch (overdraw)
+ {
+ case ALWAYS:
+ // don't skip any positions
+ return new HashSet<>();
+
+ case DYNAMIC:
+
+ if (chunkRenderDist > MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW
+ && chunkRenderDist <= MINIMUM_RENDER_DISTANCE_FOR_FAR_OVERDRAW)
+ {
+ // This is a small render distance (but greater than the minimum partial
+ // distance), skip positions that are greater than 2/3 the render distance
+ skipRadius = (int) Math.ceil(chunkRenderDist * (2.0/3.0));
+ }
+ else
+ {
+ // This is a large render distance. Skip positions that are greater than
+ // 4/5ths the render distance
+ skipRadius = (int) Math.ceil(chunkRenderDist * (4.0 / 5.0));
+ }
+ break;
+
+ default:
+ case BORDER:
+ case NEVER:
+ // skip chunks in render distance that are rendered
+ // by vanilla minecraft
+ skipRadius = 0;
+ break;
+ }
+
+
+ // get the chunks that are going to be rendered by Minecraft
+ HashSet posToSkip = MC_RENDER.getRenderedChunks();
+
+
+ // remove everything outside the skipRadius,
+ // if the skipRadius is being used
+ if (skipRadius != 0)
+ {
+ for (int x = centerChunk.getX() - chunkRenderDist; x < centerChunk.getX() + chunkRenderDist; x++)
+ {
+ for (int z = centerChunk.getZ() - chunkRenderDist; z < centerChunk.getZ() + chunkRenderDist; z++)
+ {
+ if (x <= centerChunk.getX() - skipRadius || x >= centerChunk.getX() + skipRadius
+ || z <= centerChunk.getZ() - skipRadius || z >= centerChunk.getZ() + skipRadius)
+ posToSkip.remove(FACTORY.createChunkPos(x, z));
+ }
+ }
+ }
+ return posToSkip;
+ }
+
+
+ /**
+ * This method find if a given chunk is a border chunk of the renderable ones
+ * @param vanillaRenderedChunks matrix of the vanilla rendered chunks
+ * @param x relative (to the matrix) x chunk to check
+ * @param z relative (to the matrix) z chunk to check
+ * @return true if and only if the chunk is a border of the renderable chunks
+ */
+ public static boolean isBorderChunk(boolean[][] vanillaRenderedChunks, int x, int z)
+ {
+ if (x < 0 || z < 0 || x >= vanillaRenderedChunks.length || z >= vanillaRenderedChunks[0].length)
+ return false;
+ int tempX;
+ int tempZ;
+ for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
+ {
+ tempX = x + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x;
+ tempZ = z + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z;
+ if (vanillaRenderedChunks[x][z] || (!(tempX < 0 || tempZ < 0 || tempX >= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length)
+ && !vanillaRenderedChunks[tempX][tempZ]))
+ return true;
+ }
+ return false;
+ }
+
+
+ /** This is copied from Minecraft's MathHelper class */
+ public static float fastInvSqrt(float numb)
+ {
+ float half = 0.5F * numb;
+ int i = Float.floatToIntBits(numb);
+ i = 1597463007 - (i >> 1);
+ numb = Float.intBitsToFloat(i);
+ return numb * (1.5F - half * numb * numb);
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/util/SingletonHandler.java b/src/main/java/com/seibel/lod/core/util/SingletonHandler.java
new file mode 100644
index 000000000..1770aef20
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/SingletonHandler.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class takes care of dependency injection
+ * for singletons.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class SingletonHandler
+{
+ private static final Map, Object> singletons = new HashMap, Object>();
+
+
+
+
+
+ /**
+ * Adds the given singleton so it can be referenced later.
+ *
+ * @param interfaceClass
+ * @param singletonReference
+ * @throws IllegalStateException
+ */
+ public static void bind(Class> interfaceClass, Object singletonReference) throws IllegalStateException
+ {
+ // make sure we haven't already bound this singleton
+ if (singletons.containsKey(interfaceClass))
+ {
+ throw new IllegalStateException("The singleton [" + interfaceClass.getSimpleName() + "] has already been bound.");
+ }
+
+
+ // make sure the given singleton implements the interface
+ boolean singletonImplementsInterface = false;
+ for (Class> singletonInterface : singletonReference.getClass().getInterfaces())
+ {
+ if (singletonInterface.equals(interfaceClass))
+ {
+ singletonImplementsInterface = true;
+ break;
+ }
+ }
+ if (!singletonImplementsInterface)
+ {
+ throw new IllegalStateException("The singleton [" + interfaceClass.getSimpleName() + "] doesn't implement the interface [" + interfaceClass.getSimpleName() + "].");
+ }
+
+
+ singletons.put(interfaceClass, singletonReference);
+ }
+
+ /**
+ * Returns a singleton of type T
+ * if one has been bound.
+ *
+ * @param class of the singleton
+ * @param objectClass class of the singleton, but as a parameter!
+ * @return the singleton of type T
+ * @throws NullPointerException if no singleton of type T has been bound.
+ * @throws ClassCastException if the singleton isn't able to be cast to type T. (this shouldn't normally happen, unless the bound object changed somehow)
+ */
+ @SuppressWarnings("unchecked")
+ public static T get(Class objectClass) throws NullPointerException, ClassCastException
+ {
+ // throw an error if the given singleton doesn't exist.
+ if (!singletons.containsKey(objectClass))
+ {
+ throw new NullPointerException("The singleton [" + objectClass.getSimpleName() + "] was never bound. If you are calling [bind], make sure it is happening before you call [get].");
+ }
+
+
+ return (T) singletons.get(objectClass);
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java b/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java
new file mode 100644
index 000000000..7cf8405b3
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java
@@ -0,0 +1,218 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.objects.VertexOptimizer;
+
+/**
+ * Holds data used by specific threads so
+ * the data doesn't have to be recreated every
+ * time it is needed.
+ *
+ * @author Leonardo Amato
+ * @version 9-25-2021
+ */
+public class ThreadMapUtil
+{
+ public static final ConcurrentMap threadSingleUpdateMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap threadBuilderArrayMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap threadBuilderVerticalArrayMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap threadVerticalAddDataMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap saveContainer = new ConcurrentHashMap<>();
+ public static final ConcurrentMap projectionArrayMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap heightAndDepthMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap singleDataToMergeMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap verticalUpdate = new ConcurrentHashMap<>();
+
+
+ //________________________//
+ // used in BufferBuilder //
+ //________________________//
+
+ public static final ConcurrentMap adjShadeDisabled = new ConcurrentHashMap<>();
+ public static final ConcurrentMap> adjDataMap = new ConcurrentHashMap<>();
+ public static final ConcurrentMap boxMap = new ConcurrentHashMap<>();
+
+
+
+ /** returns the array NOT cleared every time */
+ public static boolean[] getAdjShadeDisabledArray()
+ {
+ if (!adjShadeDisabled.containsKey(Thread.currentThread().getName())
+ || (adjShadeDisabled.get(Thread.currentThread().getName()) == null))
+ {
+ adjShadeDisabled.put(Thread.currentThread().getName(), new boolean[VertexOptimizer.DIRECTIONS.length]);
+ }
+ Arrays.fill(adjShadeDisabled.get(Thread.currentThread().getName()), false);
+ return adjShadeDisabled.get(Thread.currentThread().getName());
+ }
+
+ /** returns the array NOT cleared every time */
+ public static Map getAdjDataArray(int verticalData)
+ {
+ if (!adjDataMap.containsKey(Thread.currentThread().getName())
+ || (adjDataMap.get(Thread.currentThread().getName()) == null)
+ || (adjDataMap.get(Thread.currentThread().getName()).get(LodDirection.NORTH) == null)
+ || (adjDataMap.get(Thread.currentThread().getName()).get(LodDirection.NORTH).length != verticalData))
+ {
+ adjDataMap.put(Thread.currentThread().getName(), new HashMap<>());
+ adjDataMap.get(Thread.currentThread().getName()).put(LodDirection.UP, new long[1]);
+ adjDataMap.get(Thread.currentThread().getName()).put(LodDirection.DOWN, new long[1]);
+ for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
+ adjDataMap.get(Thread.currentThread().getName()).put(lodDirection, new long[verticalData]);
+ }
+ else
+ {
+
+ for (LodDirection lodDirection : VertexOptimizer.ADJ_DIRECTIONS)
+ Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(lodDirection), DataPointUtil.EMPTY_DATA);
+ }
+ return adjDataMap.get(Thread.currentThread().getName());
+ }
+
+ public static VertexOptimizer getBox()
+ {
+ if (!boxMap.containsKey(Thread.currentThread().getName())
+ || (boxMap.get(Thread.currentThread().getName()) == null))
+ {
+ boxMap.put(Thread.currentThread().getName(), new VertexOptimizer());
+ }
+ boxMap.get(Thread.currentThread().getName()).reset();
+ return boxMap.get(Thread.currentThread().getName());
+ }
+
+ //________________________//
+ // used in DataPointUtil //
+ // mergeVerticalData //
+ //________________________//
+
+
+ //________________________//
+ // used in DataPointUtil //
+ // mergeSingleData //
+ //________________________//
+
+
+
+ /** returns the array filled with 0's */
+ public static long[] getBuilderVerticalArray(int detailLevel)
+ {
+ if (!threadBuilderVerticalArrayMap.containsKey(Thread.currentThread().getName()) || (threadBuilderVerticalArrayMap.get(Thread.currentThread().getName()) == null))
+ {
+ long[][] array = new long[5][];
+ int size;
+ for (int i = 0; i < 5; i++)
+ {
+ size = 1 << i;
+ array[i] = new long[size * size * (DataPointUtil.WORLD_HEIGHT / 2 + 1)];
+ }
+ threadBuilderVerticalArrayMap.put(Thread.currentThread().getName(), array);
+ }
+ Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
+ return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel];
+ }
+
+ /** returns the array NOT cleared every time */
+ public static byte[] getSaveContainer(int detailLevel)
+ {
+ if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
+ {
+ byte[][] array = new byte[LodUtil.DETAIL_OPTIONS][];
+ int size = 1;
+ for (int i = LodUtil.DETAIL_OPTIONS - 1; i >= 0; i--)
+ {
+ array[i] = new byte[2 + 8 * size * size * DetailDistanceUtil.getMaxVerticalData(i)];
+ size = size << 1;
+ }
+ saveContainer.put(Thread.currentThread().getName(), array);
+ }
+ //Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
+ return saveContainer.get(Thread.currentThread().getName())[detailLevel];
+ }
+
+
+ /** returns the array filled with 0's */
+ public static long[] getVerticalDataArray(int arrayLength)
+ {
+ if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
+ {
+ threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
+ }
+ else
+ {
+ Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
+ }
+ return threadVerticalAddDataMap.get(Thread.currentThread().getName());
+ }
+
+
+
+ /** returns the array NOT cleared every time */
+ public static short[] getHeightAndDepth(int arrayLength)
+ {
+ if (!heightAndDepthMap.containsKey(Thread.currentThread().getName()) || (heightAndDepthMap.get(Thread.currentThread().getName()) == null))
+ {
+ heightAndDepthMap.put(Thread.currentThread().getName(), new short[arrayLength]);
+ }
+ return heightAndDepthMap.get(Thread.currentThread().getName());
+ }
+
+
+ /** returns the array filled with 0's */
+ public static long[] getVerticalUpdateArray(int detailLevel)
+ {
+ if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
+ {
+ long[][] array = new long[LodUtil.DETAIL_OPTIONS][];
+ for (int i = 1; i < LodUtil.DETAIL_OPTIONS; i++)
+ array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
+ verticalUpdate.put(Thread.currentThread().getName(), array);
+ }
+ else
+ {
+ Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0);
+ }
+ return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
+ }
+
+ /** clears all arrays so they will have to be rebuilt */
+ public static void clearMaps()
+ {
+ adjShadeDisabled.clear();
+ adjDataMap.clear();
+ boxMap.clear();
+ threadSingleUpdateMap.clear();
+ threadBuilderArrayMap.clear();
+ threadBuilderVerticalArrayMap.clear();
+ threadVerticalAddDataMap.clear();
+ saveContainer.clear();
+ projectionArrayMap.clear();
+ heightAndDepthMap.clear();
+ singleDataToMergeMap.clear();
+ verticalUpdate.clear();
+ }
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java
new file mode 100644
index 000000000..123aa33f7
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces;
+
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
+
+/**
+ * This handles creating abstract wrapper objects.
+ *
+ * @author James Seibel
+ * @version 11-18-2021
+ */
+public interface IWrapperFactory
+{
+ AbstractBlockPosWrapper createBlockPos();
+ AbstractBlockPosWrapper createBlockPos(int x, int y, int z);
+
+
+ AbstractChunkPosWrapper createChunkPos();
+ AbstractChunkPosWrapper createChunkPos(int x, int z);
+ AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
+ AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
+
+
+ AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java
new file mode 100644
index 000000000..a8cf65f6d
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.block;
+
+import com.seibel.lod.core.enums.LodDirection;
+
+/**
+ * BlockPos needs to be abstract instead of an interfaces
+ * so that we can define its constructors.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public abstract class AbstractBlockPosWrapper
+{
+ public AbstractBlockPosWrapper() { }
+ public AbstractBlockPosWrapper(int x, int y, int z) { }
+
+
+
+ public abstract void set(int x, int y, int z);
+
+ public abstract int getX();
+ public abstract int getY();
+ public abstract int getZ();
+
+ public abstract int get(LodDirection.Axis axis);
+
+ /** returns itself */
+ public abstract AbstractBlockPosWrapper offset(int x, int y, int z);
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java
new file mode 100644
index 000000000..aeef4ba9d
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java
@@ -0,0 +1,35 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.block;
+
+/**
+ * Contains methods that would have been static in BlockColorWrapper.
+ * Since interfaces can't create/implement static methods we have
+ * to split the object up in two.
+ *
+ * @author James Seibel
+ * @version 11-17-2021
+ */
+public interface IBlockColorSingletonWrapper
+{
+ /** @returns the base color of water (grey) */
+ IBlockColorWrapper getWaterColor();
+}
+
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java
new file mode 100644
index 000000000..7bc4f82dc
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.block;
+
+/**
+ * @author James Seibel
+ * @version 11-17-2021
+ */
+public interface IBlockColorWrapper
+{
+ //--------------//
+ //Colors getters//
+ //--------------//
+
+ boolean hasColor();
+
+ int getColor();
+
+
+ //------------//
+ //Tint getters//
+ //------------//
+
+ boolean hasTint();
+
+ boolean hasGrassTint();
+
+ boolean hasFolliageTint();
+
+ boolean hasWaterTint();
+
+}
+
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java
new file mode 100644
index 000000000..4a3b3448b
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.block;
+
+/**
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public interface IBlockShapeWrapper
+{
+ boolean ofBlockToAvoid();
+
+ //-----------------//
+ //Avoidance getters//
+ //-----------------//
+
+ boolean isNonFull();
+
+ boolean hasNoCollision();
+
+ boolean isToAvoid();
+}
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java
new file mode 100644
index 000000000..374d43f7b
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.chunk;
+
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+
+/**
+ * This is abstract instead of an interface, so
+ * we can define its constructors.
+ *
+ * @author James Seibel
+ * @version 11-18-2021
+ */
+public abstract class AbstractChunkPosWrapper
+{
+ public AbstractChunkPosWrapper(AbstractChunkPosWrapper newChunkPos) { }
+ public AbstractChunkPosWrapper(AbstractBlockPosWrapper blockPos) { }
+ public AbstractChunkPosWrapper(int chunkX, int chunkZ) { }
+ public AbstractChunkPosWrapper() { }
+
+
+
+ public abstract int getX();
+ public abstract int getZ();
+
+ public abstract int getMinBlockX();
+ public abstract int getMinBlockZ();
+
+ public abstract int getRegionX();
+ public abstract int getRegionZ();
+
+ public abstract AbstractBlockPosWrapper getWorldPosition();
+
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java
new file mode 100644
index 000000000..6aaa2a497
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.chunk;
+
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
+
+/**
+ * @author James Seibel
+ * @version 11-17-2021
+ */
+public interface IChunkWrapper
+{
+ int getHeight();
+
+ boolean isPositionInWater(AbstractBlockPosWrapper blockPos);
+
+ int getHeightMapValue(int xRel, int zRel);
+
+ IBiomeWrapper getBiome(int xRel, int yAbs, int zRel);
+
+ IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos);
+
+ IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos);
+
+ AbstractChunkPosWrapper getPos();
+
+ boolean isLightCorrect();
+
+ boolean isWaterLogged(AbstractBlockPosWrapper blockPos);
+
+ int getEmittedBrightness(AbstractBlockPosWrapper blockPos);
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
new file mode 100644
index 000000000..2d0fc46e6
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
@@ -0,0 +1,512 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.config;
+
+import com.seibel.lod.core.enums.config.BlocksToAvoid;
+import com.seibel.lod.core.enums.config.BufferRebuildTimes;
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.enums.config.GenerationPriority;
+import com.seibel.lod.core.enums.config.GpuUploadMethod;
+import com.seibel.lod.core.enums.config.HorizontalQuality;
+import com.seibel.lod.core.enums.config.HorizontalResolution;
+import com.seibel.lod.core.enums.config.HorizontalScale;
+import com.seibel.lod.core.enums.config.LodTemplate;
+import com.seibel.lod.core.enums.config.VanillaOverdraw;
+import com.seibel.lod.core.enums.config.VerticalQuality;
+import com.seibel.lod.core.enums.rendering.DebugMode;
+import com.seibel.lod.core.enums.rendering.FogColorMode;
+import com.seibel.lod.core.enums.rendering.FogDistance;
+import com.seibel.lod.core.enums.rendering.FogDrawMode;
+import com.seibel.lod.core.objects.MinDefaultMax;
+import com.seibel.lod.core.util.LodUtil;
+
+/**
+ * This holds the config defaults, setters/getters
+ * that should be hooked into the host mod loader (Fabric, Forge, etc.), and
+ * the options that should be implemented in a configWrapperSingleton.
+ *
+ * @author James Seibel
+ * @version 12-1-2021
+ */
+public interface ILodConfigWrapperSingleton
+{
+ IClient client();
+
+
+ interface IClient
+ {
+ IGraphics graphics();
+ IWorldGenerator worldGenerator();
+ IAdvanced advanced();
+
+
+ //==================//
+ // Graphics Configs //
+ //==================//
+ interface IGraphics
+ {
+ String DESC = "These settings control how the mod will look in game";
+
+ IQuality quality();
+ IFogQuality fogQuality();
+ IAdvancedGraphics advancedGraphics();
+
+
+ interface IQuality
+ {
+ String DESC = "These settings control how detailed the fake chunks will be.";
+
+ HorizontalResolution DRAW_RESOLUTION_DEFAULT = HorizontalResolution.BLOCK;
+ String DRAW_RESOLUTION_DESC = ""
+ + " What is the maximum detail fake chunks should be drawn at? \n"
+ + " This setting will only affect closer chunks.\n"
+ + " Higher settings will increase memory and GPU usage. \n"
+ + "\n"
+ + " " + HorizontalResolution.CHUNK + ": render 1 LOD for each Chunk. \n"
+ + " " + HorizontalResolution.HALF_CHUNK + ": render 4 LODs for each Chunk. \n"
+ + " " + HorizontalResolution.FOUR_BLOCKS + ": render 16 LODs for each Chunk. \n"
+ + " " + HorizontalResolution.TWO_BLOCKS + ": render 64 LODs for each Chunk. \n"
+ + " " + HorizontalResolution.BLOCK + ": render 256 LODs for each Chunk (width of one block). \n"
+ + "\n"
+ + " Lowest Quality: " + HorizontalResolution.CHUNK
+ + " Highest Quality: " + HorizontalResolution.BLOCK;
+ HorizontalResolution getDrawResolution();
+ void setDrawResolution(HorizontalResolution newHorizontalResolution);
+
+ MinDefaultMax LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX = new MinDefaultMax(16, 64, 1024);
+ String LOD_CHUNK_RENDER_DISTANCE_DESC = ""
+ + " The radius of the mod's render distance. (measured in chunks) \n";
+ int getLodChunkRenderDistance();
+ void setLodChunkRenderDistance(int newLodChunkRenderDistance);
+
+ VerticalQuality VERTICAL_QUALITY_DEFAULT = VerticalQuality.MEDIUM;
+ String VERTICAL_QUALITY_DESC = ""
+ + " This indicates how detailed fake chunks will represent \n"
+ + " overhangs, caves, floating islands, ect. \n"
+ + " Higher options will make the world more accurate, but"
+ + " will increase memory and GPU usage. \n"
+ + "\n"
+ + " " + VerticalQuality.LOW + ": uses at max 2 columns per position. \n"
+ + " " + VerticalQuality.MEDIUM + ": uses at max 4 columns per position. \n"
+ + " " + VerticalQuality.HIGH + ": uses at max 8 columns per position. \n"
+ + "\n"
+ + " Lowest Quality: " + VerticalQuality.LOW
+ + " Highest Quality: " + VerticalQuality.HIGH;
+ VerticalQuality getVerticalQuality();
+ void setVerticalQuality(VerticalQuality newVerticalQuality);
+
+ MinDefaultMax HORIZONTAL_SCALE_MIN_DEFAULT_MAX = new MinDefaultMax(2, 8, 32);
+ String HORIZONTAL_SCALE_DESC = ""
+ + " This indicates how quickly fake chunks decrease in quality the further away they are. \n"
+ + " Higher settings will render higher quality fake chunks farther away, \n"
+ + " but will increase memory and GPU usage.";
+ int getHorizontalScale();
+ void setHorizontalScale(int newHorizontalScale);
+
+ HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM;
+ String HORIZONTAL_QUALITY_DESC = ""
+ + " This indicates the exponential base of the quadratic drop-off \n"
+ + "\n"
+ + " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n"
+ + " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
+ + " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
+ + " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n"
+ + "\n"
+ + " Lowest Quality: " + HorizontalQuality.LOWEST
+ + " Highest Quality: " + HorizontalQuality.HIGH;
+ HorizontalQuality getHorizontalQuality();
+ void setHorizontalQuality(HorizontalQuality newHorizontalQuality);
+ }
+
+ interface IFogQuality
+ {
+ String DESC = "These settings control the fog quality.";
+
+ FogDistance FOG_DISTANCE_DEFAULT = FogDistance.FAR;
+ String FOG_DISTANCE_DESC = ""
+ + " At what distance should Fog be drawn on the fake chunks? \n"
+ + "\n"
+ + " This setting shouldn't affect performance.";
+ FogDistance getFogDistance();
+ void setFogDistance(FogDistance newFogDistance);
+
+ FogDrawMode FOG_DRAW_MODE_DEFAULT = FogDrawMode.FOG_ENABLED;
+ String FOG_DRAW_MODE_DESC = ""
+ + " When should fog be drawn? \n"
+ + "\n"
+ + " " + FogDrawMode.USE_OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawMode.FOG_ENABLED + ". \n"
+ + " " + FogDrawMode.FOG_ENABLED + ": Never draw fog on the LODs \n"
+ + " " + FogDrawMode.FOG_DISABLED + ": Always draw fast fog on the LODs \n"
+ + "\n"
+ + " Disabling fog will improve GPU performance.";
+ FogDrawMode getFogDrawMode();
+ void setFogDrawMode(FogDrawMode newFogDrawMode);
+
+ FogColorMode FOG_COLOR_MODE_DEFAULT = FogColorMode.USE_WORLD_FOG_COLOR;
+ String FOG_COLOR_MODE_DESC = ""
+ + " What color should fog use? \n"
+ + "\n"
+ + " " + FogColorMode.USE_WORLD_FOG_COLOR + ": Use the world's fog color. \n"
+ + " " + FogColorMode.USE_SKY_COLOR + ": Use the sky's color. \n"
+ + "\n"
+ + " This setting doesn't affect performance.";
+ FogColorMode getFogColorMode();
+ void setFogColorMode(FogColorMode newFogColorMode);
+
+ boolean DISABLE_VANILLA_FOG_DEFAULT = false;
+ String DISABLE_VANILLA_FOG_DESC = ""
+ + " If true disable Minecraft's fog. \n"
+ + "\n"
+ + " Experimental! Will cause issues with Sodium and \n"
+ + " may not play nice with other mods that edit fog. \n";
+ boolean getDisableVanillaFog();
+ void setDisableVanillaFog(boolean newDisableVanillaFog);
+ }
+
+ interface IAdvancedGraphics
+ {
+ String DESC = "Graphics options that are a bit more technical.";
+
+ LodTemplate LOD_TEMPLATE_DEFAULT = LodTemplate.CUBIC;
+ String LOD_TEMPLATE_DESC = ""
+ + " How should the LODs be drawn? \n"
+ + " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n"
+ + " \n"
+ + " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
+ + " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n"
+ + " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between each other, \n"
+ + " " + " unless a neighboring chunk is at a significantly different height. \n";
+ LodTemplate getLodTemplate();
+ void setLodTemplate(LodTemplate newLodTemplate);
+
+ boolean DISABLE_DIRECTIONAL_CULLING_DEFAULT = false;
+ String DISABLE_DIRECTIONAL_CULLING_DESC = ""
+ + " If false fake chunks behind the player's camera \n"
+ + " aren't drawn, increasing GPU performance. \n"
+ + "\n"
+ + " If true all LODs are drawn, even those behind \n"
+ + " the player's camera, decreasing GPU performance. \n"
+ + "\n"
+ + " Disable this if you see LODs disappearing at the corners of your vision. \n";
+ boolean getDisableDirectionalCulling();
+ void setDisableDirectionalCulling(boolean newDisableDirectionalCulling);
+
+ boolean ALWAYS_DRAW_AT_MAD_QUALITY_DEFAULT = false;
+ String ALWAYS_DRAW_AT_MAD_QUALITY_DESC = ""
+ + " Disable quality falloff, \n"
+ + " all fake chunks will be drawn at the highest \n"
+ + " available detail level. \n"
+ + "\n"
+ + " WARNING: \n"
+ + " This could cause an Out Of Memory crash when using render \n"
+ + " distances higher than 128 and will drastically increase GPU usage. \n";
+ boolean getAlwaysDrawAtMaxQuality();
+ void setAlwaysDrawAtMaxQuality(boolean newAlwaysDrawAtMaxQuality);
+
+ VanillaOverdraw VANILLA_OVERDRAW_DEFAULT = VanillaOverdraw.DYNAMIC;
+ String VANILLA_OVERDRAW_DESC = ""
+ + " How often should LODs be drawn on top of regular chunks? \n"
+ + " HALF and ALWAYS will prevent holes in the world, but may look odd for transparent blocks or in caves. \n"
+ + "\n"
+ + " " + VanillaOverdraw.NEVER + ": LODs won't render on top of vanilla chunks. \n"
+ + " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n"
+ + " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n"
+ + " " + " More effective on higher render distances. \n"
+ + " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n"
+ + " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " will be used depending on the dimension. \n"
+ + " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n"
+ + "\n"
+ + " This setting shouldn't affect performance. \n";
+ VanillaOverdraw getVanillaOverdraw();
+ void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw);
+
+ boolean USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT = false;
+ String USE_EXTENDED_NEAR_CLIP_PLANE_DESC = ""
+ + " Will prevent some overdraw issues, but may cause nearby fake chunks to render incorrectly \n"
+ + " especially when in/near an ocean. \n"
+ + "\n"
+ + " This setting shouldn't affect performance. \n";
+ boolean getUseExtendedNearClipPlane();
+ void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane);
+ }
+ }
+
+
+
+
+ //========================//
+ // WorldGenerator Configs //
+ //========================//
+ interface IWorldGenerator
+ {
+ String DESC = "These settings control how fake chunks outside your normal view range are generated.";
+
+ GenerationPriority GENERATION_PRIORITY_DEFAULT = GenerationPriority.AUTO;
+ String GENERATION_PRIORITY_DESC = ""
+ + " In what order should fake chunks be generated outside the vanilla render distance? \n"
+ + "\n"
+ + " " + GenerationPriority.FAR_FIRST + " \n"
+ + " Fake chunks are generated from lowest to highest detail \n"
+ + " with a small priority for far away regions. \n"
+ + " This fills in the world fastest, but you will have large low detail \n"
+ + " blocks for a while while the generation happens. \n"
+ + "\n"
+ + " " + GenerationPriority.NEAR_FIRST + " \n"
+ + " Fake chunks are generated around the player \n"
+ + " in a spiral, similar to vanilla minecraft. \n"
+ + " Best used when on a server since we can't generate \n"
+ + " fake chunks. \n"
+ + "\n"
+ + " " + GenerationPriority.AUTO + " \n"
+ + " Uses " + GenerationPriority.FAR_FIRST + " when on a single player world \n"
+ + " and " + GenerationPriority.NEAR_FIRST + " when connected to a server. \n"
+ + "\n"
+ + " This shouldn't affect performance.";
+ GenerationPriority getGenerationPriority();
+ void setGenerationPriority(GenerationPriority newGenerationPriority);
+
+ DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
+ String DISTANCE_GENERATION_MODE_DESC = ""
+ + " How detailed should fake chunks be generated outside the vanilla render distance? \n"
+ + "\n"
+ + " " + DistanceGenerationMode.NONE + " \n"
+ + " Don't run the distance generator. \n"
+ + " No CPU usage - Fastest \n"
+ + "\n"
+ + " " + DistanceGenerationMode.BIOME_ONLY + " \n"
+ + " Only generate the biomes and use the biome's \n"
+ + " grass color, water color, or snow color. \n"
+ + " Doesn't generate height, everything is shown at sea level. \n"
+ + " Multithreaded - Fastest (2-5 ms) \n"
+ + "\n"
+ + " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
+ + " Same as " + DistanceGenerationMode.BIOME_ONLY + ", except instead \n"
+ + " of always using sea level as the LOD height \n"
+ + " different biome types (mountain, ocean, forest, etc.) \n"
+ + " use predetermined heights to simulate having height data. \n"
+ + " Multithreaded - Fastest (2-5 ms) \n"
+ + "\n"
+ + " " + DistanceGenerationMode.SURFACE + " \n"
+ + " Generate the world surface, \n"
+ + " this does NOT include trees, \n"
+ + " or structures. \n"
+ + " Multithreaded - Faster (10-20 ms) \n"
+ + "\n"
+ + " " + DistanceGenerationMode.FEATURES + " \n"
+ + " Generate everything except structures. \n"
+ + " WARNING: This may cause world generation bugs or instability! \n"
+ + " Multithreaded - Fast (15-20 ms) \n"
+ + "\n"
+ + " " + DistanceGenerationMode.FULL + " \n"
+ + " Ask the local server to generate/load each chunk. \n"
+ + " This will show player made structures, which can \n"
+ + " be useful if you are adding the mod to a pre-existing world. \n"
+ + " This is the most compatible, but causes server/simulation lag. \n"
+ + " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n"
+ + "\n"
+ + " The multithreaded options may increase CPU load significantly (while generating) \n"
+ + " depending on how many world generation threads you have allocated. \n";
+ DistanceGenerationMode getDistanceGenerationMode();
+ void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode);
+
+ boolean ALLOW_UNSTABLE_FEATURE_GENERATION_DEFAULT = false;
+ String ALLOW_UNSTABLE_FEATURE_GENERATION_DESC = ""
+ + " When using the " + DistanceGenerationMode.FEATURES + " generation mode \n"
+ + " some features may not be thread safe, which could \n"
+ + " cause instability and crashes. \n"
+ + " By default (false) those features are skipped, \n"
+ + " improving stability, but decreasing how many features are \n"
+ + " actually generated. \n"
+ + " (for example: some tree generation is unstable, \n"
+ + " so some trees may not be generated.) \n"
+ + " By setting this to true, all features will be generated, \n"
+ + " but your game will be more unstable and crashes may occur. \n"
+ + "\n"
+ + " I would love to remove this option and always generate everything, \n"
+ + " but I'm not sure how to do that. \n"
+ + " If you are a Java wizard, check out the git issue here: \n"
+ + " https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues/35 \n";
+ boolean getAllowUnstableFeatureGeneration();
+ void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration);
+
+ BlocksToAvoid BLOCKS_TO_AVOID_DEFAULT = BlocksToAvoid.BOTH;
+ String BLOCKS_TO_AVOID_DESC = ""
+ + " When generating fake chunks, what blocks should be ignored? \n"
+ + " Ignored blocks don't affect the height of the fake chunk, but might affect the color. \n"
+ + " So using " + BlocksToAvoid.BOTH + " will prevent snow covered blocks from appearing one block too tall, \n"
+ + " but will still show the snow's color.\n"
+ + "\n"
+ + " " + BlocksToAvoid.NONE + ": Use all blocks when generating fake chunks \n"
+ + " " + BlocksToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, tall grass, etc.) \n"
+ + " " + BlocksToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores tall grass, torches, etc.) \n"
+ + " " + BlocksToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n"
+ + "\n"
+ + " This wont't affect performance.";
+ BlocksToAvoid getBlocksToAvoid();
+ void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
+ }
+
+
+
+
+ //============================//
+ // AdvancedModOptions Configs //
+ //============================//
+ interface IAdvanced
+ {
+ String DESC = "Advanced mod settings";
+
+ IThreading threading();
+ IDebugging debugging();
+ IBuffers buffers();
+
+
+ interface IThreading
+ {
+ String DESC = "These settings control how many CPU threads the mod uses for different tasks.";
+
+ MinDefaultMax NUMBER_OF_WORLD_GENERATION_THREADS_DEFAULT
+ = new MinDefaultMax(1,
+ Runtime.getRuntime().availableProcessors() / 2,
+ Runtime.getRuntime().availableProcessors());
+ String NUMBER_OF_WORLD_GENERATION_THREADS_DESC = ""
+ + " How many threads should be used when generating fake chunks outside \n"
+ + " the normal render distance? \n"
+ + "\n"
+ + " If you experience stuttering when generating distant LODs, decrease \n"
+ + " this number. If you want to increase LOD generation speed, \n"
+ + " increase this number. \n"
+ + "\n"
+ + " This and the number of buffer builder threads are independent, \n"
+ + " so if they add up to more threads than your CPU has cores, \n"
+ + " that shouldn't cause an issue. \n"
+ + "\n"
+ + " The maximum value is the number of logical processors on your CPU. \n"
+ + " Requires a restart to take effect. \n";
+ int getNumberOfWorldGenerationThreads();
+ void setNumberOfWorldGenerationThreads(int newNumberOfWorldGenerationThreads);
+
+ MinDefaultMax NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX
+ = new MinDefaultMax(1,
+ Runtime.getRuntime().availableProcessors() / 2,
+ Runtime.getRuntime().availableProcessors());
+ String NUMBER_OF_BUFFER_BUILDER_THREADS_DESC = ""
+ + " How many threads are used when building vertex buffers? \n"
+ + " (The things sent to your GPU to draw the fake chunks). \n"
+ + "\n"
+ + " If you experience high CPU usage when NOT generating distant \n"
+ + " fake chunks, lower this number. A higher number will make fake\n"
+ + " fake chunks' transition faster when moving around the world. \n"
+ + "\n"
+ + " This and the number of world generator threads are independent, \n"
+ + " so if they add up to more threads than your CPU has cores, \n"
+ + " that shouldn't cause an issue. \n"
+ + "\n"
+ + " The maximum value is the number of logical processors on your CPU. \n"
+ + " Requires a restart to take effect. \n";
+ int getNumberOfBufferBuilderThreads();
+ void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads);
+ }
+
+ interface IDebugging
+ {
+ String DESC = "These settings can be used to look for bugs, or see how certain aspects of the mod work.";
+
+ boolean DRAW_LODS_DEFAULT = true;
+ String DRAW_LODS_DESC = ""
+ + " If true, the mod is enabled and fake chunks will be drawn. \n"
+ + " If false, the mod will still generate fake chunks, \n"
+ + " but they won't be rendered. \n"
+ + "\n"
+ + " Disabling rendering will reduce GPU usage \n";
+ boolean getDrawLods();
+ void setDrawLods(boolean newDrawLods);
+
+ DebugMode DEBUG_MODE_DEFAULT = DebugMode.OFF;
+ String DEBUG_MODE_DESC = ""
+ + " Should specialized colors/rendering modes be used? \n"
+ + "\n"
+ + " " + DebugMode.OFF + ": Fake chunks will be drawn with their normal colors. \n"
+ + " " + DebugMode.SHOW_DETAIL + ": Fake chunks color will be based on their detail level. \n"
+ + " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": Fake chunks color will be based on their detail level, drawn as a wireframe. \n";
+ DebugMode getDebugMode();
+ void setDebugMode(DebugMode newDebugMode);
+
+ boolean DEBUG_KEYBINDINGS_ENABLED_DEFAULT = true;
+ String DEBUG_KEYBINDINGS_ENABLED_DESC = ""
+ + " If true the F4 key can be used to cycle through the different debug modes. \n"
+ + " and the F6 key can be used to enable and disable LOD rendering.";
+ boolean getDebugKeybindingsEnabled();
+ void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings);
+ }
+
+ interface IBuffers
+ {
+ String DESC = "These settings affect how often geometry is rebuilt.";
+
+ GpuUploadMethod GPU_UPLOAD_METHOD_DEFAULT = GpuUploadMethod.AUTO;
+ String GPU_UPLOAD_METHOD_DESC = ""
+ + " What method should be used to upload geometry to the GPU? \n"
+ + "\n"
+ + " " + GpuUploadMethod.AUTO + ": Picks the best option based on the GPU you have. \n"
+ + " " + GpuUploadMethod.BUFFER_STORAGE + ": Default for NVIDIA if OpenGL 4.5 is supported. \n"
+ + " Fast rendering, no stuttering. \n"
+ + " " + GpuUploadMethod.SUB_DATA + ": Backup option for NVIDIA. \n"
+ + " Fast rendering but may stutter when uploading. \n"
+ + " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly the best option for integrated GPUs. \n"
+ + " Default option for AMD/Intel. \n"
+ + " May end up storing buffers in System memory. \n"
+ + " Fast rending if in GPU memory, slow if in system memory, \n"
+ + " but won't stutter when uploading. \n"
+ + " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ + " Backup option for AMD/Intel. \n"
+ + " Fast rendering but may stutter when uploading. \n"
+ + "\n"
+ + " If you don't see any difference when changing these settings, or the world looks corrupted: \n"
+ + " Restart the game to clear the old buffers. \n";
+ GpuUploadMethod getGpuUploadMethod();
+ void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod);
+
+ MinDefaultMax GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DEFAULT = new MinDefaultMax(0, 0, 5000);
+ String GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DESC = ""
+ + " How long should we wait before uploading a buffer to the GPU? \n"
+ + " Helpful resource for frame times: https://fpstoms.com \n"
+ + "\n"
+ + " Longer times may reduce stuttering but will make fake chunks \n"
+ + " transition and load slower. \n"
+ + "\n"
+ + " NOTE:\n"
+ + " This should be a last resort option."
+ + " Only change this from [0], after you have tried all of the \n"
+ + " \"GPU Upload methods\" and determined even the best stutters with yoru hardware.";
+ int getGpuUploadTimeoutInMilliseconds();
+ void setGpuUploadTimeoutInMilliseconds(int newTimeoutInMilliseconds);
+
+ String REBUILD_TIMES_DESC = ""
+ + " How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
+ + " Higher settings may cause stuttering, but will prevent holes in the world \n";
+ BufferRebuildTimes REBUILD_TIMES_DEFAULT = BufferRebuildTimes.NORMAL;
+ BufferRebuildTimes getRebuildTimes();
+ void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
new file mode 100644
index 000000000..b39289b1a
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
@@ -0,0 +1,69 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.minecraft;
+
+import java.awt.Color;
+import java.util.HashSet;
+
+import com.seibel.lod.core.objects.math.Mat4f;
+import com.seibel.lod.core.objects.math.Vec3d;
+import com.seibel.lod.core.objects.math.Vec3f;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+
+/**
+ * Contains everything related to
+ * rendering in Minecraft.
+ *
+ * @author James Seibel
+ * @version 11-26-2021
+ */
+public interface IMinecraftRenderWrapper
+{
+ Vec3f getLookAtVector();
+
+ AbstractBlockPosWrapper getCameraBlockPosition();
+
+ boolean playerHasBlindnessEffect();
+
+ Vec3d getCameraExactPosition();
+
+ Mat4f getDefaultProjectionMatrix(float partialTicks);
+
+ double getGamma();
+
+ Color getFogColor();
+
+ Color getSkyColor();
+
+ double getFov(float partialTicks);
+
+ /** Measured in chunks */
+ int getRenderDistance();
+
+ int getScreenWidth();
+ int getScreenHeight();
+
+ /**
+ * This method returns the ChunkPos of all chunks that Minecraft
+ * is going to render this frame.
+ */
+ HashSet getRenderedChunks();
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java
new file mode 100644
index 000000000..f6668e0dc
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java
@@ -0,0 +1,157 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.minecraft;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.ArrayList;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+
+/**
+ * Contains everything related to the Minecraft object.
+ *
+ * @author James Seibel
+ * @version 9-16-2021
+ */
+public interface IMinecraftWrapper
+{
+ //================//
+ // helper methods //
+ //================//
+
+ /**
+ * This should be called at the beginning of every frame to
+ * clear any Minecraft data that becomes out of date after a frame.
+ *
+ * LightMaps and other time sensitive objects fall in this category.
+ *
+ * This doesn't affect OpenGL objects in any way.
+ */
+ void clearFrameObjectCache();
+
+
+
+ //=================//
+ // method wrappers //
+ //=================//
+
+ float getShade(LodDirection lodDirection);
+
+ boolean hasSinglePlayerServer();
+
+ String getCurrentServerName();
+ String getCurrentServerIp();
+ String getCurrentServerVersion();
+
+ /** Returns the dimension the player is currently in */
+ IDimensionTypeWrapper getCurrentDimension();
+
+ String getCurrentDimensionId();
+
+ /** This texture changes every frame */
+ ILightMapWrapper getCurrentLightMap();
+
+ /**
+ * Returns the color int at the given pixel coordinates
+ * from the current lightmap.
+ * @param u x location in texture space
+ * @param v z location in texture space
+ */
+ int getColorIntFromLightMap(int u, int v);
+
+ /**
+ * Returns the Color at the given pixel coordinates
+ * from the current lightmap.
+ * @param u x location in texture space
+ * @param v z location in texture space
+ */
+ Color getColorFromLightMap(int u, int v);
+
+
+
+
+ //=============//
+ // Simple gets //
+ //=============//
+
+ boolean playerExists();
+
+ AbstractBlockPosWrapper getPlayerBlockPos();
+
+ AbstractChunkPosWrapper getPlayerChunkPos();
+
+ /**
+ * Attempts to get the ServerWorld for the dimension
+ * the user is currently in.
+ * @returns null if no ServerWorld is available
+ */
+ IWorldWrapper getWrappedServerWorld();
+
+ IWorldWrapper getWrappedClientWorld();
+
+ File getGameDirectory();
+
+ IProfilerWrapper getProfiler();
+
+ float getSkyDarken(float partialTicks);
+
+ boolean connectedToServer();
+
+ /** Returns all worlds available to the server */
+ ArrayList getAllServerWorlds();
+
+
+
+ void sendChatMessage(String string);
+
+ /**
+ * Crashes Minecraft, displaying the given errorMessage
+ * In the following format:
+ *
+ * The game crashed whilst errorMessage
+ * Error: ExceptionClass: exceptionErrorMessage
+ * Exit Code: -1
+ */
+ void crashMinecraft(String errorMessage, Throwable exception);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java
new file mode 100644
index 000000000..bf081fb72
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.minecraft;
+
+/**
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public interface IProfilerWrapper
+{
+ void push(String newSection);
+
+ void popPush(String newSection);
+
+ void pop();
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java
new file mode 100644
index 000000000..76453b5b8
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.misc;
+
+/**
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public interface ILightMapWrapper
+{
+ int getLightValue(int skyLight, int blockLight);
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java
new file mode 100644
index 000000000..76a1c1768
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.world;
+
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+
+/**
+ * Contains everything related to biome colors.
+ *
+ * @author James Seibel
+ * @version 11-15-2021
+ */
+public interface IBiomeColorWrapperSingleton
+{
+ IBiomeColorWrapperSingleton getInstance();
+
+ int getGrassColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
+ int getWaterColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
+ int getFoliageColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java
new file mode 100644
index 000000000..eb1b620f6
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java
@@ -0,0 +1,37 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.world;
+
+/**
+ * @author James Seibel
+ * @version 11-15-2021
+ */
+public interface IBiomeWrapper
+{
+ /** Returns a color int for the given biome. */
+ int getColorForBiome(int x, int z);
+
+ int getGrassTint(int x, int z);
+
+ int getFolliageTint();
+
+ int getWaterTint();
+
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java
new file mode 100644
index 000000000..cbafd5cd6
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java
@@ -0,0 +1,33 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.world;
+
+/**
+ * @author James Seibel
+ * @version 11-15-2021
+ */
+public interface IDimensionTypeWrapper
+{
+ String getDimensionName();
+
+ boolean hasCeiling();
+
+ boolean hasSkyLight();
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java
new file mode 100644
index 000000000..cb35e1fd5
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.world;
+
+import java.io.File;
+
+import com.seibel.lod.core.enums.WorldType;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+/**
+ * Can be either a Server world or a Client world.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public interface IWorldWrapper
+{
+ IDimensionTypeWrapper getDimensionType();
+
+ WorldType getWorldType();
+
+ int getBlockLight(AbstractBlockPosWrapper blockPos);
+
+ int getSkyLight(AbstractBlockPosWrapper blockPos);
+
+ IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos);
+
+ boolean hasCeiling();
+
+ boolean hasSkyLight();
+
+ // Pls don't use this
+ // If the world is null then this can't be called and gives an error
+ boolean isEmpty();
+
+ int getHeight();
+
+ int getSeaLevel();
+
+ /** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
+ File getSaveFolder() throws UnsupportedOperationException;
+
+
+}
diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java
new file mode 100644
index 000000000..696c89e86
--- /dev/null
+++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the Distant Horizon mod (formerly 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.core.wrapperInterfaces.worldGeneration;
+
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.enums.config.DistanceGenerationMode;
+import com.seibel.lod.core.objects.lod.LodDimension;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+
+/**
+ * This is used for generating chunks
+ * in a variety of detail and threading levels.
+ *
+ * Abstract instead of an interface, so
+ * we can define its constructors.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public abstract class AbstractWorldGeneratorWrapper
+{
+ public AbstractWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { }
+
+
+ public abstract void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode);
+
+ public abstract void generateSurface(AbstractChunkPosWrapper pos);
+
+ public abstract void generateFeatures(AbstractChunkPosWrapper pos);
+
+ public abstract void generateFull(AbstractChunkPosWrapper pos);
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/ClientProxy.java b/src/main/java/com/seibel/lod/fabric/ClientProxy.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/ClientProxy.java
rename to src/main/java/com/seibel/lod/fabric/ClientProxy.java
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/Main.java b/src/main/java/com/seibel/lod/fabric/Main.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/Main.java
rename to src/main/java/com/seibel/lod/fabric/Main.java
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java b/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java
rename to src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
similarity index 86%
rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
rename to src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
index 24856cf04..572491ef2 100644
--- a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
+++ b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
@@ -22,7 +22,7 @@ import java.util.function.Supplier;
@Mixin(ClientLevel.class)
public class MixinClientLevel {
@Inject(method = "", at = @At("TAIL"))
- private void loadWorldEvent(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData, ResourceKey resourceKey, DimensionType dimensionType, int i, Supplier supplier, LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) {
+ private void loadWorldEvent(ClientPacketListener clientPacketListener, ClientLevel.ClientLevelData clientLevelData, ResourceKey resourceKey, DimensionType dimensionType, int i, int j, Supplier supplier, LevelRenderer levelRenderer, boolean bl, long l, CallbackInfo ci) {
Main.client_proxy.worldLoadEvent((ClientLevel) (Object) this);
}
}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java
rename to src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java b/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java
rename to src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java b/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java
rename to src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java b/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java
similarity index 100%
rename from fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java
rename to src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java
diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftRenderWrapper.java b/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java
similarity index 98%
rename from common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftRenderWrapper.java
rename to src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java
index 3fe97ea0f..fa183b241 100644
--- a/common/src/main/java/com/seibel/lod/common/wrappers/minecraft/MinecraftRenderWrapper.java
+++ b/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java
@@ -1,4 +1,4 @@
-package com.seibel.lod.common.wrappers.minecraft;
+package com.seibel.lod.fabric.wrappers.minecraft;
import java.awt.*;
import java.util.HashSet;
@@ -143,6 +143,8 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
// go through every RenderInfo to get the compiled chunks
LevelRenderer renderer = mc.levelRenderer;
+ // TODO[1.18]: Fix this
+ /*
for (RenderChunkInfo worldRenderer$LocalRenderInformationContainer : renderer.renderChunks)
{
CompiledChunk compiledChunk = worldRenderer$LocalRenderInformationContainer.chunk.getCompiledChunk();
@@ -154,6 +156,7 @@ public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
loadedPos.add(new ChunkPosWrapper(bpos));
}
}
+ */
return loadedPos;
}
diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/LodServerWorld.java b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java
similarity index 93%
rename from common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/LodServerWorld.java
rename to src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java
index 4bb09f586..922821627 100644
--- a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/LodServerWorld.java
+++ b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java
@@ -17,13 +17,12 @@
* along with this program. If not, see .
*/
-package com.seibel.lod.common.wrappers.worldGeneration;
+package com.seibel.lod.fabric.wrappers.worldGeneration;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.function.Predicate;
-import java.util.stream.Stream;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.common.wrappers.WrapperUtil;
@@ -39,8 +38,6 @@ import net.minecraft.sounds.SoundSource;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
-import net.minecraft.world.level.EmptyTickList;
-import net.minecraft.world.level.TickList;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
@@ -62,6 +59,8 @@ import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.storage.LevelData;
import net.minecraft.world.phys.AABB;
+import net.minecraft.world.ticks.LevelTickAccess;
+import net.minecraft.world.ticks.TickAccess;
import org.jetbrains.annotations.Nullable;
@@ -71,8 +70,9 @@ import org.jetbrains.annotations.Nullable;
* of the actual ServerWorld, allowing
* multithread generation.
*
+ * @author coolGi2007
* @author James Seibel
- * @version 7-26-2021
+ * @version 12-06-2021
*/
public class LodServerWorld implements WorldGenLevel
{
@@ -106,7 +106,7 @@ public class LodServerWorld implements WorldGenLevel
@Override
public Biome getBiome(BlockPos pos)
{
- return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
+ return chunk.getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
}
@Override
@@ -140,9 +140,19 @@ public class LodServerWorld implements WorldGenLevel
}
@Override
- public TickList getBlockTicks()
+ public long nextSubTickCount() {
+ return 0;
+ }
+
+ @Override
+ public LevelTickAccess getBlockTicks()
{
- return EmptyTickList.empty();
+ return null;
+ }
+
+ @Override
+ public LevelTickAccess getFluidTicks() {
+ return null;
}
@Override
@@ -152,17 +162,11 @@ public class LodServerWorld implements WorldGenLevel
}
@Override
- public Stream extends StructureStart>> startsForFeature(SectionPos p_241827_1_, StructureFeature> p_241827_2_)
+ public List extends StructureStart>> startsForFeature(SectionPos p_241827_1_, StructureFeature> p_241827_2_)
{
return serverWorld.startsForFeature(p_241827_1_, p_241827_2_);
}
- @Override
- public TickList getLiquidTicks()
- {
- return EmptyTickList.empty();
- }
-
@Override
public LevelLightEngine getLightEngine()
{
diff --git a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/WorldGeneratorWrapper.java b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java
similarity index 98%
rename from common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/WorldGeneratorWrapper.java
rename to src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java
index f880a10c0..da5d5e5a8 100644
--- a/common/src/main/java/com/seibel/lod/common/wrappers/worldGeneration/WorldGeneratorWrapper.java
+++ b/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java
@@ -1,4 +1,4 @@
-package com.seibel.lod.common.wrappers.worldGeneration;
+package com.seibel.lod.fabric.wrappers.worldGeneration;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
@@ -24,6 +24,7 @@ import com.seibel.lod.common.wrappers.chunk.ChunkPosWrapper;
import com.seibel.lod.common.wrappers.chunk.ChunkWrapper;
import com.seibel.lod.common.wrappers.world.WorldWrapper;
+import com.seibel.lod.fabric.wrappers.worldGeneration.LodServerWorld;
import net.minecraft.core.Registry;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
@@ -78,6 +79,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper
@Override
public void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode)
{
+ /*
List chunkList = new LinkedList<>();
ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY, serverWorld);
chunkList.add(chunk);
@@ -182,6 +184,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper
// long startTime = System.currentTimeMillis();
// long endTime = System.currentTimeMillis();
// System.out.println(endTime - startTime);
+ */
}
@@ -189,6 +192,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper
@Override
public void generateSurface(AbstractChunkPosWrapper pos)
{
+ /*
List chunkList = new LinkedList<>();
ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY, serverWorld);
chunkList.add(chunk);
@@ -232,6 +236,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper
@Override
public void generateFeatures(AbstractChunkPosWrapper pos)
{
+ /*
List chunkList = new LinkedList<>();
ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY, serverWorld);
chunkList.add(chunk);
@@ -338,6 +343,7 @@ public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper
// generate a Lod like normal
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.FEATURES));
+ */
}
diff --git a/common/src/main/resources/assets/lod/lang/en_us.json b/src/main/resources/assets/lod/lang/en_us.json
similarity index 100%
rename from common/src/main/resources/assets/lod/lang/en_us.json
rename to src/main/resources/assets/lod/lang/en_us.json
diff --git a/common/src/main/resources/assets/lod/textures/gui/button.png b/src/main/resources/assets/lod/textures/gui/button.png
similarity index 100%
rename from common/src/main/resources/assets/lod/textures/gui/button.png
rename to src/main/resources/assets/lod/textures/gui/button.png
diff --git a/fabric/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
similarity index 93%
rename from fabric/src/main/resources/fabric.mod.json
rename to src/main/resources/fabric.mod.json
index 56a6256c4..9e6700246 100644
--- a/fabric/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -37,10 +37,10 @@
"accessWidener" : "lod.accesswidener",
"depends": {
- "fabricloader": ">=0.11.3",
+ "fabricloader": ">=0.12.6",
"fabric": "*",
- "minecraft": "1.17.x",
- "java": ">=16"
+ "minecraft": "1.18",
+ "java": ">=17"
},
"suggests": {
"another-mod": "*"
diff --git a/common/src/main/resources/icon.png b/src/main/resources/icon.png
similarity index 100%
rename from common/src/main/resources/icon.png
rename to src/main/resources/icon.png
diff --git a/common/src/main/resources/lod.accesswidener b/src/main/resources/lod.accesswidener
similarity index 100%
rename from common/src/main/resources/lod.accesswidener
rename to src/main/resources/lod.accesswidener
diff --git a/fabric/src/main/resources/lod.common.mixins.json b/src/main/resources/lod.common.mixins.json
similarity index 87%
rename from fabric/src/main/resources/lod.common.mixins.json
rename to src/main/resources/lod.common.mixins.json
index 610c2637e..ec976a475 100644
--- a/fabric/src/main/resources/lod.common.mixins.json
+++ b/src/main/resources/lod.common.mixins.json
@@ -2,7 +2,7 @@
"required": true,
"minVersion": "0.8",
"package": "com.seibel.lod.common.mixins",
- "compatibilityLevel": "JAVA_16",
+ "compatibilityLevel": "JAVA_17",
"mixins": [],
"client": [
"MixinOptionsScreen",
diff --git a/fabric/src/main/resources/lod.mixins.json b/src/main/resources/lod.mixins.json
similarity index 89%
rename from fabric/src/main/resources/lod.mixins.json
rename to src/main/resources/lod.mixins.json
index 478d3c133..f25127bd0 100644
--- a/fabric/src/main/resources/lod.mixins.json
+++ b/src/main/resources/lod.mixins.json
@@ -2,7 +2,7 @@
"required": true,
"minVersion": "0.8",
"package": "com.seibel.lod.fabric.mixins",
- "compatibilityLevel": "JAVA_16",
+ "compatibilityLevel": "JAVA_17",
"mixins": [],
"client": [
"MixinMinecraft",
diff --git a/src/main/resources/shaders/flat_shaded.frag b/src/main/resources/shaders/flat_shaded.frag
new file mode 100644
index 000000000..7677dfa66
--- /dev/null
+++ b/src/main/resources/shaders/flat_shaded.frag
@@ -0,0 +1,89 @@
+#version 150 core
+
+in vec4 vertexColor;
+in vec4 vertexWorldPos;
+//in vec2 textureCoord;
+
+
+out vec4 fragColor;
+
+
+//uniform sampler2D texImage;
+uniform vec3 cameraPos;
+
+uniform bool fogEnabled;
+uniform bool nearFogEnabled;
+uniform bool farFogEnabled;
+
+uniform float nearFogStart;
+uniform float nearFogEnd;
+uniform float farFogStart;
+uniform float farFogEnd;
+uniform vec4 fogColor;
+
+
+// method definitions
+float getFogAlpha(float start, float end, float dist);
+
+
+
+/**
+ * Fragment Shader
+ *
+ * author: James Seibel
+ * version: 11-26-2021
+ */
+void main()
+{
+ // TODO: add a white texture to support Optifine shaders
+ //vec4 textureColor = texture(texImage, textureCoord);
+ //fragColor = vertexColor * textureColor;
+
+
+ vec4 returnColor;
+ if (fogEnabled)
+ {
+ // add fog
+
+ float dist = distance(vertexWorldPos, vec4(cameraPos,1));
+ // no fog by default
+ float fogAlpha = 0;
+
+ // less than because nearFogStart is farther away than nearFogEnd
+ if (nearFogEnabled && dist < nearFogStart)
+ {
+ fogAlpha = getFogAlpha(nearFogStart, nearFogEnd, dist);
+ }
+ else if (farFogEnabled)
+ {
+ fogAlpha = getFogAlpha(farFogStart, farFogEnd, dist);
+ }
+
+ returnColor = mix(vertexColor, vec4(fogColor.xyz, 1), fogAlpha);
+ }
+ else
+ {
+ // simple flat color
+ returnColor = vertexColor;
+ }
+
+
+
+ fragColor = returnColor;
+}
+
+
+
+
+/**
+ * Returns the fog strength for the given fragment.
+ * This is the same implementation as legacy OpenGL's Linear fog option.
+ * 1 = completely opaque fog
+ * 0 = no fog
+ */
+float getFogAlpha(float start, float end, float dist)
+{
+ float fogAlpha = 1 - ((end - dist) / (end - start));
+ return clamp(fogAlpha, 0, 1);
+}
+
diff --git a/src/main/resources/shaders/standard.vert b/src/main/resources/shaders/standard.vert
new file mode 100644
index 000000000..32d457ac6
--- /dev/null
+++ b/src/main/resources/shaders/standard.vert
@@ -0,0 +1,34 @@
+#version 150 core
+
+in vec3 vPosition;
+in vec4 color;
+
+
+out vec4 vertexColor;
+out vec4 vertexWorldPos;
+//out vec2 textureCoord;
+out float depth;
+
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+
+/**
+ * Vertex Shader
+ *
+ * author: James Seibel
+ * version: 11-26-2021
+ */
+void main()
+{
+ // TODO: add a simple white texture to support Optifine shaders
+ //textureCoord = textureCoord;
+
+ vertexColor = color;
+ vertexWorldPos = vec4(vPosition, 1);
+
+ // the vPosition needs to be converted to a vec4 so it can be multiplied
+ // by the 4x4 matrices
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1);
+}