diff --git a/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch b/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch index f2ca146f9c..72aee1fb3e 100644 --- a/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch +++ b/patches/net/minecraft/client/renderer/block/LiquidBlockRenderer.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/client/renderer/block/LiquidBlockRenderer.java +++ b/net/minecraft/client/renderer/block/LiquidBlockRenderer.java -@@ -37,6 +_,7 @@ +@@ -37,12 +_,17 @@ this.waterIcons[0] = Minecraft.getInstance().getModelManager().getBlockModelShaper().getBlockModel(Blocks.WATER.defaultBlockState()).getParticleIcon(); this.waterIcons[1] = ModelBakery.WATER_FLOW.sprite(); this.waterOverlay = ModelBakery.WATER_OVERLAY.sprite(); @@ -8,8 +8,29 @@ } private static boolean isNeighborSameFluid(FluidState p_203186_, FluidState p_203187_) { -@@ -70,8 +_,9 @@ + return p_203187_.getType().isSame(p_203186_.getType()); + } + ++ private static boolean isNeighborStateHidingOverlay(FluidState selfState, BlockState otherState, Direction neighborFace) { ++ return otherState.shouldHideAdjacentFluidFace(neighborFace, selfState); ++ } ++ + private static boolean isFaceOccludedByState(Direction p_110980_, float p_110981_, BlockState p_110983_) { + VoxelShape voxelshape = p_110983_.getFaceOcclusionShape(p_110980_.getOpposite()); + if (voxelshape == Shapes.empty()) { +@@ -64,14 +_,20 @@ + return isFaceOccludedByState(p_110963_.getOpposite(), 1.0F, p_110962_); + } ++ /** @deprecated Neo: use overload that accepts BlockState */ + public static boolean shouldRenderFace(FluidState p_203169_, BlockState p_203170_, Direction p_203171_, FluidState p_203172_) { + return !isFaceOccludedBySelf(p_203170_, p_203171_) && !isNeighborSameFluid(p_203169_, p_203172_); + } + ++ public static boolean shouldRenderFace(FluidState fluidState, BlockState selfState, Direction direction, BlockState otherState) { ++ return !isFaceOccludedBySelf(selfState, direction) && !isNeighborStateHidingOverlay(fluidState, otherState, direction.getOpposite()); ++ } ++ public void tesselate(BlockAndTintGetter p_234370_, BlockPos p_234371_, VertexConsumer p_234372_, BlockState p_234373_, FluidState p_234374_) { boolean flag = p_234374_.is(FluidTags.LAVA); - TextureAtlasSprite[] atextureatlassprite = flag ? this.lavaIcons : this.waterIcons; @@ -20,6 +41,25 @@ float f = (float)(i >> 16 & 0xFF) / 255.0F; float f1 = (float)(i >> 8 & 0xFF) / 255.0F; float f2 = (float)(i & 0xFF) / 255.0F; +@@ -87,12 +_,12 @@ + FluidState fluidstate4 = blockstate4.getFluidState(); + BlockState blockstate5 = p_234370_.getBlockState(p_234371_.relative(Direction.EAST)); + FluidState fluidstate5 = blockstate5.getFluidState(); +- boolean flag1 = !isNeighborSameFluid(p_234374_, fluidstate1); +- boolean flag2 = shouldRenderFace(p_234374_, p_234373_, Direction.DOWN, fluidstate) && !isFaceOccludedByNeighbor(Direction.DOWN, 0.8888889F, blockstate); +- boolean flag3 = shouldRenderFace(p_234374_, p_234373_, Direction.NORTH, fluidstate2); +- boolean flag4 = shouldRenderFace(p_234374_, p_234373_, Direction.SOUTH, fluidstate3); +- boolean flag5 = shouldRenderFace(p_234374_, p_234373_, Direction.WEST, fluidstate4); +- boolean flag6 = shouldRenderFace(p_234374_, p_234373_, Direction.EAST, fluidstate5); ++ boolean flag1 = !isNeighborStateHidingOverlay(p_234374_, blockstate1, Direction.DOWN); ++ boolean flag2 = shouldRenderFace(p_234374_, p_234373_, Direction.DOWN, blockstate) && !isFaceOccludedByNeighbor(Direction.DOWN, 0.8888889F, blockstate); ++ boolean flag3 = shouldRenderFace(p_234374_, p_234373_, Direction.NORTH, blockstate2); ++ boolean flag4 = shouldRenderFace(p_234374_, p_234373_, Direction.SOUTH, blockstate3); ++ boolean flag5 = shouldRenderFace(p_234374_, p_234373_, Direction.WEST, blockstate4); ++ boolean flag6 = shouldRenderFace(p_234374_, p_234373_, Direction.EAST, blockstate5); + if (flag1 || flag2 || flag6 || flag5 || flag3 || flag4) { + float f3 = p_234370_.getShade(Direction.DOWN, true); + float f4 = p_234370_.getShade(Direction.UP, true); @@ -180,15 +_,15 @@ float f57 = f4 * f; float f29 = f4 * f1; diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java index 813b8a398f..f20dcdec2f 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java @@ -1019,4 +1019,16 @@ default BubbleColumnDirection getBubbleColumnDirection(BlockState state) { return BubbleColumnDirection.NONE; } } + + /** + * Determines if a fluid adjacent to the block on the given side should not be rendered. + * + * @param state the block state of the block + * @param selfFace the face of this block that the fluid is adjacent to + * @param adjacentFluid the fluid that is touching that face + * @return true if this block should cause the fluid's face to not render + */ + default boolean shouldHideAdjacentFluidFace(BlockState state, Direction selfFace, FluidState adjacentFluid) { + return state.getFluidState().getType().isSame(adjacentFluid.getType()); + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java index 3e08a8b5b0..ce05f6783f 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java @@ -755,4 +755,15 @@ default boolean isEmpty() { default BubbleColumnDirection getBubbleColumnDirection() { return self().getBlock().getBubbleColumnDirection(self()); } + + /** + * Determines if a fluid adjacent to the block on the given side should not be rendered. + * + * @param selfFace the face of this block that the fluid is adjacent to + * @param adjacentFluid the fluid that is touching that face + * @return true if this block should cause the fluid's face to not render + */ + default boolean shouldHideAdjacentFluidFace(Direction selfFace, FluidState adjacentFluid) { + return self().getBlock().shouldHideAdjacentFluidFace(self(), selfFace, adjacentFluid); + } } diff --git a/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/blockstates/water_glass.json b/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/blockstates/water_glass.json new file mode 100644 index 0000000000..ed35a1c6c0 --- /dev/null +++ b/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/blockstates/water_glass.json @@ -0,0 +1,7 @@ +{ + "variants": { + "": { + "model": "neotests_test_water_glass_face_removal:block/water_glass" + } + } +} \ No newline at end of file diff --git a/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/lang/en_us.json b/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/lang/en_us.json new file mode 100644 index 0000000000..e016ca7125 --- /dev/null +++ b/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "block.neotests_test_water_glass_face_removal.water_glass": "Water Glass" +} \ No newline at end of file diff --git a/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/models/block/water_glass.json b/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/models/block/water_glass.json new file mode 100644 index 0000000000..10740130a9 --- /dev/null +++ b/tests/src/generated/resources/assets/neotests_test_water_glass_face_removal/models/block/water_glass.json @@ -0,0 +1,7 @@ +{ + "parent": "minecraft:block/cube_all", + "render_type": "minecraft:cutout", + "textures": { + "all": "minecraft:block/glass" + } +} \ No newline at end of file diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/fluid/ClientFluidTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/ClientFluidTests.java new file mode 100644 index 0000000000..cc40433fce --- /dev/null +++ b/tests/src/main/java/net/neoforged/neoforge/debug/fluid/ClientFluidTests.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.debug.fluid; + +import net.minecraft.client.renderer.block.LiquidBlockRenderer; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.TransparentBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.neoforge.client.model.generators.BlockStateProvider; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.registration.RegistrationHelper; + +@ForEachTest(groups = ClientFluidTests.GROUP, side = Dist.CLIENT) +public class ClientFluidTests { + public static final String GROUP = "level.fluid.client"; + + static class WaterGlassBlock extends TransparentBlock { + private static final Direction HIDE_DIRECTION = Direction.NORTH; + + public WaterGlassBlock(Properties p_309186_) { + super(p_309186_); + } + + @Override + public boolean shouldHideAdjacentFluidFace(BlockState state, Direction selfFace, FluidState adjacentFluid) { + if (selfFace == HIDE_DIRECTION) { + return adjacentFluid.getFluidType() == Fluids.WATER.getFluidType(); + } else { + return super.shouldHideAdjacentFluidFace(state, selfFace, adjacentFluid); + } + } + } + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests if blocks can prevent neighboring fluids from rendering against them") + static void testWaterGlassFaceRemoval(final DynamicTest test, final RegistrationHelper reg) { + final var glass = reg.blocks().registerBlock("water_glass", WaterGlassBlock::new, BlockBehaviour.Properties.ofFullCopy(Blocks.GLASS)).withLang("Water Glass").withBlockItem(); + reg.provider(BlockStateProvider.class, prov -> prov.simpleBlock(glass.get(), prov.models() + .cubeAll("water_glass", ResourceLocation.withDefaultNamespace("block/glass")) + .renderType("cutout"))); + final var waterPosition = new BlockPos(1, 1, 2); + final var glassDirection = WaterGlassBlock.HIDE_DIRECTION.getOpposite(); + final var glassPosition = waterPosition.relative(glassDirection); + test.onGameTest(helper -> helper.startSequence() + .thenExecute(() -> helper.setBlock(glassPosition, glass.get().defaultBlockState())) + .thenExecute(() -> helper.setBlock(waterPosition, Blocks.WATER.defaultBlockState())) + // Check that the north side of the water is not rendered + .thenExecute(() -> helper.assertFalse( + LiquidBlockRenderer.shouldRenderFace( + helper.getBlockState(waterPosition).getFluidState(), + helper.getBlockState(waterPosition), + glassDirection, + helper.getBlockState(glassPosition)), + "Fluid face rendering is not skipped")) + .thenSucceed()); + } +}