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 p_207843_, Climate.Sampler p_207844_) { return (new Climate.SpawnFinder(p_207843_, p_207844_)).result.location(); } interface DistanceMetric { long distance(Climate.RTree.Node p_186810_, long[] p_186811_); } public record Parameter(long min, long max) { public static final Codec 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 { private final List> values; private final Climate.RTree index; public static Codec> codec(MapCodec p_275523_) { return ExtraCodecs.nonEmptyList( RecordCodecBuilder.>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> p_186849_) { this.values = p_186849_; this.index = Climate.RTree.create(p_186849_); } public List> 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> iterator = this.values().iterator(); Pair pair = iterator.next(); long i = pair.getFirst().fitness(p_204255_); T t = pair.getSecond(); while (iterator.hasNext()) { Pair 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 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 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 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 { private static final int CHILDREN_PER_NODE = 6; private final Climate.RTree.Node root; private final ThreadLocal> lastResult = new ThreadLocal<>(); private RTree(Climate.RTree.Node p_186913_) { this.root = p_186913_; } public static Climate.RTree create(List> 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> list = p_186936_.stream() .map(p_186934_ -> new Climate.RTree.Leaf(p_186934_.getFirst(), p_186934_.getSecond())) .collect(Collectors.toCollection(ArrayList::new)); return new Climate.RTree<>(build(i, list)); } } } private static Climate.RTree.Node build(int p_186921_, List> 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)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> list = null; for (int k = 0; k < p_186921_; k++) { sort(p_186922_, p_186921_, k, false); List> list1 = bucketize(p_186922_); long l = 0L; for (Climate.RTree.SubTree 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 void sort(List> p_186938_, int p_186939_, int p_186940_, boolean p_186941_) { Comparator> 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 Comparator> 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 List> bucketize(List> p_186945_) { List> list = Lists.newArrayList(); List> 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 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 List buildParameterSpace(List> p_186947_) { if (p_186947_.isEmpty()) { throw new IllegalArgumentException("SubTree needs at least one child"); } else { int i = 7; List list = Lists.newArrayList(); for (int j = 0; j < 7; j++) { list.add(null); } for (Climate.RTree.Node 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 p_186932_) { long[] along = p_186931_.toParameterArray(); Climate.RTree.Leaf leaf = this.root.search(along, this.lastResult.get(), p_186932_); this.lastResult.set(leaf); return leaf.value; } static final class Leaf extends Climate.RTree.Node { final T value; Leaf(Climate.ParameterPoint p_186950_, T p_186951_) { super(p_186950_.parameterSpace()); this.value = p_186951_; } @Override protected Climate.RTree.Leaf search(long[] p_186953_, @Nullable Climate.RTree.Leaf p_186954_, Climate.DistanceMetric p_186955_) { return this; } } abstract static class Node { protected final Climate.Parameter[] parameterSpace; protected Node(List p_186958_) { this.parameterSpace = p_186958_.toArray(new Climate.Parameter[0]); } protected abstract Climate.RTree.Leaf search(long[] p_186961_, @Nullable Climate.RTree.Leaf p_186962_, Climate.DistanceMetric 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 extends Climate.RTree.Node { final Climate.RTree.Node[] children; protected SubTree(List> p_186967_) { this(Climate.RTree.buildParameterSpace(p_186967_), p_186967_); } protected SubTree(List p_186969_, List> p_186970_) { super(p_186969_); this.children = p_186970_.toArray(new Climate.RTree.Node[0]); } @Override protected Climate.RTree.Leaf search(long[] p_186972_, @Nullable Climate.RTree.Leaf p_186973_, Climate.DistanceMetric p_186974_) { long i = p_186973_ == null ? Long.MAX_VALUE : p_186974_.distance(p_186973_, p_186972_); Climate.RTree.Leaf leaf = p_186973_; for (Climate.RTree.Node node : this.children) { long j = p_186974_.distance(node, p_186972_); if (i > j) { Climate.RTree.Leaf 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 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 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 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 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}; } } }