Editor/Carve/ArchCutGen.cs

Editor utility for generating the positive geometry of a carved architectural cut. It builds enclosure walls, head (housing, coping or lid) and roof/lid geometry using provided mesh canvas, palette, kit and style, delegating to ArchCarveRing, ArchRun, ArchFloorGen and band/brush helpers.

File Access
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// The positive half: hole derived every rebuild, this builds the shaft walls and the housing.
public static class ArchCutGen
{
	// Takes the HOST's palette, not the host: a chamber cut off a bore is skinned by the road that holds it, and
	// a shaft by its building, through one call.
	public static void Build( ArchMesh canvas, ArchCutPart cut, ArchPalette owner, ArchKit kit, ArchStyle style )
	{
		if ( cut is not { HasContent: true, Builds: true } )
		{
			return;
		}

		var chain = new[] { cut.Palette, owner ?? new ArchPalette() };

		if ( cut.Enclosure == CutEnclosure.Walls )
		{
			using ( canvas.Part( ArchPieces.Enclosure ) )
			{
				Enclosure( canvas, cut, style.Brush( ArchSurface.WallExterior, chain ) );
			}
		}

		if ( cut.Head != CutHead.None )
		{
			using ( canvas.Part( ArchPieces.Capping ) )
			{
				Head( canvas, cut, kit, style, chain );
			}
		}
	}

	// Per leg, not the chain - the legs climb, so the shaft is a stack following the stair.
	static void Enclosure( ArchMesh canvas, ArchCutPart cut, ArchBrush brush )
	{
		var thickness = MathF.Max( 1f, cut.WallThickness );

		foreach ( var leg in cut.Legs() )
		{
			ArchCarveRing.Walls( canvas, leg.Outline(), thickness, leg.BaseHeight, leg.TopHeight, brush );
		}
	}

	// Last leg's footprint (it reached the roof), standing on that leg's head, not a typed height.
	static void Head( ArchMesh canvas, ArchCutPart cut, ArchKit kit, ArchStyle style, ArchPalette[] chain )
	{
		if ( cut.Legs().LastOrDefault() is not { } top )
		{
			return;
		}

		var thickness = MathF.Max( 1f, cut.WallThickness );
		var outline = top.Outline();
		var foot = top.TopHeight;

		if ( cut.Head == CutHead.Housing )
		{
			var head = foot + MathF.Max( 12f, cut.HeadHeight );

			ArchCarveRing.Walls( canvas, outline, thickness, foot, head, style.Brush( ArchSurface.WallExterior, chain ) );

			if ( cut.HeadRoof )
			{
				Lid( canvas, outline, thickness, head, MathF.Max( 1f, kit.FloorThickness ), style.Brush( ArchSurface.Roof, chain ) );
			}

			return;
		}

		if ( cut.Head == CutHead.Coping )
		{
			Coping( canvas, cut, top, thickness, style.Brush( ArchSurface.WallCap, chain ), kit );

			return;
		}

		Lid( canvas, outline, thickness, foot, MathF.Max( 1f, kit.FloorThickness ), style.Brush( ArchSurface.Roof, chain ) );
	}

	// The mouth's own band, walked round the leg's loop rather than round a grown one, so it stands over the
	// enclosure wall and oversails INTO the hole. Same section a platform's outside edge wears, bitten into the
	// head it stands on for the same reason: sitting on it puts three faces on every edge.
	static void Coping( ArchMesh canvas, ArchCutPart cut, ArchCutSegment top, float thickness, ArchBrush brush, ArchKit kit )
	{
		var head = top.TopHeight;

		var band = ArchBandSection.Between(
			-MathF.Max( 0f, cut.CopingOversail ),
			thickness,
			head - ArchContact.Bite( kit ),
			head + MathF.Max( 1f, cut.CopingHeight ) );

		ArchRun.Around( ArchFootprint.Wind( top.Outline() ) ).Level( head ).Emit( canvas, band, brush );
	}


	static void Lid( ArchMesh canvas, IReadOnlyList<Vector2> outline, float thickness, float bottom, float depth, ArchBrush brush )
	{
		var grown = ArchFootprint.Grow( new List<List<Vector2>> { outline.ToList() }, thickness ).First();

		ArchFloorGen.Slab( canvas, grown, null, bottom, bottom + depth, brush );
	}
}