editor/AdvancedMappingTools/Procedural/SwitchbackGenerator.cs
namespace AdvancedMappingTools;
public enum SwitchbackStyle
{
[Title( "Concrete Stairwell" )] Concrete,
[Title( "Steel Fire Escape" )] FireEscape
}
public sealed class SwitchbackSettings : GeneratorSettings
{
/// <summary>
/// The length of one flight, set by the tool.
/// </summary>
public float Run { get; set; } = 160.0f;
[Property, Group( "Stairs" ), WideMode]
public SwitchbackStyle Style { get; set; } = SwitchbackStyle.Concrete;
[Property, Group( "Stairs" ), Range( 1, 32, slider: false ), WideMode, Description( "Storeys the stairs climb." )]
public int Storeys { get; set; } = 3;
[Property, Group( "Stairs" ), Title( "Storey Height" ), Range( 48, 512, slider: false ), WideMode]
public float StoreyHeight { get; set; } = 128.0f;
[Property, Group( "Stairs" ), Title( "Flight Width" ), Range( 16, 256, slider: false ), WideMode]
public float FlightWidth { get; set; } = 44.0f;
[Property, Group( "Stairs" ), Title( "Gap Between Flights" ), Range( 0, 128, slider: false ), WideMode]
public float Gap { get; set; } = 8.0f;
[Property, Group( "Stairs" ), Title( "Landing Depth" ), Range( 16, 256, slider: false ), WideMode]
public float LandingDepth { get; set; } = 48.0f;
[Property, Group( "Stairs" ), Title( "Step Height" ), Range( 2, 16, slider: false ), WideMode]
public float StepHeight { get; set; } = 7.5f;
[Property, Group( "Details" ), Title( "Railings" ), WideMode]
public bool Railings { get; set; } = true;
[Property, ShowIf( nameof( Railings ), true ), Group( "Details" ), Title( "Railing Height" ), Range( 16, 64, slider: false ), WideMode]
public float RailingHeight { get; set; } = 36.0f;
[Property, Group( "Details" ), Title( "Broken Steps" ), Range( 0, 1 ), WideMode]
public float BrokenSteps { get; set; } = 0.0f;
[Property, Group( "Materials" ), Title( "Steps" ), ResourceType( "vmat" ), WideMode]
public string StepMaterial { get; set; }
[Property, Group( "Materials" ), Title( "Railings" ), ResourceType( "vmat" ), WideMode]
public string TrimMaterial { get; set; }
}
/// <summary>
/// Stairs that double back at every landing, from a stairwell to a fire escape on an outside wall.
/// The first flight runs along +X from the origin, the next one back above it.
/// </summary>
public sealed class SwitchbackGenerator : IPartGenerator
{
const string Category = "stair";
public string Id => "switchback";
public string Title => "Switchback Stairs";
public Type SettingsType => typeof( SwitchbackSettings );
public PartPlan Generate( GeneratorSettings settings, GenContext context )
{
var s = (SwitchbackSettings)settings;
var root = context.Group( "root", "Switchback Stairs", "Building", Category );
if ( s.Run < 32.0f ) return root;
var steps = context.Material( s.StepMaterial );
var trim = context.Material( s.TrimMaterial );
var steel = s.Style == SwitchbackStyle.FireEscape;
var half = s.StoreyHeight * 0.5f;
var count = Math.Max( (int)MathF.Round( half / MathF.Max( s.StepHeight, 2.0f ) ), 2 );
var offset = (s.FlightWidth + s.Gap) * 0.5f;
var landingHeight = steel ? 2.0f : 8.0f;
var total = s.FlightWidth + s.Gap * 0.5f;
for ( var flight = 0; flight < s.Storeys * 2; flight++ )
{
var outward = flight % 2 == 0;
var z = flight * half;
var id = $"flight{flight}";
var request = new StairRequest( id, $"Flight {flight + 1}", s.Run, s.FlightWidth, half, count, steel ? 1.5f : 4.0f,
steel, s.Railings, s.RailingHeight, s.BrokenSteps, s.Variation, steps, trim );
var plan = StairGenerator.Generate( request, context );
root.Add( outward ? plan.At( new Vector3( 0.0f, -offset, z ) ) : plan.At( new Vector3( s.Run, offset, z ), 180.0f ) );
var x0 = outward ? s.Run : -s.LandingDepth;
var x1 = x0 + s.LandingDepth;
var top = z + half;
var name = $"landing{flight}";
root.Add( context.Solid( name, $"Landing {flight + 1}", "Landing", Category, steel ? trim : steps,
kit => kit.Box( new Vector3( x0, -total, top - landingHeight ), new Vector3( x1, total, top ) ) ) );
if ( !s.Railings ) continue;
var end = outward ? x1 - 2.0f : x0;
root.Add( context.Solid( $"{name}_rail_end", "Landing Rail", "LandingRail", Category, trim,
kit => kit.Box( new Vector3( end, -total, top + s.RailingHeight ), new Vector3( end + 2.0f, total, top + s.RailingHeight + 2.0f ) ) ) );
foreach ( var (y, side) in new[] { (-total, "a"), (total - 2.0f, "b") } )
{
root.Add( context.Solid( $"{name}_post_{side}", "Landing Post", "LandingPost", Category, trim,
kit => kit.Box( new Vector3( end, y, top ), new Vector3( end + 2.0f, y + 2.0f, top + s.RailingHeight ) ) ) );
}
}
return root;
}
}