Terrain/TerrainBrush.cs
using System;
using Sandbox.Utility;

namespace HC3.Terrain;

#nullable enable

public interface ITerrainBrush
{
	int Size { get; }

	float GetWeight( Vector2Int tileIndex );

	void Apply( SlopeTileset tileset, TileArraySlice src, TileArraySlice dst, int startHeight, int targetHeight );
}

public sealed class MountainBrush : ITerrainBrush
{
	public float InnerRadius { get; }
	public float OuterRadius { get; }

	public MountainBrush( float radius, int margin = 8 )
	{
		InnerRadius = radius;
		OuterRadius = radius + margin;
	}

	public int Size => (int)MathF.Ceiling( OuterRadius * 2 );

	public float GetWeight( Vector2Int tileIndex )
	{
		var center = Size / 2f;
		var dist = Math.Clamp( (tileIndex - center).Length / InnerRadius, 0f, 1f );

		return 1f - Easing.EaseIn( dist );
	}

	public void Apply( SlopeTileset tileset, TileArraySlice src, TileArraySlice dst, int startHeight, int targetHeight )
	{
		Span<int> heights = stackalloc int[4];

		if ( targetHeight == startHeight )
		{
			src.CopyTo( dst );
			return;
		}

		var margin = (int)MathF.Floor( OuterRadius - InnerRadius );

		targetHeight = Math.Clamp( targetHeight, startHeight - margin * 4, startHeight + margin * 4 );

		var center = Size / 2f;

		for ( var y = 0; y < src.Size.y; ++y )
		{
			for ( var x = 0; x < src.Size.x; ++x )
			{
				var originalTile = src[new Vector2Int( x, y )];

				for ( var i = 0; i < 4; ++i )
				{
					var corner = (TileCorner)i;
					var gridPos = new Vector2Int( x, y ) + corner.GetHorizontalOffset();
					var dist = (gridPos - center).Length;
					var cornerStartHeight = originalTile.GetCornerHeight( corner );

					var offset = dist < InnerRadius
						? Easing.EaseIn( dist / InnerRadius ) * InnerRadius * 2f
						: dist * 4f - InnerRadius * 2f;

					var offsetInt = (int)MathF.Round( offset );

					heights[i] = targetHeight > startHeight
						? Math.Max( cornerStartHeight, targetHeight - offsetInt )
						: Math.Min( cornerStartHeight, targetHeight + offsetInt );
				}

				dst[new Vector2Int( x, y )] = tileset.FromCornerHeights( heights, originalTile.Paint );
			}
		}
	}
}