Editor/Designers/ArchOpeningStage.cs

Static helper for staging and describing an architectural opening preset. It builds a simple mock building/room/wall with a centered opening for preview, computes an interest bounding box, generates a cache identity string from preset properties, and returns a short textual description.

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

namespace Sunless.Architecture;

// One staging of an opening preset, shared by the designer's turntable and the browser's card art - two
// stagings of the same preset are free to disagree about what the drag will actually build.
public static class ArchOpeningStage
{
	public static ArchStaged Of( ArchOpeningPreset unit, ArchKit kit )
	{
		var span = MathF.Max( 192f, unit.Width + 96f );
		var wallHeight = MathF.Max( 128f, unit.SillHeight + unit.Height + 32f );

		var wall = new ArchWall
		{
			Id = 2,
			Start = Vector2.Zero,
			End = new Vector2( span, 0f ),
			Height = wallHeight,
			Thickness = kit.WallThickness,
			Baseboard = false,
			Cap = true,
			Openings = new List<ArchOpening> { unit.Spawn( 3, span * 0.5f ) }
		};

		var room = new ArchRoom
		{
			Id = 1,
			WallHeight = wallHeight,
			HasFloor = false,
			RaisedFoundation = false,
			Walls = new List<ArchWall> { wall }
		};

		var building = new ArchBuilding
		{
			Id = 1,
			Rooms = new List<ArchRoom> { room },
			StoreyHeight = wallHeight
		};

		return new ArchStaged(
			new ArchPlan { Units = new List<ArchUnit> { building } },
			new Vector3( span * 0.5f, 0f, wallHeight * 0.5f ),
			MathF.Max( 260f, MathF.Max( span, wallHeight ) * 1.65f ) );
	}

	// Where the unit stands in the staging, generously enough to hold its casing and sill but nowhere near
	// the ends of the run, whose cut faces wear the Reveal role too.
	public static BBox Interest( ArchOpeningPreset unit, ArchKit kit )
	{
		var span = MathF.Max( 192f, unit.Width + 96f );
		var casing = MathF.Max( unit.CasingWidth > 0f ? unit.CasingWidth : kit.CasingWidth, 8f );
		var margin = casing + 12f;
		var thickness = MathF.Max( kit.WallThickness, 16f );

		return new BBox(
			new Vector3( span * 0.5f - unit.Width * 0.5f - margin, -thickness, unit.SillHeight - margin ),
			new Vector3( span * 0.5f + unit.Width * 0.5f + margin, thickness, unit.SillHeight + unit.Height + margin ) );
	}

	// The preview cache key: everything the geometry is derived from, so an edited preset re-stages and
	// an untouched one never does.
	public static string Identity( ArchOpeningPreset unit )
	{
		return string.Join( ".",
			"opening",
			unit.Name,
			unit.Kind,
			$"{unit.Width:0.##}x{unit.Height:0.##}",
			$"{unit.SillHeight:0.##}",
			$"{unit.CasingWidth:0.##}",
			unit.Cased,
			unit.HasSill,
			unit.Leaf,
			unit.Glazed,
			unit.AutoLayout,
			unit.Lights,
			unit.Sashes,
			$"{unit.PanesAcross}x{unit.PanesDown}",
			unit.Glass,
			unit.Breakable,
			unit.Furniture );
	}

	public static string Describe( ArchOpeningPreset unit )
	{
		return unit.IsGlazed
			? $"{unit.Width:0}x{unit.Height:0} · {unit.PanesAcross * unit.PanesDown} panes"
			: $"{unit.Width:0}x{unit.Height:0}";
	}
}