editor/AdvancedMappingTools/Architecture/PergolaEditor.cs
namespace AdvancedMappingTools;

/// <summary>
/// A pergola along a path: post pairs, two side beams and slats across. The path is the middle line.
/// </summary>
[ToolCategory( "Buildings" )]
[Title( "Pergola" ), Icon( "deck" )]
public sealed class PergolaEditor( PrimitiveTool tool ) : PathEditor( tool )
{
	protected override bool SnapsToLines => true;
	protected override bool CanClose => true;

	protected override int MinPoints => 2;

	[Property, Group( "Pergola" ), Range( 16, 1024, slider: false ), WideMode]
	public float Width { get; set; } = 160.0f;

	[Property, Group( "Pergola" ), Range( 16, 1024, slider: false ), WideMode]
	public float Height { get; set; } = 128.0f;

	[Property, Group( "Pergola" ), Title( "Post Spacing" ), Range( 16, 1024, slider: false ), WideMode]
	public float PostSpacing { get; set; } = 128.0f;

	[Property, Group( "Pergola" ), Title( "Post Size" ), Range( 1, 32, slider: false ), WideMode]
	public float PostSize { get; set; } = 8.0f;

	[Property, Group( "Pergola" ), Title( "Slats" ), WideMode]
	public bool Slats { get; set; } = true;

	[Property, ShowIf( nameof( Slats ), true ), Group( "Pergola" ), Title( "Slat Spacing" ), Range( 4, 128, slider: false ), WideMode]
	public float SlatSpacing { get; set; } = 24.0f;

	protected override void BuildMesh( MeshKit kit )
	{
		var path = SamplePath();

		if ( path.Count < 2 ) return;

		var half = MathF.Max( Width, 8.0f ) * 0.5f;
		var height = MathF.Max( Height, 8.0f );
		var size = Math.Clamp( PostSize, 1.0f, 32.0f );
		var spacing = MathF.Max( PostSpacing, 8.0f );
		var posts = WalkPath( path, Closed, spacing );
		var left = posts.Select( x => x.Position + Normal( x.Direction ) * half ).ToList();
		var right = posts.Select( x => x.Position - Normal( x.Direction ) * half ).ToList();

		kit.Material = SurfaceMaterial;

		foreach ( var point in left )
			kit.Box( point + new Vector3( -size * 0.5f, -size * 0.5f, 0.0f ), point + new Vector3( size * 0.5f, size * 0.5f, height ) );

		foreach ( var point in right )
			kit.Box( point + new Vector3( -size * 0.5f, -size * 0.5f, 0.0f ), point + new Vector3( size * 0.5f, size * 0.5f, height ) );

		kit.Material = TrimMaterial;

		for ( var i = 0; i + 1 < left.Count; i++ )
		{
			kit.Beam( left[i].WithZ( height ), left[i + 1].WithZ( height ), 6.0f, 10.0f );
			kit.Beam( right[i].WithZ( height ), right[i + 1].WithZ( height ), 6.0f, 10.0f );
		}

		if ( !Slats ) return;

		var step = MathF.Max( SlatSpacing, 4.0f );

		foreach ( var (position, direction) in WalkPath( path, Closed, step ) )
		{
			var n = Normal( direction ) * half;

			kit.Beam( position + n + Vector3.Up * (height + 10.0f), position - n + Vector3.Up * (height + 10.0f), 3.0f, 4.0f );
		}
	}

	static Vector3 Normal( Vector3 direction )
	{
		var flat = direction.WithZ( 0.0f ).Normal;
		return new Vector3( -flat.y, flat.x, 0.0f );
	}
}