feat: add WorldGenerator filling vertical chunk stacks
This commit is contained in:
70
VoxelEngine.Tests/WorldGen/WorldGeneratorTests.cs
Normal file
70
VoxelEngine.Tests/WorldGen/WorldGeneratorTests.cs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
using VoxelEngine.World.Chunk;
|
||||||
|
using VoxelEngine.World.WorldGen;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace VoxelEngine.Tests.WorldGen;
|
||||||
|
|
||||||
|
public class WorldGeneratorTests
|
||||||
|
{
|
||||||
|
private static BlockRegistry Blocks => new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BlockAt_AboveTerrainAndSea_IsAir()
|
||||||
|
{
|
||||||
|
var blocks = Blocks;
|
||||||
|
var biomes = new BiomeRegistry(1, blocks);
|
||||||
|
ushort id = WorldGenerator.BlockAt(80, surfaceHeight: 70, biomes.Plains, blocks);
|
||||||
|
Assert.Equal(blocks.Air.Id, id); // 80 > 70 and 80 > SeaLevel(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BlockAt_AboveTerrainBelowSea_IsWater()
|
||||||
|
{
|
||||||
|
var blocks = Blocks;
|
||||||
|
var biomes = new BiomeRegistry(1, blocks);
|
||||||
|
ushort id = WorldGenerator.BlockAt(60, surfaceHeight: 50, biomes.Plains, blocks);
|
||||||
|
Assert.Equal(blocks.Water.Id, id); // 60 > 50 but 60 <= SeaLevel(64)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BlockAt_Surface_IsBiomeTop()
|
||||||
|
{
|
||||||
|
var blocks = Blocks;
|
||||||
|
var biomes = new BiomeRegistry(1, blocks);
|
||||||
|
ushort id = WorldGenerator.BlockAt(70, surfaceHeight: 70, biomes.Desert, blocks);
|
||||||
|
Assert.Equal(blocks.Sand.Id, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BlockAt_FillerBand_IsBiomeFiller()
|
||||||
|
{
|
||||||
|
var blocks = Blocks;
|
||||||
|
var biomes = new BiomeRegistry(1, blocks);
|
||||||
|
ushort id = WorldGenerator.BlockAt(68, surfaceHeight: 70, biomes.Plains, blocks);
|
||||||
|
Assert.Equal(blocks.Dirt.Id, id); // within 3 of surface
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BlockAt_Deep_IsStone()
|
||||||
|
{
|
||||||
|
var blocks = Blocks;
|
||||||
|
var biomes = new BiomeRegistry(1, blocks);
|
||||||
|
ushort id = WorldGenerator.BlockAt(40, surfaceHeight: 70, biomes.Plains, blocks);
|
||||||
|
Assert.Equal(blocks.Stone.Id, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Generate_IsDeterministic()
|
||||||
|
{
|
||||||
|
var gen1 = new WorldGenerator(99, Blocks);
|
||||||
|
var gen2 = new WorldGenerator(99, Blocks);
|
||||||
|
Chunk a = gen1.Generate(new Vector3i(0, 4, 0));
|
||||||
|
Chunk b = gen2.Generate(new Vector3i(0, 4, 0));
|
||||||
|
for (int x = 0; x < Chunk.Size; x++)
|
||||||
|
for (int y = 0; y < Chunk.Size; y++)
|
||||||
|
for (int z = 0; z < Chunk.Size; z++)
|
||||||
|
Assert.Equal(a[x, y, z], b[x, y, z]);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
World/WorldGen/WorldGenerator.cs
Normal file
61
World/WorldGen/WorldGenerator.cs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
using OpenTK.Mathematics;
|
||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
using VoxelChunk = VoxelEngine.World.Chunk.Chunk;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World.WorldGen;
|
||||||
|
|
||||||
|
public sealed class WorldGenerator
|
||||||
|
{
|
||||||
|
private const int FillerDepth = 3;
|
||||||
|
private const float CellSize = 96f;
|
||||||
|
|
||||||
|
private readonly BlockRegistry _blocks;
|
||||||
|
private readonly BiomeMap _biomeMap;
|
||||||
|
private readonly TerrainSampler _terrain;
|
||||||
|
|
||||||
|
public WorldGenerator(int seed, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VoxelChunk Generate(Vector3i chunkPos)
|
||||||
|
{
|
||||||
|
VoxelChunk chunk = new();
|
||||||
|
int baseX = chunkPos.X * VoxelChunk.Size;
|
||||||
|
int baseY = chunkPos.Y * VoxelChunk.Size;
|
||||||
|
int baseZ = chunkPos.Z * VoxelChunk.Size;
|
||||||
|
|
||||||
|
for (int x = 0; x < VoxelChunk.Size; x++)
|
||||||
|
for (int z = 0; z < VoxelChunk.Size; z++)
|
||||||
|
{
|
||||||
|
int worldX = baseX + x;
|
||||||
|
int worldZ = baseZ + z;
|
||||||
|
int height = this._terrain.HeightAt(worldX, worldZ);
|
||||||
|
IBiome biome = this._biomeMap.BiomeAt(worldX, worldZ);
|
||||||
|
|
||||||
|
for (int y = 0; y < VoxelChunk.Size; y++)
|
||||||
|
{
|
||||||
|
int worldY = baseY + y;
|
||||||
|
chunk[x, y, z] = WorldGenerator.BlockAt(worldY, height, biome, this._blocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pure column fill rule, extracted for testability.
|
||||||
|
public static ushort BlockAt(int worldY, int surfaceHeight, IBiome biome, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
return biome.Surface.Filler.Id;
|
||||||
|
return biome.Surface.Under.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user