Editor/Roof/ArchRoofLightGen.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public readonly struct ArchDeckPatch {
	public Vector2 Min { get; init; }
	public Vector2 Max { get; init; }
	public ArchCarvePlane Plane { get; init; }

	public List<Vector2> Footprint() => ArchFootprint.Rect( Min, Max );
}

public static partial class ArchRoofGen {
	public static ArchCarvePlane Facet( ArchRoofPart roof, Vector2 at ) {
		ArchFootprint.Bounds( roof.Outline(), out var min, out var max );

		var gradient = ArchRoofPlane.Slope( roof );
		var eaves = new[]
		{
			(Reach: at.y - min.y, Plane: ArchCarvePlane.Through( new Vector2( 0f, min.y ), roof.BaseHeight, new Vector2( 0f, gradient ) )),
			(Reach: max.y - at.y, Plane: ArchCarvePlane.Through( new Vector2( 0f, max.y ), roof.BaseHeight, new Vector2( 0f, -gradient ) )),
			(Reach: at.x - min.x, Plane: ArchCarvePlane.Through( new Vector2( min.x, 0f ), roof.BaseHeight, new Vector2( gradient, 0f ) )),
			(Reach: max.x - at.x, Plane: ArchCarvePlane.Through( new Vector2( max.x, 0f ), roof.BaseHeight, new Vector2( -gradient, 0f ) ))
		};

		return eaves.OrderBy( eave => eave.Reach ).First().Plane;
	}

	public static List<ArchLitPatch> LitFacets( ArchRoofPart roof ) {
		var lit = new List<ArchLitPatch>();

		foreach ( var light in roof.Lights ) {
			if ( !ArchRoofPlane.Carries( roof, light.Form ) || light.Size.x <= 4f || light.Size.y <= 4f || !ArchLayerGate.On( light ) ) {
				continue;
			}

			lit.Add( new ArchLitPatch {
				Light = light,
				Min = light.Min,
				Max = light.Max,
				OpenMin = new Vector2( 1f, 1f ),
				OpenMax = new Vector2( 1f, 1f ),
				Plane = Facet( roof, (light.Min + light.Max) * 0.5f )
			} );
		}

		return lit;
	}

	public static List<ArchDeckPatch> Patches( ArchRoofPart roof, IReadOnlyList<List<Vector2>> rafters, Vector2 min, Vector2 max ) {
		var patches = new List<ArchDeckPatch>();
		var acrossY = roof.RidgeAlongX;
		var acrossMin = acrossY ? min.y : min.x;
		var acrossMax = acrossY ? max.y : max.x;

		foreach ( var cell in ArchFootprint.Cells( rafters, null ) ) {
			if ( roof.Style != RoofStyle.Gable ) {
				patches.Add( Patch( cell.Min, cell.Max, Plane( roof, min, max ) ) );
				continue;
			}

			var centre = (cell.Min + cell.Max) * 0.5f;
			var (footing, head) = ArchRoofPlane.AcrossSpan( acrossY, rafters, centre, acrossMin, acrossMax );
			var ridge = (footing + head) * 0.5f;

			var low = acrossY ? cell.Min.y : cell.Min.x;
			var high = acrossY ? cell.Max.y : cell.Max.x;

			if ( low < ridge - ArchCarve.Grain && high > ridge + ArchCarve.Grain ) {
				patches.Add( Patch( cell.Min, Across( cell.Max, acrossY, ridge ), Slope( roof, footing, head, true ) ) );
				patches.Add( Patch( Across( cell.Min, acrossY, ridge ), cell.Max, Slope( roof, footing, head, false ) ) );

				continue;
			}

			patches.Add( Patch( cell.Min, cell.Max, Slope( roof, footing, head, high <= ridge + ArchCarve.Grain ) ) );
		}

		return patches;
	}

