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 ) );
}
}