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.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
using VoxelEngine.Core;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen;
|
||||
|
||||
public sealed class BiomeMap
|
||||
@@ -5,32 +7,51 @@ 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)
|
||||
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)
|
||||
{
|
||||
VoronoiCells.Cell cell = this._cells.Nearest(worldX, worldZ);
|
||||
// Sample climate at the cell center so the biome is constant across the 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 BiomeMap.Classify(temperature, elevation, forest, this._biomes);
|
||||
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)
|
||||
public static IBiome Classify(float temperature, float elevation, float forest, BiomeRegistry biomes, Configuration cfg)
|
||||
{
|
||||
if (elevation > 0.25f) return biomes.Mountains; // high ground
|
||||
if (temperature > 0.33f) return biomes.Desert; // hot, low
|
||||
if (temperature > -0.33f && forest > 0f) return biomes.Forest; // temperate, wooded
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using VoxelEngine.Core;
|
||||
using VoxelEngine.World.Blocks;
|
||||
using VoxelEngine.World.WorldGen.Biomes;
|
||||
|
||||
@@ -10,11 +11,11 @@ public sealed class BiomeRegistry
|
||||
public IBiome Mountains { get; }
|
||||
public IBiome Forest { get; }
|
||||
|
||||
public BiomeRegistry(int seed, BlockRegistry blocks)
|
||||
public BiomeRegistry(int seed, BlockRegistry blocks, Configuration cfg)
|
||||
{
|
||||
this.Plains = new PlainsBiome(seed, blocks);
|
||||
this.Desert = new DesertBiome(seed, blocks);
|
||||
this.Mountains = new MountainsBiome(seed, blocks);
|
||||
this.Forest = new ForestBiome(seed, blocks);
|
||||
this.Plains = new PlainsBiome(seed, blocks, cfg);
|
||||
this.Desert = new DesertBiome(seed, blocks, cfg);
|
||||
this.Mountains = new MountainsBiome(seed, blocks, cfg);
|
||||
this.Forest = new ForestBiome(seed, blocks, cfg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using VoxelEngine.Core;
|
||||
using VoxelEngine.World.Blocks;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
@@ -5,21 +6,23 @@ namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
public sealed class DesertBiome : IBiome
|
||||
{
|
||||
private readonly FastNoiseLite _detail;
|
||||
private readonly Configuration _cfg;
|
||||
|
||||
public string Name => "Desert";
|
||||
public SurfacePalette Surface { get; }
|
||||
|
||||
public DesertBiome(int seed, BlockRegistry blocks)
|
||||
public DesertBiome(int seed, BlockRegistry blocks, Configuration cfg)
|
||||
{
|
||||
this._cfg = cfg;
|
||||
this._detail = new FastNoiseLite(seed + 101);
|
||||
this._detail.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._detail.SetFrequency(0.015f);
|
||||
this._detail.SetFrequency(cfg.DesertDetailFrequency);
|
||||
this.Surface = new SurfacePalette(blocks.Sand, blocks.Sand, blocks.Stone);
|
||||
}
|
||||
|
||||
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||
=> WorldConstants.SeaLevel + 16f * noise.Continent(worldX, worldZ)
|
||||
+ 6f * this._detail.GetNoise(worldX, worldZ);
|
||||
=> WorldConstants.SeaLevel + this._cfg.DesertContinentAmplitude * noise.Continent(worldX, worldZ)
|
||||
+ this._cfg.DesertDetailAmplitude * this._detail.GetNoise(worldX, worldZ);
|
||||
|
||||
public IBlock TopBlock(int worldY) => this.Surface.Top;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using VoxelEngine.Core;
|
||||
using VoxelEngine.World.Blocks;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
@@ -5,21 +6,23 @@ namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
public sealed class ForestBiome : IBiome
|
||||
{
|
||||
private readonly FastNoiseLite _detail;
|
||||
private readonly Configuration _cfg;
|
||||
|
||||
public string Name => "Forest";
|
||||
public SurfacePalette Surface { get; }
|
||||
|
||||
public ForestBiome(int seed, BlockRegistry blocks)
|
||||
public ForestBiome(int seed, BlockRegistry blocks, Configuration cfg)
|
||||
{
|
||||
this._cfg = cfg;
|
||||
this._detail = new FastNoiseLite(seed + 103);
|
||||
this._detail.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._detail.SetFrequency(0.03f);
|
||||
this._detail.SetFrequency(cfg.ForestDetailFrequency);
|
||||
this.Surface = new SurfacePalette(blocks.Grass, blocks.Dirt, blocks.Stone);
|
||||
}
|
||||
|
||||
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||
=> WorldConstants.SeaLevel + 24f * noise.Continent(worldX, worldZ)
|
||||
+ 16f * this._detail.GetNoise(worldX, worldZ);
|
||||
=> WorldConstants.SeaLevel + this._cfg.ForestContinentAmplitude * noise.Continent(worldX, worldZ)
|
||||
+ this._cfg.ForestDetailAmplitude * this._detail.GetNoise(worldX, worldZ);
|
||||
|
||||
public IBlock TopBlock(int worldY) => this.Surface.Top;
|
||||
}
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
using VoxelEngine.Core;
|
||||
using VoxelEngine.World.Blocks;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
|
||||
public sealed class MountainsBiome : IBiome
|
||||
{
|
||||
private const int SnowLine = 384;
|
||||
private readonly FastNoiseLite _ridged;
|
||||
private readonly IBlock _stone;
|
||||
private readonly IBlock _snow;
|
||||
private readonly Configuration _cfg;
|
||||
|
||||
public string Name => "Mountains";
|
||||
public SurfacePalette Surface { get; }
|
||||
|
||||
public MountainsBiome(int seed, BlockRegistry blocks)
|
||||
public MountainsBiome(int seed, BlockRegistry blocks, Configuration cfg)
|
||||
{
|
||||
this._cfg = cfg;
|
||||
this._ridged = new FastNoiseLite(seed + 102);
|
||||
this._ridged.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._ridged.SetFractalType(FastNoiseLite.FractalType.Ridged);
|
||||
this._ridged.SetFractalOctaves(4);
|
||||
this._ridged.SetFrequency(0.01f);
|
||||
this._ridged.SetFractalOctaves(cfg.MountainsRidgedOctaves);
|
||||
this._ridged.SetFrequency(cfg.MountainsRidgedFrequency);
|
||||
this._stone = blocks.Stone;
|
||||
this._snow = blocks.Snow;
|
||||
this.Surface = new SurfacePalette(blocks.Stone, blocks.Stone, blocks.Stone);
|
||||
}
|
||||
|
||||
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||
=> WorldConstants.SeaLevel + 40f * noise.Continent(worldX, worldZ)
|
||||
+ 200f * this._ridged.GetNoise(worldX, worldZ);
|
||||
=> WorldConstants.SeaLevel + this._cfg.MountainsContinentAmplitude * noise.Continent(worldX, worldZ)
|
||||
+ this._cfg.MountainsRidgedAmplitude * this._ridged.GetNoise(worldX, worldZ);
|
||||
|
||||
public IBlock TopBlock(int worldY) => worldY >= MountainsBiome.SnowLine ? this._snow : this._stone;
|
||||
public IBlock TopBlock(int worldY) => worldY >= this._cfg.MountainsSnowLine ? this._snow : this._stone;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using VoxelEngine.Core;
|
||||
using VoxelEngine.World.Blocks;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
@@ -5,21 +6,23 @@ namespace VoxelEngine.World.WorldGen.Biomes;
|
||||
public sealed class PlainsBiome : IBiome
|
||||
{
|
||||
private readonly FastNoiseLite _detail;
|
||||
private readonly Configuration _cfg;
|
||||
|
||||
public string Name => "Plains";
|
||||
public SurfacePalette Surface { get; }
|
||||
|
||||
public PlainsBiome(int seed, BlockRegistry blocks)
|
||||
public PlainsBiome(int seed, BlockRegistry blocks, Configuration cfg)
|
||||
{
|
||||
this._cfg = cfg;
|
||||
this._detail = new FastNoiseLite(seed + 100);
|
||||
this._detail.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._detail.SetFrequency(0.02f);
|
||||
this._detail.SetFrequency(cfg.PlainsDetailFrequency);
|
||||
this.Surface = new SurfacePalette(blocks.Grass, blocks.Dirt, blocks.Stone);
|
||||
}
|
||||
|
||||
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||
=> WorldConstants.SeaLevel + 24f * noise.Continent(worldX, worldZ)
|
||||
+ 8f * this._detail.GetNoise(worldX, worldZ);
|
||||
=> WorldConstants.SeaLevel + this._cfg.PlainsContinentAmplitude * noise.Continent(worldX, worldZ)
|
||||
+ this._cfg.PlainsDetailAmplitude * this._detail.GetNoise(worldX, worldZ);
|
||||
|
||||
public IBlock TopBlock(int worldY) => this.Surface.Top;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using VoxelEngine.Core;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen;
|
||||
|
||||
// Seeded wrapper over FastNoiseLite. All Get* methods return roughly [-1, 1].
|
||||
@@ -7,23 +9,23 @@ public sealed class NoiseSource
|
||||
private readonly FastNoiseLite _temperature;
|
||||
private readonly FastNoiseLite _forest;
|
||||
|
||||
public NoiseSource(int seed)
|
||||
public NoiseSource(int seed, Configuration cfg)
|
||||
{
|
||||
this._continent = new FastNoiseLite(seed);
|
||||
this._continent.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._continent.SetFractalType(FastNoiseLite.FractalType.FBm);
|
||||
this._continent.SetFractalOctaves(4);
|
||||
this._continent.SetFrequency(0.0035f);
|
||||
this._continent.SetFractalOctaves(cfg.ContinentOctaves);
|
||||
this._continent.SetFrequency(cfg.ContinentFrequency);
|
||||
|
||||
this._temperature = new FastNoiseLite(seed + 1);
|
||||
this._temperature.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._temperature.SetFractalType(FastNoiseLite.FractalType.FBm);
|
||||
this._temperature.SetFractalOctaves(3);
|
||||
this._temperature.SetFrequency(0.0015f);
|
||||
this._temperature.SetFractalOctaves(cfg.TemperatureOctaves);
|
||||
this._temperature.SetFrequency(cfg.TemperatureFrequency);
|
||||
|
||||
this._forest = new FastNoiseLite(seed + 2);
|
||||
this._forest.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||
this._forest.SetFrequency(0.02f);
|
||||
this._forest.SetFrequency(cfg.ForestFrequency);
|
||||
}
|
||||
|
||||
public float Continent(float x, float z) => this._continent.GetNoise(x, z);
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
using VoxelEngine.Core;
|
||||
|
||||
namespace VoxelEngine.World.WorldGen;
|
||||
|
||||
public sealed class TerrainSampler
|
||||
{
|
||||
// Box-blur radius (in blocks) applied to the biome-height field to smooth
|
||||
// biome borders. A full (2R+1)x(2R+1) kernel at 1-block spacing bounds the
|
||||
// per-block height change to ~maxBiomeHeightDiff / (2R+1), preventing cliffs.
|
||||
// A sparse kernel does NOT bound it (axis-aligned borders flip several samples
|
||||
// at once), so the kernel must be dense.
|
||||
private const int BlendRadius = 6;
|
||||
|
||||
private readonly NoiseSource _noise;
|
||||
private readonly BiomeMap _biomes;
|
||||
private readonly Configuration _cfg;
|
||||
|
||||
public TerrainSampler(NoiseSource noise, BiomeMap biomes)
|
||||
public TerrainSampler(NoiseSource noise, BiomeMap biomes, Configuration cfg)
|
||||
{
|
||||
this._noise = noise;
|
||||
this._biomes = biomes;
|
||||
this._cfg = cfg;
|
||||
}
|
||||
|
||||
public int HeightAt(float worldX, float worldZ)
|
||||
{
|
||||
// For each kernel point find the biome THERE, but evaluate its height AT the
|
||||
// column. Averaging blurs the hard biome borders into smooth transitions.
|
||||
float sum = 0f;
|
||||
int count = 0;
|
||||
for (int dz = -TerrainSampler.BlendRadius; dz <= TerrainSampler.BlendRadius; dz++)
|
||||
for (int dx = -TerrainSampler.BlendRadius; dx <= TerrainSampler.BlendRadius; dx++)
|
||||
{
|
||||
IBiome biome = this._biomes.BiomeAt(worldX + dx, worldZ + dz);
|
||||
sum += biome.SampleHeight(this._noise, worldX, worldZ);
|
||||
count++;
|
||||
}
|
||||
float avg = sum / count;
|
||||
int h = (int)System.MathF.Round(avg);
|
||||
return System.Math.Clamp(h, 0, WorldConstants.WorldHeight - 1);
|
||||
// Blend the two nearest biomes' heights (each evaluated AT this column) by
|
||||
// how deep inside the nearer cell we are: exactly on the border they mix
|
||||
// 50/50, easing to 100% of the nearer biome over BiomeBlendWidth blocks.
|
||||
BiomeMap.BiomeBlend blend = this._biomes.NearestTwo(worldX, worldZ);
|
||||
float hNear = blend.Near.SampleHeight(this._noise, worldX, worldZ);
|
||||
float hFar = blend.Far.SampleHeight(this._noise, worldX, worldZ);
|
||||
|
||||
float fracFar = 0.5f * (1f - SmoothStep01(blend.BorderDistance / this._cfg.BiomeBlendWidth));
|
||||
float avg = hNear * (1f - fracFar) + hFar * fracFar;
|
||||
|
||||
int h = (int)MathF.Round(avg);
|
||||
return Math.Clamp(h, 0, WorldConstants.WorldHeight - 1);
|
||||
}
|
||||
|
||||
// Hermite smoothstep on [0,1].
|
||||
private static float SmoothStep01(float t)
|
||||
{
|
||||
t = Math.Clamp(t, 0f, 1f);
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ public sealed class VoronoiCells
|
||||
|
||||
public Cell Nearest(float x, float z)
|
||||
{
|
||||
int gx = (int)System.MathF.Floor(x / this._cellSize);
|
||||
int gz = (int)System.MathF.Floor(z / this._cellSize);
|
||||
int gx = (int)MathF.Floor(x / this._cellSize);
|
||||
int gz = (int)MathF.Floor(z / this._cellSize);
|
||||
|
||||
float bestDist = float.MaxValue;
|
||||
int bestId = 0;
|
||||
@@ -28,8 +28,8 @@ public sealed class VoronoiCells
|
||||
{
|
||||
int cgx = gx + ox;
|
||||
int cgz = gz + oz;
|
||||
float jitterX = VoronoiCells.Jitter(cgx, cgz, this._seed);
|
||||
float jitterZ = VoronoiCells.Jitter(cgx, cgz, this._seed + 9871);
|
||||
float jitterX = Jitter(cgx, cgz, this._seed);
|
||||
float jitterZ = Jitter(cgx, cgz, this._seed + 9871);
|
||||
float cx = (cgx + jitterX) * this._cellSize;
|
||||
float cz = (cgz + jitterZ) * this._cellSize;
|
||||
float dx = cx - x, dz = cz - z;
|
||||
@@ -37,7 +37,7 @@ public sealed class VoronoiCells
|
||||
if (dist < bestDist)
|
||||
{
|
||||
bestDist = dist;
|
||||
bestId = (int)VoronoiCells.Hash(cgx, cgz, this._seed);
|
||||
bestId = (int)Hash(cgx, cgz, this._seed);
|
||||
bestCx = cx;
|
||||
bestCz = cz;
|
||||
}
|
||||
@@ -45,8 +45,48 @@ public sealed class VoronoiCells
|
||||
return new Cell(bestId, bestCx, bestCz);
|
||||
}
|
||||
|
||||
public readonly record struct NearestPair(Cell First, Cell Second, float FirstDist, float SecondDist);
|
||||
|
||||
// The two nearest cells to (x,z) with their (Euclidean) distances. Used to
|
||||
// ease terrain height across the border between adjacent biome cells. Same
|
||||
// 3x3 jittered-grid search as Nearest — the two closest centres always fall
|
||||
// within that neighbourhood for jitter in [0,1).
|
||||
public NearestPair NearestTwo(float x, float z)
|
||||
{
|
||||
int gx = (int)MathF.Floor(x / this._cellSize);
|
||||
int gz = (int)MathF.Floor(z / this._cellSize);
|
||||
|
||||
float best = float.MaxValue, second = float.MaxValue;
|
||||
Cell bestCell = default, secondCell = default;
|
||||
|
||||
for (int oz = -1; oz <= 1; oz++)
|
||||
for (int ox = -1; ox <= 1; ox++)
|
||||
{
|
||||
int cgx = gx + ox;
|
||||
int cgz = gz + oz;
|
||||
float jitterX = Jitter(cgx, cgz, this._seed);
|
||||
float jitterZ = Jitter(cgx, cgz, this._seed + 9871);
|
||||
float cx = (cgx + jitterX) * this._cellSize;
|
||||
float cz = (cgz + jitterZ) * this._cellSize;
|
||||
float dx = cx - x, dz = cz - z;
|
||||
float dist = dx * dx + dz * dz;
|
||||
Cell cell = new((int)Hash(cgx, cgz, this._seed), cx, cz);
|
||||
if (dist < best)
|
||||
{
|
||||
second = best; secondCell = bestCell;
|
||||
best = dist; bestCell = cell;
|
||||
}
|
||||
else if (dist < second)
|
||||
{
|
||||
second = dist; secondCell = cell;
|
||||
}
|
||||
}
|
||||
return new NearestPair(bestCell, secondCell,
|
||||
MathF.Sqrt(best), MathF.Sqrt(second));
|
||||
}
|
||||
|
||||
private static float Jitter(int x, int z, int seed)
|
||||
=> (VoronoiCells.Hash(x, z, seed) & 0xFFFFFF) / (float)0x1000000; // [0,1)
|
||||
=> (Hash(x, z, seed) & 0xFFFFFF) / (float)0x1000000; // [0,1)
|
||||
|
||||
private static uint Hash(int x, int z, int seed)
|
||||
{
|
||||
|
||||
@@ -7,9 +7,7 @@ namespace VoxelEngine.World.WorldGen;
|
||||
|
||||
public sealed class WorldGenerator
|
||||
{
|
||||
private const int FillerDepth = 3;
|
||||
private const float CellSize = 96f;
|
||||
|
||||
private readonly Configuration _cfg;
|
||||
private readonly BlockRegistry _blocks;
|
||||
private readonly BiomeMap _biomeMap;
|
||||
private readonly TerrainSampler _terrain;
|
||||
@@ -17,17 +15,20 @@ public sealed class WorldGenerator
|
||||
|
||||
public WorldGenerator(int seed, BlockRegistry blocks, Configuration cfg)
|
||||
{
|
||||
this._cfg = cfg;
|
||||
this._blocks = blocks;
|
||||
NoiseSource noise = new(seed);
|
||||
VoronoiCells cells = new(seed, WorldGenerator.CellSize);
|
||||
BiomeRegistry biomes = new(seed, blocks);
|
||||
this._biomeMap = new BiomeMap(noise, cells, biomes);
|
||||
this._terrain = new TerrainSampler(noise, this._biomeMap);
|
||||
NoiseSource noise = new(seed, cfg);
|
||||
VoronoiCells cells = new(seed, cfg.BiomeCellSize);
|
||||
BiomeRegistry biomes = new(seed, blocks, cfg);
|
||||
this._biomeMap = new BiomeMap(noise, cells, biomes, cfg);
|
||||
this._terrain = new TerrainSampler(noise, this._biomeMap, cfg);
|
||||
this._caves = new CaveCarver(seed, blocks, cfg);
|
||||
}
|
||||
|
||||
public int SurfaceHeightAt(int worldX, int worldZ) => this._terrain.HeightAt(worldX, worldZ);
|
||||
|
||||
public string BiomeNameAt(int worldX, int worldZ) => this._biomeMap.BiomeAt(worldX, worldZ).Name;
|
||||
|
||||
public VoxelChunk Generate(Vector3i chunkPos)
|
||||
{
|
||||
VoxelChunk chunk = new();
|
||||
@@ -46,7 +47,7 @@ public sealed class WorldGenerator
|
||||
for (int y = 0; y < VoxelChunk.Size; y++)
|
||||
{
|
||||
int worldY = baseY + y;
|
||||
chunk[x, y, z] = WorldGenerator.BlockAt(worldY, height, biome, this._blocks);
|
||||
chunk[x, y, z] = BlockAt(worldY, height, biome, this._blocks, this._cfg.SurfaceFillerDepth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +55,13 @@ public sealed class WorldGenerator
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public static ushort BlockAt(int worldY, int surfaceHeight, IBiome biome, BlockRegistry blocks)
|
||||
public static ushort BlockAt(int worldY, int surfaceHeight, IBiome biome, BlockRegistry blocks, int fillerDepth)
|
||||
{
|
||||
if (worldY > surfaceHeight)
|
||||
return worldY <= WorldConstants.SeaLevel ? blocks.Water.Id : blocks.Air.Id;
|
||||
if (worldY == surfaceHeight)
|
||||
return biome.TopBlock(worldY).Id;
|
||||
if (worldY >= surfaceHeight - WorldGenerator.FillerDepth)
|
||||
if (worldY >= surfaceHeight - fillerDepth)
|
||||
return biome.Surface.Filler.Id;
|
||||
return biome.Surface.Under.Id;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user