	public static ArchCarvePlane Plane( ArchRoofPart roof, Vector2 min, Vector2 max ) {
		if ( roof.Style != RoofStyle.Shed ) {
			return ArchCarvePlane.Level( roof.BaseHeight );
		}

		var acrossY = roof.RidgeAlongX;
		var span = MathF.Max( 1f, acrossY ? max.y - min.y : max.x - min.x );
		var rise = ArchRoofPlane.Rise( roof, span );
		var gradient = rise / span * (roof.Reversed ? -1f : 1f);
		var datum = roof.Reversed ? roof.BaseHeight + rise : roof.BaseHeight;

		return ArchCarvePlane.Through( min, datum, acrossY ? new Vector2( 0f, gradient ) : new Vector2( gradient, 0f ) );
	}

	static ArchCarvePlane Slope( ArchRoofPart roof, float acrossMin, float acrossMax, bool low ) {
		var gradient = ArchRoofPlane.Rise( roof, 1f ) * (low ? 1f : -1f);
		var eave = low ? acrossMin : acrossMax;
		var acrossY = roof.RidgeAlongX;

		return ArchCarvePlane.Through(
			acrossY ? new Vector2( 0f, eave ) : new Vector2( eave, 0f ),
			roof.BaseHeight,
			acrossY ? new Vector2( 0f, gradient ) : new Vector2( gradient, 0f ) );
	}

	static ArchDeckPatch Patch( Vector2 min, Vector2 max, ArchCarvePlane plane ) {
		return new ArchDeckPatch { Min = min, Max = max, Plane = plane };
	}

	static Vector2 Across( Vector2 point, bool acrossY, float at ) {
		return acrossY ? new Vector2( point.x, at ) : new Vector2( at, point.y );
	}

	static void Deck(
		ArchMesh canvas,
		ArchRoofPart roof,
		ArchBuilding building,
		List<List<Vector2>> rafters,
		Vector2 deckMin,
		Vector2 deckMax,
		float thickness,
		ArchPlan plan,
		ArchKit kit,
		ArchStyle style,
		ArchPalette[] chain,
		ArchBrush deck ) {
		var patches = Patches( roof, rafters, deckMin, deckMax );
		var standing = Lit( roof, patches );
		var cuts = Piercing( roof, building, plan, kit, patches, thickness );

		if ( !standing.Any( piece => piece.Light.Form.Pierces() ) && cuts.Count == 0 ) {
			Unpierced( canvas, roof, building, rafters, deckMin, deckMax, thickness, kit, deck );
		} else {
			var shape = Pierced( patches, thickness, standing, cuts );
			var reveal = style.Brush( ArchSurface.Reveal, chain );

			foreach ( var face in shape.Faces ) {
				canvas.Polygon( face.Points, face.Side == ArchCarveSide.Jamb ? reveal : deck );
			}
		}

		foreach ( var piece in standing ) {
			using ( canvas.Part( piece.Light.Form.Piece() ) ) {
				Light( canvas, piece, thickness, kit, style, chain );
			}
		}
	}

	static void Unpierced(
		ArchMesh canvas,
		ArchRoofPart roof,
		ArchBuilding building,
		List<List<Vector2>> rafters,
		Vector2 min,
		Vector2 max,
		float thickness,
		ArchKit kit,
		ArchBrush deck ) {
		switch ( roof.Style ) {
			case RoofStyle.Shed:
				Shed( canvas, roof, rafters, min, max, thickness, deck );
				return;

			case RoofStyle.Gable:
				Gable( canvas, roof, building, kit, rafters, min, max, thickness, deck );
				return;

			default:
				ArchFloorGen.Solid( canvas, rafters, null, roof.BaseHeight, roof.BaseHeight + thickness, deck );
				return;
		}
	}

	public readonly struct ArchLitPatch {
		public ArchRoofLightPart Light { get; init; }
		public Vector2 Min { get; init; }
		public Vector2 Max { get; init; }
		public Vector2 OpenMin { get; init; }
		public Vector2 OpenMax { get; init; }
		public ArchCarvePlane Plane { get; init; }

		public List<Vector2> Footprint() => ArchFootprint.Rect( Min, Max );

		public List<Vector2> Grown( float by ) {
			return ArchFootprint.Rect( Min - OpenMin * by, Max + OpenMax * by );
		}
	}

