Editor service that generates an architectural wing section for a building plan. It resolves placement, creates a room, opens connections to the host building, optionally merges footprints or creates a roof, and returns an ArchSection describing the new wing.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public sealed class ArchWingGenerationService
{
readonly ArchPlan plan;
readonly ArchBuilding building;
readonly ArchKit kit;
int level;
float baseHeight;
Vector2 min;
Vector2 max;
SectionRoof choice;
float drop;
bool floor;
bool gutters;
RidgeRun ridge;
public ArchWingGenerationService( ArchPlan plan, ArchBuilding building, ArchKit kit )
{
this.plan = plan;
this.building = building;
this.kit = kit;
}
public ArchWingGenerationService On( int level, float baseHeight, Vector2 min, Vector2 max )
{
this.level = level;
this.baseHeight = baseHeight;
this.min = min;
this.max = max;
return this;
}
public ArchWingGenerationService WithRoof( SectionRoof choice, float drop, bool gutters, RidgeRun ridge )
{
this.choice = choice;
this.drop = drop;
this.gutters = gutters;
this.ridge = ridge;
return this;
}
public ArchWingGenerationService WithFloor( bool floor )
{
this.floor = floor;
return this;
}
public ArchSection Create()
{
var resolved = new ArchBoundaryPlacementService( plan, kit ).Outside( level, min, max, building );
if ( !resolved.IsUsable || !resolved.TouchesHost )
{
return null;
}
min = resolved.Min;
max = resolved.Max;
var placement = new ArchWingPlacementService( building, kit, level );
var continuing = choice == SectionRoof.Continue;
var host = continuing ? placement.Host( min, max ) : null;
var merge = host is not null;
var wallHeight = merge
? MathF.Max( 32f, host.BaseHeight - baseHeight )
: MathF.Max( 32f, kit.WallHeight - (continuing ? 0f : MathF.Max( 0f, drop )) );
var party = new List<(Vector2 From, Vector2 To)>();
var wing = ArchBuild.CreateRoom( plan, building, $"Wing{building.Rooms.Count}", level, baseHeight, wallHeight, min, max, floor, party, kit.WallThickness );
var connections = new ArchConnectionService( plan, kit );
foreach ( var edge in party )
{
connections.OpenRooms( wing, building, edge.From, edge.To, wallHeight, wing.Floor );
}
// The breach took the host's wall out of the span, so the wing's own must not stand in its place.
foreach ( var (from, to) in party )
{
var partyWall = wing.Walls.FirstOrDefault( wall => SameRun( wall, from, to ) );
if ( partyWall is not null )
{
plan.Unfile( partyWall );
}
}
connections.NormalizeBoundaries();
if ( merge )
{
var merged = ArchFootprint.Merge( host.Outline(), ArchFootprint.Rect( min, max ) );
if ( merged is not null )
{
host.Reshape( merged );
host.Style = host.Style == RoofStyle.Flat ? RoofStyle.Flat : RoofStyle.Hip;
return new ArchSection { Room = wing, Roof = host, SharedWalls = party.Count, Merged = true };
}
}
if ( choice == SectionRoof.None )
{
return new ArchSection { Room = wing, SharedWalls = party.Count };
}
var roof = ArchBuild.Roof( plan, kit, level, ArchBuild.Winged( choice ), min, max, baseHeight + wallHeight, gutters, choice == SectionRoof.Capped, ridge );
plan.File( ArchKind.Roof, building, roof );
return new ArchSection { Room = wing, Roof = roof, SharedWalls = party.Count };
}
static bool SameRun( ArchWall wall, Vector2 from, Vector2 to )
{
return ( wall.Start - from ).Length <= 0.01f && ( wall.End - to ).Length <= 0.01f
|| ( wall.Start - to ).Length <= 0.01f && ( wall.End - from ).Length <= 0.01f;
}
}