Terrain/Terraforming/MountainMode.cs
using HC3.Terrain;
using Sandbox.Utility;
using System;
namespace HC3.Terraforming;
#nullable enable
public sealed class MountainMode : TerraformMode
{
public override string Title => "Mountain Tool";
public override string Description => "Make a mountain out of a molehill.";
public override string Icon => "landscape";
public override int Order => 1;
public const float MinRadius = 1;
public const float MaxRadius = 16;
public const int MaxGradient = 2;
private float _radius = 1;
[Property, Range( min: MinRadius, max: MaxRadius ), Step( 0.5f )]
public float InnerRadius
{
get => _radius;
set
{
value = Math.Clamp( value, MinRadius, MaxRadius );
// ReSharper disable once CompareOfFloatsByEqualityOperator
if ( _radius == value ) return;
_radius = value;
Brush = new RadialBrush( value );
}
}
public float OuterRadius => InnerRadius + Margin;
public MountainMode()
{
Margin = 8;
}
protected override void OnActivate()
{
Brush ??= new RadialBrush( InnerRadius );
}
protected override void OnUpdate()
{
if ( Input.Down( "Run" ) )
{
InnerRadius += Math.Sign( Input.MouseWheel.y ) * 0.5f;
}
}
protected override void OnApply( TileArraySlice original, TileArraySlice modified, TerraformContext context )
{
Span<int> heights = stackalloc int[4];
Span<int> materials = stackalloc int[4];
var materialIndex = Terraformer.Instance?.MaterialIndex;
var center = original.Size / 2f;
if ( context.Delta == 0 )
{
original.CopyTo( modified );
if ( materialIndex is { } matIndex )
{
foreach ( var (index, originalTile) in original )
{
for ( var i = 0; i < 4; ++i )
{
var corner = (TileCorner)i;
var gridPos = index + corner.GetHorizontalOffset();
var dist = (gridPos - center).Length;
materials[i] = dist < InnerRadius
? matIndex
: originalTile.Paint.GetMaterialIndex( corner );
}
modified[index] = originalTile with { Paint = TilePaint.FromMaterialIndices( materials ) };
}
}
return;
}
var margin = (int)MathF.Floor( OuterRadius - InnerRadius );
var sign = Math.Sign( context.Delta );
var tileset = context.Terrain.Tileset;
var minDist = int.MaxValue;
var maxDist = int.MinValue;
foreach ( var (index, originalTile) in original )
{
for ( var i = 0; i < 4; ++i )
{
var corner = (TileCorner)i;
var gridPos = index + corner.GetHorizontalOffset();
var height = originalTile.GetCornerHeight( corner );
var target = -sign * GetOffset( (gridPos - center).Length );
var dist = sign * (height - target);
minDist = Math.Min( minDist, dist );
maxDist = Math.Max( maxDist, dist );
}
}
var delta = Math.Clamp( context.Delta, -margin * 4, +margin * 4 );
foreach ( var (index, originalTile) in original )
{
for ( var i = 0; i < 4; ++i )
{
var corner = (TileCorner)i;
var gridPos = index + corner.GetHorizontalOffset();
var cornerStartHeight = originalTile.GetCornerHeight( corner );
var dist = (gridPos - center).Length;
var target = sign * minDist + delta - sign * GetOffset( dist );
heights[i] = delta > 0
? Math.Max( cornerStartHeight, target )
: Math.Min( cornerStartHeight, target );
materials[i] = dist < InnerRadius
? materialIndex ?? originalTile.Paint.GetMaterialIndex( corner )
: originalTile.Paint.GetMaterialIndex( corner );
}
modified[index] = tileset.FromCornerHeights( heights, TilePaint.FromMaterialIndices( materials ) );
}
}
private int GetOffset( float dist )
{
var offset = dist < InnerRadius
? Easing.EaseIn( dist / InnerRadius ) * InnerRadius * MaxGradient * 0.5f
: dist * MaxGradient - InnerRadius * MaxGradient * 0.5f;
return (int)MathF.Round( offset );
}
}