package net.minecraft.client.renderer; import com.mojang.blaze3d.vertex.VertexConsumer; import java.util.ArrayList; import java.util.List; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.core.Direction.Axis; import net.minecraft.core.particles.ParticleOptions; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ParticleStatus; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.tags.FluidTags; import net.minecraft.util.ARGB; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.CampfireBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.levelgen.Heightmap.Types; import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; @Environment(EnvType.CLIENT) public class WeatherEffectRenderer { private static final int RAIN_RADIUS = 10; private static final int RAIN_DIAMETER = 21; private static final ResourceLocation RAIN_LOCATION = ResourceLocation.withDefaultNamespace("textures/environment/rain.png"); private static final ResourceLocation SNOW_LOCATION = ResourceLocation.withDefaultNamespace("textures/environment/snow.png"); private static final int RAIN_TABLE_SIZE = 32; private static final int HALF_RAIN_TABLE_SIZE = 16; private int rainSoundTime; private final float[] columnSizeX = new float[1024]; private final float[] columnSizeZ = new float[1024]; public WeatherEffectRenderer() { for (int i = 0; i < 32; i++) { for (int j = 0; j < 32; j++) { float f = j - 16; float g = i - 16; float h = Mth.length(f, g); this.columnSizeX[i * 32 + j] = -g / h; this.columnSizeZ[i * 32 + j] = f / h; } } } public void render(Level level, MultiBufferSource bufferSource, int ticks, float partialTick, Vec3 cameraPosition) { float f = level.getRainLevel(partialTick); if (!(f <= 0.0F)) { int i = Minecraft.useFancyGraphics() ? 10 : 5; List list = new ArrayList(); List list2 = new ArrayList(); this.collectColumnInstances(level, ticks, partialTick, cameraPosition, i, list, list2); if (!list.isEmpty() || !list2.isEmpty()) { this.render(bufferSource, cameraPosition, i, f, list, list2); } } } private void collectColumnInstances( Level level, int ticks, float partialTick, Vec3 cameraPosition, int radius, List rainColumnInstances, List snowColumnInstances ) { int i = Mth.floor(cameraPosition.x); int j = Mth.floor(cameraPosition.y); int k = Mth.floor(cameraPosition.z); BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); RandomSource randomSource = RandomSource.create(); for (int l = k - radius; l <= k + radius; l++) { for (int m = i - radius; m <= i + radius; m++) { int n = level.getHeight(Types.MOTION_BLOCKING, m, l); int o = Math.max(j - radius, n); int p = Math.max(j + radius, n); if (p - o != 0) { Biome.Precipitation precipitation = this.getPrecipitationAt(level, mutableBlockPos.set(m, j, l)); if (precipitation != Biome.Precipitation.NONE) { int q = m * m * 3121 + m * 45238971 ^ l * l * 418711 + l * 13761; randomSource.setSeed(q); int r = Math.max(j, n); int s = LevelRenderer.getLightColor(level, mutableBlockPos.set(m, r, l)); if (precipitation == Biome.Precipitation.RAIN) { rainColumnInstances.add(this.createRainColumnInstance(randomSource, ticks, m, o, p, l, s, partialTick)); } else if (precipitation == Biome.Precipitation.SNOW) { snowColumnInstances.add(this.createSnowColumnInstance(randomSource, ticks, m, o, p, l, s, partialTick)); } } } } } } private void render( MultiBufferSource bufferSource, Vec3 cameraPosition, int radius, float rainLevel, List rainColumnInstances, List snowColumnInstances ) { if (!rainColumnInstances.isEmpty()) { RenderType renderType = RenderType.weather(RAIN_LOCATION, Minecraft.useShaderTransparency()); this.renderInstances(bufferSource.getBuffer(renderType), rainColumnInstances, cameraPosition, 1.0F, radius, rainLevel); } if (!snowColumnInstances.isEmpty()) { RenderType renderType = RenderType.weather(SNOW_LOCATION, Minecraft.useShaderTransparency()); this.renderInstances(bufferSource.getBuffer(renderType), snowColumnInstances, cameraPosition, 0.8F, radius, rainLevel); } } private WeatherEffectRenderer.ColumnInstance createRainColumnInstance( RandomSource random, int ticks, int x, int bottomY, int topY, int z, int lightCoords, float partialTick ) { int i = ticks & 131071; int j = x * x * 3121 + x * 45238971 + z * z * 418711 + z * 13761 & 0xFF; float f = 3.0F + random.nextFloat(); float g = -(i + j + partialTick) / 32.0F * f; float h = g % 32.0F; return new WeatherEffectRenderer.ColumnInstance(x, z, bottomY, topY, 0.0F, h, lightCoords); } private WeatherEffectRenderer.ColumnInstance createSnowColumnInstance( RandomSource random, int ticks, int x, int bottomY, int topY, int z, int lightCoords, float partialTick ) { float f = ticks + partialTick; float g = (float)(random.nextDouble() + f * 0.01F * (float)random.nextGaussian()); float h = (float)(random.nextDouble() + f * (float)random.nextGaussian() * 0.001F); float i = -((ticks & 511) + partialTick) / 512.0F; int j = LightTexture.pack((LightTexture.block(lightCoords) * 3 + 15) / 4, (LightTexture.sky(lightCoords) * 3 + 15) / 4); return new WeatherEffectRenderer.ColumnInstance(x, z, bottomY, topY, g, i + h, j); } private void renderInstances( VertexConsumer buffer, List columnInstances, Vec3 cameraPosition, float amount, int radius, float rainLevel ) { for (WeatherEffectRenderer.ColumnInstance columnInstance : columnInstances) { float f = (float)(columnInstance.x + 0.5 - cameraPosition.x); float g = (float)(columnInstance.z + 0.5 - cameraPosition.z); float h = (float)Mth.lengthSquared(f, g); float i = Mth.lerp(h / (radius * radius), amount, 0.5F) * rainLevel; int j = ARGB.white(i); int k = (columnInstance.z - Mth.floor(cameraPosition.z) + 16) * 32 + columnInstance.x - Mth.floor(cameraPosition.x) + 16; float l = this.columnSizeX[k] / 2.0F; float m = this.columnSizeZ[k] / 2.0F; float n = f - l; float o = f + l; float p = (float)(columnInstance.topY - cameraPosition.y); float q = (float)(columnInstance.bottomY - cameraPosition.y); float r = g - m; float s = g + m; float t = columnInstance.uOffset + 0.0F; float u = columnInstance.uOffset + 1.0F; float v = columnInstance.bottomY * 0.25F + columnInstance.vOffset; float w = columnInstance.topY * 0.25F + columnInstance.vOffset; buffer.addVertex(n, p, r).setUv(t, v).setColor(j).setLight(columnInstance.lightCoords); buffer.addVertex(o, p, s).setUv(u, v).setColor(j).setLight(columnInstance.lightCoords); buffer.addVertex(o, q, s).setUv(u, w).setColor(j).setLight(columnInstance.lightCoords); buffer.addVertex(n, q, r).setUv(t, w).setColor(j).setLight(columnInstance.lightCoords); } } public void tickRainParticles(ClientLevel level, Camera camera, int ticks, ParticleStatus particleStatus) { float f = level.getRainLevel(1.0F) / (Minecraft.useFancyGraphics() ? 1.0F : 2.0F); if (!(f <= 0.0F)) { RandomSource randomSource = RandomSource.create(ticks * 312987231L); BlockPos blockPos = BlockPos.containing(camera.getPosition()); BlockPos blockPos2 = null; int i = (int)(100.0F * f * f) / (particleStatus == ParticleStatus.DECREASED ? 2 : 1); for (int j = 0; j < i; j++) { int k = randomSource.nextInt(21) - 10; int l = randomSource.nextInt(21) - 10; BlockPos blockPos3 = level.getHeightmapPos(Types.MOTION_BLOCKING, blockPos.offset(k, 0, l)); if (blockPos3.getY() > level.getMinY() && blockPos3.getY() <= blockPos.getY() + 10 && blockPos3.getY() >= blockPos.getY() - 10 && this.getPrecipitationAt(level, blockPos3) == Biome.Precipitation.RAIN) { blockPos2 = blockPos3.below(); if (particleStatus == ParticleStatus.MINIMAL) { break; } double d = randomSource.nextDouble(); double e = randomSource.nextDouble(); BlockState blockState = level.getBlockState(blockPos2); FluidState fluidState = level.getFluidState(blockPos2); VoxelShape voxelShape = blockState.getCollisionShape(level, blockPos2); double g = voxelShape.max(Axis.Y, d, e); double h = fluidState.getHeight(level, blockPos2); double m = Math.max(g, h); ParticleOptions particleOptions = !fluidState.is(FluidTags.LAVA) && !blockState.is(Blocks.MAGMA_BLOCK) && !CampfireBlock.isLitCampfire(blockState) ? ParticleTypes.RAIN : ParticleTypes.SMOKE; level.addParticle(particleOptions, blockPos2.getX() + d, blockPos2.getY() + m, blockPos2.getZ() + e, 0.0, 0.0, 0.0); } } if (blockPos2 != null && randomSource.nextInt(3) < this.rainSoundTime++) { this.rainSoundTime = 0; if (blockPos2.getY() > blockPos.getY() + 1 && level.getHeightmapPos(Types.MOTION_BLOCKING, blockPos).getY() > Mth.floor((float)blockPos.getY())) { level.playLocalSound(blockPos2, SoundEvents.WEATHER_RAIN_ABOVE, SoundSource.WEATHER, 0.1F, 0.5F, false); } else { level.playLocalSound(blockPos2, SoundEvents.WEATHER_RAIN, SoundSource.WEATHER, 0.2F, 1.0F, false); } } } } private Biome.Precipitation getPrecipitationAt(Level level, BlockPos pos) { if (!level.getChunkSource().hasChunk(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()))) { return Biome.Precipitation.NONE; } else { Biome biome = level.getBiome(pos).value(); return biome.getPrecipitationAt(pos, level.getSeaLevel()); } } @Environment(EnvType.CLIENT) record ColumnInstance(int x, int z, int bottomY, int topY, float uOffset, float vOffset, int lightCoords) { } }