Placeholder
namespace AdvancedMappingTools;
public enum DormerStyle
{
[Title( "Gable" )] Gable,
[Title( "Shed" )] Shed,
Flat,
Hip,
[Title( "Barrel (Round)" )] Barrel,
[Title( "Segmental Arch" )] Segmental
}
public sealed class DormerSettings : PropSettings
{
[Property, Group( "Dormer" ), Title( "Style" ), WideMode]
public DormerStyle Style { get; set; } = DormerStyle.Gable;
[Property, Group( "Dormer" ), Range( 16, 512, slider: false ), WideMode]
public float Width { get; set; } = 64.0f;
[Property, Group( "Dormer" ), Range( 8, 512, slider: false ), WideMode]
public float Depth { get; set; } = 56.0f;
[Property, Group( "Dormer" ), Range( 8, 512, slider: false ), WideMode]
public float Height { get; set; } = 56.0f;
[Property, Group( "Dormer" ), Title( "Roof Rise" ), Range( 2, 256, slider: false ), WideMode]
public float RoofRise { get; set; } = 24.0f;
[Property, Group( "Dormer" ), Title( "Overhang" ), Range( 0, 96, slider: false ), WideMode]
public float Overhang { get; set; } = 6.0f;
[Property, Group( "Window" ), WideMode]
public bool Window { get; set; } = true;
[Property, ShowIf( nameof( Window ), true ), Group( "Window" ), Title( "Round" ), WideMode, Description( "A round window in the front face instead of a rectangular one." )]
public bool RoundWindow { get; set; } = false;
[Property, ShowIf( nameof( Window ), true ), Group( "Window" ), Title( "Window Width" ), Range( 4, 512, slider: false ), WideMode]
public float WindowWidth { get; set; } = 36.0f;
[Property, ShowIf( nameof( Window ), true ), Group( "Window" ), Title( "Window Height" ), Range( 4, 512, slider: false ), WideMode]
public float WindowHeight { get; set; } = 36.0f;
[Property, Group( "Dormer" ), Title( "Sits On A Sloped Roof" ), WideMode, Description( "Build the dormer for a roof of the given pitch: the cheeks and its own roof end exactly in the roof surface, the window sits in a real reveal, with sill and flashing. Off: the free-standing box of earlier versions." )]
public bool OnSlope { get; set; }
[Property, Group( "Dormer" ), Title( "Main Roof Pitch" ), Range( 5, 75, slider: false ), WideMode, Description( "Pitch of the roof the dormer stands on, in degrees. Place the dormer with its front facing down the slope." )]
public float RoofPitch { get; set; } = 35.0f;
[Property, Group( "Dormer" ), Title( "Flashing" ), WideMode]
public bool Flashing { get; set; } = true;
[Property, Group( "Window" ), Title( "Window Sill" ), WideMode]
public bool WindowSill { get; set; } = true;
[Property, Group( "Window" ), Title( "Frame Corners" ), WideMode]
public FrameCorner WindowCorner { get; set; } = FrameCorner.Mitred;
[Property, Group( "Window" ), Title( "Frame Profile" ), WideMode]
public FrameProfile WindowProfile { get; set; } = FrameProfile.Chamfered;
[Property, Group( "Window" ), Title( "Frame Detail" ), WideMode]
public WindowDetail WindowDetail { get; set; } = WindowDetail.Standard;
[Property, Group( "Window" ), Title( "Glazing" ), WideMode]
public Glazing WindowGlazing { get; set; } = Glazing.Double;
[Property, Group( "Window" ), Title( "Panes Across" ), Range( 1, 6, slider: false ), WideMode]
public int WindowPanesAcross { get; set; } = 1;
[Property, Group( "Window" ), Title( "Panes Up" ), Range( 1, 6, slider: false ), WideMode]
public int WindowPanesUp { get; set; } = 1;
public override string PropName => Style.ToString();
public override IReadOnlySet<string> UnusedOptions
{
get
{
var hidden = base.UnusedOptions is { } shown ? new HashSet<string>( shown ) : new HashSet<string>();
string[] slope = [nameof( RoofPitch ), nameof( Flashing ), nameof( WindowSill ), nameof( WindowCorner ), nameof( WindowProfile ), nameof( WindowDetail ), nameof( WindowGlazing ), nameof( WindowPanesAcross ), nameof( WindowPanesUp )];
// The new roof shapes exist only as dormers for a sloped roof.
if ( !OnSlope && Style is DormerStyle.Gable or DormerStyle.Shed )
hidden.UnionWith( slope );
if ( Style is not (DormerStyle.Gable or DormerStyle.Shed) )
hidden.Add( nameof( OnSlope ) );
else
{
// On a slope the depth follows from the pitch, and windows are always rectangular.
hidden.UnionWith( [nameof( Depth ), nameof( RoundWindow )] );
if ( !Window )
hidden.UnionWith( [nameof( WindowSill ), nameof( WindowCorner ), nameof( WindowProfile ), nameof( WindowDetail ), nameof( WindowGlazing ), nameof( WindowPanesAcross ), nameof( WindowPanesUp )] );
if ( Style is DormerStyle.Shed or DormerStyle.Flat or DormerStyle.Barrel )
hidden.Add( nameof( RoofRise ) );
}
return hidden;
}
}
}
/// <summary>
/// A dormer that sits on a roof: a small body with a window and a gable or shed roof of its
/// own. The window looks out of the front face; the body is open at the back so it can meet
/// the roof plane.
/// </summary>
public sealed class DormerGenerator : PropGenerator
{
public override string Id => "dormer";
public override string Title => "Dormer";
public override Type SettingsType => typeof( DormerSettings );
protected override void Build( PropSettings settings, PropBuilder b )
{
var s = (DormerSettings)settings;
var width = MathF.Max( s.Width, 8.0f );
var depth = MathF.Max( s.Depth, 8.0f );
var height = MathF.Max( s.Height, 8.0f );
var half = width * 0.5f;
var deep = depth * 0.5f;
var over = MathF.Max( s.Overhang, 0.0f );
var rise = MathF.Max( s.RoofRise, 2.0f );
if ( s.OnSlope || s.Style is not (DormerStyle.Gable or DormerStyle.Shed) )
{
OnSlope( s, b );
return;
}
b.Box( "body", "Body", b.Main, new( -half, -deep, 0.0f ), new( half, deep, height ) );
if ( s.Window )
{
if ( s.RoundWindow )
{
var radius = MathF.Min( MathF.Min( s.WindowWidth, s.WindowHeight ), MathF.Min( width, height ) * 0.4f ) * 0.5f;
b.Disc( "window", "Window", b.Detail, 0.0f, height * 0.55f, radius, -deep - 1.2f, -deep, 16 );
b.Disc( "window_ring", "Ring", b.Metal, 0.0f, height * 0.55f, radius + 2.0f, -deep - 1.4f, -deep - 1.2f, 16 );
}
else
{
var w = MathF.Min( s.WindowWidth, width * 0.8f );
var h = MathF.Min( s.WindowHeight, height * 0.75f );
var bottom = height * 0.3f;
var left = -w * 0.5f;
var right = w * 0.5f;
b.Box( "window", "Window", b.Detail, new( left, -deep - 1.2f, bottom ), new( right, -deep, bottom + h ) );
b.Box( "frame_top", "Frame", b.Metal, new( left - 2.0f, -deep - 1.4f, bottom + h ), new( right + 2.0f, -deep, bottom + h + 2.0f ) );
b.Box( "frame_bottom", "Frame", b.Metal, new( left - 2.0f, -deep - 1.4f, bottom - 2.0f ), new( right + 2.0f, -deep, bottom ) );
b.Box( "frame_left", "Frame", b.Metal, new( left - 2.0f, -deep - 1.4f, bottom ), new( left, -deep, bottom + h ) );
b.Box( "frame_right", "Frame", b.Metal, new( right, -deep - 1.4f, bottom ), new( right + 2.0f, -deep, bottom + h ) );
}
}
if ( s.Style == DormerStyle.Gable )
{
var roof = new List<Vector2> { new( -half - over, height ), new( half + over, height ), new( 0.0f, height + rise ) };
b.Side( "roof", "Roof", b.Metal, roof, -deep - over, deep + over );
}
else
{
var roof = new List<Vector2>
{
new( -half - over, height ),
new( half + over, height + rise ),
new( half + over, height + rise + 3.0f ),
new( -half - over, height + 3.0f )
};
b.Side( "roof", "Roof", b.Metal, roof, -deep - over, deep + over );
}
}
/// <summary>
/// The dormer for a sloped roof from <see cref="DormerKit"/>, each piece its own part. The front
/// wall stands at y = -Depth / 2 as in the box version; the roof surface rises toward +Y.
/// </summary>
static void OnSlope( DormerSettings s, PropBuilder b )
{
var spec = new DormerSpec
{
Shape = s.Style switch
{
DormerStyle.Shed => DormerShape.Shed,
DormerStyle.Flat => DormerShape.Flat,
DormerStyle.Hip => DormerShape.Hip,
DormerStyle.Barrel => DormerShape.Barrel,
DormerStyle.Segmental => DormerShape.Segmental,
_ => DormerShape.Gable
},
Width = MathF.Max( s.Width, 8.0f ),
WallHeight = MathF.Max( s.Height, 8.0f ),
Pitch = Math.Clamp( s.RoofPitch, 5, 75 ),
RoofRise = MathF.Max( s.RoofRise, 2.0f ),
Overhang = MathF.Max( s.Overhang, 0.0f ),
Window = s.Window,
WindowWidth = s.WindowWidth,
WindowHeight = s.WindowHeight,
WindowSill = 6,
Sill = s.WindowSill,
Flashing = s.Flashing,
BodyMaterial = b.Main,
RoofMaterial = b.Metal,
TrimMaterial = b.Metal,
Frame = new WindowSpec
{
Width = 2.0f,
Depth = 2.5f,
Corner = s.WindowCorner,
Profile = s.WindowProfile,
Detail = s.WindowDetail,
Glazing = s.WindowGlazing,
Columns = Math.Clamp( s.WindowPanesAcross, 1, 6 ),
Rows = Math.Clamp( s.WindowPanesUp, 1, 6 ),
FrameMaterial = b.Metal,
GlassMaterial = b.Detail,
DetailMaterial = b.Main
}
};
var front = -MathF.Max( s.Depth, 8.0f ) * 0.5f;
void Piece( string id, string label, Material material, DormerPieces piece ) => b.Part( id, label, material, kit =>
{
kit.Frame = kit.Frame.ToWorld( new Transform( new Vector3( 0, front, 0 ) ) );
DormerKit.Build( kit, spec, piece );
} );
Piece( "body", "Body", b.Main, DormerPieces.Body );
if ( s.Window )
{
Piece( "window", "Window", b.Metal, DormerPieces.Window );
if ( s.WindowSill )
Piece( "sill", "Window Sill", b.Metal, DormerPieces.Sill );
}
Piece( "roof", "Roof", b.Metal, DormerPieces.Roof );
if ( s.Flashing )
Piece( "flashing", "Flashing", b.Metal, DormerPieces.Flashing );
}
}