	public static List<ArchLitPatch> Lit( ArchRoofPart roof, IReadOnlyList<ArchDeckPatch> patches ) {
		var lit = new List<ArchLitPatch>();

		foreach ( var light in roof.Lights.Where( entry => entry.Size.x > 4f && entry.Size.y > 4f && ArchLayerGate.On( entry ) ) ) {
			var pieces = new List<(Vector2 Min, Vector2 Max, ArchCarvePlane Plane)>();

			foreach ( var patch in patches ) {
				var min = Vector2.Max( light.Min, patch.Min );
				var max = Vector2.Min( light.Max, patch.Max );

				if ( max.x - min.x < 4f || max.y - min.y < 4f ) {
					continue;
				}

				pieces.Add( (min, max, patch.Plane) );
			}

			if ( pieces.Count == 0 ) {
				Log.Info( $"Architecture: {roof.Name}/{light.Name} falls entirely off the deck, so nothing was cut. Drag it over the roof." );
				continue;
			}

			foreach ( var piece in pieces ) {
				lit.Add( new ArchLitPatch {
					Light = light,
					Min = piece.Min,
					Max = piece.Max,
					OpenMin = Open( piece.Min, piece.Max, pieces, false ),
					OpenMax = Open( piece.Min, piece.Max, pieces, true ),
					Plane = piece.Plane
				} );
			}
		}

		return lit;
	}

	static Vector2 Open( Vector2 min, Vector2 max, IReadOnlyList<(Vector2 Min, Vector2 Max, ArchCarvePlane Plane)> pieces, bool upper ) {
		var open = new Vector2( 1f, 1f );

		foreach ( var other in pieces ) {
			if ( other.Min == min && other.Max == max ) {
				continue;
			}

			if ( Meets( upper ? max.x : min.x, upper ? other.Min.x : other.Max.x, min.y, max.y, other.Min.y, other.Max.y ) ) {
				open.x = 0f;
			}

			if ( Meets( upper ? max.y : min.y, upper ? other.Min.y : other.Max.y, min.x, max.x, other.Min.x, other.Max.x ) ) {
				open.y = 0f;
			}
		}

		return open;
	}

	static bool Meets( float edge, float against, float from, float to, float otherFrom, float otherTo ) {
		return MathF.Abs( edge - against ) < ArchCarve.Grain
			&& MathF.Min( to, otherTo ) - MathF.Max( from, otherFrom ) > ArchCarve.Grain;
	}

	public static ArchCarveShape Pierced(
		IReadOnlyList<ArchDeckPatch> patches,
		float thickness,
		IReadOnlyList<ArchLitPatch> lights,
		IReadOnlyList<ArchCarveVolume> cuts = null ) {
		var carve = new ArchCarve();

		foreach ( var patch in patches ) {
			carve.Plus( ArchCarveVolume.Raked( patch.Footprint(), patch.Plane, thickness ) );
		}

		foreach ( var light in lights.Where( piece => piece.Light.Form.Pierces() ) ) {
			carve.Less( ArchCarveVolume.Raked( light.Footprint(), light.Plane.Raised( -1f ), thickness + 2f ) );
		}

		foreach ( var volume in cuts ?? Array.Empty<ArchCarveVolume>() ) {
			carve.Less( volume );
		}

		return carve.Resolve();
	}

	static List<ArchCarveVolume> Piercing(
		ArchRoofPart roof,
		ArchBuilding building,
		ArchPlan plan,
		ArchKit kit,
		IReadOnlyList<ArchDeckPatch> patches,
		float thickness ) {
		var low = float.MaxValue;
		var high = float.MinValue;

		foreach ( var patch in patches ) {
			foreach ( var corner in patch.Footprint() ) {
				low = MathF.Min( low, patch.Plane.At( corner ) );
				high = MathF.Max( high, patch.Plane.At( corner ) + thickness );
			}
		}

		return Piercing( roof, building, plan, kit, low, high );
	}

