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.
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;
}
}