From 3c3156f1c844c23f6d40ed3630c19306d24950b3 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Tue, 26 Dec 2023 01:08:13 +0000 Subject: [PATCH] Filtered wind effects and rendering, and uniform snow build-up --- .../client/FancyPrecipitationRenderer.java | 11 +- .../impl/data/world/WeatherChunkData.java | 105 ++++++++++++++++-- .../tempest/impl/mixin/LevelMixin.java | 2 +- .../tempest/impl/mixin/LivingEntityMixin.java | 2 +- .../impl/util/QuasiRandomChunkVisitor.java | 49 ++++++++ 5 files changed, 151 insertions(+), 18 deletions(-) create mode 100644 common/src/main/java/dev/lukebemish/tempest/impl/util/QuasiRandomChunkVisitor.java diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java b/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java index 20ab7fb..538af16 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/client/FancyPrecipitationRenderer.java @@ -58,7 +58,7 @@ public void tickWeather(Camera camera, int ticks) { if (particlePos.getY() > level.getMinBuildHeight() && particlePos.getY() <= cameraPos.getY() + 10 && particlePos.getY() >= cameraPos.getY() - 10) { var chunk = level.getChunkAt(particlePos); var data = Services.PLATFORM.getChunkData(chunk); - var status = data.getWeatherStatus(particlePos); + var status = data.getWeatherStatusWindAware(particlePos); if (status != null) { if (random.nextFloat() <= status.intensity) { if (status.category == WeatherCategory.RAIN || status.category == WeatherCategory.SLEET || status.category == WeatherCategory.HAIL) { @@ -93,7 +93,7 @@ public void tickWeather(Camera camera, int ticks) { BlockPos soundPos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, cameraPos.offset(k, 0, l)); var chunk = level.getChunkAt(soundPos); var data = Services.PLATFORM.getChunkData(chunk); - var status = data.getWeatherStatus(soundPos); + var status = data.getWeatherStatusWindAware(soundPos); if (status != null && random.nextInt(3) < this.rainSoundTime++) { this.rainSoundTime = 0; if (status.category == WeatherCategory.RAIN || status.category == WeatherCategory.SLEET || status.category == WeatherCategory.HAIL) { @@ -129,12 +129,15 @@ public void renderWeather(LightTexture lightTexture, float partialTick, double c float time = (float) ticks + partialTick; RenderSystem.setShader(GameRenderer::getParticleShader); BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); + BlockPos.MutableBlockPos checkingPos = new BlockPos.MutableBlockPos(); for(int z = floorZ - layers; z <= floorZ + layers; ++z) { for (int x = floorX - layers; x <= floorX + layers; ++x) { int sizeIdx = (z - floorZ + 16) * 32 + x - floorX + 16; float sizeX = this.rainSizeX[sizeIdx]; float sizeZ = this.rainSizeZ[sizeIdx]; mutableBlockPos.set(x, camY, z); + checkingPos.setX(x); + checkingPos.setZ(z); //noinspection DataFlowIssue var chunk = level.getChunkAt(mutableBlockPos); var data = Services.PLATFORM.getChunkData(chunk); @@ -142,12 +145,13 @@ public void renderWeather(LightTexture lightTexture, float partialTick, double c if (status != null) { float precipLevel = status.intensity; int lowerY = level.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z); + checkingPos.setY(lowerY); int minY = Math.max(floorY - belowOffset, lowerY); int maxY = Math.max(floorY + layers, lowerY); int upperY = Math.max(floorY, lowerY); - if (minY != maxY) { + if (data.canSeeWind(checkingPos) && minY != maxY) { // we actually have somewhere to render // 3121: prime // 45238971: 3, 15079657 @@ -167,7 +171,6 @@ public void renderWeather(LightTexture lightTexture, float partialTick, double c bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE); } - //float slowVerticalOffset = -((ticks & (512 - 1)) + partialTick) / 512.0F; float speed = Mth.clamp(status.speed, 0, 1.25f); float relSpeed = (speed / 1.25f); relSpeed = 1 - (1 - relSpeed) * (1 - relSpeed) * (1 - relSpeed) * (1 - relSpeed); diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/data/world/WeatherChunkData.java b/common/src/main/java/dev/lukebemish/tempest/impl/data/world/WeatherChunkData.java index 57e8711..72c3516 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/data/world/WeatherChunkData.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/data/world/WeatherChunkData.java @@ -4,6 +4,7 @@ import dev.lukebemish.tempest.impl.Constants; import dev.lukebemish.tempest.impl.data.WeatherCategory; import dev.lukebemish.tempest.impl.data.WeatherMapData; +import dev.lukebemish.tempest.impl.util.QuasiRandomChunkVisitor; import it.unimi.dsi.fastutil.ints.*; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -13,11 +14,9 @@ import net.minecraft.nbt.Tag; import net.minecraft.server.level.ServerLevel; import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; -import net.minecraft.world.level.block.LiquidBlock; -import net.minecraft.world.level.block.SnowLayerBlock; +import net.minecraft.world.level.block.*; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.levelgen.Heightmap; @@ -49,6 +48,8 @@ public class WeatherChunkData { private static final int[] ZS = new int[] {0, 16, 0, 16}; private boolean initialized; + private int visitIndex = -1; + private final List windCheckPositions = new ArrayList<>(); private final @Nullable Runnable setDirtyCallback; @@ -254,6 +255,7 @@ public void tick(ServerLevel level, WeatherMapData.Built weatherMap) { windZ[i] = weatherMap.windZ().query(x+XS[i], z+ZS[i], gameTime); thunder[i] = Mth.clamp(weatherMap.thunder().query(x+XS[i], z+ZS[i], gameTime), -1, 1); } + recalculateWindCheckPositions(level); this.markDirty(); } @@ -289,18 +291,42 @@ public void tick(ServerLevel level, WeatherMapData.Built weatherMap) { } } - if (meltAndFreeze(level, x, z)) { - boolean tryAgain = meltAndFreeze(level, x, z); + if (meltAndFreeze(level)) { + boolean tryAgain = meltAndFreeze(level); if (tryAgain && level.random.nextBoolean()) { - meltAndFreeze(level, x, z); + meltAndFreeze(level); } } this.update(); } - private boolean meltAndFreeze(ServerLevel level, int x, int z) { - BlockPos waterySurface = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, level.getBlockRandomPos(x, 0, z, 15)).below(); + private void recalculateWindCheckPositions(Level level) { + var centerPos = chunk.getPos().getBlockAt(8, 0, 8); + float windX = windX(level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerPos)); + float windZ = windZ(level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, centerPos)); + float speed = Mth.sqrt(windX * windX + windZ * windZ); + float angle = Mth.clamp(speed, 0, 1.25F)/ 1.25F; + float singleX = -(float) Math.cos(angle) * windX / speed; + float singleY = (float) Math.sin(angle); + float singleZ = -(float) Math.cos(angle) * windZ / speed; + windCheckPositions.clear(); + float xOff = 0; + float yOff = 0; + float zOff = 0; + for (int i = 0; i < 12; i++) { + xOff += singleX; + yOff += singleY; + zOff += singleZ; + windCheckPositions.add(new BlockPos(Math.round(xOff), Math.round(yOff), Math.round(zOff))); + } + } + + private boolean meltAndFreeze(ServerLevel level) { + BlockPos waterySurface = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, QuasiRandomChunkVisitor.INSTANCE.inChunk(chunk, visitIndex, i -> this.visitIndex = i)).below(); + if (!canSeeWindIgnoreLeaves(waterySurface)) { + return false; + } float temp = temperature(waterySurface); float precip = precipitation(waterySurface); float thunder = thunder(waterySurface); @@ -310,8 +336,10 @@ private boolean meltAndFreeze(ServerLevel level, int x, int z) { if (isHailing(temp, precip, thunder)) { if (level.random.nextFloat() < 0.4 * precip) { BlockPos hailSurface = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, waterySurface).below(); - if (tryHailBreak(level, hailSurface.above())) { - tryHailBreak(level, hailSurface); + if (canSeeWind(hailSurface)) { + if (tryHailBreak(level, hailSurface.above())) { + tryHailBreak(level, hailSurface); + } } } repeat = level.random.nextFloat() < precip; @@ -376,7 +404,7 @@ private boolean tryFreezeBlock(ServerLevel level, BlockPos toFreeze) { if (state.getBlock() == Blocks.SNOW) { int levels = state.getValue(SnowLayerBlock.LAYERS); BlockState newState; - if (levels < 7) { + if (levels < 6) { newState = state.setValue(SnowLayerBlock.LAYERS, levels + 1); } else { if (level.random.nextFloat() < 0.75f) { @@ -391,6 +419,15 @@ private boolean tryFreezeBlock(ServerLevel level, BlockPos toFreeze) { if (hasSpaceForSnow(level, toSnow)) { level.setBlockAndUpdate(toSnow, Blocks.SNOW.defaultBlockState()); } + } else if (state.getBlock() == Blocks.POWDER_SNOW) { + BlockPos aboveSnow = toSnow.above(); + BlockState upState = level.getBlockState(aboveSnow); + level.setBlockAndUpdate(toSnow, Blocks.SNOW_BLOCK.defaultBlockState()); + if (upState.canBeReplaced() && Blocks.SNOW.defaultBlockState().canSurvive(level, aboveSnow)) { + if (hasSpaceForSnow(level, aboveSnow)) { + level.setBlockAndUpdate(aboveSnow, Blocks.SNOW.defaultBlockState()); + } + } } return level.random.nextFloat() < precip; } @@ -400,6 +437,36 @@ private boolean tryFreezeBlock(ServerLevel level, BlockPos toFreeze) { return level.random.nextFloat() < (level.random.nextBoolean() ? -temp : precip); } + public boolean canSeeWindIgnoreLeaves(BlockPos pos) { + var mutablePos = new BlockPos.MutableBlockPos(); + for (BlockPos check : windCheckPositions) { + mutablePos.setWithOffset(pos, check); + if (chunk.getLevel().isLoaded(mutablePos)) { + BlockState blockState = chunk.getLevel().getBlockState(mutablePos); + //noinspection deprecation + if ((blockState.blocksMotion() || !blockState.getFluidState().isEmpty()) && !(blockState.getBlock() instanceof LeavesBlock)) { + return false; + } + } + } + return true; + } + + public boolean canSeeWind(BlockPos pos) { + var mutablePos = new BlockPos.MutableBlockPos(); + for (BlockPos check : windCheckPositions) { + mutablePos.setWithOffset(pos, check); + if (chunk.getLevel().isLoaded(mutablePos)) { + BlockState blockState = chunk.getLevel().getBlockState(mutablePos); + //noinspection deprecation + if (blockState.blocksMotion() || !blockState.getFluidState().isEmpty()) { + return false; + } + } + } + return true; + } + private static boolean hasSpaceForSnow(ServerLevel level, BlockPos pos) { for (int i = 0; i < 4; i++) { pos = pos.below(); @@ -556,12 +623,18 @@ private boolean isFreezableWater(ServerLevel level, BlockPos toFreeze) { } void update(UpdateWeatherChunk updateWeatherChunk, Consumer posUpdater) { + boolean recalcChecks = this.windX != updateWeatherChunk.windX || this.windZ != updateWeatherChunk.windZ; + this.temperature = updateWeatherChunk.temperature; this.precipitation = updateWeatherChunk.precipitation; this.windX = updateWeatherChunk.windX; this.windZ = updateWeatherChunk.windZ; this.thunder = updateWeatherChunk.thunder; + if (recalcChecks) { + recalculateWindCheckPositions(chunk.getLevel()); + } + BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); for (int i = 0; i < updateWeatherChunk.posData.length; i++) { int key = updateWeatherChunk.posData[i]; @@ -572,6 +645,14 @@ void update(UpdateWeatherChunk updateWeatherChunk, Consumer posUpdater } } + public @Nullable WeatherCategory.WeatherStatus getWeatherStatusWindAware(BlockPos pos) { + var status = getWeatherStatus(pos); + if (canSeeWind(pos)) { + return status; + } + return null; + } + public @Nullable WeatherCategory.WeatherStatus getWeatherStatus(BlockPos pos) { float precip = precipitation(pos); if (precipitation(pos) > 0f) { diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LevelMixin.java b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LevelMixin.java index e525434..c96459d 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LevelMixin.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LevelMixin.java @@ -20,7 +20,7 @@ public class LevelMixin { //noinspection DataFlowIssue var level = (Level) (Object) this; var data = Services.PLATFORM.getChunkData(level.getChunkAt(pos)); - var status = data.getWeatherStatus(pos); + var status = data.getWeatherStatusWindAware(pos); if (status != null && (status.category == WeatherCategory.RAIN || status.category == WeatherCategory.SLEET)) { cir.setReturnValue(true); } diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java index a954ecd..181a54a 100644 --- a/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java +++ b/common/src/main/java/dev/lukebemish/tempest/impl/mixin/LivingEntityMixin.java @@ -75,7 +75,7 @@ public abstract class LivingEntityMixin extends Entity { var pos = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); if (level().canSeeSky(pos)){ var weatherData = Services.PLATFORM.getChunkData(this.level().getChunkAt(pos)); - var status = weatherData.getWeatherStatus(pos); + var status = weatherData.getWeatherStatusWindAware(pos); if (!this.level().isClientSide()) { if ((this.tickCount & 8) == 0 && status != null && status.category == WeatherCategory.HAIL) { var source = new DamageSource(this.level().registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(Constants.HAIL_DAMAGE_TYPE)); diff --git a/common/src/main/java/dev/lukebemish/tempest/impl/util/QuasiRandomChunkVisitor.java b/common/src/main/java/dev/lukebemish/tempest/impl/util/QuasiRandomChunkVisitor.java new file mode 100644 index 0000000..f0c2f4e --- /dev/null +++ b/common/src/main/java/dev/lukebemish/tempest/impl/util/QuasiRandomChunkVisitor.java @@ -0,0 +1,49 @@ +package dev.lukebemish.tempest.impl.util; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.chunk.LevelChunk; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.function.IntConsumer; + +public class QuasiRandomChunkVisitor { + public static final QuasiRandomChunkVisitor INSTANCE = new QuasiRandomChunkVisitor(9875331409874325L, 8); + private final int[] xs; + private final int[] zs; + public final int size; + + public QuasiRandomChunkVisitor(long seed, int iterations) { + List> positions = new ArrayList<>(); + for (int i = 0; i < iterations; i++) { + List> temp = new ArrayList<>(); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + temp.add(Pair.of(x, z)); + } + } + Collections.shuffle(temp, new Random(seed + i)); + positions.addAll(temp); + } + xs = new int[positions.size()]; + zs = new int[positions.size()]; + for (int i = 0; i < positions.size(); i++) { + xs[i] = positions.get(i).getFirst(); + zs[i] = positions.get(i).getSecond(); + } + size = positions.size(); + } + + public BlockPos inChunk(LevelChunk chunk, int index, IntConsumer consumer) { + if (index < 0 || index >= size) { + index = chunk.getLevel().getRandom().nextInt(size); + } + int x = xs[index]; + int z = zs[index]; + consumer.accept((index + 1) % size); + return chunk.getPos().getBlockAt(x, 0, z); + } +}