362 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			362 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| 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.fabricmc.api.EnvType;
 | |
| import net.fabricmc.api.Environment;
 | |
| import net.minecraft.client.CloudStatus;
 | |
| import net.minecraft.client.Minecraft;
 | |
| import net.minecraft.core.Direction;
 | |
| 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.joml.Matrix4f;
 | |
| import org.joml.Vector3f;
 | |
| import org.joml.Vector4f;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| public class CloudRenderer extends SimplePreparableReloadListener<Optional<CloudRenderer.TextureData>> implements AutoCloseable {
 | |
| 	private static final int FLAG_INSIDE_FACE = 16;
 | |
| 	private static final int FLAG_USE_TOP_COLOR = 32;
 | |
| 	private static final int MAX_RADIUS_CHUNKS = 128;
 | |
| 	private static final float CELL_SIZE_IN_BLOCKS = 12.0F;
 | |
| 	private static final int UBO_SIZE = new Std140SizeCalculator().putVec4().putVec3().putVec3().get();
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	private static final ResourceLocation TEXTURE_LOCATION = ResourceLocation.withDefaultNamespace("textures/environment/clouds.png");
 | |
| 	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;
 | |
| 	private int quadCount = 0;
 | |
| 	private final RenderSystem.AutoStorageIndexBuffer indices = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
 | |
| 	private final MappableRingBuffer ubo = new MappableRingBuffer(() -> "Cloud UBO", 130, UBO_SIZE);
 | |
| 	@Nullable
 | |
| 	private MappableRingBuffer utb;
 | |
| 
 | |
| 	/**
 | |
| 	 * Performs any reloading that can be done off-thread, such as file IO
 | |
| 	 */
 | |
| 	protected Optional<CloudRenderer.TextureData> prepare(ResourceManager resourceManager, ProfilerFiller profiler) {
 | |
| 		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();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private static int getSizeForCloudDistance(int cloudDistance) {
 | |
| 		int i = 4;
 | |
| 		int j = (cloudDistance + 1) * 2 * (cloudDistance + 1) * 2 / 2;
 | |
| 		int k = j * 4 + 54;
 | |
| 		return k * 3;
 | |
| 	}
 | |
| 
 | |
| 	protected void apply(Optional<CloudRenderer.TextureData> object, ResourceManager resourceManager, ProfilerFiller profiler) {
 | |
| 		this.texture = (CloudRenderer.TextureData)object.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 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) {
 | |
| 			int i = Math.min(Minecraft.getInstance().options.cloudRange().get(), 128) * 16;
 | |
| 			int j = Mth.ceil(i / 12.0F);
 | |
| 			int k = getSizeForCloudDistance(j);
 | |
| 			if (this.utb == null || this.utb.currentBuffer().size() != k) {
 | |
| 				if (this.utb != null) {
 | |
| 					this.utb.close();
 | |
| 				}
 | |
| 
 | |
| 				this.utb = new MappableRingBuffer(() -> "Cloud UTB", 258, k);
 | |
| 			}
 | |
| 
 | |
| 			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 l = this.texture.height * 12.0;
 | |
| 			d -= Mth.floor(d / h) * h;
 | |
| 			e -= Mth.floor(e / l) * l;
 | |
| 			int m = Mth.floor(d / 12.0);
 | |
| 			int n = Mth.floor(e / 12.0);
 | |
| 			float o = (float)(d - m * 12.0F);
 | |
| 			float p = (float)(e - n * 12.0F);
 | |
| 			boolean bl = cloudStatus == CloudStatus.FANCY;
 | |
| 			RenderPipeline renderPipeline = bl ? RenderPipelines.CLOUDS : RenderPipelines.FLAT_CLOUDS;
 | |
| 			if (this.needsRebuild || m != this.prevCellX || n != this.prevCellZ || relativeCameraPos != this.prevRelativeCameraPos || cloudStatus != this.prevType) {
 | |
| 				this.needsRebuild = false;
 | |
| 				this.prevCellX = m;
 | |
| 				this.prevCellZ = n;
 | |
| 				this.prevRelativeCameraPos = relativeCameraPos;
 | |
| 				this.prevType = cloudStatus;
 | |
| 				this.utb.rotate();
 | |
| 
 | |
| 				try (GpuBuffer.MappedView mappedView = RenderSystem.getDevice().createCommandEncoder().mapBuffer(this.utb.currentBuffer(), false, true)) {
 | |
| 					this.buildMesh(relativeCameraPos, mappedView.data(), m, n, bl, j);
 | |
| 					this.quadCount = mappedView.data().position() / 3;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			if (this.quadCount != 0) {
 | |
| 				try (GpuBuffer.MappedView mappedView = RenderSystem.getDevice().createCommandEncoder().mapBuffer(this.ubo.currentBuffer(), false, true)) {
 | |
| 					Std140Builder.intoBuffer(mappedView.data())
 | |
| 						.putVec4(ARGB.redFloat(cloudColor), ARGB.greenFloat(cloudColor), ARGB.blueFloat(cloudColor), 1.0F)
 | |
| 						.putVec3(-o, f, -p)
 | |
| 						.putVec3(12.0F, 4.0F, 12.0F);
 | |
| 				}
 | |
| 
 | |
| 				GpuBufferSlice gpuBufferSlice = RenderSystem.getDynamicUniforms()
 | |
| 					.writeTransform(RenderSystem.getModelViewMatrix(), new Vector4f(1.0F, 1.0F, 1.0F, 1.0F), new Vector3f(), new Matrix4f(), 0.0F);
 | |
| 				RenderTarget renderTarget = Minecraft.getInstance().getMainRenderTarget();
 | |
| 				RenderTarget renderTarget2 = Minecraft.getInstance().levelRenderer.getCloudsTarget();
 | |
| 				RenderSystem.AutoStorageIndexBuffer autoStorageIndexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
 | |
| 				GpuBuffer gpuBuffer = autoStorageIndexBuffer.getBuffer(6 * this.quadCount);
 | |
| 				GpuTextureView gpuTextureView;
 | |
| 				GpuTextureView gpuTextureView2;
 | |
| 				if (renderTarget2 != null) {
 | |
| 					gpuTextureView = renderTarget2.getColorTextureView();
 | |
| 					gpuTextureView2 = renderTarget2.getDepthTextureView();
 | |
| 				} else {
 | |
| 					gpuTextureView = renderTarget.getColorTextureView();
 | |
| 					gpuTextureView2 = renderTarget.getDepthTextureView();
 | |
| 				}
 | |
| 
 | |
| 				try (RenderPass renderPass = RenderSystem.getDevice()
 | |
| 						.createCommandEncoder()
 | |
| 						.createRenderPass(() -> "Clouds", gpuTextureView, OptionalInt.empty(), gpuTextureView2, OptionalDouble.empty())) {
 | |
| 					renderPass.setPipeline(renderPipeline);
 | |
| 					RenderSystem.bindDefaultUniforms(renderPass);
 | |
| 					renderPass.setUniform("DynamicTransforms", gpuBufferSlice);
 | |
| 					renderPass.setIndexBuffer(gpuBuffer, autoStorageIndexBuffer.type());
 | |
| 					renderPass.setVertexBuffer(0, RenderSystem.getQuadVertexBuffer());
 | |
| 					renderPass.setUniform("CloudInfo", this.ubo.currentBuffer());
 | |
| 					renderPass.setUniform("CloudFaces", this.utb.currentBuffer());
 | |
| 					renderPass.setPipeline(renderPipeline);
 | |
| 					renderPass.drawIndexed(0, 0, 6 * this.quadCount, 1);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void buildMesh(CloudRenderer.RelativeCameraPos relativeCameraPos, ByteBuffer buffer, int cellX, int cellZ, boolean fancyClouds, int size) {
 | |
| 		if (this.texture != null) {
 | |
| 			long[] ls = this.texture.cells;
 | |
| 			int i = this.texture.width;
 | |
| 			int j = this.texture.height;
 | |
| 
 | |
| 			for (int k = 0; k <= 2 * size; k++) {
 | |
| 				for (int l = -k; l <= k; l++) {
 | |
| 					int m = k - Math.abs(l);
 | |
| 					if (m >= 0 && m <= size && l * l + m * m <= size * size) {
 | |
| 						if (m != 0) {
 | |
| 							this.tryBuildCell(relativeCameraPos, buffer, cellX, cellZ, fancyClouds, l, i, -m, j, ls);
 | |
| 						}
 | |
| 
 | |
| 						this.tryBuildCell(relativeCameraPos, buffer, cellX, cellZ, fancyClouds, l, i, m, j, ls);
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void tryBuildCell(
 | |
| 		CloudRenderer.RelativeCameraPos relativeCameraPos,
 | |
| 		ByteBuffer buffer,
 | |
| 		int cellX,
 | |
| 		int cellZ,
 | |
| 		boolean fancyClouds,
 | |
| 		int x,
 | |
| 		int width,
 | |
| 		int z,
 | |
| 		int height,
 | |
| 		long[] cells
 | |
| 	) {
 | |
| 		int i = Math.floorMod(cellX + x, width);
 | |
| 		int j = Math.floorMod(cellZ + z, height);
 | |
| 		long l = cells[i + j * width];
 | |
| 		if (l != 0L) {
 | |
| 			if (fancyClouds) {
 | |
| 				this.buildExtrudedCell(relativeCameraPos, buffer, x, z, l);
 | |
| 			} else {
 | |
| 				this.buildFlatCell(buffer, x, z);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void buildFlatCell(ByteBuffer buffer, int cellX, int cellZ) {
 | |
| 		this.encodeFace(buffer, cellX, cellZ, Direction.DOWN, 32);
 | |
| 	}
 | |
| 
 | |
| 	private void encodeFace(ByteBuffer buffer, int cellX, int cellZ, Direction face, int offset) {
 | |
| 		int i = face.get3DDataValue() | offset;
 | |
| 		i |= (cellX & 1) << 7;
 | |
| 		i |= (cellZ & 1) << 6;
 | |
| 		buffer.put((byte)(cellX >> 1)).put((byte)(cellZ >> 1)).put((byte)i);
 | |
| 	}
 | |
| 
 | |
| 	private void buildExtrudedCell(CloudRenderer.RelativeCameraPos relativeCameraPos, ByteBuffer buffer, int cellX, int cellZ, long cellData) {
 | |
| 		if (relativeCameraPos != CloudRenderer.RelativeCameraPos.BELOW_CLOUDS) {
 | |
| 			this.encodeFace(buffer, cellX, cellZ, Direction.UP, 0);
 | |
| 		}
 | |
| 
 | |
| 		if (relativeCameraPos != CloudRenderer.RelativeCameraPos.ABOVE_CLOUDS) {
 | |
| 			this.encodeFace(buffer, cellX, cellZ, Direction.DOWN, 0);
 | |
| 		}
 | |
| 
 | |
| 		if (isNorthEmpty(cellData) && cellZ > 0) {
 | |
| 			this.encodeFace(buffer, cellX, cellZ, Direction.NORTH, 0);
 | |
| 		}
 | |
| 
 | |
| 		if (isSouthEmpty(cellData) && cellZ < 0) {
 | |
| 			this.encodeFace(buffer, cellX, cellZ, Direction.SOUTH, 0);
 | |
| 		}
 | |
| 
 | |
| 		if (isWestEmpty(cellData) && cellX > 0) {
 | |
| 			this.encodeFace(buffer, cellX, cellZ, Direction.WEST, 0);
 | |
| 		}
 | |
| 
 | |
| 		if (isEastEmpty(cellData) && cellX < 0) {
 | |
| 			this.encodeFace(buffer, cellX, cellZ, Direction.EAST, 0);
 | |
| 		}
 | |
| 
 | |
| 		boolean bl = Math.abs(cellX) <= 1 && Math.abs(cellZ) <= 1;
 | |
| 		if (bl) {
 | |
| 			for (Direction direction : Direction.values()) {
 | |
| 				this.encodeFace(buffer, cellX, cellZ, direction, 16);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	public void markForRebuild() {
 | |
| 		this.needsRebuild = true;
 | |
| 	}
 | |
| 
 | |
| 	public void endFrame() {
 | |
| 		this.ubo.rotate();
 | |
| 	}
 | |
| 
 | |
| 	public void close() {
 | |
| 		this.ubo.close();
 | |
| 		if (this.utb != null) {
 | |
| 			this.utb.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) {
 | |
| 	}
 | |
| }
 |