Editor helper that computes cable route geometry for the Advanced Mapping Tools package. Samples a path into offset, sagged polylines per cable, handles round bundles and loose rows, and returns PathTrack geometry plus corner indices for meshes and mounts.
namespace AdvancedMappingTools;
public sealed record CableTrackGeometry( int Index, PathTrack Track, int[] Corners );
/// <summary>The same sampled, offset and sagged geometry is used by cable meshes and their mounts.</summary>
public static class CableRouteGeometry
{
static float[] SagPattern => [0, .7f, -.5f, .35f, -.8f, .55f];
/// <summary>Cables held together by ties or in a round bundle hang as one; only a loose row varies.</summary>
public static bool Tied( CableRouteSettings s ) => Math.Clamp( s.Cables, 1, 12 ) > 1 && (s.BundleLayout == CableBundleLayout.Round || float.IsFinite( s.TieSpacing ) && s.TieSpacing >= 8);
/// <summary>Where cable <paramref name="cable"/> sits in the cross section, as (left, up).</summary>
public static Vector2 Offset( CableRouteSettings s, int cable )
{
var radius = Math.Clamp( s.Radius, .25f, 32 );
var cables = Math.Clamp( s.Cables, 1, 12 );
var pitch = radius * 2 + Math.Clamp( float.IsFinite( s.CableGap ) ? s.CableGap : 0, 0, 32 );
if ( s.BundleLayout != CableBundleLayout.Round || cables < 3 )
return new( (cable - (cables - 1) * .5f) * pitch, 0 );
// Round: on a circle wide enough that neighbours keep the gap.
var ring = pitch * .5f / MathF.Sin( MathF.PI / cables );
var angle = MathF.PI * 2 * cable / cables + MathF.PI * .5f;
return new( MathF.Cos( angle ) * ring, MathF.Sin( angle ) * ring );
}
/// <summary>The middle line of the bundle, sampled and sagged like the cables.</summary>
public static PathTrack Center( CableRouteSettings s ) => Build( s, Vector2.Zero, 1 )?.Track;
public static IEnumerable<CableTrackGeometry> Tracks( CableRouteSettings s )
{
if ( s.Path == null || s.Path.SegmentCount == 0 )
yield break;
var cables = Math.Clamp( s.Cables, 1, 12 );
for ( var cable = 0; cable < cables; cable++ )
{
// Each cable of a loose row hangs a little deeper or shallower than its neighbour.
var sagScale = cables > 1 && !Tied( s ) ? 1 + Math.Clamp( float.IsFinite( s.SagVariation ) ? s.SagVariation : 0, 0, 1 ) * SagPattern[cable % SagPattern.Length] : 1;
var built = Build( s, Offset( s, cable ), sagScale );
if ( built is not null )
yield return built with { Index = cable };
}
}
static CableTrackGeometry Build( CableRouteSettings s, Vector2 offset, float sagScale )
{
if ( s.Path == null || s.Path.SegmentCount == 0 )
return null;
var nodes = s.Path.Nodes.Select( n => n.Position ).ToArray();
var control = PathTrack.Along( nodes, s.Path.Closed, FrameMode.Upright );
var route = control.Frames.Take( s.Path.Closed ? control.Count - 1 : control.Count ).Select( f => f.Place( offset.x, offset.y ) ).ToArray();
var points = new List<Vector3>();
var corners = new List<int>();
var segments = s.Path.Closed ? route.Length : route.Length - 1;
for ( var i = 0; i < segments; i++ )
{
var a = route[i];
var b = route[(i + 1) % route.Length];
if ( a.Distance( b ) < .01f )
continue;
corners.Add( points.Count );
var count = Math.Clamp( (int)MathF.Ceiling( a.Distance( b ) / Math.Clamp( s.SampleStep, 4, 128 ) ), 2, 256 );
for ( var j = 0; j < count; j++ )
{
var t = j / (float)count;
points.Add( Vector3.Lerp( a, b, t ) - Vector3.Up * (s.Sag ? 4 * t * (1 - t) * MathF.Min( Math.Clamp( s.SagDepth, 0, 256 ) * sagScale, a.Distance( b ) * .45f ) : 0) );
}
}
if ( !s.Path.Closed )
{
corners.Add( points.Count );
points.Add( route[^1] );
}
if ( points.Count < 2 )
return null;
return new( 0, PathTrack.Along( points, s.Path.Closed, FrameMode.ParallelTransport ), corners.ToArray() );
}
}