Editor/Roof/ArchRoofGuard.cs

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.

File AccessNative Interop
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 );
	}
}