Editor/Layers/ArchLayerShape.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// A layer's shape — children can inherit the outline (e.g. a trim inside a circular cut follows the circle).
public static class ArchLayerShape {
public static IReadOnlyList<List<Vector2>> Loops( ArchPlan plan, int itemId ) {
var kinds = ArchKinds.Load();
return Loops( Find( plan, itemId ), kinds );
}
public static float HeightOf( ArchPlan plan, int itemId, float fallback ) {
var kinds = ArchKinds.Load();
return Height( Find( plan, itemId ), fallback, kinds );
}
public static IReadOnlyList<List<Vector2>> Loops( object payload ) {
return Loops( payload, ArchKinds.Load() );
}
// Fewer than three corners is not a shape anything can follow, and the manifests disagree about saying so -
// beams, roofs, rooms and porches refuse one themselves, cuts, platforms and trims hand it back - so the
// refusal is stated once here rather than trusted to each of them.
public static IReadOnlyList<List<Vector2>> Loops( object payload, ArchKinds kinds ) {
var loops = kinds.Claiming( payload )?.Loops( payload ) ?? Array.Empty<List<Vector2>>();
return loops.Where( loop => loop.Count >= 3 ).ToList();
}
// The height a follower sits at, so it lands on its host rather than on the storey's work plane.
public static float Height( object payload, float fallback ) {
return Height( payload, fallback, ArchKinds.Load() );
}
public static float Height( object payload, float fallback, ArchKinds kinds ) {
return kinds.Claiming( payload )?.Height( payload, fallback ) ?? fallback;
}
// One lookup for "which authored part is this id", so a follower and its host never resolve the same id two
// different ways. Named outright rather than walked through the store: these are the plan document's own types
// and they stay in core, and the store's walk reaches a part only through the host that files it - which a
// bool standing on a bare building does not yet have.
public static object Find( ArchPlan plan, int itemId ) {
if ( itemId == 0 || plan is null ) {
return null;
}
if ( plan.FindCut( itemId ) is { } cut ) {
return cut;
}
if ( plan.AllRooms().FirstOrDefault( room => room.Id == itemId ) is { } room ) {
return room;
}
if ( plan.Buildings.SelectMany( building => building.Platforms ).FirstOrDefault( platform => platform.Id == itemId ) is { } platform ) {
return platform;
}
if ( plan.FindBeam( itemId ) is { } beam ) {
return beam;
}
if ( plan.Buildings.SelectMany( building => building.Roofs ).FirstOrDefault( roof => roof.Id == itemId ) is { } roof ) {
return roof;
}
if ( plan.AllRooms().SelectMany( entry => entry.Porches ).FirstOrDefault( porch => porch.Id == itemId ) is { } porch ) {
return porch;
}
return plan.AllRooms()
.SelectMany( entry => entry.Trims.Concat( entry.Porches.SelectMany( standing => standing.Trims ) ) )
.FirstOrDefault( trim => trim.Id == itemId );
}
}