Editor/Services/ArchFixtureGuard.cs

Editor utility for balcony/fixture barriers. It builds a barrier specification from a path and kit values, resolves barrier geometry into a shape, and exposes Build and Ghost helpers to generate mesh or ghost preview.

File Access
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// Edge protection on anything hung off an elevation - a balcony's balustrade, a fire escape's landing
// rail, the rake beside a flight. The barrier engine already owns posts, bays, rails and balusters, so
// nothing here grows a second one: a fixture says where its edge is and how high to stand on it, exactly
// as ArchRoofGuard hands a parapet over.
public static class ArchFixtureGuard
{
	// Bays are authored EDGE BY EDGE, so every station lands either on a corner or inside one straight
	// stretch. Divided by arc length alone a bay spans a corner and its rail cuts the corner off.
	public static ArchBarrierSpec Spec( ArchKit kit, float height, IReadOnlyList<Vector3> path, BarrierGround ground )
	{
		var spec = new ArchBarrierSpec
		{
			Style = BarrierStyle.Picket,
			Top = PicketTop.Flat,
			Ground = ground,
			Height = MathF.Max( 12f, height ),
			PanelLength = MathF.Max( 24f, kit.RoofGuardBay ),
			PostSize = MathF.Max( 1f, kit.NewelSize ),
			Rails = 2
		};

		for ( var edge = 0; edge + 1 < (path?.Count ?? 0); edge++ )
		{
			var division = ArchDivide.AtMost( (path[edge + 1] - path[edge]).Length, spec.PanelLength );

			for ( var bay = 0; bay < division.Count; bay++ )
			{
				spec.Bays.Add( new ArchBarrierBay { Span = division.Step } );
			}
		}

		return spec;
	}

	// One resolve for the generator and the ghost: the posts drawn are the posts built.
	public static ArchBarrierShape Resolve( IReadOnlyList<Vector3> path, ArchBarrierSpec spec )
	{
		if ( path is not { Count: >= 2 } )
		{
			return new ArchBarrierShape();
		}

		return ArchBarrierShape.Resolve( ArchCurve.Polyline( path ), spec );
	}

	public static void Build( ArchMesh canvas, IReadOnlyList<Vector3> path, ArchKit kit, float height, BarrierGround ground, ArchBarrierBrushes brushes )
	{
		var spec = Spec( kit, height, path, ground );

		ArchBarrierGen.Build( canvas, Resolve( path, spec ), spec, kit, brushes );
	}

	public static void Ghost( IReadOnlyList<Vector3> path, ArchKit kit, float height, BarrierGround ground )
	{
		var spec = Spec( kit, height, path, ground );

		ArchGhost.Barrier( Resolve( path, spec ), spec );
	}
}