You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
4.7 KiB
128 lines
4.7 KiB
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<Mesh>(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<TextureCoords4>(); |
|
|
|
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<Game>().GetOrThrow<Canvas>().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; |
|
} |
|
}
|
|
|