Major refactor and organization, optimizations to chunk, world and renderer

This commit is contained in:
max
2026-03-24 22:31:40 +01:00
parent dbc546fd0e
commit eb6294c09e
25 changed files with 410 additions and 441 deletions

176
World/Chunk.cs Normal file
View File

@@ -0,0 +1,176 @@
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<FaceData> _faceBuffer = new List<FaceData>(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 or use ThreadLocal for multi-threading later
lock (_faceBuffer)
{
_faceBuffer.Clear();
GenerateFaces(_faceBuffer);
_chunkMesh.SetFaces(_faceBuffer);
}
IsDirty = false;
}
private void GenerateFaces(List<FaceData> 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;
}
}
}