Editor service that generates walkway (bridge) architecture pieces. It computes geometry, creates an ArchRoom and roof deck, files them into the plan, and connects endpoints; supports rising or flat walkways with options for piers, skirting and cornice.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
sealed class ArchWalkwayGenerationService
{
readonly ArchPlan plan;
readonly ArchBuilding building;
readonly ArchKit kit;
readonly ArchConnectionService connections;
readonly ArchWalkwayGeometryService geometry;
readonly ArchKinds kinds;
int level;
float baseHeight;
Vector2 dragMin;
Vector2 dragMax;
Vector2 dragFrom;
float width;
float height;
ArchPillarPart pier;
int rise;
WalkwayInterior interior = WalkwayInterior.None;
bool cornice = true;
bool skirting = true;
public ArchWalkwayGenerationService( ArchPlan plan, ArchBuilding building, ArchKit kit )
{
this.plan = plan;
this.building = building;
this.kit = kit;
connections = new ArchConnectionService( plan, kit );
geometry = new ArchWalkwayGeometryService( connections );
kinds = ArchKinds.Load();
}
public ArchWalkwayGenerationService Between( int level, float baseHeight, Vector2 dragMin, Vector2 dragMax, Vector2 dragFrom )
{
this.level = level;
this.baseHeight = baseHeight;
this.dragMin = dragMin;
this.dragMax = dragMax;
this.dragFrom = dragFrom;
return this;
}
public ArchWalkwayGenerationService WithWidth( float width )
{
this.width = width;
return this;
}
public ArchWalkwayGenerationService WithHeight( float height )
{
this.height = height;
return this;
}
public ArchWalkwayGenerationService WithPiers( ArchPillarPart pier )
{
this.pier = pier;
return this;
}
public ArchWalkwayGenerationService WithRise( int rise )
{
this.rise = rise;
return this;
}
public ArchWalkwayGenerationService WithInterior( WalkwayInterior interior )
{
this.interior = interior;
return this;
}
public ArchWalkwayGenerationService WithCornice( bool cornice )
{
this.cornice = cornice;
return this;
}
public ArchWalkwayGenerationService WithSkirting( bool skirting )
{
this.skirting = skirting;
return this;
}
public ArchWalkway.Link Create()
{
if ( building is null )
{
return null;
}
var span = geometry.Resolve( level, rise, dragFrom, dragMin, dragMax, width );
if ( span is null )
{
return null;
}
var clear = MathF.Max( 60f, height );
var connection = new ArchWalkwayConnectionService( connections, building, span.Mouths, clear );
var endpoints = connection.ResolveEndpoints( span );
var lowFloor = endpoints.Min( endpoint => endpoint.Floor );
var highFloor = endpoints.Max( endpoint => endpoint.Floor );
var rising = highFloor > lowFloor;
// A flat link stands at the tallest end it touches; a rising link starts at the low one.
var lowEnd = endpoints.FirstOrDefault( endpoint => endpoint.Floor == lowFloor );
var resolvedBaseHeight = rising
? (lowEnd?.Height ?? baseHeight)
: MathF.Max( baseHeight, endpoints.Select( endpoint => endpoint.Height ).DefaultIfEmpty( baseHeight ).Max() );
var standing = ArchContact.Bury( kit, clear, -1f );
var room = CreateRoom( span, resolvedBaseHeight, standing, rising, lowFloor, highFloor, endpoints );
var connected = connection.Connect( room, endpoints );
room.Name = $"Walkway{connected.Host.Rooms.Count}";
plan.File( ArchKind.Walkway, connected.Host, room, kinds );
if ( rising )
{
ArchWalkway.SetInterior( plan, room, kit, interior );
}
else
{
new ArchWalkwayTransitionService( plan, room, kit ).AddLowerFlights( span, endpoints );
new ArchWalkwaySupportService( plan, connected.Host, room, kit ).AddPiers( pier, span.Min, span.Max, span.AlongX );
}
var deck = CreateDeck( span, resolvedBaseHeight, room.WalkwayTop, clear, connected.Host, rising, room.Floor, endpoints );
plan.File( ArchKind.Roof, connected.Host, deck, kinds );
return new ArchWalkway.Link { Room = room, Roof = deck, Host = connected.Host, Endpoints = connected.Endpoints, Breached = connected.Opened.Count, Rising = rising };
}
ArchRoom CreateRoom( ArchWalkwaySpan span, float baseHeight, float standing, bool rising, int lowFloor, int highFloor, IReadOnlyList<ArchWalkwayEndpoint> endpoints )
{
var room = new ArchRoom
{
Id = plan.AllocateId(),
Floor = lowFloor,
BaseHeight = baseHeight,
WallHeight = standing,
HasFloor = !rising,
Spans = true,
Interior = interior,
WalkwaySkirting = skirting,
WalkwayTop = rising ? endpoints.First( endpoint => endpoint.Floor == highFloor ).Height : 0f,
Footprint = ArchFootprint.Rect( span.Min, span.Max )
};
for ( var index = 0; index < room.Footprint.Count; index++ )
{
var start = room.Footprint[index];
var end = room.Footprint[(index + 1) % room.Footprint.Count];
var runsAlong = span.AlongX
? MathF.Abs( end.x - start.x ) > MathF.Abs( end.y - start.y )
: MathF.Abs( end.y - start.y ) > MathF.Abs( end.x - start.x );
if ( !runsAlong )
{
continue;
}
plan.File( ArchKind.Wall, room, new ArchWall
{
Id = plan.AllocateId(),
Start = start,
End = end,
Exterior = true,
Cap = false,
Height = standing,
// The link draws its own boards so they die square at the mouth corners.
Baseboard = false
}, kinds );
}
ArchWalkway.SeatSides( room, span );
return room;
}
ArchRoofPart CreateDeck( ArchWalkwaySpan span, float baseHeight, float highBase, float clear, ArchBuilding host, bool rising, int floor, IReadOnlyList<ArchWalkwayEndpoint> endpoints )
{
var deck = ArchBuild.Roof( plan, kit, floor, rising ? RoofStyle.Shed : RoofStyle.Flat, span.DeckMin, span.DeckMax, baseHeight + clear, false, false );
deck.Name = $"WalkwayRoof{plan.CountFiled( ArchKind.Roof, host, kinds ) + 1}";
deck.Overhang = 0f;
deck.RidgeCap = false;
deck.Fascia = false;
deck.Soffit = false;
deck.Walkway = true;
if ( !rising )
{
deck.RidgeAlongX = span.AlongX;
deck.Ceiling = true;
deck.Cornice = cornice;
return deck;
}
// The shed follows the climb: its low eave is the low end's headroom, its pitch the rise over the run.
var rise = MathF.Max( 1f, highBase - baseHeight );
var run = span.AlongX ? span.DeckMax.x - span.DeckMin.x : span.DeckMax.y - span.DeckMin.y;
var highAtMin = endpoints[0].Floor > endpoints[1].Floor;
deck.Pitch = MathF.Atan( rise / MathF.Max( 1f, run ) ).RadianToDegree();
deck.RidgeAlongX = !span.AlongX;
deck.Reversed = highAtMin;
deck.EndWalls = false;
return deck;
}
}