package net.minecraft.client.renderer.block; import com.mojang.blaze3d.vertex.VertexConsumer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BiomeColors; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.tags.FluidTags; import net.minecraft.util.Mth; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.HalfTransparentBlock; import net.minecraft.world.level.block.LeavesBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @Environment(EnvType.CLIENT) public class LiquidBlockRenderer { private static final float MAX_FLUID_HEIGHT = 0.8888889F; private final TextureAtlasSprite[] lavaIcons = new TextureAtlasSprite[2]; private final TextureAtlasSprite[] waterIcons = new TextureAtlasSprite[2]; private TextureAtlasSprite waterOverlay; protected void setupSprites() { this.lavaIcons[0] = Minecraft.getInstance().getModelManager().getBlockModelShaper().getBlockModel(Blocks.LAVA.defaultBlockState()).particleIcon(); this.lavaIcons[1] = ModelBakery.LAVA_FLOW.sprite(); this.waterIcons[0] = Minecraft.getInstance().getModelManager().getBlockModelShaper().getBlockModel(Blocks.WATER.defaultBlockState()).particleIcon(); this.waterIcons[1] = ModelBakery.WATER_FLOW.sprite(); this.waterOverlay = ModelBakery.WATER_OVERLAY.sprite(); } private static boolean isNeighborSameFluid(FluidState firstState, FluidState secondState) { return secondState.getType().isSame(firstState.getType()); } private static boolean isFaceOccludedByState(Direction face, float height, BlockState state) { VoxelShape voxelShape = state.getFaceOcclusionShape(face.getOpposite()); if (voxelShape == Shapes.empty()) { return false; } else if (voxelShape == Shapes.block()) { boolean bl = height == 1.0F; return face != Direction.UP || bl; } else { VoxelShape voxelShape2 = Shapes.box(0.0, 0.0, 0.0, 1.0, height, 1.0); return Shapes.blockOccludes(voxelShape2, voxelShape, face); } } private static boolean isFaceOccludedByNeighbor(Direction face, float height, BlockState state) { return isFaceOccludedByState(face, height, state); } private static boolean isFaceOccludedBySelf(BlockState state, Direction face) { return isFaceOccludedByState(face.getOpposite(), 1.0F, state); } public static boolean shouldRenderFace(FluidState fluidState, BlockState blockState, Direction side, FluidState neighborFluid) { return !isFaceOccludedBySelf(blockState, side) && !isNeighborSameFluid(fluidState, neighborFluid); } public void tesselate(BlockAndTintGetter level, BlockPos pos, VertexConsumer buffer, BlockState blockState, FluidState fluidState) { boolean bl = fluidState.is(FluidTags.LAVA); TextureAtlasSprite[] textureAtlasSprites = bl ? this.lavaIcons : this.waterIcons; int i = bl ? 16777215 : BiomeColors.getAverageWaterColor(level, pos); float f = (i >> 16 & 0xFF) / 255.0F; float g = (i >> 8 & 0xFF) / 255.0F; float h = (i & 0xFF) / 255.0F; BlockState blockState2 = level.getBlockState(pos.relative(Direction.DOWN)); FluidState fluidState2 = blockState2.getFluidState(); BlockState blockState3 = level.getBlockState(pos.relative(Direction.UP)); FluidState fluidState3 = blockState3.getFluidState(); BlockState blockState4 = level.getBlockState(pos.relative(Direction.NORTH)); FluidState fluidState4 = blockState4.getFluidState(); BlockState blockState5 = level.getBlockState(pos.relative(Direction.SOUTH)); FluidState fluidState5 = blockState5.getFluidState(); BlockState blockState6 = level.getBlockState(pos.relative(Direction.WEST)); FluidState fluidState6 = blockState6.getFluidState(); BlockState blockState7 = level.getBlockState(pos.relative(Direction.EAST)); FluidState fluidState7 = blockState7.getFluidState(); boolean bl2 = !isNeighborSameFluid(fluidState, fluidState3); boolean bl3 = shouldRenderFace(fluidState, blockState, Direction.DOWN, fluidState2) && !isFaceOccludedByNeighbor(Direction.DOWN, 0.8888889F, blockState2); boolean bl4 = shouldRenderFace(fluidState, blockState, Direction.NORTH, fluidState4); boolean bl5 = shouldRenderFace(fluidState, blockState, Direction.SOUTH, fluidState5); boolean bl6 = shouldRenderFace(fluidState, blockState, Direction.WEST, fluidState6); boolean bl7 = shouldRenderFace(fluidState, blockState, Direction.EAST, fluidState7); if (bl2 || bl3 || bl7 || bl6 || bl4 || bl5) { float j = level.getShade(Direction.DOWN, true); float k = level.getShade(Direction.UP, true); float l = level.getShade(Direction.NORTH, true); float m = level.getShade(Direction.WEST, true); Fluid fluid = fluidState.getType(); float n = this.getHeight(level, fluid, pos, blockState, fluidState); float o; float p; float q; float r; if (n >= 1.0F) { o = 1.0F; p = 1.0F; q = 1.0F; r = 1.0F; } else { float s = this.getHeight(level, fluid, pos.north(), blockState4, fluidState4); float t = this.getHeight(level, fluid, pos.south(), blockState5, fluidState5); float u = this.getHeight(level, fluid, pos.east(), blockState7, fluidState7); float v = this.getHeight(level, fluid, pos.west(), blockState6, fluidState6); o = this.calculateAverageHeight(level, fluid, n, s, u, pos.relative(Direction.NORTH).relative(Direction.EAST)); p = this.calculateAverageHeight(level, fluid, n, s, v, pos.relative(Direction.NORTH).relative(Direction.WEST)); q = this.calculateAverageHeight(level, fluid, n, t, u, pos.relative(Direction.SOUTH).relative(Direction.EAST)); r = this.calculateAverageHeight(level, fluid, n, t, v, pos.relative(Direction.SOUTH).relative(Direction.WEST)); } float s = pos.getX() & 15; float t = pos.getY() & 15; float u = pos.getZ() & 15; float v = 0.001F; float w = bl3 ? 0.001F : 0.0F; if (bl2 && !isFaceOccludedByNeighbor(Direction.UP, Math.min(Math.min(p, r), Math.min(q, o)), blockState3)) { p -= 0.001F; r -= 0.001F; q -= 0.001F; o -= 0.001F; Vec3 vec3 = fluidState.getFlow(level, pos); float x; float z; float ab; float ad; float y; float aa; float ac; float ae; if (vec3.x == 0.0 && vec3.z == 0.0) { TextureAtlasSprite textureAtlasSprite = textureAtlasSprites[0]; x = textureAtlasSprite.getU(0.0F); y = textureAtlasSprite.getV(0.0F); z = x; aa = textureAtlasSprite.getV(1.0F); ab = textureAtlasSprite.getU(1.0F); ac = aa; ad = ab; ae = y; } else { TextureAtlasSprite textureAtlasSprite = textureAtlasSprites[1]; float af = (float)Mth.atan2(vec3.z, vec3.x) - (float) (Math.PI / 2); float ag = Mth.sin(af) * 0.25F; float ah = Mth.cos(af) * 0.25F; float ai = 0.5F; x = textureAtlasSprite.getU(0.5F + (-ah - ag)); y = textureAtlasSprite.getV(0.5F + (-ah + ag)); z = textureAtlasSprite.getU(0.5F + (-ah + ag)); aa = textureAtlasSprite.getV(0.5F + (ah + ag)); ab = textureAtlasSprite.getU(0.5F + (ah + ag)); ac = textureAtlasSprite.getV(0.5F + (ah - ag)); ad = textureAtlasSprite.getU(0.5F + (ah - ag)); ae = textureAtlasSprite.getV(0.5F + (-ah - ag)); } float aj = (x + z + ab + ad) / 4.0F; float af = (y + aa + ac + ae) / 4.0F; float ag = textureAtlasSprites[0].uvShrinkRatio(); x = Mth.lerp(ag, x, aj); z = Mth.lerp(ag, z, aj); ab = Mth.lerp(ag, ab, aj); ad = Mth.lerp(ag, ad, aj); y = Mth.lerp(ag, y, af); aa = Mth.lerp(ag, aa, af); ac = Mth.lerp(ag, ac, af); ae = Mth.lerp(ag, ae, af); int ak = this.getLightColor(level, pos); float ai = k * f; float al = k * g; float am = k * h; this.vertex(buffer, s + 0.0F, t + p, u + 0.0F, ai, al, am, x, y, ak); this.vertex(buffer, s + 0.0F, t + r, u + 1.0F, ai, al, am, z, aa, ak); this.vertex(buffer, s + 1.0F, t + q, u + 1.0F, ai, al, am, ab, ac, ak); this.vertex(buffer, s + 1.0F, t + o, u + 0.0F, ai, al, am, ad, ae, ak); if (fluidState.shouldRenderBackwardUpFace(level, pos.above())) { this.vertex(buffer, s + 0.0F, t + p, u + 0.0F, ai, al, am, x, y, ak); this.vertex(buffer, s + 1.0F, t + o, u + 0.0F, ai, al, am, ad, ae, ak); this.vertex(buffer, s + 1.0F, t + q, u + 1.0F, ai, al, am, ab, ac, ak); this.vertex(buffer, s + 0.0F, t + r, u + 1.0F, ai, al, am, z, aa, ak); } } if (bl3) { float xx = textureAtlasSprites[0].getU0(); float zx = textureAtlasSprites[0].getU1(); float abx = textureAtlasSprites[0].getV0(); float adx = textureAtlasSprites[0].getV1(); int an = this.getLightColor(level, pos.below()); float aax = j * f; float acx = j * g; float aex = j * h; this.vertex(buffer, s, t + w, u + 1.0F, aax, acx, aex, xx, adx, an); this.vertex(buffer, s, t + w, u, aax, acx, aex, xx, abx, an); this.vertex(buffer, s + 1.0F, t + w, u, aax, acx, aex, zx, abx, an); this.vertex(buffer, s + 1.0F, t + w, u + 1.0F, aax, acx, aex, zx, adx, an); } int ao = this.getLightColor(level, pos); for (Direction direction : Direction.Plane.HORIZONTAL) { float adx; float yx; float aax; float acx; float aex; float ap; boolean bl8; switch (direction) { case NORTH: adx = p; yx = o; aax = s; aex = s + 1.0F; acx = u + 0.001F; ap = u + 0.001F; bl8 = bl4; break; case SOUTH: adx = q; yx = r; aax = s + 1.0F; aex = s; acx = u + 1.0F - 0.001F; ap = u + 1.0F - 0.001F; bl8 = bl5; break; case WEST: adx = r; yx = p; aax = s + 0.001F; aex = s + 0.001F; acx = u + 1.0F; ap = u; bl8 = bl6; break; default: adx = o; yx = q; aax = s + 1.0F - 0.001F; aex = s + 1.0F - 0.001F; acx = u; ap = u + 1.0F; bl8 = bl7; } if (bl8 && !isFaceOccludedByNeighbor(direction, Math.max(adx, yx), level.getBlockState(pos.relative(direction)))) { BlockPos blockPos = pos.relative(direction); TextureAtlasSprite textureAtlasSprite2 = textureAtlasSprites[1]; if (!bl) { Block block = level.getBlockState(blockPos).getBlock(); if (block instanceof HalfTransparentBlock || block instanceof LeavesBlock) { textureAtlasSprite2 = this.waterOverlay; } } float ah = textureAtlasSprite2.getU(0.0F); float ai = textureAtlasSprite2.getU(0.5F); float al = textureAtlasSprite2.getV((1.0F - adx) * 0.5F); float am = textureAtlasSprite2.getV((1.0F - yx) * 0.5F); float aq = textureAtlasSprite2.getV(0.5F); float ar = direction.getAxis() == Direction.Axis.Z ? l : m; float as = k * ar * f; float at = k * ar * g; float au = k * ar * h; this.vertex(buffer, aax, t + adx, acx, as, at, au, ah, al, ao); this.vertex(buffer, aex, t + yx, ap, as, at, au, ai, am, ao); this.vertex(buffer, aex, t + w, ap, as, at, au, ai, aq, ao); this.vertex(buffer, aax, t + w, acx, as, at, au, ah, aq, ao); if (textureAtlasSprite2 != this.waterOverlay) { this.vertex(buffer, aax, t + w, acx, as, at, au, ah, aq, ao); this.vertex(buffer, aex, t + w, ap, as, at, au, ai, aq, ao); this.vertex(buffer, aex, t + yx, ap, as, at, au, ai, am, ao); this.vertex(buffer, aax, t + adx, acx, as, at, au, ah, al, ao); } } } } } private float calculateAverageHeight(BlockAndTintGetter level, Fluid fluid, float currentHeight, float height1, float height2, BlockPos pos) { if (!(height2 >= 1.0F) && !(height1 >= 1.0F)) { float[] fs = new float[2]; if (height2 > 0.0F || height1 > 0.0F) { float f = this.getHeight(level, fluid, pos); if (f >= 1.0F) { return 1.0F; } this.addWeightedHeight(fs, f); } this.addWeightedHeight(fs, currentHeight); this.addWeightedHeight(fs, height2); this.addWeightedHeight(fs, height1); return fs[0] / fs[1]; } else { return 1.0F; } } private void addWeightedHeight(float[] output, float height) { if (height >= 0.8F) { output[0] += height * 10.0F; output[1] += 10.0F; } else if (height >= 0.0F) { output[0] += height; output[1]++; } } private float getHeight(BlockAndTintGetter level, Fluid fluid, BlockPos pos) { BlockState blockState = level.getBlockState(pos); return this.getHeight(level, fluid, pos, blockState, blockState.getFluidState()); } private float getHeight(BlockAndTintGetter level, Fluid fluid, BlockPos pos, BlockState blockState, FluidState fluidState) { if (fluid.isSame(fluidState.getType())) { BlockState blockState2 = level.getBlockState(pos.above()); return fluid.isSame(blockState2.getFluidState().getType()) ? 1.0F : fluidState.getOwnHeight(); } else { return !blockState.isSolid() ? 0.0F : -1.0F; } } private void vertex(VertexConsumer buffer, float x, float y, float z, float red, float green, float blue, float u, float v, int packedLight) { buffer.addVertex(x, y, z).setColor(red, green, blue, 1.0F).setUv(u, v).setLight(packedLight).setNormal(0.0F, 1.0F, 0.0F); } private int getLightColor(BlockAndTintGetter level, BlockPos pos) { int i = LevelRenderer.getLightColor(level, pos); int j = LevelRenderer.getLightColor(level, pos.above()); int k = i & 0xFF; int l = j & 0xFF; int m = i >> 16 & 0xFF; int n = j >> 16 & 0xFF; return (k > l ? k : l) | (m > n ? m : n) << 16; } }