Editor/Carve/ArchCarveVolume.cs

Editor-side immutable value types describing carving volumes and planes used by the level/architecture editor. Defines ArchCarvePlane (a planar height with origin and fall), ArchCarveBreak (ruin/break metadata), and ArchCarveVolume (a footprint with floor/ceiling planes and helper constructors for common volume shapes).

Reflection
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// The fall is what makes a raked deck a carveable solid - not a lofted quad.
public readonly struct ArchCarvePlane
{
	public float Datum { get; init; }
	public Vector2 Origin { get; init; }
	public Vector2 Fall { get; init; }

	public static ArchCarvePlane Level( float height ) => new() { Datum = height };

	public static ArchCarvePlane Through( Vector2 origin, float datum, Vector2 fall )
	{
		return new ArchCarvePlane { Origin = origin, Datum = datum, Fall = fall };
	}

	public bool Rakes => Fall.Length > 0.0001f;

	public float At( Vector2 point ) => Datum + Vector2.Dot( Fall, point - Origin );

	public ArchCarvePlane Raised( float by ) => new() { Datum = Datum + by, Origin = Origin, Fall = Fall };
}

// A ruined edge, carried by the volume that takes the bite. The seed is the cut's own id, so the same shaft
// hands back the same ruin after a hotload and the build cache is not defeated by a vertex that moved.
public readonly struct ArchCarveBreak
{
	public int Seed { get; init; }
	public float Jitter { get; init; }

	public bool Breaks => Seed != 0 && Jitter > ArchGridService.FinestSize;
}

// Both sides of a carve - the solid and the cut - are this same volume.
public readonly struct ArchCarveVolume
{
	public IReadOnlyList<Vector2> Footprint { get; init; }
	public ArchCarvePlane Floor { get; init; }
	public ArchCarvePlane Ceiling { get; init; }
	public ArchCarveBreak Break { get; init; }

	public static ArchCarveVolume Over( IReadOnlyList<Vector2> footprint, float from, float to )
	{
		return new ArchCarveVolume
		{
			Footprint = footprint,
			Floor = ArchCarvePlane.Level( MathF.Min( from, to ) ),
			Ceiling = ArchCarvePlane.Level( MathF.Max( from, to ) )
		};
	}

	public ArchCarveVolume Breaking( ArchCarveBreak breaking )
	{
		return new ArchCarveVolume { Footprint = Footprint, Floor = Floor, Ceiling = Ceiling, Break = breaking };
	}

	// A level base under a raked top - the wedge a ramp is. Not Raked(): that is two parallel planes a constant
	// thickness apart, which is a sloping slab and not something whose foot meets the ground it stands on.
	public static ArchCarveVolume Under( IReadOnlyList<Vector2> footprint, float from, ArchCarvePlane top )
	{
		return new ArchCarveVolume { Footprint = footprint, Floor = ArchCarvePlane.Level( from ), Ceiling = top };
	}

	// The same wedge upside down, and the shape a SUBTRACTED ramp is: nothing taken out at the head, the whole
	// band gone at the foot, so what is left under it keeps the raked floor as its own top.
	public static ArchCarveVolume Above( IReadOnlyList<Vector2> footprint, ArchCarvePlane floor, float to )
	{
		return new ArchCarveVolume { Footprint = footprint, Floor = floor, Ceiling = ArchCarvePlane.Level( to ) };
	}

	// The two faces are parallel, which is what keeps the interval algebra one-dimensional.
	public static ArchCarveVolume Raked( IReadOnlyList<Vector2> footprint, ArchCarvePlane plane, float thickness )
	{
		var depth = MathF.Max( 0.05f, thickness );

		return new ArchCarveVolume { Footprint = footprint, Floor = plane, Ceiling = plane.Raised( depth ) };
	}

	public bool Rakes => Floor.Rakes || Ceiling.Rakes;

	public bool Covers( Vector2 point ) => ArchFootprint.Encloses( new[] { Footprint }, point );
}