Editor/Pipe/ArchPipeSection.cs

Utility class that produces 2D cross-section profiles for pipe-like architecture elements. It returns parametric ArchProfile shapes (round polygon, box, channel, bar) used by an extruder to generate geometry.

Native Interop
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// The sections a service run is extruded through. These are PARAMETRIC and per-run, which is why they are
// not kit stock: the whole point of the detail block is that the same corridor comes out six-sided on a
// backdrop and sixteen-sided in a room the player walks through, and a kit profile is one authored shape.
//
// Section space is the extruder's: x runs across the path and y runs up, so with an identity orientation a
// tray opens toward the sky whether it is hung off a ceiling or strapped to a wall - which is how a real one
// is hung, cables being laid in rather than threaded.
public static class ArchPipeSection
{
	public const int LeastSides = 3;

	public static ArchProfile Of( ArchPipePart part )
	{
		var radius = part.Radius;
		var sides = Math.Max( LeastSides, part.Detail.Sides );

		return part.Content switch
		{
			PipeContent.Duct => Box( radius, radius ),
			PipeContent.Tray => Channel( radius, radius * 0.8f, MathF.Max( 0.3f, radius * 0.12f ) ),
			_ => Round( radius, sides )
		};
	}

	public static ArchProfile Round( float radius, int sides )
	{
		var points = new List<Vector2>();
		var count = Math.Max( LeastSides, sides );

		for ( var step = 0; step < count; step++ )
		{
			var angle = MathF.Tau * step / count;

			points.Add( new Vector2( MathF.Cos( angle ) * radius, MathF.Sin( angle ) * radius ) );
		}

		return new ArchProfile { Name = "pipe_run", Points = points };
	}

	public static ArchProfile Box( float half, float rise )
	{
		return new ArchProfile
		{
			Name = "pipe_duct",
			Points = new List<Vector2> { new( -half, -rise ), new( half, -rise ), new( half, rise ), new( -half, rise ) }
		};
	}

	// A U traced as one closed loop rather than three bars, so the extruder mitres the whole channel round a
	// bend in one piece and the inside corners never open up.
	public static ArchProfile Channel( float half, float rise, float thickness )
	{
		var wall = MathF.Min( thickness, half * 0.5f );

		return new ArchProfile
		{
			Name = "pipe_tray",
			Points = new List<Vector2>
			{
				new( -half, -rise ),
				new( half, -rise ),
				new( half, rise ),
				new( half - wall, rise ),
				new( half - wall, -rise + wall ),
				new( -half + wall, -rise + wall ),
				new( -half + wall, rise ),
				new( -half, rise )
			}
		};
	}

	// Flat stock, laid so its width runs across the path - every hanger member, strap and cross-piece comes
	// off it, so a bracket run has one section and cannot step at a joint.
	public static ArchProfile Bar( float width, float thickness )
	{
		var across = MathF.Max( 0.2f, width ) * 0.5f;
		var deep = MathF.Max( 0.2f, thickness ) * 0.5f;

		return Box( across, deep );
	}
}