Editor code that generates guardrail runs for architectural roofs. It computes abutments, resolves roof regions, generates guard run shapes and specs, and builds barriers onto a mesh using kit/style/palette data.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// The barrier engine already owns posts, bays and rails; a roof only says where its edge is and how high
// to stand on it. Nothing about a guardrail is re-implemented here.
public static partial class ArchRoofGen
{
public static List<ArchGuardRun> Guardrails( ArchRoofPart roof, ArchBuilding building, ArchKit kit )
{
var guards = new List<ArchGuardRun>();
if ( !roof.Guardrail || building is null )
{
return guards;
}
var abutments = Abutments( roof, building, ArchKinds.Load() );
bool Abuts( Vector2 point, Vector2 outward ) => Buried( abutments, point + outward * ArchProbe.Step );
foreach ( var region in new ArchRoofRegionService( building, roof ).Resolve() )
{
guards.AddRange( Guardrails( roof, kit, region.Outer, Abuts ) );
}
return guards;
}
static List<ArchGuardRun> Guardrails( ArchRoofPart roof, ArchKit kit, IReadOnlyList<Vector2> outline, Func<Vector2, Vector2, bool> abuts )
{
var guards = new List<ArchGuardRun>();
foreach ( var run in Runs( ArchFootprint.Wind( outline.ToList() ), GuardSeat( roof, kit ), abuts ) )
{
var spec = GuardSpec( roof, kit, run );
var curve = ArchCurve.Polyline( run.Raised(), run.Closed );
guards.Add( new ArchGuardRun
{
Shape = ArchBarrierShape.Resolve( curve, spec, 0f, 0f, kit ),
Spec = spec,
Closed = run.Closed,
Length = curve.Length
} );
}
return guards;
}
static void Guardrail( ArchMesh canvas, ArchRoofPart roof, IReadOnlyList<Vector2> outline, ArchKit kit, ArchStyle style, ArchPalette[] chain, Func<Vector2, Vector2, bool> abuts )
{
var brushes = ArchBarrierBrushes.Of( style, chain );
foreach ( var guard in Guardrails( roof, kit, outline, abuts ) )
{
ArchBarrierGen.Build( canvas, guard.Shape, guard.Spec, kit, brushes, guard.Doubled() );
}
}
// It stands on whatever finishes the edge: the parapet's top where there is one, the deck where there is not.
public static float GuardSeat( ArchRoofPart roof, ArchKit kit )
{
if ( roof.Parapet )
{
return roof.BaseHeight + MathF.Max( 4f, roof.ParapetHeight );
}
return roof.BaseHeight + ArchRoofPlane.Thickness( roof, kit );
}
static ArchBarrierSpec GuardSpec( ArchRoofPart roof, ArchKit kit, ArchRunPath run )
{
return ArchGuards.Spec( MathF.Max( 12f, roof.GuardHeight ), kit, run );
}
}