Editor/Geometry/ArchGuards.cs

Editor-side geometry helper. Defines ArchGuardRun which represents one protection run along an edge including shape, spec, closed flag and length, and provides a function that tests whether a point lies in the run's doubled closing region. Also defines ArchGuards.Spec which constructs an ArchBarrierSpec for a given height, kit and run by setting defaults and creating bay spans from the run edges.

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

namespace Sunless.Architecture;

// One run of edge protection, resolved for a generator and read back by the tests.
public sealed class ArchGuardRun
{
	public ArchBarrierShape Shape { get; init; }
	public ArchBarrierSpec Spec { get; init; }
	public bool Closed { get; init; }
	public float Length { get; init; }

	// A closed run's last station IS its first, and posted twice it stands two posts inside each other.
	public Func<float, bool> Doubled()
	{
		if ( !Closed )
		{
			return null;
		}

		var closing = Length - 0.5f;

		return distance => distance > closing;
	}
}

// The barrier engine already owns posts, bays and rails; a deck only says where its edge is and how high to stand
// on it. A roof's guard and a platform's are the same rail, so the spec is core geometry.
public static class ArchGuards
{
	// 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( float height, ArchKit kit, ArchRunPath run )
	{
		var spec = new ArchBarrierSpec
		{
			Style = BarrierStyle.PostAndRail,
			Ground = BarrierGround.Level,
			Height = height,
			PanelLength = MathF.Max( 24f, kit.RoofGuardBay ),
			PostSize = MathF.Max( 1f, kit.RoofGuardPost ),
			Rails = 2
		};

		for ( var edge = 0; edge < run.Edges; edge++ )
		{
			var division = ArchDivide.AtMost( (run.At( edge + 1 ) - run.At( edge )).Length, spec.PanelLength );

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

		return spec;
	}
}