Terrain/Extensions.cs
using System;

namespace HC3.Terrain;

#nullable enable

public static class MathExtensions
{
	public static RectInt Clamp( this RectInt rect, RectInt other )
	{
		var min = Vector2Int.Max( rect.Position, other.Position );
		var max = Vector2Int.Min( rect.Position + rect.Size, other.Position + other.Size );

		return new RectInt( min, max - min );
	}

	public static Vector2Int Clamp( this Vector2Int vector, RectInt rect ) =>
		Vector2Int.Max( rect.Position, Vector2Int.Min( rect.Position + rect.Size, vector ) );

	public static Vector2Int GetTileIndex( this Vector2Int chunkIndex, int chunkSize ) =>
		chunkIndex * chunkSize;

	public static Vector2Int GetChunkIndex( this Vector2Int tileIndex, int chunkSize )
	{
		var chunkX = tileIndex.x >= 0 ? tileIndex.x / chunkSize : -(chunkSize - 1 - tileIndex.x) / chunkSize;
		var chunkY = tileIndex.y >= 0 ? tileIndex.y / chunkSize : -(chunkSize - 1 - tileIndex.y) / chunkSize;

		return new Vector2Int( chunkX, chunkY );
	}

	public static RectInt GetTileRange( this Vector2Int chunkIndex, int chunkSize )
	{
		var min = chunkIndex.GetTileIndex( chunkSize );
		var max = (chunkIndex + 1).GetTileIndex( chunkSize );

		return new RectInt( min, max - min );
	}

	public static IEnumerable<Vector2Int> GetChunkIndices( this RectInt tileRange, int chunkSize )
	{
		var min = tileRange.Position.GetChunkIndex( chunkSize );
		var max = (tileRange.Position + tileRange.Size + chunkSize - 1).GetChunkIndex( chunkSize );

		return new RectInt( min, max - min ).AsEnumerable();
	}

	public static IEnumerable<Vector2Int> AsEnumerable( this RectInt tileRange )
	{
		var min = tileRange.Position;
		var max = tileRange.Position + tileRange.Size;

		return Enumerable.Range( min.y, max.y - min.y )
			.SelectMany( y => Enumerable.Range( min.x, max.x - min.x )
				.Select( x => new Vector2Int( x, y ) ) );
	}

	public static RectInt Offset( this RectInt rect, Vector2Int delta ) =>
		new( rect.Position + delta, rect.Size );

	public static Vector2Int FloorToInt( this Vector2 vector ) =>
		new( vector.x.FloorToInt(), vector.y.FloorToInt() );

	public static Vector3Int FloorToInt( this Vector3 vector ) =>
		new( vector.x.FloorToInt(), vector.y.FloorToInt(), vector.z.FloorToInt() );

	public static int RoundToInt( this float value ) => (int)MathF.Round( value );

	public static Vector2Int RoundToInt( this Vector2 vector ) =>
		new( vector.x.RoundToInt(), vector.y.RoundToInt() );

	public static Vector3Int RoundToInt( this Vector3 vector ) =>
		new( vector.x.RoundToInt(), vector.y.RoundToInt(), vector.z.RoundToInt() );

	public static Vector2Int CeilToInt( this Vector2 vector ) =>
		new( vector.x.CeilToInt(), vector.y.CeilToInt() );

	public static Vector3Int CeilToInt( this Vector3 vector ) =>
		new( vector.x.CeilToInt(), vector.y.CeilToInt(), vector.z.CeilToInt() );
}