Files
voxel/Chunk.cs
2025-12-14 14:35:58 +01:00

238 lines
7.0 KiB
C#

using System.Runtime.CompilerServices;
namespace Voxel
{
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;
private Dictionary<ushort, BlockData> _blockData;
private Blocks[] _blocks;
public Dictionary<Orientation, Chunk> Neighbors = new();
private static readonly (int dx, int dy, int dz)[] Offsets = new (int, int, int)[6]
{
( 1, 0, 0), // +X
(-1, 0, 0), // -X
( 0, 1, 0), // +Y
( 0, -1, 0), // -Y
( 0, 0, 1), // +Z
( 0, 0, -1) // -Z
};
private static readonly Dictionary<int, Orientation> FaceToNeighborMap = new()
{
{ 0, Orientation.West }, // +X face
{ 1, Orientation.East }, // -X face
{ 4, Orientation.North }, // +Z face
{ 5, Orientation.South } // -Z face
};
public Chunk(int x, int y)
{
X = x;
Y = y;
_blockData = new Dictionary<ushort, BlockData>();
_blocks = new Blocks[Size * Size * Height];
_chunkMesh = new ChunkMesh(X, Y);
Initialize();
}
public void SetBlock(int x, int y, int z, Blocks block, bool updateMesh = true)
{
int i = GetBlockIndex(x, y, z);
if (i == -1) return;
_blocks[i] = block;
if (updateMesh)
{
UpdateChunkMesh();
Renderer.MarkBuffersDirty();
}
}
public void SetBlockIndex(int i, Blocks block)
{
if (i < 0 || i >= TotalBlocks) return;
_blocks[i] = block;
}
public Blocks GetBlock(int x, int y, int z)
{
int i = GetBlockIndex(x, y, z);
if (i == -1) return Blocks.Air;
return _blocks[i];
}
public BlockData GetBlockData(int x, int y, int z)
{
return new BlockData(); // TODO: Implement
}
private void Initialize()
{
for (int x = 0; x < Size; x++)
{
for (int z = 0; z < Size; z++)
{
var position = GetWorldCoordinates(x, 0, z);
int height = Worldgen.GetHeight(position.x, position.z);
for (int y = 0; y < Height; y++)
{
Blocks block = Worldgen.GetBlock(y, height);
SetBlock(x, y, z, block, false);
}
}
}
UpdateChunkMesh();
}
public (int x, int y, int z) IndexToPosition(int i)
{
if (i < 0 || i >= TotalBlocks)
return (0, 0, 0);
int y = i >> 8; // y * 256
int remainder = i & 0xFF; // Lower 8 bits: x + z * 16
int z = remainder >> 4; // z * 16
int x = remainder & 0xF; // x
return (x, y, z);
}
public (int x, int y, int z) GetWorldCoordinates(int localX, int localY, int localZ)
{
return (localX + (X * Size), localY, localZ + (Y * Size));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetBlockIndex(int x, int y, int z)
{
// bit shifting
if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size)
return -1;
return x + (z << 4) + (y << 8); // x * 1, z * 16, y * 256
}
public void UpdateChunkMesh()
{
List<FaceData> faces = new List<FaceData>(TotalBlocks / 2); // Approximate capacity
GenerateFaces(faces);
_chunkMesh.SetFaces(faces);
}
private void GenerateFaces(List<FaceData> faces)
{
for (int x = 0; x < Size; x++)
{
for (int z = 0; z < Size; z++)
{
for (int y = 0; y < Height; y++)
{
ProcessBlockFaces(x, y, z, faces);
}
}
}
}
private void ProcessBlockFaces(int x, int y, int z, List<FaceData> faces)
{
int blockIndex = GetBlockIndex(x, y, z);
if (blockIndex == -1) return;
Blocks block = _blocks[blockIndex];
if (block == Blocks.Air) return;
var blockDef = BlockDefinitions.Blocks[block];
for (int face = 0; face < 6; face++)
{
if (ShouldAddFace(x, y, z, face))
{
AddFace(x, y, z, face, blockDef.FaceTextures[face], faces);
}
}
}
private bool ShouldAddFace(int x, int y, int z, int face)
{
var offset = Offsets[face];
int nx = x + offset.dx;
int ny = y + offset.dy;
int nz = z + offset.dz;
int neighborIndex = GetBlockIndex(nx, ny, nz);
if (neighborIndex != -1)
{
return _blocks[neighborIndex] == Blocks.Air;
}
return IsFaceVisibleAtChunkBoundary(x, y, z, face);
}
private bool IsFaceVisibleAtChunkBoundary(int x, int y, int z, int face)
{
if (!FaceToNeighborMap.TryGetValue(face, out Orientation neighborOrientation))
{
// top bottom faces always visible at chunk bounds
return true;
}
if (!Neighbors.TryGetValue(neighborOrientation, out Chunk neighbor) || neighbor == null)
{
return true; // no neighbor, face is visible
}
// Calculate coordinates in neighbor chunk
var offset = Offsets[face];
int localX = x;
int localZ = z;
if (offset.dx != 0) // east, west face
{
localX = WrapCoordinate(x + offset.dx, Size);
}
else if (offset.dz != 0) // north, south face
{
localZ = WrapCoordinate(z + offset.dz, Size);
}
return neighbor.GetBlock(localX, y, localZ) == Blocks.Air;
}
private int WrapCoordinate(int coord, int size)
{
if (coord < 0) return coord + size;
if (coord >= size) return coord - size;
return coord;
}
private void AddFace(int x, int y, int z, int face, Textures texture, List<FaceData> faces)
{
faces.Add(new FaceData
{
Facing = (Orientation)face,
Texture = texture,
X = (byte)x,
Y = (byte)y,
Z = (byte)z
});
}
public ChunkMesh GetChunkMesh()
{
return _chunkMesh;
}
}
}