238 lines
7.0 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|