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
+ {
+ // clean up any potentially open resources
+ if (buildableBuffers != null)
+ closeBuffers(fullRegen, lodDim);
+
+ try
+ {
+ // 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)
+ {
+ 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_DYNAMIC_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_DYNAMIC_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);
+ 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()
+ {
+ 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;
+
+ 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();
+
+ 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().graphics().advancedGraphics().getGpuUploadMethod();
+ if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
+ {
+ // if buffer storage isn't supported
+ // default to SUB_DATA
+ CONFIG.client().graphics().advancedGraphics().setGpuUploadMethod(GpuUploadMethod.SUB_DATA);
+ uploadMethod = GpuUploadMethod.SUB_DATA;
+ }
+
+ // 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();
+ int storageBufferId = 0;
+ if (buildableStorageBufferIds != null)
+ storageBufferId = buildableStorageBufferIds[x][z][i];
+
+ vboUpload(buildableVbos[x][z][i], storageBufferId, uploadBuffer, true, uploadMethod);
+ lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
+ }
+ }
+ }
+ }
+ }
+ 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();
+
+ // 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(LodVertexBuffer vbo, int storageBufferId, ByteBuffer uploadBuffer,
+ boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
+ {
+ // 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(vbo, storageBufferId, 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());
+
+ // alternative way that doesn't require GL45
+// GL15.glBindBuffer(GL45.GL_COPY_WRITE_BUFFER, storageBufferId);
+// GL45.glCopyBufferSubData(GL15.GL_ARRAY_BUFFER, GL45.GL_COPY_WRITE_BUFFER, 0, 0, uploadBuffer.capacity());
+// GL15.glBindBuffer(GL45.GL_COPY_WRITE_BUFFER, 0);
+ }
+ }
+ 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);
+ 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_DYNAMIC_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, GL15.GL_DYNAMIC_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_DYNAMIC_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())
+ {
+ LodVertexBuffer[][][] tmpVbo = drawableVbos;
+ drawableVbos = buildableVbos;
+ buildableVbos = tmpVbo;
+
+ int[][][] tmpStorage = drawableStorageBufferIds;
+ drawableStorageBufferIds = buildableStorageBufferIds;
+ buildableStorageBufferIds = tmpStorage;
+
+ drawableCenterChunkPos = buildableCenterChunkPos;
+
+ // the vbos have been swapped
+ switchVbos = false;
+ 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/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java b/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/AbstractLodTemplate.java
new file mode 100644
index 000000000..41239212e
--- /dev/null
+++ b/common/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.Box;
+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, Box box, 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/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java b/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/CubicLodTemplate.java
new file mode 100644
index 000000000..29e3fda66
--- /dev/null
+++ b/common/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.Box;
+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, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
+ {
+ if (box == 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(
+ box,
+ 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, box);
+ }
+
+ private void generateBoundingBox(Box box,
+ 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();
+ box.reset();
+ box.setColor(color, adjShadeDisabled);
+ box.setLights(skyLight, blockLight);
+ box.setWidth(width, height - depth, width);
+ box.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
+ box.setUpCulling(32, bufferCenterBlockPos);
+ box.setAdjData(adjData);
+ }
+
+ private void addBoundingBoxToBuffer(LodBufferBuilder buffer, Box box)
+ {
+ int color;
+ int skyLight;
+ int blockLight;
+ for (LodDirection lodDirection : Box.DIRECTIONS)
+ {
+ if(box.isCulled(lodDirection))
+ continue;
+
+ int verticalFaceIndex = 0;
+ while (box.shouldRenderFace(lodDirection, verticalFaceIndex))
+ {
+ for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++)
+ {
+ color = box.getColor(lodDirection);
+ skyLight = box.getSkyLight(lodDirection, verticalFaceIndex);
+ blockLight = box.getBlockLight();
+ color = ColorUtil.applyLightValue(color, skyLight, blockLight);
+ addPosAndColor(buffer,
+ box.getX(lodDirection, vertexIndex),
+ box.getY(lodDirection, vertexIndex, verticalFaceIndex),
+ box.getZ(lodDirection, vertexIndex),
+ color);
+ }
+ verticalFaceIndex++;
+ }
+ }
+ }
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java b/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/DynamicLodTemplate.java
new file mode 100644
index 000000000..e53fd85d2
--- /dev/null
+++ b/common/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.Box;
+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, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
+ {
+ ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
+ }
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java b/common/src/main/java/com/seibel/lod/core/builders/bufferBuilding/lodTemplates/TriangularLodTemplate.java
new file mode 100644
index 000000000..ffe8c2679
--- /dev/null
+++ b/common/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.Box;
+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, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
+ {
+ ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
+ }
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java b/common/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java
new file mode 100644
index 000000000..4a7d9efd5
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java
@@ -0,0 +1,534 @@
+/*
+ * 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
+ */
+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(() ->
+ {
+ 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.worldHeight / 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.worldHeight / 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.worldHeight + 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, depth, 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 && !world.isEmpty())
+ {
+ // 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.getWrappedServerWorld();
+ if (world.isEmpty())
+ return 0;
+ // 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;
+ }
+ }
+ if (hasSkyLight)
+ 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/common/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java b/common/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilderConfig.java
new file mode 100644
index 000000000..e88a5995b
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java b/common/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodGenWorker.java
new file mode 100644
index 000000000..82802e062
--- /dev/null
+++ b/common/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 then 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 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/common/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java b/common/src/main/java/com/seibel/lod/core/builders/worldGeneration/LodWorldGenerator.java
new file mode 100644
index 000000000..e97498c7c
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/LodDirection.java b/common/src/main/java/com/seibel/lod/core/enums/LodDirection.java
new file mode 100644
index 000000000..d6800cffa
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/LodDirection.java
@@ -0,0 +1,532 @@
+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;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * 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));
+
+
+
+ private 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;
+ }
+
+ @Nullable
+ public static LodDirection byName(@Nullable 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 static 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;
+
+ private Axis(String name)
+ {
+ this.name = name;
+ }
+
+ @Nullable
+ 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(@Nullable 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 static enum AxisDirection
+ {
+ POSITIVE(1, "Towards positive"),
+ NEGATIVE(-1, "Towards negative");
+
+ private final int step;
+ private final String name;
+
+ private 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/common/src/main/java/com/seibel/lod/core/enums/WorldType.java b/common/src/main/java/com/seibel/lod/core/enums/WorldType.java
new file mode 100644
index 000000000..a36b06194
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java b/common/src/main/java/com/seibel/lod/core/enums/config/BlocksToAvoid.java
new file mode 100644
index 000000000..bdfcaf363
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java b/common/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.java
new file mode 100644
index 000000000..de7ea7c41
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/config/BufferRebuildTimes.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.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),
+
+ NORMAL(2000, 1000, 5000),
+
+ RARE(5000, 2000, 10000);
+
+ public final int playerMoveTimeout;
+ public final int renderedChunkTimeout;
+ public final int chunkChangeTimeout;
+
+ BufferRebuildTimes(int playerMoveTimeout, int renderedChunkTimeout, int chunkChangeTimeout)
+ {
+ this.playerMoveTimeout = playerMoveTimeout;
+ this.renderedChunkTimeout = renderedChunkTimeout;
+ this.chunkChangeTimeout = chunkChangeTimeout;
+ }
+}
diff --git a/common/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java b/common/src/main/java/com/seibel/lod/core/enums/config/DistanceGenerationMode.java
new file mode 100644
index 000000000..712de3cc3
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java b/common/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.java
new file mode 100644
index 000000000..5fd22cd7a
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/config/GenerationPriority.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.enums.config;
+
+/**
+ * Near_First
+ * Far_First
+ *
+ * Determines which LODs should have priority when generating
+ * outside the normal view distance.
+ *
+ * @author Leonardo Amato
+ * @version 9-25-2021
+ */
+public enum GenerationPriority
+{
+ NEAR_FIRST,
+
+ FAR_FIRST
+}
diff --git a/common/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java b/common/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.java
new file mode 100644
index 000000000..44e21c1fa
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/config/GpuUploadMethod.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;
+
+/**
+ * Buffer_Storage, Sub_Data, Buffer_Mapping
+ *
+ * @author James Seibel
+ * @version 11-21-2021
+ */
+public enum GpuUploadMethod
+{
+ /** Default if OpenGL 4.5 is supported. Fast rendering, no stuttering. */
+ BUFFER_STORAGE,
+
+ /** Default if OpenGL 4.5 is NOT supported. Fast rendering but may stutter when uploading. */
+ SUB_DATA,
+
+ /** Fast rendering but will stutter when uploading. */
+ DATA,
+
+ /** May end up storing buffers in System memory. Slower rendering but won't stutter when uploading. */
+ BUFFER_MAPPING,
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java b/common/src/main/java/com/seibel/lod/core/enums/config/HorizontalQuality.java
new file mode 100644
index 000000000..e91ed0f68
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java b/common/src/main/java/com/seibel/lod/core/enums/config/HorizontalResolution.java
new file mode 100644
index 000000000..794b67f71
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java b/common/src/main/java/com/seibel/lod/core/enums/config/HorizontalScale.java
new file mode 100644
index 000000000..e710de006
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java b/common/src/main/java/com/seibel/lod/core/enums/config/LodTemplate.java
new file mode 100644
index 000000000..397699c7d
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java b/common/src/main/java/com/seibel/lod/core/enums/config/ShadingMode.java
new file mode 100644
index 000000000..1971d51bb
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java b/common/src/main/java/com/seibel/lod/core/enums/config/VanillaOverdraw.java
new file mode 100644
index 000000000..e28552893
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java b/common/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java
new file mode 100644
index 000000000..9d82f9486
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java b/common/src/main/java/com/seibel/lod/core/enums/rendering/DebugMode.java
new file mode 100644
index 000000000..cdc02fa1f
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java b/common/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.java
new file mode 100644
index 000000000..a52d33f7b
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/rendering/FogDistance.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.enums.rendering;
+
+/**
+ * NEAR, FAR, or NEAR_AND_FAR.
+ *
+ * @author James Seibel
+ * @version 02-14-2021
+ */
+public enum FogDistance
+{
+ /** good for fast or fancy fog qualities. */
+ NEAR,
+
+ /** good for fast or fancy fog qualities. */
+ FAR,
+
+ /** only looks good if the fog quality is set to Fancy. */
+ NEAR_AND_FAR
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawOverride.java b/common/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawOverride.java
new file mode 100644
index 000000000..63363a14e
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/rendering/FogDrawOverride.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.rendering;
+
+/**
+ * USE_OPTIFINE_FOG_SETTING,
+ * NEVER_DRAW_FOG,
+ * ALWAYS_DRAW_FOG_FAST,
+ * ALWAYS_DRAW_FOG_FANCY
+ *
+ * @author James Seibel
+ * @version 7-3-2021
+ */
+public enum FogDrawOverride
+{
+ /**
+ * Use whatever Fog setting optifine is using.
+ * If optifine isn't installed this defaults to ALWAYS_DRAW_FOG.
+ */
+ OPTIFINE_SETTING,
+
+ /** Never draw fog on the LODs */
+ NO_FOG,
+
+ /** Always draw fast fog on the LODs */
+ FAST,
+
+ /** Always draw fancy fog on the LODs */
+ FANCY
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/seibel/lod/core/enums/rendering/FogQuality.java b/common/src/main/java/com/seibel/lod/core/enums/rendering/FogQuality.java
new file mode 100644
index 000000000..97528e85e
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/enums/rendering/FogQuality.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;
+
+/**
+ * fast, fancy, or off
+ *
+ * @author James Seibel
+ * @version 02-14-2021
+ */
+public enum FogQuality
+{
+ FAST,
+ FANCY,
+ OFF
+}
diff --git a/common/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java b/common/src/main/java/com/seibel/lod/core/enums/rendering/GLProxyContext.java
new file mode 100644
index 000000000..b83bcc6c3
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java b/common/src/main/java/com/seibel/lod/core/handlers/ChunkFileLoader.java
new file mode 100644
index 000000000..81cb0068e
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java b/common/src/main/java/com/seibel/lod/core/handlers/IReflectionHandler.java
new file mode 100644
index 000000000..fe0da0f80
--- /dev/null
+++ b/common/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.FogQuality;
+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-20-2021
+ */
+public interface IReflectionHandler
+{
+ /** @returns the type of fog optifine is currently set to render. */
+ public FogQuality getFogQuality();
+
+ /** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
+ public 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.
+ */
+ public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
+}
diff --git a/common/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java b/common/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java
new file mode 100644
index 000000000..87ab42002
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/handlers/LodDimensionFileHandler.java
@@ -0,0 +1,423 @@
+/*
+ * 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.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 = 6;
+
+ /**
+ * 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
+ DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL;
+ while (tempGenMode != generationMode)
+ {
+ 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;
+ }
+ 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 < LOD_SAVE_FILE_VERSION)
+ {
+ // 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;
+ }
+
+
+ // 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));
+ }
+ 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();
+ }
+ }
+ }
+
+
+
+
+
+ //================//
+ // helper methods //
+ //================//
+
+ public byte[] getHashFromFile(byte detailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
+ {
+ int regionX = regionPos.x;
+ int regionZ = regionPos.z;
+ String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
+ try (InputStream is = Files.newInputStream(Paths.get(fileName))) {
+ return org.apache.commons.codec.digest.DigestUtils.md5(is);
+ }
+ catch (IOException ioEx)
+ {
+ ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
+ ioEx.printStackTrace();
+ }
+ return new byte[0];
+ }
+
+
+ /**
+ * 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/common/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java b/common/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java
new file mode 100644
index 000000000..f9caae835
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/handlers/ReflectionHandler.java
@@ -0,0 +1,196 @@
+/*
+ * 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.FogQuality;
+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-20-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 FogQuality getFogQuality()
+ {
+ if (ofFogField == null)
+ {
+ // either optifine isn't installed,
+ // the variable name was changed, or
+ // the setup method wasn't called yet.
+ return FogQuality.FANCY;
+ }
+
+ 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:
+ return FogQuality.FAST;
+ case 2:
+ return FogQuality.FANCY;
+ case 3:
+ return FogQuality.OFF;
+ }
+ }
+
+
+
+ /** 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/common/src/main/java/com/seibel/lod/core/objects/Box.java b/common/src/main/java/com/seibel/lod/core/objects/Box.java
new file mode 100644
index 000000000..a617daefb
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/objects/Box.java
@@ -0,0 +1,627 @@
+/*
+ * 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;
+
+/**
+ * Similar to Minecraft's AxisAlignedBoundingBox.
+ *
+ * @author Leonardo Amato
+ * @version 10-2-2021
+ */
+public class Box
+{
+ 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 Box()
+ {
+ 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 sky light
+ 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/common/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java b/common/src/main/java/com/seibel/lod/core/objects/MinDefaultMax.java
new file mode 100644
index 000000000..e16f0d21b
--- /dev/null
+++ b/common/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 T minValue;
+ public T defaultValue;
+ public T maxValue;
+
+ public MinDefaultMax(T newMinValue, T newDefaultValue, T newMaxValue)
+ {
+ minValue = newMinValue;
+ defaultValue = newDefaultValue;
+ maxValue = newMaxValue;
+ }
+}
diff --git a/common/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java b/common/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java
new file mode 100644
index 000000000..730c9d6cb
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java b/common/src/main/java/com/seibel/lod/core/objects/PosToRenderContainer.java
new file mode 100644
index 000000000..5a42e7212
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java b/common/src/main/java/com/seibel/lod/core/objects/lod/LevelContainer.java
new file mode 100644
index 000000000..b314fbcf7
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/common/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
new file mode 100644
index 000000000..0180b8434
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java
@@ -0,0 +1,908 @@
+/*
+ * 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 + "lod 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);
+ if (region != null)
+ region.getPosToRender(posToRender, playerPosX, playerPosZ, CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.NEAR_FIRST);
+ }
+
+ /**
+ * 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/common/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/common/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java
new file mode 100644
index 000000000..7acd6b8cf
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java b/common/src/main/java/com/seibel/lod/core/objects/lod/LodWorld.java
new file mode 100644
index 000000000..9897d297d
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java b/common/src/main/java/com/seibel/lod/core/objects/lod/RegionPos.java
new file mode 100644
index 000000000..f50754d02
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/common/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
new file mode 100644
index 000000000..7938f5790
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java
@@ -0,0 +1,240 @@
+/*
+ * 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 tempIndex;
+ int index = 0;
+ long newData;
+ detailLevel = inputData[index];
+ index++;
+ maxVerticalData = inputData[index] & 0b01111111;
+ index++;
+ size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
+ int x = size * size * maxVerticalData;
+ this.dataContainer = new long[x];
+ 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;
+ dataContainer[i] = newData;
+ }
+ }
+
+ @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/common/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java b/common/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java
new file mode 100644
index 000000000..9d843ddf4
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/objects/math/Mat4f.java
@@ -0,0 +1,556 @@
+/*
+ * 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()
+ {
+ StringBuilder stringbuilder = new StringBuilder();
+ stringbuilder.append("Matrix4f:\n");
+ stringbuilder.append(this.m00);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m01);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m02);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m03);
+ stringbuilder.append("\n");
+ stringbuilder.append(this.m10);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m11);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m12);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m13);
+ stringbuilder.append("\n");
+ stringbuilder.append(this.m20);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m21);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m22);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m23);
+ stringbuilder.append("\n");
+ stringbuilder.append(this.m30);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m31);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m32);
+ stringbuilder.append(" ");
+ stringbuilder.append(this.m33);
+ stringbuilder.append("\n");
+ return stringbuilder.toString();
+ }
+
+
+ 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;
+ }
+}
diff --git a/common/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java b/common/src/main/java/com/seibel/lod/core/objects/math/Vec3d.java
new file mode 100644
index 000000000..c2ba8aa24
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java b/common/src/main/java/com/seibel/lod/core/objects/math/Vec3f.java
new file mode 100644
index 000000000..fe25d8889
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java b/common/src/main/java/com/seibel/lod/core/objects/math/Vec3i.java
new file mode 100644
index 000000000..a65a404f0
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java b/common/src/main/java/com/seibel/lod/core/objects/opengl/DefaultLodVertexFormats.java
new file mode 100644
index 000000000..116ed0f0c
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java b/common/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java
new file mode 100644
index 000000000..b1c0608cc
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/objects/opengl/LodBufferBuilder.java
@@ -0,0 +1,547 @@
+/*
+ * 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.jetbrains.annotations.Nullable;
+
+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;
+ @Nullable
+ 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);
+ ((Buffer) this.buffer).position(0);
+ bytebuffer.put(this.buffer);
+ ((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;
+ ((Buffer) 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();
+ LodVertexFormatElement LodVertexFormatelement = immutablelist.get(this.elementIndex);
+ this.currentElement = LodVertexFormatelement;
+// 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++);
+ ((Buffer) this.buffer).position(this.totalUploadedBytes);
+ this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize();
+ ((Buffer) 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
+ ((Buffer) 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());
+ ((Buffer) 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/common/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java b/common/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexBuffer.java
new file mode 100644
index 000000000..a933e84ba
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java b/common/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormat.java
new file mode 100644
index 000000000..c0281b6b2
--- /dev/null
+++ b/common/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 ? false : 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/common/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java b/common/src/main/java/com/seibel/lod/core/objects/opengl/LodVertexFormatElement.java
new file mode 100644
index 000000000..65e892a6b
--- /dev/null
+++ b/common/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 static 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;
+
+ private 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/common/src/main/java/com/seibel/lod/core/objects/rending/NearFarFogSettings.java b/common/src/main/java/com/seibel/lod/core/objects/rending/NearFarFogSettings.java
new file mode 100644
index 000000000..d7d542d86
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/objects/rending/NearFarFogSettings.java
@@ -0,0 +1,64 @@
+/*
+ * 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.FogQuality;
+
+/**
+ * This object is just a replacement for an array
+ * to make things easier to understand in the LodRenderer.
+ *
+ * @author James Seibel
+ * @version 7-03-2021
+ */
+public class NearFarFogSettings
+{
+ public final NearOrFarSetting near = new NearOrFarSetting(FogDistance.NEAR);
+ public final NearOrFarSetting far = new NearOrFarSetting(FogDistance.FAR);
+
+ /**
+ * If true that means Minecraft is
+ * rendering fog
+ */
+ public boolean vanillaIsRenderingFog = true;
+
+ public NearFarFogSettings()
+ {
+
+ }
+
+
+
+ /**
+ * This holds all relevant data to rendering fog at either
+ * near or far distances.
+ */
+ public static class NearOrFarSetting
+ {
+ public FogQuality quality = FogQuality.FANCY;
+ public FogDistance distance;
+
+ public NearOrFarSetting(FogDistance newFogDistance)
+ {
+ distance = newFogDistance;
+ }
+ }
+}
diff --git a/common/src/main/java/com/seibel/lod/core/render/GLProxy.java b/common/src/main/java/com/seibel/lod/core/render/GLProxy.java
new file mode 100644
index 000000000..0bf20ac1c
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/render/GLProxy.java
@@ -0,0 +1,369 @@
+/*
+ * 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.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.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.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.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://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one
+ *
+ * @author James Seibel
+ * @version 11-21-2021
+ */
+public class GLProxy
+{
+ private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
+
+ private static 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
+ // TODO re-add buffer storage support
+ 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.");
+ }
+
+
+
+
+
+ //==============//
+ // shader setup //
+ //==============//
+
+ //setGlContext(GLProxyContext.LOD_RENDER);
+ 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/unshaded.vert", false);
+ fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders/unshaded.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/unshaded.vert", true);
+// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/unshaded.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);
+ }
+ }
+
+
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/render/LodRenderer.java b/common/src/main/java/com/seibel/lod/core/render/LodRenderer.java
new file mode 100644
index 000000000..b797d807f
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/render/LodRenderer.java
@@ -0,0 +1,930 @@
+/*
+ * 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.util.HashSet;
+
+import org.lwjgl.opengl.GL15;
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.opengl.GL30;
+import org.lwjgl.opengl.NVFogDistance;
+
+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.rendering.DebugMode;
+import com.seibel.lod.core.enums.rendering.FogDistance;
+import com.seibel.lod.core.enums.rendering.FogDrawOverride;
+import com.seibel.lod.core.enums.rendering.FogQuality;
+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.NearFarFogSettings;
+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-8-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);
+
+
+// /**
+// * this is the light used when rendering the LODs,
+// * it should be something different from what is used by Minecraft
+// */
+// private static final int LOD_GL_LIGHT_NUMBER = GL15.GL_LIGHT2;
+
+ /**
+ * 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;
+ }
+
+
+
+
+ // TODO move the buffer regeneration logic into its own class (probably called in the client proxy 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 proxy 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 = offsetTheModelViewMatrix(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, partialTicks);
+
+
+ // commented out until we can add shaders to handle lighting
+ //setupLighting(lodDim, partialTicks);
+
+
+// // determine the current fog settings, so they can be
+// // reset after drawing the LODs
+// float defaultFogStartDist = GL15.glGetFloat(GL15.GL_FOG_START);
+// float defaultFogEndDist = GL15.glGetFloat(GL15.GL_FOG_END);
+// int defaultFogMode = GL15.glGetInteger(GL15.GL_FOG_MODE);
+// int defaultFogDistance = glProxy.fancyFogAvailable ? GL15.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV) : -1;
+
+ //ShaderInstance mcShader = RenderSystem.getShader();
+
+// NearFarFogSettings fogSettings = determineFogSettings();
+
+
+
+
+
+ //===========//
+ // rendering //
+ //===========//
+
+ profiler.popPush("LOD draw");
+
+ if (vbos != null)
+ {
+ Vec3f cameraDir = MC_RENDER.getLookAtVector();
+
+ // TODO re-enable once rendering is totally working
+ boolean cullingDisabled = true; //LodConfig.client().graphics.advancedGraphicsOption.disableDirectionalCulling.get();
+// boolean renderBufferStorage = config.client().graphics().advancedGraphics().getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE && glProxy.bufferStorageSupported;
+
+ // used to determine what type of fog to render
+// int halfWidth = vbos.length / 2;
+// int quarterWidth = vbos.length / 4;
+
+ // where the center of the built buffers is (needed when culling regions)
+ RegionPos vboCenterRegionPos = new RegionPos(vbosCenter);
+
+
+
+
+ // 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);
+
+
+
+ // upload the required uniforms
+ int mvmUniform = shaderProgram.getUniformLocation("modelViewMatrix");
+ shaderProgram.setUniform(mvmUniform, modelViewMatrix);
+ int projUniform = shaderProgram.getUniformLocation("projectionMatrix");
+ shaderProgram.setUniform(projUniform, projectionMatrix);
+
+
+ // render each of the buffers
+ for (int x = 0; x < vbos.length; x++)
+ {
+ for (int z = 0; z < vbos.length; z++)
+ {
+ RegionPos vboPos = new RegionPos(
+ x + vboCenterRegionPos.x - (lodDim.getWidth() / 2),
+ z + vboCenterRegionPos.z - (lodDim.getWidth() / 2));
+
+ if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(), cameraDir, vboPos.blockPos()))
+ {
+ // TODO add fog to the fragment shader
+// if ((x > halfWidth - quarterWidth && x < halfWidth + quarterWidth)
+// && (z > halfWidth - quarterWidth && z < halfWidth + quarterWidth))
+// setupFog(fogSettings.near.distance, fogSettings.near.quality);
+// else
+// setupFog(fogSettings.far.distance, fogSettings.far.quality);
+
+// if (storageBufferIds != null && renderBufferStorage)
+// for (int i = 0; i < storageBufferIds[x][z].length; i++)
+// drawArrays(storageBufferIds[x][z][i], vbos[x][z][i].vertexCount, posAttrib, colAttrib);
+// else
+ for (int i = 0; i < vbos[x][z].length; i++)
+ drawArrays(vbos[x][z][i].id, vbos[x][z][i].vertexCount, posAttrib, colAttrib);
+ }
+ }
+ }
+
+
+ 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);
+ //RenderSystem.setShader(() -> mcShader);
+
+ // clear the depth buffer so everything drawn 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);
+
+
+ GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
+ GL30.glBindVertexArray(0);
+
+ GL20.glDisableVertexAttribArray(posAttrib);
+ GL20.glDisableVertexAttribArray(colAttrib);
+ }
+
+
+
+
+
+ //=================//
+ // Setup Functions //
+ //=================//
+
+ @SuppressWarnings("unused")
+ private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
+ {
+ if (fogQuality == FogQuality.OFF)
+ {
+ GL15.glDisable(GL15.GL_FOG);
+ return;
+ }
+
+ if (fogDistance == FogDistance.NEAR_AND_FAR)
+ {
+ throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
+ }
+
+ // determine the fog distance mode to use
+ int glFogDistanceMode;
+ if (fogQuality == FogQuality.FANCY)
+ {
+ // fancy fog (fragment distance based fog)
+ glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV;
+ }
+ else
+ {
+ // fast fog (frustum distance based fog)
+ glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
+ }
+
+ // the multipliers are percentages
+ // of the regular view distance.
+ if (fogDistance == FogDistance.FAR)
+ {
+ // the reason that I wrote fogEnd then fogStart backwards
+ // is because we are using fog backwards to how
+ // it is normally used, with it hiding near objects
+ // instead of far objects.
+
+ if (fogQuality == FogQuality.FANCY)
+ {
+ // for more realistic fog when using FAR
+ if (CONFIG.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
+ GL15.glFogf(GL15.GL_FOG_START, farPlaneBlockDistance * 1.6f * 0.9f);
+ else
+ GL15.glFogf(GL15.GL_FOG_START, Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f * 1.6f));
+ GL15.glFogf(GL15.GL_FOG_END, farPlaneBlockDistance * 1.6f);
+ }
+ else if (fogQuality == FogQuality.FAST)
+ {
+ // for the far fog of the normal chunks
+ // to start right where the LODs' end use:
+ // end = 0.8f, start = 1.5f
+ GL15.glFogf(GL15.GL_FOG_START, farPlaneBlockDistance * 0.75f);
+ GL15.glFogf(GL15.GL_FOG_END, farPlaneBlockDistance * 1.0f);
+ }
+ }
+ else if (fogDistance == FogDistance.NEAR)
+ {
+ if (fogQuality == FogQuality.FANCY)
+ {
+ GL15.glFogf(GL15.GL_FOG_END, vanillaBlockRenderedDistance * 1.41f);
+ GL15.glFogf(GL15.GL_FOG_START, vanillaBlockRenderedDistance * 1.6f);
+ }
+ else if (fogQuality == FogQuality.FAST)
+ {
+ GL15.glFogf(GL15.GL_FOG_END, vanillaBlockRenderedDistance * 1.0f);
+ GL15.glFogf(GL15.GL_FOG_START, vanillaBlockRenderedDistance * 1.5f);
+ }
+ }
+
+ GL15.glEnable(GL15.GL_FOG);
+ GL15.glFogi(GL15.GL_FOG_MODE, GL15.GL_LINEAR);
+ }
+
+ /**
+ * Revert any changes that were made to the fog
+ * and sets up the fog for Minecraft.
+ */
+ @SuppressWarnings("unused")
+ private void cleanupFog(NearFarFogSettings fogSettings,
+ float defaultFogStartDist, float defaultFogEndDist,
+ int defaultFogMode, int defaultFogDistance)
+ {
+ GL15.glFogf(GL15.GL_FOG_START, defaultFogStartDist);
+ GL15.glFogf(GL15.GL_FOG_END, defaultFogEndDist);
+ GL15.glFogi(GL15.GL_FOG_MODE, defaultFogMode);
+
+ // disable fog if Minecraft wasn't rendering fog
+ // or we want it disabled
+ if (!fogSettings.vanillaIsRenderingFog
+ || CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
+ {
+ // Make fog render a infinite distance away.
+ // This doesn't technically disable Minecraft's fog
+ // so performance will probably be the same regardless, unlike
+ // Optifine's no fog setting.
+
+ // we can't disable minecraft's fog outright because by default
+ // minecraft will re-enable the fog after our code
+
+ GL15.glFogf(GL15.GL_FOG_START, 0.0F);
+ GL15.glFogf(GL15.GL_FOG_END, Float.MAX_VALUE);
+ GL15.glFogf(GL15.GL_FOG_DENSITY, Float.MAX_VALUE);
+ }
+ }
+
+
+ /**
+ * 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 offsetTheModelViewMatrix(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;
+ }
+
+ /**
+ * 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
+ * @param partialTicks how many ticks into the frame we are
+ */
+ private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance, float partialTicks)
+ {
+ // create the new projection matrix
+
+ Mat4f lodProj = Mat4f.perspective(
+ MC_RENDER.getFov(partialTicks),
+ (float) this.MC_RENDER.getScreenWidth() / (float) this.MC_RENDER.getScreenHeight(),
+ CONFIG.client().graphics().advancedGraphics().getUseExtendedNearClipPlane() ? vanillaBlockRenderedDistance / 5 : 1,
+ farPlaneBlockDistance * LodUtil.CHUNK_WIDTH / 2);
+
+
+ // get Minecraft's un-edited projection matrix
+ // (this is before it is zoomed, distorted, etc.)
+ Mat4f defaultMcProj = MC_RENDER.getDefaultProjectionMatrix(partialTicks);
+ // true here means use "use fov setting" (probably)
+
+ // this logic strips away the defaultMcProj matrix, so we
+ // can get the distortionMatrix, which represents all
+ // transformations, zooming, distortions, etc. done
+ // to Minecraft's Projection matrix
+ Mat4f defaultMcProjInv = defaultMcProj.copy();
+ defaultMcProjInv.invert();
+
+ Mat4f distortionMatrix = defaultMcProjInv.copy();
+ distortionMatrix.multiply(currentProjectionMatrix);
+
+
+ // edit the lod projection to match Minecraft's
+ // (so the LODs line up with the real world)
+ lodProj.multiply(distortionMatrix);
+
+ return lodProj;
+ }
+
+ ///** setup the lighting to be used for the LODs */
+ /*private void setupLighting(LodDimension lodDimension, float partialTicks)
+ {
+ // Determine if the player has night vision
+ boolean playerHasNightVision = false;
+ if (this.mc.getPlayer() != null)
+ {
+ Iterator iterator = this.mc.getPlayer().getActiveEffects().iterator();
+ while (iterator.hasNext())
+ {
+ EffectInstance instance = iterator.next();
+ if (instance.getEffect() == Effects.NIGHT_VISION)
+ {
+ playerHasNightVision = true;
+ break;
+ }
+ }
+ }
+
+ float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.getSkyDarken(partialTicks) : 0.2f;
+ sunBrightness = playerHasNightVision ? 1.0f : sunBrightness;
+ float gamma = (float) mc.getOptions().gamma - 0.0f;
+ float dayEffect = (sunBrightness - 0.2f) * 1.25f;
+ float lightStrength = (gamma * 0.34f - 0.01f) * (1.0f - dayEffect) + dayEffect - 0.20f; //gamma * 0.2980392157f + 0.1647058824f
+ float blueLightStrength = (gamma * 0.44f + 0.12f) * (1.0f - dayEffect) + dayEffect - 0.20f; //gamma * 0.4235294118f + 0.2784313725f
+
+ float[] lightAmbient = {lightStrength, lightStrength, blueLightStrength, 1.0f};
+
+
+ // can be used for debugging
+ // if (partialTicks < 0.005)
+ // ClientProxy.LOGGER.debug(lightStrength);
+
+ ByteBuffer temp = ByteBuffer.allocateDirect(16);
+ temp.order(ByteOrder.nativeOrder());
+ GL15.glLightfv(LOD_GL_LIGHT_NUMBER, GL15.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
+ GL15.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
+
+ RenderSystem.enableLighting();
+ }*/
+
+ /** Create all buffers that will be used. */
+ public void setupBuffers(LodDimension lodDim)
+ {
+ lodBufferBuilderFactory.setupBuffers(lodDim);
+ }
+
+
+ //======================//
+ // 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();
+ }
+
+ /** Return what fog settings should be used when rendering. */
+ @SuppressWarnings("unused")
+ private NearFarFogSettings determineFogSettings()
+ {
+ NearFarFogSettings fogSettings = new NearFarFogSettings();
+
+
+ FogQuality quality = REFLECTION_HANDLER.getFogQuality();
+ FogDrawOverride override = CONFIG.client().graphics().fogQuality().getFogDrawOverride();
+
+
+ fogSettings.vanillaIsRenderingFog = quality != FogQuality.OFF;
+
+
+ // use any fog overrides the user may have set
+ switch (override)
+ {
+ case FANCY:
+ quality = FogQuality.FANCY;
+ break;
+
+ case NO_FOG:
+ quality = FogQuality.OFF;
+ break;
+
+ case FAST:
+ quality = FogQuality.FAST;
+ break;
+
+ case OPTIFINE_SETTING:
+ // don't override anything
+ break;
+ }
+
+
+ // how different distances are drawn depends on the quality set
+ switch (quality)
+ {
+ case FANCY:
+ fogSettings.near.quality = FogQuality.FANCY;
+ fogSettings.far.quality = FogQuality.FANCY;
+
+ switch (CONFIG.client().graphics().fogQuality().getFogDistance())
+ {
+ case NEAR_AND_FAR:
+ fogSettings.near.distance = FogDistance.NEAR;
+ fogSettings.far.distance = FogDistance.FAR;
+ break;
+
+ case NEAR:
+ fogSettings.near.distance = FogDistance.NEAR;
+ fogSettings.far.distance = FogDistance.NEAR;
+ break;
+
+ case FAR:
+ fogSettings.near.distance = FogDistance.FAR;
+ fogSettings.far.distance = FogDistance.FAR;
+ break;
+ }
+ break;
+
+ case FAST:
+ fogSettings.near.quality = FogQuality.FAST;
+ fogSettings.far.quality = FogQuality.FAST;
+
+ // fast fog setting should only have one type of
+ // fog, since the LODs are separated into a near
+ // and far portion; and fast fog is rendered from the
+ // frustrum's perspective instead of the camera
+ switch (CONFIG.client().graphics().fogQuality().getFogDistance())
+ {
+ case NEAR_AND_FAR:
+ case NEAR:
+ fogSettings.near.distance = FogDistance.NEAR;
+ fogSettings.far.distance = FogDistance.NEAR;
+ break;
+
+ case FAR:
+ fogSettings.near.distance = FogDistance.FAR;
+ fogSettings.far.distance = FogDistance.FAR;
+ break;
+ }
+ break;
+
+ case OFF:
+ fogSettings.near.quality = FogQuality.OFF;
+ fogSettings.far.quality = FogQuality.OFF;
+ break;
+ }
+ return fogSettings;
+ }
+
+
+ /** 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
+ || MC.getPlayerChunkPos().getX() != LevelPosUtil.getPosX(previousPos)
+ || MC.getPlayerChunkPos().getZ() != LevelPosUtil.getPosZ(previousPos))
+ {
+ 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/common/src/main/java/com/seibel/lod/core/render/RenderUtil.java b/common/src/main/java/com/seibel/lod/core/render/RenderUtil.java
new file mode 100644
index 000000000..2d1a49aa0
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/render/RenderUtil.java
@@ -0,0 +1,125 @@
+/*
+ * 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);
+ Vec3f vboCenterVec = vboVec;
+
+
+ int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
+
+ // calculate the 4 corners
+ Vec3f vboSeVec = new Vec3f(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
+ Vec3f vboSwVec = new Vec3f(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z + halfRegionWidth);
+ Vec3f vboNwVec = new Vec3f(vboCenterVec.x - halfRegionWidth, vboCenterVec.y, vboCenterVec.z - halfRegionWidth);
+ Vec3f vboNeVec = new Vec3f(vboCenterVec.x + halfRegionWidth, vboCenterVec.y, vboCenterVec.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/common/src/main/java/com/seibel/lod/core/render/shader/LodShader.java b/common/src/main/java/com/seibel/lod/core/render/shader/LodShader.java
new file mode 100644
index 000000000..70605c338
--- /dev/null
+++ b/common/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 it's 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/common/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java b/common/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java
new file mode 100644
index 000000000..36e561eea
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/render/shader/LodShaderProgram.java
@@ -0,0 +1,184 @@
+/*
+ * 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.nio.FloatBuffer;
+
+import org.lwjgl.opengl.GL20;
+import org.lwjgl.system.MemoryStack;
+
+import com.seibel.lod.core.objects.math.Mat4f;
+
+
+/**
+ * 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-8-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 an 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);
+ }
+
+ /**
+ * Sets the uniform variable for specified location.
+ *
+ * @param location Uniform location
+ * @param value Value to set
+ */
+ public void setUniform(int location, int value)
+ {
+ GL20.glUniform1i(location, value);
+ }
+
+ /**
+ * Sets the uniform variable for specified location.
+ *
+ * @param location Uniform location
+ * @param value Value to set
+ */
+ 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);
+ }
+ }
+
+
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/util/ColorUtil.java b/common/src/main/java/com/seibel/lod/core/util/ColorUtil.java
new file mode 100644
index 000000000..1933246d2
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/util/DataPointUtil.java b/common/src/main/java/com/seibel/lod/core/util/DataPointUtil.java
new file mode 100644
index 000000000..3c59c6261
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/util/DataPointUtil.java
@@ -0,0 +1,520 @@
+/*
+ * 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 int worldHeight = 256;
+
+ 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((worldHeight / 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 = worldHeight;
+ 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/common/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java b/common/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java
new file mode 100644
index 000000000..219c236ee
--- /dev/null
+++ b/common/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 = (byte) Math.max(CONFIG.client().graphics().quality().getDrawResolution().detailLevel, 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().distanceUnit;
+ 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().distanceUnit;
+ 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(int detail)
+ {
+ if (detail < minDrawDetail)
+ return minDrawDetail;
+ else
+ return (byte) 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/common/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java b/common/src/main/java/com/seibel/lod/core/util/LevelPosUtil.java
new file mode 100644
index 000000000..2dfe29440
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java b/common/src/main/java/com/seibel/lod/core/util/LodThreadFactory.java
new file mode 100644
index 000000000..c12e8e0ff
--- /dev/null
+++ b/common/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/common/src/main/java/com/seibel/lod/core/util/LodUtil.java b/common/src/main/java/com/seibel/lod/core/util/LodUtil.java
new file mode 100644
index 000000000..84e745b92
--- /dev/null
+++ b/common/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.Box;
+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 : Box.ADJ_DIRECTIONS)
+ {
+ tempX = x + Box.DIRECTION_NORMAL_MAP.get(lodDirection).x;
+ tempZ = z + Box.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/common/src/main/java/com/seibel/lod/core/util/SingletonHandler.java b/common/src/main/java/com/seibel/lod/core/util/SingletonHandler.java
new file mode 100644
index 000000000..cb6791660
--- /dev/null
+++ b/common/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 objectClass
+ * @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/common/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java b/common/src/main/java/com/seibel/lod/core/util/ThreadMapUtil.java
new file mode 100644
index 000000000..03f311b87
--- /dev/null
+++ b/common/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.Box;
+
+/**
+ * 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[Box.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 : Box.ADJ_DIRECTIONS)
+ adjDataMap.get(Thread.currentThread().getName()).put(lodDirection, new long[verticalData]);
+ }
+ else
+ {
+
+ for (LodDirection lodDirection : Box.ADJ_DIRECTIONS)
+ Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(lodDirection), DataPointUtil.EMPTY_DATA);
+ }
+ return adjDataMap.get(Thread.currentThread().getName());
+ }
+
+ public static Box getBox()
+ {
+ if (!boxMap.containsKey(Thread.currentThread().getName())
+ || (boxMap.get(Thread.currentThread().getName()) == null))
+ {
+ boxMap.put(Thread.currentThread().getName(), new Box());
+ }
+ 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.worldHeight / 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/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/IWrapperFactory.java
new file mode 100644
index 000000000..8aadc4783
--- /dev/null
+++ b/common/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
+{
+ public AbstractBlockPosWrapper createBlockPos();
+ public AbstractBlockPosWrapper createBlockPos(int x, int y, int z);
+
+
+ public AbstractChunkPosWrapper createChunkPos();
+ public AbstractChunkPosWrapper createChunkPos(int x, int z);
+ public AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
+ public AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
+
+
+ public AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/AbstractBlockPosWrapper.java
new file mode 100644
index 000000000..880606d4d
--- /dev/null
+++ b/common/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 a 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/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorSingletonWrapper.java
new file mode 100644
index 000000000..cf905a3be
--- /dev/null
+++ b/common/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) */
+ public IBlockColorWrapper getWaterColor();
+}
+
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockColorWrapper.java
new file mode 100644
index 000000000..1ee012905
--- /dev/null
+++ b/common/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//
+ //--------------//
+
+ public boolean hasColor();
+
+ public int getColor();
+
+
+ //------------//
+ //Tint getters//
+ //------------//
+
+ public boolean hasTint();
+
+ public boolean hasGrassTint();
+
+ public boolean hasFolliageTint();
+
+ public boolean hasWaterTint();
+
+}
+
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/block/IBlockShapeWrapper.java
new file mode 100644
index 000000000..a36cd8474
--- /dev/null
+++ b/common/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
+{
+ public boolean ofBlockToAvoid();
+
+ //-----------------//
+ //Avoidance getters//
+ //-----------------//
+
+ public boolean isNonFull();
+
+ public boolean hasNoCollision();
+
+ public boolean isToAvoid();
+}
\ No newline at end of file
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/AbstractChunkPosWrapper.java
new file mode 100644
index 000000000..a19b8c964
--- /dev/null
+++ b/common/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 a 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/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java
new file mode 100644
index 000000000..c258c2cf7
--- /dev/null
+++ b/common/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
+{
+ public int getHeight();
+
+ public boolean isPositionInWater(AbstractBlockPosWrapper blockPos);
+
+ public int getHeightMapValue(int xRel, int zRel);
+
+ public IBiomeWrapper getBiome(int xRel, int yAbs, int zRel);
+
+ public IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos);
+
+ public IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos);
+
+ public AbstractChunkPosWrapper getPos();
+
+ public boolean isLightCorrect();
+
+ public boolean isWaterLogged(AbstractBlockPosWrapper blockPos);
+
+ public int getEmittedBrightness(AbstractBlockPosWrapper blockPos);
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
new file mode 100644
index 000000000..f2610852f
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/config/ILodConfigWrapperSingleton.java
@@ -0,0 +1,426 @@
+/*
+ * 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.FogDistance;
+import com.seibel.lod.core.enums.rendering.FogDrawOverride;
+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 11-21-2021
+ */
+public interface ILodConfigWrapperSingleton
+{
+ public IClient client();
+
+
+ public interface IClient
+ {
+ public IGraphics graphics();
+ public IWorldGenerator worldGenerator();
+ public IAdvanced advanced();
+
+
+ //==================//
+ // Graphics Configs //
+ //==================//
+ public interface IGraphics
+ {
+ public static final String DESC = "These settings control how the mod will look in game";
+
+ public IQuality quality();
+ public IFogQuality fogQuality();
+ public IAdvancedGraphics advancedGraphics();
+
+
+ public interface IQuality
+ {
+ public static final String DESC = "These settings control how detailed the fake chunks will be.";
+
+ HorizontalResolution DRAW_RESOLUTION_DEFAULT = HorizontalResolution.BLOCK;
+ public static final String DRAW_RESOLUTION_DESC = ""
+ + " What is the maximum detail fake chunks should be drawn at? \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. \n";
+ public HorizontalResolution getDrawResolution();
+ public void setDrawResolution(HorizontalResolution newHorizontalResolution);
+
+ MinDefaultMax LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX = new MinDefaultMax(16, 64, 1024);
+ String LOD_CHUNK_RENDER_DISTANCE_DESC = ""
+ + " The mod's render distance, measured in chunks. \n";
+ public int getLodChunkRenderDistance();
+ public 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 use more memory and increase GPU usage. \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";
+ public VerticalQuality getVerticalQuality();
+ public void setVerticalQuality(VerticalQuality newVerticalQuality);
+
+ HorizontalScale HORIZONTAL_SCALE_DEFAULT = HorizontalScale.MEDIUM;
+ String HORIZONTAL_SCALE_DESC = ""
+ + " This indicates how quickly fake chunks drop off in quality. \n"
+ + " " + HorizontalScale.LOW + ": quality drops every " + HorizontalScale.LOW.distanceUnit / 16 + " chunks. \n"
+ + " " + HorizontalScale.MEDIUM + ": quality drops every " + HorizontalScale.MEDIUM.distanceUnit / 16 + " chunks. \n"
+ + " " + HorizontalScale.HIGH + ": quality drops every " + HorizontalScale.HIGH.distanceUnit / 16 + " chunks. \n";
+ public HorizontalScale getHorizontalScale();
+ public void setHorizontalScale(HorizontalScale newHorizontalScale);
+
+ HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM;
+ String HORIZONTAL_QUALITY_DESC = ""
+ + " This indicates the exponential base of the quadratic drop-off \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";
+ public HorizontalQuality getHorizontalQuality();
+ public void setHorizontalQuality(HorizontalQuality newHorizontalQuality);
+ }
+
+ public 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"
+ + " If the fog cuts off abruptly or you are using Optifine's \"fast\" fog option \n"
+ + " set this to " + FogDistance.NEAR + " or " + FogDistance.FAR + ". \n";
+ public FogDistance getFogDistance();
+ public void setFogDistance(FogDistance newFogDistance);
+
+ FogDrawOverride FOG_DRAW_OVERRIDE_DEFAULT = FogDrawOverride.FANCY;
+ String FOG_DRAW_OVERRIDE_DESC = ""
+ + " When should fog be drawn? \n"
+ + " " + FogDrawOverride.OPTIFINE_SETTING + ": Use whatever Fog setting Optifine is using. If Optifine isn't installed this defaults to " + FogDrawOverride.FANCY + ". \n"
+ + " " + FogDrawOverride.NO_FOG + ": Never draw fog on the LODs \n"
+ + " " + FogDrawOverride.FAST + ": Always draw fast fog on the LODs \n"
+ + " " + FogDrawOverride.FANCY + ": Always draw fancy fog on the LODs (if your graphics card supports it) \n";
+ public FogDrawOverride getFogDrawOverride();
+ public void setFogDrawOverride(FogDrawOverride newFogDrawOverride);
+
+ boolean DISABLE_VANILLA_FOG_DEFAULT = false;
+ String DISABLE_VANILLA_FOG_DESC = ""
+ + " If true disable Minecraft's fog. \n\n"
+ + ""
+ + " Experimental! May cause issues with Sodium. \n\n"
+ + ""
+ + " Unlike Optifine or Sodium's fog disabling option this won't change \n"
+ + " performance (we don't actually disable the fog, we just tell it to render a infinite distance away). \n"
+ + " May or may not play nice with other mods that edit fog. \n";
+ public boolean getDisableVanillaFog();
+ public void setDisableVanillaFog(boolean newDisableVanillaFog);
+ }
+
+ public 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";
+ public LodTemplate getLodTemplate();
+ public 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 performance. \n\n"
+ + ""
+ + " If true all LODs are drawn, even those behind \n"
+ + " the player's camera, decreasing performance. \n\n"
+ + ""
+ + " Disable this if you see LODs disappearing. \n"
+ + " (Which may happen if you are using a camera mod) \n";
+ public boolean getDisableDirectionalCulling();
+ public 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 a Out Of Memory crash on render \n"
+ + " distances higher than 128 \n";
+ public boolean getAlwaysDrawAtMaxQuality();
+ public 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 + " may be used depending on the dimension. \n"
+ + " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n";
+ public VanillaOverdraw getVanillaOverdraw();
+ public void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw);
+
+ GpuUploadMethod GPU_UPLOAD_METHOD_DEFAULT = GpuUploadMethod.BUFFER_STORAGE;
+ String GPU_UPLOAD_METHOD_DESC = ""
+ + " What method should be used to upload geometry to the GPU? \n"
+ + " Listed in the suggested order of best to worst. \n\n"
+ + ""
+ + " " + GpuUploadMethod.BUFFER_STORAGE + ": Default if OpenGL 4.5 is supported. Fast rendering, no stuttering. \n"
+ + " " + GpuUploadMethod.SUB_DATA + ": Default if OpenGL 4.5 is NOT supported. Fast rendering but may stutter when uploading. \n"
+ + " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
+ + " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly better than " + GpuUploadMethod.SUB_DATA + " if using a integrated GPU. \n";
+ public GpuUploadMethod getGpuUploadMethod();
+ public void setGpuUploadMethod(GpuUploadMethod newDisableVanillaFog);
+
+ 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";
+ public boolean getUseExtendedNearClipPlane();
+ public void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane);
+ }
+ }
+
+
+
+
+ //========================//
+ // WorldGenerator Configs //
+ //========================//
+ public interface IWorldGenerator
+ {
+ String DESC = "These settings control how fake chunks outside your normal view range are generated.";
+
+ GenerationPriority GENERATION_PRIORITY_DEFAULT = GenerationPriority.FAR_FIRST;
+ String GENERATION_PRIORITY_DESC = ""
+ + " " + GenerationPriority.FAR_FIRST + " \n"
+ + " LODs are generated from low to high detail \n"
+ + " with a small priority for far away regions. \n"
+ + " This fills in the world fastest. \n\n"
+ + ""
+ + " " + GenerationPriority.NEAR_FIRST + " \n"
+ + " LODs are generated around the player \n"
+ + " in a spiral, similar to vanilla minecraft. \n";
+ public GenerationPriority getGenerationPriority();
+ public void setGenerationPriority(GenerationPriority newGenerationPriority);
+
+ DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
+ String DISTANCE_GENERATION_MODE_DESC = ""
+ + " Note: The times listed here are the amount of time it took \n"
+ + " one of the developer's PC to generate 1 chunk, \n"
+ + " and are included so you can compare the \n"
+ + " different generation options. Your mileage may vary. \n\n"
+ + ""
+ + " " + DistanceGenerationMode.NONE + " \n"
+ + " Don't run the distance generator. \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 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";
+ public DistanceGenerationMode getDistanceGenerationMode();
+ public 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";
+ public boolean getAllowUnstableFeatureGeneration();
+ public void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration);
+
+ BlocksToAvoid BLOCKS_TO_AVOID_DEFAULT = BlocksToAvoid.BOTH;
+ String BLOCKS_TO_AVOID_DESC = ""
+ + " " + BlocksToAvoid.NONE + ": Use all blocks when generating fake chunks \n\n"
+ + ""
+ + " " + BlocksToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, grass, etc.) \n\n"
+ + ""
+ + " " + BlocksToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores grass, torches, etc.) \n"
+ + ""
+ + " " + BlocksToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n";
+ public BlocksToAvoid getBlocksToAvoid();
+ public void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
+ }
+
+
+
+
+ //============================//
+ // AdvancedModOptions Configs //
+ //============================//
+ public interface IAdvanced
+ {
+ public static final String DESC = "Advanced mod settings";
+
+ public IThreading threading();
+ public IDebugging debugging();
+ public IBuffers buffers();
+
+
+ public 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 = ""
+ + " This is how many threads are used when generating LODs outside \n"
+ + " the normal render distance. \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"
+ + ""
+ + " The maximum value is the number of logical processors on your CPU. \n"
+ + " Requires a restart to take effect. \n";
+ public int getNumberOfWorldGenerationThreads();
+ public 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 = ""
+ + " This is how many threads are used when building vertex buffers \n"
+ + " (The things sent to your GPU to draw the fake chunks). \n"
+ + " If you experience high CPU usage when NOT generating distant \n"
+ + " fake chunks, lower this number. \n"
+ + " \n"
+ + " The maximum value is the number of logical processors on your CPU. \n"
+ + " Requires a restart to take effect. \n";
+ public int getNumberOfBufferBuilderThreads();
+ public void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads);
+ }
+
+ public 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";
+ public boolean getDrawLods();
+ public void setDrawLods(boolean newDrawLods);
+
+ DebugMode DEBUG_MODE_DEFAULT = DebugMode.OFF;
+ String DEBUG_MODE_DESC = ""
+ + " " + 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";
+ public DebugMode getDebugMode();
+ public 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.";
+ public boolean getDebugKeybindingsEnabled();
+ public void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings);
+ }
+
+ public interface IBuffers
+ {
+ String DESC = "These settings affect how often geometry is rebuilt.";
+
+ String REBUILD_TIMES_DESC = ""
+ + " How frequently should 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;
+ public BufferRebuildTimes getRebuildTimes();
+ public void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes);
+ }
+ }
+ }
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
new file mode 100644
index 000000000..e84e8fa39
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftRenderWrapper.java
@@ -0,0 +1,64 @@
+/*
+ * 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.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-18-2021
+ */
+public interface IMinecraftRenderWrapper
+{
+ public Vec3f getLookAtVector();
+
+ public AbstractBlockPosWrapper getCameraBlockPosition();
+
+ public boolean playerHasBlindnessEffect();
+
+ public Vec3d getCameraExactPosition();
+
+ public Mat4f getDefaultProjectionMatrix(float partialTicks);
+
+ public double getGamma();
+
+ public double getFov(float partialTicks);
+
+ /** Measured in chunks */
+ public int getRenderDistance();
+
+ public int getScreenWidth();
+ public int getScreenHeight();
+
+ /**
+ * This method returns the ChunkPos of all chunks that Minecraft
+ * is going to render this frame.
+ */
+ public HashSet getRenderedChunks();
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IMinecraftWrapper.java
new file mode 100644
index 000000000..6fd49e28d
--- /dev/null
+++ b/common/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.
+ */
+ public void clearFrameObjectCache();
+
+
+
+ //=================//
+ // method wrappers //
+ //=================//
+
+ public float getShade(LodDirection lodDirection);
+
+ public boolean hasSinglePlayerServer();
+
+ public String getCurrentServerName();
+ public String getCurrentServerIp();
+ public String getCurrentServerVersion();
+
+ /** Returns the dimension the player is currently in */
+ public IDimensionTypeWrapper getCurrentDimension();
+
+ public 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
+ */
+ public 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
+ */
+ public Color getColorFromLightMap(int u, int v);
+
+
+
+
+ //=============//
+ // Simple gets //
+ //=============//
+
+ public boolean playerExists();
+
+ public AbstractBlockPosWrapper getPlayerBlockPos();
+
+ public AbstractChunkPosWrapper getPlayerChunkPos();
+
+ /**
+ * Attempts to get the ServerWorld for the dimension
+ * the user is currently in.
+ * @returns null if no ServerWorld is available
+ */
+ public IWorldWrapper getWrappedServerWorld();
+
+ public IWorldWrapper getWrappedClientWorld();
+
+ public File getGameDirectory();
+
+ public IProfilerWrapper getProfiler();
+
+ public float getSkyDarken(float partialTicks);
+
+ boolean connectedToServer();
+
+ /** Returns all worlds available to the server */
+ public ArrayList getAllServerWorlds();
+
+
+
+ public 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
+ */
+ public void crashMinecraft(String errorMessage, Throwable exception);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/minecraft/IProfilerWrapper.java
new file mode 100644
index 000000000..702a2eb4c
--- /dev/null
+++ b/common/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
+{
+ public void push(String newSection);
+
+ public void popPush(String newSection);
+
+ public void pop();
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/misc/ILightMapWrapper.java
new file mode 100644
index 000000000..7ec9d0cf0
--- /dev/null
+++ b/common/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
+{
+ public int getLightValue(int skyLight, int blockLight);
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeColorWrapperSingleton.java
new file mode 100644
index 000000000..ddf8a2ad9
--- /dev/null
+++ b/common/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
+{
+ public IBiomeColorWrapperSingleton getInstance();
+
+ public int getGrassColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
+ public int getWaterColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
+ public int getFoliageColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos);
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IBiomeWrapper.java
new file mode 100644
index 000000000..239518878
--- /dev/null
+++ b/common/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. */
+ public int getColorForBiome(int x, int z);
+
+ public int getGrassTint(int x, int z);
+
+ public int getFolliageTint();
+
+ public int getWaterTint();
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IDimensionTypeWrapper.java
new file mode 100644
index 000000000..bc3b463c0
--- /dev/null
+++ b/common/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
+{
+ public String getDimensionName();
+
+ public boolean hasCeiling();
+
+ public boolean hasSkyLight();
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.java
new file mode 100644
index 000000000..ee7fd28bc
--- /dev/null
+++ b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/world/IWorldWrapper.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.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
+{
+ public IDimensionTypeWrapper getDimensionType();
+
+ public WorldType getWorldType();
+
+ public int getBlockLight(AbstractBlockPosWrapper blockPos);
+
+ public int getSkyLight(AbstractBlockPosWrapper blockPos);
+
+ public IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos);
+
+ public boolean hasCeiling();
+
+ public boolean hasSkyLight();
+
+ public boolean isEmpty();
+
+ public int getHeight();
+
+ public int getSeaLevel();
+
+ /** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
+ public File getSaveFolder() throws UnsupportedOperationException;
+
+
+}
diff --git a/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java b/common/src/main/java/com/seibel/lod/core/wrapperInterfaces/worldGeneration/AbstractWorldGeneratorWrapper.java
new file mode 100644
index 000000000..f462bf0ab
--- /dev/null
+++ b/common/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 a 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/common/src/main/resources/icon.png b/common/src/main/resources/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..6e8dd25f12b8f847508abb2fb7aaf634b1eb9c0b
GIT binary patch
literal 11623
zcma)CQ+OR*(>*7R8lIrB?KF*@6EwDMtFdj{Y;43fDMe?T
zpsxVYtxf?o)qS}3>H1*Zg0{BDh2>z~F-=uxF2j8lEKMkcg3#Al%R}C;N6Z%?Ubk-A
zgd%g05W6oqg#DS!%-yXI*$z{kn1BYenLLpQ40?_KVRMGBq{%&1AGR2K9)qH|kC_JY?&epYkD1DswUV-CrUp{E=KDsG?Ha(#6;MOuz#os2BLtijh{a
z_{t-U>)HF`<5j@eOZxulbr&XsM#4}61=7Y+brn$q*aS+?rXO6>U*Y0y`txa#(3}@U
z|DHzH*M{{Y@st=AHgqw~&GGvX6<=LH;U9u&tuN|REST;%lDQAS0
z0DhqVG@yTJd|<_HjR9`mEF2DkRbAS8Vbp)1eU)Z>yBsL6O5`1>;!6lF(u}jx9(pet
zch*D%5Q!u$2J4F$E9>_yxQw#PN(Mr${CeWm073wK`?39soV#`+t=%s~3@>F$V@g>SfWCY%g;@UwRM%%AGvUY
zzg3!P)E_27K!VW)m>bw@9I|V(@+^;$0f3??E%ORGY3}%p+lTY#m5E@s>)+Bxjt~X2
zbaogr-Vv!31Q`OLg62kk=^hE}Zk~@nLltu`DP$yFb(L@C(p*u1T9z-*n9MIsh57Go>K(e~?8s}eA5@6}k
z#*RcyOInHiaQzlT_R#eAA6>YhKmfiFcW~6-xhTh?I|G0~jrG}Ic9Jrh=*@&Q`-|8b
zw)}p!5PSt+*AC*9KDRZzrosm{kRWbDBR{{t0>dONgKH-R?fC`nce=x0>Q*I{_;Tb}
z_$*@V6o&e+(8RC-w*7}H#zcV7pZ!>Lf#3i~FI+r05gW@CC5ey{7)WAJjU9W#C4r68
z>~CBTMn5XXDOnGuqPrC@%!%(L3EE!Li&(Ws@WGN`4gjLO^e=XI806e8!D
zyGHdoTx?#1D5op7Z0%uQH*Sz$WcMX=OO=LW*b9%3p)p^u?K#KV)O5uIA4{^{^4NJ3
zg0Z0i#=}3`!Y9W5qu@zrjOd*&Vq6o9KhNRZ41d~r?}qy)p2QTy-*fU=xj+aT&iFQl
zWH)=sCICo)Cx7F+!FmN<-@9#WHC%i3TVoM5IG^YZ>#`PoK@y^G1U~qM3MxA4c+H%&
zSI?Yy;xSh1#;?$AU4OQVT+5(*qQwH|V{Q~|E4L3suU0gdRohYY{&K|Z4CryYrM=3q
zlgWN$Gi7NNjf_S!>ShsW+l7?um>C^#UAd6VyK3M@BNsLr9}P1r5}f)Ll0zsChrQE9
zA0WX7J!Ow*t7s;<@WgmniYmK#A_5v>jDcr@zAU_`z1A&zKSGnWtuIl}b%b!S2dS
z#e5a~J-xO^jYBPf4h=!KhS1LOf<-{2aQS7<96mU1CBEXgGK>*k7Q#$3vT(1&sm*HD
zmP#1O1%Xv
z;Oy;{+5y-EUV6fb9Ska2{b@twZ*dhcm)fT+QhF^#deZwgQ2_&`cZJ@3(VE;)`);gr
z9JOa0WG+sTe7+ue_7i^q#Zd#jwfsw=poW(ErMnRH!=!O(fgovKA{!=Aa40aVV_B$z
zM;*w)vr=36<`&aXRB>;nCDzLTF07g!G9JL=jeb~2{#h*HW{AYkZDOr6*{^9tvpPLy
z{)$AC#f%03MWsf`j|9rAuW{2#T$AJ_hBs9PSTh3!IbgRqu$EkJS>cG}U`bj289t&H
zLNNT1E?t`N&~}a%Yxr$%^~yG*&KA+7q#i(2Q&W}L&d&R92n-9MXSoJW1_KdHf!l
zjGo#bgJCK9`zI%@r2xUX{I~fB#ptk4s@L&VRn?XB)c@3waIFpI4GD21m7I2~>_Lzi
zi77*8qtc$9YD@wT;6el{5lyUm~3Gjjj`F3wxc>aei1yuhIZ)N-%U~T{UZ({3#i0
zYOvYXrtP=9VFixu6$vZ04uZS&)7;upIbWc*>>|O|XQ$11a-j~7ZZnqlt-Z~=bqlM1
zek()5q5^v+6V}IPBD`Q_d)ym>r5pVKd9!P6Uu<>vP6I3>_at&v!usouoOKeKOaBy
z+KgG`3ZNr^aX2F1R0M)h0mO+|&wcNbG@@tnX}^g9Y95-;%9wLMi$#($dlcmc(Ya>e
z{RQINtjn#GHrN?Ue-nSKRkw?gg|j@lNVxlB^_{B2
zO_Tejl4^ImBd>z}=I^#lWxt*nV0fk3R{3aE%4t=(9S{`-?3r8lY))#Q(V;`sPQlY~
zjQ*&)C+mldAO&4XKe0Bnc|FP(7>FoHNd3D_J>c{(SE|dNqB^OkSZL#xHyZ&&1#wux
zKU<+`ri+M+kG-7^gz|s;o|%{5f8{G@_EYoaoi*J*c4c;8Q!$@Grn-9Dc%@xd!)Bl5
z*_I`oih9iZRc`%$oD4dD0(krItcO%^(VXLb&3n_3{Bjz_5ATG#Z`KB7wWd;dSC
z1)TKYQYu%~#^XN$6+x&$d1W59<@nC)2vhp+?7CkKzGA6HS8m|_fCt|E-O!==i@fvYDaNKHk?Ip5;l!3Y~lo7=6{n#EfD4H-&laGL%e
z)teWoX3~&0v>-Jc-_b^C&h&}~*GiIHBx^5}Pt5D|@>kGEIjGODn$SdsW~|D4XW_3=
zvZic(20FAiEWo>U5E=}U%};<%0q2T-X{RLu7Ir
zWv4J5fFdlMk>Z%_#MCyZ!&?A@p1Yal@Kt;-fOf(S`K9!R3~68@qu~^tKl>_g;zatE
z)CiucAjo-6@FrfU9F-R;q00AcT%BK|OEQb~i?A6d2&7{Wuoh1?>Qv|hdUc}-k=93i
zPIn@4X|*w5_M%3ZNwW5Qf1*jvdBE%bERDUEtTRk08Y0v#i_ITk&@MjHUqyO{kLvr7
zMV_01dPF9IH7Ioyv}HE!G&e(6N`9nyP%_tfb8BPB7M$*FZ8C`m}hjk
zOh|C2J-YU5L&M@~h@emH+DEzG6%{<1c)=8c2Aal`Mv}nHft-mmyr-OWCsy
zOw~FyO}b+6%Z%A68W{zvlzvy)HnJ^)wY^Crb+fU1&pqUvLKPM=vOSy+fF2fpHfA@S
zAz9EHthsSIGY_0_v-~f
z^H$k~jgTcMhVjT-)Q1GxAsSuhtTXSKB|19T?m^s_+UP!cT16Ae0h}UErCW}!-=~QG
ziEQX|!UdyXL*~KeYVb*b06;N6-zWnt-y}HBhd(oaMwv{t0uhtg0jw3(iQ*z_TIX5B
zS5rulU*rr%EVuSoF|>d3fb}nB%9`#-&4l3}zk>WComlJVvxIU%vA50Sm^ZH9{LpDs
zq>515Ahu$r@FNChLkDX7zlt+KQQj>nwd>?tRj;(*FsRaOO*~?{71NXjsX89HvPB0r
zs)A2wtE*76!@W~^85q^EZOoai!=nxoxu1))Usw~1g7=HH`AZYSbmK}WwB^k3PDC#%
z%_$ec{U7H$QuE<)2K^;qVyL;L&O|eZw?bt$?HRMm;NS72QOJ|T@yzz~*uVPq^5_^{
z9c}leOR|unCio6}s|uIK(%#g&8N(Z#?^g^N5)C-`-s$722IQO3jo}6P-6BXi6{W8-
zwX5vH>ynA0XU0kTh~@L!LOJ%DMQETdNVZPG%KZ<
z64orHv;YDCx;YnDj-Vca=y0?1hKdw*l~{c9Nm2jIiGwJUB*+pcdbss#N|K>rNxZ%1
zFXSKWO%hGvUC+Nrdpn$(yGKb4-aQJB5JAT@{agCJe_ZEyDAEzWb(=bihK1LrCYmz+
zS)E~=MG&V@JN#%(y4hI!vkZW(lw;uC$2U&9QtPF`EF6j9bLgh^_`5OmM8bx6T=#4aY}r
zM;w&;vaCjtsllQW@)8L#)m~rVT4Hhc6?{(jk1fY~=0*Z?<3w(BuX9rcjK8DodZiV&;Q&z$p6B^V(L~@ab`PlF$gn!
zmga-x7zGrLv~jZ|k@z!=j*Yy-RbuVrwLkcoEAyPRg0Y4k3zrCeZrk1m7An5vxgc)8
zY2qJAEej`g!4@Mp>J%d>jC~O0oz)}1q8v>tyeozEU0Z606m7C(#(CXjyOp`CY07**m{y<9Q#Fk3nbd(r|%{r$CHA{yJakaDJSoy0YBq`qf
zazK6B;EeioV)6^vA+$usk@WLE)?d5%riQ}KEO(4Sxv{-YX<@J5)W!SEwAXdCE3!ho
zmIC%+a_C8D7^RR#B$1H-NxqpoKWvyH&YDz|G+o(A&z?>6on%@I-v++Y!B_K|A31#hi+mrby8~;?Mh!gB%oeN9+M6Wk11KG`BL7@o@t^Bw10r9_9J^>06wIq~2)8j|%`ekb04*(8O|kPplva
zFbf<&9|Vcs=njZ1Vy9;nL?;R?*$IFKV?h>BVFlOv=JE+ytT=5mHf1yC%~5wP6p+GR
z626PCPK#kEd7-aZh91rBrUVuDGZd~QrLexvD9IN>p#91cu;1BhbQ~_pFwQ{WG<0`E
zT2J0x_j$3zneR)-^s-{QIDN{DSx7Fj^1^NF@8Wp4(hfO)R|Gde3Fu+1
zG=-oxCC}B}+hpt<-nN4h7=T_eo7(+~Y@8
zMUof}^5M)S8>3kd-^Hy;@!(9GRe{(%J90~Yi4e3cm`gG%)d#Q%tJB>UWoGxS`
zf^5WgOuTp^Hhm{_h45JY-|+)ctv%=Q7~`Q;k4l7Jw@;jL0{T&}1??g;j!`CD4;q;!
z|M^HI$I$2jQJ@SC{orcP{kv9%h
zhXK*WkiokrOlE~DTdR_-6g3Vc?BTk@W>Mk<^xdL$o$Pyxo*bLGw}RI7!!I0pGBGjl
zG8(#ZPGr1HW|(xAp!9aBv|`)Srf)%lC(Y+O=GxyxE<$P6(H&g|4~a^+YwgO}Jr>>u}Nr^k$vM9W3m(mBhvE8E0}Il8_MfoBa3RT_rUS(?-E$_T
z+dIh_94#5qGUOGbD!rW9;Aj)BXKkv>DknxvG;fb>Ex1pKa3t-`PfX`l9BF``SloMe
zyt23*)mr%;bUWfNJ;hjXt4f8!f)mi|9$B|$VkMyqe7`8;jTI!7I%FNPai#9QmwQcE
z^%%d|S>O8+hbo=R+;*?+;xE;ldeiMD04crLthH6tZ6(ObZ)f_Pkue+rPoR@R3&ajE
zu|C@H*+Kjv;v*$W5W_B7+;Q}fn-;vw$vez_jm2jqs~;c?tp~*N6(9$Ab86V&y#3w<
z`1b|CrprO-g|Y&00#VeyQf0u`fgy!K)VTt`3qxz##L9;8e$2KGPmLOdzb{R`5^$YG
z`2Zk+IAG+dre7ww>dVZ~6#ReIQQT<%)|P3w7Me#t|SJYST%VR#3l4l5SuKJw`5s1xOA`5MabS7kR-#Z4Yq;NvVep*$9@cILCqZ#b1rsA4}
z#&1qO#Jtp7PSoAgiiHt+j4JA@Iu`>IK2h)E!L5gJ-*6WdC`*j#c>@eUwR3`lXj~YI
z%N3kfi(A1-p4)&>QGpOx>#RT&|M%pDYh35*v|3IZ@-2qJt`pw%TSVe_t(n>G@{uZf
z_|{|t-FW3@L@-F7nV%dhRete4>-;fVqM#lj5~jWN?&B;ADdEP!VX#DcoLmwfHDw^g
z`p&B4pt*$yL1pY3j#-dmh;Nk%BM!6qcB%_gn4|)3Pel7_B=Rdqm5rf*C2VZ}KfsB1
z26J-hCuzx(wVjova>xAnH_REw3>xGu(x-D4@~GP%Kh2}V@O4*}5?q$kPsaB0M(sxv
z@)MmYKQ*bk{KyjS8?qE&hN=4fJr>0;(o`?ZkquT*m&U?OP(?SxjgIX!R!F69Pr&d1
zMbYt&u4{#tv_?TrJ-&_e{s`k>cGmKtGx$g+~i|i0HH&=RrSH
z-h%la#R8KkP7HF5*UBW5qc*wO{7)X|{OGm=vFcyEv4~?NmOHw#rVv5>Txv9!%nna)
zhq)!N(5V};q;~|;s3Ru)fNc5+4mEn+V^1_Q<~!ha!rOiHlV^dWJV0
zT^y01i;Y38TQOI=OI5Bc>s7;nrp2L5?_w0l)wO9+>N0vb32&!4Chnt{EN8x+9%AM;
zGThZ8Ti|?6Kb?Qi-JXc}Nq&yCOBGG~^PDM(llDaz5>GnHpz3@RpJ+K0F&4s#-Y#r)
zPkBmH=iLMD_Lx^@_w`-BBRjb9wu`H&NN?JSN0lK1-TU9|K9Om1{^e0?gZ?N=Q2sZC
zmR5Ix=>fG(m(`-Dufxcd93#F?ZiC`U%Otf0Wk(~y$tx;U1Ir3-B8KW4^JJ}cb-nc$
z`;u(giloz6b*c_KdO-~g6tt`@vVxCQM@H+O8XufZvK72b&9xj#5y^4hq+`z#?`}4a
z^1~f2qW3HAM5NGD_R{*;CufiXx$`6sDlz`%N_ngPZaae2+AH{{Mu&8W-APn
z1b%Oou3-2R#U>TbBzm=D6lC*sSTM^)A$y=EZfnGAcfzTV=+dE!fFMtfY?@E!u2?(7
z(O|d|ca`?KTH-BqlU19$Td&`JqIsyI)pI~i-4P}CPFZnJnb|VyK9YssBa2ucD)bBU
z8pycEKK>+}@fwerUbxGC=UefLRK^PmB&>+;k)|m1NkH+Kr)08k@9t8M+vvfLo;vlw
zg37O@f>&OB#<7kM27>_GSh!Lgcr{dK1Mhd-VuA|0oqx3!*Ibgid%r7GT6hq_#&>pP
zHbZMKC3d;@PRs<^o*;`r-dNGn1Qusxr1j`cKeOOu4>rxQn`$uekY1Qpgmq;6qbo~T
z`|`odrv!TfKUpk{*CZL!+O^m->yV06$%rp&fWzKQg%1YV8Jk~cw@ha_2L<`5@_13-
zygarV9i}Ppv#(zFQzgyzZD-bC+veDuko)!Bz`Pm}9CA(5C5sJ}>R}%}%oHiw!9sxA
z+?Q-%dD^^9#LTvX`Jnmr`9Hc_z!v9nVpxv2BWyAFpe`6WmY`W
z@Q9dyy(zsp;T$S@RI9O(T8!lK9ej~p-+0|#&X{f6fJhdgnk*i6D1&0<*{
zr_x-C?%!r#5UM8U%$woiVl8yG>S-yg<7W-!|cME>nCsvP&;f
zcs=eW03xcPD?NdhwG(G%TV&Q(e|BrR*Nm&`Z_t?5iiqkNE2~>;?j8!UIbM)Up=P}p
z#oJ>ZV@Q)Aj+761ME@Z#NO58FahRe>sjtq{abcD5BGVa3Xc7MikVYfA%&|imn_c_0
zFltz?_;8_BheV^u=jqK^DhW2ZgNFPG`5y>^S(u|yR)>d`eM+ijKmN4KTI+Vc%NBRM
z)|LO+1J0<8qzV%=g*y5(L9%+_1#}esc$gZ%gu#cq`SRg-7?vi$O15+LqXEj-(>o!b
z5L}w5ocOIfjquGip}FK8_B^s>K>@R7aFagrGttlWFX?s2@N!vL+IK1uB#%I@7#;!6uU#PhMjNHyWu}DjHIh8@{liu!iJo2yey`rM5
z90IV_F4Kb%zWmi1vkwK+P
z=VcA^j&cRWk}f0nSvN1_-8a9pDDD#0=5@Alat=>)s
z>v3n1&viRM^um}yP{mw{`-^9mHupv5D!=PO+Zpyp?-|S_G99N8UJ&1kxC&+v#hB-6
zzh!IDZ0{BD=g
z6yt0{`5+-cS8}_GHTQ3~m;Pr7feccOTe|pDLqFHu2sz%F+#F>AX
z9Q~`R!rz{{Wx8#_Ax4I>NVH8u#(aK}P5x@iv>=17ifj8aPIk;+N;)4vB;cj~-0A
z&W&TdCLwCOfKIDAtZg=NAUU$~*1UBWFy!v4HD)uDObzO;>WQ1u7v4C@g^~
z7GhMk4L#3R-E5zGk!iN7{NLU^9baxIJjhj-%3@>|x7O#OufWN&D2kDmR&nBTbK_>?
ztprM;v}5qjm%ce^LD}by+dB|`Oa=N^k;rLZJ{k|qJ0Y)ZKs>e_E6z%9;el0addN3H455;1<|%tbw99a(`F{W
zi>sI^I^ncB6zr?1XJ}pR_9Nl@o9ad_ze-hEX_aMayt~%zcHr2OZ$};Zr
z@uHXsookWOcEz$)D~+~`KNwbpH>;^rN-aFjf@J1D!x0@$ZbjC-N(s$G|$e%sv?q-{p%f!^oYe{U$;TdzSZv?b3|^{Kj}=lIa4w`1gm*t>CrO9
z)!->XY)71RpY!u0QCY1cMR|G5o6be_g;7BRLSxsT{N4}a1M&f9
zjM=MLoStUg^%_@i{2IV_+*s=YtPbOyipL(q@tC6G1LIc~+w!Tq?*3PDRP63T%s9!U
zw2sGZCPx2RFYCkoyA+vM@26&63X$(=j*NG0oA1+bmp6Kuc=RcFE8SdPqkW_Bj8Gvu
z>X+keA&&!@ynm9sJ`+3DO!e-45jhRaKv`Jc7LTi;FCi(#%PXlY?F=o^xlD7I}r^Hjqg)L
zvn{9lt4&V2Mz7}QQS2aCRGu{}6&M2TsguRUBxautfvJEOmFvu`iG3%)6>${AXnpF-
zGQrFuvrI#9uZnq`g#}s9KT>d9{&3Za-HXhCRjHE7EROU1SArO3gYVxRX_R9ssYMX^
z{iTW~q@rjEUM{V!V=D&g&+*2pD%Am>(V-{sM;jaCKVag6o=G>T<(r>gyX9uZ=%`aE_
z+`{s@3>}GB>NS2-<+Rr2#~ExMM+UX%u9svq9utp^hO#;B+)6~Crm6*ZWA#Z{L!h#xM0dKc1bgS^!_XR<@e%%kcx|R73q0
zbulZ)Wy>dp^!}9BEreoRDh3KNUAkQUO=R*e57z2>Hj0!Z8?&;^S-~|3q;`6HtVD^czce*ISLQcQJjua76S#X@(B|$7n-Wwa5R71nSNjPg7Hqlpe($
z4B>Jk=+t^;FK^hh@2ijHdQI0v#>M6}8F=Ldaq!Br4^T{5DX)B+1;hq(U45~!x;oBl
zPP))592&r+7u?b8ku5DpgD95akHqJ-@!HgM=_?W+sOxLGFRhFfNAE~v59-g!7AMdK
zDQ}z)z%+;gy>mM{P<86hcavHjTsS+z#4Wk>X1D1{uHOb`W|q{l^;rU!Ih@f`P+t3!Y(%r_E}N2>h0zOr
zgC)>gfF=n7nIQOp*AsGodFe3U(lTgNiKbS_O81_iprL}Iw5bS4Ek@(H5a%py)~tPb
zVCK$*jlCBx{+IpU`||ORR+`k+I;FM+T;5Od$<_$sonh%^9^&p?z@gao7cL3-N9yI}
z!SFrWn9QqV6Q7gCL9_*zJI6;T067wQuc(RUbY-w~4G%`nhw?{SJ$#EmVuiMf
zuB+oR`tZx8WcFS;e&=EFE>)sYe5$S4j8LLs?DA%|8rnTt^}vVk*f1Y}aSZ-+Nd<))
zWB3#(yrZMPd&l90J55|1YEz1LwKeq6K%C8?V!}^cdfq5WRZ7YUSR^=fgn)eKoR<9>
zC`zV8L1D~5aIpAR&B^?RR1=x{MPr-~)?W-Pzg*cLQeEMVgOfs=RQ7yZbuUzmz6FDW
z6WG%VKh;9~TRdQY3YX7hq6GL73YnS2WxcS-MyAv9>^RtZBdY@onN>IQf|+H4u!QI7
zO)n29
zPhsFajm^sTsV!|*=`}92yJ`(NU|2i39H0H-hrL{w)!hAtkk$6-U2s7VuwUKAV`5};
zSk3!l(dtbep$~sJdUow7h>4re@1XgtYdBrJ(`Jj2DF-y45n97PLY0+6^<4Gf&g@Xy
z9y`3~EFqLiZEPFriwIt|->4Eq<8j;VgqAM~Hf~!&R|2{P<>gE5=hG8XOrJk&2+=Zb
zxe@wx>0T755?N8=>}ZE%*QbpSqT!Lt%PhDS1}br;oqOrY`1~j{ZyY9v;<~h_CbQVx
zV;Yl|TaZ6s{Buc|s9#RQ6q}ocg~^gf_g3_EBxerY.
+ */
+
+package com.seibel.lod.fabric;
+
+import com.seibel.lod.core.api.EventApi;
+import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkWrapper;
+import com.seibel.lod.fabric.wrappers.world.DimensionTypeWrapper;
+import com.seibel.lod.fabric.wrappers.world.WorldWrapper;
+
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientChunkEvents;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents;
+import net.minecraft.client.KeyMapping;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.world.level.BlockEventData;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.LevelAccessor;
+import net.minecraft.world.level.chunk.LevelChunk;
+import org.lwjgl.glfw.GLFW;
+
+/**
+ * This handles all events sent to the client,
+ * and is the starting point for most of the mod.
+ *
+ * @author coolGi2007
+ * @author Ran
+ * @version 11-23-2021
+ */
+public class ClientProxy
+{
+ private final EventApi eventApi = EventApi.INSTANCE;
+
+
+ /**
+ * Registers Fabric Events
+ * @author Ran
+ */
+ public void registerEvents() {
+ // TODO: Fix this if it's wrong
+
+ /* World Events */
+ ServerTickEvents.START_SERVER_TICK.register(this::serverTickEvent);
+ ServerTickEvents.END_SERVER_TICK.register(this::serverTickEvent);
+
+ /* World Events */
+ ServerChunkEvents.CHUNK_LOAD.register(this::chunkLoadEvent);
+ ClientChunkEvents.CHUNK_LOAD.register(this::chunkLoadEvent);
+
+ /* World Events */
+ ServerWorldEvents.LOAD.register((server, level) -> this.worldLoadEvent(level));
+ ServerWorldEvents.UNLOAD.register((server, level) -> this.worldUnloadEvent());
+ /* The Client World Events are in the mixins
+ Client world load event is in MixinClientLevel
+ Client world unload event is in MixinMinecraft */
+ /* The save events are in MixinServerLevel */
+
+ /* Keyboard Events */
+ ClientTickEvents.END_CLIENT_TICK.register(client -> {
+ if (client.player != null) onKeyInput();
+ });
+ }
+
+
+ public void serverTickEvent(MinecraftServer server)
+ {
+ eventApi.serverTickEvent();
+ }
+
+ public void chunkLoadEvent(LevelAccessor level, LevelChunk chunk)
+ {
+ eventApi.chunkLoadEvent(new ChunkWrapper(chunk), DimensionTypeWrapper.getDimensionTypeWrapper(level.dimensionType()));
+ }
+
+ public void worldSaveEvent()
+ {
+ eventApi.worldSaveEvent();
+ }
+
+ /** This is also called when a new dimension loads */
+ public void worldLoadEvent(Level level)
+ {
+ eventApi.worldLoadEvent(WorldWrapper.getWorldWrapper(level));
+ }
+
+ public void worldUnloadEvent()
+ {
+ eventApi.worldUnloadEvent();
+ }
+
+ /*
+ public void blockChangeEvent(BlockEventData event)
+ {
+ // we only care about certain block events
+ if (event.getClass() == BlockEventData.BreakEvent.class ||
+ event.getClass() == BlockEventData.EntityPlaceEvent.class ||
+ event.getClass() == BlockEventData.EntityMultiPlaceEvent.class ||
+ event.getClass() == BlockEventData.FluidPlaceBlockEvent.class ||
+ event.getClass() == BlockEventData.PortalSpawnEvent.class)
+ {
+ IChunkWrapper chunk = new ChunkWrapper(event.getWorld().getChunk(event.getPos()));
+ DimensionTypeWrapper dimType = DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType());
+
+ // recreate the LOD where the blocks were changed
+ eventApi.blockChangeEvent(chunk, dimType);
+ }
+ }
+ */
+
+
+
+ // The debug mode keybinding, which will be registered
+ public static final KeyMapping DebugToggle = KeyBindingHelper.registerKeyBinding(
+ new KeyMapping("key.lod.DebugToggle", GLFW.GLFW_KEY_F4, "key.lod.category"));
+
+ // The draw toggle keybinding, which will be registered
+ public static final KeyMapping DrawToggle = KeyBindingHelper.registerKeyBinding(
+ new KeyMapping("key.lod.DrawToggle", GLFW.GLFW_KEY_F6, "key.lod.category"));
+
+ boolean PreDebugToggle = false;
+ boolean PreDrawToggle = false;
+ public void onKeyInput() {
+ if (Config.Client.AdvancedModOptions.Debugging.enableDebugKeybindings)
+ {
+ // Only activates when you press the key
+ if (DebugToggle.isDown() && DebugToggle.isDown() != PreDebugToggle)
+ Config.Client.AdvancedModOptions.Debugging.debugMode = Config.Client.AdvancedModOptions.Debugging.debugMode.getNext();
+
+ if (DrawToggle.isDown() && DrawToggle.isDown() != PreDebugToggle)
+ Config.Client.AdvancedModOptions.Debugging.drawLods = !Config.Client.AdvancedModOptions.Debugging.drawLods;
+ }
+ PreDebugToggle = DebugToggle.isDown();
+ PreDrawToggle = DrawToggle.isDown();
+ }
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/Config.java b/fabric/src/main/java/com/seibel/lod/fabric/Config.java
new file mode 100644
index 000000000..d6f9cb7f1
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/Config.java
@@ -0,0 +1,245 @@
+/*
+ * 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.fabric;
+
+import com.seibel.lod.core.ModInfo;
+import com.seibel.lod.core.enums.config.*;
+import com.seibel.lod.core.enums.rendering.*;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.*;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.*;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IWorldGenerator;
+import me.shedaniel.autoconfig.ConfigData;
+import me.shedaniel.autoconfig.annotation.ConfigEntry;
+
+/**
+ * This handles any configuration the user has access to.
+ * @author coolGi2007
+ * @version 11-23-2021
+ */
+@me.shedaniel.autoconfig.annotation.Config(name = ModInfo.ID)
+public class Config implements ConfigData
+{
+ // CONFIG STRUCTURE
+ // -> Client
+ // |
+ // |-> Graphics
+ // | |-> QualityOption
+ // | |-> FogQualityOption
+ // | |-> AdvancedGraphicsOption
+ // |
+ // |-> World Generation
+ // |
+ // |-> Advanced Mod Option
+ // |-> Threads
+ // |-> Buffers
+ // |-> Debugging
+
+ // Since the original config system uses forge stuff, that means we have to rewrite the whole config system
+ // TODO: Stop using autoconfig and use manual config for cloth config
+
+ @ConfigEntry.Gui.Excluded
+ //@ConfigEntry.Category("lod.debug")
+ public int ConfigVersion = 1;
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public Client client = new Client();
+
+ public static class Client
+ {
+ @ConfigEntry.Gui.CollapsibleObject
+ public Graphics graphics = new Graphics();
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public WorldGenerator worldGenerator = new WorldGenerator();
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public AdvancedModOptions advancedModOptions = new AdvancedModOptions();
+
+
+ public static class Graphics
+ {
+ @ConfigEntry.Gui.CollapsibleObject
+ public QualityOption qualityOption = new QualityOption();
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public FogQualityOption fogQualityOption = new FogQualityOption();
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public AdvancedGraphicsOption advancedGraphicsOption = new AdvancedGraphicsOption();
+
+
+ public static class QualityOption
+ {
+ @ConfigEntry.Category("lod.Graphics.QualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static HorizontalResolution drawResolution = IQuality.DRAW_RESOLUTION_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.QualityOption")
+ @ConfigEntry.Gui.Tooltip
+ @ConfigEntry.BoundedDiscrete(min = 16, max = 1024)
+ public static int lodChunkRenderDistance = IQuality.LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX.defaultValue;
+
+ @ConfigEntry.Category("lod.Graphics.QualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static VerticalQuality verticalQuality = IQuality.VERTICAL_QUALITY_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.QualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static HorizontalScale horizontalScale = IQuality.HORIZONTAL_SCALE_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.QualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static HorizontalQuality horizontalQuality = IQuality.HORIZONTAL_QUALITY_DEFAULT;
+ }
+
+ public static class FogQualityOption
+ {
+ @ConfigEntry.Category("lod.Graphics.FogQualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static FogDistance fogDistance = IFogQuality.FOG_DISTANCE_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.FogQualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static FogDrawOverride fogDrawOverride = IFogQuality.FOG_DRAW_OVERRIDE_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.FogQualityOption")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean disableVanillaFog = IFogQuality.DISABLE_VANILLA_FOG_DEFAULT;
+ }
+
+ public static class AdvancedGraphicsOption
+ {
+ @ConfigEntry.Category("lod.Graphics.AdvancedGraphicsOption")
+ @ConfigEntry.Gui.Tooltip
+ public static LodTemplate lodTemplate = IAdvancedGraphics.LOD_TEMPLATE_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.AdvancedGraphicsOption")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean disableDirectionalCulling = IAdvancedGraphics.DISABLE_DIRECTIONAL_CULLING_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.AdvancedGraphicsOption")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean alwaysDrawAtMaxQuality = IAdvancedGraphics.ALWAYS_DRAW_AT_MAD_QUALITY_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.AdvancedGraphicsOption")
+ @ConfigEntry.Gui.Tooltip
+ public static VanillaOverdraw vanillaOverdraw = IAdvancedGraphics.VANILLA_OVERDRAW_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.AdvancedGraphicsOption")
+ @ConfigEntry.Gui.Tooltip
+ public static GpuUploadMethod gpuUploadMethod = IAdvancedGraphics.GPU_UPLOAD_METHOD_DEFAULT;
+
+ @ConfigEntry.Category("lod.Graphics.AdvancedGraphicsOption")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean useExtendedNearClipPlane = IAdvancedGraphics.USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT;
+ }
+ }
+
+
+ //========================//
+ // WorldGenerator Configs //
+ //========================//
+ public static class WorldGenerator
+ {
+ @ConfigEntry.Category("lod.WorldGenerator")
+ @ConfigEntry.Gui.Tooltip
+ public static GenerationPriority generationPriority = IWorldGenerator.GENERATION_PRIORITY_DEFAULT;
+
+ @ConfigEntry.Category("lod.WorldGenerator")
+ @ConfigEntry.Gui.Tooltip
+ public static DistanceGenerationMode distanceGenerationMode = IWorldGenerator.DISTANCE_GENERATION_MODE_DEFAULT;
+
+ @ConfigEntry.Category("lod.WorldGenerator")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean allowUnstableFeatureGeneration = IWorldGenerator.ALLOW_UNSTABLE_FEATURE_GENERATION_DEFAULT;
+
+ @ConfigEntry.Category("lod.WorldGenerator")
+ @ConfigEntry.Gui.Tooltip
+ public static BlocksToAvoid blocksToAvoid = IWorldGenerator.BLOCKS_TO_AVOID_DEFAULT;
+
+ /*
+ @ConfigEntry.Category("lod.WorldGenerator")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean useExperimentalPreGenLoading = false;
+ */
+ }
+
+
+ //============================//
+ // AdvancedModOptions Configs //
+ //============================//
+ public static class AdvancedModOptions
+ {
+ @ConfigEntry.Gui.CollapsibleObject
+ public Threading threading = new Threading();
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public Debugging debugging = new Debugging();
+
+ @ConfigEntry.Gui.CollapsibleObject
+ public Buffers buffers = new Buffers();
+
+
+ public static class Threading
+ {
+ @ConfigEntry.Category("lod.AdvancedModOptions.Threading")
+ @ConfigEntry.Gui.Tooltip
+ // Find a way to set the max to a variable
+ @ConfigEntry.BoundedDiscrete(min = 1, max = 50)
+ public static int numberOfWorldGenerationThreads = IThreading.NUMBER_OF_WORLD_GENERATION_THREADS_DEFAULT.defaultValue;
+
+ @ConfigEntry.Category("lod.AdvancedModOptions.Threading")
+ @ConfigEntry.Gui.Tooltip
+ // Find a way to set the max to a variable
+ @ConfigEntry.BoundedDiscrete(min = 1, max = 50)
+ public static int numberOfBufferBuilderThreads = IThreading.NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX.defaultValue;
+ }
+
+
+
+
+ //===============//
+ // Debug Options //
+ //===============//
+ public static class Debugging
+ {
+ @ConfigEntry.Category("lod.AdvancedModOptions.Debugging")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean drawLods = IDebugging.DRAW_LODS_DEFAULT;
+
+ @ConfigEntry.Category("lod.AdvancedModOptions.Debugging")
+ @ConfigEntry.Gui.Tooltip
+ public static DebugMode debugMode = IDebugging.DEBUG_MODE_DEFAULT;
+
+ @ConfigEntry.Category("lod.AdvancedModOptions.Debugging")
+ @ConfigEntry.Gui.Tooltip
+ public static boolean enableDebugKeybindings = IDebugging.DEBUG_KEYBINDINGS_ENABLED_DEFAULT;
+ }
+
+
+ public static class Buffers
+ {
+ @ConfigEntry.Category("lod.AdvancedModOptions.Buffers")
+ @ConfigEntry.Gui.Tooltip
+ public static BufferRebuildTimes rebuildTimes = IBuffers.REBUILD_TIMES_DEFAULT;
+ }
+ }
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/Main.java b/fabric/src/main/java/com/seibel/lod/fabric/Main.java
new file mode 100644
index 000000000..768a03638
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/Main.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.fabric;
+
+import com.seibel.lod.core.ModInfo;
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.fabric.wrappers.DependencySetup;
+
+import me.shedaniel.autoconfig.AutoConfig;
+import me.shedaniel.autoconfig.serializer.Toml4jConfigSerializer;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
+import net.minecraftforge.common.WorldWorkerManager;
+
+/**
+ * Initialize and setup the Mod.
+ * If you are looking for the real start of the mod
+ * check out the ClientProxy.
+ *
+ * @author coolGi2007
+ * @author Ran
+ * @version 11-21-2021
+ */
+public class Main implements ClientModInitializer
+{
+ // This is a client mod so it should implement ClientModInitializer and in fabric.mod.json it should have "environment": "client"
+ // Once it works on servers change the implement to ModInitializer and in fabric.mod.json it should be "environment": "*"
+
+ public static Main instance;
+
+ public static ClientProxy client_proxy;
+
+ public static final Config CONFIG = AutoConfig.register(Config.class, Toml4jConfigSerializer::new).getConfig();
+
+
+ // Do if implements ClientModInitializer
+ @Override
+ public void onInitializeClient() {
+
+ }
+
+ public static void init() {
+ if (instance != null) return;
+ instance = new Main();
+
+ DependencySetup.createInitialBindings();
+ ClientApi.LOGGER.info(ModInfo.READABLE_NAME + ", Version: " + ModInfo.VERSION);
+ initializeForge();
+
+ // Check if this works
+ client_proxy = new ClientProxy();
+ client_proxy.registerEvents();
+ }
+
+ /**
+ * This method makes forge classes work instead of just sitting there
+ * @author Ran
+ */
+ public static void initializeForge() {
+ ServerTickEvents.START_SERVER_TICK.register((server) -> WorldWorkerManager.tick(true));
+ ServerTickEvents.END_SERVER_TICK.register((server) -> WorldWorkerManager.tick(false));
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java b/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java
new file mode 100644
index 000000000..648b78cc5
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinMinecraft.java
@@ -0,0 +1,17 @@
+package com.seibel.lod.fabric.mixins;
+
+import com.seibel.lod.fabric.Main;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.main.GameConfig;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(value = Minecraft.class)
+public class MixinMinecraft {
+ @Inject(method = "", at = @At("TAIL"))
+ private void startMod(GameConfig gameConfig, CallbackInfo ci) {
+ Main.init();
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinWorldRenderer.java b/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinWorldRenderer.java
new file mode 100644
index 000000000..7c01d9579
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/mixins/MixinWorldRenderer.java
@@ -0,0 +1,83 @@
+/*
+ * 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.fabric.mixins;
+
+import com.mojang.blaze3d.vertex.PoseStack;
+import com.mojang.math.Matrix4f;
+import net.minecraft.client.renderer.LevelRenderer;
+import org.lwjgl.opengl.GL15;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.objects.math.Mat4f;
+import com.seibel.lod.fabric.wrappers.McObjectConverter;
+
+import net.minecraft.client.renderer.RenderType;
+
+/**
+ * This class is used to mix in my rendering code
+ * before Minecraft starts rendering blocks.
+ * If this wasn't done, and we used Forge's
+ * render last event, the LODs would render on top
+ * of the normal terrain.
+ *
+ * @author coolGi2007
+ * @author James Seibel
+ * @version 11-21-2021
+ */
+@Mixin(LevelRenderer.class)
+public class MixinWorldRenderer
+{
+ private static float previousPartialTicks = 0;
+
+ @Inject(at = @At("RETURN"), method = "renderClouds")
+ private void renderClouds(PoseStack poseStack, Matrix4f matrix4f, float f, double d, double e, double g, CallbackInfo ci)
+// private void renderClouds(PoseStack matrixStackIn, float partialTicks, CallbackInfo callback)
+ {
+ // get the partial ticks since renderBlockLayer doesn't
+ // have access to them
+ previousPartialTicks = f;
+ }
+
+ @Inject(at = @At("HEAD"), method = "renderChunkLayer")
+ private void renderChunkLayer(RenderType renderType, PoseStack matrixStackIn, double d, double e, double f, Matrix4f matrix4f, CallbackInfo ci)
+ {
+ // only render if LODs are enabled and
+ // only render before solid blocks
+ if (renderType.equals(RenderType.solid()))
+ {
+ // get MC's current projection matrix
+ float[] mcProjMatrixRaw = new float[16];
+ GL15.glGetFloatv(GL15.GL_PROJECTION_MATRIX, mcProjMatrixRaw);
+ Mat4f mcProjectionMatrix = new Mat4f(mcProjMatrixRaw);
+ // OpenGl outputs their matrices in col,row form instead of row,col
+ // (or maybe vice versa I have no idea :P)
+ mcProjectionMatrix.transpose();
+
+
+ Mat4f mcModelViewMatrix = McObjectConverter.Convert(matrixStackIn.last().pose());
+
+ ClientApi.INSTANCE.renderLods(mcModelViewMatrix, mcProjectionMatrix, previousPartialTicks);
+ }
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java b/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
new file mode 100644
index 000000000..24856cf04
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinClientLevel.java
@@ -0,0 +1,28 @@
+package com.seibel.lod.fabric.mixins.events;
+
+import com.seibel.lod.fabric.Main;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+import net.minecraft.client.renderer.LevelRenderer;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.util.profiling.ProfilerFiller;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.dimension.DimensionType;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import java.util.function.Supplier;
+
+/**
+ * This class is used for world loading events
+ * @author Ran
+ */
+@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) {
+ Main.client_proxy.worldLoadEvent((ClientLevel) (Object) this);
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java b/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java
new file mode 100644
index 000000000..b882be721
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinMinecraft.java
@@ -0,0 +1,31 @@
+package com.seibel.lod.fabric.mixins.events;
+
+import com.seibel.lod.fabric.Main;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.multiplayer.ClientLevel;
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * This class is used for world unloading events
+ * @author Ran
+ */
+@Mixin(Minecraft.class)
+public class MixinMinecraft {
+ @Shadow @Nullable public ClientLevel level;
+
+ @Inject(method = "setLevel", at = @At("HEAD"))
+ private void unloadWorldEvent_sL(ClientLevel clientLevel, CallbackInfo ci) {
+ if (level != null) Main.client_proxy.worldUnloadEvent();
+ }
+
+ @Inject(method = "clearLevel(Lnet/minecraft/client/gui/screens/Screen;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;updateScreenAndTick(Lnet/minecraft/client/gui/screens/Screen;)V", shift = At.Shift.AFTER))
+ private void unloadWorldEvent_cL(Screen screen, CallbackInfo ci) {
+ if (this.level != null) Main.client_proxy.worldUnloadEvent();
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java b/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java
new file mode 100644
index 000000000..21929f60e
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/mixins/events/MixinServerLevel.java
@@ -0,0 +1,26 @@
+package com.seibel.lod.fabric.mixins.events;
+
+import com.seibel.lod.fabric.Main;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.util.ProgressListener;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+/**
+ * This class is used for world saving events
+ * @author Ran
+ */
+@Mixin(ServerLevel.class)
+public class MixinServerLevel {
+ @Inject(method = "save", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;saveAll()V", shift = At.Shift.AFTER))
+ private void saveWorldEvent_sA(ProgressListener progressListener, boolean bl, boolean bl2, CallbackInfo ci) {
+ Main.client_proxy.worldSaveEvent();
+ }
+
+ @Inject(method = "save", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/entity/PersistentEntitySectionManager;autoSave()V", shift = At.Shift.AFTER))
+ private void saveWorldEvent_aS(ProgressListener progressListener, boolean bl, boolean bl2, CallbackInfo ci) {
+ Main.client_proxy.worldSaveEvent();
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java
new file mode 100644
index 000000000..acc2e70f4
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/DependencySetup.java
@@ -0,0 +1,37 @@
+package com.seibel.lod.fabric.wrappers;
+
+import com.seibel.lod.core.handlers.IReflectionHandler;
+import com.seibel.lod.core.handlers.ReflectionHandler;
+import com.seibel.lod.core.util.SingletonHandler;
+import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
+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.fabric.wrappers.block.BlockColorSingletonWrapper;
+import com.seibel.lod.fabric.wrappers.config.LodConfigWrapperSingleton;
+import com.seibel.lod.fabric.wrappers.minecraft.MinecraftRenderWrapper;
+import com.seibel.lod.fabric.wrappers.minecraft.MinecraftWrapper;
+
+/**
+ * Binds all necessary dependencies so we
+ * can access them in Core.
+ * This needs to be called before any Core classes
+ * are loaded.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class DependencySetup
+{
+ public static void createInitialBindings()
+ {
+ SingletonHandler.bind(ILodConfigWrapperSingleton.class, LodConfigWrapperSingleton.INSTANCE);
+ SingletonHandler.bind(IBlockColorSingletonWrapper.class, BlockColorSingletonWrapper.INSTANCE);
+ SingletonHandler.bind(IMinecraftWrapper.class, MinecraftWrapper.INSTANCE);
+ SingletonHandler.bind(IMinecraftRenderWrapper.class, MinecraftRenderWrapper.INSTANCE);
+ SingletonHandler.bind(IWrapperFactory.class, WrapperFactory.INSTANCE);
+
+ SingletonHandler.bind(IReflectionHandler.class, ReflectionHandler.createSingleton(MinecraftWrapper.INSTANCE.getOptions().getClass().getDeclaredFields(), MinecraftWrapper.INSTANCE.getOptions()));
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/McObjectConverter.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/McObjectConverter.java
new file mode 100644
index 000000000..69f313b2d
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/McObjectConverter.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.fabric.wrappers;
+
+import java.nio.FloatBuffer;
+
+import com.mojang.math.Matrix4f;
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.objects.math.Mat4f;
+
+import net.minecraft.core.Direction;
+
+/**
+ * This class converts to and from Minecraft objects (Ex: Matrix4f)
+ * and objects we created (Ex: Mat4f).
+ * Since we don't want to deal with a bunch of tiny changes
+ * every time Minecraft renames a variable in Matrix4f or something.
+ *
+ * @author James Seibel
+ * @version 11-11-2021
+ */
+public class McObjectConverter
+{
+
+ public McObjectConverter()
+ {
+
+ }
+
+
+ /** 4x4 float matrix converter */
+ public static Mat4f Convert(Matrix4f mcMatrix)
+ {
+ FloatBuffer buffer = FloatBuffer.allocate(16);
+ mcMatrix.store(buffer);
+ Mat4f matrix = new Mat4f(buffer);
+ matrix.transpose();
+ return matrix;
+ }
+
+
+ public static Direction Convert(LodDirection lodDirection)
+ {
+ return Direction.byName(lodDirection.name());
+ }
+
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/WrapperFactory.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/WrapperFactory.java
new file mode 100644
index 000000000..94a2c5c0a
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/WrapperFactory.java
@@ -0,0 +1,72 @@
+package com.seibel.lod.fabric.wrappers;
+
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.objects.lod.LodDimension;
+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.world.IWorldWrapper;
+import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkPosWrapper;
+import com.seibel.lod.fabric.wrappers.worldGeneration.WorldGeneratorWrapper;
+
+/**
+ * This handles creating abstract wrapper objects.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class WrapperFactory implements IWrapperFactory
+{
+ public static final WrapperFactory INSTANCE = new WrapperFactory();
+
+
+ @Override
+ public AbstractBlockPosWrapper createBlockPos()
+ {
+ return new BlockPosWrapper();
+ }
+
+ @Override
+ public AbstractBlockPosWrapper createBlockPos(int x, int y, int z)
+ {
+ return new BlockPosWrapper(x,y,z);
+ }
+
+
+
+
+ @Override
+ public AbstractChunkPosWrapper createChunkPos()
+ {
+ return new ChunkPosWrapper();
+ }
+
+ @Override
+ public AbstractChunkPosWrapper createChunkPos(int x, int z)
+ {
+ return new ChunkPosWrapper(x, z);
+ }
+
+ @Override
+ public AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos)
+ {
+ return new ChunkPosWrapper(newChunkPos);
+ }
+
+ @Override
+ public AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos)
+ {
+ return new ChunkPosWrapper(blockPos);
+ }
+
+
+
+ @Override
+ public AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper)
+ {
+ return new WorldGeneratorWrapper(newLodBuilder, newLodDimension, worldWrapper);
+ }
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/WrapperUtil.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/WrapperUtil.java
new file mode 100644
index 000000000..6a28eee8c
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/WrapperUtil.java
@@ -0,0 +1,18 @@
+package com.seibel.lod.fabric.wrappers;
+
+import net.minecraft.world.level.levelgen.Heightmap;
+
+/**
+ * Stores any variables or code that
+ * may be shared between wrapper objects.
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class WrapperUtil
+{
+
+ /** If we ever need to use a heightmap for any reason, use this one. */
+ public static final Heightmap.Types DEFAULT_HEIGHTMAP = Heightmap.Types.WORLD_SURFACE_WG;
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockColorSingletonWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockColorSingletonWrapper.java
new file mode 100644
index 000000000..73599d06f
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockColorSingletonWrapper.java
@@ -0,0 +1,25 @@
+package com.seibel.lod.fabric.wrappers.block;
+
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
+import net.minecraft.world.level.block.Blocks;
+
+
+/**
+ * This class wraps the minecraft Block class
+ *
+ * @author ??
+ * @version 11-17-2021
+ */
+public class BlockColorSingletonWrapper implements IBlockColorSingletonWrapper
+{
+ public static final BlockColorSingletonWrapper INSTANCE = new BlockColorSingletonWrapper();
+
+ /** return base color of water (grey value) */
+ @Override
+ public IBlockColorWrapper getWaterColor()
+ {
+ return BlockColorWrapper.getBlockColorWrapper(Blocks.WATER);
+ }
+}
+
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockColorWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockColorWrapper.java
new file mode 100644
index 000000000..9de7164b6
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockColorWrapper.java
@@ -0,0 +1,304 @@
+package com.seibel.lod.fabric.wrappers.block;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
+import com.seibel.lod.fabric.wrappers.minecraft.MinecraftWrapper;
+import net.minecraft.client.renderer.block.model.BakedQuad;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.state.BlockState;
+
+
+/**
+ * This class wraps the minecraft Block color class
+ *
+ * @author ??
+ * @version 11-17-2021
+ */
+public class BlockColorWrapper implements IBlockColorWrapper
+{
+ //set of block which require tint
+ public static final ConcurrentMap blockColorWrapperMap = new ConcurrentHashMap<>();
+// public static final ModelDataMap dataMap = new ModelDataMap.Builder().build();
+ public static final BlockPos blockPos = new BlockPos(0, 0, 0);
+ public static final Random random = new Random(0);
+ //public static BlockColourWrapper WATER_COLOR = getBlockColorWrapper(Blocks.WATER);
+ public static final Direction[] directions = new Direction[] { Direction.UP, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.DOWN };
+
+ private final Block block;
+ private int color;
+ private boolean isColored;
+ private boolean toTint;
+ private boolean foliageTint;
+ private boolean grassTint;
+ private boolean waterTint;
+
+
+ /**Constructor only require for the block instance we are wrapping**/
+ public BlockColorWrapper(Block block)
+ {
+ this.block = block;
+ this.color = 0;
+ this.isColored = true;
+ this.toTint = false;
+ this.foliageTint = false;
+ this.grassTint = false;
+ this.waterTint = false;
+ setupColorAndTint();
+ /*StringBuilder s = new StringBuilder();
+ s.append(block + "\n"
+ + Integer.toHexString(
+ Minecraft.getInstance().getBlockColors().createDefault().getColor(
+ block.defaultBlockState(),
+ (World) MinecraftWrapper.INSTANCE.getWrappedServerLevel().getLevel(),
+ blockPosWrapper.getBlockPos())) + "\n"
+ );
+ for(Property x : Minecraft.getInstance().getBlockColors().getColoringProperties(block))
+ s.append(x.getName() + " " + x.getPossibleValues() + '\n');
+ System.out.println(s);*/
+ //System.out.println(block + " color " + Integer.toHexString(color) + " to tint " + toTint + " folliageTint " + folliageTint + " grassTint " + grassTint + " waterTint " + waterTint);
+ }
+
+ /**
+ * this return a wrapper of the block in input
+ * @param block object of the block to wrap
+ */
+ public static IBlockColorWrapper getBlockColorWrapper(Block block)
+ {
+ //first we check if the block has already been wrapped
+ if (blockColorWrapperMap.containsKey(block) && blockColorWrapperMap.get(block) != null)
+ return blockColorWrapperMap.get(block);
+
+
+ //if it hasn't been created yet, we create it and save it in the map
+ BlockColorWrapper blockWrapper = new BlockColorWrapper(block);
+ blockColorWrapperMap.put(block, blockWrapper);
+
+ //we return the newly created wrapper
+ return blockWrapper;
+ }
+
+ /**
+ * Generate the color of the given block from its texture
+ * and store it for later use.
+ */
+ private void setupColorAndTint()
+ {
+ BlockState blockState = block.defaultBlockState();
+ BlockPosWrapper blockPosWrapper = new BlockPosWrapper();
+ MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
+ TextureAtlasSprite texture;
+ List quads = null;
+
+ boolean isTinted = false;
+ int listSize = 0;
+
+ // first step is to check if this block has a tinted face
+ for (Direction direction : directions)
+ {
+// quads = mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random, dataMap);
+ quads = mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random);
+ listSize = Math.max(listSize, quads.size());
+ for (BakedQuad bakedQuad : quads)
+ {
+ isTinted |= bakedQuad.isTinted();
+ }
+ }
+
+ //if it contains a tinted face then we store this block in the toTint set
+ if (isTinted)
+ this.toTint = true;
+
+ //now we get the first non empty face
+ for (Direction direction : directions)
+ {
+// quads = mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random, dataMap);
+ quads = mc.getModelManager().getBlockModelShaper().getBlockModel(block.defaultBlockState()).getQuads(blockState, direction, random);
+ if (!quads.isEmpty())
+ break;
+ }
+
+ //the quads list is not empty we extract the first one
+ if (!quads.isEmpty())
+ {
+ isColored = true;
+ texture = quads.get(0).getSprite();
+ }
+ else
+ {
+ isColored = true;
+ texture = mc.getModelManager().getBlockModelShaper().getParticleIcon(block.defaultBlockState());
+ }
+
+ int count = 0;
+ int alpha = 0;
+ int red = 0;
+ int green = 0;
+ int blue = 0;
+ int numberOfGreyPixel = 0;
+ int tempColor;
+ int colorMultiplier;
+
+ // generate the block's color
+// for (int frameIndex = 0; frameIndex < texture.getFrameCount(); frameIndex++)
+ int frameIndex = 0; // TODO
+ {
+ // textures normally use u and v instead of x and y
+ for (int u = 0; u < texture.getWidth(); u++)
+ {
+ for (int v = 0; v < texture.getHeight(); v++)
+ {
+
+ tempColor = TextureAtlasSpriteWrapper.getPixelRGBA(texture, frameIndex, u, v);
+
+ if (ColorUtil.getAlpha(TextureAtlasSpriteWrapper.getPixelRGBA(texture, frameIndex, u, v)) == 0)
+ continue;
+
+ // determine if this pixel is gray
+ int colorMax = Math.max(Math.max(ColorUtil.getBlue(tempColor), ColorUtil.getGreen(tempColor)), ColorUtil.getRed(tempColor));
+ int colorMin = 4 + Math.min(Math.min(ColorUtil.getBlue(tempColor), ColorUtil.getGreen(tempColor)), ColorUtil.getRed(tempColor));
+ boolean isGray = colorMax < colorMin;
+ if (isGray)
+ numberOfGreyPixel++;
+
+
+ // for flowers, weight their non-green color higher
+ if (block instanceof FlowerBlock && (!(ColorUtil.getGreen(tempColor) > (ColorUtil.getBlue(tempColor) + 30)) || !(ColorUtil.getGreen(tempColor) > (ColorUtil.getRed(tempColor) + 30))))
+ colorMultiplier = 5;
+ else
+ colorMultiplier = 1;
+
+
+ // add to the running averages
+ count += colorMultiplier;
+ alpha += ColorUtil.getAlpha(tempColor) * colorMultiplier;
+ red += ColorUtil.getBlue(tempColor) * colorMultiplier;
+ green += ColorUtil.getGreen(tempColor) * colorMultiplier;
+ blue += ColorUtil.getRed(tempColor) * colorMultiplier;
+ }
+ }
+ }
+
+
+ if (count == 0)
+ // this block is entirely transparent
+ tempColor = 0;
+ else
+ {
+ // determine the average color
+ alpha /= count;
+ red /= count;
+ green /= count;
+ blue /= count;
+ tempColor = ColorUtil.rgbToInt(alpha, red, green, blue);
+ }
+
+ // determine if this block should use the biome color tint
+ if ((grassInstance() || leavesInstance() || waterIstance()) && (float) numberOfGreyPixel / count > 0.75f)
+ this.toTint = true;
+
+ // we check which kind of tint we need to apply
+ this.grassTint = grassInstance() && toTint;
+
+ this.foliageTint = leavesInstance() && toTint;
+
+ this.waterTint = waterIstance() && toTint;
+
+ color = tempColor;
+ }
+
+ /** determine if the given block should use the biome's grass color */
+ private boolean grassInstance()
+ {
+ return block instanceof GrassBlock
+ || block instanceof BushBlock
+ || block instanceof GrowingPlantBlock;
+ }
+
+ /** determine if the given block should use the biome's foliage color */
+ private boolean leavesInstance()
+ {
+ return block instanceof LeavesBlock
+ || block == Blocks.VINE
+ || block == Blocks.SUGAR_CANE;
+ }
+
+ /** determine if the given block should use the biome's foliage color */
+ private boolean waterIstance()
+ {
+ return block == Blocks.WATER;
+ }
+
+//--------------//
+//Colors getters//
+//--------------//
+
+ @Override
+ public boolean hasColor()
+ {
+ return isColored;
+ }
+
+ @Override
+ public int getColor()
+ {
+ return color;
+ }
+
+//------------//
+//Tint getters//
+//------------//
+
+
+ @Override
+ public boolean hasTint()
+ {
+ return toTint;
+ }
+
+ @Override
+ public boolean hasGrassTint()
+ {
+ return grassTint;
+ }
+
+ @Override
+ public boolean hasFolliageTint()
+ {
+ return foliageTint;
+ }
+
+ @Override
+ public boolean hasWaterTint()
+ {
+ return waterTint;
+ }
+
+
+
+
+ @Override public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (!(o instanceof BlockColorWrapper))
+ return false;
+ BlockColorWrapper that = (BlockColorWrapper) o;
+ return Objects.equals(block, that.block);
+ }
+
+ @Override public int hashCode()
+ {
+ return Objects.hash(block);
+ }
+
+}
+
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockPosWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockPosWrapper.java
new file mode 100644
index 000000000..df3569473
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockPosWrapper.java
@@ -0,0 +1,73 @@
+package com.seibel.lod.fabric.wrappers.block;
+
+import java.util.Objects;
+
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+
+import net.minecraft.core.BlockPos;
+
+public class BlockPosWrapper extends AbstractBlockPosWrapper
+{
+ private final BlockPos.MutableBlockPos blockPos;
+
+
+ public BlockPosWrapper()
+ {
+ this.blockPos = new BlockPos.MutableBlockPos(0, 0, 0);
+ }
+
+ public BlockPosWrapper(int x, int y, int z)
+ {
+ this.blockPos = new BlockPos.MutableBlockPos(x, y, z);
+ }
+
+ public void set(int x, int y, int z)
+ {
+ blockPos.set(x, y, z);
+ }
+
+ public int getX()
+ {
+ return blockPos.getX();
+ }
+
+ public int getY()
+ {
+ return blockPos.getY();
+ }
+
+ public int getZ()
+ {
+ return blockPos.getZ();
+ }
+
+ public int get(LodDirection.Axis axis)
+ {
+ return axis.choose(getX(), getY(), getZ());
+ }
+
+ public BlockPos.MutableBlockPos getBlockPos()
+ {
+ return blockPos;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return blockPos.equals(o);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(blockPos);
+ }
+
+ public BlockPosWrapper offset(int x, int y, int z)
+ {
+ blockPos.set(blockPos.getX() + x, blockPos.getY() + y, blockPos.getZ() + z);
+ return this;
+ }
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockShapeWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockShapeWrapper.java
new file mode 100644
index 000000000..35231018b
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/BlockShapeWrapper.java
@@ -0,0 +1,162 @@
+
+package com.seibel.lod.fabric.wrappers.block;
+
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkWrapper;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.phys.AABB;
+import net.minecraft.world.phys.shapes.VoxelShape;
+
+
+/**
+ * This class wraps Minecraft's Block class
+ *
+ * @author ??
+ * @version 11-18-2021
+ */
+public class BlockShapeWrapper implements IBlockShapeWrapper
+{
+ //set of block which require tint
+ public static final ConcurrentMap blockShapeWrapperMap = new ConcurrentHashMap<>();
+ public static BlockShapeWrapper WATER_SHAPE = new BlockShapeWrapper();
+
+ private final Block block;
+ private boolean toAvoid;
+ private boolean nonFull;
+ private boolean noCollision;
+
+ /**Constructor only require for the block instance we are wrapping**/
+ public BlockShapeWrapper(Block block, IChunkWrapper chunkWrapper, AbstractBlockPosWrapper blockPosWrapper)
+ {
+ this.block = block;
+ this.nonFull = false;
+ this.noCollision = false;
+ this.toAvoid = ofBlockToAvoid();
+ setupShapes((ChunkWrapper) chunkWrapper, (BlockPosWrapper) blockPosWrapper);
+ //System.out.println(block + " non full " + nonFull + " no collision " + noCollision + " to avoid " + toAvoid);
+ }
+
+ private BlockShapeWrapper()
+ {
+ this.block = Blocks.WATER;
+ this.nonFull = false;
+ this.noCollision = false;
+ this.toAvoid = false;
+ }
+
+ /**
+ * this return a wrapper of the block in input
+ * @param block Block object to wrap
+ */
+ static public BlockShapeWrapper getBlockShapeWrapper(Block block, IChunkWrapper chunkWrapper, AbstractBlockPosWrapper blockPosWrapper)
+ {
+ //first we check if the block has already been wrapped
+ if (blockShapeWrapperMap.containsKey(block) && blockShapeWrapperMap.get(block) != null)
+ return blockShapeWrapperMap.get(block);
+
+
+ //if it hasn't been created yet, we create it and save it in the map
+ BlockShapeWrapper blockWrapper = new BlockShapeWrapper(block, chunkWrapper, blockPosWrapper);
+ blockShapeWrapperMap.put(block, blockWrapper);
+
+ //we return the newly created wrapper
+ return blockWrapper;
+ }
+
+ private void setupShapes(ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper)
+ {
+ ChunkAccess chunk = chunkWrapper.getChunk();
+ BlockPos blockPos = blockPosWrapper.getBlockPos();
+ boolean noCollisionSetted = false;
+ boolean nonFullSetted = false;
+ if (!block.defaultBlockState().getFluidState().isEmpty()) // || block instanceof SixWayBlock)
+ {
+ noCollisionSetted = true;
+ nonFullSetted = true;
+ noCollision = false;
+ nonFull = false;
+ }
+ if (!nonFullSetted)
+ {
+ VoxelShape voxelShape = block.defaultBlockState().getShape(chunk, blockPos);
+
+ if (!voxelShape.isEmpty())
+ {
+ AABB bbox = voxelShape.bounds();
+ double xWidth = (bbox.maxX - bbox.minX);
+ double yWidth = (bbox.maxY - bbox.minY);
+ double zWidth = (bbox.maxZ - bbox.minZ);
+ nonFull = xWidth < 1 && zWidth < 1 && yWidth < 1;
+ }
+ else
+ {
+ nonFull = false;
+ }
+ }
+
+ if (!noCollisionSetted)
+ {
+ VoxelShape collisionShape = block.defaultBlockState().getCollisionShape(chunk, blockPos);
+ noCollision = collisionShape.isEmpty();
+ }
+ }
+
+ @Override
+ public boolean ofBlockToAvoid()
+ {
+ return block.equals(Blocks.AIR)
+ || block.equals(Blocks.CAVE_AIR)
+ || block.equals(Blocks.BARRIER)
+ || block.equals(Blocks.VOID_AIR);
+ }
+//-----------------//
+//Avoidance getters//
+//-----------------//
+
+
+ @Override
+ public boolean isNonFull()
+ {
+ return nonFull;
+ }
+
+ @Override
+ public boolean hasNoCollision()
+ {
+ return noCollision;
+ }
+
+ @Override
+ public boolean isToAvoid()
+ {
+ return toAvoid;
+ }
+
+
+
+
+ @Override public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (!(o instanceof BlockShapeWrapper))
+ return false;
+ BlockShapeWrapper that = (BlockShapeWrapper) o;
+ return Objects.equals(block, that.block);
+ }
+
+ @Override public int hashCode()
+ {
+ return Objects.hash(block);
+ }
+}
\ No newline at end of file
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/TextureAtlasSpriteWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/TextureAtlasSpriteWrapper.java
new file mode 100644
index 000000000..64b596ad5
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/block/TextureAtlasSpriteWrapper.java
@@ -0,0 +1,28 @@
+package com.seibel.lod.fabric.wrappers.block;
+
+
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+
+/**
+ * For wrapping/utilizing around TextureAtlasSprite
+ * @author Ran
+ */
+public class TextureAtlasSpriteWrapper {
+
+ /**
+ * This code is from Minecraft Forge
+ * Which is licensed under the terms of GNU Lesser General Public License
+ * as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * The code has been modified to use TextureAtlasSprite
+ */
+ public static int getPixelRGBA(TextureAtlasSprite sprite, int frameIndex, int x, int y) {
+ if (sprite.animatedTexture != null) {
+ x += sprite.animatedTexture.getFrameX(frameIndex) * sprite.width;
+ y += sprite.animatedTexture.getFrameY(frameIndex) * sprite.height;
+ }
+
+ return sprite.mainImage[0].getPixelRGBA(x, y);
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/chunk/ChunkPosWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/chunk/ChunkPosWrapper.java
new file mode 100644
index 000000000..d8fdcb140
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/chunk/ChunkPosWrapper.java
@@ -0,0 +1,118 @@
+package com.seibel.lod.fabric.wrappers.chunk;
+
+import java.util.Objects;
+
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.level.ChunkPos;
+
+
+/**
+ * This class wraps minecraft's ChunkPos class
+ *
+ * @author James Seibel
+ * @version 11-18-2021
+ */
+public class ChunkPosWrapper extends AbstractChunkPosWrapper
+{
+ private final ChunkPos chunkPos;
+
+ public ChunkPosWrapper(ChunkPos newChunkPos)
+ {
+ this.chunkPos = newChunkPos;
+ }
+
+ public ChunkPosWrapper(BlockPos blockPos)
+ {
+ this.chunkPos = new ChunkPos(blockPos);
+ }
+
+
+ public ChunkPosWrapper(AbstractChunkPosWrapper newChunkPos)
+ {
+ this.chunkPos = ((ChunkPosWrapper) newChunkPos).chunkPos;
+ }
+
+ public ChunkPosWrapper(AbstractBlockPosWrapper blockPos)
+ {
+ this.chunkPos = new ChunkPos(((BlockPosWrapper) blockPos).getBlockPos());
+ }
+
+ public ChunkPosWrapper(int chunkX, int chunkZ)
+ {
+ this.chunkPos = new ChunkPos(chunkX, chunkZ);
+ }
+
+ public ChunkPosWrapper()
+ {
+ this.chunkPos = new ChunkPos(0, 0);
+ }
+
+
+
+ @Override
+ public int getX()
+ {
+ return chunkPos.x;
+ }
+
+ @Override
+ public int getZ()
+ {
+ return chunkPos.z;
+ }
+
+ @Override
+ public int getMinBlockX()
+ {
+ return chunkPos.getMinBlockX();
+ }
+
+ @Override
+ public int getMinBlockZ()
+ {
+ return chunkPos.getMinBlockZ();
+ }
+
+ @Override
+ public int getRegionX()
+ {
+ return chunkPos.getRegionX();
+ }
+
+ @Override
+ public int getRegionZ()
+ {
+ return chunkPos.getRegionZ();
+ }
+
+ public ChunkPos getChunkPos()
+ {
+ return chunkPos;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o)
+ {
+ return chunkPos.equals(o);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(chunkPos);
+ }
+
+ @Override
+ public BlockPosWrapper getWorldPosition()
+ {
+ BlockPos blockPos = chunkPos.getWorldPosition();
+ return new BlockPosWrapper(blockPos.getX(), blockPos.getY(), blockPos.getZ());
+ }
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/chunk/ChunkWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/chunk/ChunkWrapper.java
new file mode 100644
index 000000000..36ca45568
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/chunk/ChunkWrapper.java
@@ -0,0 +1,106 @@
+package com.seibel.lod.fabric.wrappers.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.chunk.IChunkWrapper;
+import com.seibel.lod.fabric.wrappers.WrapperUtil;
+import com.seibel.lod.fabric.wrappers.block.BlockColorWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockShapeWrapper;
+import com.seibel.lod.fabric.wrappers.world.BiomeWrapper;
+import net.minecraft.world.level.block.LiquidBlockContainer;
+import net.minecraft.world.level.block.SimpleWaterloggedBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.block.state.properties.BlockStateProperties;
+import net.minecraft.world.level.chunk.ChunkAccess;
+
+/**
+ *
+ * @author ??
+ * @version 11-17-2021
+ */
+public class ChunkWrapper implements IChunkWrapper
+{
+ private final ChunkAccess chunk;
+ private final ChunkPosWrapper chunkPos;
+
+ @Override
+ public int getHeight()
+ {
+ return chunk.getMaxBuildHeight();
+ }
+
+ @Override
+ public boolean isPositionInWater(AbstractBlockPosWrapper blockPos)
+ {
+ BlockState blockState = chunk.getBlockState(((BlockPosWrapper) blockPos).getBlockPos());
+
+ //This type of block is always in water
+ return ((blockState.getBlock() instanceof LiquidBlockContainer) && !(blockState.getBlock() instanceof SimpleWaterloggedBlock))
+ || (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED));
+ }
+
+ @Override
+ public int getHeightMapValue(int xRel, int zRel)
+ {
+ return chunk.getOrCreateHeightmapUnprimed(WrapperUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
+ }
+
+ @Override
+ public BiomeWrapper getBiome(int xRel, int yAbs, int zRel)
+ {
+ return BiomeWrapper.getBiomeWrapper(chunk.getBiomes().getNoiseBiome(xRel >> 2, yAbs >> 2, zRel >> 2));
+ }
+
+ @Override
+ public IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos)
+ {
+ return BlockColorWrapper.getBlockColorWrapper(chunk.getBlockState(((BlockPosWrapper) blockPos).getBlockPos()).getBlock());
+ }
+
+ @Override
+ public IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos)
+ {
+ return BlockShapeWrapper.getBlockShapeWrapper(chunk.getBlockState(((BlockPosWrapper) blockPos).getBlockPos()).getBlock(), this, blockPos);
+ }
+
+ public ChunkWrapper(ChunkAccess chunk)
+ {
+ this.chunk = chunk;
+ this.chunkPos = new ChunkPosWrapper(chunk.getPos());
+ }
+
+ public ChunkAccess getChunk()
+ {
+ return chunk;
+ }
+
+ @Override
+ public ChunkPosWrapper getPos()
+ {
+ return chunkPos;
+ }
+
+ @Override
+ public boolean isLightCorrect()
+ {
+ return chunk.isLightCorrect();
+ }
+
+ @Override
+ public boolean isWaterLogged(AbstractBlockPosWrapper blockPos)
+ {
+ BlockState blockState = chunk.getBlockState(((BlockPosWrapper)blockPos).getBlockPos());
+
+ //This type of block is always in water
+ return ((blockState.getBlock() instanceof LiquidBlockContainer) && !(blockState.getBlock() instanceof SimpleWaterloggedBlock))
+ || (blockState.hasProperty(BlockStateProperties.WATERLOGGED) && blockState.getValue(BlockStateProperties.WATERLOGGED));
+ }
+
+ @Override
+ public int getEmittedBrightness(AbstractBlockPosWrapper blockPos)
+ {
+ return chunk.getLightEmission(((BlockPosWrapper)blockPos).getBlockPos());
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/LodConfigWrapperSingleton.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/LodConfigWrapperSingleton.java
new file mode 100644
index 000000000..e5dd130c1
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/LodConfigWrapperSingleton.java
@@ -0,0 +1,463 @@
+package com.seibel.lod.fabric.wrappers.config;
+
+import com.seibel.lod.core.enums.config.*;
+import com.seibel.lod.core.enums.rendering.*;
+import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
+import com.seibel.lod.fabric.Config;
+
+/**
+ * This holds the config defaults and setters/getters
+ * that should be hooked into the host mod loader (Fabric, Forge, etc.).
+ *
+ * @author James Seibel
+ * @version 11-16-2021
+ */
+public class LodConfigWrapperSingleton implements ILodConfigWrapperSingleton
+{
+ public static final LodConfigWrapperSingleton INSTANCE = new LodConfigWrapperSingleton();
+
+
+ private static final Client client = new Client();
+ @Override
+ public IClient client()
+ {
+ return client;
+ }
+
+ public static class Client implements IClient
+ {
+ public final IGraphics graphics;
+ public final IWorldGenerator worldGenerator;
+ public final IAdvanced advanced;
+
+
+ @Override
+ public IGraphics graphics()
+ {
+ return graphics;
+ }
+
+ @Override
+ public IWorldGenerator worldGenerator()
+ {
+ return worldGenerator;
+ }
+
+ @Override
+ public IAdvanced advanced()
+ {
+ return advanced;
+ }
+
+
+
+ //================//
+ // Client Configs //
+ //================//
+ public Client()
+ {
+ graphics = new Graphics();
+ worldGenerator = new WorldGenerator();
+ advanced = new Advanced();
+ }
+
+
+ //==================//
+ // Graphics Configs //
+ //==================//
+ public static class Graphics implements IGraphics
+ {
+ public final IQuality quality;
+ public final IFogQuality fogQuality;
+ public final IAdvancedGraphics advancedGraphics;
+
+
+
+ @Override
+ public IQuality quality()
+ {
+ return quality;
+ }
+
+ @Override
+ public IFogQuality fogQuality()
+ {
+ return fogQuality;
+ }
+
+ @Override
+ public IAdvancedGraphics advancedGraphics()
+ {
+ return advancedGraphics;
+ }
+
+
+ Graphics()
+ {
+ quality = new Quality();
+ advancedGraphics = new AdvancedGraphics();
+ fogQuality = new FogQuality();
+ }
+
+
+ public static class Quality implements IQuality
+ {
+ @Override
+ public HorizontalResolution getDrawResolution()
+ {
+ return Config.Client.Graphics.QualityOption.drawResolution;
+ }
+ @Override
+ public void setDrawResolution(HorizontalResolution newHorizontalResolution)
+ {
+ Config.Client.Graphics.QualityOption.drawResolution = newHorizontalResolution;
+ }
+
+
+ @Override
+ public int getLodChunkRenderDistance()
+ {
+ return Config.Client.Graphics.QualityOption.lodChunkRenderDistance;
+ }
+ @Override
+ public void setLodChunkRenderDistance(int newLodChunkRenderDistance)
+ {
+ Config.Client.Graphics.QualityOption.lodChunkRenderDistance = newLodChunkRenderDistance;
+ }
+
+
+ @Override
+ public VerticalQuality getVerticalQuality()
+ {
+ return Config.Client.Graphics.QualityOption.verticalQuality;
+ }
+ @Override
+ public void setVerticalQuality(VerticalQuality newVerticalQuality)
+ {
+ Config.Client.Graphics.QualityOption.verticalQuality = newVerticalQuality;
+ }
+
+
+ @Override
+ public HorizontalScale getHorizontalScale()
+ {
+ return Config.Client.Graphics.QualityOption.horizontalScale;
+ }
+ @Override
+ public void setHorizontalScale(HorizontalScale newHorizontalScale)
+ {
+ Config.Client.Graphics.QualityOption.horizontalScale = newHorizontalScale;
+ }
+
+
+ @Override
+ public HorizontalQuality getHorizontalQuality()
+ {
+ return Config.Client.Graphics.QualityOption.horizontalQuality;
+ }
+ @Override
+ public void setHorizontalQuality(HorizontalQuality newHorizontalQuality)
+ {
+ Config.Client.Graphics.QualityOption.horizontalQuality = newHorizontalQuality;
+ }
+ }
+
+
+ public static class FogQuality implements IFogQuality
+ {
+ @Override
+ public FogDistance getFogDistance()
+ {
+ return Config.Client.Graphics.FogQualityOption.fogDistance;
+ }
+ @Override
+ public void setFogDistance(FogDistance newFogDistance)
+ {
+ Config.Client.Graphics.FogQualityOption.fogDistance = newFogDistance;
+ }
+
+
+ @Override
+ public FogDrawOverride getFogDrawOverride()
+ {
+ return Config.Client.Graphics.FogQualityOption.fogDrawOverride;
+ }
+ @Override
+ public void setFogDrawOverride(FogDrawOverride newFogDrawOverride)
+ {
+ Config.Client.Graphics.FogQualityOption.fogDrawOverride = newFogDrawOverride;
+ }
+
+
+ @Override
+ public boolean getDisableVanillaFog()
+ {
+ return Config.Client.Graphics.FogQualityOption.disableVanillaFog;
+ }
+ @Override
+ public void setDisableVanillaFog(boolean newDisableVanillaFog)
+ {
+ Config.Client.Graphics.FogQualityOption.disableVanillaFog = newDisableVanillaFog;
+ }
+ }
+
+
+ public static class AdvancedGraphics implements IAdvancedGraphics
+ {
+ @Override
+ public LodTemplate getLodTemplate()
+ {
+ return Config.Client.Graphics.AdvancedGraphicsOption.lodTemplate;
+ }
+ @Override
+ public void setLodTemplate(LodTemplate newLodTemplate)
+ {
+ Config.Client.Graphics.AdvancedGraphicsOption.lodTemplate = newLodTemplate;
+ }
+
+
+ @Override
+ public boolean getDisableDirectionalCulling()
+ {
+ return Config.Client.Graphics.AdvancedGraphicsOption.disableDirectionalCulling;
+ }
+ @Override
+ public void setDisableDirectionalCulling(boolean newDisableDirectionalCulling)
+ {
+ Config.Client.Graphics.AdvancedGraphicsOption.disableDirectionalCulling = newDisableDirectionalCulling;
+ }
+
+
+ @Override
+ public boolean getAlwaysDrawAtMaxQuality()
+ {
+ return Config.Client.Graphics.AdvancedGraphicsOption.alwaysDrawAtMaxQuality;
+ }
+ @Override
+ public void setAlwaysDrawAtMaxQuality(boolean newAlwaysDrawAtMaxQuality)
+ {
+ Config.Client.Graphics.AdvancedGraphicsOption.alwaysDrawAtMaxQuality = newAlwaysDrawAtMaxQuality;
+ }
+
+
+ @Override
+ public VanillaOverdraw getVanillaOverdraw()
+ {
+ return Config.Client.Graphics.AdvancedGraphicsOption.vanillaOverdraw;
+ }
+ @Override
+ public void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw)
+ {
+ Config.Client.Graphics.AdvancedGraphicsOption.vanillaOverdraw = newVanillaOverdraw;
+ }
+
+
+ @Override
+ public GpuUploadMethod getGpuUploadMethod()
+ {
+ return Config.Client.Graphics.AdvancedGraphicsOption.gpuUploadMethod;
+ }
+ @Override
+ public void setGpuUploadMethod(GpuUploadMethod newDisableVanillaFog)
+ {
+ Config.Client.Graphics.AdvancedGraphicsOption.gpuUploadMethod = newDisableVanillaFog;
+ }
+
+
+ @Override
+ public boolean getUseExtendedNearClipPlane()
+ {
+ return Config.Client.Graphics.AdvancedGraphicsOption.useExtendedNearClipPlane;
+ }
+ @Override
+ public void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane)
+ {
+ Config.Client.Graphics.AdvancedGraphicsOption.useExtendedNearClipPlane = newUseExtendedNearClipPlane;
+ }
+ }
+ }
+
+
+
+
+ //========================//
+ // WorldGenerator Configs //
+ //========================//
+ public static class WorldGenerator implements IWorldGenerator
+ {
+ @Override
+ public GenerationPriority getGenerationPriority()
+ {
+ return Config.Client.WorldGenerator.generationPriority;
+ }
+ @Override
+ public void setGenerationPriority(GenerationPriority newGenerationPriority)
+ {
+ Config.Client.WorldGenerator.generationPriority = newGenerationPriority;
+ }
+
+
+ @Override
+ public DistanceGenerationMode getDistanceGenerationMode()
+ {
+ return Config.Client.WorldGenerator.distanceGenerationMode;
+ }
+ @Override
+ public void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode)
+ {
+ Config.Client.WorldGenerator.distanceGenerationMode = newDistanceGenerationMode;
+ }
+
+
+ @Override
+ public boolean getAllowUnstableFeatureGeneration()
+ {
+ return Config.Client.WorldGenerator.allowUnstableFeatureGeneration;
+ }
+ @Override
+ public void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration)
+ {
+ Config.Client.WorldGenerator.allowUnstableFeatureGeneration = newAllowUnstableFeatureGeneration;
+ }
+
+
+ @Override
+ public BlocksToAvoid getBlocksToAvoid()
+ {
+ return Config.Client.WorldGenerator.blocksToAvoid;
+ }
+ @Override
+ public void setBlockToAvoid(BlocksToAvoid newBlockToAvoid)
+ {
+ Config.Client.WorldGenerator.blocksToAvoid = newBlockToAvoid;
+ }
+ }
+
+
+
+
+ //============================//
+ // AdvancedModOptions Configs //
+ //============================//
+ public static class Advanced implements IAdvanced
+ {
+ public final IThreading threading;
+ public final IDebugging debugging;
+ public final IBuffers buffers;
+
+
+ @Override
+ public IThreading threading()
+ {
+ return threading;
+ }
+
+
+ @Override
+ public IDebugging debugging()
+ {
+ return debugging;
+ }
+
+
+ @Override
+ public IBuffers buffers()
+ {
+ return buffers;
+ }
+
+
+ public Advanced()
+ {
+ threading = new Threading();
+ debugging = new Debugging();
+ buffers = new Buffers();
+ }
+
+ public static class Threading implements IThreading
+ {
+ @Override
+ public int getNumberOfWorldGenerationThreads()
+ {
+ return Config.Client.AdvancedModOptions.Threading.numberOfWorldGenerationThreads;
+ }
+ @Override
+ public void setNumberOfWorldGenerationThreads(int newNumberOfWorldGenerationThreads)
+ {
+ Config.Client.AdvancedModOptions.Threading.numberOfWorldGenerationThreads = newNumberOfWorldGenerationThreads;
+ }
+
+
+ @Override
+ public int getNumberOfBufferBuilderThreads()
+ {
+ return Config.Client.AdvancedModOptions.Threading.numberOfBufferBuilderThreads;
+ }
+ @Override
+ public void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads)
+ {
+ Config.Client.AdvancedModOptions.Threading.numberOfBufferBuilderThreads = newNumberOfWorldBuilderThreads;
+ }
+ }
+
+
+
+
+ //===============//
+ // Debug Options //
+ //===============//
+ public static class Debugging implements IDebugging
+ {
+ @Override
+ public boolean getDrawLods()
+ {
+ return Config.Client.AdvancedModOptions.Debugging.drawLods;
+ }
+ @Override
+ public void setDrawLods(boolean newDrawLods)
+ {
+ Config.Client.AdvancedModOptions.Debugging.drawLods = newDrawLods;
+ }
+
+
+ @Override
+ public DebugMode getDebugMode()
+ {
+ return Config.Client.AdvancedModOptions.Debugging.debugMode;
+ }
+ @Override
+ public void setDebugMode(DebugMode newDebugMode)
+ {
+ Config.Client.AdvancedModOptions.Debugging.debugMode = newDebugMode;
+ }
+
+
+ @Override
+ public boolean getDebugKeybindingsEnabled()
+ {
+ return Config.Client.AdvancedModOptions.Debugging.enableDebugKeybindings;
+ }
+ @Override
+ public void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings)
+ {
+ Config.Client.AdvancedModOptions.Debugging.enableDebugKeybindings = newEnableDebugKeybindings;
+ }
+ }
+
+
+ public static class Buffers implements IBuffers
+ {
+ @Override
+ public BufferRebuildTimes getRebuildTimes()
+ {
+ return Config.Client.AdvancedModOptions.Buffers.rebuildTimes;
+ }
+ @Override
+ public void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes)
+ {
+ Config.Client.AdvancedModOptions.Buffers.rebuildTimes = newBufferRebuildTimes;
+ }
+ }
+ }
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java
new file mode 100644
index 000000000..7eacb9895
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/config/ModMenuIntegration.java
@@ -0,0 +1,19 @@
+package com.seibel.lod.fabric.wrappers.config;
+
+import com.seibel.lod.fabric.Config;
+import com.terraformersmc.modmenu.api.ConfigScreenFactory;
+import com.terraformersmc.modmenu.api.ModMenuApi;
+import me.shedaniel.autoconfig.AutoConfig;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+
+/**
+ * For making the config show up in modmenu
+ */
+@Environment(EnvType.CLIENT)
+public class ModMenuIntegration implements ModMenuApi {
+ @Override
+ public ConfigScreenFactory> getModConfigScreenFactory() {
+ return parent -> AutoConfig.getConfigScreen(Config.class, parent).get();
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java
new file mode 100644
index 000000000..df6faa5bb
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftRenderWrapper.java
@@ -0,0 +1,141 @@
+package com.seibel.lod.fabric.wrappers.minecraft;
+
+import java.util.HashSet;
+
+import com.mojang.math.Vector3d;
+import com.mojang.math.Vector3f;
+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;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
+import com.seibel.lod.fabric.wrappers.McObjectConverter;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkPosWrapper;
+
+import net.minecraft.client.Camera;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.renderer.GameRenderer;
+import net.minecraft.client.renderer.LevelRenderer;
+import net.minecraft.client.renderer.chunk.ChunkRenderDispatcher.CompiledChunk;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.effect.MobEffects;
+import net.minecraft.world.phys.Vec3;
+
+/**
+ * A singleton that contains everything
+ * related to rendering in Minecraft.
+ *
+ * @author James Seibel
+ * @version 11-18-2021
+ */
+public class MinecraftRenderWrapper implements IMinecraftRenderWrapper
+{
+ public static final MinecraftRenderWrapper INSTANCE = new MinecraftRenderWrapper();
+
+ private final GameRenderer gameRenderer = Minecraft.getInstance().gameRenderer;
+ private final static Minecraft mc = Minecraft.getInstance();
+
+
+
+ @Override
+ public Vec3f getLookAtVector()
+ {
+ Camera camera = gameRenderer.getMainCamera();
+ Vector3f cameraDir = camera.getLookVector();
+ return new Vec3f(cameraDir.x(), cameraDir.y(), cameraDir.z());
+ }
+
+ @Override
+ public AbstractBlockPosWrapper getCameraBlockPosition()
+ {
+ Camera camera = gameRenderer.getMainCamera();
+ BlockPos blockPos = camera.getBlockPosition();
+ return new BlockPosWrapper(blockPos.getX(), blockPos.getY(), blockPos.getZ());
+ }
+
+ @Override
+ public boolean playerHasBlindnessEffect()
+ {
+ return mc.player.getActiveEffectsMap().get(MobEffects.BLINDNESS) != null;
+ }
+
+ @Override
+ public Vec3d getCameraExactPosition()
+ {
+ Camera camera = gameRenderer.getMainCamera();
+ Vec3 projectedView = camera.getPosition();
+
+ return new Vec3d(projectedView.x, projectedView.y, projectedView.z);
+ }
+
+ @Override
+ public Mat4f getDefaultProjectionMatrix(float partialTicks)
+ {
+// return McObjectConverter.Convert(gameRenderer.getProjectionMatrix(gameRenderer.getMainCamera(), partialTicks, true));
+ return McObjectConverter.Convert(gameRenderer.getProjectionMatrix(partialTicks));
+ }
+
+ @Override
+ public double getGamma()
+ {
+ return mc.options.gamma;
+ }
+
+ @Override
+ public double getFov(float partialTicks)
+ {
+ return gameRenderer.getFov(gameRenderer.getMainCamera(), partialTicks, true);
+ }
+
+ /** Measured in chunks */
+ @Override
+ public int getRenderDistance()
+ {
+ return mc.options.renderDistance;
+ }
+
+ @Override
+ public int getScreenWidth()
+ {
+ return mc.getWindow().getWidth();
+ }
+ @Override
+ public int getScreenHeight()
+ {
+ return mc.getWindow().getHeight();
+ }
+
+ /**
+ * This method returns the ChunkPos of all chunks that Minecraft
+ * is going to render this frame.
+ *
+ * Note: This isn't perfect. It will return some chunks that are outside
+ * the clipping plane. (For example, if you are high above the ground some chunks
+ * will be incorrectly added, even though they are outside render range).
+ */
+ @Override
+ public HashSet getRenderedChunks()
+ {
+ HashSet loadedPos = new HashSet<>();
+
+ // Wow, those are some long names!
+
+ // go through every RenderInfo to get the compiled chunks
+ LevelRenderer renderer = mc.levelRenderer;
+ for (LevelRenderer.RenderChunkInfo worldRenderer$LocalRenderInformationContainer : renderer.renderChunks)
+ {
+ CompiledChunk compiledChunk = worldRenderer$LocalRenderInformationContainer.chunk.getCompiledChunk();
+ if (!compiledChunk.hasNoRenderableLayers())
+ {
+ // add the ChunkPos for every rendered chunk
+ BlockPos bpos = worldRenderer$LocalRenderInformationContainer.chunk.getOrigin();
+
+ loadedPos.add(new ChunkPosWrapper(bpos));
+ }
+ }
+
+ return loadedPos;
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftWrapper.java
new file mode 100644
index 000000000..61cbfcddf
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/MinecraftWrapper.java
@@ -0,0 +1,394 @@
+/*
+ * 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.fabric.wrappers.minecraft;
+
+import java.awt.Color;
+import java.io.File;
+import java.util.ArrayList;
+
+import com.mojang.blaze3d.platform.NativeImage;
+import com.mojang.blaze3d.platform.Window;
+import com.seibel.lod.core.ModInfo;
+import com.seibel.lod.core.api.ClientApi;
+import com.seibel.lod.core.enums.LodDirection;
+import com.seibel.lod.core.util.LodUtil;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
+import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+import com.seibel.lod.fabric.wrappers.McObjectConverter;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkPosWrapper;
+import com.seibel.lod.fabric.wrappers.misc.LightMapWrapper;
+import com.seibel.lod.fabric.wrappers.world.DimensionTypeWrapper;
+import com.seibel.lod.fabric.wrappers.world.WorldWrapper;
+
+import net.minecraft.CrashReport;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.Options;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.client.multiplayer.ClientPacketListener;
+import net.minecraft.client.multiplayer.ServerData;
+import net.minecraft.client.player.LocalPlayer;
+import net.minecraft.client.renderer.GameRenderer;
+import net.minecraft.client.renderer.LevelRenderer;
+import net.minecraft.client.renderer.LightTexture;
+import net.minecraft.client.resources.model.ModelManager;
+import net.minecraft.client.server.IntegratedServer;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.network.chat.TextComponent;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.Entity;
+import net.minecraft.world.level.dimension.DimensionType;
+
+/**
+ * A singleton that wraps the Minecraft class
+ * to allow for easier movement between Minecraft versions.
+ *
+ * @author James Seibel
+ * @version 9-16-2021
+ */
+public class MinecraftWrapper implements IMinecraftWrapper
+{
+ public static final MinecraftWrapper INSTANCE = new MinecraftWrapper();
+
+ private final Minecraft mc = Minecraft.getInstance();
+
+ /**
+ * The lightmap for the current:
+ * Time, dimension, brightness setting, etc.
+ */
+ private NativeImage lightMap = null;
+
+ private ProfilerWrapper profilerWrapper;
+
+
+ private MinecraftWrapper()
+ {
+
+ }
+
+
+
+ //================//
+ // 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.
+ */
+ @Override
+ public void clearFrameObjectCache()
+ {
+ lightMap = null;
+ }
+
+
+
+ //=================//
+ // method wrappers //
+ //=================//
+
+ @Override
+ public float getShade(LodDirection lodDirection)
+ {
+ Direction mcDir = McObjectConverter.Convert(lodDirection);
+ return mc.level.getShade(mcDir, true);
+ }
+
+ @Override
+ public boolean hasSinglePlayerServer()
+ {
+ return mc.hasSingleplayerServer();
+ }
+
+ @Override
+ public String getCurrentServerName()
+ {
+ return mc.getCurrentServer().name;
+ }
+
+ @Override
+ public String getCurrentServerIp()
+ {
+ return mc.getCurrentServer().ip;
+ }
+
+ @Override
+ public String getCurrentServerVersion()
+ {
+ return mc.getCurrentServer().version.getString();
+ }
+
+ /** Returns the dimension the player is currently in */
+ @Override
+ public IDimensionTypeWrapper getCurrentDimension()
+ {
+ return DimensionTypeWrapper.getDimensionTypeWrapper(mc.player.level.dimensionType());
+ }
+
+ @Override
+ public String getCurrentDimensionId()
+ {
+ return LodUtil.getDimensionIDFromWorld(WorldWrapper.getWorldWrapper(mc.level));
+ }
+
+ /** This texture changes every frame */
+ @Override
+ public ILightMapWrapper getCurrentLightMap()
+ {
+ // get the current lightMap if the cache is empty
+ if (lightMap == null)
+ {
+ LightTexture tex = mc.gameRenderer.lightTexture();
+ lightMap = tex.lightPixels;
+ }
+ return new LightMapWrapper(lightMap);
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public int getColorIntFromLightMap(int u, int v)
+ {
+ if (lightMap == null)
+ {
+ // make sure the lightMap is up-to-date
+ getCurrentLightMap();
+ }
+
+ return lightMap.getPixelRGBA(u, 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
+ */
+ @Override
+ public Color getColorFromLightMap(int u, int v)
+ {
+ return LodUtil.intToColor(lightMap.getPixelRGBA(u, v));
+ }
+
+
+
+
+ //=============//
+ // Simple gets //
+ //=============//
+
+ public LocalPlayer getPlayer()
+ {
+ return mc.player;
+ }
+
+ @Override
+ public boolean playerExists()
+ {
+ return mc.player != null;
+ }
+
+ @Override
+ public BlockPosWrapper getPlayerBlockPos()
+ {
+ BlockPos playerPos = getPlayer().blockPosition();
+ return new BlockPosWrapper(playerPos.getX(), playerPos.getY(), playerPos.getZ());
+ }
+
+ @Override
+ public ChunkPosWrapper getPlayerChunkPos()
+ {
+ return new ChunkPosWrapper(getPlayer().chunkPosition().x, getPlayer().chunkPosition().z);
+ }
+
+ public Options getOptions()
+ {
+ return mc.options;
+ }
+
+ public ModelManager getModelManager()
+ {
+ return mc.getModelManager();
+ }
+
+ public ClientLevel getClientWorld()
+ {
+ return mc.level;
+ }
+
+ /**
+ * Attempts to get the ServerWorld for the dimension
+ * the user is currently in.
+ * @returns null if no ServerWorld is available
+ */
+ @Override
+ public IWorldWrapper getWrappedServerWorld()
+ {
+ if (mc.level == null)
+ return null;
+
+ DimensionType dimension = mc.level.dimensionType();
+ IntegratedServer server = mc.getSingleplayerServer();
+
+ if (server == null)
+ return null;
+
+ ServerLevel serverWorld = null;
+ Iterable worlds = server.getAllLevels();
+ for (ServerLevel world : worlds)
+ {
+ if (world.dimensionType() == dimension)
+ {
+ serverWorld = world;
+ break;
+ }
+ }
+ return WorldWrapper.getWorldWrapper(serverWorld);
+ }
+
+ @Override
+ public IWorldWrapper getWrappedClientWorld()
+ {
+ return WorldWrapper.getWorldWrapper(mc.level);
+ }
+
+ @Override
+ public File getGameDirectory()
+ {
+ return mc.gameDirectory;
+ }
+
+ @Override
+ public IProfilerWrapper getProfiler()
+ {
+ if (profilerWrapper == null)
+ profilerWrapper = new ProfilerWrapper(mc.getProfiler());
+ else if (mc.getProfiler() != profilerWrapper.profiler)
+ profilerWrapper.profiler = mc.getProfiler();
+
+ return profilerWrapper;
+ }
+
+ public ClientPacketListener getConnection()
+ {
+ return mc.getConnection();
+ }
+
+ public GameRenderer getGameRenderer()
+ {
+ return mc.gameRenderer;
+ }
+
+ public Entity getCameraEntity()
+ {
+ return mc.cameraEntity;
+ }
+
+ public Window getWindow()
+ {
+ return mc.getWindow();
+ }
+
+ @Override
+ public float getSkyDarken(float partialTicks)
+ {
+ return mc.level.getSkyDarken(partialTicks);
+ }
+
+ public IntegratedServer getSinglePlayerServer()
+ {
+ return mc.getSingleplayerServer();
+ }
+
+ @Override
+ public boolean connectedToServer()
+ {
+ return mc.getCurrentServer() != null;
+ }
+
+ public ServerData getCurrentServer()
+ {
+ return mc.getCurrentServer();
+ }
+
+ public LevelRenderer getLevelRenderer()
+ {
+ return mc.levelRenderer;
+ }
+
+ /** Returns all worlds available to the server */
+ @Override
+ public ArrayList getAllServerWorlds()
+ {
+ ArrayList worlds = new ArrayList();
+
+ Iterable serverWorlds = mc.getSingleplayerServer().getAllLevels();
+ for (ServerLevel world : serverWorlds)
+ {
+ worlds.add(WorldWrapper.getWorldWrapper(world));
+ }
+
+ return worlds;
+ }
+
+
+
+ @Override
+ public void sendChatMessage(String string)
+ {
+ getPlayer().sendMessage(new TextComponent(string), getPlayer().getUUID());
+ }
+
+ /**
+ * Crashes Minecraft, displaying the given errorMessage
+ * In the following format:
+ *
+ * The game crashed whilst errorMessage
+ * Error: ExceptionClass: exceptionErrorMessage
+ * Exit Code: -1
+ */
+ @Override
+ public void crashMinecraft(String errorMessage, Throwable exception)
+ {
+ ClientApi.LOGGER.error(ModInfo.READABLE_NAME + " had the following error: [" + errorMessage + "]. Crashing Minecraft...");
+ CrashReport report = new CrashReport(errorMessage, exception);
+ Minecraft.crash(report);
+ }
+
+
+
+
+
+
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/ProfilerWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/ProfilerWrapper.java
new file mode 100644
index 000000000..1816a8104
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/minecraft/ProfilerWrapper.java
@@ -0,0 +1,43 @@
+package com.seibel.lod.fabric.wrappers.minecraft;
+
+import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
+
+import net.minecraft.util.profiling.ProfilerFiller;
+
+/**
+ *
+ *
+ * @author James Seibel
+ * @version 11-20-2021
+ */
+public class ProfilerWrapper implements IProfilerWrapper
+{
+ public ProfilerFiller profiler;
+
+ public ProfilerWrapper(ProfilerFiller newProfiler)
+ {
+ profiler = newProfiler;
+ }
+
+
+ /** starts a new section inside the currently running section */
+ @Override
+ public void push(String newSection)
+ {
+ profiler.push(newSection);
+ }
+
+ /** ends the currently running section and starts a new one */
+ @Override
+ public void popPush(String newSection)
+ {
+ profiler.popPush(newSection);
+ }
+
+ /** ends the currently running section */
+ @Override
+ public void pop()
+ {
+ profiler.pop();
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/misc/LightMapWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/misc/LightMapWrapper.java
new file mode 100644
index 000000000..a13e0832c
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/misc/LightMapWrapper.java
@@ -0,0 +1,31 @@
+package com.seibel.lod.fabric.wrappers.misc;
+
+import com.mojang.blaze3d.platform.NativeImage;
+import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper;
+
+
+/**
+ *
+ * @author Leonardo Amato
+ * @version 11-13-2021
+ */
+public class LightMapWrapper implements ILightMapWrapper
+{
+ private NativeImage lightMap = null;
+
+ public LightMapWrapper(NativeImage newlightMap)
+ {
+ lightMap = newlightMap;
+ }
+
+ public void setLightMap(NativeImage newlightMap)
+ {
+ lightMap = newlightMap;
+ }
+
+ @Override
+ public int getLightValue(int skyLight, int blockLight)
+ {
+ return lightMap.getPixelRGBA(skyLight, blockLight);
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/BiomeColorWrapperSingleton.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/BiomeColorWrapperSingleton.java
new file mode 100644
index 000000000..797fa03c4
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/BiomeColorWrapperSingleton.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.fabric.wrappers.world;
+
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IBiomeColorWrapperSingleton;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+
+import net.minecraft.client.renderer.BiomeColors;
+
+
+/**
+ *
+ * @author Cola?
+ * @version 11-15-2021
+ */
+public class BiomeColorWrapperSingleton implements IBiomeColorWrapperSingleton
+{
+ private static final BiomeColorWrapperSingleton instance = new BiomeColorWrapperSingleton();
+
+ @Override
+ public IBiomeColorWrapperSingleton getInstance()
+ {
+ return instance;
+ }
+
+
+ @Override
+ public int getGrassColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos)
+ {
+ return BiomeColors.getAverageGrassColor(((WorldWrapper)world).getWorld(), ((BlockPosWrapper) blockPos).getBlockPos());
+ }
+ @Override
+ public int getWaterColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos)
+ {
+ return BiomeColors.getAverageWaterColor(((WorldWrapper)world).getWorld(), ((BlockPosWrapper) blockPos).getBlockPos());
+ }
+ @Override
+ public int getFoliageColor(IWorldWrapper world, AbstractBlockPosWrapper blockPos)
+ {
+ return BiomeColors.getAverageFoliageColor(((WorldWrapper)world).getWorld(), ((BlockPosWrapper) blockPos).getBlockPos());
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/BiomeWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/BiomeWrapper.java
new file mode 100644
index 000000000..1d4673aba
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/BiomeWrapper.java
@@ -0,0 +1,157 @@
+package com.seibel.lod.fabric.wrappers.world;
+
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.seibel.lod.core.util.ColorUtil;
+import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockColorSingletonWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockColorWrapper;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.block.Blocks;
+
+
+/**
+ * This class wraps the minecraft BlockPos.Mutable (and BlockPos) class
+ *
+ * @author James Seibel
+ * @version 11-15-2021
+ */
+public class BiomeWrapper implements IBiomeWrapper
+{
+ public static final ConcurrentMap biomeWrapperMap = new ConcurrentHashMap<>();
+ private final Biome biome;
+
+ public BiomeWrapper(Biome biome)
+ {
+ this.biome = biome;
+ }
+
+ static public BiomeWrapper getBiomeWrapper(Biome biome)
+ {
+ //first we check if the biome has already been wrapped
+ if(biomeWrapperMap.containsKey(biome) && biomeWrapperMap.get(biome) != null)
+ return biomeWrapperMap.get(biome);
+
+
+ //if it hasn't been created yet, we create it and save it in the map
+ BiomeWrapper biomeWrapper = new BiomeWrapper(biome);
+ biomeWrapperMap.put(biome, biomeWrapper);
+
+ //we return the newly created wrapper
+ return biomeWrapper;
+ }
+
+
+ /** Returns a color int for the given biome. */
+ @Override
+ public int getColorForBiome(int x, int z)
+ {
+ int colorInt;
+ int tintValue = 0;
+
+ switch (biome.getBiomeCategory())
+ {
+
+ case NETHER:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.NETHERRACK).getColor();
+ break;
+
+ case THEEND:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.END_STONE).getColor();
+ break;
+
+ case BEACH:
+ case DESERT:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.SAND).getColor();
+ break;
+
+ case EXTREME_HILLS:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.STONE).getColor();
+ break;
+
+ case MUSHROOM:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.MYCELIUM).getColor();
+ break;
+
+ case ICY:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.SNOW).getColor();
+ break;
+
+ case MESA:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.RED_SAND).getColor();
+ break;
+
+ case OCEAN:
+ case RIVER:
+ colorInt = BlockColorSingletonWrapper.INSTANCE.getWaterColor().getColor();
+ tintValue = biome.getWaterColor();
+ break;
+
+ case PLAINS:
+ case SAVANNA:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.GRASS_BLOCK).getColor();
+ tintValue = biome.getGrassColor(x, z);
+ colorInt = ColorUtil.multiplyRGBcolors(colorInt,tintValue);
+ break;
+
+ case TAIGA:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.SPRUCE_LEAVES).getColor();
+ tintValue = biome.getFoliageColor();
+ colorInt = ColorUtil.multiplyRGBcolors(colorInt,tintValue);
+ break;
+ case JUNGLE:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.JUNGLE_LEAVES).getColor();
+ tintValue = biome.getFoliageColor();
+ colorInt = ColorUtil.multiplyRGBcolors(colorInt,tintValue);
+ break;
+
+ case NONE:
+ default:
+ case SWAMP:
+ case FOREST:
+ colorInt = BlockColorWrapper.getBlockColorWrapper(Blocks.OAK_LEAVES).getColor();
+ tintValue = biome.getFoliageColor();
+ colorInt = ColorUtil.multiplyRGBcolors(colorInt,tintValue);
+ break;
+ }
+
+ return colorInt;
+ }
+
+ @Override
+ public int getGrassTint(int x, int z)
+ {
+ return biome.getGrassColor(x, z);
+ }
+
+ @Override
+ public int getFolliageTint()
+ {
+ return biome.getFoliageColor();
+ }
+
+ @Override
+ public int getWaterTint()
+ {
+ return biome.getWaterColor();
+ }
+
+
+ @Override public boolean equals(Object o)
+ {
+ if (this == o)
+ return true;
+ if (!(o instanceof BiomeWrapper))
+ return false;
+ BiomeWrapper that = (BiomeWrapper) o;
+ return Objects.equals(biome, that.biome);
+ }
+
+ @Override public int hashCode()
+ {
+ return Objects.hash(biome);
+ }
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/DimensionTypeWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/DimensionTypeWrapper.java
new file mode 100644
index 000000000..6a6bf53f6
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/DimensionTypeWrapper.java
@@ -0,0 +1,56 @@
+package com.seibel.lod.fabric.wrappers.world;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
+import net.minecraft.world.level.dimension.DimensionType;
+
+/**
+ *
+ * @author ??
+ * @version 11-15-2021
+ */
+public class DimensionTypeWrapper implements IDimensionTypeWrapper
+{
+ private static final ConcurrentMap dimensionTypeWrapperMap = new ConcurrentHashMap<>();
+ private final DimensionType dimensionType;
+
+ public DimensionTypeWrapper(DimensionType dimensionType)
+ {
+ this.dimensionType = dimensionType;
+ }
+
+ public static DimensionTypeWrapper getDimensionTypeWrapper(DimensionType dimensionType)
+ {
+ //first we check if the biome has already been wrapped
+ if(dimensionTypeWrapperMap.containsKey(dimensionType) && dimensionTypeWrapperMap.get(dimensionType) != null)
+ return dimensionTypeWrapperMap.get(dimensionType);
+
+
+ //if it hasn't been created yet, we create it and save it in the map
+ DimensionTypeWrapper dimensionTypeWrapper = new DimensionTypeWrapper(dimensionType);
+ dimensionTypeWrapperMap.put(dimensionType, dimensionTypeWrapper);
+
+ //we return the newly created wrapper
+ return dimensionTypeWrapper;
+ }
+
+ @Override
+ public String getDimensionName()
+ {
+ return dimensionType.effectsLocation().getPath();
+ }
+
+ @Override
+ public boolean hasCeiling()
+ {
+ return dimensionType.hasCeiling();
+ }
+
+ @Override
+ public boolean hasSkyLight()
+ {
+ return dimensionType.hasSkyLight();
+ }
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/WorldWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/WorldWrapper.java
new file mode 100644
index 000000000..50663f6d8
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/world/WorldWrapper.java
@@ -0,0 +1,152 @@
+package com.seibel.lod.fabric.wrappers.world;
+
+import java.io.File;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import com.seibel.lod.core.enums.WorldType;
+import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
+import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
+import com.seibel.lod.fabric.wrappers.block.BlockPosWrapper;
+
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.level.Level;
+
+/**
+ *
+ * @author James Seibel
+ * @author ??
+ * @version 11-15-2021
+ */
+public class WorldWrapper implements IWorldWrapper
+{
+ private static final ConcurrentMap worldWrapperMap = new ConcurrentHashMap<>();
+ private final Level world;
+ public final WorldType worldType;
+
+
+ public WorldWrapper(Level newWorld)
+ {
+ world = newWorld;
+
+ if (world.getClass() == ServerLevel.class)
+ worldType = WorldType.ServerWorld;
+ else if (world.getClass() == ClientLevel.class)
+ worldType = WorldType.ClientWorld;
+ else
+ worldType = WorldType.Unknown;
+ }
+
+
+
+ public static WorldWrapper getWorldWrapper(Level world)
+ {
+ if (world == null) return null;
+ //first we check if the biome has already been wrapped
+ if(worldWrapperMap.containsKey(world) && worldWrapperMap.get(world) != null)
+ return worldWrapperMap.get(world);
+
+
+ //if it hasn't been created yet, we create it and save it in the map
+ WorldWrapper worldWrapper = new WorldWrapper(world);
+ worldWrapperMap.put(world, worldWrapper);
+
+ //we return the newly created wrapper
+ return worldWrapper;
+ }
+
+ public static void clearMap()
+ {
+ worldWrapperMap.clear();
+ }
+
+ @Override
+ public WorldType getWorldType() {
+ return worldType;
+ }
+
+ @Override
+ public DimensionTypeWrapper getDimensionType()
+ {
+ return DimensionTypeWrapper.getDimensionTypeWrapper(world.dimensionType());
+ }
+
+ @Override
+ public int getBlockLight(AbstractBlockPosWrapper blockPos)
+ {
+ return world.getLightEngine().blockEngine.getLightValue(((BlockPosWrapper) blockPos).getBlockPos());
+ }
+
+ @Override
+ public int getSkyLight(AbstractBlockPosWrapper blockPos)
+ {
+ return world.getLightEngine().skyEngine.getLightValue(((BlockPosWrapper) blockPos).getBlockPos());
+ }
+
+ @Override
+ public BiomeWrapper getBiome(AbstractBlockPosWrapper blockPos)
+ {
+ return BiomeWrapper.getBiomeWrapper(world.getBiome(((BlockPosWrapper) blockPos).getBlockPos()));
+ }
+
+ public Level getWorld()
+ {
+ return world;
+ }
+
+ @Override
+ public boolean hasCeiling()
+ {
+ return world.dimensionType().hasCeiling();
+ }
+
+ @Override
+ public boolean hasSkyLight()
+ {
+ return world.dimensionType().hasSkyLight();
+ }
+
+ @Override
+ public boolean isEmpty()
+ {
+ return world == null;
+ }
+
+ @Override
+ public int getHeight()
+ {
+ return world.getHeight();
+ }
+
+ /** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
+ @Override
+ public File getSaveFolder() throws UnsupportedOperationException
+ {
+ if (worldType != WorldType.ServerWorld)
+ throw new UnsupportedOperationException("getSaveFolder can only be called for ServerWorlds.");
+
+ ServerChunkCache chunkSource = ((ServerLevel) world).getChunkSource();
+ return chunkSource.dataStorage.dataFolder;
+ }
+
+
+ /** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
+ public ServerLevel getServerWorld() throws UnsupportedOperationException
+ {
+ if (worldType != WorldType.ServerWorld)
+ throw new UnsupportedOperationException("getSaveFolder can only be called for ServerWorlds.");
+
+ return (ServerLevel) world;
+ }
+
+ @Override
+ public int getSeaLevel()
+ {
+ // TODO this is depreciated, what should we use instead?
+ return world.getSeaLevel();
+ }
+
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java
new file mode 100644
index 000000000..9cbee2929
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/LodServerWorld.java
@@ -0,0 +1,358 @@
+/*
+ * 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.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.fabric.wrappers.WrapperUtil;
+
+import com.sun.jna.Structure;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.Direction;
+import net.minecraft.core.RegistryAccess;
+import net.minecraft.core.SectionPos;
+import net.minecraft.core.particles.ParticleOptions;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.sounds.SoundEvent;
+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;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.entity.BlockEntity;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.border.WorldBorder;
+import net.minecraft.world.level.chunk.ChunkAccess;
+import net.minecraft.world.level.chunk.ChunkSource;
+import net.minecraft.world.level.chunk.ChunkStatus;
+import net.minecraft.world.level.dimension.DimensionType;
+import net.minecraft.world.level.entity.EntityTypeTest;
+import net.minecraft.world.level.gameevent.GameEvent;
+import net.minecraft.world.level.levelgen.Heightmap;
+import net.minecraft.world.level.levelgen.feature.StructureFeature;
+import net.minecraft.world.level.levelgen.structure.StructureStart;
+import net.minecraft.world.level.lighting.LevelLightEngine;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.level.storage.LevelData;
+import net.minecraft.world.phys.AABB;
+import org.jetbrains.annotations.Nullable;
+
+
+/**
+ * This is a fake ServerWorld used when generating features.
+ * It allows us to keep each LodChunk generation independent
+ * of the actual ServerWorld, allowing
+ * multithread generation.
+ *
+ * @author James Seibel
+ * @version 7-26-2021
+ */
+public class LodServerWorld implements WorldGenLevel
+{
+
+ public HashMap heightmaps = new HashMap<>();
+
+ public final ChunkAccess chunk;
+
+ public final ServerLevel serverWorld;
+
+ public LodServerWorld(ServerLevel newServerWorld, ChunkAccess newChunk)
+ {
+ chunk = newChunk;
+ serverWorld = newServerWorld;
+ }
+
+
+ @Override
+ public int getHeight(Heightmap.Types heightmapType, int x, int z)
+ {
+ // make sure the block position is set relative to the chunk
+ x = x % LodUtil.CHUNK_WIDTH;
+ x = (x < 0) ? x + 16 : x;
+
+ z = z % LodUtil.CHUNK_WIDTH;
+ z = (z < 0) ? z + 16 : z;
+
+ return chunk.getOrCreateHeightmapUnprimed(WrapperUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(x, z);
+ }
+
+ @Override
+ public Biome getBiome(BlockPos pos)
+ {
+ return chunk.getBiomes().getNoiseBiome(pos.getX() >> 2, pos.getY() >> 2, pos.getZ() >> 2);
+ }
+
+ @Override
+ public boolean setBlock(BlockPos pos, BlockState state, int flags, int recursionLeft)
+ {
+ return chunk.setBlockState(pos, state, false) == state;
+ }
+
+ @Override
+ public BlockState getBlockState(BlockPos pos)
+ {
+ return chunk.getBlockState(pos);
+ }
+
+ @Override
+ public FluidState getFluidState(BlockPos pos)
+ {
+ return chunk.getFluidState(pos);
+ }
+
+
+ @Override
+ public boolean isStateAtPosition(BlockPos pos, Predicate state)
+ {
+ return state.test(chunk.getBlockState(pos));
+ }
+
+ @Override
+ public boolean isFluidAtPosition(BlockPos pos, Predicate state) {
+ return state.test(chunk.getFluidState(pos));
+ }
+
+ @Override
+ public TickList getBlockTicks()
+ {
+ return EmptyTickList.empty();
+ }
+
+ @Override
+ public ChunkAccess getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull)
+ {
+ return chunk;
+ }
+
+ @Override
+ public Stream 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()
+ {
+ return new LevelLightEngine(null, false, false);
+ }
+
+ @Override
+ public long getSeed()
+ {
+ return serverWorld.getSeed();
+ }
+
+ @Override
+ public RegistryAccess registryAccess()
+ {
+ return serverWorld.registryAccess();
+ }
+
+
+ /**
+ * All methods below shouldn't be needed
+ * and thus have been left unimplemented.
+ */
+
+
+ @Override
+ public Random getRandom()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public void playSound(Player player, BlockPos pos, SoundEvent soundIn, SoundSource category, float volume,
+ float pitch)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public void addParticle(ParticleOptions particleData, double x, double y, double z, double xSpeed, double ySpeed,
+ double zSpeed)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public BiomeManager getBiomeManager()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public int getSeaLevel()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public float getShade(Direction p_230487_1_, boolean p_230487_2_)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public WorldBorder getWorldBorder()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public boolean removeBlock(BlockPos pos, boolean isMoving)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public boolean destroyBlock(BlockPos pos, boolean dropBlock, Entity entity, int recursionLeft)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public ServerLevel getLevel()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public ChunkSource getChunkSource()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Nullable
+ @Override
+ public MinecraftServer getServer() {
+ return serverWorld.getServer();
+ }
+
+
+ @Override
+ public LevelData getLevelData()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public void levelEvent(Player arg0, int arg1, BlockPos arg2, int arg3)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+
+ }
+
+ @Override
+ public void gameEvent(@Nullable Entity entity, GameEvent gameEvent, BlockPos blockPos) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+
+ @Override
+ public List getEntities(Entity arg0, AABB arg1, Predicate super Entity> arg2)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+ @Override
+ public List getEntities(EntityTypeTest entityTypeTest, AABB aABB, Predicate super T> predicate) {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public List getEntitiesOfClass(Class arg0, AABB arg1,
+ Predicate super T> arg2)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public List extends Player> players()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public int getSkyDarken()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public Biome getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public boolean isClientSide()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public DimensionType dimensionType()
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+
+ @Override
+ public BlockEntity getBlockEntity(BlockPos p_175625_1_)
+ {
+ throw new UnsupportedOperationException("Not Implemented");
+ }
+
+}
diff --git a/fabric/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java
new file mode 100644
index 000000000..42eaefe9f
--- /dev/null
+++ b/fabric/src/main/java/com/seibel/lod/fabric/wrappers/worldGeneration/WorldGeneratorWrapper.java
@@ -0,0 +1,393 @@
+package com.seibel.lod.fabric.wrappers.worldGeneration;
+
+import java.util.ConcurrentModificationException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
+import com.seibel.lod.core.builders.lodBuilding.LodBuilderConfig;
+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.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;
+import com.seibel.lod.fabric.wrappers.WrapperUtil;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkPosWrapper;
+import com.seibel.lod.fabric.wrappers.chunk.ChunkWrapper;
+import com.seibel.lod.fabric.wrappers.world.WorldWrapper;
+
+import net.minecraft.core.Registry;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.server.level.ThreadedLevelLightEngine;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.biome.Biome;
+import net.minecraft.world.level.chunk.*;
+import net.minecraft.world.level.levelgen.Heightmap;
+import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
+import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
+
+/**
+ * This is used for generating chunks
+ * in a variety of detail and threading levels.
+ *
+ * @author coolGi2007
+ * @author James Seibel
+ * @version 11-23-2021
+ */
+public class WorldGeneratorWrapper extends AbstractWorldGeneratorWrapper
+{
+ private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
+
+ /**
+ * If a configured feature fails for whatever reason,
+ * add it to this list. This will hopefully remove any
+ * features that could cause issues down the line.
+ */
+ private static final ConcurrentHashMap> FEATURES_TO_AVOID = new ConcurrentHashMap<>();
+
+
+ public final ServerLevel serverWorld;
+ public final LodDimension lodDim;
+ public final LodBuilder lodBuilder;
+
+ public WorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper)
+ {
+ super(newLodBuilder, newLodDimension, worldWrapper);
+
+ lodBuilder = newLodBuilder;
+ lodDim = newLodDimension;
+ serverWorld = ((WorldWrapper) worldWrapper).getServerWorld();
+ }
+
+
+
+
+
+
+
+ /** takes about 2-5 ms */
+ @Override
+ public void generateBiomesOnly(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode)
+ {
+ /*
+ List chunkList = new LinkedList<>();
+ ProtoChunk chunk = new ProtoChunk((ChunkPosWrapper) pos, UpgradeData.EMPTY, serverWorld);
+ chunkList.add(chunk);
+
+ ServerChunkCache chunkSource = serverWorld.getChunkSource();
+ ChunkGenerator chunkGen = chunkSource.getGenerator();
+
+ // generate the terrain (this is thread safe)
+// ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ThreadedLevelLightEngine) serverWorld.getLightEngine(), null, chunkList);
+ // override the chunk status, so we can run the next generator stage
+ chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
+ chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
+ chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
+
+
+
+
+ // generate fake height data for this LOD
+ int seaLevel = serverWorld.getSeaLevel();
+
+ boolean simulateHeight = generationMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
+ boolean inTheEnd = false;
+
+ // add fake heightmap data so our LODs aren't at height 0
+ Heightmap heightmap = new Heightmap(chunk, WrapperUtil.DEFAULT_HEIGHTMAP);
+ for (int x = 0; x < LodUtil.CHUNK_WIDTH && !inTheEnd; x++)
+ {
+ for (int z = 0; z < LodUtil.CHUNK_WIDTH && !inTheEnd; z++)
+ {
+ if (simulateHeight)
+ {
+ // these heights are of course aren't super accurate,
+ // they are just to simulate height data where there isn't any
+ switch (chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
+ {
+ case NETHER:
+ heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
+ break;
+
+ case EXTREME_HILLS:
+ heightmap.setHeight(x, z, seaLevel + 30);
+ break;
+ case MESA:
+ case JUNGLE:
+ heightmap.setHeight(x, z, seaLevel + 20);
+ break;
+ case BEACH:
+ heightmap.setHeight(x, z, seaLevel + 5);
+ break;
+ case NONE:
+ heightmap.setHeight(x, z, 0);
+ break;
+
+ case OCEAN:
+ case RIVER:
+ heightmap.setHeight(x, z, seaLevel);
+ break;
+
+ case THEEND:
+ inTheEnd = true;
+ break;
+
+ // DESERT
+ // FOREST
+ // ICY
+ // MUSHROOM
+ // SAVANNA
+ // SWAMP
+ // TAIGA
+ // PLAINS
+ default:
+ heightmap.setHeight(x, z, seaLevel + 10);
+ break;
+ }// heightmap switch
+ }
+ else
+ {
+ // we aren't simulating height
+ // always use sea level
+ heightmap.setHeight(x, z, seaLevel);
+ }
+ }// z
+ }// x
+
+ chunk.setHeightmap(WrapperUtil.DEFAULT_HEIGHTMAP, heightmap.getRawData());
+
+
+ if (!inTheEnd)
+ {
+ lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(true, true, false));
+ }
+ else
+ {
+ // if we are in the end, don't generate any chunks.
+ // Since we don't know where the islands are, everything
+ // generates the same, and it looks awful.
+ //TODO it appears that 'if' can be collapsed, but comment says that it should not be a case
+ lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(true, true, false));
+ }
+
+
+// long startTime = System.currentTimeMillis();
+// long endTime = System.currentTimeMillis();
+// System.out.println(endTime - startTime);
+ */
+ }
+
+
+ /** takes about 10 - 20 ms */
+ @Override
+ public void generateSurface(AbstractChunkPosWrapper pos)
+ {
+ /*
+ List chunkList = new LinkedList<>();
+ ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY);
+ chunkList.add(chunk);
+ LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
+
+ ServerChunkCache chunkSource = serverWorld.getChunkSource();
+ ThreadedLevelLightEngine lightEngine = (ThreadedLevelLightEngine) serverWorld.getLightEngine();
+ StructureManager templateManager = serverWorld.getStructureManager();
+ ChunkGenerator chunkGen = chunkSource.getGenerator();
+
+
+ // generate the terrain (this is thread safe)
+ ChunkStatus.EMPTY.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
+ // override the chunk status, so we can run the next generator stage
+ chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
+ chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
+ ChunkStatus.NOISE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
+ ChunkStatus.SURFACE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
+
+ // this feature has been proven to be thread safe,
+ // so we will add it
+ IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
+ snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
+
+
+ lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.SURFACE));
+
+ /*TODO if we want to use Biome utils and terrain utils for overworld
+ * lodBuilder.generateLodNodeFromChunk(lodDim, pos ,detailLevel, serverWorld.getSeed());*/
+ }
+
+
+ /**
+ * takes about 15 - 20 ms
+ *
+ * Causes concurrentModification Exceptions,
+ * which could cause instability or world generation bugs
+ */
+ @Override
+ public void generateFeatures(AbstractChunkPosWrapper pos)
+ {
+ /*
+ List chunkList = new LinkedList<>();
+ ProtoChunk chunk = new ProtoChunk(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY);
+ chunkList.add(chunk);
+ LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
+
+ ServerChunkProvider chunkSource = serverWorld.getChunkSource();
+ ServerWorldLightManager lightEngine = (ServerWorldLightManager) serverWorld.getLightEngine();
+ TemplateManager templateManager = serverWorld.getStructureManager();
+ ChunkGenerator chunkGen = chunkSource.generator;
+
+
+ // generate the terrain (this is thread safe)
+ ChunkStatus.EMPTY.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
+ // override the chunk status, so we can run the next generator stage
+ chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
+ chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
+ ChunkStatus.NOISE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
+ ChunkStatus.SURFACE.generate(serverWorld, chunkGen, templateManager, lightEngine, null, chunkList);
+
+
+ // get all the biomes in the chunk
+ HashSet biomes = new HashSet<>();
+ for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++)
+ {
+ for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++)
+ {
+ Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2);
+
+ // Issue #35
+ // For some reason Jungle biomes cause incredible lag
+ // the features here must be interacting with each other
+ // in unpredictable ways (specifically tree feature generation).
+ // When generating Features my CPU usage generally hovers around 30 - 40%
+ // when generating Jungles it spikes to 100%.
+ if (biome.getBiomeCategory() != Biome.BiomeCategory.JUNGLE)
+ {
+ // should probably use the heightmap here instead of seaLevel,
+ // but this seems to get the job done well enough
+ biomes.add(biome);
+ }
+ }
+ }
+
+ boolean allowUnstableFeatures = CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration();
+
+ // generate all the features related to this chunk.
+ // this may or may not be thread safe
+ for (Biome biome : biomes)
+ {
+ List>>> featuresForState = biome.generationSettings.features();
+
+ for (List>> suppliers : featuresForState)
+ {
+ for (Supplier> featureSupplier : suppliers)
+ {
+ ConfiguredFeature, ?> configuredFeature = featureSupplier.get();
+
+ if (!allowUnstableFeatures &&
+ FEATURES_TO_AVOID.containsKey(configuredFeature.hashCode()))
+ continue;
+
+
+ try
+ {
+ configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
+ }
+ catch (ConcurrentModificationException | UnsupportedOperationException e)
+ {
+ // This will happen. I'm not sure what to do about it
+ // except pray that it doesn't affect the normal world generation
+ // in any harmful way.
+ // Update: this can cause crashes and high CPU usage.
+
+ // Issue #35
+ // I tried cloning the config for each feature, but that
+ // path was blocked since I can't clone lambda methods.
+ // I tried using a deep cloning library and discovered
+ // the problem there.
+ // ( https://github.com/kostaskougios/cloning
+ // and
+ // https://github.com/EsotericSoftware/kryo )
+
+ if (!allowUnstableFeatures)
+ FEATURES_TO_AVOID.put(configuredFeature.hashCode(), configuredFeature);
+// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
+ }
+ // This will happen when the LodServerWorld
+ // isn't able to return something that a feature
+ // generator needs
+ catch (Exception e)
+ {
+ // I'm not sure what happened, print to the log
+
+ e.printStackTrace();
+
+ if (!allowUnstableFeatures)
+ FEATURES_TO_AVOID.put(configuredFeature.hashCode(), configuredFeature);
+// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
+ }
+ }
+ }
+ }
+
+ // generate a Lod like normal
+ lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.FEATURES));
+ */
+ }
+
+
+ /**
+ * Generates using MC's ServerWorld.
+ *
+ * on pre generated chunks 0 - 1 ms
+ * on un generated chunks 0 - 50 ms
+ * with the median seeming to hover around 15 - 30 ms
+ * and outliers in the 100 - 200 ms range
+ *
+ * Note this should not be multithreaded and does cause server/simulation lag
+ * (Higher lag for generating than loading)
+ */
+ @Override
+ public void generateFull(AbstractChunkPosWrapper pos)
+ {
+ lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(serverWorld.getChunk(pos.getX(), pos.getZ(), ChunkStatus.FEATURES)), new LodBuilderConfig(DistanceGenerationMode.FULL));
+ }
+
+
+
+
+
+
+
+ /*
+ * performance/generation tests related to
+ * serverWorld.getChunk(x, z, ChunkStatus. *** )
+
+ true/false is whether they generated blocks or not
+ the time is how long it took to generate
+
+ ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
+ ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
+ ChunkStatus.BIOMES 1 - 10 ms false (no height)
+ ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
+ ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
+ ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
+ ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
+ ChunkStatus.FEATURES 7 - 25 ms true
+ ChunkStatus.HEIGHTMAPS 20 - 40 ms true
+ ChunkStatus.LIGHT 20 - 40 ms true
+ ChunkStatus.FULL 30 - 50 ms true
+ ChunkStatus.SPAWN 50 - 80 ms true
+
+
+ At this point I would suggest using FEATURES, as it generates snow and trees
+ (and any other object that are needed to make biomes distinct)
+
+ Otherwise, if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
+ */
+}
diff --git a/fabric/src/main/java/net/minecraftforge/common/WorldWorkerManager.java b/fabric/src/main/java/net/minecraftforge/common/WorldWorkerManager.java
new file mode 100644
index 000000000..8d4eb0257
--- /dev/null
+++ b/fabric/src/main/java/net/minecraftforge/common/WorldWorkerManager.java
@@ -0,0 +1,97 @@
+/*
+ * Minecraft Forge
+ * Copyright (c) 2016-2021.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation version 2.1
+ * of the License.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+package net.minecraftforge.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WorldWorkerManager
+{
+ private static List workers = new ArrayList();
+ private static long startTime = -1;
+ private static int index = 0;
+
+ public static void tick(boolean start)
+ {
+ if (start)
+ {
+ startTime = System.currentTimeMillis();
+ return;
+ }
+
+ index = 0;
+ IWorker task = getNext();
+ if (task == null)
+ return;
+
+ long time = 50 - (System.currentTimeMillis() - startTime);
+ if (time < 10)
+ time = 10; //If ticks are lagging, give us at least 10ms to do something.
+ time += System.currentTimeMillis();
+
+ while (System.currentTimeMillis() < time && task != null)
+ {
+ boolean again = task.doWork();
+
+ if (!task.hasWork())
+ {
+ remove(task);
+ task = getNext();
+ }
+ else if (!again)
+ {
+ task = getNext();
+ }
+ }
+ }
+
+ public static synchronized void addWorker(IWorker worker)
+ {
+ workers.add(worker);
+ }
+
+ private static synchronized IWorker getNext()
+ {
+ return workers.size() > index ? workers.get(index++) : null;
+ }
+
+ private static synchronized void remove(IWorker worker)
+ {
+ workers.remove(worker);
+ index--;
+ }
+
+ //Internal only, used to clear everything when the server shuts down.
+ public static synchronized void clear()
+ {
+ workers.clear();
+ }
+
+ public static interface IWorker
+ {
+ boolean hasWork();
+
+ /**
+ * Perform a task, returning true from this will have the manager call this function again this tick if there is time left.
+ * Returning false will skip calling this worker until next tick.
+ */
+ boolean doWork();
+ }
+}
diff --git a/fabric/src/main/resources/assets/lod/lang/en_us.json b/fabric/src/main/resources/assets/lod/lang/en_us.json
new file mode 100644
index 000000000..b953b9e8e
--- /dev/null
+++ b/fabric/src/main/resources/assets/lod/lang/en_us.json
@@ -0,0 +1,66 @@
+{
+ "lod.title": "Distant Horizons mod",
+ "text.autoconfig.lod.title": "Distant Horizons config",
+ "text.autoconfig.lod.option.client": "Client",
+ "text.autoconfig.lod.option.client.graphics": "Graphics",
+ "text.autoconfig.lod.option.client.graphics.qualityOption": "Quality option",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.drawResolution": "Draw resolution",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.drawResolution.@Tooltip": "What is the maximum detail fake chunks should be drawn at?",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.lodChunkRenderDistance": "Chunk render distance",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.lodChunkRenderDistance.@Tooltip": "The mod's render distance, measured in chunks",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.verticalQuality": "Vertical quality",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.verticalQuality.@Tooltip": "This indicates how detailed fake chunks will represent overhangs, caves, floating islands, ect. \nHigher options will use more memory and increase GPU usage",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.horizontalScale": "Horizontal scale",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.horizontalScale.@Tooltip": "This indicates how quickly fake chunks drop off in quality",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.horizontalQuality": "Horizontal quality",
+ "text.autoconfig.lod.option.client.graphics.qualityOption.horizontalQuality.@Tooltip": "This indicates the exponential base of the quadratic drop-off",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption": "Fog quality option",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption.fogDistance": "Fog distance",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption.fogDistance.@Tooltip": "At what distance should Fog be drawn on the fake chunks?",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption.fogDrawOverride": "Fog draw override",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption.fogDrawOverride.@Tooltip": "When should fog be drawn?",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption.disableVanillaFog": "Disable vanilla fog",
+ "text.autoconfig.lod.option.client.graphics.fogQualityOption.disableVanillaFog.@Tooltip": "If true disable Minecraft's fog. \nMay cause issues with other mods that edit fog. \nMay cause errors with other fog editing mods",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption": "Advanced quality option",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.lodTemplate": "LOD template",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.lodTemplate.@Tooltip": "How should the LODs be drawn? \nNOTE: Currently only CUBIC is implemented!",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.disableDirectionalCulling": "Disable directional culling",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.disableDirectionalCulling.@Tooltip": "If false fake chunks behind the player's camera aren't drawn, increasing performance. \nIf true all LODs are drawn, even those behind the player's camera, decreasing performance",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality": "Always draw at max quality",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.@Tooltip": "Disable quality falloff, all fake chunks will be drawn at the highest available detail level",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.vanillaOverdraw": "Vanilla overdraw",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.vanillaOverdraw.@Tooltip": "How often should LODs be drawn on top of regular chunks? \nHALF and ALWAYS will prevent holes in the world, but may look odd for transparent blocks or in caves.",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.gpuUploadMethod": "GPU upload method",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.gpuUploadMethod.@Tooltip": "What method should be used to upload geometry to the GPU?",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.useExtendedNearClipPlane": "Use extended near clip plane",
+ "text.autoconfig.lod.option.client.graphics.advancedGraphicsOption.useExtendedNearClipPlane.@Tooltip": "Will prevent some overdraw issues, but may cause nearby fake chunks to render incorrectly especially when in/near an ocean",
+ "text.autoconfig.lod.option.client.worldGenerator": "World generator",
+ "text.autoconfig.lod.option.client.worldGenerator.generationPriority": "Generation priority",
+ "text.autoconfig.lod.option.client.worldGenerator.generationPriority.@Tooltip": "What is the priority of the chunks being generated around the player",
+ "text.autoconfig.lod.option.client.worldGenerator.distanceGenerationMode": "Distance generation mode",
+ "text.autoconfig.lod.option.client.worldGenerator.distanceGenerationMode.@Tooltip": "How much of the generation should be used when generating fake chunks",
+ "text.autoconfig.lod.option.client.worldGenerator.allowUnstableFeatureGeneration": "Allow unstable feature generation",
+ "text.autoconfig.lod.option.client.worldGenerator.allowUnstableFeatureGeneration.@Tooltip": "Some features may not be thread safe. \nCould cause instability and crashes",
+ "text.autoconfig.lod.option.client.worldGenerator.blockToAvoid": "Block to avoid",
+ "text.autoconfig.lod.option.client.worldGenerator.blockToAvoid.@Tooltip": "What block to avoid when generating fake chunks",
+ "text.autoconfig.lod.option.client.advancedModOptions": "Advance mod options",
+ "text.autoconfig.lod.option.client.advancedModOptions.threading": "Threading",
+ "text.autoconfig.lod.option.client.advancedModOptions.threading.numberOfWorldGenerationThreads": "NO. of world generation threads",
+ "text.autoconfig.lod.option.client.advancedModOptions.threading.numberOfWorldGenerationThreads.@Tooltip": "This is how many threads are used when generating LODs outside the normal render distance. \nIf you experience stuttering when generating distant LODs, decrease this number. If you want to increase LOD generation speed, increase this number \nCan only be between 1 and your current number of threads",
+ "text.autoconfig.lod.option.client.advancedModOptions.threading.numberOfBufferBuilderThreads": "NO. of buffer builder threads",
+ "text.autoconfig.lod.option.client.advancedModOptions.threading.numberOfBufferBuilderThreads.@Tooltip": "This is how many threads are used when building vertex buffers (The things sent to your GPU to draw the fake chunks) \nCan only be between 1 and your current number of threads",
+ "text.autoconfig.lod.option.client.advancedModOptions.buffers": "Buffers",
+ "text.autoconfig.lod.option.client.advancedModOptions.buffers.rebuildTimes": "Rebuild times",
+ "text.autoconfig.lod.option.client.advancedModOptions.buffers.rebuildTimes.@Tooltip": "Rebuild times",
+ "text.autoconfig.lod.option.client.debug": "Debug",
+ "text.autoconfig.lod.option.client.debug.drawLods": "Draw LOD's",
+ "text.autoconfig.lod.option.client.debug.drawLods.@Tooltip": "If true, the mod is enabled and fake chunks will be drawn.",
+ "text.autoconfig.lod.option.client.debug.debugMode": "Debug mode",
+ "text.autoconfig.lod.option.client.debug.debugMode.@Tooltip": "What type of debug mode do you want",
+ "text.autoconfig.lod.option.client.debug.enableDebugKeybindings": "Enable debug keybindings",
+ "text.autoconfig.lod.option.client.debug.enableDebugKeybindings.@Tooltip": "Enable debug keybindings to change the Debug mode on the fly in game",
+ "toast.lod.title": "Distant Horizons",
+ "key.lod.category": "Distant Horizons",
+ "key.lod.DebugToggle": "Debug toggle",
+ "key.lod.DrawToggle": "Draw toggle"
+}
diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json
new file mode 100644
index 000000000..2f02c4c62
--- /dev/null
+++ b/fabric/src/main/resources/fabric.mod.json
@@ -0,0 +1,57 @@
+{
+ "schemaVersion": 1,
+ "id": "lod",
+ "version": "${version}",
+
+ "name": "Distant Horizons Fabric",
+ "description": "This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost",
+ "authors": [
+ "coolGi2007",
+ "Ran",
+ "Original LOD Creators"
+ ],
+
+ "contact": {
+ "homepage": "https://www.curseforge.com/minecraft/mc-mods/lod-level-of-detail",
+ "sources": "https://gitlab.com/jeseibel/minecraft-lod-mod/-/tree/1.17.1_fabric",
+ "issues": "https://gitlab.com/jeseibel/minecraft-lod-mod/-/issues"
+ },
+
+ "license": "CC0-1.0",
+ "icon": "icon.png",
+
+ "environment": "client",
+ "entrypoints": {
+ "client": [
+ "com.seibel.lod.fabric.Main"
+ ],
+ "modmenu": [
+ "com.seibel.lod.fabric.wrappers.config.ModMenuIntegration"
+ ]
+ },
+
+ "mixins": [
+ "lod.mixins.json"
+ ],
+
+ "accessWidener" : "lod.accesswidener",
+ "depends": {
+ "fabricloader": ">=0.11.3",
+ "fabric": "*",
+ "minecraft": "1.17.x",
+ "java": ">=16",
+ "modmenu": ">=2.0.14",
+ "cloth-config2": ">=5.0.38"
+ },
+ "suggests": {
+ "another-mod": "*"
+ },
+
+ "custom": {
+ "modmenu": {
+ "links": {
+ "modmenu.discord": "https://discord.gg/xAB8G4cENx"
+ }
+ }
+ }
+}
diff --git a/fabric/src/main/resources/lod.mixins.json b/fabric/src/main/resources/lod.mixins.json
new file mode 100644
index 000000000..5ab4b868e
--- /dev/null
+++ b/fabric/src/main/resources/lod.mixins.json
@@ -0,0 +1,18 @@
+{
+ "required": true,
+ "minVersion": "0.8",
+ "package": "com.seibel.lod.fabric.mixins",
+ "compatibilityLevel": "JAVA_16",
+ "mixins": [],
+ "client": [
+ "MixinMinecraft",
+ "MixinWorldRenderer",
+ "events.MixinClientLevel",
+ "events.MixinMinecraft",
+ "events.MixinServerLevel"
+ ],
+ "server": [],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}
diff --git a/forge/build.gradle b/forge/build.gradle
new file mode 100644
index 000000000..8fce6f89b
--- /dev/null
+++ b/forge/build.gradle
@@ -0,0 +1,84 @@
+plugins {
+ id "com.github.johnrengelman.shadow" version "7.0.0"
+}
+
+loom {
+ accessWidenerPath.set(project(":common").file("src/main/resources/lod.accesswidener"))
+
+ forge {
+ convertAccessWideners.set(true)
+ extraAccessWideners.add("lod.accesswidener")
+ mixinConfigs("lod.mixins.json")
+ }
+}
+
+architectury {
+ platformSetupLoomIde()
+ forge()
+}
+
+configurations {
+ common
+ shadowMe // Don't use shadow from the shadow plugin because we don't want IDEA to index this.
+ compileClasspath.extendsFrom common
+ runtimeClasspath.extendsFrom common
+ developmentForge.extendsFrom common
+ implementation.extendsFrom shadowMe
+}
+
+dependencies {
+ forge "net.minecraftforge:forge:${rootProject.minecraft_version}-${rootProject.forge_version}"
+
+ common(project(path: ":common", configuration: "namedElements")) { transitive false }
+ shadowMe(project(path: ":common", configuration: "transformProductionForge")) { transitive = false }
+
+ forgeDependencies('org.tukaani:xz:1.9')
+ forgeDependencies('org.apache.commons:commons-compress:1.21')
+ shadowMe 'org.tukaani:xz:1.9'
+ shadowMe 'org.apache.commons:commons-compress:1.21'
+}
+
+shadowJar {
+ exclude "fabric.mod.json"
+ configurations = [project.configurations.shadowMe]
+ relocate 'org.tukaani', 'shaded.tukaani'
+ relocate 'org.apache.commons.compress', 'shaded.apache.commons.compress'
+
+ classifier "dev-shadow"
+}
+
+remapJar {
+ input.set shadowJar.archiveFile
+ dependsOn shadowJar
+ classifier null
+}
+
+jar {
+ classifier "dev"
+}
+
+sourcesJar {
+ def commonSources = project(":common").sourcesJar
+ dependsOn commonSources
+ from commonSources.archiveFile.map { zipTree(it) }
+}
+
+components.java {
+ withVariantsFromConfiguration(project.configurations.shadowRuntimeElements) {
+ skip()
+ }
+}
+
+publishing {
+ publications {
+ mavenForge(MavenPublication) {
+ artifactId = rootProject.archives_base_name + "-" + project.name
+ from components.java
+ }
+ }
+
+ // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
+ repositories {
+ // Add repositories to publish to here.
+ }
+}
diff --git a/forge/gradle.properties b/forge/gradle.properties
new file mode 100644
index 000000000..32f842a63
--- /dev/null
+++ b/forge/gradle.properties
@@ -0,0 +1 @@
+loom.platform=forge
\ No newline at end of file
diff --git a/src/main/java/com/seibel/lod/LodMain.java b/forge/src/main/java/com/seibel/lod/LodMain.java
similarity index 100%
rename from src/main/java/com/seibel/lod/LodMain.java
rename to forge/src/main/java/com/seibel/lod/LodMain.java
diff --git a/src/main/java/com/seibel/lod/ModInfo.java b/forge/src/main/java/com/seibel/lod/ModInfo.java
similarity index 100%
rename from src/main/java/com/seibel/lod/ModInfo.java
rename to forge/src/main/java/com/seibel/lod/ModInfo.java
diff --git a/src/main/java/com/seibel/lod/forge/ForgeClientProxy.java b/forge/src/main/java/com/seibel/lod/forge/ForgeClientProxy.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/ForgeClientProxy.java
rename to forge/src/main/java/com/seibel/lod/forge/ForgeClientProxy.java
diff --git a/src/main/java/com/seibel/lod/forge/ForgeConfig.java b/forge/src/main/java/com/seibel/lod/forge/ForgeConfig.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/ForgeConfig.java
rename to forge/src/main/java/com/seibel/lod/forge/ForgeConfig.java
diff --git a/src/main/java/com/seibel/lod/forge/ForgeMain.java b/forge/src/main/java/com/seibel/lod/forge/ForgeMain.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/ForgeMain.java
rename to forge/src/main/java/com/seibel/lod/forge/ForgeMain.java
diff --git a/src/main/java/com/seibel/lod/forge/mixins/MixinWorldRenderer.java b/forge/src/main/java/com/seibel/lod/forge/mixins/MixinWorldRenderer.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/mixins/MixinWorldRenderer.java
rename to forge/src/main/java/com/seibel/lod/forge/mixins/MixinWorldRenderer.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/ForgeDependencySetup.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/McObjectConverter.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/McObjectConverter.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/McObjectConverter.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/McObjectConverter.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/WrapperFactory.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/WrapperFactory.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/WrapperFactory.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/WrapperFactory.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/WrapperUtil.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/WrapperUtil.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/WrapperUtil.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/WrapperUtil.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorSingletonWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorSingletonWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorSingletonWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorSingletonWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockColorWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/block/BlockPosWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockPosWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/block/BlockPosWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockPosWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/block/BlockShapeWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockShapeWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/block/BlockShapeWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/block/BlockShapeWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkPosWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkPosWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkPosWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkPosWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/chunk/ChunkWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/config/LodConfigWrapperSingleton.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/config/LodConfigWrapperSingleton.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/config/LodConfigWrapperSingleton.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/config/LodConfigWrapperSingleton.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftRenderWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftRenderWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftRenderWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftRenderWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/minecraft/MinecraftWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/minecraft/ProfilerWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/minecraft/ProfilerWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/minecraft/ProfilerWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/minecraft/ProfilerWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/misc/LightMapWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/misc/LightMapWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/misc/LightMapWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/misc/LightMapWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/world/BiomeColorWrapperSingleton.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/world/BiomeColorWrapperSingleton.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/world/BiomeColorWrapperSingleton.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/world/BiomeColorWrapperSingleton.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/world/BiomeWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/world/BiomeWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/world/BiomeWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/world/BiomeWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/world/DimensionTypeWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/world/DimensionTypeWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/world/DimensionTypeWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/world/DimensionTypeWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/world/WorldWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/world/WorldWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/world/WorldWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/world/WorldWrapper.java
diff --git a/src/main/java/com/seibel/lod/forge/wrappers/worldGeneration/WorldGeneratorWrapper.java b/forge/src/main/java/com/seibel/lod/forge/wrappers/worldGeneration/WorldGeneratorWrapper.java
similarity index 100%
rename from src/main/java/com/seibel/lod/forge/wrappers/worldGeneration/WorldGeneratorWrapper.java
rename to forge/src/main/java/com/seibel/lod/forge/wrappers/worldGeneration/WorldGeneratorWrapper.java
diff --git a/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml
similarity index 93%
rename from src/main/resources/META-INF/mods.toml
rename to forge/src/main/resources/META-INF/mods.toml
index 17a69a251..eb2c699fe 100644
--- a/src/main/resources/META-INF/mods.toml
+++ b/forge/src/main/resources/META-INF/mods.toml
@@ -38,6 +38,9 @@ displayURL="https://www.curseforge.com/minecraft/mc-mods/lod-level-of-detail" #o
#// A file name (in the root of the mod JAR) containing a logo for display
logoFile="logo.png" #optional
+#// A file name (in the root of the mod JAR) containing a icon for display by catalogue
+catalogueImageIcon="icon.png"
+
#// A text field displayed in the mod UI
credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional
@@ -45,4 +48,4 @@ credits="TechnoVision, Vike, and Darkhax for their modding tutorials." #optional
authors="James Seibel, Leonardo Amato, and Cola" #optional
#// The description text for the mod (multi line!) (#mandatory)
-description='''This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost.'''
\ No newline at end of file
+description='''This mod generates and renders simplified terrain beyond the normal view distance, at a low performance cost.'''
diff --git a/src/main/resources/lod.mixins.json b/forge/src/main/resources/lod.mixins.json
similarity index 83%
rename from src/main/resources/lod.mixins.json
rename to forge/src/main/resources/lod.mixins.json
index dd0878cf7..b85560a4e 100644
--- a/src/main/resources/lod.mixins.json
+++ b/forge/src/main/resources/lod.mixins.json
@@ -4,7 +4,7 @@
"compatibilityLevel": "JAVA_8",
"refmap": "lod.refmap.json",
"mixins": [
- "MixinWorldRenderer"
+ "MixinWorldRenderer"
],
"minVersion": "0.8"
}
\ No newline at end of file
diff --git a/src/main/resources/pack.mcmeta b/forge/src/main/resources/pack.mcmeta
similarity index 100%
rename from src/main/resources/pack.mcmeta
rename to forge/src/main/resources/pack.mcmeta
diff --git a/gradle.properties b/gradle.properties
index 878bf1f7e..47e95e4e9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,12 @@
-# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
-# This is required to provide enough memory for the Minecraft decompilation process.
-org.gradle.jvmargs=-Xmx3G
-org.gradle.daemon=false
\ No newline at end of file
+org.gradle.jvmargs=-Xmx2048M
+
+minecraft_version=1.17.1
+
+archives_base_name=Distant-Horizons_1.17.1
+mod_version=1.5.3a
+maven_group=com.seibel.lod
+
+fabric_loader_version=0.11.6
+fabric_api_version=0.37.1+1.17
+
+forge_version=37.0.69
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ffed3a254..69a971507 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index cccdd3d51..744e882ed 100644
--- a/gradlew
+++ b/gradlew
@@ -1,5 +1,21 @@
#!/usr/bin/env sh
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
##############################################################################
##
## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
@@ -56,7 +72,7 @@ case "`uname`" in
Darwin* )
darwin=true
;;
- MINGW* )
+ MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
@@ -66,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -109,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -138,19 +156,19 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -159,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index f9553162f..107acd32c 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 000000000..d5b410663
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,14 @@
+pluginManagement {
+ repositories {
+ maven { url "https://maven.fabricmc.net/" }
+ maven { url "https://maven.architectury.dev/" }
+ maven { url "https://maven.minecraftforge.net/" }
+ gradlePluginPortal()
+ }
+}
+
+include("common")
+include("fabric")
+include("forge")
+
+rootProject.name = "DistantHorizons-Architectury"
diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg
deleted file mode 100644
index 32a31049f..000000000
--- a/src/main/resources/META-INF/accesstransformer.cfg
+++ /dev/null
@@ -1,93 +0,0 @@
-# Note: to update code in eclipse run the "eclipse" command in graldew
-
-
-## used when creating the projection matrix
-#public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D # getFOVModifier
-#public net.minecraft.client.renderer.GameRenderer field_78529_t # rendererUpdateCount
-#public net.minecraft.client.renderer.GameRenderer func_228380_a_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # hurtCameraEffect
-#public net.minecraft.client.renderer.GameRenderer func_228383_b_(Lcom/mojang/blaze3d/matrix/MatrixStack;F)V # applyBobbing
-#
-## used when accessing built byteBuffers
-#public net.minecraft.client.renderer.BufferBuilder field_179001_a # byteBuffer
-#
-## used when generating LodChunks
-#public net.minecraft.block.AbstractBlock$AbstractBlockState field_235704_h_ # materialColor
-#
-## used when determining which chunks Vanilla Minecraft is going to render
-#public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer
-#public net.minecraft.client.renderer.WorldRenderer field_72755_R # renderInfos
-#public net.minecraft.client.renderer.WorldRenderer$LocalRenderInformationContainer field_178036_a # renderChunk
-#
-## used in world generation
-#public net.minecraft.world.server.ServerWorld field_241106_P_ # structuremanager
-#public net.minecraft.world.gen.Heightmap func_202267_b(II)I # getDataArrayIndex
-#public net.minecraft.world.gen.Heightmap func_202272_a(III)V # set
-#public net.minecraft.world.chunk.Chunk field_76634_f # heightMap
-#public net.minecraft.world.chunk.Chunk field_76652_q # sections
-#public net.minecraft.world.chunk.ChunkPrimer field_201661_i # sections
-#public net.minecraft.world.server.ChunkManager field_219269_w # templateManager
-#public net.minecraft.world.server.ChunkManager field_219256_j # lightManager
-#public net.minecraft.world.gen.feature.template.TemplateManager field_186240_a # templates
-#public net.minecraft.world.biome.Biome field_242424_k # biomeGenerationSettings
-#public net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider field_227406_b_ # weightedStates
-#public net.minecraft.world.gen.placement.ConfiguredPlacement field_215096_a # decorator
-#public net.minecraft.world.gen.placement.ConfiguredPlacement field_215097_b # config
-#public net.minecraft.util.WeightedList field_220658_a # weightedEntries
-#public net.minecraft.world.gen.feature.FeatureSpread field_242250_b # base
-#public net.minecraft.world.gen.feature.FeatureSpread field_242251_c # spread
-#public net.minecraft.world.gen.feature.ConfiguredFeature #func_242765_a(Lnet/minecraft/world/ISeedReader;Lnet/minecraft/world/gen/ChunkGenerator;Ljava/util/Random;Lnet/minecraft/util/math/BlockPos;)Z # place
-#public net.minecraft.world.server.ServerChunkProvider field_217244_j # dataStorage
-#public net.minecraft.world.lighting.WorldLightManager field_215576_a # blockEngine
-#public net.minecraft.world.lighting.WorldLightManager field_215577_b # skyEngine
-#public net.minecraft.world.gen.feature.Feature field_236290_a_ # configuredCodec
-#
-## used for uploading vertex buffers off the render thread
-#public net.minecraft.client.renderer.vertex.VertexBuffer field_177365_a # id
-#public net.minecraft.client.renderer.vertex.VertexBuffer field_177363_b # format
-#public net.minecraft.client.renderer.vertex.VertexBuffer field_177364_c # vertexCount
-#
-## used for accessing the lightmap
-#public net.minecraft.client.renderer.LightTexture field_205111_b # lightPixels
-
-
-
-# used when determining where to save files too
-public net.minecraft.world.level.storage.DimensionDataStorage f_78146_ # dataFolder
-
-# used when rendering
-public com.mojang.blaze3d.vertex.VertexBuffer f_166863_ # indexCount
-public com.mojang.blaze3d.vertex.VertexBuffer f_166859_ # vertextBufferId
-public net.minecraft.client.renderer.GameRenderer m_109141_(Lnet/minecraft/client/Camera;FZ)D # getFov
-
-# pre-render setup
-public net.minecraft.client.renderer.LevelRenderer f_109467_ # renderChunks
-public net.minecraft.client.renderer.LevelRenderer$RenderChunkInfo
-public net.minecraft.client.renderer.LevelRenderer$RenderChunkInfo f_109839_ # chunk
-
-# lighting
-public net.minecraft.client.renderer.LightTexture f_109871_ # lightPixels
-public net.minecraft.world.level.lighting.LevelLightEngine f_75802_ # blockEngine
-public net.minecraft.world.level.lighting.LevelLightEngine f_75803_ # skyEngine
-
-# world generation
-public net.minecraft.world.level.levelgen.Heightmap m_64245_(III)V # setHeight
-
-
-
-#=====================#
-# Examples from Forge #
-#=====================#
-
-# Makes public the IScreenFactory class in ScreenManager
-# public net.minecraft.client.gui.ScreenManager$IScreenFactory
-
-# Makes protected and removes the final modifier from 'random' in MinecraftServer
-# protected-f net.minecraft.server.MinecraftServer field_147146_q #random
-
-# Makes public the 'createNamedService' method in Util,
-# accepting a String and returns an ExecutorService
-# public net.minecraft.util.Util func_240979_a_(Ljava/lang/String;)Ljava/util/concurrent/ExecutorService; #createNamedService
-
-# Makes public the 'func_239776_a_' method in UUIDCodec,
-# accepting two longs and returning an int[]
-# public net.minecraft.util.UUIDCodec func_239776_a_(JJ)[I #func_239776_a_
\ No newline at end of file