Add DH-Core

This commit is contained in:
James Seibel
2021-11-22 19:24:33 -06:00
parent 48741a5364
commit faf38ef8e3
89 changed files with 2600 additions and 12590 deletions
+6
View File
@@ -0,0 +1,6 @@
[submodule "distant-horizons-core"]
path = distant-horizons-core
url = git@gitlab.com:jeseibel/distant-horizons-core.git
[submodule "core"]
path = core
url = git@gitlab.com:jeseibel/distant-horizons-core.git
+11
View File
@@ -228,3 +228,14 @@ publishing {
mixin { mixin {
add sourceSets.main, "lod.refmap.json" add sourceSets.main, "lod.refmap.json"
} }
// add Distant Horizon's Core source folders
sourceSets {
main {
java {
srcDirs("core/src/main/java");
srcDirs("core/src/main/resources")
}
}
}
Submodule
+1
Submodule core added at 0bc96a98cf
+7 -5
View File
@@ -19,8 +19,9 @@
package com.seibel.lod; package com.seibel.lod;
import com.seibel.lod.config.LodConfig; import com.seibel.lod.forge.ForgeClientProxy;
import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.forge.ForgeConfig;
import com.seibel.lod.forge.wrappers.ForgeDependencySetup;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
@@ -46,12 +47,13 @@ public class LodMain
{ {
public static LodMain instance; public static LodMain instance;
public static ClientProxy client_proxy; public static ForgeClientProxy client_proxy;
private void init(final FMLCommonSetupEvent event) private void init(final FMLCommonSetupEvent event)
{ {
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.CLIENT_SPEC); ForgeDependencySetup.createInitialBindings();
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ForgeConfig.CLIENT_SPEC);
} }
@@ -67,7 +69,7 @@ public class LodMain
private void onClientStart(final FMLClientSetupEvent event) private void onClientStart(final FMLClientSetupEvent event)
{ {
client_proxy = new ClientProxy(); client_proxy = new ForgeClientProxy();
MinecraftForge.EVENT_BUS.register(client_proxy); MinecraftForge.EVENT_BUS.register(client_proxy);
} }
@@ -1,998 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.GlProxyContext;
import com.seibel.lod.enums.GpuUploadMethod;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.PosToRenderContainer;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.Direction;
import net.minecraft.world.level.LightLayer;
/**
* This object is used to create NearFarBuffer objects.
* @author James Seibel
* @version 11-9-2021
*/
public class LodBufferBuilder
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** The thread used to generate new LODs off the main thread. */
public static final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilder.class.getSimpleName() + " - main"));
/** The threads used to generate buffers. */
public static final ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfBufferBuilderThreads.get(), new ThreadFactoryBuilder().setNameFormat("Buffer-Builder-%d").build());
/**
* When uploading to a buffer that is too small,
* recreate it this many times bigger than the upload payload
*/
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.5;
/**
* When buffers are first created they are allocated to this size (in Bytes).
* This size will be too small, more than likely. The buffers will be expanded
* when need be to fit the larger sizes.
*/
public static final int DEFAULT_MEMORY_ALLOCATION = 1024;
// TODO this should be moved to LodUtil
public static int skyLightPlayer = 15;
/**
* How many buffers there are for the given region. <Br>
* This is done because some regions may require more memory than
* can be directly allocated, so we split the regions into smaller sections. <Br>
* This keeps track of those sections.
*/
public volatile int[][] numberOfBuffersPerRegion;
/** Stores the vertices when building the VBOs */
public volatile BufferBuilder[][][] buildableBuffers;
/** The OpenGL IDs of the storage buffers used by the buildableVbos */
public int[][][] buildableStorageBufferIds;
/** The OpenGL IDs of the storage buffers used by the drawableVbos */
public int[][][] drawableStorageBufferIds;
/** Used when building new VBOs */
public volatile VertexBuffer[][][] buildableVbos;
/** VBOs that are sent over to the LodNodeRenderer */
public volatile VertexBuffer[][][] drawableVbos;
/**
* if this is true the LOD buffers are currently being
* regenerated.
*/
public boolean generatingBuffers = false;
/**
* if this is true new LOD buffers have been generated
* and are waiting to be swapped with the drawable buffers
*/
private boolean switchVbos = false;
/** Size of the buffer builders in bytes last time we created them */
public int previousBufferSize = 0;
/** Width of the dimension in regions last time we created the buffers */
public int previousRegionWidth = 0;
/** this is used to prevent multiple threads creating, destroying, or using the buffers at the same time */
private final ReentrantLock bufferLock = new ReentrantLock();
private volatile Box[][] boxCache;
private volatile PosToRenderContainer[][] setsToRender;
private volatile RegionPos center;
/**
* This is the ChunkPosWrapper the player was at the last time the buffers were built.
* IE the center of the buffers last time they were built
*/
private volatile ChunkPosWrapper drawableCenterChunkPos = new ChunkPosWrapper(0, 0);
private volatile ChunkPosWrapper buildableCenterChunkPos = new ChunkPosWrapper(0, 0);
public LodBufferBuilder()
{
}
/**
* Create a thread to asynchronously generate LOD buffers
* centered around the given camera X and Z.
* <br>
* This method will write to the drawable near and far buffers.
* <br>
* After the buildable buffers have been generated they must be
* swapped with the drawable buffers in the LodRenderer to be drawn.
*/
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
BlockPosWrapper playerBlockPos, boolean fullRegen)
{
// only allow one generation process to happen at a time
if (generatingBuffers)
return;
if (buildableBuffers == null)
// setupBuffers hasn't been called yet
return;
generatingBuffers = true;
Thread thread = new Thread(() -> generateLodBuffersThread(renderer, lodDim, playerBlockPos, fullRegen));
mainGenThread.execute(thread);
}
// this was pulled out as a separate method so that it could be
// more easily edited by hot swapping. Because, As far as James is aware
// you can't hot swap lambda expressions.
private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim,
BlockPosWrapper playerBlockPos, boolean fullRegen)
{
bufferLock.lock();
try
{
// round the player's block position down to the nearest chunk BlockPos
ChunkPosWrapper playerChunkPos = new ChunkPosWrapper(playerBlockPos);
BlockPosWrapper playerBlockPosRounded = playerChunkPos.getWorldPosition();
long startTime = System.currentTimeMillis();
ArrayList<Callable<Boolean>> nodeToRenderThreads = new ArrayList<>(lodDim.getWidth() * lodDim.getWidth());
startBuffers(fullRegen, lodDim);
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
if (center == null)
center = playerRegionPos;
if (setsToRender == null)
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (setsToRender.length != lodDim.getWidth())
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache == null)
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
if (boxCache.length != lodDim.getWidth())
boxCache = new Box[lodDim.getWidth()][lodDim.getWidth()];
// this will be the center of the VBOs once they have been built
buildableCenterChunkPos = playerChunkPos;
//================================//
// create the nodeToRenderThreads //
//================================//
ClientLevel world = mc.getClientLevel();
skyLightPlayer = world.getBrightness(LightLayer.SKY, playerBlockPos.getBlockPos());
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
{
for (int zRegion = 0; zRegion < lodDim.getWidth(); zRegion++)
{
if (lodDim.doesRegionNeedBufferRegen(xRegion, zRegion) || fullRegen)
{
RegionPos regionPos = new RegionPos(
xRegion + lodDim.getCenterRegionPosX() - lodDim.getWidth() / 2,
zRegion + lodDim.getCenterRegionPosZ() - lodDim.getWidth() / 2);
// local position in the vbo and bufferBuilder arrays
BufferBuilder[] currentBuffers = buildableBuffers[xRegion][zRegion];
LodRegion region = lodDim.getRegion(regionPos.x, regionPos.z);
if (region == null)
continue;
// make sure the buffers weren't
// changed while we were running this method
if (currentBuffers == null || !currentBuffers[0].building())
return;
byte minDetail = region.getMinDetailLevel();
final int xR = xRegion;
final int zR = zRegion;
//we create the Callable to use for the buffer builder creation
Callable<Boolean> dataToRenderThread = () ->
{
//Variable initialization
byte detailLevel;
int posX;
int posZ;
int xAdj;
int zAdj;
int bufferIndex;
boolean posNotInPlayerChunk;
boolean adjPosInPlayerChunk;
Box box = ThreadMapUtil.getBox();
boolean[] adjShadeDisabled = ThreadMapUtil.getAdjShadeDisabledArray();
// determine how many LODs we can stack vertically
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte) 0);
//we get or create the map that will contain the adj data
Map<Direction, long[]> adjData = ThreadMapUtil.getAdjDataArray(maxVerticalData);
//previous setToRender cache
if (setsToRender[xR][zR] == null)
setsToRender[xR][zR] = new PosToRenderContainer(minDetail, regionPos.x, regionPos.z);
//We ask the lod dimension which block we have to render given the player position
PosToRenderContainer posToRender = setsToRender[xR][zR];
posToRender.clear(minDetail, regionPos.x, regionPos.z);
lodDim.getPosToRender(
posToRender,
regionPos,
playerBlockPosRounded.getX(),
playerBlockPosRounded.getZ());
// keep a local version, so we don't have to worry about indexOutOfBounds Exceptions
// if it changes in the LodRenderer while we are working here
boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks;
short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1);
for (int index = 0; index < posToRender.getNumberOfPos(); index++)
{
bufferIndex = index % currentBuffers.length;
detailLevel = posToRender.getNthDetailLevel(index);
posX = posToRender.getNthPosX(index);
posZ = posToRender.getNthPosZ(index);
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.getX();
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.getZ();
//We don't want to render this fake block if
//The block is inside the render distance with, is not bigger than a chunk and is positioned in a chunk set as vanilla rendered
//
//The block is in the player chunk or in a chunk adjacent to the player
if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance))
{
continue;
}
//we check if the block to render is not in player chunk
posNotInPlayerChunk = !(chunkXdist == 0 && chunkZdist == 0);
// We extract the adj data in the four cardinal direction
// we first reset the adjShadeDisabled. This is used to disable the shade on the border when we have transparent block like water or glass
// to avoid having a "darker border" underground
Arrays.fill(adjShadeDisabled, false);
//We check every adj block in each direction
for (Direction direction : Box.ADJ_DIRECTIONS)
{
xAdj = posX + Box.DIRECTION_NORMAL_MAP.get(direction).getX();
zAdj = posZ + Box.DIRECTION_NORMAL_MAP.get(direction).getZ();
long data;
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.getX();
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.getZ();
adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0);
//If the adj block is rendered in the same region and with same detail
// and is positioned in a place that is not going to be rendered by vanilla game
// then we can set this position as adj
// We avoid cases where the adjPosition is in player chunk while the position is not
// to always have a wall underwater
if(posToRender.contains(detailLevel, xAdj, zAdj)
&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance)
&& !(posNotInPlayerChunk && adjPosInPlayerChunk))
{
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
{
data = lodDim.getData(detailLevel, xAdj, zAdj, verticalIndex);
adjShadeDisabled[Box.DIRECTION_INDEX.get(direction)] = false;
adjData.get(direction)[verticalIndex] = data;
}
}
else
{
//Other wise we check if this position is
data = lodDim.getSingleData(detailLevel, xAdj, zAdj);
adjData.get(direction)[0] = DataPointUtil.EMPTY_DATA;
if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk))
&& !DataPointUtil.isVoid(data))
{
adjShadeDisabled[Box.DIRECTION_INDEX.get(direction)] = DataPointUtil.getAlpha(data) < 255;
}
}
}
// We render every vertical lod present in this position
// We only stop when we find a block that is void or non existing block
long data;
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ); verticalIndex++)
{
//we get the above block as adj UP
if (verticalIndex > 0)
{
adjData.get(Direction.UP)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex - 1);
}
else
{
adjData.get(Direction.UP)[0] = DataPointUtil.EMPTY_DATA;
}
//we get the below block as adj DOWN
if (verticalIndex < lodDim.getMaxVerticalData(detailLevel, posX, posZ) - 1)
{
adjData.get(Direction.DOWN)[0] = lodDim.getData(detailLevel, posX, posZ, verticalIndex + 1);
}
else
{
adjData.get(Direction.DOWN)[0] = DataPointUtil.EMPTY_DATA;
}
//We extract the data to render
data = lodDim.getData(detailLevel, posX, posZ, verticalIndex);
//If the data is not renderable (Void or non existing) we stop since there is no data left in this position
if (DataPointUtil.isVoid(data) || !DataPointUtil.doesItExist(data))
break;
//We send the call to create the vertices
LodConfig.CLIENT.graphics.advancedGraphicsOption.lodTemplate.get().template.addLodToBuffer(currentBuffers[bufferIndex], playerBlockPosRounded, data, adjData,
detailLevel, posX, posZ, box, renderer.previousDebugMode, adjShadeDisabled);
}
} // for pos to in list to render
// the thread executed successfully
return true;
};
nodeToRenderThreads.add(dataToRenderThread);
}
} // region z
} // region z
long executeStart = System.currentTimeMillis();
// wait for all threads to finish
List<Future<Boolean>> futuresBuffer = bufferBuilderThreads.invokeAll(nodeToRenderThreads);
for (Future<Boolean> future : futuresBuffer)
{
// the future will be false if its thread failed
if (!future.get())
{
ClientProxy.LOGGER.warn("LodBufferBuilder ran into trouble and had to start over.");
break;
}
}
long executeEnd = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
@SuppressWarnings("unused")
long buildTime = endTime - startTime;
@SuppressWarnings("unused")
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)
{
ClientProxy.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)
{
ClientProxy.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, ChunkPosWrapper 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 (LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.get() == 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
boolean vanillaRenderedPosition = gameChunkRenderDistance >= Math.abs(chunkXdist)
&& gameChunkRenderDistance >= Math.abs(chunkZdist)
&& detailLevel <= LodUtil.CHUNK_DETAIL_LEVEL
&& vanillaRenderedChunks[chunkXdist + gameChunkRenderDistance + 1][chunkZdist + gameChunkRenderDistance + 1];
return (vanillaRenderedPosition && (!(isItBorderPos)));
}
//===============================//
// BufferBuilder related methods //
//===============================//
/**
* Called from the LodRenderer to create the
* BufferBuilders. <br><br>
* <p>
* 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 BufferBuilder[numbRegionsWide][numbRegionsWide][];
buildableVbos = new VertexBuffer[numbRegionsWide][numbRegionsWide][];
drawableVbos = new VertexBuffer[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) Math.ceil(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 BufferBuilder[numberOfBuffers];
buildableVbos[x][z] = new VertexBuffer[numberOfBuffers];
drawableVbos[x][z] = new VertexBuffer[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 BufferBuilder[1];
buildableVbos[x][z] = new VertexBuffer[1];
drawableVbos[x][z] = new VertexBuffer[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 BufferBuilder((int) regionMemoryRequired);
buildableVbos[x][z][i] = new VertexBuffer();
drawableVbos[x][z][i] = new VertexBuffer();
// create the initial mapped buffers (system memory)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].vertextBufferId);
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].vertextBufferId);
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 <br>
* and destroys any bound OpenGL objects. <br><br>
* <p>
* 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];
// Send this over to the render thread, if this is being
// called we aren't worried about stuttering anyway.
// This way we don't have to worry about what context this
// was called from (if any).
RenderSystem.recordRenderCall(() ->
{
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].vertextBufferId;
else
buildableId = 0;
if (drawableVbos[i][j][k] != null)
drawableId = drawableVbos[i][j][k].vertextBufferId;
else
drawableId = 0;
RenderSystem.recordRenderCall(() ->
{
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(VertexFormat.Mode.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 = LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.get();
if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
{
// if buffer storage isn't supported
// default to SUB_DATA
LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.set(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].popNextBuffer().getSecond();
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.
ClientProxy.LOGGER.error(LodBufferBuilder.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(VertexBuffer vbo, int storageBufferId, ByteBuffer uploadBuffer,
boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
{
// this shouldn't happen, but just to be safe
if (vbo.vertextBufferId != -1 && GlProxy.getInstance().getGlContext() == GlProxyContext.LOD_BUILDER)
{
// this is how many points will be rendered
vbo.indexCount = (uploadBuffer.capacity() / (Float.BYTES * 3) + (Byte.BYTES * 4)); // TODO make this change with the LodTemplate
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.vertextBufferId);
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.vertextBufferId, storageBufferId, 0, 0, uploadBuffer.capacity());
}
}
else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
{
// no stuttering but high GPU usage
// stores everything in system memory instead of GPU memory
// making rendering much slower.
// Unless the user is running integrated graphics,
// in that case this will actually work better than SUB_DATA.
ByteBuffer vboBuffer = null;
// 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
{
// 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)
{
ClientProxy.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())
{
VertexBuffer[][][] 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 VertexBuffer[][][] vbos;
public final int[][][] storageBufferIds;
public final ChunkPosWrapper drawableCenterChunkPos;
public VertexBuffersAndOffset(VertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, ChunkPosWrapper 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;
}
}
@@ -1,53 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.core.Direction;
/**
* This is the abstract class used to create different
* BufferBuilders.
* @author James Seibel
* @version 10-10-2021
*/
public abstract class AbstractLodTemplate
{
/** Uploads the given LOD to the buffer. */
public abstract void addLodToBuffer(BufferBuilder buffer, BlockPosWrapper bufferCenterBlockPos, long data, Map<Direction, long[]> 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(BufferBuilder buffer,
double x, double y, double 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();
}
}
@@ -1,616 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.core.Direction;
import net.minecraft.core.Vec3i;
/**
* Similar to Minecraft's AxisAlignedBoundingBox.
* @author Leonardo Amato
* @version 10-2-2021
*/
public class Box
{
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 Direction[] DIRECTIONS = new Direction[] {
Direction.UP,
Direction.DOWN,
Direction.WEST,
Direction.EAST,
Direction.NORTH,
Direction.SOUTH };
/** North, South, East, West */
public static final Direction[] ADJ_DIRECTIONS = new Direction[] {
Direction.EAST,
Direction.WEST,
Direction.SOUTH,
Direction.NORTH };
/** All the faces and vertices of a cube. This is used to extract the vertex from the column */
public static final Map<Direction, int[][]> DIRECTION_VERTEX_MAP = new HashMap<Direction, int[][]>()
{{
put(Direction.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(Direction.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(Direction.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(Direction.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(Direction.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(Direction.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
*/
public static final Map<Direction, int[]> FACE_DIRECTION = new HashMap<Direction, int[]>()
{{
put(Direction.UP, new int[] { Y, MAX });
put(Direction.DOWN, new int[] { Y, MIN });
put(Direction.EAST, new int[] { X, MAX });
put(Direction.WEST, new int[] { X, MIN });
put(Direction.SOUTH, new int[] { Z, MAX });
put(Direction.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
*/
public static final Map<Direction, Vec3i> DIRECTION_NORMAL_MAP = new HashMap<Direction, Vec3i>()
{{
put(Direction.UP, Direction.UP.getNormal());
put(Direction.DOWN, Direction.DOWN.getNormal());
put(Direction.EAST, Direction.EAST.getNormal());
put(Direction.WEST, Direction.WEST.getNormal());
put(Direction.SOUTH, Direction.SOUTH.getNormal());
put(Direction.NORTH, Direction.NORTH.getNormal());
}};
/** We use this index for all array that are going to */
public static final Map<Direction, Integer> DIRECTION_INDEX = new HashMap<Direction, Integer>()
{{
put(Direction.UP, 0);
put(Direction.DOWN, 1);
put(Direction.EAST, 2);
put(Direction.WEST, 3);
put(Direction.SOUTH, 4);
put(Direction.NORTH, 5);
}};
public static final Map<Direction, Integer> ADJ_DIRECTION_INDEX = new HashMap<Direction, Integer>()
{{
put(Direction.EAST, 0);
put(Direction.WEST, 1);
put(Direction.SOUTH, 2);
put(Direction.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<Direction, int[]> adjHeight;
public final Map<Direction, int[]> adjDepth;
public final Map<Direction, byte[]> skyLights;
public byte blockLight;
/** Holds if the given direction should be culled or not */
public final boolean[] culling;
/** creates an empty box */
public Box()
{
boxOffset = new int[3];
boxWidth = new int[3];
colorMap = new int[6];
skyLights = new HashMap<Direction, byte[]>()
{{
put(Direction.UP, new byte[1]);
put(Direction.DOWN, new byte[1]);
put(Direction.EAST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new byte[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjHeight = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
}};
adjDepth = new HashMap<Direction, int[]>()
{{
put(Direction.EAST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.WEST, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
put(Direction.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(Direction.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 (Direction direction : DIRECTIONS)
{
if (!adjShadeDisabled[DIRECTION_INDEX.get(direction)])
colorMap[DIRECTION_INDEX.get(direction)] = ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientLevel().getShade(direction, true));
else
colorMap[DIRECTION_INDEX.get(direction)] = color;
}
}
/**
* @param direction of the face of which we want to get the color
* @return color of the face
*/
public int getColor(Direction direction)
{
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() != DebugMode.SHOW_DETAIL)
return colorMap[DIRECTION_INDEX.get(direction)];
else
return ColorUtil.applyShade(color, MinecraftWrapper.INSTANCE.getClientLevel().getShade(direction, true));
}
/**
*/
public byte getSkyLight(Direction direction, int verticalIndex)
{
if(direction == Direction.UP || direction == Direction.DOWN)
return skyLights.get(direction)[0];
else
return skyLights.get(direction)[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 (Direction direction : ADJ_DIRECTIONS)
{
for (int i = 0; i < adjHeight.get(direction).length; i++)
{
adjHeight.get(direction)[i] = VOID_FACE;
adjDepth.get(direction)[i] = VOID_FACE;
skyLights.get(direction)[i] = 0;
}
}
}
/** determine which faces should be culled */
public void setUpCulling(int cullingDistance, BlockPosWrapper playerPos)
{
for (Direction direction : DIRECTIONS)
{
if (direction == Direction.DOWN || direction == Direction.WEST || direction == Direction.NORTH)
culling[DIRECTION_INDEX.get(direction)] = playerPos.get(direction.getAxis()) > getFacePos(direction) + cullingDistance;
else if (direction == Direction.UP || direction == Direction.EAST || direction == Direction.SOUTH)
culling[DIRECTION_INDEX.get(direction)] = playerPos.get(direction.getAxis()) < getFacePos(direction) - cullingDistance;
culling[DIRECTION_INDEX.get(direction)] = false;
}
}
/**
* @param direction 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(Direction direction)
{
return culling[DIRECTION_INDEX.get(direction)];
}
/**
* 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<Direction, long[]> 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(Direction.DOWN)[0];
if(DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(Direction.DOWN)[0] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(Direction.DOWN)[0] = skyLights.get(Direction.UP)[0];
//other sided
//TODO clean some similar cases
for (Direction direction : ADJ_DIRECTIONS)
{
if (isCulled(direction))
continue;
long[] dataPoint = adjData.get(direction);
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
{
adjHeight.get(direction)[0] = maxY;
adjDepth.get(direction)[0] = minY;
adjHeight.get(direction)[1] = VOID_FACE;
adjDepth.get(direction)[1] = VOID_FACE;
skyLights.get(direction)[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(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(direction)[faceToDraw] = getMinY();
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
}
faceToDraw++;
toFinish = false;
// break since all the other data will be lower
break;
}
else if (depth <= minY && height >= maxY)
{
// the adj data is inside the current data
// don't draw the face
adjHeight.get(direction)[0] = VOID_FACE;
adjDepth.get(direction)[0] = VOID_FACE;
break;
}
else if (depth <= minY)//&& 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(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = height;
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
}
else
{
adjDepth.get(direction)[faceToDraw] = height;
skyLights.get(direction)[faceToDraw] = (byte) 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(direction)[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(direction)[0] = getMaxY();
}
adjDepth.get(direction)[faceToDraw] = height;
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
faceToDraw++;
adjHeight.get(direction)[faceToDraw] = depth;
firstFace = false;
toFinish = true;
toFinishIndex = i + 1;
}
}
}
if (allAbove)
{
adjHeight.get(direction)[0] = getMaxY();
adjDepth.get(direction)[0] = getMinY();
skyLights.get(direction)[0] = skyLights.get(Direction.UP)[0];
faceToDraw++;
}
else if (toFinish)
{
adjDepth.get(direction)[faceToDraw] = minY;
if(toFinishIndex < dataPoint.length)
{
singleAdjDataPoint = dataPoint[toFinishIndex];
if (DataPointUtil.doesItExist(singleAdjDataPoint))
skyLights.get(direction)[faceToDraw] = (byte) DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
else
skyLights.get(direction)[faceToDraw] = skyLights.get(Direction.UP)[0];
}
faceToDraw++;
}
adjHeight.get(direction)[faceToDraw] = VOID_FACE;
adjDepth.get(direction)[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 direction that we want to check
* @return position in the axis of the face
*/
public int getFacePos(Direction direction)
{
return boxOffset[FACE_DIRECTION.get(direction)[0]] + boxWidth[FACE_DIRECTION.get(direction)[0]] * FACE_DIRECTION.get(direction)[1];
}
/**
* returns true if the given direction should be rendered.
*/
public boolean shouldRenderFace(Direction direction, int adjIndex)
{
if (direction == Direction.UP || direction == Direction.DOWN)
return adjIndex == 0;
return !(adjHeight.get(direction)[adjIndex] == VOID_FACE && adjDepth.get(direction)[adjIndex] == VOID_FACE);
}
/**
* @param direction 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(Direction direction, int vertexIndex)
{
return boxOffset[X] + boxWidth[X] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][X];
}
/**
* @param direction 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(Direction direction, int vertexIndex)
{
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
}
/**
* @param direction 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(Direction direction, int vertexIndex, int adjIndex)
{
if (direction == Direction.DOWN || direction == Direction.UP)
return boxOffset[Y] + boxWidth[Y] * DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y];
else
{
// this could probably be cleaned up more,
// but it still works
if (1 - DIRECTION_VERTEX_MAP.get(direction)[vertexIndex][Y] == ADJACENT_HEIGHT_INDEX)
return adjHeight.get(direction)[adjIndex];
else
return adjDepth.get(direction)[adjIndex];
}
}
/**
* @param direction 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(Direction direction, int vertexIndex)
{
return boxOffset[Z] + boxWidth[Z] * DIRECTION_VERTEX_MAP.get(direction)[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];
}
}
@@ -1,142 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.core.Direction;
/**
* Builds LODs as rectangular prisms.
* @author James Seibel
* @version 11-8-2021
*/
public class CubicLodTemplate extends AbstractLodTemplate
{
public CubicLodTemplate()
{
}
@Override
public void addLodToBuffer(BufferBuilder buffer, BlockPosWrapper bufferCenterBlockPos, long data, Map<Direction, long[]> 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,
BlockPosWrapper bufferCenterBlockPos,
Map<Direction, long[]> 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(BufferBuilder buffer, Box box)
{
int color;
int skyLight;
int blockLight;
for (Direction direction : Box.DIRECTIONS)
{
if(box.isCulled(direction))
continue;
int verticalFaceIndex = 0;
while (box.shouldRenderFace(direction, verticalFaceIndex))
{
for (int vertexIndex = 0; vertexIndex < 6; vertexIndex++)
{
color = box.getColor(direction);
skyLight = box.getSkyLight(direction, verticalFaceIndex);
blockLight = box.getBlockLight();
color = ColorUtil.applyLightValue(color, skyLight, blockLight);
addPosAndColor(buffer,
box.getX(direction, vertexIndex),
box.getY(direction, vertexIndex, verticalFaceIndex),
box.getZ(direction, vertexIndex),
color);
}
verticalFaceIndex++;
}
}
}
}
@@ -1,48 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.core.Direction;
/**
* 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(BufferBuilder buffer, BlockPosWrapper bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -1,46 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.bufferBuilding.lodTemplates;
import java.util.Map;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.core.Direction;
/**
* 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(BufferBuilder buffer, BlockPosWrapper bufferCenterBlockPos, long data, Map<Direction, long[]> adjData,
byte detailLevel, int posX, int posZ, Box box, DebugMode debugging, boolean[] adjShadeDisabled)
{
ClientProxy.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
}
}
@@ -1,553 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodBuilding;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.util.ColorUtil;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockColorWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Block.BlockShapeWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import com.seibel.lod.wrappers.World.BiomeColorWrapper;
import com.seibel.lod.wrappers.World.BiomeWrapper;
import com.seibel.lod.wrappers.World.WorldWrapper;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.dimension.DimensionType;
/**
* 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 MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName()));
/** 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;
/**
* How wide LodDimensions should be in regions <br>
* Is automatically set before the first frame in ClientProxy.
*/
public int defaultDimensionWidthInRegions = 0;
//public static final boolean useExperimentalLighting = true;
public LodBuilder()
{
}
public void generateLodNodeAsync(ChunkWrapper chunk, LodWorld lodWorld, LevelAccessor world)
{
generateLodNodeAsync(chunk, lodWorld, world, DistanceGenerationMode.SERVER);
}
public void generateLodNodeAsync(ChunkWrapper chunk, LodWorld lodWorld, LevelAccessor world, 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.getClientLevel() == 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.getSinglePlayerServer() == null && mc.getCurrentServer() == null)
return;
DimensionType dim = world.dimensionType();
// 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, ChunkWrapper 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, ChunkWrapper chunk, LodBuilderConfig config)
throws IllegalArgumentException
{
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
int startX;
int startZ;
int endX;
int endZ;
LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
if (region == 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, ChunkWrapper 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;
ChunkPosWrapper 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.getClientLevel().dimensionType().hasCeiling();
boolean hasSkyLight = mc.getClientLevel().dimensionType().hasSkyLight();
boolean isDefault;
BlockPosWrapper blockPos = new BlockPosWrapper();
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 + 2;
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, hasCeiling, hasSkyLight, topBlock);
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(ChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, BlockPosWrapper 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(ChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, BlockPosWrapper 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(ChunkWrapper chunk, LodBuilderConfig config, int xRel, int yAbs, int zRel, BlockPosWrapper blockPos)
{
int colorInt = 0;
if (config.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 (LodConfig.CLIENT.worldGenerator.blockToAvoid.get().nonFull || LodConfig.CLIENT.worldGenerator.blockToAvoid.get().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(ChunkWrapper chunk, BlockPosWrapper blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight = 0;
int blockLight;
// 1 means the lighting is a guess
int isDefault = 0;
WorldWrapper world = MinecraftWrapper.INSTANCE.getWrappedServerLevel();
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.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.getClientLevel().getSeaLevel() - 5)
{
skyLight = 12;
isDefault = 1;
}
else
skyLight = 0;
}
}
else
{
world = MinecraftWrapper.INSTANCE.getWrappedClientLevel();
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.getClientLevel().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(ChunkWrapper chunk, BlockPosWrapper 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();
BlockColorWrapper blockColorWrapper;
BlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(blockPos);
if (chunk.isWaterLogged(blockPos))
{
BiomeWrapper biome = chunk.getBiome(xRel, y, zRel);
return biome.getWaterTint();
}
else
{
blockColorWrapper = chunk.getBlockColorWrapper(blockPos);
}
if (blockShapeWrapper.isToAvoid())
{
return 0;
}
colorOfBlock = blockColorWrapper.getColor();
if (blockColorWrapper.hasTint())
{
WorldWrapper world = MinecraftWrapper.INSTANCE.getWrappedServerLevel();
if (world.isEmpty())
{
world = MinecraftWrapper.INSTANCE.getWrappedClientLevel();
}
int tintValue;
if (blockColorWrapper.hasGrassTint())
{
// grass and green plants
tintValue = BiomeColorWrapper.getGrassColor(world, blockPos);
}
else if (blockColorWrapper.hasFolliageTint())
{
tintValue = BiomeColorWrapper.getFoliageColor(world, blockPos);
}
else
{
//we can reintroduce this with the wrappers
tintValue = BiomeColorWrapper.getWaterColor(world, blockPos);
}
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(ChunkWrapper chunk, BlockPosWrapper blockPos)
{
if (chunk.isWaterLogged(blockPos))
return true;
boolean nonFullAvoidance = LodConfig.CLIENT.worldGenerator.blockToAvoid.get().nonFull;
boolean noCollisionAvoidance = LodConfig.CLIENT.worldGenerator.blockToAvoid.get().noCollision;
BlockShapeWrapper block = chunk.getBlockShapeWrapper(blockPos);
return !block.isToAvoid()
&& !(nonFullAvoidance && block.isNonFull())
&& !(noCollisionAvoidance && block.hasNoCollision());
}
}
@@ -1,95 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.lodBuilding;
import com.seibel.lod.enums.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 <br>
* useHeightmap = false <br>
* useBiomeColors = false <br>
* useSolidBlocksInColorGen = true <br>
* generationMode = Server <br>
*/
public LodBuilderConfig()
{
useHeightmap = false;
useBiomeColors = false;
useSolidBlocksInColorGen = true;
distanceGenerationMode = DistanceGenerationMode.SERVER;
}
/**
* @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;
}
}
@@ -1,570 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilderConfig;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import net.minecraft.core.Registry;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraftforge.common.WorldWorkerManager.IWorker;
/**
* This is used to generate a LodChunk at a given ChunkPos.
*
* @author James Seibel
* @version 11-8-2021
*/
public class LodGenWorker implements IWorker
{
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
private boolean threadStarted = false;
private final LodChunkGenThread thread;
/**
* If a configured feature fails for whatever reason,
* add it to this list, this is to hopefully remove any
* features that could cause issues down the line.
*/
private static final ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
public LodGenWorker(ChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerLevel newServerLevel)
{
// 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 (newServerLevel == null)
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerLevel");
thread = new LodChunkGenThread(newPos, newGenerationMode,
newLodBuilder,
newLodDimension, newServerLevel);
}
@Override
public boolean doWork()
{
if (!threadStarted)
{
if (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
{
// if we are using SERVER generation that has to be done
// synchronously to prevent crashing and harmful
// interactions with the normal world generator
thread.run();
}
else
{
// Every other method can
// be done asynchronously
Thread newThread = new Thread(thread);
newThread.setPriority(5);
genThreads.execute(newThread);
}
threadStarted = true;
// useful for debugging
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
// ClientProxy.LOGGER.info(genThreads.toString());
}
return false;
}
@Override
public boolean hasWork()
{
return !threadStarted;
}
private static class LodChunkGenThread implements Runnable
{
public final ServerLevel serverLevel;
public final LodDimension lodDim;
public final DistanceGenerationMode generationMode;
public final LodBuilder lodBuilder;
private final ChunkPosWrapper pos;
public LodChunkGenThread(ChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
LodBuilder newLodBuilder,
LodDimension newLodDimension, ServerLevel newServerLevel)
{
pos = newPos;
generationMode = newGenerationMode;
lodBuilder = newLodBuilder;
lodDim = newLodDimension;
serverLevel = newServerLevel;
}
@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))
{
// TODO Only server appears to be working currently
generateWithServer();
// switch (generationMode)
// {
// case NONE:
// // don't generate
// break;
// case BIOME_ONLY:
// case BIOME_ONLY_SIMULATE_HEIGHT:
// // fastest
// generateUsingBiomesOnly();
// break;
// case SURFACE:
// // faster
// generateUsingSurface();
// break;
// case FEATURES:
// // fast
// generateUsingFeatures();
// break;
// case SERVER:
// // very slow
// generateWithServer();
// break;
// }
/*
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)
{
ClientProxy.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
/**
* takes about 2-5 ms
*/
private void generateUsingBiomesOnly()
{
List<ChunkAccess> chunkList = new LinkedList<>();
ProtoChunk chunk = new ProtoChunk(pos.getChunkPos(), UpgradeData.EMPTY, serverLevel);
chunkList.add(chunk);
ServerChunkCache chunkSource = serverLevel.getChunkSource();
ChunkGenerator chunkGen = chunkSource.generator;
// generate the terrain (this is thread safe)
//ChunkStatus.EMPTY.generate(serverLevel, chunkGen, serverLevel.getStructureManager(), (ServerLevelLightManager) serverLevel.getLightEngine(), null, chunkList);
// override the chunk status, so we can run the next generator stage
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
chunkGen.createBiomes(serverLevel.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// generate fake height data for this LOD
int seaLevel = serverLevel.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, LodUtil.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, serverLevel.getHeight() / 2);
break;
case EXTREME_HILLS:
heightmap.setHeight(x, z, seaLevel + 30);
break;
case MESA:
heightmap.setHeight(x, z, seaLevel + 20);
break;
case JUNGLE:
heightmap.setHeight(x, z, seaLevel + 20);
break;
case BEACH:
heightmap.setHeight(x, z, seaLevel + 5);
break;
case NONE:
heightmap.setHeight(x, z, 0);
break;
case OCEAN:
case RIVER:
heightmap.setHeight(x, z, seaLevel);
break;
case THEEND:
inTheEnd = true;
break;
// DESERT
// FOREST
// ICY
// MUSHROOM
// SAVANNA
// SWAMP
// TAIGA
// PLAINS
default:
heightmap.setHeight(x, z, seaLevel + 10);
break;
}// heightmap switch
}
else
{
// we aren't simulating height
// always use sea level
heightmap.setHeight(x, z, seaLevel);
}
}// z
}// x
chunk.setHeightmap(LodUtil.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
// */
// private void generateUsingSurface()
// {
// List<ChunkAccess> chunkList = new LinkedList<>();
// ProtoChunk chunk = new ProtoChunk(pos.getChunkPos(), UpgradeData.EMPTY, serverLevel);
// chunkList.add(chunk);
// //LodServerLevel lodServerLevel = new LodServerLevel(serverLevel, chunk);
//
// ServerChunkCache chunkSource = serverLevel.getChunkSource();
// ChunkGenerator chunkGen = chunkSource.generator;
//
// LevelLightEngine lightEngine = serverLevel.getLightEngine();
// StructureManager templateManager = serverLevel.getStructureManager();
//
//
// // generate the terrain (this is thread safe)
// //ChunkStatus.EMPTY.generate(serverLevel, chunkGen, templateManager, lightEngine, null, chunkList);
// // override the chunk status, so we can run the next generator stage
// chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// chunkGen.createBiomes(serverLevel.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
// ChunkStatus.NOISE.generate(serverLevel, chunkGen, templateManager, lightEngine, null, chunkList);
// ChunkStatus.SURFACE.generate(serverLevel, chunkGen, templateManager, lightEngine, null, chunkList);
//
// // this feature has been proven to be thread safe,
// // so we will add it
// SnowAndFreezeFeature snowFeature = new SnowAndFreezeFeature(NoneFeatureConfiguration.CODEC);
// snowFeature.place(serverLevel, chunkGen, serverLevel.random, chunk.getPos().getWorldPosition(), null);
//
//
// lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.SURFACE));
// }
//
//
// /**
// * takes about 15 - 20 ms
// * <p>
// * Causes concurrentModification Exceptions,
// * which could cause instability or world generation bugs
// */
// private void generateUsingFeatures()
// {
// List<ChunkAccess> chunkList = new LinkedList<>();
// ProtoChunk chunk = new ProtoChunk(pos.getChunkPos(), UpgradeData.EMPTY, serverLevel);
// chunkList.add(chunk);
// LodServerLevel lodServerLevel = new LodServerLevel(serverLevel, chunk);
//
// ServerChunkCache chunkSource = serverLevel.getChunkSource();
// ChunkGenerator chunkGen = chunkSource.generator;
//
// ServerLevelLightManager lightEngine = (ServerLevelLightManager) serverLevel.getLightEngine();
// TemplateManager templateManager = serverLevel.getStructureManager();
//
//
// // generate the terrain (this is thread safe)
// ChunkStatus.EMPTY.generate(serverLevel, chunkGen, templateManager, lightEngine, null, chunkList);
// // override the chunk status, so we can run the next generator stage
// chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// chunkGen.createBiomes(serverLevel.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
// ChunkStatus.NOISE.generate(serverLevel, chunkGen, templateManager, lightEngine, null, chunkList);
// ChunkStatus.SURFACE.generate(serverLevel, chunkGen, templateManager, lightEngine, null, chunkList);
//
//
// // get all the biomes in the chunk
// HashSet<Biome> 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, serverLevel.getSeaLevel() >> 2, z >> 2);
//
// // Issue #35
// // For some reason Jungle biomes cause incredible lag
// // the features here must be interacting with each other
// // in unpredictable ways (specifically tree feature generation).
// // When generating Features my CPU usage generally hovers around 30 - 40%
// // when generating Jungles it spikes to 100%.
// if (biome.getBiomeCategory() != Biome.Category.JUNGLE)
// {
// // should probably use the heightmap here instead of seaLevel,
// // but this seems to get the job done well enough
// biomes.add(biome);
// }
// }
// }
//
// boolean allowUnstableFeatures = LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.get();
//
// // generate all the features related to this chunk.
// // this may or may not be thread safe
// for (Biome biome : biomes)
// {
// List<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
//
// for (List<Supplier<ConfiguredFeature<?, ?>>> suppliers : featuresForState)
// {
// for (Supplier<ConfiguredFeature<?, ?>> featureSupplier : suppliers)
// {
// ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
//
// if (!allowUnstableFeatures &&
// configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
// continue;
//
//
// try
// {
// configuredFeature.place(lodServerLevel, chunkGen, serverLevel.random, chunk.getPos().getWorldPosition());
// }
// catch (ConcurrentModificationException e)
// {
// // This will happen. I'm not sure what to do about it
// // except pray that it doesn't 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)
// configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
//// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
// }
// catch (UnsupportedOperationException e)
// {
// // This will happen when the LodServerLevel
// // isn't able to return something that a feature
// // generator needs
//
// if (!allowUnstableFeatures)
// configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
//// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
// }
// catch (Exception e)
// {
// // I'm not sure what happened, print to the log
//
// e.printStackTrace();
//
// if (!allowUnstableFeatures)
// configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
//// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
// }
// }
// }
// }
//
// // generate a Lod like normal
// lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(chunk), new LodBuilderConfig(DistanceGenerationMode.FEATURES));
// }
/**
* 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
* <p>
* Note this should not be multithreaded and does cause server/simulation lag
* (Higher lag for generating than loading)
*/
private void generateWithServer()
{
lodBuilder.generateLodNodeFromChunk(lodDim, new ChunkWrapper(serverLevel.getChunk(pos.getX(), pos.getZ(), ChunkStatus.FEATURES)), new LodBuilderConfig(DistanceGenerationMode.FEATURES));
}
}
/**
* Stops the current genThreads if they are running
* and then recreates the Executor service. <br><br>
* <p>
* 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(LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
}
/*
* performance/generation tests related to
* ServerLevel.getChunk(x, z, ChunkStatus. *** )
true/false is whether they generated blocks or not
the time is how long it took to generate
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
ChunkStatus.BIOMES 1 - 10 ms false (no height)
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
ChunkStatus.FEATURES 7 - 25 ms true
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
ChunkStatus.LIGHT 20 - 40 ms true
ChunkStatus.FULL 30 - 50 ms true
ChunkStatus.SPAWN 50 - 80 ms true
At this point I would suggest using FEATURES, as it generates snow and trees
(and any other object that is needed to make biomes distinct)
Otherwise, if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
*/
}
@@ -1,307 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.builders.worldGeneration;
import java.util.HashMap;
import java.util.Random;
import java.util.stream.Stream;
import com.seibel.lod.util.LodUtil;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.level.EmptyTickList;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.border.WorldBorder;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.material.FluidState;
/**
* This is a fake ServerWorld used when generating features.
* This allows us to keep each LodChunk generation independent
* of the actual ServerWorld, allowing us
* to multithread generation.
* @author James Seibel
* @version 7-26-2021
*/
public class LodServerLevel implements Level
{
public HashMap<Heightmap.Type, Heightmap> heightmaps = new HashMap<>();
public final IChunk chunk;
public final ServerWorld serverWorld;
public LodServerLevel(ServerWorld newServerWorld, IChunk newChunk)
{
chunk = newChunk;
serverWorld = newServerWorld;
}
@Override
public int getHeight(Type 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(LodUtil.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<BlockState> state)
{
return state.test(chunk.getBlockState(pos));
}
@Override
public ITickList<Block> getBlockTicks()
{
return EmptyTickList.empty();
}
@Override
public IChunk getChunk(int x, int z, ChunkStatus requiredStatus, boolean nonnull)
{
return chunk;
}
@Override
public Stream<? extends StructureStart<?>> startsForFeature(SectionPos p_241827_1_, Structure<?> p_241827_2_)
{
return serverWorld.startsForFeature(p_241827_1_, p_241827_2_);
}
@Override
public ITickList<Fluid> getLiquidTicks()
{
return EmptyTickList.empty();
}
@Override
public WorldLightManager getLightEngine()
{
return new WorldLightManager(null, false, false);
}
@Override
public long getSeed()
{
return serverWorld.getSeed();
}
@Override
public DynamicRegistries 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(PlayerEntity player, BlockPos pos, SoundEvent soundIn, SoundCategory category, float volume,
float pitch)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void addParticle(IParticleData 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 ServerWorld getLevel()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public AbstractChunkProvider getChunkSource()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public DifficultyInstance getCurrentDifficultyAt(BlockPos arg0)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public IWorldInfo getLevelData()
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public void levelEvent(PlayerEntity arg0, int arg1, BlockPos arg2, int arg3)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<Entity> getEntities(Entity arg0, AxisAlignedBB arg1, Predicate<? super Entity> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public <T extends Entity> List<T> getEntitiesOfClass(Class<? extends T> arg0, AxisAlignedBB arg1,
Predicate<? super T> arg2)
{
throw new UnsupportedOperationException("Not Implemented");
}
@Override
public List<? extends PlayerEntity> 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 TileEntity getBlockEntity(BlockPos p_175625_1_)
{
throw new UnsupportedOperationException("Not Implemented");
}
}
@@ -1,204 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.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.builders.lodBuilding.LodBuilder;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.PosToGenerateContainer;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import net.minecraft.server.level.ServerLevel;
import net.minecraftforge.common.WorldWorkerManager;
/**
* A singleton that handles all long distance LOD world generation.
* @author James Seibel
* @version 9-25-2021
*/
public class LodWorldGenerator
{
public final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This holds the thread used to generate new LODs 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<ChunkPosWrapper> 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 (LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get() != 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 = LodConfig.CLIENT.advancedModOptions.threading.numberOfWorldGenerationThreads.get() * 8;
Thread generatorThread = new Thread(() ->
{
try
{
// round the player's block position down to the nearest chunk BlockPos
int playerPosX = mc.getPlayer().blockPosition().getX();
int playerPosZ = mc.getPlayer().blockPosition().getZ();
//=======================================//
// fill in positionsWaitingToBeGenerated //
//=======================================//
ServerLevel serverWorld = LodUtil.getServerLevelFromDimension(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++;
ChunkPosWrapper chunkPos = new ChunkPosWrapper(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);
WorldWorkerManager.addWorker(genWorker);
}
// 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++;
ChunkPosWrapper chunkPos = new ChunkPosWrapper(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);
WorldWorkerManager.addWorker(genWorker);
}
}
}
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
}
@@ -1,573 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.config;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.tuple.Pair;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.BlockToAvoid;
import com.seibel.lod.enums.BufferRebuildTimes;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.GpuUploadMethod;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.HorizontalScale;
import com.seibel.lod.enums.LodTemplate;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.util.LodUtil;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.common.Mod;
/**
* This handles any configuration the user has access to.
* @author Leonardo Amato
* @author James Seibel
* @version 10-25-2021
*/
@Mod.EventBusSubscriber
public class LodConfig
{
// CONFIG STRUCTURE
// -> Client
// |
// |-> Graphics
// | |-> QualityOption
// | |-> FogQualityOption
// | |-> AdvancedGraphicsOption
// |
// |-> World Generation
// |
// |-> Advanced Mod Option
// |-> Threads
// |-> Buffers
// |-> Debugging
public static class Client
{
public final Graphics graphics;
public final WorldGenerator worldGenerator;
public final AdvancedModOptions advancedModOptions;
//================//
// Client Configs //
//================//
public Client(ForgeConfigSpec.Builder builder)
{
builder.push(this.getClass().getSimpleName());
{
graphics = new Graphics(builder);
worldGenerator = new WorldGenerator(builder);
advancedModOptions = new AdvancedModOptions(builder);
}
builder.pop();
}
//==================//
// Graphics Configs //
//==================//
public static class Graphics
{
public final QualityOption qualityOption;
public final FogQualityOption fogQualityOption;
public final AdvancedGraphicsOption advancedGraphicsOption;
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how the mod will look in game").push("Graphics");
{
qualityOption = new QualityOption(builder);
advancedGraphicsOption = new AdvancedGraphicsOption(builder);
fogQualityOption = new FogQualityOption(builder);
}
builder.pop();
}
public static class QualityOption
{
public final ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
public final ForgeConfigSpec.IntValue lodChunkRenderDistance;
public final ForgeConfigSpec.EnumValue<VerticalQuality> verticalQuality;
public final ForgeConfigSpec.EnumValue<HorizontalScale> horizontalScale;
public final ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
QualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how detailed the fake chunks will be.").push(this.getClass().getSimpleName());
verticalQuality = builder
.comment("\n\n"
+ " 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")
.defineEnum("Vertical Quality", VerticalQuality.MEDIUM);
horizontalScale = builder
.comment("\n\n"
+ " 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")
.defineEnum("Horizontal Scale", HorizontalScale.MEDIUM);
horizontalQuality = builder
.comment("\n\n"
+ " 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")
.defineEnum("Horizontal Quality", HorizontalQuality.MEDIUM);
drawResolution = builder
.comment("\n\n"
+ " 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")
.defineEnum("Block size", HorizontalResolution.BLOCK);
lodChunkRenderDistance = builder
.comment("\n\n"
+ " The mod's render distance, measured in chunks. \n")
.defineInRange("Lod Render Distance", 64, 32, 1024);
builder.pop();
}
}
public static class FogQualityOption
{
public final ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public final ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public final ForgeConfigSpec.BooleanValue disableVanillaFog;
FogQualityOption(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control the fog quality.").push(this.getClass().getSimpleName());
fogDistance = builder
.comment("\n\n"
+ " 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")
.defineEnum("Fog Distance", FogDistance.FAR);
fogDrawOverride = builder
.comment("\n\n"
+ " 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")
.defineEnum("Fog Draw Override", FogDrawOverride.FANCY);
disableVanillaFog = builder
.comment("\n\n"
+ " If true disable vanilla Minecraft's fog. \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 edit fog. \n")
.define("Disable Vanilla Fog", true);
builder.pop();
}
}
public static class AdvancedGraphicsOption
{
public final ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public final ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public final ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
public final ForgeConfigSpec.EnumValue<VanillaOverdraw> vanillaOverdraw;
public final ForgeConfigSpec.EnumValue<GpuUploadMethod> gpuUploadMethod;
public final ForgeConfigSpec.BooleanValue useExtendedNearClipPlane;
AdvancedGraphicsOption(ForgeConfigSpec.Builder builder)
{
builder.comment("Advanced graphics option for the mod").push(this.getClass().getSimpleName());
lodTemplate = builder
.comment("\n\n"
+ " 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")
.defineEnum("LOD Template", LodTemplate.CUBIC);
disableDirectionalCulling = builder
.comment("\n\n"
+ " 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")
.define("Disable Directional Culling", false);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ " 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")
.define("Always Use Max Quality", false);
vanillaOverdraw = builder
.comment("\n\n"
+ " 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")
.defineEnum("Vanilla Overdraw", VanillaOverdraw.DYNAMIC);
gpuUploadMethod = builder
.comment("\n\n"
+ " What method should be used to upload geometry to the GPU? \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.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly better than " + GpuUploadMethod.SUB_DATA + " if using a integrated GPU. \n")
.defineEnum("GPU Upload Method", GpuUploadMethod.BUFFER_STORAGE);
// This is a temporary fix (like vanilla overdraw)
// hopefully we can remove both once we get individual chunk rendering figured out
useExtendedNearClipPlane = builder
.comment("\n\n"
+ " Will prevent some overdraw issues, but may cause nearby fake chunks to render incorrectly \n"
+ " especially when in/near an ocean. \n")
.define("Use Extended Near Clip Plane", false);
builder.pop();
}
}
}
//========================//
// WorldGenerator Configs //
//========================//
public static class WorldGenerator
{
public final ForgeConfigSpec.EnumValue<GenerationPriority> generationPriority;
public final ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public final ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public final ForgeConfigSpec.EnumValue<BlockToAvoid> blockToAvoid;
//public final ForgeConfigSpec.BooleanValue useExperimentalPreGenLoading;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how fake chunks outside your normal view range are generated.").push("Generation");
generationPriority = builder
.comment("\n\n"
+ " " + 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")
.defineEnum("Generation Priority", GenerationPriority.FAR_FIRST);
distanceGenerationMode = builder
.comment("\n\n"
+ " 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.SERVER + " \n"
+ " Ask the 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")
.defineEnum("Distance Generation Mode", DistanceGenerationMode.SURFACE);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ " 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")
.define("Allow Unstable Feature Generation", false);
blockToAvoid = builder
.comment("\n\n"
+ " " + BlockToAvoid.NONE + ": Use all blocks when generating fake chunks \n\n"
+ ""
+ " " + BlockToAvoid.NON_FULL + ": Only use full blocks when generating fake chunks (ignores slabs, lanterns, torches, grass, etc.) \n\n"
+ ""
+ " " + BlockToAvoid.NO_COLLISION + ": Only use solid blocks when generating fake chunks (ignores grass, torches, etc.) \n"
+ ""
+ " " + BlockToAvoid.BOTH + ": Only use full solid blocks when generating fake chunks \n"
+ "\n")
.defineEnum("Block to avoid", BlockToAvoid.BOTH);
/*useExperimentalPreGenLoading = builder
.comment("\n\n"
+ " if a chunk has been pre-generated, then the mod would use the real chunk for the \n"
+ "fake chunk creation. May require a deletion of the lod file to see the result. \n")
.define("Use pre-generated chunks", false);*/
builder.pop();
}
}
//============================//
// AdvancedModOptions Configs //
//============================//
public static class AdvancedModOptions
{
public final Threading threading;
public final Debugging debugging;
public final Buffers buffers;
public AdvancedModOptions(ForgeConfigSpec.Builder builder)
{
builder.comment("Advanced mod settings").push(this.getClass().getSimpleName());
{
threading = new Threading(builder);
debugging = new Debugging(builder);
buffers = new Buffers(builder);
}
builder.pop();
}
public static class Threading
{
public final ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
public final ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
Threading(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings control how many CPU threads the mod uses for different tasks.").push(this.getClass().getSimpleName());
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ " 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")
.defineInRange("numberOfWorldGenerationThreads", Math.max(1, Runtime.getRuntime().availableProcessors() / 2), 1, Runtime.getRuntime().availableProcessors());
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ " 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")
.defineInRange("numberOfBufferBuilderThreads", Math.max(1, Runtime.getRuntime().availableProcessors() / 2), 1, Runtime.getRuntime().availableProcessors());
builder.pop();
}
}
//===============//
// Debug Options //
//===============//
public static class Debugging
{
public final ForgeConfigSpec.BooleanValue drawLods;
public final ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public final ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings can be used to look for bugs, or see how certain aspects of the mod work.").push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ " 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")
.define("Enable Rendering", true);
debugMode = builder
.comment("\n\n"
+ " " + DebugMode.OFF + ": Fake chunks will be drawn with their normal colors. \n"
+ " " + DebugMode.SHOW_DETAIL + ": Fake chunks color will be based on their detail level. \n"
+ " " + DebugMode.SHOW_DETAIL_WIREFRAME + ": Fake chunks color will be based on their detail level, drawn as a wireframe. \n")
.defineEnum("Debug Mode", DebugMode.OFF);
enableDebugKeybindings = builder
.comment("\n\n"
+ " 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.")
.define("Enable Debug Keybinding", false);
builder.pop();
}
}
public static class Buffers
{
public final ForgeConfigSpec.EnumValue<BufferRebuildTimes> rebuildTimes;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment("These settings affect how often geometry is are built.").push(this.getClass().getSimpleName());
rebuildTimes = builder
.comment("\n\n"
+ " 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")
.defineEnum("rebuildFrequency", BufferRebuildTimes.NORMAL);
builder.pop();
}
}
}
}
/** {@link Path} to the configuration file of this mod */
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.NAME + ".toml");
public static final ForgeConfigSpec CLIENT_SPEC;
public static final Client CLIENT;
static
{
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
CLIENT_SPEC = specPair.getRight();
CLIENT = specPair.getLeft();
CommentedFileConfig clientConfig = CommentedFileConfig.builder(CONFIG_PATH)
.writingMode(WritingMode.REPLACE)
.build();
clientConfig.load();
clientConfig.save();
CLIENT_SPEC.setConfig(clientConfig);
}
}
@@ -1,47 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* heightmap <br>
* multi_lod <br>
*
* @author Leonardo Amato
* @version 19-10-2021
*/
public enum BlockToAvoid
{
NONE(false, false),
NON_FULL(true, false),
NO_COLLISION(false, true),
BOTH(true, true);
public boolean nonFull;
public boolean noCollision;
BlockToAvoid(boolean nonFull, boolean noCollision)
{
this.nonFull = nonFull;
this.noCollision = noCollision;
}
}
@@ -1,49 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Near_First <br>
* Far_First <br>
* <br>
* 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;
}
}
@@ -1,95 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* NONE <br>
* BIOME_ONLY <br>
* BIOME_ONLY_SIMULATE_HEIGHT <br>
* SURFACE <br>
* FEATURES <br>
* SERVER <br><br>
* <p>
* 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 to a pre-existing world.
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms)
*/
SERVER((byte) 5);
/**
* The higher the number the more complete the generation is.
*/
public final byte complexity;
DistanceGenerationMode(byte complexity)
{
this.complexity = complexity;
}
}
@@ -1,38 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* 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
}
@@ -1,47 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* USE_OPTIFINE_FOG_SETTING, <br>
* NEVER_DRAW_FOG, <br>
* ALWAYS_DRAW_FOG_FAST, <br>
* ALWAYS_DRAW_FOG_FANCY <br>
*
* @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
}
@@ -1,37 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Near_First <br>
* Far_First <br>
* <br>
* 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
}
@@ -1,41 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Minecraft, Lod_Builder, Lod_Render, None
*
* @author James Seibel
* @version 10-31-2021
*/
public enum GlProxyContext
{
/** Minecraft's render thread */
MINECRAFT,
/** The context we send buffers to the GPU on */
LOD_BUILDER,
/** The context we draw LODs on */
LOD_RENDER,
/** used to un-bind threads */
NONE,
}
@@ -1,38 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Buffer_Storage, Sub_Data, Buffer_Mapping
*
* @author James Seibel
* @version 10-23-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,
/** May end up storing buffers in System memory. Slower rendering but won't stutter when uploading. */
BUFFER_MAPPING,
}
@@ -1,53 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Lowest <br>
* Low <br>
* Medium <br>
* High <br>
* <br>
* 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;
}
}
@@ -1,175 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import java.util.ArrayList;
import java.util.Collections;
import com.seibel.lod.util.LodUtil;
/**
* chunk <Br>
* half_chunk <Br>
* four_blocks <br>
* two_blocks <Br>
* block <br>
*
* @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 <br>
* 2nd dimension: An array of all LodDetails that are less than or <br>
* 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<HorizontalResolution> 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];
}
}
@@ -1,49 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* Low <br>
* Medium <br>
* High <br>
* <br>
* 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;
}
}
@@ -1,62 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
import com.seibel.lod.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;
}
}
@@ -1,22 +0,0 @@
package com.seibel.lod.enums;
/**
* 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
}
@@ -1,45 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* None, Dynamic, Always
*
* <p>
* 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,
}
@@ -1,80 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.enums;
/**
* heightmap <br>
* multi_lod <br>
*
* @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;
}
}
@@ -0,0 +1,105 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge;
import com.seibel.lod.core.api.EventApi;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.forge.wrappers.chunk.ChunkWrapper;
import com.seibel.lod.forge.wrappers.world.DimensionTypeWrapper;
import com.seibel.lod.forge.wrappers.world.WorldWrapper;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
/**
* This handles all events sent to the client,
* and is the starting point for most of the mod.
*
* @author James_Seibel
* @version 11-12-2021
*/
public class ForgeClientProxy
{
private final EventApi eventApi = EventApi.INSTANCE;
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
eventApi.serverTickEvent();
}
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
{
eventApi.chunkLoadEvent(new ChunkWrapper(event.getChunk()), DimensionTypeWrapper.getDimensionTypeWrapper(event.getWorld().dimensionType()));
}
@SubscribeEvent
public void worldSaveEvent(WorldEvent.Save event)
{
eventApi.worldSaveEvent();
}
/** This is also called when a new dimension loads */
@SubscribeEvent
public void worldLoadEvent(WorldEvent.Load event)
{
eventApi.worldLoadEvent(WorldWrapper.getWorldWrapper(event.getWorld()));
}
@SubscribeEvent
public void worldUnloadEvent(WorldEvent.Unload event)
{
eventApi.worldUnloadEvent();
}
@SubscribeEvent
public void blockChangeEvent(BlockEvent event)
{
// we only care about certain block events
if (event.getClass() == BlockEvent.BreakEvent.class ||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
event.getClass() == BlockEvent.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);
}
}
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
{
eventApi.onKeyInput(event.getKey(), event.getAction());
}
}
@@ -0,0 +1,422 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.tuple.Pair;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.io.WritingMode;
import com.seibel.lod.core.ModInfo;
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.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.IBuffers;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.IDebugging;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IAdvanced.IThreading;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.IAdvancedGraphics;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.IFogQuality;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IGraphics.IQuality;
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton.IClient.IWorldGenerator;
import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.fml.common.Mod;
/**
* This handles any configuration the user has access to.
* @author Leonardo Amato
* @author James Seibel
* @version 11-16-2021
*/
@Mod.EventBusSubscriber
public class ForgeConfig
{
// CONFIG STRUCTURE
// -> Client
// |
// |-> Graphics
// | |-> QualityOption
// | |-> FogQualityOption
// | |-> AdvancedGraphicsOption
// |
// |-> World Generation
// |
// |-> Advanced Mod Option
// |-> Threads
// |-> Buffers
// |-> Debugging
public static class Client
{
public final Graphics graphics;
public final WorldGenerator worldGenerator;
public final Advanced advanced;
//================//
// Client Configs //
//================//
public Client(ForgeConfigSpec.Builder builder)
{
builder.push(this.getClass().getSimpleName());
{
graphics = new Graphics(builder);
worldGenerator = new WorldGenerator(builder);
advanced = new Advanced(builder);
}
builder.pop();
}
//==================//
// Graphics Configs //
//==================//
public static class Graphics
{
public final Quality quality;
public final FogQuality fogQuality;
public final AdvancedGraphics advancedGraphics;
Graphics(ForgeConfigSpec.Builder builder)
{
builder.comment(IGraphics.DESC).push("Graphics");
{
quality = new Quality(builder);
advancedGraphics = new AdvancedGraphics(builder);
fogQuality = new FogQuality(builder);
}
builder.pop();
}
public static class Quality
{
public final ForgeConfigSpec.EnumValue<HorizontalResolution> drawResolution;
public final ForgeConfigSpec.IntValue lodChunkRenderDistance;
public final ForgeConfigSpec.EnumValue<VerticalQuality> verticalQuality;
public final ForgeConfigSpec.EnumValue<HorizontalScale> horizontalScale;
public final ForgeConfigSpec.EnumValue<HorizontalQuality> horizontalQuality;
Quality(ForgeConfigSpec.Builder builder)
{
builder.comment(IQuality.DESC).push(this.getClass().getSimpleName());
verticalQuality = builder
.comment("\n\n"
+ IQuality.VERTICAL_QUALITY_DESC)
.defineEnum("Vertical Quality", IQuality.VERTICAL_QUALITY_DEFAULT);
horizontalScale = builder
.comment("\n\n"
+ IQuality.HORIZONTAL_SCALE_DESC)
.defineEnum("Horizontal Scale", IQuality.HORIZONTAL_SCALE_DEFAULT);
horizontalQuality = builder
.comment("\n\n"
+ IQuality.HORIZONTAL_QUALITY_DESC)
.defineEnum("Horizontal Quality", IQuality.HORIZONTAL_QUALITY_DEFAULT);
drawResolution = builder
.comment("\n\n"
+ IQuality.DRAW_RESOLUTION_DESC)
.defineEnum("Block size", IQuality.DRAW_RESOLUTION_DEFAULT);
MinDefaultMax<Integer> minDefaultMax = IQuality.LOD_CHUNK_RENDER_DISTANCE_MIN_DEFAULT_MAX;
lodChunkRenderDistance = builder
.comment("\n\n"
+ IQuality.LOD_CHUNK_RENDER_DISTANCE_DESC)
.defineInRange("Lod Render Distance", minDefaultMax.defaultValue, minDefaultMax.minValue, minDefaultMax.maxValue);
builder.pop();
}
}
public static class FogQuality
{
public final ForgeConfigSpec.EnumValue<FogDistance> fogDistance;
public final ForgeConfigSpec.EnumValue<FogDrawOverride> fogDrawOverride;
public final ForgeConfigSpec.BooleanValue disableVanillaFog;
FogQuality(ForgeConfigSpec.Builder builder)
{
builder.comment(IFogQuality.DESC).push(this.getClass().getSimpleName());
fogDistance = builder
.comment("\n\n"
+ IFogQuality.FOG_DISTANCE_DESC)
.defineEnum("Fog Distance", IFogQuality.FOG_DISTANCE_DEFAULT);
fogDrawOverride = builder
.comment("\n\n"
+ IFogQuality.FOG_DRAW_OVERRIDE_DESC)
.defineEnum("Fog Draw Override", IFogQuality.FOG_DRAW_OVERRIDE_DEFAULT);
disableVanillaFog = builder
.comment("\n\n"
+ IFogQuality.DISABLE_VANILLA_FOG_DESC)
.define("Experimental Disable Vanilla Fog", IFogQuality.DISABLE_VANILLA_FOG_DEFAULT);
builder.pop();
}
}
public static class AdvancedGraphics
{
public final ForgeConfigSpec.EnumValue<LodTemplate> lodTemplate;
public final ForgeConfigSpec.BooleanValue disableDirectionalCulling;
public final ForgeConfigSpec.BooleanValue alwaysDrawAtMaxQuality;
public final ForgeConfigSpec.EnumValue<VanillaOverdraw> vanillaOverdraw;
public final ForgeConfigSpec.EnumValue<GpuUploadMethod> gpuUploadMethod;
public final ForgeConfigSpec.BooleanValue useExtendedNearClipPlane;
AdvancedGraphics(ForgeConfigSpec.Builder builder)
{
builder.comment(IAdvancedGraphics.DESC).push(this.getClass().getSimpleName());
lodTemplate = builder
.comment("\n\n"
+ IAdvancedGraphics.LOD_TEMPLATE_DESC)
.defineEnum("LOD Template", IAdvancedGraphics.LOD_TEMPLATE_DEFAULT);
disableDirectionalCulling = builder
.comment("\n\n"
+ IAdvancedGraphics.DISABLE_DIRECTIONAL_CULLING_DESC)
.define("Disable Directional Culling", IAdvancedGraphics.DISABLE_DIRECTIONAL_CULLING_DEFAULT);
alwaysDrawAtMaxQuality = builder
.comment("\n\n"
+ IAdvancedGraphics.ALWAYS_DRAW_AT_MAD_QUALITY_DESC)
.define("Always Use Max Quality", IAdvancedGraphics.ALWAYS_DRAW_AT_MAD_QUALITY_DEFAULT);
vanillaOverdraw = builder
.comment("\n\n"
+ IAdvancedGraphics.VANILLA_OVERDRAW_DESC)
.defineEnum("Vanilla Overdraw", IAdvancedGraphics.VANILLA_OVERDRAW_DEFAULT);
gpuUploadMethod = builder
.comment("\n\n"
+ IAdvancedGraphics.GPU_UPLOAD_METHOD_DESC)
.defineEnum("GPU Upload Method", IAdvancedGraphics.GPU_UPLOAD_METHOD_DEFAULT);
// This is a temporary fix (like vanilla overdraw)
// hopefully we can remove both once we get individual chunk rendering figured out
useExtendedNearClipPlane = builder
.comment("\n\n"
+ IAdvancedGraphics.USE_EXTENDED_NEAR_CLIP_PLANE_DESC)
.define("Use Extended Near Clip Plane", IAdvancedGraphics.USE_EXTENDED_NEAR_CLIP_PLANE_DEFAULT);
builder.pop();
}
}
}
//========================//
// WorldGenerator Configs //
//========================//
public static class WorldGenerator
{
public final ForgeConfigSpec.EnumValue<GenerationPriority> generationPriority;
public final ForgeConfigSpec.EnumValue<DistanceGenerationMode> distanceGenerationMode;
public final ForgeConfigSpec.BooleanValue allowUnstableFeatureGeneration;
public final ForgeConfigSpec.EnumValue<BlocksToAvoid> blocksToAvoid;
//public final ForgeConfigSpec.BooleanValue useExperimentalPreGenLoading;
WorldGenerator(ForgeConfigSpec.Builder builder)
{
builder.comment(IWorldGenerator.DESC).push("Generation");
generationPriority = builder
.comment("\n\n"
+ IWorldGenerator.GENERATION_PRIORITY_DESC)
.defineEnum("Generation Priority", IWorldGenerator.GENERATION_PRIORITY_DEFAULT);
distanceGenerationMode = builder
.comment("\n\n"
+ IWorldGenerator.DISTANCE_GENERATION_MODE_DESC)
.defineEnum("Distance Generation Mode", IWorldGenerator.DISTANCE_GENERATION_MODE_DEFAULT);
allowUnstableFeatureGeneration = builder
.comment("\n\n"
+ IWorldGenerator.ALLOW_UNSTABLE_FEATURE_GENERATION_DESC)
.define("Allow Unstable Feature Generation", IWorldGenerator.ALLOW_UNSTABLE_FEATURE_GENERATION_DEFAULT);
blocksToAvoid = builder
.comment("\n\n"
+ IWorldGenerator.BLOCKS_TO_AVOID_DESC)
.defineEnum("Blocks to avoid", IWorldGenerator.BLOCKS_TO_AVOID_DEFAULT);
/*useExperimentalPreGenLoading = builder
.comment("\n\n"
+ " if a chunk has been pre-generated, then the mod would use the real chunk for the \n"
+ "fake chunk creation. May require a deletion of the lod file to see the result. \n")
.define("Use pre-generated chunks", false);*/
builder.pop();
}
}
//============================//
// AdvancedModOptions Configs //
//============================//
public static class Advanced
{
public final Threading threading;
public final Debugging debugging;
public final Buffers buffers;
public Advanced(ForgeConfigSpec.Builder builder)
{
builder.comment(IAdvanced.DESC).push(this.getClass().getSimpleName());
{
threading = new Threading(builder);
debugging = new Debugging(builder);
buffers = new Buffers(builder);
}
builder.pop();
}
public static class Threading
{
public final ForgeConfigSpec.IntValue numberOfWorldGenerationThreads;
public final ForgeConfigSpec.IntValue numberOfBufferBuilderThreads;
Threading(ForgeConfigSpec.Builder builder)
{
builder.comment(IThreading.DESC).push(this.getClass().getSimpleName());
MinDefaultMax<Integer> minDefaultMax = IThreading.NUMBER_OF_WORLD_GENERATION_THREADS_DEFAULT;
numberOfWorldGenerationThreads = builder
.comment("\n\n"
+ IThreading.NUMBER_OF_WORLD_GENERATION_THREADS_DESC)
.defineInRange("numberOfWorldGenerationThreads", minDefaultMax.defaultValue, minDefaultMax.minValue, minDefaultMax.maxValue);
minDefaultMax = IThreading.NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX;
numberOfBufferBuilderThreads = builder
.comment("\n\n"
+ IThreading.NUMBER_OF_BUFFER_BUILDER_THREADS_MIN_DEFAULT_MAX)
.defineInRange("numberOfBufferBuilderThreads", minDefaultMax.defaultValue, minDefaultMax.minValue, minDefaultMax.maxValue);
builder.pop();
}
}
//===============//
// Debug Options //
//===============//
public static class Debugging
{
public final ForgeConfigSpec.BooleanValue drawLods;
public final ForgeConfigSpec.EnumValue<DebugMode> debugMode;
public final ForgeConfigSpec.BooleanValue enableDebugKeybindings;
Debugging(ForgeConfigSpec.Builder builder)
{
builder.comment(IDebugging.DESC).push(this.getClass().getSimpleName());
drawLods = builder
.comment("\n\n"
+ IDebugging.DRAW_LODS_DESC)
.define("Enable Rendering", IDebugging.DRAW_LODS_DEFAULT);
debugMode = builder
.comment("\n\n"
+ IDebugging.DEBUG_MODE_DESC)
.defineEnum("Debug Mode", IDebugging.DEBUG_MODE_DEFAULT);
enableDebugKeybindings = builder
.comment("\n\n"
+ IDebugging.DEBUG_KEYBINDINGS_ENABLED_DESC)
.define("Enable Debug Keybinding", IDebugging.DEBUG_KEYBINDINGS_ENABLED_DEFAULT);
builder.pop();
}
}
public static class Buffers
{
public final ForgeConfigSpec.EnumValue<BufferRebuildTimes> rebuildTimes;
Buffers(ForgeConfigSpec.Builder builder)
{
builder.comment(IBuffers.DESC).push(this.getClass().getSimpleName());
rebuildTimes = builder
.comment("\n\n"
+ IBuffers.REBUILD_TIMES_DESC)
.defineEnum("rebuildFrequency", IBuffers.REBUILD_TIMES_DEFAULT);
builder.pop();
}
}
}
}
/** {@link Path} to the configuration file of this mod */
private static final Path CONFIG_PATH = Paths.get("config", ModInfo.NAME + ".toml");
public static final ForgeConfigSpec CLIENT_SPEC;
public static final Client CLIENT;
static
{
final Pair<Client, ForgeConfigSpec> specPair = new ForgeConfigSpec.Builder().configure(Client::new);
CLIENT_SPEC = specPair.getRight();
CLIENT = specPair.getLeft();
CommentedFileConfig clientConfig = CommentedFileConfig.builder(CONFIG_PATH)
.writingMode(WritingMode.REPLACE)
.build();
clientConfig.load();
clientConfig.save();
CLIENT_SPEC.setConfig(clientConfig);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge;
import com.seibel.lod.core.ModInfo;
import com.seibel.lod.forge.wrappers.ForgeDependencySetup;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fmlserverevents.FMLServerStartedEvent;
/**
* Initialize and setup the Mod. <br>
* If you are looking for the real start of the mod
* check out the ClientProxy.
*
* @author James Seibel
* @version 11-21-2021
*/
@Mod(ModInfo.ID)
public class ForgeMain
{
public static ForgeClientProxy forgeClientProxy;
private void init(final FMLCommonSetupEvent event)
{
// make sure the dependencies are set up before the mod needs them
ForgeDependencySetup.createInitialBindings();
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ForgeConfig.CLIENT_SPEC);
}
public ForgeMain()
{
// Register the methods
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::init);
FMLJavaModLoadingContext.get().getModEventBus().addListener(this::onClientStart);
// Register ourselves for server and other game events we are interested in
MinecraftForge.EVENT_BUS.register(this);
}
private void onClientStart(final FMLClientSetupEvent event)
{
forgeClientProxy = new ForgeClientProxy();
MinecraftForge.EVENT_BUS.register(forgeClientProxy);
}
@SubscribeEvent
public void onServerStarting(FMLServerStartedEvent event)
{
// this is called when the server starts
}
}
@@ -17,7 +17,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.lod.mixin; package com.seibel.lod.forge.mixins;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@@ -26,8 +26,9 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import com.seibel.lod.LodMain; import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.config.LodConfig; import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.forge.wrappers.McObjectConverter;
import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
@@ -59,10 +60,14 @@ public class MixinWorldRenderer
@Inject(at = @At("HEAD"), method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLcom/mojang/math/Matrix4f;)V") @Inject(at = @At("HEAD"), method = "renderChunkLayer(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/PoseStack;DDDLcom/mojang/math/Matrix4f;)V")
private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double cameraXBlockPos, double cameraYBlockPos, double cameraZBlockPos, Matrix4f projectionMatrix, CallbackInfo callback) private void renderChunkLayer(RenderType renderType, PoseStack modelViewMatrixStack, double cameraXBlockPos, double cameraYBlockPos, double cameraZBlockPos, Matrix4f projectionMatrix, CallbackInfo callback)
{ {
// only render if LODs are enabled and
// only render before solid blocks // only render before solid blocks
if (LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get() && renderType.equals(RenderType.solid())) if (renderType.equals(RenderType.solid()))
LodMain.client_proxy.renderLods(modelViewMatrixStack, projectionMatrix, previousPartialTicks); {
Mat4f mcModelViewMatrix = McObjectConverter.Convert(modelViewMatrixStack.last().pose());
Mat4f mcProjectionMatrix = McObjectConverter.Convert(projectionMatrix);
ClientApi.INSTANCE.renderLods(mcModelViewMatrix, mcProjectionMatrix, previousPartialTicks);
}
} }
} }
@@ -0,0 +1,37 @@
package com.seibel.lod.forge.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.forge.wrappers.block.BlockColorSingletonWrapper;
import com.seibel.lod.forge.wrappers.config.LodConfigWrapperSingleton;
import com.seibel.lod.forge.wrappers.minecraft.MinecraftRenderWrapper;
import com.seibel.lod.forge.wrappers.minecraft.MinecraftWrapper;
/**
* Binds all necessary dependencies so we
* can access them in Core. <br>
* This needs to be called before any Core classes
* are loaded.
*
* @author James Seibel
* @version 11-20-2021
*/
public class ForgeDependencySetup
{
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()));
}
}
@@ -17,38 +17,40 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.lod.enums; package com.seibel.lod.forge.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;
/** /**
* off, detail, detail wireframe * This class converts to and from Minecraft objects (Ex: Matrix4f)
* and objects we created (Ex: Mat4f).
* *
* @author James Seibel * @author James Seibel
* @version 8-28-2021 * @version 11-20-2021
*/ */
public enum DebugMode public class McObjectConverter
{ {
/** LODs are rendered normally */ /** 4x4 float matrix converter */
OFF, public static Mat4f Convert(Matrix4f mcMatrix)
/** 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; FloatBuffer buffer = FloatBuffer.allocate(16);
SHOW_DETAIL.next = SHOW_DETAIL_WIREFRAME; mcMatrix.store(buffer);
SHOW_DETAIL_WIREFRAME.next = OFF; Mat4f matrix = new Mat4f(buffer);
matrix.transpose();
return matrix;
}
public static Direction Convert(LodDirection lodDirection)
{
return Direction.byName(lodDirection.name());
} }
/** returns the next debug mode */
public DebugMode getNext()
{
return this.next;
}
} }
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge.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.forge.wrappers.block.BlockPosWrapper;
import com.seibel.lod.forge.wrappers.chunk.ChunkPosWrapper;
import com.seibel.lod.forge.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);
}
}
@@ -17,17 +17,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.lod.enums; package com.seibel.lod.forge.wrappers;
import net.minecraft.world.level.levelgen.Heightmap;
/** /**
* fast, fancy, or off * Stores any variables or code that
* may be shared between wrapper objects.
* *
* @author James Seibel * @author James Seibel
* @version 02-14-2021 * @version 11-20-2021
*/ */
public enum FogQuality public class WrapperUtil
{ {
FAST, /** If we ever need to use a heightmap for any reason, use this one. */
FANCY, public static final Heightmap.Types DEFAULT_HEIGHTMAP = Heightmap.Types.WORLD_SURFACE_WG;
OFF
} }
@@ -17,30 +17,30 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.lod.util; package com.seibel.lod.forge.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;
import java.util.concurrent.ThreadFactory;
/** /**
* Just a simple ThreadFactory to name ExecutorService * Contains methods that would have been static in BlockColorWrapper.
* threads, which can be helpful when debugging. * Since interfaces can't create/implement static methods we have
* to split the object up in two.
*
* @author James Seibel * @author James Seibel
* @version 8-15-2021 * @version 11-17-2021
*/ */
public class LodThreadFactory implements ThreadFactory public class BlockColorSingletonWrapper implements IBlockColorSingletonWrapper
{ {
public final String threadName; public static final BlockColorSingletonWrapper INSTANCE = new BlockColorSingletonWrapper();
public LodThreadFactory(String newThreadName)
{
threadName = newThreadName + " Thread";
}
@Override @Override
public Thread newThread(Runnable r) public IBlockColorWrapper getWaterColor()
{ {
return new Thread(r, threadName); return BlockColorWrapper.getBlockColorWrapper(Blocks.WATER.defaultBlockState(), new BlockPosWrapper(0,0, 0));
} }
} }
@@ -1,4 +1,4 @@
package com.seibel.lod.wrappers.Block; package com.seibel.lod.forge.wrappers.block;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -6,8 +6,10 @@ import java.util.Random;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.util.ColorUtil; import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.wrappers.MinecraftWrapper; import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
import com.seibel.lod.forge.wrappers.minecraft.MinecraftWrapper;
import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlasSprite;
@@ -23,13 +25,16 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraftforge.client.model.data.ModelDataMap; import net.minecraftforge.client.model.data.ModelDataMap;
//This class wraps the minecraft Block class /**
public class BlockColorWrapper * @author James Seibel
* @version 11-21-2021
*/
public class BlockColorWrapper implements IBlockColorWrapper
{ {
//set of block which require tint //set of block which require tint
public static final ConcurrentMap<Block, BlockColorWrapper> blockColorWrapperMap = new ConcurrentHashMap<>(); public static final ConcurrentMap<Block, IBlockColorWrapper> blockColorWrapperMap = new ConcurrentHashMap<>();
public static final ModelDataMap dataMap = new ModelDataMap.Builder().build(); public static final ModelDataMap dataMap = new ModelDataMap.Builder().build();
public static final BlockPosWrapper blockPos = new BlockPosWrapper(0,0,0); public static final AbstractBlockPosWrapper blockPos = new BlockPosWrapper(0,0,0);
public static Random random = new Random(0); public static Random random = new Random(0);
//public static BlockColourWrapper WATER_COLOR = getBlockColorWrapper(Blocks.WATER); //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 }; public static final Direction[] directions = new Direction[] { Direction.UP, Direction.EAST, Direction.SOUTH, Direction.WEST, Direction.NORTH, Direction.DOWN };
@@ -44,7 +49,7 @@ public class BlockColorWrapper
/**Constructor only require for the block instance we are wrapping**/ /**Constructor only require for the block instance we are wrapping**/
public BlockColorWrapper(BlockState blockState, BlockPosWrapper blockPosWrapper) public BlockColorWrapper(BlockState blockState, AbstractBlockPosWrapper blockPosWrapper)
{ {
this.block = blockState.getBlock(); this.block = blockState.getBlock();
this.color = 0; this.color = 0;
@@ -61,7 +66,7 @@ public class BlockColorWrapper
* this return a wrapper of the block in input * this return a wrapper of the block in input
* @param blockState of the block to wrap * @param blockState of the block to wrap
*/ */
static public BlockColorWrapper getBlockColorWrapper(BlockState blockState, BlockPosWrapper blockPosWrapper) static public IBlockColorWrapper getBlockColorWrapper(BlockState blockState, AbstractBlockPosWrapper blockPosWrapper)
{ {
//first we check if the block has already been wrapped //first we check if the block has already been wrapped
if (blockColorWrapperMap.containsKey(blockState.getBlock()) && blockColorWrapperMap.get(blockState.getBlock()) != null) if (blockColorWrapperMap.containsKey(blockState.getBlock()) && blockColorWrapperMap.get(blockState.getBlock()) != null)
@@ -69,7 +74,7 @@ public class BlockColorWrapper
//if it hasn't been created yet, we create it and save it in the map //if it hasn't been created yet, we create it and save it in the map
BlockColorWrapper blockWrapper = new BlockColorWrapper(blockState, blockPosWrapper); IBlockColorWrapper blockWrapper = new BlockColorWrapper(blockState, blockPosWrapper);
blockColorWrapperMap.put(blockState.getBlock(), blockWrapper); blockColorWrapperMap.put(blockState.getBlock(), blockWrapper);
//we return the newly created wrapper //we return the newly created wrapper
@@ -80,7 +85,7 @@ public class BlockColorWrapper
* Generate the color of the given block from its texture * Generate the color of the given block from its texture
* and store it for later use. * and store it for later use.
*/ */
private void setupColorAndTint(BlockState blockState, BlockPosWrapper blockPosWrapper) private void setupColorAndTint(BlockState blockState, AbstractBlockPosWrapper blockPosWrapper)
{ {
MinecraftWrapper mc = MinecraftWrapper.INSTANCE; MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
TextureAtlasSprite texture; TextureAtlasSprite texture;
@@ -121,7 +126,7 @@ public class BlockColorWrapper
else else
{ {
isColored = true; isColored = true;
texture = mc.getModelManager().getBlockModelShaper().getTexture(block.defaultBlockState(), mc.getClientLevel(), blockPosWrapper.getBlockPos()); texture = mc.getModelManager().getBlockModelShaper().getTexture(block.defaultBlockState(), mc.getClientLevel(), ((BlockPosWrapper) blockPosWrapper).getBlockPos());
} }
int count = 0; int count = 0;
@@ -227,11 +232,13 @@ public class BlockColorWrapper
//Colors getters// //Colors getters//
//--------------// //--------------//
@Override
public boolean hasColor() public boolean hasColor()
{ {
return isColored; return isColored;
} }
@Override
public int getColor() public int getColor()
{ {
return color; return color;
@@ -242,21 +249,25 @@ public class BlockColorWrapper
//------------// //------------//
@Override
public boolean hasTint() public boolean hasTint()
{ {
return toTint; return toTint;
} }
@Override
public boolean hasGrassTint() public boolean hasGrassTint()
{ {
return grassTint; return grassTint;
} }
@Override
public boolean hasFolliageTint() public boolean hasFolliageTint()
{ {
return folliageTint; return folliageTint;
} }
@Override
public boolean hasWaterTint() public boolean hasWaterTint()
{ {
return waterTint; return waterTint;
@@ -1,13 +1,18 @@
package com.seibel.lod.wrappers.Block; package com.seibel.lod.forge.wrappers.block;
import java.util.Objects; import java.util.Objects;
import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
//This class wraps the minecraft BlockPos.Mutable (and BlockPos) class /**
public class BlockPosWrapper * @author James Seibel
* @version 11-21-2021
*/
public class BlockPosWrapper extends AbstractBlockPosWrapper
{ {
private BlockPos.MutableBlockPos blockPos; private BlockPos.MutableBlockPos blockPos;
@@ -24,33 +29,36 @@ public class BlockPosWrapper
@Override
public void set(int x, int y, int z) public void set(int x, int y, int z)
{ {
blockPos.set(x, y, z); blockPos.set(x, y, z);
} }
@Override
public int getX() public int getX()
{ {
return blockPos.getX(); return blockPos.getX();
} }
@Override
public int getY() public int getY()
{ {
return blockPos.getY(); return blockPos.getY();
} }
@Override
public int getZ() public int getZ()
{ {
return blockPos.getZ(); return blockPos.getZ();
} }
@Override
public int get(Direction.Axis axis) public int get(LodDirection.Axis axis)
{ {
return axis.choose(getX(), getY(), getZ()); return axis.choose(getX(), getY(), getZ());
} }
public BlockPos.MutableBlockPos getBlockPos() public BlockPos.MutableBlockPos getBlockPos()
{ {
return blockPos; return blockPos;
@@ -66,6 +74,7 @@ public class BlockPosWrapper
return Objects.hash(blockPos); return Objects.hash(blockPos);
} }
@Override
public BlockPosWrapper offset(int x, int y, int z) public BlockPosWrapper offset(int x, int y, int z)
{ {
blockPos.set(blockPos.getX() + x, blockPos.getY() + y, blockPos.getZ() + z); blockPos.set(blockPos.getX() + x, blockPos.getY() + y, blockPos.getZ() + z);
@@ -1,11 +1,14 @@
package com.seibel.lod.wrappers.Block; package com.seibel.lod.forge.wrappers.block;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper; 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.forge.wrappers.chunk.ChunkWrapper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
@@ -15,8 +18,11 @@ import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraft.world.phys.shapes.VoxelShape;
//This class wraps the minecraft Block class /**
public class BlockShapeWrapper * @author James Seibel
* @version 11-21-2021
*/
public class BlockShapeWrapper implements IBlockShapeWrapper
{ {
//set of block which require tint //set of block which require tint
public static final ConcurrentMap<Block, BlockShapeWrapper> blockShapeWrapperMap = new ConcurrentHashMap<>(); public static final ConcurrentMap<Block, BlockShapeWrapper> blockShapeWrapperMap = new ConcurrentHashMap<>();
@@ -28,7 +34,7 @@ public class BlockShapeWrapper
private boolean noCollision; private boolean noCollision;
/**Constructor only require for the block instance we are wrapping**/ /**Constructor only require for the block instance we are wrapping**/
public BlockShapeWrapper(Block block, ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper) public BlockShapeWrapper(Block block, IChunkWrapper chunkWrapper, AbstractBlockPosWrapper blockPosWrapper)
{ {
this.block = block; this.block = block;
this.nonFull = false; this.nonFull = false;
@@ -50,7 +56,7 @@ public class BlockShapeWrapper
* this return a wrapper of the block in input * this return a wrapper of the block in input
* @param block Block object to wrap * @param block Block object to wrap
*/ */
static public BlockShapeWrapper getBlockShapeWrapper(Block block, ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper) static public BlockShapeWrapper getBlockShapeWrapper(Block block, ChunkWrapper chunkWrapper, AbstractBlockPosWrapper blockPosWrapper)
{ {
//first we check if the block has already been wrapped //first we check if the block has already been wrapped
if (blockShapeWrapperMap.containsKey(block) && blockShapeWrapperMap.get(block) != null) if (blockShapeWrapperMap.containsKey(block) && blockShapeWrapperMap.get(block) != null)
@@ -65,10 +71,10 @@ public class BlockShapeWrapper
return blockWrapper; return blockWrapper;
} }
private void setupShapes(ChunkWrapper chunkWrapper, BlockPosWrapper blockPosWrapper) private void setupShapes(IChunkWrapper chunkWrapper, AbstractBlockPosWrapper blockPosWrapper)
{ {
ChunkAccess chunk = chunkWrapper.getChunk(); ChunkAccess chunk = ((ChunkWrapper) chunkWrapper).getChunk();
BlockPos blockPos = blockPosWrapper.getBlockPos(); BlockPos blockPos = ((BlockPosWrapper) blockPosWrapper).getBlockPos();
boolean noCollisionSetted = false; boolean noCollisionSetted = false;
boolean nonFullSetted = false; boolean nonFullSetted = false;
if (!block.defaultBlockState().getFluidState().isEmpty())// || block instanceof SixWayBlock) if (!block.defaultBlockState().getFluidState().isEmpty())// || block instanceof SixWayBlock)
@@ -103,6 +109,7 @@ public class BlockShapeWrapper
} }
} }
@Override
public boolean ofBlockToAvoid() public boolean ofBlockToAvoid()
{ {
return block.equals(Blocks.AIR) return block.equals(Blocks.AIR)
@@ -114,16 +121,19 @@ public class BlockShapeWrapper
//-----------------// //-----------------//
@Override
public boolean isNonFull() public boolean isNonFull()
{ {
return nonFull; return nonFull;
} }
@Override
public boolean hasNoCollision() public boolean hasNoCollision()
{ {
return noCollision; return noCollision;
} }
@Override
public boolean isToAvoid() public boolean isToAvoid()
{ {
return toAvoid; return toAvoid;
@@ -1,21 +1,26 @@
package com.seibel.lod.wrappers.Chunk; package com.seibel.lod.forge.wrappers.chunk;
import java.util.Objects; import java.util.Objects;
import com.seibel.lod.wrappers.Block.BlockPosWrapper; import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
import com.seibel.lod.forge.wrappers.block.BlockPosWrapper;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
//This class wraps the minecraft ChunkPos class /**
public class ChunkPosWrapper * @author James Seibel
* @version 11-21-2021
*/
public class ChunkPosWrapper extends AbstractChunkPosWrapper
{ {
private net.minecraft.world.level.ChunkPos chunkPos; private net.minecraft.world.level.ChunkPos chunkPos;
public ChunkPosWrapper(ChunkPos newChunkPos) public ChunkPosWrapper()
{ {
this.chunkPos = newChunkPos; this.chunkPos = new ChunkPos(0, 0);
} }
public ChunkPosWrapper(BlockPos blockPos) public ChunkPosWrapper(BlockPos blockPos)
@@ -23,50 +28,61 @@ public class ChunkPosWrapper
this.chunkPos = new ChunkPos(blockPos); this.chunkPos = new ChunkPos(blockPos);
} }
public ChunkPosWrapper(AbstractChunkPosWrapper newChunkPos)
public ChunkPosWrapper(ChunkPosWrapper newChunkPos)
{ {
this.chunkPos = newChunkPos.chunkPos; this.chunkPos = ((ChunkPosWrapper) newChunkPos).chunkPos;
} }
public ChunkPosWrapper(BlockPosWrapper blockPos) public ChunkPosWrapper(AbstractBlockPosWrapper blockPos)
{ {
this.chunkPos = new ChunkPos(blockPos.getBlockPos()); this.chunkPos = new ChunkPos(((BlockPosWrapper) blockPos).getBlockPos());
} }
public ChunkPosWrapper(int chunkX, int chunkZ) public ChunkPosWrapper(int chunkX, int chunkZ)
{ {
this.chunkPos = new ChunkPos(chunkX, chunkZ); this.chunkPos = new ChunkPos(chunkX, chunkZ);
} }
public ChunkPosWrapper(ChunkPos pos)
{
this.chunkPos = pos;
}
@Override
public int getX() public int getX()
{ {
return chunkPos.x; return chunkPos.x;
} }
@Override
public int getZ() public int getZ()
{ {
return chunkPos.z; return chunkPos.z;
} }
@Override
public int getMinBlockX() public int getMinBlockX()
{ {
return chunkPos.getMinBlockX(); return chunkPos.getMinBlockX();
} }
@Override
public int getMinBlockZ() public int getMinBlockZ()
{ {
return chunkPos.getMinBlockZ(); return chunkPos.getMinBlockZ();
} }
@Override
public int getRegionX() public int getRegionX()
{ {
return chunkPos.getRegionX(); return chunkPos.getRegionX();
} }
@Override
public int getRegionZ() public int getRegionZ()
{ {
return chunkPos.getRegionZ(); return chunkPos.getRegionZ();
@@ -77,17 +93,20 @@ public class ChunkPosWrapper
return chunkPos; return chunkPos;
} }
@Override public boolean equals(Object o) @Override
public boolean equals(Object o)
{ {
return chunkPos.equals(o); return chunkPos.equals(o);
} }
@Override public int hashCode() @Override
public int hashCode()
{ {
return Objects.hash(chunkPos); return Objects.hash(chunkPos);
} }
public BlockPosWrapper getWorldPosition() @Override
public AbstractBlockPosWrapper getWorldPosition()
{ {
// the parameter here is the y position // the parameter here is the y position
BlockPos blockPos = chunkPos.getMiddleBlockPosition(0); BlockPos blockPos = chunkPos.getMiddleBlockPosition(0);
@@ -0,0 +1,130 @@
package com.seibel.lod.forge.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.AbstractChunkPosWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.lod.forge.wrappers.WrapperUtil;
import com.seibel.lod.forge.wrappers.block.BlockColorWrapper;
import com.seibel.lod.forge.wrappers.block.BlockPosWrapper;
import com.seibel.lod.forge.wrappers.block.BlockShapeWrapper;
import com.seibel.lod.forge.wrappers.world.BiomeWrapper;
import net.minecraft.world.level.block.LiquidBlock;
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 James Seibel
* @version 11-21-2021
*/
public class ChunkWrapper implements IChunkWrapper
{
private ChunkAccess chunk;
private AbstractChunkPosWrapper 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
if((blockState.getBlock() instanceof LiquidBlock))// && !(blockState.getBlock() instanceof IWaterLoggable))
return true;
//This type of block could be in water
if(blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get())
return true;
return false;
}
@Override
public int getHeightMapValue(int xRel, int zRel)
{
return chunk.getOrCreateHeightmapUnprimed(WrapperUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
@Override
public IBiomeWrapper 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()), blockPos);
}
@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 AbstractChunkPosWrapper getPos()
{
return chunkPos;
}
@Override
public boolean isLightCorrect(){
return chunk.isLightCorrect();
}
public boolean
isWaterLogged(BlockPosWrapper blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos.getBlockPos());
// //This type of block is always in water
// if((blockState.getBlock() instanceof ILiquidContainer) && !(blockState.getBlock() instanceof IWaterLoggable))
// return true;
//This type of block could be in water
if(blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get())
return true;
return false;
}
public int getEmittedBrightness(BlockPosWrapper blockPos)
{
return chunk.getLightEmission(blockPos.getBlockPos());
}
@Override
public boolean isWaterLogged(AbstractBlockPosWrapper blockPos)
{
// TODO Auto-generated method stub
return false;
}
@Override
public int getEmittedBrightness(AbstractBlockPosWrapper blockPos)
{
// TODO Auto-generated method stub
return 0;
}
}
@@ -0,0 +1,491 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge.wrappers.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.wrapperInterfaces.config.ILodConfigWrapperSingleton;
import com.seibel.lod.forge.ForgeConfig;
/**
* @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 ForgeConfig.CLIENT.graphics.quality.drawResolution.get();
}
@Override
public void setDrawResolution(HorizontalResolution newHorizontalResolution)
{
ForgeConfig.CLIENT.graphics.quality.drawResolution.set(newHorizontalResolution);
}
@Override
public int getLodChunkRenderDistance()
{
return ForgeConfig.CLIENT.graphics.quality.lodChunkRenderDistance.get();
}
@Override
public void setLodChunkRenderDistance(int newLodChunkRenderDistance)
{
ForgeConfig.CLIENT.graphics.quality.lodChunkRenderDistance.set(newLodChunkRenderDistance);
}
@Override
public VerticalQuality getVerticalQuality()
{
return ForgeConfig.CLIENT.graphics.quality.verticalQuality.get();
}
@Override
public void setVerticalQuality(VerticalQuality newVerticalQuality)
{
ForgeConfig.CLIENT.graphics.quality.verticalQuality.set(newVerticalQuality);
}
@Override
public HorizontalScale getHorizontalScale()
{
return ForgeConfig.CLIENT.graphics.quality.horizontalScale.get();
}
@Override
public void setHorizontalScale(HorizontalScale newHorizontalScale)
{
ForgeConfig.CLIENT.graphics.quality.horizontalScale.set(newHorizontalScale);
}
@Override
public HorizontalQuality getHorizontalQuality()
{
return ForgeConfig.CLIENT.graphics.quality.horizontalQuality.get();
}
@Override
public void setHorizontalQuality(HorizontalQuality newHorizontalQuality)
{
ForgeConfig.CLIENT.graphics.quality.horizontalQuality.set(newHorizontalQuality);
}
}
public static class FogQuality implements IFogQuality
{
@Override
public FogDistance getFogDistance()
{
return ForgeConfig.CLIENT.graphics.fogQuality.fogDistance.get();
}
@Override
public void setFogDistance(FogDistance newFogDistance)
{
ForgeConfig.CLIENT.graphics.fogQuality.fogDistance.set(newFogDistance);
}
@Override
public FogDrawOverride getFogDrawOverride()
{
return ForgeConfig.CLIENT.graphics.fogQuality.fogDrawOverride.get();
}
@Override
public void setFogDrawOverride(FogDrawOverride newFogDrawOverride)
{
ForgeConfig.CLIENT.graphics.fogQuality.fogDrawOverride.set(newFogDrawOverride);
}
@Override
public boolean getDisableVanillaFog()
{
return ForgeConfig.CLIENT.graphics.fogQuality.disableVanillaFog.get();
}
@Override
public void setDisableVanillaFog(boolean newDisableVanillaFog)
{
ForgeConfig.CLIENT.graphics.fogQuality.disableVanillaFog.set(newDisableVanillaFog);
}
}
public static class AdvancedGraphics implements IAdvancedGraphics
{
@Override
public LodTemplate getLodTemplate()
{
return ForgeConfig.CLIENT.graphics.advancedGraphics.lodTemplate.get();
}
@Override
public void setLodTemplate(LodTemplate newLodTemplate)
{
ForgeConfig.CLIENT.graphics.advancedGraphics.lodTemplate.set(newLodTemplate);
}
@Override
public boolean getDisableDirectionalCulling()
{
return ForgeConfig.CLIENT.graphics.advancedGraphics.disableDirectionalCulling.get();
}
@Override
public void setDisableDirectionalCulling(boolean newDisableDirectionalCulling)
{
ForgeConfig.CLIENT.graphics.advancedGraphics.disableDirectionalCulling.set(newDisableDirectionalCulling);
}
@Override
public boolean getAlwaysDrawAtMaxQuality()
{
return ForgeConfig.CLIENT.graphics.advancedGraphics.alwaysDrawAtMaxQuality.get();
}
@Override
public void setAlwaysDrawAtMaxQuality(boolean newAlwaysDrawAtMaxQuality)
{
ForgeConfig.CLIENT.graphics.advancedGraphics.alwaysDrawAtMaxQuality.set(newAlwaysDrawAtMaxQuality);
}
@Override
public VanillaOverdraw getVanillaOverdraw()
{
return ForgeConfig.CLIENT.graphics.advancedGraphics.vanillaOverdraw.get();
}
@Override
public void setVanillaOverdraw(VanillaOverdraw newVanillaOverdraw)
{
ForgeConfig.CLIENT.graphics.advancedGraphics.vanillaOverdraw.set(newVanillaOverdraw);
}
@Override
public GpuUploadMethod getGpuUploadMethod()
{
return ForgeConfig.CLIENT.graphics.advancedGraphics.gpuUploadMethod.get();
}
@Override
public void setGpuUploadMethod(GpuUploadMethod newDisableVanillaFog)
{
ForgeConfig.CLIENT.graphics.advancedGraphics.gpuUploadMethod.set(newDisableVanillaFog);
}
@Override
public boolean getUseExtendedNearClipPlane()
{
return ForgeConfig.CLIENT.graphics.advancedGraphics.useExtendedNearClipPlane.get();
}
@Override
public void setUseExtendedNearClipPlane(boolean newUseExtendedNearClipPlane)
{
ForgeConfig.CLIENT.graphics.advancedGraphics.useExtendedNearClipPlane.set(newUseExtendedNearClipPlane);
}
}
}
//========================//
// WorldGenerator Configs //
//========================//
public static class WorldGenerator implements IWorldGenerator
{
@Override
public GenerationPriority getGenerationPriority()
{
return ForgeConfig.CLIENT.worldGenerator.generationPriority.get();
}
@Override
public void setGenerationPriority(GenerationPriority newGenerationPriority)
{
ForgeConfig.CLIENT.worldGenerator.generationPriority.set(newGenerationPriority);
}
@Override
public DistanceGenerationMode getDistanceGenerationMode()
{
return ForgeConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
}
@Override
public void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode)
{
ForgeConfig.CLIENT.worldGenerator.distanceGenerationMode.set(newDistanceGenerationMode);
}
@Override
public boolean getAllowUnstableFeatureGeneration()
{
return ForgeConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.get();
}
@Override
public void setAllowUnstableFeatureGeneration(boolean newAllowUnstableFeatureGeneration)
{
ForgeConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.set(newAllowUnstableFeatureGeneration);
}
@Override
public BlocksToAvoid getBlocksToAvoid()
{
return ForgeConfig.CLIENT.worldGenerator.blocksToAvoid.get();
}
@Override
public void setBlockToAvoid(BlocksToAvoid newBlockToAvoid)
{
ForgeConfig.CLIENT.worldGenerator.blocksToAvoid.set(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 ForgeConfig.CLIENT.advanced.threading.numberOfWorldGenerationThreads.get();
}
@Override
public void setNumberOfWorldGenerationThreads(int newNumberOfWorldGenerationThreads)
{
ForgeConfig.CLIENT.advanced.threading.numberOfWorldGenerationThreads.set(newNumberOfWorldGenerationThreads);
}
@Override
public int getNumberOfBufferBuilderThreads()
{
return ForgeConfig.CLIENT.advanced.threading.numberOfBufferBuilderThreads.get();
}
@Override
public void setNumberOfBufferBuilderThreads(int newNumberOfWorldBuilderThreads)
{
ForgeConfig.CLIENT.advanced.threading.numberOfBufferBuilderThreads.set(newNumberOfWorldBuilderThreads);
}
}
//===============//
// Debug Options //
//===============//
public static class Debugging implements IDebugging
{
@Override
public boolean getDrawLods()
{
return ForgeConfig.CLIENT.advanced.debugging.drawLods.get();
}
@Override
public void setDrawLods(boolean newDrawLods)
{
ForgeConfig.CLIENT.advanced.debugging.drawLods.set(newDrawLods);
}
@Override
public DebugMode getDebugMode()
{
return ForgeConfig.CLIENT.advanced.debugging.debugMode.get();
}
@Override
public void setDebugMode(DebugMode newDebugMode)
{
ForgeConfig.CLIENT.advanced.debugging.debugMode.set(newDebugMode);
}
@Override
public boolean getDebugKeybindingsEnabled()
{
return ForgeConfig.CLIENT.advanced.debugging.enableDebugKeybindings.get();
}
@Override
public void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings)
{
ForgeConfig.CLIENT.advanced.debugging.enableDebugKeybindings.set(newEnableDebugKeybindings);
}
}
public static class Buffers implements IBuffers
{
@Override
public BufferRebuildTimes getRebuildTimes()
{
return ForgeConfig.CLIENT.advanced.buffers.rebuildTimes.get();
}
@Override
public void setRebuildTimes(BufferRebuildTimes newBufferRebuildTimes)
{
ForgeConfig.CLIENT.advanced.buffers.rebuildTimes.set(newBufferRebuildTimes);
}
}
}
}
}
@@ -0,0 +1,141 @@
package com.seibel.lod.forge.wrappers.minecraft;
import java.util.HashSet;
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.forge.wrappers.McObjectConverter;
import com.seibel.lod.forge.wrappers.block.BlockPosWrapper;
import com.seibel.lod.forge.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.LevelRenderer.RenderChunkInfo;
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.getFov(gameRenderer.getMainCamera(), partialTicks, true)));
}
@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. <br><br>
* <p>
* 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<AbstractChunkPosWrapper> getRenderedChunks()
{
HashSet<AbstractChunkPosWrapper> loadedPos = new HashSet<>();
// Wow, those are some long names!
// go through every RenderInfo to get the compiled chunks
LevelRenderer renderer = mc.levelRenderer;
for (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;
}
}
@@ -17,19 +17,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.seibel.lod.wrappers; package com.seibel.lod.forge.wrappers.minecraft;
import java.awt.Color; import java.awt.Color;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.platform.Window;
import com.seibel.lod.ModInfo; import com.seibel.lod.ModInfo;
import com.seibel.lod.proxy.ClientProxy; import com.seibel.lod.core.api.ClientApi;
import com.seibel.lod.util.LodUtil; import com.seibel.lod.core.enums.LodDirection;
import com.seibel.lod.wrappers.Block.BlockPosWrapper; import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper; import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
import com.seibel.lod.wrappers.World.WorldWrapper; 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.forge.wrappers.McObjectConverter;
import com.seibel.lod.forge.wrappers.block.BlockPosWrapper;
import com.seibel.lod.forge.wrappers.chunk.ChunkPosWrapper;
import com.seibel.lod.forge.wrappers.misc.LightMapWrapper;
import com.seibel.lod.forge.wrappers.world.DimensionTypeWrapper;
import com.seibel.lod.forge.wrappers.world.WorldWrapper;
import net.minecraft.CrashReport; import net.minecraft.CrashReport;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@@ -45,8 +55,8 @@ import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.server.IntegratedServer; import net.minecraft.client.server.IntegratedServer;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.dimension.DimensionType; import net.minecraft.world.level.dimension.DimensionType;
@@ -58,7 +68,7 @@ import net.minecraft.world.level.dimension.DimensionType;
* @author James Seibel * @author James Seibel
* @version 9-16-2021 * @version 9-16-2021
*/ */
public class MinecraftWrapper public class MinecraftWrapper implements IMinecraftWrapper
{ {
public static final MinecraftWrapper INSTANCE = new MinecraftWrapper(); public static final MinecraftWrapper INSTANCE = new MinecraftWrapper();
@@ -70,6 +80,9 @@ public class MinecraftWrapper
*/ */
private NativeImage lightMap = null; private NativeImage lightMap = null;
private ProfilerWrapper profilerWrapper;
private MinecraftWrapper() private MinecraftWrapper()
{ {
@@ -89,6 +102,7 @@ public class MinecraftWrapper
* <p> * <p>
* This doesn't affect OpenGL objects in any way. * This doesn't affect OpenGL objects in any way.
*/ */
@Override
public void clearFrameObjectCache() public void clearFrameObjectCache()
{ {
lightMap = null; lightMap = null;
@@ -100,30 +114,53 @@ public class MinecraftWrapper
// method wrappers // // method wrappers //
//=================// //=================//
public float getShading(Direction direction) @Override
public float getShade(LodDirection lodDirection)
{ {
return mc.level.getShade(Direction.UP, true); Direction mcDir = McObjectConverter.Convert(lodDirection);
return mc.level.getShade(mcDir, true);
} }
@Override
public boolean hasSinglePlayerServer() public boolean hasSinglePlayerServer()
{ {
return mc.hasSingleplayerServer(); return mc.hasSingleplayerServer();
} }
public DimensionType getCurrentDimension() @Override
public String getCurrentServerName()
{ {
return mc.player.level.dimensionType(); 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() public String getCurrentDimensionId()
{ {
return LodUtil.getDimensionIDFromWorld(mc.level); return LodUtil.getDimensionIDFromWorld(WorldWrapper.getWorldWrapper(mc.level));
} }
/** /** This texture changes every frame */
* This texture changes every frame @Override
*/ public ILightMapWrapper getCurrentLightMap()
public NativeImage getCurrentLightMap()
{ {
// get the current lightMap if the cache is empty // get the current lightMap if the cache is empty
if (lightMap == null) if (lightMap == null)
@@ -131,7 +168,7 @@ public class MinecraftWrapper
LightTexture tex = mc.gameRenderer.lightTexture(); LightTexture tex = mc.gameRenderer.lightTexture();
lightMap = tex.lightPixels; lightMap = tex.lightPixels;
} }
return lightMap; return new LightMapWrapper(lightMap);
} }
/** /**
@@ -140,6 +177,7 @@ public class MinecraftWrapper
* @param u x location in texture space * @param u x location in texture space
* @param v z location in texture space * @param v z location in texture space
*/ */
@Override
public int getColorIntFromLightMap(int u, int v) public int getColorIntFromLightMap(int u, int v)
{ {
if (lightMap == null) if (lightMap == null)
@@ -157,6 +195,7 @@ public class MinecraftWrapper
* @param u x location in texture space * @param u x location in texture space
* @param v z location in texture space * @param v z location in texture space
*/ */
@Override
public Color getColorFromLightMap(int u, int v) public Color getColorFromLightMap(int u, int v)
{ {
return LodUtil.intToColor(lightMap.getPixelRGBA(u, v)); return LodUtil.intToColor(lightMap.getPixelRGBA(u, v));
@@ -173,13 +212,21 @@ public class MinecraftWrapper
{ {
return mc.player; return mc.player;
} }
@Override
public boolean playerExists()
{
return mc.player != null;
}
@Override
public BlockPosWrapper getPlayerBlockPos() public BlockPosWrapper getPlayerBlockPos()
{ {
BlockPos playerPos = getPlayer().blockPosition(); BlockPos playerPos = getPlayer().blockPosition();
return new BlockPosWrapper(playerPos.getX(), playerPos.getY(), playerPos.getZ()); return new BlockPosWrapper(playerPos.getX(), playerPos.getY(), playerPos.getZ());
} }
@Override
public ChunkPosWrapper getPlayerChunkPos() public ChunkPosWrapper getPlayerChunkPos()
{ {
ChunkPos playerPos = getPlayer().chunkPosition(); ChunkPos playerPos = getPlayer().chunkPosition();
@@ -201,6 +248,31 @@ public class MinecraftWrapper
return mc.level; return mc.level;
} }
@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<ServerLevel> worlds = server.getAllLevels();
for (ServerLevel world : worlds)
{
if (world.dimensionType() == dimension)
{
serverWorld = world;
break;
}
}
return WorldWrapper.getWorldWrapper(serverWorld);
}
public WorldWrapper getWrappedClientLevel() public WorldWrapper getWrappedClientLevel()
{ {
return WorldWrapper.getWorldWrapper(mc.level); return WorldWrapper.getWorldWrapper(mc.level);
@@ -231,21 +303,27 @@ public class MinecraftWrapper
return WorldWrapper.getWorldWrapper(returnWorld); return WorldWrapper.getWorldWrapper(returnWorld);
} }
/** Measured in chunks */ @Override
public int getRenderDistance() public IWorldWrapper getWrappedClientWorld()
{ {
return mc.options.renderDistance; return WorldWrapper.getWorldWrapper(mc.level);
} }
@Override
public File getGameDirectory() public File getGameDirectory()
{ {
return mc.gameDirectory; return mc.gameDirectory;
} }
public ProfilerFiller getProfiler() @Override
public IProfilerWrapper getProfiler()
{ {
return mc.getProfiler(); if (profilerWrapper == null)
} profilerWrapper = new ProfilerWrapper(mc.getProfiler());
else if (mc.getProfiler() != profilerWrapper.profiler)
profilerWrapper.profiler = mc.getProfiler();
return profilerWrapper; }
public ClientPacketListener getConnection() public ClientPacketListener getConnection()
{ {
@@ -267,6 +345,7 @@ public class MinecraftWrapper
return mc.getWindow(); return mc.getWindow();
} }
@Override
public float getSkyDarken(float partialTicks) public float getSkyDarken(float partialTicks)
{ {
return mc.level.getSkyDarken(partialTicks); return mc.level.getSkyDarken(partialTicks);
@@ -277,6 +356,12 @@ public class MinecraftWrapper
return mc.getSingleplayerServer(); return mc.getSingleplayerServer();
} }
@Override
public boolean connectedToServer()
{
return mc.getCurrentServer() != null;
}
public ServerData getCurrentServer() public ServerData getCurrentServer()
{ {
return mc.getCurrentServer(); return mc.getCurrentServer();
@@ -287,21 +372,47 @@ public class MinecraftWrapper
return mc.levelRenderer; return mc.levelRenderer;
} }
/** Returns all worlds available to the server */
@Override
public ArrayList<IWorldWrapper> getAllServerWorlds()
{
ArrayList<IWorldWrapper> worlds = new ArrayList<IWorldWrapper>();
Iterable<ServerLevel> 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 <br> <br> * Crashes Minecraft, displaying the given errorMessage <br> <br>
* In the following format: <br> * In the following format: <br>
* *
* The game crashed whilst <strong>errorMessage</strong> <br> * The game crashed whilst <strong>errorMessage</strong> <br>
* Error: <strong>java.lang.ExceptionClass: exceptionErrorMessage</strong> <br> * Error: <strong>ExceptionClass: exceptionErrorMessage</strong> <br>
* Exit Code: -1 <br> * Exit Code: -1 <br>
*/ */
@Override
public void crashMinecraft(String errorMessage, Throwable exception) public void crashMinecraft(String errorMessage, Throwable exception)
{ {
ClientProxy.LOGGER.error(ModInfo.READABLE_NAME + " had the following error: [" + errorMessage + "]. Crashing Minecraft..."); ClientApi.LOGGER.error(ModInfo.READABLE_NAME + " had the following error: [" + errorMessage + "]. Crashing Minecraft...");
CrashReport report = new CrashReport(errorMessage, exception); CrashReport report = new CrashReport(errorMessage, exception);
Minecraft.crash(report); Minecraft.crash(report);
} }
} }
@@ -0,0 +1,41 @@
package com.seibel.lod.forge.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();
}
}
@@ -0,0 +1,29 @@
package com.seibel.lod.forge.wrappers.misc;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.lod.core.wrapperInterfaces.misc.ILightMapWrapper;
/**
* @author James Seibel
* @version 11-21-2021
*/
public class LightMapWrapper implements ILightMapWrapper
{
static NativeImage lightMap = null;
public LightMapWrapper(NativeImage newLightMap)
{
lightMap = newLightMap;
}
public static void setLightMap(NativeImage newLightMap)
{
lightMap = newLightMap;
}
@Override
public int getLightValue(int skyLight, int blockLight)
{
return lightMap.getPixelRGBA(skyLight, blockLight);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge.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.forge.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());
}
}
@@ -1,18 +1,38 @@
package com.seibel.lod.wrappers.World; /*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge.wrappers.world;
import java.awt.Color; import java.awt.Color;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.util.LodUtil; import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.MaterialColor; import net.minecraft.world.level.material.MaterialColor;
//This class wraps the minecraft BlockPos.Mutable (and BlockPos) class //This class wraps the minecraft BlockPos.Mutable (and BlockPos) class
public class BiomeWrapper public class BiomeWrapper implements IBiomeWrapper
{ {
public static final ConcurrentMap<Biome, BiomeWrapper> biomeWrapperMap = new ConcurrentHashMap<>(); public static final ConcurrentMap<Biome, BiomeWrapper> biomeWrapperMap = new ConcurrentHashMap<>();
@@ -23,7 +43,7 @@ public class BiomeWrapper
this.biome = biome; this.biome = biome;
} }
static public BiomeWrapper getBiomeWrapper(Biome biome) static public IBiomeWrapper getBiomeWrapper(Biome biome)
{ {
//first we check if the biome has already been wrapped //first we check if the biome has already been wrapped
if(biomeWrapperMap.containsKey(biome) && biomeWrapperMap.get(biome) != null) if(biomeWrapperMap.containsKey(biome) && biomeWrapperMap.get(biome) != null)
@@ -40,6 +60,7 @@ public class BiomeWrapper
/** Returns a color int for the given biome. */ /** Returns a color int for the given biome. */
@Override
public int getColorForBiome(int x, int z) public int getColorForBiome(int x, int z)
{ {
int colorInt; int colorInt;
@@ -99,29 +120,32 @@ public class BiomeWrapper
return colorInt; return colorInt;
} }
@Override
public int getGrassTint(int x, int z) public int getGrassTint(int x, int z)
{ {
return biome.getGrassColor(x, z); return biome.getGrassColor(x, z);
} }
@Override
public int getFolliageTint() public int getFolliageTint()
{ {
return biome.getFoliageColor(); return biome.getFoliageColor();
} }
@Override
public int getWaterTint() public int getWaterTint()
{ {
return biome.getWaterColor(); return biome.getWaterColor();
} }
@Override public boolean equals(Object o) @Override public boolean equals(Object obj)
{ {
if (this == o) if (this == obj)
return true; return true;
if (!(o instanceof BiomeWrapper)) if (!(obj instanceof BiomeWrapper))
return false; return false;
BiomeWrapper that = (BiomeWrapper) o; BiomeWrapper that = (BiomeWrapper) obj;
return Objects.equals(biome, that.biome); return Objects.equals(biome, that.biome);
} }
@@ -0,0 +1,82 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge.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 James Seibel
* @version 11-21-2021
*/
public class DimensionTypeWrapper implements IDimensionTypeWrapper
{
private static final ConcurrentMap<DimensionType, DimensionTypeWrapper> dimensionTypeWrapperMap = new ConcurrentHashMap<>();
private 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;
}
public static void clearMap()
{
dimensionTypeWrapperMap.clear();
}
@Override
public String getDimensionName()
{
return dimensionType.effectsLocation().getPath();
}
@Override
public boolean hasCeiling()
{
return dimensionType.hasCeiling();
}
@Override
public boolean hasSkyLight()
{
return dimensionType.hasSkyLight();
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.forge.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.IBiomeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
import com.seibel.lod.forge.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.LevelAccessor;
/**
* @author James Seibel
* @author ??
* @version 11-21-2021
*/
public class WorldWrapper implements IWorldWrapper
{
private static final ConcurrentMap<LevelAccessor, WorldWrapper> worldWrapperMap = new ConcurrentHashMap<>();
private final LevelAccessor world;
public final WorldType worldType;
public WorldWrapper(LevelAccessor 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(LevelAccessor world)
{
//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 IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos)
{
return BiomeWrapper.getBiomeWrapper(world.getBiome(((BlockPosWrapper) blockPos).getBlockPos()));
}
public LevelAccessor 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.getDataStorage().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();
}
}
@@ -0,0 +1,369 @@
package com.seibel.lod.forge.wrappers.worldGeneration;
import java.util.concurrent.ConcurrentHashMap;
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.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.forge.wrappers.chunk.ChunkWrapper;
import com.seibel.lod.forge.wrappers.world.WorldWrapper;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
/**
* @author James Seibel
* @version 11-13-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<Integer, ConfiguredFeature<?, ?>> 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<IChunk> chunkList = new LinkedList<>();
// ChunkPrimer chunk = new ChunkPrimer(((ChunkPosWrapper) pos).getChunkPos(), UpgradeData.EMPTY);
// chunkList.add(chunk);
//
// ServerChunkProvider chunkSource = serverWorld.getChunkSource();
// ChunkGenerator chunkGen = chunkSource.generator;
//
// // generate the terrain (this is thread safe)
// ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
// // override the chunk status, so we can run the next generator stage
// chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
// chunkGen.createBiomes(serverWorld.registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), chunk);
// chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
//
//
//
//
// // generate fake height data for this LOD
// int seaLevel = serverWorld.getSeaLevel();
//
// boolean simulateHeight = 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<IChunk> chunkList = new LinkedList<>();
// ChunkPrimer chunk = new ChunkPrimer(((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);
//
// // 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
* <p>
* Causes concurrentModification Exceptions,
* which could cause instability or world generation bugs
*/
@Override
public void generateFeatures(AbstractChunkPosWrapper pos)
{
// List<IChunk> chunkList = new LinkedList<>();
// ChunkPrimer chunk = new ChunkPrimer(((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<Biome> 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.Category.JUNGLE)
// {
// // should probably use the heightmap here instead of seaLevel,
// // but this seems to get the job done well enough
// biomes.add(biome);
// }
// }
// }
//
// boolean allowUnstableFeatures = 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<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
//
// for (List<Supplier<ConfiguredFeature<?, ?>>> suppliers : featuresForState)
// {
// for (Supplier<ConfiguredFeature<?, ?>> 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.
* <p>
* on pre generated chunks 0 - 1 ms <br>
* on un generated chunks 0 - 50 ms <br>
* with the median seeming to hover around 15 - 30 ms <br>
* and outliers in the 100 - 200 ms range <br>
* <p>
* 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)
*/
}
@@ -1,74 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.io.File;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.storage.ChunkSerializer;
/**
*
* @author ??
* @version ??
*/
public class ChunkLoader
{
public static ChunkAccess getChunkFromFile(ChunkPos pos){
ClientLevel ClientLevel = MinecraftWrapper.INSTANCE.getClientLevel();
if (ClientLevel == null)
return null;
ServerLevel serverWorld = LodUtil.getServerLevelFromDimension(ClientLevel.dimensionType());
try
{
File file = new File(serverWorld.getChunkSource().getDataStorage().dataFolder.getParent() + File.separatorChar + "region", "r." + (pos.x >> 5) + "." + (pos.z >> 5) + ".mca");
if(!file.exists())
return null;
ChunkAccess 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;
}
}
}
@@ -1,401 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
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.enums.DistanceGenerationMode;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodRegion;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.objects.VerticalLevelContainer;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.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 <br>
* Added to the end of the file path when saving to prevent
* nulling a currently existing file. <br>
* 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.SERVER;
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.SERVER)
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();
ClientProxy.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();
ClientProxy.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)
{
ClientProxy.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
ClientProxy.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.<br>
* Note: <br>
* 1. If a file already exists for a newer version
* the file won't be written.<br>
* 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)
{
ClientProxy.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.");
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;
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
{
fileVersion = inputStream.read();
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 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(region.getLevel(detailLevel).toDataString());
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)
{
ClientProxy.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
e.printStackTrace();
}
}
}
//================//
// helper methods //
//================//
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
* Returns null if this object isn't available to read and write. <br><br>
* <p>
* example: "lod.0.0.txt" <br>
* <p>
* 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)
{
ClientProxy.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
e.printStackTrace();
return null;
}
}
}
@@ -1,120 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.handlers;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import com.seibel.lod.enums.FogQuality;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
* This object is used to get variables from methods
* where they are private. Specifically the fog setting
* in Optifine.
*
* @author James Seibel
* @version 9-25-2021
*/
public class ReflectionHandler
{
public static final ReflectionHandler INSTANCE = new ReflectionHandler();
private final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
public Field ofFogField = null;
public Method vertexBufferUploadMethod = null;
private ReflectionHandler()
{
setupFogField();
}
/**
* finds the Optifine fog type field
*/
private void setupFogField()
{
// get every variable from the entity renderer
Field[] optionFields = mc.getOptions().getClass().getDeclaredFields();
// 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
ClientProxy.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.
*/
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(mc.getOptions());
}
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;
}
}
}
@@ -1,115 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
/**
* 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();
}
@@ -1,913 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.GenerationPriority;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.handlers.LodDimensionFileHandler;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodThreadFactory;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.dimension.DimensionType;
/**
* This object holds all loaded LOD regions
* for a given dimension. <Br><Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
* @author Leonardo Amato
* @author James Seibel
* @version 10-10-2021
*/
public class LodDimension
{
public final DimensionType 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 ChunkPos lastExpandedChunk;
/** prevents the cutAndExpandThread from cutting at the same location multiple times */
private volatile ChunkPos 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(DimensionType newDimension, LodWorld lodWorld, int newWidth)
{
lastCutChunk = null;
lastExpandedChunk = null;
dimension = newDimension;
width = newWidth;
halfWidth = width / 2;
MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
if (newDimension != null && lodWorld != null)
{
try
{
File saveDir;
if (mc.hasSinglePlayerServer())
{
// local world
ServerLevel serverWorld = LodUtil.getServerLevelFromDimension(newDimension);
// provider needs a separate variable to prevent
// the compiler from complaining
ServerChunkCache provider = serverWorld.getChunkSource();
saveDir = new File(provider.getDataStorage().dataFolder.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. <br><br>
* <p>
* 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
* <br>
* 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
* <br>
* 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)
{
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
if (lastCutChunk == null)
lastCutChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
// don't run the tree cutter multiple times
// for the same location
if (newPlayerChunk.x != lastCutChunk.x || newPlayerChunk.z != lastCutChunk.z)
{
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 = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
ChunkPos newPlayerChunk = new ChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
VerticalQuality verticalQuality = LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get();
if (lastExpandedChunk == null)
lastExpandedChunk = new ChunkPos(newPlayerChunk.x + 1, newPlayerChunk.z - 1);
// don't run the expander multiple times
// for the same location
if (newPlayerChunk.x != lastExpandedChunk.x || newPlayerChunk.z != lastExpandedChunk.z)
{
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 (LodConfig.CLIENT.worldGenerator.generationPriority.get())
{
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 = LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get().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, LodConfig.CLIENT.worldGenerator.generationPriority.get() == 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.
* <br>
* 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.
* <br>
* 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];
}
/**
* 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.
* <br>
* 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 xChunkPos, int zChunkPos)
//{
//
// LodRegion region = getRegion(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos, zChunkPos);
// if (region == null)
// return false;
//
// return region.isChunkPreGenerated(xChunkPos, zChunkPos);
//}
/**
* 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;
}
}
@@ -1,625 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.VerticalQuality;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
/**
* This object holds all loaded LevelContainers acting as a quad tree
* for a given region. <Br><Br>
*
* <strong>Coordinate Standard: </strong><br>
* Coordinate called posX or posZ are relative LevelPos coordinates <br>
* unless stated otherwise. <br>
*
* @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 DistanceGenerationMode generationMode;
/**
* the vertical quality of this region
*/
private 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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
* <p>
* 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 == null || 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();
}
}
@@ -1,173 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.Hashtable;
import java.util.Map;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.wrappers.World.DimensionTypeWrapper;
import net.minecraft.world.level.dimension.DimensionType;
/**
* 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<DimensionTypeWrapper, LodDimension> 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. <br>
* This should be done whenever loading a new world. <br><br>
* <p>
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
* @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. <br>
* This should be done whenever unloaded a world. <br><br>
* <p>
* Note a System.gc() call may be in order after calling this <Br>
* since a lot of LOD data is now homeless. <br>
*/
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(DimensionTypeWrapper.getDimensionTypeWrapper(newDimension.dimension), newDimension);
}
/**
* Returns null if no LodDimension exists for the given dimension
*/
public LodDimension getLodDimension(DimensionType dimension)
{
if (lodDimensions == null)
return null;
return lodDimensions.get(DimensionTypeWrapper.getDimensionTypeWrapper(dimension));
}
/**
* Resizes the max width in regions that each LodDimension
* should use.
*/
public void resizeDimensionRegionWidth(int newRegionWidth)
{
if (lodDimensions == null)
return;
saveAllDimensions();
for (DimensionTypeWrapper 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
ClientProxy.LOGGER.info("Saving LODs");
for (DimensionTypeWrapper 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;
}
}
@@ -1,64 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.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;
}
}
}
@@ -1,210 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LevelPosUtil;
/**
* Holds the levelPos that need to be generated.
* TODO is that correct?
*
* @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();
}
}
@@ -1,148 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.Arrays;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
/**
* @author Leonardo Amato
* @version 9-18-2021
*/
public class PosToRenderContainer
{
public byte minDetail;
private final int size;
private int regionPosX;
private int regionPosZ;
private int numberOfPosToRender;
private int[] posToRender;
/*TODO this population matrix could be converted to boolean to improve memory use
* no since bools are stored as bytes anyway - cola*/
private byte[][] population;
public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
{
this.minDetail = minDetail;
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
this.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
ClientProxy.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();
}
}
@@ -1,90 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
/**
* This object is similar to ChunkPos or BlockPos.
*
* @author James Seibel
* @version 8-21-2021
*/
public class RegionPos
{
public int x;
public int z;
/**
* Default Constructor <br><br>
* <p>
* 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(BlockPosWrapper pos)
{
this(new ChunkPosWrapper(pos));
}
/** Converts from a ChunkPos to a RegionPos */
public RegionPos(ChunkPosWrapper 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 ChunkPosWrapper chunkPos()
{
return new ChunkPosWrapper(
(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 BlockPosWrapper blockPos()
{
return chunkPos().getWorldPosition()
.offset(LodUtil.CHUNK_WIDTH / 2, 0, LodUtil.CHUNK_WIDTH / 2);
}
@Override
public String toString()
{
return "(" + x + "," + z + ")";
}
}
@@ -1,233 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.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];
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 * maxVerticalData;
int tempIndex;
long current;
byte[] tempData = ThreadMapUtil.getSaveContainer(detailLevel);
tempData[index] = detailLevel;
index++;
tempData[index] = (byte) maxVerticalData;
index++;
for (int i = 0; i < x; i++)
{
current = dataContainer[i];
for (tempIndex = 0; tempIndex < 8; tempIndex++)
tempData[index + tempIndex] = (byte) (current >>> (8 * tempIndex));
index += 8;
}
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();
}
}
@@ -1,390 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.proxy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.glfw.GLFW;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f;
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder;
import com.seibel.lod.builders.lodBuilding.LodBuilder;
import com.seibel.lod.builders.worldGeneration.LodGenWorker;
import com.seibel.lod.builders.worldGeneration.LodWorldGenerator;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.GlProxyContext;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.render.LodRenderer;
import com.seibel.lod.util.DataPointUtil;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.util.ThreadMapUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkWrapper;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraftforge.client.event.InputEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
/**
* This handles all events sent to the client,
* and is the starting point for most of the mod.
* @author James_Seibel
* @version 11-8-2021
*/
public class ClientProxy
{
public static final Logger LOGGER = LogManager.getLogger("LOD");
private static final LodWorld lodWorld = new LodWorld();
private static final LodBuilder lodBuilder = new LodBuilder();
private static final LodBufferBuilder lodBufferBuilder = new LodBufferBuilder();
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
private static final LodWorldGenerator lodWorldGenerator = LodWorldGenerator.INSTANCE;
private boolean configOverrideReminderPrinted = false;
private final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** This is used to determine if the LODs should be regenerated */
public static int previousChunkRenderDistance = 0;
/** This is used to determine if the LODs should be regenerated */
public static int previousLodRenderDistance = 0;
/**
* can be set if we want to recalculate variables related
* to the LOD view distance
*/
private boolean recalculateWidths = false;
public ClientProxy()
{
}
//==============//
// render event //
//==============//
/** Do any setup that is required to draw LODs and then tell the LodRenderer to draw. */
public void renderLods(PoseStack mcModelViewMatrix, Matrix4f projectionMatrix, float partialTicks)
{
// comment out when creating a release
// applyConfigOverrides();
// clear any out of date objects
mc.clearFrameObjectCache();
// make sure the GlProxy is created before the LodBufferBuilder needs it
GlProxy glProxy = GlProxy.getInstance();
try
{
if (mc == null || mc.getPlayer() == null || lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getCurrentDimension());
if (lodDim == null)
return;
DetailDistanceUtil.updateSettings();
viewDistanceChangedEvent();
playerMoveEvent(lodDim);
lodDim.cutRegionNodesAsync((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
lodDim.expandOrLoadRegionsAsync((int) mc.getPlayer().getX(), (int) mc.getPlayer().getZ());
// Note to self:
// if "unspecified" shows up in the pie chart, it is
// possibly because the amount of time between sections
// is too small for the profiler to measure
ProfilerFiller profiler = mc.getProfiler();
profiler.pop(); // get out of "terrain"
profiler.push("LOD");
renderer.drawLODs(lodDim, mcModelViewMatrix, projectionMatrix, partialTicks, mc.getProfiler());
profiler.pop(); // end LOD
profiler.push("terrain"); // go back into "terrain"
// these can't be set until after the buffers are built (in renderer.drawLODs)
// otherwise the buffers may be set to the wrong size, or not changed at all
previousChunkRenderDistance = mc.getRenderDistance();
previousLodRenderDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get();
}
catch (Exception e)
{
LOGGER.error("client proxy: " + e.getMessage());
e.printStackTrace();
}
finally
{
glProxy.setGlContext(GlProxyContext.MINECRAFT);
}
}
/** used in a development environment to change settings on the fly */
@SuppressWarnings("unused")
private void applyConfigOverrides()
{
// remind the developer(s) that the config override is active
if (!configOverrideReminderPrinted)
{
// TODO add a send message method to the MC wrapper
// mc.getPlayer().sendMessage(new TextComponent("LOD experimental build " + ModInfo.VERSION), mc.getPlayer().getUUID());
// mc.getPlayer().sendMessage(new TextComponent("Here be dragons!"), mc.getPlayer().getUUID());
mc.getPlayer().sendMessage(new TextComponent("Debug settings enabled!"), mc.getPlayer().getUUID());
configOverrideReminderPrinted = true;
}
// LodConfig.CLIENT.graphics.drawResolution.set(HorizontalResolution.BLOCK);
// LodConfig.CLIENT.worldGenerator.generationResolution.set(HorizontalResolution.BLOCK);
// requires a world restart?
// LodConfig.CLIENT.worldGenerator.lodQualityMode.set(VerticalQuality.VOXEL);
// LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.set(FogDistance.FAR);
// LodConfig.CLIENT.graphics.fogQualityOption.fogDrawOverride.set(FogDrawOverride.FANCY);
// LodConfig.CLIENT.graphics.fogQualityOption.disableVanillaFog.set(true);
// LodConfig.CLIENT.graphics.shadingMode.set(ShadingMode.DARKEN_SIDES);
// LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.set(VanillaOverdraw.DYNAMIC);
// LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.set(GpuUploadMethod.BUFFER_STORAGE);
// LodConfig.CLIENT.worldGenerator.distanceGenerationMode.set(DistanceGenerationMode.SURFACE);
// LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.set(128);
// LodConfig.CLIENT.worldGenerator.lodDistanceCalculatorType.set(DistanceCalculatorType.LINEAR);
// LodConfig.CLIENT.worldGenerator.allowUnstableFeatureGeneration.set(false);
// LodConfig.CLIENT.buffers.rebuildTimes.set(BufferRebuildTimes.FREQUENT);
LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.set(true);
// LodConfig.CLIENT.debugging.debugMode.set(DebugMode.SHOW_DETAIL);
}
//==============//
// forge events //
//==============//
@SubscribeEvent
public void serverTickEvent(TickEvent.ServerTickEvent event)
{
if (mc == null || mc.getPlayer() == null || lodWorld.getIsWorldNotLoaded())
return;
LodDimension lodDim = lodWorld.getLodDimension(mc.getPlayer().level.dimensionType());
if (lodDim == null)
return;
lodWorldGenerator.queueGenerationRequests(lodDim, renderer, lodBuilder);
}
@SubscribeEvent
public void chunkLoadEvent(ChunkEvent.Load event)
{
lodBuilder.generateLodNodeAsync(new ChunkWrapper(event.getChunk()), lodWorld, event.getWorld(), DistanceGenerationMode.SERVER);
}
@SubscribeEvent
public void worldSaveEvent(WorldEvent.Save event)
{
if (lodWorld != null)
lodWorld.saveAllDimensions();
}
/** This is also called when a new dimension loads */
@SubscribeEvent
public void worldLoadEvent(WorldEvent.Load event)
{
DataPointUtil.worldHeight = event.getWorld().getHeight();
//LodNodeGenWorker.restartExecutorService();
//ThreadMapUtil.clearMaps();
// the player just loaded a new world/dimension
lodWorld.selectWorld(LodUtil.getWorldID(event.getWorld()));
// make sure the correct LODs are being rendered
// (if this isn't done the previous world's LODs may be drawn)
renderer.regenerateLODsNextFrame();
}
@SubscribeEvent
public void worldUnloadEvent(WorldEvent.Unload event)
{
// the player just unloaded a world/dimension
ThreadMapUtil.clearMaps();
if (mc.getConnection().getLevel() == null)
{
// the player just left the server
// TODO should "resetMod()" be called here? -James
// if this isn't done unfinished tasks may be left in the queue
// preventing new LodChunks form being generated
//LodNodeGenWorker.restartExecutorService(); // TODO why was this commented out? -James
//ThreadMapUtil.clearMaps();
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
lodWorld.deselectWorld();
// prevent issues related to the buffer builder
// breaking when changing worlds.
renderer.destroyBuffers();
recalculateWidths = true;
renderer = new LodRenderer(lodBufferBuilder);
// make sure the nulled objects are freed.
// (this prevents an out of memory error when
// changing worlds)
System.gc();
}
}
@SubscribeEvent
public void blockChangeEvent(BlockEvent event)
{
if (event.getClass() == BlockEvent.BreakEvent.class ||
event.getClass() == BlockEvent.EntityPlaceEvent.class ||
event.getClass() == BlockEvent.EntityMultiPlaceEvent.class ||
event.getClass() == BlockEvent.FluidPlaceBlockEvent.class ||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
{
// recreate the LOD where the blocks were changed
lodBuilder.generateLodNodeAsync(new ChunkWrapper(event.getWorld().getChunk(event.getPos())), lodWorld, event.getWorld());
}
}
@SubscribeEvent
public void onKeyInput(InputEvent.KeyInputEvent event)
{
if (LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F4 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.advancedModOptions.debugging.debugMode.set(LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get().getNext());
}
if (LodConfig.CLIENT.advancedModOptions.debugging.enableDebugKeybindings.get()
&& event.getKey() == GLFW.GLFW_KEY_F6 && event.getAction() == GLFW.GLFW_PRESS)
{
LodConfig.CLIENT.advancedModOptions.debugging.drawLods.set(!LodConfig.CLIENT.advancedModOptions.debugging.drawLods.get());
}
}
//============//
// LOD events //
//============//
/** Re-centers the given LodDimension if it needs to be. */
private void playerMoveEvent(LodDimension lodDim)
{
// make sure the dimension is centered
RegionPos playerRegionPos = new RegionPos(mc.getPlayerBlockPos());
RegionPos worldRegionOffset = new RegionPos(playerRegionPos.x - lodDim.getCenterRegionPosX(), playerRegionPos.z - lodDim.getCenterRegionPosZ());
if (worldRegionOffset.x != 0 || worldRegionOffset.z != 0)
{
lodWorld.saveAllDimensions();
lodDim.move(worldRegionOffset);
}
}
/** Re-sizes all LodDimensions if they need to be. */
private void viewDistanceChangedEvent()
{
// calculate how wide the dimension(s) should be in regions
int chunksWide;
if (mc.getClientLevel().dimensionType().hasCeiling())
chunksWide = Math.min(LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * 2 + 1;
else
chunksWide = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 2 + 1;
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
// make sure we have an odd number of regions
newWidth += (newWidth & 1) == 0 ? 1 : 2;
// do the dimensions need to change in size?
if (lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
{
lodWorld.saveAllDimensions();
// update the dimensions to fit the new width
lodWorld.resizeDimensionRegionWidth(newWidth);
lodBuilder.defaultDimensionWidthInRegions = newWidth;
renderer.setupBuffers(lodWorld.getLodDimension(mc.getClientLevel().dimensionType()));
recalculateWidths = false;
//LOGGER.info("new dimension width in regions: " + newWidth + "\t potential: " + newWidth );
}
DetailDistanceUtil.updateSettings();
}
/** this method reset some static data every time we change world */
private void resetMod()
{
// TODO when should this be used?
ThreadMapUtil.clearMaps();
LodGenWorker.restartExecutorService();
}
//================//
// public getters //
//================//
public static LodWorld getLodWorld()
{
return lodWorld;
}
public static LodBuilder getLodBuilder()
{
return lodBuilder;
}
public static LodRenderer getRenderer()
{
return renderer;
}
}
@@ -1,321 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.proxy;
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.mojang.blaze3d.systems.RenderSystem;
import com.seibel.lod.ModInfo;
import com.seibel.lod.enums.GlProxyContext;
import com.seibel.lod.render.shader.LodShader;
import com.seibel.lod.render.shader.LodShaderProgram;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
* A singleton that holds references to different openGL contexts
* and GPU capabilities.
*
* <p>
* Helpful OpenGL resources: <br><br>
* <p>
* https://www.seas.upenn.edu/~pcozzi/OpenGLInsights/OpenGLInsights-AsynchronousBufferTransfers.pdf <br>
* https://learnopengl.com/Advanced-OpenGL/Advanced-Data <br>
* https://gamedev.stackexchange.com/questions/91995/edit-vbo-data-or-create-a-new-one <br><br>
*
* @author James Seibel
* @version 11-8-2021
*/
public class GlProxy
{
private static GlProxy instance = null;
private static MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/** 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;
/** 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;
/** Does this computer's GPU support fancy fog? */
public final boolean fancyFogAvailable;
/** Requires OpenGL 4.5, and offers the best buffer uploading */
public final boolean bufferStorageSupported;
/** Requires OpenGL 3.0 */
public final boolean mapBufferRangeSupported;
private GlProxy()
{
ClientProxy.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 (!RenderSystem.isOnRenderThread())
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();
//==================================//
// get any GPU related capabilities //
//==================================//
ClientProxy.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 = false; //lodBuilderGlCapabilities.glBufferStorage != 0;
mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0;
fancyFogAvailable = minecraftGlCapabilities.GL_NV_fog_distance;
// display the capabilities
if (!bufferStorageSupported)
{
String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5";
ClientProxy.LOGGER.error("This GPU doesn't support Buffer Storage (OpenGL 4.5), falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance.");
}
if (!fancyFogAvailable)
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that the fancy fog option will not be available.");
//==============//
// shader setup //
//==============//
//setGlContext(GlProxyContext.LOD_RENDER);
setGlContext(GlProxyContext.MINECRAFT);
createShaderProgram();
// Note: VAO objects can not be shared between contexts,
// this must be created on the LOD 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
ClientProxy.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)
{
ClientProxy.LOGGER.error("Unable to compile shaders. Error: " + e.getMessage());
}
}
/**
* A wrapper function to make switching contexts easier. <br>
* 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;
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 == 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 + "], "
+ "no context [0].");
}
public static GlProxy getInstance()
{
if (instance == null)
instance = new GlProxy();
return instance;
}
}
@@ -1,954 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.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.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexBuffer;
import com.mojang.math.Matrix4f;
import com.mojang.math.Vector3f;
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder;
import com.seibel.lod.builders.bufferBuilding.LodBufferBuilder.VertexBuffersAndOffset;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DebugMode;
import com.seibel.lod.enums.FogDistance;
import com.seibel.lod.enums.FogDrawOverride;
import com.seibel.lod.enums.FogQuality;
import com.seibel.lod.enums.GpuUploadMethod;
import com.seibel.lod.handlers.ReflectionHandler;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.NearFarFogSettings;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.proxy.ClientProxy;
import com.seibel.lod.proxy.GlProxy;
import com.seibel.lod.render.shader.LodShaderProgram;
import com.seibel.lod.util.DetailDistanceUtil;
import com.seibel.lod.util.LevelPosUtil;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.util.profiling.ProfilerFiller;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.phys.Vec3;
/**
* This is where all the magic happens. <br>
* This is where LODs are draw to the world.
*
* @author James Seibel
* @version 11-8-2021
*/
public class LodRenderer
{
/**
* 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 final MinecraftWrapper mc;
private final GameRenderer gameRender;
private ProfilerFiller profiler;
private int farPlaneBlockDistance;
/** This is used to generate the buildable buffers */
private final LodBufferBuilder lodBufferBuilder;
/** Each VertexBuffer represents 1 region */
private VertexBuffer[][][] 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)
*/
private int[][][] storageBufferIds;
private ChunkPosWrapper vbosCenter = new ChunkPosWrapper(0, 0);
/** 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(LodBufferBuilder newLodNodeBufferBuilder)
{
mc = MinecraftWrapper.INSTANCE;
gameRender = mc.getGameRenderer();
lodBufferBuilder = 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, PoseStack mcModelViewMatrix, Matrix4f mcProjectionMatrix, float partialTicks, ProfilerFiller newProfiler)
{
//=================================//
// determine if LODs should render //
//=================================//
if (lodDim == null)
{
// if there aren't any loaded LodChunks
// don't try drawing anything
return;
}
if (mc.getPlayer().getActiveEffectsMap().get(MobEffects.BLINDNESS) != null)
{
// 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) && !lodBufferBuilder.generatingBuffers && !lodBufferBuilder.newBuffersAvailable())
{
// generate the LODs on a separate thread to prevent stuttering or freezing
lodBufferBuilder.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 (lodBufferBuilder.newBuffersAvailable())
{
swapBuffers();
}
//===============//
// initial setup //
//===============//
profiler = newProfiler;
profiler.push("LOD setup");
GlProxy glProxy = GlProxy.getInstance();
// set the required open GL settings
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() == 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);
Matrix4f modelViewMatrix = offsetTheModelViewMatrix(mcModelViewMatrix, partialTicks);
vanillaBlockRenderedDistance = mc.getRenderDistance() * LodUtil.CHUNK_WIDTH;
// required for setupFog and setupProjectionMatrix
if (mc.getClientLevel().dimensionType().hasCeiling())
farPlaneBlockDistance = Math.min(LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
else
farPlaneBlockDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * LodUtil.CHUNK_WIDTH;
Matrix4f 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)
{
Camera camera = mc.getGameRenderer().getMainCamera();
Vector3f cameraDir = camera.getLookVector();
// TODO re-enable once rendering is totally working
boolean cullingDisabled = true; //LodConfig.CLIENT.graphics.advancedGraphicsOption.disableDirectionalCulling.get();
boolean renderBufferStorage = LodConfig.CLIENT.graphics.advancedGraphicsOption.gpuUploadMethod.get() == 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(camera.getBlockPosition(), 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].indexCount, posAttrib, colAttrib);
else
for (int i = 0; i < vbos[x][z].length; i++)
drawArrays(vbos[x][z][i].vertextBufferId, vbos[x][z][i].indexCount, posAttrib, 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?
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);
}
//=================//
// Setup Functions //
//=================//
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 (LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get() == 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);
if (GlProxy.getInstance().fancyFogAvailable)
GL15.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode);
}
/**
* Revert any changes that were made to the fog
* and sets up the fog for Minecraft.
*/
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);
// this setting is only valid if the GPU supports fancy fog
if (GlProxy.getInstance().fancyFogAvailable)
GL15.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance);
// disable fog if Minecraft wasn't rendering fog
// or we want it disabled
if (!fogSettings.vanillaIsRenderingFog
|| LodConfig.CLIENT.graphics.fogQualityOption.disableVanillaFog.get())
{
// 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 Matrix4f offsetTheModelViewMatrix(PoseStack mcModelViewMatrix, float partialTicks)
{
// duplicate the last matrix
mcModelViewMatrix.pushPose();
// get all relevant camera info
Camera camera = mc.getGameRenderer().getMainCamera();
Vec3 projectedView = camera.getPosition();
// 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)
BlockPosWrapper bufferPos = vbosCenter.getWorldPosition();
double xDiff = projectedView.x - bufferPos.getX();
double zDiff = projectedView.z - bufferPos.getZ();
mcModelViewMatrix.translate(-xDiff, -projectedView.y, -zDiff);
// get the modified model view matrix
Matrix4f lodModelViewMatrix = mcModelViewMatrix.last().pose();
// remove the lod ModelViewMatrix
mcModelViewMatrix.popPose();
return lodModelViewMatrix;
}
/**
* 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 Matrix4f createProjectionMatrix(Matrix4f currentProjectionMatrix, float vanillaBlockRenderedDistance, float partialTicks)
{
// create the new projection matrix
Matrix4f lodProj =
Matrix4f.perspective(
getFov(partialTicks, true),
(float) this.mc.getWindow().getScreenWidth() / (float) this.mc.getWindow().getScreenHeight(),
LodConfig.CLIENT.graphics.advancedGraphicsOption.useExtendedNearClipPlane.get() ? vanillaBlockRenderedDistance / 5 : 1,
farPlaneBlockDistance * LodUtil.CHUNK_WIDTH / 2);
// get Minecraft's un-edited projection matrix
// (this is before it is zoomed, distorted, etc.)
Matrix4f defaultMcProj = mc.getGameRenderer().getProjectionMatrix(getFov(partialTicks, true));
// 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
Matrix4f defaultMcProjInv = defaultMcProj.copy();
defaultMcProjInv.invert();
Matrix4f 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<EffectInstance> 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)
{
lodBufferBuilder.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. <br><br>
* <p>
* 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 = lodBufferBuilder.getVertexBuffers();
vbos = result.vbos;
storageBufferIds = result.storageBufferIds;
vbosCenter = result.drawableCenterChunkPos;
}
/** Calls the BufferBuilder's destroyBuffers method. */
public void destroyBuffers()
{
lodBufferBuilder.destroyBuffers();
}
// TODO move this into the MC wrapper
private double getFov(float partialTicks, boolean useFovSetting)
{
return mc.getGameRenderer().getFov(mc.getGameRenderer().getMainCamera(), partialTicks, useFovSetting);
}
/** Return what fog settings should be used when rendering. */
private NearFarFogSettings determineFogSettings()
{
NearFarFogSettings fogSettings = new NearFarFogSettings();
FogQuality quality = ReflectionHandler.INSTANCE.getFogQuality();
FogDrawOverride override = LodConfig.CLIENT.graphics.fogQualityOption.fogDrawOverride.get();
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;
}
// only use fancy fog if the user's GPU can deliver
if (!GlProxy.getInstance().fancyFogAvailable && quality == FogQuality.FANCY)
{
quality = FogQuality.FAST;
}
// how different distances are drawn depends on the quality set
switch (quality)
{
case FANCY:
fogSettings.near.quality = FogQuality.FANCY;
fogSettings.far.quality = FogQuality.FANCY;
switch (LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get())
{
case NEAR_AND_FAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.FAR;
break;
case NEAR:
fogSettings.near.distance = FogDistance.NEAR;
fogSettings.far.distance = FogDistance.NEAR;
break;
case FAR:
fogSettings.near.distance = FogDistance.FAR;
fogSettings.far.distance = FogDistance.FAR;
break;
}
break;
case FAST:
fogSettings.near.quality = FogQuality.FAST;
fogSettings.far.quality = FogQuality.FAST;
// fast fog setting should only have one type of
// fog, since the LODs are separated into a near
// and far portion; and fast fog is rendered from the
// frustrum's perspective instead of the camera
switch (LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get())
{
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.getRenderDistance();
int vanillaRenderedChunksWidth = chunkRenderDistance * 2 + 2;
//=============//
// full regens //
//=============//
// check if the view distance changed
if (ClientProxy.previousLodRenderDistance != LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get()
|| chunkRenderDistance != prevRenderDistance
|| prevFogDistance != LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get())
{
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
DetailDistanceUtil.updateSettings();
fullRegen = true;
previousPos = LevelPosUtil.createLevelPos((byte) 4, mc.getPlayerChunkPos().getZ(), mc.getPlayerChunkPos().getZ());
prevFogDistance = LodConfig.CLIENT.graphics.fogQualityOption.fogDistance.get();
prevRenderDistance = chunkRenderDistance;
}
// did the user change the debug setting?
if (LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get() != previousDebugMode)
{
previousDebugMode = LodConfig.CLIENT.advancedModOptions.debugging.debugMode.get();
fullRegen = true;
}
long newTime = System.currentTimeMillis();
// check if the player has moved
if (newTime - prevPlayerPosTime > LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get().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 (LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get())
{
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.getOptions().gamma != prevBrightness)
{
fullRegen = true;
prevBrightness = mc.getOptions().gamma;
prevSkyBrightness = skyBrightness;
}
/*if (lightMap != lastLightMap)
{
fullRegen = true;
lastLightMap = lightMap;
}*/
//================//
// partial regens //
//================//
// check if the vanilla rendered chunks changed
if (newTime - prevVanillaChunkTime > LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get().renderedChunkTimeout)
{
if (vanillaRenderedChunksChanged)
{
partialRegen = true;
vanillaRenderedChunksChanged = false;
}
prevVanillaChunkTime = newTime;
}
// check if there is any newly generated terrain to show
if (newTime - prevChunkTime > LodConfig.CLIENT.advancedModOptions.buffers.rebuildTimes.get().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<ChunkPos> chunkPosToSkip = LodUtil.getNearbyLodChunkPosToSkip(lodDim, mc.getPlayerBlockPos());
int xIndex;
int zIndex;
for (ChunkPos pos : chunkPosToSkip)
{
vanillaRenderedChunksEmptySkip = false;
xIndex = (pos.x - mc.getPlayerChunkPos().getX()) + (chunkRenderDistance + 1);
zIndex = (pos.z - 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.getPlayer().position().y > 256 && !vanillaRenderedChunksEmptySkip)
{
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
vanillaRenderedChunksChanged = true;
vanillaRenderedChunksEmptySkip = true;
}
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
}
}
@@ -1,126 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.render;
import com.mojang.math.Vector3f;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.ChunkPos;
/**
* 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 MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
* 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(ChunkPos pos, ChunkPos center)
{
return (pos.x >= center.x - mc.getRenderDistance()
&& pos.x <= center.x + mc.getRenderDistance())
&&
(pos.z >= center.z - mc.getRenderDistance()
&& pos.z <= center.z + mc.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.getRenderDistance()
&& x <= centerCoordinate + mc.getRenderDistance())
&&
(z >= centerCoordinate - mc.getRenderDistance()
&& z <= centerCoordinate + mc.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(BlockPos playerBlockPos, Vector3f cameraDir, BlockPosWrapper vboCenterPos)
{
// convert the vbo position into a direction vector
// starting from the player's position
Vector3f vboVec = new Vector3f(vboCenterPos.getX(), 0, vboCenterPos.getZ());
Vector3f playerVec = new Vector3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
vboVec.sub(playerVec);
Vector3f vboCenterVec = vboVec;
int halfRegionWidth = LodUtil.REGION_WIDTH / 2;
// calculate the 4 corners
Vector3f vboSeVec = new Vector3f(vboCenterVec.x() + halfRegionWidth, vboCenterVec.y(), vboCenterVec.z() + halfRegionWidth);
Vector3f vboSwVec = new Vector3f(vboCenterVec.x() - halfRegionWidth, vboCenterVec.y(), vboCenterVec.z() + halfRegionWidth);
Vector3f vboNwVec = new Vector3f(vboCenterVec.x() - halfRegionWidth, vboCenterVec.y(), vboCenterVec.z() - halfRegionWidth);
Vector3f vboNeVec = new Vector3f(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(Vector3f objectVector, Vector3f 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.dot(cameraDir) > -0.1;
}
}
@@ -1,116 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.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.proxy.ClientProxy;
/**
* 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)
{
ClientProxy.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));
}
}
@@ -1,183 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.render.shader;
import java.nio.FloatBuffer;
import org.lwjgl.opengl.GL20;
import org.lwjgl.system.MemoryStack;
import com.mojang.math.Matrix4f;
/**
* 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, Matrix4f value)
{
try (MemoryStack stack = MemoryStack.stackPush())
{
FloatBuffer buffer = stack.mallocFloat(4 * 4);
value.store(buffer);
GL20.glUniformMatrix4fv(location, false, buffer);
}
}
}
@@ -1,115 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import java.awt.Color;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
*
* @author Cola
* @author Leonardo Amato
* @version ??
*/
public class ColorUtil
{
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;
}
public static int getAlpha(int color)
{
return (color >> 24) & 0xFF;
}
public static int getRed(int color)
{
return (color >> 16) & 0xFF;
}
public static int getGreen(int color)
{
return (color >> 8) & 0xFF;
}
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 = MinecraftWrapper.INSTANCE.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));
}
}
@@ -1,553 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import static com.seibel.lod.builders.bufferBuilding.LodBufferBuilder.skyLightPlayer;
import com.mojang.blaze3d.platform.NativeImage;
import com.seibel.lod.enums.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 int getLightSky(long dataPoint)
{
return (int) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static int getLightSkyAlt(long dataPoint)
{
if (skyLightPlayer == 0 && ((dataPoint >>> FLAG_SHIFT) & FLAG_MASK) == 1)
return 0;
else
return (int) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
}
public static int getLightBlock(long dataPoint)
{
return (int) ((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 method apply the lightmap to the color to use */
public static int getLightColor(long dataPoint, NativeImage lightMap)
{
int lightBlock = getLightBlock(dataPoint);
int lightSky = getLightSky(dataPoint);
int color = lightMap.getPixelRGBA(lightBlock, lightSky);
int red = ColorUtil.getBlue(color);
int green = ColorUtil.getGreen(color);
int blue = ColorUtil.getRed(color);
return ColorUtil.multiplyRGBcolors(getColor(dataPoint), ColorUtil.rgbToInt(red, green, blue));
}
/** 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.SERVER.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 cut the 1 block gaps
/*int j;
for (i = 0; i < count - 1; i++)
{
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] == 1)
{
heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1];
for (j = i + 1; j < count - 1; j++)
{
heightAndDepth[j * 2] = heightAndDepth[(j + 1) * 2];
heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1];
}
count--;
i--;
}
}*/
//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.SERVER.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;
}
}
@@ -1,178 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.enums.HorizontalQuality;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.wrappers.MinecraftWrapper;
/**
*
* @author Leonardo Amato
* @version ??
*/
public class DetailDistanceUtil
{
private static final double genMultiplier = 1.0;
private static final double treeGenMultiplier = 1.0;
private static final double treeCutMultiplier = 1.0;
private static int minGenDetail = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel;
private static int minDrawDetail = Math.max(LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel, LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel);
private static final int maxDetail = LodUtil.REGION_DETAIL_LEVEL + 1;
private static final int minDistance = 0;
private static int minDetailDistance = (int) (MinecraftWrapper.INSTANCE.getRenderDistance()*16 * 1.42f);
private static int maxDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 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) (MinecraftWrapper.INSTANCE.getRenderDistance()*16 * 1.42f);
minGenDetail = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel;
minDrawDetail = Math.max(LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel, LodConfig.CLIENT.graphics.qualityOption.drawResolution.get().detailLevel);
maxDistance = LodConfig.CLIENT.graphics.qualityOption.lodChunkRenderDistance.get() * 16 * 8;
}
public static int baseDistanceFunction(int detail)
{
if (detail <= minGenDetail)
return minDistance;
if (detail >= maxDetail)
return maxDistance;
int distanceUnit = LodConfig.CLIENT.graphics.qualityOption.horizontalScale.get().distanceUnit;
if (LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get() == HorizontalQuality.LOWEST)
return (detail * distanceUnit);
else
{
double base = LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get().quadraticBase;
return (int) (Math.pow(base, detail) * distanceUnit);
}
}
public static int getDrawDistanceFromDetail(int detail)
{
return baseDistanceFunction(detail);
}
public static byte baseInverseFunction(int distance, int minDetail, boolean useRenderMinDistance)
{
int detail;
if (distance == 0)
return (byte) minDetail;
if (distance < minDetailDistance && useRenderMinDistance)
return (byte) minDetail;
int distanceUnit = LodConfig.CLIENT.graphics.qualityOption.horizontalScale.get().distanceUnit;
if (LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get() == HorizontalQuality.LOWEST)
detail = (byte) distance / distanceUnit;
else
{
double base = LodConfig.CLIENT.graphics.qualityOption.horizontalQuality.get().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 LodConfig.CLIENT.worldGenerator.distanceGenerationMode.get();
}
public static byte getLodDrawDetail(int detail)
{
if (detail < minDrawDetail)
{
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.get())
return getLodGenDetail(minDrawDetail).detailLevel;
else
return (byte) minDrawDetail;
}
else
{
if (LodConfig.CLIENT.graphics.advancedGraphicsOption.alwaysDrawAtMaxQuality.get())
return getLodGenDetail(detail).detailLevel;
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 LodConfig.CLIENT.graphics.qualityOption.verticalQuality.get().maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)];
}
}
@@ -1,269 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.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);
}
}
@@ -1,499 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import java.awt.Color;
import java.io.File;
import java.util.HashSet;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.seibel.lod.builders.bufferBuilding.lodTemplates.Box;
import com.seibel.lod.config.LodConfig;
import com.seibel.lod.enums.HorizontalResolution;
import com.seibel.lod.enums.VanillaOverdraw;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.wrappers.MinecraftWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Chunk.ChunkPosWrapper;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.client.server.IntegratedServer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.levelgen.Heightmap;
/**
* This class holds methods and constants that may be used in multiple places.
*
* @author James Seibel
* @version 10-20-2021
*/
public class LodUtil
{
private static final MinecraftWrapper mc = MinecraftWrapper.INSTANCE;
/**
* 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: <br>
* 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 <br>
* detail level max - 1
*/
public static final short REGION_WIDTH = 1 << REGION_DETAIL_LEVEL;
/**
* measured in Blocks <br>
* detail level 4
*/
public static final short CHUNK_WIDTH = 16;
/**
* measured in Blocks <br>
* 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;
/**
* 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;
/**
* 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. <br><br>
* <p>
* I know there are commands to change that amount
* (specifically "-XX:MaxDirectMemorySize"), but
* I have no idea how to access that amount. <br>
* So I guess this will be the hard limit for now. <br><br>
* <p>
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
*/
public static final int MAX_ALLOCATABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormat.POSITION_COLOR;
/**
* Gets the first valid ServerLevel.
* @return null if there are no ServerLevels
*/
public static ServerLevel getFirstValidServerLevel()
{
if (mc.hasSinglePlayerServer())
return null;
Iterable<ServerLevel> worlds = mc.getSinglePlayerServer().getAllLevels();
for (ServerLevel world : worlds)
return world;
return null;
}
/**
* Gets the ServerLevel for the relevant dimension.
* @return null if there is no ServerLevel for the given dimension
*/
public static ServerLevel getServerLevelFromDimension(DimensionType dimension)
{
IntegratedServer server = mc.getSinglePlayerServer();
if (server == null)
return null;
Iterable<ServerLevel> worlds = server.getAllLevels();
ServerLevel returnWorld = null;
for (ServerLevel world : worlds)
{
if (world.dimensionType() == dimension)
{
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));
}
/**
* Return whether the given chunk
* has any data in it.
*/
public static boolean chunkHasBlockData(ChunkAccess chunk)
{
LevelChunkSection[] blockStorage = chunk.getSections();
for (LevelChunkSection section : blockStorage)
{
if (section != null && !section.isEmpty())
return true;
}
return false;
}
/**
* 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(LevelAccessor levelAccessor)
{
if (mc.hasSinglePlayerServer())
{
// chop off the dimension ID as it is not needed/wanted
String dimId = getDimensionIDFromWorld(levelAccessor);
// 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.<br>
* <br>
* This can be used to determine where to save files for a given
* dimension.
*/
public static String getDimensionIDFromWorld(LevelAccessor levelAccessor)
{
if (mc.hasSinglePlayerServer())
{
// this will return the world save location
// and the dimension folder
ServerLevel ServerLevel = LodUtil.getServerLevelFromDimension(levelAccessor.dimensionType());
if (ServerLevel == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerLevel for the dimension " + levelAccessor.dimensionType().effectsLocation().getPath());
ServerChunkCache provider = ServerLevel.getChunkSource();
if (provider == null)
throw new NullPointerException("getDimensionIDFromWorld wasn't able to get the ServerChunkProvider for the dimension " + levelAccessor.dimensionType().effectsLocation().getPath());
return provider.getDataStorage().dataFolder.toString();
}
else
{
return getServerId() + File.separatorChar + "dim_" + levelAccessor.dimensionType().effectsLocation().getPath() + File.separatorChar;
}
}
/** returns the server name, IP and game version. */
public static String getServerId()
{
ServerData server = mc.getCurrentServer();
String serverName = server.name.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverIp = server.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
String serverMcVersion = server.version.getString().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<ChunkPos> getNearbyLodChunkPosToSkip(LodDimension lodDim, BlockPosWrapper blockPosWrapper)
{
int chunkRenderDist = mc.getRenderDistance();
ChunkPosWrapper centerChunk = new ChunkPosWrapper(blockPosWrapper);
int skipRadius;
VanillaOverdraw overdraw = LodConfig.CLIENT.graphics.advancedGraphicsOption.vanillaOverdraw.get();
HorizontalResolution drawRes = LodConfig.CLIENT.graphics.qualityOption.drawResolution.get();
// 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<ChunkPos> posToSkip = 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(new ChunkPos(x, z));
}
}
}
return posToSkip;
}
/**
* This method returns the ChunkPos of all chunks that Minecraft
* is going to render this frame. <br><br>
* <p>
* Note: This isn't perfect. It will return some chunks that are outside
* the clipping plane. (For example, if you are high above the ground some chunks
* will be incorrectly added, even though they are outside render range).
*/
public static HashSet<ChunkPos> getRenderedChunks()
{
HashSet<ChunkPos> loadedPos = new HashSet<>();
// Wow, those are some long names!
// go through every RenderInfo to get the compiled chunks
LevelRenderer renderer = mc.getLevelRenderer();
for (LevelRenderer.RenderChunkInfo chunkInfo : renderer.renderChunks)
{
if (!chunkInfo.chunk.getCompiledChunk().hasNoRenderableLayers())
{
// add the ChunkPos for every rendered chunk
BlockPos bpos = chunkInfo.chunk.getOrigin();
loadedPos.add(new ChunkPos(bpos));
}
}
return loadedPos;
}
/**
* 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 (Direction direction : Box.ADJ_DIRECTIONS)
{
tempX = x + Box.DIRECTION_NORMAL_MAP.get(direction).getX();
tempZ = z + Box.DIRECTION_NORMAL_MAP.get(direction).getZ();
if (vanillaRenderedChunks[x][z] || (!(tempX < 0 || tempZ < 0 || tempX >= vanillaRenderedChunks.length || tempZ >= vanillaRenderedChunks[0].length)
&& !vanillaRenderedChunks[tempX][tempZ]))
return true;
}
return false;
}
}
@@ -1,219 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.util;
import static com.seibel.lod.util.LodUtil.DETAIL_OPTIONS;
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.builders.bufferBuilding.lodTemplates.Box;
import net.minecraft.core.Direction;
/**
* 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<String, long[]> threadSingleUpdateMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> threadBuilderVerticalArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> threadVerticalAddDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, byte[][]> saveContainer = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> projectionArrayMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, short[]> heightAndDepthMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[]> singleDataToMergeMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, long[][]> verticalUpdate = new ConcurrentHashMap<>();
//________________________//
// used in BufferBuilder //
//________________________//
public static final ConcurrentMap<String, boolean[]> adjShadeDisabled = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Map<Direction, long[]>> adjDataMap = new ConcurrentHashMap<>();
public static final ConcurrentMap<String, Box> 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<Direction, long[]> getAdjDataArray(int verticalData)
{
if (!adjDataMap.containsKey(Thread.currentThread().getName())
|| (adjDataMap.get(Thread.currentThread().getName()) == null)
|| (adjDataMap.get(Thread.currentThread().getName()).get(Direction.NORTH) == null)
|| (adjDataMap.get(Thread.currentThread().getName()).get(Direction.NORTH).length != verticalData))
{
adjDataMap.put(Thread.currentThread().getName(), new HashMap<Direction, long[]>());
adjDataMap.get(Thread.currentThread().getName()).put(Direction.UP, new long[1]);
adjDataMap.get(Thread.currentThread().getName()).put(Direction.DOWN, new long[1]);
for (Direction direction : Box.ADJ_DIRECTIONS)
adjDataMap.get(Thread.currentThread().getName()).put(direction, new long[verticalData]);
}
else
{
for (Direction direction : Box.ADJ_DIRECTIONS)
Arrays.fill(adjDataMap.get(Thread.currentThread().getName()).get(direction), 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[DETAIL_OPTIONS][];
int size = 1;
for (int i = 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[DETAIL_OPTIONS][];
for (int i = 1; i < 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();
}
}
@@ -1,7 +0,0 @@
package com.seibel.lod.wrappers.Chunk;
//This class will contain all methods usefull to generate the fake ChunkWrapper
public class ChunkGenerator
{
}
@@ -1,95 +0,0 @@
package com.seibel.lod.wrappers.Chunk;
import com.seibel.lod.util.LodUtil;
import com.seibel.lod.wrappers.Block.BlockColorWrapper;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import com.seibel.lod.wrappers.Block.BlockShapeWrapper;
import com.seibel.lod.wrappers.World.BiomeWrapper;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.chunk.ChunkAccess;
public class ChunkWrapper
{
private ChunkAccess chunk;
private ChunkPosWrapper chunkPos;
public int getHeight(){
return chunk.getMaxBuildHeight();
}
public boolean isPositionInWater(BlockPosWrapper blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos.getBlockPos());
//This type of block is always in water
if((blockState.getBlock() instanceof LiquidBlock))// && !(blockState.getBlock() instanceof IWaterLoggable))
return true;
//This type of block could be in water
if(blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get())
return true;
return false;
}
public int getHeightMapValue(int xRel, int zRel){
return chunk.getOrCreateHeightmapUnprimed(LodUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
}
public BiomeWrapper getBiome(int xRel, int yAbs, int zRel)
{
return BiomeWrapper.getBiomeWrapper(chunk.getBiomes().getNoiseBiome(xRel >> 2, yAbs >> 2, zRel >> 2));
}
public BlockColorWrapper getBlockColorWrapper(BlockPosWrapper blockPos)
{
return BlockColorWrapper.getBlockColorWrapper(chunk.getBlockState(blockPos.getBlockPos()),blockPos);
}
public BlockShapeWrapper getBlockShapeWrapper(BlockPosWrapper blockPos)
{
return BlockShapeWrapper.getBlockShapeWrapper(chunk.getBlockState(blockPos.getBlockPos()).getBlock(), this, blockPos);
}
public ChunkWrapper(ChunkAccess chunk)
{
this.chunk = chunk;
this.chunkPos = new ChunkPosWrapper(chunk.getPos());
}
public ChunkAccess getChunk(){
return chunk;
}
public ChunkPosWrapper getPos(){
return chunkPos;
}
public boolean isLightCorrect(){
return chunk.isLightCorrect();
}
public boolean
isWaterLogged(BlockPosWrapper blockPos)
{
BlockState blockState = chunk.getBlockState(blockPos.getBlockPos());
// //This type of block is always in water
// if((blockState.getBlock() instanceof ILiquidContainer) && !(blockState.getBlock() instanceof IWaterLoggable))
// return true;
//This type of block could be in water
if(blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).isPresent() && blockState.getOptionalValue(BlockStateProperties.WATERLOGGED).get())
return true;
return false;
}
public int getEmittedBrightness(BlockPosWrapper blockPos)
{
return chunk.getLightEmission(blockPos.getBlockPos());
}
}
@@ -1,18 +0,0 @@
package com.seibel.lod.wrappers;
import com.mojang.blaze3d.platform.NativeImage;
public class LightMapWrapper
{
static NativeImage lightMap = null;
public static void setLightMap(NativeImage newLightMap)
{
lightMap = newLightMap;
}
public static int getLightValue(int skyLight, int blockLight)
{
return lightMap.getPixelRGBA(skyLight, blockLight);
}
}
@@ -1,5 +0,0 @@
package com.seibel.lod.wrappers.Vertex;
public class BufferBuilderWrapper
{
}
@@ -1,5 +0,0 @@
package com.seibel.lod.wrappers.Vertex;
public class VertexBufferWrapper
{
}
@@ -1,25 +0,0 @@
package com.seibel.lod.wrappers.World;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.client.renderer.BiomeColors;
public class BiomeColorWrapper
{
public static int getGrassColor(WorldWrapper worldWrapper, BlockPosWrapper blockPosWrapper)
{
return BiomeColors.getAverageGrassColor(worldWrapper.getWorld(), blockPosWrapper.getBlockPos());
}
public static int getWaterColor(WorldWrapper worldWrapper, BlockPosWrapper blockPosWrapper)
{
return BiomeColors.getAverageWaterColor(worldWrapper.getWorld(), blockPosWrapper.getBlockPos());
}
public static int getFoliageColor(WorldWrapper worldWrapper, BlockPosWrapper blockPosWrapper)
{
return BiomeColors.getAverageFoliageColor(worldWrapper.getWorld(), blockPosWrapper.getBlockPos());
}
}
@@ -1,37 +0,0 @@
package com.seibel.lod.wrappers.World;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.minecraft.world.level.dimension.DimensionType;
public class DimensionTypeWrapper
{
private static final ConcurrentMap<DimensionType, DimensionTypeWrapper> dimensionTypeWrapperMap = new ConcurrentHashMap<>();
private 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;
}
public static void clearMap()
{
dimensionTypeWrapperMap.clear();
}
}
@@ -1,7 +0,0 @@
package com.seibel.lod.wrappers.World;
//We will use this class to get all the light information from the game like skylight, blocklight and light emission;
public class WorldLightWrapper
{
}
@@ -1,80 +0,0 @@
package com.seibel.lod.wrappers.World;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.seibel.lod.wrappers.Block.BlockPosWrapper;
import net.minecraft.world.level.Level;
public class WorldWrapper
{
private static final ConcurrentMap<Level, WorldWrapper> worldWrapperMap = new ConcurrentHashMap<>();
private Level world;
public WorldWrapper(Level world)
{
this.world = world;
}
public static WorldWrapper getWorldWrapper(Level world)
{
//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();
}
public DimensionTypeWrapper getDimensionType()
{
return DimensionTypeWrapper.getDimensionTypeWrapper(world.dimensionType());
}
public int getBlockLight(BlockPosWrapper blockPos)
{
return world.getLightEngine().blockEngine.getLightValue(blockPos.getBlockPos());
}
public int getSkyLight(BlockPosWrapper blockPos)
{
return world.getLightEngine().skyEngine.getLightValue(blockPos.getBlockPos());
}
public BiomeWrapper getBiome(BlockPosWrapper blockPos)
{
return BiomeWrapper.getBiomeWrapper(world.getBiome(blockPos.getBlockPos()));
}
public Level getWorld()
{
return world;
}
public boolean hasCeiling()
{
return world.dimensionType().hasCeiling();
}
public boolean hasSkyLight()
{
return world.dimensionType().hasSkyLight();
}
public boolean isEmpty()
{
return world == null;
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"required": true, "required": true,
"package": "com.seibel.lod.mixin", "package": "com.seibel.lod.forge.mixins",
"compatibilityLevel": "JAVA_8", "compatibilityLevel": "JAVA_8",
"refmap": "lod.refmap.json", "refmap": "lod.refmap.json",
"mixins": [ "mixins": [