Code/net/minecraft/world/level/biome/Climate.java

558 lines
23 KiB
Java
Raw Permalink Normal View History

2025-07-01 06:20:03 +00:00
package net.minecraft.world.level.biome;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import com.mojang.serialization.codecs.RecordCodecBuilder.Instance;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.core.QuartPos;
import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.Mth;
import net.minecraft.world.level.levelgen.DensityFunction;
import net.minecraft.world.level.levelgen.DensityFunctions;
public class Climate {
private static final boolean DEBUG_SLOW_BIOME_SEARCH = false;
private static final float QUANTIZATION_FACTOR = 10000.0F;
@VisibleForTesting
protected static final int PARAMETER_COUNT = 7;
public static Climate.TargetPoint target(float p_186782_, float p_186783_, float p_186784_, float p_186785_, float p_186786_, float p_186787_) {
return new Climate.TargetPoint(
quantizeCoord(p_186782_), quantizeCoord(p_186783_), quantizeCoord(p_186784_), quantizeCoord(p_186785_), quantizeCoord(p_186786_), quantizeCoord(p_186787_)
);
}
public static Climate.ParameterPoint parameters(
float p_186789_, float p_186790_, float p_186791_, float p_186792_, float p_186793_, float p_186794_, float p_186795_
) {
return new Climate.ParameterPoint(
Climate.Parameter.point(p_186789_),
Climate.Parameter.point(p_186790_),
Climate.Parameter.point(p_186791_),
Climate.Parameter.point(p_186792_),
Climate.Parameter.point(p_186793_),
Climate.Parameter.point(p_186794_),
quantizeCoord(p_186795_)
);
}
public static Climate.ParameterPoint parameters(
Climate.Parameter p_186799_,
Climate.Parameter p_186800_,
Climate.Parameter p_186801_,
Climate.Parameter p_186802_,
Climate.Parameter p_186803_,
Climate.Parameter p_186804_,
float p_186805_
) {
return new Climate.ParameterPoint(p_186799_, p_186800_, p_186801_, p_186802_, p_186803_, p_186804_, quantizeCoord(p_186805_));
}
public static long quantizeCoord(float p_186780_) {
return (long)(p_186780_ * 10000.0F);
}
public static float unquantizeCoord(long p_186797_) {
return (float)p_186797_ / 10000.0F;
}
public static Climate.Sampler empty() {
DensityFunction densityfunction = DensityFunctions.zero();
return new Climate.Sampler(densityfunction, densityfunction, densityfunction, densityfunction, densityfunction, densityfunction, List.of());
}
public static BlockPos findSpawnPosition(List<Climate.ParameterPoint> p_207843_, Climate.Sampler p_207844_) {
return (new Climate.SpawnFinder(p_207843_, p_207844_)).result.location();
}
interface DistanceMetric<T> {
long distance(Climate.RTree.Node<T> p_186810_, long[] p_186811_);
}
public record Parameter(long min, long max) {
public static final Codec<Climate.Parameter> CODEC = ExtraCodecs.intervalCodec(
Codec.floatRange(-2.0F, 2.0F),
"min",
"max",
(p_275164_, p_275165_) -> p_275164_.compareTo(p_275165_) > 0
? DataResult.error(() -> "Cannon construct interval, min > max (" + p_275164_ + " > " + p_275165_ + ")")
: DataResult.success(new Climate.Parameter(Climate.quantizeCoord(p_275164_), Climate.quantizeCoord(p_275165_))),
p_186841_ -> Climate.unquantizeCoord(p_186841_.min()),
p_186839_ -> Climate.unquantizeCoord(p_186839_.max())
);
public static Climate.Parameter point(float p_186821_) {
return span(p_186821_, p_186821_);
}
public static Climate.Parameter span(float p_186823_, float p_186824_) {
if (p_186823_ > p_186824_) {
throw new IllegalArgumentException("min > max: " + p_186823_ + " " + p_186824_);
} else {
return new Climate.Parameter(Climate.quantizeCoord(p_186823_), Climate.quantizeCoord(p_186824_));
}
}
public static Climate.Parameter span(Climate.Parameter p_186830_, Climate.Parameter p_186831_) {
if (p_186830_.min() > p_186831_.max()) {
throw new IllegalArgumentException("min > max: " + p_186830_ + " " + p_186831_);
} else {
return new Climate.Parameter(p_186830_.min(), p_186831_.max());
}
}
@Override
public String toString() {
return this.min == this.max
? String.format(Locale.ROOT, "%d", this.min)
: String.format(Locale.ROOT, "[%d-%d]", this.min, this.max);
}
public long distance(long p_186826_) {
long i = p_186826_ - this.max;
long j = this.min - p_186826_;
return i > 0L ? i : Math.max(j, 0L);
}
public long distance(Climate.Parameter p_186828_) {
long i = p_186828_.min() - this.max;
long j = this.min - p_186828_.max();
return i > 0L ? i : Math.max(j, 0L);
}
public Climate.Parameter span(@Nullable Climate.Parameter p_186837_) {
return p_186837_ == null
? this
: new Climate.Parameter(Math.min(this.min, p_186837_.min()), Math.max(this.max, p_186837_.max()));
}
}
public static class ParameterList<T> {
private final List<Pair<Climate.ParameterPoint, T>> values;
private final Climate.RTree<T> index;
public static <T> Codec<Climate.ParameterList<T>> codec(MapCodec<T> p_275523_) {
return ExtraCodecs.nonEmptyList(
RecordCodecBuilder.<Pair<Climate.ParameterPoint, T>>create(
p_275233_ -> p_275233_.group(
Climate.ParameterPoint.CODEC.fieldOf("parameters").forGetter(Pair::getFirst), p_275523_.forGetter(Pair::getSecond)
)
.apply(p_275233_, Pair::of)
)
.listOf()
)
.xmap(Climate.ParameterList::new, Climate.ParameterList::values);
}
public ParameterList(List<Pair<Climate.ParameterPoint, T>> p_186849_) {
this.values = p_186849_;
this.index = Climate.RTree.create(p_186849_);
}
public List<Pair<Climate.ParameterPoint, T>> values() {
return this.values;
}
public T findValue(Climate.TargetPoint p_204253_) {
return this.findValueIndex(p_204253_);
}
@VisibleForTesting
public T findValueBruteForce(Climate.TargetPoint p_204255_) {
Iterator<Pair<Climate.ParameterPoint, T>> iterator = this.values().iterator();
Pair<Climate.ParameterPoint, T> pair = iterator.next();
long i = pair.getFirst().fitness(p_204255_);
T t = pair.getSecond();
while (iterator.hasNext()) {
Pair<Climate.ParameterPoint, T> pair1 = iterator.next();
long j = pair1.getFirst().fitness(p_204255_);
if (j < i) {
i = j;
t = pair1.getSecond();
}
}
return t;
}
public T findValueIndex(Climate.TargetPoint p_186852_) {
return this.findValueIndex(p_186852_, Climate.RTree.Node::distance);
}
protected T findValueIndex(Climate.TargetPoint p_186854_, Climate.DistanceMetric<T> p_186855_) {
return this.index.search(p_186854_, p_186855_);
}
}
public record ParameterPoint(
Climate.Parameter temperature,
Climate.Parameter humidity,
Climate.Parameter continentalness,
Climate.Parameter erosion,
Climate.Parameter depth,
Climate.Parameter weirdness,
long offset
) {
public static final Codec<Climate.ParameterPoint> CODEC = RecordCodecBuilder.create(
p_186885_ -> p_186885_.group(
Climate.Parameter.CODEC.fieldOf("temperature").forGetter(p_186905_ -> p_186905_.temperature),
Climate.Parameter.CODEC.fieldOf("humidity").forGetter(p_186902_ -> p_186902_.humidity),
Climate.Parameter.CODEC.fieldOf("continentalness").forGetter(p_186897_ -> p_186897_.continentalness),
Climate.Parameter.CODEC.fieldOf("erosion").forGetter(p_186894_ -> p_186894_.erosion),
Climate.Parameter.CODEC.fieldOf("depth").forGetter(p_186891_ -> p_186891_.depth),
Climate.Parameter.CODEC.fieldOf("weirdness").forGetter(p_186888_ -> p_186888_.weirdness),
Codec.floatRange(0.0F, 1.0F).fieldOf("offset").xmap(Climate::quantizeCoord, Climate::unquantizeCoord).forGetter(p_186881_ -> p_186881_.offset)
)
.apply(p_186885_, Climate.ParameterPoint::new)
);
long fitness(Climate.TargetPoint p_186883_) {
return Mth.square(this.temperature.distance(p_186883_.temperature))
+ Mth.square(this.humidity.distance(p_186883_.humidity))
+ Mth.square(this.continentalness.distance(p_186883_.continentalness))
+ Mth.square(this.erosion.distance(p_186883_.erosion))
+ Mth.square(this.depth.distance(p_186883_.depth))
+ Mth.square(this.weirdness.distance(p_186883_.weirdness))
+ Mth.square(this.offset);
}
protected List<Climate.Parameter> parameterSpace() {
return ImmutableList.of(
this.temperature,
this.humidity,
this.continentalness,
this.erosion,
this.depth,
this.weirdness,
new Climate.Parameter(this.offset, this.offset)
);
}
}
protected static final class RTree<T> {
private static final int CHILDREN_PER_NODE = 6;
private final Climate.RTree.Node<T> root;
private final ThreadLocal<Climate.RTree.Leaf<T>> lastResult = new ThreadLocal<>();
private RTree(Climate.RTree.Node<T> p_186913_) {
this.root = p_186913_;
}
public static <T> Climate.RTree<T> create(List<Pair<Climate.ParameterPoint, T>> p_186936_) {
if (p_186936_.isEmpty()) {
throw new IllegalArgumentException("Need at least one value to build the search tree.");
} else {
int i = p_186936_.get(0).getFirst().parameterSpace().size();
if (i != 7) {
throw new IllegalStateException("Expecting parameter space to be 7, got " + i);
} else {
List<Climate.RTree.Leaf<T>> list = p_186936_.stream()
.map(p_186934_ -> new Climate.RTree.Leaf<T>(p_186934_.getFirst(), p_186934_.getSecond()))
.collect(Collectors.toCollection(ArrayList::new));
return new Climate.RTree<>(build(i, list));
}
}
}
private static <T> Climate.RTree.Node<T> build(int p_186921_, List<? extends Climate.RTree.Node<T>> p_186922_) {
if (p_186922_.isEmpty()) {
throw new IllegalStateException("Need at least one child to build a node");
} else if (p_186922_.size() == 1) {
return (Climate.RTree.Node<T>)p_186922_.get(0);
} else if (p_186922_.size() <= 6) {
p_186922_.sort(Comparator.comparingLong(p_186916_ -> {
long i1 = 0L;
for (int j1 = 0; j1 < p_186921_; j1++) {
Climate.Parameter climate$parameter = p_186916_.parameterSpace[j1];
i1 += Math.abs((climate$parameter.min() + climate$parameter.max()) / 2L);
}
return i1;
}));
return new Climate.RTree.SubTree<>(p_186922_);
} else {
long i = Long.MAX_VALUE;
int j = -1;
List<Climate.RTree.SubTree<T>> list = null;
for (int k = 0; k < p_186921_; k++) {
sort(p_186922_, p_186921_, k, false);
List<Climate.RTree.SubTree<T>> list1 = bucketize(p_186922_);
long l = 0L;
for (Climate.RTree.SubTree<T> subtree : list1) {
l += cost(subtree.parameterSpace);
}
if (i > l) {
i = l;
j = k;
list = list1;
}
}
sort(list, p_186921_, j, true);
return new Climate.RTree.SubTree<>(
list.stream().map(p_186919_ -> build(p_186921_, Arrays.asList(p_186919_.children))).collect(Collectors.toList())
);
}
}
private static <T> void sort(List<? extends Climate.RTree.Node<T>> p_186938_, int p_186939_, int p_186940_, boolean p_186941_) {
Comparator<Climate.RTree.Node<T>> comparator = comparator(p_186940_, p_186941_);
for (int i = 1; i < p_186939_; i++) {
comparator = comparator.thenComparing(comparator((p_186940_ + i) % p_186939_, p_186941_));
}
p_186938_.sort(comparator);
}
private static <T> Comparator<Climate.RTree.Node<T>> comparator(int p_186924_, boolean p_186925_) {
return Comparator.comparingLong(p_186929_ -> {
Climate.Parameter climate$parameter = p_186929_.parameterSpace[p_186924_];
long i = (climate$parameter.min() + climate$parameter.max()) / 2L;
return p_186925_ ? Math.abs(i) : i;
});
}
private static <T> List<Climate.RTree.SubTree<T>> bucketize(List<? extends Climate.RTree.Node<T>> p_186945_) {
List<Climate.RTree.SubTree<T>> list = Lists.newArrayList();
List<Climate.RTree.Node<T>> list1 = Lists.newArrayList();
int i = (int)Math.pow(6.0, Math.floor(Math.log(p_186945_.size() - 0.01) / Math.log(6.0)));
for (Climate.RTree.Node<T> node : p_186945_) {
list1.add(node);
if (list1.size() >= i) {
list.add(new Climate.RTree.SubTree<>(list1));
list1 = Lists.newArrayList();
}
}
if (!list1.isEmpty()) {
list.add(new Climate.RTree.SubTree<>(list1));
}
return list;
}
private static long cost(Climate.Parameter[] p_186943_) {
long i = 0L;
for (Climate.Parameter climate$parameter : p_186943_) {
i += Math.abs(climate$parameter.max() - climate$parameter.min());
}
return i;
}
static <T> List<Climate.Parameter> buildParameterSpace(List<? extends Climate.RTree.Node<T>> p_186947_) {
if (p_186947_.isEmpty()) {
throw new IllegalArgumentException("SubTree needs at least one child");
} else {
int i = 7;
List<Climate.Parameter> list = Lists.newArrayList();
for (int j = 0; j < 7; j++) {
list.add(null);
}
for (Climate.RTree.Node<T> node : p_186947_) {
for (int k = 0; k < 7; k++) {
list.set(k, node.parameterSpace[k].span(list.get(k)));
}
}
return list;
}
}
public T search(Climate.TargetPoint p_186931_, Climate.DistanceMetric<T> p_186932_) {
long[] along = p_186931_.toParameterArray();
Climate.RTree.Leaf<T> leaf = this.root.search(along, this.lastResult.get(), p_186932_);
this.lastResult.set(leaf);
return leaf.value;
}
static final class Leaf<T> extends Climate.RTree.Node<T> {
final T value;
Leaf(Climate.ParameterPoint p_186950_, T p_186951_) {
super(p_186950_.parameterSpace());
this.value = p_186951_;
}
@Override
protected Climate.RTree.Leaf<T> search(long[] p_186953_, @Nullable Climate.RTree.Leaf<T> p_186954_, Climate.DistanceMetric<T> p_186955_) {
return this;
}
}
abstract static class Node<T> {
protected final Climate.Parameter[] parameterSpace;
protected Node(List<Climate.Parameter> p_186958_) {
this.parameterSpace = p_186958_.toArray(new Climate.Parameter[0]);
}
protected abstract Climate.RTree.Leaf<T> search(long[] p_186961_, @Nullable Climate.RTree.Leaf<T> p_186962_, Climate.DistanceMetric<T> p_186963_);
protected long distance(long[] p_186960_) {
long i = 0L;
for (int j = 0; j < 7; j++) {
i += Mth.square(this.parameterSpace[j].distance(p_186960_[j]));
}
return i;
}
@Override
public String toString() {
return Arrays.toString((Object[])this.parameterSpace);
}
}
static final class SubTree<T> extends Climate.RTree.Node<T> {
final Climate.RTree.Node<T>[] children;
protected SubTree(List<? extends Climate.RTree.Node<T>> p_186967_) {
this(Climate.RTree.buildParameterSpace(p_186967_), p_186967_);
}
protected SubTree(List<Climate.Parameter> p_186969_, List<? extends Climate.RTree.Node<T>> p_186970_) {
super(p_186969_);
this.children = p_186970_.toArray(new Climate.RTree.Node[0]);
}
@Override
protected Climate.RTree.Leaf<T> search(long[] p_186972_, @Nullable Climate.RTree.Leaf<T> p_186973_, Climate.DistanceMetric<T> p_186974_) {
long i = p_186973_ == null ? Long.MAX_VALUE : p_186974_.distance(p_186973_, p_186972_);
Climate.RTree.Leaf<T> leaf = p_186973_;
for (Climate.RTree.Node<T> node : this.children) {
long j = p_186974_.distance(node, p_186972_);
if (i > j) {
Climate.RTree.Leaf<T> leaf1 = node.search(p_186972_, leaf, p_186974_);
long k = node == leaf1 ? j : p_186974_.distance(leaf1, p_186972_);
if (i > k) {
i = k;
leaf = leaf1;
}
}
}
return leaf;
}
}
}
public record Sampler(
DensityFunction temperature,
DensityFunction humidity,
DensityFunction continentalness,
DensityFunction erosion,
DensityFunction depth,
DensityFunction weirdness,
List<Climate.ParameterPoint> spawnTarget
) {
public Climate.TargetPoint sample(int p_186975_, int p_186976_, int p_186977_) {
int i = QuartPos.toBlock(p_186975_);
int j = QuartPos.toBlock(p_186976_);
int k = QuartPos.toBlock(p_186977_);
DensityFunction.SinglePointContext densityfunction$singlepointcontext = new DensityFunction.SinglePointContext(i, j, k);
return Climate.target(
(float)this.temperature.compute(densityfunction$singlepointcontext),
(float)this.humidity.compute(densityfunction$singlepointcontext),
(float)this.continentalness.compute(densityfunction$singlepointcontext),
(float)this.erosion.compute(densityfunction$singlepointcontext),
(float)this.depth.compute(densityfunction$singlepointcontext),
(float)this.weirdness.compute(densityfunction$singlepointcontext)
);
}
public BlockPos findSpawnPosition() {
return this.spawnTarget.isEmpty() ? BlockPos.ZERO : Climate.findSpawnPosition(this.spawnTarget, this);
}
}
static class SpawnFinder {
private static final long MAX_RADIUS = 2048L;
Climate.SpawnFinder.Result result;
SpawnFinder(List<Climate.ParameterPoint> p_207872_, Climate.Sampler p_207873_) {
this.result = getSpawnPositionAndFitness(p_207872_, p_207873_, 0, 0);
this.radialSearch(p_207872_, p_207873_, 2048.0F, 512.0F);
this.radialSearch(p_207872_, p_207873_, 512.0F, 32.0F);
}
private void radialSearch(List<Climate.ParameterPoint> p_207875_, Climate.Sampler p_207876_, float p_207877_, float p_207878_) {
float f = 0.0F;
float f1 = p_207878_;
BlockPos blockpos = this.result.location();
while (f1 <= p_207877_) {
int i = blockpos.getX() + (int)(Math.sin(f) * f1);
int j = blockpos.getZ() + (int)(Math.cos(f) * f1);
Climate.SpawnFinder.Result climate$spawnfinder$result = getSpawnPositionAndFitness(p_207875_, p_207876_, i, j);
if (climate$spawnfinder$result.fitness() < this.result.fitness()) {
this.result = climate$spawnfinder$result;
}
f += p_207878_ / f1;
if (f > Math.PI * 2) {
f = 0.0F;
f1 += p_207878_;
}
}
}
private static Climate.SpawnFinder.Result getSpawnPositionAndFitness(List<Climate.ParameterPoint> p_207880_, Climate.Sampler p_207881_, int p_207882_, int p_207883_) {
Climate.TargetPoint climate$targetpoint = p_207881_.sample(QuartPos.fromBlock(p_207882_), 0, QuartPos.fromBlock(p_207883_));
Climate.TargetPoint climate$targetpoint1 = new Climate.TargetPoint(
climate$targetpoint.temperature(),
climate$targetpoint.humidity(),
climate$targetpoint.continentalness(),
climate$targetpoint.erosion(),
0L,
climate$targetpoint.weirdness()
);
long i = Long.MAX_VALUE;
for (Climate.ParameterPoint climate$parameterpoint : p_207880_) {
i = Math.min(i, climate$parameterpoint.fitness(climate$targetpoint1));
}
long k = Mth.square(p_207882_) + Mth.square(p_207883_);
long j = i * Mth.square(2048L) + k;
return new Climate.SpawnFinder.Result(new BlockPos(p_207882_, 0, p_207883_), j);
}
record Result(BlockPos location, long fitness) {
}
}
public record TargetPoint(long temperature, long humidity, long continentalness, long erosion, long depth, long weirdness) {
@VisibleForTesting
protected long[] toParameterArray() {
return new long[]{this.temperature, this.humidity, this.continentalness, this.erosion, this.depth, this.weirdness, 0L};
}
}
}