Editor/Services/ArchDivide.cs
using System;
using System.Collections.Generic;
namespace Sunless.Architecture;
// Spacing only decides how MANY bays; the step is span/count, so the last lands exactly on the end.
public readonly struct ArchDivision {
public float Span { get; init; }
public int Count { get; init; }
public float Step => Count > 0 ? Span / Count : 0f;
public float At( int index ) => Step * index;
public (float From, float To) Bay( int index ) => (At( index ), At( index + 1 ));
// Includes both ends: a corner lands on a post, never between two.
public IEnumerable<float> Nodes {
get {
for ( var index = 0; index <= Count; index++ ) {
yield return At( index );
}
}
}
public IEnumerable<float> Inner {
get {
for ( var index = 1; index < Count; index++ ) {
yield return At( index );
}
}
}
public IEnumerable<float> Centres {
get {
for ( var index = 0; index < Count; index++ ) {
yield return At( index ) + Step * 0.5f;
}
}
}
public IEnumerable<(float From, float To)> Bays {
get {
for ( var index = 0; index < Count; index++ ) {
yield return Bay( index );
}
}
}
}
// Even division in one place - generators used to disagree whether spacing was a ceiling or a floor.
public static class ArchDivide {
// Spacing is a MAXIMUM: no bay comes out longer - the only answer that covers the span.
public static ArchDivision AtMost( float span, float spacing ) {
return Cut( span, spacing < 0.05f ? 1f : MathF.Ceiling( span / spacing ) );
}
// Spacing is a MINIMUM: no bay comes out shorter, so what stands in them cannot touch.
public static ArchDivision AtLeast( float span, float spacing ) {
return Cut( span, spacing < 0.05f ? 1f : MathF.Floor( span / spacing ) );
}
public static ArchDivision Into( float span, int count ) {
return Cut( span, Math.Max( 1, count ) );
}
// Fixed panels do not stretch: a too-short remainder snaps the last node onto the end instead of leaving a sliver.
public static List<float> Fixed( float span, float size ) {
var nodes = new List<float> { 0f };
if ( span < 0.05f ) {
return nodes;
}
var pitch = MathF.Max( 1f, size );
var whole = (int)MathF.Floor( span / pitch );
for ( var index = 1; index <= whole; index++ ) {
nodes.Add( index * pitch );
}
if ( span - nodes[^1] < pitch * 0.15f && nodes.Count > 1 ) {
nodes[^1] = span;
return nodes;
}
if ( span - nodes[^1] > 0.05f ) {
nodes.Add( span );
}
return nodes;
}
// A zero span has no bays and one node - a single column under a drag with no reach.
static ArchDivision Cut( float span, float count ) {
return new ArchDivision {
Span = span,
Count = span < 0.05f ? 0 : Math.Max( 1, (int)count )
};
}
}