Editor/Roof/ArchRoofCapping.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public static partial class ArchRoofGen {
// By exact name: a kit saved before the cap existed just gets no cap.
static void Capping( ArchMesh canvas, List<(Vector3 From, Vector3 To)> creases, float thickness, ArchKit kit, ArchBrush brush, ArchPlan plan, int level, int hostId ) {
var profile = ArchProfiles.Named( kit, "ridge_cap" );
if ( profile is null ) {
return;
}
var lift = Vector3.Up * thickness;
var reach = MathF.Max( 1f, profile.Max.x );
var junctions = Junctions( creases );
// Corners stop short (one block takes them); collinear bands overlap; hip low ends stop at the eave.
foreach ( var crease in creases ) {
var along = (crease.To - crease.From).Normal;
var flat = MathF.Abs( crease.To.z - crease.From.z ) < 0.1f;
var from = crease.From + lift + along * (Meets( junctions, crease.From ) ? reach * 0.6f : flat ? -reach * 0.75f : 0f);
var to = crease.To + lift - along * (Meets( junctions, crease.To ) ? reach * 0.6f : -reach * 0.75f);
if ( (to - from).Length < 1f ) {
continue;
}
foreach ( var span in ArchCut.Outside( plan, kit, level, hostId,
new Vector2( from.x, from.y ), new Vector2( to.x, to.y ),
MathF.Min( from.z, to.z ), MathF.Max( from.z, to.z ), ArchCutAffects.Roofs ) ) {
canvas.Extrude( new List<Vector3> { Vector3.Lerp( from, to, span.From ), Vector3.Lerp( from, to, span.To ) }, profile, 1f, Rotation.Identity, brush );
}
}
foreach ( var node in junctions ) {
var corner = new Vector3( reach, reach, 0f );
var seat = node + lift;
// The bands round it break over a cut, so the block that takes their corner has to go with them -
// left standing it is a cube floating in mid-air over the hole, attached to nothing.
if ( ArchCut.Covers( plan, kit, level, hostId, seat + Vector3.Up * profile.Min.y, seat + Vector3.Up * profile.Max.y, ArchCutAffects.Roofs ) ) {
continue;
}
canvas.Box(
seat + Vector3.Up * profile.Min.y - corner,
seat + Vector3.Up * profile.Max.y + corner,
brush );
}
}
// Same-direction ends at one spot are one band split, not a corner - left to overlap.
static List<Vector3> Junctions( List<(Vector3 From, Vector3 To)> creases ) {
var ends = new List<(Vector3 At, Vector3 Along)>();
foreach ( var crease in creases ) {
var along = (crease.To - crease.From).Normal;
ends.Add( (crease.From, along) );
ends.Add( (crease.To, along) );
}
var junctions = new List<Vector3>();
foreach ( var end in ends ) {
var meeting = ends.Where( other => (other.At - end.At).Length < 1f ).ToList();
if ( meeting.Count < 2 || meeting.All( other => MathF.Abs( Vector3.Dot( other.Along, end.Along ) ) > 0.99f ) ) {
continue;
}
if ( !junctions.Any( node => (node - end.At).Length < 1f ) ) {
junctions.Add( end.At );
}
}
return junctions;
}
static bool Meets( List<Vector3> junctions, Vector3 point ) {
return junctions.Any( node => (node - point).Length < 1f );
}
// A flat deck's only capping is the flashing where it dies into a taller section.
static List<(Vector3 From, Vector3 To)> Abutted( List<List<Vector2>> loops, Func<Vector2, Vector2, bool> abuts, float height ) {
var lines = new List<(Vector3 From, Vector3 To)>();
foreach ( var loop in loops ) {
var blocked = ArchBrokenRun.Blocked( loop, abuts );
for ( var index = 0; index < loop.Count; index++ ) {
var from = loop[index];
var to = loop[(index + 1) % loop.Count];
if ( blocked[index] && (to - from).Length > 4f ) {
lines.Add( (new Vector3( from.x, from.y, height ), new Vector3( to.x, to.y, height )) );
}
}
}
return lines;
}
}