From 8af14ad3e74653f67367b177c911ac9eca0c2194 Mon Sep 17 00:00:00 2001 From: coolGi Date: Mon, 20 Nov 2023 21:59:01 +1030 Subject: [PATCH 01/32] Added logging for % downloaded --- .../core/config/file/ConfigFileHandling.java | 3 +-- .../core/jar/installer/WebDownloader.java | 12 +++++++++--- .../core/jar/updater/SelfUpdater.java | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java index b63d5cae1..fe8500c31 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java @@ -25,7 +25,6 @@ import com.seibel.distanthorizons.core.config.ConfigBase; import com.seibel.distanthorizons.core.config.types.AbstractConfigType; import com.seibel.distanthorizons.core.config.types.ConfigEntry; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -112,7 +111,7 @@ public class ConfigFileHandling // Attempt to get the version number currentCfgVersion = (Integer) tmpNightConfig.get("_version"); tmpNightConfig.close(); - } catch (Exception e) {e.printStackTrace();} + } catch (Exception ignored) { } if (currentCfgVersion == configBase.configVersion) {} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/jar/installer/WebDownloader.java b/core/src/main/java/com/seibel/distanthorizons/core/jar/installer/WebDownloader.java index 7a2184648..6be2f9f25 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/jar/installer/WebDownloader.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/jar/installer/WebDownloader.java @@ -74,13 +74,19 @@ public class WebDownloader fos, 1024)) { byte[] data = new byte[1024]; - int i; + int i, percent = -1; while ((i = in.read(data, 0, 1024)) >= 0) { totalDataRead = totalDataRead + i; bout.write(data, 0, i); -// int percent = (int) ((totalDataRead * 100) / filesize); -// System.out.println(percent); + + // TODO: Link this to an atomic integer rather than printing it to log + int newPercent = (int) ((totalDataRead * 100) / filesize); + if (percent != newPercent) + { + percent = newPercent; + System.out.println(String.valueOf(percent) +"% downloaded"); + } } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java b/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java index 6f5894d93..641a85455 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java @@ -141,7 +141,7 @@ public class SelfUpdater if (!pipeline.get("status").equals("success")) { - LOGGER.warn("Pipeline for branch ["+ ModJarInfo.Git_Branch +"], commit ["+ pipeline.get("id") +"], has either failed to build, or still building."); + LOGGER.warn("Pipeline for branch ["+ ModJarInfo.Git_Branch +"], pipeline ID ["+ pipeline.get("id") +"], has either failed to build, or is still building."); return false; } From 7e869105cbeeb319bd8bd574da2148ace6ea45ff Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 20 Nov 2023 07:35:10 -0600 Subject: [PATCH 02/32] Fix null pointers when moving between multiverse levels --- .../core/api/internal/SharedApi.java | 5 +++++ .../ColumnRenderBufferBuilder.java | 16 ++++++++++++++-- .../transformers/ChunkToLodBuilder.java | 8 +++++++- .../core/file/fullDatafile/FullDataMetaFile.java | 4 ++-- .../file/renderfile/RenderSourceFileHandler.java | 7 +++++-- .../core/generation/WorldGenerationQueue.java | 3 ++- .../core/render/LodRenderSection.java | 8 ++++---- .../core/util/threading/ThreadPools.java | 7 +++++++ 8 files changed, 46 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 9691c82a4..2fb441c5d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -232,6 +232,11 @@ public class SharedApi // lighting the chunk needs to be done on a separate thread to prevent lagging any of the event threads ThreadPoolExecutor executor = ThreadPools.getLightPopulatorExecutor(); + if (executor == null) + { + return; + } + executor.execute(() -> { LOGGER.trace(chunkWrapper.getChunkPos() + " " + executor.getActiveCount() + " / " + executor.getQueue().size() + " - " + executor.getCompletedTaskCount()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index dcdd65be7..046b2c8af 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -39,6 +39,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; /** * Used to populate the buffers in a {@link ColumnRenderSource} object. @@ -66,6 +67,17 @@ public class ColumnRenderBufferBuilder IDhClientLevel clientLevel, Reference renderBufferRef, ColumnRenderSource renderSource, ColumnRenderSource[] adjData) { + ThreadPoolExecutor bufferBuilderExecutor = ThreadPools.getBufferBuilderExecutor(); + ThreadPoolExecutor bufferUploaderExecutor = ThreadPools.getBufferUploaderExecutor(); + if ((bufferBuilderExecutor == null || bufferBuilderExecutor.isTerminated()) || + (bufferUploaderExecutor == null || bufferUploaderExecutor.isTerminated())) + { + // one or more of the thread pools has been shut down + CompletableFuture future = new CompletableFuture<>(); + future.cancel(true); + return future; + } + //LOGGER.info("RenderRegion startBuild @ "+renderSource.sectionPos); return CompletableFuture.supplyAsync(() -> { @@ -101,7 +113,7 @@ public class ColumnRenderBufferBuilder LOGGER.error("\"LodNodeBufferBuilder\" was unable to build quads: ", e3); throw e3; } - }, ThreadPools.getBufferBuilderExecutor()) + }, bufferBuilderExecutor) .thenApplyAsync((quadBuilder) -> { try @@ -136,7 +148,7 @@ public class ColumnRenderBufferBuilder LOGGER.error("\"LodNodeBufferBuilder\" was unable to upload buffer: ", e3); throw e3; } - }, ThreadPools.getBufferUploaderExecutor()) + }, bufferUploaderExecutor) .handle((columnRenderBuffer, ex) -> { //LOGGER.info("RenderRegion endBuild @ {}", renderSource.sectionPos); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java index 19d78f4d0..3fd433a62 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/ChunkToLodBuilder.java @@ -102,6 +102,12 @@ public class ChunkToLodBuilder implements AutoCloseable return; } + ThreadPoolExecutor lodBuilderExecutor = ThreadPools.getChunkToLodBuilderExecutor(); + if (lodBuilderExecutor == null) + { + return; + } + for (int i = 0; i < threadCount; i++) { @@ -116,7 +122,7 @@ public class ChunkToLodBuilder implements AutoCloseable { this.runningCount.decrementAndGet(); } - }, ThreadPools.getChunkToLodBuilderExecutor()); + }, lodBuilderExecutor); } } private void tickThreadTask() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java index c13f21890..3c09bfa5b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/FullDataMetaFile.java @@ -247,7 +247,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); - if (!executor.isTerminated()) + if (executor != null && !executor.isTerminated()) { // load the data source @@ -344,7 +344,7 @@ public class FullDataMetaFile extends AbstractMetaDataContainerFile implements I else { ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); - if (!executor.isTerminated()) + if (executor != null && !executor.isTerminated()) { // wait for the update to finish before returning the data source diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java index 6396ba783..d417929e6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/renderfile/RenderSourceFileHandler.java @@ -104,7 +104,7 @@ public class RenderSourceFileHandler implements IRenderSourceProvider { // don't continue if the handler has been shut down ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); - if (executor.isTerminated()) + if (executor != null && executor.isTerminated()) { return CompletableFuture.completedFuture(null); } @@ -266,11 +266,14 @@ public class RenderSourceFileHandler implements IRenderSourceProvider private String[] f3Log() { ThreadPoolExecutor executor = ThreadPools.getFileHandlerExecutor(); + String queueSize = executor != null ? executor.getQueue().size()+"" : "-"; + String completedTaskSize = executor != null ? executor.getCompletedTaskCount()+"" : "-"; + ArrayList lines = new ArrayList<>(); lines.add("Render Source File Handler [" + this.clientLevel.getClientLevelWrapper().getDimensionType().getDimensionName() + "]"); lines.add(" Loaded files: " + this.loadedMetaFileBySectionPos.size()); - lines.add(" Thread pool tasks: " + executor.getQueue().size() + " (completed: " + executor.getCompletedTaskCount() + ")"); + lines.add(" Thread pool tasks: " + queueSize + " (completed: " + completedTaskSize + ")"); int totalFutures = this.taskTracker.size(); EnumMap tasksOutstanding = new EnumMap<>(ETaskType.class); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 0fd8cafe4..3a4510aed 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -518,7 +518,8 @@ public class WorldGenerationQueue implements IWorldGenerationQueue, IDebugRender try { int waitTimeInSeconds = 3; - if (!ThreadPools.getWorldGenExecutor().awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)) + ThreadPoolExecutor executor = ThreadPools.getWorldGenExecutor(); + if (executor != null && !executor.awaitTermination(waitTimeInSeconds, TimeUnit.SECONDS)) { LOGGER.warn("World generator thread pool shutdown didn't complete after [" + waitTimeInSeconds + "] seconds. Some world generator requests may still be running."); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index 25c09c34b..dbb6358cc 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -324,10 +324,10 @@ public class LodRenderSection implements IDebugRenderable if (showRenderSectionStatus && this.pos.getDetailLevel() == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL) { DebugRenderer.makeParticle( - new DebugRenderer.BoxParticle( - new DebugRenderer.Box(this.pos, 32f, 64f, 0.2f, Color.yellow), - 0.5, 16f - ) + new DebugRenderer.BoxParticle( + new DebugRenderer.Box(this.pos, 32f, 64f, 0.2f, Color.yellow), + 0.5, 16f + ) ); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java index a4f851b7c..1084c4d8d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ThreadPools.java @@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.util.threading; import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.util.ThreadUtil; +import org.jetbrains.annotations.Nullable; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadPoolExecutor; @@ -42,13 +43,16 @@ public class ThreadPools public static final DhThreadFactory FILE_HANDLER_THREAD_FACTORY = new DhThreadFactory("File Handler", Thread.MIN_PRIORITY); private static ConfigThreadPool fileHandlerThreadPool; + @Nullable public static ThreadPoolExecutor getFileHandlerExecutor() { return fileHandlerThreadPool.executor; } public static final DhThreadFactory WORLD_GEN_THREAD_FACTORY = new DhThreadFactory("World Gen", Thread.MIN_PRIORITY); private static ConfigThreadPool worldGenThreadPool; + @Nullable public static ThreadPoolExecutor getWorldGenExecutor() { return worldGenThreadPool.executor; } private static ThreadPoolExecutor bufferUploaderThreadPool; + @Nullable public static ThreadPoolExecutor getBufferUploaderExecutor() { return bufferUploaderThreadPool; } @@ -63,14 +67,17 @@ public class ThreadPools public static final DhThreadFactory LIGHT_POPULATOR_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Light Populator", Thread.MIN_PRIORITY); private static ConfigThreadPool lightPopulatorThreadPool; + @Nullable public static ThreadPoolExecutor getLightPopulatorExecutor() { return lightPopulatorThreadPool.executor; } public static final DhThreadFactory CHUNK_TO_LOD_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Chunk to Lod Builder", Thread.MIN_PRIORITY); private static ConfigThreadPool chunkToLodBuilderThreadPool; + @Nullable public static ThreadPoolExecutor getChunkToLodBuilderExecutor() { return chunkToLodBuilderThreadPool.executor; } public static final DhThreadFactory BUFFER_BUILDER_THREAD_FACTORY = new DhThreadFactory("LOD Builder - Buffer Builder", Thread.MIN_PRIORITY); private static ConfigThreadPool bufferBuilderThreadPool; + @Nullable public static ThreadPoolExecutor getBufferBuilderExecutor() { return bufferBuilderThreadPool.executor; } From 9c918c7bbb3cd4c08d1f2b370fded20627a03f9d Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 21 Nov 2023 07:43:46 -0600 Subject: [PATCH 03/32] Improve multiverse similarity logic and fix incorrect log --- .../core/file/subDimMatching/SubDimCompare.java | 7 +++++-- .../core/file/subDimMatching/SubDimensionLevelMatcher.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimCompare.java b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimCompare.java index 1688efd20..7cb01b7ae 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimCompare.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimCompare.java @@ -57,7 +57,7 @@ public class SubDimCompare implements Comparable } /** returns a number between 0 (no equal datapoint) and 1 (totally equal) */ - public double getPercentEqual() { return (double) equalDataPoints / (double) totalDataPoints; } + public double getPercentEqual() { return (double) this.equalDataPoints / (double) this.totalDataPoints; } @Override @@ -66,7 +66,7 @@ public class SubDimCompare implements Comparable if (this.equalDataPoints != other.equalDataPoints) { // compare based on data points - return Integer.compare(this.equalDataPoints, other.equalDataPoints); + return Double.compare(this.getPercentEqual(), other.getPercentEqual()); } else { @@ -83,4 +83,7 @@ public class SubDimCompare implements Comparable || this.playerPosDist <= MAX_SIMILAR_PLAYER_POS_DISTANCE_IN_BLOCKS; } + @Override + public String toString() { return this.equalDataPoints + "/" + this.totalDataPoints + ": " + this.getPercentEqual() + " playerPos: " + this.playerPosDist; } + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java index 276c48dd4..2b087059a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java @@ -332,7 +332,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable String subDimShortName = LodUtil.shortenString(testLevelFolder.getName(), 8); // variables are separated out for easier debugging - String equalPercent = LodUtil.shortenString(subDimCompare.getPercentEqual()+"", 5); + String equalPercent = LodUtil.shortenString(mostSimilarSubDim.getPercentEqual()+"", 5); LOGGER.info("Sub dimension ["+subDimShortName+"...] is current dimension probability: "+equalPercent+" ("+equalDataPoints+"/"+totalDataPointCount+")"); } catch (Exception e) From e73e3a5587d168277ba0636fc88ffbcc21a8254c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 21 Nov 2023 07:44:18 -0600 Subject: [PATCH 04/32] add possible comments to IColumnDataView --- .../core/dataObjects/render/columnViews/IColumnDataView.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/IColumnDataView.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/IColumnDataView.java index f0214991d..825824162 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/IColumnDataView.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/columnViews/IColumnDataView.java @@ -25,6 +25,7 @@ public interface IColumnDataView { long get(int index); + // FIXME probably horizontal size in blocks? int size(); default Iterator iterator() @@ -43,8 +44,10 @@ public interface IColumnDataView }; } + // FIXME measured in blocks? int verticalSize(); + // FIXME how many datapoints in this LOD? int dataCount(); IColumnDataView subView(int dataIndexStart, int dataCount); From 7959097fe35c952e372b9a3c17d186942f741fdb Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 24 Nov 2023 09:46:45 -0600 Subject: [PATCH 05/32] Fix a potential concurrent error on DhClientServerWorld shutdown --- .../distanthorizons/core/world/DhClientServerWorld.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index 935299539..6d64be5ca 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -160,7 +160,12 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor this.saveAndFlush(); this.f3Message.close(); - for (DhClientServerLevel level : this.dhLevels) + + // clear dhLevels to prevent concurrent modification errors + HashSet levelsToClose = new HashSet<>(this.dhLevels); + this.dhLevels.clear(); + // close each level + for (DhClientServerLevel level : levelsToClose) { LOGGER.info("Unloading level " + level.getServerLevelWrapper().getDimensionType().getDimensionName()); From b641436d80739b3086426b4e6e09305043395ecf Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 24 Nov 2023 13:35:48 -0600 Subject: [PATCH 06/32] Add ColulmnRenderBufferBuilder TODOs --- .../render/bufferBuilding/ColumnRenderBufferBuilder.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index 046b2c8af..ebb7052a4 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -180,10 +180,11 @@ public class ColumnRenderBufferBuilder // Variable initialization EDebugRendering debugMode = Config.Client.Advanced.Debugging.debugRendering.get(); + // TODO make a config for this // can be uncommented to limit which section positions are build and thus, rendered // useful when debugging a specific section -// if (renderSource.sectionPos.sectionDetailLevel == 6 -// && renderSource.sectionPos.sectionZ == 0 && renderSource.sectionPos.sectionX == 0) +// if (renderSource.sectionPos.getDetailLevel() == 6 +// && renderSource.sectionPos.getZ() == 0 && renderSource.sectionPos.getX() == 0) // { // int test = 0; // } @@ -197,10 +198,11 @@ public class ColumnRenderBufferBuilder { for (int z = 0; z < ColumnRenderSource.SECTION_SIZE; z++) { + // TODO make a config for this // can be uncommented to limit the buffer building to a specific // relative position in this section. // useful for debugging a single column's rendering -// if (x != 1 || z != 1) +// if (x != 0 || (z != 0 && z != 1)) // { // continue; // } @@ -316,6 +318,7 @@ public class ColumnRenderBufferBuilder // We only stop when we find a block that is void or non-existing block for (int i = 0; i < columnRenderData.size(); i++) { + // TODO make a config for this // can be uncommented to limit which vertical LOD is generated // if (i != 0) // { From fc248d095656b7328e489a1866446cf4d3ddafea Mon Sep 17 00:00:00 2001 From: James Seibel Date: Fri, 24 Nov 2023 14:02:26 -0600 Subject: [PATCH 07/32] Fix merging for transparent LODs over the void --- .../render/bufferBuilding/ColumnBox.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java index e231289c7..0584aa79b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java @@ -52,6 +52,8 @@ public class ColumnBox // if there isn't any data below this LOD, make this LOD's color opaque to prevent seeing void through transparent blocks // Note: this LOD should still be considered transparent for this method's checks, otherwise rendering bugs may occur + // FIXME this transparency change should be applied before this point since this could affect other areas + // This may also be better than handling the LOD as transparent, but that is TBD if (!RenderDataPointUtil.doesDataPointExist(bottomData)) { color = ColorUtil.setAlpha(color, 255); @@ -269,7 +271,17 @@ public class ColumnBox adjIndex++) { long adjPoint = adjColumnView.get(adjIndex); - boolean adjTransparent = RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled; + + // if the adjacent data point is over the void + // don't consider it as transparent + // FIXME this transparency change should be applied before this point since this could affect other areas + boolean adjOverVoid = false; + if (adjIndex > 0) + { + long adjBellowPoint = adjColumnView.get(adjIndex-1); + adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBellowPoint); + } + boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled; // continue if this data point is transparent or the adjacent point is not From 42135636af60165581c79e71a67fbb7e7c011e59 Mon Sep 17 00:00:00 2001 From: Builderb0y Date: Sat, 25 Nov 2023 05:05:55 +0000 Subject: [PATCH 08/32] add and make use of RenderDataPointReducingList. --- .../util/RenderDataPointReducingList.java | 844 ++++++++++++++++++ .../core/util/RenderDataPointUtil.java | 14 +- 2 files changed, 857 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java new file mode 100644 index 000000000..8944ce7e2 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -0,0 +1,844 @@ +package com.seibel.distanthorizons.core.util; + +import com.google.common.annotations.VisibleForTesting; +import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; +import com.seibel.distanthorizons.core.dataObjects.render.columnViews.IColumnDataView; +import com.seibel.distanthorizons.core.util.LodUtil.AssertFailureException; +import it.unimi.dsi.fastutil.longs.LongArrays; +import it.unimi.dsi.fastutil.shorts.ShortArrays; + +/** +a list of data points whose sole purpose is to {@link #reduce(int)} them. +each data point, henceforth referred to as a "node", is represented by 2 packed longs. +the "data" long contains the data point itself, as encoded by +{@link RenderDataPointUtil#createDataPoint(int, int, int, int, int, int, int, int, int)}. +the "links" long contains 4 packed 16-bit integers, which "point" to other nodes +in the sense that the index represented by the integer is another node in this list. +the 4 links are: bigger, smaller, higher, and lower. +all nodes are stored in 2 parallel long[]'s, namely {@link #data} and {@link #links}. + +all nodes are internally sorted in 2 different orders at the same time: +lowest-to-highest, and smallest-to-biggest. +both of these orders are important for reduction logic. +traversal in both orders is equally possible and important. + +@author Builderb0y +*/ +public class RenderDataPointReducingList { + + /** + setting this to true will cause the list to sanity-check + its own links automatically every time it modifies itself. + this is mostly just useful for debugging. + this should be set to false in production, + because these sanity checks are slow and happen often. + */ + private static final boolean ASSERTS = false; + /** + number of special cases to use for step 1 of {@link #reduce(int)}. + 2 works well for big globe worlds. + 0 is probably better for vanilla, but vanilla has vastly fewer segments/nodes + than big globe does, so the difference in efficiency matters a lot less. + */ + private static final int SPECIAL_CASES = 2; + public static final int + /** the bit offset of {@link #links} where the lower link is stored. */ + LOWER_SHIFT = 0, + /** the bit offset of {@link #links} where the higher link is stored. */ + HIGHER_SHIFT = 16, + /** the bit offset of {@link #links} where the smaller link is stored. */ + SMALLER_SHIFT = 32, + /** the bit offset of {@link #links} where the bigger link is stored. */ + BIGGER_SHIFT = 48, + /** + a bit mask for extracting links from elements of {@link #links}. + all links are 16 bits in length, so this constant has the lower 16 bits set, + and all remaining bits cleared. + */ + LINK_MASK = 0xFFFF, + /** a constant to indicate that a link is non-existent. */ + NULL = LINK_MASK; + public static final long + /** the default element of {@link #data} to indicate that there is no data. */ + DEFAUlT_DATA = 0L, + /** the default element of {@link #links} to indicate that a node is not linked to any other nodes. */ + DEFAULT_LINKS = -1L; + + /** + indexes of the nodes at the ends of this list. + access these fields through the getters, + not by the backing fields. the getters will + perform automatic short <-> int conversions. + + @implNote these fields behave as if they were unsigned, + and the getters will behave accordingly. + not that DH supports a wide enough Y range + to overflow these fields, but still. + */ + private short lowest, highest, smallest, biggest; + private short sizeWithAir, sizeWithoutAir; + private final long[] links, data; + /** + a temporary array to be used for sorting nodes. + the array is first populated such that every index + up to our current size represents a valid index. + then this array is sorted. + finally, the nodes are re-linked according + to the order of elements in this array. + */ + private final short[] sortingArray; + + public RenderDataPointReducingList(IColumnDataView view) { + int size = view.size(); + if (size == 0) { + this.setLowest(NULL); + this.setHighest(NULL); + this.setSmallest(NULL); + this.setBiggest(NULL); + this.links = LongArrays.EMPTY_ARRAY; + this.data = LongArrays.EMPTY_ARRAY; + this.sortingArray = ShortArrays.EMPTY_ARRAY; + return; + } + //allocate an array big enough to hold 2 * size - 1 nodes. + //this is the number of nodes we would have if none + //of the nodes in the provided view are touching, + //and we need to add air nodes between all of them. + //we will use this array for sorting the nodes, + //first by lowest-to-highest, then by smallest-to-biggest. + int arrayCapacity = (size << 1) - 1; + this.sortingArray = new short[arrayCapacity]; + this.links = new long[arrayCapacity]; + java.util.Arrays.fill(this.links, DEFAULT_LINKS); + this.data = new long[arrayCapacity]; + int sizeWithoutAir = 0; + for (int index = 0; index < size; index++) { + long packedData = view.get(index); + //first "pass" (if you can call it that) skips nodes with 0 height, and nodes that aren't visible. + //air nodes will be inserted *after* the nodes have been sorted by Y level. + if (isDataVisible(packedData) && RenderDataPointUtil.getYMin(packedData) < RenderDataPointUtil.getYMax(packedData)) { + this.setData(sizeWithoutAir, packedData); + this.setSortingIndex(sizeWithoutAir, sizeWithoutAir); + sizeWithoutAir++; + } + } + //sort the nodes by Y level. + this.sortByPosition(sizeWithoutAir); + //next pass: link the nodes together, and insert air nodes as necessary. + int sizeWithAir = sizeWithoutAir; + for (int sortingIndex = 1; sortingIndex < sizeWithoutAir; sortingIndex++) { + int lowerIndex = this.getSortingIndex(sortingIndex - 1); + int higherIndex = this.getSortingIndex(sortingIndex); + long lowerData = this.getData(lowerIndex); + long higherData = this.getData(higherIndex); + int lowerMaxY = RenderDataPointUtil.getYMax(lowerData); + int higherMinY = RenderDataPointUtil.getYMin(higherData); + if (lowerMaxY == higherMinY) { //the two nodes touch. + this.setHigher(lowerIndex, higherIndex); + this.setLower(higherIndex, lowerIndex); + } + else if (lowerMaxY < higherMinY) { //the two nodes do not touch. + this.setData( + sizeWithAir, + RenderDataPointUtil.createDataPoint( + 0, + 0, + 0, + 0, + higherMinY, + lowerMaxY, + RenderDataPointUtil.getLightSky(higherData), + RenderDataPointUtil.getLightBlock(higherData), + RenderDataPointUtil.getGenerationMode(higherData) + ) + ); + this.setSortingIndex(sizeWithAir, sizeWithAir); + this.setLower(higherIndex, sizeWithAir); + this.setHigher(lowerIndex, sizeWithAir); + this.setLower(sizeWithAir, lowerIndex); + this.setHigher(sizeWithAir, higherIndex); + sizeWithAir++; + } + else { //the two nodes overlap. + throw new IllegalArgumentException(RenderDataPointUtil.toString(lowerData) + " overlaps with " + RenderDataPointUtil.toString(higherData)); + } + } + this.lowest = this.sortingArray[0]; + this.highest = this.sortingArray[sizeWithoutAir - 1]; + + //now sort by size. + this.sortBySize(sizeWithAir); + for (int sortingIndex = 1; sortingIndex < sizeWithAir; sortingIndex++) { + int smallerIndex = this.getSortingIndex(sortingIndex - 1); + int biggerIndex = this.getSortingIndex(sortingIndex); + this.setBigger(smallerIndex, biggerIndex); + this.setSmaller(biggerIndex, smallerIndex); + } + this.smallest = this.sortingArray[0]; + this.biggest = this.sortingArray[sizeWithAir - 1]; + + this.setSizeWithAir(sizeWithAir); + this.setSizeWithoutAir(sizeWithoutAir); + + if (ASSERTS) this.checkLinks(); + } + + //////////////////////////////// operations //////////////////////////////// + + /** + verifies that this list is in the "correct" state, + and throws an {@link AssertFailureException} if it isn't. + */ + @VisibleForTesting + public void checkLinks() { + LodUtil.assertTrue(this.getSizeWithoutAir() <= this.getSizeWithAir(), "more segments without air than with air"); + if (this.getSizeWithAir() == 0) { + LodUtil.assertTrue(this.getSmallest() == NULL, "size is 0, but we have a smallest node"); + LodUtil.assertTrue(this.getBiggest() == NULL, "size is 0, but we have a biggest node"); + LodUtil.assertTrue(this.getLowest() == NULL, "size is 0, but we have a lowest node"); + LodUtil.assertTrue(this.getHighest() == NULL, "size is 0, but we have a highest node"); + } + else { + LodUtil.assertTrue(this.getSizeWithAir() > 0 && this.getSizeWithoutAir() >= 0, "at least one of our sizes is negative"); + int sizeWithAir = 0, sizeWithoutAir = 0; + for (int index = this.getSmallest(); index != NULL; index = this.getBigger(index)) { + int smaller = this.getSmaller(index); + int bigger = this.getBigger(index); + LodUtil.assertTrue((smaller != NULL ? this.getBigger(smaller) : this.getSmallest()) == index, "one-way link"); + LodUtil.assertTrue((bigger != NULL ? this.getSmaller(bigger) : this.getBiggest()) == index, "one-way link"); + LodUtil.assertTrue(smaller == NULL || this.getSize(index) >= this.getSize(smaller), "node is not sorted by size"); + sizeWithAir++; + if (this.isIndexVisible(index)) sizeWithoutAir++; + } + LodUtil.assertTrue(sizeWithAir == this.getSizeWithAir() && sizeWithoutAir == this.getSizeWithoutAir(), "node count does not match size"); + + sizeWithAir = sizeWithoutAir = 0; + for (int index = this.getLowest(); index != NULL; index = this.getHigher(index)) { + int lower = this.getLower(index); + int higher = this.getHigher(index); + LodUtil.assertTrue((lower != NULL ? this.getHigher(lower) : this.getLowest()) == index, "one-way link"); + LodUtil.assertTrue((higher != NULL ? this.getLower(higher) : this.getHighest()) == index, "one-way link"); + LodUtil.assertTrue(this.getMaxY(index) > this.getMinY(index), "node has inverted Y levels"); + LodUtil.assertTrue(lower == NULL || this.getMinY(index) == this.getMaxY(lower), "node does not touch its lower neighbor"); + sizeWithAir++; + if (this.isIndexVisible(index)) sizeWithoutAir++; + } + LodUtil.assertTrue(sizeWithAir == this.getSizeWithAir() && sizeWithoutAir == this.getSizeWithoutAir(), "node count does not match size"); + } + } + + /** removes the node at the given index from this list. */ + public void remove(int index) { + int + lower = this.getLower (index), + higher = this.getHigher (index), + smaller= this.getSmaller(index), + bigger = this.getBigger (index), + alpha = this.getAlpha (index); + if (lower != NULL) this.setHigher(lower, higher); + else this.setLowest(higher); + if (higher != NULL) this.setLower(higher, lower); + else this.setHighest(lower); + if (smaller != NULL) this.setBigger(smaller, bigger); + else this.setSmallest(bigger); + if (bigger != NULL) this.setSmaller(bigger, smaller); + else this.setBiggest(smaller); + this.setData(index, DEFAUlT_DATA); + this.links[index] = DEFAULT_LINKS; + this.sizeWithAir--; + if (isAlphaVisible(alpha)) this.sizeWithoutAir--; + } + + /** + refreshes the smallest-to-biggest order of this list. + as a reminder, the list is internally sorted from smallest-to-biggest + and lowest-to-highest at the same time. part of reduction logic + can invalidate the smallest-to-biggest order, so this method re-computes it. + this method does not touch the lowest-to-highest order of the list. + + this method requires that all nodes are already sorted from + lowest-to-highest, so it is not applicable to use this method in + the constructor before the lowest-to-highest order is initialized. + */ + @VisibleForTesting + public void sortBySizeAndReLink() { + long[] datas = this.data; + int writeIndex = 0; + for (int readIndex = this.getLowest(); readIndex != NULL; readIndex = this.getHigher(readIndex)) { + if (datas[readIndex] != DEFAUlT_DATA) { + this.setSortingIndex(writeIndex++, readIndex); + } + } + this.sortBySize(writeIndex); + for (int index = 1; index < writeIndex; index++) { + int smaller = this.getSortingIndex(index - 1); + int bigger = this.getSortingIndex(index); + this.setSmaller(bigger, smaller); + this.setBigger(smaller, bigger); + } + this.smallest = this.sortingArray[0]; + this.biggest = this.sortingArray[writeIndex - 1]; + this.setSmaller(this.getSmallest(), NULL); + this.setBigger(this.getBiggest(), NULL); + } + + /** + sorts our {@link #sortingArray} in order of smallest-to-biggest, + but does NOT update our links accordingly. + */ + @VisibleForTesting + public void sortBySize(int size) { + short[] array = this.sortingArray; + it.unimi.dsi.fastutil.Arrays.quickSort( + 0, + size, + (int index1, int index2) -> { + return Integer.compare( + this.getSize(this.getSortingIndex(index1)), + this.getSize(this.getSortingIndex(index2)) + ); + }, + (int index1, int index2) -> { + ShortArrays.swap(array, index1, index2); + } + ); + } + + /** + sorts our {@link #sortingArray} in order of lowest-to-highest, + but does NOT update our links accordingly. + */ + @VisibleForTesting + public void sortByPosition(int size) { + short[] array = this.sortingArray; + it.unimi.dsi.fastutil.Arrays.quickSort( + 0, + size, + (int index1, int index2) -> { + return Integer.compare( + this.getMinY(this.getSortingIndex(index1)), + this.getMinY(this.getSortingIndex(index2)) + ); + }, + (int index1, int index2) -> { + ShortArrays.swap(array, index1, index2); + } + ); + } + + /** + moves the smaller node to the correct position in the list, + under the assumption that all other nodes are already sorted. + this method should be called when the smaller node is + merged with another node, causing it to become bigger. + + important: this method ONLY handles the case where a node + is made bigger. it does NOT handle the case where a node + is made smaller. if the node is made smaller, it will be + left in its current position, even if that position is wrong. + */ + public void resortSize(int smaller) { + int bigger = this.getBigger(smaller); + + //check if the node needs to be moved at all, + //and return if it doesn't. + if (bigger == NULL || this.getSize(smaller) <= this.getSize(bigger)) return; + + //first remove smaller from before bigger. + int smallest = this.getSmaller(smaller); + if (smallest != NULL) this.setBigger(smallest, bigger); + else this.setSmallest(bigger); + this.setSmaller(bigger, smallest); + + //next, find the position to re-insert the node. + do bigger = this.getBigger(bigger); + while (bigger != NULL && this.getSize(smaller) > this.getSize(bigger)); + + //lastly, re-insert the node where it belongs. + this.setSmaller(smaller, bigger != NULL ? this.getSmaller(bigger) : this.getBiggest()); + this.setBigger(smaller, bigger); + if (bigger != NULL) this.setSmaller(bigger, smaller); + else this.setBiggest(smaller); + smallest = this.getSmaller(smaller); + if (smallest != NULL) this.setBigger(smallest, smaller); + else this.setSmallest(smaller); + } + + /** + shared logic for merging segments in step 1 documented in {@link #reduce(int)}. + + returns the index of the next node to be used for iteration. + + @param fastPath if true, we are in the "fast path" for removing + segments whose size is less than or equal to {@link #SPECIAL_CASES}. + this fast path functions somewhat differently from the normal path, + the important things to note for this method are: + + the fast path does not re-sort nodes when their size changes. + this leaves the list in an invalid state, and it is up to the caller to re-sort + the list via {@link #sortBySizeAndReLink()} after the fast path is done. + + at the time of writing this, the fast path iterates in reverse order. + as such, when fastPath is set to true, this method will return + current's smaller neighbor, when fastPath is set to false, + this method will return current's bigger neighbor instead. + */ + private int tryMergeStep1(int current, boolean fastPath) { + int + result = fastPath ? this.getSmaller(current) : this.getBigger(current), + higher = this.getHigher(current), + lower = this.getLower(current), + toExtendDownwards, + toRemove; + if (higher != NULL && this.getAlpha(higher) == this.getAlpha(current)) { + if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) { + if (this.getSize(higher) <= this.getSize(lower)) { + toExtendDownwards = higher; + toRemove = current; + } + else { + toExtendDownwards = current; + toRemove = lower; + } + } + else { + toExtendDownwards = higher; + toRemove = current; + } + } + else { + if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) { + toExtendDownwards = current; + toRemove = lower; + } + else { + return result; + } + } + //if we're about to remove the next node for iteration, + //then we need to continue iterating at the node after that. + //result will only be returned if fastPath is true, + //so the node after that is always the smaller one. that's why I don't need to do + //if (result == toRemove) result = fastPath ? this.getSmaller(result) : this.getBigger(result); + if (result == toRemove) result = this.getSmaller(result); + this.setMinY(toExtendDownwards, this.getMinY(toRemove)); + if (!fastPath) this.resortSize(toExtendDownwards); + this.remove(toRemove); + //if we're NOT on the fast path, and we reach this line, + //then we have just modified the list in a way which may + //invalidate assumptions made by the step 1 loop. + //so, return smallest to signal that the loop should start over. + //starting over is not usually a big deal, + //because small nodes are usually merged quite quickly. + //in my testing, I didn't see the step 1 loop run more + //than twice as many times as the starting list size. + return fastPath ? result : this.getSmallest(); + } + + /** + returns the largest node whose height is strictly less than the provided size, + or null if all contained nodes are greater than or equal to the provided size. + + special cases: + if the list is empty, then null is returned, + because the loop will not run and biggest will be null. + + if all nodes are less tall than size, then the largest node is returned, + because the loop will run for all nodes, but will not return any of them, + so the fallback path of returning the biggest node is used. + + if all nodes are at least as tall as size, then null is returned, + because the loop will immediately return the + smallest node's smaller neighbor, which is null. + */ + private int lowerNode(int size) { + for (int node = this.getSmallest(); node != NULL; node = this.getBigger(node)) { + if (this.getSize(node) >= size) return this.getSmaller(node); + } + return this.getBiggest(); + } + + /** + handles special cases for step 1 of {@link #reduce(int)}. + in other words, handles all the nodes whose size + is less than or equal to {@link #SPECIAL_CASES}. + + returns true if this step single-handedly brought + the list's size down to less than or equal to target, + or false if more steps need to be performed. + */ + private boolean reduceStep1SpecialCases(int target) { + for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) { + for (int current = this.lowerNode(specialCase + 1); current != NULL;) { + if (this.getSizeWithoutAir() <= target) { + this.sortBySizeAndReLink(); + if (ASSERTS) this.checkLinks(); + return true; + } + current = this.tryMergeStep1(current, true); + } + this.sortBySizeAndReLink(); + if (ASSERTS) this.checkLinks(); + } + return false; + } + + /** + handles the general case for step 1 of {@link #reduce(int)}. + in other words, handles all the nodes whose size + is strictly greater than {@link #SPECIAL_CASES}, + and all the nodes which are smaller, but failed + to be merged in {@link #reduceStep1SpecialCases(int)} + + returns true if this step single-handedly brought + the list's size down to less than or equal to target, + or false if more steps need to be performed. + */ + private boolean reduceStep1GeneralCases(int target) { + for (int current = this.getSmallest(); current != NULL;) { + if (this.getSizeWithoutAir() <= target) return true; + current = this.tryMergeStep1(current, false); + if (ASSERTS) this.checkLinks(); + } + return false; + } + + /** + handles step 2 of {@link #reduce(int)}, where nodes are allowed to be erased. + + returns true if this step single-handedly brought + the list's size down to less than or equal to target, + or false if more steps need to be performed. + */ + private boolean reduceStep2(int target) { + for (int center = this.getSmallest(); center != NULL;) { + if (this.getSizeWithoutAir() <= target) return true; + int lower = this.getLower (center); + int higher = this.getHigher(center); + if (lower != NULL && higher != NULL && this.getAlpha(lower) == this.getAlpha(higher)) { + this.setMinY(higher, this.getMinY(lower)); + this.resortSize(higher); + this.remove(lower); + this.remove(center); + if (ASSERTS) this.checkLinks(); + center = this.getSmallest(); + } + else { + center = this.getBigger(center); + } + } + return false; + } + + /** + handles step 3 of {@link #reduce(int)}, where nodes + are forced to merge in order to fit the desired target, + even if they normally shouldn't merge because it would look bad. + + returns true if this step brought the list's + size down to less than or equal to target, + or false if we need to go back to step 1. + */ + private boolean reduceStep3(int target) { + if (this.getSizeWithoutAir() <= target) return true; + int lowest = this.getLowest(); + int higher = this.getHigher(lowest); + if (higher != NULL) { + if (this.getAlpha(higher) >= this.getAlpha(lowest)) { + this.setMinY(higher, this.getMinY(lowest)); + this.remove(lowest); + } + else { + this.setMaxY(lowest, this.getMaxY(higher)); + this.resortSize(lowest); + this.remove(higher); + } + if (ASSERTS) this.checkLinks(); + return false; //go back to step 1. + } + else { + //if we reach this line, then target is 0 or negative. + this.setLowest(NULL); + this.setHighest(NULL); + this.setSmallest(NULL); + this.setBiggest(NULL); + this.setSizeWithAir(0); + this.setSizeWithoutAir(0); + return true; + } + } + + /** + merges and/or eliminates nodes until our {@link #sizeWithoutAir} + is less than or equal to the provided target size. + this method assumes that the list is already sorted by size. + if it is not sorted, you should call {@link #sortBySizeAndReLink()} first. + note also that the list is sorted in its constructor, + so if this is a new, unmodified list, then it is safe to call this method. + + algorithm: + 1: try to merge the smallest segment with the segment above or below it. + this will only succeed if the adjacent node has the same alpha as it. + 1a: if there is only one adjacent node which matches this criteria, + we will merge with that node. + + 1b: if both adjacent nodes match this criteria, + attempt to merge with the smaller one. + 1b1: if both adjacent nodes are the same height, + merge with the higher one. + + 1c: if there are no adjacent nodes which match this criteria, + repeat step 1 with the next smallest segment instead. + continue trying bigger and bigger segments until we either: + * have a success, or + * reach the end of this list. + 2: if we reach the end of the list before having a success, try again, + but this time, we are allowed to erase a segment entirely without merging it + if there are equal-alpha'd segments above and below it. + 3: if we still fail, force the lowest segment to merge with the segment above it, + with no restrictions on alpha. + the highest alpha of the two segments takes priority though. + 4: repeat until our size is less than or equal to the target size. + notes: + changing the size of a node requires re-sorting that node, + but it does not require re-sorting the whole list. + additionally, because of the fact that nodes are sorted smallest to biggest, + when a node is expanded, its new size will be + strictly less than or equal to twice its old size. + the significance of this is that in practice, + nodes should not need to be moved very far to be re-sorted. + + special case: there are a lot of segments of length 1 in big globe worlds. + these will genuinely have a long way to move on re-sort. + so, they are handled in a separate loop. + + after step 1 is completed, step 2 can't change the + list in a way which would give step 1 more work to do, + so step 2 is repeated as many times as necessary, + without jumping back to the start. + step 3 however can change the list in a way which gives previous + steps more work to do, so after step 3 merges something, + we jump back to step 1 and start over. + */ + public void reduce(int target) { + if (this.reduceStep1SpecialCases(target)) return; + + while (true) { + if (this.reduceStep1GeneralCases(target)) return; + if (this.reduceStep2(target)) return; + if (this.reduceStep3(target)) return; + } + } + + /** transfers the contents of this list to the provided view, in order of highest to lowest. */ + public void copyTo(ColumnArrayView view) { + //reminder: DH explodes horribly when I copy the nodes + //from lowest to highest instead of highest to lowest. + int writeIndex = 0; + for (int node = this.getHighest(); node != NULL; node = this.getLower(node)) { + if (this.isIndexVisible(node)) { + view.set(writeIndex++, this.getData(node)); + } + } + for (int size = view.size(); writeIndex < size; writeIndex++) { + view.set(writeIndex, 0L); + } + } + + //////////////////////////////// getters //////////////////////////////// + + public int getSmallest() { + return Short.toUnsignedInt(this.smallest); + } + + public int getBiggest() { + return Short.toUnsignedInt(this.biggest); + } + + public int getLowest() { + return Short.toUnsignedInt(this.lowest); + } + + public int getHighest() { + return Short.toUnsignedInt(this.highest); + } + + public int getSizeWithAir() { + return Short.toUnsignedInt(this.sizeWithAir); + } + + public int getSizeWithoutAir() { + return Short.toUnsignedInt(this.sizeWithoutAir); + } + + public int getSortingIndex(int index) { + return Short.toUnsignedInt(this.sortingArray[index]); + } + + public int getLower(int index) { + return ((int)(this.links[index] >>> LOWER_SHIFT)) & LINK_MASK; + } + + public int getHigher(int index) { + return ((int)(this.links[index] >>> HIGHER_SHIFT)) & LINK_MASK; + } + + public int getSmaller(int index) { + return ((int)(this.links[index] >>> SMALLER_SHIFT)) & LINK_MASK; + } + + public int getBigger(int index) { + return ((int)(this.links[index] >>> BIGGER_SHIFT)) & LINK_MASK; + } + + public long getData(int index) { + return this.data[index]; + } + + public int getMinY(int index) { + return RenderDataPointUtil.getYMin(this.getData(index)); + } + + public int getMaxY(int index) { + return RenderDataPointUtil.getYMax(this.getData(index)); + } + + public int getSize(int index) { + long data = this.getData(index); + return RenderDataPointUtil.getYMax(data) - RenderDataPointUtil.getYMin(data); + } + + public int getRed(int index) { + return RenderDataPointUtil.getRed(this.getData(index)); + } + + public int getGreen(int index) { + return RenderDataPointUtil.getGreen(this.getData(index)); + } + + public int getBlue(int index) { + return RenderDataPointUtil.getBlue(this.getData(index)); + } + + public int getAlpha(int index) { + return RenderDataPointUtil.getAlpha(this.getData(index)); + } + + public int getBlockLight(int index) { + return RenderDataPointUtil.getLightBlock(this.getData(index)); + } + + public int getSkyLight(int index) { + return RenderDataPointUtil.getLightSky(this.getData(index)); + } + + //////////////////////////////// setters //////////////////////////////// + + public void setSmallest(int smallest) { + this.smallest = (short)(smallest); + } + + public void setBiggest(int biggest) { + this.biggest = (short)(biggest); + } + + public void setLowest(int lowest) { + this.lowest = (short)(lowest); + } + + public void setHighest(int highest) { + this.highest = (short)(highest); + } + + public void setSizeWithAir(int sizeWithAir) { + this.sizeWithAir = (short)(sizeWithAir); + } + + public void setSizeWithoutAir(int sizeWithoutAir) { + this.sizeWithoutAir = (short)(sizeWithoutAir); + } + + public void setSortingIndex(int index, int to) { + this.sortingArray[index] = (short)(to); + } + + public void setLower(int index, int lowerIndex) { + this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << LOWER_SHIFT)) | (((long)(lowerIndex & LINK_MASK)) << LOWER_SHIFT); + } + + public void setHigher(int index, int higherIndex) { + this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << HIGHER_SHIFT)) | (((long)(higherIndex & LINK_MASK)) << HIGHER_SHIFT); + } + + public void setSmaller(int index, int smallerIndex) { + this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << SMALLER_SHIFT)) | (((long)(smallerIndex & LINK_MASK)) << SMALLER_SHIFT); + } + + public void setBigger(int index, int biggerIndex) { + this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << BIGGER_SHIFT)) | (((long)(biggerIndex & LINK_MASK)) << BIGGER_SHIFT); + } + + public void setData(int index, long data) { + this.data[index] = data; + } + + public void setMinY(int index, int minY) { + this.data[index] = (this.data[index] & ~RenderDataPointUtil.DEPTH_SHIFTED_MASK) | ((minY & RenderDataPointUtil.DEPTH_MASK) << RenderDataPointUtil.DEPTH_SHIFT); + } + + public void setMaxY(int index, int maxY) { + this.data[index] = (this.data[index] & ~RenderDataPointUtil.HEIGHT_SHIFTED_MASK) | ((maxY & RenderDataPointUtil.HEIGHT_MASK) << RenderDataPointUtil.HEIGHT_SHIFT); + } + + public void setRed(int index, int red) { + this.data[index] = (this.data[index] & ~(RenderDataPointUtil.RED_MASK << RenderDataPointUtil.RED_SHIFT)) | ((red & RenderDataPointUtil.RED_MASK) << RenderDataPointUtil.RED_SHIFT); + } + + public void setGreen(int index, int green) { + this.data[index] = (this.data[index] & ~(RenderDataPointUtil.GREEN_MASK << RenderDataPointUtil.GREEN_SHIFT)) | ((green & RenderDataPointUtil.GREEN_MASK) << RenderDataPointUtil.GREEN_SHIFT); + } + + public void setBlue(int index, int blue) { + this.data[index] = (this.data[index] & ~(RenderDataPointUtil.BLUE_MASK << RenderDataPointUtil.BLUE_SHIFT)) | ((blue & RenderDataPointUtil.BLUE_MASK) << RenderDataPointUtil.BLUE_SHIFT); + } + + public void setAlpha(int index, int alpha) { + alpha >>>= RenderDataPointUtil.ALPHA_DOWNSIZE_SHIFT; + this.data[index] = (this.data[index] & ~(RenderDataPointUtil.ALPHA_MASK << RenderDataPointUtil.ALPHA_SHIFT)) | ((alpha & RenderDataPointUtil.ALPHA_MASK) << RenderDataPointUtil.ALPHA_SHIFT); + } + + public void setBlockLight(int index, int blockLight) { + this.data[index] = (this.data[index] & ~(RenderDataPointUtil.BLOCK_LIGHT_MASK << RenderDataPointUtil.BLOCK_LIGHT_SHIFT)) | ((blockLight & RenderDataPointUtil.BLOCK_LIGHT_MASK) << RenderDataPointUtil.BLOCK_LIGHT_SHIFT); + } + + public void setSkyLight(int index, int skyLight) { + this.data[index] = (this.data[index] & ~(RenderDataPointUtil.SKY_LIGHT_MASK << RenderDataPointUtil.SKY_LIGHT_SHIFT)) | ((skyLight & RenderDataPointUtil.SKY_LIGHT_MASK) << RenderDataPointUtil.SKY_LIGHT_SHIFT); + } + + //////////////////////////////// utility //////////////////////////////// + + public boolean isIndexVisible(int index) { + return isDataVisible(this.getData(index)); + } + + public static boolean isDataVisible(long data) { + return isAlphaVisible(RenderDataPointUtil.getAlpha(data)); + } + + public static boolean isAlphaVisible(int alpha) { + return alpha >= 16; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(this.sizeWithAir << 8).append("lowest to highest:"); + for (int index = this.lowest; index != NULL; index = this.getHigher(index)) { + builder.append('\n').append(RenderDataPointUtil.toString(this.getData(index))); + } + builder.append("\nsmallest to biggest:"); + for (int index = this.smallest; index != NULL; index = this.getBigger(index)) { + builder.append('\n').append(RenderDataPointUtil.toString(this.getData(index))); + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java index 4edca2d40..3cee54b34 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java @@ -256,12 +256,17 @@ public class RenderDataPointUtil // TODO this should probably be moved // TODO what is the purpose of these? + //these were needed by the old logic for mergeMultiData(), + //which has now been replaced by RenderDataPointReducingList. + //so, these are no longer necessary, but left here for the same + //reason the old logic is left here: in case it's ever needed again. + /* private static final ThreadLocal tLocalIndices = new ThreadLocal<>(); private static final ThreadLocal tLocalIncreaseIndex = new ThreadLocal<>(); private static final ThreadLocal tLocalIndexHandled = new ThreadLocal<>(); private static final ThreadLocal tLocalHeightAndDepth = new ThreadLocal<>(); private static final ThreadLocal tDataIndexCache = new ThreadLocal<>(); - + */ /** * This method merge column of multiple data together @@ -271,6 +276,12 @@ public class RenderDataPointUtil */ public static void mergeMultiData(IColumnDataView sourceData, ColumnArrayView output) { + RenderDataPointReducingList list = new RenderDataPointReducingList(sourceData); + list.reduce(output.verticalSize()); + list.copyTo(output); + + //old logic left here in case it's ever needed again. + /* if (output.dataCount() != 1) { throw new IllegalArgumentException("output must be only reserved for one datapoint!"); @@ -612,6 +623,7 @@ public class RenderDataPointUtil } } + */ } } \ No newline at end of file From d1d7cc4dd24f67a1280849fe4db9c82a8bb53d9c Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 25 Nov 2023 07:39:06 -0600 Subject: [PATCH 09/32] catch unexpected ClientTick exceptions to prevent crashing MC --- .../distanthorizons/core/level/DhClientLevel.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java index 1bacd0eaf..6edb8640e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/DhClientLevel.java @@ -77,8 +77,15 @@ public class DhClientLevel extends DhLevel implements IDhClientLevel @Override public void clientTick() { - this.chunkToLodBuilder.tick(); - this.clientside.clientTick(); + try + { + this.chunkToLodBuilder.tick(); + this.clientside.clientTick(); + } + catch (Exception e) + { + LOGGER.error("Unexpected clientTick Exception: "+e.getMessage(), e); + } } @Override From 124dbe700f68ecdacce399d5f54d32e1676a5a21 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 25 Nov 2023 07:39:31 -0600 Subject: [PATCH 10/32] Attempt to prevent threadPool null pointers --- .../distanthorizons/core/util/threading/ConfigThreadPool.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java index 47a509b91..108d41415 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/threading/ConfigThreadPool.java @@ -90,9 +90,8 @@ public class ConfigThreadPool { if (this.executor != null) { - //LOGGER.info("Stopping File Handler"); + //LOGGER.info("Stopping thread pool"); this.executor.shutdownNow(); - this.executor = null; } this.threadCount = 0; From ab60df62401bf6d7024f47dad0df360acebb0b3a Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 25 Nov 2023 08:44:41 -0600 Subject: [PATCH 11/32] Add out of bounds handling for LodDataBuilder --- .../transformers/LodDataBuilder.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java index b6513df41..f6eecfa6e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java @@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.dataObjects.transformers; import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.ChunkSizedFullDataAccessor; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; @@ -28,12 +29,15 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.apache.logging.log4j.Logger; public class LodDataBuilder { - + private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper(); + private static boolean getTopErrorLogged = false; + public static ChunkSizedFullDataAccessor createChunkData(IChunkWrapper chunkWrapper) { @@ -65,10 +69,24 @@ public class LodDataBuilder IBlockStateWrapper topBlockState = chunkWrapper.getBlockState(x, y, z); while (!topBlockState.isAir() && y < chunkWrapper.getMaxBuildHeight()) { - // This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light. - // Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks. - y++; - topBlockState = chunkWrapper.getBlockState(x, y, z); + try + { + // This is necessary in some edge cases with snow layers and some other blocks that may not appear in the height map but do block light. + // Interestingly this doesn't appear to be the case in the DhLightingEngine, if this same logic is added there the lighting breaks for the affected blocks. + y++; + topBlockState = chunkWrapper.getBlockState(x, y, z); + } + catch (Exception e) + { + if (!getTopErrorLogged) + { + LOGGER.warn("Unexpected issue in LodDataBuilder, future errors won't be logged. Chunk [" + chunkWrapper.getChunkPos() + "] with max height: [" + chunkWrapper.getMaxBuildHeight() + "] had issue getting block at pos [" + x + "," + y + "," + z + "] error: " + e.getMessage(), e); + getTopErrorLogged = true; + } + + y--; + break; + } } From e693e2f9e0b82ebc8f4be1fb9e18816ffb772a48 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 25 Nov 2023 08:45:40 -0600 Subject: [PATCH 12/32] Add transparency constants to IBlockStateWrapper --- .../core/wrapperInterfaces/block/IBlockStateWrapper.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/block/IBlockStateWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/block/IBlockStateWrapper.java index becf4cf33..b4b503095 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/block/IBlockStateWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/block/IBlockStateWrapper.java @@ -24,6 +24,11 @@ import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper; /** A Minecraft version independent way of handling Blocks. */ public interface IBlockStateWrapper extends IDhApiBlockStateWrapper { + int FULLY_TRANSPARENT = 0; + int FULLY_OPAQUE = 16; + + + String getSerialString(); /** From bd3592ee5bf20b522cdf8729449de7e2dd6664c0 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 25 Nov 2023 11:08:36 -0600 Subject: [PATCH 13/32] Fix lighting on some servers --- .../core/generation/DhLightingEngine.java | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java index d5d2fc1fe..86c6a54ed 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java @@ -72,6 +72,8 @@ public class DhLightingEngine DhChunkPos centerChunkPos = centerChunk.getChunkPos(); AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk); + long startTimeNs = System.nanoTime(); + // try-finally to handle the stableArray resources StableLightPosStack blockLightPosQueue = null; @@ -143,25 +145,25 @@ public class DhLightingEngine { for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) { - // get the light - int maxY = chunk.getLightBlockingHeightMapValue(relX, relZ); - DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, maxY, chunk.getMinBlockZ() + relZ); - if (skyLightPos.y < chunk.getMinBuildHeight() || skyLightPos.y > chunk.getMaxBuildHeight()) + // set each pos' sky light all the way down until a opaque block is hit + for (int y = chunk.getMaxBuildHeight(); y >= chunk.getMinBuildHeight(); y--) { - // this shouldn't normally happen - if (!warningLogged) + IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ); + if (block != null && block.getOpacity() != IBlockStateWrapper.FULLY_TRANSPARENT) { - warningLogged = true; - LOGGER.debug("Lighting chunk at pos " + chunk.getChunkPos() + " may have a missing or incomplete heightmap. Chunk min/max [" + chunk.getMinBuildHeight() + "/" + chunk.getMaxBuildHeight() + "], skylight pos: " + skyLightPos); + // keep moving down until we find a non-transparent block + break; } - continue; + + + // add sky light to the queue + DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinBlockX() + relX, y, chunk.getMinBlockZ() + relZ); + skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight); + + // set the chunk's sky light + skyLightPos.mutateToChunkRelativePos(relBlockPos); + chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight); } - skyLightPosQueue.push(skyLightPos.x, skyLightPos.y, skyLightPos.z, maxSkyLight); - - - // set the light - skyLightPos.mutateToChunkRelativePos(relBlockPos); - chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight); } } } @@ -199,7 +201,10 @@ public class DhLightingEngine centerChunk.setIsDhLightCorrect(true); centerChunk.setUseDhLighting(true); - LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "]"); + + long endTimeNs = System.nanoTime(); + float totalTimeMs = (endTimeNs - startTimeNs) / 1_000_000.0f; + LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "] in ["+totalTimeMs+"] milliseconds"); } /** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */ From efc2ce84e48ae6f9380fe9c5721d6883bd362a23 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 25 Nov 2023 11:39:39 -0600 Subject: [PATCH 14/32] Fix null pointer when joining some multiverse servers --- .../core/api/internal/ClientApi.java | 125 ++++++++++++------ .../core/world/DhClientServerWorld.java | 7 +- .../core/world/DhClientWorld.java | 7 +- .../core/world/DhServerWorld.java | 7 +- .../distanthorizons/core/world/IDhWorld.java | 7 +- 5 files changed, 97 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java index acb18b5f9..31aa4be55 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/ClientApi.java @@ -48,6 +48,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp //import io.netty.buffer.ByteBuf; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import java.nio.ByteBuffer; @@ -160,46 +161,73 @@ public class ClientApi // level events // //==============// - public void clientLevelUnloadEvent(IClientLevelWrapper level) + public void clientLevelUnloadEvent(@Nullable IClientLevelWrapper level) { - LOGGER.info("Unloading client level [" + level + "]."); - - AbstractDhWorld world = SharedApi.getAbstractDhWorld(); - if (world != null) + try { - world.unloadLevel(level); - ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level)); + if (level == null) + { + // can happen on certain multiverse servers + return; + } + LOGGER.info("Unloading client level [" + level + "]."); + + AbstractDhWorld world = SharedApi.getAbstractDhWorld(); + if (world != null) + { + world.unloadLevel(level); + ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level)); + } + else + { + this.waitingClientLevels.remove(level); + } } - else + catch (Exception e) { - this.waitingClientLevels.remove(level); + // handle errors here to prevent blowing up a mixin or API up stream + LOGGER.error("Unexpected error in ClientApi.clientLevelUnloadEvent(), error: "+e.getMessage(), e); } } - public void clientLevelLoadEvent(IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); } - public void multiverseClientLevelLoadEvent(IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); } - private void clientLevelLoadEvent(IClientLevelWrapper level, boolean isServerCommunication) + public void clientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, false); } + public void multiverseClientLevelLoadEvent(@Nullable IClientLevelWrapper level) { this.clientLevelLoadEvent(level, true); } + private void clientLevelLoadEvent(@Nullable IClientLevelWrapper level, boolean isServerCommunication) { - if (this.isServerCommunicationEnabled && !isServerCommunication) + try { - LOGGER.info("Server supports communication, deferring loading."); - return; - } - - - LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "]."); - - AbstractDhWorld world = SharedApi.getAbstractDhWorld(); - if (world != null) - { - world.getOrLoadLevel(level); - ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level)); + if (this.isServerCommunicationEnabled && !isServerCommunication) + { + LOGGER.info("Server supports communication, deferring loading."); + return; + } + if (level == null) + { + // can happen on certain multiverse servers + return; + } - this.loadWaitingChunksForLevel(level); + + + LOGGER.info("Loading " + (isServerCommunication ? "Multiverse" : "") + " client level [" + level + "]."); + + AbstractDhWorld world = SharedApi.getAbstractDhWorld(); + if (world != null) + { + world.getOrLoadLevel(level); + ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(level)); + + this.loadWaitingChunksForLevel(level); + } + else + { + this.waitingClientLevels.add(level); + } } - else + catch (Exception e) { - this.waitingClientLevels.add(level); + // handle errors here to prevent blowing up a mixin or API up stream + LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e); } } private void loadWaitingChunksForLevel(IClientLevelWrapper level) @@ -257,26 +285,35 @@ public class ClientApi IProfilerWrapper profiler = MC.getProfiler(); profiler.push("DH-ClientTick"); - boolean doFlush = System.nanoTime() - this.lastFlushNanoTime >= SPAM_LOGGER_FLUSH_NS; - if (doFlush) + try { - this.lastFlushNanoTime = System.nanoTime(); - SpamReducedLogger.flushAll(); - } - ConfigBasedLogger.updateAll(); - ConfigBasedSpamLogger.updateAll(doFlush); - - IDhClientWorld clientWorld = SharedApi.getIDhClientWorld(); - if (clientWorld != null) - { - clientWorld.clientTick(); - - // Ignore local world gen, as it's managed by server ticking - if (!(clientWorld instanceof DhClientServerWorld)) + boolean doFlush = System.nanoTime() - this.lastFlushNanoTime >= SPAM_LOGGER_FLUSH_NS; + if (doFlush) { - SharedApi.worldGenTick(clientWorld::doWorldGen); + this.lastFlushNanoTime = System.nanoTime(); + SpamReducedLogger.flushAll(); + } + ConfigBasedLogger.updateAll(); + ConfigBasedSpamLogger.updateAll(doFlush); + + IDhClientWorld clientWorld = SharedApi.getIDhClientWorld(); + if (clientWorld != null) + { + clientWorld.clientTick(); + + // Ignore local world gen, as it's managed by server ticking + if (!(clientWorld instanceof DhClientServerWorld)) + { + SharedApi.worldGenTick(clientWorld::doWorldGen); + } } } + catch (Exception e) + { + // handle errors here to prevent blowing up a mixin or API up stream + LOGGER.error("Unexpected error in ClientApi.clientTickEvent(), error: "+e.getMessage(), e); + } + profiler.pop(); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java index 6d64be5ca..c0584ced3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientServerWorld.java @@ -29,6 +29,7 @@ import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.HashMap; @@ -69,7 +70,7 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor //=========// @Override - public DhClientServerLevel getOrLoadLevel(ILevelWrapper wrapper) + public DhClientServerLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper) { if (wrapper instanceof IServerLevelWrapper) { @@ -105,13 +106,13 @@ public class DhClientServerWorld extends AbstractDhWorld implements IDhClientWor } @Override - public DhClientServerLevel getLevel(ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); } + public DhClientServerLevel getLevel(@NotNull ILevelWrapper wrapper) { return this.levelWrapperByDhLevel.get(wrapper); } @Override public Iterable getAllLoadedLevels() { return this.dhLevels; } @Override - public void unloadLevel(ILevelWrapper wrapper) + public void unloadLevel(@NotNull ILevelWrapper wrapper) { if (this.levelWrapperByDhLevel.containsKey(wrapper)) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java index 9daa63b94..7aa0e7a64 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhClientWorld.java @@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftCli import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.util.concurrent.CompletableFuture; @@ -111,7 +112,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld //=========// @Override - public DhClientLevel getOrLoadLevel(ILevelWrapper wrapper) + public DhClientLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper) { if (!(wrapper instanceof IClientLevelWrapper)) { @@ -132,7 +133,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld } @Override - public DhClientLevel getLevel(ILevelWrapper wrapper) + public DhClientLevel getLevel(@NotNull ILevelWrapper wrapper) { if (!(wrapper instanceof IClientLevelWrapper)) { @@ -146,7 +147,7 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public Iterable getAllLoadedLevels() { return this.levels.values(); } @Override - public void unloadLevel(ILevelWrapper wrapper) + public void unloadLevel(@NotNull ILevelWrapper wrapper) { if (!(wrapper instanceof IClientLevelWrapper)) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java index 58c5d73fa..4d35d79b8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/DhServerWorld.java @@ -32,6 +32,7 @@ import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.jetbrains.annotations.NotNull; //import io.netty.channel.ChannelHandlerContext; import java.io.File; @@ -138,7 +139,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld } @Override - public DhServerLevel getOrLoadLevel(ILevelWrapper wrapper) + public DhServerLevel getOrLoadLevel(@NotNull ILevelWrapper wrapper) { if (!(wrapper instanceof IServerLevelWrapper)) { @@ -154,7 +155,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld } @Override - public DhServerLevel getLevel(ILevelWrapper wrapper) + public DhServerLevel getLevel(@NotNull ILevelWrapper wrapper) { if (!(wrapper instanceof IServerLevelWrapper)) { @@ -168,7 +169,7 @@ public class DhServerWorld extends AbstractDhWorld implements IDhServerWorld public Iterable getAllLoadedLevels() { return this.levels.values(); } @Override - public void unloadLevel(ILevelWrapper wrapper) + public void unloadLevel(@NotNull ILevelWrapper wrapper) { if (!(wrapper instanceof IServerLevelWrapper)) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/world/IDhWorld.java b/core/src/main/java/com/seibel/distanthorizons/core/world/IDhWorld.java index 388929e89..dd631015c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/world/IDhWorld.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/world/IDhWorld.java @@ -21,17 +21,18 @@ package com.seibel.distanthorizons.core.world; import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; +import org.jetbrains.annotations.NotNull; import java.util.concurrent.CompletableFuture; public interface IDhWorld { - IDhLevel getOrLoadLevel(ILevelWrapper levelWrapper); - IDhLevel getLevel(ILevelWrapper wrapper); + IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper); + IDhLevel getLevel(@NotNull ILevelWrapper wrapper); Iterable getAllLoadedLevels(); - void unloadLevel(ILevelWrapper levelWrapper); + void unloadLevel(@NotNull ILevelWrapper levelWrapper); CompletableFuture saveAndFlush(); From 0dc267f0381db428e799ef09171be0164d9606af Mon Sep 17 00:00:00 2001 From: NULL511 Date: Sun, 26 Nov 2023 14:53:50 -0500 Subject: [PATCH 15/32] add shader near clip using distance --- .../render/renderer/LodRenderProgram.java | 11 +++- .../core/render/renderer/LodRenderer.java | 2 +- .../main/resources/shaders/flat_shaded.frag | 61 +++++++++++-------- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java index bd2d64a73..90d07b631 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java @@ -30,6 +30,7 @@ import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexAtt import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.render.fog.LodFogConfig; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import com.seibel.distanthorizons.coreapi.util.math.Vec3f; import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants; @@ -40,6 +41,7 @@ public class LodRenderProgram extends ShaderProgram public static final String VERTEX_CURVE_SHADER_PATH = "shaders/curve.vert"; public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag"; private static final IVersionConstants VERSION_CONSTANTS = SingletonInjector.INSTANCE.get(IVersionConstants.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); public final AbstractVertexAttribute vao; @@ -54,7 +56,8 @@ public class LodRenderProgram extends ShaderProgram public final int lightMapUniform; - // Fog Uniforms + // Fog/Clip Uniforms + public final int clipDistanceUniform; // Noise Uniforms public final int noiseEnabledUniform; @@ -84,6 +87,8 @@ public class LodRenderProgram extends ShaderProgram lightMapUniform = getUniformLocation("lightMap"); + // Fog/Clip Uniforms + clipDistanceUniform = getUniformLocation("clipDistance"); // Noise Uniforms noiseEnabledUniform = getUniformLocation("noiseEnabled"); @@ -118,6 +123,10 @@ public class LodRenderProgram extends ShaderProgram if (earthRadiusUniform != -1) setUniform(earthRadiusUniform, /*6371KM*/ 6371000.0f / fogConfig.earthCurveRatio); + // Fog/Clip Uniforms + float vanillaBlockRenderedDistance = ((float)MC_RENDER.getRenderDistance() - 0.5f) * LodUtil.CHUNK_WIDTH; + setUniform(clipDistanceUniform, vanillaBlockRenderedDistance); + // Noise Uniforms setUniform(noiseEnabledUniform, fogConfig.noiseEnable); setUniform(noiseStepsUniform, fogConfig.noiseSteps); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index 840c35abc..d8c8af92c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -254,7 +254,7 @@ public class LodRenderer // and often do change the projection entirely, as well as the output usage. //EVENT_LOGGER.debug("Skipping shadow pass render."); - //return; + return; } // Note: Since lightmapTexture is changing every frame, it's faster to recreate it than to reuse the old one. diff --git a/core/src/main/resources/shaders/flat_shaded.frag b/core/src/main/resources/shaders/flat_shaded.frag index edcddbcca..72643cd53 100644 --- a/core/src/main/resources/shaders/flat_shaded.frag +++ b/core/src/main/resources/shaders/flat_shaded.frag @@ -8,6 +8,9 @@ in vec4 vPos; out vec4 fragColor; +// Fog/Clip Uniforms +uniform float clipDistance = 0.0; + // Noise Uniforms uniform bool noiseEnabled; uniform int noiseSteps; @@ -28,6 +31,33 @@ vec3 quantize(vec3 val, int stepSize) { return floor(val * stepSize) / stepSize; } +void applyNoise(inout vec4 fragColor, const in float viewDist) { + vec3 vertexNormal = normalize(cross(dFdy(vPos.xyz), dFdx(vPos.xyz))); + // This bit of code is required to fix the vertex position problem cus of floats in the verted world position varuable + vec3 fixedVPos = vPos.xyz + vertexNormal * 0.001; + + float noiseAmplification = noiseIntensity * 0.01; + float lum = (fragColor.r + fragColor.g + fragColor.b) / 3.0; + noiseAmplification = (1.0 - pow(lum * 2.0 - 1.0, 2.0)) * noiseAmplification; // Lessen the effect on depending on how dark the object is, equasion for this is -(2x-1)^{2}+1 + noiseAmplification *= fragColor.a; // The effect would lessen on transparent objects + + // Random value for each position + float randomValue = rand(quantize(fixedVPos, noiseSteps)) + * 2.0 * noiseAmplification - noiseAmplification; + + // Modifies the color + // A value of 0 on the randomValue will result in the original color, while a value of 1 will result in a fully bright color + vec3 newCol = fragColor.rgb + (1.0 - fragColor.rgb) * randomValue; + newCol = clamp(newCol, 0.0, 1.0); + + if (noiseDropoff != 0) { + float distF = min(viewDist / noiseDropoff, 1.0); + newCol = mix(newCol, fragColor.rgb, distF); // The further away it gets, the less noise gets applied + } + + fragColor.rgb = newCol; +} + /** * Fragment Shader @@ -40,31 +70,8 @@ void main() { fragColor = vertexColor; - // TODO: Move into its own function instead of in an if statement - if (noiseEnabled) { - vec3 vertexNormal = normalize(cross(dFdy(vPos.xyz), dFdx(vPos.xyz))); - // This bit of code is required to fix the vertex position problem cus of floats in the verted world position varuable - vec3 fixedVPos = vPos.xyz + vertexNormal * 0.001; - - float noiseAmplification = noiseIntensity * 0.01; - float lum = (fragColor.r + fragColor.g + fragColor.b) / 3.0; - noiseAmplification = (1.0 - pow(lum * 2.0 - 1.0, 2.0)) * noiseAmplification; // Lessen the effect on depending on how dark the object is, equasion for this is -(2x-1)^{2}+1 - noiseAmplification *= fragColor.a; // The effect would lessen on transparent objects - - // Random value for each position - float randomValue = rand(quantize(fixedVPos, noiseSteps)) - * 2.0 * noiseAmplification - noiseAmplification; - - // Modifies the color - // A value of 0 on the randomValue will result in the original color, while a value of 1 will result in a fully bright color - vec3 newCol = fragColor.rgb + (1.0 - fragColor.rgb) * randomValue; - newCol = clamp(newCol, 0.0, 1.0); - - if (noiseDropoff != 0) { - float distF = min(length(vertexWorldPos) / noiseDropoff, 1.0); - newCol = mix(newCol, fragColor.rgb, distF); // The further away it gets, the less noise gets applied - } - - fragColor.rgb = newCol; - } + float viewDist = length(vertexWorldPos); + if (viewDist < clipDistance && clipDistance > 0.0) discard; + + if (noiseEnabled) applyNoise(fragColor, viewDist); } From ddc3291bf72f9756219da0232bf22cb58efd4933 Mon Sep 17 00:00:00 2001 From: NULL511 Date: Sun, 26 Nov 2023 15:18:45 -0500 Subject: [PATCH 16/32] fix shader near plane; remove dh near --- .../core/render/renderer/LodRenderProgram.java | 15 ++++++++------- .../core/render/renderer/LodRenderer.java | 4 ++-- .../distanthorizons/core/util/RenderUtil.java | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java index 90d07b631..19d43ed23 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java @@ -30,6 +30,7 @@ import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexAtt import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.render.fog.LodFogConfig; +import com.seibel.distanthorizons.core.util.RenderUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import com.seibel.distanthorizons.coreapi.util.math.Vec3f; @@ -41,7 +42,7 @@ public class LodRenderProgram extends ShaderProgram public static final String VERTEX_CURVE_SHADER_PATH = "shaders/curve.vert"; public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag"; private static final IVersionConstants VERSION_CONSTANTS = SingletonInjector.INSTANCE.get(IVersionConstants.class); - private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + //private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); public final AbstractVertexAttribute vao; @@ -123,10 +124,6 @@ public class LodRenderProgram extends ShaderProgram if (earthRadiusUniform != -1) setUniform(earthRadiusUniform, /*6371KM*/ 6371000.0f / fogConfig.earthCurveRatio); - // Fog/Clip Uniforms - float vanillaBlockRenderedDistance = ((float)MC_RENDER.getRenderDistance() - 0.5f) * LodUtil.CHUNK_WIDTH; - setUniform(clipDistanceUniform, vanillaBlockRenderedDistance); - // Noise Uniforms setUniform(noiseEnabledUniform, fogConfig.noiseEnable); setUniform(noiseStepsUniform, fogConfig.noiseSteps); @@ -175,10 +172,10 @@ public class LodRenderProgram extends ShaderProgram vao.unbindBuffersFromAllBindingPoint(); } - public void fillUniformData(Mat4f combinedMatrix, int lightmapBindPoint, int worldYOffset, int vanillaDrawDistance) + public void fillUniformData(Mat4f combinedMatrix, int lightmapBindPoint, int worldYOffset, float partialTicks) { super.bind(); - vanillaDrawDistance += 32; // Give it a 2 chunk boundary for near fog. + //vanillaDrawDistance += 32; // Give it a 2 chunk boundary for near fog. // uniforms setUniform(combinedMatUniform, combinedMatrix); setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset @@ -191,6 +188,10 @@ public class LodRenderProgram extends ShaderProgram // Debug setUniform(whiteWorldUniform, Config.Client.Advanced.Debugging.enableWhiteWorld.get()); + // Fog/Clip Uniforms + //float vanillaBlockRenderedDistance = ((float)MC_RENDER.getRenderDistance() - 0.5f) * LodUtil.CHUNK_WIDTH; + float dhNearClipDistance = RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks); + setUniform(clipDistanceUniform, dhNearClipDistance); } public void setModelPos(Vec3f modelPos) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java index d8c8af92c..1f2b4633f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderer.java @@ -365,7 +365,7 @@ public class LodRenderer } /*---------Get required data--------*/ - int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH; + //int vanillaBlockRenderedDistance = MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH; //Mat4f modelViewProjectionMatrix = RenderUtil.createCombinedModelViewProjectionMatrix(baseProjectionMatrix, baseModelViewMatrix, partialTicks); Mat4f projectionMatrix = RenderUtil.createLodProjectionMatrix(baseProjectionMatrix, partialTicks); @@ -375,7 +375,7 @@ public class LodRenderer /*---------Fill uniform data--------*/ this.shaderProgram.fillUniformData(modelViewProjectionMatrix, /*Light map = GL_TEXTURE0*/ 0, - MC.getWrappedClientLevel().getMinHeight(), vanillaBlockRenderedDistance); + MC.getWrappedClientLevel().getMinHeight(), partialTicks); lightmap.bind(); if (ENABLE_IBO) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index ed351f84c..060ed38bd 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -152,7 +152,7 @@ public class RenderUtil // Set new far and near clip plane values. lodProj.setClipPlanes( - getNearClipPlaneDistanceInBlocks(partialTicks), + 16f, (float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2))); return lodProj; From eee52c8f5567b8b02fadff5aa2ab31d6fc714bfc Mon Sep 17 00:00:00 2001 From: NULL511 Date: Sun, 26 Nov 2023 15:57:22 -0500 Subject: [PATCH 17/32] cleanup --- .../core/render/renderer/LodRenderProgram.java | 5 +---- .../com/seibel/distanthorizons/core/util/RenderUtil.java | 5 ++--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java index 19d43ed23..66b63a02e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/renderer/LodRenderProgram.java @@ -31,7 +31,6 @@ import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPoi import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.render.fog.LodFogConfig; import com.seibel.distanthorizons.core.util.RenderUtil; -import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.coreapi.util.math.Mat4f; import com.seibel.distanthorizons.coreapi.util.math.Vec3f; import com.seibel.distanthorizons.core.wrapperInterfaces.IVersionConstants; @@ -42,7 +41,6 @@ public class LodRenderProgram extends ShaderProgram public static final String VERTEX_CURVE_SHADER_PATH = "shaders/curve.vert"; public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag"; private static final IVersionConstants VERSION_CONSTANTS = SingletonInjector.INSTANCE.get(IVersionConstants.class); - //private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); public final AbstractVertexAttribute vao; @@ -175,7 +173,7 @@ public class LodRenderProgram extends ShaderProgram public void fillUniformData(Mat4f combinedMatrix, int lightmapBindPoint, int worldYOffset, float partialTicks) { super.bind(); - //vanillaDrawDistance += 32; // Give it a 2 chunk boundary for near fog. + // uniforms setUniform(combinedMatUniform, combinedMatrix); setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset @@ -189,7 +187,6 @@ public class LodRenderProgram extends ShaderProgram setUniform(whiteWorldUniform, Config.Client.Advanced.Debugging.enableWhiteWorld.get()); // Fog/Clip Uniforms - //float vanillaBlockRenderedDistance = ((float)MC_RENDER.getRenderDistance() - 0.5f) * LodUtil.CHUNK_WIDTH; float dhNearClipDistance = RenderUtil.getNearClipPlaneDistanceInBlocks(partialTicks); setUniform(clipDistanceUniform, dhNearClipDistance); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index 060ed38bd..c11b8fd2e 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -146,14 +146,13 @@ public class RenderUtil public static Mat4f createLodProjectionMatrix(Mat4f mcProjMat, float partialTicks) { int farPlaneDistanceInBlocks = RenderUtil.getFarClipPlaneDistanceInBlocks(); + float farClipDist = (float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2)); // Create a copy of the current matrix, so it won't be modified. Mat4f lodProj = mcProjMat.copy(); // Set new far and near clip plane values. - lodProj.setClipPlanes( - 16f, - (float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2))); + lodProj.setClipPlanes(16f, farClipDist); return lodProj; } From d21244ce233cc62111303fb267bd926b800ec7e2 Mon Sep 17 00:00:00 2001 From: Builderb0y Date: Mon, 27 Nov 2023 01:52:22 +0000 Subject: [PATCH 18/32] handle edge case where there are many segments to merge but all of them are invisible. --- .../util/RenderDataPointReducingList.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java index 8944ce7e2..25243147c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -98,6 +98,7 @@ public class RenderDataPointReducingList { this.links = LongArrays.EMPTY_ARRAY; this.data = LongArrays.EMPTY_ARRAY; this.sortingArray = ShortArrays.EMPTY_ARRAY; + if (ASSERTS) this.checkLinks(); return; } //allocate an array big enough to hold 2 * size - 1 nodes. @@ -122,6 +123,18 @@ public class RenderDataPointReducingList { sizeWithoutAir++; } } + + //check if all segments to merge are air or otherwise invisible (barriers). + //if they are, then this list can stay empty. + if (sizeWithoutAir == 0) { + this.setLowest(NULL); + this.setHighest(NULL); + this.setSmallest(NULL); + this.setBiggest(NULL); + if (ASSERTS) this.checkLinks(); + return; + } + //sort the nodes by Y level. this.sortByPosition(sizeWithoutAir); //next pass: link the nodes together, and insert air nodes as necessary. @@ -191,6 +204,8 @@ public class RenderDataPointReducingList { */ @VisibleForTesting public void checkLinks() { + LodUtil.assertTrue(this.getSizeWithAir() >= 0, "size with air < 0"); + LodUtil.assertTrue(this.getSizeWithoutAir() >= 0, "size without air < 0"); LodUtil.assertTrue(this.getSizeWithoutAir() <= this.getSizeWithAir(), "more segments without air than with air"); if (this.getSizeWithAir() == 0) { LodUtil.assertTrue(this.getSmallest() == NULL, "size is 0, but we have a smallest node"); @@ -199,7 +214,6 @@ public class RenderDataPointReducingList { LodUtil.assertTrue(this.getHighest() == NULL, "size is 0, but we have a highest node"); } else { - LodUtil.assertTrue(this.getSizeWithAir() > 0 && this.getSizeWithoutAir() >= 0, "at least one of our sizes is negative"); int sizeWithAir = 0, sizeWithoutAir = 0; for (int index = this.getSmallest(); index != NULL; index = this.getBigger(index)) { int smaller = this.getSmaller(index); @@ -262,6 +276,7 @@ public class RenderDataPointReducingList { */ @VisibleForTesting public void sortBySizeAndReLink() { + if (this.getSizeWithAir() <= 1) return; long[] datas = this.data; int writeIndex = 0; for (int readIndex = this.getLowest(); readIndex != NULL; readIndex = this.getHigher(readIndex)) { @@ -640,6 +655,12 @@ public class RenderDataPointReducingList { view.set(writeIndex++, this.getData(node)); } } + //this list could be empty if all the segments for merging are invisible, + //but we must ensure that the view is non-empty. + //so, if we didn't set any data points, add a void data point. + if (writeIndex == 0) { + view.set(writeIndex++, RenderDataPointUtil.createVoidDataPoint((byte)(1))); + } for (int size = view.size(); writeIndex < size; writeIndex++) { view.set(writeIndex, 0L); } From 466ebe050359eb7b48e56d2968b8552bdf114ddb Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 29 Nov 2023 07:43:54 -0600 Subject: [PATCH 19/32] Fix seeing the near clip plane on low render distances --- .../seibel/distanthorizons/core/util/RenderUtil.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index c11b8fd2e..7078dd788 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -145,15 +145,17 @@ public class RenderUtil */ public static Mat4f createLodProjectionMatrix(Mat4f mcProjMat, float partialTicks) { + // in James' testing a near clip plane distance of 2 blocks is enough to allow the fragment + // culling to take effect instead of seeing the near clip plane. + float nearClipDist = 2f; //MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH / 4.0f; //getNearClipPlaneDistanceInBlocks(partialTicks); + int farPlaneDistanceInBlocks = RenderUtil.getFarClipPlaneDistanceInBlocks(); float farClipDist = (float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2)); // Create a copy of the current matrix, so it won't be modified. Mat4f lodProj = mcProjMat.copy(); - // Set new far and near clip plane values. - lodProj.setClipPlanes(16f, farClipDist); - + lodProj.setClipPlanes(nearClipDist, farClipDist); return lodProj; } @@ -224,7 +226,7 @@ public class RenderUtil } } - // modify the based on the player's FOV + // modify based on the player's FOV double fov = MC_RENDER.getFov(partialTicks); double aspectRatio = (double) MC_RENDER.getScreenWidth() / MC_RENDER.getScreenHeight(); From ca9dfed516ddcc336c2bb95d37dcecd2b381f9e7 Mon Sep 17 00:00:00 2001 From: Builderb0y Date: Fri, 1 Dec 2023 15:50:38 +0000 Subject: [PATCH 20/32] use higher Y level instead of higher opacity when forcing the lowest segment to merge. --- .../core/util/RenderDataPointReducingList.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java index 25243147c..c19f6f149 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -559,15 +559,9 @@ public class RenderDataPointReducingList { int lowest = this.getLowest(); int higher = this.getHigher(lowest); if (higher != NULL) { - if (this.getAlpha(higher) >= this.getAlpha(lowest)) { - this.setMinY(higher, this.getMinY(lowest)); - this.remove(lowest); - } - else { - this.setMaxY(lowest, this.getMaxY(higher)); - this.resortSize(lowest); - this.remove(higher); - } + this.setMinY(higher, this.getMinY(lowest)); + this.resortSize(higher); + this.remove(lowest); if (ASSERTS) this.checkLinks(); return false; //go back to step 1. } From 22e91d62b696150f202af98a23e42e6db39cbea5 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 2 Dec 2023 09:15:52 -0600 Subject: [PATCH 21/32] Fix near clip plane on Lod only mode --- .../java/com/seibel/distanthorizons/core/util/RenderUtil.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index 7078dd788..ac9ff4b65 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -148,6 +148,10 @@ public class RenderUtil // in James' testing a near clip plane distance of 2 blocks is enough to allow the fragment // culling to take effect instead of seeing the near clip plane. float nearClipDist = 2f; //MC_RENDER.getRenderDistance() * LodUtil.CHUNK_WIDTH / 4.0f; //getNearClipPlaneDistanceInBlocks(partialTicks); + if (Config.Client.Advanced.Debugging.lodOnlyMode.get()) + { + nearClipDist = 0.1f; + } int farPlaneDistanceInBlocks = RenderUtil.getFarClipPlaneDistanceInBlocks(); float farClipDist = (float) ((farPlaneDistanceInBlocks + LodUtil.REGION_WIDTH) * Math.sqrt(2)); From 39e477f8cac4d743803b9069ed97c9935c7847d1 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 2 Dec 2023 12:03:36 -0600 Subject: [PATCH 22/32] Fix Powdered Snow not rendering as a full block --- .../transformers/FullDataToRenderDataTransformer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index f3d34615a..8f077881b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -280,7 +280,7 @@ public class FullDataToRenderDataTransformer // solid block check - if (avoidSolidBlocks && !block.isSolid() && !block.isLiquid()) + if (avoidSolidBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != IBlockStateWrapper.FULLY_OPAQUE) { if (colorBelowWithAvoidedBlocks) { From b5853d20eed474490b8fdbe61216c17c34c92777 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 2 Dec 2023 12:24:12 -0600 Subject: [PATCH 23/32] Add a limit to the number of queued update chunks --- .../distanthorizons/core/api/internal/SharedApi.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 2fb441c5d..ea0800a8f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -52,6 +52,8 @@ public class SharedApi private static final Logger LOGGER = DhLoggerBuilder.getLogger(); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final Set UPDATING_CHUNK_SET = ConcurrentHashMap.newKeySet(); + private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 100; + private static AbstractDhWorld currentWorld; @@ -221,6 +223,13 @@ public class SharedApi } private static void bakeChunkLightingAndSendToLevelAsync(IChunkWrapper chunkWrapper, @Nullable ArrayList neighbourChunkList, IDhLevel dhLevel) { + if (UPDATING_CHUNK_SET.size() >= MAX_UPDATING_CHUNK_COUNT_PER_THREAD * Config.Client.Advanced.MultiThreading.numberOfLodBuilderThreads.get()) + { + // The maximum number of chunks are already queued, don't add more. + // This is done to prevent overloading the system if the user flys extremely fast and queues too many chunks + return; + } + // prevent duplicate update requests if (UPDATING_CHUNK_SET.contains(chunkWrapper.getChunkPos())) { From 5e413cfe93666df88156fdb0069dfc9f342292fb Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 2 Dec 2023 12:37:27 -0600 Subject: [PATCH 24/32] Add a config to enable/disable using MC's lighting engine --- .../core/api/internal/SharedApi.java | 3 ++- .../seibel/distanthorizons/core/config/Config.java | 13 +++++++++++++ .../assets/distanthorizons/lang/en_us.json | 4 ++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index ea0800a8f..ce4ca259f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -254,7 +254,8 @@ public class SharedApi { // Save or populate the chunk wrapper's lighting // this is done so we don't have to worry about MC unloading the lighting data for this chunk - if (chunkWrapper.isLightCorrect()) + boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get(); + if (!onlyUseDhLighting && chunkWrapper.isLightCorrect()) { try { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index ef344012d..452e4efd5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -764,6 +764,19 @@ public class Config + "") .build(); + public static ConfigEntry onlyUseDhLightingEngine = new ConfigEntry.Builder() + .set(false) + .comment("" + + "If false LODs will be lit by Minecraft's lighting engine when possible \n" + + "and fall back to the DH lighting engine only when necessary. \n" + + "\n" + + "If true LODs will only be lit using Distant Horizons' lighting engine. \n" + + "\n" + + "Generally it is best to leave this disabled and should only be enabled \n" + + "if there are lighting issues or for debugging. \n" + + "") + .build(); + } public static class Multiplayer diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index 49722f353..2248d0579 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -335,6 +335,10 @@ "Minimum Time Between Chunk Updates In Seconds", "distanthorizons.config.client.advanced.lodBuilding.minTimeBetweenChunkUpdatesInSeconds.@tooltip": "Determines how long must pass between LOD chunk updates before another. \nupdate can occur\n\nIncreasing this value will reduce CPU load but may may cause \nLODs to become outdated more frequently or for longer.", + "distanthorizons.config.client.advanced.lodBuilding.onlyUseDhLightingEngine": + "Only Use DH Lighting Engine", + "distanthorizons.config.client.advanced.lodBuilding.onlyUseDhLightingEngine.@tooltip": + "If false LODs will be lit by Minecraft's lighting engine when possible \nand fall back to the DH lighting engine only when necessary. \n\nIf true LODs will only be lit using Distant Horizons' lighting engine. \n\nGenerally it is best to leave this disabled and should only be enabled \nif there are lighting issues or for debugging.", "distanthorizons.config.client.advanced.multiplayer": From f32e25f52fac692a85ff36e423f7617ae8940370 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 3 Dec 2023 17:18:53 -0600 Subject: [PATCH 25/32] Remove "-dev" from the version number --- .../main/java/com/seibel/distanthorizons/coreapi/ModInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index 2dff42bd3..ed1f966c3 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -34,7 +34,7 @@ public final class ModInfo public static final String NAME = "DistantHorizons"; /** Human-readable version of NAME */ public static final String READABLE_NAME = "Distant Horizons"; - public static final String VERSION = "2.0.1-a-dev"; + public static final String VERSION = "2.0.1-a"; /** Returns true if the current build is an unstable developer build, false otherwise. */ public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); From b999a321c7f1fbc0144a7c5bcb76ad5e1bdcd678 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 3 Dec 2023 19:13:06 -0600 Subject: [PATCH 26/32] up the version number 2.0.1-a -> 2.0.2-a --- .../main/java/com/seibel/distanthorizons/coreapi/ModInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index ed1f966c3..d582a9cab 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -34,7 +34,7 @@ public final class ModInfo public static final String NAME = "DistantHorizons"; /** Human-readable version of NAME */ public static final String READABLE_NAME = "Distant Horizons"; - public static final String VERSION = "2.0.1-a"; + public static final String VERSION = "2.0.2-a-dev"; /** Returns true if the current build is an unstable developer build, false otherwise. */ public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev"); From 614e1e027f0f249accda19b3dd35eeb1f184a0e2 Mon Sep 17 00:00:00 2001 From: coolGi Date: Mon, 4 Dec 2023 23:34:37 +1030 Subject: [PATCH 27/32] Added version number to updated jar --- .../seibel/distanthorizons/core/jar/updater/SelfUpdater.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java b/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java index 641a85455..380aa58b5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/jar/updater/SelfUpdater.java @@ -117,7 +117,7 @@ public class SelfUpdater LOGGER.info("New version (" + ModrinthGetter.getLatestNameForVersion(mcVersion) + ") of " + ModInfo.READABLE_NAME + " is available"); - newFileLocation = JarUtils.jarFile.getParentFile().toPath().resolve("update").resolve(ModInfo.NAME + "-" + ModrinthGetter.getLatestNameForVersion(mcVersion) + ".jar").toFile(); + newFileLocation = JarUtils.jarFile.getParentFile().toPath().resolve("update").resolve(ModInfo.NAME + "-" + ModrinthGetter.getLatestNameForVersion(mcVersion) + "-" + mcVersion + ".jar").toFile(); if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get()) { // Auto-update mod From 9460fc9b04ccb3e134a2931cdaa21d83e0b14aec Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 4 Dec 2023 07:46:18 -0600 Subject: [PATCH 28/32] reformat --- .../util/RenderDataPointReducingList.java | 1120 +++++++++-------- 1 file changed, 615 insertions(+), 505 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java index c19f6f149..df48d7d05 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -1,3 +1,22 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package com.seibel.distanthorizons.core.util; import com.google.common.annotations.VisibleForTesting; @@ -8,89 +27,98 @@ import it.unimi.dsi.fastutil.longs.LongArrays; import it.unimi.dsi.fastutil.shorts.ShortArrays; /** -a list of data points whose sole purpose is to {@link #reduce(int)} them. -each data point, henceforth referred to as a "node", is represented by 2 packed longs. -the "data" long contains the data point itself, as encoded by -{@link RenderDataPointUtil#createDataPoint(int, int, int, int, int, int, int, int, int)}. -the "links" long contains 4 packed 16-bit integers, which "point" to other nodes -in the sense that the index represented by the integer is another node in this list. -the 4 links are: bigger, smaller, higher, and lower. -all nodes are stored in 2 parallel long[]'s, namely {@link #data} and {@link #links}. - -all nodes are internally sorted in 2 different orders at the same time: -lowest-to-highest, and smallest-to-biggest. -both of these orders are important for reduction logic. -traversal in both orders is equally possible and important. - -@author Builderb0y -*/ -public class RenderDataPointReducingList { + * A list of data points whose sole purpose is to {@link #reduce(int)} them. + * Each data point, henceforth referred to as a "node", is represented by 2 packed longs. + * The "data" long contains the data point itself, as encoded by + * {@link RenderDataPointUtil#createDataPoint(int, int, int, int, int, int, int, int, int)}. + * The "links" long contains 4 packed 16-bit integers, which "point" to other nodes + * In the sense that the index represented by the integer is another node in this list. + * The 4 links are: bigger, smaller, higher, and lower. + * All nodes are stored in 2 parallel long[]'s, namely {@link #data} and {@link #links}.

