Rendering/ArenaSpace.cs

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 );
	}
}