	// Cuts are plan-wide — a shaft from a neighbouring building can open this roof
	static List<ArchCarveVolume> Piercing( ArchRoofPart roof, ArchBuilding building, ArchPlan plan, ArchKit kit, float low, float high ) {
		if ( building is null || high <= low ) {
			return new List<ArchCarveVolume>();
		}

		return ArchCut.Volumes( plan, roof.Level, kit, low - ArchLap.Bite( kit ), high, building.Id, ArchCutAffects.Roofs ).ToList();
	}

	public static List<List<Vector3>> Pierced(
		IReadOnlyList<List<Vector3>> faces,
		ArchRoofPart roof,
		ArchBuilding building,
		ArchKit kit,
		float thickness,
		ArchPlan plan,
		IReadOnlyList<ArchLitPatch> standing = null ) {
		var low = float.MaxValue;
		var high = float.MinValue;

		foreach ( var point in faces.SelectMany( face => face ) ) {
			low = MathF.Min( low, point.z );
			high = MathF.Max( high, point.z );
		}

		var cuts = Piercing( roof, building, plan, kit, low, high + thickness ).ToList();

		foreach ( var piece in standing ?? Array.Empty<ArchLitPatch>() ) {
			if ( piece.Light.Form.Pierces() ) {
				cuts.Add( ArchCarveVolume.Raked( piece.Footprint(), piece.Plane.Raised( -1f ), thickness + 2f ) );
			}
		}

		return ArchFaceCarve.Surround( faces, cuts, thickness, ArchLap.Bite( kit ) );
	}

	public readonly struct ArchRoofLightStack {
		public float Upstand { get; init; }
		public float Coping { get; init; }
		public float Head { get; init; }
		public float Standing { get; init; }
		public float Ridge { get; init; }
		public float Top { get; init; }
	}

	public static ArchRoofLightStack Stack( ArchRoofLightPart light, ArchKit kit ) {
		var form = light.Form;
		var rise = MathF.Max( 1f, light.Curb );
		var upstand = form.Kerbed() ? rise : 0f;
		var coping = form.Coped() ? MathF.Max( 1f, kit.WallCapHeight ) : 0f;
		var head = upstand + coping;
		var standing = form.Stands() ? rise : 0f;
		var lid = (form.Glazes() && light.Glazed && !form.Fronted()) || form.Lidded() ? MathF.Max( 1f, kit.FrameDepth ) : 0f;
		var ridge = form.Roofed() ? ArchPitch.Slope( kit.RoofPitch ) * MathF.Max( 1f, MathF.Min( light.Size.x, light.Size.y ) ) * 0.5f : 0f;

		return new ArchRoofLightStack {
			Upstand = upstand,
			Coping = coping,
			Head = head,
			Standing = standing,
			Ridge = ridge,
			Top = head + standing + lid + ridge
		};
	}

	static void Light( ArchMesh canvas, ArchLitPatch piece, float thickness, ArchKit kit, ArchStyle style, ArchPalette[] chain ) {
		var light = piece.Light;
		var form = light.Form;
		var skin = new[] { light.Palette }.Concat( chain ).ToArray();
		var surface = piece.Plane.Raised( thickness );
		var stack = Stack( light, kit );
		var width = MathF.Max( 1f, light.CurbWidth );

		if ( form.Roofed() ) {
			Dormer( canvas, piece, stack, width, thickness, kit, style, skin );

			return;
		}

		if ( form.Kerbed() ) {
			Upstand( canvas, piece, stack, width, surface, kit, style, skin );
		}

		if ( form.Stands() ) {
			Stood( canvas, piece, stack, width, Seat( piece, piece.Grown( width ), thickness ), kit, style, skin );
			return;
		}

		if ( form.Lidded() ) {
			Lid( canvas, piece, width, surface.Raised( stack.Head ), kit, style, skin );
			return;
		}

		if ( form.Glazes() && light.Glazed ) {
			Glazing( canvas, piece, stack, surface, kit, style, skin );
		}
	}