+ * + * All nodes are internally sorted in 2 different orders at the same time: + * lowest-to-highest, and smallest-to-biggest. + * Both of these orders are important for reduction logic. + * Traversal in both orders is equally possible and important. + * + * @author Builderb0y + */ +public class RenderDataPointReducingList +{ /** - setting this to true will cause the list to sanity-check - its own links automatically every time it modifies itself. - this is mostly just useful for debugging. - this should be set to false in production, - because these sanity checks are slow and happen often. - */ + * Setting this to true will cause the list to sanity-check + * its own links automatically every time it modifies itself. + * This is mostly just useful for debugging. + * This should be set to false in production, + * because these sanity checks are slow and happen often. + */ private static final boolean ASSERTS = false; /** - number of special cases to use for step 1 of {@link #reduce(int)}. - 2 works well for big globe worlds. - 0 is probably better for vanilla, but vanilla has vastly fewer segments/nodes - than big globe does, so the difference in efficiency matters a lot less. - */ + * Number of special cases to use for step 1 of {@link #reduce(int)}. + * 2 works well for big globe worlds. + * 0 is probably better for vanilla, but vanilla has vastly fewer segments/nodes + * than big globe does, so the difference in efficiency matters a lot less. + */ private static final int SPECIAL_CASES = 2; - public static final int - /** the bit offset of {@link #links} where the lower link is stored. */ - LOWER_SHIFT = 0, - /** the bit offset of {@link #links} where the higher link is stored. */ - HIGHER_SHIFT = 16, - /** the bit offset of {@link #links} where the smaller link is stored. */ - SMALLER_SHIFT = 32, - /** the bit offset of {@link #links} where the bigger link is stored. */ - BIGGER_SHIFT = 48, - /** - a bit mask for extracting links from elements of {@link #links}. - all links are 16 bits in length, so this constant has the lower 16 bits set, - and all remaining bits cleared. - */ - LINK_MASK = 0xFFFF, - /** a constant to indicate that a link is non-existent. */ - NULL = LINK_MASK; - public static final long - /** the default element of {@link #data} to indicate that there is no data. */ - DEFAUlT_DATA = 0L, - /** the default element of {@link #links} to indicate that a node is not linked to any other nodes. */ - DEFAULT_LINKS = -1L; - + + /** the bit offset of {@link #links} where the lower link is stored. */ + public static final int LOWER_SHIFT = 0; + /** the bit offset of {@link #links} where the higher link is stored. */ + public static final int HIGHER_SHIFT = 16; + /** the bit offset of {@link #links} where the smaller link is stored. */ + public static final int SMALLER_SHIFT = 32; + /** the bit offset of {@link #links} where the bigger link is stored. */ + public static final int BIGGER_SHIFT = 48; /** - indexes of the nodes at the ends of this list. - access these fields through the getters, - not by the backing fields. the getters will - perform automatic short <-> int conversions. - - @implNote these fields behave as if they were unsigned, - and the getters will behave accordingly. - not that DH supports a wide enough Y range - to overflow these fields, but still. - */ + * a bit mask for extracting links from elements of {@link #links}. + * all links are 16 bits in length, so this constant has the lower 16 bits set, + * and all remaining bits cleared. + */ + public static final int LINK_MASK = 0xFFFF; + /** a constant to indicate that a link is non-existent. */ + public static final int NULL = LINK_MASK; + + /** the default element of {@link #data} to indicate that there is no data. */ + public static final long DEFAUlT_DATA = 0L; + /** the default element of {@link #links} to indicate that a node is not linked to any other nodes. */ + public static final long DEFAULT_LINKS = -1L; + + /** + * indexes of the nodes at the ends of this list. + * access these fields through the getters, + * not by the backing fields. the getters will + * perform automatic short <-> int conversions. + * + * @implNote these fields behave as if they were unsigned, + * and the getters will behave accordingly. + * not that DH supports a wide enough Y range + * to overflow these fields, but still. + */ private short lowest, highest, smallest, biggest; private short sizeWithAir, sizeWithoutAir; private final long[] links, data; /** - a temporary array to be used for sorting nodes. - the array is first populated such that every index - up to our current size represents a valid index. - then this array is sorted. - finally, the nodes are re-linked according - to the order of elements in this array. - */ + * a temporary array to be used for sorting nodes. + * the array is first populated such that every index + * up to our current size represents a valid index. + * then this array is sorted. + * finally, the nodes are re-linked according + * to the order of elements in this array. + */ private final short[] sortingArray; - - public RenderDataPointReducingList(IColumnDataView view) { + + + + //=============// + // constructor // + //=============// + + public RenderDataPointReducingList(IColumnDataView view) + { int size = view.size(); - if (size == 0) { + if (size == 0) + { this.setLowest(NULL); this.setHighest(NULL); this.setSmallest(NULL); @@ -99,39 +127,45 @@ public class RenderDataPointReducingList { this.data = LongArrays.EMPTY_ARRAY; this.sortingArray = ShortArrays.EMPTY_ARRAY; if (ASSERTS) this.checkLinks(); + return; } - //allocate an array big enough to hold 2 * size - 1 nodes. - //this is the number of nodes we would have if none - //of the nodes in the provided view are touching, - //and we need to add air nodes between all of them. - //we will use this array for sorting the nodes, - //first by lowest-to-highest, then by smallest-to-biggest. + + // Allocate an array big enough to hold (2 * size - 1) nodes. + // This is the number of nodes we would have if none + // of the nodes in the provided view are touching, + // and we need to add air nodes between all of them. + // We will use this array for sorting the nodes, + // first by lowest-to-highest, then by smallest-to-biggest. int arrayCapacity = (size << 1) - 1; this.sortingArray = new short[arrayCapacity]; this.links = new long[arrayCapacity]; java.util.Arrays.fill(this.links, DEFAULT_LINKS); this.data = new long[arrayCapacity]; int sizeWithoutAir = 0; - for (int index = 0; index < size; index++) { + for (int index = 0; index < size; index++) + { long packedData = view.get(index); - //first "pass" (if you can call it that) skips nodes with 0 height, and nodes that aren't visible. - //air nodes will be inserted *after* the nodes have been sorted by Y level. - if (isDataVisible(packedData) && RenderDataPointUtil.getYMin(packedData) < RenderDataPointUtil.getYMax(packedData)) { + // first "pass" (if you can call it that) skips nodes with 0 height, and nodes that aren't visible. + // air nodes will be inserted *after* the nodes have been sorted by Y level. + if (isDataVisible(packedData) && RenderDataPointUtil.getYMin(packedData) < RenderDataPointUtil.getYMax(packedData)) + { this.setData(sizeWithoutAir, packedData); this.setSortingIndex(sizeWithoutAir, sizeWithoutAir); sizeWithoutAir++; } } - //check if all segments to merge are air or otherwise invisible (barriers). - //if they are, then this list can stay empty. - if (sizeWithoutAir == 0) { + // Check if all segments to merge are air or otherwise invisible (barriers). + // If they are, then this list can stay empty. + if (sizeWithoutAir == 0) + { this.setLowest(NULL); this.setHighest(NULL); this.setSmallest(NULL); this.setBiggest(NULL); if (ASSERTS) this.checkLinks(); + return; } @@ -139,18 +173,24 @@ public class RenderDataPointReducingList { this.sortByPosition(sizeWithoutAir); //next pass: link the nodes together, and insert air nodes as necessary. int sizeWithAir = sizeWithoutAir; - for (int sortingIndex = 1; sortingIndex < sizeWithoutAir; sortingIndex++) { + for (int sortingIndex = 1; sortingIndex < sizeWithoutAir; sortingIndex++) + { int lowerIndex = this.getSortingIndex(sortingIndex - 1); int higherIndex = this.getSortingIndex(sortingIndex); long lowerData = this.getData(lowerIndex); long higherData = this.getData(higherIndex); int lowerMaxY = RenderDataPointUtil.getYMax(lowerData); int higherMinY = RenderDataPointUtil.getYMin(higherData); - if (lowerMaxY == higherMinY) { //the two nodes touch. + + if (lowerMaxY == higherMinY) + { + //the two nodes touch. this.setHigher(lowerIndex, higherIndex); this.setLower(higherIndex, lowerIndex); } - else if (lowerMaxY < higherMinY) { //the two nodes do not touch. + else if (lowerMaxY < higherMinY) + { + //the two nodes do not touch. this.setData( sizeWithAir, RenderDataPointUtil.createDataPoint( @@ -165,6 +205,7 @@ public class RenderDataPointReducingList { RenderDataPointUtil.getGenerationMode(higherData) ) ); + this.setSortingIndex(sizeWithAir, sizeWithAir); this.setLower(higherIndex, sizeWithAir); this.setHigher(lowerIndex, sizeWithAir); @@ -172,21 +213,25 @@ public class RenderDataPointReducingList { this.setHigher(sizeWithAir, higherIndex); sizeWithAir++; } - else { //the two nodes overlap. + else + { + // the two nodes overlap. throw new IllegalArgumentException(RenderDataPointUtil.toString(lowerData) + " overlaps with " + RenderDataPointUtil.toString(higherData)); } } this.lowest = this.sortingArray[0]; this.highest = this.sortingArray[sizeWithoutAir - 1]; - //now sort by size. + // now sort by size. this.sortBySize(sizeWithAir); - for (int sortingIndex = 1; sortingIndex < sizeWithAir; sortingIndex++) { + for (int sortingIndex = 1; sortingIndex < sizeWithAir; sortingIndex++) + { int smallerIndex = this.getSortingIndex(sortingIndex - 1); int biggerIndex = this.getSortingIndex(sortingIndex); this.setBigger(smallerIndex, biggerIndex); this.setSmaller(biggerIndex, smallerIndex); } + this.smallest = this.sortingArray[0]; this.biggest = this.sortingArray[sizeWithAir - 1]; @@ -195,39 +240,120 @@ public class RenderDataPointReducingList { if (ASSERTS) this.checkLinks(); } - - //////////////////////////////// operations //////////////////////////////// - + + + + //========// + // reduce // + //========// + /** - verifies that this list is in the "correct" state, - and throws an {@link AssertFailureException} if it isn't. - */ + * merges and/or eliminates nodes until our {@link #sizeWithoutAir} + * is less than or equal to the provided target size. + * this method assumes that the list is already sorted by size. + * if it is not sorted, you should call {@link #sortBySizeAndReLink()} first. + * note also that the list is sorted in its constructor, + * so if this is a new, unmodified list, then it is safe to call this method.

+ * + * algorithm: + * 1: try to merge the smallest segment with the segment above or below it. + * this will only succeed if the adjacent node has the same alpha as it. + * 1a: if there is only one adjacent node which matches this criteria, + * we will merge with that node.

+ * + * 1b: if both adjacent nodes match this criteria, + * attempt to merge with the smaller one. + * 1b1: if both adjacent nodes are the same height, + * merge with the higher one.

+ * + * 1c: if there are no adjacent nodes which match this criteria, + * repeat step 1 with the next smallest segment instead. + * continue trying bigger and bigger segments until we either: + * * have a success, or + * * reach the end of this list. + * 2: if we reach the end of the list before having a success, try again, + * but this time, we are allowed to erase a segment entirely without merging it + * if there are equal-alpha'd segments above and below it. + * 3: if we still fail, force the lowest segment to merge with the segment above it, + * with no restrictions on alpha. + * the highest alpha of the two segments takes priority though. + * 4: repeat until our size is less than or equal to the target size. + * notes: + * changing the size of a node requires re-sorting that node, + * but it does not require re-sorting the whole list. + * additionally, because of the fact that nodes are sorted smallest to biggest, + * when a node is expanded, its new size will be + * strictly less than or equal to twice its old size. + * the significance of this is that in practice, + * nodes should not need to be moved very far to be re-sorted.

+ * + * special case: there are a lot of segments of length 1 in big globe worlds. + * these will genuinely have a long way to move on re-sort. + * so, they are handled in a separate loop.

+ * + * after step 1 is completed, step 2 can't change the + * list in a way which would give step 1 more work to do, + * so step 2 is repeated as many times as necessary, + * without jumping back to the start. + * step 3 however can change the list in a way which gives previous + * steps more work to do, so after step 3 merges something, + * we jump back to step 1 and start over. + */ + public void reduce(int target) + { + if (this.reduceStep1SpecialCases(target)) return; + + while (true) + { + if (this.reduceStep1GeneralCases(target)) return; + if (this.reduceStep2(target)) return; + if (this.reduceStep3(target)) return; + } + } + + + + //======================// + // reduction operations // + //======================// + + /** + * verifies that this list is in the "correct" state, + * and throws an {@link AssertFailureException} if it isn't. + */ @VisibleForTesting - public void checkLinks() { + public void checkLinks() + { LodUtil.assertTrue(this.getSizeWithAir() >= 0, "size with air < 0"); LodUtil.assertTrue(this.getSizeWithoutAir() >= 0, "size without air < 0"); LodUtil.assertTrue(this.getSizeWithoutAir() <= this.getSizeWithAir(), "more segments without air than with air"); - if (this.getSizeWithAir() == 0) { + + if (this.getSizeWithAir() == 0) + { LodUtil.assertTrue(this.getSmallest() == NULL, "size is 0, but we have a smallest node"); LodUtil.assertTrue(this.getBiggest() == NULL, "size is 0, but we have a biggest node"); LodUtil.assertTrue(this.getLowest() == NULL, "size is 0, but we have a lowest node"); LodUtil.assertTrue(this.getHighest() == NULL, "size is 0, but we have a highest node"); } - else { + else + { int sizeWithAir = 0, sizeWithoutAir = 0; - for (int index = this.getSmallest(); index != NULL; index = this.getBigger(index)) { + for (int index = this.getSmallest(); index != NULL; index = this.getBigger(index)) + { int smaller = this.getSmaller(index); int bigger = this.getBigger(index); LodUtil.assertTrue((smaller != NULL ? this.getBigger(smaller) : this.getSmallest()) == index, "one-way link"); LodUtil.assertTrue((bigger != NULL ? this.getSmaller(bigger) : this.getBiggest()) == index, "one-way link"); LodUtil.assertTrue(smaller == NULL || this.getSize(index) >= this.getSize(smaller), "node is not sorted by size"); sizeWithAir++; + if (this.isIndexVisible(index)) sizeWithoutAir++; } LodUtil.assertTrue(sizeWithAir == this.getSizeWithAir() && sizeWithoutAir == this.getSizeWithoutAir(), "node count does not match size"); sizeWithAir = sizeWithoutAir = 0; - for (int index = this.getLowest(); index != NULL; index = this.getHigher(index)) { + for (int index = this.getLowest(); index != NULL; index = this.getHigher(index)) + { int lower = this.getLower(index); int higher = this.getHigher(index); LodUtil.assertTrue((lower != NULL ? this.getHigher(lower) : this.getLowest()) == index, "one-way link"); @@ -235,6 +361,7 @@ public class RenderDataPointReducingList { LodUtil.assertTrue(this.getMaxY(index) > this.getMinY(index), "node has inverted Y levels"); LodUtil.assertTrue(lower == NULL || this.getMinY(index) == this.getMaxY(lower), "node does not touch its lower neighbor"); sizeWithAir++; + if (this.isIndexVisible(index)) sizeWithoutAir++; } LodUtil.assertTrue(sizeWithAir == this.getSizeWithAir() && sizeWithoutAir == this.getSizeWithoutAir(), "node count does not match size"); @@ -242,331 +369,415 @@ public class RenderDataPointReducingList { } /** removes the node at the given index from this list. */ - public void remove(int index) { - int - lower = this.getLower (index), - higher = this.getHigher (index), - smaller= this.getSmaller(index), - bigger = this.getBigger (index), - alpha = this.getAlpha (index); + public void remove(int index) + { + int lower = this.getLower (index); + int higher = this.getHigher (index); + int smaller= this.getSmaller(index); + int bigger = this.getBigger (index); + int alpha = this.getAlpha (index); + + if (lower != NULL) this.setHigher(lower, higher); else this.setLowest(higher); + if (higher != NULL) this.setLower(higher, lower); else this.setHighest(lower); + if (smaller != NULL) this.setBigger(smaller, bigger); else this.setSmallest(bigger); + if (bigger != NULL) this.setSmaller(bigger, smaller); else this.setBiggest(smaller); + this.setData(index, DEFAUlT_DATA); this.links[index] = DEFAULT_LINKS; this.sizeWithAir--; + if (isAlphaVisible(alpha)) this.sizeWithoutAir--; } - + /** - refreshes the smallest-to-biggest order of this list. - as a reminder, the list is internally sorted from smallest-to-biggest - and lowest-to-highest at the same time. part of reduction logic - can invalidate the smallest-to-biggest order, so this method re-computes it. - this method does not touch the lowest-to-highest order of the list. - - this method requires that all nodes are already sorted from - lowest-to-highest, so it is not applicable to use this method in - the constructor before the lowest-to-highest order is initialized. - */ + * refreshes the smallest-to-biggest order of this list. + * as a reminder, the list is internally sorted from smallest-to-biggest + * and lowest-to-highest at the same time. part of reduction logic + * can invalidate the smallest-to-biggest order, so this method re-computes it. + * this method does not touch the lowest-to-highest order of the list.

+ * + * this method requires that all nodes are already sorted from + * lowest-to-highest, so it is not applicable to use this method in + * the constructor before the lowest-to-highest order is initialized. + */ @VisibleForTesting - public void sortBySizeAndReLink() { - if (this.getSizeWithAir() <= 1) return; + public void sortBySizeAndReLink() + { + if (this.getSizeWithAir() <= 1) + { + return; + } + + long[] datas = this.data; int writeIndex = 0; - for (int readIndex = this.getLowest(); readIndex != NULL; readIndex = this.getHigher(readIndex)) { - if (datas[readIndex] != DEFAUlT_DATA) { + for (int readIndex = this.getLowest(); readIndex != NULL; readIndex = this.getHigher(readIndex)) + { + if (datas[readIndex] != DEFAUlT_DATA) + { this.setSortingIndex(writeIndex++, readIndex); } } + this.sortBySize(writeIndex); - for (int index = 1; index < writeIndex; index++) { + for (int index = 1; index < writeIndex; index++) + { int smaller = this.getSortingIndex(index - 1); int bigger = this.getSortingIndex(index); this.setSmaller(bigger, smaller); this.setBigger(smaller, bigger); } + this.smallest = this.sortingArray[0]; this.biggest = this.sortingArray[writeIndex - 1]; this.setSmaller(this.getSmallest(), NULL); this.setBigger(this.getBiggest(), NULL); } - + /** - sorts our {@link #sortingArray} in order of smallest-to-biggest, - but does NOT update our links accordingly. - */ + * sorts our {@link #sortingArray} in order of smallest-to-biggest, + * but does NOT update our links accordingly. + */ @VisibleForTesting - public void sortBySize(int size) { + public void sortBySize(int size) + { short[] array = this.sortingArray; it.unimi.dsi.fastutil.Arrays.quickSort( 0, size, - (int index1, int index2) -> { + // comparator + (int index1, int index2) -> + { return Integer.compare( this.getSize(this.getSortingIndex(index1)), this.getSize(this.getSortingIndex(index2)) ); }, - (int index1, int index2) -> { + // swapper + (int index1, int index2) -> + { ShortArrays.swap(array, index1, index2); } ); } - + /** - sorts our {@link #sortingArray} in order of lowest-to-highest, - but does NOT update our links accordingly. - */ + * sorts our {@link #sortingArray} in order of lowest-to-highest, + * but does NOT update our links accordingly. + */ @VisibleForTesting - public void sortByPosition(int size) { + public void sortByPosition(int size) + { short[] array = this.sortingArray; it.unimi.dsi.fastutil.Arrays.quickSort( 0, size, - (int index1, int index2) -> { + // comparator + (int index1, int index2) -> + { return Integer.compare( this.getMinY(this.getSortingIndex(index1)), this.getMinY(this.getSortingIndex(index2)) ); }, - (int index1, int index2) -> { + // swapper + (int index1, int index2) -> + { ShortArrays.swap(array, index1, index2); } ); } - + /** - moves the smaller node to the correct position in the list, - under the assumption that all other nodes are already sorted. - this method should be called when the smaller node is - merged with another node, causing it to become bigger. - - important: this method ONLY handles the case where a node - is made bigger. it does NOT handle the case where a node - is made smaller. if the node is made smaller, it will be - left in its current position, even if that position is wrong. - */ - public void resortSize(int smaller) { + * moves the smaller node to the correct position in the list, + * under the assumption that all other nodes are already sorted. + * this method should be called when the smaller node is + * merged with another node, causing it to become bigger.

+ * + * important: this method ONLY handles the case where a node + * is made bigger. it does NOT handle the case where a node + * is made smaller. if the node is made smaller, it will be + * left in its current position, even if that position is wrong. + */ + public void resortSize(int smaller) + { int bigger = this.getBigger(smaller); - - //check if the node needs to be moved at all, - //and return if it doesn't. - if (bigger == NULL || this.getSize(smaller) <= this.getSize(bigger)) return; - - //first remove smaller from before bigger. + + // check if the node needs to be moved at all, + // and return if it doesn't. + if (bigger == NULL || this.getSize(smaller) <= this.getSize(bigger)) + { + return; + } + + // first remove smaller from before bigger. int smallest = this.getSmaller(smaller); if (smallest != NULL) this.setBigger(smallest, bigger); else this.setSmallest(bigger); this.setSmaller(bigger, smallest); - - //next, find the position to re-insert the node. + + // next, find the position to re-insert the node. do bigger = this.getBigger(bigger); while (bigger != NULL && this.getSize(smaller) > this.getSize(bigger)); - - //lastly, re-insert the node where it belongs. + + // lastly, re-insert the node where it belongs. this.setSmaller(smaller, bigger != NULL ? this.getSmaller(bigger) : this.getBiggest()); this.setBigger(smaller, bigger); + if (bigger != NULL) this.setSmaller(bigger, smaller); else this.setBiggest(smaller); + smallest = this.getSmaller(smaller); if (smallest != NULL) this.setBigger(smallest, smaller); else this.setSmallest(smaller); } - + /** - shared logic for merging segments in step 1 documented in {@link #reduce(int)}. - - returns the index of the next node to be used for iteration. - - @param fastPath if true, we are in the "fast path" for removing - segments whose size is less than or equal to {@link #SPECIAL_CASES}. - this fast path functions somewhat differently from the normal path, - the important things to note for this method are: - - the fast path does not re-sort nodes when their size changes. - this leaves the list in an invalid state, and it is up to the caller to re-sort - the list via {@link #sortBySizeAndReLink()} after the fast path is done. - - at the time of writing this, the fast path iterates in reverse order. - as such, when fastPath is set to true, this method will return - current's smaller neighbor, when fastPath is set to false, - this method will return current's bigger neighbor instead. - */ - private int tryMergeStep1(int current, boolean fastPath) { - int - result = fastPath ? this.getSmaller(current) : this.getBigger(current), - higher = this.getHigher(current), - lower = this.getLower(current), - toExtendDownwards, - toRemove; - if (higher != NULL && this.getAlpha(higher) == this.getAlpha(current)) { - if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) { - if (this.getSize(higher) <= this.getSize(lower)) { + * shared logic for merging segments in step 1 documented in {@link #reduce(int)}. + * + * returns the index of the next node to be used for iteration. + * + * @param fastPath if true, we are in the "fast path" for removing + * segments whose size is less than or equal to {@link #SPECIAL_CASES}. + * this fast path functions somewhat differently from the normal path, + * the important things to note for this method are: + * + * the fast path does not re-sort nodes when their size changes. + * this leaves the list in an invalid state, and it is up to the caller to re-sort + * the list via {@link #sortBySizeAndReLink()} after the fast path is done. + * + * at the time of writing this, the fast path iterates in reverse order. + * as such, when fastPath is set to true, this method will return + * current's smaller neighbor, when fastPath is set to false, + * this method will return current's bigger neighbor instead. + */ + private int tryMergeStep1(int current, boolean fastPath) + { + int result = fastPath ? this.getSmaller(current) : this.getBigger(current); + int higher = this.getHigher(current); + int lower = this.getLower(current); + int toExtendDownwards; + int toRemove; + + + if (higher != NULL && this.getAlpha(higher) == this.getAlpha(current)) + { + if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) + { + if (this.getSize(higher) <= this.getSize(lower)) + { toExtendDownwards = higher; toRemove = current; } - else { + else + { toExtendDownwards = current; toRemove = lower; } } - else { + else + { toExtendDownwards = higher; toRemove = current; } } - else { - if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) { + else + { + if (lower != NULL && this.getAlpha(lower) == this.getAlpha(current)) + { toExtendDownwards = current; toRemove = lower; } - else { + else + { return result; } } - //if we're about to remove the next node for iteration, - //then we need to continue iterating at the node after that. - //result will only be returned if fastPath is true, - //so the node after that is always the smaller one. that's why I don't need to do - //if (result == toRemove) result = fastPath ? this.getSmaller(result) : this.getBigger(result); + + // if we're about to remove the next node for iteration, + // then we need to continue iterating at the node after that. + // result will only be returned if fastPath is true, + // so the node after that is always the smaller one. that's why I don't need to do + // if (result == toRemove) result = fastPath ? this.getSmaller(result) : this.getBigger(result); if (result == toRemove) result = this.getSmaller(result); + this.setMinY(toExtendDownwards, this.getMinY(toRemove)); if (!fastPath) this.resortSize(toExtendDownwards); + this.remove(toRemove); - //if we're NOT on the fast path, and we reach this line, - //then we have just modified the list in a way which may - //invalidate assumptions made by the step 1 loop. - //so, return smallest to signal that the loop should start over. - //starting over is not usually a big deal, - //because small nodes are usually merged quite quickly. - //in my testing, I didn't see the step 1 loop run more - //than twice as many times as the starting list size. + + // if we're NOT on the fast path, and we reach this line, + // then we have just modified the list in a way which may + // invalidate assumptions made by the step 1 loop. + // so, return smallest to signal that the loop should start over. + // starting over is not usually a big deal, + // because small nodes are usually merged quite quickly. + // in my testing, I didn't see the step 1 loop run more + // than twice as many times as the starting list size. return fastPath ? result : this.getSmallest(); } - + /** - returns the largest node whose height is strictly less than the provided size, - or null if all contained nodes are greater than or equal to the provided size. - - special cases: - if the list is empty, then null is returned, - because the loop will not run and biggest will be null. - - if all nodes are less tall than size, then the largest node is returned, - because the loop will run for all nodes, but will not return any of them, - so the fallback path of returning the biggest node is used. - - if all nodes are at least as tall as size, then null is returned, - because the loop will immediately return the - smallest node's smaller neighbor, which is null. - */ - private int lowerNode(int size) { - for (int node = this.getSmallest(); node != NULL; node = this.getBigger(node)) { - if (this.getSize(node) >= size) return this.getSmaller(node); + * returns the largest node whose height is strictly less than the provided size, + * or null if all contained nodes are greater than or equal to the provided size. + * + * special cases: + * if the list is empty, then null is returned, + * because the loop will not run and biggest will be null. + * + * if all nodes are less tall than size, then the largest node is returned, + * because the loop will run for all nodes, but will not return any of them, + * so the fallback path of returning the biggest node is used. + * + * if all nodes are at least as tall as size, then null is returned, + * because the loop will immediately return the + * smallest node's smaller neighbor, which is null. + */ + private int lowerNode(int size) + { + for (int node = this.getSmallest(); node != NULL; node = this.getBigger(node)) + { + if (this.getSize(node) >= size) + { + return this.getSmaller(node); + } } return this.getBiggest(); } - + /** - handles special cases for step 1 of {@link #reduce(int)}. - in other words, handles all the nodes whose size - is less than or equal to {@link #SPECIAL_CASES}. - - returns true if this step single-handedly brought - the list's size down to less than or equal to target, - or false if more steps need to be performed. - */ - private boolean reduceStep1SpecialCases(int target) { - for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) { - for (int current = this.lowerNode(specialCase + 1); current != NULL;) { - if (this.getSizeWithoutAir() <= target) { + * handles special cases for step 1 of {@link #reduce(int)}. + * in other words, handles all the nodes whose size + * is less than or equal to {@link #SPECIAL_CASES}.

+ * + * returns true if this step single-handedly brought + * the list's size down to less than or equal to target, + * or false if more steps need to be performed. + */ + private boolean reduceStep1SpecialCases(int target) + { + for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) + { + for (int current = this.lowerNode(specialCase + 1); current != NULL; ) + { + if (this.getSizeWithoutAir() <= target) + { this.sortBySizeAndReLink(); if (ASSERTS) this.checkLinks(); + return true; } current = this.tryMergeStep1(current, true); } this.sortBySizeAndReLink(); + if (ASSERTS) this.checkLinks(); } + return false; } - + /** - handles the general case for step 1 of {@link #reduce(int)}. - in other words, handles all the nodes whose size - is strictly greater than {@link #SPECIAL_CASES}, - and all the nodes which are smaller, but failed - to be merged in {@link #reduceStep1SpecialCases(int)} - - returns true if this step single-handedly brought - the list's size down to less than or equal to target, - or false if more steps need to be performed. - */ - private boolean reduceStep1GeneralCases(int target) { - for (int current = this.getSmallest(); current != NULL;) { - if (this.getSizeWithoutAir() <= target) return true; + * handles the general case for step 1 of {@link #reduce(int)}. + * in other words, handles all the nodes whose size + * is strictly greater than {@link #SPECIAL_CASES}, + * and all the nodes which are smaller, but failed + * to be merged in {@link #reduceStep1SpecialCases(int)} + * + * returns true if this step single-handedly brought + * the list's size down to less than or equal to target, + * or false if more steps need to be performed. + */ + private boolean reduceStep1GeneralCases(int target) + { + for (int current = this.getSmallest(); current != NULL;) + { + if (this.getSizeWithoutAir() <= target) + { + return true; + } + current = this.tryMergeStep1(current, false); if (ASSERTS) this.checkLinks(); } return false; } - + /** - handles step 2 of {@link #reduce(int)}, where nodes are allowed to be erased. - - returns true if this step single-handedly brought - the list's size down to less than or equal to target, - or false if more steps need to be performed. - */ - private boolean reduceStep2(int target) { - for (int center = this.getSmallest(); center != NULL;) { - if (this.getSizeWithoutAir() <= target) return true; - int lower = this.getLower (center); + * handles step 2 of {@link #reduce(int)}, where nodes are allowed to be erased.

+ * + * returns true if this step single-handedly brought + * the list's size down to less than or equal to target, + * or false if more steps need to be performed. + */ + private boolean reduceStep2(int target) + { + for (int center = this.getSmallest(); center != NULL; ) + { + if (this.getSizeWithoutAir() <= target) + { + return true; + } + + int lower = this.getLower(center); int higher = this.getHigher(center); - if (lower != NULL && higher != NULL && this.getAlpha(lower) == this.getAlpha(higher)) { + if (lower != NULL && higher != NULL && this.getAlpha(lower) == this.getAlpha(higher)) + { this.setMinY(higher, this.getMinY(lower)); this.resortSize(higher); this.remove(lower); this.remove(center); if (ASSERTS) this.checkLinks(); + center = this.getSmallest(); } - else { + else + { center = this.getBigger(center); } } return false; } - + /** - handles step 3 of {@link #reduce(int)}, where nodes - are forced to merge in order to fit the desired target, - even if they normally shouldn't merge because it would look bad. - - returns true if this step brought the list's - size down to less than or equal to target, - or false if we need to go back to step 1. - */ - private boolean reduceStep3(int target) { - if (this.getSizeWithoutAir() <= target) return true; + * handles step 3 of {@link #reduce(int)}, where nodes + * are forced to merge in order to fit the desired target, + * even if they normally shouldn't merge because it would look bad.

+ * + * returns true if this step brought the list's + * size down to less than or equal to target, + * or false if we need to go back to step 1. + */ + private boolean reduceStep3(int target) + { + if (this.getSizeWithoutAir() <= target) + { + return true; + } + + int lowest = this.getLowest(); int higher = this.getHigher(lowest); - if (higher != NULL) { + if (higher != NULL) + { this.setMinY(higher, this.getMinY(lowest)); this.resortSize(higher); this.remove(lowest); if (ASSERTS) this.checkLinks(); - return false; //go back to step 1. + + return false; // go back to step 1. } - else { - //if we reach this line, then target is 0 or negative. + else + { + // if we reach this line, then target is 0 or negative. this.setLowest(NULL); this.setHighest(NULL); this.setSmallest(NULL); @@ -576,284 +787,183 @@ public class RenderDataPointReducingList { return true; } } - - /** - merges and/or eliminates nodes until our {@link #sizeWithoutAir} - is less than or equal to the provided target size. - this method assumes that the list is already sorted by size. - if it is not sorted, you should call {@link #sortBySizeAndReLink()} first. - note also that the list is sorted in its constructor, - so if this is a new, unmodified list, then it is safe to call this method. - - algorithm: - 1: try to merge the smallest segment with the segment above or below it. - this will only succeed if the adjacent node has the same alpha as it. - 1a: if there is only one adjacent node which matches this criteria, - we will merge with that node. - - 1b: if both adjacent nodes match this criteria, - attempt to merge with the smaller one. - 1b1: if both adjacent nodes are the same height, - merge with the higher one. - - 1c: if there are no adjacent nodes which match this criteria, - repeat step 1 with the next smallest segment instead. - continue trying bigger and bigger segments until we either: - * have a success, or - * reach the end of this list. - 2: if we reach the end of the list before having a success, try again, - but this time, we are allowed to erase a segment entirely without merging it - if there are equal-alpha'd segments above and below it. - 3: if we still fail, force the lowest segment to merge with the segment above it, - with no restrictions on alpha. - the highest alpha of the two segments takes priority though. - 4: repeat until our size is less than or equal to the target size. - notes: - changing the size of a node requires re-sorting that node, - but it does not require re-sorting the whole list. - additionally, because of the fact that nodes are sorted smallest to biggest, - when a node is expanded, its new size will be - strictly less than or equal to twice its old size. - the significance of this is that in practice, - nodes should not need to be moved very far to be re-sorted. - - special case: there are a lot of segments of length 1 in big globe worlds. - these will genuinely have a long way to move on re-sort. - so, they are handled in a separate loop. - - after step 1 is completed, step 2 can't change the - list in a way which would give step 1 more work to do, - so step 2 is repeated as many times as necessary, - without jumping back to the start. - step 3 however can change the list in a way which gives previous - steps more work to do, so after step 3 merges something, - we jump back to step 1 and start over. - */ - public void reduce(int target) { - if (this.reduceStep1SpecialCases(target)) return; - - while (true) { - if (this.reduceStep1GeneralCases(target)) return; - if (this.reduceStep2(target)) return; - if (this.reduceStep3(target)) return; - } - } - + /** transfers the contents of this list to the provided view, in order of highest to lowest. */ - public void copyTo(ColumnArrayView view) { - //reminder: DH explodes horribly when I copy the nodes - //from lowest to highest instead of highest to lowest. + public void copyTo(ColumnArrayView view) + { + // reminder: DH explodes horribly when I copy the nodes + // from lowest to highest instead of highest to lowest. int writeIndex = 0; - for (int node = this.getHighest(); node != NULL; node = this.getLower(node)) { - if (this.isIndexVisible(node)) { + for (int node = this.getHighest(); node != NULL; node = this.getLower(node)) + { + if (this.isIndexVisible(node)) + { view.set(writeIndex++, this.getData(node)); } } - //this list could be empty if all the segments for merging are invisible, - //but we must ensure that the view is non-empty. - //so, if we didn't set any data points, add a void data point. - if (writeIndex == 0) { - view.set(writeIndex++, RenderDataPointUtil.createVoidDataPoint((byte)(1))); + + // this list could be empty if all the segments for merging are invisible, + // but we must ensure that the view is non-empty. + // so, if we didn't set any data points, add a void data point. + if (writeIndex == 0) + { + view.set(writeIndex++, RenderDataPointUtil.createVoidDataPoint((byte) (1))); } - for (int size = view.size(); writeIndex < size; writeIndex++) { + + for (int size = view.size(); writeIndex < size; writeIndex++) + { view.set(writeIndex, 0L); } } - - //////////////////////////////// getters //////////////////////////////// - - public int getSmallest() { - return Short.toUnsignedInt(this.smallest); - } - - public int getBiggest() { - return Short.toUnsignedInt(this.biggest); - } - - public int getLowest() { - return Short.toUnsignedInt(this.lowest); - } - - public int getHighest() { - return Short.toUnsignedInt(this.highest); - } - - public int getSizeWithAir() { - return Short.toUnsignedInt(this.sizeWithAir); - } - - public int getSizeWithoutAir() { - return Short.toUnsignedInt(this.sizeWithoutAir); - } - - public int getSortingIndex(int index) { - return Short.toUnsignedInt(this.sortingArray[index]); - } - - public int getLower(int index) { - return ((int)(this.links[index] >>> LOWER_SHIFT)) & LINK_MASK; - } - - public int getHigher(int index) { - return ((int)(this.links[index] >>> HIGHER_SHIFT)) & LINK_MASK; - } - - public int getSmaller(int index) { - return ((int)(this.links[index] >>> SMALLER_SHIFT)) & LINK_MASK; - } - - public int getBigger(int index) { - return ((int)(this.links[index] >>> BIGGER_SHIFT)) & LINK_MASK; - } - - public long getData(int index) { - return this.data[index]; - } - - public int getMinY(int index) { - return RenderDataPointUtil.getYMin(this.getData(index)); - } - - public int getMaxY(int index) { - return RenderDataPointUtil.getYMax(this.getData(index)); - } - - public int getSize(int index) { + + + + //=========// + // getters // + //=========// + + public int getSmallest() { return Short.toUnsignedInt(this.smallest); } + public int getBiggest() { return Short.toUnsignedInt(this.biggest); } + + public int getLowest() { return Short.toUnsignedInt(this.lowest); } + public int getHighest() { return Short.toUnsignedInt(this.highest); } + + public int getSizeWithAir() { return Short.toUnsignedInt(this.sizeWithAir); } + public int getSizeWithoutAir() { return Short.toUnsignedInt(this.sizeWithoutAir); } + + public int getSortingIndex(int index) { return Short.toUnsignedInt(this.sortingArray[index]); } + + public int getLower(int index) { return ((int) (this.links[index] >>> LOWER_SHIFT)) & LINK_MASK; } + public int getHigher(int index) { return ((int) (this.links[index] >>> HIGHER_SHIFT)) & LINK_MASK; } + + public int getSmaller(int index) { return ((int) (this.links[index] >>> SMALLER_SHIFT)) & LINK_MASK; } + public int getBigger(int index) { return ((int) (this.links[index] >>> BIGGER_SHIFT)) & LINK_MASK; } + + public long getData(int index) { return this.data[index]; } + + public int getMinY(int index) { return RenderDataPointUtil.getYMin(this.getData(index)); } + public int getMaxY(int index) { return RenderDataPointUtil.getYMax(this.getData(index)); } + + public int getSize(int index) + { long data = this.getData(index); return RenderDataPointUtil.getYMax(data) - RenderDataPointUtil.getYMin(data); } + + public int getRed(int index) { return RenderDataPointUtil.getRed(this.getData(index)); } + public int getGreen(int index) { return RenderDataPointUtil.getGreen(this.getData(index)); } + public int getBlue(int index) { return RenderDataPointUtil.getBlue(this.getData(index)); } + public int getAlpha(int index) { return RenderDataPointUtil.getAlpha(this.getData(index)); } + + public int getBlockLight(int index) { return RenderDataPointUtil.getLightBlock(this.getData(index)); } + public int getSkyLight(int index) { return RenderDataPointUtil.getLightSky(this.getData(index)); } + + + + //=========// + // setters // + //=========// + + public void setSmallest(int smallest) { this.smallest = (short)(smallest); } + public void setBiggest(int biggest) { this.biggest = (short)(biggest); } + + public void setLowest(int lowest) { this.lowest = (short)(lowest); } + public void setHighest(int highest) { this.highest = (short)(highest); } + + public void setSizeWithAir(int sizeWithAir) { this.sizeWithAir = (short)(sizeWithAir); } + public void setSizeWithoutAir(int sizeWithoutAir) { this.sizeWithoutAir = (short)(sizeWithoutAir); } - public int getRed(int index) { - return RenderDataPointUtil.getRed(this.getData(index)); - } + public void setSortingIndex(int index, int to) { this.sortingArray[index] = (short)(to); } - public int getGreen(int index) { - return RenderDataPointUtil.getGreen(this.getData(index)); - } - - public int getBlue(int index) { - return RenderDataPointUtil.getBlue(this.getData(index)); - } - - public int getAlpha(int index) { - return RenderDataPointUtil.getAlpha(this.getData(index)); - } - - public int getBlockLight(int index) { - return RenderDataPointUtil.getLightBlock(this.getData(index)); - } - - public int getSkyLight(int index) { - return RenderDataPointUtil.getLightSky(this.getData(index)); - } - - //////////////////////////////// setters //////////////////////////////// - - public void setSmallest(int smallest) { - this.smallest = (short)(smallest); - } - - public void setBiggest(int biggest) { - this.biggest = (short)(biggest); - } - - public void setLowest(int lowest) { - this.lowest = (short)(lowest); - } - - public void setHighest(int highest) { - this.highest = (short)(highest); - } - - public void setSizeWithAir(int sizeWithAir) { - this.sizeWithAir = (short)(sizeWithAir); - } - - public void setSizeWithoutAir(int sizeWithoutAir) { - this.sizeWithoutAir = (short)(sizeWithoutAir); - } - - public void setSortingIndex(int index, int to) { - this.sortingArray[index] = (short)(to); - } - - public void setLower(int index, int lowerIndex) { + public void setLower(int index, int lowerIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << LOWER_SHIFT)) | (((long)(lowerIndex & LINK_MASK)) << LOWER_SHIFT); } - - public void setHigher(int index, int higherIndex) { + public void setHigher(int index, int higherIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << HIGHER_SHIFT)) | (((long)(higherIndex & LINK_MASK)) << HIGHER_SHIFT); } - public void setSmaller(int index, int smallerIndex) { + public void setSmaller(int index, int smallerIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << SMALLER_SHIFT)) | (((long)(smallerIndex & LINK_MASK)) << SMALLER_SHIFT); } - - public void setBigger(int index, int biggerIndex) { + public void setBigger(int index, int biggerIndex) + { this.links[index] = (this.links[index] & ~(((long)(LINK_MASK)) << BIGGER_SHIFT)) | (((long)(biggerIndex & LINK_MASK)) << BIGGER_SHIFT); } - public void setData(int index, long data) { - this.data[index] = data; - } + public void setData(int index, long data) { this.data[index] = data; } - public void setMinY(int index, int minY) { + public void setMinY(int index, int minY) + { this.data[index] = (this.data[index] & ~RenderDataPointUtil.DEPTH_SHIFTED_MASK) | ((minY & RenderDataPointUtil.DEPTH_MASK) << RenderDataPointUtil.DEPTH_SHIFT); } - - public void setMaxY(int index, int maxY) { + public void setMaxY(int index, int maxY) + { this.data[index] = (this.data[index] & ~RenderDataPointUtil.HEIGHT_SHIFTED_MASK) | ((maxY & RenderDataPointUtil.HEIGHT_MASK) << RenderDataPointUtil.HEIGHT_SHIFT); } - public void setRed(int index, int red) { + public void setRed(int index, int red) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.RED_MASK << RenderDataPointUtil.RED_SHIFT)) | ((red & RenderDataPointUtil.RED_MASK) << RenderDataPointUtil.RED_SHIFT); } - - public void setGreen(int index, int green) { + public void setGreen(int index, int green) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.GREEN_MASK << RenderDataPointUtil.GREEN_SHIFT)) | ((green & RenderDataPointUtil.GREEN_MASK) << RenderDataPointUtil.GREEN_SHIFT); } public void setBlue(int index, int blue) { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.BLUE_MASK << RenderDataPointUtil.BLUE_SHIFT)) | ((blue & RenderDataPointUtil.BLUE_MASK) << RenderDataPointUtil.BLUE_SHIFT); } - - public void setAlpha(int index, int alpha) { + public void setAlpha(int index, int alpha) + { alpha >>>= RenderDataPointUtil.ALPHA_DOWNSIZE_SHIFT; this.data[index] = (this.data[index] & ~(RenderDataPointUtil.ALPHA_MASK << RenderDataPointUtil.ALPHA_SHIFT)) | ((alpha & RenderDataPointUtil.ALPHA_MASK) << RenderDataPointUtil.ALPHA_SHIFT); } - - public void setBlockLight(int index, int blockLight) { + + public void setBlockLight(int index, int blockLight) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.BLOCK_LIGHT_MASK << RenderDataPointUtil.BLOCK_LIGHT_SHIFT)) | ((blockLight & RenderDataPointUtil.BLOCK_LIGHT_MASK) << RenderDataPointUtil.BLOCK_LIGHT_SHIFT); } - - public void setSkyLight(int index, int skyLight) { + public void setSkyLight(int index, int skyLight) + { this.data[index] = (this.data[index] & ~(RenderDataPointUtil.SKY_LIGHT_MASK << RenderDataPointUtil.SKY_LIGHT_SHIFT)) | ((skyLight & RenderDataPointUtil.SKY_LIGHT_MASK) << RenderDataPointUtil.SKY_LIGHT_SHIFT); } + + + //================// + // helper methods // + //================// + + public boolean isIndexVisible(int index) { return isDataVisible(this.getData(index)); } - //////////////////////////////// utility //////////////////////////////// - - public boolean isIndexVisible(int index) { - return isDataVisible(this.getData(index)); - } - - public static boolean isDataVisible(long data) { - return isAlphaVisible(RenderDataPointUtil.getAlpha(data)); - } - - public static boolean isAlphaVisible(int alpha) { - return alpha >= 16; - } + public static boolean isDataVisible(long data) { return isAlphaVisible(RenderDataPointUtil.getAlpha(data)); } + public static boolean isAlphaVisible(int alpha) { return alpha >= 16; } + + + + //==============// + // base methods // + //==============// + @Override - public String toString() { + public String toString() + { StringBuilder builder = new StringBuilder(this.sizeWithAir << 8).append("lowest to highest:"); - for (int index = this.lowest; index != NULL; index = this.getHigher(index)) { + for (int index = this.lowest; index != NULL; index = this.getHigher(index)) + { builder.append('\n').append(RenderDataPointUtil.toString(this.getData(index))); } + builder.append("\nsmallest to biggest:"); - for (int index = this.smallest; index != NULL; index = this.getBigger(index)) { + for (int index = this.smallest; index != NULL; index = this.getBigger(index)) + { builder.append('\n').append(RenderDataPointUtil.toString(this.getData(index))); } + return builder.toString(); } + } \ No newline at end of file From 104be7804cd0c43375156f97b1fbee1fcf631f9d Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 5 Dec 2023 07:15:09 -0600 Subject: [PATCH 29/32] Add RenderDataPointUtil method renames and fast path for single target size --- .../util/RenderDataPointReducingList.java | 166 +++++++++++++----- .../core/util/RenderDataPointUtil.java | 28 ++- 2 files changed, 147 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java index df48d7d05..800763083 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointReducingList.java @@ -277,8 +277,9 @@ public class RenderDataPointReducingList * 3: if we still fail, force the lowest segment to merge with the segment above it, * with no restrictions on alpha. * the highest alpha of the two segments takes priority though. - * 4: repeat until our size is less than or equal to the target size. - * notes: + * 4: repeat until our size is less than or equal to the target size.

+ * + * notes:
* changing the size of a node requires re-sorting that node, * but it does not require re-sorting the whole list. * additionally, because of the fact that nodes are sorted smallest to biggest, @@ -301,14 +302,11 @@ public class RenderDataPointReducingList */ public void reduce(int target) { - if (this.reduceStep1SpecialCases(target)) return; + if (this.mergeVerySmallConnectedSegments(target)) return; - while (true) - { - if (this.reduceStep1GeneralCases(target)) return; - if (this.reduceStep2(target)) return; - if (this.reduceStep3(target)) return; - } + if (this.mergeConnectedSegments(target)) return; + if (this.removeLeastImportantSegments(target)) return; + this.forceBottomToMerge(target); } @@ -662,7 +660,7 @@ public class RenderDataPointReducingList * the list's size down to less than or equal to target, * or false if more steps need to be performed. */ - private boolean reduceStep1SpecialCases(int target) + private boolean mergeVerySmallConnectedSegments(int target) { for (int specialCase = 1; specialCase <= SPECIAL_CASES; specialCase++) { @@ -690,13 +688,13 @@ public class RenderDataPointReducingList * in other words, handles all the nodes whose size * is strictly greater than {@link #SPECIAL_CASES}, * and all the nodes which are smaller, but failed - * to be merged in {@link #reduceStep1SpecialCases(int)} + * to be merged in {@link #mergeVerySmallConnectedSegments(int)} * * returns true if this step single-handedly brought * the list's size down to less than or equal to target, * or false if more steps need to be performed. */ - private boolean reduceStep1GeneralCases(int target) + private boolean mergeConnectedSegments(int target) { for (int current = this.getSmallest(); current != NULL;) { @@ -718,7 +716,7 @@ public class RenderDataPointReducingList * the list's size down to less than or equal to target, * or false if more steps need to be performed. */ - private boolean reduceStep2(int target) + private boolean removeLeastImportantSegments(int target) { for (int center = this.getSmallest(); center != NULL; ) { @@ -752,42 +750,124 @@ public class RenderDataPointReducingList * are forced to merge in order to fit the desired target, * even if they normally shouldn't merge because it would look bad.

* - * returns true if this step brought the list's - * size down to less than or equal to target, - * or false if we need to go back to step 1. + * returns after this step brings the list's + * size down to less than or equal to target. */ - private boolean reduceStep3(int target) + private void forceBottomToMerge(int target) { - if (this.getSizeWithoutAir() <= target) + for (int lowest = this.getLowest(); lowest != NULL; ) { - return true; - } - - - int lowest = this.getLowest(); - int higher = this.getHigher(lowest); - if (higher != NULL) - { - this.setMinY(higher, this.getMinY(lowest)); - this.resortSize(higher); - this.remove(lowest); - if (ASSERTS) this.checkLinks(); + if (this.getSizeWithoutAir() <= target) + { + return; + } - return false; // go back to step 1. - } - else - { - // if we reach this line, then target is 0 or negative. - this.setLowest(NULL); - this.setHighest(NULL); - this.setSmallest(NULL); - this.setBiggest(NULL); - this.setSizeWithAir(0); - this.setSizeWithoutAir(0); - return true; + int lowY = this.getMinY(lowest); + int higher = this.getHigher(lowest); + inner: + while (true) + { + if (higher == NULL) + { + //if we reach this line, then target is 0 or negative. + this.setLowest(NULL); + this.setHighest(NULL); + this.setSmallest(NULL); + this.setBiggest(NULL); + this.setSizeWithAir(0); + this.setSizeWithoutAir(0); + + if (ASSERTS) this.checkLinks(); + + return; + } + + // don't merge the lowest segment with an invisible segment. + // in other words, we don't want + // visible + // invisible + // visible + // to be replaced with + // visible + // invisible + // instead, we want to eliminate the invisible segment too, + // and set the minY of the top visible segment + // to the minY of the bottom visible segment. + if (this.isIndexVisible(higher)) + { + this.setMinY(higher, lowY); + this.resortSize(higher); + this.remove(lowest); + + if (ASSERTS) this.checkLinks(); + + lowest = this.getLowest(); + break inner; + } + else + { + this.remove(lowest); + lowest = higher; + higher = this.getHigher(higher); + //don't update lowY. + } + } } } + /** + * reduces the view to a single data point, + * whose min Y is the lowest of all data points in the provided view, + * and every other property of the returned data point + * matches those of the data point with the highest + * Y level in the provided view. + * + * @implNote this method does not allocate any objects. + */ + public static long reduceToOne(IColumnDataView view) + { + int size = view.size(); + if (size <= 0) + { + return RenderDataPointUtil.createVoidDataPoint((byte)(1)); + } + + long highest; + int lowest; + int index = 0; + //first loop: find the first visible segment. + foundVisible: + { + for (; index < size; index++) + { + long dataPoint = view.get(index); + if (isDataVisible(dataPoint)) + { + highest = dataPoint; + lowest = RenderDataPointUtil.getYMin(dataPoint); + break foundVisible; + } + } + //no visible segments, return void. + return RenderDataPointUtil.createVoidDataPoint((byte) (1)); + } + + //second loop: merge the rest of the segments. + for (; index < size; index++) + { + long dataPoint = view.get(index); + if (isDataVisible(dataPoint)) + { + int y = RenderDataPointUtil.getYMin(dataPoint); + if (y > highest) highest = dataPoint; + else if (y < lowest) lowest = y; + } + } + + return (highest & ~RenderDataPointUtil.DEPTH_SHIFTED_MASK) | ((lowest & RenderDataPointUtil.DEPTH_MASK) << RenderDataPointUtil.DEPTH_SHIFT); + } + + /** transfers the contents of this list to the provided view, in order of highest to lowest. */ public void copyTo(ColumnArrayView view) { @@ -812,7 +892,7 @@ public class RenderDataPointReducingList for (int size = view.size(); writeIndex < size; writeIndex++) { - view.set(writeIndex, 0L); + view.set(writeIndex, RenderDataPointUtil.EMPTY_DATA); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java index 3cee54b34..77f6459c8 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java @@ -276,10 +276,30 @@ public class RenderDataPointUtil */ public static void mergeMultiData(IColumnDataView sourceData, ColumnArrayView output) { - RenderDataPointReducingList list = new RenderDataPointReducingList(sourceData); - list.reduce(output.verticalSize()); - list.copyTo(output); - + int target = output.verticalSize(); + if (target <= 0) + { + // I expect this to never be the case, + // but RenderDataPointReducingList handles it sanely, + // so I might as well handle it sanely here too. + output.fill(EMPTY_DATA); + } + else if (target == 1) + { + output.set(0, RenderDataPointReducingList.reduceToOne(sourceData)); + for (int index = 1, size = output.size(); index < size; index++) + { + output.set(index, EMPTY_DATA); + } + } + else + { + RenderDataPointReducingList list = new RenderDataPointReducingList(sourceData); + list.reduce(output.verticalSize()); + list.copyTo(output); + } + + //old logic left here in case it's ever needed again. /* if (output.dataCount() != 1) From 1880c65078f839c35c5f687d2a46e1ee76ce0941 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 6 Dec 2023 07:29:42 -0600 Subject: [PATCH 30/32] Fix config handling failing for Doubles and add additional logging --- .../core/config/file/ConfigFileHandling.java | 16 ++++++++++-- .../config/file/ConfigTypeConverters.java | 26 ++++++++++++------- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java index fe8500c31..0bd02668f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigFileHandling.java @@ -224,9 +224,21 @@ public class ConfigFileHandling return; } - entry.pureSet((T) ConfigTypeConverters.attemptToConvertFromString(entry.getType(), nightConfig.get(entry.getNameWCategory()))); + // try converting the value if necessary + Class expectedValueClass = entry.getType(); + Object value = nightConfig.get(entry.getNameWCategory()); + Object convertedValue = ConfigTypeConverters.attemptToConvertFromString(expectedValueClass, value); + if (!convertedValue.getClass().equals(expectedValueClass)) + { + LOGGER.error("Unable to convert config value ["+value+"] from ["+(value != null ? value.getClass() : "NULL")+"] to ["+expectedValueClass+"] for config ["+entry.name+"], " + + "the default config value will be used instead ["+entry.getDefaultValue()+"]. " + + "Make sure a converter is defined in ["+ConfigTypeConverters.class.getSimpleName()+"]."); + convertedValue = entry.getDefaultValue(); + } + entry.pureSet((T) convertedValue); - if (entry.getTrueValue() == null) { + if (entry.getTrueValue() == null) + { LOGGER.warn("Entry [" + entry.getNameWCategory() + "] returned as null from the config. Using default value."); entry.pureSet(entry.getDefaultValue()); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigTypeConverters.java b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigTypeConverters.java index 3b8ba9711..57418ad80 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigTypeConverters.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/file/ConfigTypeConverters.java @@ -37,12 +37,13 @@ public class ConfigTypeConverters // Once you've made a converter add it to here where the first value is the type you want to convert and the 2nd value is the converter public static final Map, ConverterBase> convertObjects = new HashMap, ConverterBase>() {{ - put(Short.class, new ShortConverter()); - put(Long.class, new LongConverter()); - put(Float.class, new FloatConverter()); - put(Byte.class, new ByteConverter()); + this.put(Short.class, new ShortConverter()); + this.put(Long.class, new LongConverter()); + this.put(Float.class, new FloatConverter()); + this.put(Double.class, new DoubleConverter()); + this.put(Byte.class, new ByteConverter()); - put(Map.class, new MapConverter()); + this.put(Map.class, new MapConverter()); }}; public static Class isClassConvertable(Class clazz) @@ -73,10 +74,12 @@ public class ConfigTypeConverters { return attemptToConvertFromString(value.getClass(), value); } - public static Object attemptToConvertFromString(Class clazz, Object value) + public static Object attemptToConvertFromString(Class outputClass, Object value) { - Class convertablClass = isClassConvertable(clazz); - if (convertablClass != null) { + boolean valueNeedsConverting = (value == null || value.getClass().equals(String.class)); + Class convertablClass = isClassConvertable(outputClass); + if (valueNeedsConverting && convertablClass != null) + { return convertFromString(convertablClass, (String) value); } return value; @@ -140,7 +143,12 @@ public class ConfigTypeConverters { @Override public String convertToString(Object item) { return ((Float) item).toString(); } @Override public Float convertFromString(String s) { return Float.valueOf(s); } - + } + + public static class DoubleConverter extends ConverterBase + { + @Override public String convertToString(Object item) { return ((Double) item).toString(); } + @Override public Double convertFromString(String s) { return Double.valueOf(s); } } public static class ByteConverter extends ConverterBase From 20452ceb76f0a3e43710e4272f680b946a97e564 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 6 Dec 2023 07:49:19 -0600 Subject: [PATCH 31/32] change overdraw to a number config --- .../api/enums/config/EOverdrawPrevention.java | 12 ++- .../interfaces/config/IDhApiConfigValue.java | 6 +- .../config/client/IDhApiGraphicsConfig.java | 21 ++++- .../config/client/DhApiGraphicsConfig.java | 7 +- .../distanthorizons/core/config/Config.java | 36 ++++++-- ...rawPreventionPresetConfigEventHandler.java | 86 +++++++++++++++++++ .../types/enums/EConfigEntryAppearance.java | 4 +- .../distanthorizons/core/util/RenderUtil.java | 29 ++----- .../assets/distanthorizons/lang/en_us.json | 2 +- .../main/resources/shaders/flat_shaded.frag | 24 +++--- 10 files changed, 181 insertions(+), 46 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/OverdrawPreventionPresetConfigEventHandler.java diff --git a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EOverdrawPrevention.java b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EOverdrawPrevention.java index c00df2790..46f82868b 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EOverdrawPrevention.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/enums/config/EOverdrawPrevention.java @@ -24,9 +24,14 @@ package com.seibel.distanthorizons.api.enums.config; * LIGHT
* MEDIUM
* HEAVY
+ * + * CUSTOM
* * @since API 1.0.0 + * @deprecated will be removed when DH updates to MC 1.21
+ * After removal a float value will be used to control overdraw instead. */ +@Deprecated public enum EOverdrawPrevention { // Reminder: @@ -36,6 +41,11 @@ public enum EOverdrawPrevention NONE, LIGHT, MEDIUM, - HEAVY; + HEAVY, + /** + * Should not be passed in.
+ * Is returned if the overdraw value doesn't match any of the enums defined here. + */ + CUSTOM; } diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfigValue.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfigValue.java index ecb7079be..90b9d13aa 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfigValue.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/IDhApiConfigValue.java @@ -53,8 +53,10 @@ public interface IDhApiConfigValue * Sets the config's value.
* If the newValue is set to null then the config * will revert to using the True Value.
- * If the config cannot be set via the API this method will return false. - * + * If the config cannot be set via the API this method will return false.

+ * + * To unset the config's value pass in Null.
+ * * @return true if the value was set, false otherwise. */ boolean setValue(T newValue); diff --git a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGraphicsConfig.java b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGraphicsConfig.java index d09fed095..f31d1fc9b 100644 --- a/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGraphicsConfig.java +++ b/api/src/main/java/com/seibel/distanthorizons/api/interfaces/config/client/IDhApiGraphicsConfig.java @@ -113,13 +113,30 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup //===========================// /** - * If enabled the near clip plane is extended to reduce - * overdraw and improve Z-fighting at extreme render distances.
+ * Sets the distance used by the near clip plane to reduce + * overdraw.
* Disabling this reduces holes in the world due to the near clip plane * being too close to the camera and the terrain not being covered by vanilla terrain. + * + * @deprecated Use {@link IDhApiGraphicsConfig#overdrawPreventionRadius()} instead. */ + @Deprecated IDhApiConfigValue overdrawPrevention(); + /** + * Sets the radius used by the near clip shader to reduce + * overdraw.
+ * Measured in percentages of the render distance, IE:
+ * 0.5 = 50% vanilla render distance
+ * 0.1 = 10% vanilla render distance
+ *
+ * Setting this to 0 will reduce/prevent holes in the world due to clipping to close to the camera + * but may cause overdraw issues with transparent or non-full blocks. + * + * @since API 1.1.0 + */ + IDhApiConfigValue overdrawPreventionRadius(); + /** * Modifies how bright fake chunks are.
* This is done when generating the vertex data and is applied before any shaders. diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGraphicsConfig.java b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGraphicsConfig.java index c43ed456f..985a4b15d 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGraphicsConfig.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/external/methods/config/client/DhApiGraphicsConfig.java @@ -109,9 +109,14 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig // public IDhApiConfigValue getDisableDirectionalCulling() // { return new DhApiConfigValue(AdvancedGraphics.disableDirectionalCulling); } + @Deprecated @Override public IDhApiConfigValue overdrawPrevention() - { return new DhApiConfigValue(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); } + { return new DhApiConfigValue(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset); } + + @Override + public IDhApiConfigValue overdrawPreventionRadius() + { return new DhApiConfigValue(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention); } @Override public IDhApiConfigValue brightnessMultiplier() diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java index 452e4efd5..71200ea5f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/Config.java @@ -546,17 +546,29 @@ public class Config // + "Disable this if you see LODs disappearing at the corners of your vision.") // .build(); - public static ConfigEntry overdrawPrevention = new ConfigEntry.Builder() + /** + * @deprecated Use overdrawPrevention instead, will be removed when DH updates to MC 1.21
+ * After removal a float value will be used to control overdraw instead.
+ */ + @Deprecated + public static ConfigEntry overdrawPreventionPreset = new ConfigEntry.Builder() .set(EOverdrawPrevention.MEDIUM) + .comment("") + .setAppearance(EConfigEntryAppearance.ONLY_IN_API) + .setPerformance(EConfigEntryPerformance.NONE) + .build(); + + public static ConfigEntry overdrawPrevention = new ConfigEntry.Builder() + .setMinDefaultMax(0.0, 0.4, 1.0) .comment("" - + "Determines how far Distant Horizon's near clip plane will render. \n" + + "Determines how far from the camera Distant Horizons will start rendering. \n" + + "Measured as a percentage of the vanilla render distance.\n" + "\n" + "Higher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\n" + "but may cause holes to appear in the LODs. \n" - + "Holes are most likely at the left and right edges of the screen \n" - + "when flying through unloaded terrain. \n" + + "Holes are most likely to appear when flying through unloaded terrain. \n" + "\n" - + "Increasing the vanilla render distance increases the effectiveness of these options." + + "Increasing the vanilla render distance increases the effectiveness of this setting." + "") .setPerformance(EConfigEntryPerformance.NONE) .build(); @@ -1398,14 +1410,26 @@ public class Config try { + // listener can only be added after the config has finished initializing + Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset.addListener(OverdrawPreventionPresetConfigEventHandler.INSTANCE); + } + catch (Exception e) + { + LOGGER.error("Unexpected exception when running config delayed listener setup. Error: [" + e.getMessage() + "].", e); + } + + try + { + // TODO automatically get all instances of AbstractPresetConfigEventHandler and fire "setUiOnlyConfigValues" ThreadPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); RenderQualityPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); QuickRenderToggleConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); + OverdrawPreventionPresetConfigEventHandler.INSTANCE.setUiOnlyConfigValues(); RenderCacheConfigEventHandler.getInstance(); } catch (Exception e) { - LOGGER.error("Unexpected exception when setting up complicated config listeners. Error: [" + e.getMessage() + "].", e); + LOGGER.error("Unexpected exception when running config delayed UI setup. Error: [" + e.getMessage() + "].", e); } } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/OverdrawPreventionPresetConfigEventHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/OverdrawPreventionPresetConfigEventHandler.java new file mode 100644 index 000000000..216238d53 --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/presets/OverdrawPreventionPresetConfigEventHandler.java @@ -0,0 +1,86 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020-2023 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.core.config.eventHandlers.presets; + +import com.seibel.distanthorizons.api.enums.config.EMaxHorizontalResolution; +import com.seibel.distanthorizons.api.enums.config.EOverdrawPrevention; +import com.seibel.distanthorizons.api.enums.config.quickOptions.EQualityPreset; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.ConfigEntryWithPresetOptions; +import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; +import com.seibel.distanthorizons.coreapi.interfaces.config.IConfigEntry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class OverdrawPreventionPresetConfigEventHandler extends AbstractPresetConfigEventHandler +{ + public static final OverdrawPreventionPresetConfigEventHandler INSTANCE = new OverdrawPreventionPresetConfigEventHandler(); + + private static final Logger LOGGER = LogManager.getLogger(); + + + private final ConfigEntryWithPresetOptions overdrawPrevention = new ConfigEntryWithPresetOptions<>(Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention, + new HashMap() + {{ + this.put(EOverdrawPrevention.HEAVY, 0.6); + this.put(EOverdrawPrevention.MEDIUM, 0.4); + this.put(EOverdrawPrevention.LIGHT, 0.25); + this.put(EOverdrawPrevention.NONE, 0.0); + }}); + + + + //==============// + // constructors // + //==============// + + /** private since we only ever need one handler at a time */ + private OverdrawPreventionPresetConfigEventHandler() + { + // add each config used by this preset + this.configList.add(this.overdrawPrevention); + + + for (ConfigEntryWithPresetOptions config : this.configList) + { + // ignore try-using, the listener should only ever be added once and should never be removed + new ConfigChangeListener<>(config.configEntry, (val) -> { this.onConfigValueChanged(); }); + } + } + + + + //==============// + // enum getters // + //==============// + + @Override + protected IConfigEntry getPresetConfigEntry() { return Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPreventionPreset; } + + @Override + protected List getPresetEnumList() { return Arrays.asList(EOverdrawPrevention.values()); } + @Override + protected EOverdrawPrevention getCustomPresetEnum() { return EOverdrawPrevention.CUSTOM; } + +} diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/types/enums/EConfigEntryAppearance.java b/core/src/main/java/com/seibel/distanthorizons/core/config/types/enums/EConfigEntryAppearance.java index 06b367832..24dd3fd51 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/config/types/enums/EConfigEntryAppearance.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/types/enums/EConfigEntryAppearance.java @@ -32,7 +32,9 @@ public enum EConfigEntryAppearance /** Will only show the option in the UI. The option will be reverted on game restart */ ONLY_IN_GUI(true, false), /** Only show the option in the file. There would be no way to access it using the UI */ - ONLY_IN_FILE(true, false); + ONLY_IN_FILE(true, false), + /** The option is only available via code. Generally this is only used for deprecated options. */ + ONLY_IN_API(false, false); /** Sets whether the option should show in the UI */ public final boolean showInGui; diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java index ac9ff4b65..5411e8057 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderUtil.java @@ -203,30 +203,17 @@ public class RenderUtil else { // TODO make this option dependent on player speed. - // if the player is flying quickly, lower the near clip plane to account for slow chunk loading. + // If the player is flying quickly, lower the near clip plane to account for slow chunk loading. // If the player is moving quickly they are less likely to notice overdraw. - EOverdrawPrevention clipPlaneDistance = Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention.get(); - switch (clipPlaneDistance) + nearClipPlane = Config.Client.Advanced.Graphics.AdvancedGraphics.overdrawPrevention.get().floatValue(); + nearClipPlane *= vanillaBlockRenderedDistance; + + // the near clip plane should never be closer than 1/10th of a block, + // otherwise Z-fighting and other issues may occur + if (nearClipPlane < 0.1f) { - default: // shouldn't be necessary, just here to make the compiler happy - case NONE: - nearClipPlane = 0.1f; - break; - - case LIGHT: - nearClipPlane = vanillaBlockRenderedDistance * 0.25f; - break; - - case MEDIUM: - nearClipPlane = vanillaBlockRenderedDistance * 0.4f; - break; - - - case HEAVY: - // recommend render distance ot 6 or higher, otherwise holes may appear - nearClipPlane = vanillaBlockRenderedDistance * 0.6f; - break; + nearClipPlane = 0.1f; } } diff --git a/core/src/main/resources/assets/distanthorizons/lang/en_us.json b/core/src/main/resources/assets/distanthorizons/lang/en_us.json index 2248d0579..7152b0b20 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -268,7 +268,7 @@ "distanthorizons.config.client.advanced.graphics.advancedGraphics.overdrawPrevention": "Overdraw Prevention", "distanthorizons.config.client.advanced.graphics.advancedGraphics.overdrawPrevention.@tooltip": - "Enabling this will prevent some overdraw issues,\nbut may cause nearby LODs to render incorrectly, especially when near fancy leaves or non-full blocks.\nLess noticeable with a longer vanilla render distance.", + "Determines how far from the camera Distant Horizons will start rendering. \nMeasured as a percentage of the vanilla render distance.\n\nHigher values will prevent LODs from rendering behind vanilla blocks at a higher distance,\nbut may cause holes to appear in the LODs. \nHoles are most likely to appear when flying through unloaded terrain. \n\nIncreasing the vanilla render distance increases the effectiveness of this setting.", "distanthorizons.config.client.advanced.graphics.advancedGraphics.seamlessOverdraw": "(Experimental) Seamless Overdraw", "distanthorizons.config.client.advanced.graphics.advancedGraphics.seamlessOverdraw.@tooltip": diff --git a/core/src/main/resources/shaders/flat_shaded.frag b/core/src/main/resources/shaders/flat_shaded.frag index 72643cd53..e6d7a9407 100644 --- a/core/src/main/resources/shaders/flat_shaded.frag +++ b/core/src/main/resources/shaders/flat_shaded.frag @@ -27,11 +27,13 @@ float rand(vec3 co) { return rand(co.xy + rand(co.z)); } // EG. setting stepSize to 4 then this would be the result of this function // In: 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, ..., 1.1, 1.2, 1.3 // Out: 0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, ..., 1.0, 1.0, 1.25 -vec3 quantize(vec3 val, int stepSize) { +vec3 quantize(vec3 val, int stepSize) +{ return floor(val * stepSize) / stepSize; } -void applyNoise(inout vec4 fragColor, const in float viewDist) { +void applyNoise(inout vec4 fragColor, const in float viewDist) +{ vec3 vertexNormal = normalize(cross(dFdy(vPos.xyz), dFdx(vPos.xyz))); // This bit of code is required to fix the vertex position problem cus of floats in the verted world position varuable vec3 fixedVPos = vPos.xyz + vertexNormal * 0.001; @@ -58,20 +60,20 @@ void applyNoise(inout vec4 fragColor, const in float viewDist) { fragColor.rgb = newCol; } + -/** - * Fragment Shader - * - * author: James Seibel - * author: coolGi - * version: 7-2-2023 - */ void main() { fragColor = vertexColor; float viewDist = length(vertexWorldPos); - if (viewDist < clipDistance && clipDistance > 0.0) discard; + if (viewDist < clipDistance && clipDistance > 0.0) + { + discard; + } - if (noiseEnabled) applyNoise(fragColor, viewDist); + if (noiseEnabled) + { + applyNoise(fragColor, viewDist); + } } From 070b52da5ebfdca7b603122d5c87f24776af3761 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Wed, 6 Dec 2023 07:49:38 -0600 Subject: [PATCH 32/32] Up the api version number 1.0.0 -> 1.1.0 --- .../main/java/com/seibel/distanthorizons/coreapi/ModInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java index d582a9cab..9283d700a 100644 --- a/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java +++ b/api/src/main/java/com/seibel/distanthorizons/coreapi/ModInfo.java @@ -41,7 +41,7 @@ public final class ModInfo /** This version should only be updated when breaking changes are introduced to the DH API */ public static final int API_MAJOR_VERSION = 1; /** This version should be updated whenever new methods are added to the DH API */ - public static final int API_MINOR_VERSION = 0; + public static final int API_MINOR_VERSION = 1; /** This version should be updated whenever non-breaking fixes are added to the DH API */ public static final int API_PATH_VERSION = 0;