Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2834a73551 | |||
| 5a7af89f5c | |||
| eff491127b | |||
| 61502c32a6 | |||
| b7911ec4c8 | |||
| 1115c5f6c5 | |||
| 5d001bf7ea | |||
| 4e732f7916 | |||
| 30d392f861 | |||
| f8910b0c3b |
@@ -1 +0,0 @@
|
||||
<mxfile host="app.diagrams.net" modified="2021-12-22T02:14:44.485Z" agent="5.0 (Windows)" etag="8Lz4CpREcKLpQpROSPVl" version="16.0.3" type="gitlab"><diagram id="xLs7mM1S-vncSruOQYJG" name="Page-1">xZVNj5swEIZ/DcetAJdkc2yTbXvZVaQcuunNtSfgrsGRcRbor6+Jx4BDom3VSr1Enmc+7HnHOBFZl+1nTY/Fo+IgozTmbUQ2UZou71P724POAZIlDuRacIcmYCd+AsIY6UlwqINAo5Q04hhCpqoKmAkY1Vo1YdhByXDXI81hBnaMyjn9KrgpHL3P4pF/AZEXfuckRk9JfTCCuqBcNRNEHiKy1koZtyrbNcheO6+Ly/t0wzscTENlfichf/r2ohqemP2Pp+3dtn3s9tkdVnml8oQNCwOlJeioTeeV0OpUceiLxRH52BQ2cHekrPc2dvSWFaaU1krsEsuCNtDePG8yqGBvD6gSjO5sCCasUDe8OMl7tJtxDInXtpiMYIGM4uTzofIojl2gPn+gVXpLq/TvtDoIKddKKn3OJYcDLBizvDZavcDEw5er73H8b9QlSSjv4n+rS2bqzlSFin/oP2lrMUnrWrBQyFB1aIV5Rk+/3vf8XYbWpp2EbTpvVLaV56nhspaZt8e8s+UT3VmBzx6Ti3nYftRJM3j7kzRU52Deuo7z+U4GmF2Zn2caJDXiNTzutaHiDlslbCM3r89Q15dwbWLW9FW6LLS4+MxXF4WcDrNC5zs2tH3t2llzfFxd+PgPRR5+AQ==</diagram></mxfile>
|
||||
@@ -61,7 +61,6 @@ public class ClientApi
|
||||
private boolean firstTimeSetupComplete = false;
|
||||
private boolean configOverrideReminderPrinted = false;
|
||||
|
||||
public boolean rendererDisabledBecauseOfExceptions = false;
|
||||
|
||||
|
||||
private ClientApi()
|
||||
@@ -113,17 +112,9 @@ public class ClientApi
|
||||
profiler.pop(); // get out of "terrain"
|
||||
profiler.push("LOD");
|
||||
|
||||
if (!rendererDisabledBecauseOfExceptions) {
|
||||
try {
|
||||
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
|
||||
} catch (RuntimeException e) {
|
||||
rendererDisabledBecauseOfExceptions = true;
|
||||
try {
|
||||
//ClientApi.renderer.ma ();
|
||||
} catch (RuntimeException welpLookLikeWeWillLeakResource) {}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
|
||||
|
||||
profiler.pop(); // end LOD
|
||||
profiler.push("terrain"); // go back into "terrain"
|
||||
}
|
||||
@@ -182,8 +173,6 @@ public class ClientApi
|
||||
// Lod maintenance //
|
||||
//=================//
|
||||
|
||||
// FIXME: I need a onLastFrameCleanup() callback in Render Thread... Which calls renderer.cleanup()
|
||||
|
||||
/** This event is called once during the first frame Minecraft renders in the world. */
|
||||
public void firstFrameSetup()
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ package com.seibel.lod.core.api;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.core.builders.worldGeneration.LodGenWorker;
|
||||
import com.seibel.lod.core.builders.worldGeneration.LodWorldGenerator;
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
@@ -81,8 +81,7 @@ public class EventApi
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
|
||||
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
|
||||
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ClientApi.renderer, ApiShared.lodBuilder);
|
||||
}
|
||||
|
||||
|
||||
@@ -106,8 +105,6 @@ public class EventApi
|
||||
public void worldLoadEvent(IWorldWrapper world)
|
||||
{
|
||||
DataPointUtil.WORLD_HEIGHT = world.getHeight();
|
||||
LodBuilder.MIN_WORLD_HEIGHT = world.getMinHeight(); // This updates the World height
|
||||
|
||||
//LodNodeGenWorker.restartExecutorService();
|
||||
//ThreadMapUtil.clearMaps();
|
||||
|
||||
@@ -124,8 +121,6 @@ public class EventApi
|
||||
{
|
||||
// the player just unloaded a world/dimension
|
||||
ThreadMapUtil.clearMaps();
|
||||
// ClientApi.renderer.markForCleanup();
|
||||
// ClientApi.renderer.destroyBuffers();
|
||||
|
||||
new Thread(() -> checkIfDisconnectedFromServer()).start();
|
||||
}
|
||||
@@ -152,7 +147,7 @@ public class EventApi
|
||||
|
||||
// if this isn't done unfinished tasks may be left in the queue
|
||||
// preventing new LodChunks form being generated
|
||||
LodWorldGenerator.INSTANCE.restartExecutorService();
|
||||
LodGenWorker.restartExecutorService();
|
||||
|
||||
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
|
||||
ApiShared.lodWorld.deselectWorld();
|
||||
@@ -161,11 +156,9 @@ public class EventApi
|
||||
// prevent issues related to the buffer builder
|
||||
// breaking or retaining previous data when changing worlds.
|
||||
ClientApi.renderer.destroyBuffers();
|
||||
ClientApi.renderer.requestCleanup();
|
||||
recalculateWidths = true;
|
||||
// TODO: Check if after the refactoring, is this still needed
|
||||
ClientApi.renderer = new LodRenderer(ApiShared.lodBufferBuilderFactory);
|
||||
ClientApi.INSTANCE.rendererDisabledBecauseOfExceptions = false;
|
||||
|
||||
|
||||
// make sure the nulled objects are freed.
|
||||
// (this prevents an out of memory error when
|
||||
|
||||
+249
-215
@@ -20,7 +20,6 @@
|
||||
package com.seibel.lod.core.builders.bufferBuilding;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -29,11 +28,14 @@ import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
import com.seibel.lod.core.enums.config.LodTemplate;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL45;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
@@ -57,6 +59,9 @@ import com.seibel.lod.core.util.LodThreadFactory;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.util.ThreadMapUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
|
||||
@@ -65,40 +70,26 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
* rendered by the LodRenderer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-9-2021
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public class LodBufferBuilderFactory
|
||||
{
|
||||
public static class LagSpikeCatcher {
|
||||
|
||||
long timer = System.nanoTime();
|
||||
public LagSpikeCatcher() {}
|
||||
public void end(String source) {
|
||||
timer = System.nanoTime() - timer;
|
||||
if (timer> 16000000) { //16 ms
|
||||
ClientApi.LOGGER.debug("NOTE: "+source+" took "+Duration.ofNanos(timer)+"!");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
|
||||
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
|
||||
/** The thread used to generate new LODs off the main thread. */
|
||||
public static final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(LodBufferBuilderFactory.class.getSimpleName() + " - main"));
|
||||
/** The threads used to generate buffers. */
|
||||
public static final ExecutorService bufferBuilderThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfBufferBuilderThreads(), new ThreadFactoryBuilder().setNameFormat("Buffer-Builder-%d").build());
|
||||
|
||||
public static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = TimeUnit.NANOSECONDS.convert(1, TimeUnit.SECONDS);
|
||||
|
||||
|
||||
/**
|
||||
* 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.3;
|
||||
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.5;
|
||||
|
||||
/**
|
||||
* When buffers are first created they are allocated to this size (in Bytes).
|
||||
@@ -155,6 +146,8 @@ public class LodBufferBuilderFactory
|
||||
|
||||
private volatile VertexOptimizer[][] vertexOptimizerCache;
|
||||
private volatile PosToRenderContainer[][] setsToRender;
|
||||
private volatile int centerRegionX = 0;
|
||||
private volatile int centerRegionZ = 0;
|
||||
|
||||
/**
|
||||
* This is the ChunkPosWrapper the player was at the last time the buffers were built.
|
||||
@@ -162,8 +155,8 @@ public class LodBufferBuilderFactory
|
||||
*/
|
||||
private volatile int drawableCenterChunkPosX = 0;
|
||||
private volatile int drawableCenterChunkPosZ = 0;
|
||||
private volatile int buildableCenterBlockPosX = 0;
|
||||
private volatile int buildableCenterBlockPosZ = 0;
|
||||
private volatile int buildableCenterChunkPosX = 0;
|
||||
private volatile int buildableCenterChunkPosZ = 0;
|
||||
|
||||
|
||||
|
||||
@@ -221,8 +214,8 @@ public class LodBufferBuilderFactory
|
||||
// round the player's block position down to the nearest chunk BlockPos
|
||||
int playerChunkX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
int playerChunkZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
//int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.REGION_DETAIL_LEVEL);
|
||||
//int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.REGION_DETAIL_LEVEL);
|
||||
int playerRegionX = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerX,LodUtil.REGION_DETAIL_LEVEL);
|
||||
int playerRegionZ = LevelPosUtil.convert(LodUtil.BLOCK_DETAIL_LEVEL,playerZ,LodUtil.REGION_DETAIL_LEVEL);
|
||||
|
||||
|
||||
//long startTime = System.currentTimeMillis();
|
||||
@@ -244,10 +237,8 @@ public class LodBufferBuilderFactory
|
||||
vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()];
|
||||
|
||||
// this will be the center of the VBOs once they have been built
|
||||
//buildableCenterChunkPosX = playerChunkX;
|
||||
//buildableCenterChunkPosZ = playerChunkZ;
|
||||
buildableCenterBlockPosX = playerX;
|
||||
buildableCenterBlockPosZ = playerZ;
|
||||
buildableCenterChunkPosX = playerChunkX;
|
||||
buildableCenterChunkPosZ = playerChunkZ;
|
||||
|
||||
|
||||
//================================//
|
||||
@@ -327,8 +318,6 @@ public class LodBufferBuilderFactory
|
||||
|
||||
// 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
|
||||
// FIXME: THIS IS NOT HOW IT WORKS! We also can't just loop and copy it. Think of an
|
||||
// idea to fix this!
|
||||
boolean[][] vanillaRenderedChunks = renderer.vanillaRenderedChunks;
|
||||
short gameChunkRenderDistance = (short) (vanillaRenderedChunks.length / 2 - 1);
|
||||
|
||||
@@ -344,7 +333,6 @@ public class LodBufferBuilderFactory
|
||||
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkX;
|
||||
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkZ;
|
||||
|
||||
// FIXME: We don't need to ignore rendered chunks! Just build it and leave it for the renderer to decide!
|
||||
//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
|
||||
//
|
||||
@@ -433,7 +421,7 @@ public class LodBufferBuilderFactory
|
||||
break;
|
||||
|
||||
//We send the call to create the vertices
|
||||
CubicLodTemplate.addLodToBuffer(currentBuffers[bufferIndex], playerX, playerZ, data, adjData,
|
||||
LodTemplate.CUBIC.template.addLodToBuffer(currentBuffers[bufferIndex], playerX, playerY, playerZ, data, adjData,
|
||||
detailLevel, posX, posZ, vertexOptimizer, renderer.previousDebugMode, adjShadeDisabled);
|
||||
}
|
||||
|
||||
@@ -623,27 +611,27 @@ public class LodBufferBuilderFactory
|
||||
|
||||
|
||||
// create the initial mapped buffers (system memory)
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
|
||||
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW);
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
|
||||
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, regionMemoryRequired, GL32.GL_STATIC_DRAW);
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, drawableVbos[x][z][i].id);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
|
||||
if (glProxy.bufferStorageSupported)
|
||||
{
|
||||
// create the buffer storage (GPU memory)
|
||||
buildableStorageBufferIds[x][z][i] = GL44.glGenBuffers();
|
||||
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
|
||||
GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT);
|
||||
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, 0);
|
||||
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] = GL44.glGenBuffers();
|
||||
GL44.glBindBuffer(GL44.GL_ARRAY_BUFFER, drawableStorageBufferIds[x][z][i]);
|
||||
GL44.glBufferStorage(GL44.GL_ARRAY_BUFFER, regionMemoryRequired, GL44.GL_DYNAMIC_STORAGE_BIT);
|
||||
GL44.glBindBuffer(GL44.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,73 +651,106 @@ public class LodBufferBuilderFactory
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the buffers and Vbos to null, forcing them to be recreated <br>
|
||||
* and destroys any bound OpenGL objects. <br>
|
||||
* <br>
|
||||
* and destroys any bound OpenGL objects. <br><br>
|
||||
* <p>
|
||||
* May have to wait for the bufferLock to open.
|
||||
*/
|
||||
public void destroyBuffers() {
|
||||
int[][][] toBeDeletedBuildableStorageBufferIds;
|
||||
int[][][] toBeDeletedDrawableStorageBufferIds;
|
||||
LodVertexBuffer[][][] toBeDeletedBuildableVbos;
|
||||
LodVertexBuffer[][][] toBeDeletedDrawableVbos;
|
||||
bufferLock.lock();
|
||||
try {
|
||||
toBeDeletedBuildableStorageBufferIds = buildableStorageBufferIds;
|
||||
toBeDeletedDrawableStorageBufferIds = drawableStorageBufferIds;
|
||||
toBeDeletedBuildableVbos = buildableVbos;
|
||||
toBeDeletedDrawableVbos = drawableVbos;
|
||||
public void destroyBuffers()
|
||||
{
|
||||
try
|
||||
{
|
||||
bufferLock.lock();
|
||||
|
||||
|
||||
// destroy the buffer storages if they aren't already
|
||||
if (buildableStorageBufferIds != null)
|
||||
{
|
||||
for (int x = 0; x < buildableStorageBufferIds.length; x++)
|
||||
{
|
||||
for (int z = 0; z < buildableStorageBufferIds.length; z++)
|
||||
{
|
||||
for (int i = 0; i < buildableStorageBufferIds[x][z].length; i++)
|
||||
{
|
||||
int buildableId = buildableStorageBufferIds[x][z][i];
|
||||
int drawableId = drawableStorageBufferIds[x][z][i];
|
||||
|
||||
// make sure the buffers are deleted in a openGL context
|
||||
GLProxy.getInstance().recordOpenGlCall(() ->
|
||||
{
|
||||
GL15.glDeleteBuffers(buildableId);
|
||||
GL15.glDeleteBuffers(drawableId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildableStorageBufferIds = null;
|
||||
drawableStorageBufferIds = null;
|
||||
|
||||
|
||||
|
||||
|
||||
// destroy the VBOs if they aren't already
|
||||
if (buildableVbos != null)
|
||||
{
|
||||
for (int i = 0; i < buildableVbos.length; i++)
|
||||
{
|
||||
for (int j = 0; j < buildableVbos.length; j++)
|
||||
{
|
||||
for (int k = 0; k < buildableVbos[i][j].length; k++)
|
||||
{
|
||||
int buildableId;
|
||||
int drawableId;
|
||||
|
||||
// variables passed into a lambda expression
|
||||
// need to be effectively final, so we have
|
||||
// to use an else statement here
|
||||
if (buildableVbos[i][j][k] != null)
|
||||
buildableId = buildableVbos[i][j][k].id;
|
||||
else
|
||||
buildableId = 0;
|
||||
|
||||
if (drawableVbos[i][j][k] != null)
|
||||
drawableId = drawableVbos[i][j][k].id;
|
||||
else
|
||||
drawableId = 0;
|
||||
|
||||
|
||||
GLProxy.getInstance().recordOpenGlCall(() ->
|
||||
{
|
||||
if (buildableId != 0)
|
||||
GL15.glDeleteBuffers(buildableId);
|
||||
if (drawableId != 0)
|
||||
GL15.glDeleteBuffers(drawableId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildableVbos = null;
|
||||
drawableVbos = null;
|
||||
|
||||
|
||||
// these don't contain any OpenGL objects, so
|
||||
// they don't require any special clean-up
|
||||
buildableBuffers = null;
|
||||
} finally {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ClientApi.LOGGER.info("destroyBuffers ran into trouble: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// this shouldn't normally happen, but just in case it sill prevent deadlock
|
||||
bufferLock.unlock();
|
||||
}
|
||||
|
||||
// make sure the buffers are deleted in a openGL context
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> {
|
||||
|
||||
// destroy the buffer storages if they aren't already
|
||||
if (toBeDeletedBuildableStorageBufferIds != null) {
|
||||
for (int x = 0; x < toBeDeletedBuildableStorageBufferIds.length; x++) {
|
||||
for (int z = 0; z < toBeDeletedBuildableStorageBufferIds.length; z++) {
|
||||
for (int i = 0; i < toBeDeletedBuildableStorageBufferIds[x][z].length; i++) {
|
||||
int buildableId = toBeDeletedBuildableStorageBufferIds[x][z][i];
|
||||
int drawableId = toBeDeletedDrawableStorageBufferIds[x][z][i];
|
||||
|
||||
GL32.glDeleteBuffers(buildableId);
|
||||
GL32.glDeleteBuffers(drawableId);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// destroy the VBOs if they aren't already
|
||||
if (toBeDeletedBuildableVbos != null) {
|
||||
for (int i = 0; i < toBeDeletedBuildableVbos.length; i++) {
|
||||
for (int j = 0; j < toBeDeletedBuildableVbos.length; j++) {
|
||||
for (int k = 0; k < toBeDeletedBuildableVbos[i][j].length; k++) {
|
||||
if (toBeDeletedBuildableVbos[i][j][k] != null) {
|
||||
int buildableId = toBeDeletedBuildableVbos[i][j][k].id;
|
||||
GL32.glDeleteBuffers(buildableId);
|
||||
}
|
||||
if (toBeDeletedDrawableVbos[i][j][k] != null) {
|
||||
int drawableId = toBeDeletedDrawableVbos[i][j][k].id;
|
||||
GL32.glDeleteBuffers(drawableId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/** Calls begin on each of the buildable BufferBuilders. */
|
||||
private void startBuffers(boolean fullRegen, LodDimension lodDim)
|
||||
{
|
||||
@@ -741,12 +762,12 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
for (int i = 0; i < buildableBuffers[x][z].length; i++)
|
||||
{
|
||||
// FIXME: for some reason BufferBuilder.vertexCounts
|
||||
// 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(GL32.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
|
||||
buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -768,6 +789,8 @@ public class LodBufferBuilderFactory
|
||||
private void uploadBuffers(boolean fullRegen, LodDimension lodDim)
|
||||
{
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
long fence = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// make sure we are uploading to the builder context,
|
||||
@@ -775,12 +798,25 @@ public class LodBufferBuilderFactory
|
||||
glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
|
||||
|
||||
// determine the upload method
|
||||
GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod();
|
||||
GpuUploadMethod uploadMethod = CONFIG.client().advanced().buffers().getGpuUploadMethod();
|
||||
if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
|
||||
{
|
||||
// if buffer storage isn't supported
|
||||
// default to SUB_DATA
|
||||
CONFIG.client().advanced().buffers().setGpuUploadMethod(GpuUploadMethod.SUB_DATA);
|
||||
uploadMethod = GpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
|
||||
// determine the upload timeout
|
||||
int MBPerMS = CONFIG.client().advanced().buffers().getGpuUploadPerMegabyteInMilliseconds();
|
||||
long BPerNS = MBPerMS; // MB -> B = 1/1,000,000. MS -> NS = 1,000,000. So, MBPerMS = BPerNS.
|
||||
long remainingNS = 0; // We don't want to pause for like 0.1 ms... so we store those tiny MS.
|
||||
int uploadTimeoutInMS = CONFIG.client().advanced().buffers().getGpuUploadTimeoutInMilliseconds();
|
||||
|
||||
// James has no idea if this does anything helpful,
|
||||
// but in theory it should prevent OpenGL from drawing and
|
||||
// writing to a buffer at the same time.
|
||||
GL45.glMemoryBarrier(GL45.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
|
||||
fence = GL45.glFenceSync(GL45.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
||||
|
||||
|
||||
// actually upload the buffers
|
||||
for (int x = 0; x < buildableVbos.length; x++)
|
||||
@@ -791,57 +827,46 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
for (int i = 0; i < buildableBuffers[x][z].length; i++)
|
||||
{
|
||||
ByteBuffer uploadBuffer = null;
|
||||
//FIXME: The sonme Buffers aren't closed/end() and causing errors!
|
||||
try {
|
||||
LagSpikeCatcher b = new LagSpikeCatcher();
|
||||
uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
|
||||
b.end("getCleanedByteBuffer");
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
// NOTE: Temp try/catch for above FIXME.
|
||||
// e.printStackTrace();
|
||||
}
|
||||
if (uploadBuffer == null) continue;
|
||||
if (uploadBuffer.capacity() == 0) continue;
|
||||
LagSpikeCatcher vboU = new LagSpikeCatcher();
|
||||
vboUpload(x,z,i, uploadBuffer, uploadMethod);
|
||||
vboU.end("vboUpload");
|
||||
LagSpikeCatcher setR = new LagSpikeCatcher();
|
||||
ByteBuffer uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
|
||||
vboUpload(x,z,i, uploadBuffer, true, uploadMethod);
|
||||
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
|
||||
setR.end("setRegenRegionBufferByArrayIndex");
|
||||
|
||||
|
||||
|
||||
// upload buffers over an extended period of time
|
||||
// to hopefully prevent stuttering.
|
||||
remainingNS += uploadBuffer.capacity()*BPerNS;
|
||||
if (remainingNS >= TimeUnit.NANOSECONDS.convert(1000/60, TimeUnit.MILLISECONDS)) {
|
||||
if (remainingNS > MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS) remainingNS = MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS;
|
||||
Thread.sleep(remainingNS/1000000, (int) (remainingNS%1000000));
|
||||
remainingNS = 0;
|
||||
}
|
||||
if (uploadTimeoutInMS != 0)
|
||||
Thread.sleep(uploadTimeoutInMS);
|
||||
GL15.glFinish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure all of the uploads finish before continuing
|
||||
GL45.glClientWaitSync(fence, GL45.GL_SYNC_FLUSH_COMMANDS_BIT, 5L * 1000000000); // wait up to 5 seconds
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// this doesn't appear to be necessary anymore, but just in case.
|
||||
ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
// newSingleThreadExecutor doesn't mean that all jobs will be on a single, same
|
||||
// thread. It just means that it can at most use one thread. If there are no
|
||||
// jobs for a certain amount of time, or something happened when a job is
|
||||
// executing, it could decide to delete the thread, and create a new one for the
|
||||
// next job. So we will need to release the gl context.
|
||||
LagSpikeCatcher end = new LagSpikeCatcher();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GL15.glFinish();
|
||||
if (fence != 0)
|
||||
GL45.glDeleteSync(fence);
|
||||
|
||||
// close the context so it can be re-used later.
|
||||
// I'm guessing we can't just leave it because the executor service
|
||||
// does something that invalidates the OpenGL context.
|
||||
glProxy.setGlContext(GLProxyContext.NONE);
|
||||
end.end("GLSwitchContext");
|
||||
}
|
||||
}
|
||||
|
||||
/** Uploads the uploadBuffer so the GPU can use it. */
|
||||
private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod)
|
||||
private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer,
|
||||
boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
|
||||
{
|
||||
// get the vbos, buffers, ids, etc.
|
||||
int storageBufferId = 0;
|
||||
@@ -850,101 +875,115 @@ public class LodBufferBuilderFactory
|
||||
|
||||
LodVertexBuffer vbo = buildableVbos[xIndex][zIndex][iIndex];
|
||||
|
||||
|
||||
|
||||
|
||||
// this shouldn't happen, but just to be safe
|
||||
if (vbo.id != -1 && GLProxy.getInstance().getGlContext() == GLProxyContext.LOD_BUILDER)
|
||||
{
|
||||
// this is how many points will be rendered
|
||||
vbo.vertexCount = (uploadBuffer.capacity() / LodUtil.LOD_VERTEX_FORMAT.getByteSize());
|
||||
// If size is zero, just ignore it.
|
||||
if (uploadBuffer.capacity()==0) return;
|
||||
|
||||
LagSpikeCatcher bindBuff = new LagSpikeCatcher();
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, vbo.id);
|
||||
bindBuff.end("glBindBuffer vbo.id");
|
||||
vbo.vertexCount = (uploadBuffer.capacity() / ((Float.BYTES * 3) + (Byte.BYTES * 4) + Byte.BYTES + Byte.BYTES)); // TODO make this change with the LodTemplate
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
|
||||
try
|
||||
{
|
||||
// if possible use the faster buffer storage route
|
||||
if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0)
|
||||
{
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId);
|
||||
|
||||
LagSpikeCatcher getParm = new LagSpikeCatcher();
|
||||
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
|
||||
getParm.end("glGetBufferParameteri BuffStorage");
|
||||
if (size < uploadBuffer.capacity())
|
||||
// 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 newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
|
||||
LagSpikeCatcher buffResizeRegen = new LagSpikeCatcher();
|
||||
GL32.glDeleteBuffers(storageBufferId);
|
||||
buildableStorageBufferIds[xIndex][zIndex][iIndex] = GL32.glGenBuffers();
|
||||
buffResizeRegen.end("glDeleteBuffers BuffStorage resize");
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, storageBufferId);
|
||||
storageBufferId = buildableStorageBufferIds[xIndex][zIndex][iIndex];
|
||||
LagSpikeCatcher buffResize = new LagSpikeCatcher();
|
||||
GL44.glBufferStorage(GL32.GL_ARRAY_BUFFER, newSize, GL44.GL_DYNAMIC_STORAGE_BIT);
|
||||
buffResize.end("glBufferStorage BuffStorage resize");
|
||||
int previousCapacity = uploadBuffer.capacity();
|
||||
|
||||
// only expand the buffers if the uploadBuffer actually
|
||||
// has something in it and expansion is allowed
|
||||
if (previousCapacity != 0 && allowBufferExpansion)
|
||||
{
|
||||
// the buffer(s) aren't big enough, expand them.
|
||||
// This does cause lag/stuttering, so it should be avoided!
|
||||
|
||||
// expand the buffer in system memory
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_DYNAMIC_DRAW);
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
|
||||
// un-bind the system memory buffer
|
||||
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// expand the buffer storage
|
||||
GL15.glDeleteBuffers(storageBufferId);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, storageBufferId);
|
||||
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), 0);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
|
||||
// recursively try to upload into the newly created buffer storage
|
||||
// but don't recurse again if that fails
|
||||
// (we don't want an infinitely expanding buffer!)
|
||||
vboUpload(xIndex,zIndex,iIndex, uploadBuffer, false, uploadMethod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// upload the buffer into system memory...
|
||||
vboBuffer.put(uploadBuffer);
|
||||
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
|
||||
// ...then upload into GPU memory
|
||||
// (uploading into GPU memory directly can only be done
|
||||
// through the glCopyBufferSubData/glCopyNamed... methods)
|
||||
GL45.glCopyNamedBufferSubData(vbo.id, storageBufferId, 0, 0, uploadBuffer.capacity());
|
||||
}
|
||||
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
|
||||
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
buffSubData.end("glBufferSubData BuffStorage");
|
||||
}
|
||||
else if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
|
||||
{
|
||||
// TODO: Check this half reasonable comment!
|
||||
// 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.
|
||||
LagSpikeCatcher getParm = new LagSpikeCatcher();
|
||||
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
|
||||
getParm.end("glGetBufferParameteri BuffMapping");
|
||||
if (size < uploadBuffer.capacity())
|
||||
{
|
||||
int newSize = (int) (uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
|
||||
LagSpikeCatcher buffResize = new LagSpikeCatcher();
|
||||
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
|
||||
buffResize.end("glBufferData BuffMapping resize");
|
||||
}
|
||||
|
||||
|
||||
ByteBuffer vboBuffer;
|
||||
// map buffer range is better since it can be explicitly unsynchronized
|
||||
LagSpikeCatcher buffMap = new LagSpikeCatcher();
|
||||
vboBuffer = GL32.glMapBufferRange(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(),
|
||||
GL32.GL_MAP_WRITE_BIT | GL32.GL_MAP_UNSYNCHRONIZED_BIT | GL32.GL_MAP_INVALIDATE_BUFFER_BIT);
|
||||
buffMap.end("glMapBufferRange BuffMapping");
|
||||
LagSpikeCatcher buffWrite = new LagSpikeCatcher();
|
||||
vboBuffer.put(uploadBuffer);
|
||||
buffWrite.end("WriteData BuffMapping");
|
||||
|
||||
// map buffer range is better since it can be explicitly unsynchronized
|
||||
if (GLProxy.getInstance().mapBufferRangeSupported)
|
||||
vboBuffer = GL30.glMapBufferRange(GL30.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT);
|
||||
else
|
||||
vboBuffer = GL15.glMapBuffer(GL30.GL_ARRAY_BUFFER, uploadBuffer.capacity());
|
||||
|
||||
|
||||
if (vboBuffer == null)
|
||||
{
|
||||
GL15.glBufferData(GL45.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
vboBuffer.put(uploadBuffer);
|
||||
}
|
||||
}
|
||||
else if (uploadMethod == GpuUploadMethod.DATA)
|
||||
{
|
||||
// TODO: Check this nonsense comment!
|
||||
// hybrid bufferData //
|
||||
// high stutter, low GPU usage
|
||||
// But simplest/most compatible
|
||||
LagSpikeCatcher buffData = new LagSpikeCatcher();
|
||||
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW);
|
||||
buffData.end("glBufferData Data");
|
||||
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.capacity(), GL15.GL_STATIC_DRAW);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15.GL_STATIC_DRAW);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Check this nonsense comment!
|
||||
// hybrid subData/bufferData //
|
||||
// less stutter, low GPU usage
|
||||
LagSpikeCatcher getParm = new LagSpikeCatcher();
|
||||
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
|
||||
getParm.end("glGetBufferParameteri SubData");
|
||||
if (size < uploadBuffer.capacity())
|
||||
|
||||
long size = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (size < uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER)
|
||||
{
|
||||
int newSize = (int)(uploadBuffer.capacity()*BUFFER_EXPANSION_MULTIPLIER);
|
||||
LagSpikeCatcher buffResize = new LagSpikeCatcher();
|
||||
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, newSize, GL32.GL_STATIC_DRAW);
|
||||
buffResize.end("glBufferData SubData resize");
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
|
||||
}
|
||||
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
|
||||
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
buffSubData.end("glBufferSubData SubData");
|
||||
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -954,14 +993,10 @@ public class LodBufferBuilderFactory
|
||||
}
|
||||
finally
|
||||
{
|
||||
LagSpikeCatcher buffUnmap = new LagSpikeCatcher();
|
||||
if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
|
||||
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
|
||||
buffUnmap.end("glUnmapBuffer");
|
||||
|
||||
LagSpikeCatcher buffUnbind = new LagSpikeCatcher();
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
buffUnbind.end("glBindBuffer 0");
|
||||
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
}//if vbo exists and in correct GL context
|
||||
@@ -972,7 +1007,6 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
// don't wait for the lock to open,
|
||||
// since this is called on the main render thread
|
||||
// TODO: Use atomic swap instead of locks!
|
||||
if (bufferLock.tryLock())
|
||||
{
|
||||
try
|
||||
@@ -985,8 +1019,8 @@ public class LodBufferBuilderFactory
|
||||
drawableStorageBufferIds = buildableStorageBufferIds;
|
||||
buildableStorageBufferIds = tmpStorage;
|
||||
|
||||
drawableCenterChunkPosX = buildableCenterBlockPosX;
|
||||
drawableCenterChunkPosZ = buildableCenterBlockPosZ;
|
||||
drawableCenterChunkPosX = buildableCenterChunkPosX;
|
||||
drawableCenterChunkPosZ = buildableCenterChunkPosZ;
|
||||
|
||||
// the vbos have been swapped
|
||||
switchVbos = false;
|
||||
@@ -1010,15 +1044,15 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
public final LodVertexBuffer[][][] vbos;
|
||||
public final int[][][] storageBufferIds;
|
||||
public int drawableCenterBlockPosX;
|
||||
public int drawableCenterBlockPosZ;
|
||||
public int drawableCenterChunkPosX;
|
||||
public int drawableCenterChunkPosZ;
|
||||
|
||||
public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterBlockPosX, int newDrawableCenterBlockPosZ)
|
||||
public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterChunkPosX, int newDrawableCenterChunkPosZ)
|
||||
{
|
||||
vbos = newVbos;
|
||||
storageBufferIds = newStorageBufferIds;
|
||||
drawableCenterBlockPosX = newDrawableCenterBlockPosX;
|
||||
drawableCenterBlockPosZ = newDrawableCenterBlockPosZ;
|
||||
drawableCenterChunkPosX = newDrawableCenterChunkPosX;
|
||||
drawableCenterChunkPosZ = newDrawableCenterChunkPosZ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.core.builders.bufferBuilding.lodTemplates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.util.ColorUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
|
||||
/**
|
||||
* This is the abstract class used to create different
|
||||
* BufferBuilders.
|
||||
* @author James Seibel
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public abstract class AbstractLodTemplate
|
||||
{
|
||||
/** Uploads the given LOD to the buffer. */
|
||||
public abstract void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
|
||||
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled);
|
||||
|
||||
/** add the given position and color to the buffer */
|
||||
protected void addPosAndColor(LodBufferBuilder buffer,
|
||||
float x, float y, float z,
|
||||
int color, byte skyLightValue, byte blockLightValue)
|
||||
{
|
||||
// TODO re-add transparency by replacing the color 255 with "ColorUtil.getAlpha(color)"
|
||||
buffer.position(x, y, z)
|
||||
.color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255)
|
||||
.minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue)
|
||||
.endVertex();
|
||||
}
|
||||
|
||||
}
|
||||
+14
-39
@@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.bufferBuilding;
|
||||
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -25,25 +25,24 @@ import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.util.ColorUtil;
|
||||
import com.seibel.lod.core.util.DataPointUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
|
||||
import static com.seibel.lod.core.builders.lodBuilding.LodBuilder.MIN_WORLD_HEIGHT;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Builds LODs as rectangular prisms.
|
||||
* @author James Seibel
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public class CubicLodTemplate
|
||||
public class CubicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
//TODO make it a config
|
||||
static int cullingRange = 128;
|
||||
|
||||
public static void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerZ, long data, Map<LodDirection, long[]> adjData,
|
||||
public CubicLodTemplate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
|
||||
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
|
||||
{
|
||||
if (vertexOptimizer == null)
|
||||
@@ -66,6 +65,7 @@ public class CubicLodTemplate
|
||||
blockWidth,
|
||||
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
|
||||
playerX,
|
||||
playerY,
|
||||
playerZ,
|
||||
adjData,
|
||||
color,
|
||||
@@ -76,24 +76,10 @@ public class CubicLodTemplate
|
||||
addBoundingBoxToBuffer(buffer, vertexOptimizer);
|
||||
}
|
||||
|
||||
/** add the given position and color to the buffer */
|
||||
public static void addPosAndColor(LodBufferBuilder buffer,
|
||||
float x, float y, float z,
|
||||
int color, byte skyLightValue, byte blockLightValue)
|
||||
{
|
||||
// TODO transparency re-add by replacing the color 255 with "ColorUtil.getAlpha(color)"
|
||||
buffer.position(x, y, z)
|
||||
.color(ColorUtil.getRed(color), ColorUtil.getGreen(color), ColorUtil.getBlue(color), 255)
|
||||
.minecraftLightValue(skyLightValue).minecraftLightValue(blockLightValue)
|
||||
.endVertex();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void generateBoundingBox(VertexOptimizer vertexOptimizer,
|
||||
private void generateBoundingBox(VertexOptimizer vertexOptimizer,
|
||||
int height, int depth, int width,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
int playerX, int playerZ,
|
||||
int playerX, int playerY, int playerZ,
|
||||
Map<LodDirection, long[]> adjData,
|
||||
int color, byte skyLight, byte blockLight,
|
||||
boolean[] adjShadeDisabled)
|
||||
@@ -120,24 +106,13 @@ public class CubicLodTemplate
|
||||
vertexOptimizer.setAdjData(adjData);
|
||||
}
|
||||
|
||||
private static void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
|
||||
private void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
|
||||
{
|
||||
int color;
|
||||
byte skyLight;
|
||||
byte blockLight;
|
||||
|
||||
for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS)
|
||||
{
|
||||
//if(vertexOptimizer.isCulled(lodDirection))
|
||||
// continue;
|
||||
// culling
|
||||
if (lodDirection == LodDirection.NORTH && vertexOptimizer.getZ(lodDirection, 0) < -cullingRange
|
||||
|| lodDirection == LodDirection.EAST && vertexOptimizer.getX(lodDirection, 0) > cullingRange
|
||||
|| lodDirection == LodDirection.SOUTH && vertexOptimizer.getZ(lodDirection, 0) > cullingRange
|
||||
|| lodDirection == LodDirection.WEST && vertexOptimizer.getX(lodDirection, 0) < -cullingRange)
|
||||
continue;
|
||||
|
||||
|
||||
int verticalFaceIndex = 0;
|
||||
while (vertexOptimizer.shouldRenderFace(lodDirection, verticalFaceIndex))
|
||||
{
|
||||
@@ -148,7 +123,7 @@ public class CubicLodTemplate
|
||||
color = vertexOptimizer.getColor(lodDirection);
|
||||
addPosAndColor(buffer,
|
||||
vertexOptimizer.getX(lodDirection, vertexIndex),
|
||||
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + MIN_WORLD_HEIGHT,
|
||||
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + DataPointUtil.VERTICAL_OFFSET,
|
||||
vertexOptimizer.getZ(lodDirection, vertexIndex),
|
||||
color, skyLight, blockLight );
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
|
||||
/**
|
||||
* TODO DynamicLodTemplate
|
||||
* Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height.
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class DynamicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
|
||||
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
|
||||
{
|
||||
ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
|
||||
/**
|
||||
* TODO #21 TriangularLodTemplate
|
||||
* Builds each LOD chunk as a singular rectangular prism.
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class TriangularLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerY, int playerZ, long data, Map<LodDirection, long[]> adjData,
|
||||
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
|
||||
{
|
||||
ClientApi.LOGGER.error(DynamicLodTemplate.class.getSimpleName() + " is not implemented!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,9 +35,12 @@ import com.seibel.lod.core.util.LodThreadFactory;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.util.ThreadMapUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorSingletonWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
@@ -51,18 +54,18 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
* @author Cola
|
||||
* @author Leonardo Amato
|
||||
* @author James Seibel
|
||||
* @version 12-11-2021
|
||||
* @version 10-22-2021
|
||||
*/
|
||||
@SuppressWarnings("GrazieInspection")
|
||||
public class LodBuilder
|
||||
@SuppressWarnings("GrazieInspection") public class LodBuilder
|
||||
{
|
||||
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
|
||||
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
|
||||
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
|
||||
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
|
||||
|
||||
/** This cannot be final! Different world have different height, and in menu, this causes Null Exceptions*/
|
||||
//public static final short MIN_WORLD_HEIGHT = MC.getWrappedClientWorld().getMinHeight();
|
||||
public static short MIN_WORLD_HEIGHT = 0; // Currently modified in EventApi.onWorldLoaded(...)
|
||||
/** 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;
|
||||
|
||||
@@ -80,7 +83,6 @@ public class LodBuilder
|
||||
|
||||
//public static final boolean useExperimentalLighting = true;
|
||||
|
||||
private static int timesToEdgeDetect = 1;
|
||||
|
||||
|
||||
|
||||
@@ -108,8 +110,8 @@ public class LodBuilder
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
//noinspection GrazieInspection
|
||||
//try
|
||||
//{
|
||||
try
|
||||
{
|
||||
// we need a loaded client world in order to
|
||||
// get the textures for blocks
|
||||
if (MC.getWrappedClientWorld() == null)
|
||||
@@ -132,14 +134,14 @@ public class LodBuilder
|
||||
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.
|
||||
//}
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -196,16 +198,9 @@ public class LodBuilder
|
||||
|
||||
long[] data;
|
||||
long[] dataToMergeVertical = createVerticalDataToMerge(detail, chunk, config, startX, startZ);
|
||||
data = DataPointUtil.mergeMultiData(dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1, DetailDistanceUtil.getMaxVerticalData(detailLevel));
|
||||
|
||||
|
||||
//lodDim.clear(detailLevel, posX, posZ);
|
||||
if (data != null && data.length != 0)
|
||||
{
|
||||
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
|
||||
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
|
||||
lodDim.addVerticalData(detailLevel, posX, posZ, data, false);
|
||||
}
|
||||
posX = LevelPosUtil.convert((byte) 0, chunk.getChunkPosX() * 16 + startX, detail.detailLevel);
|
||||
posZ = LevelPosUtil.convert((byte) 0, chunk.getChunkPosZ() * 16 + startZ, detail.detailLevel);
|
||||
lodDim.mergeMultiData(detailLevel, posX, posZ, false, dataToMergeVertical, DataPointUtil.WORLD_HEIGHT / 2 + 1);
|
||||
}
|
||||
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getChunkPosX(), chunk.getChunkPosZ());
|
||||
//executeTime = System.currentTimeMillis() - executeTime;
|
||||
@@ -246,38 +241,40 @@ public class LodBuilder
|
||||
zAbs = chunk.getMinZ() + zRel;
|
||||
|
||||
//Calculate the height of the lod
|
||||
yAbs = chunk.getMaxY(xRel,zRel) - MIN_WORLD_HEIGHT;
|
||||
yAbs = chunk.getMaxY(xRel, zRel); //DataPointUtil.WORLD_HEIGHT - DataPointUtil.VERTICAL_OFFSET + 1;
|
||||
int count = 0;
|
||||
boolean topBlock = true;
|
||||
if (yAbs <= 0);
|
||||
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
|
||||
while (yAbs > 0)
|
||||
{
|
||||
height = determineHeightPointFrom(chunk, config, xAbs, yAbs, zAbs);
|
||||
|
||||
// If the lod is at the default height, it must be void data
|
||||
if (height == 0)
|
||||
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, xAbs, yAbs, zAbs, count < timesToEdgeDetect && !hasCeiling);
|
||||
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs);
|
||||
if (hasCeiling && topBlock)
|
||||
{
|
||||
yAbs = depth;
|
||||
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, true, hasSkyLight, true);
|
||||
light = getLightValue(chunk, xAbs,yAbs,zAbs, true, hasSkyLight, true);
|
||||
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
|
||||
}
|
||||
else
|
||||
{
|
||||
light = getLightValue(chunk, xAbs, yAbs + MIN_WORLD_HEIGHT, zAbs, hasCeiling, hasSkyLight, topBlock);
|
||||
light = getLightValue(chunk, xAbs, yAbs, zAbs, hasCeiling, hasSkyLight, topBlock);
|
||||
color = generateLodColor(chunk, config, xAbs, yAbs, 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);
|
||||
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height - DataPointUtil.VERTICAL_OFFSET, depth - DataPointUtil.VERTICAL_OFFSET, color, lightSky, lightBlock, generation, isDefault);
|
||||
topBlock = false;
|
||||
yAbs = depth - 1;
|
||||
count++;
|
||||
@@ -290,40 +287,17 @@ public class LodBuilder
|
||||
* Find the lowest valid point from the bottom.
|
||||
* Used when creating a vertical LOD.
|
||||
*/
|
||||
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
|
||||
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
|
||||
{
|
||||
short depth = 0;
|
||||
short depth = DEFAULT_DEPTH;
|
||||
|
||||
int colorOfBlock = 0;
|
||||
if (strictEdge)
|
||||
for (int y = yAbs; y >= 0; y--)
|
||||
{
|
||||
colorOfBlock = chunk.getBlockColorWrapper(xAbs, yAbs, zAbs).getColor();
|
||||
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(xAbs, yAbs + 1, zAbs);
|
||||
if (block != null && ((this.config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|
||||
|| (this.config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
|
||||
{
|
||||
int aboveColorInt = chunk.getBlockColorWrapper(xAbs, yAbs + 1, zAbs).getColor();
|
||||
if (aboveColorInt != 0)
|
||||
colorOfBlock = aboveColorInt;
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = yAbs - 1; y >= 0; y--)
|
||||
{
|
||||
|
||||
if (!isLayerValidLodPoint(chunk, xAbs, y, zAbs))
|
||||
{
|
||||
depth = (short) (y + 1);
|
||||
break;
|
||||
}
|
||||
if (strictEdge)
|
||||
{
|
||||
if (colorOfBlock != chunk.getBlockColorWrapper(xAbs, y, zAbs).getColor())
|
||||
{
|
||||
depth = (short) (y + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
@@ -331,8 +305,8 @@ public class LodBuilder
|
||||
/** Find the highest valid point from the Top */
|
||||
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
|
||||
{
|
||||
//TODO find a way to skip bottom of the world
|
||||
short height = 0;
|
||||
|
||||
short height = DEFAULT_HEIGHT;
|
||||
if (config.useHeightmap)
|
||||
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
|
||||
else
|
||||
@@ -376,10 +350,10 @@ public class LodBuilder
|
||||
// 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;
|
||||
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y + 1, z);
|
||||
if (block != null && ((config.client().worldGenerator().getBlocksToAvoid().nonFull && block.isNonFull())
|
||||
|| (config.client().worldGenerator().getBlocksToAvoid().noCollision && block.hasNoCollision())))
|
||||
aboveColorInt = getColorForBlock(chunk, x, y + 1, z);
|
||||
if (config.client().worldGenerator().getBlocksToAvoid().nonFull || config.client().worldGenerator().getBlocksToAvoid().noCollision)
|
||||
{
|
||||
aboveColorInt = getColorForBlock(chunk, x, y, z);
|
||||
}
|
||||
|
||||
//if (colorInt == 0 && yAbs > 0)
|
||||
// if this block is invisible, check the block below it
|
||||
@@ -491,19 +465,17 @@ public class LodBuilder
|
||||
int colorOfBlock;
|
||||
int colorInt;
|
||||
|
||||
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
|
||||
|
||||
if (blockShapeWrapper == null || blockShapeWrapper.isToAvoid())
|
||||
return 0;
|
||||
|
||||
IBlockColorWrapper blockColorWrapper;
|
||||
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
|
||||
|
||||
if (chunk.isWaterLogged(x, y, z))
|
||||
blockColorWrapper = BLOCK_COLOR.getWaterColor();
|
||||
else
|
||||
blockColorWrapper = chunk.getBlockColorWrapper(x, y, z);
|
||||
|
||||
|
||||
if (blockShapeWrapper.isToAvoid())
|
||||
return 0;
|
||||
|
||||
colorOfBlock = blockColorWrapper.getColor();
|
||||
|
||||
@@ -539,7 +511,6 @@ public class LodBuilder
|
||||
boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision;
|
||||
|
||||
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y, z);
|
||||
if (block == null) return false;
|
||||
return !block.isToAvoid()
|
||||
&& !(nonFullAvoidance && block.isNonFull())
|
||||
&& !(noCollisionAvoidance && block.hasNoCollision());
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.worldGeneration;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
|
||||
|
||||
/**
|
||||
* This is used to generate a LodChunk at a given ChunkPos.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-20-2021
|
||||
*/
|
||||
public class LodGenWorker
|
||||
{
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
|
||||
public static ExecutorService genThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
|
||||
|
||||
private final LodChunkGenThread thread;
|
||||
|
||||
|
||||
|
||||
public LodGenWorker(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
|
||||
LodBuilder newLodBuilder,
|
||||
LodDimension newLodDimension, IWorldWrapper serverWorld)
|
||||
{
|
||||
// just a few sanity checks
|
||||
if (newPos == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
|
||||
|
||||
if (newLodBuilder == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
|
||||
|
||||
if (newLodDimension == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
|
||||
|
||||
if (serverWorld == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
|
||||
|
||||
|
||||
|
||||
thread = new LodChunkGenThread(newPos, newGenerationMode,
|
||||
newLodBuilder,
|
||||
newLodDimension, serverWorld);
|
||||
}
|
||||
|
||||
public void queueWork()
|
||||
{
|
||||
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() == DistanceGenerationMode.FULL)
|
||||
{
|
||||
// if we are using FULL generation there is no reason
|
||||
// to queue up a bunch of generation requests,
|
||||
// because MC's internal server (as of 1.16.5) only
|
||||
// responds with a single thread. And we don't
|
||||
// want to cause more lag than necessary or queue up
|
||||
// requests that may end up being unneeded.
|
||||
thread.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Every other method can
|
||||
// be done asynchronously
|
||||
genThreads.execute(thread);
|
||||
}
|
||||
|
||||
// useful for debugging
|
||||
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static class LodChunkGenThread implements Runnable
|
||||
{
|
||||
private final AbstractWorldGeneratorWrapper worldGenWrapper;
|
||||
|
||||
public final LodDimension lodDim;
|
||||
public final DistanceGenerationMode generationMode;
|
||||
|
||||
private final AbstractChunkPosWrapper pos;
|
||||
|
||||
public LodChunkGenThread(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
|
||||
LodBuilder newLodBuilder,
|
||||
LodDimension newLodDimension, IWorldWrapper worldWrapper)
|
||||
{
|
||||
worldGenWrapper = FACTORY.createWorldGenerator(newLodBuilder, newLodDimension, worldWrapper);
|
||||
|
||||
pos = newPos;
|
||||
generationMode = newGenerationMode;
|
||||
lodDim = newLodDimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
//try
|
||||
{
|
||||
// only generate LodChunks if they can
|
||||
// be added to the current LodDimension
|
||||
|
||||
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
|
||||
{
|
||||
switch (generationMode)
|
||||
{
|
||||
case NONE:
|
||||
// don't generate
|
||||
break;
|
||||
case BIOME_ONLY:
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
// fastest
|
||||
worldGenWrapper.generateBiomesOnly(pos, generationMode);
|
||||
break;
|
||||
case SURFACE:
|
||||
// faster
|
||||
worldGenWrapper.generateSurface(pos);
|
||||
break;
|
||||
case FEATURES:
|
||||
// fast
|
||||
worldGenWrapper.generateFeatures(pos);
|
||||
break;
|
||||
case FULL:
|
||||
// very slow
|
||||
worldGenWrapper.generateFull(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
|
||||
// if (dataExistence)
|
||||
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
|
||||
// else
|
||||
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
|
||||
|
||||
// shows the pool size, active threads, queued tasks and completed tasks
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
|
||||
// long endTime = System.currentTimeMillis();
|
||||
// System.out.println(endTime - startTime);
|
||||
|
||||
}// if in range
|
||||
}
|
||||
//catch (Exception e)
|
||||
//{
|
||||
// ClientApi.LOGGER.error(LodChunkGenThread.class.getSimpleName() + ": ran into an error: " + e.getMessage());
|
||||
// e.printStackTrace();
|
||||
//}
|
||||
//finally
|
||||
//{
|
||||
// decrement how many threads are running
|
||||
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
|
||||
|
||||
// this position is no longer being generated
|
||||
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
|
||||
//}
|
||||
}// run
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops the current genThreads if they are running
|
||||
* and then recreates the Executor service. <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(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(), new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
|
||||
}
|
||||
|
||||
}
|
||||
+34
-198
@@ -25,47 +25,50 @@ import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
import com.seibel.lod.core.objects.PosToGenerateContainer;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.render.LodRenderer;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.LevelPosUtil;
|
||||
import com.seibel.lod.core.util.LodThreadFactory;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
|
||||
|
||||
/**
|
||||
* A singleton that handles all long distance LOD world generation.
|
||||
* @author Leonardo Amato
|
||||
* @author James Seibel
|
||||
* @version 12-11-2021
|
||||
* @version 9-25-2021
|
||||
*/
|
||||
public class LodWorldGenerator
|
||||
{
|
||||
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
private static final IWrapperFactory WRAPPER_FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
private static final IVersionConstants VERSION_CONSTANTS = SingletonHandler.get(IVersionConstants.class);
|
||||
|
||||
|
||||
/** This holds the thread used to create LOD generation requests off the main thread. */
|
||||
private final ExecutorService mainGenThread = Executors.newSingleThreadExecutor(new LodThreadFactory(this.getClass().getSimpleName() + " world generator"));
|
||||
private ExecutorService genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
|
||||
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
|
||||
|
||||
|
||||
/** 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
|
||||
@@ -79,64 +82,32 @@ public class LodWorldGenerator
|
||||
* Singleton copy of this object
|
||||
*/
|
||||
public static final LodWorldGenerator INSTANCE = new LodWorldGenerator();
|
||||
public AbstractExperimentalWorldGeneratorWrapper experimentalWorldGenerator;
|
||||
|
||||
private LodWorldGenerator() {}
|
||||
|
||||
|
||||
private LodWorldGenerator()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues up LodNodeGenWorkers for the given lodDimension.
|
||||
* renderer needed so the LodNodeGenWorkers can flag that the
|
||||
* @param renderer needed so the LodNodeGenWorkers can flag that the
|
||||
* buffers need to be rebuilt.
|
||||
*/
|
||||
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
|
||||
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
|
||||
{
|
||||
|
||||
IWorldWrapper world = LodUtil.getServerWorldFromDimension(lodDim.dimension);
|
||||
|
||||
// TODO: Rename the config option
|
||||
if (CONFIG.client().worldGenerator().getAllowUnstableFeatureGeneration()) {
|
||||
if (experimentalWorldGenerator == null) {
|
||||
experimentalWorldGenerator = WRAPPER_FACTORY.createExperimentalWorldGenerator(lodBuilder, lodDim, world);
|
||||
if (experimentalWorldGenerator == null) CONFIG.client().worldGenerator().setAllowUnstableFeatureGeneration(false);
|
||||
}
|
||||
} else {
|
||||
if (experimentalWorldGenerator != null) {
|
||||
experimentalWorldGenerator.stop();
|
||||
experimentalWorldGenerator = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (experimentalWorldGenerator != null) {
|
||||
experimentalWorldGenerator.queueGenerationRequests(lodDim, lodBuilder);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: This currently doesn't use the DetailDistanceUtil.getDistanceGenerationMode(int detail) to get the mode.
|
||||
// This is fine currently since DistanceGenerationMode doesn't care about the detail level for now.
|
||||
// However, If that was to be changed, This will need to be fixed.
|
||||
DistanceGenerationMode mode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
|
||||
|
||||
if (mode != DistanceGenerationMode.NONE
|
||||
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE
|
||||
&& !generatorThreadRunning
|
||||
&& MC.hasSinglePlayerServer())
|
||||
{
|
||||
// the thread is now running, don't queue up another thread
|
||||
generatorThreadRunning = true;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
int genRequestPerThread = VERSION_CONSTANTS.getWorldGenerationCountPerThread();
|
||||
int maxChunkGenRequests;
|
||||
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
|
||||
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * genRequestPerThread;
|
||||
else maxChunkGenRequests = genRequestPerThread;
|
||||
|
||||
Runnable generatorFunc = (() ->
|
||||
// just in case the config changed
|
||||
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8;
|
||||
|
||||
Thread generatorThread = new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -156,6 +127,7 @@ public class LodWorldGenerator
|
||||
playerPosX,
|
||||
playerPosZ);
|
||||
|
||||
|
||||
byte detailLevel;
|
||||
int posX;
|
||||
int posZ;
|
||||
@@ -168,7 +140,7 @@ public class LodWorldGenerator
|
||||
// an easy way to do so.
|
||||
|
||||
// add the near positions
|
||||
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
|
||||
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
|
||||
{
|
||||
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
|
||||
posX = posToGenerate.getNthPosX(nearIndex, true);
|
||||
@@ -187,12 +159,13 @@ public class LodWorldGenerator
|
||||
|
||||
positionsWaitingToBeGenerated.add(chunkPos);
|
||||
numberOfChunksWaitingToGenerate.addAndGet(1);
|
||||
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
|
||||
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
|
||||
genWorker.queueWork();
|
||||
}
|
||||
|
||||
|
||||
// add the far positions
|
||||
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
|
||||
if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
|
||||
{
|
||||
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
|
||||
posX = posToGenerate.getNthPosX(farIndex, false);
|
||||
@@ -212,12 +185,13 @@ public class LodWorldGenerator
|
||||
|
||||
positionsWaitingToBeGenerated.add(chunkPos);
|
||||
numberOfChunksWaitingToGenerate.addAndGet(1);
|
||||
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
|
||||
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
|
||||
genWorker.queueWork();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (RuntimeException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
// this shouldn't ever happen, but just in case
|
||||
e.printStackTrace();
|
||||
@@ -228,146 +202,8 @@ public class LodWorldGenerator
|
||||
}
|
||||
});
|
||||
|
||||
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
|
||||
{
|
||||
generatorFunc.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
mainGenThread.execute(generatorFunc);
|
||||
}
|
||||
mainGenThread.execute(generatorThread);
|
||||
} // if distanceGenerationMode != DistanceGenerationMode.NONE && !generatorThreadRunning
|
||||
} // queueGenerationRequests
|
||||
|
||||
private void queueWork(AbstractChunkPosWrapper newPos, DistanceGenerationMode newGenerationMode,
|
||||
LodBuilder newLodBuilder,
|
||||
LodDimension newLodDimension, IWorldWrapper serverWorld)
|
||||
{
|
||||
// just a few sanity checks
|
||||
if (newPos == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
|
||||
|
||||
if (newLodBuilder == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
|
||||
|
||||
if (newLodDimension == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
|
||||
|
||||
if (serverWorld == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
|
||||
|
||||
Runnable method = (() -> {generateChunk(newPos, newGenerationMode,
|
||||
newLodBuilder, newLodDimension, serverWorld);});
|
||||
|
||||
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(newGenerationMode))
|
||||
{
|
||||
// --Note: This is now using version constants--
|
||||
// if we are using FULL generation there is no reason
|
||||
// to queue up a bunch of generation requests,
|
||||
// because MC's internal server (as of 1.16.5) only
|
||||
// responds with a single thread. And we don't
|
||||
// want to cause more lag than necessary or queue up
|
||||
// requests that may end up being unneeded.
|
||||
// In 1.17+, world generation becomes completely single
|
||||
// threaded. So to allow that, we check the boolean for
|
||||
// whether the wrapper requires single thread
|
||||
method.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Every other method can
|
||||
// be done asynchronously
|
||||
genSubThreads.execute(method);
|
||||
}
|
||||
|
||||
// useful for debugging
|
||||
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
}
|
||||
|
||||
private void generateChunk(AbstractChunkPosWrapper pos, DistanceGenerationMode generationMode,
|
||||
LodBuilder newLodBuilder, LodDimension lodDim, IWorldWrapper worldWrapper)
|
||||
{
|
||||
// try
|
||||
{
|
||||
AbstractWorldGeneratorWrapper worldGenWrapper = WRAPPER_FACTORY.createWorldGenerator(newLodBuilder, lodDim, worldWrapper);
|
||||
// only generate LodChunks if they can
|
||||
// be added to the current LodDimension
|
||||
|
||||
if (lodDim.regionIsInRange(pos.getX() / LodUtil.REGION_WIDTH_IN_CHUNKS, pos.getZ() / LodUtil.REGION_WIDTH_IN_CHUNKS))
|
||||
{
|
||||
switch (generationMode)
|
||||
{
|
||||
case NONE:
|
||||
// don't generate
|
||||
break;
|
||||
case BIOME_ONLY:
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
// fastest
|
||||
worldGenWrapper.generateBiomesOnly(pos, generationMode);
|
||||
break;
|
||||
case SURFACE:
|
||||
// faster
|
||||
worldGenWrapper.generateSurface(pos);
|
||||
break;
|
||||
case FEATURES:
|
||||
// fast
|
||||
worldGenWrapper.generateFeatures(pos);
|
||||
break;
|
||||
case FULL:
|
||||
// very slow
|
||||
worldGenWrapper.generateFull(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
// boolean dataExistence = lodDim.doesDataExist(new LevelPos((byte) 3, pos.x, pos.z));
|
||||
// if (dataExistence)
|
||||
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
|
||||
// else
|
||||
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
|
||||
|
||||
// shows the pool size, active threads, queued tasks and completed tasks
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
|
||||
}// if in range
|
||||
}
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// ClientApi.LOGGER.error(LodWorldGenerator.class.getSimpleName() + ": ran into an error: " + e.getMessage());
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// finally
|
||||
{
|
||||
// decrement how many threads are running
|
||||
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.addAndGet(-1);
|
||||
|
||||
// this position is no longer being generated
|
||||
LodWorldGenerator.INSTANCE.positionsWaitingToBeGenerated.remove(pos);
|
||||
}
|
||||
}// run
|
||||
|
||||
/**
|
||||
* Stops the current genThreads if they are running
|
||||
* and then recreates the Executor service. <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 void restartExecutorService()
|
||||
{
|
||||
if (experimentalWorldGenerator != null) {
|
||||
experimentalWorldGenerator.stop();
|
||||
experimentalWorldGenerator = null;
|
||||
}
|
||||
|
||||
if (genSubThreads != null && !genSubThreads.isShutdown())
|
||||
{
|
||||
genSubThreads.shutdownNow();
|
||||
}
|
||||
genSubThreads = Executors.newFixedThreadPool(CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads(),
|
||||
new ThreadFactoryBuilder().setNameFormat("Gen-Worker-Thread-%d").build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.seibel.lod.core.config;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Where the annotations for the config are defined
|
||||
*
|
||||
* @author coolGi2007
|
||||
* @version 12-28-2021
|
||||
*/
|
||||
public class ConfigAnnotations {
|
||||
/** a textField, button, etc. that can be interacted with */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Entry
|
||||
{
|
||||
String name() default "";
|
||||
|
||||
int width() default 150;
|
||||
|
||||
double minValue() default Double.MIN_NORMAL;
|
||||
|
||||
double maxValue() default Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface ScreenEntry
|
||||
{
|
||||
String name() default "";
|
||||
|
||||
int width() default 100;
|
||||
}
|
||||
|
||||
|
||||
/** Used when sorting the configs in the menu */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Category
|
||||
{
|
||||
String value();
|
||||
}
|
||||
|
||||
|
||||
/** Makes text (looks like @Entry but dosnt save and has no button */
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Comment
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,35 +11,4 @@ public class ColorFormat
|
||||
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 static int createColorData(int alpha, int red, int green, int blue)
|
||||
{
|
||||
int colorData = 0;
|
||||
colorData += (alpha & ALPHA_MASK) << ALPHA_SHIFT;
|
||||
colorData += (red & RED_MASK) << RED_SHIFT;
|
||||
colorData += (green & GREEN_MASK) << GREEN_SHIFT;
|
||||
colorData += (blue & BLUE_MASK) << BLUE_SHIFT;
|
||||
|
||||
return colorData;
|
||||
}
|
||||
|
||||
public static short getAlpha(long dataPoint)
|
||||
{
|
||||
return (short) ((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.seibel.lod.core.dataFormat;
|
||||
|
||||
public class DataMergeUtil
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,27 +12,27 @@ public class LightFormat
|
||||
public final static byte SKY_LIGHT_MASK = 0b1111;
|
||||
|
||||
|
||||
public static byte formatLightAsByte(byte skyLight, byte blockLight)
|
||||
public byte formatLightAsByte(byte skyLight, byte blockLight)
|
||||
{
|
||||
return (byte) (((skyLight & SKY_LIGHT_MASK) << (BYTE_SKY_LIGHT_SHIFT + 4)) | ((blockLight & BLOCK_LIGHT_MASK) << (BYTE_BLOCK_LIGHT_SHIFT + 4)));
|
||||
return (byte) (((skyLight & SKY_LIGHT_MASK) << BYTE_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << BYTE_BLOCK_LIGHT_SHIFT));
|
||||
}
|
||||
|
||||
public static int formatLightAsInt(byte skyLight, byte blockLight)
|
||||
public int formatLightAsInt(byte skyLight, byte blockLight)
|
||||
{
|
||||
return ((skyLight & SKY_LIGHT_MASK) << INT_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << INT_BLOCK_LIGHT_SHIFT);
|
||||
}
|
||||
|
||||
public static int convertByteToIntFormat(byte lights)
|
||||
public int convertByteToIntFormat(byte lights)
|
||||
{
|
||||
return formatLightAsInt((byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK), (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK));
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static byte getSkyLight(byte lights)
|
||||
public byte getSkyLight(byte lights)
|
||||
{
|
||||
return (byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
|
||||
}
|
||||
|
||||
public static byte getBlockLight(byte lights)
|
||||
public byte getBlockLight(byte lights)
|
||||
{
|
||||
return (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
|
||||
}
|
||||
|
||||
@@ -40,46 +40,39 @@ public class PositionDataFormat
|
||||
return positionData;
|
||||
}
|
||||
|
||||
public static String toString(short verticalData, short positionData)
|
||||
{
|
||||
return getLodCount(verticalData) + " " +
|
||||
getLightFlag(verticalData) + " " +
|
||||
getGenerationMode(verticalData) + " " +
|
||||
isVoid(verticalData) + " " +
|
||||
doesItExist(verticalData) + " " +'\n';
|
||||
}
|
||||
|
||||
public static byte getLodCount(short dataPoint)
|
||||
{
|
||||
return (byte) ((dataPoint >>> LOD_COUNT_SHIFT) & LOD_COUNT_MASK);
|
||||
}
|
||||
public static boolean getFlag(short dataPoint)
|
||||
|
||||
public static boolean getLightFlag(short dataPoint)
|
||||
{
|
||||
return ((dataPoint >>> CORRECT_LIGHT_SHIFT) & CORRECT_LIGHT_MASK) == 1;
|
||||
}
|
||||
|
||||
public static byte getGenerationMode(short dataPoint)
|
||||
{
|
||||
return (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK);
|
||||
}
|
||||
|
||||
|
||||
public static boolean isVoid(short dataPoint)
|
||||
{
|
||||
return (((dataPoint >>> VOID_SHIFT) & VOID_MASK) == 1);
|
||||
}
|
||||
|
||||
public static boolean doesItExist(short dataPoint)
|
||||
{
|
||||
return (((dataPoint >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
|
||||
}
|
||||
|
||||
public static short setLodCount(short dataPoint, short lodCount)
|
||||
{
|
||||
return (short) (dataPoint | ((lodCount & LOD_COUNT_MASK) << LOD_COUNT_SHIFT));
|
||||
}
|
||||
public static short setFlag(short dataPoint)
|
||||
{
|
||||
return (short) (dataPoint | ((CORRECT_LIGHT_MASK) << CORRECT_LIGHT_SHIFT));
|
||||
}
|
||||
public static short setGenerationMode(short dataPoint, byte generationMode)
|
||||
{
|
||||
return (short) (dataPoint | ((generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT));
|
||||
}
|
||||
public static short setVoid(short dataPoint)
|
||||
{
|
||||
return (short) (dataPoint | (VOID_MASK << VOID_SHIFT));
|
||||
}
|
||||
public static short setExistence(short dataPoint)
|
||||
{
|
||||
return (short) (dataPoint | (EXISTENCE_MASK << EXISTENCE_SHIFT));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,58 +4,58 @@ public class VerticalDataFormat
|
||||
{
|
||||
public final static short MIN_WORLD_HEIGHT = -2048;
|
||||
public final static short MAX_WORLD_HEIGHT = 2047;
|
||||
public final static short WORLD_HEIGHT = MAX_WORLD_HEIGHT - MIN_WORLD_HEIGHT;
|
||||
|
||||
public final static byte HEIGHT_SHIFT = 20;
|
||||
public final static byte DEPTH_SHIFT = 8;
|
||||
public final static byte LEVEL_SHIFT = 3;
|
||||
public final static byte BOTTOM_TYPE_SHIFT = 2;
|
||||
public final static byte TRANSPARENCY_SHIFT = 1;
|
||||
public final static byte EXISTENCE_SHIFT = 0;
|
||||
public final static byte EMPTY_LOD_SHIFT = 0;
|
||||
|
||||
|
||||
public final static int FULL_MASK = ~0;
|
||||
|
||||
public final static int HEIGHT_MASK = 0b1111_1111_1111;
|
||||
public final static int DEPTH_MASK = 0b1111_1111_1111;
|
||||
public final static int LEVEL_MASK = 0b111;
|
||||
public final static int TRANSPARENCY_MASK = 0b1;
|
||||
public final static int BOTTOM_TYPE_MASK = 0b1;
|
||||
public final static int EXISTENCE_MASK = 0b1;
|
||||
|
||||
|
||||
public final static int HEIGHT_RESET = ~(HEIGHT_MASK << HEIGHT_SHIFT);
|
||||
public final static int DEPTH_RESET = ~(DEPTH_MASK << DEPTH_SHIFT);
|
||||
public final static int LEVEL_RESET = ~(LEVEL_MASK << LEVEL_SHIFT);
|
||||
public final static int TRANSPARENCY_RESET = ~(TRANSPARENCY_MASK << BOTTOM_TYPE_SHIFT);
|
||||
public final static int BOTTOM_TYPE_RESET = ~(BOTTOM_TYPE_MASK << TRANSPARENCY_SHIFT);
|
||||
public final static int EXISTENCE_RESET = ~(EXISTENCE_MASK << EXISTENCE_SHIFT);
|
||||
public final static int EMPTY_LOD_MASK = 0b1;
|
||||
|
||||
public final static int EMPTY_LOD = 0;
|
||||
|
||||
|
||||
public static int createVerticalData(int height, int depth, int level, boolean transparent, boolean bottom)
|
||||
{
|
||||
int verticalData = 0;
|
||||
verticalData |= (height & HEIGHT_MASK) << HEIGHT_SHIFT;
|
||||
verticalData |= (depth & DEPTH_MASK) << DEPTH_SHIFT;
|
||||
verticalData |= ((height - MIN_WORLD_HEIGHT) & HEIGHT_MASK) << HEIGHT_SHIFT;
|
||||
verticalData |= ((depth - MIN_WORLD_HEIGHT) & DEPTH_MASK) << DEPTH_SHIFT;
|
||||
verticalData |= (level & LEVEL_MASK) << LEVEL_SHIFT;
|
||||
if (bottom)
|
||||
verticalData |= BOTTOM_TYPE_MASK << BOTTOM_TYPE_SHIFT;
|
||||
if (transparent)
|
||||
verticalData |= TRANSPARENCY_MASK << TRANSPARENCY_SHIFT;
|
||||
verticalData |= EXISTENCE_MASK << EXISTENCE_SHIFT;
|
||||
verticalData |= EMPTY_LOD_MASK << EMPTY_LOD_SHIFT;
|
||||
|
||||
return verticalData;
|
||||
}
|
||||
|
||||
public static String toString(int verticalData, short positionData)
|
||||
{
|
||||
return getHeight(verticalData) + " " +
|
||||
getDepth(verticalData) + " " +
|
||||
getLevel(verticalData) + " " +
|
||||
isTransparent(verticalData) + " " +
|
||||
isBottom(verticalData) + " " +
|
||||
doesItExist(verticalData) + " " + '\n';
|
||||
}
|
||||
|
||||
public static short getHeight(int verticalData)
|
||||
{
|
||||
return (short) ((verticalData >>> HEIGHT_SHIFT) & HEIGHT_MASK);
|
||||
return (short) (((verticalData >>> HEIGHT_SHIFT) & HEIGHT_MASK) + MIN_WORLD_HEIGHT);
|
||||
}
|
||||
|
||||
public static short getDepth(int verticalData)
|
||||
{
|
||||
return (short) ((verticalData >>> DEPTH_SHIFT) & DEPTH_MASK);
|
||||
return (short) (((verticalData >>> DEPTH_SHIFT) & DEPTH_MASK) + MIN_WORLD_HEIGHT);
|
||||
}
|
||||
|
||||
public static byte getLevel(int verticalData)
|
||||
@@ -75,37 +75,7 @@ public class VerticalDataFormat
|
||||
|
||||
public static boolean doesItExist(int verticalData)
|
||||
{
|
||||
return (((verticalData >>> EXISTENCE_SHIFT) & EXISTENCE_MASK) == 1);
|
||||
return (((verticalData >>> EMPTY_LOD_SHIFT) & EMPTY_LOD_MASK) == 1);
|
||||
}
|
||||
|
||||
|
||||
public static int setHeight(int verticalData, int height)
|
||||
{
|
||||
return verticalData | ((height & HEIGHT_MASK) << HEIGHT_SHIFT);
|
||||
}
|
||||
|
||||
public static int setDepth(int verticalData, int depth)
|
||||
{
|
||||
return verticalData | ((depth & DEPTH_MASK) << DEPTH_SHIFT);
|
||||
}
|
||||
|
||||
public static int setLevel(int verticalData, int level)
|
||||
{
|
||||
return verticalData | ((level & LEVEL_MASK) << LEVEL_SHIFT);
|
||||
}
|
||||
|
||||
public static int setTransparency(int verticalData)
|
||||
{
|
||||
return verticalData | ((TRANSPARENCY_MASK) << TRANSPARENCY_SHIFT);
|
||||
}
|
||||
|
||||
public static int setBottom(int verticalData)
|
||||
{
|
||||
return verticalData | ((BOTTOM_TYPE_MASK) << BOTTOM_TYPE_SHIFT);
|
||||
}
|
||||
|
||||
public static int setExistence(int verticalData)
|
||||
{
|
||||
return verticalData | ((EXISTENCE_MASK) << EXISTENCE_SHIFT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
package com.seibel.lod.core.enums.config;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* NONE <br>
|
||||
* BIOME_ONLY <br>
|
||||
@@ -94,46 +92,4 @@ public enum DistanceGenerationMode
|
||||
{
|
||||
this.complexity = complexity;
|
||||
}
|
||||
|
||||
// Note: return null if out of range
|
||||
@Nullable
|
||||
public static DistanceGenerationMode previous(DistanceGenerationMode mode) {
|
||||
switch (mode) {
|
||||
case FULL:
|
||||
return DistanceGenerationMode.FEATURES;
|
||||
case FEATURES:
|
||||
return DistanceGenerationMode.SURFACE;
|
||||
case SURFACE:
|
||||
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
return DistanceGenerationMode.BIOME_ONLY;
|
||||
case BIOME_ONLY:
|
||||
return DistanceGenerationMode.NONE;
|
||||
case NONE:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: return null if out of range
|
||||
@Nullable
|
||||
public static DistanceGenerationMode next(DistanceGenerationMode mode) {
|
||||
switch (mode) {
|
||||
case FULL:
|
||||
return null;
|
||||
case FEATURES:
|
||||
return DistanceGenerationMode.FULL;
|
||||
case SURFACE:
|
||||
return DistanceGenerationMode.FEATURES;
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
return DistanceGenerationMode.SURFACE;
|
||||
case BIOME_ONLY:
|
||||
return DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
|
||||
case NONE:
|
||||
return DistanceGenerationMode.BIOME_ONLY;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.enums.config;
|
||||
|
||||
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.AbstractLodTemplate;
|
||||
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.CubicLodTemplate;
|
||||
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.DynamicLodTemplate;
|
||||
import com.seibel.lod.core.builders.bufferBuilding.lodTemplates.TriangularLodTemplate;
|
||||
|
||||
/**
|
||||
* Cubic, Triangular, Dynamic
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 10-10-2021
|
||||
*/
|
||||
public enum LodTemplate
|
||||
{
|
||||
/**
|
||||
* LODs are rendered as
|
||||
* rectangular prisms.
|
||||
*/
|
||||
CUBIC(new CubicLodTemplate()),
|
||||
|
||||
/**
|
||||
* LODs smoothly transition between
|
||||
* each other.
|
||||
*/
|
||||
TRIANGULAR(new TriangularLodTemplate()),
|
||||
|
||||
/**
|
||||
* LODs smoothly transition between
|
||||
* each other, unless a neighboring LOD
|
||||
* is at a significantly different height.
|
||||
*/
|
||||
DYNAMIC(new DynamicLodTemplate());
|
||||
|
||||
|
||||
public final AbstractLodTemplate template;
|
||||
|
||||
LodTemplate(AbstractLodTemplate newTemplate)
|
||||
{
|
||||
template = newTemplate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
package com.seibel.lod.core.enums.config;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* heightmap <br>
|
||||
* multi_lod <br>
|
||||
@@ -79,34 +77,4 @@ public enum VerticalQuality
|
||||
{
|
||||
this.maxVerticalData = maxVerticalData;
|
||||
}
|
||||
|
||||
// Note: return null if out of range
|
||||
@Nullable
|
||||
public static VerticalQuality previous(VerticalQuality mode) {
|
||||
switch (mode) {
|
||||
case HIGH:
|
||||
return VerticalQuality.MEDIUM;
|
||||
case MEDIUM:
|
||||
return VerticalQuality.LOW;
|
||||
case LOW:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Note: return null if out of range
|
||||
@Nullable
|
||||
public static VerticalQuality next(VerticalQuality mode) {
|
||||
switch (mode) {
|
||||
case HIGH:
|
||||
return null;
|
||||
case MEDIUM:
|
||||
return VerticalQuality.HIGH;
|
||||
case LOW:
|
||||
return VerticalQuality.MEDIUM;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.seibel.lod.core.handlers;
|
||||
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
|
||||
/**
|
||||
* A singleton used to get variables from methods
|
||||
@@ -34,7 +35,7 @@ import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
* different MC versions.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-14-2021
|
||||
* @version 11-26-2021
|
||||
*/
|
||||
public interface IReflectionHandler
|
||||
{
|
||||
@@ -44,6 +45,14 @@ public interface IReflectionHandler
|
||||
/** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
|
||||
boolean vivecraftPresent();
|
||||
|
||||
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
|
||||
boolean sodiumPresent();
|
||||
/**
|
||||
* Modifies the projection matrix's clip planes.
|
||||
* The projection matrix must be in column-major format.
|
||||
*
|
||||
* @param projectionMatrix The projection matrix to be modified.
|
||||
* @param newNearClipPlane the new near clip plane value.
|
||||
* @param newFarClipPlane the new far clip plane value.
|
||||
* @return The modified matrix.
|
||||
*/
|
||||
Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import java.util.concurrent.Executors;
|
||||
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
@@ -79,7 +78,7 @@ public class LodDimensionFileHandler
|
||||
* 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 = 8;
|
||||
public static final int LOD_SAVE_FILE_VERSION = 7;
|
||||
|
||||
/**
|
||||
* Allow saving asynchronously, but never try to save multiple regions
|
||||
@@ -101,8 +100,6 @@ public class LodDimensionFileHandler
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// read from file //
|
||||
//================//
|
||||
@@ -119,65 +116,130 @@ public class LodDimensionFileHandler
|
||||
|
||||
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
|
||||
{
|
||||
File file = getBestMatchingRegionFile(tempDetailLevel, regionX, regionZ, generationMode, verticalQuality);
|
||||
if (file == null) continue; // Failed to find the file for this detail level. continue and try next one
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
|
||||
|
||||
long fileSize = file.length();
|
||||
if (fileSize == 0) continue; // file is empty. Let's not try parsing empty files
|
||||
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
|
||||
try
|
||||
{
|
||||
int fileVersion;
|
||||
fileVersion = inputStream.read();
|
||||
// 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.");
|
||||
|
||||
// check if this file can be read by this file handler
|
||||
if (fileVersion < 6)
|
||||
File file = new File(fileName);
|
||||
if (!file.exists())
|
||||
{
|
||||
// the file we are reading is an older version,
|
||||
// close the reader and delete the file.
|
||||
inputStream.close();
|
||||
file.delete();
|
||||
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
|
||||
+ " version found: " + fileVersion
|
||||
+ ", version requested: " + LOD_SAVE_FILE_VERSION
|
||||
+ ". File has been deleted.");
|
||||
// This should not break, but be continue to see whether other detail levels can be loaded or updated
|
||||
continue;
|
||||
//there is no file for current gen mode
|
||||
//search others above current from the most to the least detailed
|
||||
VerticalQuality tempVerticalQuality = VerticalQuality.HIGH;
|
||||
do {
|
||||
DistanceGenerationMode tempGenMode = DistanceGenerationMode.FULL;
|
||||
do {
|
||||
fileName = getFileNameAndPathForRegion(regionX, regionZ, tempGenMode, tempDetailLevel, verticalQuality);
|
||||
if (fileName != null)
|
||||
{
|
||||
file = new File(fileName);
|
||||
if (file.exists())
|
||||
break;
|
||||
}
|
||||
//decrease gen mode
|
||||
if (tempGenMode == DistanceGenerationMode.FULL)
|
||||
tempGenMode = DistanceGenerationMode.FEATURES;
|
||||
else if (tempGenMode == DistanceGenerationMode.FEATURES)
|
||||
tempGenMode = DistanceGenerationMode.SURFACE;
|
||||
else if (tempGenMode == DistanceGenerationMode.SURFACE)
|
||||
tempGenMode = DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
|
||||
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT)
|
||||
tempGenMode = DistanceGenerationMode.BIOME_ONLY;
|
||||
else if (tempGenMode == DistanceGenerationMode.BIOME_ONLY)
|
||||
tempGenMode = DistanceGenerationMode.NONE;
|
||||
} while (tempGenMode != generationMode);
|
||||
if (fileName != null)
|
||||
{
|
||||
file = new File(fileName);
|
||||
if (file.exists())
|
||||
break;
|
||||
}
|
||||
if (tempVerticalQuality == VerticalQuality.HIGH)
|
||||
tempVerticalQuality = VerticalQuality.MEDIUM;
|
||||
else if (tempVerticalQuality == VerticalQuality.MEDIUM)
|
||||
tempVerticalQuality = VerticalQuality.LOW;
|
||||
} while (tempVerticalQuality != verticalQuality);
|
||||
if (!file.exists())
|
||||
//there wasn't a file, don't return anything
|
||||
continue;
|
||||
}
|
||||
else if (fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
|
||||
|
||||
|
||||
// don't try parsing empty files
|
||||
long dataSize = file.length();
|
||||
dataSize -= 1;
|
||||
if (dataSize > 0)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// close the reader and ignore the file, we don't
|
||||
// want to accidentally delete anything the user may want.
|
||||
inputStream.close();
|
||||
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
|
||||
+ " version found: " + fileVersion
|
||||
+ ", version requested: " + LOD_SAVE_FILE_VERSION
|
||||
+ " this region will not be written to in order to protect the newer file.");
|
||||
// This should not break, but be continue to see whether other detail levels can be loaded or updated
|
||||
continue;
|
||||
}
|
||||
else if (fileVersion < LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
ClientApi.LOGGER.debug("Old LOD region file for region: (" + regionX + "," + regionZ + ")"
|
||||
+ " version found: " + fileVersion
|
||||
+ ", version requested: " + LOD_SAVE_FILE_VERSION
|
||||
+ ". File will be loaded and updated to new format in next save.");
|
||||
// this is old, but readable version
|
||||
// read and add the data to our region
|
||||
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), fileVersion));
|
||||
inputStream.close();
|
||||
} else
|
||||
{
|
||||
// this file is a readable version,
|
||||
// read and add the data to our region
|
||||
region.addLevelContainer(new VerticalLevelContainer(new DataInputStream(inputStream), LOD_SAVE_FILE_VERSION));
|
||||
inputStream.close();
|
||||
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(file)))
|
||||
{
|
||||
int fileVersion;
|
||||
fileVersion = inputStream.read();
|
||||
|
||||
// check if this file can be read by this file handler
|
||||
if (fileVersion < 6)
|
||||
{
|
||||
// the file we are reading is an older version,
|
||||
// close the reader and delete the file.
|
||||
inputStream.close();
|
||||
file.delete();
|
||||
ClientApi.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ")"
|
||||
+ " version found: " + fileVersion
|
||||
+ ", version requested: " + LOD_SAVE_FILE_VERSION
|
||||
+ ". File was been deleted.");
|
||||
|
||||
break;
|
||||
}
|
||||
else if (fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// close the reader and ignore the file, we don't
|
||||
// want to accidentally delete anything the user may want.
|
||||
inputStream.close();
|
||||
ClientApi.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ")"
|
||||
+ " version found: " + fileVersion
|
||||
+ ", version requested: " + LOD_SAVE_FILE_VERSION
|
||||
+ " this region will not be written to in order to protect the newer file.");
|
||||
|
||||
break;
|
||||
}
|
||||
else if (fileVersion == 6)
|
||||
{
|
||||
//this is old, but readable version
|
||||
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
|
||||
inputStream.read(data);
|
||||
inputStream.close();
|
||||
// add the data to our region
|
||||
region.addLevelContainer(new VerticalLevelContainer(data, 6));
|
||||
} else
|
||||
{
|
||||
// this file is a readable version,
|
||||
// read the file
|
||||
byte[] data = ThreadMapUtil.getSaveContainer(tempDetailLevel);
|
||||
inputStream.read(data);
|
||||
inputStream.close();
|
||||
// add the data to our region
|
||||
region.addLevelContainer(new VerticalLevelContainer(data, LOD_SAVE_FILE_VERSION));
|
||||
}
|
||||
}
|
||||
catch (IOException ioEx)
|
||||
{
|
||||
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
|
||||
ioEx.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ioEx)
|
||||
catch (Exception e)
|
||||
{
|
||||
ClientApi.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
|
||||
ioEx.printStackTrace();
|
||||
// the buffered reader encountered a
|
||||
// problem reading the file
|
||||
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + e.getMessage() + "]: ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}// for each detail level
|
||||
|
||||
@@ -232,111 +294,163 @@ public class LodDimensionFileHandler
|
||||
{
|
||||
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
|
||||
{
|
||||
// Get the old file
|
||||
File oldFile = getRegionFile(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
|
||||
ClientApi.LOGGER.debug("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
|
||||
String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
|
||||
|
||||
boolean isFileFullyGened = false;
|
||||
// make sure the file and folder exists
|
||||
if (!oldFile.exists())
|
||||
// if the fileName was null that means the folder is inaccessible
|
||||
// for some reason
|
||||
if (fileName == null)
|
||||
{
|
||||
// the file doesn't exist,
|
||||
// create it and the folder if need be
|
||||
if (!oldFile.getParentFile().exists())
|
||||
oldFile.getParentFile().mkdirs();
|
||||
try {
|
||||
ClientApi.LOGGER.warn("Unable to save region [" + region.regionPosX + ", " + region.regionPosZ + "] to file, file is inaccessible.");
|
||||
return;
|
||||
}
|
||||
File oldFile = new File(fileName);
|
||||
//ClientProxy.LOGGER.info("saving region [" + region.regionPosX + ", " + region.regionPosZ + "] to file.");
|
||||
byte[] temp = region.getLevel(detailLevel).toDataString();
|
||||
|
||||
try
|
||||
{
|
||||
// make sure the file and folder exists
|
||||
if (!oldFile.exists())
|
||||
{
|
||||
// the file doesn't exist,
|
||||
// create it and the folder if need be
|
||||
if (!oldFile.getParentFile().exists())
|
||||
oldFile.getParentFile().mkdirs();
|
||||
oldFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
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)))
|
||||
else
|
||||
{
|
||||
fileVersion = inputStream.read();
|
||||
inputStream.skip(1);
|
||||
isFileFullyGened = (inputStream.read() & 0b10000000) != 0;
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
ClientApi.LOGGER.warn("LOD file write warning. Unable to read existing file [" + oldFile + "] version. Treating it as latest version. [" + e.getMessage() + "]: ");
|
||||
e.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.
|
||||
continue;
|
||||
}
|
||||
// if we got this far then we are good
|
||||
// to overwrite the old file
|
||||
}
|
||||
|
||||
// Now create a new temporary save file
|
||||
File tempFile;
|
||||
try {
|
||||
tempFile = File.createTempFile(oldFile.getName(), TMP_FILE_EXTENSION);
|
||||
} catch (IOException e) {
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to create temp file for [" + oldFile + "] error [" + e.getMessage() + "]: ");
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
tempFile.deleteOnExit(); // Mark it to be deleted on exit if any unexcepted terminations happen
|
||||
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(tempFile), 3))
|
||||
{
|
||||
// add the version of this file
|
||||
outputStream.write(LOD_SAVE_FILE_VERSION);
|
||||
// add each LodChunk to the file
|
||||
boolean isNewDataFullyGened = region.getLevel(detailLevel).writeData(new DataOutputStream(outputStream));
|
||||
outputStream.close();
|
||||
|
||||
if (!isNewDataFullyGened && isFileFullyGened)
|
||||
{
|
||||
// existing file is complete while new one is only partially generate
|
||||
// this can happen is for some reason loading failed
|
||||
// this doesn't fix the bug, but at least protects old data
|
||||
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + oldFile + "]");
|
||||
try {
|
||||
tempFile.delete();
|
||||
} catch (SecurityException e) {
|
||||
// Failed to delete temp file... just continue.
|
||||
// the file exists, make sure it
|
||||
// is the correct version.
|
||||
// (to make sure we don't overwrite a newer
|
||||
// version file if it exists)
|
||||
int fileVersion = LOD_SAVE_FILE_VERSION;
|
||||
int isFull = 0;
|
||||
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
|
||||
{
|
||||
fileVersion = inputStream.read();
|
||||
inputStream.skip(1);
|
||||
isFull = inputStream.read() & 0b10000000;
|
||||
inputStream.close();
|
||||
}
|
||||
continue;
|
||||
catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
// check if this file can be written to by the file handler
|
||||
if (fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// don't write anything, we don't want to accidentally
|
||||
// delete anything the user may want.
|
||||
return;
|
||||
}
|
||||
if ((temp[1] & 0b10000000) != 0b10000000 && isFull == 0b10000000)
|
||||
{
|
||||
// existing file is complete while new one is only partially generate
|
||||
// this can happen is for some reason loading failed
|
||||
// this doesn't fix the bug, but at least protects old data
|
||||
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + fileName + "]");
|
||||
return;
|
||||
}
|
||||
// if we got this far then we are good
|
||||
// to overwrite the old file
|
||||
}
|
||||
// the old file is good, now create a new temporary save file
|
||||
File newFile = new File(fileName + TMP_FILE_EXTENSION);
|
||||
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
|
||||
{
|
||||
// add the version of this file
|
||||
outputStream.write(LOD_SAVE_FILE_VERSION);
|
||||
|
||||
// add each LodChunk to the file
|
||||
outputStream.write(temp);
|
||||
outputStream.close();
|
||||
|
||||
// overwrite the old file with the new one
|
||||
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
catch (Exception e)
|
||||
{
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to write to temp file [" + tempFile + "] error [" + e.getMessage() + "]: ");
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
|
||||
// overwrite the old file with the new one
|
||||
try {
|
||||
Files.move(tempFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to update file [" + oldFile + "] error [" + e.getMessage() + "]: ");
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void saveRegionFile (byte[] regionFile, RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
|
||||
{
|
||||
int regionX = regionPos.x;
|
||||
int regionZ = regionPos.z;
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
|
||||
|
||||
if (fileName != null)
|
||||
{
|
||||
File oldFile = new File(fileName);
|
||||
File newFile = new File(fileName + TMP_FILE_EXTENSION);
|
||||
try (OutputStream os = new FileOutputStream(newFile))
|
||||
{
|
||||
os.write(regionFile);
|
||||
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
os.close();
|
||||
}
|
||||
catch (IOException ioEx)
|
||||
{
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
|
||||
ioEx.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getRegionFile (RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
|
||||
{
|
||||
int regionX = regionPos.x;
|
||||
int regionZ = regionPos.z;
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
|
||||
if (fileName != null)
|
||||
{
|
||||
File file = new File(fileName);
|
||||
try (InputStream is = new FileInputStream(file))
|
||||
{
|
||||
byte[] data = ThreadMapUtil.getSaveContainer(detailLevel);
|
||||
is.read(data);
|
||||
is.close();
|
||||
return Arrays.copyOf(data, (int) file.length());
|
||||
}
|
||||
catch (IOException ioEx)
|
||||
{
|
||||
ClientApi.LOGGER.error("LOD file read error. Unable to read to [" + fileName + "] error [" + ioEx.getMessage() + "]: ");
|
||||
ioEx.printStackTrace();
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
public int getHashFromFile(RegionPos regionPos, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
|
||||
{
|
||||
int regionX = regionPos.x;
|
||||
int regionZ = regionPos.z;
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, detailLevel, verticalQuality);
|
||||
if (fileName == null)
|
||||
return 0;
|
||||
|
||||
File file = new File(fileName);
|
||||
return file.hashCode();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the name of the file that should contain the
|
||||
* region at the given x and z. <br>
|
||||
@@ -346,40 +460,26 @@ public class LodDimensionFileHandler
|
||||
* <p>
|
||||
* Returns null if there is an IO or security Exception.
|
||||
*/
|
||||
|
||||
private String getFileBasePath() {
|
||||
try {
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar;
|
||||
} catch (IOException e) {
|
||||
ClientApi.LOGGER.warn("Unable to get the base save file path. One possible cause is that"
|
||||
+ " the process failed to read the current path location due to security configs.");
|
||||
throw new RuntimeException("DistantHorizons Get Save File Path Failure");
|
||||
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
|
||||
{
|
||||
try
|
||||
{
|
||||
// saveFolder is something like
|
||||
// ".\Super Flat\DIM-1\data\"
|
||||
// or
|
||||
// ".\Super Flat\data\"
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
|
||||
verticalQuality + File.separatorChar +
|
||||
generationMode.toString() + File.separatorChar +
|
||||
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
|
||||
}
|
||||
catch (IOException | SecurityException e)
|
||||
{
|
||||
ClientApi.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private File getRegionFile(int regionX, int regionZ, DistanceGenerationMode genMode, byte detail, VerticalQuality vertQuality) {
|
||||
return new File(getFileBasePath() + vertQuality + File.separatorChar +
|
||||
genMode + File.separatorChar +
|
||||
DETAIL_FOLDER_NAME_PREFIX + detail + File.separatorChar +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION);
|
||||
}
|
||||
|
||||
// Return null if no file found
|
||||
@Nullable
|
||||
private File getBestMatchingRegionFile(byte detailLevel, int regionX, int regionZ, DistanceGenerationMode targetGenMode, VerticalQuality targetVertQuality) {
|
||||
DistanceGenerationMode genMode = targetGenMode;
|
||||
// Search from least GenMode to max GenMode, than least vertQuality to max vertQuality
|
||||
do {
|
||||
File file = getRegionFile(regionX, regionZ, genMode, detailLevel, targetVertQuality);
|
||||
if (file.exists()) return file; // Found target file.
|
||||
targetGenMode = DistanceGenerationMode.next(targetGenMode);
|
||||
if (targetGenMode == null) { // Failed to find any files for this vertQuality. Try next one up.
|
||||
targetGenMode = genMode;
|
||||
targetVertQuality = VerticalQuality.next(targetVertQuality);
|
||||
}
|
||||
} while (targetVertQuality != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -26,28 +26,26 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.seibel.lod.core.ModInfo;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
|
||||
/**
|
||||
* A singleton used to get variables from methods
|
||||
* where they are private or potentially absent.
|
||||
* For example: the fog setting in Optifine or the
|
||||
* presence/absence of Vivecraft.
|
||||
* Specifically the fog setting in Optifine or the
|
||||
* presence/absence of other mods.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-14-2021
|
||||
* @version 11-26-2021
|
||||
*/
|
||||
public class ReflectionHandler implements IReflectionHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName());
|
||||
|
||||
public static ReflectionHandler instance;
|
||||
private static ReflectionHandler instance;
|
||||
|
||||
private Field ofFogField = null;
|
||||
private final Object mcOptionsObject;
|
||||
|
||||
private Boolean sodiumPresent = null;
|
||||
|
||||
|
||||
|
||||
|
||||
private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject)
|
||||
@@ -77,8 +75,6 @@ public class ReflectionHandler implements IReflectionHandler
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** finds the Optifine fog type field */
|
||||
private void setupFogField(Field[] optionFields)
|
||||
{
|
||||
@@ -159,29 +155,41 @@ public class ReflectionHandler implements IReflectionHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Modifies the projection matrix's clip planes.
|
||||
* The projection matrix must be in column-major format.
|
||||
*
|
||||
* @param projectionMatrix The projection matrix to be modified.
|
||||
* @param newNearClipPlane the new near clip plane value.
|
||||
* @param newFarClipPlane the new far clip plane value.
|
||||
* @return The modified matrix.
|
||||
*/
|
||||
@Override
|
||||
public boolean sodiumPresent()
|
||||
public Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane)
|
||||
{
|
||||
// we don't want to run a potentially expensive
|
||||
// reflection search operation every time this method is called
|
||||
if (sodiumPresent == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Class.forName("me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer");
|
||||
|
||||
sodiumPresent = true;
|
||||
}
|
||||
catch (ClassNotFoundException e)
|
||||
{
|
||||
sodiumPresent = false;
|
||||
}
|
||||
}
|
||||
// find the matrix values.
|
||||
float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
|
||||
float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
|
||||
|
||||
return sodiumPresent;
|
||||
try
|
||||
{
|
||||
// TODO this was originally created before we had the Mat4f object,
|
||||
// so this doesn't need to be done with reflection anymore.
|
||||
// And should be moved to RenderUtil
|
||||
|
||||
// get the fields of the projectionMatrix
|
||||
Field[] fields = projectionMatrix.getClass().getDeclaredFields();
|
||||
// bypass the security protections on the fields that encode near and far plane values.
|
||||
fields[10].setAccessible(true);
|
||||
fields[11].setAccessible(true);
|
||||
// Change the values of the near and far plane.
|
||||
fields[10].set(projectionMatrix, nearMatrixValue);
|
||||
fields[11].set(projectionMatrix, farMatrixValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
return projectionMatrix;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -200,9 +200,6 @@ public class VertexOptimizer
|
||||
public final Map<LodDirection, byte[]> skyLights;
|
||||
public byte blockLight;
|
||||
|
||||
boolean skipTop;
|
||||
boolean skipBot;
|
||||
|
||||
|
||||
|
||||
/** creates an empty box */
|
||||
@@ -320,13 +317,15 @@ public class VertexOptimizer
|
||||
int maxY = getMaxY();
|
||||
long singleAdjDataPoint;
|
||||
|
||||
// TODO transparency uncomment final condition to see ocean floor
|
||||
/* TODO implement attached vertical face culling
|
||||
//Up direction case
|
||||
singleAdjDataPoint = adjData.get(LodDirection.UP)[0];
|
||||
skipTop = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getDepth(singleAdjDataPoint) == maxY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
|
||||
if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
|
||||
{
|
||||
height = DataPointUtil.getHeight(singleAdjDataPoint);
|
||||
depth = DataPointUtil.getDepth(singleAdjDataPoint);
|
||||
}*/
|
||||
//Down direction case
|
||||
singleAdjDataPoint = adjData.get(LodDirection.DOWN)[0];
|
||||
skipBot = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getHeight(singleAdjDataPoint) == minY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
|
||||
if(DataPointUtil.doesItExist(singleAdjDataPoint))
|
||||
skyLights.get(LodDirection.DOWN)[0] = DataPointUtil.getLightSkyAlt(singleAdjDataPoint);
|
||||
else
|
||||
@@ -352,22 +351,17 @@ public class VertexOptimizer
|
||||
boolean toFinish = false;
|
||||
int toFinishIndex = 0;
|
||||
boolean allAbove = true;
|
||||
// TODO transparency ocean floor fix
|
||||
//boolean isOpaque = ((colorMap[0] >> 24) & 0xFF) == 255;
|
||||
for (i = 0; i < dataPoint.length; i++)
|
||||
{
|
||||
singleAdjDataPoint = dataPoint[i];
|
||||
|
||||
if (DataPointUtil.isVoid(singleAdjDataPoint) || !DataPointUtil.doesItExist(singleAdjDataPoint))
|
||||
break;
|
||||
|
||||
// TODO transparency ocean floor fix
|
||||
//if (isOpaque && DataPointUtil.getAlpha(singleAdjDataPoint) != 255)
|
||||
// continue;
|
||||
|
||||
height = DataPointUtil.getHeight(singleAdjDataPoint);
|
||||
depth = DataPointUtil.getDepth(singleAdjDataPoint);
|
||||
|
||||
if (depth < maxY)
|
||||
if (depth <= maxY)
|
||||
{
|
||||
allAbove = false;
|
||||
if (height < minY)
|
||||
@@ -421,7 +415,7 @@ public class VertexOptimizer
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (height >= maxY)// && depth > minY
|
||||
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
|
||||
@@ -494,14 +488,13 @@ public class VertexOptimizer
|
||||
boxOffset[Z] = zOffset;
|
||||
}
|
||||
|
||||
/** returns true if the given direction should be rendered. */
|
||||
/**
|
||||
* returns true if the given direction should be rendered.
|
||||
*/
|
||||
public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
|
||||
{
|
||||
if (lodDirection == LodDirection.UP)
|
||||
return adjIndex == 0 && !skipTop;
|
||||
if (lodDirection == LodDirection.DOWN)
|
||||
return adjIndex == 0 && !skipBot;
|
||||
|
||||
if (lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
|
||||
return adjIndex == 0;
|
||||
return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
|
||||
package com.seibel.lod.core.objects.lod;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* A level container is a quad tree level
|
||||
*/
|
||||
@@ -85,7 +81,7 @@ public interface LevelContainer
|
||||
byte getDetailLevel();
|
||||
|
||||
|
||||
int getVerticalSize();
|
||||
int getMaxVerticalData();
|
||||
|
||||
/** Clears the dataPoint at the given array index */
|
||||
void clear(int posX, int posZ);
|
||||
@@ -103,17 +99,19 @@ public interface LevelContainer
|
||||
* @param posZ z position in the detail level to update
|
||||
*/
|
||||
void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
|
||||
|
||||
|
||||
/**
|
||||
* This will write the raw data with metadata to the output stream
|
||||
* @return isAllGenerated whether the data is all generated
|
||||
* @throws IOException
|
||||
* This will give the data to save in the file
|
||||
* @return data as a String
|
||||
*/
|
||||
boolean writeData(DataOutputStream output) throws IOException;
|
||||
byte[] toDataString();
|
||||
|
||||
|
||||
/**
|
||||
* This will give the data to save in the file
|
||||
* @return data as a String
|
||||
*/
|
||||
int getMaxNumberOfLods();
|
||||
|
||||
void mergeMultiData(int posX, int posZ, long[] dataToMergeVertical, int inputVerticalData);
|
||||
}
|
||||
|
||||
@@ -310,32 +310,6 @@ public class LodDimension
|
||||
|
||||
regions[xIndex][zIndex] = newRegion;
|
||||
}
|
||||
public interface PosComsumer {
|
||||
void run(int x, int z);
|
||||
}
|
||||
|
||||
public void iterateWithSpiral(PosComsumer r) {
|
||||
int ox,oy,dx,dy;
|
||||
ox = oy = dx = 0;
|
||||
dy = -1;
|
||||
int len = regions.length;
|
||||
int maxI = len*len;
|
||||
int halfLen = len/2;
|
||||
for(int i =0; i < maxI; i++){
|
||||
if ((-halfLen <= ox) && (ox <= halfLen) && (-halfLen <= oy) && (oy <= halfLen)){
|
||||
int x = ox+halfLen;
|
||||
int z = oy+halfLen;
|
||||
r.run(x, z);
|
||||
}
|
||||
if( (ox == oy) || ((ox < 0) && (ox == -oy)) || ((ox > 0) && (ox == 1-oy))){
|
||||
int temp = dx;
|
||||
dx = -dy;
|
||||
dy = temp;
|
||||
}
|
||||
ox += dx;
|
||||
oy += dy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -351,99 +325,116 @@ public class LodDimension
|
||||
|
||||
// don't run the tree cutter multiple times
|
||||
// for the same location
|
||||
if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ()) {
|
||||
if (newPlayerChunk.getX() != lastCutChunk.getX() || newPlayerChunk.getZ() != lastCutChunk.getZ())
|
||||
{
|
||||
lastCutChunk = newPlayerChunk;
|
||||
|
||||
Runnable thread = () -> {
|
||||
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
int regionX;
|
||||
int regionZ;
|
||||
int minDistance;
|
||||
byte detail;
|
||||
byte minAllowedDetailLevel;
|
||||
|
||||
// go over every region in the dimension
|
||||
iterateWithSpiral((int x, int z) -> {
|
||||
int regionX;
|
||||
int regionZ;
|
||||
int minDistance;
|
||||
byte detail;
|
||||
byte minAllowedDetailLevel;
|
||||
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;
|
||||
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) {
|
||||
public void expandOrLoadRegionsAsync(int playerPosX, int playerPosZ)
|
||||
{
|
||||
DistanceGenerationMode generationMode = CONFIG.client().worldGenerator().getDistanceGenerationMode();
|
||||
AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX),
|
||||
LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
|
||||
AbstractChunkPosWrapper newPlayerChunk = FACTORY.createChunkPos(LevelPosUtil.getChunkPos((byte) 0, playerPosX), LevelPosUtil.getChunkPos((byte) 0, playerPosZ));
|
||||
VerticalQuality verticalQuality = CONFIG.client().graphics().quality().getVerticalQuality();
|
||||
|
||||
|
||||
|
||||
if (lastExpandedChunk == null)
|
||||
lastExpandedChunk = FACTORY.createChunkPos(newPlayerChunk.getX() + 1, newPlayerChunk.getZ() - 1);
|
||||
|
||||
|
||||
// don't run the expander multiple times
|
||||
// for the same location
|
||||
if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ()) {
|
||||
if (newPlayerChunk.getX() != lastExpandedChunk.getX() || newPlayerChunk.getZ() != lastExpandedChunk.getZ())
|
||||
{
|
||||
lastExpandedChunk = newPlayerChunk;
|
||||
|
||||
Runnable thread = () -> {
|
||||
|
||||
iterateWithSpiral((int x, int z) -> {
|
||||
int regionX;
|
||||
int regionZ;
|
||||
LodRegion region;
|
||||
int minDistance;
|
||||
byte detail;
|
||||
byte levelToGen;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -690,10 +681,7 @@ public class LodDimension
|
||||
LodRegion region = getRegion(regionPos.x, regionPos.z);
|
||||
|
||||
// use FAR_FIRST on local worlds and NEAR_FIRST on servers
|
||||
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority();
|
||||
if (generationPriority == GenerationPriority.AUTO)
|
||||
generationPriority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
|
||||
|
||||
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority() == GenerationPriority.AUTO && MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
|
||||
boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST;
|
||||
|
||||
if (region != null)
|
||||
@@ -921,4 +909,39 @@ public class LodDimension
|
||||
{
|
||||
isRegionDirty[i][j] = val;
|
||||
}
|
||||
|
||||
public void mergeMultiData(byte detailLevel, int posX, int posZ, boolean dontSave, long[] dataToMergeVertical, int inputVerticalData)
|
||||
{
|
||||
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;
|
||||
|
||||
region.mergeMultiData(detailLevel, posX, posZ, dataToMergeVertical, inputVerticalData);
|
||||
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ public class LodRegion
|
||||
{
|
||||
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z))
|
||||
{
|
||||
if (!requireCorrectDetailLevel)
|
||||
if (requireCorrectDetailLevel)
|
||||
childrenCount++;
|
||||
else
|
||||
getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel);
|
||||
@@ -394,7 +394,7 @@ public class LodRegion
|
||||
}
|
||||
|
||||
|
||||
if (!requireCorrectDetailLevel)
|
||||
if (requireCorrectDetailLevel)
|
||||
{
|
||||
// If all the four children exist go deeper
|
||||
if (childrenCount == 4)
|
||||
@@ -601,7 +601,7 @@ public class LodRegion
|
||||
|
||||
public int getMaxVerticalData(byte detailLevel)
|
||||
{
|
||||
return dataContainer[detailLevel].getVerticalSize();
|
||||
return dataContainer[detailLevel].getMaxVerticalData();
|
||||
}
|
||||
|
||||
|
||||
@@ -610,4 +610,16 @@ public class LodRegion
|
||||
{
|
||||
return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString();
|
||||
}
|
||||
|
||||
public void mergeMultiData(byte detailLevel, int posX, int posZ, long[] dataToMergeVertical, int inputVerticalData)
|
||||
{
|
||||
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].mergeMultiData(posX, posZ, dataToMergeVertical, inputVerticalData);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +0,0 @@
|
||||
package com.seibel.lod.core.objects.lod.quadtree;
|
||||
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
|
||||
public class LodQuadTree
|
||||
{
|
||||
public LodSection[][][] quadTreeStructure;
|
||||
public QuadTreeProperties properties;
|
||||
|
||||
public LodQuadTree()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package com.seibel.lod.core.objects.lod.quadtree;
|
||||
|
||||
|
||||
/**
|
||||
A lod section rappresent a distinct section in a precise level of the quadtree.
|
||||
The section holds all the lods information as color (computed with the blockBiome object referenced by the ID), size, light...
|
||||
The save and load of a section is handled by the section itself together with a handler class.
|
||||
*/
|
||||
public class LodSection
|
||||
{
|
||||
//level of detail of this section
|
||||
public final int detail;
|
||||
|
||||
//level position of this section
|
||||
public final int sectionPosX;
|
||||
public final int sectionPosZ;
|
||||
|
||||
//horizontal size of this section
|
||||
public final int horizontalSize;
|
||||
//vertical size of this section
|
||||
public final int verticalSize;
|
||||
|
||||
//how many id we save for each lod
|
||||
public final int idPerLod;
|
||||
|
||||
//What generation mode should be used for chunk in this section
|
||||
public final byte generationMode;
|
||||
|
||||
//if present in region file, use pregenerated chunk in lod creation
|
||||
public boolean usePregeneratedChunk;
|
||||
|
||||
//Position data hold information about each level position like generation mode used, number of vertical lods in that area...
|
||||
private byte[] positionData;
|
||||
|
||||
//Position data hold vertical information about each lod, like minY and maxY...
|
||||
private int[] verticalData;
|
||||
|
||||
//Lights data hold lights information about each lod, like skylight and block light values...
|
||||
private byte[] lightsData;
|
||||
|
||||
//BlockBiomeId hold a unique ID for each lod relative to a block-biome couple. This couple is capable of generating color
|
||||
//We could just reference
|
||||
private int[] BlockBiomeId;
|
||||
|
||||
//BlockBiomeFrequency for each lod BlockBiomeId
|
||||
private byte[] BlockBiomeFrequency;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param detail
|
||||
* @param horizontalSize
|
||||
* @param verticalSize
|
||||
* @param levelPosX
|
||||
* @param levelPosZ
|
||||
* @param generationMode
|
||||
* @param avoidPregeneratedChunk
|
||||
* @param idPerLod
|
||||
*/
|
||||
public LodSection(int detail, int horizontalSize, int verticalSize, int levelPosX, int levelPosZ, byte generationMode, boolean avoidPregeneratedChunk, int idPerLod)
|
||||
{
|
||||
this.detail = detail;
|
||||
this.sectionPosX = levelPosX;
|
||||
this.sectionPosZ = levelPosZ;
|
||||
this.horizontalSize = horizontalSize;
|
||||
this.verticalSize = verticalSize;
|
||||
this.generationMode = generationMode;
|
||||
this.usePregeneratedChunk = avoidPregeneratedChunk;
|
||||
this.idPerLod = idPerLod;
|
||||
//Now we should search if there is a file with this values
|
||||
//if so we load it and end the process
|
||||
|
||||
//Otherwise we initialize this section
|
||||
positionData = new byte[horizontalSize*horizontalSize];
|
||||
verticalData = new int[horizontalSize*horizontalSize*verticalSize];
|
||||
lightsData = new byte[horizontalSize*horizontalSize*verticalSize];
|
||||
BlockBiomeId = new int[horizontalSize*horizontalSize*verticalSize*idPerLod];
|
||||
BlockBiomeFrequency = new byte[horizontalSize*horizontalSize*verticalSize*idPerLod];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param otherSection
|
||||
* @param inputStartX
|
||||
* @param inputStartZ
|
||||
* @param inputEndX
|
||||
* @param inputEndZ
|
||||
* @param targetStartX
|
||||
* @param targetStartZ
|
||||
* @param targetEndX
|
||||
* @param targetEndZ
|
||||
*/
|
||||
public void mergeAndAdd(LodSection otherSection,
|
||||
int inputStartX, int inputStartZ, int inputEndX, int inputEndZ,
|
||||
int targetStartX, int targetStartZ, int targetEndX, int targetEndZ)
|
||||
{
|
||||
}
|
||||
|
||||
public short getPositionData(int posX, int posZ)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long[] getData(int posX, int posZ)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void save()
|
||||
{
|
||||
}
|
||||
|
||||
public void tryload()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package com.seibel.lod.core.objects.lod.quadtree;
|
||||
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
|
||||
public class QuadTreeMover
|
||||
{
|
||||
public static void move(LodQuadTree lqt, RenderQuadTree rqt, int centerX, int centerZ)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.seibel.lod.core.objects.lod.quadtree;
|
||||
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
|
||||
public class QuadTreeProperties
|
||||
{
|
||||
public int MAX_NUMBER_OF_DETAIL=23;
|
||||
public int SECTION_SIZE=128;
|
||||
public int[] absoluteDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
|
||||
public int[] relativeDetailCircleSize = new int[MAX_NUMBER_OF_DETAIL];
|
||||
public int[] generationModeOfDetail = new int[MAX_NUMBER_OF_DETAIL];
|
||||
|
||||
public LodQuadTree lodQuadTree;
|
||||
public RenderQuadTree renderQuadTree;
|
||||
|
||||
public void update()
|
||||
{
|
||||
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
|
||||
{
|
||||
//Compute circle distance for this detail in term of blocks
|
||||
absoluteDetailCircleSize[detail] = DetailDistanceUtil.getDrawDistanceFromDetail(detail);
|
||||
//Compute circle distance in terms of number of section
|
||||
relativeDetailCircleSize[detail] = (int) Math.ceil(absoluteDetailCircleSize[detail]/(SECTION_SIZE*2^detail));
|
||||
}
|
||||
updateGridSize();
|
||||
}
|
||||
|
||||
private void updateGridSize()
|
||||
{
|
||||
for(int detail = 0; detail < MAX_NUMBER_OF_DETAIL; detail++)
|
||||
{
|
||||
//Use this value to change to update the size of the matrices
|
||||
//relativeDetailCircleSize[detail];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package com.seibel.lod.core.objects.lod.quadtree;
|
||||
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
|
||||
public class RenderQuadTree
|
||||
{
|
||||
//public RenderSection[][][] quadTreeStructure;
|
||||
public QuadTreeProperties properties;
|
||||
|
||||
public RenderQuadTree()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ import com.google.common.collect.Lists;
|
||||
* OpenGL buffers.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-9-2021
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public class LodBufferBuilder
|
||||
{
|
||||
@@ -83,7 +83,7 @@ public class LodBufferBuilder
|
||||
/** make sure the buffer doesn't overflow when inserting new elements */
|
||||
private void ensureVertexCapacity()
|
||||
{
|
||||
this.ensureCapacity(this.format.getByteSize());
|
||||
this.ensureCapacity(this.format.getVertexSize());
|
||||
}
|
||||
private void ensureCapacity(int vertexSizeInBytes)
|
||||
{
|
||||
@@ -282,7 +282,7 @@ public class LodBufferBuilder
|
||||
{
|
||||
this.building = false;
|
||||
this.vertexCounts.add(new LodBufferBuilder.DrawState(this.format, this.vertices, this.mode));
|
||||
this.totalRenderedBytes += this.vertices * this.format.getByteSize();
|
||||
this.totalRenderedBytes += this.vertices * this.format.getVertexSize();
|
||||
this.vertices = 0;
|
||||
this.currentElement = null;
|
||||
this.elementIndex = 0;
|
||||
@@ -450,7 +450,7 @@ public class LodBufferBuilder
|
||||
{
|
||||
LodBufferBuilder.DrawState bufferbuilder$drawstate = this.vertexCounts.get(this.lastRenderedCountIndex++);
|
||||
this.buffer.position(this.totalUploadedBytes);
|
||||
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
|
||||
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getVertexSize();
|
||||
this.buffer.limit(this.totalUploadedBytes);
|
||||
if (this.lastRenderedCountIndex == this.vertexCounts.size() && this.vertices == 0)
|
||||
{
|
||||
@@ -458,7 +458,7 @@ public class LodBufferBuilder
|
||||
}
|
||||
|
||||
ByteBuffer bytebuffer = this.buffer.slice();
|
||||
//bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
|
||||
bytebuffer.order(this.buffer.order()); // FORGE: Fix incorrect byte order
|
||||
this.buffer.clear();
|
||||
return bytebuffer; // the original method also returned bufferbuilder$drawstate
|
||||
}
|
||||
@@ -543,10 +543,10 @@ public class LodBufferBuilder
|
||||
// Forge added methods
|
||||
public void putBulkData(ByteBuffer buffer)
|
||||
{
|
||||
ensureCapacity(buffer.limit() + this.format.getByteSize());
|
||||
this.buffer.position(this.vertices * this.format.getByteSize());
|
||||
ensureCapacity(buffer.limit() + this.format.getVertexSize());
|
||||
this.buffer.position(this.vertices * this.format.getVertexSize());
|
||||
this.buffer.put(buffer);
|
||||
this.vertices += buffer.limit() / this.format.getByteSize();
|
||||
this.vertices += buffer.limit() / this.format.getVertexSize();
|
||||
this.nextElementByte += buffer.limit();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
package com.seibel.lod.core.objects.opengl;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import com.seibel.lod.core.enums.rendering.GLProxyContext;
|
||||
import com.seibel.lod.core.render.GLProxy;
|
||||
@@ -41,7 +41,7 @@ public class LodVertexBuffer implements AutoCloseable
|
||||
if (GLProxy.getInstance().getGlContext() == GLProxyContext.NONE)
|
||||
throw new IllegalStateException("Thread [" +Thread.currentThread().getName() + "] tried to create a [" + LodVertexBuffer.class.getSimpleName() + "] outside a OpenGL contex.");
|
||||
|
||||
this.id = GL32.glGenBuffers();
|
||||
this.id = GL15.glGenBuffers();
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class LodVertexBuffer implements AutoCloseable
|
||||
{
|
||||
if (this.id >= 0)
|
||||
{
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> GL32.glDeleteBuffers(this.id));
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> GL15.glDeleteBuffers(this.id));
|
||||
this.id = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,13 +35,13 @@ import it.unimi.dsi.fastutil.ints.IntList;
|
||||
* were commented out since we didn't need them.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-9-2021
|
||||
* @version 11-13-2021
|
||||
*/
|
||||
public class LodVertexFormat
|
||||
{
|
||||
private final ImmutableList<LodVertexFormatElement> elements;
|
||||
private final IntList offsets = new IntArrayList();
|
||||
private final int byteSize;
|
||||
private final int vertexSize;
|
||||
|
||||
public LodVertexFormat(ImmutableList<LodVertexFormatElement> elementList)
|
||||
{
|
||||
@@ -54,12 +54,17 @@ public class LodVertexFormat
|
||||
i += LodVertexFormatElement.getByteSize();
|
||||
}
|
||||
|
||||
this.byteSize = i;
|
||||
this.vertexSize = i;
|
||||
}
|
||||
|
||||
public int getByteSize()
|
||||
public int getIntegerSize()
|
||||
{
|
||||
return this.byteSize;
|
||||
return this.getVertexSize() / 4;
|
||||
}
|
||||
|
||||
public int getVertexSize()
|
||||
{
|
||||
return this.vertexSize;
|
||||
}
|
||||
|
||||
public ImmutableList<LodVertexFormatElement> getElements()
|
||||
@@ -93,7 +98,7 @@ public class LodVertexFormat
|
||||
else if (obj != null && this.getClass() == obj.getClass())
|
||||
{
|
||||
LodVertexFormat vertexformat = (LodVertexFormat) obj;
|
||||
return this.byteSize == vertexformat.byteSize && this.elements.equals(vertexformat.elements);
|
||||
return this.vertexSize == vertexformat.vertexSize && this.elements.equals(vertexformat.elements);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.objects.rending;
|
||||
|
||||
import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
|
||||
/**
|
||||
* This object is just a replacement for an array
|
||||
* to make things easier to understand in the LodRenderer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-26-2021
|
||||
*/
|
||||
public class LodFogConfig
|
||||
{
|
||||
public FogDrawMode fogDrawMode;
|
||||
public FogDistance fogDistance;
|
||||
|
||||
|
||||
public float nearFogStart = 0;
|
||||
public float nearFogEnd = 0;
|
||||
|
||||
public float farFogStart = 0;
|
||||
public float farFogEnd = 0;
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2021 James Seibel
|
||||
* 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
|
||||
@@ -19,26 +19,25 @@
|
||||
|
||||
package com.seibel.lod.core.render;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GLCapabilities;
|
||||
import org.lwjgl.opengl.GLUtil;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.seibel.lod.core.ModInfo;
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.enums.config.GpuUploadMethod;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.enums.rendering.GLProxyContext;
|
||||
import com.seibel.lod.core.render.shader.LodShader;
|
||||
import com.seibel.lod.core.render.shader.LodShaderProgram;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
@@ -58,16 +57,15 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
* https://stackoverflow.com/questions/63509735/massive-performance-loss-with-glmapbuffer <br><br>
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-9-2021
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public class GLProxy
|
||||
{
|
||||
|
||||
private static final IMinecraftWrapper MC = SingletonHandler.get(IMinecraftWrapper.class);
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
|
||||
private static final ExecutorService workerThread = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(GLProxy.class.getSimpleName() + "-Worker-Thread").build());
|
||||
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
|
||||
|
||||
private static GLProxy instance = null;
|
||||
|
||||
@@ -86,72 +84,22 @@ public class GLProxy
|
||||
/** the proxyWorker's GL capabilities */
|
||||
public final GLCapabilities proxyWorkerGlCapabilities;
|
||||
|
||||
/** Requires OpenGL 4.4, and offers the best buffer uploading */
|
||||
|
||||
|
||||
/** 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;
|
||||
/** This is the 2D texture that holds MC's lightmap */
|
||||
public final int lightMapTextureId;
|
||||
|
||||
|
||||
/** Requires OpenGL 4.5, and offers the best buffer uploading */
|
||||
public final boolean bufferStorageSupported;
|
||||
|
||||
/** Requires OpenGL 4.5 */
|
||||
public final boolean namedObjectSupported;
|
||||
|
||||
/** Requires OpenGL 4.3 */
|
||||
public final boolean VertexAttributeBufferBindingSupported;
|
||||
/** Requires OpenGL 3.0 */
|
||||
public final boolean mapBufferRangeSupported;
|
||||
|
||||
/** Requires OpenGL 3.0, which will current min requirement as 3.3, should always be true */
|
||||
@Deprecated
|
||||
public final boolean mapBufferRangeSupported = true;
|
||||
|
||||
private final GpuUploadMethod preferredUploadMethod;
|
||||
|
||||
|
||||
private String getFailedVersionInfo(GLCapabilities c) {
|
||||
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
|
||||
|
||||
str.append("1.1: "+c.OpenGL11+"\n");
|
||||
str.append("1.2: "+c.OpenGL12+"\n");
|
||||
str.append("1.3: "+c.OpenGL13+"\n");
|
||||
str.append("1.4: "+c.OpenGL14+"\n");
|
||||
str.append("1.5: "+c.OpenGL15+"\n");
|
||||
str.append("2.0: "+c.OpenGL20+"\n");
|
||||
str.append("2.1: "+c.OpenGL21+"\n");
|
||||
str.append("3.0: "+c.OpenGL30+"\n");
|
||||
str.append("3.1: "+c.OpenGL31+"\n");
|
||||
str.append("3.2: "+c.OpenGL32+" <- REQUIRED\n");
|
||||
str.append("3.3: "+c.OpenGL33+"\n");
|
||||
str.append("4.0: "+c.OpenGL40+"\n");
|
||||
str.append("4.1: "+c.OpenGL41+"\n");
|
||||
str.append("4.2: "+c.OpenGL42+"\n");
|
||||
str.append("4.3: "+c.OpenGL43+" <- optional improvement\n");
|
||||
str.append("4.4: "+c.OpenGL44+" <- optional improvement\n");
|
||||
str.append("4.5: "+c.OpenGL45+"\n");
|
||||
str.append("4.6: "+c.OpenGL46+"\n");
|
||||
|
||||
str.append("If you noticed that your computer supports higher OpenGL versions"
|
||||
+ " but not the required version, try running the game in compatibility mode."
|
||||
+ " (How you turn that on, I have no clue~)");
|
||||
return str.toString();
|
||||
}
|
||||
private String getVersionInfo(GLCapabilities c) {
|
||||
StringBuilder str = new StringBuilder("Your supported OpenGL version:\n");
|
||||
|
||||
str.append("1.1: "+c.OpenGL11+"\n");
|
||||
str.append("1.2: "+c.OpenGL12+"\n");
|
||||
str.append("1.3: "+c.OpenGL13+"\n");
|
||||
str.append("1.4: "+c.OpenGL14+"\n");
|
||||
str.append("1.5: "+c.OpenGL15+"\n");
|
||||
str.append("2.0: "+c.OpenGL20+"\n");
|
||||
str.append("2.1: "+c.OpenGL21+"\n");
|
||||
str.append("3.0: "+c.OpenGL30+"\n");
|
||||
str.append("3.1: "+c.OpenGL31+"\n");
|
||||
str.append("3.2: "+c.OpenGL32+" <- REQUIRED\n");
|
||||
str.append("3.3: "+c.OpenGL33+"\n");
|
||||
str.append("4.0: "+c.OpenGL40+"\n");
|
||||
str.append("4.1: "+c.OpenGL41+"\n");
|
||||
str.append("4.2: "+c.OpenGL42+"\n");
|
||||
str.append("4.3: "+c.OpenGL43+" <- optional improvement\n");
|
||||
str.append("4.4: "+c.OpenGL44+" <- optional improvement\n");
|
||||
str.append("4.5: "+c.OpenGL45+"\n");
|
||||
str.append("4.6: "+c.OpenGL46+"\n");
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -161,14 +109,7 @@ public class GLProxy
|
||||
*/
|
||||
private GLProxy()
|
||||
{
|
||||
|
||||
boolean enableDebugLogging = CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL;
|
||||
|
||||
// this must be created on minecraft's render context to work correctly
|
||||
|
||||
ClientApi.LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see in the log there must have been a OpenGL error.");
|
||||
|
||||
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
|
||||
|
||||
// getting Minecraft's context has to be done on the render thread,
|
||||
// where the GL context is
|
||||
@@ -182,20 +123,7 @@ public class GLProxy
|
||||
// get Minecraft's context
|
||||
minecraftGlContext = GLFW.glfwGetCurrentContext();
|
||||
minecraftGlCapabilities = GL.getCapabilities();
|
||||
|
||||
// crash the game if the GPU doesn't support OpenGL 3.2
|
||||
if (!minecraftGlCapabilities.OpenGL32)
|
||||
{
|
||||
String supportedVersionInfo = getFailedVersionInfo(minecraftGlCapabilities);
|
||||
|
||||
// Note: as of MC 1.17 this shouldn't happen since MC
|
||||
// requires OpenGL 3.2, but for older MC version this will warn the player.
|
||||
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName()
|
||||
+ " and discovered this GPU doesn't support OpenGL 3.2." + " Sorry I couldn't tell you sooner :(\n"+
|
||||
"Additional info:\n"+supportedVersionInfo;
|
||||
MC.crashMinecraft(errorMessage, new UnsupportedOperationException("This GPU doesn't support OpenGL 3.2."));
|
||||
}
|
||||
ClientApi.LOGGER.info("minecraftGlCapabilities:\n"+getVersionInfo(minecraftGlCapabilities));
|
||||
|
||||
|
||||
// context creation setup
|
||||
GLFW.glfwDefaultWindowHints();
|
||||
@@ -206,24 +134,21 @@ public class GLProxy
|
||||
// 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();
|
||||
ClientApi.LOGGER.info("lodBuilderGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
|
||||
|
||||
|
||||
// create the proxyWorker's context
|
||||
proxyWorkerGlContext = GLFW.glfwCreateWindow(64, 48, "LOD proxy worker Window", 0L, minecraftGlContext);
|
||||
GLFW.glfwMakeContextCurrent(proxyWorkerGlContext);
|
||||
proxyWorkerGlCapabilities = GL.createCapabilities();
|
||||
ClientApi.LOGGER.info("proxyWorkerGlCapabilities:\n"+getVersionInfo(lodBuilderGlCapabilities));
|
||||
|
||||
// Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after
|
||||
VertexAttributeBufferBindingSupported = minecraftGlCapabilities.glBindVertexBuffer != 0L; // Nullptr
|
||||
|
||||
// UNUSED currently
|
||||
// Check if we can use the named version of all calls, which is available in GL4.5 or after
|
||||
namedObjectSupported = minecraftGlCapabilities.glNamedBufferData != 0L; //Nullptr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==================================//
|
||||
@@ -231,62 +156,88 @@ public class GLProxy
|
||||
//==================================//
|
||||
|
||||
setGlContext(GLProxyContext.LOD_BUILDER);
|
||||
// TODO: Enable this but disable INFO logging
|
||||
|
||||
File proxyLog = new File("OpenGL-Lod-ProxyContext.log");
|
||||
try {
|
||||
proxyLog.createNewFile();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
|
||||
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
|
||||
|
||||
// crash the game if the GPU doesn't support OpenGL 2.0
|
||||
if (!minecraftGlCapabilities.OpenGL20)
|
||||
{
|
||||
// Note: as of MC 1.17 this shouldn't happen since MC
|
||||
// requires OpenGL 3.3, but just in case.
|
||||
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 2.0 or greater.";
|
||||
MC.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 2.0 or greater."));
|
||||
}
|
||||
if (enableDebugLogging)
|
||||
try {
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(proxyLog));
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get specific capabilities
|
||||
// Check if we can use the Buffer Storage, which is available in GL4.4 or after
|
||||
bufferStorageSupported = minecraftGlCapabilities.glBufferStorage != 0L && lodBuilderGlCapabilities.glBufferStorage != 0L; // Nullptr
|
||||
//bufferStorageSupported = true;
|
||||
bufferStorageSupported = lodBuilderGlCapabilities.glBufferStorage != 0;
|
||||
mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0;
|
||||
|
||||
// display the capabilities
|
||||
if (!bufferStorageSupported)
|
||||
{
|
||||
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
|
||||
String fallBackVersion = mapBufferRangeSupported ? "3.0" : "1.5";
|
||||
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.5), falling back to OpenGL " + fallBackVersion + ". This may cause stuttering and reduced performance.");
|
||||
}
|
||||
|
||||
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
|
||||
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
|
||||
{
|
||||
// NVIDIA card
|
||||
preferredUploadMethod = bufferStorageSupported ? GpuUploadMethod.BUFFER_STORAGE : GpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
// AMD or Intel card
|
||||
preferredUploadMethod = GpuUploadMethod.BUFFER_MAPPING;
|
||||
}
|
||||
|
||||
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Preferred upload method is [" + preferredUploadMethod + "].");
|
||||
|
||||
setGlContext(GLProxyContext.PROXY_WORKER);
|
||||
File workerLog = new File("OpenGL-Lod-WorkerContext.log");
|
||||
try {
|
||||
workerLog.createNewFile();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (enableDebugLogging)
|
||||
try {
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(workerLog));
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
// if using AUTO gpuUpload
|
||||
// determine a good default for the GPU
|
||||
if (CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.AUTO)
|
||||
{
|
||||
GpuUploadMethod uploadMethod;
|
||||
String vendor = GL15.glGetString(GL15.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
|
||||
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
|
||||
{
|
||||
// NVIDIA card
|
||||
|
||||
if (bufferStorageSupported)
|
||||
{
|
||||
uploadMethod = GpuUploadMethod.BUFFER_STORAGE;
|
||||
}
|
||||
else
|
||||
{
|
||||
uploadMethod = GpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// AMD or Intel card
|
||||
|
||||
if (mapBufferRangeSupported)
|
||||
{
|
||||
uploadMethod = GpuUploadMethod.BUFFER_MAPPING;
|
||||
}
|
||||
else
|
||||
{
|
||||
uploadMethod = GpuUploadMethod.DATA;
|
||||
}
|
||||
}
|
||||
|
||||
CONFIG.client().advanced().buffers().setGpuUploadMethod(uploadMethod);
|
||||
ClientApi.LOGGER.info("GPU Vendor [" + vendor + "], Upload method set to [" + uploadMethod + "].");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// shader setup //
|
||||
//==============//
|
||||
|
||||
setGlContext(GLProxyContext.MINECRAFT);
|
||||
|
||||
createShaderProgram();
|
||||
|
||||
// Note: VAO objects can not be shared between contexts,
|
||||
// this must be created on minecraft's render context to work correctly
|
||||
vertexArrayObjectId = GL30.glGenVertexArrays();
|
||||
|
||||
lightMapTextureId = GL30.glGenTextures();
|
||||
|
||||
|
||||
|
||||
|
||||
//==========//
|
||||
// clean up //
|
||||
@@ -295,10 +246,88 @@ public class GLProxy
|
||||
// Since this is created on the render thread, make sure the Minecraft context is used in the end
|
||||
setGlContext(GLProxyContext.MINECRAFT);
|
||||
|
||||
|
||||
// GLProxy creation success
|
||||
ClientApi.LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all required shaders
|
||||
* @throws RuntimeException
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
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/standard.vert", false);
|
||||
fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders/flat_shaded.frag", false);
|
||||
|
||||
// this can be used when testing shaders,
|
||||
// since we can't hot swap the files in the resource folder
|
||||
// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/standard.vert", true);
|
||||
// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.frag", true);
|
||||
|
||||
|
||||
// create the shaders
|
||||
|
||||
lodShaderProgram = new LodShaderProgram();
|
||||
|
||||
// Attach the compiled shaders to the program
|
||||
lodShaderProgram.attachShader(vertexShader);
|
||||
lodShaderProgram.attachShader(fragmentShader);
|
||||
|
||||
// activate the fragment shader output
|
||||
GL30.glBindFragDataLocation(lodShaderProgram.id, 0, "fragColor");
|
||||
|
||||
// attach the shader program to the OpenGL context
|
||||
lodShaderProgram.link();
|
||||
|
||||
// after the shaders have been attached to the program
|
||||
// we don't need their OpenGL references anymore
|
||||
GL20.glDeleteShader(vertexShader.id);
|
||||
GL20.glDeleteShader(fragmentShader.id);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ClientApi.LOGGER.error("Unable to compile shaders. Error: " + e.getMessage());
|
||||
}
|
||||
// get the shaders from the resource folder
|
||||
// Use File.separator ONLY if the file is external (not in the resourse), otherwise use "/"
|
||||
vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "shaders/standard.vert", false);
|
||||
fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "shaders/flat_shaded.frag", false);
|
||||
|
||||
// this can be used when testing shaders,
|
||||
// since we can't hot swap the files in the resource folder
|
||||
// vertexShader = LodShader.loadShader(GL20.GL_VERTEX_SHADER, "C:/Users/James Seibel/Desktop/shaders/standard.vert", true);
|
||||
// fragmentShader = LodShader.loadShader(GL20.GL_FRAGMENT_SHADER, "C:/Users/James Seibel/Desktop/shaders/flat_shaded.frag", true);
|
||||
|
||||
|
||||
// create the shaders
|
||||
lodShaderProgram = new LodShaderProgram();
|
||||
|
||||
// Attach the compiled shaders to the program, throws RuntimeException on link error
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper function to make switching contexts easier. <br>
|
||||
* Does nothing if the calling thread is already using newContext.
|
||||
@@ -368,29 +397,21 @@ public class GLProxy
|
||||
+ "no context [0].");
|
||||
}
|
||||
|
||||
public static boolean hasInstance() {
|
||||
return instance != null;
|
||||
}
|
||||
|
||||
public static GLProxy getInstance()
|
||||
{
|
||||
if (instance == null)
|
||||
instance = new GLProxy();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public GpuUploadMethod getGpuUploadMethod() {
|
||||
GpuUploadMethod method = CONFIG.client().advanced().buffers().getGpuUploadMethod();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (!bufferStorageSupported && method == GpuUploadMethod.BUFFER_STORAGE)
|
||||
{
|
||||
// if buffer storage isn't supported
|
||||
// default to SUB_DATA
|
||||
method = GpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
|
||||
return method == GpuUploadMethod.AUTO ? preferredUploadMethod : method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously calls the given runnable on proxy's OpenGL context.
|
||||
@@ -431,7 +452,7 @@ public class GLProxy
|
||||
* This only works with Legacy OpenGL because James hasn't
|
||||
* looking into a way for it to work with Modern OpenGL.
|
||||
*/
|
||||
public boolean disableLegacyFog()
|
||||
public void disableLegacyFog()
|
||||
{
|
||||
// make sure this is a legacy OpenGL context
|
||||
if (minecraftGlCapabilities.glFogf != 0)
|
||||
@@ -444,9 +465,7 @@ public class GLProxy
|
||||
GL11.glFogf(GL11.GL_FOG_START, 0.0f);
|
||||
GL11.glFogf(GL11.GL_FOG_END, Float.MAX_VALUE);
|
||||
GL11.glFogf(GL11.GL_FOG_DENSITY, 0.0f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,75 +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.core.render;
|
||||
|
||||
import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.handlers.IReflectionHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
|
||||
/**
|
||||
* This object is just a replacement for an array
|
||||
* to make things easier to understand in the LodRenderer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-26-2021
|
||||
*/
|
||||
public class LodFogConfig
|
||||
{
|
||||
public FogDrawMode fogDrawMode;
|
||||
public FogDistance fogDistance;
|
||||
|
||||
public float nearFogStart = 0;
|
||||
public float nearFogEnd = 0;
|
||||
|
||||
public float farFogStart = 0;
|
||||
public float farFogEnd = 0;
|
||||
|
||||
public LodFogConfig(ILodConfigWrapperSingleton config, IReflectionHandler reflectionHandler, int farPlaneBlockDistance, int vanillaBlockRenderedDistance) {
|
||||
|
||||
fogDrawMode = config.client().graphics().fogQuality().getFogDrawMode();
|
||||
if (fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
|
||||
fogDrawMode = reflectionHandler.getFogDrawMode();
|
||||
|
||||
// how different distances are drawn depends on the quality set
|
||||
fogDistance = config.client().graphics().fogQuality().getFogDistance();
|
||||
|
||||
// far fog //
|
||||
|
||||
if (config.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
|
||||
farFogStart = farPlaneBlockDistance * 0.9f;
|
||||
else
|
||||
// for more realistic fog when using FAR
|
||||
farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f);
|
||||
|
||||
farFogEnd = farPlaneBlockDistance;
|
||||
|
||||
|
||||
// near fog //
|
||||
|
||||
// the reason that I wrote fogEnd then fogStart backwards
|
||||
// is because we are using fog backwards to how
|
||||
// it is normally used, hiding near objects
|
||||
// instead of far objects.
|
||||
nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
|
||||
nearFogStart = vanillaBlockRenderedDistance * 1.6f;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2021 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.core.render;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
import com.seibel.lod.core.render.objects.ShaderProgram;
|
||||
import com.seibel.lod.core.render.objects.VertexAttribute;
|
||||
import com.seibel.lod.core.render.objects.VertexAttributePostGL43;
|
||||
import com.seibel.lod.core.render.objects.VertexAttributePreGL43;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
|
||||
public class LodRenderProgram extends ShaderProgram {
|
||||
public static final String VERTEX_SHADER_PATH = "shaders/standard.vert";
|
||||
public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag";
|
||||
|
||||
public final VertexAttribute vao;
|
||||
|
||||
// Attributes
|
||||
public final int posAttrib;
|
||||
public final int colAttrib;
|
||||
public final int blockSkyLightAttrib;
|
||||
public final int blockLightAttrib;
|
||||
// Uniforms
|
||||
public final int mvmUniform;
|
||||
public final int projUniform;
|
||||
public final int cameraUniform;
|
||||
public final int fogColorUniform;
|
||||
// public final int skyLightUniform; worldSkyLight is currently not used
|
||||
public final int lightMapUniform;
|
||||
// Fog Uniforms
|
||||
public final int fogEnabledUniform;
|
||||
public final int nearFogEnabledUniform;
|
||||
public final int farFogEnabledUniform;
|
||||
public final int nearFogStartUniform;
|
||||
public final int nearFogEndUniform;
|
||||
public final int farFogStartUniform;
|
||||
public final int farFogEndUniform;
|
||||
|
||||
// This will bind VertexAttribute
|
||||
public LodRenderProgram() {
|
||||
super(VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH, "fragColor");
|
||||
|
||||
posAttrib = getAttributeLocation("vPosition");
|
||||
colAttrib = getAttributeLocation("color");
|
||||
blockSkyLightAttrib = getAttributeLocation("blockSkyLight");
|
||||
blockLightAttrib = getAttributeLocation("blockLight");
|
||||
|
||||
mvmUniform = getUniformLocation("modelViewMatrix");
|
||||
projUniform = getUniformLocation("projectionMatrix");
|
||||
cameraUniform = getUniformLocation("cameraPos");
|
||||
fogColorUniform = getUniformLocation("fogColor");
|
||||
// skyLightUniform = getUniformLocation("worldSkyLight");
|
||||
lightMapUniform = getUniformLocation("lightMap");
|
||||
|
||||
// Fog uniforms
|
||||
fogEnabledUniform = getUniformLocation("fogEnabled");
|
||||
nearFogEnabledUniform = getUniformLocation("nearFogEnabled");
|
||||
farFogEnabledUniform = getUniformLocation("farFogEnabled");
|
||||
// near
|
||||
nearFogStartUniform = getUniformLocation("nearFogStart");
|
||||
nearFogEndUniform = getUniformLocation("nearFogEnd");
|
||||
// far
|
||||
farFogStartUniform = getUniformLocation("farFogStart");
|
||||
farFogEndUniform = getUniformLocation("farFogEnd");
|
||||
|
||||
// TODO: Add better use of the LODFormat thing
|
||||
int vertexByteCount = LodUtil.LOD_VERTEX_FORMAT.getByteSize();
|
||||
if (GLProxy.getInstance().VertexAttributeBufferBindingSupported)
|
||||
vao = new VertexAttributePostGL43(); // also binds VertexAttribute
|
||||
else
|
||||
vao = new VertexAttributePreGL43(); // also binds VertexAttribute
|
||||
//vao.bind();
|
||||
vao.setVertexAttribute(0, posAttrib, VertexAttribute.VertexPointer.addVec3Pointer(false));
|
||||
vao.setVertexAttribute(0, colAttrib, VertexAttribute.VertexPointer.addUnsignedBytesPointer(4, true));
|
||||
vao.setVertexAttribute(0, blockSkyLightAttrib, VertexAttribute.VertexPointer.addUnsignedBytePointer(false));
|
||||
vao.setVertexAttribute(0, blockLightAttrib, VertexAttribute.VertexPointer.addUnsignedBytePointer(false));
|
||||
vao.completeAndCheck(vertexByteCount);
|
||||
}
|
||||
|
||||
// Override ShaderProgram.bind()
|
||||
public void bind() {
|
||||
super.bind();
|
||||
vao.bind();
|
||||
}
|
||||
// Override ShaderProgram.unbind()
|
||||
public void unbind() {
|
||||
super.unbind();
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
// Override ShaderProgram.free()
|
||||
public void free() {
|
||||
vao.free();
|
||||
super.free();
|
||||
}
|
||||
|
||||
public void bindVertexBuffer(int vbo) {
|
||||
vao.bindBufferToAllBindingPoint(vbo);
|
||||
}
|
||||
|
||||
public void unbindVertexBuffer() {
|
||||
vao.unbindBuffersFromAllBindingPoint();
|
||||
}
|
||||
|
||||
public void fillUniformData(Mat4f modelViewMatrix, Mat4f projectionMatrix, Vec3f cameraPos, Color fogColor, int skyLight, int lightmapBindPoint) {
|
||||
super.bind();
|
||||
// uniforms
|
||||
setUniform(mvmUniform, modelViewMatrix);
|
||||
setUniform(projUniform, projectionMatrix);
|
||||
setUniform(cameraUniform, cameraPos);
|
||||
setUniform(fogColorUniform, fogColor);
|
||||
// setUniform(skyLightUniform, skyLight);
|
||||
setUniform(lightMapUniform, lightmapBindPoint);
|
||||
}
|
||||
|
||||
public void fillUniformDataForFog(LodFogConfig fogSettings) {
|
||||
super.bind();
|
||||
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED) {
|
||||
setUniform(fogEnabledUniform, true);
|
||||
setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
|
||||
setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
|
||||
// near
|
||||
setUniform(nearFogStartUniform, fogSettings.nearFogStart);
|
||||
setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
|
||||
// far
|
||||
setUniform(farFogStartUniform, fogSettings.farFogStart);
|
||||
setUniform(farFogEndUniform, fogSettings.farFogEnd);
|
||||
} else {
|
||||
setUniform(fogEnabledUniform, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2021 James Seibel
|
||||
* 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
|
||||
@@ -22,7 +22,10 @@ package com.seibel.lod.core.render;
|
||||
import java.awt.Color;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import com.seibel.lod.core.api.ApiShared;
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
@@ -32,18 +35,21 @@ import com.seibel.lod.core.enums.config.GpuUploadMethod;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.enums.rendering.FogColorMode;
|
||||
import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.handlers.IReflectionHandler;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.objects.lod.RegionPos;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3d;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
|
||||
import com.seibel.lod.core.render.objects.LightmapTexture;
|
||||
import com.seibel.lod.core.objects.rending.LodFogConfig;
|
||||
import com.seibel.lod.core.render.shader.LodShaderProgram;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.LevelPosUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
@@ -54,7 +60,7 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-12-2021
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public class LodRenderer
|
||||
{
|
||||
@@ -62,6 +68,8 @@ public class LodRenderer
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
|
||||
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
|
||||
|
||||
/**
|
||||
* If true the LODs colors will be replaced with
|
||||
@@ -69,8 +77,8 @@ public class LodRenderer
|
||||
*/
|
||||
public DebugMode previousDebugMode = DebugMode.OFF;
|
||||
|
||||
// This tells us if the renderer is enabled or not. If in a world, it should be enabled.
|
||||
private boolean isSetupComplete = false;
|
||||
private int farPlaneBlockDistance;
|
||||
|
||||
|
||||
/** This is used to generate the buildable buffers */
|
||||
private final LodBufferBuilderFactory lodBufferBuilderFactory;
|
||||
@@ -82,18 +90,19 @@ public class LodRenderer
|
||||
* These have to be separate because we can't override the
|
||||
* buffers in the VBOs (and we don't want to)
|
||||
*/
|
||||
private int[][][] storageBufferIds = null;
|
||||
|
||||
// The shader program
|
||||
LodRenderProgram shaderProgram = null;
|
||||
@SuppressWarnings("unused")
|
||||
private int[][][] storageBufferIds;
|
||||
|
||||
private int vbosCenterX = 0;
|
||||
private int vbosCenterZ = 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;
|
||||
@@ -109,7 +118,6 @@ public class LodRenderer
|
||||
*/
|
||||
private volatile boolean partialRegen = false;
|
||||
private volatile boolean fullRegen = true;
|
||||
private volatile boolean markToCleanup = false;
|
||||
|
||||
/**
|
||||
* This HashSet contains every chunk that Vanilla Minecraft
|
||||
@@ -118,16 +126,22 @@ public class LodRenderer
|
||||
public boolean[][] vanillaRenderedChunks;
|
||||
public boolean vanillaRenderedChunksChanged;
|
||||
public boolean vanillaRenderedChunksEmptySkip = false;
|
||||
public int vanillaBlockRenderedDistance;
|
||||
|
||||
|
||||
|
||||
private boolean canVanillaFogBeDisabled = true;
|
||||
|
||||
public void requestCleanup() {markToCleanup = true;}
|
||||
|
||||
public LodRenderer(LodBufferBuilderFactory newLodNodeBufferBuilder)
|
||||
{
|
||||
lodBufferBuilderFactory = newLodNodeBufferBuilder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Besides drawing the LODs this method also starts
|
||||
* the async process of generating the Buffers that hold those LODs.
|
||||
@@ -149,8 +163,6 @@ public class LodRenderer
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (MC_RENDER.playerHasBlindnessEffect())
|
||||
{
|
||||
// if the player is blind, don't render LODs,
|
||||
@@ -158,25 +170,16 @@ public class LodRenderer
|
||||
// which blindness relies on.
|
||||
return;
|
||||
}
|
||||
|
||||
// get MC's shader program
|
||||
// Save all MC render state
|
||||
int currentProgram = GL32.glGetInteger(GL32.GL_CURRENT_PROGRAM);
|
||||
int currentVBO = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING);
|
||||
int currentVAO = GL32.glGetInteger(GL32.GL_VERTEX_ARRAY_BINDING);
|
||||
int currentActiveText = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE);
|
||||
boolean currentBlend = GL32.glGetBoolean(GL32.GL_BLEND);
|
||||
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
|
||||
if (!glProxy.disableLegacyFog())
|
||||
if (!MC_RENDER.tryDisableVanillaFog())
|
||||
canVanillaFogBeDisabled = false;
|
||||
if (CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
|
||||
GLProxy.getInstance().disableLegacyFog();
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO move the buffer regeneration logic into its own class (probably called in the client api instead)
|
||||
// starting here...
|
||||
determineIfLodsShouldRegenerate(lodDim, partialTicks);
|
||||
|
||||
|
||||
//=================//
|
||||
// create the LODs //
|
||||
@@ -190,7 +193,7 @@ public class LodRenderer
|
||||
if ((partialRegen || fullRegen) && !lodBufferBuilderFactory.generatingBuffers && !lodBufferBuilderFactory.newBuffersAvailable())
|
||||
{
|
||||
// generate the LODs on a separate thread to prevent stuttering or freezing
|
||||
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen);
|
||||
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), true);
|
||||
|
||||
// the regen process has been started,
|
||||
// it will be done when lodBufferBuilder.newBuffersAvailable()
|
||||
@@ -207,169 +210,353 @@ public class LodRenderer
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
if (vbos == null) {
|
||||
// There is still no vbos, which means nothing needs to be drawn. So no rendering needed
|
||||
// (Vbos should be setup by now)
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Currently, we check for last Lod Dimension so that we can trigger a cleanup() if dimension has changed
|
||||
// The better thing to do is to call cleanup() on leaving dimensions in the EventApi, but only for client-side.
|
||||
if (markToCleanup) {
|
||||
markToCleanup = false;
|
||||
cleanup(); // This will unset the isSetupComplete, causing a setup() call.
|
||||
}
|
||||
|
||||
//===================//
|
||||
// draw params setup //
|
||||
//===================//
|
||||
|
||||
profiler.push("LOD draw setup");
|
||||
|
||||
/*---------Set GL State--------*/
|
||||
// Make sure to unbind current VBO so we don't mess up vanilla settings
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
|
||||
//===============//
|
||||
// initial setup //
|
||||
//===============//
|
||||
|
||||
profiler.push("LOD setup");
|
||||
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
|
||||
|
||||
|
||||
// set the required open GL settings
|
||||
|
||||
if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME)
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
|
||||
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE);
|
||||
else
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
||||
|
||||
GL32.glEnable(GL32.GL_CULL_FACE);
|
||||
GL32.glEnable(GL32.GL_DEPTH_TEST);
|
||||
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
|
||||
// GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||
// GL32.glEnable(GL32.GL_BLEND);
|
||||
GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL15.glEnable(GL15.GL_BLEND);
|
||||
|
||||
// get MC's shader program
|
||||
int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
|
||||
/*---------Bind required objects--------*/
|
||||
// Setup LodRenderProgram and the LightmapTexture if it has not yet been done
|
||||
// also binds LightmapTexture, VAO, and ShaderProgram
|
||||
if (!isSetupComplete) {
|
||||
setup();
|
||||
} else {
|
||||
shaderProgram.bind();
|
||||
}
|
||||
GL32.glActiveTexture(GL32.GL_TEXTURE0);
|
||||
LightmapTexture lightmapTexture = new LightmapTexture();
|
||||
|
||||
/*---------Get required data--------*/
|
||||
// Get the matrixs for rendering
|
||||
Mat4f modelViewMatrix = translateModelViewMatrix(mcModelViewMatrix, partialTicks);
|
||||
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
int farPlaneBlockDistance;
|
||||
vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
// required for setupFog and setupProjectionMatrix
|
||||
if (MC.getWrappedClientWorld().getDimensionType().hasCeiling())
|
||||
farPlaneBlockDistance = Math.min(CONFIG.client().graphics().quality().getLodChunkRenderDistance(), LodUtil.CEILED_DIMENSION_MAX_RENDER_DISTANCE) * LodUtil.CHUNK_WIDTH;
|
||||
else
|
||||
farPlaneBlockDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance, farPlaneBlockDistance);
|
||||
LodFogConfig fogSettings = new LodFogConfig(CONFIG, REFLECTION_HANDLER, farPlaneBlockDistance, vanillaBlockRenderedDistance);
|
||||
|
||||
/*---------Fill uniform data--------*/
|
||||
// Fill the uniform data. Note: GL33.GL_TEXTURE0 == texture bindpoint 0
|
||||
shaderProgram.fillUniformData(modelViewMatrix, projectionMatrix, getTranslatedCameraPos(),
|
||||
getFogColor(), (int) (MC.getSkyDarken(partialTicks) * 15), 0);
|
||||
// Previous guy said fog setting may be different from region to region, but the fogSettings never changed... soooooo...
|
||||
shaderProgram.fillUniformDataForFog(fogSettings);
|
||||
// Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one.
|
||||
lightmapTexture.fillData(MC_RENDER.getLightmapTextureWidth(), MC_RENDER.getLightmapTextureHeight(), MC_RENDER.getLightmapPixels());
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
profiler.popPush("LOD draw");
|
||||
|
||||
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
|
||||
boolean usingBufferStorage = glProxy.getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE;
|
||||
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance);
|
||||
|
||||
// where the center of the buffers is (needed when culling regions)
|
||||
// render each of the buffers
|
||||
for (int x = 0; x < vbos.length; x++)
|
||||
LodFogConfig fogSettings = determineFogConfig();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (vbos != null)
|
||||
{
|
||||
for (int z = 0; z < vbos.length; z++)
|
||||
//==============//
|
||||
// shader setup //
|
||||
//==============//
|
||||
|
||||
// can be used when testing shaders
|
||||
// glProxy.createShaderProgram();
|
||||
|
||||
|
||||
LodShaderProgram shaderProgram = glProxy.lodShaderProgram;
|
||||
shaderProgram.use();
|
||||
|
||||
|
||||
// determine the VertexArrayObject's element positions
|
||||
int posAttrib = shaderProgram.getAttributeLocation("vPosition");
|
||||
shaderProgram.enableVertexAttribute(posAttrib);
|
||||
int colAttrib = shaderProgram.getAttributeLocation("color");
|
||||
shaderProgram.enableVertexAttribute(colAttrib);
|
||||
int blockSkyLightAttrib = shaderProgram.getAttributeLocation("blockSkyLight");
|
||||
// TODO the block sky light is being passed in correctly but the data
|
||||
// we were given appears to be incorrect, so we won't use it for now
|
||||
//shaderProgram.enableVertexAttribute(blockSkyLightAttrib);
|
||||
int blockLightAttrib = shaderProgram.getAttributeLocation("blockLight");
|
||||
shaderProgram.enableVertexAttribute(blockLightAttrib);
|
||||
|
||||
|
||||
|
||||
// global uniforms
|
||||
int mvmUniform = shaderProgram.getUniformLocation("modelViewMatrix");
|
||||
shaderProgram.setUniform(mvmUniform, modelViewMatrix);
|
||||
int projUniform = shaderProgram.getUniformLocation("projectionMatrix");
|
||||
shaderProgram.setUniform(projUniform, projectionMatrix);
|
||||
int cameraUniform = shaderProgram.getUniformLocation("cameraPos");
|
||||
shaderProgram.setUniform(cameraUniform, getTranslatedCameraPos());
|
||||
int fogColorUniform = shaderProgram.getUniformLocation("fogColor");
|
||||
shaderProgram.setUniform(fogColorUniform, getFogColor());
|
||||
|
||||
|
||||
int skyLightUniform = shaderProgram.getUniformLocation("worldSkyLight");
|
||||
shaderProgram.setUniform(skyLightUniform, (int) (MC.getSkyDarken(partialTicks) * 15));
|
||||
|
||||
int lightMapUniform = shaderProgram.getUniformLocation("lightMap");
|
||||
|
||||
|
||||
|
||||
GL20.glBindTexture(GL20.GL_TEXTURE_2D, glProxy.lightMapTextureId);
|
||||
|
||||
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_S, GL20.GL_CLAMP_TO_BORDER);
|
||||
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_WRAP_T, GL20.GL_CLAMP_TO_BORDER);
|
||||
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MIN_FILTER, GL20.GL_NEAREST);
|
||||
GL20.glTexParameteri(GL20.GL_TEXTURE_2D, GL20.GL_TEXTURE_MAG_FILTER, GL20.GL_NEAREST);
|
||||
|
||||
|
||||
// get the latest lightmap from MC
|
||||
try
|
||||
{
|
||||
//int tempX = LodUtil.convertLevelPos(x + vbosCenterX - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
//int tempY = LodUtil.convertLevelPos(z + vbosCenterZ - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
|
||||
MC_RENDER.getLookAtVector(),
|
||||
vbosCenterX + LodUtil.convertLevelPos(x - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL),
|
||||
vbosCenterZ + LodUtil.convertLevelPos(z - (lodDim.getWidth() / 2), LodUtil.REGION_DETAIL_LEVEL , LodUtil.BLOCK_DETAIL_LEVEL)))
|
||||
int lightMapHeight = MC_RENDER.getLightmapTextureHeight();
|
||||
int lightMapWidth = MC_RENDER.getLightmapTextureWidth();
|
||||
int[] pixels = MC_RENDER.getLightmapPixels();
|
||||
|
||||
// comment me out to see when the lightmap is changing
|
||||
// boolean same = true;
|
||||
// int badIndex = 0;
|
||||
// if (testArray != null && pixels != null)
|
||||
// for (int i = 0; i < pixels.length; i++)
|
||||
// {
|
||||
// if(pixels[i] != testArray[i])
|
||||
// {
|
||||
// same = false;
|
||||
// badIndex = i;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// testArray = pixels;
|
||||
// MC.sendChatMessage(same + " " + badIndex);
|
||||
|
||||
// comment this line out to prevent uploading the new lightmap
|
||||
GL20.glTexImage2D(GL20.GL_TEXTURE_2D, 0, GL20.GL_RGBA, lightMapWidth,
|
||||
lightMapHeight, 0, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
// TODO is this needed/correct?
|
||||
shaderProgram.setUniform(lightMapUniform, 0);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ClientApi.LOGGER.info(e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// region dependent uniforms
|
||||
int fogEnabledUniform = shaderProgram.getUniformLocation("fogEnabled");
|
||||
int nearFogEnabledUniform = shaderProgram.getUniformLocation("nearFogEnabled");
|
||||
int farFogEnabledUniform = shaderProgram.getUniformLocation("farFogEnabled");
|
||||
// near
|
||||
int nearFogStartUniform = shaderProgram.getUniformLocation("nearFogStart");
|
||||
int nearFogEndUniform = shaderProgram.getUniformLocation("nearFogEnd");
|
||||
// far
|
||||
int farFogStartUniform = shaderProgram.getUniformLocation("farFogStart");
|
||||
int farFogEndUniform = shaderProgram.getUniformLocation("farFogEnd");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
|
||||
profiler.popPush("LOD draw");
|
||||
|
||||
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
|
||||
boolean renderBufferStorage = CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE && glProxy.bufferStorageSupported;
|
||||
|
||||
// where the center of the buffers is (needed when culling regions)
|
||||
int vboCenterRegionPosX = vbosCenterX;
|
||||
int vboCenterRegionPosZ = vbosCenterZ;
|
||||
int vboPosX;
|
||||
int vboPosZ;
|
||||
|
||||
|
||||
// render each of the buffers
|
||||
for (int x = 0; x < vbos.length; x++)
|
||||
{
|
||||
for (int z = 0; z < vbos.length; z++)
|
||||
{
|
||||
vboPosX = x + vboCenterRegionPosX - (lodDim.getWidth() / 2);
|
||||
vboPosZ = z + vboCenterRegionPosZ - (lodDim.getWidth() / 2);
|
||||
|
||||
// actual rendering
|
||||
int bufferId = 0;
|
||||
for (int i = 0; i < vbos[x][z].length; i++)
|
||||
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(),
|
||||
MC_RENDER.getLookAtVector(),
|
||||
LodUtil.convertLevelPos(LodUtil.REGION_DETAIL_LEVEL, vboPosX, LodUtil.BLOCK_DETAIL_LEVEL),
|
||||
LodUtil.convertLevelPos(LodUtil.REGION_DETAIL_LEVEL, vboPosZ, LodUtil.BLOCK_DETAIL_LEVEL)))
|
||||
{
|
||||
bufferId = (storageBufferIds != null && usingBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
|
||||
if (bufferId==0) continue;
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, bufferId);
|
||||
shaderProgram.bindVertexBuffer(bufferId);
|
||||
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbos[x][z][i].vertexCount);
|
||||
// fog may be different from region to region
|
||||
applyFog(shaderProgram,
|
||||
fogSettings, fogEnabledUniform, nearFogEnabledUniform, farFogEnabledUniform,
|
||||
nearFogStartUniform, nearFogEndUniform, farFogStartUniform, farFogEndUniform);
|
||||
|
||||
|
||||
// actual rendering
|
||||
int bufferId = 0;
|
||||
for (int i = 0; i < vbos[x][z].length; i++)
|
||||
{
|
||||
bufferId = (storageBufferIds != null && renderBufferStorage) ? storageBufferIds[x][z][i] : vbos[x][z][i].id;
|
||||
drawArrays(bufferId, vbos[x][z][i].vertexCount, posAttrib, colAttrib, blockLightAttrib, blockSkyLightAttrib);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GL20.glBindTexture(GL20.GL_TEXTURE_2D, 0);
|
||||
|
||||
//================//
|
||||
// render cleanup //
|
||||
//================//
|
||||
|
||||
// if this cleanup isn't done MC will crash
|
||||
// when trying to render its own terrain
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GL30.glBindVertexArray(0);
|
||||
|
||||
GL20.glDisableVertexAttribArray(posAttrib);
|
||||
GL20.glDisableVertexAttribArray(colAttrib);
|
||||
// GL20.glDisableVertexAttribArray(blockSkyLightAttrib);
|
||||
GL20.glDisableVertexAttribArray(blockLightAttrib);
|
||||
}
|
||||
|
||||
//================//
|
||||
// render cleanup //
|
||||
//================//
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
|
||||
profiler.popPush("LOD cleanup");
|
||||
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
|
||||
shaderProgram.unbind();
|
||||
lightmapTexture.free();
|
||||
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
||||
if (currentBlend)
|
||||
GL32.glEnable(GL32.GL_BLEND);
|
||||
else
|
||||
GL32.glDisable(GL32.GL_BLEND);
|
||||
|
||||
// if this cleanup isn't done MC will crash
|
||||
// when trying to render its own terrain
|
||||
// And may causes mod compat issue
|
||||
GL32.glUseProgram(currentProgram);
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, currentVBO);
|
||||
GL32.glBindVertexArray(currentVAO);
|
||||
GL32.glActiveTexture(currentActiveText);
|
||||
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
|
||||
GL15.glDisable(GL15.GL_BLEND); // TODO: what should this be reset to?
|
||||
|
||||
// clear the depth buffer so everything is drawn over the LODs
|
||||
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
|
||||
GL20.glUseProgram(currentProgram);
|
||||
|
||||
// clear the depth buffer so everything is drawn
|
||||
// over the LODs
|
||||
GL15.glClear(GL15.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
|
||||
|
||||
// end of internal LOD profiling
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
// Temporary variables James was using while working with the shader lightmap
|
||||
int[] testArray = null;
|
||||
int testInt = 0;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** This is where the actual drawing happens. */
|
||||
private void drawArrays(int glBufferId, int vertexCount, int posAttrib, int colAttrib, int blockLightAttrib, int blockSkyLightAttrib)
|
||||
{
|
||||
if (glBufferId == 0)
|
||||
return;
|
||||
|
||||
// can be used to check for OpenGL errors
|
||||
// int error = GL15.glGetError();
|
||||
// ClientApi.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) + Byte.BYTES + Byte.BYTES; // TODO move this into the template
|
||||
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);
|
||||
GL20.glEnableVertexAttribArray(blockLightAttrib);
|
||||
GL20.glVertexAttribPointer(blockLightAttrib, 1, GL15.GL_UNSIGNED_BYTE, false, vertexByteCount, Float.BYTES * (3 + 1));
|
||||
// GL20.glEnableVertexAttribArray(blockSkyLightAttrib);
|
||||
// GL20.glVertexAttribPointer(blockSkyLightAttrib, 1, GL15.GL_UNSIGNED_BYTE, false, vertexByteCount, Float.BYTES * (3 + 1 + 1));
|
||||
|
||||
|
||||
// draw the LODs
|
||||
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, vertexCount);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// Setup Functions //
|
||||
//=================//
|
||||
|
||||
/** Setup all render objects - REQUIRES to be in render thread */
|
||||
private void setup() {
|
||||
if (isSetupComplete) {
|
||||
ClientApi.LOGGER.warn("Renderer setup called but it has already completed setup!");
|
||||
return;
|
||||
}
|
||||
if (!GLProxy.hasInstance()) {
|
||||
ClientApi.LOGGER.warn("Renderer setup called but GLProxy has not yet been setup!");
|
||||
return;
|
||||
}
|
||||
|
||||
isSetupComplete = true;
|
||||
shaderProgram = new LodRenderProgram();
|
||||
}
|
||||
|
||||
|
||||
/** Create all buffers that will be used. */
|
||||
public void setupBuffers(LodDimension lodDim)
|
||||
{
|
||||
lodBufferBuilderFactory.setupBuffers(lodDim);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** Return what fog settings should be used when rendering. */
|
||||
private LodFogConfig determineFogConfig()
|
||||
{
|
||||
LodFogConfig fogConfig = new LodFogConfig();
|
||||
|
||||
|
||||
fogConfig.fogDrawMode = CONFIG.client().graphics().fogQuality().getFogDrawMode();
|
||||
if (fogConfig.fogDrawMode == FogDrawMode.USE_OPTIFINE_SETTING)
|
||||
fogConfig.fogDrawMode = REFLECTION_HANDLER.getFogDrawMode();
|
||||
|
||||
|
||||
// how different distances are drawn depends on the quality set
|
||||
fogConfig.fogDistance = CONFIG.client().graphics().fogQuality().getFogDistance();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// far fog //
|
||||
|
||||
if (CONFIG.client().graphics().fogQuality().getFogDistance() == FogDistance.NEAR_AND_FAR)
|
||||
fogConfig.farFogStart = farPlaneBlockDistance * 0.9f;
|
||||
else
|
||||
// for more realistic fog when using FAR
|
||||
fogConfig.farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f);
|
||||
|
||||
fogConfig.farFogEnd = farPlaneBlockDistance;
|
||||
|
||||
|
||||
// near fog //
|
||||
|
||||
// the reason that I wrote fogEnd then fogStart backwards
|
||||
// is because we are using fog backwards to how
|
||||
// it is normally used, hiding near objects
|
||||
// instead of far objects.
|
||||
fogConfig.nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
|
||||
fogConfig.nearFogStart = vanillaBlockRenderedDistance * 1.6f;
|
||||
|
||||
|
||||
return fogConfig;
|
||||
}
|
||||
|
||||
private Color getFogColor()
|
||||
{
|
||||
Color fogColor;
|
||||
@@ -397,10 +584,8 @@ public class LodRenderer
|
||||
// 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)
|
||||
//int bufferPosX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
//int bufferPosZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
int bufferPosX = vbosCenterX;
|
||||
int bufferPosZ = vbosCenterZ;
|
||||
int bufferPosX = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterX, LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
int bufferPosZ = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterZ, LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
double xDiff = projectedView.x - bufferPosX;
|
||||
double zDiff = projectedView.z - bufferPosZ;
|
||||
mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff);
|
||||
@@ -414,10 +599,8 @@ public class LodRenderer
|
||||
*/
|
||||
private Vec3f getTranslatedCameraPos()
|
||||
{
|
||||
//int worldCenterX = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterX, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
//int worldCenterZ = LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, vbosCenterZ, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
int worldCenterX = vbosCenterX;
|
||||
int worldCenterZ = vbosCenterZ;
|
||||
int worldCenterX = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterX, LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
int worldCenterZ = LevelPosUtil.convert(LodUtil.REGION_DETAIL_LEVEL, vbosCenterZ, LodUtil.CHUNK_DETAIL_LEVEL);
|
||||
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
|
||||
return new Vec3f((float)cameraPos.x - worldCenterX, (float)cameraPos.y, (float)cameraPos.z - worldCenterZ);
|
||||
}
|
||||
@@ -427,7 +610,7 @@ public class LodRenderer
|
||||
* @param currentProjectionMatrix this is Minecraft's current projection matrix
|
||||
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
|
||||
*/
|
||||
private static Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance, int farPlaneBlockDistance)
|
||||
private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance)
|
||||
{
|
||||
//Create a copy of the current matrix, so the current matrix isn't modified.
|
||||
Mat4f lodProj = currentProjectionMatrix.copy();
|
||||
@@ -440,37 +623,38 @@ public class LodRenderer
|
||||
return lodProj;
|
||||
}
|
||||
|
||||
//======================//
|
||||
// Cleanup Functions //
|
||||
//======================//
|
||||
|
||||
/** cleanup and free all render objects. REQUIRES to be in render thread
|
||||
* (Many objects are Native, outside of JVM, and need manual cleanup) */
|
||||
private void cleanup() {
|
||||
if (!isSetupComplete) {
|
||||
ClientApi.LOGGER.warn("Renderer cleanup called but Renderer has not completed setup!");
|
||||
return;
|
||||
}
|
||||
if (!GLProxy.hasInstance()) {
|
||||
ClientApi.LOGGER.warn("Renderer Cleanup called but the GLProxy has never been inited!");
|
||||
return;
|
||||
}
|
||||
isSetupComplete = false;
|
||||
ClientApi.LOGGER.info("Renderer Cleanup Started");
|
||||
shaderProgram.free();
|
||||
ClientApi.LOGGER.info("Renderer Cleanup Complete");
|
||||
}
|
||||
|
||||
/** Calls the BufferBuilder's destroyBuffers method. */
|
||||
public void destroyBuffers()
|
||||
private void applyFog(LodShaderProgram shaderProgram,
|
||||
LodFogConfig fogSettings, int fogEnabledUniform, int nearFogEnabledUniform, int farFogEnabledUniform,
|
||||
int nearFogStartUniform, int nearFogEndUniform, int farFogStartUniform, int farFogEndUniform)
|
||||
{
|
||||
lodBufferBuilderFactory.destroyBuffers();
|
||||
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED)
|
||||
{
|
||||
shaderProgram.setUniform(fogEnabledUniform, true);
|
||||
shaderProgram.setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
|
||||
shaderProgram.setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
|
||||
|
||||
// near
|
||||
shaderProgram.setUniform(nearFogStartUniform, fogSettings.nearFogStart);
|
||||
shaderProgram.setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
|
||||
// far
|
||||
shaderProgram.setUniform(farFogStartUniform, fogSettings.farFogStart);
|
||||
shaderProgram.setUniform(farFogEndUniform, fogSettings.farFogEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
shaderProgram.setUniform(fogEnabledUniform, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// Other Misc Functions //
|
||||
//======================//
|
||||
|
||||
|
||||
/**
|
||||
* If this is called then the next time "drawLODs" is called
|
||||
* the LODs will be regenerated; the same as if the player moved.
|
||||
@@ -494,10 +678,18 @@ public class LodRenderer
|
||||
VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers();
|
||||
vbos = result.vbos;
|
||||
storageBufferIds = result.storageBufferIds;
|
||||
vbosCenterX = result.drawableCenterBlockPosX;
|
||||
vbosCenterZ = result.drawableCenterBlockPosZ;
|
||||
vbosCenterX = result.drawableCenterChunkPosX;
|
||||
vbosCenterZ = result.drawableCenterChunkPosZ;
|
||||
}
|
||||
|
||||
/** Calls the BufferBuilder's destroyBuffers method. */
|
||||
public void destroyBuffers()
|
||||
{
|
||||
lodBufferBuilderFactory.destroyBuffers();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Determines if the LODs should have a fullRegen or partialRegen */
|
||||
private void determineIfLodsShouldRegenerate(LodDimension lodDim, float partialTicks)
|
||||
{
|
||||
@@ -546,10 +738,46 @@ public class LodRenderer
|
||||
prevPlayerPosTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// determine how far the lighting has to
|
||||
// change in order to rebuild the buffers
|
||||
|
||||
// the max brightness is 1 and the minimum is 0.2
|
||||
float skyBrightness = lodDim.dimension.hasSkyLight() ? MC.getSkyDarken(partialTicks) : 0.2f;
|
||||
float minLightingDifference;
|
||||
switch (CONFIG.client().advanced().buffers().getRebuildTimes())
|
||||
{
|
||||
case FREQUENT:
|
||||
minLightingDifference = 0.025f;
|
||||
break;
|
||||
case NORMAL:
|
||||
minLightingDifference = 0.05f;
|
||||
break;
|
||||
default:
|
||||
case RARE:
|
||||
minLightingDifference = 0.1f;
|
||||
break;
|
||||
}
|
||||
|
||||
// check if the lighting changed
|
||||
if (Math.abs(skyBrightness - prevSkyBrightness) > minLightingDifference
|
||||
// make sure the lighting gets to the max/minimum value
|
||||
// (just in case the minLightingDifference is too large to notice the change)
|
||||
|| (skyBrightness == 1.0f && prevSkyBrightness != 1.0f) // noon
|
||||
|| (skyBrightness == 0.2f && prevSkyBrightness != 0.2f) // midnight
|
||||
|| MC_RENDER.getGamma() != prevBrightness)
|
||||
{
|
||||
fullRegen = true;
|
||||
prevBrightness = MC_RENDER.getGamma();
|
||||
prevSkyBrightness = skyBrightness;
|
||||
}*/
|
||||
|
||||
//================//
|
||||
// partial regens //
|
||||
//================//
|
||||
|
||||
|
||||
// check if the vanilla rendered chunks changed
|
||||
if (newTime - prevVanillaChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().renderedChunkTimeout)
|
||||
{
|
||||
@@ -561,6 +789,7 @@ public class LodRenderer
|
||||
prevVanillaChunkTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
// check if there is any newly generated terrain to show
|
||||
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
|
||||
{
|
||||
@@ -572,6 +801,8 @@ public class LodRenderer
|
||||
prevChunkTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// LOD skipping //
|
||||
//==============//
|
||||
@@ -613,5 +844,5 @@ public class LodRenderer
|
||||
|
||||
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.seibel.lod.core.render.objects;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
public class LightmapTexture {
|
||||
public int id;
|
||||
|
||||
public LightmapTexture() {
|
||||
id = GL32.glGenTextures();
|
||||
bind();
|
||||
}
|
||||
|
||||
public void bind() {
|
||||
GL32.glBindTexture(GL32.GL_TEXTURE_2D, id);
|
||||
}
|
||||
public void unbind() {
|
||||
GL32.glBindTexture(GL32.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
GL32.glDeleteTextures(id);
|
||||
}
|
||||
|
||||
// private int[] testArray;
|
||||
|
||||
public void fillData(int lightMapWidth, int lightMapHeight, int[] pixels) {
|
||||
GL32.glDeleteTextures(id);
|
||||
id = GL32.glGenTextures();
|
||||
GL32.glBindTexture(GL32.GL_TEXTURE_2D, id);
|
||||
if (pixels.length != lightMapWidth*lightMapHeight)
|
||||
throw new RuntimeException("Lightmap Width*Height not equal to pixels provided!");
|
||||
|
||||
// comment me out to see when the lightmap is changing
|
||||
/*
|
||||
boolean same = true;
|
||||
int badIndex = 0;
|
||||
if (testArray != null && pixels != null)
|
||||
for (int i = 0; i < pixels.length; i++)
|
||||
{
|
||||
if(pixels[i] != testArray[i])
|
||||
{
|
||||
same = false;
|
||||
badIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
testArray = pixels;
|
||||
MC.sendChatMessage(same + " " + badIndex);
|
||||
*/
|
||||
// comment this line out to prevent uploading the new lightmap
|
||||
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA, lightMapWidth,
|
||||
lightMapHeight, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_BYTE, pixels);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_BORDER);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_BORDER);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST);
|
||||
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,169 +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.core.render.objects;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3d;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
|
||||
|
||||
/**
|
||||
* This object holds the reference to a OpenGL shader program
|
||||
* and contains a few methods that can be used with OpenGL shader programs.
|
||||
* The reason for many of these simple wrapper methods is as reminders of what
|
||||
* can (and needs to be) done with a shader program.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-26-2021
|
||||
*/
|
||||
public class ShaderProgram
|
||||
{
|
||||
/** Stores the handle of the program. */
|
||||
public final int id;
|
||||
|
||||
// TODO: A better way to set the fragData output name
|
||||
/** Creates a shader program.
|
||||
* This will bind ShaderProgram */
|
||||
public ShaderProgram(String vert, String frag, String fragDataOutputName)
|
||||
{
|
||||
Shader vertShader = new Shader(GL32.GL_VERTEX_SHADER, vert, false);
|
||||
Shader fragShader = new Shader(GL32.GL_FRAGMENT_SHADER, frag, false);
|
||||
|
||||
id = GL32.glCreateProgram();
|
||||
|
||||
GL32.glAttachShader(this.id, vertShader.id);
|
||||
GL32.glAttachShader(this.id, fragShader.id);
|
||||
//GL32.glBindFragDataLocation(id, 0, fragDataOutputName);
|
||||
GL32.glLinkProgram(this.id);
|
||||
|
||||
vertShader.free(); // important!
|
||||
fragShader.free(); // important!
|
||||
|
||||
int status = GL32.glGetProgrami(this.id, GL32.GL_LINK_STATUS);
|
||||
if (status != GL32.GL_TRUE) {
|
||||
String message = "Shader Link Error. Details: "+GL32.glGetProgramInfoLog(this.id);
|
||||
free(); // important!
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
GL32.glUseProgram(id); // This HAVE to be a direct call to prevent calling the overloaded version
|
||||
}
|
||||
|
||||
/** This will bind ShaderProgram */
|
||||
public void bind()
|
||||
{
|
||||
GL32.glUseProgram(id);
|
||||
}
|
||||
/** This will unbind ShaderProgram */
|
||||
public void unbind() {
|
||||
GL32.glUseProgram(0);
|
||||
}
|
||||
|
||||
// REMEMBER to always free the resource!
|
||||
public void free()
|
||||
{
|
||||
GL32.glDeleteProgram(id);
|
||||
}
|
||||
|
||||
/** WARNING: Slow native call! Cache it if possible!
|
||||
* Gets the location of an attribute variable with specified name.
|
||||
* Calls GL20.glGetAttribLocation(id, name)
|
||||
*
|
||||
* @param name Attribute name
|
||||
* @throws RuntimeException if attribute not found
|
||||
* @return Location of the attribute
|
||||
*/
|
||||
public int getAttributeLocation(CharSequence name)
|
||||
{
|
||||
int i = GL32.glGetAttribLocation(id, name);
|
||||
if (i==-1) throw new RuntimeException("Attribute name not found: "+name);
|
||||
return i;
|
||||
}
|
||||
|
||||
/** WARNING: Slow native call! Cache it if possible!
|
||||
* Gets the location of a uniform variable with specified name.
|
||||
* Calls GL20.glGetUniformLocation(id, name)
|
||||
*
|
||||
* @param name Uniform name
|
||||
* @throws RuntimeException if uniform not found
|
||||
* @return Location of the Uniform
|
||||
*/
|
||||
public int getUniformLocation(CharSequence name)
|
||||
{
|
||||
int i = GL32.glGetUniformLocation(id, name);
|
||||
if (i==-1) throw new RuntimeException("Uniform name not found: "+name);
|
||||
return i;
|
||||
}
|
||||
|
||||
/** Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, boolean value)
|
||||
{
|
||||
// This use -1 for false as that equals all one set
|
||||
GL32.glUniform1i(location, value ? 1 : 0);
|
||||
}
|
||||
|
||||
/** Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, int value)
|
||||
{
|
||||
GL32.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
/** Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, float value)
|
||||
{
|
||||
GL32.glUniform1f(location, value);
|
||||
}
|
||||
|
||||
/** Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, Vec3f value)
|
||||
{
|
||||
GL32.glUniform3f(location, value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
/** Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, Vec3d value)
|
||||
{
|
||||
GL32.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
|
||||
}
|
||||
|
||||
/** Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, Mat4f value)
|
||||
{
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
FloatBuffer buffer = stack.mallocFloat(4 * 4);
|
||||
value.store(buffer);
|
||||
GL32.glUniformMatrix4fv(location, false, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the color's RGBA values into values between 0 and 1.
|
||||
* Requires ShaderProgram binded. */
|
||||
public void setUniform(int location, Color value)
|
||||
{
|
||||
GL32.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2021 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.core.render.objects;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
public abstract class VertexAttribute {
|
||||
|
||||
public static final class VertexPointer {
|
||||
public final int elementCount;
|
||||
public final int glType;
|
||||
public final boolean normalized;
|
||||
public final int byteSize;
|
||||
public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize) {
|
||||
this.elementCount = elementCount;
|
||||
this.glType = glType;
|
||||
this.normalized = normalized;
|
||||
this.byteSize = byteSize;
|
||||
}
|
||||
public static VertexPointer addFloatPointer(boolean normalized) {
|
||||
return new VertexPointer(1, GL32.GL_FLOAT, normalized, 4);
|
||||
}
|
||||
public static VertexPointer addVec2Pointer(boolean normalized) {
|
||||
return new VertexPointer(2, GL32.GL_FLOAT, normalized, 8);
|
||||
}
|
||||
public static VertexPointer addVec3Pointer(boolean normalized) {
|
||||
return new VertexPointer(3, GL32.GL_FLOAT, normalized, 12);
|
||||
}
|
||||
public static VertexPointer addVec4Pointer(boolean normalized) {
|
||||
return new VertexPointer(1, GL32.GL_FLOAT, normalized, 16);
|
||||
}
|
||||
public static VertexPointer addUnsignedBytePointer(boolean normalized) {
|
||||
return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 1);
|
||||
}
|
||||
public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized) {
|
||||
return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, elementCount);
|
||||
}
|
||||
public static VertexPointer addIntPointer(boolean normalized) {
|
||||
return new VertexPointer(1, GL32.GL_INT, normalized, 4);
|
||||
}
|
||||
public static VertexPointer addIvec2Pointer(boolean normalized) {
|
||||
return new VertexPointer(2, GL32.GL_INT, normalized, 8);
|
||||
}
|
||||
public static VertexPointer addIvec3Pointer(boolean normalized) {
|
||||
return new VertexPointer(3, GL32.GL_INT, normalized, 12);
|
||||
}
|
||||
public static VertexPointer addIvec4Pointer(boolean normalized) {
|
||||
return new VertexPointer(4, GL32.GL_INT, normalized, 16);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Stores the handle of the VertexAttribute. */
|
||||
public final int id;
|
||||
|
||||
// This will bind VertexAttribute
|
||||
protected VertexAttribute() {
|
||||
id = GL32.glGenVertexArrays();
|
||||
GL32.glBindVertexArray(id);
|
||||
}
|
||||
|
||||
// This will bind VertexAttribute
|
||||
public void bind() {
|
||||
GL32.glBindVertexArray(id);
|
||||
}
|
||||
|
||||
// This will unbind VertexAttribute
|
||||
public void unbind() {
|
||||
GL32.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
// REMEMBER to always free the resource!
|
||||
public void free() {
|
||||
GL32.glDeleteVertexArrays(id);
|
||||
}
|
||||
|
||||
// Requires VertexAttribute binded, VertexBuffer binded
|
||||
public abstract void bindBufferToAllBindingPoint(int buffer);
|
||||
// Requires VertexAttribute binded, VertexBuffer binded
|
||||
public abstract void bindBufferToBindingPoint(int buffer, int bindingPoint);
|
||||
// Requires VertexAttribute binded
|
||||
public abstract void unbindBuffersFromAllBindingPoint();
|
||||
// Requires VertexAttribute binded
|
||||
public abstract void unbindBuffersFromBindingPoint(int bindingPoint);
|
||||
// Requires VertexAttribute binded
|
||||
public abstract void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute);
|
||||
// Requires VertexAttribute binded
|
||||
public abstract void completeAndCheck(int expectedStrideSize);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.seibel.lod.core.render.objects;
|
||||
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
|
||||
// In OpenGL 4.3 and later, Vertex Attribute got a make-over.
|
||||
// Now it provides support for buffer binding points natively.
|
||||
// This means that setting up the VAO just use ONE native call when
|
||||
// binding to a buffer.
|
||||
//
|
||||
// Since I no longer needs to implement binding points, I also no
|
||||
// longer needs to keep track of Pointers.
|
||||
|
||||
public final class VertexAttributePostGL43 extends VertexAttribute {
|
||||
|
||||
int numberOfBindingPoints = 0;
|
||||
int strideSize = 0;
|
||||
|
||||
// This will bind VertexAttribute
|
||||
public VertexAttributePostGL43() {
|
||||
super(); // also bind VertexAttribute
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded, VertexBuffer binded
|
||||
public void bindBufferToAllBindingPoint(int buffer) {
|
||||
for (int i=0; i<numberOfBindingPoints; i++)
|
||||
GL43.glBindVertexBuffer(i, buffer, 0, strideSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded, VertexBuffer binded
|
||||
public void bindBufferToBindingPoint(int buffer, int bindingPoint) {
|
||||
GL43.glBindVertexBuffer(bindingPoint, buffer, 0, strideSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void unbindBuffersFromAllBindingPoint() {
|
||||
for (int i=0; i<numberOfBindingPoints; i++)
|
||||
GL43.glBindVertexBuffer(i, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void unbindBuffersFromBindingPoint(int bindingPoint) {
|
||||
GL43.glBindVertexBuffer(bindingPoint, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
|
||||
GL43.glVertexAttribFormat(attributeIndex, attribute.elementCount, attribute.glType,
|
||||
attribute.normalized, strideSize); // Here strideSize is new attrib offset
|
||||
strideSize += attribute.byteSize;
|
||||
if (numberOfBindingPoints <= bindingPoint) numberOfBindingPoints = bindingPoint+1;
|
||||
GL43.glVertexAttribBinding(attributeIndex, bindingPoint);
|
||||
GL43.glEnableVertexAttribArray(attributeIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void completeAndCheck(int expectedStrideSize) {
|
||||
if (strideSize != expectedStrideSize) {
|
||||
ClientApi.LOGGER.error("Vertex Attribute calculated stride size " + strideSize +
|
||||
" does not match the provided expected stride size " + expectedStrideSize + "!");
|
||||
}
|
||||
ClientApi.LOGGER.info("Vertex Attribute (GL43+) completed. It contains "+numberOfBindingPoints
|
||||
+" binding points and a stride size of "+strideSize);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
package com.seibel.lod.core.render.objects;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
|
||||
|
||||
public final class VertexAttributePreGL43 extends VertexAttribute {
|
||||
|
||||
// I tried to use as much raw arrays as possible as those lookups
|
||||
// happens every frame, and the speed directly effects fps
|
||||
int strideSize = 0;
|
||||
int[][] bindingPointsToIndex;
|
||||
VertexPointer[] pointers;
|
||||
int[] pointersOffset;
|
||||
|
||||
|
||||
TreeMap<Integer, TreeSet<Integer>> bindingPointsToIndexBuilder;
|
||||
ArrayList<VertexPointer> pointersBuilder;
|
||||
|
||||
// This will bind VertexAttribute
|
||||
public VertexAttributePreGL43() {
|
||||
super(); // also bind VertexAttribute
|
||||
bindingPointsToIndexBuilder = new TreeMap<Integer, TreeSet<Integer>>();
|
||||
pointersBuilder = new ArrayList<VertexPointer>();
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded, VertexBuffer binded
|
||||
public void bindBufferToAllBindingPoint(int buffer) {
|
||||
for (int i=0; i<pointers.length; i++)
|
||||
GL32.glEnableVertexAttribArray(i);
|
||||
|
||||
for (int i=0; i< pointers.length; i++) {
|
||||
VertexPointer pointer = pointers[i];
|
||||
if (pointer==null) continue;
|
||||
GL32.glVertexAttribPointer(i, pointer.elementCount, pointer.glType,
|
||||
pointer.normalized, strideSize, pointersOffset[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded, VertexBuffer binded
|
||||
public void bindBufferToBindingPoint(int buffer, int bindingPoint) {
|
||||
int[] toBind = bindingPointsToIndex[bindingPoint];
|
||||
|
||||
for (int i=0; i<toBind.length; i++)
|
||||
GL32.glEnableVertexAttribArray(toBind[i]);
|
||||
|
||||
for (int i=0; i< toBind.length; i++) {
|
||||
VertexPointer pointer = pointers[toBind[i]];
|
||||
if (pointer==null) continue;
|
||||
GL32.glVertexAttribPointer(toBind[i], pointer.elementCount, pointer.glType,
|
||||
pointer.normalized, strideSize, pointersOffset[toBind[i]]);
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void unbindBuffersFromAllBindingPoint() {
|
||||
for (int i=0; i<pointers.length; i++)
|
||||
GL32.glDisableVertexAttribArray(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void unbindBuffersFromBindingPoint(int bindingPoint) {
|
||||
int[] toBind = bindingPointsToIndex[bindingPoint];
|
||||
|
||||
for (int i=0; i<toBind.length; i++)
|
||||
GL32.glDisableVertexAttribArray(toBind[i]);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) {
|
||||
TreeSet<Integer> intArray = bindingPointsToIndexBuilder.get(bindingPoint);
|
||||
if (intArray == null) {
|
||||
intArray = new TreeSet<Integer>();
|
||||
bindingPointsToIndexBuilder.put(bindingPoint, intArray);
|
||||
}
|
||||
intArray.add(attributeIndex);
|
||||
|
||||
while (pointersBuilder.size() <= attributeIndex) {
|
||||
// This is dumb, but ArrayList doesn't have a resize, And this code
|
||||
// should only be ran when it's building the Vertex Attribute anyways.
|
||||
pointersBuilder.add(null);
|
||||
}
|
||||
pointersBuilder.set(attributeIndex, attribute);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Requires VertexAttribute binded
|
||||
public void completeAndCheck(int expectedStrideSize) {
|
||||
int maxBindPointNumber = bindingPointsToIndexBuilder.lastKey();
|
||||
bindingPointsToIndex = new int[maxBindPointNumber+1][];
|
||||
|
||||
bindingPointsToIndexBuilder.forEach((Integer i, TreeSet<Integer> set) -> {
|
||||
bindingPointsToIndex[i] = new int[set.size()];
|
||||
Iterator<Integer> iter = set.iterator();
|
||||
for (int j = 0; j<set.size(); j++) {
|
||||
bindingPointsToIndex[i][j] = iter.next();
|
||||
}
|
||||
});
|
||||
|
||||
pointers = pointersBuilder.toArray(new VertexPointer[pointersBuilder.size()]);
|
||||
pointersOffset = new int[pointers.length];
|
||||
pointersBuilder = null; // Release the builder
|
||||
bindingPointsToIndexBuilder = null; // Release the builder
|
||||
|
||||
// Check if all pointers are valid
|
||||
int currentOffset = 0;
|
||||
for (int i = 0; i < pointers.length; i++) {
|
||||
VertexPointer pointer = pointers[i];
|
||||
if (pointer == null) {
|
||||
ClientApi.LOGGER.warn("Vertex Attribute index "+i+" is not set! No index should be skipped normally!");
|
||||
continue;
|
||||
}
|
||||
pointersOffset[i] = currentOffset;
|
||||
currentOffset += pointer.byteSize;
|
||||
}
|
||||
if (currentOffset != expectedStrideSize)
|
||||
ClientApi.LOGGER.error("Vertex Attribute calculated stride size " + currentOffset +
|
||||
" does not match the provided expected stride size " + expectedStrideSize + "!");
|
||||
strideSize = currentOffset;
|
||||
ClientApi.LOGGER.info("Vertex Attribute (pre GL43) completed.");
|
||||
|
||||
// Debug logging
|
||||
ClientApi.LOGGER.info("Vertex Attribute Debug Data:");
|
||||
ClientApi.LOGGER.info("AttributeIndex: ElementCount, glType, normalized, strideSize, offset");
|
||||
|
||||
for (int i=0; i< pointers.length; i++) {
|
||||
VertexPointer pointer = pointers[i];
|
||||
if (pointer==null) {
|
||||
ClientApi.LOGGER.warn(i + ": Null!!!!");
|
||||
continue;
|
||||
}
|
||||
ClientApi.LOGGER.info(i + ": "+pointer.elementCount+", "+
|
||||
pointer.glType+", "+pointer.normalized+", "+strideSize+", "+pointersOffset[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+54
-34
@@ -2,7 +2,7 @@
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2021 James Seibel
|
||||
* 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
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.render.objects;
|
||||
package com.seibel.lod.core.render.shader;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
@@ -26,9 +26,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
/**
|
||||
* This object holds a OpenGL reference to a shader
|
||||
@@ -37,42 +35,31 @@ import com.seibel.lod.core.api.ClientApi;
|
||||
* @author James Seibel
|
||||
* @version 11-8-2021
|
||||
*/
|
||||
public class Shader
|
||||
public class LodShader
|
||||
{
|
||||
/** OpenGL shader ID */
|
||||
public final int id;
|
||||
|
||||
/** Creates a shader with specified type.
|
||||
|
||||
|
||||
/** 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 RuntimeException if the shader fails to compile
|
||||
* @throws Exception if the shader fails to compile
|
||||
*/
|
||||
public Shader(int type, String path, boolean absoluteFilePath)
|
||||
public static LodShader loadShader(int type, String path, boolean absoluteFilePath)
|
||||
{
|
||||
ClientApi.LOGGER.info("Loading shader at "+path);
|
||||
// Create an empty shader object
|
||||
id = GL32.glCreateShader(type);
|
||||
StringBuilder source = loadFile(path, absoluteFilePath);
|
||||
GL32.glShaderSource(id, source);
|
||||
|
||||
GL32.glCompileShader(id);
|
||||
// check if the shader compiled
|
||||
int status = GL32.glGetShaderi(id, GL32.GL_COMPILE_STATUS);
|
||||
if (status != GL32.GL_TRUE) {
|
||||
String message = "Shader compiler error. Details: "+GL32.glGetShaderInfoLog(id);
|
||||
free(); // important!
|
||||
throw new RuntimeException(message);
|
||||
}
|
||||
ClientApi.LOGGER.info("Shader at "+path+" loaded sucessfully.");
|
||||
}
|
||||
|
||||
// REMEMBER to always free the resource!
|
||||
public void free() {
|
||||
GL32.glDeleteShader(id);
|
||||
}
|
||||
|
||||
private StringBuilder loadFile(String path, boolean absoluteFilePath) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
|
||||
try
|
||||
@@ -83,7 +70,7 @@ public class Shader
|
||||
// Throws FileNotFoundException
|
||||
in = new FileInputStream(path); // Note: this should use OS path seperator
|
||||
} else {
|
||||
in = Shader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
|
||||
in = LodShader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
|
||||
if (in == null) {
|
||||
throw new FileNotFoundException("Shader file not found in resource: "+path);
|
||||
}
|
||||
@@ -99,6 +86,39 @@ public class Shader
|
||||
{
|
||||
throw new RuntimeException("Unable to load shader from file [" + path + "]. Error: " + e.getMessage());
|
||||
}
|
||||
return stringBuilder;
|
||||
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)
|
||||
{
|
||||
LodShader shader = new LodShader(type);
|
||||
GL20.glShaderSource(shader.id, source);
|
||||
shader.compile();
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles the shader and checks its status afterwards.
|
||||
* @throws Exception if the shader fails to compile
|
||||
*/
|
||||
public void compile()
|
||||
{
|
||||
GL20.glCompileShader(id);
|
||||
|
||||
// check if the shader compiled
|
||||
int status = GL20.glGetShaderi(id, GL20.GL_COMPILE_STATUS);
|
||||
if (status != GL20.GL_TRUE)
|
||||
throw new RuntimeException("Shader compiler error. Details: "+GL20.glGetShaderInfoLog(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.render.shader;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3d;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
|
||||
|
||||
/**
|
||||
* This object holds the reference to a OpenGL shader program
|
||||
* and contains a few methods that can be used with OpenGL shader programs.
|
||||
* The reason for many of these simple wrapper methods is as reminders of what
|
||||
* can (and needs to be) done with a shader program.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-26-2021
|
||||
*/
|
||||
public class LodShaderProgram
|
||||
{
|
||||
/** Stores the handle of the program. */
|
||||
public final int id;
|
||||
|
||||
/** Creates a shader program. */
|
||||
public LodShaderProgram()
|
||||
{
|
||||
id = GL20.glCreateProgram();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** Calls GL20.glUseProgram(this.id) */
|
||||
public void use()
|
||||
{
|
||||
GL20.glUseProgram(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls GL20.glAttachShader(this.id, shader.id)
|
||||
*
|
||||
* @param shader Shader to get attached
|
||||
*/
|
||||
public void attachShader(LodShader shader)
|
||||
{
|
||||
GL20.glAttachShader(this.id, shader.id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Links the shader program to the current OpenGL context.
|
||||
* @throws Exception Exception if the program failed to link
|
||||
*/
|
||||
public void link()
|
||||
{
|
||||
GL20.glLinkProgram(this.id);
|
||||
checkLinkStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the program was linked successfully.
|
||||
* @throws Exception if the program failed to link
|
||||
*/
|
||||
public void checkLinkStatus()
|
||||
{
|
||||
int status = GL20.glGetProgrami(this.id, GL20.GL_LINK_STATUS);
|
||||
if (status != GL20.GL_TRUE)
|
||||
throw new RuntimeException("Shader Link Error. Details: "+GL20.glGetProgramInfoLog(this.id));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Gets the location of an attribute variable with specified name.
|
||||
* Calls GL20.glGetAttribLocation(id, name)
|
||||
*
|
||||
* @param name Attribute name
|
||||
*
|
||||
* @return Location of the attribute
|
||||
*/
|
||||
public int getAttributeLocation(CharSequence name)
|
||||
{
|
||||
return GL20.glGetAttribLocation(id, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls GL20.glEnableVertexAttribArray(location)
|
||||
*
|
||||
* @param location Location of the vertex attribute
|
||||
*/
|
||||
public void enableVertexAttribute(int location)
|
||||
{
|
||||
GL20.glEnableVertexAttribArray(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls GL20.glDisableVertexAttribArray(location)
|
||||
*
|
||||
* @param location Location of the vertex attribute
|
||||
*/
|
||||
public void disableVertexAttribute(int location)
|
||||
{
|
||||
GL20.glDisableVertexAttribArray(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the vertex attribute pointer.
|
||||
* Calls GL20.glVertexAttribPointer(...)
|
||||
*
|
||||
* @param location Location of the vertex attribute
|
||||
* @param size Number of values per vertex
|
||||
* @param stride Offset between consecutive generic vertex attributes in
|
||||
* bytes
|
||||
* @param offset Offset of the first component of the first generic vertex
|
||||
* attribute in bytes
|
||||
*/
|
||||
public void pointVertexAttribute(int location, int size, int stride, int offset)
|
||||
{
|
||||
GL20.glVertexAttribPointer(location, size, GL20.GL_FLOAT, false, stride, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the location of a uniform variable with specified name.
|
||||
* Calls GL20.glGetUniformLocation(id, name)
|
||||
*
|
||||
* @param name Uniform name
|
||||
*
|
||||
* @return -1 = error value, 0 = first value, 1 = second value, etc.
|
||||
*/
|
||||
public int getUniformLocation(CharSequence name)
|
||||
{
|
||||
return GL20.glGetUniformLocation(id, name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setUniform(int location, boolean value)
|
||||
{
|
||||
GL20.glUniform1i(location, value ? 1 : 0);
|
||||
}
|
||||
|
||||
public void setUniform(int location, int value)
|
||||
{
|
||||
GL20.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
public void setUniform(int location, float value)
|
||||
{
|
||||
GL20.glUniform1f(location, value);
|
||||
}
|
||||
|
||||
public void setUniform(int location, Vec3f value)
|
||||
{
|
||||
GL20.glUniform3f(location, value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
public void setUniform(int location, Vec3d value)
|
||||
{
|
||||
GL20.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
|
||||
}
|
||||
|
||||
public void setUniform(int location, Mat4f value)
|
||||
{
|
||||
try (MemoryStack stack = MemoryStack.stackPush())
|
||||
{
|
||||
FloatBuffer buffer = stack.mallocFloat(4 * 4);
|
||||
value.store(buffer);
|
||||
GL20.glUniformMatrix4fv(location, false, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the color's RGBA values into values between 0 and 1. */
|
||||
public void setUniform(int location, Color value)
|
||||
{
|
||||
GL20.glUniform4f(location, value.getRed() / 256.0f, value.getGreen() / 256.0f, value.getBlue() / 256.0f, value.getAlpha() / 256.0f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactor
|
||||
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Leonardo Amato
|
||||
@@ -103,9 +102,6 @@ public class DataPointUtil
|
||||
public final static long VOID_MASK = 1;
|
||||
public final static long EXISTENCE_MASK = 1;
|
||||
|
||||
public final static long HEIGHT_SHIFTED_MASK = HEIGHT_MASK << HEIGHT_SHIFT;
|
||||
public final static long DEPTH_SHIFTED_MASK = DEPTH_MASK << DEPTH_SHIFT;
|
||||
|
||||
|
||||
public static long createVoidDataPoint(int generationMode)
|
||||
{
|
||||
@@ -144,12 +140,6 @@ public class DataPointUtil
|
||||
return dataPoint;
|
||||
}
|
||||
|
||||
public static long shiftHeightAndDepth(long dataPoint, short offset) {
|
||||
long height = (dataPoint + (offset << HEIGHT_SHIFT)) & HEIGHT_SHIFTED_MASK;
|
||||
long depth = (dataPoint + (offset << DEPTH_SHIFT)) & DEPTH_SHIFTED_MASK;
|
||||
return dataPoint & ~(HEIGHT_SHIFTED_MASK | DEPTH_SHIFTED_MASK) | height | depth;
|
||||
}
|
||||
|
||||
public static short getHeight(long dataPoint)
|
||||
{
|
||||
return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK);
|
||||
@@ -221,8 +211,7 @@ public class DataPointUtil
|
||||
|
||||
public static int getColor(long dataPoint)
|
||||
{
|
||||
// TODO re-add transparency by replacing the color 255 with what is in comment
|
||||
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111) << 24);
|
||||
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
|
||||
}
|
||||
|
||||
/** This is used to convert a dataPoint to string (useful for the print function) */
|
||||
@@ -274,18 +263,19 @@ public class DataPointUtil
|
||||
* @param maxVerticalData max vertical size of the merged data
|
||||
* @return one column of correctly parsed data
|
||||
*/
|
||||
@Deprecated
|
||||
public static long[] mergeMultiData(long[] dataToMerge, int inputVerticalData, int maxVerticalData)
|
||||
{
|
||||
int size = dataToMerge.length / inputVerticalData;
|
||||
|
||||
// We initialize the arrays that are going to be used
|
||||
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT + 1) * 2);
|
||||
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2);
|
||||
long[] dataPoint = ThreadMapUtil.getVerticalDataArray(DetailDistanceUtil.getMaxVerticalData(0));
|
||||
|
||||
|
||||
int genMode = DistanceGenerationMode.FULL.complexity;
|
||||
boolean allEmpty = true;
|
||||
boolean allVoid = true;
|
||||
boolean limited = false;
|
||||
boolean allDefault;
|
||||
long singleData;
|
||||
|
||||
@@ -299,139 +289,115 @@ public class DataPointUtil
|
||||
//We collect the indexes of the data, ordered by the depth
|
||||
for (int index = 0; index < size; index++)
|
||||
{
|
||||
if (index == 0)
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
{
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
singleData = dataToMerge[index * inputVerticalData + dataIndex];
|
||||
if (doesItExist(singleData))
|
||||
{
|
||||
singleData = dataToMerge[dataIndex];
|
||||
if (doesItExist(singleData))
|
||||
genMode = Math.min(genMode, getGenerationMode(singleData));
|
||||
allEmpty = false;
|
||||
if (!isVoid(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++)
|
||||
{
|
||||
allVoid = false;
|
||||
count++;
|
||||
heightAndDepth[dataIndex * 2] = getHeight(singleData);
|
||||
heightAndDepth[dataIndex * 2 +1] = getDepth(singleData);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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))
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
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 (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
|
||||
{
|
||||
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;
|
||||
}
|
||||
topPos = i;
|
||||
break;
|
||||
}
|
||||
for (i = 0; i < count; i++)
|
||||
else if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
|
||||
{
|
||||
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;
|
||||
}
|
||||
topPos = i;
|
||||
topExtend = true;
|
||||
break;
|
||||
}
|
||||
if (topPos == -1)
|
||||
}
|
||||
if (topPos == -1)
|
||||
{
|
||||
if (botPos == -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;
|
||||
}
|
||||
//whole block falls above
|
||||
extendArray(heightAndDepth, 2, 0, 1, count);
|
||||
heightAndDepth[0] = height;
|
||||
heightAndDepth[1] = depth;
|
||||
count++;
|
||||
}
|
||||
else if (!topExtend)
|
||||
else if (!botExtend)
|
||||
{
|
||||
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;
|
||||
//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
|
||||
{
|
||||
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;
|
||||
}
|
||||
//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;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,8 +414,7 @@ public class DataPointUtil
|
||||
int j = 0;
|
||||
while (count > maxVerticalData)
|
||||
{
|
||||
limited = true;
|
||||
ii = WORLD_HEIGHT;
|
||||
ii = WORLD_HEIGHT - VERTICAL_OFFSET;
|
||||
for (i = 0; i < count - 1; i++)
|
||||
{
|
||||
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
|
||||
@@ -468,104 +433,88 @@ public class DataPointUtil
|
||||
count--;
|
||||
}
|
||||
//As standard the vertical lods are ordered from top to bottom
|
||||
if (!limited && size == 1)
|
||||
for (j = count - 1; j >= 0; j--)
|
||||
{
|
||||
for (j = 0; j < count; j++)
|
||||
dataPoint[j] = dataToMerge[j];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (j = 0; j < count; j++)
|
||||
height = heightAndDepth[j * 2];
|
||||
depth = heightAndDepth[j * 2 + 1];
|
||||
|
||||
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
|
||||
break;
|
||||
|
||||
int numberOfChildren = 0;
|
||||
int tempAlpha = 0;
|
||||
int tempRed = 0;
|
||||
int tempGreen = 0;
|
||||
int tempBlue = 0;
|
||||
int tempLightBlock = 0;
|
||||
int tempLightSky = 0;
|
||||
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
|
||||
allEmpty = true;
|
||||
allVoid = true;
|
||||
allDefault = true;
|
||||
long data = 0;
|
||||
|
||||
for (int index = 0; index < size; index++)
|
||||
{
|
||||
height = heightAndDepth[j * 2];
|
||||
depth = heightAndDepth[j * 2 + 1];
|
||||
|
||||
if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2)
|
||||
break;
|
||||
|
||||
int numberOfChildren = 0;
|
||||
int tempAlpha = 0;
|
||||
int tempRed = 0;
|
||||
int tempGreen = 0;
|
||||
int tempBlue = 0;
|
||||
int tempLightBlock = 0;
|
||||
int tempLightSky = 0;
|
||||
byte tempGenMode = DistanceGenerationMode.FULL.complexity;
|
||||
allEmpty = true;
|
||||
allVoid = true;
|
||||
allDefault = true;
|
||||
long data = 0;
|
||||
|
||||
for (int index = 0; index < size; index++)
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
{
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
singleData = dataToMerge[index * inputVerticalData + dataIndex];
|
||||
if (doesItExist(singleData) && !isVoid(singleData))
|
||||
{
|
||||
singleData = dataToMerge[index * inputVerticalData + dataIndex];
|
||||
if (doesItExist(singleData) && !isVoid(singleData))
|
||||
|
||||
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|
||||
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
|
||||
{
|
||||
if ((depth <= getDepth(singleData) && getDepth(singleData) < height)
|
||||
|| (depth < getHeight(singleData) && getHeight(singleData) <= height))
|
||||
{
|
||||
if (getHeight(singleData) > getHeight(data))
|
||||
data = singleData;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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);
|
||||
break;
|
||||
}
|
||||
if (!doesItExist(data))
|
||||
{
|
||||
singleData = dataToMerge[index * inputVerticalData];
|
||||
data = createVoidDataPoint(getGenerationMode(singleData));
|
||||
}
|
||||
|
||||
if (allEmpty)
|
||||
//no child has been initialized
|
||||
dataPoint[j] = EMPTY_DATA;
|
||||
else if (allVoid)
|
||||
//all the children are void
|
||||
dataPoint[j] = createVoidDataPoint(tempGenMode);
|
||||
else
|
||||
if (doesItExist(data))
|
||||
{
|
||||
//we have at least 1 child
|
||||
if (size != 1)
|
||||
allEmpty = false;
|
||||
if (!isVoid(data))
|
||||
{
|
||||
tempAlpha = tempAlpha / numberOfChildren;
|
||||
tempRed = tempRed / numberOfChildren;
|
||||
tempGreen = tempGreen / numberOfChildren;
|
||||
tempBlue = tempBlue / numberOfChildren;
|
||||
tempLightBlock = tempLightBlock / numberOfChildren;
|
||||
tempLightSky = tempLightSky / numberOfChildren;
|
||||
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;
|
||||
}
|
||||
//data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
|
||||
//if (j > 0 && getColor(data) == getColor(dataPoint[j]))
|
||||
//{
|
||||
// add simplification at the end due to color
|
||||
//}
|
||||
dataPoint[j] = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault);
|
||||
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;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
package com.seibel.lod.core.util;
|
||||
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
import com.seibel.lod.core.enums.config.HorizontalQuality;
|
||||
import com.seibel.lod.core.enums.config.HorizontalResolution;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
@@ -133,14 +134,10 @@ public class DetailDistanceUtil
|
||||
return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true);
|
||||
}
|
||||
|
||||
|
||||
// NOTE: The recent LodWorldGenerator changes assumes that this value doesn't change with 'detail'.
|
||||
// If this is changed, LodWorldGenerator needs to be fixed!
|
||||
/*
|
||||
public static DistanceGenerationMode getDistanceGenerationMode(int detail)
|
||||
{
|
||||
return CONFIG.client().worldGenerator().getDistanceGenerationMode();
|
||||
}*/
|
||||
}
|
||||
|
||||
public static byte getLodDrawDetail(byte detail)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.HashSet;
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.config.HorizontalResolution;
|
||||
import com.seibel.lod.core.enums.config.VanillaOverdraw;
|
||||
import com.seibel.lod.core.handlers.IReflectionHandler;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.objects.lod.RegionPos;
|
||||
@@ -45,7 +44,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
* This class holds methods and constants that may be used in multiple places.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-14-2021
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public class LodUtil
|
||||
{
|
||||
@@ -53,7 +52,6 @@ public class LodUtil
|
||||
private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class);
|
||||
private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class);
|
||||
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
private static final IReflectionHandler REFLECTION_HANDLER = SingletonHandler.get(IReflectionHandler.class);
|
||||
|
||||
/**
|
||||
* Vanilla render distances less than or equal to this will not allow partial
|
||||
@@ -344,17 +342,18 @@ public class LodUtil
|
||||
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
|
||||
// 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
|
||||
// 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;
|
||||
@@ -370,7 +369,7 @@ public class LodUtil
|
||||
|
||||
|
||||
// get the chunks that are going to be rendered by Minecraft
|
||||
HashSet<AbstractChunkPosWrapper> posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks();
|
||||
HashSet<AbstractChunkPosWrapper> posToSkip = MC_RENDER.getRenderedChunks();
|
||||
|
||||
|
||||
// remove everything outside the skipRadius,
|
||||
@@ -383,9 +382,7 @@ public class LodUtil
|
||||
{
|
||||
if (x <= centerChunk.getX() - skipRadius || x >= centerChunk.getX() + skipRadius
|
||||
|| z <= centerChunk.getZ() - skipRadius || z >= centerChunk.getZ() + skipRadius)
|
||||
{
|
||||
posToSkip.remove(FACTORY.createChunkPos(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,6 @@ import java.util.concurrent.ConcurrentMap;
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
|
||||
// FIXME: Nuke this whole thing and use ThreadLocal instead. And no more redundant get() please!
|
||||
|
||||
/**
|
||||
* Holds data used by specific threads so
|
||||
* the data doesn't have to be recreated every
|
||||
@@ -44,6 +42,7 @@ public class ThreadMapUtil
|
||||
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<>();
|
||||
@@ -118,8 +117,7 @@ public class ThreadMapUtil
|
||||
//________________________//
|
||||
|
||||
|
||||
|
||||
//TODO: Maybe use actual valid total world height instead of always assuming the worse and alloc 1024 blocks.
|
||||
|
||||
/** returns the array filled with 0's */
|
||||
public static long[] getBuilderVerticalArray(int detailLevel)
|
||||
{
|
||||
@@ -138,22 +136,41 @@ public class ThreadMapUtil
|
||||
return threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel];
|
||||
}
|
||||
|
||||
/** returns the array NOT cleared every time */
|
||||
public static byte[] getSaveContainer(int detailLevel)
|
||||
{
|
||||
if (!saveContainer.containsKey(Thread.currentThread().getName()) || (saveContainer.get(Thread.currentThread().getName()) == null))
|
||||
{
|
||||
byte[][] array = new byte[LodUtil.DETAIL_OPTIONS][];
|
||||
int size = 1;
|
||||
for (int i = LodUtil.DETAIL_OPTIONS - 1; i >= 0; i--)
|
||||
{
|
||||
array[i] = new byte[2 + 8 * size * size * DetailDistanceUtil.getMaxVerticalData(i)];
|
||||
size = size << 1;
|
||||
}
|
||||
saveContainer.put(Thread.currentThread().getName(), array);
|
||||
}
|
||||
//Arrays.fill(threadBuilderVerticalArrayMap.get(Thread.currentThread().getName())[detailLevel], 0);
|
||||
return saveContainer.get(Thread.currentThread().getName())[detailLevel];
|
||||
}
|
||||
|
||||
|
||||
/** returns the array filled with 0's */
|
||||
public static long[] getVerticalDataArray(int arrayLength)
|
||||
{
|
||||
long[] array = threadVerticalAddDataMap.get(Thread.currentThread().getName());
|
||||
if (array == null || array.length != arrayLength)
|
||||
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
|
||||
{
|
||||
array = new long[arrayLength];
|
||||
threadVerticalAddDataMap.put(Thread.currentThread().getName(), array);
|
||||
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
|
||||
}
|
||||
else
|
||||
Arrays.fill(array, 0);
|
||||
return array;
|
||||
{
|
||||
Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
|
||||
}
|
||||
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
|
||||
//FIXME: If the arrayLength change, this may return incorrect sized array
|
||||
|
||||
|
||||
/** returns the array NOT cleared every time */
|
||||
public static short[] getHeightAndDepth(int arrayLength)
|
||||
{
|
||||
@@ -168,19 +185,18 @@ public class ThreadMapUtil
|
||||
/** returns the array filled with 0's */
|
||||
public static long[] getVerticalUpdateArray(int detailLevel)
|
||||
{
|
||||
long[][] arrays = verticalUpdate.get(Thread.currentThread().getName());
|
||||
if (arrays == null)
|
||||
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
|
||||
{
|
||||
arrays = new long[LodUtil.DETAIL_OPTIONS][];
|
||||
verticalUpdate.put(Thread.currentThread().getName(), arrays);
|
||||
long[][] array = new long[LodUtil.DETAIL_OPTIONS][];
|
||||
for (int i = 1; i < LodUtil.DETAIL_OPTIONS; i++)
|
||||
array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
|
||||
verticalUpdate.put(Thread.currentThread().getName(), array);
|
||||
}
|
||||
long[] array = arrays[detailLevel];
|
||||
int arrayLength = DetailDistanceUtil.getMaxVerticalData(detailLevel) * 4;
|
||||
if (array == null || array.length != arrayLength)
|
||||
array = new long[arrayLength];
|
||||
else
|
||||
Arrays.fill(array, 0);
|
||||
return array;
|
||||
{
|
||||
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 */
|
||||
@@ -193,6 +209,7 @@ public class ThreadMapUtil
|
||||
threadBuilderArrayMap.clear();
|
||||
threadBuilderVerticalArrayMap.clear();
|
||||
threadVerticalAddDataMap.clear();
|
||||
saveContainer.clear();
|
||||
projectionArrayMap.clear();
|
||||
heightAndDepthMap.clear();
|
||||
singleDataToMergeMap.clear();
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.seibel.lod.core.wrapperInterfaces;
|
||||
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
|
||||
/**
|
||||
* A singleton that contains variables specific to each version of Minecraft
|
||||
* which can be used to change how DH-Core runs.
|
||||
* For example: After MC 1.17 blocks can be negative, which changes how we generate LODs.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-11-2021
|
||||
*/
|
||||
public interface IVersionConstants
|
||||
{
|
||||
/** @returns the minimum height blocks can be generated */
|
||||
int getMinimumWorldHeight();
|
||||
|
||||
|
||||
/**
|
||||
* @Returns True if the given DistanceGenerationMode can be run on our own thread. <br>
|
||||
* False if the generation must be run on Minecraft's server thread.
|
||||
*/
|
||||
boolean isWorldGeneratorSingleThreaded(DistanceGenerationMode distanceGenerationMode);
|
||||
|
||||
/**
|
||||
* @Returns the number of generations call per thread.
|
||||
*/
|
||||
default int getWorldGenerationCountPerThread() {
|
||||
return 8;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,14 +24,13 @@ import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractExperimentalWorldGeneratorWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.worldGeneration.AbstractWorldGeneratorWrapper;
|
||||
|
||||
/**
|
||||
* This handles creating abstract wrapper objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-14-2021
|
||||
* @version 11-18-2021
|
||||
*/
|
||||
public interface IWrapperFactory
|
||||
{
|
||||
@@ -40,21 +39,10 @@ public interface IWrapperFactory
|
||||
|
||||
|
||||
AbstractChunkPosWrapper createChunkPos();
|
||||
public default AbstractChunkPosWrapper createChunkPos(long xAndZPositionCombined)
|
||||
{
|
||||
int x = (int) (xAndZPositionCombined & Integer.MAX_VALUE);
|
||||
int z = (int) (xAndZPositionCombined >> Long.SIZE / 2) & Integer.MAX_VALUE;
|
||||
|
||||
return createChunkPos(x, z);
|
||||
}
|
||||
AbstractChunkPosWrapper createChunkPos(int x, int z);
|
||||
AbstractChunkPosWrapper createChunkPos(AbstractChunkPosWrapper newChunkPos);
|
||||
AbstractChunkPosWrapper createChunkPos(AbstractBlockPosWrapper blockPos);
|
||||
|
||||
|
||||
AbstractWorldGeneratorWrapper createWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper);
|
||||
// Return null to signal that there is no AbstractWorldGenerator
|
||||
public default AbstractExperimentalWorldGeneratorWrapper createExperimentalWorldGenerator(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockColorWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockShapeWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
import com.seibel.lod.forge.wrappers.block.BlockShapeWrapper;
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
/**
|
||||
* @author James Seibel
|
||||
|
||||
+27
-39
@@ -26,6 +26,8 @@ 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;
|
||||
@@ -34,7 +36,6 @@ import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.objects.MinDefaultMax;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
|
||||
|
||||
/**
|
||||
* This holds the config defaults, setters/getters
|
||||
@@ -42,7 +43,7 @@ import com.seibel.lod.core.wrapperInterfaces.IVersionConstants;
|
||||
* the options that should be implemented in a configWrapperSingleton.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-14-2021
|
||||
* @version 12-1-2021
|
||||
*/
|
||||
public interface ILodConfigWrapperSingleton
|
||||
{
|
||||
@@ -121,12 +122,12 @@ public interface ILodConfigWrapperSingleton
|
||||
|
||||
HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM;
|
||||
String HORIZONTAL_QUALITY_DESC = ""
|
||||
+ " This indicates how much farther away each drop in quality is. \n"
|
||||
+ " This indicates the exponential base of the quadratic drop-off \n"
|
||||
+ "\n"
|
||||
+ " " + HorizontalQuality.LOWEST + ": each drop in quality is the same distance away. \n"
|
||||
+ " " + HorizontalQuality.LOW + ": each drop in quality is " + HorizontalQuality.LOW.quadraticBase + " times farther away. \n"
|
||||
+ " " + HorizontalQuality.MEDIUM + ": each drop in quality is " + HorizontalQuality.MEDIUM.quadraticBase + " times farther away. \n"
|
||||
+ " " + HorizontalQuality.HIGH + ": each drop in quality is " + HorizontalQuality.HIGH.quadraticBase + " times farther away. \n"
|
||||
+ " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n"
|
||||
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
|
||||
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
|
||||
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n"
|
||||
+ "\n"
|
||||
+ " Lowest Quality: " + HorizontalQuality.LOWEST
|
||||
+ " Highest Quality: " + HorizontalQuality.HIGH;
|
||||
@@ -183,6 +184,7 @@ public interface ILodConfigWrapperSingleton
|
||||
{
|
||||
String DESC = "Graphics options that are a bit more technical.";
|
||||
|
||||
|
||||
boolean DISABLE_DIRECTIONAL_CULLING_DEFAULT = false;
|
||||
String DISABLE_DIRECTIONAL_CULLING_DESC = ""
|
||||
+ " If false fake chunks behind the player's camera \n"
|
||||
@@ -213,12 +215,12 @@ public interface ILodConfigWrapperSingleton
|
||||
+ " 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 some holes in the world. \n"
|
||||
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks preventing only some holes in the world. \n"
|
||||
+ " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n"
|
||||
+ " " + " More effective on higher render distances. \n"
|
||||
+ " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n"
|
||||
+ " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " will be used depending on the dimension. \n"
|
||||
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing all holes in the world. \n"
|
||||
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n"
|
||||
+ "\n"
|
||||
+ " This setting shouldn't affect performance. \n";
|
||||
VanillaOverdraw getVanillaOverdraw();
|
||||
@@ -270,16 +272,9 @@ public interface ILodConfigWrapperSingleton
|
||||
void setGenerationPriority(GenerationPriority newGenerationPriority);
|
||||
|
||||
DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
|
||||
public static String getDistanceGenerationModeDesc(IVersionConstants versionConstants)
|
||||
{
|
||||
return ""
|
||||
String DISTANCE_GENERATION_MODE_DESC = ""
|
||||
+ " How detailed should fake chunks be generated outside the vanilla render distance? \n"
|
||||
+ "\n"
|
||||
+ " The times are the amount of time it took one of the developer's PC to generate \n"
|
||||
+ " one chunk in Minecraft 1.16.5 and may be inaccurate for different Minecraft versions. \n"
|
||||
+ " They are included to give a rough estimate as to how the different options \n"
|
||||
+ " may perform in comparison to each other. \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.NONE + " \n"
|
||||
+ " Don't run the distance generator. \n"
|
||||
+ " No CPU usage - Fastest \n"
|
||||
@@ -288,36 +283,35 @@ public interface ILodConfigWrapperSingleton
|
||||
+ " 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"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY) + " - Fastest (2-5 ms) \n"
|
||||
+ " Multithreaded - Fastest (2-5 ms) \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
|
||||
+ " Same as " + DistanceGenerationMode.BIOME_ONLY + ", except instead \n"
|
||||
+ " of always using sea level as the LOD height \n"
|
||||
+ " different biome types (mountain, ocean, forest, etc.) \n"
|
||||
+ " use predetermined heights to simulate having height data. \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT) + " - Fastest (2-5 ms) \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"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.SURFACE) + " - Faster (10-20 ms) \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"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FEATURES) + " - Fast (15-20 ms) \n"
|
||||
+ " Multithreaded - Fast (15-20 ms) \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.FULL + " \n"
|
||||
+ " Ask the local server to generate/load each chunk. \n"
|
||||
+ " This will show player made structures, which can \n"
|
||||
+ " be useful if you are adding the mod to a pre-existing world. \n"
|
||||
+ " This is the most compatible, but causes server/simulation lag. \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FULL) + " - Slow (15-50 ms, with spikes up to 200 ms) \n"
|
||||
+ " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n"
|
||||
+ "\n"
|
||||
+ " The multithreaded options may increase CPU load significantly (while generating) \n"
|
||||
+ " depending on how many world generation threads you have allocated. \n";
|
||||
}
|
||||
DistanceGenerationMode getDistanceGenerationMode();
|
||||
void setDistanceGenerationMode(DistanceGenerationMode newDistanceGenerationMode);
|
||||
|
||||
@@ -356,13 +350,6 @@ public interface ILodConfigWrapperSingleton
|
||||
+ " This wont't affect performance.";
|
||||
BlocksToAvoid getBlocksToAvoid();
|
||||
void setBlockToAvoid(BlocksToAvoid newBlockToAvoid);
|
||||
|
||||
|
||||
/** description helper method */
|
||||
static String multiOrSingleThreadText(IVersionConstants versionConstants, DistanceGenerationMode distanceGenerationMode)
|
||||
{
|
||||
return versionConstants.isWorldGeneratorSingleThreaded(distanceGenerationMode) ? "Singlethreaded" : "Multithreaded";
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -475,7 +462,7 @@ public interface ILodConfigWrapperSingleton
|
||||
+ " " + GpuUploadMethod.BUFFER_MAPPING + ": Slow rendering but won't stutter when uploading. Possibly the best option for integrated GPUs. \n"
|
||||
+ " Default option for AMD/Intel. \n"
|
||||
+ " May end up storing buffers in System memory. \n"
|
||||
+ " Fast rendering if in GPU memory, slow if in system memory, \n"
|
||||
+ " Fast rending if in GPU memory, slow if in system memory, \n"
|
||||
+ " but won't stutter when uploading. \n"
|
||||
+ " " + GpuUploadMethod.DATA + ": Fast rendering but will stutter when uploading. \n"
|
||||
+ " Backup option for AMD/Intel. \n"
|
||||
@@ -486,19 +473,20 @@ public interface ILodConfigWrapperSingleton
|
||||
GpuUploadMethod getGpuUploadMethod();
|
||||
void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod);
|
||||
|
||||
MinDefaultMax<Integer> GPU_UPLOAD_PER_MEGABYTE_IN_MILLISECONDS_DEFAULT = new MinDefaultMax<Integer>(0, 10, 5000);
|
||||
String GPU_UPLOAD_PER_MEGABYTE_IN_MILLISECONDS_DESC = ""
|
||||
+ " How long should a buffer wait per Megabyte of data uploaded?\n"
|
||||
MinDefaultMax<Integer> GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DEFAULT = new MinDefaultMax<Integer>(0, 0, 5000);
|
||||
String GPU_UPLOAD_TIMEOUT_IN_MILLISECONDS_DESC = ""
|
||||
+ " How long should we wait before uploading a buffer to the GPU? \n"
|
||||
+ " Helpful resource for frame times: https://fpstoms.com \n"
|
||||
+ "\n"
|
||||
+ " Longer times may reduce stuttering but will make fake chunks \n"
|
||||
+ " transition and load slower. Change this to [0] for no timeout.\n"
|
||||
+ " transition and load slower. \n"
|
||||
+ "\n"
|
||||
+ " NOTE:\n"
|
||||
+ " Before changing this config, try changing \"GPU Upload methods\"\n"
|
||||
+ " and determined the best method for your hardware first. \n";
|
||||
int getGpuUploadPerMegabyteInMilliseconds();
|
||||
void setGpuUploadPerMegabyteInMilliseconds(int newMilliseconds);
|
||||
+ " This should be a last resort option."
|
||||
+ " Only change this from [0], after you have tried all of the \n"
|
||||
+ " \"GPU Upload methods\" and determined even the best stutters with yoru hardware.";
|
||||
int getGpuUploadTimeoutInMilliseconds();
|
||||
void setGpuUploadTimeoutInMilliseconds(int newTimeoutInMilliseconds);
|
||||
|
||||
String REBUILD_TIMES_DESC = ""
|
||||
+ " How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
|
||||
|
||||
+3
-58
@@ -25,8 +25,6 @@ import java.util.HashSet;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3d;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
|
||||
@@ -35,7 +33,7 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
* rendering in Minecraft.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-14-2021
|
||||
* @version 12-8-2021
|
||||
*/
|
||||
public interface IMinecraftRenderWrapper
|
||||
{
|
||||
@@ -66,56 +64,9 @@ public interface IMinecraftRenderWrapper
|
||||
/**
|
||||
* This method returns the ChunkPos of all chunks that Minecraft
|
||||
* is going to render this frame.
|
||||
* <br>
|
||||
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
|
||||
*/
|
||||
public default HashSet<AbstractChunkPosWrapper> getVanillaRenderedChunks()
|
||||
{
|
||||
return getMaximumRenderedChunks();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ChunkPos of every chunk that
|
||||
* Sodium is going to render this frame.
|
||||
* <br>
|
||||
* If not implemented this calls {@link #getMaximumRenderedChunks()}.
|
||||
*/
|
||||
public default HashSet<AbstractChunkPosWrapper> getSodiumRenderedChunks()
|
||||
{
|
||||
return getMaximumRenderedChunks();
|
||||
}
|
||||
|
||||
/**
|
||||
* <strong>Doesn't need to be implemented.</strong> <br>
|
||||
* Returns every chunk position within the vanilla render distance.
|
||||
*/
|
||||
public default HashSet<AbstractChunkPosWrapper> getMaximumRenderedChunks()
|
||||
{
|
||||
IMinecraftWrapper mcWrapper = SingletonHandler.get(IMinecraftWrapper.class);
|
||||
IWrapperFactory factory = SingletonHandler.get(IWrapperFactory.class);
|
||||
|
||||
int chunkRenderDist = this.getRenderDistance();
|
||||
// if we have a odd render distance, we'll have a empty gap. This way we'll overlap by 1 instead,
|
||||
// which is preferable to having a hole in the world
|
||||
chunkRenderDist = chunkRenderDist % 2 == 0 ? chunkRenderDist : chunkRenderDist - 1;
|
||||
|
||||
AbstractChunkPosWrapper centerChunkPos = mcWrapper.getPlayerChunkPos();
|
||||
int startChunkX = centerChunkPos.getX() - chunkRenderDist;
|
||||
int startChunkZ = centerChunkPos.getZ() - chunkRenderDist;
|
||||
|
||||
// add every position within render distance
|
||||
HashSet<AbstractChunkPosWrapper> renderedPos = new HashSet<AbstractChunkPosWrapper>();
|
||||
for (int chunkX = 0; chunkX < (chunkRenderDist * 2); chunkX++)
|
||||
{
|
||||
for(int chunkZ = 0; chunkZ < (chunkRenderDist * 2); chunkZ++)
|
||||
{
|
||||
renderedPos.add(factory.createChunkPos(startChunkX + chunkX, startChunkZ + chunkZ));
|
||||
}
|
||||
}
|
||||
|
||||
return renderedPos;
|
||||
}
|
||||
|
||||
HashSet<AbstractChunkPosWrapper> getRenderedChunks();
|
||||
|
||||
/** @returns null if there was a issue getting the lightmap */
|
||||
int[] getLightmapPixels();
|
||||
|
||||
@@ -125,10 +76,4 @@ public interface IMinecraftRenderWrapper
|
||||
int getLightmapTextureWidth();
|
||||
/** @returns -1 if there was an issue getting the lightmap */
|
||||
public int getLightmapGLFormat();
|
||||
|
||||
/** Try and disable vanilla fog. Return true if successful, or false if not able to.
|
||||
* If we are still using legacy fog, this method will not be called. */
|
||||
public default boolean tryDisableVanillaFog() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,6 @@ public interface IWorldWrapper
|
||||
|
||||
int getSeaLevel();
|
||||
|
||||
default short getMinHeight()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @throws UnsupportedOperationException if the WorldWrapper isn't for a ServerWorld */
|
||||
File getSaveFolder() throws UnsupportedOperationException;
|
||||
|
||||
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package com.seibel.lod.core.wrapperInterfaces.worldGeneration;
|
||||
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
public abstract class AbstractExperimentalWorldGeneratorWrapper {
|
||||
public AbstractExperimentalWorldGeneratorWrapper(LodBuilder newLodBuilder, LodDimension newLodDimension, IWorldWrapper worldWrapper) { }
|
||||
public abstract void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder);
|
||||
public abstract void stop();
|
||||
}
|
||||
@@ -34,8 +34,8 @@ void main()
|
||||
float skyLightTex = blockSkyLight / 16.0;
|
||||
|
||||
// we don't really need alpha in the lightmap
|
||||
// vertexColor = color * vec4(texture(lightMap, vec2(skyLightTex, blockLightTex)).xyz, 1);
|
||||
vertexColor = color * texture(lightMap, vec2(skyLightTex, blockLightTex));
|
||||
// vertexColor = color * vec4(texture(lightMap, vec2(blockLightTex, skyLightTex)).xyz, 1);
|
||||
vertexColor = color * texture(lightMap, vec2(blockLightTex, skyLightTex));
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user