feat: add Plains/Desert/Mountains/Forest biomes and registry
This commit is contained in:
46
VoxelEngine.Tests/WorldGen/BiomeTests.cs
Normal file
46
VoxelEngine.Tests/WorldGen/BiomeTests.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
using VoxelEngine.World.WorldGen;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace VoxelEngine.Tests.WorldGen;
|
||||||
|
|
||||||
|
public class BiomeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Desert_TopAndFiller_AreSand()
|
||||||
|
{
|
||||||
|
var blocks = new BlockRegistry();
|
||||||
|
var biomes = new BiomeRegistry(42, blocks);
|
||||||
|
Assert.Equal("Sand", biomes.Desert.Surface.Top.Name);
|
||||||
|
Assert.Equal("Sand", biomes.Desert.Surface.Filler.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Mountains_UseSnowAboveSnowLine_StoneBelow()
|
||||||
|
{
|
||||||
|
var blocks = new BlockRegistry();
|
||||||
|
var biomes = new BiomeRegistry(42, blocks);
|
||||||
|
Assert.Equal("Stone", biomes.Mountains.TopBlock(70).Name);
|
||||||
|
Assert.Equal("Snow Block", biomes.Mountains.TopBlock(120).Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Plains_TopBlock_IsGrass_RegardlessOfHeight()
|
||||||
|
{
|
||||||
|
var blocks = new BlockRegistry();
|
||||||
|
var biomes = new BiomeRegistry(42, blocks);
|
||||||
|
Assert.Equal("Grass", biomes.Plains.TopBlock(40).Name);
|
||||||
|
Assert.Equal("Grass", biomes.Plains.TopBlock(120).Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SampleHeight_IsDeterministic()
|
||||||
|
{
|
||||||
|
var blocks = new BlockRegistry();
|
||||||
|
var biomes = new BiomeRegistry(42, blocks);
|
||||||
|
var noise = new NoiseSource(42);
|
||||||
|
float h1 = biomes.Mountains.SampleHeight(noise, 100f, 200f);
|
||||||
|
float h2 = biomes.Mountains.SampleHeight(noise, 100f, 200f);
|
||||||
|
Assert.Equal(h1, h2);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
World/WorldGen/BiomeRegistry.cs
Normal file
20
World/WorldGen/BiomeRegistry.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
using VoxelEngine.World.WorldGen.Biomes;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World.WorldGen;
|
||||||
|
|
||||||
|
public sealed class BiomeRegistry
|
||||||
|
{
|
||||||
|
public IBiome Plains { get; }
|
||||||
|
public IBiome Desert { get; }
|
||||||
|
public IBiome Mountains { get; }
|
||||||
|
public IBiome Forest { get; }
|
||||||
|
|
||||||
|
public BiomeRegistry(int seed, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
this.Plains = new PlainsBiome(seed, blocks);
|
||||||
|
this.Desert = new DesertBiome(seed, blocks);
|
||||||
|
this.Mountains = new MountainsBiome(seed, blocks);
|
||||||
|
this.Forest = new ForestBiome(seed, blocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
World/WorldGen/Biomes/DesertBiome.cs
Normal file
25
World/WorldGen/Biomes/DesertBiome.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||||
|
|
||||||
|
public sealed class DesertBiome : IBiome
|
||||||
|
{
|
||||||
|
private readonly FastNoiseLite _detail;
|
||||||
|
|
||||||
|
public string Name => "Desert";
|
||||||
|
public SurfacePalette Surface { get; }
|
||||||
|
|
||||||
|
public DesertBiome(int seed, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
this._detail = new FastNoiseLite(seed + 101);
|
||||||
|
this._detail.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||||
|
this._detail.SetFrequency(0.015f);
|
||||||
|
this.Surface = new SurfacePalette(blocks.Sand, blocks.Sand, blocks.Stone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||||
|
=> WorldConstants.SeaLevel + 4f * noise.Continent(worldX, worldZ)
|
||||||
|
+ 1.5f * this._detail.GetNoise(worldX, worldZ);
|
||||||
|
|
||||||
|
public IBlock TopBlock(int worldY) => this.Surface.Top;
|
||||||
|
}
|
||||||
25
World/WorldGen/Biomes/ForestBiome.cs
Normal file
25
World/WorldGen/Biomes/ForestBiome.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||||
|
|
||||||
|
public sealed class ForestBiome : IBiome
|
||||||
|
{
|
||||||
|
private readonly FastNoiseLite _detail;
|
||||||
|
|
||||||
|
public string Name => "Forest";
|
||||||
|
public SurfacePalette Surface { get; }
|
||||||
|
|
||||||
|
public ForestBiome(int seed, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
this._detail = new FastNoiseLite(seed + 103);
|
||||||
|
this._detail.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||||
|
this._detail.SetFrequency(0.03f);
|
||||||
|
this.Surface = new SurfacePalette(blocks.Grass, blocks.Dirt, blocks.Stone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||||
|
=> WorldConstants.SeaLevel + 6f * noise.Continent(worldX, worldZ)
|
||||||
|
+ 4f * this._detail.GetNoise(worldX, worldZ);
|
||||||
|
|
||||||
|
public IBlock TopBlock(int worldY) => this.Surface.Top;
|
||||||
|
}
|
||||||
32
World/WorldGen/Biomes/MountainsBiome.cs
Normal file
32
World/WorldGen/Biomes/MountainsBiome.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||||
|
|
||||||
|
public sealed class MountainsBiome : IBiome
|
||||||
|
{
|
||||||
|
private const int SnowLine = 96;
|
||||||
|
private readonly FastNoiseLite _ridged;
|
||||||
|
private readonly IBlock _stone;
|
||||||
|
private readonly IBlock _snow;
|
||||||
|
|
||||||
|
public string Name => "Mountains";
|
||||||
|
public SurfacePalette Surface { get; }
|
||||||
|
|
||||||
|
public MountainsBiome(int seed, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
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._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 + 10f * noise.Continent(worldX, worldZ)
|
||||||
|
+ 45f * this._ridged.GetNoise(worldX, worldZ);
|
||||||
|
|
||||||
|
public IBlock TopBlock(int worldY) => worldY >= MountainsBiome.SnowLine ? this._snow : this._stone;
|
||||||
|
}
|
||||||
25
World/WorldGen/Biomes/PlainsBiome.cs
Normal file
25
World/WorldGen/Biomes/PlainsBiome.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using VoxelEngine.World.Blocks;
|
||||||
|
|
||||||
|
namespace VoxelEngine.World.WorldGen.Biomes;
|
||||||
|
|
||||||
|
public sealed class PlainsBiome : IBiome
|
||||||
|
{
|
||||||
|
private readonly FastNoiseLite _detail;
|
||||||
|
|
||||||
|
public string Name => "Plains";
|
||||||
|
public SurfacePalette Surface { get; }
|
||||||
|
|
||||||
|
public PlainsBiome(int seed, BlockRegistry blocks)
|
||||||
|
{
|
||||||
|
this._detail = new FastNoiseLite(seed + 100);
|
||||||
|
this._detail.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2);
|
||||||
|
this._detail.SetFrequency(0.02f);
|
||||||
|
this.Surface = new SurfacePalette(blocks.Grass, blocks.Dirt, blocks.Stone);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float SampleHeight(NoiseSource noise, float worldX, float worldZ)
|
||||||
|
=> WorldConstants.SeaLevel + 6f * noise.Continent(worldX, worldZ)
|
||||||
|
+ 2f * this._detail.GetNoise(worldX, worldZ);
|
||||||
|
|
||||||
|
public IBlock TopBlock(int worldY) => this.Surface.Top;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user