	static void Upstand( ArchMesh canvas, ArchLitPatch piece, ArchRoofLightStack stack, float width, ArchCarvePlane surface, ArchKit kit, ArchStyle style, ArchPalette[] skin ) {
		var bite = ArchLap.Bite( kit );

		// Bitten into the deck so underside clears the roof plane
		Ring(
			canvas,
			piece.Grown( width ),
			piece.Footprint(),
			surface.Raised( -bite ),
			bite + stack.Upstand,
			style.Brush( piece.Light.Form.Clad() ? ArchSurface.WallExterior : ArchSurface.RoofEdge, skin ) );

		if ( stack.Coping <= 0f ) {
			return;
		}

		var oversail = ArchLap.Proud( kit );

		Ring( canvas, piece.Grown( width + oversail ), piece.Grown( -oversail ), surface.Raised( stack.Upstand ),
			stack.Coping, style.Brush( ArchSurface.WallCap, skin ) );
	}

	static void Glazing( ArchMesh canvas, ArchLitPatch piece, ArchRoofLightStack stack, ArchCarvePlane surface, ArchKit kit, ArchStyle style, ArchPalette[] skin ) {
		var face = MathF.Max( 1f, kit.FrameFace );
		var depth = MathF.Max( 1f, kit.FrameDepth );
		var head = surface.Raised( stack.Head );
		var frame = style.Brush( ArchSurface.WindowFrame, skin );

		var glazing = piece.Grown( -face );

		Ring( canvas, piece.Footprint(), glazing, head, depth, frame );
		Slab( canvas, glazing, head, MathF.Max( 0.4f, kit.GlazingBar ), style.Brush( ArchSurface.Glass, skin ) );
		Bars( canvas, glazing, head, piece.Light.PaneSpan, face, depth, frame );
	}

	static void Dormer( ArchMesh canvas, ArchLitPatch piece, ArchRoofLightStack stack, float width, float thickness, ArchKit kit, ArchStyle style, ArchPalette[] skin ) {
		var outer = piece.Grown( width );
		var seat = Seat( piece, outer, thickness );
		var cheek = MathF.Max( 4f, stack.Upstand );
		var bite = ArchLap.Bite( kit );

		// Level cheeks — uphill buries, downhill stands full height
		Ring( canvas, outer, piece.Footprint(), ArchCarvePlane.Level( seat - bite ), bite + cheek,
			style.Brush( ArchSurface.WallExterior, skin ) );

		ArchFootprint.Bounds( outer, out var min, out var max );

		var downhill = Downhill( piece, max - min );
		var alongX = MathF.Abs( downhill.x ) > 0.5f;
		var plate = seat + cheek;
		var head = plate + stack.Ridge;
		var oversail = ArchLap.Proud( kit );
		var lid = MathF.Max( 1f, kit.RoofThickness );
		var deck = style.Brush( ArchSurface.Roof, skin );

		var eaves = ArchFootprint.Rect( min - new Vector2( oversail, oversail ), max + new Vector2( oversail, oversail ) );
		ArchFootprint.Bounds( eaves, out var eaveMin, out var eaveMax );

		var acrossMin = alongX ? eaveMin.y : eaveMin.x;
		var acrossMax = alongX ? eaveMax.y : eaveMax.x;
		var ridge = (acrossMin + acrossMax) * 0.5f;
		var gradient = ridge - acrossMin > 0.5f ? stack.Ridge / (ridge - acrossMin) : 0f;

		foreach ( var low in new[] { true, false } ) {
			var from = low ? acrossMin : ridge;
			var to = low ? ridge : acrossMax;
			var eave = low ? acrossMin : acrossMax;
			var fall = alongX ? new Vector2( 0f, low ? gradient : -gradient ) : new Vector2( low ? gradient : -gradient, 0f );
			var origin = alongX ? new Vector2( eaveMin.x, eave ) : new Vector2( eave, eaveMin.y );

			Slab( canvas,
				alongX
					? ArchFootprint.Rect( new Vector2( eaveMin.x, from ), new Vector2( eaveMax.x, to ) )
					: ArchFootprint.Rect( new Vector2( from, eaveMin.y ), new Vector2( to, eaveMax.y ) ),
				ArchCarvePlane.Through( origin, plate, fall ), lid, deck );
		}

		DormerGables( canvas, min, max, alongX, plate, head, thickness, style.Brush( ArchSurface.WallExterior, skin ) );

		if ( piece.Light.Glazed ) {
			DormerWindow( canvas, min, max, downhill, alongX, seat, plate, kit, style, skin );
		}
	}

