From da2454b249a0102dd756b52013af39b78bdb508e Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 15 Mar 2026 20:53:21 -0500 Subject: [PATCH] Add the ability to cull lilly pads, seaGrass, etc. --- .../distanthorizons/core/config/Config.java | 55 ++++++++------- .../RenderBlockCacheCsvHandler.java | 69 +++++++++++++++++++ .../FullDataToRenderDataTransformer.java | 51 +++++++++++--- .../wrapperInterfaces/IWrapperFactory.java | 7 +- .../assets/distanthorizons/lang/en_us.json | 14 +++- 5 files changed, 152 insertions(+), 44 deletions(-) create mode 100644 core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderBlockCacheCsvHandler.java 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 15fcd533f..0d750ca10 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 @@ -745,57 +745,56 @@ public class Config + "Disable this if shadows render incorrectly.") .build(); + public static ConfigUISpacer ignoreCsvStartSpacer = new ConfigUISpacer.Builder().build(); + public static ConfigEntry ignoredRenderBlockCsv = new ConfigEntry.Builder() .set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire,minecraft:brown_mushroom") .setAppearance(EConfigEntryAppearance.ALL) + .addListener(RenderBlockCacheCsvHandler.INSTANCE) .comment("" + "A comma separated list of block resource locations that won't be rendered by DH. \n" + "Air is always included in this list. \n" - + "Requires a restart to change. \n" + "\n" + "Note:\n" + "If you see gaps, or holes you may have to change\n" + "worldCompression to ["+EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS+"] and re-generate the LODs.\n" - + "Black spots may happen occur to block lighting being zero for covered blocks.\n" + "") .build(); public static ConfigEntry ignoredRenderCaveBlockCsv = new ConfigEntry.Builder() - .set("") // config is empty since most cave blocks will be automatically ignored due to being: transparent, non-solid, or liquids, but new blocks can be added here if needed + .set("") .setAppearance(EConfigEntryAppearance.ALL) + .addListener(RenderBlockCacheCsvHandler.INSTANCE) .comment("" + "A comma separated list of block resource locations that shouldn't be rendered \n" + "if they are in a 0 sky light underground area. \n" + "Air is always included in this list. \n" - + "Requires a restart to change. \n" + + "\n" + + "Defaults to an empty list since most cave blocks will be automatically ignored due to being: \n" + + "transparent, non-solid, or liquids, but new blocks can be added here if needed.\n" + "") .build(); + public static ConfigEntry waterSubSurfaceBlockReplacementCsv = new ConfigEntry.Builder() + .set("minecraft:kelp,minecraft:tall_seagrass,minecraft:seagrass") + .setAppearance(EConfigEntryAppearance.ALL) + .addListener(RenderBlockCacheCsvHandler.INSTANCE) + .comment("" + + "A comma separated list of block resource locations that will be replaced by water \n" + + "if they're visible on the water's surface. \n" + + "") + .build(); + + public static ConfigEntry waterSurfaceBlockReplacementCsv = new ConfigEntry.Builder() + .set("minecraft:lily_pad") + .setAppearance(EConfigEntryAppearance.ALL) + .addListener(RenderBlockCacheCsvHandler.INSTANCE) + .comment("" + + "A comma separated list of block resource locations that will be removed \n" + + "when on top of water. \n" + + "") + .build(); - static - { - ignoredRenderBlockCsv.addListener(new ConfigChangeListener(ignoredRenderBlockCsv, - (blockCsv) -> - { - IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); - if (wrapperFactory != null) - { - wrapperFactory.resetRendererIgnoredBlocksSet(); - DhApi.Delayed.renderProxy.clearRenderDataCache(); - } - })); - - ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener(ignoredRenderCaveBlockCsv, - (blockCsv) -> - { - IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); - if (wrapperFactory != null) - { - wrapperFactory.resetRendererIgnoredCaveBlocks(); - DhApi.Delayed.renderProxy.clearRenderDataCache(); - } - })); - } } public static class Experimental diff --git a/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderBlockCacheCsvHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderBlockCacheCsvHandler.java new file mode 100644 index 000000000..51a0940be --- /dev/null +++ b/core/src/main/java/com/seibel/distanthorizons/core/config/eventHandlers/RenderBlockCacheCsvHandler.java @@ -0,0 +1,69 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU 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; + +import com.seibel.distanthorizons.api.DhApi; +import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.listeners.IConfigListener; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.distanthorizons.coreapi.util.StringUtil; + +public class RenderBlockCacheCsvHandler implements IConfigListener +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + public static RenderBlockCacheCsvHandler INSTANCE = new RenderBlockCacheCsvHandler(); + + + + //=============// + // constructor // + //=============// + + /** private since we only ever need one handler at a time */ + private RenderBlockCacheCsvHandler() { } + + + + //=================// + // config handling // + //=================// + + @Override + public void onConfigValueSet() + { + IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class); + if (wrapperFactory != null) + { + wrapperFactory.resetCachedIgnoredBlocksSets(); + DhApi.Delayed.renderProxy.clearRenderDataCache(); + } + } + + + +} 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 b4a736e73..12157e112 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 @@ -194,8 +194,12 @@ public class FullDataToRenderDataTransformer boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING); boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get(); - ObjectOpenHashSet blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper); - ObjectOpenHashSet caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper); + final ObjectOpenHashSet blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper); + final ObjectOpenHashSet caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper); + final ObjectOpenHashSet waterSubsurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSubsurfaceReplacementBlocks(levelWrapper); + final ObjectOpenHashSet waterSurfaceReplacementBlocks = WRAPPER_FACTORY.getWaterSurfaceReplacementBlocks(levelWrapper); + final IBlockStateWrapper water = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper); + // build snow block cache if needed if (snowLayerBlockStates == null) @@ -223,6 +227,7 @@ public class FullDataToRenderDataTransformer int colorToApplyToNextBlock = -1; int lastColor = 0; int lastBottom = -10_000; + IBlockStateWrapper lastBlock = null; int skylightToApplyToNextBlock = -1; int blocklightToApplyToNextBlock = -1; @@ -283,6 +288,12 @@ public class FullDataToRenderDataTransformer // cave culling check // //====================// + if (waterSubsurfaceReplacementBlocks.contains(block) + && (lastBlock == null || lastBlock.isAir())) + { + block = water; + } + boolean ignoreBlock = blockStatesToIgnore.contains(block); boolean caveBlock = caveBlockStatesToIgnore.contains(block); if (caveBlock @@ -328,6 +339,9 @@ public class FullDataToRenderDataTransformer else if (ignoreBlock) { // this is an ignored block, but shouldn't be merged like a cave block + + // applying this sky light to the next block should prevent black spots for opaque covering blocks + skylightToApplyToNextBlock = skyLight; continue; } @@ -343,21 +357,37 @@ public class FullDataToRenderDataTransformer && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE; - // merge snow into the block below it - if (snowLayerBlockStates.contains(block)) + // handle height reduction + boolean isSnowLayer = snowLayerBlockStates.contains(block); + boolean isWaterSurfaceReplacement = waterSurfaceReplacementBlocks.contains(block); + if (isSnowLayer || isWaterSurfaceReplacement) { - // sometimes a snow datapoint will be multiple blocks tall, + if (isWaterSurfaceReplacement) + { + // replace the block with water + block = WRAPPER_FACTORY.getWaterBlockStateWrapper(levelWrapper); + } + + // sometimes a datapoint will be multiple blocks tall, // in that case we just want to drop the top by 1 blockHeight -= 1; if (blockHeight == 0) { - // this snow block was entirely removed, just color the block below it + // this block was entirely removed, just color the block below it ignoreNonSolidBlock = true; - // snow is a special case where it should always tint the block - // below it, if not done grass will appear as gray - int snowColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); - colorToApplyToNextBlock = ColorUtil.setAlpha(snowColor, 255); + + if (isSnowLayer) + { + // snow is a special case where it should always tint the block + // below it, if not done grass will appear as gray + int snowColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); + colorToApplyToNextBlock = ColorUtil.setAlpha(snowColor, 255); + } + else //if (isWaterSurfaceReplacement) + { + colorToApplyToNextBlock = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); + } } } @@ -449,6 +479,7 @@ public class FullDataToRenderDataTransformer } lastBottom = bottomY; lastColor = color; + lastBlock = block; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/IWrapperFactory.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/IWrapperFactory.java index d5daec17d..5685a38a5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/IWrapperFactory.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/IWrapperFactory.java @@ -65,6 +65,7 @@ public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable IBlockStateWrapper deserializeBlockStateWrapper(String str, ILevelWrapper levelWrapper) throws IOException; IBlockStateWrapper getAirBlockStateWrapper(); + IBlockStateWrapper getWaterBlockStateWrapper(ILevelWrapper levelWrapper); default IBlockStateWrapper deserializeBlockStateWrapperOrGetDefault(String str, ILevelWrapper levelWrapper) { IBlockStateWrapper blockState; @@ -92,10 +93,10 @@ public interface IWrapperFactory extends IDhApiWrapperFactory, IBindable */ ObjectOpenHashSet getRendererIgnoredCaveBlocks(ILevelWrapper levelWrapper); + ObjectOpenHashSet getWaterSubsurfaceReplacementBlocks(ILevelWrapper levelWrapper); + ObjectOpenHashSet getWaterSurfaceReplacementBlocks(ILevelWrapper levelWrapper); /** clears the cached values */ - void resetRendererIgnoredCaveBlocks(); - /** clears the cached values */ - void resetRendererIgnoredBlocksSet(); + void resetCachedIgnoredBlocksSets(); /** 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 4a885828b..79dd4d1ae 100644 --- a/core/src/main/resources/assets/distanthorizons/lang/en_us.json +++ b/core/src/main/resources/assets/distanthorizons/lang/en_us.json @@ -391,12 +391,20 @@ "distanthorizons.config.client.advanced.graphics.culling.ignoredRenderBlockCsv": "Ignored Block CSV", "distanthorizons.config.client.advanced.graphics.culling.ignoredRenderBlockCsv.@tooltip": - "A comma separated list of block resource locations that won't be rendered by DH. \nNote: air is always included in this list.", + "A comma separated list of block resource locations that won't be rendered by DH. \nAir is always included in this list. \n\nNote: \nIf you see gaps, or holes you may have to change \nworldCompression to [MERGE_SAME_BLOCKS] and re-generate the LODs.", "distanthorizons.config.client.advanced.graphics.culling.ignoredRenderCaveBlockCsv": "Ignored Cave Block CSV", "distanthorizons.config.client.advanced.graphics.culling.ignoredRenderCaveBlockCsv.@tooltip": - "A comma separated list of block resource locations that shouldn't be rendered \nif they are in a 0 sky light underground area. \nNote: air is always included in this list.", - + "A comma separated list of block resource locations that shouldn't be rendered \nif they are in a 0 sky light underground area. \nAir is always included in this list. \n\nDefaults to an empty list since most cave blocks will be automatically ignored due to being: \ntransparent, non-solid, or liquids, but new blocks can be added here if needed.", + "distanthorizons.config.client.advanced.graphics.culling.waterSubSurfaceBlockReplacementCsv": + "Water Sub-Surface Replacement", + "distanthorizons.config.client.advanced.graphics.culling.waterSubSurfaceBlockReplacementCsv.@tooltip": + "A comma separated list of block resource locations that will be replaced by water \nif they're visible on the water's surface.", + "distanthorizons.config.client.advanced.graphics.culling.waterSurfaceBlockReplacementCsv": + "Water Surface Replacement", + "distanthorizons.config.client.advanced.graphics.culling.waterSurfaceBlockReplacementCsv.@tooltip": + "A comma separated list of block resource locations that will be removed \nwhen on top of water.", + "distanthorizons.config.client.advanced.graphics.overrideVanillaGraphicsSettings": "Override Vanilla Settings", "distanthorizons.config.client.advanced.graphics.overrideVanillaGraphicsSettings.@tooltip":