A partial class for ArchMesh that computes extruded mesh rings along a path. It generates per-path "rings" of vertex positions from a 2D cross-section, handling orientation, mitre/stretching at corners, and closed or open paths.
using System;
using System.Collections.Generic;
using HalfEdgeMesh;
using Sandbox;
namespace Sunless.Architecture;
public sealed partial class ArchMesh
{
// One frame for the whole path - per-ring derivation spun the section on near-vertical segments.
static List<Vector3[]> Rings( IReadOnlyList<Vector3> path, IReadOnlyList<Vector2> section, float scale, Rotation orientation, bool loop )
{
var rings = new List<Vector3[]>();
var right = Seed( Direction( path, 0, loop ), orientation );
for ( var index = 0; index < path.Count; index++ )
{
var incoming = Direction( path, index - 1, loop );
var outgoing = Direction( path, index, loop );
var forward = (incoming + outgoing).Normal;
if ( forward.IsNearZeroLength )
{
forward = outgoing;
}
right = (right - forward * Vector3.Dot( right, forward )).Normal;
if ( right.IsNearZeroLength )
{
right = Seed( forward, orientation );
}
var up = Vector3.Cross( forward, right ).Normal;
// The bisector-plane ring stretches across the turn, or the runs leave a notch instead of a mitre.
var lean = Vector3.Dot( forward, outgoing );
var axis = Vector3.Cross( incoming, outgoing );
var stretch = axis.IsNearZeroLength || lean < 0.15f ? Vector3.Zero : Vector3.Cross( axis.Normal, forward ).Normal;
var mitre = stretch.IsNearZeroLength ? 0f : 1f / lean - 1f;
var ring = new Vector3[section.Count];
for ( var point = 0; point < section.Count; point++ )
{
var offset = right * (section[point].x * scale) + up * (section[point].y * scale);
ring[point] = path[index] + offset + stretch * (Vector3.Dot( offset, stretch ) * mitre);
}
rings.Add( ring );
}
return rings;
}
// Wrap on a closed run, clamp on an open one - end rings sit square to their own run.
static Vector3 Direction( IReadOnlyList<Vector3> path, int index, bool loop )
{
var count = path.Count;
if ( !loop )
{
var clamped = Math.Clamp( index, 0, count - 2 );
return (path[clamped + 1] - path[clamped]).Normal;
}
var from = ((index % count) + count) % count;
return (path[(from + 1) % count] - path[from]).Normal;
}
static Vector3 Seed( Vector3 forward, Rotation orientation )
{
var reference = orientation.Up;
if ( MathF.Abs( Vector3.Dot( forward, reference ) ) > 0.99f )
{
reference = orientation.Forward;
}
return Vector3.Cross( reference, forward ).Normal;
}
}