Editor utility for roof lights in the architecture editor. It finds roof parts under or over a point, enumerates roof sections for a plan/level, tests whether a point lies in a roof outline, and creates a new ArchRoofLightPart with basic validation and initialization.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// Deliberately permissive: the module that lofts the deck does the clipping, so a ridge drag becomes a light on
// each slope. Roof plant - a chimney, a vent stack, a tank, a hatch - is placed through here too, because it is the same
// rectangle dragged on the same deck; what each form builds is ArchRoofLightKinds' answer, not a second path.
public static class ArchRoofLight
{
public const float MinSize = 12f;
// Every building on the storey, never the active one - its placeholder lookup reports nothing there. The FORM
// decides which sections count: ArchRoofPlane.Carries is the one answer, and a dormer is carried by a hip where
// a light is not.
public static ArchRoofPart Under( ArchPlan plan, int level, Vector2 point, RoofLightForm form = RoofLightForm.Kerbed )
{
return Under( plan?.Buildings, level, point, form );
}
// Handed the buildings, because a recipe stands a monitor on a building not yet filed in the plan.
public static ArchRoofPart Under( IEnumerable<ArchBuilding> buildings, int level, Vector2 point, RoofLightForm form = RoofLightForm.Kerbed )
{
return Sections( buildings, level ).FirstOrDefault( roof => ArchRoofPlane.Carries( roof, form ) && Holds( roof, point ) );
}
// Planar or not, so a refusal can name the style it found.
public static ArchRoofPart Over( ArchPlan plan, int level, Vector2 point )
{
return Sections( plan?.Buildings, level ).FirstOrDefault( roof => Holds( roof, point ) );
}
public static IEnumerable<ArchRoofPart> Sections( ArchPlan plan, int level ) => Sections( plan?.Buildings, level );
static IEnumerable<ArchRoofPart> Sections( IEnumerable<ArchBuilding> buildings, int level )
{
if ( buildings is null )
{
return Enumerable.Empty<ArchRoofPart>();
}
var kinds = ArchKinds.Load();
return buildings
.SelectMany( building => ArchPlanStore.FiledOn( ArchKind.Roof, building, kinds ) )
.OfType<ArchRoofPart>()
.Where( roof => roof.Level == level );
}
static bool Holds( ArchRoofPart roof, Vector2 point )
{
return ArchFootprint.Encloses( new[] { (IReadOnlyList<Vector2>)roof.Outline() }, point );
}
// No clamping: Lit clips to covered patches, so half-on stays half-on.
public static ArchRoofLightPart Add( ArchPlan plan, ArchRoofPart roof, Vector2 from, Vector2 to, ArchRoofLightPart draft )
{
if ( roof is null )
{
return null;
}
var min = new Vector2( MathF.Min( from.x, to.x ), MathF.Min( from.y, to.y ) );
var max = new Vector2( MathF.Max( from.x, to.x ), MathF.Max( from.y, to.y ) );
if ( max.x - min.x < MinSize || max.y - min.y < MinSize )
{
return null;
}
var light = new ArchRoofLightPart
{
Id = plan.AllocateId(),
Name = $"{draft.Form.Tag()}{roof.Lights.Count + 1}",
Form = draft.Form,
Min = min,
Max = max,
Curb = draft.Curb,
CurbWidth = draft.CurbWidth,
Glazed = draft.Glazed,
PaneSpan = draft.PaneSpan
};
roof.Lights.Add( light );
return light;
}
}