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.
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 );
}
}