Editor/Roof/ArchRoofTrim.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public static partial class ArchRoofGen {
	static void Soffit( ArchMesh canvas, ArchRoofPart roof, IReadOnlyList<List<Vector2>> plates, ArchKit kit, ArchBrush brush, Func<Vector2, Vector2, bool> abuts, ArchPlan plan, int hostId ) {
		var height = roof.BaseHeight - kit.FasciaHeight;
		var thickness = Math.Max( 1f, kit.CasingDepth );

		foreach ( var loop in plates ) {
			Band( canvas, loop, 0f, roof.Overhang, height - thickness, height, brush, abuts, kit, plan, roof.Level, hostId, ArchCutAffects.Trims );
		}
	}

	static void Fascia( ArchMesh canvas, ArchRoofPart roof, List<List<Vector2>> eaves, ArchKit kit, ArchBrush brush, Func<Vector2, Vector2, bool> abuts, ArchPlan plan, int hostId ) {
		var height = kit.FasciaHeight;

		if ( height <= 0.1f ) {
			return;
		}

		var top = roof.BaseHeight;

		// Closed on top: the deck climbs from BaseHeight, so an open top reads as a slot.
		foreach ( var loop in eaves ) {
			Band( canvas, loop, 0f, ArchProfiles.FasciaDepth( kit ), top - height, top, brush, abuts, kit, plan, roof.Level, hostId, ArchCutAffects.Trims );
		}
	}

	// Wall carried up past the deck: how a low section butts a taller one without an eave.
	static void Parapet( ArchMesh canvas, ArchRoofPart roof, IReadOnlyList<Vector2> outline, ArchKit kit, ArchBrush wall, ArchBrush cap, Func<Vector2, Vector2, bool> abuts, ArchPlan plan, int hostId ) {
		var height = MathF.Max( 4f, roof.ParapetHeight );
		var thickness = MathF.Max( 2f, kit.WallThickness );
		var top = roof.BaseHeight + height;
		var crowned = kit.FindCornice( roof.Cornice ) is { Stands: true };

		// Bite into the plate, not a fascia depth: there is no eave behind a parapet. Under a crown the head laps
		// UP into the entablature's own top course as well, or the two share the parapet's top plane all the way
		// round and the crown reads as a band with a second wall standing on it.
		Band( canvas, outline, thickness * -0.5f, thickness, ArchLap.Bury( kit, roof.BaseHeight, 1f ),
			crowned ? ArchLap.Bury( kit, top, 1f ) : top, wall, abuts, kit, plan, roof.Level, hostId, ArchCutAffects.Trims );

		var profile = kit.FindProfile( "parapet_cap" );

		// A crowned deck is finished by the cornice's own top course, exactly as a crowned wall head is.
		if ( profile is null || !profile.IsUsable || crowned ) {
			return;
		}

		foreach ( var run in Runs( ArchFootprint.Wind( outline.ToList() ), top, abuts ) ) {
			canvas.Extrude( run.Raised(), profile, 1f, Rotation.Identity, cap, run.Closed );
		}
	}

	// The entablature the deck EDGE wears - the same RING a storey head wears, hung from the parapet top where one
	// stands and from the eave line where none does. One emitter, two callers.
	static void Crown(
		ArchMesh canvas,
		ArchRoofPart roof,
		IReadOnlyList<Vector2> outline,
		ArchKit kit,
		ArchStyle style,
		ArchPalette[] chain,
		Func<Vector2, Vector2, bool> abuts,
		ArchPlan plan,
		int hostId ) {
		new ArchCorniceRing {
			Crown = kit.FindCornice( roof.Cornice ),
			Outline = outline,
			Head = roof.Parapet ? roof.BaseHeight + MathF.Max( 4f, roof.ParapetHeight ) : roof.BaseHeight,
			Thickness = MathF.Max( 2f, kit.WallThickness ),
			Abuts = abuts,
			Kit = kit,
			Style = style,
			Chain = chain,
			Plan = plan,
			Level = roof.Level,
			HostId = hostId
		}.Lay( canvas );
	}

	// A walkway deck is open at the mouths, so its cornice runs only the two long walls.
	static void WalkwayCornice( ArchMesh canvas, ArchRoofPart roof, IReadOnlyList<ArchFloorCutout> wells, ArchKit kit, float height, ArchBrush brush ) {
		var profile = ArchProfiles.Named( kit, "cornice" );

		if ( profile is null || !roof.CeilingCornice ) {
			return;
		}

		var bite = ArchLap.Bite( kit );

		// Eroded ACROSS the run only, to bring the moulding onto the passage face. Eroding along it
		// too - which a square inset does - stopped the cornice half a wall short of each mouth,
		// where the run has to reach the host wall's inner face and cap on it.
		var erode = roof.RidgeAlongX
			? new Vector2( 0f, kit.WallThickness * 0.5f - bite )
			: new Vector2( kit.WallThickness * 0.5f - bite, 0f );

		ArchCornice.Sides( canvas, new[]
		{
			new Vector2( roof.Min.x + erode.x, roof.Min.y + erode.y ),
			new Vector2( roof.Max.x - erode.x, roof.Min.y + erode.y ),
			new Vector2( roof.Max.x - erode.x, roof.Max.y - erode.y ),
			new Vector2( roof.Min.x + erode.x, roof.Max.y - erode.y )
		}, roof.RidgeAlongX, wells, kit, height, brush );
	}

	static void Band(
		ArchMesh canvas,
		IReadOnlyList<Vector2> loop,
		float shift,
		float depth,
		float bottom,
		float top,
		ArchBrush brush,
		Func<Vector2, Vector2, bool> abuts,
		ArchKit kit = null,
		ArchPlan plan = null,
		int level = 0,
		int hostId = 0,
		ArchCutAffects target = ArchCutAffects.Trims ) {
		if ( loop.Count < 3 ) {
			return;
		}

		var section = new ArchBandSection {
			Shift = shift,
			Depth = depth,
			Bottom = bottom,
			Top = top
		};

		// Leveled at the band's own bottom, the way a gutter run is leveled at the channel: a cut has to
		// reach the band's lower edge before it can break the run, so a shaft that only opens the storey
		// below leaves a fascia running over its head.
		foreach ( var run in ArchBrokenRun.Of( loop, abuts ).Level( bottom ).Resolve() ) {
			foreach ( var surviving in ArchCut.Runs( plan, kit, level, hostId, run, target ) ) {
				section.Emit( canvas, surviving, brush );
			}
		}
	}
}