	static void DormerGables( ArchMesh canvas, Vector2 min, Vector2 max, bool alongX, float plate, float head, float thickness, ArchBrush brush ) {
		var span = alongX ? new Vector2( min.y, max.y ) : new Vector2( min.x, max.x );
		var mid = (span.x + span.y) * 0.5f;

		foreach ( var end in alongX ? new[] { min.x, max.x } : new[] { min.y, max.y } ) {
			var inward = MathF.Abs( end - (alongX ? min.x : min.y) ) < 0.01f ? thickness : -thickness;

			ArchSlab.Face( canvas, new List<Vector3>
			{
				DormerCorner( alongX, end, span.x, plate ),
				DormerCorner( alongX, end, span.y, plate ),
				DormerCorner( alongX, end, mid, head )
			}, DormerAxis( alongX ) * inward, brush );
		}
	}

	static void DormerWindow( ArchMesh canvas, Vector2 min, Vector2 max, Vector2 downhill, bool alongX, float seat, float plate, ArchKit kit, ArchStyle style, ArchPalette[] skin ) {
		var face = MathF.Max( 1f, kit.FrameFace );
		var depth = MathF.Max( 1f, kit.FrameDepth );
		var sill = seat + face * 2f;
		var lintel = plate - face;

		if ( lintel - sill < face * 2f ) {
			return;
		}

		var outward = alongX ? (downhill.x > 0f ? max.x : min.x) : (downhill.y > 0f ? max.y : min.y);
		var inward = outward + (alongX ? (downhill.x > 0f ? -depth : depth) : (downhill.y > 0f ? -depth : depth));
		var span = alongX ? new Vector2( min.y + face, max.y - face ) : new Vector2( min.x + face, max.x - face );

		if ( span.y - span.x < face * 2f ) {
			return;
		}

		var frame = style.Brush( ArchSurface.WindowFrame, skin );

		DormerBoard( canvas, alongX, outward, inward, new Vector2( span.x, span.x + face ), sill, lintel + face, frame );
		DormerBoard( canvas, alongX, outward, inward, new Vector2( span.y - face, span.y ), sill, lintel + face, frame );
		DormerBoard( canvas, alongX, outward, inward, span, sill, sill + face, frame );
		DormerBoard( canvas, alongX, outward, inward, span, lintel, lintel + face, frame );

		DormerBoard( canvas, alongX, inward, inward + (outward < inward ? -1f : 1f) * MathF.Max( 0.4f, kit.GlazingBar ),
			new Vector2( span.x + face, span.y - face ), sill + face, lintel,
			style.Brush( ArchSurface.Glass, skin ) );
	}

	static void DormerBoard( ArchMesh canvas, bool alongX, float outward, float inward, Vector2 span, float from, float to, ArchBrush brush ) {
		var low = alongX
			? new Vector3( MathF.Min( outward, inward ), span.x, from )
			: new Vector3( span.x, MathF.Min( outward, inward ), from );
		var high = alongX
			? new Vector3( MathF.Max( outward, inward ), span.y, to )
			: new Vector3( span.y, MathF.Max( outward, inward ), to );

		canvas.Box( low, high, brush );
	}

