package net.minecraft.world.level.levelgen.synth; import com.google.common.annotations.VisibleForTesting; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.Locale; import java.util.stream.IntStream; import net.minecraft.util.KeyDispatchDataCodec; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.level.levelgen.DensityFunction; import net.minecraft.world.level.levelgen.XoroshiroRandomSource; /** * This class wraps three individual perlin noise octaves samplers. * It computes the octaves of the main noise, and then uses that as a linear interpolation value between the minimum and maximum limit noises. */ public class BlendedNoise implements DensityFunction.SimpleFunction { private static final Codec SCALE_RANGE = Codec.doubleRange(0.001, 1000.0); private static final MapCodec DATA_CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( SCALE_RANGE.fieldOf("xz_scale").forGetter(blendedNoise -> blendedNoise.xzScale), SCALE_RANGE.fieldOf("y_scale").forGetter(blendedNoise -> blendedNoise.yScale), SCALE_RANGE.fieldOf("xz_factor").forGetter(blendedNoise -> blendedNoise.xzFactor), SCALE_RANGE.fieldOf("y_factor").forGetter(blendedNoise -> blendedNoise.yFactor), Codec.doubleRange(1.0, 8.0).fieldOf("smear_scale_multiplier").forGetter(blendedNoise -> blendedNoise.smearScaleMultiplier) ) .apply(instance, BlendedNoise::createUnseeded) ); public static final KeyDispatchDataCodec CODEC = KeyDispatchDataCodec.of(DATA_CODEC); private final PerlinNoise minLimitNoise; private final PerlinNoise maxLimitNoise; private final PerlinNoise mainNoise; private final double xzMultiplier; private final double yMultiplier; private final double xzFactor; private final double yFactor; private final double smearScaleMultiplier; private final double maxValue; private final double xzScale; private final double yScale; public static BlendedNoise createUnseeded(double xzScale, double yScale, double xzFactor, double yFactor, double smearScaleMultiplier) { return new BlendedNoise(new XoroshiroRandomSource(0L), xzScale, yScale, xzFactor, yFactor, smearScaleMultiplier); } private BlendedNoise( PerlinNoise minLimitNoise, PerlinNoise maxLimitNoise, PerlinNoise mainNoise, double xzScale, double yScale, double xzFactor, double yFactor, double smearScaleMultiplier ) { this.minLimitNoise = minLimitNoise; this.maxLimitNoise = maxLimitNoise; this.mainNoise = mainNoise; this.xzScale = xzScale; this.yScale = yScale; this.xzFactor = xzFactor; this.yFactor = yFactor; this.smearScaleMultiplier = smearScaleMultiplier; this.xzMultiplier = 684.412 * this.xzScale; this.yMultiplier = 684.412 * this.yScale; this.maxValue = minLimitNoise.maxBrokenValue(this.yMultiplier); } @VisibleForTesting public BlendedNoise(RandomSource random, double xzScale, double yScale, double xzFactor, double yFactor, double smearScaleMultiplier) { this( PerlinNoise.createLegacyForBlendedNoise(random, IntStream.rangeClosed(-15, 0)), PerlinNoise.createLegacyForBlendedNoise(random, IntStream.rangeClosed(-15, 0)), PerlinNoise.createLegacyForBlendedNoise(random, IntStream.rangeClosed(-7, 0)), xzScale, yScale, xzFactor, yFactor, smearScaleMultiplier ); } public BlendedNoise withNewRandom(RandomSource random) { return new BlendedNoise(random, this.xzScale, this.yScale, this.xzFactor, this.yFactor, this.smearScaleMultiplier); } @Override public double compute(DensityFunction.FunctionContext context) { double d = context.blockX() * this.xzMultiplier; double e = context.blockY() * this.yMultiplier; double f = context.blockZ() * this.xzMultiplier; double g = d / this.xzFactor; double h = e / this.yFactor; double i = f / this.xzFactor; double j = this.yMultiplier * this.smearScaleMultiplier; double k = j / this.yFactor; double l = 0.0; double m = 0.0; double n = 0.0; boolean bl = true; double o = 1.0; for (int p = 0; p < 8; p++) { ImprovedNoise improvedNoise = this.mainNoise.getOctaveNoise(p); if (improvedNoise != null) { n += improvedNoise.noise(PerlinNoise.wrap(g * o), PerlinNoise.wrap(h * o), PerlinNoise.wrap(i * o), k * o, h * o) / o; } o /= 2.0; } double q = (n / 10.0 + 1.0) / 2.0; boolean bl2 = q >= 1.0; boolean bl3 = q <= 0.0; o = 1.0; for (int r = 0; r < 16; r++) { double s = PerlinNoise.wrap(d * o); double t = PerlinNoise.wrap(e * o); double u = PerlinNoise.wrap(f * o); double v = j * o; if (!bl2) { ImprovedNoise improvedNoise2 = this.minLimitNoise.getOctaveNoise(r); if (improvedNoise2 != null) { l += improvedNoise2.noise(s, t, u, v, e * o) / o; } } if (!bl3) { ImprovedNoise improvedNoise2 = this.maxLimitNoise.getOctaveNoise(r); if (improvedNoise2 != null) { m += improvedNoise2.noise(s, t, u, v, e * o) / o; } } o /= 2.0; } return Mth.clampedLerp(l / 512.0, m / 512.0, q) / 128.0; } @Override public double minValue() { return -this.maxValue(); } @Override public double maxValue() { return this.maxValue; } @VisibleForTesting public void parityConfigString(StringBuilder builder) { builder.append("BlendedNoise{minLimitNoise="); this.minLimitNoise.parityConfigString(builder); builder.append(", maxLimitNoise="); this.maxLimitNoise.parityConfigString(builder); builder.append(", mainNoise="); this.mainNoise.parityConfigString(builder); builder.append( String.format( Locale.ROOT, ", xzScale=%.3f, yScale=%.3f, xzMainScale=%.3f, yMainScale=%.3f, cellWidth=4, cellHeight=8", 684.412, 684.412, 8.555150000000001, 4.277575000000001 ) ) .append('}'); } @Override public KeyDispatchDataCodec codec() { return CODEC; } }