Skip to content

Instantly share code, notes, and snippets.

@hYdos
Created January 22, 2024 13:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hYdos/b51f0fdeacbf7df6bb7d143cf327fead to your computer and use it in GitHub Desktop.
Save hYdos/b51f0fdeacbf7df6bb7d143cf327fead to your computer and use it in GitHub Desktop.
package me.hydos.naclmon.world.gen;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import me.hydos.naclmon.world.gen.noise.OpenSimplexNoise;
import me.hydos.naclmon.world.gen.noise.VoronoiNoise;
import me.hydos.naclmon.world.gen.settings.RegionSettings;
import net.fabricmc.fabric.impl.biome.MultiNoiseSamplerHooks;
import net.minecraft.core.Holder;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Climate;
import net.minecraft.world.level.levelgen.XoroshiroRandomSource;
import org.joml.Vector2d;
import org.joml.Vector2i;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
/**
* What idiot at mojang thought limiting biomes to weather conditions was a good idea. Nice way to make me hack Random everywhere...
*/
public class RegionBiomeSource extends BiomeSource implements BiomeManager.NoiseBiomeSource {
public static final Codec<RegionBiomeSource> CODEC = RecordCodecBuilder.create(instance -> instance.group(
Codec.DOUBLE.fieldOf("scale").forGetter(generator -> generator.scale),
Biome.CODEC.fieldOf("ocean").forGetter(generator -> generator.oceanBiome),
RegionSettings.CODEC.listOf().fieldOf("regions").forGetter(generator -> generator.regions)
).apply(instance, instance.stable(RegionBiomeSource::new)));
private final double scale;
public final Holder<Biome> oceanBiome;
private final List<RegionSettings> regions;
private OpenSimplexNoise regionNoise;
private OpenSimplexNoise commonBiomeNoise;
private VoronoiNoise voronoiNoise;
public RegionBiomeSource(double scale, Holder<Biome> oceanBiome, List<RegionSettings> regionSettings) {
this.scale = scale;
this.oceanBiome = oceanBiome;
this.regions = regionSettings;
}
@Override
protected Codec<? extends BiomeSource> codec() {
return CODEC;
}
@Override
protected Stream<Holder<Biome>> collectPossibleBiomes() {
var biomes = new ArrayList<Holder<Biome>>();
for (var region : regions) biomes.addAll(region.commonBiomes());
biomes.add(oceanBiome);
return biomes.stream();
}
@Override
public Holder<Biome> getNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
// I believe this is called on multiple Threads. In case we don't have voronoi initialized in time initialize it on other threads too
if (voronoiNoise == null) {
// I can either do this myself, Or I can let FabricAPI do it.
//noinspection UnstableApiUsage
var random = new XoroshiroRandomSource(((MultiNoiseSamplerHooks) (Object) sampler).fabric_getSeed());
this.regionNoise = new OpenSimplexNoise(random);
this.commonBiomeNoise = new OpenSimplexNoise(random);
this.voronoiNoise = new VoronoiNoise(random);
}
var sampleResult = voronoiNoise.distanceFromNearestCellSamplePoint(new Vector2d(x / scale, z / scale));
if (sampleResult.distance() > 0.5) return oceanBiome;
else return pickRegionBiome(sampleResult.closestCell(), sampleResult.distance(), x, z);
}
private static <T> T sampleList(double positivelyBoundRandom, List<T> list) {
return list.get((int) (list.size() * positivelyBoundRandom));
}
// TODO: include distance in this too for regions that are smaller like island like ones.
private Holder<Biome> pickRegionBiome(Vector2i cellPos, double distance, int x, int z) {
var region = sampleList(regionNoise.samplePositiveRange(cellPos.x, cellPos.y), regions);
if (distance > region.size()) return oceanBiome;
return sampleList(commonBiomeNoise.samplePositiveRange(x * 0.01, z * 0.01), region.commonBiomes());
}
@Override
public Holder<Biome> getNoiseBiome(int x, int y, int z) {
// I believe this is called on multiple Threads. In case we don't have voronoi initialized in time initialize it on other threads too
if (voronoiNoise == null) throw new RuntimeException("No random available to sample noise");
var sampleResult = voronoiNoise.distanceFromNearestCellSamplePoint(new Vector2d(x / scale, z / scale));
if (sampleResult.distance() > 0.5) return oceanBiome;
else return pickRegionBiome(sampleResult.closestCell(), sampleResult.distance(), x, z);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment