code/Utility/AmbientOcclusion.cs
namespace Boxfish.Utility;

/// <summary>
/// A little helper static class for looking up baked AO values.
/// </summary>
public static class AmbientOcclusion
{
	// This is messy I know!
	private static readonly IReadOnlyDictionary<int, List<(sbyte x, sbyte y, sbyte z)[]>> _aoTable 
		= new Dictionary<int, List<(sbyte x, sbyte y, sbyte z)[]>>()
	{
		// +z
		[0] = new()
		{
			new (sbyte, sbyte, sbyte)[3] { (0, 1, -1), (-1, 1, 0), (-1, 1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (0, 1, 1), (-1, 1, 0), (-1, 1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (0, 1, 1), (1, 1, 0), (1, 1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (0, 1, -1), (1, 1, 0), (1, 1, -1) }
		},

		// -z
		[1] = new()
		{
			new (sbyte, sbyte, sbyte)[3] { (0, -1, -1), (1, -1, 0), (1, -1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (0, -1, 1), (1, -1, 0), (1, -1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (0, -1, 1), (-1, -1, 0), (-1, -1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (0, -1, -1), (-1, -1, 0), (-1, -1, -1) }
		},

		// -x
		[2] = new()
		{
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, -1), (-1, 1, 0), (-1, 1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, -1), (-1, -1, 0), (-1, -1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, 1), (-1, -1, 0), (-1, -1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, 1), (-1, 1, 0), (-1, 1, 1) }
		},

		// +y
		[3] = new()
		{
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, 1), (0, 1, 1), (-1, 1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, 1), (0, -1, 1), (-1, -1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (1, 0, 1), (0, -1, 1), (1, -1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (1, 0, 1), (0, 1, 1), (1, 1, 1) }
		},

		// +x
		[4] = new()
		{
			new (sbyte, sbyte, sbyte)[3] { (1, 0, 1), (1, 1, 0), (1, 1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (1, 0, 1), (1, -1, 0), (1, -1, 1) },
			new (sbyte, sbyte, sbyte)[3] { (1, 0, -1), (1, -1, 0), (1, -1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (1, 0, -1), (1, 1, 0), (1, 1, -1) }
		},

		// -y
		[5] = new()
		{
			new (sbyte, sbyte, sbyte)[3] { (1, 0, -1), (0, 1, -1), (1, 1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (1, 0, -1), (0, -1, -1), (1, -1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, -1), (0, -1, -1), (-1, -1, -1) },
			new (sbyte, sbyte, sbyte)[3] { (-1, 0, -1), (0, 1, -1), (-1, 1, -1) }
		}
	};

	private static int Occlusion<T, U>( BaseVoxelVolume<T, U>.Chunk chunk, Vector3Int localPos, int x, int y, int z )
		where T : struct
		where U : unmanaged
	{
		if ( chunk.RelativeQuery( localPos.x + x, localPos.y + z, localPos.z + y ).HasVoxel )
			return 1;

		return 0;
	}

	/// <summary>
	/// Builds AO values for a voxel based on the parameters.
	/// </summary>
	/// <param name="chunk"></param>
	/// <param name="localPos"></param>
	/// <param name="face"></param>
	/// <param name="vertex"></param>
	/// <returns>A value between 0 and 3 determining the ambient occlusion strength for a vertex.</returns>
	public static byte Fetch<T, U>( BaseVoxelVolume<T, U>.Chunk chunk, Vector3Int localPos, int face, int vertex ) 
		where T : struct
		where U : unmanaged
	{
		if ( !_aoTable.TryGetValue( face, out var values ) )
			return 0;

		var table = values[vertex];
		var ao = Occlusion( chunk, localPos, table[0].x, table[0].y, table[0].z )
			+ Occlusion( chunk, localPos, table[1].x, table[1].y, table[1].z )
			+ Occlusion( chunk, localPos, table[2].x, table[2].y, table[2].z );

		return (byte)ao;
	}
}