diff --git a/Chunk.cs b/Chunk.cs index c6fdb53..330d5e1 100644 --- a/Chunk.cs +++ b/Chunk.cs @@ -6,6 +6,7 @@ public static int Height = 256; public readonly int X; public readonly int Y; + private ChunkMesh _chunkMesh; private Dictionary _blockData; private Blocks[] _blocks; @@ -17,14 +18,18 @@ _blockData = new Dictionary(); _blocks = new Blocks[Size * Size * Height]; + _chunkMesh = new ChunkMesh(X, Y); Initialize(); } public void SetBlock(int x, int y, int z, Blocks block) { + Console.WriteLine(x.ToString() + ", " + y.ToString()); int i = GetBlockIndex(x, y, z); + if (i == -1) return; _blocks[i] = block; + UpdateChunkMesh(); } public void SetBlockIndex(int i, Blocks block) @@ -35,6 +40,7 @@ public Blocks GetBlock(int x, int y, int z) { int i = GetBlockIndex(x, y, z); + if (i == -1) return Blocks.Air; return _blocks[i]; } @@ -51,6 +57,7 @@ _blocks[i] = Blocks.Stone; for (int i = Size * Size * 15; i < Size * Size * 16; i++) _blocks[i] = Blocks.Grass; + UpdateChunkMesh(); } // todo @@ -66,7 +73,7 @@ private int GetBlockIndex(int x, int y, int z) { if (x < 0 || x > 15 || y < 0 || y > 255 || z < 0 || z > 15) - return 0; + return -1; return x + z * Size + y * Size * Size; } @@ -81,14 +88,10 @@ ( 0, 0, -1) // -Z }; - - public ChunkMesh GetChunkMesh() + private void UpdateChunkMesh() { - ChunkMesh chunkMesh = new ChunkMesh(X, Y); List faces = new List(Size * Size * Height / 2); - // offsets table - for (int x = 0; x < Size; x++) { for (int z = 0; z < Size; z++) @@ -100,6 +103,20 @@ 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; @@ -107,30 +124,29 @@ int nz = z + Offsets[face].dz; // check neighbor, ignore if at chunk edge - if (nx >= 0 && nx < Size && - ny >= 0 && ny < Height && - nz >= 0 && nz < Size && - _blocks[GetBlockIndex(nx, ny, nz)] != 0) + + int ni = GetBlockIndex(nx, ny, nz); + if (GetBlockIndex(nx, ny, nz) == -1) + { + AddFace(); continue; + } - 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 (_blocks[ni] == Blocks.Air) + { + AddFace(); + continue; + } } } } } + _chunkMesh.SetFaces(faces); + } - chunkMesh.SetFaces(faces); - - return chunkMesh; + public ChunkMesh GetChunkMesh() + { + return _chunkMesh; } } } diff --git a/ChunkMesh.cs b/ChunkMesh.cs index d522a38..0b64e3b 100644 --- a/ChunkMesh.cs +++ b/ChunkMesh.cs @@ -1,4 +1,5 @@ -using System; +using OpenTK.Graphics.OpenGL4; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,22 +11,36 @@ namespace Voxel { public int X; public int Y; - public byte[] PackedData; - public bool NeedsUpdate = true; + private byte[] _packedData; + public bool NeedsUpdate = false; public int Length = 0; + private List _faces; + public int SSBO; public ChunkMesh(int x, int y) { - PackedData = new byte[0]; + _packedData = new byte[0]; X = x; Y = y; + SSBO = GL.GenBuffer(); } public void SetFaces(List faces) { Length = faces.Count; - PackedData = faces.SelectMany(f => f.Pack()).ToArray(); + _faces = faces; NeedsUpdate = true; } + + public byte[] GetPackedData() + { + if (NeedsUpdate) + { + _packedData = _faces.SelectMany(f => f.Pack()).ToArray(); + NeedsUpdate = false; + } + + return _packedData; + } } } diff --git a/Player.cs b/Player.cs index 0b65af3..e7b6fbc 100644 --- a/Player.cs +++ b/Player.cs @@ -20,11 +20,9 @@ namespace Voxel public void BreakBlock() { - var (hit, x, y, z) = _world.Raycast(5f); // max 5 blocks - if (hit != Blocks.Air) - { - _world.SetBlock(x, y, z, Blocks.Air); - } + var (success, hit, x, y, z) = _world.Raycast(Camera.Position, Camera.Front * 10, 100); + if (!success) return; + _world.SetBlock(x, y, z, Blocks.Air); } public void Update(float deltaTime) diff --git a/Program.cs b/Program.cs index 01c6a7d..9a7c79a 100644 --- a/Program.cs +++ b/Program.cs @@ -15,14 +15,17 @@ internal class Program window.Player = player; - Chunk chunk0 = new Chunk(0, 0); - Chunk chunk1 = new Chunk(0, 1); + for (int x = 0; x < 2; x++) + { + for (int y = 0; y < 2; y++) + { + Chunk chunk = new Chunk(1+ x, 1 +y); - world.AddChunk(chunk0); - world.AddChunk(chunk1); + world.AddChunk(chunk); + } + } - Renderer.AddChunkMesh(chunk0.GetChunkMesh()); - Renderer.AddChunkMesh(chunk1.GetChunkMesh()); + Renderer.SetWorld(world); window.Run(); } diff --git a/Renderer.cs b/Renderer.cs index faac26d..a76f19e 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -9,7 +9,7 @@ namespace Voxel private static List _chunkMeshes = new List(); private static Shader _shader; private static Texture _texture; - private static bool _needsUpdate = false; + private static World _world; static Renderer() { @@ -35,32 +35,31 @@ namespace Voxel GL.BindVertexArray(_vao); GL.BindBuffer(BufferTarget.ShaderStorageBuffer, _ssbo); - //if (_needsUpdate) - //{ - // _needsUpdate = false; - // byte[] data = _faces.SelectMany(f => f.Pack()).ToArray(); - // GL.BufferData(BufferTarget.ShaderStorageBuffer, data.Length, data, BufferUsageHint.StaticRead); - //} - _shader.Use(); _shader.SetMatrix4("view", Camera.view); _shader.SetMatrix4("projection", Camera.projection); - for (int i = 0; i < _chunkMeshes.Count; i++) - { - ChunkMesh chunkMesh = _chunkMeshes[i]; - _shader.SetInt("chunkX", chunkMesh.X); - _shader.SetInt("chunkY", chunkMesh.Y); - GL.BufferData(BufferTarget.ShaderStorageBuffer, chunkMesh.PackedData.Length, chunkMesh.PackedData, BufferUsageHint.StaticRead); - GL.DrawArrays(PrimitiveType.Triangles, 0, chunkMesh.Length * 6); - } - - //Console.WriteLine("Rendered " + _faces.Count.ToString() + " faces"); + RenderWorld(); } - public static void AddChunkMesh(ChunkMesh chunkMesh) + private static void RenderWorld() { - _chunkMeshes.Add(chunkMesh); + if (_world == null) return; + + foreach (Chunk chunk in _world.GetAllChunks()) + { + ChunkMesh chunkMesh = chunk.GetChunkMesh(); + byte[] data = chunkMesh.GetPackedData(); + _shader.SetInt("chunkX", chunk.X); + _shader.SetInt("chunkY", chunk.Y); + GL.BufferData(BufferTarget.ShaderStorageBuffer, chunkMesh.Length * 8, data, BufferUsageHint.StaticRead); + GL.DrawArrays(PrimitiveType.Triangles, 0, chunkMesh.Length * 6); + } + } + + public static void SetWorld(World world) + { + _world = world; } } } diff --git a/World.cs b/World.cs index fc7dcb9..a2e1aef 100644 --- a/World.cs +++ b/World.cs @@ -12,32 +12,27 @@ namespace Voxel _chunks = new Dictionary<(int, int), Chunk>(); } - // Get a chunk at world coordinates, returns null if not loaded public Chunk GetChunk(int chunkX, int chunkZ) { _chunks.TryGetValue((chunkX, chunkZ), out Chunk chunk); return chunk; } - // Add a chunk public void AddChunk(Chunk chunk) { _chunks[(chunk.X, chunk.Y)] = chunk; } - // Remove a chunk public void RemoveChunk(int chunkX, int chunkZ) { _chunks.Remove((chunkX, chunkZ)); } - // Iterate over all chunks public IEnumerable GetAllChunks() { return _chunks.Values; } - // Optional: get a block at world coordinates public Blocks GetBlock(int worldX, int worldY, int worldZ) { int chunkX = worldX / Chunk.Size; @@ -51,7 +46,6 @@ namespace Voxel return chunk.GetBlock(localX, worldY, localZ); } - // Optional: set a block at world coordinates public void SetBlock(int worldX, int worldY, int worldZ, Blocks block) { int chunkX = worldX / Chunk.Size; @@ -65,32 +59,74 @@ namespace Voxel chunk.SetBlock(localX, worldY, localZ, block); } - public (Blocks block, int x, int y, int z) Raycast(float length) + public (bool success, Blocks block, int x, int y, int z) Raycast(Vector3 origin, Vector3 direction, float maxDistance) { - Vector3 start = Camera.Position; - Vector3 dir = Camera.Front.Normalized(); + int x = (int)MathF.Floor(origin.X); + int y = (int)MathF.Floor(origin.Y); + int z = (int)MathF.Floor(origin.Z); - float stepSize = 0.01f; - float distanceTraveled = 0f; + int stepX = direction.X > 0 ? 1 : -1; + int stepY = direction.Y > 0 ? 1 : -1; + int stepZ = direction.Z > 0 ? 1 : -1; - while (distanceTraveled < length) + float tDeltaX = direction.X != 0 ? MathF.Abs(1 / direction.X) : float.MaxValue; + float tDeltaY = direction.Y != 0 ? MathF.Abs(1 / direction.Y) : float.MaxValue; + float tDeltaZ = direction.Z != 0 ? MathF.Abs(1 / direction.Z) : float.MaxValue; + + float tMaxX = direction.X > 0 + ? (MathF.Floor(origin.X) + 1 - origin.X) * tDeltaX + : (origin.X - MathF.Floor(origin.X)) * tDeltaX; + float tMaxY = direction.Y > 0 + ? (MathF.Floor(origin.Y) + 1 - origin.Y) * tDeltaY + : (origin.Y - MathF.Floor(origin.Y)) * tDeltaY; + float tMaxZ = direction.Z > 0 + ? (MathF.Floor(origin.Z) + 1 - origin.Z) * tDeltaZ + : (origin.Z - MathF.Floor(origin.Z)) * tDeltaZ; + + float distance = 0f; + + while (distance <= maxDistance) { - Vector3 point = start + dir * distanceTraveled; - - int bx = (int)MathF.Floor(point.X); - int by = (int)MathF.Floor(point.Y); - int bz = (int)MathF.Floor(point.Z); - - Blocks block = GetBlock(bx, by, bz); + Blocks block = GetBlock(x, y, z); if (block != Blocks.Air) { - return (block, bx, by, bz); + return (true, block, x, y, z); } - distanceTraveled += stepSize; + // step to next voxel + if (tMaxX < tMaxY) + { + if (tMaxX < tMaxZ) + { + x += stepX; + distance = tMaxX; + tMaxX += tDeltaX; + } + else + { + z += stepZ; + distance = tMaxZ; + tMaxZ += tDeltaZ; + } + } + else + { + if (tMaxY < tMaxZ) + { + y += stepY; + distance = tMaxY; + tMaxY += tDeltaY; + } + else + { + z += stepZ; + distance = tMaxZ; + tMaxZ += tDeltaZ; + } + } } - return (Blocks.Air, 0, 0, 0); + return (false, Blocks.Air, 0, 0, 0); } public void Update()