Editor/Stair/ArchStairEdges.cs

Editor utility for stair editing. Provides methods to manipulate an ArchStairLane by treating a step as a rectangular lane: computing along/across coordinates, shifting, lengthening, widening, growing, adjusting left/right flanks, and computing base heights for a stair part.

namespace Sunless.Architecture;

// Which of a step's four numbers an edge IS. A step is a rectangle laid out of the corner its walk leaves from, so
// every edge question reduces to a distance along its own travel or across it - asked by the viewport's widgets, by
// the designer window's grips and by a typed figure on the step's own sheet alike. Reading it in the step's OWN
// frame is also what lets a swept segment be dragged: the four numbers stay its unturned footprint whatever angle
// it stands at.
public static class ArchStairEdges
{
	public static void Head( ArchStairLane lane, Vector2 local )
	{
		Lengthen( lane, Along( lane, local ) );
	}

	// Width runs to the LEFT of travel. The left flank is the far edge, so pushing it is a width; the right flank is
	// the seat's own edge, so pushing that moves the corner the step is laid out from and leaves the head standing.
	public static void Flank( ArchStairLane lane, Vector2 local, bool left )
	{
		var across = Across( lane, local );

		if ( left )
		{
			Widen( lane, across );

			return;
		}

		var width = lane.Width - across;

		Shift( lane, lane.Leftward * across );
		Widen( lane, width );
	}

	// The head pushed OUT by a distance rather than moved to a point, which is how a platform's far edge is grown.
	public static void Grow( ArchStairLane lane, float push )
	{
		Lengthen( lane, lane.Length + push );
	}

	// Set outright, so a run or a depth typed on a sheet lands on the same number a dragged edge writes. The seat
	// never moves: a step grows out of the corner it was laid from, which is the corner the step below it meets.
	public static void Lengthen( ArchStairLane lane, float length )
	{
		var reach = MathF.Max( ArchStairLanes.MinLane, length );

		switch ( lane.Walk )
		{
			case StairWalk.Ahead:
				lane.AlongTo = lane.AlongFrom + reach;
				break;
			case StairWalk.Back:
				lane.AlongFrom = lane.AlongTo - reach;
				break;
			case StairWalk.Left:
				lane.AcrossTo = lane.AcrossFrom + reach;
				break;
			default:
				lane.AcrossFrom = lane.AcrossTo - reach;
				break;
		}
	}

	public static void Widen( ArchStairLane lane, float width )
	{
		var reach = MathF.Max( ArchStairLanes.MinLane, width );

		switch ( lane.Walk )
		{
			case StairWalk.Ahead:
				lane.AcrossTo = lane.AcrossFrom + reach;
				break;
			case StairWalk.Back:
				lane.AcrossFrom = lane.AcrossTo - reach;
				break;
			case StairWalk.Left:
				lane.AlongFrom = lane.AlongTo - reach;
				break;
			default:
				lane.AlongTo = lane.AlongFrom + reach;
				break;
		}
	}

	public static void Shift( ArchStairLane lane, Vector2 delta )
	{
		lane.AlongFrom += delta.x;
		lane.AlongTo += delta.x;
		lane.AcrossFrom += delta.y;
		lane.AcrossTo += delta.y;
	}

	// A point in the core's numbers read as how far along the step's own travel it lies, and how far across it.
	public static float Along( ArchStairLane lane, Vector2 local ) => Vector2.Dot( local - lane.Seat, lane.Heading );

	public static float Across( ArchStairLane lane, Vector2 local ) => Vector2.Dot( local - lane.Seat, lane.Leftward );

	// Where each step STARTS in the climb, accumulated the way the resolve accumulates it - so the window's
	// elevation and the generator cannot disagree about how high a step stands.
	public static float[] Bases( ArchStairPart stair )
	{
		var lanes = stair.Lanes;
		var climbs = ArchStairLanes.Climbs( stair.Core, lanes );
		var bases = new float[lanes.Count];
		var height = stair.BaseHeight;

		for ( var index = 0; index < lanes.Count; index++ )
		{
			if ( !lanes[index].Climbs )
			{
				height += climbs[index];
				bases[index] = height;

				continue;
			}

			bases[index] = height;
			height += climbs[index];
		}

		return bases;
	}
}