using System.Runtime.CompilerServices; using Voxel.Graphics; namespace Voxel.Core { public class Chunk { public const int Size = 16; public const int Height = 256; public const int TotalBlocks = Size * Size * Height; public readonly int X; public readonly int Y; private ChunkMesh _chunkMesh; public bool IsDirty; private Blocks[] _blocks; private readonly Chunk[] _neighbors = new Chunk[6]; private static readonly List _faceBuffer = new List(8192); private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6] { ( 1, 0, 0), // West (+X) (-1, 0, 0), // East (-X) ( 0, 1, 0), // Top (+Y) ( 0, -1, 0), // Bottom(-Y) ( 0, 0, 1), // North (+Z) ( 0, 0, -1) // South (-Z) }; public Chunk(int x, int y) { X = x; Y = y; _blocks = new Blocks[TotalBlocks]; _chunkMesh = new ChunkMesh(X, Y); Initialize(); } public void SetNeighbor(Orientation orientation, Chunk neighbor) => _neighbors[(int)orientation] = neighbor; [MethodImpl(MethodImplOptions.AggressiveInlining)] public Chunk GetNeighbor(Orientation orientation) { return _neighbors[(int)orientation]; } public void SetBlock(int x, int y, int z, Blocks block, bool updateMesh = true) { int i = GetBlockIndex(x, y, z); if (i == -1 || _blocks[i] == block) return; _blocks[i] = block; if (updateMesh) { IsDirty = true; MarkNeighborsDirtyIfEdge(x, y, z); } } private void MarkNeighborsDirtyIfEdge(int x, int y, int z) { if (x == Size - 1) _neighbors[(int)Orientation.West]?.MarkDirty(); if (x == 0) _neighbors[(int)Orientation.East]?.MarkDirty(); if (z == Size - 1) _neighbors[(int)Orientation.North]?.MarkDirty(); if (z == 0) _neighbors[(int)Orientation.South]?.MarkDirty(); } public void MarkDirty() => IsDirty = true; [MethodImpl(MethodImplOptions.AggressiveInlining)] public Blocks GetBlock(int x, int y, int z) { int i = GetBlockIndex(x, y, z); return i == -1 ? Blocks.Air : _blocks[i]; } private void Initialize() { for (int x = 0; x < Size; x++) { for (int z = 0; z < Size; z++) { int worldX = x + (X * Size); int worldZ = z + (Y * Size); int height = Worldgen.GetHeight(worldX, worldZ); for (int y = 0; y < Height; y++) { _blocks[GetBlockIndex(x, y, z)] = Worldgen.GetBlock(y, height); } } } IsDirty = true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetBlockIndex(int x, int y, int z) { if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size) return -1; return x + (z << 4) + (y << 8); // x + z*16 + y*256 } public void UpdateChunkMesh() { lock (_faceBuffer) { _faceBuffer.Clear(); GenerateFaces(_faceBuffer); _chunkMesh.SetFaces(_faceBuffer); } IsDirty = false; } private void GenerateFaces(List faces) { for (int y = 0; y < Height; y++) { int yOff = y << 8; for (int z = 0; z < Size; z++) { int zyOff = yOff + (z << 4); for (int x = 0; x < Size; x++) { int i = zyOff + x; Blocks block = _blocks[i]; if (block == Blocks.Air) continue; var blockDef = BlockDefinitions.Get(block); for (int f = 0; f < 6; f++) { if (ShouldAddFace(x, y, z, f)) { faces.Add(new FaceData((byte)x, (byte)y, (byte)z, (Orientation)f, blockDef.FaceTextures[f], 15)); } } } } } } private bool ShouldAddFace(int x, int y, int z, int faceIndex) { var offset = Offsets[faceIndex]; int nx = x + offset.dx; int ny = y + offset.dy; int nz = z + offset.dz; if ((uint)nx < Size && (uint)ny < Height && (uint)nz < Size) { return _blocks[nx + (nz << 4) + (ny << 8)] == Blocks.Air; } // external boundary check Chunk neighbor = _neighbors[faceIndex]; if (neighbor == null) return true; // At edge of loaded world // wrap coordinates for the neighbor int lx = (nx + Size) % Size; int lz = (nz + Size) % Size; return neighbor.GetBlock(lx, ny, lz) == Blocks.Air; } public ChunkMesh GetChunkMesh() { return _chunkMesh; } } }