Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d5072ed475 | |||
| ffee2141d4 | |||
| 9f4b6b8709 | |||
| b05f074f4c | |||
| 06a549983b | |||
| 0b96ca8509 | |||
| edd50096d6 | |||
| 607f3e8afe | |||
| 8b3404e5f8 | |||
| df6253af39 | |||
| b1f3b23ba1 | |||
| c4708ed173 | |||
| fcab0d3b20 | |||
| 1034360b88 | |||
| f92f656876 | |||
| e052a0c96f | |||
| 8bb8217c7b | |||
| 0563cde3c2 | |||
| 5fe192f4c5 | |||
| 428e12081c | |||
| ac102402cc | |||
| 966677b89e | |||
| 64e73b7d83 | |||
| 52ea2e96b7 | |||
| c658697ecd | |||
| a5b259f098 | |||
| e9da9c26f4 | |||
| 2f6ff1a3ea | |||
| 766e5a358d | |||
| a19189c2a8 | |||
| 0627f779d7 | |||
| 01bfb65d9e | |||
| cccad08a61 | |||
| 96be86cacf | |||
| b75791006c | |||
| 9047ebb970 | |||
| 2d1c2d6efb | |||
| 8577363438 | |||
| 4229ed75ae | |||
| b8408bc6fa | |||
| 56c4911316 | |||
| 244ead9451 | |||
| 5709a4c660 | |||
| 3913b955be | |||
| a295dcafd4 | |||
| f9914f9336 | |||
| 13e9df5b48 | |||
| 98c394bad1 | |||
| 444bf3b8bc | |||
| 455281a32d | |||
| 7b613ae8e3 | |||
| 443d6165fa | |||
| a4ebe3e3c1 | |||
| 6ac3edf280 | |||
| a1163dc340 | |||
| 38b7e66ef8 | |||
| a843a0ed65 | |||
| e0d2d2530f | |||
| 44e2936b68 | |||
| dfa717e0e3 | |||
| 69844417ce | |||
| 8370402dc1 | |||
| cbb32bc996 | |||
| ac876c0030 | |||
| 2176807a0a | |||
| 47732b7f57 | |||
| ff9afee5b4 | |||
| b5665a59c0 | |||
| 483ecf2e4d | |||
| 30eab27a49 | |||
| d1b5200fed | |||
| 4a4728d41e | |||
| c9204a2094 | |||
| c45f0b2414 | |||
| 97b2db84cd | |||
| c389f3b391 | |||
| 6049840aa6 | |||
| 2dee6b0326 | |||
| 27d9e6aeb7 | |||
| c751b6fcc6 | |||
| 31f173c8e8 | |||
| 90ca3bd394 | |||
| 30dd5526cd | |||
| a9e31b8133 | |||
| e71cd864b4 | |||
| 8efc7d54e7 | |||
| e323e8d62e | |||
| b970ad0ab8 | |||
| c811e6bad6 | |||
| 8ef0d40f0c | |||
| 227f7d0a23 | |||
| b385b018f1 | |||
| 4ece2de991 | |||
| a172961112 | |||
| 9e882951ef | |||
| 1c9fe23633 | |||
| 92b6a9695d | |||
| acc5e7af98 | |||
| aa9e49b3e7 | |||
| 7d86e24db5 | |||
| 71986d8818 | |||
| e948b4dac1 | |||
| 0a5f7d0c11 | |||
| 1c65ef8323 | |||
| 2c744dd22b | |||
| 009d7ede1b | |||
| 4199954843 | |||
| c7ab36c6b7 | |||
| 1da6544550 | |||
| c2896d1f73 | |||
| 302abb1e57 | |||
| 34c6f28173 | |||
| 75676df736 | |||
| 56b80f00e8 | |||
| c1651edb6a | |||
| c1375f7a10 | |||
| a82ef3dcde | |||
| 74a97dab0d | |||
| 16621773af | |||
| c8988ad1b7 | |||
| 1f3f432fef | |||
| 56032b6d6b | |||
| 6157c659d2 | |||
| d2cae841e7 | |||
| d1711be390 | |||
| 93332d6256 | |||
| 8292dc67c0 | |||
| 1b82acec9f |
@@ -0,0 +1 @@
|
||||
<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,6 +61,7 @@ public class ClientApi
|
||||
private boolean firstTimeSetupComplete = false;
|
||||
private boolean configOverrideReminderPrinted = false;
|
||||
|
||||
public boolean rendererDisabledBecauseOfExceptions = false;
|
||||
|
||||
|
||||
private ClientApi()
|
||||
@@ -112,9 +113,17 @@ public class ClientApi
|
||||
profiler.pop(); // get out of "terrain"
|
||||
profiler.push("LOD");
|
||||
|
||||
|
||||
ClientApi.renderer.drawLODs(lodDim, mcModelViewMatrix, mcProjectionMatrix, partialTicks, MC.getProfiler());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
profiler.pop(); // end LOD
|
||||
profiler.push("terrain"); // go back into "terrain"
|
||||
}
|
||||
@@ -173,6 +182,8 @@ 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.worldGeneration.LodGenWorker;
|
||||
import com.seibel.lod.core.builders.lodBuilding.LodBuilder;
|
||||
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,7 +81,8 @@ public class EventApi
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ClientApi.renderer, ApiShared.lodBuilder);
|
||||
// FIXME: This is in server thread. We shouldn't be accessing the client's renderer!
|
||||
LodWorldGenerator.INSTANCE.queueGenerationRequests(lodDim, ApiShared.lodBuilder);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +106,8 @@ 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();
|
||||
|
||||
@@ -121,6 +124,8 @@ public class EventApi
|
||||
{
|
||||
// the player just unloaded a world/dimension
|
||||
ThreadMapUtil.clearMaps();
|
||||
// ClientApi.renderer.markForCleanup();
|
||||
// ClientApi.renderer.destroyBuffers();
|
||||
|
||||
new Thread(() -> checkIfDisconnectedFromServer()).start();
|
||||
}
|
||||
@@ -147,7 +152,7 @@ public class EventApi
|
||||
|
||||
// if this isn't done unfinished tasks may be left in the queue
|
||||
// preventing new LodChunks form being generated
|
||||
LodGenWorker.restartExecutorService();
|
||||
LodWorldGenerator.INSTANCE.restartExecutorService();
|
||||
|
||||
LodWorldGenerator.INSTANCE.numberOfChunksWaitingToGenerate.set(0);
|
||||
ApiShared.lodWorld.deselectWorld();
|
||||
@@ -156,9 +161,11 @@ 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
|
||||
@@ -184,14 +191,16 @@ public class EventApi
|
||||
{
|
||||
if (CONFIG.client().advanced().debugging().getDebugKeybindingsEnabled())
|
||||
{
|
||||
if (key == GLFW.GLFW_KEY_F4 && keyAction == GLFW.GLFW_PRESS)
|
||||
if (key == GLFW.GLFW_KEY_F8 && keyAction == GLFW.GLFW_PRESS)
|
||||
{
|
||||
CONFIG.client().advanced().debugging().setDebugMode(CONFIG.client().advanced().debugging().getDebugMode().getNext());
|
||||
MC.sendChatMessage("F8: Set debug mode " + CONFIG.client().advanced().debugging().getDebugMode());
|
||||
}
|
||||
|
||||
if (key == GLFW.GLFW_KEY_F6 && keyAction == GLFW.GLFW_PRESS)
|
||||
{
|
||||
CONFIG.client().advanced().debugging().setDrawLods(!CONFIG.client().advanced().debugging().getDrawLods());
|
||||
MC.sendChatMessage("F6: Set rendering " + CONFIG.client().advanced().debugging().getDrawLods());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +231,7 @@ public class EventApi
|
||||
|
||||
int newWidth = (int) Math.ceil(chunksWide / (float) LodUtil.REGION_WIDTH_IN_CHUNKS);
|
||||
// make sure we have an odd number of regions
|
||||
newWidth += (newWidth & 1) == 0 ? 1 : 2;
|
||||
newWidth += (newWidth & 1) == 0 ? 1 : 0;
|
||||
|
||||
// do the dimensions need to change in size?
|
||||
if (ApiShared.lodBuilder.defaultDimensionWidthInRegions != newWidth || recalculateWidths)
|
||||
|
||||
+41
-19
@@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
|
||||
package com.seibel.lod.core.builders.bufferBuilding;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -25,25 +25,25 @@ import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.util.ColorUtil;
|
||||
import com.seibel.lod.core.util.DataPointUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
|
||||
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 extends AbstractLodTemplate
|
||||
public class CubicLodTemplate
|
||||
{
|
||||
//TODO make it a config
|
||||
static int cullingRange = 128;
|
||||
|
||||
public CubicLodTemplate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<LodDirection, long[]> adjData,
|
||||
public static void addLodToBuffer(LodBufferBuilder buffer, int playerX, int playerZ, long data, Map<LodDirection, long[]> adjData,
|
||||
byte detailLevel, int posX, int posZ, VertexOptimizer vertexOptimizer, DebugMode debugging, boolean[] adjShadeDisabled)
|
||||
{
|
||||
if (vertexOptimizer == null)
|
||||
@@ -65,7 +65,8 @@ public class CubicLodTemplate extends AbstractLodTemplate
|
||||
DataPointUtil.getDepth(data),
|
||||
blockWidth,
|
||||
posX * blockWidth, 0, posZ * blockWidth, // x, y, z offset
|
||||
bufferCenterBlockPos,
|
||||
playerX,
|
||||
playerZ,
|
||||
adjData,
|
||||
color,
|
||||
DataPointUtil.getLightSkyAlt(data),
|
||||
@@ -75,10 +76,24 @@ public class CubicLodTemplate extends AbstractLodTemplate
|
||||
addBoundingBoxToBuffer(buffer, vertexOptimizer);
|
||||
}
|
||||
|
||||
private void generateBoundingBox(VertexOptimizer 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,
|
||||
int height, int depth, int width,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
AbstractBlockPosWrapper bufferCenterBlockPos,
|
||||
int playerX, int playerZ,
|
||||
Map<LodDirection, long[]> adjData,
|
||||
int color, byte skyLight, byte blockLight,
|
||||
boolean[] adjShadeDisabled)
|
||||
@@ -95,27 +110,34 @@ public class CubicLodTemplate extends AbstractLodTemplate
|
||||
// offset the AABB by its x/z position in the world since
|
||||
// it uses doubles to specify its location, unlike the model view matrix
|
||||
// which only uses floats
|
||||
double x = -bufferCenterBlockPos.getX();
|
||||
double z = -bufferCenterBlockPos.getZ();
|
||||
double x = -playerX;
|
||||
double z = -playerZ;
|
||||
vertexOptimizer.reset();
|
||||
vertexOptimizer.setColor(color, adjShadeDisabled);
|
||||
vertexOptimizer.setLights(skyLight, blockLight);
|
||||
vertexOptimizer.setWidth(width, height - depth, width);
|
||||
vertexOptimizer.setOffset((int) (xOffset + x), (int) (depth + yOffset), (int) (zOffset + z));
|
||||
vertexOptimizer.setUpCulling(32, bufferCenterBlockPos);
|
||||
vertexOptimizer.setAdjData(adjData);
|
||||
}
|
||||
|
||||
private void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
|
||||
private static void addBoundingBoxToBuffer(LodBufferBuilder buffer, VertexOptimizer vertexOptimizer)
|
||||
{
|
||||
int color;
|
||||
byte skyLight;
|
||||
byte blockLight;
|
||||
|
||||
for (LodDirection lodDirection : VertexOptimizer.DIRECTIONS)
|
||||
{
|
||||
if(vertexOptimizer.isCulled(lodDirection))
|
||||
//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))
|
||||
{
|
||||
@@ -126,7 +148,7 @@ public class CubicLodTemplate extends AbstractLodTemplate
|
||||
color = vertexOptimizer.getColor(lodDirection);
|
||||
addPosAndColor(buffer,
|
||||
vertexOptimizer.getX(lodDirection, vertexIndex),
|
||||
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + DataPointUtil.VERTICAL_OFFSET,
|
||||
vertexOptimizer.getY(lodDirection, vertexIndex, verticalFaceIndex) + MIN_WORLD_HEIGHT,
|
||||
vertexOptimizer.getZ(lodDirection, vertexIndex),
|
||||
color, skyLight, blockLight );
|
||||
}
|
||||
+236
-264
@@ -20,6 +20,7 @@
|
||||
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;
|
||||
@@ -28,12 +29,11 @@ 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.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL45;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL44;
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
@@ -57,9 +57,6 @@ 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;
|
||||
|
||||
@@ -68,26 +65,40 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftWrapper;
|
||||
* rendered by the LodRenderer.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-8-2021
|
||||
* @version 12-9-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.5;
|
||||
public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3;
|
||||
|
||||
/**
|
||||
* When buffers are first created they are allocated to this size (in Bytes).
|
||||
@@ -144,14 +155,15 @@ public class LodBufferBuilderFactory
|
||||
|
||||
private volatile VertexOptimizer[][] vertexOptimizerCache;
|
||||
private volatile PosToRenderContainer[][] setsToRender;
|
||||
private volatile RegionPos center;
|
||||
|
||||
/**
|
||||
* This is the ChunkPosWrapper the player was at the last time the buffers were built.
|
||||
* IE the center of the buffers last time they were built
|
||||
*/
|
||||
private volatile AbstractChunkPosWrapper drawableCenterChunkPos = WRAPPER_FACTORY.createChunkPos();
|
||||
private volatile AbstractChunkPosWrapper buildableCenterChunkPos = WRAPPER_FACTORY.createChunkPos();
|
||||
private volatile int drawableCenterChunkPosX = 0;
|
||||
private volatile int drawableCenterChunkPosZ = 0;
|
||||
private volatile int buildableCenterBlockPosX = 0;
|
||||
private volatile int buildableCenterBlockPosZ = 0;
|
||||
|
||||
|
||||
|
||||
@@ -173,7 +185,7 @@ public class LodBufferBuilderFactory
|
||||
* swapped with the drawable buffers in the LodRenderer to be drawn.
|
||||
*/
|
||||
public void generateLodBuffersAsync(LodRenderer renderer, LodDimension lodDim,
|
||||
AbstractBlockPosWrapper playerBlockPos, boolean fullRegen)
|
||||
int playerX, int playerY, int playerZ, boolean fullRegen)
|
||||
{
|
||||
|
||||
// only allow one generation process to happen at a time
|
||||
@@ -191,7 +203,7 @@ public class LodBufferBuilderFactory
|
||||
generatingBuffers = true;
|
||||
|
||||
|
||||
Thread thread = new Thread(() -> generateLodBuffersThread(renderer, lodDim, playerBlockPos, fullRegen));
|
||||
Thread thread = new Thread(() -> generateLodBuffersThread(renderer, lodDim, playerX, playerY, playerZ, fullRegen));
|
||||
|
||||
mainGenThread.execute(thread);
|
||||
}
|
||||
@@ -200,15 +212,17 @@ public class LodBufferBuilderFactory
|
||||
// more easily edited by hot swapping. Because, As far as James is aware
|
||||
// you can't hot swap lambda expressions.
|
||||
private void generateLodBuffersThread(LodRenderer renderer, LodDimension lodDim,
|
||||
AbstractBlockPosWrapper playerBlockPos, boolean fullRegen)
|
||||
int playerX, int playerY, int playerZ, boolean fullRegen)
|
||||
{
|
||||
bufferLock.lock();
|
||||
|
||||
try
|
||||
{
|
||||
// round the player's block position down to the nearest chunk BlockPos
|
||||
AbstractChunkPosWrapper playerChunkPos = WRAPPER_FACTORY.createChunkPos(playerBlockPos);
|
||||
AbstractBlockPosWrapper playerBlockPosRounded = playerChunkPos.getWorldPosition();
|
||||
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);
|
||||
|
||||
|
||||
//long startTime = System.currentTimeMillis();
|
||||
@@ -217,11 +231,6 @@ public class LodBufferBuilderFactory
|
||||
|
||||
startBuffers(fullRegen, lodDim);
|
||||
|
||||
|
||||
RegionPos playerRegionPos = new RegionPos(playerChunkPos);
|
||||
if (center == null)
|
||||
center = playerRegionPos;
|
||||
|
||||
if (setsToRender == null)
|
||||
setsToRender = new PosToRenderContainer[lodDim.getWidth()][lodDim.getWidth()];
|
||||
|
||||
@@ -235,14 +244,17 @@ public class LodBufferBuilderFactory
|
||||
vertexOptimizerCache = new VertexOptimizer[lodDim.getWidth()][lodDim.getWidth()];
|
||||
|
||||
// this will be the center of the VBOs once they have been built
|
||||
buildableCenterChunkPos = playerChunkPos;
|
||||
//buildableCenterChunkPosX = playerChunkX;
|
||||
//buildableCenterChunkPosZ = playerChunkZ;
|
||||
buildableCenterBlockPosX = playerX;
|
||||
buildableCenterBlockPosZ = playerZ;
|
||||
|
||||
|
||||
//================================//
|
||||
// create the nodeToRenderThreads //
|
||||
//================================//
|
||||
|
||||
skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerBlockPos);
|
||||
skyLightPlayer = MC.getWrappedClientWorld().getSkyLight(playerX, playerY, playerZ);
|
||||
|
||||
for (int xRegion = 0; xRegion < lodDim.getWidth(); xRegion++)
|
||||
{
|
||||
@@ -308,13 +320,15 @@ public class LodBufferBuilderFactory
|
||||
lodDim.getPosToRender(
|
||||
posToRender,
|
||||
regionPos,
|
||||
playerBlockPosRounded.getX(),
|
||||
playerBlockPosRounded.getZ());
|
||||
playerX,
|
||||
playerZ);
|
||||
|
||||
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -327,14 +341,15 @@ public class LodBufferBuilderFactory
|
||||
posX = posToRender.getNthPosX(index);
|
||||
posZ = posToRender.getNthPosZ(index);
|
||||
|
||||
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.getX();
|
||||
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.getZ();
|
||||
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
|
||||
//
|
||||
//The block is in the player chunk or in a chunk adjacent to the player
|
||||
if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance))
|
||||
if(isThisPositionGoingToBeRendered(detailLevel, posX, posZ, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -355,8 +370,8 @@ public class LodBufferBuilderFactory
|
||||
xAdj = posX + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).x;
|
||||
zAdj = posZ + VertexOptimizer.DIRECTION_NORMAL_MAP.get(lodDirection).z;
|
||||
long data;
|
||||
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkPos.getX();
|
||||
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkPos.getZ();
|
||||
chunkXdist = LevelPosUtil.getChunkPos(detailLevel, xAdj) - playerChunkX;
|
||||
chunkZdist = LevelPosUtil.getChunkPos(detailLevel, zAdj) - playerChunkZ;
|
||||
adjPosInPlayerChunk = (chunkXdist == 0 && chunkZdist == 0);
|
||||
|
||||
//If the adj block is rendered in the same region and with same detail
|
||||
@@ -365,7 +380,7 @@ public class LodBufferBuilderFactory
|
||||
// We avoid cases where the adjPosition is in player chunk while the position is not
|
||||
// to always have a wall underwater
|
||||
if(posToRender.contains(detailLevel, xAdj, zAdj)
|
||||
&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance)
|
||||
&& !isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance)
|
||||
&& !(posNotInPlayerChunk && adjPosInPlayerChunk))
|
||||
{
|
||||
for (int verticalIndex = 0; verticalIndex < lodDim.getMaxVerticalData(detailLevel, xAdj, zAdj); verticalIndex++)
|
||||
@@ -382,7 +397,7 @@ public class LodBufferBuilderFactory
|
||||
|
||||
adjData.get(lodDirection)[0] = DataPointUtil.EMPTY_DATA;
|
||||
|
||||
if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkPos, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk))
|
||||
if ((isThisPositionGoingToBeRendered(detailLevel, xAdj, zAdj, playerChunkX, playerChunkZ, vanillaRenderedChunks, gameChunkRenderDistance) || (posNotInPlayerChunk && adjPosInPlayerChunk))
|
||||
&& !DataPointUtil.isVoid(data))
|
||||
{
|
||||
adjShadeDisabled[VertexOptimizer.DIRECTION_INDEX.get(lodDirection)] = DataPointUtil.getAlpha(data) < 255;
|
||||
@@ -418,7 +433,7 @@ public class LodBufferBuilderFactory
|
||||
break;
|
||||
|
||||
//We send the call to create the vertices
|
||||
CONFIG.client().graphics().advancedGraphics().getLodTemplate().template.addLodToBuffer(currentBuffers[bufferIndex], playerBlockPosRounded, data, adjData,
|
||||
CubicLodTemplate.addLodToBuffer(currentBuffers[bufferIndex], playerX, playerZ, data, adjData,
|
||||
detailLevel, posX, posZ, vertexOptimizer, renderer.previousDebugMode, adjShadeDisabled);
|
||||
}
|
||||
|
||||
@@ -488,12 +503,12 @@ public class LodBufferBuilderFactory
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, AbstractChunkPosWrapper playerChunkPos, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
|
||||
private boolean isThisPositionGoingToBeRendered(byte detailLevel, int posX, int posZ, int chunkPosX, int chunkPosZ, boolean[][] vanillaRenderedChunks, int gameChunkRenderDistance){
|
||||
|
||||
|
||||
// skip any chunks that Minecraft is going to render
|
||||
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - playerChunkPos.getX();
|
||||
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - playerChunkPos.getZ();
|
||||
int chunkXdist = LevelPosUtil.getChunkPos(detailLevel, posX) - chunkPosX;
|
||||
int chunkZdist = LevelPosUtil.getChunkPos(detailLevel, posZ) - chunkPosZ;
|
||||
|
||||
// check if the chunk is on the border
|
||||
boolean isItBorderPos;
|
||||
@@ -608,27 +623,27 @@ public class LodBufferBuilderFactory
|
||||
|
||||
|
||||
// create the initial mapped buffers (system memory)
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableVbos[x][z][i].id);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, GL15.GL_STATIC_DRAW);
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
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, drawableVbos[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);
|
||||
|
||||
|
||||
if (glProxy.bufferStorageSupported)
|
||||
{
|
||||
// create the buffer storage (GPU memory)
|
||||
buildableStorageBufferIds[x][z][i] = GL15.glGenBuffers();
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, buildableStorageBufferIds[x][z][i]);
|
||||
GL45.glBufferStorage(GL15.GL_ARRAY_BUFFER, regionMemoryRequired, 0); // the 0 flag means to create the storage in the GPUs memory
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
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);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,106 +663,73 @@ 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()
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void destroyBuffers() {
|
||||
int[][][] toBeDeletedBuildableStorageBufferIds;
|
||||
int[][][] toBeDeletedDrawableStorageBufferIds;
|
||||
LodVertexBuffer[][][] toBeDeletedBuildableVbos;
|
||||
LodVertexBuffer[][][] toBeDeletedDrawableVbos;
|
||||
bufferLock.lock();
|
||||
try {
|
||||
toBeDeletedBuildableStorageBufferIds = buildableStorageBufferIds;
|
||||
toBeDeletedDrawableStorageBufferIds = drawableStorageBufferIds;
|
||||
toBeDeletedBuildableVbos = buildableVbos;
|
||||
toBeDeletedDrawableVbos = drawableVbos;
|
||||
buildableStorageBufferIds = null;
|
||||
drawableStorageBufferIds = null;
|
||||
|
||||
|
||||
|
||||
|
||||
// destroy the VBOs if they aren't already
|
||||
if (buildableVbos != null)
|
||||
{
|
||||
for (int i = 0; i < buildableVbos.length; i++)
|
||||
{
|
||||
for (int j = 0; j < buildableVbos.length; j++)
|
||||
{
|
||||
for (int k = 0; k < buildableVbos[i][j].length; k++)
|
||||
{
|
||||
int buildableId;
|
||||
int drawableId;
|
||||
|
||||
// variables passed into a lambda expression
|
||||
// need to be effectively final, so we have
|
||||
// to use an else statement here
|
||||
if (buildableVbos[i][j][k] != null)
|
||||
buildableId = buildableVbos[i][j][k].id;
|
||||
else
|
||||
buildableId = 0;
|
||||
|
||||
if (drawableVbos[i][j][k] != null)
|
||||
drawableId = drawableVbos[i][j][k].id;
|
||||
else
|
||||
drawableId = 0;
|
||||
|
||||
|
||||
GLProxy.getInstance().recordOpenGlCall(() ->
|
||||
{
|
||||
if (buildableId != 0)
|
||||
GL15.glDeleteBuffers(buildableId);
|
||||
if (drawableId != 0)
|
||||
GL15.glDeleteBuffers(drawableId);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildableVbos = null;
|
||||
drawableVbos = null;
|
||||
|
||||
|
||||
// these don't contain any OpenGL objects, so
|
||||
// they don't require any special clean-up
|
||||
buildableBuffers = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ClientApi.LOGGER.info("destroyBuffers ran into trouble: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// this shouldn't normally happen, but just in case it sill prevent deadlock
|
||||
} finally {
|
||||
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)
|
||||
{
|
||||
@@ -759,12 +741,12 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
for (int i = 0; i < buildableBuffers[x][z].length; i++)
|
||||
{
|
||||
// for some reason BufferBuilder.vertexCounts
|
||||
// FIXME: for some reason BufferBuilder.vertexCounts
|
||||
// isn't reset unless this is called, which can cause
|
||||
// a false indexOutOfBoundsException
|
||||
buildableBuffers[x][z][i].discard();
|
||||
|
||||
buildableBuffers[x][z][i].begin(GL11.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
|
||||
buildableBuffers[x][z][i].begin(GL32.GL_QUADS, LodUtil.LOD_VERTEX_FORMAT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -786,8 +768,6 @@ 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,
|
||||
@@ -795,25 +775,12 @@ public class LodBufferBuilderFactory
|
||||
glProxy.setGlContext(GLProxyContext.LOD_BUILDER);
|
||||
|
||||
// determine the upload method
|
||||
GpuUploadMethod uploadMethod = CONFIG.client().advanced().buffers().getGpuUploadMethod();
|
||||
if (!glProxy.bufferStorageSupported && uploadMethod == GpuUploadMethod.BUFFER_STORAGE)
|
||||
{
|
||||
// if buffer storage isn't supported
|
||||
// default to SUB_DATA
|
||||
CONFIG.client().advanced().buffers().setGpuUploadMethod(GpuUploadMethod.SUB_DATA);
|
||||
uploadMethod = GpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
GpuUploadMethod uploadMethod = glProxy.getGpuUploadMethod();
|
||||
|
||||
// determine the upload timeout
|
||||
int uploadTimeoutInMS = CONFIG.client().advanced().buffers().getGpuUploadTimeoutInMilliseconds();
|
||||
|
||||
// James has no idea if this does anything helpful,
|
||||
// but in theory it should prevent OpenGL from drawing and
|
||||
// writing to a buffer at the same time.
|
||||
GL45.glMemoryBarrier(GL45.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
|
||||
fence = GL45.glFenceSync(GL45.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
|
||||
|
||||
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.
|
||||
|
||||
// actually upload the buffers
|
||||
for (int x = 0; x < buildableVbos.length; x++)
|
||||
@@ -824,46 +791,57 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
for (int i = 0; i < buildableBuffers[x][z].length; i++)
|
||||
{
|
||||
ByteBuffer uploadBuffer = buildableBuffers[x][z][i].getCleanedByteBuffer();
|
||||
vboUpload(x,z,i, uploadBuffer, true, uploadMethod);
|
||||
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();
|
||||
lodDim.setRegenRegionBufferByArrayIndex(x, z, false);
|
||||
|
||||
|
||||
setR.end("setRegenRegionBufferByArrayIndex");
|
||||
|
||||
// upload buffers over an extended period of time
|
||||
// to hopefully prevent stuttering.
|
||||
if (uploadTimeoutInMS != 0)
|
||||
Thread.sleep(uploadTimeoutInMS);
|
||||
GL15.glFinish();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make sure all of the uploads finish before continuing
|
||||
GL45.glClientWaitSync(fence, GL45.GL_SYNC_FLUSH_COMMANDS_BIT, 5L * 1000000000); // wait up to 5 seconds
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// this doesn't appear to be necessary anymore, but just in case.
|
||||
ClientApi.LOGGER.error(LodBufferBuilderFactory.class.getSimpleName() + " - UploadBuffers failed: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
GL15.glFinish();
|
||||
if (fence != 0)
|
||||
GL45.glDeleteSync(fence);
|
||||
|
||||
// close the context so it can be re-used later.
|
||||
// I'm guessing we can't just leave it because the executor service
|
||||
// does something that invalidates the OpenGL context.
|
||||
} 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();
|
||||
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,
|
||||
boolean allowBufferExpansion, GpuUploadMethod uploadMethod)
|
||||
private void vboUpload(int xIndex, int zIndex, int iIndex, ByteBuffer uploadBuffer, GpuUploadMethod uploadMethod)
|
||||
{
|
||||
// get the vbos, buffers, ids, etc.
|
||||
int storageBufferId = 0;
|
||||
@@ -872,115 +850,101 @@ 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() / ((Float.BYTES * 3) + (Byte.BYTES * 4) + Byte.BYTES + Byte.BYTES)); // TODO make this change with the LodTemplate
|
||||
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");
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo.id);
|
||||
try
|
||||
{
|
||||
// if possible use the faster buffer storage route
|
||||
if (uploadMethod == GpuUploadMethod.BUFFER_STORAGE && storageBufferId != 0)
|
||||
{
|
||||
// get a pointer to the buffer in system memory
|
||||
ByteBuffer vboBuffer = GL30.glMapBufferRange(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer.capacity(), GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_UNSYNCHRONIZED_BIT);
|
||||
if (vboBuffer == null)
|
||||
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())
|
||||
{
|
||||
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());
|
||||
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");
|
||||
}
|
||||
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
|
||||
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);
|
||||
}
|
||||
// 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");
|
||||
}
|
||||
else if (uploadMethod == GpuUploadMethod.DATA)
|
||||
{
|
||||
// TODO: Check this nonsense comment!
|
||||
// hybrid bufferData //
|
||||
// high stutter, low GPU usage
|
||||
// But simplest/most compatible
|
||||
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer.capacity(), GL15.GL_STATIC_DRAW);
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, uploadBuffer, GL15.GL_STATIC_DRAW);
|
||||
LagSpikeCatcher buffData = new LagSpikeCatcher();
|
||||
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, uploadBuffer, GL32.GL_STATIC_DRAW);
|
||||
buffData.end("glBufferData Data");
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Check this nonsense comment!
|
||||
// hybrid subData/bufferData //
|
||||
// less stutter, low GPU usage
|
||||
|
||||
long size = GL15.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
|
||||
if (size < uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER)
|
||||
LagSpikeCatcher getParm = new LagSpikeCatcher();
|
||||
long size = GL32.glGetBufferParameteri(GL32.GL_ARRAY_BUFFER, GL32.GL_BUFFER_SIZE);
|
||||
getParm.end("glGetBufferParameteri SubData");
|
||||
if (size < uploadBuffer.capacity())
|
||||
{
|
||||
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, (int) (uploadBuffer.capacity() * BUFFER_EXPANSION_MULTIPLIER), GL15.GL_STATIC_DRAW);
|
||||
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.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
LagSpikeCatcher buffSubData = new LagSpikeCatcher();
|
||||
GL32.glBufferSubData(GL32.GL_ARRAY_BUFFER, 0, uploadBuffer);
|
||||
buffSubData.end("glBufferSubData SubData");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -990,10 +954,14 @@ public class LodBufferBuilderFactory
|
||||
}
|
||||
finally
|
||||
{
|
||||
LagSpikeCatcher buffUnmap = new LagSpikeCatcher();
|
||||
if (uploadMethod == GpuUploadMethod.BUFFER_MAPPING)
|
||||
GL15.glUnmapBuffer(GL15.GL_ARRAY_BUFFER);
|
||||
|
||||
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GL32.glUnmapBuffer(GL32.GL_ARRAY_BUFFER);
|
||||
buffUnmap.end("glUnmapBuffer");
|
||||
|
||||
LagSpikeCatcher buffUnbind = new LagSpikeCatcher();
|
||||
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, 0);
|
||||
buffUnbind.end("glBindBuffer 0");
|
||||
}
|
||||
|
||||
}//if vbo exists and in correct GL context
|
||||
@@ -1004,6 +972,7 @@ 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
|
||||
@@ -1016,7 +985,8 @@ public class LodBufferBuilderFactory
|
||||
drawableStorageBufferIds = buildableStorageBufferIds;
|
||||
buildableStorageBufferIds = tmpStorage;
|
||||
|
||||
drawableCenterChunkPos = buildableCenterChunkPos;
|
||||
drawableCenterChunkPosX = buildableCenterBlockPosX;
|
||||
drawableCenterChunkPosZ = buildableCenterBlockPosZ;
|
||||
|
||||
// the vbos have been swapped
|
||||
switchVbos = false;
|
||||
@@ -1032,7 +1002,7 @@ public class LodBufferBuilderFactory
|
||||
}
|
||||
}
|
||||
|
||||
return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPos);
|
||||
return new VertexBuffersAndOffset(drawableVbos, drawableStorageBufferIds, drawableCenterChunkPosX, drawableCenterChunkPosZ);
|
||||
}
|
||||
|
||||
/** A simple container to pass multiple objects back in the getVertexBuffers method. */
|
||||
@@ -1040,13 +1010,15 @@ public class LodBufferBuilderFactory
|
||||
{
|
||||
public final LodVertexBuffer[][][] vbos;
|
||||
public final int[][][] storageBufferIds;
|
||||
public final AbstractChunkPosWrapper drawableCenterChunkPos;
|
||||
public int drawableCenterBlockPosX;
|
||||
public int drawableCenterBlockPosZ;
|
||||
|
||||
public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, AbstractChunkPosWrapper newDrawableCenterChunkPos)
|
||||
public VertexBuffersAndOffset(LodVertexBuffer[][][] newVbos, int[][][] newStorageBufferIds, int newDrawableCenterBlockPosX, int newDrawableCenterBlockPosZ)
|
||||
{
|
||||
vbos = newVbos;
|
||||
storageBufferIds = newStorageBufferIds;
|
||||
drawableCenterChunkPos = newDrawableCenterChunkPos;
|
||||
drawableCenterBlockPosX = newDrawableCenterBlockPosX;
|
||||
drawableCenterBlockPosZ = newDrawableCenterBlockPosZ;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-55
@@ -1,55 +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.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, AbstractBlockPosWrapper bufferCenterBlockPos, 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();
|
||||
}
|
||||
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
|
||||
/**
|
||||
* TODO DynamicLodTemplate
|
||||
* Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height.
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class DynamicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<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
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.builders.bufferBuilding.lodTemplates;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
import com.seibel.lod.core.enums.LodDirection;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.objects.VertexOptimizer;
|
||||
import com.seibel.lod.core.objects.opengl.LodBufferBuilder;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
|
||||
/**
|
||||
* TODO #21 TriangularLodTemplate
|
||||
* Builds each LOD chunk as a singular rectangular prism.
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class TriangularLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(LodBufferBuilder buffer, AbstractBlockPosWrapper bufferCenterBlockPos, long data, Map<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,12 +35,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.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;
|
||||
@@ -54,18 +51,18 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
* @author Cola
|
||||
* @author Leonardo Amato
|
||||
* @author James Seibel
|
||||
* @version 10-22-2021
|
||||
* @version 12-11-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 IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
|
||||
private static final IBlockColorSingletonWrapper BLOCK_COLOR = SingletonHandler.get(IBlockColorSingletonWrapper.class);
|
||||
|
||||
/** If no blocks are found in the area in determineBottomPointForArea return this */
|
||||
public static final short DEFAULT_DEPTH = 0;
|
||||
/** If no blocks are found in the area in determineHeightPointForArea return this */
|
||||
public static final short DEFAULT_HEIGHT = 0;
|
||||
|
||||
/** 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(...)
|
||||
/** Minecraft's max light value */
|
||||
public static final short DEFAULT_MAX_LIGHT = 15;
|
||||
|
||||
@@ -83,6 +80,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
//public static final boolean useExperimentalLighting = true;
|
||||
|
||||
private static int timesToEdgeDetect = 1;
|
||||
|
||||
|
||||
|
||||
@@ -110,8 +108,8 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
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)
|
||||
@@ -134,14 +132,14 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
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);
|
||||
}
|
||||
@@ -170,7 +168,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
int startZ;
|
||||
|
||||
|
||||
LodRegion region = lodDim.getRegion(chunk.getPos().getRegionX(), chunk.getPos().getRegionZ());
|
||||
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
|
||||
if (region == null)
|
||||
return;
|
||||
|
||||
@@ -204,12 +202,12 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
//lodDim.clear(detailLevel, posX, posZ);
|
||||
if (data != null && data.length != 0)
|
||||
{
|
||||
posX = LevelPosUtil.convert((byte) 0, chunk.getPos().getX() * 16 + startX, detail.detailLevel);
|
||||
posZ = LevelPosUtil.convert((byte) 0, chunk.getPos().getZ() * 16 + startZ, detail.detailLevel);
|
||||
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);
|
||||
}
|
||||
}
|
||||
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getPos().getX(), chunk.getPos().getZ());
|
||||
lodDim.updateData(LodUtil.CHUNK_DETAIL_LEVEL, chunk.getChunkPosX(), chunk.getChunkPosZ());
|
||||
//executeTime = System.currentTimeMillis() - executeTime;
|
||||
//if (executeTime > 0) ClientApi.LOGGER.info("generateLodNodeFromChunk level: " + detailLevel + " time ms: " + executeTime);
|
||||
}
|
||||
@@ -222,8 +220,6 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
long[] dataToMerge = ThreadMapUtil.getBuilderVerticalArray(detail.detailLevel);
|
||||
int verticalData = DataPointUtil.WORLD_HEIGHT / 2 + 1;
|
||||
|
||||
AbstractChunkPosWrapper chunkPos = chunk.getPos();
|
||||
int height;
|
||||
int depth;
|
||||
int color;
|
||||
@@ -240,55 +236,48 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
|
||||
boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
|
||||
boolean isDefault;
|
||||
AbstractBlockPosWrapper blockPos = FACTORY.createBlockPos();
|
||||
int index;
|
||||
|
||||
for (index = 0; index < size * size; index++)
|
||||
{
|
||||
xRel = startX + index % size;
|
||||
zRel = startZ + index / size;
|
||||
xAbs = chunkPos.getMinBlockX() + xRel;
|
||||
zAbs = chunkPos.getMinBlockZ() + zRel;
|
||||
xAbs = chunk.getMinX() + xRel;
|
||||
zAbs = chunk.getMinZ() + zRel;
|
||||
|
||||
//Calculate the height of the lod
|
||||
yAbs = DataPointUtil.WORLD_HEIGHT - DataPointUtil.VERTICAL_OFFSET + 1;
|
||||
yAbs = chunk.getMaxY(xRel,zRel) - MIN_WORLD_HEIGHT;
|
||||
int count = 0;
|
||||
boolean topBlock = true;
|
||||
if (yAbs <= 0);
|
||||
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
|
||||
while (yAbs > 0)
|
||||
{
|
||||
height = determineHeightPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
|
||||
height = determineHeightPointFrom(chunk, config, xAbs, yAbs, zAbs);
|
||||
|
||||
// If the lod is at the default height, it must be void data
|
||||
if (height == DEFAULT_HEIGHT)
|
||||
{
|
||||
if (topBlock)
|
||||
dataToMerge[index * verticalData] = DataPointUtil.createVoidDataPoint(generation);
|
||||
if (height == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
yAbs = height - 1;
|
||||
// We search light on above air block
|
||||
depth = determineBottomPointFrom(chunk, config, xRel, yAbs, zRel, blockPos);
|
||||
depth = determineBottomPointFrom(chunk, config, xAbs, yAbs, zAbs, count < timesToEdgeDetect && !hasCeiling);
|
||||
if (hasCeiling && topBlock)
|
||||
{
|
||||
yAbs = depth;
|
||||
blockPos.set(xAbs, yAbs, zAbs);
|
||||
light = getLightValue(chunk, blockPos, true, hasSkyLight, true);
|
||||
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs, blockPos);
|
||||
blockPos.set(xAbs, yAbs - 1, zAbs);
|
||||
light = getLightValue(chunk, xAbs,yAbs + MIN_WORLD_HEIGHT, zAbs, true, hasSkyLight, true);
|
||||
color = generateLodColor(chunk, config, xAbs, yAbs, zAbs);
|
||||
}
|
||||
else
|
||||
{
|
||||
blockPos.set(xAbs, yAbs, zAbs);
|
||||
light = getLightValue(chunk, blockPos, hasCeiling, hasSkyLight, topBlock);
|
||||
color = generateLodColor(chunk, config, xRel, yAbs, zRel, blockPos);
|
||||
blockPos.set(xAbs, yAbs + 1, zAbs);
|
||||
light = getLightValue(chunk, xAbs, yAbs + MIN_WORLD_HEIGHT, 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 - DataPointUtil.VERTICAL_OFFSET, depth - DataPointUtil.VERTICAL_OFFSET, color, lightSky, lightBlock, generation, isDefault);
|
||||
dataToMerge[index * verticalData + count] = DataPointUtil.createDataPoint(height, depth, color, lightSky, lightBlock, generation, isDefault);
|
||||
topBlock = false;
|
||||
yAbs = depth - 1;
|
||||
count++;
|
||||
@@ -301,34 +290,56 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
* Find the lowest valid point from the bottom.
|
||||
* Used when creating a vertical LOD.
|
||||
*/
|
||||
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos)
|
||||
private short determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, boolean strictEdge)
|
||||
{
|
||||
short depth = DEFAULT_DEPTH;
|
||||
short depth = 0;
|
||||
|
||||
for (int y = yAbs; y >= 0; y--)
|
||||
int colorOfBlock = 0;
|
||||
if (strictEdge)
|
||||
{
|
||||
blockPos.set(xAbs, y, zAbs);
|
||||
if (!isLayerValidLodPoint(chunk, blockPos))
|
||||
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;
|
||||
}
|
||||
|
||||
/** Find the highest valid point from the Top */
|
||||
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs, AbstractBlockPosWrapper blockPos)
|
||||
private short determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
|
||||
{
|
||||
short height = DEFAULT_HEIGHT;
|
||||
//TODO find a way to skip bottom of the world
|
||||
short height = 0;
|
||||
if (config.useHeightmap)
|
||||
height = (short) chunk.getHeightMapValue(xAbs, zAbs);
|
||||
else
|
||||
{
|
||||
for (int y = yAbs; y >= 0; y--)
|
||||
{
|
||||
blockPos.set(xAbs, y, zAbs);
|
||||
if (isLayerValidLodPoint(chunk, blockPos))
|
||||
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
|
||||
{
|
||||
height = (short) (y + 1);
|
||||
break;
|
||||
@@ -348,29 +359,27 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
* Generate the color for the given chunk using biome water color, foliage
|
||||
* color, and grass color.
|
||||
*/
|
||||
private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xRel, int yAbs, int zRel, AbstractBlockPosWrapper blockPos)
|
||||
private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int x, int y, int z)
|
||||
{
|
||||
int colorInt;
|
||||
if (builderConfig.useBiomeColors)
|
||||
{
|
||||
// I have no idea why I need to bit shift to the right, but
|
||||
// if I don't the biomes don't show up correctly.
|
||||
colorInt = chunk.getBiome(xRel, yAbs, zRel).getColorForBiome(xRel, zRel);
|
||||
colorInt = chunk.getBiome(x, y, z).getColorForBiome(x, z);
|
||||
}
|
||||
else
|
||||
{
|
||||
blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs, chunk.getPos().getMinBlockZ() + zRel);
|
||||
colorInt = getColorForBlock(chunk, blockPos);
|
||||
colorInt = getColorForBlock(chunk, x, y, z);
|
||||
|
||||
// if we are skipping non-full and non-solid blocks that means we ignore
|
||||
// snow, flowers, etc. Get the above block so we can still get the color
|
||||
// of the snow, flower, etc. that may be above this block
|
||||
int aboveColorInt = 0;
|
||||
if (config.client().worldGenerator().getBlocksToAvoid().nonFull || config.client().worldGenerator().getBlocksToAvoid().noCollision)
|
||||
{
|
||||
blockPos.set(chunk.getPos().getMinBlockX() + xRel, yAbs + 1, chunk.getPos().getMinBlockZ() + zRel);
|
||||
aboveColorInt = getColorForBlock(chunk, blockPos);
|
||||
}
|
||||
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 (colorInt == 0 && yAbs > 0)
|
||||
// if this block is invisible, check the block below it
|
||||
@@ -386,7 +395,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
}
|
||||
|
||||
/** Gets the light value for the given block position */
|
||||
private int getLightValue(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
|
||||
private int getLightValue(IChunkWrapper chunk, int x, int y, int z, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
|
||||
{
|
||||
int skyLight = 0;
|
||||
int blockLight;
|
||||
@@ -395,25 +404,25 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
IWorldWrapper world = MC.getWrappedServerWorld();
|
||||
|
||||
int blockBrightness = chunk.getEmittedBrightness(blockPos);
|
||||
int blockBrightness = chunk.getEmittedBrightness(x, y, z);
|
||||
// get the air block above or below this block
|
||||
if (hasCeiling && topBlock)
|
||||
blockPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
|
||||
y--;
|
||||
else
|
||||
blockPos.set(blockPos.getX(), blockPos.getY() + 1, blockPos.getZ());
|
||||
y++;
|
||||
|
||||
|
||||
|
||||
if (world != null)
|
||||
{
|
||||
// server world sky light (always accurate)
|
||||
blockLight = world.getBlockLight(blockPos);
|
||||
blockLight = world.getBlockLight(x,y,z);
|
||||
if (topBlock && !hasCeiling && hasSkyLight)
|
||||
skyLight = DEFAULT_MAX_LIGHT;
|
||||
else
|
||||
{
|
||||
if (hasSkyLight)
|
||||
skyLight = world.getSkyLight(blockPos);
|
||||
skyLight = world.getSkyLight(x,y,z);
|
||||
//else
|
||||
// skyLight = 0;
|
||||
}
|
||||
@@ -421,7 +430,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
{
|
||||
// we are on predicted terrain, and we don't know what the light here is,
|
||||
// lets just take a guess
|
||||
if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
|
||||
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
|
||||
{
|
||||
skyLight = 12;
|
||||
isDefault = 1;
|
||||
@@ -442,7 +451,7 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
else
|
||||
{
|
||||
// client world sky light (almost never accurate)
|
||||
blockLight = world.getBlockLight(blockPos);
|
||||
blockLight = world.getBlockLight(x,y,z);
|
||||
// estimate what the lighting should be
|
||||
if (hasSkyLight || !hasCeiling)
|
||||
{
|
||||
@@ -451,14 +460,14 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
else
|
||||
{
|
||||
if (hasSkyLight)
|
||||
skyLight = world.getSkyLight(blockPos);
|
||||
skyLight = world.getSkyLight(x,y,z);
|
||||
//else
|
||||
// skyLight = 0;
|
||||
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
|
||||
{
|
||||
// we don't know what the light here is,
|
||||
// lets just take a guess
|
||||
if (blockPos.getY() >= MC.getWrappedClientWorld().getSeaLevel() - 5)
|
||||
if (y >= MC.getWrappedClientWorld().getSeaLevel() - 5)
|
||||
{
|
||||
skyLight = 12;
|
||||
isDefault = 1;
|
||||
@@ -477,34 +486,31 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
}
|
||||
|
||||
/** Returns a color int for the given block. */
|
||||
private int getColorForBlock(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos)
|
||||
private int getColorForBlock(IChunkWrapper chunk, int x, int y, int z)
|
||||
{
|
||||
int colorOfBlock;
|
||||
int colorInt;
|
||||
|
||||
int xRel = blockPos.getX() - chunk.getPos().getMinBlockX();
|
||||
int zRel = blockPos.getZ() - chunk.getPos().getMinBlockZ();
|
||||
//int x = blockPos.getX();
|
||||
int y = blockPos.getY();
|
||||
//int z = blockPos.getZ();
|
||||
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(x, y, z);
|
||||
|
||||
if (blockShapeWrapper == null || blockShapeWrapper.isToAvoid())
|
||||
return 0;
|
||||
|
||||
IBlockColorWrapper blockColorWrapper;
|
||||
IBlockShapeWrapper blockShapeWrapper = chunk.getBlockShapeWrapper(blockPos);
|
||||
|
||||
if (chunk.isWaterLogged(blockPos))
|
||||
if (chunk.isWaterLogged(x, y, z))
|
||||
blockColorWrapper = BLOCK_COLOR.getWaterColor();
|
||||
else
|
||||
blockColorWrapper = chunk.getBlockColorWrapper(blockPos);
|
||||
blockColorWrapper = chunk.getBlockColorWrapper(x, y, z);
|
||||
|
||||
|
||||
if (blockShapeWrapper.isToAvoid())
|
||||
return 0;
|
||||
|
||||
colorOfBlock = blockColorWrapper.getColor();
|
||||
|
||||
|
||||
if (blockColorWrapper.hasTint())
|
||||
{
|
||||
IBiomeWrapper biome = chunk.getBiome(xRel, y, zRel);
|
||||
IBiomeWrapper biome = chunk.getBiome(x, y, z);
|
||||
int tintValue;
|
||||
if (blockColorWrapper.hasGrassTint())
|
||||
// grass and green plants
|
||||
@@ -524,15 +530,16 @@ import com.seibel.lod.core.wrapperInterfaces.world.IWorldWrapper;
|
||||
|
||||
|
||||
/** Is the block at the given blockPos a valid LOD point? */
|
||||
private boolean isLayerValidLodPoint(IChunkWrapper chunk, AbstractBlockPosWrapper blockPos)
|
||||
private boolean isLayerValidLodPoint(IChunkWrapper chunk, int x, int y, int z)
|
||||
{
|
||||
if (chunk.isWaterLogged(blockPos))
|
||||
if (chunk.isWaterLogged(x, y, z))
|
||||
return true;
|
||||
|
||||
boolean nonFullAvoidance = config.client().worldGenerator().getBlocksToAvoid().nonFull;
|
||||
boolean noCollisionAvoidance = config.client().worldGenerator().getBlocksToAvoid().noCollision;
|
||||
|
||||
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(blockPos);
|
||||
IBlockShapeWrapper block = chunk.getBlockShapeWrapper(x, y, z);
|
||||
if (block == null) return false;
|
||||
return !block.isToAvoid()
|
||||
&& !(nonFullAvoidance && block.isNonFull())
|
||||
&& !(noCollisionAvoidance && block.hasNoCollision());
|
||||
|
||||
@@ -1,212 +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.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());
|
||||
}
|
||||
|
||||
}
|
||||
+198
-34
@@ -25,50 +25,47 @@ 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 9-25-2021
|
||||
* @version 12-11-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
|
||||
@@ -82,32 +79,64 @@ 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.
|
||||
* @param renderer needed so the LodNodeGenWorkers can flag that the
|
||||
* renderer needed so the LodNodeGenWorkers can flag that the
|
||||
* buffers need to be rebuilt.
|
||||
*/
|
||||
public void queueGenerationRequests(LodDimension lodDim, LodRenderer renderer, LodBuilder lodBuilder)
|
||||
public void queueGenerationRequests(LodDimension lodDim, LodBuilder lodBuilder)
|
||||
{
|
||||
if (CONFIG.client().worldGenerator().getDistanceGenerationMode() != DistanceGenerationMode.NONE
|
||||
|
||||
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
|
||||
&& !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;
|
||||
|
||||
// just in case the config changed
|
||||
maxChunkGenRequests = CONFIG.client().advanced().threading().getNumberOfWorldGenerationThreads() * 8;
|
||||
|
||||
Thread generatorThread = new Thread(() ->
|
||||
Runnable generatorFunc = (() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -127,7 +156,6 @@ public class LodWorldGenerator
|
||||
playerPosX,
|
||||
playerPosZ);
|
||||
|
||||
|
||||
byte detailLevel;
|
||||
int posX;
|
||||
int posZ;
|
||||
@@ -140,7 +168,7 @@ public class LodWorldGenerator
|
||||
// an easy way to do so.
|
||||
|
||||
// add the near positions
|
||||
if (posToGenerate.getNthDetail(nearIndex, true) != 0 && nearIndex < posToGenerate.getNumberOfNearPos())
|
||||
if (nearIndex < posToGenerate.getNumberOfNearPos() && posToGenerate.getNthDetail(nearIndex, true) != 0)
|
||||
{
|
||||
detailLevel = (byte) (posToGenerate.getNthDetail(nearIndex, true) - 1);
|
||||
posX = posToGenerate.getNthPosX(nearIndex, true);
|
||||
@@ -159,13 +187,12 @@ public class LodWorldGenerator
|
||||
|
||||
positionsWaitingToBeGenerated.add(chunkPos);
|
||||
numberOfChunksWaitingToGenerate.addAndGet(1);
|
||||
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
|
||||
genWorker.queueWork();
|
||||
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
|
||||
}
|
||||
|
||||
|
||||
// add the far positions
|
||||
if (posToGenerate.getNthDetail(farIndex, false) != 0 && farIndex < posToGenerate.getNumberOfFarPos())
|
||||
if (farIndex < posToGenerate.getNumberOfFarPos() && posToGenerate.getNthDetail(farIndex, false) != 0)
|
||||
{
|
||||
detailLevel = (byte) (posToGenerate.getNthDetail(farIndex, false) - 1);
|
||||
posX = posToGenerate.getNthPosX(farIndex, false);
|
||||
@@ -185,13 +212,12 @@ public class LodWorldGenerator
|
||||
|
||||
positionsWaitingToBeGenerated.add(chunkPos);
|
||||
numberOfChunksWaitingToGenerate.addAndGet(1);
|
||||
LodGenWorker genWorker = new LodGenWorker(chunkPos, DetailDistanceUtil.getDistanceGenerationMode(detailLevel), lodBuilder, lodDim, serverWorld);
|
||||
genWorker.queueWork();
|
||||
queueWork(chunkPos, mode, lodBuilder, lodDim, serverWorld);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (RuntimeException e)
|
||||
{
|
||||
// this shouldn't ever happen, but just in case
|
||||
e.printStackTrace();
|
||||
@@ -202,8 +228,146 @@ public class LodWorldGenerator
|
||||
}
|
||||
});
|
||||
|
||||
mainGenThread.execute(generatorThread);
|
||||
if (VERSION_CONSTANTS.isWorldGeneratorSingleThreaded(mode))
|
||||
{
|
||||
generatorFunc.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
mainGenThread.execute(generatorFunc);
|
||||
}
|
||||
} // 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
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,4 +11,35 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.seibel.lod.core.dataFormat;
|
||||
|
||||
public class DataMerger
|
||||
{
|
||||
}
|
||||
@@ -12,27 +12,27 @@ public class LightFormat
|
||||
public final static byte SKY_LIGHT_MASK = 0b1111;
|
||||
|
||||
|
||||
public byte formatLightAsByte(byte skyLight, byte blockLight)
|
||||
public static byte formatLightAsByte(byte skyLight, byte blockLight)
|
||||
{
|
||||
return (byte) (((skyLight & SKY_LIGHT_MASK) << BYTE_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << BYTE_BLOCK_LIGHT_SHIFT));
|
||||
return (byte) (((skyLight & SKY_LIGHT_MASK) << (BYTE_SKY_LIGHT_SHIFT + 4)) | ((blockLight & BLOCK_LIGHT_MASK) << (BYTE_BLOCK_LIGHT_SHIFT + 4)));
|
||||
}
|
||||
|
||||
public int formatLightAsInt(byte skyLight, byte blockLight)
|
||||
public static int formatLightAsInt(byte skyLight, byte blockLight)
|
||||
{
|
||||
return ((skyLight & SKY_LIGHT_MASK) << INT_SKY_LIGHT_SHIFT) | ((blockLight & BLOCK_LIGHT_MASK) << INT_BLOCK_LIGHT_SHIFT);
|
||||
}
|
||||
|
||||
public int convertByteToIntFormat(byte lights)
|
||||
public static int convertByteToIntFormat(byte lights)
|
||||
{
|
||||
return 0;
|
||||
return formatLightAsInt((byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK), (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK));
|
||||
}
|
||||
|
||||
public byte getSkyLight(byte lights)
|
||||
public static byte getSkyLight(byte lights)
|
||||
{
|
||||
return (byte) ((lights >>> BYTE_SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK);
|
||||
}
|
||||
|
||||
public byte getBlockLight(byte lights)
|
||||
public static byte getBlockLight(byte lights)
|
||||
{
|
||||
return (byte) ((lights >>> BYTE_BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK);
|
||||
}
|
||||
|
||||
@@ -44,26 +44,42 @@ public class PositionDataFormat
|
||||
{
|
||||
return (byte) ((dataPoint >>> LOD_COUNT_SHIFT) & LOD_COUNT_MASK);
|
||||
}
|
||||
|
||||
public static boolean getFlag(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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,18 +10,29 @@ public class VerticalDataFormat
|
||||
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 EMPTY_LOD_SHIFT = 0;
|
||||
public final static byte EXISTENCE_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 EMPTY_LOD_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 = 0;
|
||||
|
||||
|
||||
public static int createVerticalData(int height, int depth, int level, boolean transparent, boolean bottom)
|
||||
{
|
||||
int verticalData = 0;
|
||||
@@ -32,7 +43,7 @@ public class VerticalDataFormat
|
||||
verticalData |= BOTTOM_TYPE_MASK << BOTTOM_TYPE_SHIFT;
|
||||
if (transparent)
|
||||
verticalData |= TRANSPARENCY_MASK << TRANSPARENCY_SHIFT;
|
||||
verticalData |= EMPTY_LOD_MASK << EMPTY_LOD_SHIFT;
|
||||
verticalData |= EXISTENCE_MASK << EXISTENCE_SHIFT;
|
||||
|
||||
return verticalData;
|
||||
}
|
||||
@@ -64,7 +75,37 @@ public class VerticalDataFormat
|
||||
|
||||
public static boolean doesItExist(int verticalData)
|
||||
{
|
||||
return (((verticalData >>> EMPTY_LOD_SHIFT) & EMPTY_LOD_MASK) == 1);
|
||||
return (((verticalData >>> EXISTENCE_SHIFT) & EXISTENCE_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,6 +19,8 @@
|
||||
|
||||
package com.seibel.lod.core.enums.config;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* NONE <br>
|
||||
* BIOME_ONLY <br>
|
||||
@@ -92,4 +94,46 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
|
||||
* licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.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,6 +19,8 @@
|
||||
|
||||
package com.seibel.lod.core.enums.config;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* heightmap <br>
|
||||
* multi_lod <br>
|
||||
@@ -77,4 +79,34 @@ 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,7 +20,6 @@
|
||||
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
|
||||
@@ -35,7 +34,7 @@ import com.seibel.lod.core.objects.math.Mat4f;
|
||||
* different MC versions.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-26-2021
|
||||
* @version 12-14-2021
|
||||
*/
|
||||
public interface IReflectionHandler
|
||||
{
|
||||
@@ -45,14 +44,6 @@ public interface IReflectionHandler
|
||||
/** @returns if Vivecraft is present. Attempts to find the "VRRenderer" class. */
|
||||
boolean vivecraftPresent();
|
||||
|
||||
/**
|
||||
* Modifies the projection matrix's clip planes.
|
||||
* The projection matrix must be in column-major format.
|
||||
*
|
||||
* @param projectionMatrix The projection matrix to be modified.
|
||||
* @param newNearClipPlane the new near clip plane value.
|
||||
* @param newFarClipPlane the new far clip plane value.
|
||||
* @return The modified matrix.
|
||||
*/
|
||||
Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane);
|
||||
/** @returns if Sodium (or a sodium like) mod is present. Attempts to find the "SodiumWorldRenderer" class. */
|
||||
boolean sodiumPresent();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ 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;
|
||||
@@ -78,7 +79,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 = 7;
|
||||
public static final int LOD_SAVE_FILE_VERSION = 8;
|
||||
|
||||
/**
|
||||
* Allow saving asynchronously, but never try to save multiple regions
|
||||
@@ -100,6 +101,8 @@ public class LodDimensionFileHandler
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// read from file //
|
||||
//================//
|
||||
@@ -116,130 +119,65 @@ public class LodDimensionFileHandler
|
||||
|
||||
for (byte tempDetailLevel = LodUtil.REGION_DETAIL_LEVEL; tempDetailLevel >= detailLevel; tempDetailLevel--)
|
||||
{
|
||||
String fileName = getFileNameAndPathForRegion(regionX, regionZ, generationMode, tempDetailLevel, verticalQuality);
|
||||
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
|
||||
|
||||
try
|
||||
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)))
|
||||
{
|
||||
// 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.");
|
||||
int fileVersion;
|
||||
fileVersion = inputStream.read();
|
||||
|
||||
File file = new File(fileName);
|
||||
if (!file.exists())
|
||||
// check if this file can be read by this file handler
|
||||
if (fileVersion < 6)
|
||||
{
|
||||
//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;
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// don't try parsing empty files
|
||||
long dataSize = file.length();
|
||||
dataSize -= 1;
|
||||
if (dataSize > 0)
|
||||
else if (fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
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();
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (IOException ioEx)
|
||||
{
|
||||
// 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();
|
||||
ClientApi.LOGGER.error("LOD file read error. Unable to read xz compressed file [" + file + "] error [" + ioEx.getMessage() + "]: ");
|
||||
ioEx.printStackTrace();
|
||||
}
|
||||
}// for each detail level
|
||||
|
||||
@@ -294,163 +232,111 @@ public class LodDimensionFileHandler
|
||||
{
|
||||
for (byte detailLevel = region.getMinDetailLevel(); detailLevel <= LodUtil.REGION_DETAIL_LEVEL; detailLevel++)
|
||||
{
|
||||
String fileName = getFileNameAndPathForRegion(region.regionPosX, region.regionPosZ, region.getGenerationMode(), detailLevel, region.getVerticalQuality());
|
||||
// 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.");
|
||||
|
||||
// if the fileName was null that means the folder is inaccessible
|
||||
// for some reason
|
||||
if (fileName == null)
|
||||
boolean isFileFullyGened = false;
|
||||
// make sure the file and folder exists
|
||||
if (!oldFile.exists())
|
||||
{
|
||||
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();
|
||||
// the file doesn't exist,
|
||||
// create it and the folder if need be
|
||||
if (!oldFile.getParentFile().exists())
|
||||
oldFile.getParentFile().mkdirs();
|
||||
try {
|
||||
oldFile.createNewFile();
|
||||
}
|
||||
else
|
||||
{
|
||||
// the file exists, make sure it
|
||||
// is the correct version.
|
||||
// (to make sure we don't overwrite a newer
|
||||
// version file if it exists)
|
||||
int fileVersion = LOD_SAVE_FILE_VERSION;
|
||||
int isFull = 0;
|
||||
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
|
||||
{
|
||||
fileVersion = inputStream.read();
|
||||
inputStream.skip(1);
|
||||
isFull = inputStream.read() & 0b10000000;
|
||||
inputStream.close();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
// check if this file can be written to by the file handler
|
||||
if (fileVersion > LOD_SAVE_FILE_VERSION)
|
||||
{
|
||||
// the file we are reading is a newer version,
|
||||
// don't write anything, we don't want to accidentally
|
||||
// delete anything the user may want.
|
||||
return;
|
||||
}
|
||||
if ((temp[1] & 0b10000000) != 0b10000000 && isFull == 0b10000000)
|
||||
{
|
||||
// existing file is complete while new one is only partially generate
|
||||
// this can happen is for some reason loading failed
|
||||
// this doesn't fix the bug, but at least protects old data
|
||||
ClientApi.LOGGER.error("LOD file write error. Attempted to overwrite complete region with incomplete one [" + fileName + "]");
|
||||
return;
|
||||
}
|
||||
// if we got this far then we are good
|
||||
// to overwrite the old file
|
||||
}
|
||||
// the old file is good, now create a new temporary save file
|
||||
File newFile = new File(fileName + TMP_FILE_EXTENSION);
|
||||
try (XZCompressorOutputStream outputStream = new XZCompressorOutputStream(new FileOutputStream(newFile), 3))
|
||||
{
|
||||
// add the version of this file
|
||||
outputStream.write(LOD_SAVE_FILE_VERSION);
|
||||
|
||||
// add each LodChunk to the file
|
||||
outputStream.write(temp);
|
||||
outputStream.close();
|
||||
|
||||
// overwrite the old file with the new one
|
||||
Files.move(newFile.toPath(), oldFile.toPath(), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to create parent directory for [" + oldFile + "] error [" + e.getMessage() + "]: ");
|
||||
e.printStackTrace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
else
|
||||
{
|
||||
ClientApi.LOGGER.error("LOD file write error. Unable to write to [" + fileName + "] error [" + e.getMessage() + "]: ");
|
||||
// the file exists, make sure it
|
||||
// is the correct version.
|
||||
// (to make sure we don't overwrite a newer
|
||||
// version file if it exists)
|
||||
int fileVersion = LOD_SAVE_FILE_VERSION;
|
||||
try (XZCompressorInputStream inputStream = new XZCompressorInputStream(new FileInputStream(oldFile)))
|
||||
{
|
||||
fileVersion = inputStream.read();
|
||||
inputStream.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.
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (IOException 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() + "]: ");
|
||||
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>
|
||||
@@ -460,26 +346,40 @@ public class LodDimensionFileHandler
|
||||
* <p>
|
||||
* Returns null if there is an IO or security Exception.
|
||||
*/
|
||||
private String getFileNameAndPathForRegion(int regionX, int regionZ, DistanceGenerationMode generationMode, byte detailLevel, VerticalQuality verticalQuality)
|
||||
{
|
||||
try
|
||||
{
|
||||
// saveFolder is something like
|
||||
// ".\Super Flat\DIM-1\data\"
|
||||
// or
|
||||
// ".\Super Flat\data\"
|
||||
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
|
||||
verticalQuality + File.separatorChar +
|
||||
generationMode.toString() + File.separatorChar +
|
||||
DETAIL_FOLDER_NAME_PREFIX + detailLevel + File.separatorChar +
|
||||
FILE_NAME_PREFIX + "." + regionX + "." + regionZ + FILE_EXTENSION;
|
||||
}
|
||||
catch (IOException | SecurityException e)
|
||||
{
|
||||
ClientApi.LOGGER.warn("Unable to get the filename for the region [" + regionX + ", " + regionZ + "], error: [" + e.getMessage() + "], stacktrace: ");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
|
||||
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 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,26 +26,28 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.seibel.lod.core.ModInfo;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
|
||||
/**
|
||||
* A singleton used to get variables from methods
|
||||
* where they are private or potentially absent.
|
||||
* Specifically the fog setting in Optifine or the
|
||||
* presence/absence of other mods.
|
||||
* For example: the fog setting in Optifine or the
|
||||
* presence/absence of Vivecraft.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-26-2021
|
||||
* @version 12-14-2021
|
||||
*/
|
||||
public class ReflectionHandler implements IReflectionHandler
|
||||
{
|
||||
private static final Logger LOGGER = LogManager.getLogger(ModInfo.NAME + "-" + ReflectionHandler.class.getSimpleName());
|
||||
|
||||
private static ReflectionHandler instance;
|
||||
public static ReflectionHandler instance;
|
||||
|
||||
private Field ofFogField = null;
|
||||
private final Object mcOptionsObject;
|
||||
|
||||
private Boolean sodiumPresent = null;
|
||||
|
||||
|
||||
|
||||
|
||||
private ReflectionHandler(Field[] optionFields, Object newMcOptionsObject)
|
||||
@@ -75,6 +77,8 @@ public class ReflectionHandler implements IReflectionHandler
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** finds the Optifine fog type field */
|
||||
private void setupFogField(Field[] optionFields)
|
||||
{
|
||||
@@ -155,41 +159,29 @@ 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 Mat4f ModifyProjectionClipPlanes(Mat4f projectionMatrix, float newNearClipPlane, float newFarClipPlane)
|
||||
public boolean sodiumPresent()
|
||||
{
|
||||
// find the matrix values.
|
||||
float nearMatrixValue = -((newFarClipPlane + newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
|
||||
float farMatrixValue = -((2 * newFarClipPlane * newNearClipPlane) / (newFarClipPlane - newNearClipPlane));
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return sodiumPresent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -200,8 +200,9 @@ public class VertexOptimizer
|
||||
public final Map<LodDirection, byte[]> skyLights;
|
||||
public byte blockLight;
|
||||
|
||||
/** Holds if the given direction should be culled or not */
|
||||
public final boolean[] culling;
|
||||
boolean skipTop;
|
||||
boolean skipBot;
|
||||
|
||||
|
||||
|
||||
/** creates an empty box */
|
||||
@@ -235,8 +236,6 @@ public class VertexOptimizer
|
||||
put(LodDirection.SOUTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
|
||||
put(LodDirection.NORTH, new int[LodUtil.MAX_NUMBER_OF_VERTICAL_LODS]);
|
||||
}};
|
||||
|
||||
culling = new boolean[6];
|
||||
}
|
||||
|
||||
/** Set the light of the columns */
|
||||
@@ -309,31 +308,6 @@ public class VertexOptimizer
|
||||
}
|
||||
}
|
||||
|
||||
/** determine which faces should be culled */
|
||||
public void setUpCulling(int cullingDistance, AbstractBlockPosWrapper playerPos)
|
||||
{
|
||||
for (LodDirection lodDirection : DIRECTIONS)
|
||||
{
|
||||
if (lodDirection == LodDirection.DOWN || lodDirection == LodDirection.WEST || lodDirection == LodDirection.NORTH)
|
||||
culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) > getFacePos(lodDirection) + cullingDistance;
|
||||
|
||||
else if (lodDirection == LodDirection.UP || lodDirection == LodDirection.EAST || lodDirection == LodDirection.SOUTH)
|
||||
culling[DIRECTION_INDEX.get(lodDirection)] = playerPos.get(lodDirection.getAxis()) < getFacePos(lodDirection) - cullingDistance;
|
||||
|
||||
culling[DIRECTION_INDEX.get(lodDirection)] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param lodDirection direction that we want to check if it's culled
|
||||
* @return true if and only if the face of the direction is culled
|
||||
*/
|
||||
public boolean isCulled(LodDirection lodDirection)
|
||||
{
|
||||
return culling[DIRECTION_INDEX.get(lodDirection)];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method create all the shared face culling based on the adjacent data
|
||||
* @param adjData data adjacent to the column we are going to render
|
||||
@@ -346,15 +320,13 @@ public class VertexOptimizer
|
||||
int maxY = getMaxY();
|
||||
long singleAdjDataPoint;
|
||||
|
||||
/* TODO implement attached vertical face culling
|
||||
// TODO transparency uncomment final condition to see ocean floor
|
||||
//Up direction case
|
||||
if(DataPointUtil.doesItExist(adjData.get(Direction.UP)))
|
||||
{
|
||||
height = DataPointUtil.getHeight(singleAdjDataPoint);
|
||||
depth = DataPointUtil.getDepth(singleAdjDataPoint);
|
||||
}*/
|
||||
singleAdjDataPoint = adjData.get(LodDirection.UP)[0];
|
||||
skipTop = DataPointUtil.doesItExist(singleAdjDataPoint) && DataPointUtil.getDepth(singleAdjDataPoint) == maxY;// && DataPointUtil.getAlpha(singleAdjDataPoint) == 255;
|
||||
//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
|
||||
@@ -363,9 +335,6 @@ public class VertexOptimizer
|
||||
//TODO clean some similar cases
|
||||
for (LodDirection lodDirection : ADJ_DIRECTIONS)
|
||||
{
|
||||
if (isCulled(lodDirection))
|
||||
continue;
|
||||
|
||||
long[] dataPoint = adjData.get(lodDirection);
|
||||
if (dataPoint == null || DataPointUtil.isVoid(dataPoint[0]))
|
||||
{
|
||||
@@ -383,17 +352,22 @@ 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)
|
||||
@@ -447,7 +421,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
|
||||
@@ -520,24 +494,14 @@ public class VertexOptimizer
|
||||
boxOffset[Z] = zOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method return the position of a face in the axis of the face
|
||||
* This is useful for the face culling
|
||||
* @param lodDirection that we want to check
|
||||
* @return position in the axis of the face
|
||||
*/
|
||||
public int getFacePos(LodDirection lodDirection)
|
||||
{
|
||||
return boxOffset[FACE_DIRECTION.get(lodDirection)[0]] + boxWidth[FACE_DIRECTION.get(lodDirection)[0]] * FACE_DIRECTION.get(lodDirection)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if the given direction should be rendered.
|
||||
*/
|
||||
/** returns true if the given direction should be rendered. */
|
||||
public boolean shouldRenderFace(LodDirection lodDirection, int adjIndex)
|
||||
{
|
||||
if (lodDirection == LodDirection.UP || lodDirection == LodDirection.DOWN)
|
||||
return adjIndex == 0;
|
||||
if (lodDirection == LodDirection.UP)
|
||||
return adjIndex == 0 && !skipTop;
|
||||
if (lodDirection == LodDirection.DOWN)
|
||||
return adjIndex == 0 && !skipBot;
|
||||
|
||||
return !(adjHeight.get(lodDirection)[adjIndex] == VOID_FACE && adjDepth.get(lodDirection)[adjIndex] == VOID_FACE);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -81,7 +85,7 @@ public interface LevelContainer
|
||||
byte getDetailLevel();
|
||||
|
||||
|
||||
int getMaxVerticalData();
|
||||
int getVerticalSize();
|
||||
|
||||
/** Clears the dataPoint at the given array index */
|
||||
void clear(int posX, int posZ);
|
||||
@@ -99,13 +103,13 @@ public interface LevelContainer
|
||||
* @param posZ z position in the detail level to update
|
||||
*/
|
||||
void updateData(LevelContainer lowerLevelContainer, int posX, int posZ);
|
||||
|
||||
|
||||
/**
|
||||
* This will give the data to save in the file
|
||||
* @return data as a String
|
||||
* This will write the raw data with metadata to the output stream
|
||||
* @return isAllGenerated whether the data is all generated
|
||||
* @throws IOException
|
||||
*/
|
||||
byte[] toDataString();
|
||||
|
||||
boolean writeData(DataOutputStream output) throws IOException;
|
||||
|
||||
/**
|
||||
* This will give the data to save in the file
|
||||
|
||||
@@ -310,6 +310,32 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -325,116 +351,99 @@ 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;
|
||||
|
||||
Thread thread = new Thread(() ->
|
||||
{
|
||||
int regionX;
|
||||
int regionZ;
|
||||
int minDistance;
|
||||
byte detail;
|
||||
byte minAllowedDetailLevel;
|
||||
|
||||
|
||||
Runnable thread = () -> {
|
||||
|
||||
// go over every region in the dimension
|
||||
for (int x = 0; x < regions.length; x++)
|
||||
{
|
||||
for (int z = 0; z < regions.length; z++)
|
||||
{
|
||||
regionX = (x + center.x) - halfWidth;
|
||||
regionZ = (z + center.z) - halfWidth;
|
||||
|
||||
if (regions[x][z] != null)
|
||||
{
|
||||
// check what detail level this region should be
|
||||
// and cut it if it is higher then that
|
||||
minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ);
|
||||
detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance);
|
||||
minAllowedDetailLevel = DetailDistanceUtil.getCutLodDetail(detail);
|
||||
|
||||
if (regions[x][z].getMinDetailLevel() > minAllowedDetailLevel)
|
||||
{
|
||||
regions[x][z].cutTree(minAllowedDetailLevel);
|
||||
recreateRegionBuffer[x][z] = true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
cutAndExpandThread.execute(thread);
|
||||
}
|
||||
}
|
||||
@@ -681,7 +690,10 @@ 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() == GenerationPriority.AUTO && MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
|
||||
GenerationPriority generationPriority = CONFIG.client().worldGenerator().getGenerationPriority();
|
||||
if (generationPriority == GenerationPriority.AUTO)
|
||||
generationPriority = MC.hasSinglePlayerServer() ? GenerationPriority.FAR_FIRST : GenerationPriority.NEAR_FIRST;
|
||||
|
||||
boolean requireCorrectDetailLevel = generationPriority == GenerationPriority.NEAR_FIRST;
|
||||
|
||||
if (region != null)
|
||||
|
||||
@@ -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].getMaxVerticalData();
|
||||
return dataContainer[detailLevel].getVerticalSize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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-8-2021
|
||||
* @version 12-9-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.getVertexSize());
|
||||
this.ensureCapacity(this.format.getByteSize());
|
||||
}
|
||||
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.getVertexSize();
|
||||
this.totalRenderedBytes += this.vertices * this.format.getByteSize();
|
||||
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().getVertexSize();
|
||||
this.totalUploadedBytes += bufferbuilder$drawstate.vertexCount() * bufferbuilder$drawstate.format().getByteSize();
|
||||
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.getVertexSize());
|
||||
this.buffer.position(this.vertices * this.format.getVertexSize());
|
||||
ensureCapacity(buffer.limit() + this.format.getByteSize());
|
||||
this.buffer.position(this.vertices * this.format.getByteSize());
|
||||
this.buffer.put(buffer);
|
||||
this.vertices += buffer.limit() / this.format.getVertexSize();
|
||||
this.vertices += buffer.limit() / this.format.getByteSize();
|
||||
this.nextElementByte += buffer.limit();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
package com.seibel.lod.core.objects.opengl;
|
||||
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
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 = GL15.glGenBuffers();
|
||||
this.id = GL32.glGenBuffers();
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public class LodVertexBuffer implements AutoCloseable
|
||||
{
|
||||
if (this.id >= 0)
|
||||
{
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> GL15.glDeleteBuffers(this.id));
|
||||
GLProxy.getInstance().recordOpenGlCall(() -> GL32.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 11-13-2021
|
||||
* @version 12-9-2021
|
||||
*/
|
||||
public class LodVertexFormat
|
||||
{
|
||||
private final ImmutableList<LodVertexFormatElement> elements;
|
||||
private final IntList offsets = new IntArrayList();
|
||||
private final int vertexSize;
|
||||
private final int byteSize;
|
||||
|
||||
public LodVertexFormat(ImmutableList<LodVertexFormatElement> elementList)
|
||||
{
|
||||
@@ -54,17 +54,12 @@ public class LodVertexFormat
|
||||
i += LodVertexFormatElement.getByteSize();
|
||||
}
|
||||
|
||||
this.vertexSize = i;
|
||||
this.byteSize = i;
|
||||
}
|
||||
|
||||
public int getIntegerSize()
|
||||
public int getByteSize()
|
||||
{
|
||||
return this.getVertexSize() / 4;
|
||||
}
|
||||
|
||||
public int getVertexSize()
|
||||
{
|
||||
return this.vertexSize;
|
||||
return this.byteSize;
|
||||
}
|
||||
|
||||
public ImmutableList<LodVertexFormatElement> getElements()
|
||||
@@ -98,7 +93,7 @@ public class LodVertexFormat
|
||||
else if (obj != null && this.getClass() == obj.getClass())
|
||||
{
|
||||
LodVertexFormat vertexformat = (LodVertexFormat) obj;
|
||||
return this.vertexSize == vertexformat.vertexSize && this.elements.equals(vertexformat.elements);
|
||||
return this.byteSize == vertexformat.byteSize && this.elements.equals(vertexformat.elements);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,43 +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.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) 2020 James Seibel
|
||||
* 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
|
||||
@@ -19,25 +19,26 @@
|
||||
|
||||
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.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
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;
|
||||
@@ -57,15 +58,16 @@ 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-8-2021
|
||||
* @version 12-9-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;
|
||||
|
||||
@@ -84,22 +86,72 @@ public class GLProxy
|
||||
/** the proxyWorker's GL capabilities */
|
||||
public final GLCapabilities proxyWorkerGlCapabilities;
|
||||
|
||||
|
||||
|
||||
/** This program contains all shaders required when rendering LODs */
|
||||
public LodShaderProgram lodShaderProgram;
|
||||
/** This is the VAO that is used when rendering */
|
||||
public final int vertexArrayObjectId;
|
||||
/** This is the 2D texture that holds MC's lightmap */
|
||||
public final int lightMapTextureId;
|
||||
|
||||
|
||||
/** Requires OpenGL 4.5, and offers the best buffer uploading */
|
||||
/** Requires OpenGL 4.4, and offers the best buffer uploading */
|
||||
public final boolean bufferStorageSupported;
|
||||
|
||||
/** Requires OpenGL 3.0 */
|
||||
public final boolean mapBufferRangeSupported;
|
||||
/** Requires OpenGL 4.5 */
|
||||
public final boolean namedObjectSupported;
|
||||
|
||||
/** Requires OpenGL 4.3 */
|
||||
public final boolean VertexAttributeBufferBindingSupported;
|
||||
|
||||
/** 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();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -109,7 +161,14 @@ 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
|
||||
@@ -123,7 +182,20 @@ 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();
|
||||
@@ -134,21 +206,24 @@ 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
|
||||
|
||||
|
||||
//==================================//
|
||||
@@ -156,88 +231,62 @@ public class GLProxy
|
||||
//==================================//
|
||||
|
||||
setGlContext(GLProxyContext.LOD_BUILDER);
|
||||
|
||||
ClientApi.LOGGER.info("Lod Render OpenGL version [" + GL11.glGetString(GL11.GL_VERSION) + "].");
|
||||
|
||||
// crash the game if the GPU doesn't support OpenGL 2.0
|
||||
if (!minecraftGlCapabilities.OpenGL20)
|
||||
{
|
||||
// Note: as of MC 1.17 this shouldn't happen since MC
|
||||
// requires OpenGL 3.3, but just in case.
|
||||
String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + " and discoverd this GPU doesn't support OpenGL 2.0 or greater.";
|
||||
MC.crashMinecraft(errorMessage + " Sorry I couldn't tell you sooner :(", new UnsupportedOperationException("This GPU doesn't support OpenGL 2.0 or greater."));
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
if (enableDebugLogging)
|
||||
try {
|
||||
GLUtil.setupDebugMessageCallback(new PrintStream(proxyLog));
|
||||
} catch (FileNotFoundException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// get specific capabilities
|
||||
bufferStorageSupported = lodBuilderGlCapabilities.glBufferStorage != 0;
|
||||
mapBufferRangeSupported = lodBuilderGlCapabilities.glMapBufferRange != 0;
|
||||
|
||||
// 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;
|
||||
// display the capabilities
|
||||
if (!bufferStorageSupported)
|
||||
{
|
||||
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.");
|
||||
ClientApi.LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
|
||||
}
|
||||
|
||||
|
||||
// if using AUTO gpuUpload
|
||||
// determine a good default for the GPU
|
||||
if (CONFIG.client().advanced().buffers().getGpuUploadMethod() == GpuUploadMethod.AUTO)
|
||||
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
|
||||
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
|
||||
{
|
||||
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 + "].");
|
||||
// NVIDIA card
|
||||
preferredUploadMethod = bufferStorageSupported ? GpuUploadMethod.BUFFER_STORAGE : GpuUploadMethod.SUB_DATA;
|
||||
}
|
||||
else
|
||||
{
|
||||
// AMD or Intel card
|
||||
preferredUploadMethod = GpuUploadMethod.BUFFER_MAPPING;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// 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();
|
||||
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
//==========//
|
||||
// clean up //
|
||||
@@ -246,88 +295,10 @@ 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.
|
||||
@@ -397,21 +368,29 @@ 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.
|
||||
@@ -452,7 +431,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 void disableLegacyFog()
|
||||
public boolean disableLegacyFog()
|
||||
{
|
||||
// make sure this is a legacy OpenGL context
|
||||
if (minecraftGlCapabilities.glFogf != 0)
|
||||
@@ -465,7 +444,9 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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) 2020 James Seibel
|
||||
* 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
|
||||
@@ -22,9 +22,7 @@ package com.seibel.lod.core.render;
|
||||
import java.awt.Color;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.seibel.lod.core.api.ApiShared;
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
@@ -34,22 +32,17 @@ import com.seibel.lod.core.enums.config.GpuUploadMethod;
|
||||
import com.seibel.lod.core.enums.rendering.DebugMode;
|
||||
import com.seibel.lod.core.enums.rendering.FogColorMode;
|
||||
import com.seibel.lod.core.enums.rendering.FogDistance;
|
||||
import com.seibel.lod.core.enums.rendering.FogDrawMode;
|
||||
import com.seibel.lod.core.handlers.IReflectionHandler;
|
||||
import com.seibel.lod.core.objects.lod.LodDimension;
|
||||
import com.seibel.lod.core.objects.lod.RegionPos;
|
||||
import com.seibel.lod.core.objects.math.Mat4f;
|
||||
import com.seibel.lod.core.objects.math.Vec3d;
|
||||
import com.seibel.lod.core.objects.math.Vec3f;
|
||||
import com.seibel.lod.core.objects.opengl.LodVertexBuffer;
|
||||
import com.seibel.lod.core.objects.rending.LodFogConfig;
|
||||
import com.seibel.lod.core.render.shader.LodShaderProgram;
|
||||
import com.seibel.lod.core.render.objects.LightmapTexture;
|
||||
import com.seibel.lod.core.util.DetailDistanceUtil;
|
||||
import com.seibel.lod.core.util.LevelPosUtil;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.util.SingletonHandler;
|
||||
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.AbstractBlockPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.config.ILodConfigWrapperSingleton;
|
||||
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
|
||||
@@ -61,7 +54,7 @@ import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-8-2021
|
||||
* @version 12-12-2021
|
||||
*/
|
||||
public class LodRenderer
|
||||
{
|
||||
@@ -69,8 +62,6 @@ 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
|
||||
@@ -78,8 +69,8 @@ public class LodRenderer
|
||||
*/
|
||||
public DebugMode previousDebugMode = DebugMode.OFF;
|
||||
|
||||
private int farPlaneBlockDistance;
|
||||
|
||||
// This tells us if the renderer is enabled or not. If in a world, it should be enabled.
|
||||
private boolean isSetupComplete = false;
|
||||
|
||||
/** This is used to generate the buildable buffers */
|
||||
private final LodBufferBuilderFactory lodBufferBuilderFactory;
|
||||
@@ -91,18 +82,18 @@ public class LodRenderer
|
||||
* These have to be separate because we can't override the
|
||||
* buffers in the VBOs (and we don't want to)
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private int[][][] storageBufferIds;
|
||||
private int[][][] storageBufferIds = null;
|
||||
|
||||
private AbstractChunkPosWrapper vbosCenter = FACTORY.createChunkPos();
|
||||
// The shader program
|
||||
LodRenderProgram shaderProgram = null;
|
||||
|
||||
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;
|
||||
@@ -118,6 +109,7 @@ 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
|
||||
@@ -126,22 +118,16 @@ 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.
|
||||
@@ -163,6 +149,8 @@ public class LodRenderer
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (MC_RENDER.playerHasBlindnessEffect())
|
||||
{
|
||||
// if the player is blind, don't render LODs,
|
||||
@@ -170,16 +158,25 @@ 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);
|
||||
|
||||
if (CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
|
||||
GLProxy.getInstance().disableLegacyFog();
|
||||
|
||||
|
||||
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
if (canVanillaFogBeDisabled && CONFIG.client().graphics().fogQuality().getDisableVanillaFog())
|
||||
if (!glProxy.disableLegacyFog())
|
||||
if (!MC_RENDER.tryDisableVanillaFog())
|
||||
canVanillaFogBeDisabled = false;
|
||||
|
||||
// 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 //
|
||||
@@ -193,7 +190,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(), true);
|
||||
lodBufferBuilderFactory.generateLodBuffersAsync(this, lodDim, MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getY(), MC.getPlayerBlockPos().getZ(), fullRegen);
|
||||
|
||||
// the regen process has been started,
|
||||
// it will be done when lodBufferBuilder.newBuffersAvailable()
|
||||
@@ -210,348 +207,169 @@ 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 //
|
||||
//===================//
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// initial setup //
|
||||
//===============//
|
||||
|
||||
profiler.push("LOD setup");
|
||||
|
||||
GLProxy glProxy = GLProxy.getInstance();
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// set the required open GL settings
|
||||
|
||||
if (CONFIG.client().advanced().debugging().getDebugMode() == DebugMode.SHOW_DETAIL_WIREFRAME)
|
||||
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_LINE);
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
|
||||
else
|
||||
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
|
||||
|
||||
GL15.glEnable(GL15.GL_CULL_FACE);
|
||||
GL15.glEnable(GL15.GL_DEPTH_TEST);
|
||||
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
|
||||
|
||||
GL32.glEnable(GL32.GL_CULL_FACE);
|
||||
GL32.glEnable(GL32.GL_DEPTH_TEST);
|
||||
|
||||
// enable transparent rendering
|
||||
GL15.glBlendFunc(GL15.GL_SRC_ALPHA, GL15.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL15.glEnable(GL15.GL_BLEND);
|
||||
|
||||
// get MC's shader program
|
||||
int currentProgram = GL20.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
// GL32.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA);
|
||||
// GL32.glEnable(GL32.GL_BLEND);
|
||||
|
||||
/*---------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);
|
||||
vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH;
|
||||
int farPlaneBlockDistance;
|
||||
// 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");
|
||||
|
||||
Mat4f projectionMatrix = createProjectionMatrix(mcProjectionMatrix, vanillaBlockRenderedDistance);
|
||||
boolean cullingDisabled = CONFIG.client().graphics().advancedGraphics().getDisableDirectionalCulling();
|
||||
boolean usingBufferStorage = glProxy.getGpuUploadMethod() == GpuUploadMethod.BUFFER_STORAGE;
|
||||
|
||||
LodFogConfig fogSettings = determineFogConfig();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (vbos != null)
|
||||
// where the center of the buffers is (needed when culling regions)
|
||||
// render each of the buffers
|
||||
for (int x = 0; x < vbos.length; x++)
|
||||
{
|
||||
//==============//
|
||||
// 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
|
||||
for (int z = 0; z < vbos.length; z++)
|
||||
{
|
||||
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)
|
||||
RegionPos vboCenterRegionPos = new RegionPos(vbosCenter);
|
||||
RegionPos vboPos = new RegionPos();
|
||||
|
||||
|
||||
// render each of the buffers
|
||||
for (int x = 0; x < vbos.length; x++)
|
||||
{
|
||||
for (int z = 0; z < vbos.length; z++)
|
||||
//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)))
|
||||
{
|
||||
vboPos.x = x + vboCenterRegionPos.x - (lodDim.getWidth() / 2);
|
||||
vboPos.z = z + vboCenterRegionPos.z - (lodDim.getWidth() / 2);
|
||||
|
||||
if (cullingDisabled || RenderUtil.isRegionInViewFrustum(MC_RENDER.getCameraBlockPosition(), MC_RENDER.getLookAtVector(), vboPos.blockPos()))
|
||||
// actual rendering
|
||||
int bufferId = 0;
|
||||
for (int i = 0; i < vbos[x][z].length; i++)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
|
||||
//================//
|
||||
// render cleanup //
|
||||
//================//
|
||||
|
||||
profiler.popPush("LOD cleanup");
|
||||
|
||||
GL15.glPolygonMode(GL15.GL_FRONT_AND_BACK, GL15.GL_FILL);
|
||||
GL15.glDisable(GL15.GL_BLEND); // TODO: what should this be reset to?
|
||||
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);
|
||||
|
||||
GL20.glUseProgram(currentProgram);
|
||||
// clear the depth buffer so everything is drawn over the LODs
|
||||
GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
// 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 * 1.6f * 0.9f;
|
||||
else
|
||||
// for more realistic fog when using FAR
|
||||
fogConfig.farFogStart = Math.min(vanillaBlockRenderedDistance * 1.5f, farPlaneBlockDistance * 0.9f * 1.6f);
|
||||
|
||||
fogConfig.farFogEnd = farPlaneBlockDistance * 1.6f;
|
||||
|
||||
|
||||
// near fog //
|
||||
|
||||
// the reason that I wrote fogEnd then fogStart backwards
|
||||
// is because we are using fog backwards to how
|
||||
// it is normally used, hiding near objects
|
||||
// instead of far objects.
|
||||
fogConfig.nearFogEnd = vanillaBlockRenderedDistance * 1.41f;
|
||||
fogConfig.nearFogStart = vanillaBlockRenderedDistance * 1.6f;
|
||||
|
||||
|
||||
return fogConfig;
|
||||
}
|
||||
|
||||
|
||||
private Color getFogColor()
|
||||
{
|
||||
Color fogColor;
|
||||
@@ -579,9 +397,12 @@ 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)
|
||||
AbstractBlockPosWrapper bufferPos = vbosCenter.getWorldPosition();
|
||||
double xDiff = projectedView.x - bufferPos.getX();
|
||||
double zDiff = projectedView.z - bufferPos.getZ();
|
||||
//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;
|
||||
double xDiff = projectedView.x - bufferPosX;
|
||||
double zDiff = projectedView.z - bufferPosZ;
|
||||
mcModelViewMatrix.multiplyTranslationMatrix(-xDiff, -projectedView.y, -zDiff);
|
||||
|
||||
return mcModelViewMatrix;
|
||||
@@ -593,9 +414,12 @@ public class LodRenderer
|
||||
*/
|
||||
private Vec3f getTranslatedCameraPos()
|
||||
{
|
||||
AbstractBlockPosWrapper worldCenter = vbosCenter.getWorldPosition();
|
||||
//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;
|
||||
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
|
||||
return new Vec3f((float)cameraPos.x - worldCenter.getX(), (float)cameraPos.y, (float)cameraPos.z - worldCenter.getZ());
|
||||
return new Vec3f((float)cameraPos.x - worldCenterX, (float)cameraPos.y, (float)cameraPos.z - worldCenterZ);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -603,7 +427,7 @@ public class LodRenderer
|
||||
* @param currentProjectionMatrix this is Minecraft's current projection matrix
|
||||
* @param vanillaBlockRenderedDistance Minecraft's vanilla far plane distance
|
||||
*/
|
||||
private Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance)
|
||||
private static Mat4f createProjectionMatrix(Mat4f currentProjectionMatrix, float vanillaBlockRenderedDistance, int farPlaneBlockDistance)
|
||||
{
|
||||
//Create a copy of the current matrix, so the current matrix isn't modified.
|
||||
Mat4f lodProj = currentProjectionMatrix.copy();
|
||||
@@ -616,38 +440,37 @@ public class LodRenderer
|
||||
return lodProj;
|
||||
}
|
||||
|
||||
private void applyFog(LodShaderProgram shaderProgram,
|
||||
LodFogConfig fogSettings, int fogEnabledUniform, int nearFogEnabledUniform, int farFogEnabledUniform,
|
||||
int nearFogStartUniform, int nearFogEndUniform, int farFogStartUniform, int farFogEndUniform)
|
||||
{
|
||||
if (fogSettings.fogDrawMode != FogDrawMode.FOG_DISABLED)
|
||||
{
|
||||
shaderProgram.setUniform(fogEnabledUniform, true);
|
||||
shaderProgram.setUniform(nearFogEnabledUniform, fogSettings.fogDistance != FogDistance.FAR);
|
||||
shaderProgram.setUniform(farFogEnabledUniform, fogSettings.fogDistance != FogDistance.NEAR);
|
||||
|
||||
// near
|
||||
shaderProgram.setUniform(nearFogStartUniform, fogSettings.nearFogStart);
|
||||
shaderProgram.setUniform(nearFogEndUniform, fogSettings.nearFogEnd);
|
||||
// far
|
||||
shaderProgram.setUniform(farFogStartUniform, fogSettings.farFogStart);
|
||||
shaderProgram.setUniform(farFogEndUniform, fogSettings.farFogEnd);
|
||||
//======================//
|
||||
// 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;
|
||||
}
|
||||
else
|
||||
{
|
||||
shaderProgram.setUniform(fogEnabledUniform, false);
|
||||
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()
|
||||
{
|
||||
lodBufferBuilderFactory.destroyBuffers();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// 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.
|
||||
@@ -671,17 +494,10 @@ public class LodRenderer
|
||||
VertexBuffersAndOffset result = lodBufferBuilderFactory.getVertexBuffers();
|
||||
vbos = result.vbos;
|
||||
storageBufferIds = result.storageBufferIds;
|
||||
vbosCenter = result.drawableCenterChunkPos;
|
||||
vbosCenterX = result.drawableCenterBlockPosX;
|
||||
vbosCenterZ = result.drawableCenterBlockPosZ;
|
||||
}
|
||||
|
||||
/** 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)
|
||||
{
|
||||
@@ -730,46 +546,10 @@ 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)
|
||||
{
|
||||
@@ -781,7 +561,6 @@ public class LodRenderer
|
||||
prevVanillaChunkTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
// check if there is any newly generated terrain to show
|
||||
if (newTime - prevChunkTime > CONFIG.client().advanced().buffers().getRebuildTimes().chunkChangeTimeout)
|
||||
{
|
||||
@@ -793,8 +572,6 @@ public class LodRenderer
|
||||
prevChunkTime = newTime;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// LOD skipping //
|
||||
//==============//
|
||||
@@ -836,5 +613,5 @@ public class LodRenderer
|
||||
|
||||
vanillaRenderedChunks = new boolean[vanillaRenderedChunksWidth][vanillaRenderedChunksWidth];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -85,11 +85,11 @@ public class RenderUtil
|
||||
* Returns true if one of the region's 4 corners is in front
|
||||
* of the camera.
|
||||
*/
|
||||
public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, AbstractBlockPosWrapper vboCenterPos)
|
||||
public static boolean isRegionInViewFrustum(AbstractBlockPosWrapper playerBlockPos, Vec3f cameraDir, int vboCenterPosX, int vboCenterPosZ)
|
||||
{
|
||||
// convert the vbo position into a direction vector
|
||||
// starting from the player's position
|
||||
Vec3f vboVec = new Vec3f(vboCenterPos.getX(), 0, vboCenterPos.getZ());
|
||||
Vec3f vboVec = new Vec3f(vboCenterPosX, 0, vboCenterPosZ);
|
||||
Vec3f playerVec = new Vec3f(playerBlockPos.getX(), playerBlockPos.getY(), playerBlockPos.getZ());
|
||||
|
||||
vboVec.subtract(playerVec);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
+34
-54
@@ -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) 2020 James Seibel
|
||||
* 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
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.core.render.shader;
|
||||
package com.seibel.lod.core.render.objects;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
@@ -26,7 +26,9 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
import com.seibel.lod.core.api.ClientApi;
|
||||
|
||||
/**
|
||||
* This object holds a OpenGL reference to a shader
|
||||
@@ -35,31 +37,42 @@ import org.lwjgl.opengl.GL20;
|
||||
* @author James Seibel
|
||||
* @version 11-8-2021
|
||||
*/
|
||||
public class LodShader
|
||||
public class Shader
|
||||
{
|
||||
/** OpenGL shader ID */
|
||||
public final int id;
|
||||
|
||||
|
||||
|
||||
/** Creates a shader with specified type. */
|
||||
public LodShader(int type)
|
||||
{
|
||||
id = GL20.glCreateShader(type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Loads a shader from file.
|
||||
*
|
||||
/** Creates a shader with specified type.
|
||||
* @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER.
|
||||
* @param path File path of the shader
|
||||
* @param absoluteFilePath If false the file path is relative to the resource jar folder.
|
||||
* @throws Exception if the shader fails to compile
|
||||
* @throws RuntimeException if the shader fails to compile
|
||||
*/
|
||||
public static LodShader loadShader(int type, String path, boolean absoluteFilePath)
|
||||
public Shader(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
|
||||
@@ -70,7 +83,7 @@ public class LodShader
|
||||
// Throws FileNotFoundException
|
||||
in = new FileInputStream(path); // Note: this should use OS path seperator
|
||||
} else {
|
||||
in = LodShader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
|
||||
in = Shader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/'
|
||||
if (in == null) {
|
||||
throw new FileNotFoundException("Shader file not found in resource: "+path);
|
||||
}
|
||||
@@ -86,39 +99,6 @@ public class LodShader
|
||||
{
|
||||
throw new RuntimeException("Unable to load shader from file [" + path + "]. Error: " + e.getMessage());
|
||||
}
|
||||
CharSequence shaderFileSource = stringBuilder.toString();
|
||||
|
||||
return createShader(type, shaderFileSource);
|
||||
return stringBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,169 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
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]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,203 +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.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,6 +23,7 @@ import static com.seibel.lod.core.builders.bufferBuilding.LodBufferBuilderFactor
|
||||
|
||||
import com.seibel.lod.core.enums.config.DistanceGenerationMode;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Leonardo Amato
|
||||
@@ -102,6 +103,9 @@ 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)
|
||||
{
|
||||
@@ -140,6 +144,12 @@ 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);
|
||||
@@ -211,7 +221,8 @@ public class DataPointUtil
|
||||
|
||||
public static int getColor(long dataPoint)
|
||||
{
|
||||
return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (/*((dataPoint >>> (ALPHA_SHIFT - ALPHA_DOWNSIZE_SHIFT)) | 0b1111)*/255 << 24));
|
||||
// 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);
|
||||
}
|
||||
|
||||
/** This is used to convert a dataPoint to string (useful for the print function) */
|
||||
@@ -268,13 +279,13 @@ public class DataPointUtil
|
||||
int size = dataToMerge.length / inputVerticalData;
|
||||
|
||||
// We initialize the arrays that are going to be used
|
||||
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT / 2 + 1) * 2);
|
||||
short[] heightAndDepth = ThreadMapUtil.getHeightAndDepth((WORLD_HEIGHT + 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;
|
||||
|
||||
@@ -288,115 +299,139 @@ public class DataPointUtil
|
||||
//We collect the indexes of the data, ordered by the depth
|
||||
for (int index = 0; index < size; index++)
|
||||
{
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
if (index == 0)
|
||||
{
|
||||
singleData = dataToMerge[index * inputVerticalData + dataIndex];
|
||||
if (doesItExist(singleData))
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
{
|
||||
genMode = Math.min(genMode, getGenerationMode(singleData));
|
||||
allEmpty = false;
|
||||
if (!isVoid(singleData))
|
||||
singleData = dataToMerge[dataIndex];
|
||||
if (doesItExist(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++)
|
||||
genMode = Math.min(genMode, getGenerationMode(singleData));
|
||||
allEmpty = false;
|
||||
if (!isVoid(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;
|
||||
}
|
||||
allVoid = false;
|
||||
count++;
|
||||
heightAndDepth[dataIndex * 2] = getHeight(singleData);
|
||||
heightAndDepth[dataIndex * 2 +1] = getDepth(singleData);
|
||||
}
|
||||
for (i = 0; i < count; i++)
|
||||
}
|
||||
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))
|
||||
{
|
||||
if (height <= heightAndDepth[i * 2] && height >= heightAndDepth[i * 2 + 1])
|
||||
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++)
|
||||
{
|
||||
topPos = i;
|
||||
break;
|
||||
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 if (height < heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
topPos = i;
|
||||
topExtend = true;
|
||||
break;
|
||||
if (height <= heightAndDepth[i * 2] && height > heightAndDepth[i * 2 + 1])
|
||||
{
|
||||
topPos = i;
|
||||
break;
|
||||
}
|
||||
else if (height <= heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count))
|
||||
{
|
||||
topPos = i;
|
||||
topExtend = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (topPos == -1)
|
||||
{
|
||||
if (botPos == -1)
|
||||
if (topPos == -1)
|
||||
{
|
||||
//whole block falls above
|
||||
extendArray(heightAndDepth, 2, 0, 1, count);
|
||||
heightAndDepth[0] = height;
|
||||
heightAndDepth[1] = depth;
|
||||
count++;
|
||||
if (botPos == -1)
|
||||
{
|
||||
//whole block falls above
|
||||
extendArray(heightAndDepth, 2, 0, 1, count);
|
||||
heightAndDepth[0] = height;
|
||||
heightAndDepth[1] = depth;
|
||||
count++;
|
||||
}
|
||||
else if (!botExtend)
|
||||
{
|
||||
//only top falls above extending it there, while bottom is inside existing
|
||||
shrinkArray(heightAndDepth, 2, 0, botPos, count);
|
||||
heightAndDepth[0] = height;
|
||||
count -= botPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
//top falls between some blocks, extending those as well
|
||||
shrinkArray(heightAndDepth, 2, 0, botPos, count);
|
||||
heightAndDepth[0] = height;
|
||||
heightAndDepth[1] = depth;
|
||||
count -= botPos;
|
||||
}
|
||||
}
|
||||
else if (!botExtend)
|
||||
else if (!topExtend)
|
||||
{
|
||||
//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];
|
||||
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
|
||||
{
|
||||
//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;
|
||||
if (!botExtend)
|
||||
{
|
||||
//only top is within some exiting block, extending it
|
||||
topPos++; //to make it easier
|
||||
heightAndDepth[topPos * 2] = height;
|
||||
heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1];
|
||||
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
|
||||
count -= botPos - topPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
//both top and bottom are outside existing blocks
|
||||
shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count);
|
||||
count -= botPos - topPos;
|
||||
extendArray(heightAndDepth, 2, topPos + 1, 1, count);
|
||||
count++;
|
||||
heightAndDepth[topPos * 2 + 2] = height;
|
||||
heightAndDepth[topPos * 2 + 3] = depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,7 +448,8 @@ public class DataPointUtil
|
||||
int j = 0;
|
||||
while (count > maxVerticalData)
|
||||
{
|
||||
ii = WORLD_HEIGHT - VERTICAL_OFFSET;
|
||||
limited = true;
|
||||
ii = WORLD_HEIGHT;
|
||||
for (i = 0; i < count - 1; i++)
|
||||
{
|
||||
if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii)
|
||||
@@ -432,88 +468,104 @@ public class DataPointUtil
|
||||
count--;
|
||||
}
|
||||
//As standard the vertical lods are ordered from top to bottom
|
||||
for (j = count - 1; j >= 0; j--)
|
||||
if (!limited && size == 1)
|
||||
{
|
||||
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 (j = 0; j < count; j++)
|
||||
dataPoint[j] = dataToMerge[j];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (j = 0; j < count; j++)
|
||||
{
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
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++)
|
||||
{
|
||||
singleData = dataToMerge[index * inputVerticalData + dataIndex];
|
||||
if (doesItExist(singleData) && !isVoid(singleData))
|
||||
for (dataIndex = 0; dataIndex < inputVerticalData; dataIndex++)
|
||||
{
|
||||
|
||||
if ((depth <= getDepth(singleData) && getDepth(singleData) <= height)
|
||||
|| (depth <= getHeight(singleData) && getHeight(singleData) <= height))
|
||||
singleData = dataToMerge[index * inputVerticalData + dataIndex];
|
||||
if (doesItExist(singleData) && !isVoid(singleData))
|
||||
{
|
||||
if (getHeight(singleData) > getHeight(data))
|
||||
if ((depth <= getDepth(singleData) && getDepth(singleData) < height)
|
||||
|| (depth < getHeight(singleData) && getHeight(singleData) <= height))
|
||||
{
|
||||
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
|
||||
break;
|
||||
}
|
||||
if (!doesItExist(data))
|
||||
{
|
||||
singleData = dataToMerge[index * inputVerticalData];
|
||||
data = createVoidDataPoint(getGenerationMode(singleData));
|
||||
tempGenMode = (byte) Math.min(tempGenMode, DistanceGenerationMode.NONE.complexity);
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
if (allEmpty)
|
||||
//no child has been initialized
|
||||
dataPoint[j] = EMPTY_DATA;
|
||||
else if (allVoid)
|
||||
//all the children are void
|
||||
dataPoint[j] = createVoidDataPoint(tempGenMode);
|
||||
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);
|
||||
{
|
||||
//we have at least 1 child
|
||||
if (size != 1)
|
||||
{
|
||||
tempAlpha = tempAlpha / numberOfChildren;
|
||||
tempRed = tempRed / numberOfChildren;
|
||||
tempGreen = tempGreen / numberOfChildren;
|
||||
tempBlue = tempBlue / numberOfChildren;
|
||||
tempLightBlock = tempLightBlock / numberOfChildren;
|
||||
tempLightSky = tempLightSky / numberOfChildren;
|
||||
}
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataPoint;
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
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;
|
||||
@@ -134,10 +133,14 @@ 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,6 +26,7 @@ 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;
|
||||
@@ -44,7 +45,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-8-2021
|
||||
* @version 12-14-2021
|
||||
*/
|
||||
public class LodUtil
|
||||
{
|
||||
@@ -52,6 +53,7 @@ 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
|
||||
@@ -342,18 +344,17 @@ 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;
|
||||
@@ -369,7 +370,7 @@ public class LodUtil
|
||||
|
||||
|
||||
// get the chunks that are going to be rendered by Minecraft
|
||||
HashSet<AbstractChunkPosWrapper> posToSkip = MC_RENDER.getRenderedChunks();
|
||||
HashSet<AbstractChunkPosWrapper> posToSkip = REFLECTION_HANDLER.sodiumPresent() ? MC_RENDER.getSodiumRenderedChunks() : MC_RENDER.getVanillaRenderedChunks();
|
||||
|
||||
|
||||
// remove everything outside the skipRadius,
|
||||
@@ -382,7 +383,9 @@ 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,6 +28,8 @@ 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
|
||||
@@ -42,7 +44,6 @@ 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<>();
|
||||
@@ -117,7 +118,8 @@ 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)
|
||||
{
|
||||
@@ -136,41 +138,22 @@ 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)
|
||||
{
|
||||
if (!threadVerticalAddDataMap.containsKey(Thread.currentThread().getName()) || (threadVerticalAddDataMap.get(Thread.currentThread().getName()) == null))
|
||||
long[] array = threadVerticalAddDataMap.get(Thread.currentThread().getName());
|
||||
if (array == null || array.length != arrayLength)
|
||||
{
|
||||
threadVerticalAddDataMap.put(Thread.currentThread().getName(), new long[arrayLength]);
|
||||
array = new long[arrayLength];
|
||||
threadVerticalAddDataMap.put(Thread.currentThread().getName(), array);
|
||||
}
|
||||
else
|
||||
{
|
||||
Arrays.fill(threadVerticalAddDataMap.get(Thread.currentThread().getName()), 0);
|
||||
}
|
||||
return threadVerticalAddDataMap.get(Thread.currentThread().getName());
|
||||
Arrays.fill(array, 0);
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//FIXME: If the arrayLength change, this may return incorrect sized array
|
||||
/** returns the array NOT cleared every time */
|
||||
public static short[] getHeightAndDepth(int arrayLength)
|
||||
{
|
||||
@@ -185,18 +168,19 @@ public class ThreadMapUtil
|
||||
/** returns the array filled with 0's */
|
||||
public static long[] getVerticalUpdateArray(int detailLevel)
|
||||
{
|
||||
if (!verticalUpdate.containsKey(Thread.currentThread().getName()) || (verticalUpdate.get(Thread.currentThread().getName()) == null))
|
||||
long[][] arrays = verticalUpdate.get(Thread.currentThread().getName());
|
||||
if (arrays == null)
|
||||
{
|
||||
long[][] array = new long[LodUtil.DETAIL_OPTIONS][];
|
||||
for (int i = 1; i < LodUtil.DETAIL_OPTIONS; i++)
|
||||
array[i] = new long[DetailDistanceUtil.getMaxVerticalData(i - 1) * 4];
|
||||
verticalUpdate.put(Thread.currentThread().getName(), array);
|
||||
arrays = new long[LodUtil.DETAIL_OPTIONS][];
|
||||
verticalUpdate.put(Thread.currentThread().getName(), arrays);
|
||||
}
|
||||
long[] array = arrays[detailLevel];
|
||||
int arrayLength = DetailDistanceUtil.getMaxVerticalData(detailLevel) * 4;
|
||||
if (array == null || array.length != arrayLength)
|
||||
array = new long[arrayLength];
|
||||
else
|
||||
{
|
||||
Arrays.fill(verticalUpdate.get(Thread.currentThread().getName())[detailLevel], 0);
|
||||
}
|
||||
return verticalUpdate.get(Thread.currentThread().getName())[detailLevel];
|
||||
Arrays.fill(array, 0);
|
||||
return array;
|
||||
}
|
||||
|
||||
/** clears all arrays so they will have to be rebuilt */
|
||||
@@ -209,7 +193,6 @@ public class ThreadMapUtil
|
||||
threadBuilderArrayMap.clear();
|
||||
threadBuilderVerticalArrayMap.clear();
|
||||
threadVerticalAddDataMap.clear();
|
||||
saveContainer.clear();
|
||||
projectionArrayMap.clear();
|
||||
heightAndDepthMap.clear();
|
||||
singleDataToMergeMap.clear();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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,13 +24,14 @@ 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 11-18-2021
|
||||
* @version 12-14-2021
|
||||
*/
|
||||
public interface IWrapperFactory
|
||||
{
|
||||
@@ -39,10 +40,21 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,21 +32,27 @@ public interface IChunkWrapper
|
||||
{
|
||||
int getHeight();
|
||||
|
||||
boolean isPositionInWater(AbstractBlockPosWrapper blockPos);
|
||||
boolean isPositionInWater(int x, int y, int z);
|
||||
|
||||
int getHeightMapValue(int xRel, int zRel);
|
||||
|
||||
IBiomeWrapper getBiome(int xRel, int yAbs, int zRel);
|
||||
IBiomeWrapper getBiome(int x, int y, int z);
|
||||
IBlockColorWrapper getBlockColorWrapper(int x, int y, int z);
|
||||
IBlockShapeWrapper getBlockShapeWrapper(int x, int y, int z);
|
||||
|
||||
IBlockColorWrapper getBlockColorWrapper(AbstractBlockPosWrapper blockPos);
|
||||
|
||||
IBlockShapeWrapper getBlockShapeWrapper(AbstractBlockPosWrapper blockPos);
|
||||
|
||||
AbstractChunkPosWrapper getPos();
|
||||
int getChunkPosX();
|
||||
int getChunkPosZ();
|
||||
int getRegionPosX();
|
||||
int getRegionPosZ();
|
||||
int getMaxY(int x, int z);
|
||||
int getMaxX();
|
||||
int getMaxZ();
|
||||
int getMinX();
|
||||
int getMinZ();
|
||||
|
||||
boolean isLightCorrect();
|
||||
|
||||
boolean isWaterLogged(AbstractBlockPosWrapper blockPos);
|
||||
boolean isWaterLogged(int x, int y, int z);
|
||||
|
||||
int getEmittedBrightness(AbstractBlockPosWrapper blockPos);
|
||||
int getEmittedBrightness(int x, int y, int z);
|
||||
}
|
||||
|
||||
+40
-39
@@ -26,8 +26,6 @@ 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;
|
||||
@@ -36,6 +34,7 @@ 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
|
||||
@@ -43,7 +42,7 @@ import com.seibel.lod.core.util.LodUtil;
|
||||
* the options that should be implemented in a configWrapperSingleton.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-1-2021
|
||||
* @version 12-14-2021
|
||||
*/
|
||||
public interface ILodConfigWrapperSingleton
|
||||
{
|
||||
@@ -122,12 +121,12 @@ public interface ILodConfigWrapperSingleton
|
||||
|
||||
HorizontalQuality HORIZONTAL_QUALITY_DEFAULT = HorizontalQuality.MEDIUM;
|
||||
String HORIZONTAL_QUALITY_DESC = ""
|
||||
+ " This indicates the exponential base of the quadratic drop-off \n"
|
||||
+ " This indicates how much farther away each drop in quality is. \n"
|
||||
+ "\n"
|
||||
+ " " + HorizontalQuality.LOWEST + ": base " + HorizontalQuality.LOWEST.quadraticBase + ". \n"
|
||||
+ " " + HorizontalQuality.LOW + ": base " + HorizontalQuality.LOW.quadraticBase + ". \n"
|
||||
+ " " + HorizontalQuality.MEDIUM + ": base " + HorizontalQuality.MEDIUM.quadraticBase + ". \n"
|
||||
+ " " + HorizontalQuality.HIGH + ": base " + HorizontalQuality.HIGH.quadraticBase + ". \n"
|
||||
+ " " + 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"
|
||||
+ "\n"
|
||||
+ " Lowest Quality: " + HorizontalQuality.LOWEST
|
||||
+ " Highest Quality: " + HorizontalQuality.HIGH;
|
||||
@@ -184,18 +183,6 @@ public interface ILodConfigWrapperSingleton
|
||||
{
|
||||
String DESC = "Graphics options that are a bit more technical.";
|
||||
|
||||
LodTemplate LOD_TEMPLATE_DEFAULT = LodTemplate.CUBIC;
|
||||
String LOD_TEMPLATE_DESC = ""
|
||||
+ " How should the LODs be drawn? \n"
|
||||
+ " NOTE: Currently only " + LodTemplate.CUBIC + " is implemented! \n"
|
||||
+ " \n"
|
||||
+ " " + LodTemplate.CUBIC + ": LOD Chunks are drawn as rectangular prisms (boxes). \n"
|
||||
+ " " + LodTemplate.TRIANGULAR + ": LOD Chunks smoothly transition between other. \n"
|
||||
+ " " + LodTemplate.DYNAMIC + ": LOD Chunks smoothly transition between each other, \n"
|
||||
+ " " + " unless a neighboring chunk is at a significantly different height. \n";
|
||||
LodTemplate getLodTemplate();
|
||||
void setLodTemplate(LodTemplate newLodTemplate);
|
||||
|
||||
boolean DISABLE_DIRECTIONAL_CULLING_DEFAULT = false;
|
||||
String DISABLE_DIRECTIONAL_CULLING_DESC = ""
|
||||
+ " If false fake chunks behind the player's camera \n"
|
||||
@@ -226,12 +213,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 only some holes in the world. \n"
|
||||
+ " " + VanillaOverdraw.BORDER + ": LODs will render only on the border of vanilla chunks, preventing some holes in the world. \n"
|
||||
+ " " + VanillaOverdraw.DYNAMIC + ": LODs will render on top of distant vanilla chunks to hide delayed loading. \n"
|
||||
+ " " + " More effective on higher render distances. \n"
|
||||
+ " " + " For vanilla render distances less than or equal to " + LodUtil.MINIMUM_RENDER_DISTANCE_FOR_PARTIAL_OVERDRAW + " \n"
|
||||
+ " " + " " + VanillaOverdraw.NEVER + " or " + VanillaOverdraw.ALWAYS + " will be used depending on the dimension. \n"
|
||||
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing holes in the world. \n"
|
||||
+ " " + VanillaOverdraw.ALWAYS + ": LODs will render on all vanilla chunks preventing all holes in the world. \n"
|
||||
+ "\n"
|
||||
+ " This setting shouldn't affect performance. \n";
|
||||
VanillaOverdraw getVanillaOverdraw();
|
||||
@@ -283,9 +270,16 @@ public interface ILodConfigWrapperSingleton
|
||||
void setGenerationPriority(GenerationPriority newGenerationPriority);
|
||||
|
||||
DistanceGenerationMode DISTANCE_GENERATION_MODE_DEFAULT = DistanceGenerationMode.SURFACE;
|
||||
String DISTANCE_GENERATION_MODE_DESC = ""
|
||||
public static String getDistanceGenerationModeDesc(IVersionConstants versionConstants)
|
||||
{
|
||||
return ""
|
||||
+ " 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"
|
||||
@@ -294,35 +288,36 @@ 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"
|
||||
+ " Multithreaded - Fastest (2-5 ms) \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY) + " - Fastest (2-5 ms) \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT + " \n"
|
||||
+ " Same as " + DistanceGenerationMode.BIOME_ONLY + ", except instead \n"
|
||||
+ " of always using sea level as the LOD height \n"
|
||||
+ " different biome types (mountain, ocean, forest, etc.) \n"
|
||||
+ " use predetermined heights to simulate having height data. \n"
|
||||
+ " Multithreaded - Fastest (2-5 ms) \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT) + " - Fastest (2-5 ms) \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.SURFACE + " \n"
|
||||
+ " Generate the world surface, \n"
|
||||
+ " this does NOT include trees, \n"
|
||||
+ " or structures. \n"
|
||||
+ " Multithreaded - Faster (10-20 ms) \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.SURFACE) + " - Faster (10-20 ms) \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.FEATURES + " \n"
|
||||
+ " Generate everything except structures. \n"
|
||||
+ " WARNING: This may cause world generation bugs or instability! \n"
|
||||
+ " Multithreaded - Fast (15-20 ms) \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FEATURES) + " - Fast (15-20 ms) \n"
|
||||
+ "\n"
|
||||
+ " " + DistanceGenerationMode.FULL + " \n"
|
||||
+ " Ask the local server to generate/load each chunk. \n"
|
||||
+ " This will show player made structures, which can \n"
|
||||
+ " be useful if you are adding the mod to a pre-existing world. \n"
|
||||
+ " This is the most compatible, but causes server/simulation lag. \n"
|
||||
+ " SingleThreaded - Slow (15-50 ms, with spikes up to 200 ms) \n"
|
||||
+ " " + multiOrSingleThreadText(versionConstants, DistanceGenerationMode.FULL) + " - 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);
|
||||
|
||||
@@ -361,6 +356,13 @@ 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";
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -451,7 +453,7 @@ public interface ILodConfigWrapperSingleton
|
||||
|
||||
boolean DEBUG_KEYBINDINGS_ENABLED_DEFAULT = true;
|
||||
String DEBUG_KEYBINDINGS_ENABLED_DESC = ""
|
||||
+ " If true the F4 key can be used to cycle through the different debug modes. \n"
|
||||
+ " If true the F8 key can be used to cycle through the different debug modes. \n"
|
||||
+ " and the F6 key can be used to enable and disable LOD rendering.";
|
||||
boolean getDebugKeybindingsEnabled();
|
||||
void setDebugKeybindingsEnabled(boolean newEnableDebugKeybindings);
|
||||
@@ -473,7 +475,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 rending if in GPU memory, slow if in system memory, \n"
|
||||
+ " Fast rendering 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"
|
||||
@@ -484,20 +486,19 @@ public interface ILodConfigWrapperSingleton
|
||||
GpuUploadMethod getGpuUploadMethod();
|
||||
void setGpuUploadMethod(GpuUploadMethod newGpuUploadMethod);
|
||||
|
||||
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"
|
||||
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"
|
||||
+ " Helpful resource for frame times: https://fpstoms.com \n"
|
||||
+ "\n"
|
||||
+ " Longer times may reduce stuttering but will make fake chunks \n"
|
||||
+ " transition and load slower. \n"
|
||||
+ " transition and load slower. Change this to [0] for no timeout.\n"
|
||||
+ "\n"
|
||||
+ " NOTE:\n"
|
||||
+ " This should be a last resort option."
|
||||
+ " Only change this from [0], after you have tried all of the \n"
|
||||
+ " \"GPU Upload methods\" and determined even the best stutters with yoru hardware.";
|
||||
int getGpuUploadTimeoutInMilliseconds();
|
||||
void setGpuUploadTimeoutInMilliseconds(int newTimeoutInMilliseconds);
|
||||
+ " 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);
|
||||
|
||||
String REBUILD_TIMES_DESC = ""
|
||||
+ " How frequently should vertex buffers (geometry) be rebuilt and sent to the GPU? \n"
|
||||
|
||||
+58
-3
@@ -25,6 +25,8 @@ 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;
|
||||
|
||||
@@ -33,7 +35,7 @@ import com.seibel.lod.core.wrapperInterfaces.chunk.AbstractChunkPosWrapper;
|
||||
* rendering in Minecraft.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 12-8-2021
|
||||
* @version 12-14-2021
|
||||
*/
|
||||
public interface IMinecraftRenderWrapper
|
||||
{
|
||||
@@ -64,9 +66,56 @@ 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()}.
|
||||
*/
|
||||
HashSet<AbstractChunkPosWrapper> getRenderedChunks();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** @returns null if there was a issue getting the lightmap */
|
||||
int[] getLightmapPixels();
|
||||
|
||||
@@ -76,4 +125,10 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,9 @@ public interface IWorldWrapper
|
||||
|
||||
WorldType getWorldType();
|
||||
|
||||
int getBlockLight(AbstractBlockPosWrapper blockPos);
|
||||
int getBlockLight(int x, int y, int z);
|
||||
|
||||
int getSkyLight(AbstractBlockPosWrapper blockPos);
|
||||
|
||||
IBiomeWrapper getBiome(AbstractBlockPosWrapper blockPos);
|
||||
int getSkyLight(int x, int y, int z);
|
||||
|
||||
boolean hasCeiling();
|
||||
|
||||
@@ -54,6 +52,11 @@ 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
@@ -0,0 +1,11 @@
|
||||
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(blockLightTex, skyLightTex)).xyz, 1);
|
||||
vertexColor = color * texture(lightMap, vec2(blockLightTex, skyLightTex));
|
||||
// vertexColor = color * vec4(texture(lightMap, vec2(skyLightTex, blockLightTex)).xyz, 1);
|
||||
vertexColor = color * texture(lightMap, vec2(skyLightTex, blockLightTex));
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user