Editor-side types describing a carved architectural shape. Defines enum for face sides, ArchCarveFace holding four corner points and derived accessors, ArchCarveCell representing a grid cell with bounds and end planes, and ArchCarveShape collecting faces and cells with helpers to query sides and compute outline loops.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// One question, one name: a stepped cut's Sills are its treads, its Jambs its risers.
public enum ArchCarveSide
{
Face,
Top,
Bottom,
Jamb,
Sill,
Head
}
// Wound as ArchMesh.Box winds the same side - only an angled clip makes a cap an n-gon.
public sealed class ArchCarveFace
{
public List<Vector3> Points { get; init; }
public ArchCarveSide Side { get; init; }
public Vector3 A => Points[0];
public Vector3 B => Points[1];
public Vector3 C => Points[2];
public Vector3 D => Points[3];
public float Lowest => Points.Min( point => point.z );
}
public sealed class ArchCarveCell
{
public Vector2 Min { get; init; }
public Vector2 Max { get; init; }
public Vector2 Centre { get; init; }
public IReadOnlyList<Vector2> Loop { get; init; }
// Band at the cell's centre, faces per corner - on parallel planes the two agree exactly.
public float From { get; init; }
public float To { get; init; }
// The surfaces that put those two ends where they are, which is NOT the same as the solid's own floor and
// ceiling: a bite out of the middle leaves ends belonging to the cut, and a raked bite leaves raked ends.
// Each holds the invariant that evaluating it at Centre gives back the scalar beside it.
public ArchCarvePlane Foot { get; init; }
public ArchCarvePlane Head { get; init; }
public bool Rakes => Foot.Rakes || Head.Rakes;
}
public sealed class ArchCarveShape
{
// A seam against a solid neighbour appears in NOBODY's list - that is what keeps the result manifold.
public List<ArchCarveFace> Faces { get; } = new();
public List<ArchCarveCell> Cells { get; } = new();
internal ArchCarveGrid Grid { get; set; }
internal float Frame { get; set; }
public IEnumerable<ArchCarveFace> Reveals => Faces.Where( face => face.Side is ArchCarveSide.Jamb or ArchCarveSide.Sill or ArchCarveSide.Head );
public IEnumerable<ArchCarveFace> Sides( ArchCarveSide side ) => Faces.Where( face => face.Side == side );
// Walked off the arrangement, never off the pieces' bounding rects - a trapezoid has none worth squaring off.
public List<List<Vector2>> Outline( float height )
{
if ( Grid is null )
{
return new List<List<Vector2>>();
}
return ArchCarveCells.Boundary( Grid.Pieces, height )
.Select( loop => loop.Select( point => ArchCarveFrame.Turn( point, Frame ) ).ToList() )
.ToList();
}
}