Editor-side geometry generator for porch overhangs. Computes unsupported overhang regions, creates soffit slabs and places supporting posts based on building, room, kit and style data.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// Nothing under it carries it, so it needs a boxed underside of its own.
public static class ArchOverhangGen
{
const float Walkable = 24f;
public static void Build( ArchMesh canvas, ArchRoom room, ArchBuilding building, ArchKit kit, ArchStyle style )
{
new ArchOverhangGenerationService( room, building, kit, style ).Create( canvas );
}
// Walkable depth needs carrying: posts land on open-air edges, corners first, then evenly.
internal static void Posts( ArchMesh canvas, List<List<Vector2>> region, ArchRoom room, ArchBuilding building, ArchKit kit, float top, ArchBrush brush )
{
if ( ArchFootprint.Shrink( region, Walkable * 0.5f ).Count == 0 )
{
return;
}
var support = new ArchOverhangSupportService( room, building, kit );
var spacing = MathF.Max( 32f, kit.PorchPostSize * 8f );
foreach ( var loop in ArchFootprint.Outer( region ) )
{
var open = ArchRegion.Open( loop, support.Carried ).Select( run => ArchRunPath.Of( run, top ) );
foreach ( var at in ArchBays.Posts( ArchBays.Over( open, spacing, MathF.Max( 6f, kit.PorchPostSize * 2f ) ) ) )
{
ArchPorchGen.Stand( canvas, at.Point, kit, support.GradeAt( at.Point ), top, brush );
}
}
}
// The line below grows by half a wall first, or the footprint reads unsupported.
public static List<List<Vector2>> Unsupported( ArchRoom room, ArchBuilding building, ArchKit kit )
{
return new ArchOverhangSupportService( room, building, kit ).Resolve();
}
}
sealed class ArchOverhangGenerationService
{
readonly ArchRoom room;
readonly ArchBuilding building;
readonly ArchKit kit;
readonly ArchStyle style;
public ArchOverhangGenerationService( ArchRoom room, ArchBuilding building, ArchKit kit, ArchStyle style )
{
this.room = room;
this.building = building;
this.kit = kit;
this.style = style;
}
public void Create( ArchMesh canvas )
{
var support = new ArchOverhangSupportService( room, building, kit );
var region = support.Resolve();
if ( region.Count == 0 )
{
return;
}
var chain = new[] { room.Palette, building.Palette };
var depth = MathF.Max( 3f, kit.FasciaHeight );
var top = room.BaseHeight - kit.FloorThickness;
foreach ( var loop in ArchFootprint.Outer( region ) )
{
ArchFloorGen.Slab( canvas, loop, null, top - depth, top, style.Brush( ArchSurface.Soffit, chain ) );
}
using ( canvas.Part( ArchPieces.Posts ) )
{
ArchOverhangGen.Posts( canvas, region, room, building, kit, top - depth, style.Brush( ArchSurface.Pillar, chain ) );
}
}
}
sealed class ArchOverhangSupportService
{
readonly ArchRoom room;
readonly ArchBuilding building;
readonly ArchKit kit;
readonly List<List<Vector2>> carried;
public ArchOverhangSupportService( ArchRoom room, ArchBuilding building, ArchKit kit )
{
this.room = room;
this.building = building;
this.kit = kit;
carried = room is null || building is null || room.Floor <= 0
? new List<List<Vector2>>()
: ArchRegion.Shell( ArchRegion.Storey( building, room.Floor - 1 ), kit.WallThickness );
}
public List<List<Vector2>> Resolve()
{
var none = new List<List<Vector2>>();
if ( room is null || building is null || room.Floor <= 0 || room.Spans )
{
return none;
}
var footprint = ArchFloorGen.Footprint( room );
if ( footprint.Count < 3 || !ArchFootprint.IsRectilinear( footprint ) || carried.Count == 0 )
{
return none;
}
return ArchRegion.Minus( footprint, carried );
}
public float GradeAt( Vector2 point )
{
var lower = building.Rooms
.Where( other => other.Floor == room.Floor - 1 )
.Where( other => ArchFootprint.Encloses( ArchRegion.Shell( ArchRegion.Footprints( new[] { other } ), kit.WallThickness ), point ) )
.OrderByDescending( other => other.BaseHeight )
.FirstOrDefault();
return lower?.BaseHeight ?? building.Rooms.Where( other => other.Floor == room.Floor - 1 ).Select( other => other.BaseHeight ).DefaultIfEmpty( kit.GroundClearance ).Min();
}
public IReadOnlyList<List<Vector2>> Carried => carried;
}