Editor/Geometry/ArchWallSection.cs

Utility static class for resolving wall-related geometry values and helper computations. It computes resolved heights, thickness, cap dimensions, the 2D band polygon for a wall, and centers for pilasters based on room, kit, and run settings.

Reflection
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// What a wall MEASURES, resolved once and never re-derived at a caller: wall, then room, then kit. A handle, an
// elevation, a report, a damage bite and a fixture seat all need these numbers whether or not anything is installed
// to generate the wall itself.
public static class ArchWallSection
{
	public static float Height( ArchWall wall, ArchRoom room, ArchKit kit ) => Resolve( wall.Height, room?.WallHeight ?? 0f, kit.WallHeight );

	public static float Thickness( ArchWall wall, ArchKit kit ) => Resolve( wall.Thickness, 0f, kit.WallThickness );

	// What a wall with no height of its own stands at - the ceiling a half wall is measured against.
	public static float CeilingHeight( ArchRoom room, ArchKit kit ) => Resolve( 0f, room?.WallHeight ?? 0f, kit.WallHeight );

	// One resolution of the coping section: generator, designer form and report read the same cap.
	public static float CapHeight( IArchWallTreatment wall, ArchKit kit ) => Resolve( wall.CapHeight, 0f, kit.WallCapHeight );

	public static float CapOverhang( IArchWallTreatment wall, ArchKit kit ) => Resolve( wall.CapOverhang, 0f, kit.CasingDepth );

	// A wall is a band, not a line: its centreline grown by its own thickness.
	public static List<Vector2> Band( ArchWall wall, ArchKit kit )
	{
		var side = wall.Normal * (Thickness( wall, kit ) * 0.5f);

		return new List<Vector2> { wall.Start + side, wall.End + side, wall.End - side, wall.Start - side };
	}

	// The one resolution: generator and report agree on how many.
	public static IEnumerable<float> PilasterCentres( ArchPilasterRun run, float from, float to )
	{
		var span = to - from;

		if ( run is null || !run.Stands || span < 1f )
		{
			yield break;
		}

		var strip = MathF.Min( run.Width, span );
		var division = run.Count > 0 ? ArchDivide.Into( span, Math.Max( 1, run.Count - 1 ) ) : ArchDivide.AtMost( span, run.Bay );

		foreach ( var node in division.Nodes )
		{
			yield return run.OnCorners
				? from + node
				: Math.Clamp( from + node, from + strip * 0.5f, to - strip * 0.5f );
		}
	}

	static float Resolve( float value, float roomValue, float kitValue )
	{
		if ( value > 0f )
		{
			return value;
		}

		return roomValue > 0f ? roomValue : kitValue;
	}
}