Editor/Services/ArchRunSection.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// Subclass only for a shape the three below cannot make; do not re-implement the walk.
public abstract class ArchRunSection {
	public abstract void Emit( ArchMesh canvas, ArchRunPath path, ArchBrush brush );
}

// Each corner belongs to the edge LEAVING it - carried past convex turns, back at reflex, none at open ends.
public sealed class ArchBandSection : ArchRunSection {
	public float Shift { get; init; }
	public float Depth { get; init; }
	public float Bottom { get; init; }
	public float Top { get; init; }

	public static ArchBandSection Between( float near, float far, float bottom, float top ) {
		return new ArchBandSection { Shift = near, Depth = far - near, Bottom = bottom, Top = top };
	}

	public override void Emit( ArchMesh canvas, ArchRunPath path, ArchBrush brush ) {
		if ( Depth < 0.05f || Top - Bottom < 0.05f ) {
			return;
		}

		var far = Shift + Depth;

		for ( var edge = 0; edge < path.Edges; edge++ ) {
			var start = path.At( edge );
			var end = path.At( edge + 1 );
			var span = end - start;
			var length = span.Length;

			if ( length < 0.05f ) {
				continue;
			}

			var direction = span / length;

			// -1 because ArchRegion.Outward is the right-hand normal, ArchBandGen.Outward the left.
			var foldFrom = path.Continues( edge ) ? ArchBandGen.Fold( path.At( edge ) - path.At( edge - 1 ), direction, -1f ) : 0f;
			var foldTo = path.Continues( edge + 1 ) ? ArchBandGen.Fold( direction, path.At( edge + 2 ) - path.At( edge + 1 ), -1f ) : 0f;

			canvas.Beam( start, end, Shift, far, Bottom, Top, brush, foldFrom, foldTo );
		}
	}
}

// The extruder mitres closed runs itself; open ends are simply capped.
public sealed class ArchProfileSection : ArchRunSection {
	public ArchProfile Profile { get; init; }
	public float Height { get; init; }
	public float Scale { get; init; } = 1f;
	public Rotation Roll { get; init; } = Rotation.Identity;

	public override void Emit( ArchMesh canvas, ArchRunPath path, ArchBrush brush ) {
		if ( Profile is null || !Profile.IsUsable || path.Points.Count < 2 ) {
			return;
		}

		canvas.Extrude( path.Raised( Height ), Profile, Scale, Roll, brush, path.Closed );
	}
}