Editor/Pillar/ArchPillarSoffit.cs

Utility class for pillar soffit calculations in the editor architecture code. Computes the overhead soffit height above a pillar part by checking room ceiling, platforms and beams that overlap the pillar footprint.

NetworkingFile Access
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// What a column with no authored height stands up TO. The ceiling's own UNDERSIDE is where the answer starts -
// the plate is the top of that body, and a capital measured off it finishes as deep inside the slab as the slab
// is thick. A beam or a hung platform crossing the same footprint is lower again, and a column that ignored one
// would stand straight through the thing it is there to carry.
public static class ArchPillarSoffit
{
	// Anything nearer than this to the column's own foot is something it stands ON rather than under.
	const float Clear = 8f;

	public static float Over( ArchPlan plan, ArchKit kit, ArchRoom room, ArchPillarPart pillar )
	{
		var host = plan?.OwnerOf( room );
		var ceiling = host is null
			? room.BaseHeight + ArchFloorGen.WallHeight( room, kit )
			: ArchFloorGen.Overhead( host, room, kit );

		if ( plan is null )
		{
			return ceiling;
		}

		var footprint = Footprint( pillar );
		var floor = pillar.BaseHeight;
		var soffit = ceiling;

		foreach ( var building in plan.Buildings )
		{
			foreach ( var platform in building.Platforms.Where( standing => standing.Hung && ArchLayerGate.On( standing ) ) )
			{
				soffit = Lower( soffit, platform.GradeHeight, floor, footprint, platform.Outline() );
			}

			foreach ( var beam in building.Rooms.SelectMany( standing => standing.Beams ).Where( ArchLayerGate.On ) )
			{
				soffit = Lower( soffit, beam.Soffit, floor, footprint, beam.Outline() );
			}
		}

		return soffit;
	}

	// The bounds of every column in the part, section included: ONE head for the whole grid, the way a roof
	// stands one parapet height for every one of its edges. Two columns under one arcade arriving at different
	// heights spring it from nowhere.
	public static List<Vector2> Footprint( ArchPillarPart pillar )
	{
		var half = pillar.Half;
		var min = new Vector2( float.MaxValue, float.MaxValue );
		var max = new Vector2( float.MinValue, float.MinValue );

		foreach ( var column in ArchPillarGen.Columns( pillar ) )
		{
			var at = new Vector2( column.At.x, column.At.y );

			min = Vector2.Min( min, at - half );
			max = Vector2.Max( max, at + half );
		}

		return ArchFootprint.Rect( min, max );
	}

	static float Lower( float soffit, float candidate, float floor, IReadOnlyList<Vector2> footprint, IReadOnlyList<Vector2> outline )
	{
		if ( candidate <= floor + Clear || candidate >= soffit || !ArchFootprint.Overlaps( outline, footprint ) )
		{
			return soffit;
		}

		return candidate;
	}
}