176 lines
5.5 KiB
C#
176 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|
|
} |