Editor/Pillar/ArchPillarHandler.cs
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. The RESOLVED foot, so
// the widget is on the column the author can see rather than on the seat it was placed with.
var lift = picked.Lift( tool.Plan, tool.Kit );
var foot = ArchPillarGen.Seat( pillar, tool.Kit, tool.Plan );
var seat = new Vector3( pillar.Origin.x, pillar.Origin.y, foot + 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;
// A drag that only slid the column across the plan leaves it seeking whatever is under it there; one
// that lifted it off its deck is an authored foot, or the next rebuild would drop it straight back.
pillar.Founded = pillar.Founded || MathF.Abs( travelled.z - seat.z ) > ArchGridService.FinestSize;
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, foot, 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 foot, float lift ) {
var frame = Rotation.FromYaw( pillar.Yaw );
var (min, max) = Local( pillar );
var floor = foot + 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;
pillar.Founded = pillar.Founded || MathF.Abs( pulled.Mins.z - floor ) > ArchGridService.FinestSize;
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 );
}
}