Editor subtool for placing architectural openings (doors/windows). It handles previewing a ghosted opening on a wall by ray-picking, computes shape/position from two points, shows a label, and spawns/configures the real opening into the wall when placed.
namespace Sunless.Architecture;
// The one placement flow both hole-authoring tools share: ray-pick the wall, preview the framed unit, spawn it
// into the wall's own list. What differs is only the preset family, the ghost's label and what the placed unit
// wears - two copies of this flow drifted apart once already.
public abstract class ArchOpeningPlacingSubtool( ArchTool owner ) : ArchSubtool( owner )
{
protected readonly ArchOpeningPlacement placement = new();
protected override ArchKind? DraftKind => ArchKind.Opening;
// Placement ray-picks the wall plane rather than the work plane, so an elevation is where a unit is most
// naturally dragged. Left at the plan-only default the sidebar refused the view the tool actually works in
// and dropped the preset browser with it.
public override ArchViewAxis[] Works => new[] { ArchViewAxis.Top, ArchViewAxis.Front, ArchViewAxis.Side };
protected abstract ArchOpeningPreset Preset();
protected abstract ArchOpeningPreset Shape( ArchOpeningPreset preset );
protected virtual string Label( ArchOpeningPreset preset, ArchOpeningPreset unit ) => preset.Name;
protected virtual void Adorn( ArchOpening opening ) { }
public override void OnUpdate()
{
placement.Update( Owner, Manager?.IsCurrentViewFocused == true, Preview, Place );
}
void Preview( ArchOpeningPoint from, ArchOpeningPoint to, bool resized )
{
var preset = Preset();
if ( preset is null || from.Wall != to.Wall )
{
return;
}
var unit = Shape( preset );
var shape = ArchOpeningPlacement.Shape( from, to, unit, Owner.Grid, Owner.Kit, placement.Drag );
var thickness = from.Wall.Thickness > 0f ? from.Wall.Thickness : Owner.Kit.WallThickness;
var half = shape.Width * 0.5f;
var lift = from.Building is null ? 0f : ArchAsks.Lift( Owner.Plan, from.Building, Owner.Kit );
var baseHeight = from.Room.BaseHeight + lift;
ArchGhost.Opening(
from.Wall.PointAt( shape.Offset - half ), from.Wall.PointAt( shape.Offset + half ),
baseHeight, shape.SillHeight, shape.Height, thickness );
var centre = from.Wall.PointAt( shape.Offset );
var label = resized
? $"{Label( preset, unit )} {shape.Width:0} wide {shape.SillHeight:0} to {shape.SillHeight + shape.Height:0}"
: Label( preset, unit );
ArchGhost.Note( new Vector3( centre.x, centre.y, baseHeight + shape.SillHeight + shape.Height + 6f ), label );
}
void Place( ArchOpeningPoint from, ArchOpeningPoint to, bool resized )
{
var preset = Preset();
if ( preset is null || from.Wall != to.Wall )
{
return;
}
var unit = Shape( preset );
var shape = ArchOpeningPlacement.Shape( from, to, unit, Owner.Grid, Owner.Kit, placement.Drag );
var opening = unit.Spawn( Owner.Plan.AllocateId(), shape.Offset );
opening.Width = shape.Width;
opening.Height = shape.Height;
opening.SillHeight = shape.SillHeight;
Adorn( opening );
from.Wall.Openings.Add( opening );
FinishDraft( opening.Id );
Owner.Commit( $"Place {opening.Preset}" );
}
}