Utility struct that maps grid coordinates to world-space positions for the game arena. It stores width, height and cell size and provides methods to get a cell center, lift a point off the sand, and convert grid directions into world-space unit vectors.
namespace Coilgarden;
/// <summary>
/// Converts grid coordinates into world positions. The single authority on where a cell is,
/// so the tray, the snake and the apple cannot disagree by half a cell.
/// <para>
/// <b>The layout.</b> The tray lies in the world's Y/Z plane. Grid X runs along -Y and grid Y
/// runs up +Z, so a camera sitting on -X looking down +X sees the board the right way round
/// with no rotation of its own. World <b>-X is therefore "up" out of the tray</b>, towards the
/// viewer, and <see cref="Above"/> is how anything gets lifted off the sand.
/// </para>
/// <para>
/// Cell coordinates are taken as floats rather than <see cref="GridPos"/> throughout, because
/// the movement phase will need positions <em>between</em> cells and this should not have to
/// change to allow it.
/// </para>
/// </summary>
public readonly struct ArenaSpace
{
public ArenaSpace( int width, int height, float cellSize )
{
Width = width;
Height = height;
CellSize = cellSize;
}
public int Width { get; }
public int Height { get; }
public float CellSize { get; }
/// <summary>Width of the playfield in world units, not counting the rim.</summary>
public float SandWidth => Width * CellSize;
public float SandHeight => Height * CellSize;
/// <summary>The centre of a cell, on the surface of the sand.</summary>
public Vector3 Cell( GridPos cell ) => Cell( cell.X, cell.Y );
/// <summary>
/// A point on the sand at fractional cell coordinates. Cell centres fall on whole
/// numbers, so (0,0) is the middle of the bottom-left cell rather than its corner.
/// </summary>
public Vector3 Cell( float x, float y ) => new(
0f,
-(x - (Width - 1) * 0.5f) * CellSize,
(y - (Height - 1) * 0.5f) * CellSize );
/// <summary>Lifts a point off the sand towards the viewer. Distances are in cells.</summary>
public Vector3 Above( Vector3 point, float cells ) =>
point with { x = point.x - cells * CellSize };
/// <summary>
/// A grid direction as a world-space unit vector on the tray plane. Used to point the
/// snake's eyes and, later, to lean into a turn.
/// </summary>
public static Vector3 Facing( Direction direction )
{
var delta = direction.Delta();
return new Vector3( 0f, -delta.X, delta.Y );
}
/// <summary>
/// The direction at right angles to <paramref name="direction"/> on the tray plane, so a
/// pair of eyes can be placed either side of a heading.
/// </summary>
public static Vector3 Sideways( Direction direction )
{
var delta = direction.Delta();
return new Vector3( 0f, delta.Y, delta.X );
}
}