using System; using System.Numerics; using gaemstone.Client.Systems; using gaemstone.ECS; using static gaemstone.Bloxel.Components.CoreComponents; using static gaemstone.Client.Components.RenderingComponents; using static gaemstone.Client.Components.ResourceComponents; using static gaemstone.Client.Systems.Windowing; namespace gaemstone.Bloxel.Client.Systems; [Module] public class ChunkMeshGenerator { private const int StartingCapacity = 1024; private static readonly Vector3[][] OffsetPerFacing = { new Vector3[]{ new(1,1,1), new(1,0,1), new(1,0,0), new(1,1,0) }, // East (+X) new Vector3[]{ new(0,1,0), new(0,0,0), new(0,0,1), new(0,1,1) }, // West (-X) new Vector3[]{ new(1,1,0), new(0,1,0), new(0,1,1), new(1,1,1) }, // Up (+Y) new Vector3[]{ new(1,0,1), new(0,0,1), new(0,0,0), new(1,0,0) }, // Down (-Y) new Vector3[]{ new(0,1,1), new(0,0,1), new(1,0,1), new(1,1,1) }, // South (+Z) new Vector3[]{ new(1,1,0), new(1,0,0), new(0,0,0), new(0,1,0) } // North (-Z) }; private static readonly int[] TriangleIndices = { 0, 1, 3, 1, 2, 3 }; private ushort[] _indices = new ushort[StartingCapacity]; private Vector3[] _vertices = new Vector3[StartingCapacity]; private Vector3[] _normals = new Vector3[StartingCapacity]; private Vector2[] _uvs = new Vector2[StartingCapacity]; [System] [Expression("[in] Chunk, ChunkStoreBlocks, HasBasicWorldGeneration, !(Mesh, *)")] public void GenerateChunkMeshes(Universe universe, EntityRef entity, in Chunk chunk, ChunkStoreBlocks blocks) { if (Generate(universe, chunk.Position, blocks) is MeshHandle handle) entity.Add(entity.NewChild("Mesh").Set(handle).Build()); else entity.Delete(); } public MeshHandle? Generate(Universe universe, ChunkPos chunkPos, ChunkStoreBlocks centerBlocks) { // TODO: We'll need a way to get neighbors again. // var storages = new ChunkStoreBlocks[3, 3, 3]; // foreach (var (x, y, z) in Neighbors.ALL.Prepend(Neighbor.None)) // if (_chunkStore.TryGetEntityId(chunkPos.Add(x, y, z), out var neighborId)) // if (_storageStore.TryGet(neighborId, out var storage)) // storages[x+1, y+1, z+1] = storage; // var centerStorage = storages[1, 1, 1]; var storages = new ChunkStoreBlocks[3, 3, 3]; storages[1, 1, 1] = centerBlocks; var indexCount = 0; var vertexCount = 0; for (var x = 0; x < 16; x++) for (var y = 0; y < 16; y++) for (var z = 0; z < 16; z++) { var block = universe.LookupAlive(centerBlocks[x, y, z]); if (block == null) continue; var blockVertex = new Vector3(x, y, z); var textureCell = block.GetOrThrow(); foreach (var facing in BlockFacings.All) { if (!IsNeighborEmpty(storages, x, y, z, facing)) continue; if (_indices.Length <= indexCount + 6) Array.Resize(ref _indices, _indices.Length << 1); if (_vertices.Length <= vertexCount + 4) { Array.Resize(ref _vertices, _vertices.Length << 1); Array.Resize(ref _normals , _vertices.Length << 1); Array.Resize(ref _uvs , _vertices.Length << 1); } for (var i = 0; i < TriangleIndices.Length; i++) _indices[indexCount++] = (ushort)(vertexCount + TriangleIndices[i]); var normal = facing.ToVector3(); for (var i = 0; i < 4; i++) { var offset = OffsetPerFacing[(int)facing][i]; _vertices[vertexCount] = blockVertex + offset; _normals[vertexCount] = normal; _uvs[vertexCount] = i switch { 0 => textureCell.TopLeft, 1 => textureCell.BottomLeft, 2 => textureCell.BottomRight, 3 => textureCell.TopRight, _ => throw new InvalidOperationException() }; vertexCount++; } } } // TODO: Should dynamically generating meshes require getting GL this way? var GL = universe.LookupByTypeOrThrow().GetOrThrow().GL; return (indexCount > 0) ? MeshManager.Create(GL, _indices.AsSpan(0, indexCount), _vertices.AsSpan(0, vertexCount), _normals.AsSpan(0, vertexCount), _uvs.AsSpan(0, vertexCount)) : null; } static bool IsNeighborEmpty( ChunkStoreBlocks[,,] blocks, int x, int y, int z, BlockFacing facing) { var cx = 1; var cy = 1; var cz = 1; switch (facing) { case BlockFacing.East : x += 1; if (x >= 16) cx += 1; break; case BlockFacing.West : x -= 1; if (x < 0) cx -= 1; break; case BlockFacing.Up : y += 1; if (y >= 16) cy += 1; break; case BlockFacing.Down : y -= 1; if (y < 0) cy -= 1; break; case BlockFacing.South : z += 1; if (z >= 16) cz += 1; break; case BlockFacing.North : z -= 1; if (z < 0) cz -= 1; break; } var neighborChunk = blocks[cx, cy, cz]; if (neighborChunk == null) return true; var neighborBlock = neighborChunk[x & 0b1111, y & 0b1111, z & 0b1111]; return neighborBlock.IsNone; } }