Terrain/Terraforming/RaiseLowerMode.cs
using HC3.Terrain;
using System;

namespace HC3.Terraforming;

#nullable enable

public sealed class RaiseLowerMode : TerraformMode
{
	public override string Title => "Raise / Lower";
	public override string Description => "Increase or decrease the height of a square.";
	public override string Icon => "height";

	public const int MinSize = 0;
	public const int MaxSize = 8;

	private int _size = 1;

	private TileEdge? _selectedEdge;
	private TileCorner? _selectedCorner;

	[Property, Range( min: MinSize, max: MaxSize ), Step( 1f )]
	public int Size
	{
		get => _size;
		set
		{
			value = Math.Clamp( value, MinSize, MaxSize );

			if ( _size == value && Brush is not null ) return;

			_size = value;

			_selectedEdge = null;
			_selectedCorner = null;

			Brush = new BoxBrush( Math.Max( 1, Size ) );
		}
	}

	protected override void OnActivate()
	{
		Size = 1;
	}

	protected override void OnUpdate()
	{
		if ( Input.Down( "Run" ) )
		{
			Size += Math.Sign( Input.MouseWheel.y );
		}

		if ( Size == 0 )
		{
			var gridIndex = new Vector2Int( (int)MathF.Floor( CursorGridPos.x ), (int)MathF.Floor( CursorGridPos.y ) );
			var localPos = CursorGridPos - gridIndex;

			var (edge, corner) = GetSelectedFeature( localPos );

			if ( (edge, corner) != (_selectedEdge, _selectedCorner) )
			{
				(_selectedEdge, _selectedCorner) = (edge, corner);

				Brush = edge.HasValue ? new EdgeBrush( edge.Value )
					: corner.HasValue ? new CornerBrush( corner.Value )
					: new BoxBrush( 1 );
			}
		}
	}

	private (TileEdge? Edge, TileCorner? Corner) GetSelectedFeature( Vector2 localPos )
	{
		var featurePos = new Vector2Int(
			(int)MathF.Floor( localPos.x * 3 - 1f ),
			(int)MathF.Floor( localPos.y * 3 - 1f ) );

		if ( featurePos == 0 )
		{
			return (null, null);
		}

		if ( featurePos.x == 0 )
		{
			return (featurePos.y < 0 ? TileEdge.YMin : TileEdge.YMax, null);
		}

		if ( featurePos.y == 0 )
		{
			return (featurePos.x < 0 ? TileEdge.XMin : TileEdge.XMax, null);
		}

		return featurePos.x < 0
			? (null, featurePos.y < 0 ? TileCorner.XMinYMin : TileCorner.XMinYMax)
			: (null, featurePos.y < 0 ? TileCorner.XMaxYMin : TileCorner.XMaxYMax);
	}

	protected override void OnApply( TileArraySlice original, TileArraySlice modified, TerraformContext context )
	{
		var (min, max) = original.GetHeightRange();

		var delta = context.Delta;
		var tileset = context.Terrain.Tileset;

		Span<int> corners = stackalloc int[4];

		var materialIndex = Terraformer.Instance?.MaterialIndex;

		if ( Size == 0 )
		{
			var tile = original[0];
			var paint = tile.Paint;

			tile.GetCornerHeights( corners );

			if ( _selectedCorner is { } corner )
			{
				corners[(int)corner] += delta;

				if ( materialIndex is { } matIndex )
				{
					paint = paint.WithMaterialIndex( corner, matIndex );
				}
			}
			else if ( _selectedEdge is { } edge )
			{
				var (c0, c1) = edge.GetCorners();

				corners[(int)c0] += delta;
				corners[(int)c1] += delta;

				if ( materialIndex is { } matIndex )
				{
					paint = paint.WithMaterialIndex( c0, matIndex );
					paint = paint.WithMaterialIndex( c1, matIndex );
				}
			}
			else
			{
				for ( var i = 0; i < 4; ++i )
				{
					corners[i] += delta;
				}

				if ( materialIndex is { } matIndex )
				{
					paint = TilePaint.FromMaterialIndex( matIndex );
				}
			}

			modified[0] = tileset.FromCornerHeights( corners, paint );
		}
		else
		{
			TilePaint? paint = null;

			if ( materialIndex is { } matIndex )
			{
				paint = TilePaint.FromMaterialIndex( matIndex );
			}

			foreach ( var (index, tile) in original )
			{
				tile.GetCornerHeights( corners );

				for ( var i = 0; i < 4; ++i )
				{
					corners[i] = delta > 0
						? Math.Max( corners[i], min + delta )
						: Math.Min( corners[i], max + delta );
				}

				modified[index] = tileset.FromCornerHeights( corners, paint ?? tile.Paint );
			}
		}
	}
}

public sealed class CornerBrush : ITerraformBrush
{
	public Vector2Int Size => 1;
	public TileCorner Corner { get; }

	public CornerBrush( TileCorner corner )
	{
		Corner = corner;
	}

	public float GetWeight( Vector2Int tileIndex ) => Corner.GetHorizontalOffset() == tileIndex
		? 1f
		: 0.25f;
}

public sealed class EdgeBrush : ITerraformBrush
{
	public Vector2Int Size => 1;
	public TileEdge Edge { get; }

	public EdgeBrush( TileEdge edge )
	{
		Edge = edge;
	}

	public float GetWeight( Vector2Int tileIndex )
	{
		var (min, max) = Edge.GetCorners();

		return min.GetHorizontalOffset() == tileIndex || max.GetHorizontalOffset() == tileIndex
			? 1f
			: 0.25f;
	}
}