Editor/Stair/ArchStairCore.cs
using System.Text.Json.Serialization;
namespace Sunless.Architecture;
public enum StairWalk {
Ahead,
Back,
Left,
Right
}
public static class StairWalks {
public static Vector2 Heading( this StairWalk walk ) => walk switch {
StairWalk.Back => new Vector2( -1f, 0f ),
StairWalk.Left => new Vector2( 0f, 1f ),
StairWalk.Right => new Vector2( 0f, -1f ),
_ => new Vector2( 1f, 0f )
};
public static Vector2 Leftward( this StairWalk walk ) {
var heading = walk.Heading();
return new Vector2( -heading.y, heading.x );
}
public static StairWalk Reversed( this StairWalk walk ) => walk switch {
StairWalk.Back => StairWalk.Ahead,
StairWalk.Left => StairWalk.Right,
StairWalk.Right => StairWalk.Left,
_ => StairWalk.Back
};
public static StairWalk Turned( this StairWalk walk, StairTurn turn ) => turn switch {
StairTurn.Left => walk switch {
StairWalk.Ahead => StairWalk.Left,
StairWalk.Left => StairWalk.Back,
StairWalk.Back => StairWalk.Right,
_ => StairWalk.Ahead
},
StairTurn.Right => walk switch {
StairWalk.Ahead => StairWalk.Right,
StairWalk.Right => StairWalk.Back,
StairWalk.Back => StairWalk.Left,
_ => StairWalk.Ahead
},
StairTurn.Back => walk.Reversed(),
_ => walk
};
public static StairTurn Between( this StairWalk from, StairWalk to ) {
if ( from == to ) {
return StairTurn.None;
}
if ( from.Turned( StairTurn.Left ) == to ) {
return StairTurn.Left;
}
if ( from.Turned( StairTurn.Right ) == to ) {
return StairTurn.Right;
}
return StairTurn.Back;
}
}
public enum StairFill {
Open,
Solid
}
public enum StairStep {
Flight,
Landing
}
public sealed class ArchStairCore {
public Vector2 Origin { get; set; }
public float Yaw { get; set; }
public float Length { get; set; } = 240f;
public float Width { get; set; } = 120f;
public float Rise { get; set; } = 128f;
public StairFill Fill { get; set; }
public ArchStairAxes Axes => new() { Origin = Origin, Yaw = Yaw };
public Vector2 Flat( float along, float across ) => Axes.Flat( along, across );
public List<Vector2> Outline() => Axes.Rect( 0f, Length, 0f, Width );
public ArchStairCore Copy() => new() {
Origin = Origin,
Yaw = Yaw,
Length = Length,
Width = Width,
Rise = Rise,
Fill = Fill
};
public static (ArchStairCore Core, List<ArchStairLane> Lanes) Straight( Vector2 origin, Vector2 along, float length, float width, float rise ) {
var direction = along.IsNearZeroLength ? new Vector2( 1f, 0f ) : along.Normal;
var core = new ArchStairCore {
Origin = origin,
Yaw = MathF.Atan2( direction.y, direction.x ).RadianToDegree(),
Length = MathF.Max( 1f, length ),
Width = MathF.Max( 1f, width ),
Rise = MathF.Max( 1f, rise )
};
var lanes = new List<ArchStairLane>
{
new() { AlongFrom = 0f, AlongTo = core.Length, AcrossFrom = 0f, AcrossTo = core.Width, Walk = StairWalk.Ahead }
};
return (core, lanes);
}
}
public sealed class ArchStairLane {
public int Id { get; set; }
public StairStep Step { get; set; }
public float AlongFrom { get; set; }
public float AlongTo { get; set; }
public float AcrossFrom { get; set; }
public float AcrossTo { get; set; }
public StairWalk Walk { get; set; }
// Curves are chains of segments, each carrying a share of the sweep
[JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )]
public float Bearing { get; set; }
// Pinned flights take an exact rise; zero divides the remainder
public float Rise { get; set; }
// Empty = stair-level Guard setting decides
public List<ArchStairGuardPart> Guards { get; set; } = new();
public bool Climbs => Step == StairStep.Flight;
public bool Guarding( StairEdge edge ) => Guards.Any( guard => guard.Edge == edge );
// Migrated by Normalize into a real landing lane.
[JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingDefault )]
public float LandingDepth { get; set; }
public float AlongSpan => MathF.Max( 0f, AlongTo - AlongFrom );
public float AcrossSpan => MathF.Max( 0f, AcrossTo - AcrossFrom );
public bool Sideways => Walk is StairWalk.Left or StairWalk.Right;
public float Length => Sideways ? AcrossSpan : AlongSpan;
public float Width => Sideways ? AlongSpan : AcrossSpan;
// The pivot a bearing turns about — stable across turns
public Vector2 Seat => Walk switch {
StairWalk.Back => new Vector2( AlongTo, AcrossTo ),
StairWalk.Left => new Vector2( AlongTo, AcrossFrom ),
StairWalk.Right => new Vector2( AlongFrom, AcrossTo ),
_ => new Vector2( AlongFrom, AcrossFrom )
};
public Vector2 Foot( ArchStairCore core ) => core.Flat( Seat.x, Seat.y );
public float Cardinal => Walk switch {
StairWalk.Back => 180f,
StairWalk.Left => 90f,
StairWalk.Right => -90f,
_ => 0f
};
public float Yaw( ArchStairCore core ) => core.Yaw + Cardinal + Bearing;
public ArchStairAxes Axes( ArchStairCore core ) => new() { Origin = Foot( core ), Yaw = Yaw( core ) };
public ArchStairAxes Frame => new() { Origin = Seat, Yaw = Cardinal + Bearing };
public List<Vector2> Corners() => Frame.Rect( 0f, Length, 0f, Width );
public (float AlongFrom, float AlongTo, float AcrossFrom, float AcrossTo) HeadEdge() => Walk switch {
StairWalk.Back => (AlongFrom, AlongFrom, AcrossFrom, AcrossTo),
StairWalk.Left => (AlongFrom, AlongTo, AcrossTo, AcrossTo),
StairWalk.Right => (AlongFrom, AlongTo, AcrossFrom, AcrossFrom),
_ => (AlongTo, AlongTo, AcrossFrom, AcrossTo)
};
public (float AlongFrom, float AlongTo, float AcrossFrom, float AcrossTo) FootEdge() => Walk switch {
StairWalk.Back => (AlongTo, AlongTo, AcrossFrom, AcrossTo),
StairWalk.Left => (AlongFrom, AlongTo, AcrossFrom, AcrossFrom),
StairWalk.Right => (AlongFrom, AlongTo, AcrossTo, AcrossTo),
_ => (AlongFrom, AlongFrom, AcrossFrom, AcrossTo)
};
public Vector2 Heading => Frame.Along;
public Vector2 Leftward => Frame.Across;
public List<Vector2> Loop( ArchStairCore core ) => Axes( core ).Rect( 0f, Length, 0f, Width );
public StairWalk Reversed => Walk.Reversed();
public ArchStairLane Copy() => new() {
Id = Id,
Step = Step,
AlongFrom = AlongFrom,
AlongTo = AlongTo,
AcrossFrom = AcrossFrom,
AcrossTo = AcrossTo,
Walk = Walk,
Bearing = Bearing,
Rise = Rise,
Guards = Guards.Select( guard => guard.Copy() ).ToList()
};
}