Terrain/TerrainTile.cs
using System;
namespace HC3.Terrain;
#nullable enable
/// <summary>
/// Used to look up an edge of a tile.
/// </summary>
public enum TileEdge
{
Up = 0,
Right = 1,
Down = 2,
Left = 3,
XMin = Left,
XMax = Right,
YMin = Down,
YMax = Up
}
/// <summary>
/// Used to look up a corner of a tile.
/// </summary>
public enum TileCorner
{
XMinYMin = 0,
XMaxYMin = 1,
XMinYMax = 2,
XMaxYMax = 3
}
/// <summary>
/// One tile of the terrain. Has a height and a slope shape.
/// </summary>
public readonly struct TerrainTile : IEquatable<TerrainTile>
{
internal const int SizeBytes = sizeof( ushort ) + TileSlope.SizeBytes;
public ushort BaseHeight { get; init; }
public TileSlope Slope { get; init; }
public TilePaint Paint { get; init; }
public TerrainTile( ushort baseHeight, TileSlope slope, TilePaint paint )
{
BaseHeight = baseHeight;
Slope = slope;
Paint = paint;
}
public int MinHeight => BaseHeight;
public int MaxHeight => BaseHeight + Slope.MaxHeightOffset;
public int GetCornerHeight( TileCorner corner ) => BaseHeight + Slope.GetHeightOffset( corner );
public void GetCornerHeights( Span<int> heights )
{
Slope.GetHeightOffsets( heights );
heights[0] += BaseHeight;
heights[1] += BaseHeight;
heights[2] += BaseHeight;
heights[3] += BaseHeight;
}
/// <summary>
/// Gets the height of a relative position where <c>(0,0)</c> is the height at <see cref="TileCorner.XMinYMin"/>
/// and <c>(1,1)</c> is the height at <see cref="TileCorner.XMaxYMax"/>. Position is clamped on each axis.
/// Height is in terrain units, needs to be scaled by <see cref="GridManager.HeightStep"/> to get world units.
/// </summary>
public float GetHeight( Vector2 position ) =>
BaseHeight + Slope.GetHeightOffset( position );
public bool Equals( TerrainTile other )
{
return BaseHeight == other.BaseHeight && Slope == other.Slope && Paint == other.Paint;
}
public override bool Equals( object? obj )
{
return obj is TerrainTile other && Equals( other );
}
public override int GetHashCode()
{
return HashCode.Combine( BaseHeight, Slope, Paint );
}
public static TerrainTile LevelGround( int height, TilePaint paint = default )
{
height = Math.Clamp( height, 0, ushort.MaxValue );
return new TerrainTile( (ushort)height, TileSlope.LevelGround, paint );
}
}
public static class TileExtensions
{
public static (TileCorner Min, TileCorner Max) GetCorners( this TileEdge edge ) => edge switch
{
TileEdge.XMin => (TileCorner.XMinYMin, TileCorner.XMinYMax),
TileEdge.XMax => (TileCorner.XMaxYMin, TileCorner.XMaxYMax),
TileEdge.YMin => (TileCorner.XMinYMin, TileCorner.XMaxYMin),
TileEdge.YMax => (TileCorner.XMinYMax, TileCorner.XMaxYMax),
_ => throw new ArgumentOutOfRangeException( nameof( edge ) )
};
public static Vector2Int GetHorizontalOffset( this TileCorner corner ) => corner switch
{
TileCorner.XMinYMin => new Vector2Int( 0, 0 ),
TileCorner.XMaxYMin => new Vector2Int( 1, 0 ),
TileCorner.XMinYMax => new Vector2Int( 0, 1 ),
TileCorner.XMaxYMax => new Vector2Int( 1, 1 ),
_ => throw new ArgumentOutOfRangeException( nameof( corner ) )
};
public static Vector2Int GetDirection( this TileEdge edge ) => edge switch
{
TileEdge.XMin => new Vector2Int( -1, 0 ),
TileEdge.XMax => new Vector2Int( 1, 0 ),
TileEdge.YMin => new Vector2Int( 0, -1 ),
TileEdge.YMax => new Vector2Int( 0, 1 ),
_ => throw new ArgumentOutOfRangeException( nameof( edge ) )
};
public static TileEdge ToTileEdge( this Rotation rotation )
{
var forward = rotation.Forward;
return Math.Abs( forward.x ) > Math.Abs( forward.y )
? forward.x > 0 ? TileEdge.XMax : TileEdge.XMin
: forward.y > 0 ? TileEdge.YMax : TileEdge.YMin;
}
public static TileEdge GetOpposite( this TileEdge edge ) => edge switch
{
TileEdge.XMin => TileEdge.XMax,
TileEdge.XMax => TileEdge.XMin,
TileEdge.YMin => TileEdge.YMax,
TileEdge.YMax => TileEdge.YMin,
_ => throw new ArgumentOutOfRangeException( nameof( edge ) )
};
public static PathMask ToPathMask( this TileEdge edge ) => edge switch
{
TileEdge.Left => PathMask.Left,
TileEdge.Right => PathMask.Right,
TileEdge.Down => PathMask.Down,
TileEdge.Up => PathMask.Up,
_ => throw new ArgumentOutOfRangeException( nameof( edge ) )
};
public static TileEdge Rotate( this TileEdge edge, int clockwise90Steps )
{
return (TileEdge)(((int)edge + clockwise90Steps) & 3);
}
public static TileEdge RotateDegrees( this TileEdge edge, float yaw )
{
return edge.Rotate( (int)MathF.Round( yaw / 90f ) );
}
public static TileEdge GetTileEdge( this Vector3 dir )
{
if ( MathF.Abs( dir.x ) > MathF.Abs( dir.y ) )
{
return dir.x > 0 ? TileEdge.Right : TileEdge.Left;
}
else
{
return dir.y > 0 ? TileEdge.Up : TileEdge.Down;
}
}
}