mojira.dev
MC-219762

More performant noise blending algorithm in BlendedNoise

Currently, the noise blending algorithm in the overworld samples the main noise to linearly interpolate the min and max limit noises to create the terrain for the overworld. However, due to the number of octaves used in the main noise, the noise is regularly below 0 or above 1. due to this, the clamped lerp renders either the min or max limit noise completely unused. By only calculating the limit noises when they can affect the output (i.e., when the main noise is between 0 and 1), we can eliminate 16 octaves of calculation in the majority of instances. When the main noise is below 0, only the min limit noise is calculated, and when it's above 1, just the max limit noise is calculated. Here's the code that fixes it and makes it more performant:

public double sampleAndClampNoise(int x, int y, int z, double horizontalScale, double verticalScale, double horizontalStretch, double verticalStretch) {
    double mainNoiseSum = 0.0;
    double amplitude = 1.0;
    // First calculate the main noise to select which limit noise to use

    for (int i = 0; i < 8; i++) {
        ImprovedNoise mainNoise = this.mainNoise.getOctaveNoise(i);
        if (mainNoise != null) {
            mainNoiseSum += mainNoise.noise(PerlinNoise.wrap(x * horizontalStretch * amplitude), PerlinNoise.wrap(y * verticalStretch * amplitude), PerlinNoise.wrap(z * horizontalStretch * amplitude), verticalStretch * amplitude, y * verticalStretch * amplitude) / amplitude;
        }

        amplitude /= 2.0;
    }

    // Scale the main noise for use in interpolation
    double noiseLerp = (mainNoiseSum / 10.0 + 1.0) / 2.0;

    // Reset amplitude for calculation
    amplitude = 1.0;

    // If the interpolation is below 0, calculate and use the min limit noise
    if (noiseLerp <= 0) {
        double minNoiseSum = 0.0;
        for (int i = 0; i < 16; i++) {
            double scaledX = PerlinNoise.wrap(x * horizontalScale * amplitude);
            double scaledY = PerlinNoise.wrap(y * verticalScale * amplitude);
            double scaledZ = PerlinNoise.wrap(z * horizontalScale * amplitude);
            double scaledAmplitude = verticalScale * amplitude;

            ImprovedNoise minNoise = this.minLimitNoise.getOctaveNoise(i);
            if (minNoise != null) {
                minNoiseSum += minNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
            }

            amplitude /= 2.0;
        }

        // Return the scaled min noise
        return minNoiseSum / 512.0;
    } else if (noiseLerp >= 1) { // If the interpolation is above 1, calculate and use the max limit noise
        double maxNoiseSum = 0.0;
        for (int i = 0; i < 16; i++) {
            double scaledX = PerlinNoise.wrap(x * horizontalScale * amplitude);
            double scaledY = PerlinNoise.wrap(y * verticalScale * amplitude);
            double scaledZ = PerlinNoise.wrap(z * horizontalScale * amplitude);
            double scaledAmplitude = verticalScale * amplitude;

            ImprovedNoise maxNoise = this.maxLimitNoise.getOctaveNoise(i);
            if (maxNoise != null) {
                maxNoiseSum += maxNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
            }

            amplitude /= 2.0;
        }

        // Return the scaled max noise
        return maxNoiseSum / 512.0;
    } else { // If the interpolation is between 0 and 1, calculate both noises and blend it
        double minNoiseSum = 0.0;
        double maxNoiseSum = 0.0;
        for (int i = 0; i < 16; i++) {
            double scaledX = PerlinNoise.wrap(x * horizontalScale * amplitude);
            double scaledY = PerlinNoise.wrap(y * verticalScale * amplitude);
            double scaledZ = PerlinNoise.wrap(z * horizontalScale * amplitude);
            double scaledAmplitude = verticalScale * amplitude;

            ImprovedNoise minNoise = this.minLimitNoise.getOctaveNoise(i);
            if (minNoise != null) {
                minNoiseSum += minNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
            }

            ImprovedNoise maxNoise = this.maxLimitNoise.getOctaveNoise(i);
            if (maxNoise != null) {
                maxNoiseSum += maxNoise.noise(scaledX, scaledY, scaledZ, scaledAmplitude, y * scaledAmplitude) / amplitude;
            }

            amplitude /= 2.0;
        }

        // Return the blended noise
        return Mth.lerp(noiseLerp, minNoiseSum / 512.0, maxNoiseSum / 512.0);
    }
}

This change will retain the overworld's exact shape as it only changes how the interpolation works internally. This code is licensed under CC0. On a test where I generated chunks and measured with a profiler, these changes cut the amount of time this method took by half, a significant improvement as noise calculation is one of the most intensive tasks while generating chunks.

Comments 0

No comments.

SuperCoder79

(Unassigned)

Plausible

Normal

Performance

21w10a

1.17 Pre-release 1

Retrieved