Editor/Roof/ArchCrossGable.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public sealed class ArchCrossGableJunction {
public ArchRoofPart Host { get; init; }
public ArchRoofPart Branch { get; init; }
public bool HostAtMinimum { get; init; }
public bool BranchAtMinimum { get; init; }
public float AlongMin { get; init; }
public float AlongMid { get; init; }
public float AlongMax { get; init; }
public float WallAcross { get; init; }
public float EaveAcross { get; init; }
public float ApexAcross { get; init; }
public float HostBoundaryAt( float along ) {
if ( along <= AlongMin || along >= AlongMax ) {
return EaveAcross;
}
var inward = ApexAcross - EaveAcross;
if ( along <= AlongMid ) {
return EaveAcross + inward * (along - AlongMin) / MathF.Max( 0.01f, AlongMid - AlongMin );
}
return EaveAcross + inward * (AlongMax - along) / MathF.Max( 0.01f, AlongMax - AlongMid );
}
public Vector3 HostPoint( float along, float across, float height ) {
return Host.RidgeAlongX
? new Vector3( along, across, height )
: new Vector3( across, along, height );
}
public Vector3 BranchPoint( float along, float across, float height ) {
return Branch.RidgeAlongX
? new Vector3( along, across, height )
: new Vector3( across, along, height );
}
}
public static class ArchCrossGableService {
public static List<ArchCrossGableJunction> Resolve( ArchBuilding building, ArchKit kit, ArchKinds table = null ) {
var roofs = ArchPlanStore.FiledOn( ArchKind.Roof, building, table )
.OfType<ArchRoofPart>()
.Where( roof => roof.Style == RoofStyle.Gable && roof.Outline().Count == 4 )
.ToList();
var junctions = new List<ArchCrossGableJunction>();
for ( var hostIndex = 0; hostIndex < roofs.Count; hostIndex++ ) {
for ( var branchIndex = 0; branchIndex < roofs.Count; branchIndex++ ) {
if ( hostIndex == branchIndex ) {
continue;
}
if ( TryResolve( roofs[hostIndex], roofs[branchIndex], kit ) is { } junction ) {
junctions.Add( junction );
}
}
}
return junctions;
}
public static bool JoinsBranchEnd(
ArchRoofPart roof,
Vector2 from,
Vector2 to,
IReadOnlyList<ArchCrossGableJunction> junctions ) {
var along = roof.RidgeAlongX
? (from.x + to.x) * 0.5f
: (from.y + to.y) * 0.5f;
return junctions.Any( junction =>
ReferenceEquals( junction.Branch, roof ) &&
MathF.Abs( along - junction.WallAcross ) < 0.5f );
}
public static List<List<Vector2>> FittingAbutments(
ArchRoofPart roof,
ArchKit kit,
IReadOnlyList<ArchCrossGableJunction> junctions ) {
var blockers = new List<List<Vector2>>();
foreach ( var junction in junctions.Where( junction => ReferenceEquals( junction.Host, roof ) ) ) {
var reach = MathF.Max( DeckReach( roof, kit ), DeckReach( junction.Branch, kit ) );
var outline = ArchFootprint.Wind( junction.Branch.Outline() );
blockers.AddRange( ArchFootprint.Grow( new[] { outline }, reach ) );
}
return blockers;
}
static ArchCrossGableJunction TryResolve( ArchRoofPart host, ArchRoofPart branch, ArchKit kit ) {
if ( host.Level != branch.Level ||
host.RidgeAlongX == branch.RidgeAlongX ||
MathF.Abs( host.BaseHeight - branch.BaseHeight ) > 0.5f ) {
return null;
}
ArchFootprint.Bounds( host.Outline(), out var hostMin, out var hostMax );
ArchFootprint.Bounds( branch.Outline(), out var branchMin, out var branchMax );
var hostAlongMin = host.RidgeAlongX ? hostMin.x : hostMin.y;
var hostAlongMax = host.RidgeAlongX ? hostMax.x : hostMax.y;
var hostAcrossMin = host.RidgeAlongX ? hostMin.y : hostMin.x;
var hostAcrossMax = host.RidgeAlongX ? hostMax.y : hostMax.x;
var branchAcrossMin = branch.RidgeAlongX ? branchMin.y : branchMin.x;
var branchAcrossMax = branch.RidgeAlongX ? branchMax.y : branchMax.x;
var branchAlongMin = branch.RidgeAlongX ? branchMin.x : branchMin.y;
var branchAlongMax = branch.RidgeAlongX ? branchMax.x : branchMax.y;
var touchesMinimum = MathF.Abs( branchAlongMax - hostAcrossMin ) < 0.5f;
var touchesMaximum = MathF.Abs( branchAlongMin - hostAcrossMax ) < 0.5f;
if ( !touchesMinimum && !touchesMaximum ) {
return null;
}
if ( branchAcrossMin < hostAlongMin - 0.5f || branchAcrossMax > hostAlongMax + 0.5f ) {
return null;
}
var hostReach = DeckReach( host, kit );
var branchReach = DeckReach( branch, kit );
var alongMin = branchAcrossMin - branchReach;
var alongMax = branchAcrossMax + branchReach;
var alongMid = (alongMin + alongMax) * 0.5f;
var eaveAcross = touchesMinimum ? hostAcrossMin - hostReach : hostAcrossMax + hostReach;
var inward = touchesMinimum ? 1f : -1f;
var hostSlope = ArchRoofPlane.Slope( host );
var branchSlope = ArchRoofPlane.Slope( branch );
if ( hostSlope < 0.01f || branchSlope < 0.01f ) {
return null;
}
var branchRise = branchSlope * (alongMax - alongMin) * 0.5f;
var depth = branchRise / hostSlope;
var apexAcross = eaveAcross + inward * depth;
var hostRidge = (hostAcrossMin + hostAcrossMax) * 0.5f;
if ( touchesMinimum ? apexAcross >= hostRidge - 0.5f : apexAcross <= hostRidge + 0.5f ) {
return null;
}
return new ArchCrossGableJunction {
Host = host,
Branch = branch,
HostAtMinimum = touchesMinimum,
BranchAtMinimum = touchesMaximum,
AlongMin = alongMin,
AlongMid = alongMid,
AlongMax = alongMax,
WallAcross = touchesMinimum ? hostAcrossMin : hostAcrossMax,
EaveAcross = eaveAcross,
ApexAcross = apexAcross
};
}
static float DeckReach( ArchRoofPart roof, ArchKit kit ) {
return roof.Overhang + (roof.Fascia ? ArchProfiles.FasciaDepth( kit ) + 1.5f : 0f);
}
}