using OpenTK.Mathematics; using VoxelEngine.Core; using VoxelEngine.World.Blocks; using VoxelChunk = VoxelEngine.World.Chunk.Chunk; namespace VoxelEngine.World.WorldGen; // Carves caves into an already-terrain-filled chunk. Stateless after // construction; the carve methods are pure functions of (chunk, chunkPos). // Safe to share across worker threads. public sealed class CaveCarver { private readonly int _seed; private readonly BlockRegistry _blocks; private readonly Configuration _cfg; private readonly FastNoiseLite _caveNoise; public CaveCarver(int seed, BlockRegistry blocks, Configuration cfg) { this._seed = seed; this._blocks = blocks; this._cfg = cfg; this._caveNoise = new FastNoiseLite(seed + 100); this._caveNoise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2); this._caveNoise.SetFrequency(cfg.CaveNoiseFrequency); } public void Carve(VoxelChunk chunk, Vector3i chunkPos) { this.CarveNoiseCaves(chunk, chunkPos); // Worm pass added in Task 6. } // Per-voxel 3D simplex threshold. Public for direct testability, // matching the pattern of WorldGenerator.BlockAt. public void CarveNoiseCaves(VoxelChunk chunk, Vector3i chunkPos) { 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 y = 0; y < VoxelChunk.Size; y++) for (int z = 0; z < VoxelChunk.Size; z++) { int worldY = baseY + y; if (worldY <= 0) continue; ushort id = chunk[x, y, z]; if (id == this._blocks.Air.Id) continue; if (id == this._blocks.Water.Id) continue; float density = this._caveNoise.GetNoise(baseX + x, worldY, baseZ + z); if (density < this._cfg.CaveNoiseThreshold) { chunk[x, y, z] = this._blocks.Air.Id; } } } }