2025-11-24 22:52:51 +03:00

353 lines
16 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.mojang.logging.LogUtils
* org.joml.Matrix4f
* org.joml.Matrix4fc
* org.joml.Vector3f
* org.joml.Vector3fc
* org.joml.Vector4f
* org.joml.Vector4fc
* org.jspecify.annotations.Nullable
* org.slf4j.Logger
*/
package net.minecraft.client.renderer;
import com.mojang.blaze3d.buffers.GpuBuffer;
import com.mojang.blaze3d.buffers.GpuBufferSlice;
import com.mojang.blaze3d.buffers.Std140Builder;
import com.mojang.blaze3d.buffers.Std140SizeCalculator;
import com.mojang.blaze3d.pipeline.RenderPipeline;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.NativeImage;
import com.mojang.blaze3d.systems.RenderPass;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.GpuTextureView;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.logging.LogUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import net.minecraft.client.CloudStatus;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MappableRingBuffer;
import net.minecraft.client.renderer.RenderPipelines;
import net.minecraft.core.Direction;
import net.minecraft.resources.Identifier;
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.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
public class CloudRenderer
extends SimplePreparableReloadListener<Optional<TextureData>>
implements AutoCloseable {
private static final int FLAG_INSIDE_FACE = 16;
private static final int FLAG_USE_TOP_COLOR = 32;
private static final float CELL_SIZE_IN_BLOCKS = 12.0f;
private static final int TICKS_PER_CELL = 400;
private static final float BLOCKS_PER_SECOND = 0.6f;
private static final int UBO_SIZE = new Std140SizeCalculator().putVec4().putVec3().putVec3().get();
private static final Logger LOGGER = LogUtils.getLogger();
private static final Identifier TEXTURE_LOCATION = Identifier.withDefaultNamespace("textures/environment/clouds.png");
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 RelativeCameraPos prevRelativeCameraPos = RelativeCameraPos.INSIDE_CLOUDS;
private @Nullable CloudStatus prevType;
private @Nullable TextureData texture;
private int quadCount = 0;
private final MappableRingBuffer ubo = new MappableRingBuffer(() -> "Cloud UBO", 130, UBO_SIZE);
private @Nullable MappableRingBuffer utb;
/*
* Enabled aggressive exception aggregation
*/
@Override
protected Optional<TextureData> prepare(ResourceManager manager, ProfilerFiller profiler) {
try (InputStream input = manager.open(TEXTURE_LOCATION);){
NativeImage texture = NativeImage.read(input);
try {
int width = texture.getWidth();
int height = texture.getHeight();
long[] cells = new long[width * height];
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int color = texture.getPixel(x, y);
if (CloudRenderer.isCellEmpty(color)) {
cells[x + y * width] = 0L;
continue;
}
boolean north = CloudRenderer.isCellEmpty(texture.getPixel(x, Math.floorMod(y - 1, height)));
boolean east = CloudRenderer.isCellEmpty(texture.getPixel(Math.floorMod(x + 1, height), y));
boolean south = CloudRenderer.isCellEmpty(texture.getPixel(x, Math.floorMod(y + 1, height)));
boolean west = CloudRenderer.isCellEmpty(texture.getPixel(Math.floorMod(x - 1, height), y));
cells[x + y * width] = CloudRenderer.packCellData(color, north, east, south, west);
}
}
Optional<TextureData> optional = Optional.of(new TextureData(cells, width, height));
if (texture != null) {
texture.close();
}
return optional;
}
catch (Throwable throwable) {
if (texture != null) {
try {
texture.close();
}
catch (Throwable throwable2) {
throwable.addSuppressed(throwable2);
}
}
throw throwable;
}
}
catch (IOException e) {
LOGGER.error("Failed to load cloud texture", (Throwable)e);
return Optional.empty();
}
}
private static int getSizeForCloudDistance(int radiusCells) {
int maxFacesPerCell = 4;
int maxCells = (radiusCells + 1) * 2 * ((radiusCells + 1) * 2) / 2;
int maxFaces = maxCells * 4 + 54;
return maxFaces * 3;
}
@Override
protected void apply(Optional<TextureData> preparations, ResourceManager manager, ProfilerFiller profiler) {
this.texture = preparations.orElse(null);
this.needsRebuild = true;
}
private static boolean isCellEmpty(int color) {
return ARGB.alpha(color) < 10;
}
private static long packCellData(int color, boolean north, boolean east, boolean south, boolean west) {
return (long)color << 4 | (long)((north ? 1 : 0) << 3) | (long)((east ? 1 : 0) << 2) | (long)((south ? 1 : 0) << 1) | (long)((west ? 1 : 0) << 0);
}
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 color, CloudStatus type, float bottomY, Vec3 cameraPosition, long gameTime, float partialTicks) {
GpuTextureView depthTexture;
GpuTextureView colorTexture;
GpuBuffer.MappedView view;
RenderPipeline renderPipeline;
float relativeBottomY;
float relativeTopY;
if (this.texture == null) {
return;
}
int radiusBlocks = Minecraft.getInstance().options.cloudRange().get() * 16;
int radiusCells = Mth.ceil((float)radiusBlocks / 12.0f);
int utbSize = CloudRenderer.getSizeForCloudDistance(radiusCells);
if (this.utb == null || this.utb.currentBuffer().size() != utbSize) {
if (this.utb != null) {
this.utb.close();
}
this.utb = new MappableRingBuffer(() -> "Cloud UTB", 258, utbSize);
}
RelativeCameraPos relativeCameraPos = (relativeTopY = (relativeBottomY = (float)((double)bottomY - cameraPosition.y)) + 4.0f) < 0.0f ? RelativeCameraPos.ABOVE_CLOUDS : (relativeBottomY > 0.0f ? RelativeCameraPos.BELOW_CLOUDS : RelativeCameraPos.INSIDE_CLOUDS);
float cloudOffset = (float)(gameTime % ((long)this.texture.width * 400L)) + partialTicks;
double cloudX = cameraPosition.x + (double)(cloudOffset * 0.030000001f);
double cloudZ = cameraPosition.z + (double)3.96f;
double textureWidthBlocks = (double)this.texture.width * 12.0;
double textureHeightBlocks = (double)this.texture.height * 12.0;
cloudX -= (double)Mth.floor(cloudX / textureWidthBlocks) * textureWidthBlocks;
cloudZ -= (double)Mth.floor(cloudZ / textureHeightBlocks) * textureHeightBlocks;
int cellX = Mth.floor(cloudX / 12.0);
int cellZ = Mth.floor(cloudZ / 12.0);
float xInCell = (float)(cloudX - (double)((float)cellX * 12.0f));
float zInCell = (float)(cloudZ - (double)((float)cellZ * 12.0f));
boolean fancyClouds = type == CloudStatus.FANCY;
RenderPipeline renderPipeline2 = renderPipeline = fancyClouds ? RenderPipelines.CLOUDS : RenderPipelines.FLAT_CLOUDS;
if (this.needsRebuild || cellX != this.prevCellX || cellZ != this.prevCellZ || relativeCameraPos != this.prevRelativeCameraPos || type != this.prevType) {
this.needsRebuild = false;
this.prevCellX = cellX;
this.prevCellZ = cellZ;
this.prevRelativeCameraPos = relativeCameraPos;
this.prevType = type;
this.utb.rotate();
view = RenderSystem.getDevice().createCommandEncoder().mapBuffer(this.utb.currentBuffer(), false, true);
try {
this.buildMesh(relativeCameraPos, view.data(), cellX, cellZ, fancyClouds, radiusCells);
this.quadCount = view.data().position() / 3;
}
finally {
if (view != null) {
view.close();
}
}
}
if (this.quadCount == 0) {
return;
}
view = RenderSystem.getDevice().createCommandEncoder().mapBuffer(this.ubo.currentBuffer(), false, true);
try {
Std140Builder.intoBuffer(view.data()).putVec4((Vector4fc)ARGB.vector4fFromARGB32(color)).putVec3(-xInCell, relativeBottomY, -zInCell).putVec3(12.0f, 4.0f, 12.0f);
}
finally {
if (view != null) {
view.close();
}
}
GpuBufferSlice dynamicTransforms = RenderSystem.getDynamicUniforms().writeTransform((Matrix4fc)RenderSystem.getModelViewMatrix(), (Vector4fc)new Vector4f(1.0f, 1.0f, 1.0f, 1.0f), (Vector3fc)new Vector3f(), (Matrix4fc)new Matrix4f());
RenderTarget mainRenderTarget = Minecraft.getInstance().getMainRenderTarget();
RenderTarget cloudTarget = Minecraft.getInstance().levelRenderer.getCloudsTarget();
RenderSystem.AutoStorageIndexBuffer indices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
GpuBuffer indexBuffer = indices.getBuffer(6 * this.quadCount);
if (cloudTarget != null) {
colorTexture = cloudTarget.getColorTextureView();
depthTexture = cloudTarget.getDepthTextureView();
} else {
colorTexture = mainRenderTarget.getColorTextureView();
depthTexture = mainRenderTarget.getDepthTextureView();
}
try (RenderPass renderPass = RenderSystem.getDevice().createCommandEncoder().createRenderPass(() -> "Clouds", colorTexture, OptionalInt.empty(), depthTexture, OptionalDouble.empty());){
renderPass.setPipeline(renderPipeline);
RenderSystem.bindDefaultUniforms(renderPass);
renderPass.setUniform("DynamicTransforms", dynamicTransforms);
renderPass.setIndexBuffer(indexBuffer, indices.type());
renderPass.setUniform("CloudInfo", this.ubo.currentBuffer());
renderPass.setUniform("CloudFaces", this.utb.currentBuffer());
renderPass.drawIndexed(0, 0, 6 * this.quadCount, 1);
}
}
private void buildMesh(RelativeCameraPos relativePos, ByteBuffer faceBuffer, int centerCellX, int centerCellZ, boolean extrude, int radiusCells) {
if (this.texture == null) {
return;
}
long[] cells = this.texture.cells;
int textureWidth = this.texture.width;
int textureHeight = this.texture.height;
for (int ring = 0; ring <= 2 * radiusCells; ++ring) {
for (int relativeCellX = -ring; relativeCellX <= ring; ++relativeCellX) {
int relativeCellZ = ring - Math.abs(relativeCellX);
if (relativeCellZ < 0 || relativeCellZ > radiusCells || relativeCellX * relativeCellX + relativeCellZ * relativeCellZ > radiusCells * radiusCells) continue;
if (relativeCellZ != 0) {
this.tryBuildCell(relativePos, faceBuffer, centerCellX, centerCellZ, extrude, relativeCellX, textureWidth, -relativeCellZ, textureHeight, cells);
}
this.tryBuildCell(relativePos, faceBuffer, centerCellX, centerCellZ, extrude, relativeCellX, textureWidth, relativeCellZ, textureHeight, cells);
}
}
}
private void tryBuildCell(RelativeCameraPos relativePos, ByteBuffer faceBuffer, int cellX, int cellZ, boolean extrude, int relativeCellX, int textureWidth, int relativeCellZ, int textureHeight, long[] cells) {
int indexY;
int indexX = Math.floorMod(cellX + relativeCellX, textureWidth);
long cellData = cells[indexX + (indexY = Math.floorMod(cellZ + relativeCellZ, textureHeight)) * textureWidth];
if (cellData == 0L) {
return;
}
if (extrude) {
this.buildExtrudedCell(relativePos, faceBuffer, relativeCellX, relativeCellZ, cellData);
} else {
this.buildFlatCell(faceBuffer, relativeCellX, relativeCellZ);
}
}
private void buildFlatCell(ByteBuffer faceBuffer, int x, int z) {
this.encodeFace(faceBuffer, x, z, Direction.DOWN, 32);
}
private void encodeFace(ByteBuffer faceBuffer, int x, int z, Direction direction, int flags) {
int dirAndFlags = direction.get3DDataValue() | flags;
dirAndFlags |= (x & 1) << 7;
faceBuffer.put((byte)(x >> 1)).put((byte)(z >> 1)).put((byte)(dirAndFlags |= (z & 1) << 6));
}
private void buildExtrudedCell(RelativeCameraPos relativePos, ByteBuffer faceBuffer, int x, int z, long cellData) {
boolean addInteriorFaces;
if (relativePos != RelativeCameraPos.BELOW_CLOUDS) {
this.encodeFace(faceBuffer, x, z, Direction.UP, 0);
}
if (relativePos != RelativeCameraPos.ABOVE_CLOUDS) {
this.encodeFace(faceBuffer, x, z, Direction.DOWN, 0);
}
if (CloudRenderer.isNorthEmpty(cellData) && z > 0) {
this.encodeFace(faceBuffer, x, z, Direction.NORTH, 0);
}
if (CloudRenderer.isSouthEmpty(cellData) && z < 0) {
this.encodeFace(faceBuffer, x, z, Direction.SOUTH, 0);
}
if (CloudRenderer.isWestEmpty(cellData) && x > 0) {
this.encodeFace(faceBuffer, x, z, Direction.WEST, 0);
}
if (CloudRenderer.isEastEmpty(cellData) && x < 0) {
this.encodeFace(faceBuffer, x, z, Direction.EAST, 0);
}
boolean bl = addInteriorFaces = Math.abs(x) <= 1 && Math.abs(z) <= 1;
if (addInteriorFaces) {
for (Direction direction : Direction.values()) {
this.encodeFace(faceBuffer, x, z, direction, 16);
}
}
}
public void markForRebuild() {
this.needsRebuild = true;
}
public void endFrame() {
this.ubo.rotate();
}
@Override
public void close() {
this.ubo.close();
if (this.utb != null) {
this.utb.close();
}
}
private static enum RelativeCameraPos {
ABOVE_CLOUDS,
INSIDE_CLOUDS,
BELOW_CLOUDS;
}
public record TextureData(long[] cells, int width, int height) {
}
}