code/BaseVoxelVolume/BaseVoxelVolume.Chunk.cs
namespace Boxfish;
partial class BaseVoxelVolume<T, U>
{
/// <summary>
/// Our container of chunks in this volume.
/// </summary>
public IReadOnlyDictionary<Vector3Int, Chunk> Chunks => _chunks;
internal Dictionary<Vector3Int, Chunk> _chunks = new();
/// <summary>
/// Call this method to override the chunks of this voxel volume.
/// </summary>
public void SetChunks( Dictionary<Vector3Int, Chunk> dictionary )
{
_chunks = dictionary;
if ( dictionary is null )
return;
foreach ( var (_, chunk) in dictionary )
chunk.SetParent( this );
}
/// <summary>
/// Our chunk structure, contains a 3-dimensional array of voxels.
/// </summary>
public sealed class Chunk : IEquatable<Chunk>
{
private const int CHUNK_SIZE_P2 = VoxelUtils.CHUNK_SIZE * VoxelUtils.CHUNK_SIZE;
private const int CHUNK_SIZE_P3 = CHUNK_SIZE_P2 * VoxelUtils.CHUNK_SIZE;
#pragma warning disable SB3000 // Hotloading not supported
private static Vector3Int[] _neighbors =
[
new ( 1, 0, 0 ),
new ( -1, 0, 0 ),
new ( 0, 1, 0 ),
new ( 0, -1, 0 ),
new ( 0, 0, 1 ),
new ( 0, 0, -1 ),
];
#pragma warning restore SB3000 // Hotloading not supported
public int X { get; }
public int Y { get; }
public int Z { get; }
public Vector3Int Position => new( X, Y, Z );
internal bool Empty { get; set; }
private BaseVoxelVolume<T, U> _volume;
private T[] _voxels;
private Dictionary<Vector3Int, Chunk> _chunks;
public Chunk( int x, int y, int z, Dictionary<Vector3Int, Chunk> chunks = null )
{
X = x;
Y = y;
Z = z;
_chunks = chunks;
_voxels = new T[CHUNK_SIZE_P3];
}
public Chunk( int x, int y, int z, BaseVoxelVolume<T, U> volume = null )
{
X = x;
Y = y;
Z = z;
_volume = volume;
_chunks = volume?._chunks;
_voxels = new T[CHUNK_SIZE_P3];
}
/// <summary>
/// Flattens the 3D-coordinates into an index of our voxel array.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <returns></returns>
public int Flatten( byte x, byte y, byte z )
=> x + y * VoxelUtils.CHUNK_SIZE + z * CHUNK_SIZE_P2;
public void SetParent( BaseVoxelVolume<T, U> volume )
{
_volume = volume;
_chunks = volume._chunks;
}
public T GetVoxel( byte x, byte y, byte z )
=> _voxels[Flatten( x, y, z )];
public T[] GetVoxels() => _voxels;
/// <inheritdoc cref="BaseVoxelVolume{T, U}.Query(int, int, int, BaseVoxelVolume{T, U}.Chunk)"/>
public VoxelQueryData RelativeQuery( int x, int y, int z )
=> _volume.Query( x, y, z, this );
/// <summary>
/// Set voxel in local position
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <param name="voxel"></param>
/// <exception cref="IndexOutOfRangeException">When accessing outside of the 0-15 range.</exception>
public void SetVoxel( byte x, byte y, byte z, T voxel = default )
=> _voxels[Flatten( x, y, z )] = voxel;
/// <summary>
/// Get neighbors depending on the local position given in the parameters.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <param name="includeSelf"></param>
/// <returns></returns>
public IEnumerable<Chunk> GetNeighbors( byte x, byte y, byte z, bool includeSelf = true )
{
// Let's include this chunk too if we want.
if ( includeSelf ) yield return this;
// Yield return affected neighbors.
foreach ( var direction in _neighbors )
{
// Check if we should include the neighbor.
if ( _chunks.TryGetValue( Position + direction, out var result )
&& ((direction.x == 1 && x >= VoxelUtils.CHUNK_SIZE - 1) || (direction.x == -1 && x <= 0)
|| (direction.y == 1 && y >= VoxelUtils.CHUNK_SIZE - 1) || (direction.y == -1 && y <= 0)
|| (direction.z == 1 && z >= VoxelUtils.CHUNK_SIZE - 1) || (direction.z == -1 && z <= 0)) )
{
yield return result;
continue;
}
}
int GetBorderDirection( int value )
{
if ( value <= 0 ) return -1;
if ( value >= VoxelUtils.CHUNK_SIZE - 1 ) return 1;
return 0;
}
// Get corner for refreshing AO aswell :D
var directions = new Vector3Int( GetBorderDirection( x ), GetBorderDirection( y ), GetBorderDirection( z ) );
var corner = Position + directions;
if ( !corner.Equals( Position ) && _chunks.TryGetValue( corner, out var chunk ) )
yield return chunk;
}
public bool Equals( Chunk other )
{
return other.Position.Equals( Position );
}
public override bool Equals( object obj )
{
return obj is Chunk other
&& Equals( other );
}
public override int GetHashCode()
{
return Position.GetHashCode();
}
}
}