Editor/Pillar/ArchPillarSoffit.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public static class ArchPillarSoffit {
const float Clear = 8f;
public static float Over( ArchPlan plan, ArchKit kit, ArchRoom room, ArchPillarPart pillar, float floor ) {
var host = plan?.OwnerOf( room );
var plate = host is null
? room.BaseHeight + ArchFloorGen.WallHeight( room, kit )
: ArchFloorGen.Overhead( host, room, kit );
var slab = Slab( plan, kit, pillar, floor );
if ( slab >= float.MaxValue ) {
return plate;
}
// Plate only counts where the storey actually stands over the column
return Plated( plan, kit, room, Under( pillar ) ) ? MathF.Min( plate, slab ) : slab;
}
static bool Plated( ArchPlan plan, ArchKit kit, ArchRoom room, Vector2 under ) {
foreach ( var standing in plan?.AllRooms() ?? Enumerable.Empty<ArchRoom>() ) {
if ( standing.Floor == room.Floor && standing.HasFloor && ArchLayerGate.On( standing )
&& ArchFootprint.Encloses( Shell( standing, kit ), under ) ) {
return true;
}
}
return false;
}
public static float Slab( ArchPlan plan, ArchKit kit, ArchPillarPart pillar, float floor ) {
return Covering( plan, kit, Under( pillar ), floor );
}
public static float Covering( ArchPlan plan, ArchKit kit, Vector2 under, float floor ) {
if ( plan is null ) {
return float.MaxValue;
}
var soffit = float.MaxValue;
var answers = ArchAnswers.Load();
foreach ( var building in plan.Buildings ) {
var lift = answers.Lift( plan, building, kit );
// Measured by footprint, not storey — avoids counting the carried slab as the ceiling
foreach ( var above in building.Rooms.Where( standing => standing.HasFloor && !standing.Spans && ArchLayerGate.On( standing ) ) ) {
soffit = Lower( soffit, above.BaseHeight - kit.FloorThickness, floor, under, Shell( above, kit ) );
if ( above.Floor > 0 ) {
soffit = Lower( soffit, ArchOverhangGen.Soffit( above, kit ), floor, under,
ArchOverhangGen.Unsupported( above, building, kit ) );
}
}
// Hung only — poured platforms are something to stand ON
foreach ( var platform in building.Platforms.Where( standing => standing.Hung && ArchLayerGate.On( standing ) ) ) {
soffit = Lower( soffit, platform.GradeHeight - lift, floor, under, platform.Outline() );
}
foreach ( var beam in building.Rooms.SelectMany( standing => standing.Beams ).Where( ArchLayerGate.On ) ) {
soffit = Lower( soffit, beam.Soffit, floor, under, beam.Outline() );
}
}
return soffit;
}
// Tests at the centroid, not the footprint — overlap ≠ under
static Vector2 Under( ArchPillarPart pillar ) {
var bounds = Footprint( pillar );
var centre = Vector2.Zero;
foreach ( var corner in bounds ) {
centre += corner;
}
return bounds.Count == 0 ? pillar.Origin : centre / bounds.Count;
}
public static List<Vector2> Footprint( ArchPillarPart pillar ) {
var half = pillar.Seated;
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 );
}
// Out to the outer wall face, not the centreline
static List<List<Vector2>> Shell( ArchRoom room, ArchKit kit ) {
return ArchRegion.Shell( ArchRegion.Footprints( new[] { room }, false ), kit.WallThickness );
}
static float Lower( float soffit, float candidate, float floor, Vector2 under, IReadOnlyList<Vector2> outline ) {
if ( candidate <= floor + Clear || candidate >= soffit || !ArchFootprint.Contains( outline, under ) ) {
return soffit;
}
return candidate;
}
static float Lower( float soffit, float candidate, float floor, Vector2 under, IEnumerable<IReadOnlyList<Vector2>> outlines ) {
foreach ( var outline in outlines ) {
soffit = Lower( soffit, candidate, floor, under, outline );
}
return soffit;
}
}