Editor/Tool/Subtools/ArchOpeningPlacingSubtool.cs
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 };
// Picking either hole tool starts its own sequence over - Scope names the concrete type, so the two never
// share a cursor.
public override void OnEnabled() {
base.OnEnabled();
ArchWorkflow.Restart( Scope( "flow" ) );
}
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}" );
}
}