2025-11-24 22:52:51 +03:00

454 lines
21 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.google.common.annotations.VisibleForTesting
* com.google.common.collect.ImmutableList
* com.google.common.collect.Lists
* com.mojang.datafixers.kinds.App
* com.mojang.datafixers.kinds.Applicative
* com.mojang.datafixers.util.Pair
* com.mojang.serialization.Codec
* com.mojang.serialization.DataResult
* com.mojang.serialization.MapCodec
* com.mojang.serialization.codecs.RecordCodecBuilder
* org.jspecify.annotations.Nullable
*/
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.kinds.App;
import com.mojang.datafixers.kinds.Applicative;
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 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 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;
import org.jspecify.annotations.Nullable;
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 TargetPoint target(float temperature, float humidity, float continentalness, float erosion, float depth, float weirdness) {
return new TargetPoint(Climate.quantizeCoord(temperature), Climate.quantizeCoord(humidity), Climate.quantizeCoord(continentalness), Climate.quantizeCoord(erosion), Climate.quantizeCoord(depth), Climate.quantizeCoord(weirdness));
}
public static ParameterPoint parameters(float temperature, float humidity, float continentalness, float erosion, float depth, float weirdness, float offset) {
return new ParameterPoint(Parameter.point(temperature), Parameter.point(humidity), Parameter.point(continentalness), Parameter.point(erosion), Parameter.point(depth), Parameter.point(weirdness), Climate.quantizeCoord(offset));
}
public static ParameterPoint parameters(Parameter temperature, Parameter humidity, Parameter continentalness, Parameter erosion, Parameter depth, Parameter weirdness, float offset) {
return new ParameterPoint(temperature, humidity, continentalness, erosion, depth, weirdness, Climate.quantizeCoord(offset));
}
public static long quantizeCoord(float coord) {
return (long)(coord * 10000.0f);
}
public static float unquantizeCoord(long coord) {
return (float)coord / 10000.0f;
}
public static Sampler empty() {
DensityFunction zero = DensityFunctions.zero();
return new Sampler(zero, zero, zero, zero, zero, zero, List.of());
}
public static BlockPos findSpawnPosition(List<ParameterPoint> targetClimates, Sampler sampler) {
return new SpawnFinder(targetClimates, (Sampler)sampler).result.location();
}
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};
}
}
public record ParameterPoint(Parameter temperature, Parameter humidity, Parameter continentalness, Parameter erosion, Parameter depth, Parameter weirdness, long offset) {
public static final Codec<ParameterPoint> CODEC = RecordCodecBuilder.create(i -> i.group((App)Parameter.CODEC.fieldOf("temperature").forGetter(p -> p.temperature), (App)Parameter.CODEC.fieldOf("humidity").forGetter(p -> p.humidity), (App)Parameter.CODEC.fieldOf("continentalness").forGetter(p -> p.continentalness), (App)Parameter.CODEC.fieldOf("erosion").forGetter(p -> p.erosion), (App)Parameter.CODEC.fieldOf("depth").forGetter(p -> p.depth), (App)Parameter.CODEC.fieldOf("weirdness").forGetter(p -> p.weirdness), (App)Codec.floatRange((float)0.0f, (float)1.0f).fieldOf("offset").xmap(Climate::quantizeCoord, Climate::unquantizeCoord).forGetter(p -> p.offset)).apply((Applicative)i, ParameterPoint::new));
private long fitness(TargetPoint target) {
return Mth.square(this.temperature.distance(target.temperature)) + Mth.square(this.humidity.distance(target.humidity)) + Mth.square(this.continentalness.distance(target.continentalness)) + Mth.square(this.erosion.distance(target.erosion)) + Mth.square(this.depth.distance(target.depth)) + Mth.square(this.weirdness.distance(target.weirdness)) + Mth.square(this.offset);
}
protected List<Parameter> parameterSpace() {
return ImmutableList.of((Object)this.temperature, (Object)this.humidity, (Object)this.continentalness, (Object)this.erosion, (Object)this.depth, (Object)this.weirdness, (Object)new Parameter(this.offset, this.offset));
}
}
public record Parameter(long min, long max) {
public static final Codec<Parameter> CODEC = ExtraCodecs.intervalCodec(Codec.floatRange((float)-2.0f, (float)2.0f), "min", "max", (min, max) -> {
if (min.compareTo((Float)max) > 0) {
return DataResult.error(() -> "Cannon construct interval, min > max (" + min + " > " + max + ")");
}
return DataResult.success((Object)new Parameter(Climate.quantizeCoord(min.floatValue()), Climate.quantizeCoord(max.floatValue())));
}, p -> Float.valueOf(Climate.unquantizeCoord(p.min())), p -> Float.valueOf(Climate.unquantizeCoord(p.max())));
public static Parameter point(float min) {
return Parameter.span(min, min);
}
public static Parameter span(float min, float max) {
if (min > max) {
throw new IllegalArgumentException("min > max: " + min + " " + max);
}
return new Parameter(Climate.quantizeCoord(min), Climate.quantizeCoord(max));
}
public static Parameter span(Parameter min, Parameter max) {
if (min.min() > max.max()) {
throw new IllegalArgumentException("min > max: " + String.valueOf(min) + " " + String.valueOf(max));
}
return new Parameter(min.min(), max.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 target) {
long above = target - this.max;
long below = this.min - target;
if (above > 0L) {
return above;
}
return Math.max(below, 0L);
}
public long distance(Parameter target) {
long above = target.min() - this.max;
long below = this.min - target.max();
if (above > 0L) {
return above;
}
return Math.max(below, 0L);
}
public Parameter span(@Nullable Parameter other) {
return other == null ? this : new Parameter(Math.min(this.min, other.min()), Math.max(this.max, other.max()));
}
}
public record Sampler(DensityFunction temperature, DensityFunction humidity, DensityFunction continentalness, DensityFunction erosion, DensityFunction depth, DensityFunction weirdness, List<ParameterPoint> spawnTarget) {
public TargetPoint sample(int quartX, int quartY, int quartZ) {
int blockX = QuartPos.toBlock(quartX);
int blockY = QuartPos.toBlock(quartY);
int blockZ = QuartPos.toBlock(quartZ);
DensityFunction.SinglePointContext context = new DensityFunction.SinglePointContext(blockX, blockY, blockZ);
return Climate.target((float)this.temperature.compute(context), (float)this.humidity.compute(context), (float)this.continentalness.compute(context), (float)this.erosion.compute(context), (float)this.depth.compute(context), (float)this.weirdness.compute(context));
}
public BlockPos findSpawnPosition() {
if (this.spawnTarget.isEmpty()) {
return BlockPos.ZERO;
}
return Climate.findSpawnPosition(this.spawnTarget, this);
}
}
private static class SpawnFinder {
private static final long MAX_RADIUS = 2048L;
private Result result;
private SpawnFinder(List<ParameterPoint> targetClimates, Sampler sampler) {
this.result = SpawnFinder.getSpawnPositionAndFitness(targetClimates, sampler, 0, 0);
this.radialSearch(targetClimates, sampler, 2048.0f, 512.0f);
this.radialSearch(targetClimates, sampler, 512.0f, 32.0f);
}
private void radialSearch(List<ParameterPoint> targetClimates, Sampler sampler, float maxRadius, float radiusIncrement) {
float angle = 0.0f;
float radius = radiusIncrement;
BlockPos searchOrigin = this.result.location();
while (radius <= maxRadius) {
int z;
int x = searchOrigin.getX() + (int)(Math.sin(angle) * (double)radius);
Result candidate = SpawnFinder.getSpawnPositionAndFitness(targetClimates, sampler, x, z = searchOrigin.getZ() + (int)(Math.cos(angle) * (double)radius));
if (candidate.fitness() < this.result.fitness()) {
this.result = candidate;
}
if (!((double)(angle += radiusIncrement / radius) > Math.PI * 2)) continue;
angle = 0.0f;
radius += radiusIncrement;
}
}
private static Result getSpawnPositionAndFitness(List<ParameterPoint> targetClimates, Sampler sampler, int blockX, int blockZ) {
TargetPoint targetPoint = sampler.sample(QuartPos.fromBlock(blockX), 0, QuartPos.fromBlock(blockZ));
TargetPoint zeroDepthTargetPoint = new TargetPoint(targetPoint.temperature(), targetPoint.humidity(), targetPoint.continentalness(), targetPoint.erosion(), 0L, targetPoint.weirdness());
long minFitness = Long.MAX_VALUE;
for (ParameterPoint point : targetClimates) {
minFitness = Math.min(minFitness, point.fitness(zeroDepthTargetPoint));
}
long distanceBiasToWorldOrigin = Mth.square((long)blockX) + Mth.square((long)blockZ);
long fitnessWithDistance = minFitness * Mth.square(2048L) + distanceBiasToWorldOrigin;
return new Result(new BlockPos(blockX, 0, blockZ), fitnessWithDistance);
}
private record Result(BlockPos location, long fitness) {
}
}
public static class ParameterList<T> {
private final List<Pair<ParameterPoint, T>> values;
private final RTree<T> index;
public static <T> Codec<ParameterList<T>> codec(MapCodec<T> valueCodec) {
return ExtraCodecs.nonEmptyList(RecordCodecBuilder.create(i -> i.group((App)ParameterPoint.CODEC.fieldOf("parameters").forGetter(Pair::getFirst), (App)valueCodec.forGetter(Pair::getSecond)).apply((Applicative)i, Pair::of)).listOf()).xmap(ParameterList::new, ParameterList::values);
}
public ParameterList(List<Pair<ParameterPoint, T>> values) {
this.values = values;
this.index = RTree.create(values);
}
public List<Pair<ParameterPoint, T>> values() {
return this.values;
}
public T findValue(TargetPoint target) {
return this.findValueIndex(target);
}
@VisibleForTesting
public T findValueBruteForce(TargetPoint target) {
Iterator<Pair<ParameterPoint, T>> iterator = this.values().iterator();
Pair<ParameterPoint, T> first = iterator.next();
long bestFitness = ((ParameterPoint)first.getFirst()).fitness(target);
Object best = first.getSecond();
while (iterator.hasNext()) {
Pair<ParameterPoint, T> parameter = iterator.next();
long fitness = ((ParameterPoint)parameter.getFirst()).fitness(target);
if (fitness >= bestFitness) continue;
bestFitness = fitness;
best = parameter.getSecond();
}
return (T)best;
}
public T findValueIndex(TargetPoint target) {
return this.findValueIndex(target, RTree.Node::distance);
}
protected T findValueIndex(TargetPoint target, DistanceMetric<T> distanceMetric) {
return this.index.search(target, distanceMetric);
}
}
protected static final class RTree<T> {
private static final int CHILDREN_PER_NODE = 6;
private final Node<T> root;
private final ThreadLocal<@Nullable Leaf<T>> lastResult = new ThreadLocal();
private RTree(Node<T> root) {
this.root = root;
}
public static <T> RTree<T> create(List<Pair<ParameterPoint, T>> values) {
if (values.isEmpty()) {
throw new IllegalArgumentException("Need at least one value to build the search tree.");
}
int dimensions = ((ParameterPoint)values.get(0).getFirst()).parameterSpace().size();
if (dimensions != 7) {
throw new IllegalStateException("Expecting parameter space to be 7, got " + dimensions);
}
List leaves = values.stream().map(p -> new Leaf<Object>((ParameterPoint)p.getFirst(), p.getSecond())).collect(Collectors.toCollection(ArrayList::new));
return new RTree<T>(RTree.build(dimensions, leaves));
}
private static <T> Node<T> build(int dimensions, List<? extends Node<T>> children) {
if (children.isEmpty()) {
throw new IllegalStateException("Need at least one child to build a node");
}
if (children.size() == 1) {
return children.get(0);
}
if (children.size() <= 6) {
children.sort(Comparator.comparingLong(leaf -> {
long totalMagnitude = 0L;
for (int d = 0; d < dimensions; ++d) {
Parameter parameter = leaf.parameterSpace[d];
totalMagnitude += Math.abs((parameter.min() + parameter.max()) / 2L);
}
return totalMagnitude;
}));
return new SubTree(children);
}
long minCost = Long.MAX_VALUE;
int minDimension = -1;
List<SubTree<T>> minBuckets = null;
for (int d = 0; d < dimensions; ++d) {
RTree.sort(children, dimensions, d, false);
List<SubTree<T>> buckets = RTree.bucketize(children);
long totalCost = 0L;
for (SubTree<T> bucket : buckets) {
totalCost += RTree.cost(bucket.parameterSpace);
}
if (minCost <= totalCost) continue;
minCost = totalCost;
minDimension = d;
minBuckets = buckets;
}
RTree.sort(minBuckets, dimensions, minDimension, true);
return new SubTree(minBuckets.stream().map(b -> RTree.build(dimensions, Arrays.asList(b.children))).collect(Collectors.toList()));
}
private static <T> void sort(List<? extends Node<T>> children, int dimensions, int dimension, boolean absolute) {
Comparator<Node<Node<T>>> comparator = RTree.comparator(dimension, absolute);
for (int d = 1; d < dimensions; ++d) {
comparator = comparator.thenComparing(RTree.comparator((dimension + d) % dimensions, absolute));
}
children.sort(comparator);
}
private static <T> Comparator<Node<T>> comparator(int dimension, boolean absolute) {
return Comparator.comparingLong(leaf -> {
Parameter parameter = leaf.parameterSpace[dimension];
long center = (parameter.min() + parameter.max()) / 2L;
return absolute ? Math.abs(center) : center;
});
}
private static <T> List<SubTree<T>> bucketize(List<? extends Node<T>> nodes) {
ArrayList buckets = Lists.newArrayList();
ArrayList children = Lists.newArrayList();
int expectedChildrenCount = (int)Math.pow(6.0, Math.floor(Math.log((double)nodes.size() - 0.01) / Math.log(6.0)));
for (Node<T> child : nodes) {
children.add(child);
if (children.size() < expectedChildrenCount) continue;
buckets.add(new SubTree(children));
children = Lists.newArrayList();
}
if (!children.isEmpty()) {
buckets.add(new SubTree(children));
}
return buckets;
}
private static long cost(Parameter[] parameterSpace) {
long result = 0L;
for (Parameter parameter : parameterSpace) {
result += Math.abs(parameter.max() - parameter.min());
}
return result;
}
private static <T> List<Parameter> buildParameterSpace(List<? extends Node<T>> children) {
if (children.isEmpty()) {
throw new IllegalArgumentException("SubTree needs at least one child");
}
int dimensions = 7;
ArrayList bounds = Lists.newArrayList();
for (int d = 0; d < 7; ++d) {
bounds.add(null);
}
for (Node<T> child : children) {
for (int d = 0; d < 7; ++d) {
bounds.set(d, child.parameterSpace[d].span((Parameter)bounds.get(d)));
}
}
return bounds;
}
public T search(TargetPoint target, DistanceMetric<T> distanceMetric) {
long[] targetArray = target.toParameterArray();
Leaf<T> leaf = this.root.search(targetArray, this.lastResult.get(), distanceMetric);
this.lastResult.set(leaf);
return leaf.value;
}
static abstract class Node<T> {
protected final Parameter[] parameterSpace;
protected Node(List<Parameter> parameterSpace) {
this.parameterSpace = parameterSpace.toArray(new Parameter[0]);
}
protected abstract Leaf<T> search(long[] var1, @Nullable Leaf<T> var2, DistanceMetric<T> var3);
protected long distance(long[] target) {
long distance = 0L;
for (int i = 0; i < 7; ++i) {
distance += Mth.square(this.parameterSpace[i].distance(target[i]));
}
return distance;
}
public String toString() {
return Arrays.toString(this.parameterSpace);
}
}
private static final class SubTree<T>
extends Node<T> {
private final Node<T>[] children;
protected SubTree(List<? extends Node<T>> children) {
this(RTree.buildParameterSpace(children), children);
}
protected SubTree(List<Parameter> parameterSpace, List<? extends Node<T>> children) {
super(parameterSpace);
this.children = children.toArray(new Node[0]);
}
@Override
protected Leaf<T> search(long[] target, @Nullable Leaf<T> candidate, DistanceMetric<T> distanceMetric) {
long minDistance = candidate == null ? Long.MAX_VALUE : distanceMetric.distance(candidate, target);
Leaf<T> closestLeaf = candidate;
for (Node<T> child : this.children) {
long leafDistance;
long childDistance = distanceMetric.distance(child, target);
if (minDistance <= childDistance) continue;
Leaf<T> leaf = child.search(target, closestLeaf, distanceMetric);
long l = leafDistance = child == leaf ? childDistance : distanceMetric.distance(leaf, target);
if (minDistance <= leafDistance) continue;
minDistance = leafDistance;
closestLeaf = leaf;
}
return closestLeaf;
}
}
private static final class Leaf<T>
extends Node<T> {
private final T value;
private Leaf(ParameterPoint parameterPoint, T value) {
super(parameterPoint.parameterSpace());
this.value = value;
}
@Override
protected Leaf<T> search(long[] target, @Nullable Leaf<T> candidate, DistanceMetric<T> distanceMetric) {
return this;
}
}
}
static interface DistanceMetric<T> {
public long distance(RTree.Node<T> var1, long[] var2);
}
}