minecraft-src/net/minecraft/world/level/levelgen/synth/PerlinNoise.java
2025-07-04 01:41:11 +03:00

232 lines
7.4 KiB
Java

package net.minecraft.world.level.levelgen.synth;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator;
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.stream.IntStream;
import net.minecraft.util.Mth;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.levelgen.PositionalRandomFactory;
import org.jetbrains.annotations.Nullable;
/**
* This class generates multiple octaves of perlin noise. Each individual octave is an instance of {@link net.minecraft.world.level.levelgen.synth.ImprovedNoise}.
* Mojang uses the term 'Perlin' to describe octaves or fBm (Fractal Brownian Motion) noise.
*/
public class PerlinNoise {
private static final int ROUND_OFF = 33554432;
private final ImprovedNoise[] noiseLevels;
private final int firstOctave;
private final DoubleList amplitudes;
private final double lowestFreqValueFactor;
private final double lowestFreqInputFactor;
private final double maxValue;
@Deprecated
public static PerlinNoise createLegacyForBlendedNoise(RandomSource random, IntStream octaves) {
return new PerlinNoise(
random, makeAmplitudes(new IntRBTreeSet((Collection<? extends Integer>)octaves.boxed().collect(ImmutableList.toImmutableList()))), false
);
}
@Deprecated
public static PerlinNoise createLegacyForLegacyNetherBiome(RandomSource random, int firstOctave, DoubleList amplitudes) {
return new PerlinNoise(random, Pair.of(firstOctave, amplitudes), false);
}
public static PerlinNoise create(RandomSource random, IntStream octaves) {
return create(random, (List<Integer>)octaves.boxed().collect(ImmutableList.toImmutableList()));
}
public static PerlinNoise create(RandomSource random, List<Integer> octaves) {
return new PerlinNoise(random, makeAmplitudes(new IntRBTreeSet(octaves)), true);
}
public static PerlinNoise create(RandomSource random, int firstOctave, double firstAmplitude, double... amplitudes) {
DoubleArrayList doubleArrayList = new DoubleArrayList(amplitudes);
doubleArrayList.add(0, firstAmplitude);
return new PerlinNoise(random, Pair.of(firstOctave, doubleArrayList), true);
}
public static PerlinNoise create(RandomSource random, int firstOctave, DoubleList amplitudes) {
return new PerlinNoise(random, Pair.of(firstOctave, amplitudes), true);
}
private static Pair<Integer, DoubleList> makeAmplitudes(IntSortedSet octaves) {
if (octaves.isEmpty()) {
throw new IllegalArgumentException("Need some octaves!");
} else {
int i = -octaves.firstInt();
int j = octaves.lastInt();
int k = i + j + 1;
if (k < 1) {
throw new IllegalArgumentException("Total number of octaves needs to be >= 1");
} else {
DoubleList doubleList = new DoubleArrayList(new double[k]);
IntBidirectionalIterator intBidirectionalIterator = octaves.iterator();
while (intBidirectionalIterator.hasNext()) {
int l = intBidirectionalIterator.nextInt();
doubleList.set(l + i, 1.0);
}
return Pair.of(-i, doubleList);
}
}
}
protected PerlinNoise(RandomSource random, Pair<Integer, DoubleList> octavesAndAmplitudes, boolean useNewFactory) {
this.firstOctave = octavesAndAmplitudes.getFirst();
this.amplitudes = octavesAndAmplitudes.getSecond();
int i = this.amplitudes.size();
int j = -this.firstOctave;
this.noiseLevels = new ImprovedNoise[i];
if (useNewFactory) {
PositionalRandomFactory positionalRandomFactory = random.forkPositional();
for (int k = 0; k < i; k++) {
if (this.amplitudes.getDouble(k) != 0.0) {
int l = this.firstOctave + k;
this.noiseLevels[k] = new ImprovedNoise(positionalRandomFactory.fromHashOf("octave_" + l));
}
}
} else {
ImprovedNoise improvedNoise = new ImprovedNoise(random);
if (j >= 0 && j < i) {
double d = this.amplitudes.getDouble(j);
if (d != 0.0) {
this.noiseLevels[j] = improvedNoise;
}
}
for (int kx = j - 1; kx >= 0; kx--) {
if (kx < i) {
double e = this.amplitudes.getDouble(kx);
if (e != 0.0) {
this.noiseLevels[kx] = new ImprovedNoise(random);
} else {
skipOctave(random);
}
} else {
skipOctave(random);
}
}
if (Arrays.stream(this.noiseLevels).filter(Objects::nonNull).count() != this.amplitudes.stream().filter(double_ -> double_ != 0.0).count()) {
throw new IllegalStateException("Failed to create correct number of noise levels for given non-zero amplitudes");
}
if (j < i - 1) {
throw new IllegalArgumentException("Positive octaves are temporarily disabled");
}
}
this.lowestFreqInputFactor = Math.pow(2.0, -j);
this.lowestFreqValueFactor = Math.pow(2.0, i - 1) / (Math.pow(2.0, i) - 1.0);
this.maxValue = this.edgeValue(2.0);
}
protected double maxValue() {
return this.maxValue;
}
private static void skipOctave(RandomSource random) {
random.consumeCount(262);
}
public double getValue(double x, double y, double z) {
return this.getValue(x, y, z, 0.0, 0.0, false);
}
@Deprecated
public double getValue(double x, double y, double z, double yScale, double yMax, boolean useFixedY) {
double d = 0.0;
double e = this.lowestFreqInputFactor;
double f = this.lowestFreqValueFactor;
for (int i = 0; i < this.noiseLevels.length; i++) {
ImprovedNoise improvedNoise = this.noiseLevels[i];
if (improvedNoise != null) {
double g = improvedNoise.noise(wrap(x * e), useFixedY ? -improvedNoise.yo : wrap(y * e), wrap(z * e), yScale * e, yMax * e);
d += this.amplitudes.getDouble(i) * g * f;
}
e *= 2.0;
f /= 2.0;
}
return d;
}
public double maxBrokenValue(double yMultiplier) {
return this.edgeValue(yMultiplier + 2.0);
}
private double edgeValue(double multiplier) {
double d = 0.0;
double e = this.lowestFreqValueFactor;
for (int i = 0; i < this.noiseLevels.length; i++) {
ImprovedNoise improvedNoise = this.noiseLevels[i];
if (improvedNoise != null) {
d += this.amplitudes.getDouble(i) * multiplier * e;
}
e /= 2.0;
}
return d;
}
/**
* @return A single octave of Perlin noise.
*/
@Nullable
public ImprovedNoise getOctaveNoise(int octave) {
return this.noiseLevels[this.noiseLevels.length - 1 - octave];
}
public static double wrap(double value) {
return value - Mth.lfloor(value / 3.3554432E7 + 0.5) * 3.3554432E7;
}
protected int firstOctave() {
return this.firstOctave;
}
protected DoubleList amplitudes() {
return this.amplitudes;
}
@VisibleForTesting
public void parityConfigString(StringBuilder builder) {
builder.append("PerlinNoise{");
List<String> list = this.amplitudes.stream().map(double_ -> String.format(Locale.ROOT, "%.2f", double_)).toList();
builder.append("first octave: ").append(this.firstOctave).append(", amplitudes: ").append(list).append(", noise levels: [");
for (int i = 0; i < this.noiseLevels.length; i++) {
builder.append(i).append(": ");
ImprovedNoise improvedNoise = this.noiseLevels[i];
if (improvedNoise == null) {
builder.append("null");
} else {
improvedNoise.parityConfigString(builder);
}
builder.append(", ");
}
builder.append("]");
builder.append("}");
}
}