Editor/Stair/ArchStairShape.Plan.cs
namespace Sunless.Architecture;
public sealed partial class ArchStairShape {
// Which sides of the landing face a wall - the rail asks this. The pad is NOT grown to meet them:
// a landing is the space the core has left between two flights, and reaching it out to the nearest
// walls would move it off the square the generator builds.
static void Corner( ArchStairPad pad, ArchRoom room, ArchKit kit, ArchKinds kinds ) {
if ( room is null ) {
return;
}
var middle = (pad.AlongFrom + pad.AlongTo) * 0.5f;
pad.WalledFrom = Walled( room, kit, pad.Axes, middle, pad.AcrossFrom, -1f, kinds );
pad.WalledTo = Walled( room, kit, pad.Axes, middle, pad.AcrossTo, 1f, kinds );
// The far end of a turn landing is usually the corner of the stairwell, so it is asked the same
// question the flanks are: a rail run into plaster is balusters standing inside a wall.
pad.WalledHead = ArchProbe.Against( ArchPlanStore.FiledOn( ArchKind.Wall, room, kinds ).OfType<ArchWall>(), kit,
pad.Axes.Flat( pad.AlongTo, (pad.AcrossFrom + pad.AcrossTo) * 0.5f ), pad.Axes.Along );
}
// Measured, not authored: a walled side is a plaster rail and the socket for wall-hung treads.
static bool Walled( ArchRoom room, ArchKit kit, ArchStairAxes axes, float along, float across, float sign, ArchKinds kinds ) {
return room is not null && ArchProbe.Against( ArchPlanStore.FiledOn( ArchKind.Wall, room, kinds ).OfType<ArchWall>(), kit,
axes.Flat( along, across ), axes.Across * sign );
}
// Where every step in the climb may stand once the shell has had what crosses it. Walked in climb order,
// because a step is measured in the frame of the flight below it and the storey it is bounded by is whatever
// the climb has got up to - the same two things the resolve itself carries, so the two walks cannot disagree.
static ArchStairFit[] Fitting(
ArchStairCore core,
IReadOnlyList<ArchStairLane> lanes,
IReadOnlyList<float> climbs,
ArchStairHousings housings,
ArchBuilding building,
ArchKit kit,
float baseHeight ) {
var fits = new ArchStairFit[lanes.Count];
var frame = core.Axes;
var height = baseHeight;
for ( var index = 0; index < lanes.Count; index++ ) {
var lane = lanes[index];
var housing = housings.On( FloorIndex( building, kit, height ) );
if ( lane.Climbs ) {
var axes = lane.Axes( core );
fits[index] = housing.Fit( axes, 0f, MathF.Max( 1f, lane.Length ), 0f, MathF.Max( 1f, lane.Width ) );
frame = axes;
} else {
Framed( frame, core, lane, out var alongFrom, out var alongTo, out var acrossFrom, out var acrossTo );
fits[index] = housing.Fit( frame, alongFrom, alongTo, acrossFrom, acrossTo );
}
height += climbs[index];
}
return fits;
}
// One synthetic room so a probe finds a wall whoever owns it, on whatever floor the part stands.
static ArchRoom Probe( ArchRoom room, IEnumerable<ArchRoom> storey, int level, ArchRoom fallback, ArchKinds kinds ) {
if ( storey is null ) {
return room;
}
var walls = storey
.Where( entry => entry.Floor == level )
.SelectMany( entry => ArchPlanStore.FiledOn( ArchKind.Wall, entry, kinds ) )
.OfType<ArchWall>()
.ToList();
if ( walls.Count == 0 ) {
return fallback;
}
return new ArchRoom { Walls = walls };
}
static StairTurn TurnKind( float angle ) {
var turn = MathF.Abs( angle );
if ( turn < 15f ) {
return StairTurn.None;
}
if ( turn > 165f ) {
return StairTurn.Back;
}
return angle > 0f ? StairTurn.Left : StairTurn.Right;
}
internal static float AngleDelta( float degrees ) {
var delta = degrees % 360f;
if ( delta > 180f ) {
delta -= 360f;
}
if ( delta <= -180f ) {
delta += 360f;
}
return delta;
}
}