Editor/Wall/ArchOpeningSeats.cs

Static utility for wall opening placement. Computes usable run on a wall given padding and minimum width, centers an opening within that run, and produces an ArchOpeningShape for a rhythm station using grid snapping and clamping to wall height.

File Access
using System;
using Sandbox;

namespace Sunless.Architecture;

// WHERE on a wall a unit of a given width may stand. The drag that places one by hand, the rhythm that stands
// twelve and the porch that wants an entrance in the elevation it fronts all measure the same run the same way - a
// caller that worked out its own margins would disagree with a hand-placed unit by exactly one architrave.
public static class ArchOpeningSeats
{
	// The stations an opening may reach on this wall. A pad wider than the wall can hold is worth nothing to
	// anybody - the margin gives way before the opening does.
	public static void Usable( ArchWall wall, ArchOpeningPreset preset, ArchKit kit, bool padded, float minimumWidth, out float runFrom, out float runTo )
	{
		var length = wall.Length;
		var pad = padded ? ArchOpeningClearance.Sides( preset, kit ) : 0f;

		pad = MathF.Min( pad, MathF.Max( 0f, (length - minimumWidth) * 0.5f ) );

		runFrom = pad;
		runTo = MathF.Max( pad, length - pad );
	}

	// Where a unit of this width sits when nothing was dragged wider - centred on the station and clamped into the
	// run. The click that places one and the rhythm that places twelve both land here.
	public static void Centre( float along, float width, float runFrom, float runTo, out float left, out float right )
	{
		var half = MathF.Min( width, runTo - runFrom ) * 0.5f;
		var centre = Math.Clamp( along, runFrom + half, MathF.Max( runFrom + half, runTo - half ) );

		left = centre - half;
		right = centre + half;
	}

	// One unit of a wall's rhythm: the same usable run, the same clamped centring, the same grid.
	public static ArchOpeningShape Stationed(
		ArchWall wall,
		ArchOpeningPreset preset,
		ArchKit kit,
		ArchOpeningRun run,
		float along,
		float wallHeight )
	{
		Usable( wall, preset, kit, true, ArchGridService.FinestSize, out var runFrom, out var runTo );

		Centre( along, ArchGridService.Fine( run.Width > 1f ? run.Width : preset.Width ), runFrom, runTo, out var left, out var right );

		var height = ArchGridService.Fine( run.Height > 1f ? run.Height : preset.Height );
		var sill = ArchGridService.Fine( run.SillHeight > 0.01f ? run.SillHeight : preset.SillHeight );

		sill = Math.Clamp( sill, 0f, MathF.Max( 0f, wallHeight - ArchGridService.FinestSize ) );

		return new ArchOpeningShape
		{
			Offset = (left + right) * 0.5f,
			Width = MathF.Max( ArchGridService.FinestSize, right - left ),
			Height = Math.Clamp( height, ArchGridService.FinestSize, MathF.Max( ArchGridService.FinestSize, wallHeight - sill ) ),
			SillHeight = sill
		};
	}
}