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