Editor/Services/ArchRunSection.cs

Defines three classes for generating mesh sections along a path: an abstract ArchRunSection, ArchBandSection which creates rectangular band geometry along edges with miter handling, and ArchProfileSection which extrudes a profile along the path.

Native Interop
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;

			// MITRED, not lapped. Running one edge past the corner and butting the other into its side put
			// three faces on the corner's edge and a coplanar pair beside it; folding both onto the bisector
			// is the join a board actually has, and it is ArchBandGen's answer rather than a second one here.
			// -1 because ArchRegion.Outward - the side Beam measures its offsets on - is the right-hand normal
			// and ArchBandGen.Outward is the left-hand one.
			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 );
	}
}