Cleaned up chunk and world code
This commit is contained in:
214
Chunk.cs
214
Chunk.cs
@@ -1,9 +1,13 @@
|
||||
namespace Voxel
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Voxel
|
||||
{
|
||||
public class Chunk
|
||||
{
|
||||
public static int Size = 16;
|
||||
public static int Height = 256;
|
||||
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;
|
||||
@@ -12,6 +16,24 @@
|
||||
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;
|
||||
@@ -39,6 +61,7 @@
|
||||
|
||||
public void SetBlockIndex(int i, Blocks block)
|
||||
{
|
||||
if (i < 0 || i >= TotalBlocks) return;
|
||||
_blocks[i] = block;
|
||||
}
|
||||
|
||||
@@ -51,7 +74,7 @@
|
||||
|
||||
public BlockData GetBlockData(int x, int y, int z)
|
||||
{
|
||||
return new BlockData();
|
||||
return new BlockData(); // TODO: Implement
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
@@ -63,7 +86,7 @@
|
||||
var position = GetWorldCoordinates(x, 0, z);
|
||||
int height = Worldgen.GetHeight(position.x, position.z);
|
||||
|
||||
for (int y = 0; y < 256; y++)
|
||||
for (int y = 0; y < Height; y++)
|
||||
{
|
||||
Blocks block = Worldgen.GetBlock(y, height);
|
||||
SetBlock(x, y, z, block, false);
|
||||
@@ -73,110 +96,137 @@
|
||||
UpdateChunkMesh();
|
||||
}
|
||||
|
||||
// todo
|
||||
public (int x, int y, int z) IndexToPosition(int i)
|
||||
{
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int z = 0;
|
||||
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 x, int y, int z)
|
||||
public (int x, int y, int z) GetWorldCoordinates(int localX, int localY, int localZ)
|
||||
{
|
||||
x += (Size * X);
|
||||
z += (Size * Y);
|
||||
return (x, y, z);
|
||||
return (localX + (X * Size), localY, localZ + (Y * Size));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private int GetBlockIndex(int x, int y, int z)
|
||||
{
|
||||
if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15)
|
||||
// bit shifting
|
||||
if ((uint)x >= Size || (uint)y >= Height || (uint)z >= Size)
|
||||
return -1;
|
||||
|
||||
return x + z * Size + y * Size * Size;
|
||||
return x + (z << 4) + (y << 8); // x * 1, z * 16, y * 256
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
public void UpdateChunkMesh()
|
||||
{
|
||||
List<FaceData> faces = new List<FaceData>(Size * Size * Height / 2);
|
||||
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++)
|
||||
{
|
||||
for (byte face = 0; face < 6; face++)
|
||||
{
|
||||
int indexBase = y * Size * Size + z * Size + x;
|
||||
Blocks block = _blocks[indexBase];
|
||||
|
||||
void AddFace()
|
||||
{
|
||||
FaceData faceData = new FaceData();
|
||||
|
||||
faceData.Facing = (Orientation)face;
|
||||
faceData.Texture = BlockDefinitions.Blocks[block].FaceTextures[face];
|
||||
|
||||
faceData.X = (byte)x;
|
||||
faceData.Y = (byte)y;
|
||||
faceData.Z = (byte)z;
|
||||
|
||||
faces.Add(faceData);
|
||||
}
|
||||
|
||||
if (block == Blocks.Air) continue; // ignore if air
|
||||
|
||||
int nx = x + Offsets[face].dx;
|
||||
int ny = y + Offsets[face].dy;
|
||||
int nz = z + Offsets[face].dz;
|
||||
|
||||
// check neighbor, ignore if at chunk edge
|
||||
|
||||
int ni = GetBlockIndex(nx, ny, nz);
|
||||
if (GetBlockIndex(nx, ny, nz) == -1)
|
||||
{
|
||||
if (Neighbors.TryGetValue((Orientation)face, out Chunk neighbor) && neighbor != null)
|
||||
{
|
||||
int localX = nx;
|
||||
int localZ = nz;
|
||||
|
||||
if (nx < 0) localX = nx + Size;
|
||||
if (nx >= Size) localX = nx - Size;
|
||||
|
||||
if (nz < 0) localZ = nz + Size;
|
||||
if (nz >= Size) localZ = nz - Size;
|
||||
|
||||
Blocks neighborBlock = neighbor.GetBlock(localX, y, localZ);
|
||||
if (neighborBlock != Blocks.Air)
|
||||
continue;
|
||||
}
|
||||
AddFace();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_blocks[ni] == Blocks.Air)
|
||||
{
|
||||
AddFace();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ProcessBlockFaces(x, y, z, faces);
|
||||
}
|
||||
}
|
||||
}
|
||||
_chunkMesh.SetFaces(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()
|
||||
|
||||
@@ -92,10 +92,10 @@ namespace Voxel
|
||||
//_uiTexture.Bind();
|
||||
|
||||
// Draw all UI sprites (batch by texture for efficiency)
|
||||
foreach (var sprite in _uiSprites)
|
||||
{
|
||||
sprite.Draw();
|
||||
}
|
||||
//foreach (var sprite in _uiSprites)
|
||||
//{
|
||||
// sprite.Draw();
|
||||
//}
|
||||
|
||||
// Restore 3D settings
|
||||
GL.Disable(EnableCap.Blend);
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Voxel
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
|
||||
CursorState = CursorState.Grabbed;
|
||||
VSync = VSyncMode.On;
|
||||
//VSync = VSyncMode.On;
|
||||
|
||||
Camera.UpdateSize(Width, Height);
|
||||
}
|
||||
|
||||
83
World.cs
83
World.cs
@@ -8,6 +8,14 @@ namespace Voxel
|
||||
{
|
||||
private Dictionary<(int, int), Chunk> _chunks;
|
||||
|
||||
private static readonly Dictionary<Orientation, (int x, int y)> _neighborOffsets = new()
|
||||
{
|
||||
{ Orientation.West, (1, 0) },
|
||||
{ Orientation.East, (-1, 0) },
|
||||
{ Orientation.North, (0, 1) },
|
||||
{ Orientation.South, (0, -1) }
|
||||
};
|
||||
|
||||
public World()
|
||||
{
|
||||
_chunks = new Dictionary<(int, int), Chunk>();
|
||||
@@ -23,26 +31,53 @@ namespace Voxel
|
||||
{
|
||||
_chunks[(chunk.X, chunk.Y)] = chunk;
|
||||
|
||||
Dictionary<Orientation, Orientation> oppositeOrientation = new()
|
||||
{
|
||||
{Orientation.West, Orientation.East},
|
||||
{Orientation.East, Orientation.West},
|
||||
{Orientation.North, Orientation.South},
|
||||
{Orientation.South, Orientation.North},
|
||||
};
|
||||
|
||||
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
||||
{
|
||||
neighbor.Neighbors[oppositeOrientation[orientation]] = chunk;
|
||||
chunk.Neighbors[orientation] = neighbor;
|
||||
neighbor.UpdateChunkMesh();
|
||||
}
|
||||
UpdateNeighboringChunks(chunk);
|
||||
chunk.UpdateChunkMesh();
|
||||
}
|
||||
|
||||
public void RemoveChunk(int chunkX, int chunkZ)
|
||||
{
|
||||
_chunks.Remove((chunkX, chunkZ));
|
||||
if (_chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk))
|
||||
{
|
||||
var neighbors = GetChunkNeighbors(chunk).ToList();
|
||||
_chunks.Remove((chunkX, chunkZ));
|
||||
|
||||
// 3. For each neighbor, remove reference to this chunk
|
||||
foreach (var (orientation, neighbor) in neighbors)
|
||||
{
|
||||
var opposite = GetOppositeOrientation(orientation);
|
||||
neighbor.Neighbors[opposite] = null;
|
||||
neighbor.UpdateChunkMesh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateNeighboringChunks(Chunk chunk)
|
||||
{
|
||||
chunk.Neighbors[Orientation.West] = null;
|
||||
chunk.Neighbors[Orientation.East] = null;
|
||||
chunk.Neighbors[Orientation.North] = null;
|
||||
chunk.Neighbors[Orientation.South] = null;
|
||||
|
||||
foreach (var (orientation, neighbor) in GetChunkNeighbors(chunk))
|
||||
{
|
||||
Orientation opposite = GetOppositeOrientation(orientation);
|
||||
neighbor.Neighbors[opposite] = chunk;
|
||||
chunk.Neighbors[orientation] = neighbor;
|
||||
neighbor.UpdateChunkMesh();
|
||||
}
|
||||
}
|
||||
|
||||
private Orientation GetOppositeOrientation(Orientation orientation)
|
||||
{
|
||||
return orientation switch
|
||||
{
|
||||
Orientation.West => Orientation.East,
|
||||
Orientation.East => Orientation.West,
|
||||
Orientation.North => Orientation.South,
|
||||
Orientation.South => Orientation.North,
|
||||
_ => orientation
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<Chunk> GetAllChunks()
|
||||
@@ -87,7 +122,7 @@ namespace Voxel
|
||||
|
||||
chunk.SetBlock(localX, worldY, localZ, block);
|
||||
|
||||
if (block == Blocks.Air && (localX == 15 || localX == 0) || (localZ == 15 || localZ == 0))
|
||||
if (block == Blocks.Air && ((localX == Chunk.Size - 1 || localX == 0) || (localZ == Chunk.Size - 1 || localZ == 0)))
|
||||
{
|
||||
foreach (var orientation in GetEdgeOrientations(localX, localZ, Chunk.Size))
|
||||
{
|
||||
@@ -101,20 +136,12 @@ namespace Voxel
|
||||
|
||||
public IEnumerable<(Orientation orientation, Chunk neighbor)> GetChunkNeighbors(Chunk chunk)
|
||||
{
|
||||
Dictionary<Orientation, (int x, int y)> offsets = new()
|
||||
{
|
||||
{ Orientation.West, (1, 0) },
|
||||
{ Orientation.East, (-1, 0) },
|
||||
{ Orientation.North, (0, 1) },
|
||||
{ Orientation.South, (0, -1) }
|
||||
};
|
||||
int chunkX = chunk.X;
|
||||
int chunkY = chunk.Y;
|
||||
|
||||
foreach (var kv in offsets)
|
||||
foreach (var kv in _neighborOffsets)
|
||||
{
|
||||
int nx = chunk.X + kv.Value.x;
|
||||
int ny = chunk.Y + kv.Value.y;
|
||||
|
||||
Chunk neighbor = GetChunk(nx, ny);
|
||||
Chunk neighbor = GetChunk(chunkX + kv.Value.x, chunkY + kv.Value.y);
|
||||
if (neighbor != null)
|
||||
yield return (kv.Key, neighbor);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user