using Repoverse.Core.Model;
namespace Repoverse.Core.Voxels;
/// Structural role of a voxel. Interactive things are entities, not blocks (§12).
public enum BlockType : byte
{
Air = 0,
Bedrock,
Soil,
Grass,
Road,
Sidewalk,
Foundation,
Wall,
Floor,
Roof,
Window,
Partition,
Ladder,
Scaffold,
}
///
/// 16-bit packed voxel: 8 bits block type, 4 bits material family, 4 bits state.
/// A 32³ chunk is 64 KiB.
///
public readonly record struct Voxel(ushort Packed)
{
public static readonly Voxel Air = default;
public Voxel(BlockType type, MaterialFamily material = MaterialFamily.Concrete, byte state = 0)
: this((ushort)((byte)type | ((int)material & 0xF) << 8 | (state & 0xF) << 12)) { }
public BlockType Type => (BlockType)(Packed & 0xFF);
public MaterialFamily Material => (MaterialFamily)((Packed >> 8) & 0xF);
public byte State => (byte)(Packed >> 12);
public bool IsAir => Type == BlockType.Air;
/// Blocks the player/agents can move through.
public bool IsPassable => Type is BlockType.Air or BlockType.Ladder;
/// Hides faces of neighbours. Windows and ladders are see-through.
public bool IsOpaque => Type is not (BlockType.Air or BlockType.Window or BlockType.Ladder or BlockType.Scaffold);
/// Key used to batch faces into one draw call (§14 material groups).
public ushort MeshKey => (ushort)(Packed & 0x0FFF);
}
public readonly record struct ChunkCoord(int X, int Y, int Z)
{
public static ChunkCoord FromVoxel(int x, int y, int z) =>
new(FloorDiv(x), FloorDiv(y), FloorDiv(z));
public static int FloorDiv(int v) => v >= 0 ? v / WorldConstants.ChunkSize : (v + 1) / WorldConstants.ChunkSize - 1;
public int OriginX => X * WorldConstants.ChunkSize;
public int OriginY => Y * WorldConstants.ChunkSize;
public int OriginZ => Z * WorldConstants.ChunkSize;
public int ChebyshevDistance(ChunkCoord o) => Math.Max(Math.Abs(X - o.X), Math.Max(Math.Abs(Y - o.Y), Math.Abs(Z - o.Z)));
public override string ToString() => $"{X},{Y},{Z}";
}
public sealed class Chunk
{
private const int S = WorldConstants.ChunkSize;
private readonly ushort[] _voxels = new ushort[WorldConstants.ChunkVolume];
public Chunk(ChunkCoord coord) => Coord = coord;
public ChunkCoord Coord { get; }
public int NonAirCount { get; private set; }
public bool IsEmpty => NonAirCount == 0;
public static int Index(int x, int y, int z) => (y * S + z) * S + x;
public Voxel this[int x, int y, int z]
{
get => new(_voxels[Index(x, y, z)]);
set
{
var i = Index(x, y, z);
var was = _voxels[i] & 0xFF;
var now = value.Packed & 0xFF;
if (was == 0 && now != 0) NonAirCount++;
else if (was != 0 && now == 0) NonAirCount--;
_voxels[i] = value.Packed;
}
}
public Voxel GetIndex(int i) => new(_voxels[i]);
public ReadOnlySpan Raw => _voxels;
}