Files
VoxelEngine/World/WorldGen/BiomeMap.cs
MDietermann e034d10339 feat(world-gen): larger biomes, smooth height blending, capped mountains; centralize tunables in Configuration
Terrain feel: biome regions ~10x larger (Voronoi cell size 96->960); terrain height now eases across biome borders via a Voronoi distance blend (smooth, and ~25x cheaper than the old 169-sample box blur); mountains capped to ~2/3 world height and gentled (ridged amplitude/frequency lowered), snow line lowered to match.

Centralization: ~27 world-gen/render tuning constants (biome amplitudes & frequencies, climate-noise frequencies/octaves, biome-selection thresholds, blend width, cell size, filler depth, snow line, camera near plane, spawn offset, overlay scale) hoisted from scattered literals into a region-organized, fully-commented Configuration. Configuration is threaded into NoiseSource, the biomes, BiomeRegistry, BiomeMap (+Classify), TerrainSampler, WorldGenerator (+BlockAt filler depth), and Camera via Player. Defaults equal the prior values, so generation is unchanged; worldgen tests updated for the new constructor signatures.
2026-06-03 16:27:06 +02:00

58 lines
2.4 KiB
C#

using VoxelEngine.Core;
namespace VoxelEngine.World.WorldGen;
public sealed class BiomeMap
{
private readonly NoiseSource _noise;
private readonly VoronoiCells _cells;
private readonly BiomeRegistry _biomes;
private readonly Configuration _cfg;
public BiomeMap(NoiseSource noise, VoronoiCells cells, BiomeRegistry biomes, Configuration cfg)
{
this._noise = noise;
this._cells = cells;
this._biomes = biomes;
this._cfg = cfg;
}
public int CellId(float worldX, float worldZ) => this._cells.Nearest(worldX, worldZ).Id;
public IBiome BiomeAt(float worldX, float worldZ)
=> this.BiomeForCell(this._cells.Nearest(worldX, worldZ));
// Climate -> biome, sampled at the cell centre so the biome is constant
// across the cell.
public IBiome BiomeForCell(VoronoiCells.Cell cell)
{
float temperature = this._noise.Temperature(cell.CenterX, cell.CenterZ);
float elevation = this._noise.Continent(cell.CenterX, cell.CenterZ);
float forest = this._noise.Forest(cell.CenterX, cell.CenterZ);
return Classify(temperature, elevation, forest, this._biomes, this._cfg);
}
// The biomes of the two nearest cells plus how far inside the nearer cell the
// point sits (SecondDist - FirstDist, in blocks; 0 exactly on the border).
// TerrainSampler uses this to ease height across biome borders.
public readonly record struct BiomeBlend(IBiome Near, IBiome Far, float BorderDistance);
public BiomeBlend NearestTwo(float worldX, float worldZ)
{
VoronoiCells.NearestPair p = this._cells.NearestTwo(worldX, worldZ);
return new BiomeBlend(
this.BiomeForCell(p.First),
this.BiomeForCell(p.Second),
p.SecondDist - p.FirstDist);
}
// Pure climate -> biome lookup. Thresholds in noise units (~[-1,1]).
public static IBiome Classify(float temperature, float elevation, float forest, BiomeRegistry biomes, Configuration cfg)
{
if (elevation > cfg.MountainsElevationThreshold) return biomes.Mountains; // high ground
if (temperature > cfg.DesertTemperatureThreshold) return biomes.Desert; // hot, low
if (temperature > cfg.ForestTemperatureThreshold && forest > cfg.ForestDensityThreshold) return biomes.Forest; // temperate, wooded
return biomes.Plains; // temperate/cold, low
}
}