Editor-side generator for porch frames and railings. It computes bay placement, draws columns, beams, railings and balusters onto an ArchMesh using kit and shape parameters.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// Bays are the unit - a post starts one, a railing fills one.
public static partial class ArchPorchGen
{
// The generator and the placement ghost read the same resolve, and the gaps in it are read off the
// flights actually standing - so a flight dragged along the deck takes its gap with it, and a deleted
// one closes the railing behind it with nothing to keep in step.
public static List<ArchBay> Bays( ArchPorchShape shape, ArchPorchPart porch, ArchKit kit )
{
var size = MathF.Max( 3f, kit.PorchPostSize );
return ArchBays.Over( shape.Open, MathF.Max( 24f, porch.PostSpacing ), size * 2f,
ArchPorchSteps.Gates( porch, shape ) );
}
// Anything carrying something overhead stands the same post - a porch beam or a storey.
public static void Stand( ArchMesh canvas, Vector2 at, ArchKit kit, float bottom, float top, ArchBrush brush )
{
var half = MathF.Max( 3f, kit.PorchPostSize ) * 0.5f;
var flare = half + 1f;
Column( canvas, at, half, bottom + 3f, top - 3f, brush );
Column( canvas, at, flare, bottom, bottom + 3f, brush );
Column( canvas, at, flare, top - 3f, top, brush );
}
static void Column( ArchMesh canvas, Vector2 at, float half, float bottom, float top, ArchBrush brush )
{
canvas.Box(
new Vector3( at.x - half, at.y - half, bottom ),
new Vector3( at.x + half, at.y + half, top ),
brush );
}
// Runs the whole open perimeter so the band's corner rules mitre it.
static void Beam( ArchMesh canvas, ArchPorchShape shape, ArchKit kit, ArchBrush brush )
{
var size = MathF.Max( 3f, kit.PorchPostSize );
var depth = MathF.Max( 3f, kit.PorchBeamDepth );
foreach ( var run in shape.Open )
{
ArchBandSection.Between( -size, size, shape.Head, shape.Head + depth ).Emit( canvas, run, brush );
}
}
static void Railing( ArchMesh canvas, ArchBay bay, ArchPorchShape shape, ArchKit kit, ArchBrush brush )
{
var panel = bay.Narrowed( MathF.Max( 3f, kit.PorchPostSize ) * 0.5f );
var from = panel.From.Point;
var to = panel.To.Point;
if ( panel.Span < 4f )
{
return;
}
var height = MathF.Max( 18f, kit.PorchRailHeight );
var bottom = shape.Deck + 2.5f;
var lowerTop = bottom + 2.2f;
var upperBottom = shape.Deck + height - 3.4f;
var upperTop = shape.Deck + height;
canvas.Beam( from, to, -1.5f, 1.5f, bottom, lowerTop, brush );
canvas.Beam( from, to, -1.5f, 1.5f, upperBottom, upperTop - 1.4f, brush );
canvas.Beam( from, to, -2.6f, 2.6f, upperTop - 1.4f, upperTop, brush );
Balusters( canvas, kit, from, to, lowerTop, upperBottom, brush );
}
static void Balusters( ArchMesh canvas, ArchKit kit, Vector2 from, Vector2 to, float bottom, float top, ArchBrush brush )
{
var silhouette = kit.FindProfile( "baluster_turned" );
if ( silhouette is null || silhouette.Points.Count < 2 || top - bottom < 6f )
{
return;
}
var authored = silhouette.Points[^1].y;
if ( authored < 1f )
{
return;
}
// Pitch is a minimum - it must clear the turning's widest part.
var widest = silhouette.Max.x * 2f * MathF.Max( 0.1f, kit.BalusterScale );
var pitch = MathF.Max( MathF.Max( 3f, kit.BalusterSpacing ), widest + MathF.Max( 0f, kit.BalusterGap ) );
var reach = to - from;
var span = reach.Length;
if ( span < 0.05f )
{
return;
}
var along = reach / span;
var stretch = (top - bottom) / authored;
foreach ( var centre in ArchDivide.AtLeast( span, pitch ).Centres )
{
var at = from + along * centre;
// Ends butt into rails, so the lathe's caps are never seen.
canvas.Revolve( new Vector3( at.x, at.y, bottom ), silhouette.Points, kit.BalusterSides, kit.BalusterScale, stretch, brush, caps: false );
}
}
}