Editor/Output/ArchMesh.Parts.cs

Part of the editor-side geometry output. Defines ArchMeshPiece (a named sub-canvas) and a partial ArchMesh that manages named pieces, splitting behavior, opening a piece scope, and tracking whether the mesh emitted geometry.

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

namespace Sunless.Architecture;

// A named piece of what one layer built - a fascia, a balustrade, a gutter channel. The generator says what it
// is emitting and the output service files each one as its own object, so a piece can be hidden, collided and
// argued about apart from the part that owns it.
public sealed class ArchMeshPiece
{
	public string Name { get; init; }
	public ArchMesh Canvas { get; init; }
}

public sealed partial class ArchMesh
{
	readonly List<ArchMeshPiece> pieces = new();

	// Only a canvas the output service made splits. A generator driven straight from a test or a measurement
	// wants ONE mesh out the other end, and every Part scope on it has to be the no-op it reads as.
	bool splits;

	ArchMesh open;

	public IReadOnlyList<ArchMeshPiece> Pieces => pieces;

	public ArchMesh Splitting()
	{
		splits = true;

		return this;
	}

	// Everything emitted inside the scope belongs to a named piece of this part, and a name reopened adds to the
	// piece already standing - a generator walks its regions and comes back to the same fitting on each one.
	public IDisposable Part( string name )
	{
		if ( open is { } inner )
		{
			return inner.Part( name );
		}

		if ( !splits || string.IsNullOrEmpty( name ) )
		{
			return null;
		}

		open = Piece( name );

		return new PartScope( this );
	}

	ArchMesh Piece( string name )
	{
		if ( pieces.FirstOrDefault( piece => piece.Name == name ) is { } held )
		{
			return held.Canvas;
		}

		var canvas = new ArchMesh( projection ) { splits = true };

		// A piece welds the way the part it came out of does, or a board split onto its own canvas grows the
		// doubled corners the part was welded to avoid.
		if ( welds is not null )
		{
			canvas.Welded();
		}

		pieces.Add( new ArchMeshPiece { Name = name, Canvas = canvas } );

		return canvas;
	}

	readonly struct PartScope( ArchMesh canvas ) : IDisposable
	{
		public void Dispose() => canvas.open = null;
	}

	// Whether anything at all came out of this canvas, its named pieces included: a part whose every face went
	// into a piece is empty itself, and the node it stands on still has to be placed.
	public bool Emitted => !IsEmpty || pieces.Any( piece => piece.Canvas.Emitted );
}