From e96f78b6b07b4600cc0c9ae11ee195c187df4cbd Mon Sep 17 00:00:00 2001 From: max Date: Sun, 14 Dec 2025 14:35:58 +0100 Subject: [PATCH] Cleaned up chunk and world code --- Chunk.cs | 214 ++++++++++++++++++++++++++++++++-------------------- Renderer.cs | 8 +- Window.cs | 2 +- World.cs | 83 +++++++++++++------- 4 files changed, 192 insertions(+), 115 deletions(-) diff --git a/Chunk.cs b/Chunk.cs index 095c0cb..c4df8b9 100644 --- a/Chunk.cs +++ b/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 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 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 faces = new List(Size * Size * Height / 2); + List faces = new List(TotalBlocks / 2); // Approximate capacity + GenerateFaces(faces); + _chunkMesh.SetFaces(faces); + } + private void GenerateFaces(List 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 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 faces) + { + faces.Add(new FaceData + { + Facing = (Orientation)face, + Texture = texture, + X = (byte)x, + Y = (byte)y, + Z = (byte)z + }); } public ChunkMesh GetChunkMesh() diff --git a/Renderer.cs b/Renderer.cs index 7345f14..64fb6c6 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -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); diff --git a/Window.cs b/Window.cs index d6440fe..0df6f2c 100644 --- a/Window.cs +++ b/Window.cs @@ -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); } diff --git a/World.cs b/World.cs index adbeb6d..a8a83bf 100644 --- a/World.cs +++ b/World.cs @@ -8,6 +8,14 @@ namespace Voxel { private Dictionary<(int, int), Chunk> _chunks; + private static readonly Dictionary _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 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 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 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); }