package net.minecraft.client.renderer; import com.mojang.blaze3d.buffers.BufferType; import com.mojang.blaze3d.buffers.BufferUsage; import com.mojang.blaze3d.buffers.GpuBuffer; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.CommandEncoder; import com.mojang.blaze3d.systems.RenderPass; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.GpuTexture; import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.MeshData; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.logging.LogUtils; import java.io.IOException; import java.io.InputStream; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.CloudStatus; import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.SimplePreparableReloadListener; import net.minecraft.util.ARGB; import net.minecraft.util.Mth; import net.minecraft.util.profiling.ProfilerFiller; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class CloudRenderer extends SimplePreparableReloadListener> implements AutoCloseable { private static final Logger LOGGER = LogUtils.getLogger(); private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.withDefaultNamespace("textures/environment/clouds.png"); private static final float CELL_SIZE_IN_BLOCKS = 12.0F; private static final float HEIGHT_IN_BLOCKS = 4.0F; private static final float BLOCKS_PER_SECOND = 0.6F; private static final long EMPTY_CELL = 0L; private static final int COLOR_OFFSET = 4; private static final int NORTH_OFFSET = 3; private static final int EAST_OFFSET = 2; private static final int SOUTH_OFFSET = 1; private static final int WEST_OFFSET = 0; private boolean needsRebuild = true; private int prevCellX = Integer.MIN_VALUE; private int prevCellZ = Integer.MIN_VALUE; private CloudRenderer.RelativeCameraPos prevRelativeCameraPos = CloudRenderer.RelativeCameraPos.INSIDE_CLOUDS; @Nullable private CloudStatus prevType; @Nullable private CloudRenderer.TextureData texture; @Nullable private GpuBuffer vertexBuffer = null; private int indexCount = 0; private final RenderSystem.AutoStorageIndexBuffer indices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS); protected Optional prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) { try { InputStream inputStream = resourceManager.open(TEXTURE_LOCATION); Optional var20; try (NativeImage nativeImage = NativeImage.read(inputStream)) { int i = nativeImage.getWidth(); int j = nativeImage.getHeight(); long[] ls = new long[i * j]; for (int k = 0; k < j; k++) { for (int l = 0; l < i; l++) { int m = nativeImage.getPixel(l, k); if (isCellEmpty(m)) { ls[l + k * i] = 0L; } else { boolean bl = isCellEmpty(nativeImage.getPixel(l, Math.floorMod(k - 1, j))); boolean bl2 = isCellEmpty(nativeImage.getPixel(Math.floorMod(l + 1, j), k)); boolean bl3 = isCellEmpty(nativeImage.getPixel(l, Math.floorMod(k + 1, j))); boolean bl4 = isCellEmpty(nativeImage.getPixel(Math.floorMod(l - 1, j), k)); ls[l + k * i] = packCellData(m, bl, bl2, bl3, bl4); } } } var20 = Optional.of(new CloudRenderer.TextureData(ls, i, j)); } catch (Throwable var18) { if (inputStream != null) { try { inputStream.close(); } catch (Throwable var15) { var18.addSuppressed(var15); } } throw var18; } if (inputStream != null) { inputStream.close(); } return var20; } catch (IOException var19) { LOGGER.error("Failed to load cloud texture", (Throwable)var19); return Optional.empty(); } } protected void apply(Optional optional, ResourceManager resourceManager, ProfilerFiller profilerFiller) { this.texture = (CloudRenderer.TextureData)optional.orElse(null); this.needsRebuild = true; } private static boolean isCellEmpty(int color) { return ARGB.alpha(color) < 10; } private static long packCellData(int color, boolean northEmpty, boolean eastEmpty, boolean southEmpty, boolean westEmpty) { return (long)color << 4 | (northEmpty ? 1 : 0) << 3 | (eastEmpty ? 1 : 0) << 2 | (southEmpty ? 1 : 0) << 1 | (westEmpty ? 1 : 0) << 0; } private static int getColor(long cellData) { return (int)(cellData >> 4 & 4294967295L); } private static boolean isNorthEmpty(long cellData) { return (cellData >> 3 & 1L) != 0L; } private static boolean isEastEmpty(long cellData) { return (cellData >> 2 & 1L) != 0L; } private static boolean isSouthEmpty(long cellData) { return (cellData >> 1 & 1L) != 0L; } private static boolean isWestEmpty(long cellData) { return (cellData >> 0 & 1L) != 0L; } public void render(int cloudColor, CloudStatus cloudStatus, float height, Vec3 cameraPosition, float ticks) { if (this.texture != null) { float f = (float)(height - cameraPosition.y); float g = f + 4.0F; CloudRenderer.RelativeCameraPos relativeCameraPos; if (g < 0.0F) { relativeCameraPos = CloudRenderer.RelativeCameraPos.ABOVE_CLOUDS; } else if (f > 0.0F) { relativeCameraPos = CloudRenderer.RelativeCameraPos.BELOW_CLOUDS; } else { relativeCameraPos = CloudRenderer.RelativeCameraPos.INSIDE_CLOUDS; } double d = cameraPosition.x + ticks * 0.030000001F; double e = cameraPosition.z + 3.96F; double h = this.texture.width * 12.0; double i = this.texture.height * 12.0; d -= Mth.floor(d / h) * h; e -= Mth.floor(e / i) * i; int j = Mth.floor(d / 12.0); int k = Mth.floor(e / 12.0); float l = (float)(d - j * 12.0F); float m = (float)(e - k * 12.0F); boolean bl = cloudStatus == CloudStatus.FANCY; RenderPipeline renderPipeline = bl ? RenderPipelines.CLOUDS : RenderPipelines.FLAT_CLOUDS; if (this.needsRebuild || j != this.prevCellX || k != this.prevCellZ || relativeCameraPos != this.prevRelativeCameraPos || cloudStatus != this.prevType) { this.needsRebuild = false; this.prevCellX = j; this.prevCellZ = k; this.prevRelativeCameraPos = relativeCameraPos; this.prevType = cloudStatus; try (MeshData meshData = this.buildMesh(Tesselator.getInstance(), j, k, cloudStatus, relativeCameraPos, renderPipeline)) { if (meshData == null) { this.indexCount = 0; } else { if (this.vertexBuffer != null && this.vertexBuffer.size >= meshData.vertexBuffer().remaining()) { CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); commandEncoder.writeToBuffer(this.vertexBuffer, meshData.vertexBuffer(), 0); } else { if (this.vertexBuffer != null) { this.vertexBuffer.close(); } this.vertexBuffer = RenderSystem.getDevice() .createBuffer(() -> "Cloud vertex buffer", BufferType.VERTICES, BufferUsage.DYNAMIC_WRITE, meshData.vertexBuffer()); } this.indexCount = meshData.drawState().indexCount(); } } } if (this.indexCount != 0) { RenderSystem.setShaderColor(ARGB.redFloat(cloudColor), ARGB.greenFloat(cloudColor), ARGB.blueFloat(cloudColor), 1.0F); if (bl) { this.draw(RenderPipelines.CLOUDS_DEPTH_ONLY, l, f, m); } this.draw(renderPipeline, l, f, m); RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); } } } private void draw(RenderPipeline pipeline, float x, float y, float z) { RenderSystem.setModelOffset(-x, y, -z); RenderTarget renderTarget = Minecraft.getInstance().getMainRenderTarget(); RenderTarget renderTarget2 = Minecraft.getInstance().levelRenderer.getCloudsTarget(); GpuTexture gpuTexture; GpuTexture gpuTexture2; if (renderTarget2 != null) { gpuTexture = renderTarget2.getColorTexture(); gpuTexture2 = renderTarget2.getDepthTexture(); } else { gpuTexture = renderTarget.getColorTexture(); gpuTexture2 = renderTarget.getDepthTexture(); } GpuBuffer gpuBuffer = this.indices.getBuffer(this.indexCount); try (RenderPass renderPass = RenderSystem.getDevice() .createCommandEncoder() .createRenderPass(gpuTexture, OptionalInt.empty(), gpuTexture2, OptionalDouble.empty())) { renderPass.setPipeline(pipeline); renderPass.setIndexBuffer(gpuBuffer, this.indices.type()); renderPass.setVertexBuffer(0, this.vertexBuffer); renderPass.drawIndexed(0, this.indexCount); } RenderSystem.resetModelOffset(); } @Nullable private MeshData buildMesh( Tesselator tesselator, int cellX, int cellY, CloudStatus cloudStatus, CloudRenderer.RelativeCameraPos relativeCameraPos, RenderPipeline pipeline ) { float f = 0.8F; int i = ARGB.colorFromFloat(0.8F, 1.0F, 1.0F, 1.0F); int j = ARGB.colorFromFloat(0.8F, 0.9F, 0.9F, 0.9F); int k = ARGB.colorFromFloat(0.8F, 0.7F, 0.7F, 0.7F); int l = ARGB.colorFromFloat(0.8F, 0.8F, 0.8F, 0.8F); BufferBuilder bufferBuilder = tesselator.begin(pipeline.getVertexFormatMode(), pipeline.getVertexFormat()); this.buildMesh(relativeCameraPos, bufferBuilder, cellX, cellY, k, i, j, l, cloudStatus == CloudStatus.FANCY); return bufferBuilder.build(); } private void buildMesh( CloudRenderer.RelativeCameraPos relativeCameraPos, BufferBuilder bufferBuilder, int cellX, int cellZ, int bottomColor, int topColor, int sideColor, int frontColor, boolean fancyClouds ) { if (this.texture != null) { int i = 32; long[] ls = this.texture.cells; int j = this.texture.width; int k = this.texture.height; for (int l = -32; l <= 32; l++) { for (int m = -32; m <= 32; m++) { int n = Math.floorMod(cellX + m, j); int o = Math.floorMod(cellZ + l, k); long p = ls[n + o * j]; if (p != 0L) { int q = getColor(p); if (fancyClouds) { this.buildExtrudedCell( relativeCameraPos, bufferBuilder, ARGB.multiply(bottomColor, q), ARGB.multiply(topColor, q), ARGB.multiply(sideColor, q), ARGB.multiply(frontColor, q), m, l, p ); } else { this.buildFlatCell(bufferBuilder, ARGB.multiply(topColor, q), m, l); } } } } } } private void buildFlatCell(BufferBuilder bufferBuilder, int color, int x, int y) { float f = x * 12.0F; float g = f + 12.0F; float h = y * 12.0F; float i = h + 12.0F; bufferBuilder.addVertex(f, 0.0F, h).setColor(color); bufferBuilder.addVertex(f, 0.0F, i).setColor(color); bufferBuilder.addVertex(g, 0.0F, i).setColor(color); bufferBuilder.addVertex(g, 0.0F, h).setColor(color); } private void buildExtrudedCell( CloudRenderer.RelativeCameraPos relativeCameraPos, BufferBuilder bufferBuilder, int bottomColor, int topColor, int sideColor, int frontColor, int x, int y, long cellData ) { float f = x * 12.0F; float g = f + 12.0F; float h = 0.0F; float i = 4.0F; float j = y * 12.0F; float k = j + 12.0F; if (relativeCameraPos != CloudRenderer.RelativeCameraPos.BELOW_CLOUDS) { bufferBuilder.addVertex(f, 4.0F, j).setColor(topColor); bufferBuilder.addVertex(f, 4.0F, k).setColor(topColor); bufferBuilder.addVertex(g, 4.0F, k).setColor(topColor); bufferBuilder.addVertex(g, 4.0F, j).setColor(topColor); } if (relativeCameraPos != CloudRenderer.RelativeCameraPos.ABOVE_CLOUDS) { bufferBuilder.addVertex(g, 0.0F, j).setColor(bottomColor); bufferBuilder.addVertex(g, 0.0F, k).setColor(bottomColor); bufferBuilder.addVertex(f, 0.0F, k).setColor(bottomColor); bufferBuilder.addVertex(f, 0.0F, j).setColor(bottomColor); } if (isNorthEmpty(cellData) && y > 0) { bufferBuilder.addVertex(f, 0.0F, j).setColor(frontColor); bufferBuilder.addVertex(f, 4.0F, j).setColor(frontColor); bufferBuilder.addVertex(g, 4.0F, j).setColor(frontColor); bufferBuilder.addVertex(g, 0.0F, j).setColor(frontColor); } if (isSouthEmpty(cellData) && y < 0) { bufferBuilder.addVertex(g, 0.0F, k).setColor(frontColor); bufferBuilder.addVertex(g, 4.0F, k).setColor(frontColor); bufferBuilder.addVertex(f, 4.0F, k).setColor(frontColor); bufferBuilder.addVertex(f, 0.0F, k).setColor(frontColor); } if (isWestEmpty(cellData) && x > 0) { bufferBuilder.addVertex(f, 0.0F, k).setColor(sideColor); bufferBuilder.addVertex(f, 4.0F, k).setColor(sideColor); bufferBuilder.addVertex(f, 4.0F, j).setColor(sideColor); bufferBuilder.addVertex(f, 0.0F, j).setColor(sideColor); } if (isEastEmpty(cellData) && x < 0) { bufferBuilder.addVertex(g, 0.0F, j).setColor(sideColor); bufferBuilder.addVertex(g, 4.0F, j).setColor(sideColor); bufferBuilder.addVertex(g, 4.0F, k).setColor(sideColor); bufferBuilder.addVertex(g, 0.0F, k).setColor(sideColor); } boolean bl = Math.abs(x) <= 1 && Math.abs(y) <= 1; if (bl) { bufferBuilder.addVertex(g, 4.0F, j).setColor(topColor); bufferBuilder.addVertex(g, 4.0F, k).setColor(topColor); bufferBuilder.addVertex(f, 4.0F, k).setColor(topColor); bufferBuilder.addVertex(f, 4.0F, j).setColor(topColor); bufferBuilder.addVertex(f, 0.0F, j).setColor(bottomColor); bufferBuilder.addVertex(f, 0.0F, k).setColor(bottomColor); bufferBuilder.addVertex(g, 0.0F, k).setColor(bottomColor); bufferBuilder.addVertex(g, 0.0F, j).setColor(bottomColor); bufferBuilder.addVertex(g, 0.0F, j).setColor(frontColor); bufferBuilder.addVertex(g, 4.0F, j).setColor(frontColor); bufferBuilder.addVertex(f, 4.0F, j).setColor(frontColor); bufferBuilder.addVertex(f, 0.0F, j).setColor(frontColor); bufferBuilder.addVertex(f, 0.0F, k).setColor(frontColor); bufferBuilder.addVertex(f, 4.0F, k).setColor(frontColor); bufferBuilder.addVertex(g, 4.0F, k).setColor(frontColor); bufferBuilder.addVertex(g, 0.0F, k).setColor(frontColor); bufferBuilder.addVertex(f, 0.0F, j).setColor(sideColor); bufferBuilder.addVertex(f, 4.0F, j).setColor(sideColor); bufferBuilder.addVertex(f, 4.0F, k).setColor(sideColor); bufferBuilder.addVertex(f, 0.0F, k).setColor(sideColor); bufferBuilder.addVertex(g, 0.0F, k).setColor(sideColor); bufferBuilder.addVertex(g, 4.0F, k).setColor(sideColor); bufferBuilder.addVertex(g, 4.0F, j).setColor(sideColor); bufferBuilder.addVertex(g, 0.0F, j).setColor(sideColor); } } public void markForRebuild() { this.needsRebuild = true; } public void close() { if (this.vertexBuffer != null) { this.vertexBuffer.close(); } } @Environment(EnvType.CLIENT) static enum RelativeCameraPos { ABOVE_CLOUDS, INSIDE_CLOUDS, BELOW_CLOUDS; } @Environment(EnvType.CLIENT) public record TextureData(long[] cells, int width, int height) { } }