Editor/Porch/ArchPorchCanopy.cs
using System;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// Both run across the porch, so cut from the wall edges, not the open perimeter.
public static partial class ArchPorchGen {
	static void Canopy( ArchMesh canvas, ArchPorchShape shape, ArchPorchPart porch, ArchBuilding building, ArchKit kit, ArchBrush brush ) {
		var cover = building?.Roofs.FirstOrDefault( roof => roof.Id == porch.RoofId );

		if ( cover is null ) {
			return;
		}

		var slope = MathF.Tan( MathF.Max( 1f, cover.Pitch ).DegreeToRadian() );
		var beam = MathF.Max( 3f, kit.PorchPostSize );

		foreach ( var wall in shape.Walls ) {
			var reach = wall.Depth - beam;

			if ( reach < 12f ) {
				continue;
			}

			if ( porch.Rafters ) {
				Rafters( canvas, shape, wall, kit, cover, slope, reach, brush );
			}

			if ( porch.Braces ) {
				Braces( canvas, shape, porch, wall, kit, reach, brush );
			}
		}
	}

	// Hangs from the deck's underside - one slab, no framing cavity to sit in.
	static void Rafters( ArchMesh canvas, ArchPorchShape shape, ArchPorchShape.ArchPorchWall wall, ArchKit kit, ArchRoofPart cover, float slope, float reach, ArchBrush brush ) {
		var bite = ArchLap.Bite( kit );
		var depth = MathF.Max( 2f, kit.PorchRafterDepth );
		var half = MathF.Max( 0.5f, kit.PorchRafterWidth ) * 0.5f;
		var span = (wall.To - wall.From).Length;

		if ( span < 1f ) {
			return;
		}

		var along = (wall.To - wall.From) / span;
		var inward = -wall.Outward;
		var eave = shape.Ceiling + slope * MathF.Max( 0f, cover.Overhang );

		foreach ( var station in ArchDivide.AtMost( span, MathF.Max( 12f, kit.PorchRafterSpacing ) ).Nodes ) {
			var foot = wall.From + along * station;
			var head = foot + inward * (reach + bite);
			var high = eave + slope * (reach + bite);

			canvas.Rake( foot - inward * bite, head, -half, half, high - depth, eave - depth, high, eave, brush );
		}
	}

	// A prop, not a knee brace - its foot is clamped to the deck.
	static void Braces( ArchMesh canvas, ArchPorchShape shape, ArchPorchPart porch, ArchPorchShape.ArchPorchWall wall, ArchKit kit, float reach, ArchBrush brush ) {
		var half = MathF.Max( 1f, kit.PorchBraceSize ) * 0.5f;
		var span = (wall.To - wall.From).Length;

		if ( span < 1f ) {
			return;
		}

		var along = (wall.To - wall.From) / span;
		var inward = -wall.Outward;
		var drop = reach * MathF.Max( 0.2f, kit.PorchBraceRake );
		var foot = MathF.Max( shape.Grade + half * 2f, shape.Head - drop );

		foreach ( var station in ArchDivide.AtMost( span, MathF.Max( 24f, porch.PostSpacing ) ).Nodes ) {
			var at = wall.From + along * station;
			var head = at + inward * reach;

			canvas.Strut(
				new Vector3( at.x, at.y, foot ),
				new Vector3( head.x, head.y, shape.Head ),
				half, brush );
		}
	}
}