Editor handler for pillar parts in the architecture editor. It handles picking, moving, rotating and resizing pillar collections by responding to gestures and updating pillar properties like origin, yaw, counts, spacing, width, depth and heights.
namespace Sunless.Architecture;
public sealed class ArchPillarHandler : IArchHandler
{
public ArchKind Kind => ArchKind.Pillar;
public bool Draw( ArchTool tool, ArchSelection picked )
{
if ( picked.Item is not ArchPillarPart pillar )
{
return false;
}
var gesture = ArchGesture.On( ArchKind.Pillar, pillar.Id );
// The part is authored on the flat plan and poured onto the building's grade, so a widget drawn at the
// authored height stands a foundation below the column it is meant to be holding.
var lift = picked.Lift( tool.Plan, tool.Kit );
var seat = new Vector3( pillar.Origin.x, pillar.Origin.y, pillar.BaseHeight + lift );
if ( ArchShapeHandles.Position( tool, gesture.At( "move" ), seat, out var travelled ) )
{
pillar.Origin = new Vector2( travelled.x, travelled.y );
pillar.BaseHeight = travelled.z - lift;
return true;
}
if ( ArchShapeHandles.Turned( gesture.At( "turn" ), seat, out var spun ) )
{
pillar.Yaw = ArchShapeHandles.Facing( pillar.Yaw + spun );
return true;
}
return Pulled( tool, pillar, gesture, lift );
}
// The section, the grid's reach and the rise in ONE gesture: the six faces of the box the part occupies.
// A lone column takes a pulled face as its section and a grid takes it as reach re-divided at the bay it
// already stands, so the box always means the same thing - the bounds of what is there.
static bool Pulled( ArchTool tool, ArchPillarPart pillar, ArchGesture gesture, float lift )
{
var frame = Rotation.FromYaw( pillar.Yaw );
var (min, max) = Local( pillar );
var floor = pillar.BaseHeight + lift;
var head = floor + ArchPillarGen.Height( pillar, tool.Plan.RoomStanding( pillar ), tool.Kit, tool.Plan );
if ( !ArchShapeHandles.Volume( tool, gesture.At( "volume" ), min, max, floor, head, out var pulled, frame ) )
{
return false;
}
var alongX = Divided( tool.Grid, pulled.Mins.x, pulled.Maxs.x, pillar.CountX, pillar.Spacing.x, pillar.Width );
var alongY = Divided( tool.Grid, pulled.Mins.y, pulled.Maxs.y, pillar.CountY, pillar.Spacing.y, pillar.Depth );
var origin = frame * new Vector3( alongX.Origin, alongY.Origin, 0f );
// A height already authored keeps being authored; one still reaching for its soffit is only pinned by a
// drag that moved the head, or pulling a side would quietly stop the column tracking what covers it.
var pinned = pillar.Height > 0f || MathF.Abs( pulled.Maxs.z - head ) > ArchGridService.FinestSize;
pillar.Origin = new Vector2( origin.x, origin.y );
pillar.Width = alongX.Section;
pillar.Depth = alongY.Section;
pillar.CountX = alongX.Count;
pillar.CountY = alongY.Count;
pillar.Spacing = new Vector2( alongX.Spacing, alongY.Spacing );
pillar.BaseHeight = pulled.Mins.z - lift;
if ( pinned )
{
pillar.Height = MathF.Max( 8f, pulled.Maxs.z - pulled.Mins.z );
}
return true;
}
// The part's bounds with the yaw taken out of them - the columns march on the grid's own axes whichever way
// the part is turned to face, and a signed bay runs the grid back towards its origin.
static (Vector2 Min, Vector2 Max) Local( ArchPillarPart pillar )
{
var turned = Rotation.FromYaw( -pillar.Yaw ) * new Vector3( pillar.Origin.x, pillar.Origin.y, 0f );
var seat = new Vector2( turned.x, turned.y );
var far = seat + new Vector2(
(Math.Max( 1, pillar.CountX ) - 1) * pillar.Spacing.x,
(Math.Max( 1, pillar.CountY ) - 1) * pillar.Spacing.y );
var half = pillar.Half;
return (Vector2.Min( seat, far ) - half, Vector2.Max( seat, far ) + half);
}
readonly record struct Extent( float Origin, int Count, float Spacing, float Section );
// One column has no bays to divide, so its whole span IS its section. More than one keeps the section it was
// dressed with and spends the rest of the span on bays, which is what makes a colonnade grow by columns
// rather than by stretching the ones it has.
static Extent Divided( ArchGridService grid, float low, float high, int count, float spacing, float section )
{
var span = MathF.Max( ArchGridService.FinestSize, high - low );
if ( count <= 1 )
{
return new Extent( (low + high) * 0.5f, 1, spacing, span );
}
var kept = MathF.Min( section, span );
var bays = grid.DivideAtMost( span - kept, MathF.Max( 32f, MathF.Abs( spacing ) ) );
return new Extent( low + kept * 0.5f, bays.Count + 1, bays.Step, kept );
}
}