Editor/Span/ArchSpanPart.cs

Editor class definitions for arch spans. Defines ArchSpanEnd which stores attachment info for one end of a span, and ArchSpanPart which stores properties for a span between two piers including geometry, visuals and a method to produce a dressing copy.

File Access
using System;
using Sandbox;

namespace Sunless.Architecture;

// One end of a span. It names the part it dies into rather than storing the point, so the span is re-derived from
// where that pier stands NOW - a column dragged across the room brings its arch with it and needs no carry.
public sealed class ArchSpanEnd
{
	// The pillar or wall it lands on. Zero is a bare point, which is what an end drawn away from anything is.
	public int PartId { get; set; }

	// Which column of a grid, so the end follows that column rather than the part's origin.
	public int ColumnX { get; set; }
	public int ColumnY { get; set; }

	// Where the gesture landed. The answer for a wall end and for an unattached one, and the tie-break for a grid.
	public Vector2 At { get; set; }
}

// What bridges two piers: a beam, an arch, or a cove at each end. It is a layer of its own rather than a field on a
// pillar, because two independently placed columns are two parts and an effect stored on one of them would be
// landing on a host it does not own.
public sealed class ArchSpanPart : IArchCollides, IArchPainted, IArchNamed
{
	// Overrides the kit for THIS part alone: one wall that needs its exact holes, one tower that needs none.
	public ArchCollisionMode? Collision { get; set; }

	public int Id { get; set; }
	public string Name { get; set; } = "Span";
	public int Level { get; set; }

	public ArchSpanEnd From { get; set; } = new();
	public ArchSpanEnd To { get; set; } = new();

	public PillarSpan Form { get; set; } = PillarSpan.Arch;

	// Zero takes the piers' own section, so a span drawn between two columns is as thick as they are without
	// anything being typed.
	public float Thickness { get; set; }

	// How far below the piers' head the span's top sits - room for the slab it hangs under.
	public float Drop { get; set; }

	// Where the span STARTS, measured off the room floor. Zero lets the form's own rise decide, which means the
	// band hangs off whatever the piers happen to reach and two spans set the same way spring at two heights.
	// Set, it pins them level and caps the band, which is the only way to author a deliberately thin span.
	public float Springing { get; set; }

	public float BeamDepth { get; set; } = 18f;
	public float Rise { get; set; } = 48f;
	public float Ring { get; set; } = 10f;
	public int Segments { get; set; } = 10;

	public ArchPalette Palette { get; set; } = new();

	// Measured down from the top, by the same formula a pillar's own arcade uses - see ArchPillarPart.SpanBand.
	public float Band => Form switch
	{
		PillarSpan.Arch => MathF.Max( 8f, Rise + MathF.Max( 2f, Ring ) ),
		PillarSpan.Haunch => MathF.Max( 2f, Rise ),
		_ => MathF.Max( 1f, BeamDepth )
	};

	// The dressing alone - what a placement adopts from the seed it was drawn with, and nothing about where it
	// stands. A placement that stamped its gesture on the seed carried that answer into the next one.
	public ArchSpanPart Dressing()
	{
		return new ArchSpanPart
		{
			Form = Form,
			Thickness = Thickness,
			Drop = Drop,
			Springing = Springing,
			BeamDepth = BeamDepth,
			Rise = Rise,
			Ring = Ring,
			Segments = Segments,
			Palette = Palette
		};
	}
}