editor/AdvancedMappingTools/Procedural/RockGenerator.cs

An editor tool option that stamps a seeded cluster of irregular boulders into the map. Each boulder is a random-sided prism, with a smaller tapered cap on the first half of them.

namespace AdvancedMappingTools;

/// <summary>
/// A cluster of irregular boulders, seeded so the same seed gives the same rocks.
/// </summary>
[ToolCategory( "Vegetation" )]
[Title( "Rock Formation" ), Icon( "landscape" )]
public sealed class RockEditor( PrimitiveTool tool ) : StampEditor( tool )
{
	[Property, Group( "Rock" ), Range( 4, 2048, slider: false ), WideMode]
	public float Radius { get; set; } = 96.0f;

	[Property, Group( "Rock" ), Range( 4, 2048, slider: false ), WideMode]
	public float Height { get; set; } = 72.0f;

	[Property, Group( "Rock" ), Range( 1, 24, slider: false ), WideMode]
	public int Boulders { get; set; } = 5;

	[Property, Group( "Rock" ), Range( 0, 1 ), WideMode, Description( "How much the boulders scatter around the centre." )]
	public float Spread { get; set; } = 0.6f;

	[Property, Group( "Rock" ), Range( 3, 16, slider: false ), WideMode]
	public int Sides { get; set; } = 7;

	protected override void Stamp( MeshKit kit, Transform frame, Random random )
	{
		var count = Math.Clamp( Boulders, 1, 24 );
		var sides = Math.Clamp( Sides, 3, 16 );
		var baseRadius = MathF.Max( Radius, 4.0f ) / (float)Math.Sqrt( count );
		var height = MathF.Max( Height, 4.0f );

		for ( var i = 0; i < count; i++ )
		{
			var angle = (float)random.NextDouble() * MathF.PI * 2.0f;
			var distance = (float)random.NextDouble() * Spread * MathF.Max( Radius, 4.0f ) * 0.7f;
			var center = new Vector3( MathF.Cos( angle ) * distance, MathF.Sin( angle ) * distance, 0.0f );
			var size = baseRadius * (0.55f + (float)random.NextDouble() * 0.9f);
			var tall = height * (0.45f + (float)random.NextDouble() * 0.8f);
			var points = new List<Vector3>( sides );

			for ( var s = 0; s < sides; s++ )
			{
				var a = s * MathF.PI * 2.0f / sides + (float)random.NextDouble() * 0.25f;
				var r = size * (0.7f + (float)random.NextDouble() * 0.5f);

				points.Add( center + new Vector3( MathF.Cos( a ) * r, MathF.Sin( a ) * r, 0.0f ) );
			}

			kit.Prism( points, 0.0f, tall );

			if ( i < count / 2 )
			{
				var top = new List<Vector3>( sides );

				foreach ( var p in points )
					top.Add( center + new Vector3( (p.x - center.x) * 0.55f, (p.y - center.y) * 0.55f, 0.0f ) );

				kit.Prism( top, tall * 0.8f, tall * 1.15f );
			}
		}
	}
}