Editor/Geometry/ArchGuards.cs
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, BarrierStyle style = BarrierStyle.PostAndRail ) {
var spec = new ArchBarrierSpec {
Style = style,
Ground = BarrierGround.Level,
Height = height,
PanelLength = ArchGuardStyles.Bay( style, kit ),
PostSize = MathF.Max( 1f, kit.RoofGuardPost ),
PanelThickness = kit.SolidGuardThickness,
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;
}
}