An editor-side catalog of architectural cross-section profiles. It implements IArchCatalog and returns a set of named ArchProfile objects with precomputed 2D point lists for shapes like gutters, pipes, pillars, balusters and other profiles used by the extruder/geometry generator.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sunless.Architecture;
// The one shelf core stands itself, and for the same reason it stands the select subtool: a gutter belongs to roofs,
// a downpipe to pipes, a handrail and its balusters to stairs, a shaft to pillars. No module authors sections, and
// the extruder that reads them is core's own - so this is neutral geometry wearing a catalog, not a leaf's catalog
// left behind.
public sealed class ArchProfileCatalog : IArchCatalog
{
public ArchShelf Shelf => ArchShelf.Profiles;
public string Named( object entry ) => entry is ArchProfile profile ? profile.Name : null;
public IEnumerable<object> Shipped() => Sections();
public IEnumerable<object> Authored() => Array.Empty<object>();
public bool Save( object entry ) => false;
public static List<ArchProfile> Sections()
{
return new List<ArchProfile>
{
// Extruder +x is inward on a CCW eave run, so the back wall sits on x=0.
new() { Name = "gutter", Points = BoxGutter( 6f, 4.5f, 0.7f, 2f ) },
new() { Name = "gutter_bracket", Points = Square( 1.1f ) },
// Four points is all a capping tile needs at this scale.
new()
{
Name = "ridge_cap",
Points = new List<Vector2> { new( -4f, -2.5f ), new( -1f, 2f ), new( 1f, 2f ), new( 4f, -2.5f ) }
},
new() { Name = "cornice", Points = Cove( 5f, 4f, 4 ) },
new() { Name = "downpipe", Points = Circle( 2.2f, 12 ) },
new() { Name = "downpipe_square", Points = Square( 2.1f ) },
new() { Name = "handrail", Points = Circle( 1.6f, 6 ) },
// Lathe silhouettes, not sections: x is radius, y is height off the bottom rail.
new() { Name = "baluster_turned", Closed = false, Points = TurnedBaluster() },
new() { Name = "baluster_square", Closed = false, Points = new List<Vector2> { new( 1.35f, 0f ), new( 1.35f, 28f ) } },
new()
{
Name = "baluster_colonial",
Closed = false,
Points = new List<Vector2>
{
new( 1.4f, 0f ), new( 1.4f, 4f ), new( 1f, 5.5f ), new( 1f, 21f ),
new( 1.5f, 23f ), new( 1.4f, 24.5f ), new( 1.4f, 28f )
}
},
new()
{
Name = "parapet_cap",
Points = new List<Vector2> { new( -7f, 0f ), new( 7f, 0f ), new( 7f, 3f ), new( 0f, 5f ), new( -7f, 3f ) }
},
new()
{
Name = "fascia",
Points = new List<Vector2> { new( -1f, -4f ), new( 1f, -4f ), new( 1f, 4f ), new( -1f, 4f ) }
},
new() { Name = "pipe", Points = Circle( 3f, 8 ) },
new() { Name = "pillar_square", Points = Square( 10f ) },
new() { Name = "pillar_round", Points = Circle( 10f, 12 ) },
new() { Name = "pillar_octagon", Points = Circle( 10f, 8 ) },
new() { Name = "pillar_cross", Points = Cross( 12f, 4.5f ) },
new()
{
Name = "i_beam",
Points = new List<Vector2>
{
new( -6f, -8f ), new( 6f, -8f ), new( 6f, -6f ), new( 1.5f, -6f ), new( 1.5f, 6f ),
new( 6f, 6f ), new( 6f, 8f ), new( -6f, 8f ), new( -6f, 6f ), new( -1.5f, 6f ),
new( -1.5f, -6f ), new( -6f, -6f )
}
}
};
}
// The five diameters a turned baluster reads as from across a garden.
static List<Vector2> TurnedBaluster()
{
return new List<Vector2>
{
new( 1.5f, 0f ), new( 1.75f, 7f ), new( 0.8f, 13.2f ),
new( 1.28f, 17.6f ), new( 0.72f, 23f ), new( 1.45f, 28f )
};
}
static List<Vector2> Circle( float radius, int segments )
{
return Enumerable.Range( 0, segments )
.Select( index => index / (float)segments * MathF.Tau )
.Select( angle => new Vector2( MathF.Cos( angle ), MathF.Sin( angle ) ) * radius )
.ToList();
}
// Wound clockwise so extruded normals face outward; the back wall tucks behind the drip edge.
static List<Vector2> BoxGutter( float width, float depth, float thickness, float backRise )
{
return new List<Vector2>
{
new( 0f, backRise ),
new( 0f, -depth ),
new( -width, -depth ),
new( -width, 0f ),
new( -width + thickness, 0f ),
new( -width + thickness, -depth + thickness ),
new( -thickness, -depth + thickness ),
new( -thickness, backRise )
};
}
// Interior runs are CCW, so the extruder's +x points into the room.
static List<Vector2> Cove( float reach, float drop, int segments )
{
var points = new List<Vector2> { new( 0f, 0f ), new( reach, 0f ) };
for ( var index = 1; index < segments; index++ )
{
var angle = MathF.PI * 0.5f * index / segments;
points.Add( new Vector2( reach * MathF.Cos( angle ), -drop * MathF.Sin( angle ) ) );
}
points.Add( new Vector2( 0f, -drop ) );
return points;
}
static List<Vector2> Square( float half )
{
return new List<Vector2> { new( -half, -half ), new( half, -half ), new( half, half ), new( -half, half ) };
}
static List<Vector2> Cross( float arm, float half )
{
return new List<Vector2>
{
new( -half, -arm ), new( half, -arm ), new( half, -half ), new( arm, -half ),
new( arm, half ), new( half, half ), new( half, arm ), new( -half, arm ),
new( -half, half ), new( -arm, half ), new( -arm, -half ), new( -half, -half )
};
}
}