Terrain/TilePaint.cs
using System;
namespace HC3.Terrain;
/// <summary>
/// Describes how a single tile has been painted.
/// Each corner can have a different material.
/// </summary>
public readonly struct TilePaint : IEquatable<TilePaint>
{
internal const int SizeBytes = sizeof( ushort );
private readonly ushort _encoded;
private const int BitsPerCorner = (SizeBytes << 3) >> 2;
private const ushort CornerBitMask = (1 << BitsPerCorner) - 1;
public const int MaxMaterialIndex = CornerBitMask;
private TilePaint( ushort encoded )
{
_encoded = encoded;
}
/// <summary>
/// Gets the material index of a given tile corner. This index is used to look up
/// a material in a <see cref="TerrainPalette"/>.
/// </summary>
public int GetMaterialIndex( TileCorner corner )
{
var shift = (int)corner * BitsPerCorner;
return (_encoded & (CornerBitMask << shift)) >> shift;
}
/// <summary>
/// Returns this tile paint with a particular corner's material index replaced.
/// </summary>
public TilePaint WithMaterialIndex( TileCorner corner, int index )
{
ArgumentOutOfRangeException.ThrowIfLessThan( index, 0 );
ArgumentOutOfRangeException.ThrowIfGreaterThan( index, MaxMaterialIndex );
var shift = (int)corner * BitsPerCorner;
var withoutCorner = _encoded & ~(CornerBitMask << shift);
return new TilePaint( (ushort)(withoutCorner | (index << shift)) );
}
public bool Equals( TilePaint other ) => _encoded == other._encoded;
public override bool Equals( object obj ) => obj is TilePaint other && Equals( other );
public override int GetHashCode() => _encoded.GetHashCode();
public override string ToString() =>
$"[{GetMaterialIndex( TileCorner.XMinYMin )}, " +
$"{GetMaterialIndex( TileCorner.XMaxYMin )}, " +
$"{GetMaterialIndex( TileCorner.XMinYMax )}, " +
$"{GetMaterialIndex( TileCorner.XMaxYMax )}]";
public static bool operator ==( TilePaint a, TilePaint b ) => a.Equals( b );
public static bool operator !=( TilePaint a, TilePaint b ) => !a.Equals( b );
/// <summary>
/// Count how many corners have a different material index compared to <paramref name="other"/>.
/// </summary>
public int GetTotalDistance( TilePaint other )
{
var sum = 0;
sum += GetMaterialIndex( TileCorner.XMinYMin ) != other.GetMaterialIndex( TileCorner.XMinYMin ) ? 1 : 0;
sum += GetMaterialIndex( TileCorner.XMaxYMin ) != other.GetMaterialIndex( TileCorner.XMaxYMin ) ? 1 : 0;
sum += GetMaterialIndex( TileCorner.XMinYMax ) != other.GetMaterialIndex( TileCorner.XMinYMax ) ? 1 : 0;
sum += GetMaterialIndex( TileCorner.XMaxYMax ) != other.GetMaterialIndex( TileCorner.XMaxYMax ) ? 1 : 0;
return sum;
}
public static TilePaint FromMaterialIndex( int index )
{
ArgumentOutOfRangeException.ThrowIfLessThan( index, 0, nameof( index ) );
ArgumentOutOfRangeException.ThrowIfGreaterThan( index, MaxMaterialIndex, nameof( index ) );
return new TilePaint( (ushort)(index | (index << BitsPerCorner) | (index << (BitsPerCorner * 2)) | (index << (BitsPerCorner * 3))) );
}
public static TilePaint FromMaterialIndices( ReadOnlySpan<int> indices )
{
ArgumentOutOfRangeException.ThrowIfNotEqual( indices.Length, 4, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfLessThan( indices[0], 0, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfLessThan( indices[1], 0, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfLessThan( indices[2], 0, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfLessThan( indices[3], 0, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfGreaterThan( indices[0], MaxMaterialIndex, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfGreaterThan( indices[1], MaxMaterialIndex, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfGreaterThan( indices[2], MaxMaterialIndex, nameof( indices ) );
ArgumentOutOfRangeException.ThrowIfGreaterThan( indices[3], MaxMaterialIndex, nameof( indices ) );
return new TilePaint( (ushort)(indices[0] | (indices[1] << BitsPerCorner) | (indices[2] << (BitsPerCorner * 2)) | (indices[3] << (BitsPerCorner * 3))) );
}
}