Editor/Data/ArchProfileCatalog.cs
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 ) },
// Exterior mouldings, authored the other way about: +x stands OUT of the wall and +y is up, because
// every host sweeps them with the extruder's right-hand side facing the street.
new()
{
Name = "crown",
Points = new List<Vector2>
{
new( 0f, 0f ), new( 2.2f, 1.6f ), new( 4.4f, 4.2f ), new( 6.6f, 6.4f ),
new( 8.6f, 7.4f ), new( 9f, 7.4f ), new( 9f, 9f ), new( 0f, 9f )
}
},
new()
{
Name = "bed",
Points = new List<Vector2>
{
new( 0f, 0f ), new( 1.2f, 1f ), new( 2.2f, 2.4f ), new( 3f, 3.2f ), new( 3f, 3.5f ), new( 0f, 3.5f )
}
},
new()
{
Name = "cavetto",
Points = new List<Vector2>
{
new( 0f, 0f ), new( 1.5f, 0.3f ), new( 2.8f, 1.1f ), new( 3.7f, 2.4f ), new( 4f, 4f ), new( 0f, 4f )
}
},
// A bracket is swept ACROSS its projection, so its section is the front view: a block chamfered off
// under the corona it carries.
new()
{
Name = "modillion",
Points = new List<Vector2>
{
new( -1.5f, 0f ), new( 1.5f, 0f ), new( 1.5f, 4.2f ),
new( 0.9f, 5f ), new( -0.9f, 5f ), new( -1.5f, 4.2f )
}
},
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 )
};
}
}