Editor-side generator for porch canopies. It finds the roof cover, computes slope and sizes, and adds rafter and brace geometry to an ArchMesh by iterating porch walls and placing rake and strut primitives.
using System;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// Both run across the porch, so cut from the wall edges, not the open perimeter.
public static partial class ArchPorchGen
{
static void Canopy( ArchMesh canvas, ArchPorchShape shape, ArchPorchPart porch, ArchBuilding building, ArchKit kit, ArchBrush brush )
{
var cover = building?.Roofs.FirstOrDefault( roof => roof.Id == porch.RoofId );
if ( cover is null )
{
return;
}
var slope = MathF.Tan( MathF.Max( 1f, cover.Pitch ).DegreeToRadian() );
var beam = MathF.Max( 3f, kit.PorchPostSize );
foreach ( var wall in shape.Walls )
{
var reach = wall.Depth - beam;
if ( reach < 12f )
{
continue;
}
if ( porch.Rafters )
{
Rafters( canvas, shape, wall, kit, cover, slope, reach, brush );
}
if ( porch.Braces )
{
Braces( canvas, shape, porch, wall, kit, reach, brush );
}
}
}
// Hangs from the deck's underside - one slab, no framing cavity to sit in.
static void Rafters( ArchMesh canvas, ArchPorchShape shape, ArchPorchShape.ArchPorchWall wall, ArchKit kit, ArchRoofPart cover, float slope, float reach, ArchBrush brush )
{
var bite = ArchContact.Bite( kit );
var depth = MathF.Max( 2f, kit.PorchRafterDepth );
var half = MathF.Max( 0.5f, kit.PorchRafterWidth ) * 0.5f;
var span = (wall.To - wall.From).Length;
if ( span < 1f )
{
return;
}
var along = (wall.To - wall.From) / span;
var inward = -wall.Outward;
var eave = shape.Ceiling + slope * MathF.Max( 0f, cover.Overhang );
foreach ( var station in ArchDivide.AtMost( span, MathF.Max( 12f, kit.PorchRafterSpacing ) ).Nodes )
{
var foot = wall.From + along * station;
var head = foot + inward * (reach + bite);
var high = eave + slope * (reach + bite);
canvas.Rake( foot - inward * bite, head, -half, half, high - depth, eave - depth, high, eave, brush );
}
}
// A prop, not a knee brace - its foot is clamped to the deck.
static void Braces( ArchMesh canvas, ArchPorchShape shape, ArchPorchPart porch, ArchPorchShape.ArchPorchWall wall, ArchKit kit, float reach, ArchBrush brush )
{
var half = MathF.Max( 1f, kit.PorchBraceSize ) * 0.5f;
var span = (wall.To - wall.From).Length;
if ( span < 1f )
{
return;
}
var along = (wall.To - wall.From) / span;
var inward = -wall.Outward;
var drop = reach * MathF.Max( 0.2f, kit.PorchBraceRake );
var foot = MathF.Max( shape.Grade + half * 2f, shape.Head - drop );
foreach ( var station in ArchDivide.AtMost( span, MathF.Max( 24f, porch.PostSpacing ) ).Nodes )
{
var at = wall.From + along * station;
var head = at + inward * reach;
canvas.Strut(
new Vector3( at.x, at.y, foot ),
new Vector3( head.x, head.y, shape.Head ),
half, brush );
}
}
}