Editor/Wall/ArchWallPiers.cs
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// The ONE resolve of a wall's column line, as wall MODIFIERS: a pier, a quoin stack and an extrude zone are then
// one solid built by one emitter, and one list. The generator emits it, the band courses return over it, and the
// storey ring steps its path out over it - so a pier can never be proud to one of them and invisible to another.
public static class ArchWallPiers {
	public static List<ArchWallModShape> Shapes( ArchWall wall, ArchWallJoin join, ArchKit kit, float height, float thickness, float soffit ) {
		var shapes = new List<ArchWallModShape>();
		var run = wall.Pilasters;

		if ( run is not { Stands: true } ) {
			return shapes;
		}

		var half = thickness * 0.5f;
		// A wrap landing exactly on the neighbour's proud face is a coplanar pair; a bite inside it is hidden.
		var bite = ArchLap.Bite( kit );
		var reach = MathF.Max( half, half + run.Depth - bite );
		var spread = half > 0.01f ? reach / half : 1f;

		// OnCorners widens the run the strips lie INSIDE out to the neighbour's proud face, never centres one on it.
		var from = run.OnCorners ? join.StartExtend * spread : 0f;
		var to = run.OnCorners ? join.Length + join.EndExtendCut * spread : join.Length;
		var foot = run.Foot - (run.Rise > 0.5f ? bite : 0f);
		var climb = run.Rise > 0.5f ? foot + run.Rise : height - MathF.Max( 0f, run.Headroom );
		var head = run.Rise > 0.5f ? climb : MathF.Min( climb, soffit );

		if ( head - foot < 1f || to - from < 1f ) {
			return shapes;
		}

		var strip = MathF.Min( run.Width, to - from );
		var plinth = MathF.Max( 0f, run.Plinth );
		var cap = MathF.Max( 0f, run.Cap );
		var oversail = MathF.Max( ArchLap.Proud( kit ), MathF.Min( 3f, run.Depth * 0.35f ) );

		foreach ( var centre in ArchWallSection.PilasterCentres( run, from, to ) ) {
			if ( run.Course > 0.5f ) {
				var backingHead = MathF.Max( head, height - bite );

				Add( shapes, Backing( run, centre, strip, foot, backingHead, kit ), height, thickness, kit );
			}
			// Each block seats a bite INTO the one under it, or the shaft lands on the plinth's top face and the
			// cap on the shaft's, which is a coplanar pair per pier.
			Add( shapes, Belt( run, centre, strip, oversail, foot, foot + plinth ), height, thickness, kit );
			Add( shapes, Shaft( run, centre, strip, foot + plinth - (plinth > 0.5f ? bite : 0f), head - cap ), height, thickness, kit );
			Add( shapes, Belt( run, centre, strip, oversail, head - cap - (cap > 0.5f ? bite : 0f), head ), height, thickness, kit );
		}

		return shapes;
	}

	static ArchWallModPart Backing( ArchPilasterRun run, float centre, float strip, float bottom, float top, ArchKit kit ) {
		var recess = MathF.Max( ArchLap.Proud( kit ), run.Joint > 0.01f ? run.Joint : run.Depth * 0.2f );

		return new ArchWallModPart {
			Name = "Pier",
			Kind = ArchWallMod.Pilaster,
			Along = centre,
			Width = strip,
			Base = bottom,
			Height = top - bottom,
			Depth = run.Course > 0.5f ? MathF.Max( ArchLap.Proud( kit ), run.Depth - recess ) : run.Depth,
			Field = Field( run )
		};
	}

	// The shaft, through the SAME block stack a quoin resolves to, so a course pitch rusticates it with no second
	// emitter and an authored rise climbs past the storey head exactly as an authored modifier's does.
	static ArchWallModPart Shaft( ArchPilasterRun run, float centre, float strip, float bottom, float top ) {
		return new ArchWallModPart {
			Name = "Pier",
			Kind = run.Course > 0.5f ? ArchWallMod.Course : ArchWallMod.Pilaster,
			Along = centre,
			Width = strip,
			Base = bottom,
			Height = top - bottom,
			Depth = run.Depth,
			Course = run.Course,
			Joint = run.Joint,
			Field = Field( run )
		};
	}

	// The plinth under a pier and the cap over it: one block oversailing the shaft it belts by the same on every side.
	static ArchWallModPart Belt( ArchPilasterRun run, float centre, float strip, float oversail, float bottom, float top ) {
		return new ArchWallModPart {
			Name = "Pier",
			Kind = ArchWallMod.Pilaster,
			Along = centre,
			Width = strip + oversail * 2f,
			Base = bottom,
			Height = top - bottom,
			Depth = run.Depth + oversail,
			Field = Field( run )
		};
	}

	static ArchSurface Field( ArchPilasterRun run ) {
		return run.Course > 0.5f ? ArchSurface.WallBase : ArchSurface.Pillar;
	}

	static void Add( List<ArchWallModShape> shapes, ArchWallModPart part, float height, float thickness, ArchKit kit ) {
		if ( part.Height < 0.5f ) {
			return;
		}

		var shape = ArchWallModShape.Resolve( part, part.Left, part.Right, height, thickness, kit );

		if ( shape.Stands ) {
			shapes.Add( shape );
		}
	}
}