	static Vector2 Downhill( ArchLitPatch piece, Vector2 size ) {
		var fall = piece.Plane.Fall;

		if ( fall.Length > 0.0001f ) {
			return -fall.Normal;
		}

		return size.x >= size.y ? new Vector2( 1f, 0f ) : new Vector2( 0f, 1f );
	}

	static Vector3 DormerCorner( bool alongX, float along, float across, float height ) {
		return alongX ? new Vector3( along, across, height ) : new Vector3( across, along, height );
	}

	static Vector3 DormerAxis( bool alongX ) => alongX ? Vector3.Forward : Vector3.Left;

	static void Lid( ArchMesh canvas, ArchLitPatch piece, float width, ArchCarvePlane head, ArchKit kit, ArchStyle style, ArchPalette[] skin ) {
		var oversail = ArchLap.Proud( kit );

		Slab( canvas, piece.Grown( width + oversail ), head, MathF.Max( 1f, kit.FrameDepth ), style.Brush( ArchSurface.Plant, skin ) );
	}

	static void Stood( ArchMesh canvas, ArchLitPatch piece, ArchRoofLightStack stack, float width, float seat, ArchKit kit, ArchStyle style, ArchPalette[] skin ) {
		var section = piece.Light.Form.Pillar( (piece.Min + piece.Max) * 0.5f, piece.Max - piece.Min, seat, stack.Standing, width, kit );
		var plant = style.Brush( ArchSurface.Plant, skin );

		ArchAsks.StandPillar( canvas, section, plant, plant );
	}

	// Lowest deck corner so the uphill side buries rather than the downhill hangs clear
	static float Seat( ArchLitPatch piece, IReadOnlyList<Vector2> footprint, float thickness ) {
		var lowest = float.MaxValue;

		foreach ( var corner in footprint ) {
			lowest = MathF.Min( lowest, piece.Plane.At( corner ) );
		}

		return lowest + thickness;
	}

	static void Bars( ArchMesh canvas, IReadOnlyList<Vector2> glazing, ArchCarvePlane head, float span, float face, float depth, ArchBrush brush ) {
		if ( span < face * 3f ) {
			return;
		}

		ArchFootprint.Bounds( glazing, out var min, out var max );

		var half = face * 0.5f;

		foreach ( var offset in ArchDivide.AtMost( max.x - min.x, span ).Inner ) {
			var at = min.x + offset;

			Slab( canvas, ArchFootprint.Rect( new Vector2( at - half, min.y ), new Vector2( at + half, max.y ) ), head, depth, brush );
		}

		foreach ( var offset in ArchDivide.AtMost( max.y - min.y, span ).Inner ) {
			var at = min.y + offset;

			Slab( canvas, ArchFootprint.Rect( new Vector2( min.x, at - half ), new Vector2( max.x, at + half ) ), head, depth, brush );
		}
	}

	static void Ring( ArchMesh canvas, IReadOnlyList<Vector2> outer, IReadOnlyList<Vector2> inner, ArchCarvePlane floor, float thickness, ArchBrush brush ) {
		var shape = new ArchCarve()
			.Plus( ArchCarveVolume.Raked( outer, floor, thickness ) )
			.Less( ArchCarveVolume.Raked( inner, floor.Raised( -1f ), thickness + 2f ) )
			.Resolve();

		Paint( canvas, shape, brush );
	}

	static void Slab( ArchMesh canvas, IReadOnlyList<Vector2> footprint, ArchCarvePlane floor, float thickness, ArchBrush brush ) {
		Paint( canvas, new ArchCarve().Plus( ArchCarveVolume.Raked( footprint, floor, thickness ) ).Resolve(), brush );
	}

	static void Paint( ArchMesh canvas, ArchCarveShape shape, ArchBrush brush ) {
		using var welding = canvas.Welding();

		foreach ( var face in shape.Faces ) {
			canvas.Polygon( face.Points, brush );
		}
	}
}