editor/AdvancedMappingTools/Architecture/AwningEditor.cs

Editor tool that generates a wall canopy or awning mesh: a sloped cover sheet, frame beams, optional rafters, valance and front posts. Exposes width, projection, height, drop and construction sizes as properties.

namespace AdvancedMappingTools;
/// <summary>
/// A wall canopy or awning: a sloped cover with frame beams and optional front posts.
/// </summary>
[ToolCategory( "Buildings" )]
[Title( "Canopy & Awning" ), Icon( "storefront" )]
public sealed class AwningEditor( PrimitiveTool tool ) : StampEditor( tool )
{
	[Property, Group( "Awning" ), Range( 16, 1024, slider: false ), WideMode]
	public float Width { get; set; } = 192.0f;

	[Property, Group( "Awning" ), Title( "Projection" ), Range( 8, 512, slider: false ), WideMode]
	public float Projection { get; set; } = 96.0f;

	[Property, Group( "Awning" ), Title( "Mount Height" ), Range( 8, 512, slider: false ), WideMode]
	public float Height { get; set; } = 128.0f;

	[Property, Group( "Awning" ), Title( "Drop" ), Range( 0, 256, slider: false ), WideMode]
	public float Drop { get; set; } = 24.0f;

	[Property, Group( "Awning" ), Title( "Posts" ), WideMode]
	public bool Posts { get; set; } = false;

	[Property, Group( "Awning" ), Title( "Valance" ), Range( 0, 64, slider: false ), WideMode]
	public float Valance { get; set; } = 8.0f;

	[Property, Group( "Construction" ), Title( "Cover Thickness" ), Range( .25f, 8, slider: false ), WideMode]
	public float CoverThickness { get; set; } = 1;

	[Property, Group( "Construction" ), Title( "Frame Size" ), Range( 1, 12, slider: false ), WideMode]
	public float FrameSize { get; set; } = 4;

	[Property, Group( "Construction" ), Title( "Post Size" ), Range( 1, 16, slider: false ), WideMode]
	public float PostSize { get; set; } = 6;

	[Property, Group( "Construction" ), Title( "Intermediate Rafters" ), WideMode]
	public bool RaftersEnabled { get; set; }

	[Property, Group( "Construction" ), Title( "Rafter Count" ), Range( 1, 12, slider: false ), WideMode]
	public int Rafters { get; set; } = 3;

	[Property, Group( "Construction" ), Title( "Rafter Size" ), Range( .5f, 8, slider: false ), WideMode]
	public float RafterSize { get; set; } = 2;

	public override IReadOnlySet<string> UnusedOptions
	{
		get
		{
			var hidden = new HashSet<string>();
			if ( !Posts )
				hidden.Add( nameof( PostSize ) );
			if ( !RaftersEnabled )
				hidden.UnionWith( [nameof( Rafters ), nameof( RafterSize )] );
			return hidden;
		}
	}

	public override bool ChangesOtherSettings( string property ) => property is nameof( Posts ) or nameof( RaftersEnabled );

	/// <summary>
	/// A triangle with a thickness: the corners in turn, and the direction it is thick in.
	/// </summary>
	static void Flap( MeshKit kit, Vector3 a, Vector3 b, Vector3 c, Vector3 thick )
	{
		var (d, e, f) = (a + thick, b + thick, c + thick);

		kit.BeginPart();
		kit.Face( c, b, a );
		kit.Face( d, e, f );
		kit.Face( a, b, e, d );
		kit.Face( b, c, f, e );
		kit.Face( c, a, d, f );
	}

	protected override void Stamp( MeshKit kit, Transform frame, Random random )
	{
		var sheet = Math.Clamp( CoverThickness, .25f, 8 );
		var frameSize = Math.Clamp( FrameSize, 1, 12 );
		var w = MathF.Max( Width, 16.0f ) * 0.5f;
		var depth = MathF.Max( Projection, 8.0f );
		var h = MathF.Max( Height, 8.0f );
		var drop = MathF.Max( Drop, 0.0f );
		var front = MathF.Max( h - drop, 4.0f );
		var valance = MathF.Max( Valance, 0.0f );
		var left = new Vector3( -w, 0, h );
		var right = new Vector3( w, 0, h );
		var frontLeft = new Vector3( -w, depth, front );
		var frontRight = new Vector3( w, depth, front );
		// The cover is a thin sheet, closed all round: a single face would leave open edges and
		// be invisible from one side.
		var down = Vector3.Up * sheet;
		kit.Hex( left - down, right - down, frontRight - down, frontLeft - down, left, right, frontRight, frontLeft );
		if ( valance > sheet )
			kit.Box( new Vector3( -w, depth - sheet, front - valance ), new Vector3( w, depth, front - sheet ) );
		// Side flaps from the wall down to the front edge.
		if ( valance > 0.0f )
		{
			Flap( kit, left, frontLeft - down, left - Vector3.Up * valance, Vector3.Right * sheet );
			Flap( kit, right, right - Vector3.Up * valance, frontRight - down, Vector3.Left * sheet );
		}

		kit.Material = TrimMaterial;
		kit.Beam( left, right, frameSize, frameSize );
		kit.Beam( frontLeft, frontRight, frameSize, frameSize );
		kit.Beam( left, frontLeft, frameSize * .75f, frameSize * .75f );
		kit.Beam( right, frontRight, frameSize * .75f, frameSize * .75f );
		var rafters = RaftersEnabled ? Math.Clamp( Rafters, 1, 12 ) : 0;
		var rafterSize = MathF.Min( Math.Clamp( RafterSize, .5f, 8 ), w / (rafters + 1) );
		for ( var i = 0; i < rafters; i++ )
		{
			var x = -w + 2 * w * (i + 1) / (rafters + 1);
			kit.Beam( new( x, 0, h ), new( x, depth, front ), rafterSize, rafterSize );
		}

		if ( !Posts )
			return;
		var post = MathF.Min( Math.Clamp( PostSize, 1, 16 ), w * .5f ) * .5f;
		foreach ( var x in new[]
		{
			-w + frameSize,
			w - frameSize
		}

	 )
			kit.Box( new Vector3( x - post, depth - post, 0 ), new Vector3( x + post, depth + post, front ) );
	}
}