{"TotalCount":598,"Files":[{"Ident":"sunless.lib_architecture","Path":"Editor/Tool/ArchSubtool.cs","FileName":"ArchSubtool.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Linq;\nusing Editor;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\npublic abstract class ArchSubtool : EditorTool\n{\n\tToolSidebarWidget sidebar;\n\n\tprotected ArchSubtool( ArchTool owner )\n\t{\n\t\tOwner = owner;\n\t}\n\n\tprotected ArchTool Owner { get; }\n\n\tstatic readonly ArchViewAxis[] PlanOnly = { ArchViewAxis.Top };\n\n\tArchViewAxis served = (ArchViewAxis)(-1);\n\n\tprotected bool Dragging { get; private set; }\n\tprotected Vector2 DragStart { get; private set; }\n\tprotected Vector2 DragCurrent { get; private set; }\n\tprotected float DragStartHeight { get; private set; }\n\tprotected float DragHeight { get; private set; }\n\n\t// The deck the gesture began on, so a placement files itself on the roof it was drawn against rather than\n\t// looking one up again from a point the camera has since moved past.\n\tprotected ArchRoofPart DragDeck { get; private set; }\n\n\tprotected ArchCursor Pointer { get; private set; }\n\n\tprotected virtual bool UsesDrag =\u003E true;\n\n\t// Off the grid, the cursor is read where the ray actually landed instead. The grid that answers is the\n\t// editor\u0027s own, so this follows the scene view\u0027s snap toggle rather than one of the tool\u0027s own.\n\tprotected virtual bool Snapped =\u003E ArchGridService.Snapping;\n\n\t// The snap band THIS gesture wants, off the reach the author set on the bar. Tight by default, because most\n\t// gestures are drags and a face snap sets the point flush across the line: both ends inside the band and the\n\t// drag has no across component left to place anything with. A gesture whose whole purpose is to set something\n\t// flush against what already stands answers ArchWallSnap.Flush instead.\n\tpublic virtual ArchSnapReach SnapReach( float authored ) =\u003E ArchWallSnap.Reach( authored );\n\n\t// A footprint has no meaning in an elevation, and a tool that quietly placed at the origin would be worse than one that refuses.\n\tpublic virtual ArchViewAxis[] Works =\u003E PlanOnly;\n\n\tpublic bool Serves( ArchViewAxis axis ) =\u003E Works.Contains( axis );\n\n\t// The work-plane grid is scaffolding for placing; an unfocused view is not placing either.\n\tpublic virtual bool Placing =\u003E Manager?.IsCurrentViewFocused == true;\n\n\tpublic virtual ArchSurface[] Surfaces =\u003E Array.Empty\u003CArchSurface\u003E();\n\n\t// One answer for the palette tile and the sidebar header: the authored art, else the type\u0027s own [Icon].\n\tpublic string Icon =\u003E ArchIcons.Get( ArchIcons.SubtoolSlug( this ), EditorTypeLibrary.GetType( GetType() )?.Icon ?? \u0022category\u0022 );\n\n\t// A gesture the author is actually in the middle of: the mouse held through a drag, or a chained run with an\n\t// end already taken. Merely having a placement tool up is not one, and that is the whole difference between a\n\t// stack that says what you are doing and one that says \u0022placing\u0022 from the moment you pick the tool.\n\tbool Gesturing =\u003E Dragging || Run().Active;\n\n\tpublic override void OnUpdate()\n\t{\n\t\tactiveDraft?.Show( Gesturing );\n\n\t\tif ( Manager?.IsCurrentViewFocused != true )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar axis = Owner.Axis == ArchViewAxis.Free ? ArchViewAxis.Top : Owner.Axis;\n\n\t\tif ( axis != served )\n\t\t{\n\t\t\tserved = axis;\n\t\t\tRefresh();\n\t\t}\n\n\t\t// The selection\u0027s own widgets do not depend on where the cursor lands - the gizmo hit-tests itself - so\n\t\t// they are drawn before the work plane is asked for anything, and before a free-click tool takes the\n\t\t// frame. Behind those gates, aiming at the sky took the widgets off the part that was selected, a drag\n\t\t// already under way died halfway through the gesture, and a subtool reading faces showed no widget at\n\t\t// all - which is a layer selected in the stack with nothing to drag it by.\n\t\tif ( Serves( axis ) \u0026\u0026 Adjusting() )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// A pick that is not on the work plane at all - a face overhead - has to be taken BEFORE the cursor\n\t\t// gate. Looking up at a ceiling never crosses that plane, so the frame stopped here and the click\n\t\t// simply went missing.\n\t\tif ( TakesFreeClick )\n\t\t{\n\t\t\tusing ( ArchGhost.Begin() )\n\t\t\t{\n\t\t\t\tDrawFreeHover();\n\t\t\t}\n\n\t\t\tif ( Gizmo.WasLeftMousePressed )\n\t\t\t{\n\t\t\t\tOnFreeClick();\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !Serves( axis ) || !Owner.Cursor( out var cursor ) )\n\t\t{\n\t\t\t// A gesture with nowhere to land still has to END, or the preview follows the cursor for ever and\n\t\t\t// the next release dispatches a drag from a start the author left behind minutes ago.\n\t\t\tDragging \u0026= !Gizmo.WasLeftMouseReleased;\n\n\t\t\tif ( !Dragging )\n\t\t\t{\n\t\t\t\tOwner.StepOff();\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tvar point = Snapped ? cursor.Plan : cursor.Free;\n\n\t\tPointer = cursor;\n\t\tDragCurrent = point;\n\t\tDragHeight = cursor.Height;\n\n\t\tif ( !UsesDrag )\n\t\t{\n\t\t\tusing ( ArchGhost.Begin() )\n\t\t\t{\n\t\t\t\tDrawHover( point );\n\t\t\t}\n\n\t\t\tif ( Gizmo.WasLeftMousePressed )\n\t\t\t{\n\t\t\t\tOwner.EnsureTarget();\n\t\t\t\tOnClick( point );\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Gizmo.WasLeftMousePressed )\n\t\t{\n\t\t\tDragStart = point;\n\t\t\tDragStartHeight = cursor.Height;\n\t\t\tDragDeck = cursor.OnDeck;\n\t\t\tDragging = true;\n\n\t\t\t// The whole gesture works whatever it began on. Without this the ray is re-projected onto the storey\u0027s\n\t\t\t// plane every frame, so a drag begun on a deck 128 inches up ran off across the yard the moment the\n\t\t\t// camera was not looking straight down at it.\n\t\t\tOwner.StandOn( cursor.Height, cursor.OnDeck );\n\n\t\t\treturn;\n\t\t}\n\n\t\tusing ( ArchGhost.Begin() )\n\t\t{\n\t\t\tif ( Dragging )\n\t\t\t{\n\t\t\t\tDrawPreview();\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tDrawHover( point );\n\t\t\t}\n\t\t}\n\n\t\tif ( !Gizmo.WasLeftMouseReleased || !Dragging )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tDragging = false;\n\t\tdrawn = null;\n\n\t\tOwner.StepOff();\n\t\tOwner.EnsureTarget();\n\t\tOnDrag( DragStart, point );\n\t}\n\n\t// Off by default: most tools place and move on. A tool that owns the thing it just placed turns it on\n\t// so the shape can be pulled about without leaving the tool that drew it.\n\tprotected virtual bool Adjusts =\u003E false;\n\n\t// Whoever draws the selection\u0027s widgets, they get the frame HERE - true while they hold the mouse, so the\n\t// tool\u0027s own gesture stands down. One slot, or a subtool that draws its handles somewhere further down is a\n\t// subtool whose handles are gated behind whatever it does first.\n\tprotected virtual bool Adjusting() =\u003E Adjust();\n\n\tbool adjusting;\n\n\tobject drawn;\n\n\t// The shape just drawn keeps the sidebar - its own numbers are what you reach for next - but NOT its\n\t// widgets, until a gesture has been and gone. A box dragger standing over the thing you just placed covers\n\t// the very surface you place the next one ON, and the gizmo takes the press first, so every drag after the\n\t// first went into the widget and nothing was ever placed again.\n\tprotected void Drew( object placed ) =\u003E drawn = placed;\n\n\t// A deliberate SELECT is the author asking to edit that shape, which is the one thing the memo must not\n\t// outlast: placing a bool files it as drawn, and nothing else cleared that, so picking it again in the Plan\n\t// Layers stack gave a selected part with the properties open and no widget anywhere until another shape had\n\t// been dragged over it. Placement sets Picked directly and does NOT come through here, so the shape you just\n\t// drew still waits for its gesture.\n\tinternal void Forget() =\u003E drawn = null;\n\n\t// True while the selection\u0027s own widgets have the mouse, so the placement gesture stands down. The\n\t// commit waits for the release, or a drag would stack one undo entry per pixel.\n\t//\n\t// The drawn shape is forgotten where the placement is DISPATCHED, never here: clearing it on the release\n\t// frame put the widgets back a frame early, and the engine still reports its pressed path on that frame,\n\t// so they took the release the drag was about to place on. Every gesture after the first was read as an\n\t// edit of the last shape - nothing was ever placed again and the preview never stood down.\n\tbool Adjust()\n\t{\n\t\t// A HELD WIDGET IS A GESTURE, AND A GESTURE ENDS WITH THE BUTTON. This latched true and was cleared only on a\n\t\t// release the three gates below let through, so anything that moved between the grab and the release - the\n\t\t// selection changing, the picked part becoming the one just drawn, the kind going out of this tool\u0027s reach -\n\t\t// left it set for the life of the subtool. CapturesPlacement then answered true on every frame afterwards,\n\t\t// Adjusting took the whole frame, and the tool never saw another press: a driveway that would not drag, for\n\t\t// ever, with nothing on screen to say why. The release frame is spared so the edit below still commits.\n\t\tif ( !Gizmo.IsLeftMouseDown \u0026\u0026 !Gizmo.WasLeftMouseReleased )\n\t\t{\n\t\t\tadjusting = false;\n\t\t}\n\n\t\tif ( !Adjusts || Owner.Picked is not { } picked || !ArchShapeHandles.ShowsHandles( picked.Item, drawn ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( ArchHandles.Draw( Owner, picked ) )\n\t\t{\n\t\t\tadjusting = true;\n\t\t\tOwner.Preview();\n\t\t}\n\n\t\tif ( !ArchShapeHandles.CapturesPlacement( ArchShapeHandles.HandlePressed, adjusting ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( Gizmo.WasLeftMouseReleased )\n\t\t{\n\t\t\tadjusting = false;\n\t\t\tOwner.Commit( $\u0022Edit {picked.Describe()}\u0022 );\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprotected virtual void OnDrag( Vector2 from, Vector2 to ) { }\n\n\tprotected virtual void OnClick( Vector2 point ) { }\n\n\t// For a subtool whose click is a ray into the scene rather than a point on the plan.\n\tprotected virtual bool TakesFreeClick =\u003E false;\n\n\tprotected virtual void OnFreeClick() { }\n\n\tprotected virtual void DrawFreeHover() { }\n\n\t// Overrides run inside an already-configured ArchGhost scope - no gizmo setup of their own.\n\tprotected virtual void DrawHover( Vector2 point )\n\t{\n\t\tArchGhost.Cursor( point, DragHeight, Owner.Kit.GridSize );\n\t}\n\n\tprotected virtual void DrawPreview()\n\t{\n\t\tArchGhost.Cursor( DragCurrent, DragHeight, Owner.Kit.GridSize );\n\n\t\tGizmo.Draw.Line(\n\t\t\tnew Vector3( DragStart.x, DragStart.y, DragStartHeight ),\n\t\t\tnew Vector3( DragCurrent.x, DragCurrent.y, DragHeight ) );\n\t}\n\n\tprotected void DrawRectPreview()\n\t{\n\t\tArchGhost.Plate( Min( DragStart, DragCurrent ), Max( DragStart, DragCurrent ), Owner.LevelHeight );\n\t}\n\n\tprotected static Vector2 Min( Vector2 a, Vector2 b ) =\u003E new( MathF.Min( a.x, b.x ), MathF.Min( a.y, b.y ) );\n\n\tprotected static Vector2 Max( Vector2 a, Vector2 b ) =\u003E new( MathF.Max( a.x, b.x ), MathF.Max( a.y, b.y ) );\n\n\t// A refresh rebuilds the whole sidebar, so a section can appear or disappear with the choice above it.\n\tpublic override Widget CreateToolSidebar()\n\t{\n\t\t// This tool is up in its own right now, so a refresh belongs to this panel again - the loan ended\n\t\t// whenever the shelf that borrowed it went away.\n\t\tlent = null;\n\n\t\tsidebar = new ToolSidebarWidget();\n\t\tPopulate();\n\n\t\treturn sidebar;\n\t}\n\n\t// Finish and Cancel exist only while a chained operation is live, and they ride the footer so a long\n\t// form cannot push the way out of a run off the bottom of the sidebar.\n\tpublic override Widget CreateToolFooter() =\u003E new ArchRunBar( Run, FinishRun, CancelRun );\n\n\tprotected virtual ArchRunState Run() =\u003E default;\n\n\tprotected virtual void FinishRun() { }\n\n\tprotected virtual void CancelRun() { }\n\n\t// A noun, not a gesture: the gesture belongs in the advice line.\n\tprotected virtual string Title() =\u003E \u0022Options\u0022;\n\n\tprotected virtual string Shortcut() =\u003E null;\n\n\t// Disclosures, searches and browser heights survive a refresh by being keyed to the tool, not the widget.\n\tprotected string Scope( string key ) =\u003E ArchSidebarState.Scope( this, key );\n\n\t// A placement tool names the kind its draft previews; the draft lives in the tree for the\n\t// tool\u0027s whole life and FinishDraft binds the part the placement just committed.\n\tprotected virtual ArchKind? DraftKind =\u003E null;\n\n\tArchDraft activeDraft;\n\n\t// The insertion target wins; without one the draft names the active building.\n\tprotected ArchLayerRef? PlacementParent()\n\t{\n\t\tif ( Owner.InsertionTarget is { } target )\n\t\t{\n\t\t\treturn target;\n\t\t}\n\n\t\treturn Owner.LayerTree.Find( Owner.ActiveBuildingId )?.Ref;\n\t}\n\n\tpublic override void OnEnabled()\n\t{\n\t\tbase.OnEnabled();\n\n\t\tBeginDraft();\n\t}\n\n\t// Also reached by a tool whose gesture changes the kind it is about to place: left alone, the row the\n\t// draft opened still names the old kind, and the next placement arrives under the wrong heading.\n\tprotected void BeginDraft()\n\t{\n\t\tif ( DraftKind is { } kind )\n\t\t{\n\t\t\tactiveDraft ??= Owner.BeginPlacement( kind, PlacementParent(), null, null, this );\n\t\t}\n\t}\n\n\tpublic override void OnDisabled()\n\t{\n\t\tOwner.StepOff();\n\t\tCancelDraft();\n\t\tbase.OnDisabled();\n\t}\n\n\tprotected void CancelDraft()\n\t{\n\t\tif ( activeDraft is not null )\n\t\t{\n\t\t\tOwner.CancelPlacement( activeDraft );\n\t\t\tactiveDraft = null;\n\t\t}\n\t}\n\n\tprotected void FinishDraft() =\u003E FinishDraft( 0 );\n\n\tprotected void FinishDraft( int itemId )\n\t{\n\t\tif ( activeDraft is not null )\n\t\t{\n\t\t\tOwner.FinishPlacement( activeDraft, itemId );\n\t\t\tactiveDraft = null;\n\t\t}\n\t}\n\n\t// ONE LINE, and the line is the gesture. Anything an author would otherwise be told in a paragraph standing\n\t// over the controls belongs in this tool\u0027s guide, behind the header\u0027s HOW TO chip, where it can be as long\n\t// as it needs to be and nobody has to read past it to reach step one.\n\tprotected virtual string Advice() =\u003E null;\n\n\tprotected virtual void BuildOptions( ToolSidebarWidget panel ) { }\n\n\t// Whether this tool\u0027s own controls can be pointed at a SELECTION rather than at what it last placed.\n\t// Off unless Adopt is overridden: borrowed without one, every control on the panel writes the seed for the\n\t// next drag while the author watches the picked part not move.\n\t// Whether this subtool takes the turn key for itself. A placement that follows the cursor does; anything\n\t// else leaves it for the building already standing.\n\tpublic virtual bool Turns() =\u003E false;\n\n\tpublic virtual bool Adopts =\u003E false;\n\n\t// Whether this tool\u0027s panel is already the right one for what was picked, which decides whether selecting a\n\t// layer in the stack KEEPS the tool the author was drawing with. The manifest\u0027s own answer by default: a tool\n\t// that authors the kind and adopts a selection has nothing to gain from being swapped for Select.\n\tpublic virtual bool Hosts( object payload )\n\t{\n\t\tif ( !Adopts || payload is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\treturn ArchKinds.Load().Claiming( payload )?.Kind is { } claimed \u0026\u0026 ReferenceEquals( Authoring( claimed ), this );\n\t}\n\n\t// Selection borrows this tool\u0027s own options: picking a walkway in the stack should put the\n\t// walkway\u0027s roof, finish and pier controls in the shelf, not make you re-place one to reach them.\n\t// Adopt binds them to what is selected instead of to whatever this tool last created.\n\t//\n\t// The refresh comes from the BORROWER, and it is kept: the controls laid out here call back long after this\n\t// has returned, and this tool\u0027s own sidebar is not the one on screen while its shelf is being lent out.\n\tpublic void BuildAdopted( ToolSidebarWidget panel, ArchSelection picked, Action refresh )\n\t{\n\t\tlent = refresh;\n\n\t\tAdopt( picked );\n\t\tBuildOptions( panel );\n\t}\n\n\tAction lent;\n\n\t// Whether this panel is standing in another tool\u0027s shelf right now. A tool whose own form authors the NEXT\n\t// gesture needs to know, or lent out it tunes the seed while the author watches the picked part not move.\n\tprotected bool Lent =\u003E lent is not null;\n\n\t// A placement tool edits the thing it just made; adopting points that at the selection instead.\n\tprotected virtual void Adopt( ArchSelection picked ) { }\n\n\t// The shelf tool that authors what is selected, so its controls can be borrowed.\n\tpublic ArchSubtool Authoring( ArchKind kind )\n\t{\n\t\tvar wanted = ArchKindsAsked.Subtool( kind );\n\n\t\tif ( wanted.Length == 0 )\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\treturn Owner.Tools.OfType\u003CArchSubtool\u003E().FirstOrDefault( tool =\u003E tool.GetType().Name == wanted );\n\t}\n\n\tprotected void Refresh()\n\t{\n\t\tif ( lent is not null )\n\t\t{\n\t\t\tlent();\n\n\t\t\treturn;\n\t\t}\n\n\t\tif ( !sidebar.IsValid() )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tsidebar.Layout.Clear( true );\n\t\tPopulate();\n\t}\n\n\t// The Plan Layers dock selects outside the subtool, so it needs the one refresh entry point.\n\tpublic void RefreshSidebar() =\u003E Refresh();\n\n\tvoid Populate()\n\t{\n\t\tvar serves = Serves( Owner.Axis == ArchViewAxis.Free ? ArchViewAxis.Top : Owner.Axis );\n\n\t\tArchSidebarLayout.Header( sidebar, Title(), Icon, Shortcut(), serves ? Advice() : Refusal(), Guide() );\n\n\t\t// In a view it cannot honour the whole form goes, not just its enabled state.\n\t\tif ( !serves )\n\t\t{\n\t\t\tsidebar.Layout.AddStretchCell();\n\t\t\treturn;\n\t\t}\n\n\t\tBuildOptions( sidebar );\n\n\t\tsidebar.Layout.AddStretchCell();\n\t}\n\n\tAction Guide()\n\t{\n\t\tif ( ArchGuides.For( this ) is not { } written )\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\treturn () =\u003E ArchGuideWindow.Open( written );\n\t}\n\n\tstring Refusal()\n\t{\n\t\tvar views = string.Join( \u0022, \u0022, Works.Select( axis =\u003E axis == ArchViewAxis.Top ? \u0022the plan\u0022 : $\u0022the {axis} elevation\u0022 ) );\n\n\t\treturn $\u0022Nothing to place from here \u2014 this tool works in {views}.\u0022;\n\t}\n\n\tprotected static Widget Wrapped( string text ) =\u003E ArchPartUi.Wrapped( text );\n\n}\n\npublic static class ArchPaletteUi\n{\n\tpublic static Widget Row( Widget parent, ArchPalette palette, ArchSurface surface, Action changed )\n\t{\n\t\tvar holder = new Widget( parent );\n\t\tholder.Layout = Layout.Row();\n\t\tholder.Layout.Spacing = 4;\n\n\t\tvar label = new Label( surface.ToString() );\n\t\tlabel.MinimumWidth = 92;\n\t\tholder.Layout.Add( label );\n\n\t\tpalette.TryGet( surface, out var path );\n\n\t\tvar value = new Label( string.IsNullOrWhiteSpace( path ) ? \u0022(inherited)\u0022 : System.IO.Path.GetFileNameWithoutExtension( path ) );\n\t\tvalue.MinimumWidth = 110;\n\t\tholder.Layout.Add( value );\n\n\t\tholder.Layout.Add( new Button( \u0022\u0022, \u0022image\u0022 )\n\t\t{\n\t\t\tClicked = () =\u003E\n\t\t\t{\n\t\t\t\tvar picker = AssetPicker.Create( parent, AssetType.Material );\n\t\t\t\tpicker.Window.Title = $\u0022Material for {surface}\u0022;\n\t\t\t\tpicker.OnAssetPicked = assets =\u003E\n\t\t\t\t{\n\t\t\t\t\tvar asset = assets.FirstOrDefault();\n\t\t\t\t\tif ( asset is null ) return;\n\n\t\t\t\t\tpalette.Set( surface, asset.Path );\n\t\t\t\t\tArchStyle.InvalidateCache();\n\t\t\t\t\tchanged?.Invoke();\n\t\t\t\t};\n\t\t\t\tpicker.Show();\n\t\t\t}\n\t\t} );\n\n\t\t// Blank = no override; the generator falls back to ArchMesh.TexelScale.\n\t\tvar scale = palette.ScaleFor( surface );\n\t\tvar density = new LineEdit( scale \u003E 0f ? scale.ToString( \u00220.####\u0022 ) : \u0022\u0022 )\n\t\t{\n\t\t\tPlaceholderText = ArchMesh.TexelScale.ToString( \u00220.####\u0022 ),\n\t\t\tMaximumWidth = 64,\n\t\t\tToolTip = \u0022Units per texel for this role. Blank inherits.\u0022\n\t\t};\n\n\t\tdensity.TextEdited \u002B= text =\u003E\n\t\t{\n\t\t\tpalette.SetScale( surface, float.TryParse( text, out var parsed ) ? parsed : 0f );\n\t\t\tArchStyle.InvalidateCache();\n\t\t\tchanged?.Invoke();\n\t\t};\n\n\t\tholder.Layout.Add( density );\n\n\t\tholder.Layout.Add( new Button( \u0022\u0022, \u0022close\u0022 )\n\t\t{\n\t\t\tClicked = () =\u003E\n\t\t\t{\n\t\t\t\tpalette.Set( surface, null );\n\t\t\t\tdensity.Text = \u0022\u0022;\n\t\t\t\tArchStyle.InvalidateCache();\n\t\t\t\tchanged?.Invoke();\n\t\t\t}\n\t\t} );\n\n\t\tholder.Layout.AddStretchCell();\n\n\t\treturn holder;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Tool/Subtools/ArchBuildingSubtool.cs","FileName":"ArchBuildingSubtool.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Editor;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\npublic enum BuildingMode\n{\n\tNew,\n\tExtend,\n\tPrefab,\n\tRow,\n\tSaved\n}\n\n[Title( \u0022Building\u0022 ), Icon( \u0022domain_add\u0022 ), Group( \u002202\u0022 )]\npublic sealed class ArchBuildingSubtool( ArchTool owner ) : ArchSubtool( owner )\n{\n\tprotected override ArchKind? DraftKind =\u003E ArchKind.Building;\n\n\tpublic override ArchSurface[] Surfaces =\u003E new[] { ArchSurface.WallExterior, ArchSurface.WallInterior, ArchSurface.Floor, ArchSurface.Roof, ArchSurface.Soffit, ArchSurface.WallCap };\n\n\tBuildingMode mode;\n\tstring archetype = \u0022house\u0022;\n\tbool flipX;\n\tbool flipY;\n\tbool withRoof = true;\n\tbool withFloor = true;\n\tbool withGutters = true;\n\tbool withFoundation = true;\n\tRoofStyle roofStyle = RoofStyle.Hip;\n\tRidgeRun ridge = RidgeRun.Auto;\n\tSectionRoof wingRoof = SectionRoof.Continue;\n\t// Off: a dropped eave opens a void under the storey grid above it.\n\tfloat eaveDrop;\n\n\t// Seeded from the type then editable - an invisible number can\u0027t be matched by hand.\n\tfloat wallHeight;\n\tfloat roofPitch;\n\tbool withFrame;\n\tbool withFascia = true;\n\tbool withSoffit = true;\n\tbool withCeiling;\n\n\tbool seeded;\n\n\t// The saved building being stamped, its outlines worked out once, and how far round it has been turned.\n\tArchBuildingAsset saved;\n\tArchAssetGhost savedGhost;\n\tList\u003CArchPresetItem\u003CArchBuildingAsset\u003E\u003E savedOffered;\n\tint savedStamp = -1;\n\tint quarters;\n\n\t// What the next placement is turned by. R adds quarters on top of it, so a stamp aimed at 20 degrees still\n\t// turns square corners from there.\n\tfloat placementAngle;\n\n\t// Re-dresses while still the newest in the plan; anything else authored moves the counter.\n\tint placedRoom;\n\tint placedRoof;\n\tint placedStamp;\n\tbool placedMerged;\n\n\tbool Extending =\u003E mode == BuildingMode.Extend;\n\n\tbool Rowing =\u003E mode == BuildingMode.Row;\n\n\tbool Stamping =\u003E mode == BuildingMode.Saved;\n\n\t// A stamp has nothing to size, so it takes a point rather than a rectangle.\n\tprotected override bool UsesDrag =\u003E !Stamping;\n\n\tArchArchetype Chosen =\u003E Owner.FindArchetype( archetype );\n\n\t// One bearing for the drag and the stamp alike, so the ghost, the note and what is stood all read the same.\n\tfloat Facing =\u003E placementAngle \u002B quarters * 90f;\n\n\t// A wing takes the bearing of the building it abuts and a row takes the street\u0027s, so neither has one to name.\n\tbool Aimable =\u003E !Extending \u0026\u0026 !Rowing;\n\n\tprotected override string Title() =\u003E Stamping ? \u0022Saved Building\u0022 : \u0022Building\u0022;\n\n\t// Whether the section this drag stands will carry a roof at all - a wing answers through its own rules.\n\tbool Roofed =\u003E Extending ? wingRoof != SectionRoof.None : withRoof;\n\n\tprotected override string Advice() =\u003E mode switch\n\t{\n\t\tBuildingMode.Extend =\u003E \u0022Drag a wing onto the active building. It shares the wall it abuts, and inherits its type.\u0022,\n\t\tBuildingMode.Prefab =\u003E \u0022Drag the plot. The type\u0027s canned layout is laid out across it in one go.\u0022,\n\t\tBuildingMode.Row =\u003E \u0022Drag the frontage and how far back it reaches. Along a street it takes that road\u0027s verge and the units step with the curve.\u0022,\n\t\tBuildingMode.Saved =\u003E \u0022Pick a saved building and it follows the cursor. R turns it a quarter, the Bearing box aims it at anything else; click stands it there.\u0022,\n\t\t_ =\u003E \u0022Drag the outer shell. The chosen type decides its heights, roof and trims.\u0022\n\t};\n\n\t// Picking the tool starts the sequence over, so the first step is open and asking what this drag will be.\n\tpublic override void OnEnabled()\n\t{\n\t\tbase.OnEnabled();\n\n\t\tArchWorkflow.Restart( Scope( \u0022flow\u0022 ) );\n\t}\n\n\tpublic override void OnUpdate()\n\t{\n\t\tbase.OnUpdate();\n\n\t\t// The save that fills this shelf happens in the Plan Layers stack, which has no way to reach back\n\t\t// into the sidebar - so the shelf watches the count instead of waiting to be told.\n\t\tif ( Stamping \u0026\u0026 savedOffered is not null \u0026\u0026 savedStamp != ArchLayerAssets.Revision )\n\t\t{\n\t\t\tRefresh();\n\t\t}\n\t}\n\n\t// Only while a stamp is actually following the cursor: with the shelf up and nothing chosen the key belongs\n\t// to whatever is picked in the plan.\n\tpublic override bool Turns()\n\t{\n\t\tif ( !Stamping || !Placing || saved is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tquarters = (quarters \u002B 1) % 4;\n\n\t\treturn true;\n\t}\n\n\tprotected override void DrawHover( Vector2 point )\n\t{\n\t\tbase.DrawHover( point );\n\n\t\tif ( !Stamping || savedGhost is not { Shapes.Count: \u003E 0 } ghost )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar map = ArchBuildingStamp.Landing( ghost, point, Facing );\n\n\t\tforeach ( var shape in ghost.Shapes )\n\t\t{\n\t\t\tArchGhost.Prism( shape.Loop.Select( corner =\u003E map.Of( corner ) ).ToList(), shape.Bottom, shape.Top );\n\t\t}\n\n\t\tvar span = map.Swapped( ghost.Max - ghost.Min );\n\n\t\tArchGhost.Note( new Vector3( point.x \u002B span.x, point.y \u002B span.y, Owner.LevelHeight ),\n\t\t\t$\u0022{saved.Title} \u2014 {span.x:0} x {span.y:0}, turned {Facing:0.##}\u00B0\u0022 );\n\t}\n\n\tprotected override void OnClick( Vector2 point )\n\t{\n\t\tif ( !Stamping )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif ( saved is null )\n\t\t{\n\t\t\tLog.Info( \u0022Architecture: pick a saved building before clicking - there is nothing to stand yet.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( ArchBuildingStamp.Place( Owner.Plan, saved, point, Facing ) is not { } placed )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: {saved.Title} could not be stamped there.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tOwner.ActiveBuildingId = placed.Id;\n\t\tOwner.ActiveRoomId = placed.Rooms[0].Id;\n\n\t\tFinishDraft( placed.Id );\n\t\tBeginDraft();\n\t\tOwner.Commit( $\u0022Place {saved.Title}\u0022 );\n\t}\n\n\tprotected override void DrawPreview()\n\t{\n\t\tif ( mode == BuildingMode.Prefab )\n\t\t{\n\t\t\tDrawPrefabPreview();\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Rowing )\n\t\t{\n\t\t\tDrawRowPreview();\n\t\t\treturn;\n\t\t}\n\n\t\tvar standard = ArchArchetypeRules.Standing( wallHeight, Owner.Kit );\n\t\tvar height = Extending ? MathF.Max( 32f, standard - MathF.Max( 0f, eaveDrop ) ) : standard;\n\t\tvar placement = Resolve( DragStart, DragCurrent );\n\n\t\tif ( !placement.IsUsable || Extending \u0026\u0026 !placement.TouchesHost )\n\t\t{\n\t\t\tDrawRectPreview();\n\t\t\treturn;\n\t\t}\n\n\t\tvar aimed = Aimable \u0026\u0026 MathF.Abs( Facing ) \u003E 0.001f;\n\n\t\tif ( aimed )\n\t\t{\n\t\t\tArchGhost.Prism( Turned( placement.Min, placement.Max ), Owner.LevelHeight, Owner.LevelHeight \u002B height );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tArchGhost.Volume( placement.Min, placement.Max, Owner.LevelHeight, Owner.LevelHeight \u002B height );\n\t\t}\n\n\t\tArchGhost.Note( new Vector3( placement.Max.x, placement.Max.y, Owner.LevelHeight \u002B height ),\n\t\t\taimed\n\t\t\t\t? $\u0022{placement.Max.x - placement.Min.x:0} x {placement.Max.y - placement.Min.y:0}, turned {Facing:0.##}\u00B0\u0022\n\t\t\t\t: $\u0022{placement.Max.x - placement.Min.x:0} x {placement.Max.y - placement.Min.y:0}\u0022 );\n\n\t\t// The pitch ghost is drawn off a box, so a turned shell shows its footprint and stands its roof on release.\n\t\tif ( !withRoof \u0026\u0026 !Extending || aimed )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar min = placement.Min;\n\t\tvar max = placement.Max;\n\t\tvar style = Extending ? ArchBuild.Winged( wingRoof ) : roofStyle;\n\t\tvar pitch = roofPitch \u003E 0.5f ? roofPitch : Owner.Kit.RoofPitch;\n\n\t\tArchGhost.Pitch( min, max, Owner.LevelHeight \u002B height, pitch, style, ArchBuild.Ridged( ridge, min, max ) );\n\t}\n\n\t// From the layout\u0027s resolved rectangles, so growth and placement show before release.\n\tvoid DrawPrefabPreview()\n\t{\n\t\tvar recipe = Owner.FindArchetype( archetype );\n\n\t\tif ( recipe is null || !recipe.HasLayout )\n\t\t{\n\t\t\tDrawRectPreview();\n\t\t\treturn;\n\t\t}\n\n\t\tvar plot = ResolvedPrefabPlot( recipe, DragStart, DragCurrent, out var usable );\n\n\t\tif ( !usable )\n\t\t{\n\t\t\tDrawRectPreview();\n\t\t\treturn;\n\t\t}\n\n\t\tArchGhost.Plate( plot.Min, plot.Max, Owner.LevelHeight, 8 );\n\n\t\tforeach ( var step in recipe.Steps )\n\t\t{\n\t\t\tif ( step.Kind is not (ArchStepKind.Shell or ArchStepKind.Wing or ArchStepKind.Canopy) )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tplot.Resolve( step.Rect, out var min, out var max );\n\t\t\tArchGhost.Volume( min, max, Owner.LevelHeight, Owner.LevelHeight \u002B Standing( recipe, step ) );\n\t\t}\n\n\t\tArchGhost.Note( new Vector3( plot.Max.x, plot.Max.y, Owner.LevelHeight \u002B 264f ),\n\t\t\t$\u0022{recipe.Title} \u2014 {plot.Size.x:0} x {plot.Size.y:0}\u0022 );\n\t}\n\n\t// From the one resolve the commit uses, so the units drawn are the units stood.\n\tvoid DrawRowPreview()\n\t{\n\t\tvar row = ResolvedRow( DragStart, DragCurrent );\n\n\t\tif ( !row.IsUsable )\n\t\t{\n\t\t\tDrawRectPreview();\n\t\t\treturn;\n\t\t}\n\n\t\tvar standing = ArchArchetypeRules.Standing( wallHeight, Owner.Kit );\n\t\tvar storey = standing \u002B Owner.Kit.FloorThickness;\n\t\tvar pitch = roofPitch \u003E 0.5f ? roofPitch : Owner.Kit.RoofPitch;\n\n\t\tforeach ( var bay in row.Bays )\n\t\t{\n\t\t\tvar plate = Owner.LevelHeight \u002B standing \u002B storey * (bay.Storeys - 1);\n\n\t\t\tArchGhost.Volume( bay.Min, bay.Max, Owner.LevelHeight, plate );\n\n\t\t\tif ( withRoof )\n\t\t\t{\n\t\t\t\tArchGhost.Pitch( bay.Min, bay.Max, plate, pitch, roofStyle, ArchBuild.Ridged( ridge, bay.Min, bay.Max ) );\n\t\t\t}\n\t\t}\n\n\t\tvar last = row.Bays[^1];\n\n\t\tArchGhost.Note( new Vector3( last.Max.x, last.Max.y, Owner.LevelHeight \u002B standing ),\n\t\t\trow.RoadId \u003E 0\n\t\t\t\t? $\u0022{row.Bays.Count} units on the verge \u2014 {row.Frontage:0} x {row.Depth:0}\u0022\n\t\t\t\t: $\u0022{row.Bays.Count} units \u2014 {row.Frontage:0} x {row.Depth:0}\u0022 );\n\t}\n\n\tprotected override void OnDrag( Vector2 from, Vector2 to )\n\t{\n\t\tif ( mode == BuildingMode.Prefab )\n\t\t{\n\t\t\tPlacePrefab( from, to );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Rowing )\n\t\t{\n\t\t\tPlaceRow( from, to );\n\t\t\treturn;\n\t\t}\n\n\t\tvar placement = Resolve( from, to );\n\t\tvar min = placement.Min;\n\t\tvar max = placement.Max;\n\n\t\tif ( !placement.IsUsable || Extending \u0026\u0026 !placement.TouchesHost )\n\t\t{\n\t\t\tLog.Info( Extending\n\t\t\t\t? \u0022Architecture: an extension must finish against an empty edge of the active building.\u0022\n\t\t\t\t: \u0022Architecture: that drag is fully occupied. Start or finish the drag in empty grid space.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar rules = Authored();\n\n\t\t// The drag names the building it joins, not the target picker - whose placeholder extends nothing.\n\t\tif ( Extending \u0026\u0026 placement.Host is { Rooms.Count: \u003E 0 } active )\n\t\t{\n\t\t\tactive.Archetype = Chosen?.Name ?? active.Archetype;\n\n\t\t\tOwner.ActiveBuildingId = active.Id;\n\n\t\t\tvar wing = ArchBuild.Extend(\n\t\t\t\tOwner.Plan, active, Owner.Kit, Owner.Level, Owner.LevelHeight, min, max,\n\t\t\t\twingRoof, eaveDrop, rules.Floor ?? true, rules.Gutters ?? true, ridge );\n\n\t\t\tif ( wing is null )\n\t\t\t{\n\t\t\t\tLog.Info( \u0022Architecture: that wing has no empty grid space beside the building.\u0022 );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tArchArchetypeRules.Apply( wing, rules, Owner.Kit );\n\n\t\t\tOwner.ActiveRoomId = wing.Room.Id;\n\t\t\tCancelDraft();\n\t\t\tOwner.Commit();\n\t\t\tRemember( wing );\n\n\t\t\treturn;\n\t\t}\n\n\t\tvar building = new ArchBuilding\n\t\t{\n\t\t\tId = Owner.Plan.AllocateId(),\n\t\t\tName = $\u0022{Chosen?.Title ?? \u0022Building\u0022}{Owner.Plan.Buildings.Count \u002B 1}\u0022,\n\t\t\tArchetype = Chosen?.Name ?? \u0022\u0022,\n\t\t\tGuttersEnabled = rules.Gutters ?? true\n\t\t};\n\n\t\tvar shell = ArchBuild.Shell(\n\t\t\tOwner.Plan, building, Owner.Kit, Owner.Level, Owner.LevelHeight, min, max, rules.Floor ?? true,\n\t\t\twithRoof ? roofStyle : null, rules.Gutters ?? true, ridge );\n\n\t\tif ( shell is null )\n\t\t{\n\t\t\tLog.Info( \u0022Architecture: that building footprint has no empty grid space.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tArchArchetypeRules.Apply( shell, rules, Owner.Kit );\n\n\t\tOwner.Plan.Units.Add( building );\n\n\t\tif ( Aimable \u0026\u0026 ArchHandles.Pivot( building, out var about ) )\n\t\t{\n\t\t\tArchCarry.Turn( building, about, Facing );\n\t\t}\n\n\t\tOwner.ActiveBuildingId = building.Id;\n\t\tOwner.ActiveRoomId = shell.Room.Id;\n\t\tFinishDraft( building.Id );\n\t\tOwner.Commit();\n\t\tRemember( shell );\n\t}\n\n\t// The drag rectangle swung about its own centre, which is the pivot the placement turns on too - two\n\t// centres would show the shell in one place and stand it in another.\n\tstatic List\u003CVector2\u003E Turned( Vector2 min, Vector2 max, float degrees ) =\u003E ArchFootprint.Turned( ArchFootprint.Rect( min, max ), (min \u002B max) * 0.5f, degrees );\n\n\tList\u003CVector2\u003E Turned( Vector2 min, Vector2 max ) =\u003E Turned( min, max, Facing );\n\n\tArchRectanglePlacement Resolve( Vector2 from, Vector2 to )\n\t{\n\t\treturn new ArchBoundaryPlacementService( Owner.Plan, Owner.Kit )\n\t\t\t.Outside( Owner.Level, from, to, Extending ? Owner.ActiveBuilding() : null );\n\t}\n\n\tvoid Remember( ArchSection section )\n\t{\n\t\tplacedRoom = section.Room?.Id ?? 0;\n\t\tplacedRoof = section.Roof?.Id ?? 0;\n\t\tplacedStamp = Owner.Plan.NextId;\n\t\tplacedMerged = section.Merged;\n\n\t\tRefresh();\n\t}\n\n\t// Shown in the panel - an untold live edit reads as the tool acting alone.\n\tArchRoom Editing =\u003E placedRoom != 0 \u0026\u0026 Owner.Plan.NextId == placedStamp ? Owner.Plan.FindRoom( placedRoom ) : null;\n\n\t// Edits the last drag\u0027s room and roof in place - nothing is re-created.\n\tvoid Restyle()\n\t{\n\t\tif ( placedRoom == 0 || Owner.Plan.NextId != placedStamp )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Owner.Plan.FindRoom( placedRoom ) is not { } room )\n\t\t{\n\t\t\tplacedRoom = 0;\n\t\t\treturn;\n\t\t}\n\n\t\tvar building = Owner.Plan.OwnerOf( room );\n\t\tvar roof = building?.Roofs.FirstOrDefault( part =\u003E part.Id == placedRoof );\n\n\t\tif ( building is not null \u0026\u0026 Chosen is { } chosen )\n\t\t{\n\t\t\tbuilding.Archetype = chosen.Name;\n\t\t}\n\n\t\t// A merged wing shares the section it folded into; style and ridge follow the host.\n\t\tif ( roof is not null \u0026\u0026 !placedMerged )\n\t\t{\n\t\t\troof.Style = Extending ? ArchBuild.Winged( wingRoof ) : roofStyle;\n\t\t\troof.RidgeAlongX = ArchBuild.Ridged( ridge, roof.Min, roof.Max );\n\t\t}\n\n\t\tArchArchetypeRules.Apply( new ArchSection { Room = room, Roof = roof, Merged = placedMerged }, Authored(), Owner.Kit );\n\n\t\tOwner.Touch( \u0022Restyle Section\u0022 );\n\t}\n\n\tvoid Set( Action change )\n\t{\n\t\tchange();\n\t\tRestyle();\n\t}\n\n\t// A choice that decides which rows exist re-lays out only after the standing section has been re-dressed;\n\t// refreshing from inside the change tears down the widget still handling the click.\n\tvoid Relayout( Action change )\n\t{\n\t\tSet( change );\n\t\tRefresh();\n\t}\n\n\t// The type only seeds these - every deciding number is visible and editable.\n\tArchSectionRules Authored()\n\t{\n\t\tvar type = Chosen is { } chosen ? (Extending ? chosen.Wing : chosen.Shell) : new ArchSectionRules();\n\n\t\treturn new ArchSectionRules\n\t\t{\n\t\t\tWallHeight = wallHeight,\n\t\t\tRidge = ridge,\n\t\t\tRoofPitch = roofPitch,\n\t\t\tOverhang = type.Overhang,\n\t\t\tFrameSpacing = type.FrameSpacing,\n\t\t\tFloor = withFloor,\n\t\t\tFoundation = withFoundation,\n\t\t\tCeiling = withCeiling,\n\t\t\tGutters = withGutters,\n\t\t\tFascia = withFascia,\n\t\t\tSoffit = withSoffit,\n\t\t\tFrame = withFrame,\n\t\t\tFloorBoards = type.FloorBoards,\n\t\t\tFloorBoardYaw = type.FloorBoardYaw,\n\t\t\tPalette = type.Palette\n\t\t};\n\t}\n\n\tfloat Standing( ArchArchetype recipe, ArchArchetypeStep step )\n\t{\n\t\tif ( step.Kind == ArchStepKind.Canopy )\n\t\t{\n\t\t\treturn step.HeadHeight;\n\t\t}\n\n\t\tvar type = step.Kind == ArchStepKind.Wing ? recipe.Wing : recipe.Shell;\n\t\tvar height = step.Rules.WallHeight \u003E 1f ? step.Rules.WallHeight : type.WallHeight;\n\n\t\treturn ArchArchetypeRules.Standing( height, Owner.Kit );\n\t}\n\n\tvoid PlacePrefab( Vector2 from, Vector2 to )\n\t{\n\t\tvar recipe = Owner.FindArchetype( archetype );\n\n\t\tif ( recipe is null || !recipe.HasLayout )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: \u0027{archetype}\u0027 has no canned layout - use New with the type chosen instead. Types live in Assets/{ArchStorage.ArchetypeDirectory}.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar plot = ResolvedPrefabPlot( recipe, from, to, out var usable );\n\n\t\tif ( !usable )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: {recipe.Title} has no empty grid space on that plot.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar placed = ArchArchetypeBuild.Place( Owner.Plan, Owner.Kit, recipe, plot, Owner.Level, Owner.LevelHeight, Owner.PillarTypes );\n\n\t\tif ( placed is null )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: {recipe.Title} laid out nothing on that plot.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tforeach ( var note in placed.Skipped )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: {recipe.Title} skipped {note}.\u0022 );\n\t\t}\n\n\t\tOwner.ActiveBuildingId = placed.Building.Id;\n\t\tOwner.ActiveRoomId = placed.Building.Rooms[0].Id;\n\t\tOwner.Commit( $\u0022Place {recipe.Title}\u0022 );\n\t}\n\n\tArchPlot ResolvedPrefabPlot( ArchArchetype recipe, Vector2 from, Vector2 to, out bool usable )\n\t{\n\t\treturn new ArchBoundaryPlacementService( Owner.Plan, Owner.Kit )\n\t\t\t.PlotOutside( Owner.Level, from, to, recipe.MinimumPlot, flipX, flipY, out usable );\n\t}\n\n\tvoid PlaceRow( Vector2 from, Vector2 to )\n\t{\n\t\tif ( Chosen is not { } type )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: a row takes its unit width from a building type, and none are in Assets/{ArchStorage.ArchetypeDirectory}.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar row = ResolvedRow( from, to );\n\n\t\tif ( !row.IsUsable )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: that drag names no frontage a {type.Title} unit fits along. Drag further along the street, or deeper back from it.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar placed = ArchRowPlacement.Stand(\n\t\t\tOwner.Plan, Owner.Kit, row, type, Authored(), Owner.Level, Owner.LevelHeight,\n\t\t\twithRoof ? roofStyle : null, ridge );\n\n\t\tif ( placed is null )\n\t\t{\n\t\t\tLog.Info( \u0022Architecture: every bay of that row landed on occupied ground.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tif ( row.Blocked \u003E 0 )\n\t\t{\n\t\t\tLog.Info( $\u0022Architecture: {row.Blocked} of the row\u0027s bays stood on occupied ground and were left out.\u0022 );\n\t\t}\n\n\t\tOwner.ActiveBuildingId = placed.Buildings[0].Id;\n\t\tOwner.ActiveRoomId = placed.Buildings[0].Rooms[0].Id;\n\t\tCancelDraft();\n\t\tOwner.Commit( $\u0022Place Street Row of {placed.Buildings.Count}\u0022 );\n\t}\n\n\t// The road comes from the tool\u0027s own memoised lookup - Nearest walks a whole dense curve, and the ghost asks per frame.\n\tArchRowShape ResolvedRow( Vector2 from, Vector2 to )\n\t{\n\t\tvar road = Owner.RoadAt( from, ArchRowPlacement.Verge, out _ ) ?? Owner.RoadAt( to, ArchRowPlacement.Verge, out _ );\n\n\t\treturn ArchRowPlacement.Resolve(\n\t\t\tOwner.Plan, Owner.Kit, Chosen, Owner.Level, from, to,\n\t\t\troad, road is null ? null : Owner.Resolved( road.Id, road.Curve ),\n\t\t\twithRoof \u0026\u0026 roofStyle == RoofStyle.Flat );\n\t}\n\n\t// The layout drawing leads the panel, because the shape of a shell already standing is the one thing this tool\u0027s\n\t// drag cannot go back and fix. Nothing to work on until a building stands, so it says so rather than opening empty.\n\tvoid Layout( ToolSidebarWidget panel )\n\t{\n\t\tvar building = Owner.ActiveBuilding();\n\t\tvar button = new Button.Primary( \u0022Toggle Building Layout\u0022, \u0022architecture\u0022 )\n\t\t{\n\t\t\tClicked = () =\u003E ArchBuildingWindow.Toggle( Owner, building )\n\t\t};\n\n\t\tbutton.Enabled = building is not null;\n\t\tbutton.ToolTip = building is null\n\t\t\t? \u0022Drag a shell first \u2014 the layout drawing works on a building that stands\u0022\n\t\t\t: $\u0022The plan of {building.Name}, storey by storey: move a corner, push a run, cut a corner off at an angle\u0022;\n\n\t\tpanel.AddGroup( \u0022Layout\u0022 ).AddRow().Add( button );\n\t}\n\n\t// The gesture as a sequence: which placement, what is being placed, what the shell carries, what covers it,\n\t// its numbers, and the bearing it stands at. All of those grids standing open at once is what made the first\n\t// tool on the shelf a wall of icons.\n\t//\n\t// The layout drawing leads it from OUTSIDE the sequence, because it works on a shell already standing -\n\t// the one shape the next drag cannot go back and fix.\n\tprotected override void BuildOptions( ToolSidebarWidget panel )\n\t{\n\t\tLayout( panel );\n\n\t\tInherit();\n\n\t\t// Asked whether or not the shelf is the step being shown: the stamp following the cursor is read off\n\t\t// this list, and a closed step never builds.\n\t\tif ( Stamping \u0026\u0026 (savedOffered is null || savedStamp != ArchLayerAssets.Revision) )\n\t\t{\n\t\t\tRereadSaved();\n\t\t}\n\n\t\tif ( Editing is { } room )\n\t\t{\n\t\t\tpanel.Layout.Add( ArchSidebarLayout.Advice( $\u0022Editing {room.Name} live. Place anything else and these go back to seeding the next drag.\u0022 ) );\n\t\t}\n\n\t\tusing var flow = ArchWorkflow.In( panel, Scope( \u0022flow\u0022 ), Refresh );\n\n\t\tflow.Step( \u0022Mode\u0022, ModeName, ModeGlyph, PickMode );\n\n\t\tif ( Stamping )\n\t\t{\n\t\t\tflow.Step( \u0022Saved building\u0022, saved?.Title ?? \u0022None\u0022, \u0022inventory_2\u0022, step =\u003E Stamps( panel, step ) );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tflow.Step( \u0022Type\u0022, Chosen?.Title ?? \u0022None\u0022, \u0022domain\u0022, step =\u003E Types( panel, step ) );\n\t\t}\n\n\t\t// A canned layout decides its own steps and would be contradicted by the shell\u0027s, and a stamp stands\n\t\t// exactly what it was saved as - so neither is asked anything past what it is and which way it faces.\n\t\tif ( mode == BuildingMode.Prefab )\n\t\t{\n\t\t\tflow.Step( \u0022Mirror\u0022, MirrorName, \u0022flip\u0022, PickMirror );\n\t\t}\n\t\telse if ( !Stamping )\n\t\t{\n\t\t\tflow.Step( \u0022Include\u0022, IncludeName, \u0022checklist\u0022, PickInclude );\n\n\t\t\tif ( Covered )\n\t\t\t{\n\t\t\t\tflow.Step( Extending ? \u0022Wing roof\u0022 : \u0022Roof\u0022, RoofName, RoofGlyph, PickRoof );\n\t\t\t}\n\n\t\t\tif ( Wants( ArchOptionGroup.Section ) )\n\t\t\t{\n\t\t\t\tflow.Step( \u0022Structure\u0022, StructureName, \u0022straighten\u0022, BuildStructure );\n\t\t\t}\n\t\t}\n\n\t\t// Last wherever it is asked. A bearing is typed rather than picked, so the step can never say it has\n\t\t// been answered - anywhere but the end it stops the sequence dead on a box nobody has to fill in.\n\t\tif ( Aimable )\n\t\t{\n\t\t\tflow.Step( \u0022Bearing\u0022, BearingName, \u0022explore\u0022, BuildBearing );\n\t\t}\n\t}\n\n\t// Whether a roof is asked about at all - a wing answers through its type\u0027s own rules, and a shell whose\n\t// roof was switched off in Include has nothing left to style.\n\tbool Covered =\u003E Extending ? Wants( ArchOptionGroup.WingRoof ) : withRoof;\n\n\tbool Wants( ArchOptionGroup group ) =\u003E Chosen?.Wants( group ) ?? true;\n\n\tstring ModeName =\u003E mode switch\n\t{\n\t\tBuildingMode.Extend =\u003E \u0022Extend\u0022,\n\t\tBuildingMode.Prefab =\u003E \u0022Canned layout\u0022,\n\t\tBuildingMode.Row =\u003E \u0022Street row\u0022,\n\t\tBuildingMode.Saved =\u003E \u0022Saved building\u0022,\n\t\t_ =\u003E \u0022New building\u0022\n\t};\n\n\tstring ModeGlyph =\u003E mode switch\n\t{\n\t\tBuildingMode.Extend =\u003E \u0022add_home_work\u0022,\n\t\tBuildingMode.Prefab =\u003E \u0022auto_awesome_motion\u0022,\n\t\tBuildingMode.Row =\u003E \u0022view_column\u0022,\n\t\tBuildingMode.Saved =\u003E \u0022inventory_2\u0022,\n\t\t_ =\u003E \u0022domain_add\u0022\n\t};\n\n\tvoid PickMode( ArchWorkflowStep step )\n\t{\n\t\tusing var grid = ArchIconGrid.In( step.Layout );\n\n\t\tMode( grid, step, BuildingMode.New, \u0022mode_new_building\u0022, \u0022New building \u2014 drag the outer shell\u0022, \u0022domain_add\u0022 );\n\t\tMode( grid, step, BuildingMode.Extend, \u0022mode_extend_building\u0022, \u0022Extend the active building \u2014 drag a wing onto it\u0022, \u0022add_home_work\u0022 );\n\t\tMode( grid, step, BuildingMode.Prefab, \u0022mode_archetype\u0022, \u0022Place the type\u0027s canned layout \u2014 one drag lays the whole unit out\u0022, \u0022auto_awesome_motion\u0022 );\n\t\tMode( grid, step, BuildingMode.Row, \u0022mode_street_row\u0022, \u0022Street row \u2014 drag the frontage and its depth; one party-walled unit per bay, at the type\u0027s own unit width\u0022, \u0022view_column\u0022 );\n\t\tMode( grid, step, BuildingMode.Saved, \u0022mode_saved_building\u0022, \u0022Saved building \u2014 stamp one you authored earlier back down, exactly as it stands\u0022, \u0022inventory_2\u0022 );\n\t}\n\n\tvoid Mode( ArchIconGrid grid, ArchWorkflowStep step, BuildingMode choice, string slug, string tooltip, string fallback )\n\t{\n\t\tgrid.Pick( tooltip, slug, fallback, mode == choice, () =\u003E\n\t\t{\n\t\t\tmode = choice;\n\t\t\t// Cleared, not reseeded - Extend picks up the active building\u0027s own type first.\n\t\t\tseeded = false;\n\n\t\t\tstep.Chose();\n\t\t} );\n\t}\n\n\tstring MirrorName =\u003E flipX \u0026\u0026 flipY ? \u0022X and Y\u0022 : flipX ? \u0022Across X\u0022 : flipY ? \u0022Across Y\u0022 : \u0022None\u0022;\n\n\tvoid PickMirror( ArchWorkflowStep step )\n\t{\n\t\tusing var grid = ArchIconGrid.In( step.Layout );\n\n\t\tgrid.Toggle( \u0022Mirror the layout across X\u0022, \u0022flip_x\u0022, \u0022swap_horiz\u0022, flipX, value =\u003E flipX = value );\n\t\tgrid.Toggle( \u0022Mirror the layout across Y\u0022, \u0022flip_y\u0022, \u0022swap_vert\u0022, flipY, value =\u003E flipY = value );\n\t}\n\n\tstring IncludeName\n\t{\n\t\tget\n\t\t{\n\t\t\tvar carried = new List\u003Cstring\u003E();\n\n\t\t\tif ( Wants( ArchOptionGroup.Foundation ) )\n\t\t\t{\n\t\t\t\tcarried.Add( withFoundation ? \u0022Raised\u0022 : \u0022Nested\u0022 );\n\t\t\t}\n\n\t\t\tif ( withFloor )\n\t\t\t{\n\t\t\t\tcarried.Add( \u0022floor\u0022 );\n\t\t\t}\n\n\t\t\tif ( Roofed \u0026\u0026 withGutters )\n\t\t\t{\n\t\t\t\tcarried.Add( \u0022gutters\u0022 );\n\t\t\t}\n\n\t\t\tif ( !Extending \u0026\u0026 !withRoof )\n\t\t\t{\n\t\t\t\tcarried.Add( \u0022no roof\u0022 );\n\t\t\t}\n\n\t\t\treturn carried.Count \u003E 0 ? string.Join( \u0022, \u0022, carried ) : \u0022Shell only\u0022;\n\t\t}\n\t}\n\n\t// Foundation and the include flags are one question - what this shell is made of - and were two boxes of\n\t// icons asking it. Two grids inside one step, because a Pick is exclusive within its own grid.\n\tvoid PickInclude( ArchWorkflowStep step )\n\t{\n\t\tif ( Wants( ArchOptionGroup.Foundation ) )\n\t\t{\n\t\t\tusing var footing = ArchIconGrid.In( step.Layout );\n\n\t\t\tfooting.Pick( \u0022Raised foundation \u2014 the plinth stands proud of grade and the building sits up on it\u0022,\n\t\t\t\t\u0022foundation_raised\u0022, \u0022vertical_align_top\u0022, withFoundation, () =\u003E Answer( () =\u003E withFoundation = true, step ) );\n\n\t\t\tfooting.Pick( \u0022Nested foundation \u2014 the footing is buried and the floor sits on grade\u0022,\n\t\t\t\t\u0022foundation_nested\u0022, \u0022vertical_align_bottom\u0022, !withFoundation, () =\u003E Answer( () =\u003E withFoundation = false, step ) );\n\t\t}\n\n\t\tusing var grid = ArchIconGrid.In( step.Layout );\n\n\t\tgrid.Toggle( \u0022Floor slab\u0022, \u0022opt_floor_slab\u0022, \u0022layers\u0022, withFloor, value =\u003E Set( () =\u003E withFloor = value ) );\n\n\t\tif ( Roofed )\n\t\t{\n\t\t\tgrid.Toggle( \u0022Gutters and downpipes\u0022, \u0022opt_gutters\u0022, \u0022water_damage\u0022, withGutters, value =\u003E Set( () =\u003E withGutters = value ) );\n\t\t}\n\n\t\tif ( !Extending )\n\t\t{\n\t\t\tgrid.Toggle( \u0022Roof\u0022, \u0022opt_roof\u0022, \u0022roofing\u0022, withRoof, value =\u003E Relayout( () =\u003E withRoof = value ) );\n\t\t}\n\t}\n\n\tstring RoofName =\u003E Extending ? wingRoof.ToString() : roofStyle.ToString();\n\n\tstring RoofGlyph =\u003E Extending ? Fallback( wingRoof ) : ArchIcons.RoofStyleGlyph( roofStyle );\n\n\tvoid PickRoof( ArchWorkflowStep step )\n\t{\n\t\tif ( Extending )\n\t\t{\n\t\t\tPickWingRoof( step );\n\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Wants( ArchOptionGroup.RoofStyle ) )\n\t\t{\n\t\t\tusing var grid = ArchIconGrid.In( step.Layout );\n\n\t\t\tforeach ( var value in Enum.GetValues\u003CRoofStyle\u003E() )\n\t\t\t{\n\t\t\t\tvar captured = value;\n\n\t\t\t\tgrid.Pick( captured.ToString(), ArchIcons.RoofStyleSlug( captured ), ArchIcons.RoofStyleGlyph( captured ), roofStyle == captured,\n\t\t\t\t\t() =\u003E Answer( () =\u003E roofStyle = captured, step ) );\n\t\t\t}\n\t\t}\n\n\t\tRidge( step.Layout );\n\t}\n\n\tvoid PickWingRoof( ArchWorkflowStep step )\n\t{\n\t\tusing ( var grid = ArchIconGrid.In( step.Layout ) )\n\t\t{\n\t\t\tforeach ( var value in Enum.GetValues\u003CSectionRoof\u003E() )\n\t\t\t{\n\t\t\t\tvar captured = value;\n\n\t\t\t\tgrid.Pick( Describe( captured ), $\u0022wing_{captured}\u0022.ToLowerInvariant(), Fallback( captured ), wingRoof == captured,\n\t\t\t\t\t() =\u003E Answer( () =\u003E wingRoof = captured, step ) );\n\t\t\t}\n\t\t}\n\n\t\t// Continue takes the host\u0027s eave, and no roof has no eave to drop.\n\t\tif ( wingRoof is not (SectionRoof.Continue or SectionRoof.None) )\n\t\t{\n\t\t\tstep.Layout.Add( ArchPartUi.Number( \u0022Eave drop\u0022, eaveDrop, 0f, value =\u003E Set( () =\u003E eaveDrop = value ) ) );\n\t\t}\n\n\t\tRidge( step.Layout );\n\t}\n\n\t// Advancing IS the re-lay-out: the answer just given decides which rows exist below it, and refreshing from\n\t// inside the change would tear down the widget still handling the click.\n\tvoid Answer( Action change, ArchWorkflowStep step )\n\t{\n\t\tSet( change );\n\n\t\tstep.Chose();\n\t}\n\n\tstring StructureName =\u003E $\u0022{ArchArchetypeRules.Standing( wallHeight, Owner.Kit ):0} high\u0022;\n\n\t// The type\u0027s own numbers, editable - a wing must match the shell it joins.\n\tvoid BuildStructure( ArchWorkflowStep step )\n\t{\n\t\tstep.Layout.Add( ArchPartUi.Number( \u0022Wall height\u0022, wallHeight, 0f, value =\u003E Set( () =\u003E wallHeight = value ) ) );\n\n\t\t// Pitch describes a deck; with no roof over the section there is none.\n\t\tif ( Roofed )\n\t\t{\n\t\t\tstep.Layout.Add( ArchPartUi.Number( \u0022Roof pitch\u0022, roofPitch, 0f, value =\u003E Set( () =\u003E roofPitch = value ) ) );\n\t\t}\n\n\t\tusing var grid = ArchIconGrid.In( step.Layout );\n\n\t\tgrid.Toggle( \u0022Ceiling under the roof\u0022, \u0022opt_ceiling\u0022, \u0022square\u0022, withCeiling, value =\u003E Set( () =\u003E withCeiling = value ) );\n\n\t\tif ( !Roofed )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tgrid.Toggle( \u0022Exposed rafters and purlins under the deck\u0022, \u0022opt_roof_frame\u0022, \u0022reorder\u0022, withFrame, value =\u003E Set( () =\u003E withFrame = value ) );\n\t\tgrid.Toggle( \u0022Fascia along the eaves\u0022, \u0022opt_fascia\u0022, \u0022border_bottom\u0022, withFascia, value =\u003E Set( () =\u003E withFascia = value ) );\n\t\tgrid.Toggle( \u0022Lined soffit under the overhang\u0022, \u0022opt_soffit\u0022, \u0022flip_to_back\u0022, withSoffit, value =\u003E Set( () =\u003E withSoffit = value ) );\n\t}\n\n\tstring BearingName =\u003E MathF.Abs( Facing ) \u003C 0.001f ? \u0022Square on\u0022 : $\u0022{Facing:0.##}\u00B0\u0022;\n\n\t// What the next drag stands at. R still turns quarters on top of it while a stamp follows the cursor, so\n\t// the box is the bearing and the key is the corner.\n\tvoid BuildBearing( ArchWorkflowStep step )\n\t{\n\t\tstep.Layout.Add( ArchPartUi.Angle( \u0022Angle\u0022, placementAngle, value =\u003E placementAngle = value ) );\n\t\tstep.Layout.Add( ArchPartUi.Increment( Refresh ) );\n\t}\n\n\t// Picking re-types it - being unable to change your mind is worse than a mismatch. The designer rides the\n\t// browser\u0027s own header, where every other authored kit lives; a full-width button of its own put the way\n\t// into the designer above the thing it designs.\n\t//\n\t// The shelf and the designer hang off the SIDEBAR rather than off the step, because a step\u0027s widget is torn\n\t// down on the next refresh and a modal parented to one would go with it.\n\tvoid Types( ToolSidebarWidget panel, ArchWorkflowStep step )\n\t{\n\t\tvar offered = Offered();\n\t\tvar removed = ArchStorage.HiddenArchetypes();\n\n\t\tif ( offered.Count == 0 \u0026\u0026 removed.Count == 0 )\n\t\t{\n\t\t\tstep.Layout.Add( ArchSidebarLayout.Advice( mode == BuildingMode.Prefab\n\t\t\t\t? \u0022No building type carries a canned layout. Use New with a type chosen instead.\u0022\n\t\t\t\t: $\u0022No building types. Authored ones live in Assets/{ArchStorage.ArchetypeDirectory}.\u0022 ) );\n\n\t\t\treturn;\n\t\t}\n\n\t\tvar shelf = new ArchPresetBrowser\u003CArchArchetype\u003E( panel, Owner.Kit, Scope( \u0022types\u0022 ), \u0022Building types\u0022 )\n\t\t{\n\t\t\tChosen = type =\u003E string.Equals( archetype, type.Name, StringComparison.OrdinalIgnoreCase ),\n\t\t\tChoose = type =\u003E Adopt( type, step ),\n\t\t\tDesign = () =\u003E new ArchBuildingDesigner( panel, Owner, Chosen, designed =\u003E ApplyDesigned( designed, step ) ).Show(),\n\t\t\tReload = Reread,\n\t\t\tRemove = item =\u003E Remove( item.Value ),\n\t\t\tRestore = () =\u003E\n\t\t\t{\n\t\t\t\tArchStorage.RestoreArchetypes();\n\t\t\t\tReread();\n\t\t\t}\n\t\t};\n\n\t\tshelf.DesignButton.Visible = true;\n\t\tshelf.DesignButton.ToolTip = \u0022Design a building type\u0022;\n\t\tshelf.ReloadButton.Visible = true;\n\t\tshelf.RestoreButton.Visible = removed.Count \u003E 0;\n\t\tshelf.RestoreButton.ToolTip = $\u0022Bring back {string.Join( \u0022, \u0022, removed )}\u0022;\n\t\tshelf.Set( offered );\n\n\t\tstep.Add( shelf );\n\t}\n\n\tvoid Stamps( ToolSidebarWidget panel, ArchWorkflowStep step )\n\t{\n\t\tif ( savedOffered.Count == 0 )\n\t\t{\n\t\t\tstep.Layout.Add( ArchSidebarLayout.Advice( \u0022No saved buildings yet. Right-click a building in the Plan Layers stack and Save, and it turns up here.\u0022 ) );\n\n\t\t\treturn;\n\t\t}\n\n\t\tvar shelf = new ArchPresetBrowser\u003CArchBuildingAsset\u003E( panel, Owner.Kit, Scope( \u0022stamps\u0022 ), \u0022Saved buildings\u0022 )\n\t\t{\n\t\t\tChosen = asset =\u003E saved is not null \u0026\u0026 string.Equals( saved.Name, asset.Name, StringComparison.OrdinalIgnoreCase ),\n\t\t\tChoose = asset =\u003E\n\t\t\t{\n\t\t\t\tHold( asset );\n\n\t\t\t\tstep.Chose();\n\t\t\t},\n\t\t\tReload = RereadSaved,\n\t\t\tRemove = item =\u003E Discard( item.Value )\n\t\t};\n\n\t\tshelf.ReloadButton.Visible = true;\n\t\tshelf.Set( savedOffered );\n\n\t\tstep.Add( shelf );\n\t}\n\n\tList\u003CArchPresetItem\u003CArchBuildingAsset\u003E\u003E Kept()\n\t{\n\t\treturn ArchLayerAssets.Buildings()\n\t\t\t.Select( asset =\u003E new ArchPresetItem\u003CArchBuildingAsset\u003E\n\t\t\t{\n\t\t\t\tValue = asset,\n\t\t\t\tName = asset.Title,\n\t\t\t\tDetail = ArchBuildingStamp.Describe( asset ),\n\t\t\t\tIdentity = ArchBuildingStamp.Identity( asset ),\n\t\t\t\tCategory = \u0022Saved\u0022,\n\t\t\t\tGlyph = \u0022home_work\u0022,\n\t\t\t\tTags = asset.Name,\n\t\t\t\tView = ArchPresetPreview.Quarter,\n\t\t\t\tFocus = ArchPreviewFocus.Whole,\n\t\t\t\tRecipe = () =\u003E ArchBuildingStamp.Stage( asset )\n\t\t\t} )\n\t\t\t.ToList();\n\t}\n\n\tvoid Discard( ArchBuildingAsset asset )\n\t{\n\t\tif ( !ArchLayerAssets.RemoveBuilding( asset.Name ) )\n\t\t{\n\t\t\tLog.Warning( $\u0022Architecture: could not remove the saved building \u0027{asset.Title}\u0027.\u0022 );\n\n\t\t\treturn;\n\t\t}\n\n\t\tif ( saved is not null \u0026\u0026 string.Equals( saved.Name, asset.Name, StringComparison.OrdinalIgnoreCase ) )\n\t\t{\n\t\t\tHold( null );\n\t\t}\n\n\t\tRereadSaved();\n\t\tRefresh();\n\t}\n\n\t// The outlines are worked out here and nowhere else, because the hover asks for them every frame.\n\tvoid Hold( ArchBuildingAsset asset )\n\t{\n\t\tsaved = asset;\n\t\tsavedGhost = asset is null ? null : ArchBuildingStamp.Ghost( asset );\n\t}\n\n\tvoid RereadSaved()\n\t{\n\t\tvar chosen = saved?.Name;\n\n\t\tsavedStamp = ArchLayerAssets.Revision;\n\t\tsavedOffered = Kept();\n\n\t\tHold( savedOffered.Select( item =\u003E item.Value ).FirstOrDefault( asset =\u003E string.Equals( asset.Name, chosen, StringComparison.OrdinalIgnoreCase ) ) );\n\t}\n\n\tvoid Reread()\n\t{\n\t\tOwner.ReloadArchetypes();\n\t\tSeed();\n\t\tRefresh();\n\t}\n\n\tvoid Remove( ArchArchetype type )\n\t{\n\t\tif ( !ArchStorage.RemoveArchetype( type.Name ) )\n\t\t{\n\t\t\tLog.Warning( $\u0022Architecture: could not remove the building type \u0027{type.Name}\u0027.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tOwner.ReloadArchetypes();\n\n\t\t// A drag reads its heights off the chosen type, so removing that one has to leave another standing.\n\t\tif ( Owner.FindArchetype( archetype ) is null )\n\t\t{\n\t\t\tarchetype = Owner.Archetypes.FirstOrDefault()?.Name ?? \u0022\u0022;\n\t\t}\n\n\t\tSeed();\n\t\tRefresh();\n\t}\n\n\t// A canned layout is the whole gesture in Prefab mode, so a type without one is not a choice there.\n\tList\u003CArchPresetItem\u003CArchArchetype\u003E\u003E Offered()\n\t{\n\t\treturn Owner.Archetypes\n\t\t\t.Where( type =\u003E mode != BuildingMode.Prefab || type.HasLayout )\n\t\t\t.Select( type =\u003E new ArchPresetItem\u003CArchArchetype\u003E\n\t\t\t{\n\t\t\t\tValue = type,\n\t\t\t\tName = type.Title,\n\t\t\t\tDetail = ArchArchetypeStage.Describe( type ),\n\t\t\t\tIdentity = ArchArchetypeStage.Identity( type ),\n\t\t\t\tCategory = type.HasLayout ? \u0022Has layout\u0022 : \u0022Shell\u0022,\n\t\t\t\tBadge = type.HasLayout ? \u0022layout\u0022 : null,\n\t\t\t\tGlyph = type.Icon,\n\t\t\t\tTags = $\u0022{type.Name} {type.Description}\u0022,\n\t\t\t\tView = ArchPresetPreview.Quarter,\n\t\t\t\tFocus = ArchPreviewFocus.Whole,\n\t\t\t\tRecipe = () =\u003E ArchArchetypeStage.Of( type, Owner.Kit )\n\t\t\t} )\n\t\t\t.ToList();\n\t}\n\n\t// Shown once - re-reading it every rebuild would undo the re-type pick.\n\tvoid Inherit()\n\t{\n\t\tif ( !Extending || seeded )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Owner.ActiveBuilding()?.Archetype is { Length: \u003E 0 } existing \u0026\u0026 Owner.FindArchetype( existing ) is not null )\n\t\t{\n\t\t\tarchetype = existing;\n\t\t}\n\n\t\tSeed();\n\t}\n\n\t// Seeds every switch the type has an opinion about, so the sidebar matches the drag.\n\tvoid Adopt( ArchArchetype type, ArchWorkflowStep step )\n\t{\n\t\tarchetype = type.Name;\n\n\t\tSeed();\n\t\tRestyle();\n\n\t\tstep.Chose();\n\t}\n\n\t// Adopts like a grid pick, then re-dresses the standing section.\n\tvoid ApplyDesigned( ArchArchetype designed, ArchWorkflowStep step )\n\t{\n\t\tarchetype = designed.Name;\n\n\t\tOwner.RegisterArchetype( designed );\n\t\tSeed();\n\t\tRestyle();\n\n\t\tstep.Chose();\n\t}\n\n\tvoid Seed()\n\t{\n\t\tif ( Chosen is not { } type )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar rules = Extending ? type.Wing : type.Shell;\n\n\t\tif ( rules.Roof is { } style )\n\t\t{\n\t\t\troofStyle = style;\n\t\t}\n\n\t\twingRoof = type.WingRoof;\n\t\teaveDrop = type.WingEaveDrop;\n\t\twallHeight = rules.WallHeight;\n\t\troofPitch = rules.RoofPitch;\n\t\twithGutters = rules.Gutters ?? true;\n\t\twithFloor = rules.Floor ?? true;\n\t\twithFoundation = rules.Foundation ?? true;\n\t\twithFascia = rules.Fascia ?? true;\n\t\twithSoffit = rules.Soffit ?? true;\n\t\twithCeiling = rules.Ceiling ?? false;\n\t\twithFrame = rules.Frame ?? false;\n\n\t\tseeded = true;\n\t}\n\n\t// Its own grid inside whichever roof section called it - a ridge direction and a roof style are two\n\t// exclusive choices, and sharing one grid would unlight the style when a direction was picked.\n\tvoid Ridge( Layout roof )\n\t{\n\t\tif ( !Wants( ArchOptionGroup.Ridge ) || !Roofed\n\t\t\t|| !ArchRoofPlane.Ridged( Extending ? ArchBuild.Winged( wingRoof ) : roofStyle ) )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tusing var grid = ArchIconGrid.In( roof );\n\n\t\tforeach ( var value in Enum.GetValues\u003CRidgeRun\u003E() )\n\t\t{\n\t\t\tvar captured = value;\n\n\t\t\tgrid.Pick( ArchIcons.RidgeAdvice( captured ), ArchIcons.RidgeSlug( captured ), ArchIcons.RidgeGlyph( captured ), ridge == captured,\n\t\t\t\t() =\u003E Set( () =\u003E ridge = captured ) );\n\t\t}\n\t}\n\n\tstatic string Fallback( SectionRoof choice ) =\u003E choice switch\n\t{\n\t\tSectionRoof.Continue =\u003E \u0022merge_type\u0022,\n\t\tSectionRoof.Hip =\u003E \u0022roofing\u0022,\n\t\tSectionRoof.Gable =\u003E \u0022change_history\u0022,\n\t\tSectionRoof.Capped =\u003E \u0022crop_din\u0022,\n\t\t_ =\u003E \u0022block\u0022\n\t};\n\n\tstatic string Describe( SectionRoof choice ) =\u003E choice switch\n\t{\n\t\tSectionRoof.Continue =\u003E \u0022Continue the roof (one hip, valleys)\u0022,\n\t\tSectionRoof.Hip =\u003E \u0022Own hip, stepped down\u0022,\n\t\tSectionRoof.Gable =\u003E \u0022Own gable, stepped down\u0022,\n\t\tSectionRoof.Capped =\u003E \u0022Flat with a parapet cap\u0022,\n\t\t_ =\u003E \u0022No roof\u0022\n\t};\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Tool/Subtools/ArchOpeningUi.cs","FileName":"ArchOpeningUi.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Linq;\nusing Editor;\n\nnamespace Sunless.Architecture;\n\n// The drag options both opening tools share, laid out once so the two sidebars cannot drift apart.\npublic static class ArchOpeningUi\n{\n\t// The whole furniture control: one exclusive pick in the grid the kind\u0027s other options already stand in.\n\t// What a kind may carry comes from ArchOpeningKinds and the pitch, proud and frame are kit stock, so there\n\t// is nothing else here to offer.\n\tpublic static void Furniture( ArchIconGrid grid, OpeningKind kind, OpeningFurniture fitted, Action\u003COpeningFurniture\u003E chosen )\n\t{\n\t\tvar offered = kind.Furnishings().ToList();\n\n\t\tif ( offered.Count == 0 )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tgrid.Pick( Advice( OpeningFurniture.None ), Slug( OpeningFurniture.None ), Glyph( OpeningFurniture.None ),\n\t\t\tfitted == OpeningFurniture.None, () =\u003E chosen( OpeningFurniture.None ) );\n\n\t\tforeach ( var furniture in offered )\n\t\t{\n\t\t\tvar picked = furniture;\n\n\t\t\tgrid.Pick( Advice( picked ), Slug( picked ), Glyph( picked ), fitted == picked, () =\u003E chosen( picked ) );\n\t\t}\n\t}\n\n\t// What stands OVER the head, offered in the same grid the furniture is: both are things a hole wears, and a\n\t// second panel for the one above it would only be the same control twice.\n\tpublic static void Hood( ArchIconGrid grid, OpeningKind kind, OpeningHood worn, Action\u003COpeningHood\u003E chosen )\n\t{\n\t\tvar offered = kind.Hoods().ToList();\n\n\t\tif ( offered.Count == 0 )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tgrid.Pick( Advice( OpeningHood.None ), Slug( OpeningHood.None ), Glyph( OpeningHood.None ),\n\t\t\tworn == OpeningHood.None, () =\u003E chosen( OpeningHood.None ) );\n\n\t\tforeach ( var hood in offered )\n\t\t{\n\t\t\tvar picked = hood;\n\n\t\t\tgrid.Pick( Advice( picked ), Slug( picked ), Glyph( picked ), worn == picked, () =\u003E chosen( picked ) );\n\t\t}\n\t}\n\n\tstatic string Slug( OpeningHood hood ) =\u003E $\u0022opt_hood_{hood}\u0022.ToLowerInvariant();\n\n\tstatic string Advice( OpeningHood hood ) =\u003E hood switch\n\t{\n\t\tOpeningHood.Cornice =\u003E \u0022Moulded hood on corbels over the head\u0022,\n\t\tOpeningHood.Pediment =\u003E \u0022Hood under a gable over the head\u0022,\n\t\t_ =\u003E \u0022Bare head\u0022\n\t};\n\n\tstatic string Glyph( OpeningHood hood ) =\u003E hood switch\n\t{\n\t\tOpeningHood.Cornice =\u003E \u0022horizontal_split\u0022,\n\t\tOpeningHood.Pediment =\u003E \u0022change_history\u0022,\n\t\t_ =\u003E \u0022block\u0022\n\t};\n\n\t// Named for the answer line a workflow row reads back, so the tile and the closed step agree.\n\tpublic static string Name( OpeningFurniture furniture ) =\u003E furniture switch\n\t{\n\t\tOpeningFurniture.Bars =\u003E \u0022Burglar bars\u0022,\n\t\tOpeningFurniture.Boarded =\u003E \u0022Boarded over\u0022,\n\t\tOpeningFurniture.Shutter =\u003E \u0022Roller shutter\u0022,\n\t\tOpeningFurniture.Gate =\u003E \u0022Security gate\u0022,\n\t\tOpeningFurniture.Leaves =\u003E \u0022Louvred shutters\u0022,\n\t\t_ =\u003E \u0022Nothing\u0022\n\t};\n\n\tstatic string Slug( OpeningFurniture furniture ) =\u003E $\u0022opt_furniture_{furniture}\u0022.ToLowerInvariant();\n\n\tstatic string Advice( OpeningFurniture furniture ) =\u003E furniture switch\n\t{\n\t\tOpeningFurniture.Bars =\u003E \u0022Burglar bars across it\u0022,\n\t\tOpeningFurniture.Boarded =\u003E \u0022Boarded over with planks\u0022,\n\t\tOpeningFurniture.Shutter =\u003E \u0022Roller shutter down over it\u0022,\n\t\tOpeningFurniture.Gate =\u003E \u0022Security gate across it\u0022,\n\t\tOpeningFurniture.Leaves =\u003E \u0022Louvred shutters hinged either side\u0022,\n\t\t_ =\u003E \u0022Nothing over it\u0022\n\t};\n\n\t// Classic Material Icons only - a glyph the editor\u0027s older set does not know draws an empty box.\n\tpublic static string Glyph( OpeningFurniture furniture ) =\u003E furniture switch\n\t{\n\t\tOpeningFurniture.Bars =\u003E \u0022fence\u0022,\n\t\tOpeningFurniture.Boarded =\u003E \u0022table_rows\u0022,\n\t\tOpeningFurniture.Shutter =\u003E \u0022density_small\u0022,\n\t\tOpeningFurniture.Gate =\u003E \u0022grid_on\u0022,\n\t\tOpeningFurniture.Leaves =\u003E \u0022menu_open\u0022,\n\t\t_ =\u003E \u0022block\u0022\n\t};\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Tool/Subtools/ArchPorchSubtool.cs","FileName":"ArchPorchSubtool.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Editor;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n[Title( \u0022Porch\u0022 ), Icon( \u0022deck\u0022 ), Group( \u002213\u0022 )]\npublic sealed class ArchPorchSubtool( ArchTool owner ) : ArchSubtool( owner )\n{\n\tprotected override ArchKind? DraftKind =\u003E ArchKind.Porch;\n\n\tpublic override ArchSurface[] Surfaces =\u003E new[] { ArchSurface.Deck, ArchSurface.Railing, ArchSurface.Baseboard, ArchSurface.Roof, ArchSurface.Soffit };\n\n\treadonly ArchPorchPart draft = new() { GradeHeight = -24f };\n\n\tprotected override string Title() =\u003E \u0022Porch\u0022;\n\n\tprotected override string Advice() =\u003E \u0022Drag the deck over the wall it hangs off.\u0022;\n\n\tpublic override void OnEnabled()\n\t{\n\t\tbase.OnEnabled();\n\n\t\tArchWorkflow.Restart( Scope( \u0022flow\u0022 ) );\n\t}\n\n\t// Resolved from the shape, not the drag, so the ghost matches the generator.\n\tprotected override void DrawPreview()\n\t{\n\t\tvar min = Min( DragStart, DragCurrent );\n\t\tvar max = Max( DragStart, DragCurrent );\n\t\tvar room = Owner.RoomAt( (min \u002B max) * 0.5f, out var building );\n\t\tvar deck = Owner.LevelHeight;\n\t\tvar legs = ArchPorch.Preview( building, room, Owner.Kit, min, max, draft.Standing, out var standing, out var joining );\n\n\t\tif ( legs.Count == 0 )\n\t\t{\n\t\t\tArchGhost.Plate( min, max, deck, 8 );\n\t\t\tArchGhost.Note( new Vector3( max.x, max.y, deck \u002B 60f ), \u0022porch \u2014 nowhere to stand a deck there\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\tvar sketch = Sketch( legs, standing );\n\t\tvar shape = ArchPorchShape.Resolve( sketch, room, building, Owner.Kit );\n\t\tvar head = shape.Head;\n\t\tvar rail = deck \u002B Owner.Kit.PorchRailHeight;\n\n\t\tforeach ( var leg in legs )\n\t\t{\n\t\t\tif ( draft.Deck )\n\t\t\t{\n\t\t\t\tArchGhost.Volume( leg.Min, leg.Max, deck - draft.DeckDrop, deck );\n\t\t\t}\n\n\t\t\tArchGhost.Plate( leg.Min, leg.Max, deck, 8 );\n\t\t}\n\n\t\tforeach ( var bay in ArchPorchGen.Bays( shape, sketch, Owner.Kit ) )\n\t\t{\n\t\t\tif ( draft.Posts )\n\t\t\t{\n\t\t\t\tArchGhost.Post( bay.From.Point, Owner.Kit.PorchPostSize, deck, head );\n\n\t\t\t\tif ( bay.Last )\n\t\t\t\t{\n\t\t\t\t\tArchGhost.Post( bay.To.Point, Owner.Kit.PorchPostSize, deck, head );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( draft.Railings \u0026\u0026 !bay.Gated )\n\t\t\t{\n\t\t\t\tGizmo.Draw.Line( bay.From.Raised.WithZ( rail ), bay.To.Raised.WithZ( rail ) );\n\t\t\t}\n\t\t}\n\n\t\tif ( draft.Roofed )\n\t\t{\n\t\t\tArchGhost.Ring( shape.Footprint, head \u002B Owner.Kit.PorchBeamDepth );\n\t\t}\n\n\t\tArchGhost.Note( new Vector3( max.x, max.y, head ), Note( legs, standing, joining ) );\n\t}\n\n\tstatic string Note( IReadOnlyList\u003CArchPorchLeg\u003E legs, PorchStanding standing, ArchPorchPart joining )\n\t{\n\t\tif ( joining is not null )\n\t\t{\n\t\t\treturn $\u0022porch \u2014 joining {joining.Name}\u0022;\n\t\t}\n\n\t\tif ( standing == PorchStanding.Free )\n\t\t{\n\t\t\treturn \u0022porch \u2014 free-standing, no wall to lean on\u0022;\n\t\t}\n\n\t\treturn legs.Count \u003E 1 ? $\u0022porch \u2014 {legs.Count} legs, wrapping the corner\u0022 : \u0022porch\u0022;\n\t}\n\n\t// Unattached to the plan, so the ghost resolves exactly what Attach will - the gates in its railing\n\t// included, which is why it carries the flight the placement is about to seed.\n\tArchPorchPart Sketch( List\u003CArchPorchLeg\u003E legs, PorchStanding standing )\n\t{\n\t\tvar deck = Owner.LevelHeight;\n\n\t\tvar sketch = new ArchPorchPart\n\t\t{\n\t\t\tLegs = legs,\n\t\t\tStanding = standing,\n\t\t\tBaseHeight = deck,\n\t\t\tGradeHeight = deck - draft.DeckDrop,\n\t\t\tHeadHeight = MathF.Max( 60f, draft.HeadHeight ),\n\t\t\tPostSpacing = MathF.Max( 32f, Owner.Kit.PorchPostSize * 8f ),\n\t\t\tDeck = draft.Deck,\n\t\t\tPosts = draft.Posts,\n\t\t\tRafters = draft.Rafters,\n\t\t\tBraces = draft.Braces,\n\t\t\tPitch = draft.Pitch,\n\t\t\tRailings = draft.Railings,\n\t\t\tSteps = draft.Steps,\n\t\t\tRoofed = draft.Roofed\n\t\t};\n\n\t\treturn sketch;\n\t}\n\n\tprotected override void OnDrag( Vector2 from, Vector2 to )\n\t{\n\t\tvar min = Min( from, to );\n\t\tvar max = Max( from, to );\n\n\t\tif ( (max - min).Length \u003C 24f )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar room = Owner.RoomAt( (min \u002B max) * 0.5f, out var building );\n\t\tvar placement = ArchPorch.Attach( Owner.Plan, building, room, Owner.Kit, min, max, draft );\n\n\t\tif ( placement is null )\n\t\t{\n\t\t\tLog.Info( \u0022Architecture: no room under that drag for a porch to stand in.\u0022 );\n\t\t\treturn;\n\t\t}\n\n\t\t// A porch is a cluster of legs, not one part - the draft\u0027s work is done either way.\n\t\tFinishDraft();\n\t\tOwner.Commit( \u0022Create Porch\u0022 );\n\t}\n\n\t// One list: what the deck leans on and whether anything covers it - both picks - and then the three typed rows\n\t// that can never answer. A porch is a cluster of legs rather than one part, so nothing here is ever pointed at\n\t// a selection: a standing porch is edited from its own sheet in Select.\n\tprotected override void BuildOptions( ToolSidebarWidget panel )\n\t{\n\t\tusing var flow = ArchWorkflow.In( panel, Scope( \u0022flow\u0022 ), Refresh );\n\n\t\tflow.Step( \u0022Standing\u0022, ArchPorchUi.StandingName( draft ), ArchPorchUi.StandingGlyph( draft ), step =\u003E\n\t\t\tArchPorchUi.Standing( step.Layout, draft, step.Chose, null ) );\n\n\t\tflow.Step( \u0022Roof\u0022, draft.Roofed ? \u0022Roofed\u0022 : \u0022Open\u0022, draft.Roofed ? \u0022roofing\u0022 : \u0022deck\u0022, step =\u003E\n\t\t\tArchPorchUi.Roof( step.Layout, draft, step.Chose, null ) );\n\n\t\tflow.Step( \u0022Include\u0022, IncludeName, \u0022checklist\u0022, step =\u003E ArchPorchUi.Include( step.Layout, draft, null ) );\n\n\t\tflow.Step( \u0022Frame\u0022, FrameName, \u0022view_column\u0022, step =\u003E ArchPorchUi.Frame( step.Layout, draft, null ) );\n\n\t\tflow.Step( \u0022Placement\u0022, $\u0022{draft.DeckDrop:0} drop\u0022, \u0022height\u0022, step =\u003E ArchPorchUi.Placement( step.Layout, draft, null ) );\n\t}\n\n\tstring IncludeName =\u003E ArchPartUi.Listed(\n\t\t(draft.Deck, \u0022Deck\u0022), (draft.Rafters, \u0022Rafters\u0022), (draft.Braces, \u0022Braces\u0022), (draft.Railings, \u0022Railing\u0022), (draft.Door, \u0022Door\u0022) );\n\n\tstring FrameName =\u003E ArchPartUi.Listed( (draft.Plinth, \u0022Plinth\u0022), (draft.Posts, \u0022Posts\u0022), (draft.Beam, \u0022Beam\u0022) );\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Tool/Subtools/ArchSpanSubtool.cs","FileName":"ArchSpanSubtool.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing Editor;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n// A span crosses between two piers, so it belongs to the tool that stands them: this is the Pillars subtool\u0027s\n// third gesture, composed the way ArchFixtureSubtool is composed by the bool tool, and it holds no shelf entry\n// of its own. Keeping the form in its own file keeps the two-click bridge out of the placement tool it rides in.\n[Title( \u0022Spans\u0022 ), Icon( \u0022bridge\u0022 ), Group( \u002209\u0022 )]\npublic sealed class ArchSpanSubtool( ArchTool owner ) : ArchSubtool( owner )\n{\n\tprotected override ArchKind? DraftKind =\u003E ArchKind.Span;\n\n\tpublic override ArchSurface[] Surfaces =\u003E new[] { ArchSurface.PillarCap };\n\n\tArchSpanPart draft = ArchSpanMemo.Recall();\n\n\tArchSpanEnd taken;\n\n\tpublic ArchSpanPart LastPlaced { get; private set; }\n\n\tpublic ArchKind? MergedDraftKind =\u003E DraftKind;\n\n\tpublic string MergedAdvice =\u003E Advice();\n\n\tpublic ArchRunState MergedRun =\u003E Run();\n\n\tpublic bool MergedAdjusts =\u003E Adjusts;\n\n\tpublic void MergedCancel() =\u003E CancelRun();\n\n\tpublic void MergedHover( Vector2 point ) =\u003E DrawHover( point );\n\n\t// The host does the draft bookkeeping and the commit, exactly as the bool tool does for a fixture: this\n\t// returns what it stood and nothing else, so there is one place a placement is finished.\n\tpublic ArchSpanPart MergedClick( Vector2 point )\n\t{\n\t\tLastPlaced = null;\n\n\t\tOnClick( point );\n\n\t\treturn LastPlaced;\n\t}\n\n\tprotected override bool UsesDrag =\u003E false;\n\n\t// Only over a span. The selection outlives the tool that made it, so an unconditional yes put the LAST\n\t// pillar\u0027s box dragger on screen the moment this tool was picked - and the gizmo takes the press first, so\n\t// every click meant for a pier went into a widget belonging to something else.\n\tprotected override bool Adjusts =\u003E Owner.Picked?.Item is ArchSpanPart;\n\n\tprotected override string Title() =\u003E \u0022Spans\u0022;\n\n\tprotected override string Advice() =\u003E taken is null\n\t\t? \u0022Click a column or a wall to take one end. The nearest column within reach wins, so an eyeballed click still lands on the pier.\u0022\n\t\t: \u0022Click the far column or wall and it bridges them. An end that lands on nothing stays where it was dropped.\u0022;\n\n\tprotected override ArchRunState Run() =\u003E new( taken is not null, \u0022one end taken\u0022 );\n\n\tprotected override void CancelRun() =\u003E taken = null;\n\n\tprotected override void DrawHover( Vector2 point )\n\t{\n\t\tvar room = Owner.RoomOnLevel( point );\n\n\t\tif ( room is null )\n\t\t{\n\t\t\tbase.DrawHover( point );\n\n\t\t\treturn;\n\t\t}\n\n\t\tvar landing = ArchSpanShape.Anchored( Owner.Plan, room, point );\n\n\t\tPier( room, landing, false );\n\n\t\tif ( taken is null )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tPier( room, taken, true );\n\t\tReaching( room, taken, landing );\n\t}\n\n\tprotected override void OnClick( Vector2 point )\n\t{\n\t\tvar room = Owner.RoomOnLevel( point );\n\n\t\tif ( room is null )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar landing = ArchSpanShape.Anchored( Owner.Plan, room, point );\n\n\t\tif ( taken is null )\n\t\t{\n\t\t\ttaken = landing;\n\n\t\t\treturn;\n\t\t}\n\n\t\tBridge( room, taken, landing );\n\t}\n\n\tvoid Bridge( ArchRoom room, ArchSpanEnd from, ArchSpanEnd to )\n\t{\n\t\tif ( Standing( room, from, to ) is not { } span || !ArchSpanShape.Resolve( Owner.Plan, Owner.Kit, room, span, out _ ) )\n\t\t{\n\t\t\tLog.Info( \u0022Architecture: nothing to bridge there \u2014 a span needs two ends apart, each under a pier tall enough to spring off.\u0022 );\n\n\t\t\treturn;\n\t\t}\n\n\t\tspan.Id = Owner.Plan.AllocateId();\n\t\tspan.Name = $\u0022Span{room.PierSpans.Count \u002B 1}\u0022;\n\n\t\troom.PierSpans.Add( span );\n\t\tOwner.Picked = new ArchSelection { Item = span, Room = room };\n\n\t\tLastPlaced = span;\n\t\ttaken = null;\n\t}\n\n\t// A ring on the pier\u0027s HEAD and a stem down to the floor: where the end will spring from and which column it\n\t// grabbed, drawn as a mark on something rather than as a post, which is a pillar ghost by another name.\n\tvoid Pier( ArchRoom room, ArchSpanEnd end, bool held )\n\t{\n\t\tvar pier = ArchSpanShape.Pier( Owner.Plan, Owner.Kit, room, end );\n\t\tvar lift = Lift( room );\n\t\tvar head = new Vector3( pier.At.x, pier.At.y, pier.Head \u002B lift );\n\n\t\tGizmo.Draw.LineThickness = held ? 4f : 3f;\n\t\tGizmo.Draw.Color = held ? ArchGhost.Accent : ArchGhost.Line;\n\t\tGizmo.Draw.LineCircle( head, Vector3.Up, held ? 14f : 10f );\n\t\tGizmo.Draw.Line( head, head.WithZ( room.BaseHeight \u002B lift ) );\n\t\tGizmo.Draw.LineThickness = 2f;\n\n\t\tif ( !pier.Standing )\n\t\t{\n\t\t\tArchGhost.Cursor( pier.At, room.BaseHeight \u002B lift, ArchSpanShape.Grab );\n\t\t}\n\n\t\tGizmo.Draw.Color = ArchGhost.Line;\n\t}\n\n\t// The plan is authored flat and the building is poured onto its grade, so a ghost drawn at plan height stands\n\t// a foundation below the span it is previewing.\n\tfloat Lift( ArchRoom room )\n\t{\n\t\tvar host = Owner.Plan.OwnerOf( room ) ?? Owner.ActiveBuilding();\n\n\t\treturn host is null ? 0f : ArchAsks.Lift( Owner.Plan, host, Owner.Kit );\n\t}\n\n\tvoid Reaching( ArchRoom room, ArchSpanEnd from, ArchSpanEnd to )\n\t{\n\t\tvar lift = Lift( room );\n\n\t\tif ( Standing( room, from, to ) is not { } span\n\t\t\t|| !ArchSpanShape.Resolve( Owner.Plan, Owner.Kit, room, span, out var run ) )\n\t\t{\n\t\t\tGizmo.Draw.Line(\n\t\t\t\tnew Vector3( from.At.x, from.At.y, room.BaseHeight \u002B lift ),\n\t\t\t\tnew Vector3( to.At.x, to.At.y, room.BaseHeight \u002B lift ) );\n\n\t\t\treturn;\n\t\t}\n\n\t\tvar start = new Vector3( run.From.x, run.From.y, run.Springing \u002B lift );\n\t\tvar end = new Vector3( run.To.x, run.To.y, run.Springing \u002B lift );\n\n\t\tvar top = run.Top \u002B lift;\n\n\t\tGizmo.Draw.LineThickness = 4f;\n\t\tGizmo.Draw.Color = ArchGhost.Accent;\n\n\t\tGizmo.Draw.Line( start, end );\n\t\tGizmo.Draw.Line( start.WithZ( top ), end.WithZ( top ) );\n\t\tGizmo.Draw.Line( start, start.WithZ( top ) );\n\t\tGizmo.Draw.Line( end, end.WithZ( top ) );\n\n\t\tGizmo.Draw.LineThickness = 2f;\n\t\tGizmo.Draw.Color = ArchGhost.Line;\n\n\t\tArchGhost.Note( Vector3.Lerp( start.WithZ( top ), end.WithZ( top ), 0.5f ),\n\t\t\t$\u0022{span.Form} \u2014 {(run.To - run.From).Length:0} across, {run.Top - run.Springing:0} deep, springs at {run.Springing - room.BaseHeight:0}\u0022 );\n\t}\n\n\t// ONE resolution the ghost and the placed part both read, so what the gesture showed is what the mesh comes out as.\n\tArchSpanPart Standing( ArchRoom room, ArchSpanEnd from, ArchSpanEnd to )\n\t{\n\t\tif ( room is null )\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\tvar span = draft.Dressing();\n\n\t\tspan.Level = room.Floor;\n\t\tspan.From = from;\n\t\tspan.To = to;\n\n\t\treturn span;\n\t}\n\n\t// The host lays the rows: a bridge is the Pillars tool\u0027s third gesture, so its two questions belong in that\n\t// tool\u0027s one sequence rather than in a second list numbered from 1 underneath it.\n\tpublic void MergedSteps( ArchWorkflow flow )\n\t{\n\t\tvar span = Edited;\n\t\tvar changed = Changed( span );\n\n\t\tflow.Step( \u0022Form\u0022, span.Form.ToString(), ArchSpanUi.Glyph( span.Form ), step =\u003E\n\t\t\tArchSpanUi.Form( step.Layout, span, step.Chose, changed ) );\n\n\t\tflow.Step( \u0022Section\u0022, ArchSpanUi.Measured( span ), \u0022straighten\u0022, step =\u003E\n\t\t\tArchSpanUi.Section( step.Layout, span, changed ) );\n\t}\n\n\t// The picked span if there is one, else the seed for the next bridge - the same two steps either way, which is\n\t// what keeps a tuned span and the next one drawn from agreeing.\n\tpublic ArchSpanPart Edited =\u003E Editing() ?? draft;\n\n\tpublic string PickedName =\u003E Editing()?.Name;\n\n\tAction Changed( ArchSpanPart span )\n\t{\n\t\treturn Editing() is null\n\t\t\t? () =\u003E ArchSpanMemo.Remember( draft )\n\t\t\t: () =\u003E { Seed( span ); Owner.Touch( \u0022Edit Span\u0022 ); };\n\t}\n\n\t// Tuning the span that was just placed IS the author saying how the next one should look - the panel points at\n\t// the selection, so without this every adjustment was spent on one part and the seed never moved.\n\tvoid Seed( ArchSpanPart span )\n\t{\n\t\tdraft = span.Dressing();\n\n\t\tArchSpanMemo.Remember( draft );\n\t}\n\n\tArchSpanPart Editing() =\u003E Owner.Picked?.Item as ArchSpanPart;\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Data/ArchPlan.cs","FileName":"ArchPlan.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\npublic sealed class ArchPlan\n{\n\tpublic int Version { get; set; } = 3;\n\tpublic int NextId { get; set; } = 1;\n\tpublic string KitName { get; set; } = \u0022default\u0022;\n\n\t// Every top-level thing standing on the map, in stack order, whatever kind it is.\n\tpublic List\u003CArchUnit\u003E Units { get; set; } = new();\n\n\t// Version 2 filed the two kinds in lists of their own. Read back under those names and folded into Units by\n\t// Normalize, so an old plan opens whole and is written back carrying one list.\n\t[JsonPropertyName( \u0022Buildings\u0022 )]\n\tpublic List\u003CArchBuilding\u003E LegacyBuildings { get; set; }\n\n\t[JsonPropertyName( \u0022Roads\u0022 )]\n\tpublic List\u003CArchRoadPart\u003E LegacyRoads { get; set; }\n\n\t// A view over the one list, so a pass that only cares about buildings still reads naturally. Filed through\n\t// Units - adding to a view would go nowhere, which is why it is not a List. Every other kind\u0027s view is shipped\n\t// by the module that owns it, so core names one type here and no more.\n\t[JsonIgnore]\n\tpublic IReadOnlyList\u003CArchBuilding\u003E Buildings =\u003E Units.OfType\u003CArchBuilding\u003E().ToList();\n\n\t// Cross-building relationships and placed reusable assets.\n\tpublic List\u003CArchSiteAssembly\u003E Assemblies { get; set; } = new();\n\tpublic List\u003CArchAssetInstance\u003E Instances { get; set; } = new();\n\t// Explicit layer metadata - written only when a legacy item is reparented, disabled or locked.\n\tpublic List\u003CArchLayerRecord\u003E Layers { get; set; } = new();\n\t// What an author said about one named PIECE of a layer - a roof\u0027s fascia, a porch\u0027s balustrade.\n\tpublic List\u003CArchPartRecord\u003E Parts { get; set; } = new();\n\tpublic List\u003CArchLayerLink\u003E Links { get; set; } = new();\n\n\tpublic int AllocateId() =\u003E NextId\u002B\u002B;\n\n\tpublic IEnumerable\u003CArchRoom\u003E AllRooms() =\u003E Buildings.SelectMany( building =\u003E building.Rooms );\n\n\tpublic ArchBuilding FindBuilding( int id ) =\u003E Buildings.FirstOrDefault( building =\u003E building.Id == id );\n\n\tpublic ArchRoom FindRoom( int id ) =\u003E AllRooms().FirstOrDefault( room =\u003E room.Id == id );\n\n\tpublic ArchBuilding OwnerOf( ArchRoom room ) =\u003E Buildings.FirstOrDefault( building =\u003E building.Rooms.Contains( room ) );\n\n\t// Buildings first, then roads, the order version 2 wrote them in. Run before anything counts ids, and it\n\t// clears the legacy lists so a plan opened and saved again carries Units alone.\n\tvoid Adopt()\n\t{\n\t\tif ( LegacyBuildings is { Count: \u003E 0 } )\n\t\t{\n\t\t\tUnits.AddRange( LegacyBuildings );\n\t\t}\n\n\t\tif ( LegacyRoads is { Count: \u003E 0 } )\n\t\t{\n\t\t\tUnits.AddRange( LegacyRoads );\n\t\t}\n\n\t\tLegacyBuildings = null;\n\t\tLegacyRoads = null;\n\t}\n\n\t// Empty can never be a count - a placement seeds a target before it knows whether it will fill it. A road being\n\t// drawn holds one node and no content yet, and blanking the file under it is how an authored plan is lost.\n\t[JsonIgnore]\n\tpublic bool HasContent =\u003E this.Roads().Count \u003E 0 || Units.Any( unit =\u003E unit.HasContent );\n\n\t// A placement target that was seeded and never filled is not authored content, so it must not survive\n\t// the commit - it would stand in the layer stack as an empty Building/Level/Room nobody drew.\n\tpublic int DiscardEmptyTargets()\n\t{\n\t\tvar dropped = 0;\n\n\t\tforeach ( var building in Buildings )\n\t\t{\n\t\t\tdropped \u002B= building.Rooms.RemoveAll( room =\u003E !room.HasContent );\n\t\t}\n\n\t\treturn dropped \u002B Units.RemoveAll( unit =\u003E !unit.HasContent );\n\t}\n\n\tpublic void Normalize()\n\t{\n\t\tAdopt();\n\n\t\tNextId = System.Math.Max( 1, HighestId() \u002B 1 );\n\n\t\tforeach ( var building in Buildings )\n\t\t{\n\t\t\t// Plans saved before fences carried a curve - lift the old path onto nodes once.\n\t\t\tforeach ( var fence in building.Fences.Where( entry =\u003E entry.Path.Count \u003E= 2 \u0026\u0026 entry.Nodes.Count == 0 ) )\n\t\t\t{\n\t\t\t\tfence.Nodes = fence.Path.Select( ArchCurveNode.At ).ToList();\n\t\t\t\tfence.Path = new List\u003CVector3\u003E();\n\t\t\t}\n\n\t\t\t// Plans saved before a stair was a shaft: the chain of overlapping boxes is read once into the core\n\t\t\t// that bounds them and the flights that stood in it, then let go of. Everything downstream has only\n\t\t\t// ever seen the resolve, so a converted stair builds exactly what it built before.\n\t\t\tforeach ( var room in building.Rooms )\n\t\t\t{\n\t\t\t\tforeach ( var stair in room.Stairs )\n\t\t\t\t{\n\t\t\t\t\tReshaft( stair );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var platform in building.Platforms )\n\t\t\t{\n\t\t\t\tforeach ( var stair in platform.Stairs )\n\t\t\t\t{\n\t\t\t\t\tReshaft( stair );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var porch in building.Rooms.SelectMany( room =\u003E room.Porches ) )\n\t\t\t{\n\t\t\t\tforeach ( var stair in porch.Stairs )\n\t\t\t\t{\n\t\t\t\t\tReshaft( stair );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tforeach ( var building in Buildings )\n\t\t{\n\t\t\tforeach ( var room in building.Rooms )\n\t\t\t{\n\t\t\t\troom.Walls.RemoveAll( wall =\u003E wall.Length \u003C 1f );\n\t\t\t}\n\n\t\t\tforeach ( var roof in building.Roofs )\n\t\t\t{\n\t\t\t\troof.Walls.RemoveAll( wall =\u003E wall.Length \u003C 1f );\n\t\t\t}\n\n\t\t\tforeach ( var roof in building.Roofs.Where( roof =\u003E roof.HasFootprint ) )\n\t\t\t{\n\t\t\t\troof.Reshape( roof.Footprint );\n\t\t\t}\n\n\t\t\tSeparateSingleSpanRoofs( building );\n\t\t}\n\n\t\t// And whatever a KIND says its own parts need fixing up, which is how a contributed kind migrates a plan\n\t\t// written before it changed shape. Every pass must be idempotent: Normalize runs on every load and again\n\t\t// after every connection resolve.\n\t\tforeach ( var fixup in ArchKinds.Load().All.OfType\u003CIArchNormalizes\u003E() )\n\t\t{\n\t\t\tfixup.Normalize( this );\n\t\t}\n\t}\n\n\t// Two migrations, each of which runs exactly once per plan however many times Normalize is called, because\n\t// each lets go of what it read.\n\t//\n\t// A stair authored as a chain of overlapping boxes is read into the shaft that bounds them - a stair saved\n\t// before there were legs at all still gets a shaft, because a core of nothing builds nothing. Then a stair\n\t// authored when a landing was DERIVED has that derive run one last time and left behind as real landing\n\t// steps, so nothing downstream ever works a pad out again.\n\tvoid Reshaft( ArchStairPart stair )\n\t{\n\t\tif ( stair.Legs is { Count: \u003E 0 } legs )\n\t\t{\n\t\t\tvar (core, lanes) = ArchStairLanes.FromLegs( legs );\n\n\t\t\tcore.Rise = MathF.Max( 4f, stair.StoredRise \u003E 1f ? stair.StoredRise : stair.Core?.Rise ?? 0f );\n\n\t\t\tstair.Core = core;\n\t\t\tstair.Lanes = lanes;\n\t\t\tstair.Legs = null;\n\t\t}\n\n\t\tstair.StoredRise = 0f;\n\n\t\tArchStairLanes.Settle( this, stair );\n\t\tArchStairLanes.Number( this, stair );\n\t}\n\n\tvoid SeparateSingleSpanRoofs( ArchBuilding building )\n\t{\n\t\tfor ( var roofIndex = building.Roofs.Count - 1; roofIndex \u003E= 0; roofIndex-- )\n\t\t{\n\t\t\tvar roof = building.Roofs[roofIndex];\n\t\t\tvar outline = roof.Outline();\n\n\t\t\tif ( roof.Style is not (RoofStyle.Gable or RoofStyle.Shed or RoofStyle.Sawtooth) ||\n\t\t\t\t!roof.HasFootprint ||\n\t\t\t\toutline.Count == 4 )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tvar rooms = building.Rooms\n\t\t\t\t.Where( room =\u003E room.Floor == roof.Level )\n\t\t\t\t.Select( room =\u003E (Room: room, Footprint: ArchFloorGen.Footprint( room )) )\n\t\t\t\t.Where( candidate =\u003E candidate.Footprint.Count == 4 )\n\t\t\t\t.Where( candidate =\u003E ArchRegion.Covers( new[] { outline }, candidate.Footprint ) )\n\t\t\t\t.ToList();\n\n\t\t\trooms.RemoveAll( candidate =\u003E rooms.Any( other =\u003E\n\t\t\t\t!ReferenceEquals( candidate.Room, other.Room ) \u0026\u0026\n\t\t\t\tMathF.Abs( ArchFootprint.SignedArea( other.Footprint ) ) \u003E MathF.Abs( ArchFootprint.SignedArea( candidate.Footprint ) ) \u0026\u0026\n\t\t\t\tArchRegion.Covers( new[] { other.Footprint }, candidate.Footprint ) ) );\n\n\t\t\tvar occupied = ArchFootprint.Union( rooms.Select( candidate =\u003E candidate.Footprint ).ToList() );\n\n\t\t\tif ( rooms.Count \u003C 2 ||\n\t\t\t\t!ArchRegion.Covers( occupied, outline ) ||\n\t\t\t\toccupied.Any( loop =\u003E !ArchRegion.Covers( new[] { outline }, loop ) ) )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tbuilding.Roofs.RemoveAt( roofIndex );\n\n\t\t\tfor ( var roomIndex = rooms.Count - 1; roomIndex \u003E= 0; roomIndex-- )\n\t\t\t{\n\t\t\t\tvar section = roof.Duplicate( roomIndex == 0 ? roof.Id : AllocateId(), rooms[roomIndex].Footprint );\n\t\t\t\tbuilding.Roofs.Insert( roofIndex, section );\n\t\t\t}\n\t\t}\n\t}\n\n\tint HighestId()\n\t{\n\t\tvar ids = new List\u003Cint\u003E { 0 };\n\t\tvar roads = this.Roads();\n\n\t\tids.AddRange( Assemblies.Select( assembly =\u003E assembly.Id ) );\n\t\tids.AddRange( Instances.Select( instance =\u003E instance.Id ) );\n\t\tids.AddRange( roads.Select( road =\u003E road.Id ) );\n\t\tids.AddRange( roads.SelectMany( road =\u003E road.Crossings ).Select( crossing =\u003E crossing.Id ) );\n\t\tids.AddRange( roads.SelectMany( road =\u003E road.Bridges ).Select( bridge =\u003E bridge.Id ) );\n\t\tids.AddRange( roads.SelectMany( road =\u003E road.Tunnels ).Select( tunnel =\u003E tunnel.Id ) );\n\t\tids.AddRange( roads.SelectMany( road =\u003E road.Cuts ).Select( cut =\u003E cut.Id ) );\n\n\t\tforeach ( var building in Buildings )\n\t\t{\n\t\t\tids.Add( building.Id );\n\t\t\tids.AddRange( building.Rooms.Select( room =\u003E room.Id ) );\n\t\t\tids.AddRange( building.Rooms.SelectMany( room =\u003E room.Stairs ).Select( stair =\u003E stair.Id ) );\n\t\t\tids.AddRange( building.Rooms.SelectMany( room =\u003E room.Trims ).Select( trim =\u003E trim.Id ) );\n\t\t\tids.AddRange( building.Rooms.SelectMany( room =\u003E room.Pillars ).Select( pillar =\u003E pillar.Id ) );\n\t\t\tids.AddRange( building.Rooms.SelectMany( room =\u003E room.PierSpans ).Select( span =\u003E span.Id ) );\n\t\t\tids.AddRange( building.Rooms.SelectMany( room =\u003E room.Beams ).Select( beam =\u003E beam.Id ) );\n\t\t\t// The porch is a host, so its own children are in here too - miss them and an id is handed out\n\t\t\t// twice, which in the scene is one object standing where two were meant to.\n\t\t\tvar porches = building.Rooms.SelectMany( room =\u003E room.Porches ).ToList();\n\n\t\t\tids.AddRange( porches.Select( porch =\u003E porch.Id ) );\n\t\t\tids.AddRange( porches.SelectMany( porch =\u003E porch.Stairs ).Select( stair =\u003E stair.Id ) );\n\t\t\tids.AddRange( porches.SelectMany( porch =\u003E porch.Pillars ).Select( pillar =\u003E pillar.Id ) );\n\t\t\tids.AddRange( porches.SelectMany( porch =\u003E porch.Trims ).Select( trim =\u003E trim.Id ) );\n\t\t\tids.AddRange( building.Rooms.SelectMany( room =\u003E room.Approaches ).Select( approach =\u003E approach.Id ) );\n\t\t\tids.AddRange( building.Roofs.Select( roof =\u003E roof.Id ) );\n\t\t\tids.AddRange( building.Roofs.SelectMany( roof =\u003E roof.Lights ).Select( light =\u003E light.Id ) );\n\t\t\tids.AddRange( building.Fences.Select( fence =\u003E fence.Id ) );\n\t\t\tids.AddRange( building.Downpipes.Select( pipe =\u003E pipe.Id ) );\n\t\t\tids.AddRange( building.Pipes.Select( run =\u003E run.Id ) );\n\t\t\tids.AddRange( building.Pipes.SelectMany( run =\u003E run.Nodes ).Select( node =\u003E node.Id ) );\n\t\t\tids.AddRange( building.Brackets.Select( bracket =\u003E bracket.Id ) );\n\t\t\tids.AddRange( building.Ladders.Select( ladder =\u003E ladder.Id ) );\n\t\t\tids.AddRange( building.Balconies.Select( balcony =\u003E balcony.Id ) );\n\t\t\tids.AddRange( building.ExteriorStairs.Select( flight =\u003E flight.Id ) );\n\t\t\tids.AddRange( building.Cutouts.Select( cutout =\u003E cutout.Id ) );\n\t\t\tids.AddRange( building.Cuts.Select( cut =\u003E cut.Id ) );\n\t\t\tids.AddRange( building.Platforms.Select( platform =\u003E platform.Id ) );\n\t\t\tids.AddRange( building.Platforms.SelectMany( platform =\u003E platform.Stairs ).Select( stair =\u003E stair.Id ) );\n\t\t}\n\n\t\t// Through AllWalls, or a parapet\u0027s id is handed out again the next time a build normalizes, and two\n\t\t// walls sharing an id are one object in the scene - the second stands where the first stood.\n\t\tvar walls = this.AllWalls().ToList();\n\n\t\tids.AddRange( walls.Select( wall =\u003E wall.Id ) );\n\t\tids.AddRange( walls.SelectMany( wall =\u003E wall.Openings ).Select( opening =\u003E opening.Id ) );\n\t\tids.AddRange( walls.SelectMany( wall =\u003E wall.Modifiers ).Select( modifier =\u003E modifier.Id ) );\n\n\t\t// Every step of a climb carries an id so a railing can name the one it guards, exactly as a pipe\u0027s nodes\n\t\t// do - and through Parts, because a stair stands in a room, on a porch and on a platform, and the three\n\t\t// lists that hold them are three chances to forget one.\n\t\tvar steps = this.Parts\u003CArchStairPart\u003E().SelectMany( stair =\u003E stair.Lanes ).ToList();\n\n\t\tids.AddRange( steps.Select( lane =\u003E lane.Id ) );\n\t\tids.AddRange( steps.SelectMany( lane =\u003E lane.Guards ).Select( guard =\u003E guard.Id ) );\n\n\t\t// And through the bytes no type here claims, or the parts of a kind this build cannot read are invisible\n\t\t// to the allocator and the next id handed out is one something already holds.\n\t\tids.AddRange( Units.Select( unit =\u003E unit.Id ) );\n\t\tids.AddRange( Units.Select( unit =\u003E ArchPlanStore.HighestIdIn( unit.Payloads ) ) );\n\t\tids.AddRange( AllRooms().Select( room =\u003E ArchPlanStore.HighestIdIn( room.Payloads ) ) );\n\n\t\treturn ids.Max();\n\t}\n}\n\npublic sealed class ArchPalette\n{\n\tpublic Dictionary\u003Cstring, string\u003E Materials { get; set; } = new();\n\tpublic Dictionary\u003Cstring, float\u003E TexelScales { get; set; } = new();\n\tpublic Dictionary\u003Cstring, Vector2\u003E TextureOffsets { get; set; } = new();\n\tpublic Dictionary\u003Cstring, ArchFaceUvSet\u003E FaceMappings { get; set; } = new();\n\n\tpublic bool TryGet( ArchSurface surface, out string path )\n\t{\n\t\treturn Materials.TryGetValue( surface.ToString(), out path ) \u0026\u0026 !string.IsNullOrWhiteSpace( path );\n\t}\n\n\t// The density belongs to the material, so re-pointing a role drops it.\n\tpublic void Set( ArchSurface surface, string path )\n\t{\n\t\tTexelScales.Remove( surface.ToString() );\n\n\t\tif ( string.IsNullOrWhiteSpace( path ) )\n\t\t{\n\t\t\tMaterials.Remove( surface.ToString() );\n\t\t\tTextureOffsets?.Remove( surface.ToString() );\n\t\t\treturn;\n\t\t}\n\n\t\tMaterials[surface.ToString()] = path;\n\t}\n\n\tpublic void Set( ArchSurface surface, string path, float texelScale )\n\t{\n\t\tSet( surface, path );\n\n\t\tif ( !string.IsNullOrWhiteSpace( path ) )\n\t\t{\n\t\t\tSetScale( surface, texelScale );\n\t\t}\n\t}\n\n\t// Zero means \u0022no override\u0022 - the generator falls back to ArchMesh.TexelScale.\n\tpublic void SetScale( ArchSurface surface, float texelScale )\n\t{\n\t\tif ( texelScale \u003C= 0f )\n\t\t{\n\t\t\tTexelScales.Remove( surface.ToString() );\n\t\t\treturn;\n\t\t}\n\n\t\tTexelScales[surface.ToString()] = texelScale;\n\t}\n\n\tpublic float ScaleFor( ArchSurface surface )\n\t{\n\t\treturn TexelScales.TryGetValue( surface.ToString(), out var scale ) ? scale : 0f;\n\t}\n\n\tpublic void SetOffset( ArchSurface surface, Vector2 offset )\n\t{\n\t\tif ( offset.IsNearZeroLength )\n\t\t{\n\t\t\tTextureOffsets?.Remove( surface.ToString() );\n\t\t\treturn;\n\t\t}\n\n\t\tTextureOffsets ??= new();\n\t\tTextureOffsets[surface.ToString()] = offset;\n\t}\n\n\tpublic Vector2 OffsetFor( ArchSurface surface )\n\t{\n\t\treturn TextureOffsets is not null \u0026\u0026 TextureOffsets.TryGetValue( surface.ToString(), out var offset ) ? offset : Vector2.Zero;\n\t}\n}\n\npublic interface IArchPainted\n{\n\tArchPalette Palette { get; set; }\n}\n\npublic sealed class ArchFaceUvSet\n{\n\tpublic List\u003CArchFaceUvVariation\u003E Variations { get; set; } = new();\n}\n\npublic sealed class ArchFaceUvVariation\n{\n\tpublic string Signature { get; set; }\n\tpublic List\u003CArchFaceUvFace\u003E Faces { get; set; } = new();\n}\n\npublic sealed class ArchFaceUvFace\n{\n\tpublic List\u003CVector2\u003E Coordinates { get; set; } = new();\n}\n\n// One top-level thing standing on the map. A house and a street are not the same shape and never will be, but\n// everything the STACK does to one it does to the other - name it, disable it, group it, order it, carve it - so\n// they share a base and the plan holds one list. Walking two lists is how a road quietly stopped being reached\n// by half the passes that reach a building.\n[JsonConverter( typeof( ArchUnitConverter ) )]\npublic abstract class ArchUnit : IArchCollides, IArchNamed\n{\n\t// The whole unit at once - what a far-off silhouette is set to None from.\n\tpublic ArchCollisionMode? Collision { get; set; }\n\n\t// Which module\u0027s unit this is. Written as an ordinary field rather than a polymorphic discriminator, because\n\t// System.Text.Json throws on a discriminator it does not recognise and an unknown kind is the not-installed case.\n\tpublic ArchKind Kind { get; set; }\n\n\tpublic int Id { get; set; }\n\t// The author\u0027s, not the kind\u0027s: a unit is renamed in the Plan Layers stack like any other layer.\n\tpublic string Name { get; set; } = \u0022Unit\u0022;\n\tpublic ArchPalette Palette { get; set; } = new();\n\t// One list - a stairwell, a passage and a service bay are the same part with a different profile.\n\tpublic List\u003CArchCutPart\u003E Cuts { get; set; } = new();\n\n\t// Whatever a module filed here that this editor has no type for. Written back exactly as it was read, so a plan\n\t// opened without the library that authored it saves whole instead of losing that library\u0027s work.\n\t[JsonExtensionData]\n\tpublic Dictionary\u003Cstring, JsonElement\u003E Payloads { get; set; } = new();\n\n\t[JsonIgnore]\n\tpublic abstract bool HasContent { get; }\n}\n\n// A unit whose kind no installed module claims. It carries nothing this editor can read and everything the file\n// gave it, so it round-trips byte for byte - and it always reports content, or DiscardEmptyTargets would delete\n// the one thing in the plan nobody here is able to see.\npublic sealed class ArchOpaqueUnit : ArchUnit\n{\n\tpublic override bool HasContent =\u003E true;\n}\n\npublic sealed class ArchBuilding : ArchUnit, IArchPainted\n{\n\tpublic ArchBuilding()\n\t{\n\t\tName = \u0022Building\u0022;\n\t\tKind = ArchKind.Building;\n\t}\n\n\t// A wing extended later still comes out the same type, not kit defaults.\n\tpublic string Archetype { get; set; } = \u0022\u0022;\n\tpublic List\u003CArchRoom\u003E Rooms { get; set; } = new();\n\tpublic List\u003CArchRoofPart\u003E Roofs { get; set; } = new();\n\tpublic List\u003CArchDownpipePart\u003E Downpipes { get; set; } = new();\n\t// Service corridors and the hangers under them. Filed on the unit rather than a room, because a run\n\t// crosses partitions the way a fence crosses a yard - the volume is world-space and answers to no floor.\n\tpublic List\u003CArchPipePart\u003E Pipes { get; set; } = new();\n\tpublic List\u003CArchPipeBracketPart\u003E Brackets { get; set; } = new();\n\tpublic List\u003CArchFencePart\u003E Fences { get; set; } = new();\n\t// Platforms stand in the yard, so they hang off the building like a fence.\n\tpublic List\u003CArchPlatformPart\u003E Platforms { get; set; } = new();\n\tpublic List\u003CArchLadderPart\u003E Ladders { get; set; } = new();\n\t// Standing outside a room, so they hang off the building for the same reason a ladder does.\n\tpublic List\u003CArchBalconyPart\u003E Balconies { get; set; } = new();\n\tpublic List\u003CArchExteriorStairPart\u003E ExteriorStairs { get; set; } = new();\n\tpublic bool GuttersEnabled { get; set; } = true;\n\t// How far round the shell has been turned since it was drawn. The coordinates are still the truth - this is\n\t// the LEDGER of the turns applied to them, so an angle can be named absolutely instead of only nudged.\n\tpublic float Facing { get; set; }\n\tpublic float StoreyHeight { get; set; }\n\tpublic List\u003CArchFloorCutout\u003E Cutouts { get; set; } = new();\n\n\t[JsonIgnore]\n\tpublic override bool HasContent =\u003E Roofs.Count \u003E 0 || Downpipes.Count \u003E 0 || Fences.Count \u003E 0 || Platforms.Count \u003E 0\n\t\t|| Ladders.Count \u003E 0 || Cuts.Count \u003E 0 || Balconies.Count \u003E 0 || ExteriorStairs.Count \u003E 0\n\t\t|| Pipes.Count \u003E 0 || Brackets.Count \u003E 0\n\t\t|| Rooms.Any( room =\u003E room.HasContent );\n}\n\npublic sealed class ArchRoom : IArchCollides, IArchPainted, IArchNamed\n{\n\t// Overrides the kit for the room\u0027s own shell, slab and ceiling. A part standing IN it carries its own.\n\tpublic ArchCollisionMode? Collision { get; set; }\n\n\tpublic int Id { get; set; }\n\tpublic string Name { get; set; } = \u0022Room\u0022;\n\tpublic int Floor { get; set; }\n\tpublic float BaseHeight { get; set; }\n\tpublic float WallHeight { get; set; }\n\tpublic bool HasFloor { get; set; } = true;\n\tpublic bool HasCeiling { get; set; } = true;\n\t// A shell with nothing behind it: every wall it holds is single-sided, so the far face is never emitted and\n\t// ArchCull stands a lined box behind each window instead. Its floor and ceiling are still its own to turn off.\n\tpublic bool Facade { get; set; }\n\tpublic float CeilingDepth { get; set; }\n\tpublic bool FloorBoards { get; set; }\n\tpublic float FloorBoardYaw { get; set; }\n\tpublic bool RaisedFoundation { get; set; } = true;\n\t// A link across a gap, carried by its own piers - not an overhang.\n\tpublic bool Spans { get; set; }\n\t// A row of posts under whatever this storey oversails. Off by default: an upper storey that steps out over\n\t// the one below reads as a cantilever, and propping every one of them is a look, not a rule.\n\tpublic bool OverhangPosts { get; set; }\n\t// The section every one of those posts is cut from - a pillar with no position, exactly as a pillar TYPE\n\t// holds one, so a prop under an overhang is dressed by the Pillars tool\u0027s own forms rather than a second set.\n\tpublic ArchPillarPart OverhangPost { get; set; } = new();\n\t// A rising walkway: the far end\u0027s floor height. Equal to BaseHeight on a flat link.\n\tpublic float WalkwayTop { get; set; }\n\tpublic WalkwayInterior Interior { get; set; }\n\t// The link draws its own boards so they die square at the mouth corners.\n\tpublic bool WalkwaySkirting { get; set; } = true;\n\tpublic ArchPalette Palette { get; set; } = new();\n\tpublic List\u003CArchWall\u003E Walls { get; set; } = new();\n\tpublic List\u003CArchStairPart\u003E Stairs { get; set; } = new();\n\tpublic List\u003CArchTrimPart\u003E Trims { get; set; } = new();\n\tpublic List\u003CArchPillarPart\u003E Pillars { get; set; } = new();\n\t// Not \u0022Spans\u0022 - that is already the walkway\u0027s own flag, and a slot name is a json property.\n\tpublic List\u003CArchSpanPart\u003E PierSpans { get; set; } = new();\n\tpublic List\u003CArchBeamPart\u003E Beams { get; set; } = new();\n\tpublic List\u003CArchPorchPart\u003E Porches { get; set; } = new();\n\tpublic List\u003CArchApproachPart\u003E Approaches { get; set; } = new();\n\tpublic List\u003CVector2\u003E Footprint { get; set; } = new();\n\n\t[JsonExtensionData]\n\tpublic Dictionary\u003Cstring, JsonElement\u003E Payloads { get; set; } = new();\n\n\t[JsonIgnore]\n\tpublic bool HasFootprint =\u003E Footprint.Count \u003E= 3;\n\n\t[JsonIgnore]\n\tpublic bool HasContent =\u003E HasFootprint || Walls.Count \u003E 0 || Stairs.Count \u003E 0 || Pillars.Count \u003E 0\n\t\t|| Trims.Count \u003E 0 || Porches.Count \u003E 0 || Approaches.Count \u003E 0 || Beams.Count \u003E 0 || PierSpans.Count \u003E 0;\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Layers/ArchLayerTree.cs","FileName":"ArchLayerTree.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace Sunless.Architecture;\n\n// One authored thing in the plan, shown as a tree row. Payload is the typed part itself; virtual\n// nodes (story groups) carry none. Building/Room carry the selection context so any row can become\n// an ArchSelection without re-hunting ownership.\npublic sealed class ArchLayerNode\n{\n\tpublic ArchLayerRef? Ref { get; init; }\n\tpublic ArchKind Kind { get; init; }\n\tpublic ArchLayerStage Stage { get; init; }\n\tpublic ArchLayerDomain Domain { get; init; }\n\tpublic object Payload { get; init; }\n\tpublic string Name { get; init; } = \u0022\u0022;\n\t// Records may exclude a layer from generation; absent a record, everything is enabled.\n\tpublic bool Enabled { get; init; } = true;\n\t// Locked rows still select and still generate; they refuse every edit.\n\tpublic bool Locked { get; init; }\n\t// Sibling order within a stage, for the kinds that care. Absent a record it is authoring order.\n\tpublic int Order { get; init; }\n\t// Story headers and room layers name the storey they stand on.\n\tpublic int Floor { get; init; } = int.MinValue;\n\tpublic ArchLayerNode Parent { get; internal set; }\n\tpublic List\u003CArchLayerNode\u003E Children { get; } = new();\n\t// Selection context: every row knows which building and room it belongs to.\n\tpublic ArchBuilding Building { get; init; }\n\tpublic ArchRoom Room { get; init; }\n\n\tpublic bool Virtual =\u003E Payload is null;\n\n\tpublic string DisplayName =\u003E ArchLayerNames.DisplayName( this );\n}\n\npublic sealed class ArchLayerDomainGroup\n{\n\tpublic ArchLayerDomain Domain { get; init; }\n\tpublic string Name { get; init; } = \u0022\u0022;\n\tpublic List\u003CArchLayerNode\u003E Children { get; } = new();\n}\n\n// The projected layer tree: a metadata-only view of the plan\u0027s authored ownership. Building it must\n// never resolve generator shapes - only ids, kinds, names and floors.\npublic sealed class ArchLayerTree\n{\n\tpublic List\u003CArchLayerDomainGroup\u003E Domains { get; } = new();\n\n\treadonly Dictionary\u003Cint, ArchLayerNode\u003E byId = new();\n\treadonly Dictionary\u003Cobject, ArchLayerNode\u003E byPayload = new();\n\treadonly Dictionary\u003C(int Building, int Floor), ArchLayerNode\u003E stories = new();\n\n\tpublic IReadOnlyList\u003CArchLayerLink\u003E Links { get; private set; } = Array.Empty\u003CArchLayerLink\u003E();\n\n\tpublic ArchLayerNode Find( int id ) =\u003E byId.TryGetValue( id, out var node ) ? node : null;\n\n\tpublic ArchLayerNode Find( object payload )\n\t{\n\t\treturn payload is null ? null : byPayload.TryGetValue( payload, out var node ) ? node : null;\n\t}\n\n\tpublic object Resolve( ArchLayerRef layer ) =\u003E byId.TryGetValue( layer.ItemId, out var node ) ? node.Payload : null;\n\n\t// \u0022House 2 / Level 1 / Walkway 1\u0022 - the row\u0027s authored path, not its object path.\n\tpublic string Breadcrumb( ArchLayerNode node )\n\t{\n\t\tif ( node is null )\n\t\t{\n\t\t\treturn \u0022\u0022;\n\t\t}\n\n\t\tvar parts = new List\u003Cstring\u003E();\n\n\t\tfor ( var current = node; current is not null; current = current.Parent )\n\t\t{\n\t\t\tparts.Add( current.DisplayName );\n\t\t}\n\n\t\tparts.Reverse();\n\n\t\treturn string.Join( \u0022 / \u0022, parts );\n\t}\n\n\t// A stable identity for tree widgets: payload rows key by the payload itself (which survives\n\t// commits), story rows by their building and floor.\n\tpublic object StableKey( ArchLayerNode node )\n\t{\n\t\treturn node.Payload ?? $\u0022story:{node.Building?.Id}:{node.Floor}\u0022;\n\t}\n\n\t// The explicit metadata for a layer, written the first time anything is asked of it that typed\n\t// ownership cannot answer - a parent, an order, a disabled state, a lock.\n\tpublic ArchLayerRecord Record( ArchPlan plan, ArchLayerNode node )\n\t{\n\t\tif ( node?.Ref is not { } layer )\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\tvar record = plan.Layers.FirstOrDefault( entry =\u003E entry.ItemId == layer.ItemId );\n\n\t\tif ( record is null )\n\t\t{\n\t\t\trecord = new ArchLayerRecord\n\t\t\t{\n\t\t\t\tItemId = layer.ItemId,\n\t\t\t\tParentId = layer.ParentId,\n\t\t\t\tKind = layer.Kind,\n\t\t\t\tStage = node.Stage,\n\t\t\t\tOrder = node.Order,\n\t\t\t\tEnabled = node.Enabled,\n\t\t\t\tLocked = node.Locked,\n\t\t\t};\n\n\t\t\tplan.Layers.Add( record );\n\t\t}\n\n\t\treturn record;\n\t}\n\n\t// Drag-reorder writes an explicit order for the whole sibling run, so the arrangement survives a\n\t// later addition landing at the end of its owner\u0027s typed list.\n\tpublic bool Reorder( ArchPlan plan, ArchLayerRef moving, int anchorId, bool below )\n\t{\n\t\tif ( !byId.TryGetValue( moving.ItemId, out var node ) || !byId.TryGetValue( anchorId, out var anchor ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( ReferenceEquals( node, anchor ) || !ReferenceEquals( node.Parent, anchor.Parent ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tvar siblings = (node.Parent?.Children ?? Domains.FirstOrDefault( domain =\u003E domain.Domain == node.Domain )?.Children)\n\t\t\t?.Where( child =\u003E child.Ref is not null )\n\t\t\t.ToList();\n\n\t\tif ( siblings is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tsiblings.Remove( node );\n\n\t\tvar at = siblings.IndexOf( anchor );\n\n\t\tif ( at \u003C 0 )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tsiblings.Insert( below ? at \u002B 1 : at, node );\n\n\t\tfor ( var index = 0; index \u003C siblings.Count; index\u002B\u002B )\n\t\t{\n\t\t\tRecord( plan, siblings[index] ).Order = index;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// Validated reparent: the capability matrix approves, the payload actually moves between its\n\t// ownership lists, and a layer record is written so the projection keeps the new parent.\n\tpublic bool Reparent( ArchPlan plan, ArchLayerRef layer, int newParentId )\n\t{\n\t\tif ( !byId.TryGetValue( layer.ItemId, out var node ) || node.Payload is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\t// Dropping onto the domain header takes the layer out of whatever group held it.\n\t\tif ( newParentId == 0 )\n\t\t{\n\t\t\tArchLayerGroups.Leave( plan, layer.ItemId );\n\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( layer.ItemId == newParentId || !byId.TryGetValue( newParentId, out var parentNode ) || parentNode.Payload is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\t// A folder takes anything: membership is a scope, not an ownership claim, so the payload stays\n\t\t// exactly where the generator reads it and only the layer it belongs to changes.\n\t\tif ( parentNode.Payload is ArchSiteAssembly group )\n\t\t{\n\t\t\tArchLayerGroups.Join( plan, group, layer.ItemId );\n\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( !ArchLayerRules.CanParent( parentNode.Kind, layer.Kind ).Allowed )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tArchLayerGroups.Leave( plan, layer.ItemId );\n\n\t\tif ( !MovePayload( plan, node.Payload, parentNode.Payload ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tvar record = plan.Layers.FirstOrDefault( entry =\u003E entry.ItemId == layer.ItemId );\n\n\t\tif ( record is null )\n\t\t{\n\t\t\trecord = new ArchLayerRecord { ItemId = layer.ItemId, ParentId = newParentId, Kind = layer.Kind, Stage = node.Stage };\n\t\t\tplan.Layers.Add( record );\n\t\t}\n\n\t\trecord.ParentId = newParentId;\n\n\t\treturn true;\n\t}\n\n\t// A wall lives on a room or on a deck, so it is taken out of whichever holds it before being filed anywhere.\n\tstatic bool Unfile( ArchPlan plan, ArchWall wall )\n\t{\n\t\tforeach ( var room in plan.AllRooms() )\n\t\t{\n\t\t\tif ( room.Walls.Remove( wall ) )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tforeach ( var roof in plan.AllRoofs() )\n\t\t{\n\t\t\tif ( roof.Walls.Remove( wall ) )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// A flight stands in a room, on a platform or on a porch deck, so it is taken out of whichever holds it\n\t// before being filed anywhere. The same three-homed lookup covers columns and runs.\n\tstatic bool Unfile( ArchPlan plan, ArchStairPart stair )\n\t{\n\t\tforeach ( var room in plan.AllRooms() )\n\t\t{\n\t\t\tif ( room.Stairs.Remove( stair ) || room.Porches.Any( porch =\u003E porch.Stairs.Remove( stair ) ) )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn plan.Buildings.SelectMany( building =\u003E building.Platforms ).Any( platform =\u003E platform.Stairs.Remove( stair ) );\n\t}\n\n\tstatic bool Unfile( ArchPlan plan, ArchPillarPart pillar )\n\t{\n\t\treturn plan.AllRooms().Any( room =\u003E room.Pillars.Remove( pillar ) || room.Porches.Any( porch =\u003E porch.Pillars.Remove( pillar ) ) );\n\t}\n\n\tstatic bool Unfile( ArchPlan plan, ArchTrimPart trim )\n\t{\n\t\treturn plan.AllRooms().Any( room =\u003E room.Trims.Remove( trim ) || room.Porches.Any( porch =\u003E porch.Trims.Remove( trim ) ) );\n\t}\n\n\t// The plan\u0027s typed lists are the payloads\u0027 real homes - a reparent that only rewrote the record\n\t// would show a new tree while the generator read the old ownership.\n\tstatic bool MovePayload( ArchPlan plan, object payload, object newParent )\n\t{\n\t\tswitch ( payload, newParent )\n\t\t{\n\t\t\tcase (ArchRoom room, ArchBuilding building):\n\t\t\t\tif ( plan.OwnerOf( room ) is not { } fromRoom ) return false;\n\t\t\t\tfromRoom.Rooms.Remove( room );\n\t\t\t\tbuilding.Rooms.Add( room );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchWall wall, ArchRoom room):\n\t\t\t\tif ( !Unfile( plan, wall ) ) return false;\n\t\t\t\troom.Walls.Add( wall );\n\t\t\t\treturn true;\n\n\t\t\t// Onto a deck: the wall stops standing on a floor and starts standing on a roof, which is the only\n\t\t\t// difference between a partition and a parapet.\n\t\t\tcase (ArchWall wall, ArchRoofPart roof):\n\t\t\t\tif ( !Unfile( plan, wall ) ) return false;\n\t\t\t\troof.Walls.Add( wall );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchOpening opening, ArchWall wall):\n\t\t\t\tif ( plan.AllWalls().FirstOrDefault( candidate =\u003E candidate.Openings.Contains( opening ) ) is not { } fromOpening ) return false;\n\t\t\t\tfromOpening.Openings.Remove( opening );\n\t\t\t\twall.Openings.Add( opening );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchWallModPart modifier, ArchWall host):\n\t\t\t\tif ( plan.AllWalls().FirstOrDefault( candidate =\u003E candidate.Modifiers.Contains( modifier ) ) is not { } fromModifier ) return false;\n\t\t\t\tfromModifier.Modifiers.Remove( modifier );\n\t\t\t\thost.Modifiers.Add( modifier );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchStairPart stair, ArchRoom room):\n\t\t\t\tif ( !Unfile( plan, stair ) ) return false;\n\t\t\t\troom.Stairs.Add( stair );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchTrimPart trim, ArchRoom room):\n\t\t\t\tif ( !Unfile( plan, trim ) ) return false;\n\t\t\t\troom.Trims.Add( trim );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchPillarPart pillar, ArchRoom room):\n\t\t\t\tif ( !Unfile( plan, pillar ) ) return false;\n\t\t\t\troom.Pillars.Add( pillar );\n\t\t\t\treturn true;\n\n\t\t\t// Onto a porch: the flight stops standing on a floor and starts standing on a deck, which is all\n\t\t\t// that separates an inside stair from the steps off a veranda.\n\t\t\tcase (ArchStairPart stair, ArchPorchPart porch):\n\t\t\t\tif ( !Unfile( plan, stair ) ) return false;\n\t\t\t\tporch.Stairs.Add( stair );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchPillarPart pillar, ArchPorchPart porch):\n\t\t\t\tif ( !Unfile( plan, pillar ) ) return false;\n\t\t\t\tporch.Pillars.Add( pillar );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchTrimPart trim, ArchPorchPart porch):\n\t\t\t\tif ( !Unfile( plan, trim ) ) return false;\n\t\t\t\tporch.Trims.Add( trim );\n\t\t\t\treturn true;\n\n\t\t\t// A cut is world-space and stays filed where it was: nesting it under a porch says what it belongs\n\t\t\t// to, the way a walkway\u0027s deck record does, and moves nothing the generator reads.\n\t\t\tcase (ArchCutPart, ArchPorchPart):\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchBeamPart beam, ArchRoom room):\n\t\t\t\tif ( plan.AllRooms().FirstOrDefault( candidate =\u003E candidate.Beams.Contains( beam ) ) is not { } fromBeam ) return false;\n\t\t\t\tfromBeam.Beams.Remove( beam );\n\t\t\t\troom.Beams.Add( beam );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchPorchPart porch, ArchRoom room):\n\t\t\t\tif ( plan.AllRooms().FirstOrDefault( candidate =\u003E candidate.Porches.Contains( porch ) ) is not { } fromPorch ) return false;\n\t\t\t\tfromPorch.Porches.Remove( porch );\n\t\t\t\troom.Porches.Add( porch );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchApproachPart approach, ArchRoom room):\n\t\t\t\tif ( plan.AllRooms().FirstOrDefault( candidate =\u003E candidate.Approaches.Contains( approach ) ) is not { } fromApproach ) return false;\n\t\t\t\tfromApproach.Approaches.Remove( approach );\n\t\t\t\troom.Approaches.Add( approach );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchRoofPart roof, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Roofs.Contains( roof ) ) is not { } fromRoof ) return false;\n\t\t\t\tfromRoof.Roofs.Remove( roof );\n\t\t\t\tbuilding.Roofs.Add( roof );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchRoofLightPart light, ArchRoofPart roof):\n\t\t\t\tif ( plan.Buildings.SelectMany( candidate =\u003E candidate.Roofs ).FirstOrDefault( candidate =\u003E candidate.Lights.Contains( light ) ) is not { } fromLight ) return false;\n\t\t\t\tfromLight.Lights.Remove( light );\n\t\t\t\troof.Lights.Add( light );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchPlatformPart platform, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Platforms.Contains( platform ) ) is not { } fromPlatform ) return false;\n\t\t\t\tfromPlatform.Platforms.Remove( platform );\n\t\t\t\tbuilding.Platforms.Add( platform );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchStairPart stair, ArchPlatformPart platform):\n\t\t\t\tif ( !Unfile( plan, stair ) ) return false;\n\t\t\t\tplatform.Stairs.Add( stair );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchDownpipePart pipe, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Downpipes.Contains( pipe ) ) is not { } fromPipe ) return false;\n\t\t\t\tfromPipe.Downpipes.Remove( pipe );\n\t\t\t\tbuilding.Downpipes.Add( pipe );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchPipePart run, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Pipes.Contains( run ) ) is not { } fromRun ) return false;\n\t\t\t\tfromRun.Pipes.Remove( run );\n\t\t\t\tbuilding.Pipes.Add( run );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchPipeBracketPart bracket, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Brackets.Contains( bracket ) ) is not { } fromBracket ) return false;\n\t\t\t\tfromBracket.Brackets.Remove( bracket );\n\t\t\t\tbuilding.Brackets.Add( bracket );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchFencePart fence, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Fences.Contains( fence ) ) is not { } fromFence ) return false;\n\t\t\t\tfromFence.Fences.Remove( fence );\n\t\t\t\tbuilding.Fences.Add( fence );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchLadderPart ladder, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Ladders.Contains( ladder ) ) is not { } fromLadder ) return false;\n\t\t\t\tfromLadder.Ladders.Remove( ladder );\n\t\t\t\tbuilding.Ladders.Add( ladder );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchBalconyPart balcony, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Balconies.Contains( balcony ) ) is not { } fromBalcony ) return false;\n\t\t\t\tfromBalcony.Balconies.Remove( balcony );\n\t\t\t\tbuilding.Balconies.Add( balcony );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchExteriorStairPart flight, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.ExteriorStairs.Contains( flight ) ) is not { } fromFlight ) return false;\n\t\t\t\tfromFlight.ExteriorStairs.Remove( flight );\n\t\t\t\tbuilding.ExteriorStairs.Add( flight );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchCutPart cut, ArchBuilding building):\n\t\t\t\tif ( plan.Buildings.FirstOrDefault( candidate =\u003E candidate.Cuts.Contains( cut ) ) is not { } fromCut ) return false;\n\t\t\t\tfromCut.Cuts.Remove( cut );\n\t\t\t\tbuilding.Cuts.Add( cut );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchBridgePart bridge, ArchRoadPart road):\n\t\t\t\tif ( plan.Roads().FirstOrDefault( candidate =\u003E candidate.Bridges.Contains( bridge ) ) is not { } fromBridge ) return false;\n\t\t\t\tfromBridge.Bridges.Remove( bridge );\n\t\t\t\troad.Bridges.Add( bridge );\n\t\t\t\treturn true;\n\n\t\t\tcase (ArchTunnelPart tunnel, ArchRoadPart road):\n\t\t\t\tif ( plan.Roads().FirstOrDefault( candidate =\u003E candidate.Tunnels.Contains( tunnel ) ) is not { } fromTunnel ) return false;\n\t\t\t\tfromTunnel.Tunnels.Remove( tunnel );\n\t\t\t\troad.Tunnels.Add( tunnel );\n\t\t\t\treturn true;\n\n\t\t\tdefault:\n\t\t\t\treturn false;\n\t\t}\n\t}\n\n\tpublic bool IsEnabled( object payload )\n\t{\n\t\treturn payload is null || byPayload.TryGetValue( payload, out var node ) \u0026\u0026 node.Enabled;\n\t}\n\n\tpublic bool IsEnabled( int id )\n\t{\n\t\treturn byId.TryGetValue( id, out var node ) \u0026\u0026 node.Enabled;\n\t}\n\n\tpublic bool IsLocked( int id )\n\t{\n\t\treturn byId.TryGetValue( id, out var node ) \u0026\u0026 node.Locked;\n\t}\n\n\tpublic bool IsLocked( object payload )\n\t{\n\t\treturn payload is not null \u0026\u0026 byPayload.TryGetValue( payload, out var node ) \u0026\u0026 node.Locked;\n\t}\n\n\t// LOCKED HERE OR ANYWHERE ABOVE. A lock is how an author says \u0022I have hand-edited these faces and the\n\t// generator is finished with them\u0022, so locking a house has to freeze every wall in it - a rebuild that walked\n\t// into one of them would throw the edit away, which is the one thing the lock exists to prevent.\n\tpublic bool Frozen( int itemId )\n\t{\n\t\tif ( itemId == 0 || !byId.TryGetValue( itemId, out var found ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tfor ( var node = found; node is not null; node = node.Parent )\n\t\t{\n\t\t\tif ( node.Locked )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic bool AnyFrozen() =\u003E byId.Values.Any( node =\u003E node.Locked );\n\n\t// The messages a layer must surface: unresolved required links, missing children.\n\tpublic IReadOnlyList\u003Cstring\u003E Problems( ArchLayerNode node )\n\t{\n\t\tvar problems = new List\u003Cstring\u003E();\n\n\t\tif ( node?.Ref is not { } nodeRef )\n\t\t{\n\t\t\treturn problems;\n\t\t}\n\n\t\tforeach ( var link in Links.Where( link =\u003E link.Required \u0026\u0026 link.SourceId == nodeRef.ItemId ) )\n\t\t{\n\t\t\tif ( !byId.ContainsKey( link.TargetId ) )\n\t\t\t{\n\t\t\t\tproblems.Add( $\u0022Required link {link.SourcePort} \u2192 {link.TargetPort} (id {link.TargetId}) does not resolve.\u0022 );\n\t\t\t}\n\t\t}\n\n\t\treturn problems;\n\t}\n\n\t// The ids a rebuild must touch when this layer changes: itself, its descendants, the hosts that\n\t// receive its owned effects, and required linked consumers. Generation still rebuilds the whole\n\t// scene today, but the dirty set is what partial regeneration will later replace.\n\tpublic IReadOnlySet\u003Cint\u003E DirtyClosure( int itemId )\n\t{\n\t\tvar dirty = new HashSet\u003Cint\u003E { itemId };\n\n\t\tif ( byId.TryGetValue( itemId, out var node ) )\n\t\t{\n\t\t\tCollectDescendants( node, dirty );\n\t\t}\n\n\t\tforeach ( var link in Links.Where( link =\u003E link.Required \u0026\u0026 link.TargetId == itemId ) )\n\t\t{\n\t\t\tdirty.Add( link.SourceId );\n\t\t}\n\n\t\tforeach ( var entry in byId.Values )\n\t\t{\n\t\t\tif ( OwnsEffectsOn( entry.Payload, itemId ) \u0026\u0026 entry.Ref is { } entryRef )\n\t\t\t{\n\t\t\t\tdirty.Add( entryRef.ItemId );\n\t\t\t}\n\t\t}\n\n\t\treturn dirty;\n\t}\n\n\tstatic void CollectDescendants( ArchLayerNode node, HashSet\u003Cint\u003E into )\n\t{\n\t\tforeach ( var child in node.Children )\n\t\t{\n\t\t\tif ( child.Ref is { } childRef )\n\t\t\t{\n\t\t\t\tinto.Add( childRef.ItemId );\n\t\t\t}\n\n\t\t\tCollectDescendants( child, into );\n\t\t}\n\t}\n\n\t// A wall opening or slab cutout records who made it; disabling that owner has to dirty the host.\n\tstatic bool OwnsEffectsOn( object payload, int ownerId )\n\t{\n\t\treturn payload switch\n\t\t{\n\t\t\tArchBuilding building =\u003E building.Cutouts.Any( cutout =\u003E cutout.OwnerId == ownerId ),\n\t\t\tArchRoom room =\u003E room.Walls.SelectMany( wall =\u003E wall.Openings ).Any( opening =\u003E opening.OwnerId == ownerId ),\n\t\t\t_ =\u003E false\n\t\t};\n\t}\n\n\t// Builds the tree from typed ownership; explicit records override a payload\u0027s parent, kind and\n\t// stage when present. Old plans carry no records and project unchanged.\n\tpublic static ArchLayerTree Project( ArchPlan plan )\n\t{\n\t\tvar tree = new ArchLayerTree();\n\n\t\tif ( plan is null )\n\t\t{\n\t\t\treturn tree;\n\t\t}\n\n\t\tvar kinds = ArchKinds.Load();\n\t\tvar entries = new List\u003CEntry\u003E();\n\t\tvar byId = new Dictionary\u003Cint, Entry\u003E();\n\n\t\tvoid Register( object payload, int id, int parent, ArchKind kind, ArchBuilding building, ArchRoom room,\n\t\t\tint floor = int.MinValue, ArchLayerStage? stage = null )\n\t\t{\n\t\t\tvar entry = new Entry\n\t\t\t{\n\t\t\t\tPayload = payload,\n\t\t\t\tId = id,\n\t\t\t\tParentId = parent,\n\t\t\t\tKind = kind,\n\t\t\t\tStage = stage,\n\t\t\t\tName = kinds.NameOf( kind, payload, id ),\n\t\t\t\tBuilding = building,\n\t\t\t\tRoom = room,\n\t\t\t\tFloor = floor,\n\t\t\t};\n\n\t\t\tentries.Add( entry );\n\t\t\tbyId[id] = entry;\n\t\t}\n\n\t\tforeach ( var building in plan.Buildings )\n\t\t{\n\t\t\tRegister( building, building.Id, 0, ArchKind.Building, building, null );\n\n\t\t\tforeach ( var room in building.Rooms )\n\t\t\t{\n\t\t\t\tvar kind = room.Spans ? ArchKind.Walkway : ArchKind.Room;\n\t\t\t\tRegister( room, room.Id, building.Id, kind, building, room, room.Floor );\n\n\t\t\t\tforeach ( var wall in room.Walls )\n\t\t\t\t{\n\t\t\t\t\tRegister( wall, wall.Id, room.Id, ArchKind.Wall, building, room );\n\n\t\t\t\t\tforeach ( var opening in wall.Openings )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( opening, opening.Id, wall.Id, ArchKind.Opening, building, room );\n\t\t\t\t\t}\n\n\t\t\t\t\t// The stage is the payload\u0027s, not the kind\u0027s: a pilaster builds, a recess cuts.\n\t\t\t\t\tforeach ( var modifier in wall.Modifiers )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( modifier, modifier.Id, wall.Id, ArchKind.WallMod, building, room,\n\t\t\t\t\t\t\tstage: modifier.Carves ? ArchLayerStage.Void : ArchLayerStage.Structure );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tforeach ( var stair in room.Stairs )\n\t\t\t\t{\n\t\t\t\t\tRegister( stair, stair.Id, room.Id, ArchKind.Stair, building, room );\n\t\t\t\t}\n\n\t\t\t\tforeach ( var trim in room.Trims )\n\t\t\t\t{\n\t\t\t\t\tRegister( trim, trim.Id, room.Id, ArchKind.Trim, building, room );\n\t\t\t\t}\n\n\t\t\t\tforeach ( var pillar in room.Pillars )\n\t\t\t\t{\n\t\t\t\t\tRegister( pillar, pillar.Id, room.Id, ArchKind.Pillar, building, room );\n\t\t\t\t}\n\n\t\t\t\tforeach ( var span in room.PierSpans )\n\t\t\t\t{\n\t\t\t\t\tRegister( span, span.Id, room.Id, ArchKind.Span, building, room );\n\t\t\t\t}\n\n\t\t\t\tforeach ( var beam in room.Beams )\n\t\t\t\t{\n\t\t\t\t\tRegister( beam, beam.Id, room.Id, ArchKind.Beam, building, room );\n\t\t\t\t}\n\n\t\t\t\tforeach ( var porch in room.Porches )\n\t\t\t\t{\n\t\t\t\t\tRegister( porch, porch.Id, room.Id, ArchKind.Porch, building, room );\n\n\t\t\t\t\t// A porch HOSTS, so its flights, columns and runs are rows under it rather than loose\n\t\t\t\t\t// siblings of the room\u0027s own - the same shape a walkway\u0027s contents take.\n\t\t\t\t\tforeach ( var stair in porch.Stairs )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( stair, stair.Id, porch.Id, ArchKind.Stair, building, room );\n\t\t\t\t\t}\n\n\t\t\t\t\tforeach ( var pillar in porch.Pillars )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( pillar, pillar.Id, porch.Id, ArchKind.Pillar, building, room );\n\t\t\t\t\t}\n\n\t\t\t\t\tforeach ( var trim in porch.Trims )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( trim, trim.Id, porch.Id, ArchKind.Trim, building, room );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tforeach ( var approach in room.Approaches )\n\t\t\t\t{\n\t\t\t\t\tRegister( approach, approach.Id, room.Id, ArchKind.Approach, building, room );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var roof in building.Roofs )\n\t\t\t{\n\t\t\t\tRegister( roof, roof.Id, building.Id, ArchKind.Roof, building, null );\n\n\t\t\t\tforeach ( var light in roof.Lights )\n\t\t\t\t{\n\t\t\t\t\tRegister( light, light.Id, roof.Id, ArchKind.RoofLight, building, null );\n\t\t\t\t}\n\n\t\t\t\t// A wall standing on the deck is a Wall like any other, so it picks, edits and dresses through\n\t\t\t\t// every path a wall in a room already takes.\n\t\t\t\tforeach ( var wall in roof.Walls )\n\t\t\t\t{\n\t\t\t\t\tRegister( wall, wall.Id, roof.Id, ArchKind.Wall, building, null );\n\n\t\t\t\t\tforeach ( var opening in wall.Openings )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( opening, opening.Id, wall.Id, ArchKind.Opening, building, null );\n\t\t\t\t\t}\n\n\t\t\t\t\tforeach ( var modifier in wall.Modifiers )\n\t\t\t\t\t{\n\t\t\t\t\t\tRegister( modifier, modifier.Id, wall.Id, ArchKind.WallMod, building, null,\n\t\t\t\t\t\t\tstage: modifier.Carves ? ArchLayerStage.Void : ArchLayerStage.Structure );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var pipe in building.Downpipes )\n\t\t\t{\n\t\t\t\tRegister( pipe, pipe.Id, building.Id, ArchKind.Downpipe, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var run in building.Pipes )\n\t\t\t{\n\t\t\t\tRegister( run, run.Id, building.Id, ArchKind.Pipe, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var bracket in building.Brackets )\n\t\t\t{\n\t\t\t\tRegister( bracket, bracket.Id, building.Id, ArchKind.Bracket, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var fence in building.Fences )\n\t\t\t{\n\t\t\t\tRegister( fence, fence.Id, building.Id, ArchKind.Fence, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var platform in building.Platforms )\n\t\t\t{\n\t\t\t\tRegister( platform, platform.Id, building.Id, ArchKind.Platform, building, null );\n\n\t\t\t\tforeach ( var stair in platform.Stairs )\n\t\t\t\t{\n\t\t\t\t\tRegister( stair, stair.Id, platform.Id, ArchKind.CarvedStair, building, null );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var ladder in building.Ladders )\n\t\t\t{\n\t\t\t\tRegister( ladder, ladder.Id, building.Id, ArchKind.Ladder, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var balcony in building.Balconies )\n\t\t\t{\n\t\t\t\tRegister( balcony, balcony.Id, building.Id, ArchKind.Balcony, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var flight in building.ExteriorStairs )\n\t\t\t{\n\t\t\t\tRegister( flight, flight.Id, building.Id, ArchKind.ExteriorStair, building, null );\n\t\t\t}\n\n\t\t\tforeach ( var cut in building.Cuts )\n\t\t\t{\n\t\t\t\tRegister( cut, cut.Id, building.Id, ArchKind.Cut, building, null,\n\t\t\t\t\tstage: cut.Mode != ArchZoneMode.Carve ? ArchLayerStage.Finish : ArchLayerStage.Void );\n\t\t\t}\n\t\t}\n\n\t\tforeach ( var road in plan.Roads() )\n\t\t{\n\t\t\tRegister( road, road.Id, 0, ArchKind.Road, null, null );\n\n\t\t\tforeach ( var crossing in road.Crossings )\n\t\t\t{\n\t\t\t\tRegister( crossing, crossing.Id, road.Id, ArchKind.Crossing, null, null );\n\t\t\t}\n\n\t\t\tforeach ( var bridge in road.Bridges )\n\t\t\t{\n\t\t\t\tRegister( bridge, bridge.Id, road.Id, ArchKind.Bridge, null, null );\n\t\t\t}\n\n\t\t\tforeach ( var tunnel in road.Tunnels )\n\t\t\t{\n\t\t\t\tRegister( tunnel, tunnel.Id, road.Id, ArchKind.Tunnel, null, null );\n\t\t\t}\n\n\n\t\t\tforeach ( var cut in road.Cuts )\n\t\t\t{\n\t\t\t\tRegister( cut, cut.Id, road.Id, ArchKind.Cut, null, null,\n\t\t\t\t\tstage: cut.Mode != ArchZoneMode.Carve ? ArchLayerStage.Finish : ArchLayerStage.Void );\n\t\t\t}\n\t\t}\n\n\t\t// Every OTHER top-level unit, which is how a kind an addon brought gets a row in the stack without this walk\n\t\t// naming it. A house and a street are walked above only because their children are shapes this assembly knows.\n\t\tforeach ( var unit in plan.Units )\n\t\t{\n\t\t\tif ( unit is ArchBuilding or ArchRoadPart )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Under whatever it says it hangs on, which for a unit stored flat is still the building it attached\n\t\t\t// to - where it is FILED and where it BELONGS are two questions, and the row answers the second.\n\t\t\tvar owner = kinds.Parent( unit.Kind, unit );\n\n\t\t\tRegister( unit, unit.Id, owner, unit.Kind, plan.FindBuilding( owner ), null,\n\t\t\t\tstage: kinds.Stage( unit.Kind, unit ) );\n\n\t\t\tforeach ( var cut in unit.Cuts )\n\t\t\t{\n\t\t\t\tRegister( cut, cut.Id, unit.Id, ArchKind.Cut, null, null,\n\t\t\t\t\tstage: cut.Mode != ArchZoneMode.Carve ? ArchLayerStage.Finish : ArchLayerStage.Void );\n\t\t\t}\n\t\t}\n\n\t\t// Explicit metadata overrides ownership defaults; a self-parent record is nonsense and falls back.\n\t\tforeach ( var record in plan.Layers )\n\t\t{\n\t\t\tif ( byId.TryGetValue( record.ItemId, out var entry ) \u0026\u0026 record.ParentId != record.ItemId )\n\t\t\t{\n\t\t\t\tentry.ParentId = record.ParentId;\n\t\t\t\tentry.Kind = record.Kind;\n\t\t\t\tentry.Stage = record.Stage;\n\t\t\t\tentry.Enabled = record.Enabled;\n\t\t\t\tentry.Locked = record.Locked;\n\t\t\t\tentry.Order = record.Order;\n\t\t\t}\n\t\t}\n\n\t\tvar nodes = new Dictionary\u003Cint, ArchLayerNode\u003E();\n\n\t\tforeach ( var entry in entries )\n\t\t{\n\t\t\tvar node = new ArchLayerNode\n\t\t\t{\n\t\t\t\tRef = new ArchLayerRef { ItemId = entry.Id, Kind = entry.Kind, ParentId = entry.ParentId },\n\t\t\t\tKind = entry.Kind,\n\t\t\t\tStage = entry.Stage ?? kinds.Stage( entry.Kind ),\n\t\t\t\tDomain = kinds.Domain( entry.Kind ),\n\t\t\t\tPayload = entry.Payload,\n\t\t\t\tName = entry.Name,\n\t\t\t\tEnabled = entry.Enabled,\n\t\t\t\tLocked = entry.Locked,\n\t\t\t\tOrder = entry.Order,\n\t\t\t\tFloor = entry.Floor,\n\t\t\t\tBuilding = entry.Building,\n\t\t\t\tRoom = entry.Room,\n\t\t\t};\n\n\t\t\tnodes[entry.Id] = node;\n\t\t\ttree.byId[entry.Id] = node;\n\t\t\ttree.byPayload[entry.Payload] = node;\n\t\t}\n\n\t\t// A group is a folder, so its members MOVE into it rather than being listed twice - the whole\n\t\t// point of the scope is that a layer stands in exactly one of them. A group sits in the domain\n\t\t// its members came from, so grouping two houses does not empty the Buildings branch.\n\t\tvar memberOf = new Dictionary\u003Cint, ArchLayerNode\u003E();\n\n\t\tforeach ( var assembly in plan.Assemblies )\n\t\t{\n\t\t\tvar node = new ArchLayerNode\n\t\t\t{\n\t\t\t\tRef = new ArchLayerRef { ItemId = assembly.Id, Kind = ArchKind.Assembly, ParentId = 0 },\n\t\t\t\tKind = ArchKind.Assembly,\n\t\t\t\tStage = kinds.Stage( ArchKind.Assembly ),\n\t\t\t\tDomain = DomainOf( assembly, byId, kinds ),\n\t\t\t\tPayload = assembly,\n\t\t\t\tName = assembly.Name,\n\t\t\t};\n\n\t\t\tnodes[assembly.Id] = node;\n\t\t\ttree.byId[assembly.Id] = node;\n\t\t\ttree.byPayload[assembly] = node;\n\n\t\t\tforeach ( var childId in assembly.Children.Where( childId =\u003E childId != assembly.Id ) )\n\t\t\t{\n\t\t\t\tmemberOf[childId] = node;\n\t\t\t}\n\t\t}\n\n\t\tforeach ( var instance in plan.Instances )\n\t\t{\n\t\t\tvar node = new ArchLayerNode\n\t\t\t{\n\t\t\t\tRef = new ArchLayerRef { ItemId = instance.Id, Kind = ArchKind.AssetInstance, ParentId = 0 },\n\t\t\t\tKind = ArchKind.AssetInstance,\n\t\t\t\tStage = kinds.Stage( ArchKind.AssetInstance ),\n\t\t\t\tDomain = ArchLayerDomain.Connections,\n\t\t\t\tPayload = instance,\n\t\t\t\tName = instance.Name,\n\t\t\t};\n\n\t\t\tnodes[instance.Id] = node;\n\t\t\ttree.byId[instance.Id] = node;\n\t\t\ttree.byPayload[instance] = node;\n\t\t\ttree.AddRoot( node );\n\t\t}\n\n\t\t// Story headers exist before any room attaches, so floors sort ascending under their building.\n\t\tforeach ( var building in plan.Buildings )\n\t\t{\n\t\t\tif ( !nodes.TryGetValue( building.Id, out var buildingNode ) )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tforeach ( var floor in entries\n\t\t\t\t.Where( entry =\u003E entry.ParentId == building.Id \u0026\u0026 (entry.Kind == ArchKind.Room || entry.Kind == ArchKind.Walkway) )\n\t\t\t\t.Select( entry =\u003E entry.Floor )\n\t\t\t\t.Distinct()\n\t\t\t\t.OrderBy( floor =\u003E floor ) )\n\t\t\t{\n\t\t\t\tvar story = new ArchLayerNode\n\t\t\t\t{\n\t\t\t\t\tKind = ArchKind.Story,\n\t\t\t\t\tStage = ArchLayerStage.Shape,\n\t\t\t\t\tDomain = ArchLayerDomain.Buildings,\n\t\t\t\t\tName = $\u0022Level {floor}\u0022,\n\t\t\t\t\tFloor = floor,\n\t\t\t\t\tBuilding = building,\n\t\t\t\t};\n\n\t\t\t\tstory.Parent = buildingNode;\n\t\t\t\tbuildingNode.Children.Add( story );\n\t\t\t\ttree.stories[(building.Id, floor)] = story;\n\t\t\t}\n\t\t}\n\n\t\t// Where each layer would have stood WITHOUT its group, worked out even for a member that moves into one -\n\t\t// a group hangs where its members came from, so it needs to know where that was.\n\t\tvar typedHost = new Dictionary\u003Cint, ArchLayerNode\u003E();\n\n\t\tforeach ( var entry in entries )\n\t\t{\n\t\t\tvar node = nodes[entry.Id];\n\t\t\tvar host = entry.ParentId != 0 \u0026\u0026 nodes.TryGetValue( entry.ParentId, out var parent ) ? parent : null;\n\n\t\t\tif ( host is not null\n\t\t\t\t\u0026\u0026 host.Kind == ArchKind.Building\n\t\t\t\t\u0026\u0026 (node.Kind == ArchKind.Room || node.Kind == ArchKind.Walkway)\n\t\t\t\t\u0026\u0026 tree.stories.TryGetValue( (entry.ParentId, entry.Floor), out var story ) )\n\t\t\t{\n\t\t\t\thost = story;\n\t\t\t}\n\n\t\t\tif ( host is not null )\n\t\t\t{\n\t\t\t\ttypedHost[entry.Id] = host;\n\t\t\t}\n\n\t\t\t// Group membership outranks typed ownership: the walkway leaves the house that filed it.\n\t\t\tif ( memberOf.TryGetValue( entry.Id, out var group ) )\n\t\t\t{\n\t\t\t\tgroup.Children.Add( node );\n\t\t\t\tnode.Parent = group;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif ( host is null )\n\t\t\t{\n\t\t\t\ttree.AddRoot( node );\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\thost.Children.Add( node );\n\t\t\tnode.Parent = host;\n\t\t}\n\n\t\t// A group may hold another group: the houses a connector merges gather above it, and the\n\t\t// connector is filed beneath the group of what it affects. Nested first and whole, or a\n\t\t// group listed before its holder would root itself and then be adopted as well.\n\t\tforeach ( var assembly in plan.Assemblies )\n\t\t{\n\t\t\tforeach ( var nested in plan.Assemblies.Where( inner =\u003E inner.Id != assembly.Id \u0026\u0026 assembly.Children.Contains( inner.Id ) ) )\n\t\t\t{\n\t\t\t\tvar child = nodes[nested.Id];\n\n\t\t\t\tnodes[assembly.Id].Children.Add( child );\n\t\t\t\tchild.Parent = nodes[assembly.Id];\n\t\t\t}\n\t\t}\n\n\t\t// An anchor names a layer the group reaches without owning - the far end of a connection.\n\t\t// Members already have a row, so only the outside ones are worth stating.\n\t\tforeach ( var assembly in plan.Assemblies )\n\t\t{\n\t\t\tvar node = nodes[assembly.Id];\n\n\t\t\t// Members sit in the order the group recorded them, which is the shape of the join.\n\t\t\tvar member = node.Children.OrderBy( child =\u003E Membership( assembly, child ) ).ToList();\n\n\t\t\tnode.Children.Clear();\n\t\t\tnode.Children.AddRange( member );\n\n\t\t\tforeach ( var link in plan.Links.Where( link =\u003E link.SourceId == assembly.Id ) )\n\t\t\t{\n\t\t\t\ttree.byId.TryGetValue( link.TargetId, out var targetNode );\n\n\t\t\t\t// An anchor pointing at something already in the group says nothing the rows above it\n\t\t\t\t// do not - and spelling out its whole path is how a tree turns into a wall of text.\n\t\t\t\tif ( targetNode is null || Within( targetNode, node ) )\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tnode.Children.Add( new ArchLayerNode\n\t\t\t\t{\n\t\t\t\t\tKind = ArchKind.Assembly,\n\t\t\t\t\tStage = ArchLayerStage.Reference,\n\t\t\t\t\tDomain = node.Domain,\n\t\t\t\t\tPayload = new ArchLayerReference { SourcePort = link.SourcePort, TargetId = link.TargetId, TargetPort = link.TargetPort },\n\t\t\t\t\tName = $\u0022{link.SourcePort} \u2192 {targetNode.Name}\u0022,\n\t\t\t\t\tParent = node,\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tif ( node.Parent is null \u0026\u0026 Homed( assembly, typedHost ) is { } home )\n\t\t\t{\n\t\t\t\thome.Children.Add( node );\n\t\t\t\tnode.Parent = home;\n\t\t\t}\n\n\t\t\tif ( node.Parent is null )\n\t\t\t{\n\t\t\t\ttree.AddRoot( node );\n\t\t\t}\n\t\t}\n\n\t\tforeach ( var group in tree.Domains )\n\t\t{\n\t\t\tSort( group.Children );\n\t\t}\n\n\t\ttree.Links = plan.Links;\n\n\t\treturn tree;\n\t}\n\n\t// Stage decides evaluation; Order only decides where siblings sit inside their stage, which is\n\t// what a drag in the stack rearranges.\n\tstatic void Sort( List\u003CArchLayerNode\u003E children )\n\t{\n\t\tif ( children.Count \u003E 1 )\n\t\t{\n\t\t\tvar ordered = children.OrderBy( child =\u003E child.Order ).ToList();\n\n\t\t\tchildren.Clear();\n\t\t\tchildren.AddRange( ordered );\n\t\t}\n\n\t\tforeach ( var child in children )\n\t\t{\n\t\t\tSort( child.Children );\n\t\t}\n\t}\n\n\tstatic bool Within( ArchLayerNode node, ArchLayerNode ancestor )\n\t{\n\t\tfor ( var current = node; current is not null; current = current.Parent )\n\t\t{\n\t\t\tif ( ReferenceEquals( current, ancestor ) )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// A GROUP STANDS WHERE ITS MEMBERS STOOD. Folding five runs inside one house into a folder is a scope over\n\t// those rows, not a move to the top of the plan - rooting it there took them out of the house with it.\n\t//\n\t// Members drawn from two different hosts have no one home and root at the domain as before, and so does a group\n\t// whose members\u0027 host is itself a member, which would otherwise hang the folder inside its own contents.\n\tstatic ArchLayerNode Homed( ArchSiteAssembly assembly, Dictionary\u003Cint, ArchLayerNode\u003E typedHost )\n\t{\n\t\tArchLayerNode home = null;\n\n\t\tforeach ( var childId in assembly.Children )\n\t\t{\n\t\t\tif ( !typedHost.TryGetValue( childId, out var host ) )\n\t\t\t{\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\thome ??= host;\n\n\t\t\tif ( !ReferenceEquals( home, host ) )\n\t\t\t{\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\tfor ( var walk = home; walk is not null; walk = walk.Parent )\n\t\t{\n\t\t\tif ( walk.Ref is { } layer \u0026\u0026 assembly.Children.Contains( layer.ItemId ) )\n\t\t\t{\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\treturn home;\n\t}\n\n\tstatic int Membership( ArchSiteAssembly assembly, ArchLayerNode child )\n\t{\n\t\tvar at = child.Ref is { } layer ? assembly.Children.IndexOf( layer.ItemId ) : -1;\n\n\t\treturn at \u003C 0 ? int.MaxValue : at;\n\t}\n\n\t// Where the folder sits: with whatever it holds, so a group of houses stays under Buildings.\n\tstatic ArchLayerDomain DomainOf( ArchSiteAssembly assembly, Dictionary\u003Cint, Entry\u003E byId, ArchKinds kinds )\n\t{\n\t\tforeach ( var childId in assembly.Children )\n\t\t{\n\t\t\tif ( byId.TryGetValue( childId, out var entry ) )\n\t\t\t{\n\t\t\t\treturn kinds.Domain( entry.Kind );\n\t\t\t}\n\t\t}\n\n\t\treturn ArchLayerDomain.Connections;\n\t}\n\n\tvoid AddRoot( ArchLayerNode node )\n\t{\n\t\tvar group = Domains.FirstOrDefault( domain =\u003E domain.Domain == node.Domain );\n\n\t\tif ( group is null )\n\t\t{\n\t\t\tgroup = new ArchLayerDomainGroup\n\t\t\t{\n\t\t\t\tDomain = node.Domain,\n\t\t\t\tName = node.Domain switch\n\t\t\t\t{\n\t\t\t\t\tArchLayerDomain.Buildings =\u003E \u0022Buildings\u0022,\n\t\t\t\t\tArchLayerDomain.Connections =\u003E \u0022Connections\u0022,\n\t\t\t\t\t_ =\u003E \u0022Infrastructure\u0022\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tDomains.Add( group );\n\t\t}\n\n\t\tgroup.Children.Add( node );\n\t}\n\n\tsealed class Entry\n\t{\n\t\tpublic object Payload;\n\t\tpublic int Id;\n\t\tpublic int ParentId;\n\t\tpublic ArchKind Kind;\n\t\tpublic ArchLayerStage? Stage;\n\t\tpublic bool Enabled = true;\n\t\tpublic bool Locked;\n\t\tpublic int Order;\n\t\tpublic string Name;\n\t\tpublic int Floor = int.MinValue;\n\t\tpublic ArchBuilding Building;\n\t\tpublic ArchRoom Room;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Output/ArchCull.Interiors.cs","FileName":"ArchCull.Interiors.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Editor;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n// One shallow room behind one opening on a facade nobody can walk into. DERIVED, so it carries no id and no\n// layer record - exactly as slabs, ceilings, roof decks and platforms carry none.\npublic sealed class ArchFalseInterior\n{\n\tpublic ArchBuilding Building { get; init; }\n\tpublic ArchRoom Room { get; init; }\n\tpublic ArchWall Wall { get; init; }\n\tpublic ArchOpening Opening { get; init; }\n\t// The room face of the wall, in the wall\u0027s own frame - the plane the box is measured off.\n\tpublic float Face { get; init; }\n\tpublic float Depth { get; init; }\n\n\tpublic float Back =\u003E Face \u002B Depth;\n}\n\npublic static partial class ArchCull\n{\n\t// No way in and nothing above, asked of a whole unit rather than one of its roofs: a shell with no door\n\t// and no archway, capped everywhere, is a facade. Anything else is a room, or becoming one.\n\tpublic static bool Unenterable( ArchBuilding building, ArchPlan plan, ArchKit kit )\n\t{\n\t\tif ( building is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tvar walls = building.Rooms.SelectMany( room =\u003E room.Walls.Select( wall =\u003E (Room: room, Wall: wall) ) ).ToList();\n\n\t\tif ( walls.Count == 0 )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\t// A single-sided shell emits no room face at all, so there is nothing in there to be in.\n\t\tif ( walls.All( standing =\u003E ArchWallSection.SingleSided( standing.Wall, standing.Room ) ) )\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( walls.SelectMany( standing =\u003E standing.Wall.Openings ).Any( Entered ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\treturn building.Roofs.Count \u003E 0 \u0026\u0026 building.Roofs.All( roof =\u003E Sealed( roof, building, plan, kit ) );\n\t}\n\n\t// A leaf hangs in a way in and an archway is a hole walked straight through. A window is neither.\n\tstatic bool Entered( ArchOpening opening )\n\t{\n\t\treturn opening.Kind.Hangs() || opening.Kind == OpeningKind.Archway;\n\t}\n\n\t// Every box the plan asks for, resolved without a scene, so the pass, the report and the test all read one\n\t// answer. A unit ArchCull calls enterable contributes none: behind a real room the box would z-fight the\n\t// room it is standing inside.\n\tpublic static List\u003CArchFalseInterior\u003E Behind( ArchPlan plan, ArchKit kit )\n\t{\n\t\tvar found = new List\u003CArchFalseInterior\u003E();\n\n\t\tif ( plan is null )\n\t\t{\n\t\t\treturn found;\n\t\t}\n\n\t\tvar depth = MathF.Max( 1f, kit.FalseInteriorDepth );\n\n\t\tforeach ( var building in plan.Buildings.Where( unit =\u003E Unenterable( unit, plan, kit ) ) )\n\t\t{\n\t\t\tforeach ( var room in building.Rooms )\n\t\t\t{\n\t\t\t\tforeach ( var wall in room.Walls.Where( wall =\u003E !ArchWallJoins.CoveredBy( building, room, wall ) ) )\n\t\t\t\t{\n\t\t\t\t\tvar face = ArchWallSection.Thickness( wall, kit ) * 0.5f;\n\n\t\t\t\t\tfound.AddRange( Showing( wall ).Select( opening =\u003E new ArchFalseInterior\n\t\t\t\t\t{\n\t\t\t\t\t\tBuilding = building,\n\t\t\t\t\t\tRoom = room,\n\t\t\t\t\t\tWall = wall,\n\t\t\t\t\t\tOpening = opening,\n\t\t\t\t\t\tFace = face,\n\t\t\t\t\t\tDepth = depth\n\t\t\t\t\t} ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn found;\n\t}\n\n\t// The units the wall generator actually cut a hole for - the same filter it runs, because a box behind a\n\t// hole nothing opened is a box hanging in a solid wall.\n\tstatic IEnumerable\u003CArchOpening\u003E Showing( ArchWall wall )\n\t{\n\t\treturn wall.Openings\n\t\t\t.Where( opening =\u003E opening.Width \u003E 0.5f \u0026\u0026 opening.Height \u003E 0.5f )\n\t\t\t.Where( opening =\u003E ArchLayerGate.On( opening ) \u0026\u0026 ArchLayerGate.Owned( opening.OwnerId ) )\n\t\t\t.Where( opening =\u003E opening.SwallowedBy == 0 || !ArchLayerGate.Owned( opening.SwallowedBy ) );\n\t}\n\n\t// The box, in the wall\u0027s own frame, wound to face the hole. Its front IS the hole: a face on the wall\u0027s\n\t// room plane would be the sticker this exists to remove.\n\tpublic static void Line( ArchMesh canvas, ArchFalseInterior interior, ArchBrush brush )\n\t{\n\t\tvar opening = interior.Opening;\n\t\tvar left = opening.Left;\n\t\tvar right = opening.Right;\n\t\tvar bottom = MathF.Max( 0f, opening.SillHeight );\n\t\tvar top = opening.Top;\n\t\tvar face = interior.Face;\n\t\tvar back = interior.Back;\n\n\t\tif ( right - left \u003C 0.5f || top - bottom \u003C 0.5f || interior.Depth \u003C 0.5f )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tcanvas.Quad(\n\t\t\tnew Vector3( left, back, bottom ),\n\t\t\tnew Vector3( right, back, bottom ),\n\t\t\tnew Vector3( right, back, top ),\n\t\t\tnew Vector3( left, back, top ),\n\t\t\tbrush );\n\n\t\tcanvas.Quad(\n\t\t\tnew Vector3( left, face, bottom ),\n\t\t\tnew Vector3( left, back, bottom ),\n\t\t\tnew Vector3( left, back, top ),\n\t\t\tnew Vector3( left, face, top ),\n\t\t\tbrush );\n\n\t\tcanvas.Quad(\n\t\t\tnew Vector3( right, back, bottom ),\n\t\t\tnew Vector3( right, face, bottom ),\n\t\t\tnew Vector3( right, face, top ),\n\t\t\tnew Vector3( right, back, top ),\n\t\t\tbrush );\n\n\t\tcanvas.Quad(\n\t\t\tnew Vector3( left, face, top ),\n\t\t\tnew Vector3( left, back, top ),\n\t\t\tnew Vector3( right, back, top ),\n\t\t\tnew Vector3( right, face, top ),\n\t\t\tbrush );\n\n\t\tcanvas.Quad(\n\t\t\tnew Vector3( left, back, bottom ),\n\t\t\tnew Vector3( left, face, bottom ),\n\t\t\tnew Vector3( right, face, bottom ),\n\t\t\tnew Vector3( right, back, bottom ),\n\t\t\tbrush );\n\t}\n\n\t// The pass on its own, for when the faces have already been cleaned. A rebuild prunes what it emits, the\n\t// same way it takes back the faces Clean removed.\n\tpublic static int Interiors( Scene scene, ArchPlan plan, ArchKit kit )\n\t{\n\t\tvar root = ArchScene.FindRoot( scene );\n\n\t\tif ( !root.IsValid() )\n\t\t{\n\t\t\tLog.Warning( \u0022Architecture: nothing to line - no generated root in this scene.\u0022 );\n\n\t\t\treturn 0;\n\t\t}\n\n\t\tusing ( SceneEditorSession.Active.UndoScope( \u0022Line False Interiors\u0022 ).WithGameObjectChanges( root, GameObjectUndoFlags.All ).Push() )\n\t\t{\n\t\t\treturn Lined( root, plan, kit );\n\t\t}\n\t}\n\n\t// Re-derived every run - a door added to a facade makes it a room, and the box behind its window has to GO rather\n\t// than be left standing inside it - but only HANDED OVER where the box actually changed. Drawing one is five\n\t// quads; giving it to the engine cooks a collision hull, a physics mesh and a trace mesh, and this pass reaches\n\t// every window on every facade in the plan.\n\t//\n\t// A null cache lines them all, which is what the menu action and a scene nobody has built through mean.\n\tpublic static int Lined( GameObject root, ArchPlan plan, ArchKit kit, ArchBuildCache cache = null )\n\t{\n\t\tvar nodes = Walls( root );\n\t\tvar style = new ArchStyle( kit );\n\t\tvar wanted = Behind( plan, kit ).GroupBy( interior =\u003E interior.Wall.Id ).ToDictionary( group =\u003E group.Key, group =\u003E group.ToList() );\n\t\tvar lined = 0;\n\n\t\tforeach ( var bare in nodes.Where( entry =\u003E !wanted.ContainsKey( entry.Key ) ) )\n\t\t{\n\t\t\tStrip( bare.Value );\n\t\t\tcache?.Unlined( bare.Key );\n\t\t}\n\n\t\tforeach ( var group in wanted )\n\t\t{\n\t\t\tif ( !nodes.TryGetValue( group.Key, out var node ) )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// The wall\u0027s own frame, so the boxes are drawn in the coordinates the wall generator uses and the\n\t\t\t// projection puts them on the same grain as the room face they hang behind.\n\t\t\tvar canvas = new ArchMesh( node.WorldTransform );\n\n\t\t\tforeach ( var interior in group.Value )\n\t\t\t{\n\t\t\t\tLine( canvas, interior, style.Brush(\n\t\t\t\t\tArchSurface.WallInterior, interior.Wall.Palette, interior.Room.Palette, interior.Building.Palette ) );\n\n\t\t\t\tlined\u002B\u002B;\n\t\t\t}\n\n\t\t\t// Asked of the box\u0027s own key, the way a part is - and of the scene as well, because a hand-delete leaves\n\t\t\t// the cache saying something is standing that is not.\n\t\t\tif ( cache?.Lined( group.Key, canvas.Content ) == true \u0026\u0026 Standing( node ) )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tStrip( node );\n\t\t\tFit( node, canvas );\n\t\t}\n\n\t\treturn lined;\n\t}\n\n\tstatic void Strip( GameObject wall )\n\t{\n\t\tif ( wall.Children.FirstOrDefault( child =\u003E child.Name == ArchPieces.Interior ) is { } standing )\n\t\t{\n\t\t\tstanding.DestroyImmediate();\n\t\t}\n\t}\n\n\tstatic bool Standing( GameObject wall )\n\t{\n\t\treturn wall.Children.Any( child =\u003E child.Name == ArchPieces.Interior );\n\t}\n\n\tstatic Dictionary\u003Cint, GameObject\u003E Walls( GameObject root )\n\t{\n\t\tvar nodes = new Dictionary\u003Cint, GameObject\u003E();\n\n\t\tforeach ( var node in ArchScene.Descendants( root ) )\n\t\t{\n\t\t\tif ( ArchNames.TryParseId( node.Name, \u0022Wall\u0022, out var id ) )\n\t\t\t{\n\t\t\t\tnodes[id] = node;\n\t\t\t}\n\t\t}\n\n\t\treturn nodes;\n\t}\n\n\tstatic void Fit( GameObject wall, ArchMesh canvas )\n\t{\n\t\tif ( canvas.IsEmpty )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tvar node = wall.Scene.CreateObject();\n\n\t\tnode.Name = ArchPieces.Interior;\n\t\tnode.SetParent( wall, false );\n\t\tnode.Tags.Add( ArchScene.GeneratedTag );\n\n\t\tvar renderer = node.Components.GetOrCreate\u003CMeshComponent\u003E();\n\n\t\trenderer.Color = Color.White;\n\t\trenderer.SmoothingAngle = 0f;\n\n\t\t// It must not be traceable: the cleaning pass reads cover by ray, and a box behind a window it could\n\t\t// hit would have the elevation in front of it stripped as buried.\n\t\tArchCollision.Write( node, renderer, canvas, ArchCollisionMode.None );\n\n\t\trenderer.Mesh = canvas.Finish();\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Carve/ArchCarveVolume.cs","FileName":"ArchCarveVolume.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n// The fall is what makes a raked deck a carveable solid - not a lofted quad.\npublic readonly struct ArchCarvePlane\n{\n\tpublic float Datum { get; init; }\n\tpublic Vector2 Origin { get; init; }\n\tpublic Vector2 Fall { get; init; }\n\n\tpublic static ArchCarvePlane Level( float height ) =\u003E new() { Datum = height };\n\n\tpublic static ArchCarvePlane Through( Vector2 origin, float datum, Vector2 fall )\n\t{\n\t\treturn new ArchCarvePlane { Origin = origin, Datum = datum, Fall = fall };\n\t}\n\n\tpublic bool Rakes =\u003E Fall.Length \u003E 0.0001f;\n\n\tpublic float At( Vector2 point ) =\u003E Datum \u002B Vector2.Dot( Fall, point - Origin );\n\n\tpublic ArchCarvePlane Raised( float by ) =\u003E new() { Datum = Datum \u002B by, Origin = Origin, Fall = Fall };\n\n\t// Where a ray meets this plane. A raked plane and a ray are both linear, so the crossing solves in closed\n\t// form; every deck a cursor can come to rest on is measured through here so a roof and a ramp cannot answer\n\t// the same ray two different ways.\n\tpublic bool Crosses( Ray ray, out float reach, out Vector3 hit )\n\t{\n\t\treach = 0f;\n\t\thit = default;\n\n\t\tvar closing = ray.Forward.z - Vector2.Dot( Fall, new Vector2( ray.Forward.x, ray.Forward.y ) );\n\n\t\tif ( MathF.Abs( closing ) \u003C 0.0001f )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\treach = (At( new Vector2( ray.Position.x, ray.Position.y ) ) - ray.Position.z) / closing;\n\n\t\tif ( reach \u003C 0f )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\thit = ray.Position \u002B ray.Forward * reach;\n\n\t\treturn true;\n\t}\n}\n\n// A ruined edge, carried by the volume that takes the bite. The seed is the cut\u0027s own id, so the same shaft\n// hands back the same ruin after a hotload and the build cache is not defeated by a vertex that moved.\npublic readonly struct ArchCarveBreak\n{\n\tpublic int Seed { get; init; }\n\tpublic float Jitter { get; init; }\n\n\tpublic bool Breaks =\u003E Seed != 0 \u0026\u0026 Jitter \u003E ArchGridService.FinestSize;\n}\n\n// Both sides of a carve - the solid and the cut - are this same volume.\npublic readonly struct ArchCarveVolume\n{\n\tpublic IReadOnlyList\u003CVector2\u003E Footprint { get; init; }\n\tpublic ArchCarvePlane Floor { get; init; }\n\tpublic ArchCarvePlane Ceiling { get; init; }\n\tpublic ArchCarveBreak Break { get; init; }\n\n\tpublic static ArchCarveVolume Over( IReadOnlyList\u003CVector2\u003E footprint, float from, float to )\n\t{\n\t\treturn new ArchCarveVolume\n\t\t{\n\t\t\tFootprint = footprint,\n\t\t\tFloor = ArchCarvePlane.Level( MathF.Min( from, to ) ),\n\t\t\tCeiling = ArchCarvePlane.Level( MathF.Max( from, to ) )\n\t\t};\n\t}\n\n\tpublic ArchCarveVolume Breaking( ArchCarveBreak breaking )\n\t{\n\t\treturn new ArchCarveVolume { Footprint = Footprint, Floor = Floor, Ceiling = Ceiling, Break = breaking };\n\t}\n\n\t// A level base under a raked top - the wedge a ramp is. Not Raked(): that is two parallel planes a constant\n\t// thickness apart, which is a sloping slab and not something whose foot meets the ground it stands on.\n\tpublic static ArchCarveVolume Under( IReadOnlyList\u003CVector2\u003E footprint, float from, ArchCarvePlane top )\n\t{\n\t\treturn new ArchCarveVolume { Footprint = footprint, Floor = ArchCarvePlane.Level( from ), Ceiling = top };\n\t}\n\n\t// The same wedge upside down, and the shape a SUBTRACTED ramp is: nothing taken out at the head, the whole\n\t// band gone at the foot, so what is left under it keeps the raked floor as its own top.\n\tpublic static ArchCarveVolume Above( IReadOnlyList\u003CVector2\u003E footprint, ArchCarvePlane floor, float to )\n\t{\n\t\treturn new ArchCarveVolume { Footprint = footprint, Floor = floor, Ceiling = ArchCarvePlane.Level( to ) };\n\t}\n\n\t// The two faces are parallel, which is what keeps the interval algebra one-dimensional.\n\tpublic static ArchCarveVolume Raked( IReadOnlyList\u003CVector2\u003E footprint, ArchCarvePlane plane, float thickness )\n\t{\n\t\tvar depth = MathF.Max( 0.05f, thickness );\n\n\t\treturn new ArchCarveVolume { Footprint = footprint, Floor = plane, Ceiling = plane.Raised( depth ) };\n\t}\n\n\tpublic bool Rakes =\u003E Floor.Rakes || Ceiling.Rakes;\n\n\tpublic bool Covers( Vector2 point ) =\u003E ArchFootprint.Encloses( new[] { Footprint }, point );\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Data/ArchArchetypeSteps.cs","FileName":"ArchArchetypeSteps.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"namespace Sunless.Architecture;\n\npublic readonly record struct ArchArchetypeStepContext(\n\tArchPlan Plan,\n\tArchBuilding Building,\n\tArchKit Kit,\n\tArchArchetypeStep Step,\n\tVector2 Min,\n\tVector2 Max,\n\tArchRoom Room,\n\tList\u003Cstring\u003E Skipped );\n\npublic interface IArchArchetypeStepBuilder\n{\n\tArchStepKind Kind { get; }\n\n\tstring Build( ArchArchetypeStepContext context );\n}\n\npublic sealed class ArchArchetypeSteps : ArchTable\u003CArchArchetypeSteps, ArchStepKind, IArchArchetypeStepBuilder\u003E\n{\n\tprotected override IEnumerable\u003CIArchArchetypeStepBuilder\u003E Standing()\n\t{\n\t\tyield return new ArchPorchArchetypeStep();\n\t}\n\n\tprotected override ArchStepKind KeyOf( IArchArchetypeStepBuilder builder ) =\u003E builder.Kind;\n\n\tprotected override string Collision( IArchArchetypeStepBuilder standing, IArchArchetypeStepBuilder builder )\n\t{\n\t\treturn $\u0022{builder.Kind} is built by both {standing.GetType().Name} and {builder.GetType().Name}\u0022;\n\t}\n\n\tpublic IArchArchetypeStepBuilder For( ArchStepKind kind ) =\u003E Held( kind );\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Data/ArchBuild.cs","FileName":"ArchBuild.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n// Auto lays the ridge along the longer side, so the flat ends land on the short walls.\npublic enum RidgeRun\n{\n\tAuto,\n\tAlongX,\n\tAlongY\n}\n\npublic enum SectionRoof\n{\n\tContinue,\n\tHip,\n\tGable,\n\tCapped,\n\tNone\n}\n\npublic sealed class ArchSection\n{\n\tpublic ArchRoom Room { get; init; }\n\tpublic ArchRoofPart Roof { get; init; }\n\tpublic int SharedWalls { get; init; }\n\tpublic bool Merged { get; init; }\n}\n\n// Shared by the Building subtool and the arch_* tools - both grow a plan the same way.\npublic static class ArchBuild\n{\n\t// THE STOREY GRID, and the only answer to it. A building\u0027s own override first; otherwise the storey it\n\t// actually STANDS - its ground rooms\u0027 real plate plus a floor - never the one the kit would have stood. An\n\t// archetype gives its shell taller walls than the kit\u0027s, and a grid that never learns seats the next floor\n\t// mid-wall inside the storey below, with every column under it standing straight through the slab.\n\tpublic static float StoreyHeight( ArchBuilding building, ArchKit kit )\n\t{\n\t\tif ( building is { StoreyHeight: \u003E 1f } )\n\t\t{\n\t\t\treturn building.StoreyHeight;\n\t\t}\n\n\t\treturn Ground( building, kit ) \u002B kit.FloorThickness;\n\t}\n\n\tpublic static float FloorOf( ArchBuilding building, ArchKit kit, int level )\n\t{\n\t\treturn level * StoreyHeight( building, kit ) \u002B kit.GroundClearance;\n\t}\n\n\t// The tallest room on the ground, because a storey is as tall as the walls the floor above lands on. A\n\t// building with nothing standing yet is the kit\u0027s, which is what the first drag needs.\n\tstatic float Ground( ArchBuilding building, ArchKit kit )\n\t{\n\t\tvar standing = 0f;\n\n\t\tforeach ( var room in building?.Rooms ?? Enumerable.Empty\u003CArchRoom\u003E() )\n\t\t{\n\t\t\tif ( room.Floor != 0 || room.Spans )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tstanding = MathF.Max( standing, ArchFloorGen.WallHeight( room, kit ) );\n\t\t}\n\n\t\treturn standing \u003E 1f ? standing : kit.WallHeight;\n\t}\n\n\tpublic static ArchSection Shell(\n\t\tArchPlan plan,\n\t\tArchBuilding building,\n\t\tArchKit kit,\n\t\tint level,\n\t\tfloat baseHeight,\n\t\tVector2 min,\n\t\tVector2 max,\n\t\tbool floor,\n\t\tRoofStyle? style,\n\t\tbool gutters,\n\t\tRidgeRun ridge = RidgeRun.Auto )\n\t{\n\t\tvar placement = new ArchBoundaryPlacementService( plan, kit ).Outside( level, min, max );\n\n\t\tif ( !placement.IsUsable )\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\tmin = placement.Min;\n\t\tmax = placement.Max;\n\t\tvar shell = CreateRoom( plan, building, \u0022Shell\u0022, level, baseHeight, kit.WallHeight, min, max, floor, null, kit.WallThickness );\n\n\t\tif ( style is null )\n\t\t{\n\t\t\treturn new ArchSection { Room = shell };\n\t\t}\n\n\t\tvar roof = Roof( plan, kit, level, style.Value, min, max, baseHeight \u002B kit.WallHeight, gutters, false, ridge );\n\t\tbuilding.Roofs.Add( roof );\n\n\t\treturn new ArchSection { Room = shell, Roof = roof };\n\t}\n\n\tpublic static ArchSection Partition(\n\t\tArchPlan plan,\n\t\tArchBuilding building,\n\t\tArchKit kit,\n\t\tint level,\n\t\tfloat baseHeight,\n\t\tVector2 min,\n\t\tVector2 max,\n\t\tbool floor )\n\t{\n\t\t( min, max ) = new ArchGridService().Rectangle( min, max );\n\t\tvar party = new List\u003C(Vector2 From, Vector2 To)\u003E();\n\t\tvar room = CreateRoom( plan, building, $\u0022Room{building.Rooms.Count \u002B 1}\u0022, level, baseHeight, kit.WallHeight, min, max, floor, party, kit.WallThickness );\n\n\t\treturn new ArchSection { Room = room, SharedWalls = party.Count };\n\t}\n\n\t// Two buildings on one plot each roof their own storey and the pair clip each other into nonsense.\n\tpublic static bool Stacks( ArchBuilding building, ArchKit kit, Vector2 min, Vector2 max )\n\t{\n\t\tif ( building is null || !building.HasContent )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tvar covered = ArchRegion.Shell( ArchRegion.Footprints( building.Rooms ), kit.WallThickness );\n\n\t\treturn ArchRegion.Covers( covered, ArchFootprint.Rect( min, max ) );\n\t}\n\n\tpublic static ArchSection Storey(\n\t\tArchPlan plan,\n\t\tArchBuilding building,\n\t\tArchKit kit,\n\t\tint level,\n\t\tfloat baseHeight,\n\t\tVector2 min,\n\t\tVector2 max,\n\t\tbool floor,\n\t\tRoofStyle? style,\n\t\tbool gutters,\n\t\tRidgeRun ridge = RidgeRun.Auto )\n\t{\n\t\t( min, max ) = new ArchGridService().Rectangle( min, max );\n\t\tvar snapped = new ArchWingPlacementService( building, kit, level ).Snap( min, max );\n\t\tmin = snapped.Min;\n\t\tmax = snapped.Max;\n\n\t\tvar room = CreateRoom( plan, building, $\u0022Storey{level}\u0022, level, baseHeight, kit.WallHeight, min, max, floor, null, kit.WallThickness );\n\t\tvar plate = baseHeight \u002B kit.WallHeight;\n\t\tvar outline = ArchFootprint.Rect( min, max );\n\n\t\tif ( DeckBuiltOverBy( building, outline ) is { } below )\n\t\t{\n\t\t\tbelow.Level = level;\n\t\t\tbelow.BaseHeight = plate;\n\t\t\tbelow.Reshape( outline );\n\n\t\t\treturn new ArchSection { Room = room, Roof = below, Merged = true };\n\t\t}\n\n\t\tif ( style is null )\n\t\t{\n\t\t\treturn new ArchSection { Room = room };\n\t\t}\n\n\t\tvar roof = Roof( plan, kit, level, style.Value, min, max, plate, gutters, false, ridge );\n\t\tbuilding.Roofs.Add( roof );\n\n\t\treturn new ArchSection { Room = room, Roof = roof };\n\t}\n\n\t// Only a FULLY covered deck is rebuilt - a partial one is a real stepped building.\n\tstatic ArchRoofPart DeckBuiltOverBy( ArchBuilding building, List\u003CVector2\u003E outline )\n\t{\n\t\treturn building.Roofs.FirstOrDefault( roof =\u003E ArchRegion.Covers( new[] { outline }, roof.Outline() ) );\n\t}\n\n\t// Continuing folds the wing into the section\u0027s footprint (valleys inside); otherwise its own roof, stepped by the eave drop.\n\tpublic static ArchSection Extend(\n\t\tArchPlan plan,\n\t\tArchBuilding building,\n\t\tArchKit kit,\n\t\tint level,\n\t\tfloat baseHeight,\n\t\tVector2 min,\n\t\tVector2 max,\n\t\tSectionRoof choice,\n\t\tfloat drop,\n\t\tbool floor,\n\t\tbool gutters,\n\t\tRidgeRun ridge = RidgeRun.Auto )\n\t{\n\t\t( min, max ) = new ArchGridService().Rectangle( min, max );\n\n\t\treturn new ArchWingGenerationService( plan, building, kit )\n\t\t\t.On( level, baseHeight, min, max )\n\t\t\t.WithRoof( choice, drop, gutters, ridge )\n\t\t\t.WithFloor( floor )\n\t\t\t.Create();\n\t}\n\n\tpublic static ArchRoofPart Roof(\n\t\tArchPlan plan,\n\t\tArchKit kit,\n\t\tint level,\n\t\tRoofStyle style,\n\t\tVector2 min,\n\t\tVector2 max,\n\t\tfloat baseHeight,\n\t\tbool gutters,\n\t\tbool parapet,\n\t\tRidgeRun ridge = RidgeRun.Auto )\n\t{\n\t\treturn new ArchRoofPart\n\t\t{\n\t\t\tId = plan.AllocateId(),\n\t\t\tName = \u0022Roof\u0022,\n\t\t\tLevel = level,\n\t\t\tStyle = style,\n\t\t\tMin = min,\n\t\t\tMax = max,\n\t\t\tFootprint = ArchFootprint.Rect( min, max ),\n\t\t\tBaseHeight = baseHeight,\n\t\t\tPitch = kit.RoofPitch,\n\t\t\tRidgeAlongX = Ridged( ridge, min, max ),\n\t\t\t// A parapet stands on the wall line - an overhang would leave the coping floating.\n\t\t\tOverhang = parapet ? 0f : kit.RoofOverhang,\n\t\t\tThickness = kit.RoofThickness,\n\t\t\tGutters = gutters \u0026\u0026 !parapet,\n\t\t\tFascia = !parapet,\n\t\t\tSoffit = !parapet,\n\t\t\tParapet = parapet,\n\t\t\tCeiling = Ceiling( style )\n\t\t};\n\t}\n\n\t// Shared with the ghost, so what is drawn is what gets built.\n\tpublic static bool Ridged( RidgeRun ridge, Vector2 min, Vector2 max ) =\u003E ridge switch\n\t{\n\t\tRidgeRun.AlongX =\u003E true,\n\t\tRidgeRun.AlongY =\u003E false,\n\t\t_ =\u003E max.x - min.x \u003E= max.y - min.y\n\t};\n\n\t// Hip/gable leave a void, so they get a ceiling; shed/sawtooth are meant to be seen from underneath.\n\tstatic bool Ceiling( RoofStyle style )\n\t{\n\t\treturn style is RoofStyle.Hip or RoofStyle.Gable;\n\t}\n\n\t// The one mapping for a wing\u0027s roof choice - the ghost, the generator and the re-dress must agree.\n\tpublic static RoofStyle Winged( SectionRoof choice )\n\t{\n\t\treturn choice switch\n\t\t{\n\t\t\tSectionRoof.Gable =\u003E RoofStyle.Gable,\n\t\t\tSectionRoof.Capped =\u003E RoofStyle.Flat,\n\t\t\t_ =\u003E RoofStyle.Hip\n\t\t};\n\t}\n\n\tinternal static ArchRoom CreateRoom(\n\t\tArchPlan plan,\n\t\tArchBuilding building,\n\t\tstring name,\n\t\tint level,\n\t\tfloat baseHeight,\n\t\tfloat wallHeight,\n\t\tVector2 min,\n\t\tVector2 max,\n\t\tbool floor,\n\t\tList\u003C(Vector2 From, Vector2 To)\u003E shared,\n\t\tfloat thickness )\n\t{\n\t\tvar room = new ArchRoom\n\t\t{\n\t\t\tId = plan.AllocateId(),\n\t\t\tName = name,\n\t\t\tFloor = level,\n\t\t\tBaseHeight = baseHeight,\n\t\t\tWallHeight = wallHeight,\n\t\t\tHasFloor = floor,\n\t\t\tFootprint = ArchFootprint.Rect( min, max )\n\t\t};\n\n\t\tfor ( var index = 0; index \u003C room.Footprint.Count; index\u002B\u002B )\n\t\t{\n\t\t\tvar start = room.Footprint[index];\n\t\t\tvar end = room.Footprint[(index \u002B 1) % room.Footprint.Count];\n\n\t\t\troom.Walls.Add( new ArchWall\n\t\t\t{\n\t\t\t\tId = plan.AllocateId(),\n\t\t\t\tStart = start,\n\t\t\t\tEnd = end,\n\t\t\t\tExterior = true,\n\t\t\t\tCap = true\n\t\t\t} );\n\n\t\t\tif ( shared is not null \u0026\u0026 ArchWallJoins.PartyWallExists( building, room.BaseHeight, start, end, thickness ) )\n\t\t\t{\n\t\t\t\tshared.Add( (start, end) );\n\t\t\t}\n\t\t}\n\n\t\tbuilding.Rooms.Add( room );\n\n\t\treturn room;\n\t}\n\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Data/ArchCatalogs.cs","FileName":"ArchCatalogs.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"namespace Sunless.Architecture;\n\n// Which shelf a catalog stands on. A role rather than an interface per catalog, because the catalogs differ only in\n// what they hold - a module brings its own with new ArchShelf( role ), and the reservation only stops two of them\n// meaning the same thing.\npublic readonly record struct ArchShelf( string Role )\n{\n\tpublic static readonly ArchShelf Openings = new( \u0022openings\u0022 );\n\tpublic static readonly ArchShelf Walls = new( \u0022walls\u0022 );\n\tpublic static readonly ArchShelf Pillars = new( \u0022pillars\u0022 );\n\tpublic static readonly ArchShelf RoadLines = new( \u0022roadlines\u0022 );\n\tpublic static readonly ArchShelf Profiles = new( \u0022profiles\u0022 );\n\tpublic static readonly ArchShelf Cornices = new( \u0022cornices\u0022 );\n\tpublic static readonly ArchShelf Types = new( \u0022types\u0022 );\n\n\tpublic override string ToString() =\u003E Role;\n}\n\n// One catalog of authored types, owned by the module whose subtool authors them. The TYPE itself stays in core: a\n// porch fits an opening preset it never authored, so a preset is shared geometry by the same rule a payload is,\n// while the catalog around it belongs to one module alone.\npublic interface IArchCatalog\n{\n\tArchShelf Shelf { get; }\n\n\tstring Named( object entry );\n\n\t// What the module brings with it; an authored entry of the same name wins, so a tuned preset survives upgrades.\n\tIEnumerable\u003Cobject\u003E Shipped();\n\n\t// What stands on disk beside it, which tops what ships.\n\tIEnumerable\u003Cobject\u003E Authored();\n\n\tbool Save( object entry );\n}\n\npublic sealed class ArchCatalogs : ArchTable\u003CArchCatalogs, ArchShelf, IArchCatalog\u003E\n{\n\tprotected override IEnumerable\u003CIArchCatalog\u003E Standing()\n\t{\n\t\tyield return new ArchArchetypeCatalog();\n\t\tyield return new ArchProfileCatalog();\n\t\tyield return new ArchCorniceCatalog();\n\t\tyield return new ArchOpeningCatalog();\n\t\tyield return new ArchWallCatalog();\n\t\tyield return new ArchPillarCatalog();\n\t\tyield return new ArchRoadLineCatalog();\n\t}\n\n\tprotected override ArchShelf KeyOf( IArchCatalog catalog ) =\u003E catalog.Shelf;\n\n\tprotected override bool Accepts( IArchCatalog catalog ) =\u003E !string.IsNullOrWhiteSpace( catalog.Shelf.Role );\n\n\tprotected override string Collision( IArchCatalog standing, IArchCatalog catalog )\n\t{\n\t\treturn $\u0022{catalog.Shelf} is claimed by both {standing.GetType().Name} and {catalog.GetType().Name}\u0022;\n\t}\n\n\t// A shelf no catalog claims stands empty rather than erroring.\n\tpublic IArchCatalog On( ArchShelf shelf ) =\u003E Held( shelf );\n\n\tpublic List\u003CT\u003E Ships\u003CT\u003E( ArchShelf shelf ) where T : class\n\t{\n\t\treturn On( shelf ) is { } catalog ? catalog.Shipped().OfType\u003CT\u003E().ToList() : new List\u003CT\u003E();\n\t}\n\n\t// For a catalog whose authored folder IS the offering - a picker that browses what an author has on disk.\n\tpublic List\u003CT\u003E Authors\u003CT\u003E( ArchShelf shelf ) where T : class\n\t{\n\t\treturn On( shelf ) is { } catalog ? catalog.Authored().OfType\u003CT\u003E().ToList() : new List\u003CT\u003E();\n\t}\n\n\t// What ships never displaces what is already held, and what is authored on disk always does - so an upgrade\n\t// brings new presets in beside the tuned ones and a written file is the last word on its own name.\n\tpublic void Stock\u003CT\u003E( ArchShelf shelf, List\u003CT\u003E saved ) where T : class\n\t{\n\t\tif ( saved is null || On( shelf ) is not { } catalog )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tforeach ( var entry in catalog.Shipped().OfType\u003CT\u003E() )\n\t\t{\n\t\t\tif ( !saved.Any( existing =\u003E Same( catalog.Named( existing ), catalog.Named( entry ) ) ) )\n\t\t\t{\n\t\t\t\tsaved.Add( entry );\n\t\t\t}\n\t\t}\n\n\t\tforeach ( var entry in catalog.Authored().OfType\u003CT\u003E() )\n\t\t{\n\t\t\tvar index = saved.FindIndex( existing =\u003E Same( catalog.Named( existing ), catalog.Named( entry ) ) );\n\n\t\t\tif ( index \u003E= 0 )\n\t\t\t{\n\t\t\t\tsaved[index] = entry;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tsaved.Add( entry );\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic bool Save( ArchShelf shelf, object entry ) =\u003E On( shelf ) is { } catalog \u0026\u0026 catalog.Save( entry );\n\n\tstatic bool Same( string first, string second ) =\u003E string.Equals( first, second, StringComparison.OrdinalIgnoreCase );\n}\n\n// For a one-off caller holding no table of its own. Reaching through it in a loop loads the shelf every time.\npublic static class ArchShelved\n{\n\tpublic static List\u003CT\u003E Ships\u003CT\u003E( ArchShelf shelf ) where T : class =\u003E ArchCatalogs.Load().Ships\u003CT\u003E( shelf );\n\n\tpublic static List\u003CT\u003E Authors\u003CT\u003E( ArchShelf shelf ) where T : class =\u003E ArchCatalogs.Load().Authors\u003CT\u003E( shelf );\n\n\tpublic static bool Save( ArchShelf shelf, object entry ) =\u003E ArchCatalogs.Load().Save( shelf, entry );\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Designers/ArchOpeningKinds.cs","FileName":"ArchOpeningKinds.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace Sunless.Architecture;\n\n// The one table: generators, designer and property sheet ask here, so no control lies.\npublic static class ArchOpeningKinds\n{\n\tpublic static bool Glazes( this OpeningKind kind ) =\u003E kind == OpeningKind.Window;\n\n\tpublic static bool Sills( this OpeningKind kind ) =\u003E kind == OpeningKind.Window;\n\n\tpublic static bool Hangs( this OpeningKind kind ) =\u003E kind is OpeningKind.Door or OpeningKind.DoubleDoor or OpeningKind.Garage;\n\n\t// Furniture is fixed to the frame the hole wears, so an archway carries none of it: its jambs are the wall\u0027s\n\t// own finish carried through the thickness and there is nothing there to bolt a grille to.\n\tpublic static bool Carries( this OpeningKind kind, OpeningFurniture furniture ) =\u003E furniture switch\n\t{\n\t\tOpeningFurniture.Bars =\u003E kind is OpeningKind.Window or OpeningKind.Hatch,\n\t\tOpeningFurniture.Boarded =\u003E kind is OpeningKind.Window or OpeningKind.Door or OpeningKind.DoubleDoor or OpeningKind.Hatch,\n\t\tOpeningFurniture.Shutter =\u003E kind is OpeningKind.Garage or OpeningKind.Window,\n\t\tOpeningFurniture.Gate =\u003E kind is OpeningKind.Door or OpeningKind.DoubleDoor,\n\t\tOpeningFurniture.Leaves =\u003E kind == OpeningKind.Window,\n\t\t_ =\u003E false\n\t};\n\n\t// A hood stands on the wall over the head, so it wants a hole with a frame under it to look bolted to. An\n\t// archway has none, a hatch is too small to carry one and a garage\u0027s head is where the door stacks.\n\tpublic static bool Wears( this OpeningKind kind, OpeningHood hood )\n\t{\n\t\treturn hood != OpeningHood.None \u0026\u0026 kind is OpeningKind.Window or OpeningKind.Door or OpeningKind.DoubleDoor;\n\t}\n\n\tpublic static IEnumerable\u003COpeningHood\u003E Hoods( this OpeningKind kind )\n\t{\n\t\treturn Enum.GetValues\u003COpeningHood\u003E().Where( hood =\u003E kind.Wears( hood ) );\n\t}\n\n\t// What a picker may offer, read off the same rows the generator builds from - so a form and a wall cannot\n\t// disagree about what a kind has.\n\tpublic static IEnumerable\u003COpeningFurniture\u003E Furnishings( this OpeningKind kind )\n\t{\n\t\treturn Enum.GetValues\u003COpeningFurniture\u003E().Where( furniture =\u003E kind.Carries( furniture ) );\n\t}\n\n\tpublic static bool Furnishes( this OpeningKind kind ) =\u003E kind.Furnishings().Any();\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Designers/ArchOpeningPlacement.cs","FileName":"ArchOpeningPlacement.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\npublic readonly struct ArchOpeningPoint\n{\n\tpublic ArchWall Wall { get; init; }\n\tpublic ArchRoom Room { get; init; }\n\tpublic ArchBuilding Building { get; init; }\n\tpublic float Along { get; init; }\n\tpublic float Height { get; init; }\n\tpublic bool WallPlane { get; init; }\n}\n\n// The four answers a drag needs beyond the cursor, held here because both opening tools drag the\n// same way and a toggle that lived on one of them would be a second set of rules.\npublic sealed class ArchOpeningDrag\n{\n\tpublic bool RestrictStartHeight { get; set; }\n\tpublic bool RestrictEndHeight { get; set; }\n\tpublic bool PadSides { get; set; } = true;\n\tpublic bool ClearCornice { get; set; }\n}\n\npublic sealed class ArchOpeningPlacement\n{\n\tpublic ArchOpeningDrag Drag { get; } = new();\n\n\tArchOpeningPoint anchor;\n\tbool dragging;\n\n\tpublic void Update(\n\t\tArchTool tool,\n\t\tbool focused,\n\t\tAction\u003CArchOpeningPoint, ArchOpeningPoint, bool\u003E preview,\n\t\tAction\u003CArchOpeningPoint, ArchOpeningPoint, bool\u003E place )\n\t{\n\t\tif ( !focused || !Point( tool, out var current ) )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tif ( Gizmo.WasLeftMousePressed )\n\t\t{\n\t\t\tanchor = current;\n\t\t\tdragging = true;\n\t\t\treturn;\n\t\t}\n\n\t\tusing ( ArchGhost.Begin() )\n\t\t{\n\t\t\tpreview( dragging ? anchor : current, current, dragging );\n\t\t}\n\n\t\tif ( !dragging || !Gizmo.WasLeftMouseReleased )\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\tdragging = false;\n\t\ttool.EnsureTarget();\n\n\t\tvar unit = tool.Grid.SubgridSize( 4 );\n\t\tvar resized = MathF.Abs( current.Along - anchor.Along ) \u003E= unit\n\t\t\t|| MathF.Abs( current.Height - anchor.Height ) \u003E= unit;\n\n\t\tplace( anchor, current, resized );\n\t}\n\n\tpublic static ArchOpeningShape Shape(\n\t\tArchOpeningPoint from,\n\t\tArchOpeningPoint to,\n\t\tArchOpeningPreset preset,\n\t\tArchGridService grid,\n\t\tArchKit kit,\n\t\tArchOpeningDrag drag )\n\t{\n\t\tvar unit = grid.SubgridSize( 4 );\n\t\tvar wallHeight = ArchWallSection.Height( from.Wall, from.Room, kit );\n\t\tvar along = MathF.Abs( to.Along - from.Along );\n\t\tvar vertical = MathF.Abs( to.Height - from.Height );\n\t\tvar resizedWidth = along \u003E= unit;\n\t\tvar resizedHeight = vertical \u003E= unit;\n\t\tvar resizedVerticalSpan = resizedHeight \u0026\u0026 (!drag.RestrictStartHeight || !drag.RestrictEndHeight);\n\t\tvar minimumWidth = resizedWidth ? unit : ArchGridService.FinestSize;\n\t\tvar minimumHeight = resizedVerticalSpan ? unit : ArchGridService.FinestSize;\n\t\tvar headroom = drag.ClearCornice\n\t\t\t? ArchOpeningClearance.Head( from.Room, from.Building, from.Wall, kit, wallHeight )\n\t\t\t: wallHeight;\n\n\t\t// Both ends are clamped into the run BEFORE the width is taken from them, so a drag that\n\t\t// overshoots a corner lands exactly on the margin instead of leaving whatever the cursor\n\t\t// happened to be short by. Measuring the width first and centring it afterwards is what\n\t\t// left an uneven sliver at each end of a full-length window.\n\t\tArchOpeningSeats.Usable( from.Wall, preset, kit, drag.PadSides, minimumWidth, out var runFrom, out var runTo );\n\n\t\tfloat left;\n\t\tfloat right;\n\n\t\tif ( resizedWidth )\n\t\t{\n\t\t\tleft = Math.Clamp( MathF.Min( from.Along, to.Along ), runFrom, runTo );\n\t\t\tright = Math.Clamp( MathF.Max( from.Along, to.Along ), runFrom, runTo );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tArchOpeningSeats.Centre( from.Along, ArchGridService.Fine( preset.Width ), runFrom, runTo, out left, out right );\n\t\t}\n\n\t\tvar width = MathF.Max( minimumWidth, right - left );\n\t\tvar offset = (left \u002B right) * 0.5f;\n\n\t\tvar wallPlaneDrag = resizedHeight \u0026\u0026 from.WallPlane \u0026\u0026 to.WallPlane;\n\t\tvar presetSill = ArchGridService.Fine( preset.SillHeight );\n\t\tvar presetTop = ArchGridService.Fine( preset.SillHeight \u002B preset.Height );\n\t\tvar sill = wallPlaneDrag \u0026\u0026 !drag.RestrictStartHeight\n\t\t\t? grid.Subgrid( MathF.Min( from.Height, to.Height ), 4 )\n\t\t\t: presetSill;\n\t\tvar top = wallPlaneDrag \u0026\u0026 !drag.RestrictEndHeight\n\t\t\t? grid.Subgrid( MathF.Max( from.Height, to.Height ), 4 )\n\t\t\t: presetTop;\n\n\t\tsill = Math.Clamp( sill, 0f, MathF.Max( 0f, headroom - minimumHeight ) );\n\t\ttop = Math.Clamp( top, minimumHeight, headroom );\n\n\t\tif ( top - sill \u003C minimumHeight )\n\t\t{\n\t\t\tif ( drag.RestrictEndHeight \u0026\u0026 !drag.RestrictStartHeight )\n\t\t\t{\n\t\t\t\tsill = MathF.Max( 0f, top - minimumHeight );\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\ttop = MathF.Min( headroom, sill \u002B minimumHeight );\n\t\t\t}\n\t\t}\n\n\t\treturn new ArchOpeningShape\n\t\t{\n\t\t\tOffset = offset,\n\t\t\tWidth = width,\n\t\t\tHeight = top - sill,\n\t\t\tSillHeight = sill\n\t\t};\n\t}\n\n\tbool Point( ArchTool tool, out ArchOpeningPoint point )\n\t{\n\t\tif ( dragging )\n\t\t{\n\t\t\treturn FixedWallPoint( tool, anchor, out point );\n\t\t}\n\n\t\treturn NearestWallPoint( tool, out point );\n\t}\n\n\tstatic bool NearestWallPoint( ArchTool tool, out ArchOpeningPoint point )\n\t{\n\t\tpoint = default;\n\n\t\tif ( WallPlanePoint( tool, Gizmo.CurrentRay, null, out point ) )\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\t// The AIM, not the settled point: which wall is being pointed at is a measurement, and the station along it\n\t\t// is put on the ladder below anyway. Asked of a snapped point, the search reads whichever wall the snap had\n\t\t// already chosen rather than the one under the cursor.\n\t\tif ( !tool.AimPoint( out var ground ) )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tvar wall = tool.NearestWall( ground, out var room, out var along, out var distance );\n\n\t\tif ( wall is null || room is null || distance \u003E 64f )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tpoint = new ArchOpeningPoint\n\t\t{\n\t\t\tWall = wall,\n\t\t\tRoom = room,\n\t\t\tBuilding = tool.Plan.OwnerOf( room ),\n\t\t\tAlong = tool.Grid.Subgrid( along, 4 ),\n\t\t\tHeight = 0f,\n\t\t\tWallPlane = false\n\t\t};\n\n\t\treturn true;\n\t}\n\n\tstatic bool FixedWallPoint( ArchTool tool, ArchOpeningPoint anchor, out ArchOpeningPoint point )\n\t{\n\t\tif ( WallPlanePoint( tool, Gizmo.CurrentRay, anchor.Wall, out point ) )\n\t\t{\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( !tool.AimPoint( out var ground ) )\n\t\t{\n\t\t\tpoint = anchor;\n\t\t\treturn true;\n\t\t}\n\n\t\tvar along = Math.Clamp(\n\t\t\tVector2.Dot( ground - anchor.Wall.Start, anchor.Wall.Direction ),\n\t\t\t0f,\n\t\t\tanchor.Wall.Length );\n\t\tvar wallPoint = anchor.Wall.PointAt( along );\n\n\t\tpoint = new ArchOpeningPoint\n\t\t{\n\t\t\tWall = anchor.Wall,\n\t\t\tRoom = anchor.Room,\n\t\t\tBuilding = anchor.Building,\n\t\t\tAlong = tool.Grid.Subgrid( along, 4 ),\n\t\t\tHeight = tool.Grid.Subgrid( MathF.Abs( Vector2.Dot( ground - wallPoint, anchor.Wall.Normal ) ), 4 ),\n\t\t\tWallPlane = false\n\t\t};\n\n\t\treturn true;\n\t}\n\n\tstatic bool WallPlanePoint( ArchTool tool, Ray ray, ArchWall fixedWall, out ArchOpeningPoint point )\n\t{\n\t\tpoint = default;\n\t\tvar nearest = float.MaxValue;\n\n\t\tforeach ( var building in tool.Plan.Buildings )\n\t\t{\n\t\t\tvar lift = ArchAsks.Lift( tool.Plan, building, tool.Kit );\n\n\t\t\tforeach ( var room in building.Rooms )\n\t\t\t{\n\t\t\t\tif ( room.Floor != tool.Level )\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tvar baseHeight = room.BaseHeight \u002B lift;\n\t\t\t\tvar wallHeight = room.WallHeight \u003E 0f ? room.WallHeight : tool.Kit.WallHeight;\n\n\t\t\t\tforeach ( var wall in room.Walls )\n\t\t\t\t{\n\t\t\t\t\tif ( fixedWall is not null \u0026\u0026 wall != fixedWall )\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar denominator = ray.Forward.x * wall.Normal.x \u002B ray.Forward.y * wall.Normal.y;\n\n\t\t\t\t\tif ( MathF.Abs( denominator ) \u003C 0.0001f )\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar offset = new Vector2( ray.Position.x, ray.Position.y ) - wall.Start;\n\t\t\t\t\tvar distance = -Vector2.Dot( offset, wall.Normal ) / denominator;\n\n\t\t\t\t\tif ( distance \u003C 0f || distance \u003E= nearest )\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar hit = ray.Position \u002B ray.Forward * distance;\n\t\t\t\t\tvar flat = new Vector2( hit.x, hit.y );\n\t\t\t\t\tvar along = Vector2.Dot( flat - wall.Start, wall.Direction );\n\t\t\t\t\tvar height = hit.z - baseHeight;\n\n\t\t\t\t\tif ( fixedWall is null\n\t\t\t\t\t\t\u0026\u0026 (along \u003C -8f || along \u003E wall.Length \u002B 8f || height \u003C -8f || height \u003E wallHeight \u002B 8f) )\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tnearest = distance;\n\t\t\t\t\tpoint = new ArchOpeningPoint\n\t\t\t\t\t{\n\t\t\t\t\t\tWall = wall,\n\t\t\t\t\t\tRoom = room,\n\t\t\t\t\t\tBuilding = building,\n\t\t\t\t\t\tAlong = tool.Grid.Subgrid( Math.Clamp( along, 0f, wall.Length ), 4 ),\n\t\t\t\t\t\tHeight = tool.Grid.Subgrid( Math.Clamp( height, 0f, wallHeight ), 4 ),\n\t\t\t\t\t\tWallPlane = true\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn nearest \u003C float.MaxValue;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Geometry/ArchGuards.cs","FileName":"ArchGuards.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n// One run of edge protection, resolved for a generator and read back by the tests.\npublic sealed class ArchGuardRun\n{\n\tpublic ArchBarrierShape Shape { get; init; }\n\tpublic ArchBarrierSpec Spec { get; init; }\n\tpublic bool Closed { get; init; }\n\tpublic float Length { get; init; }\n\n\t// A closed run\u0027s last station IS its first, and posted twice it stands two posts inside each other.\n\tpublic Func\u003Cfloat, bool\u003E Doubled()\n\t{\n\t\tif ( !Closed )\n\t\t{\n\t\t\treturn null;\n\t\t}\n\n\t\tvar closing = Length - 0.5f;\n\n\t\treturn distance =\u003E distance \u003E closing;\n\t}\n}\n\n// The barrier engine already owns posts, bays and rails; a deck only says where its edge is and how high to stand\n// on it. A roof\u0027s guard and a platform\u0027s are the same rail, so the spec is core geometry.\npublic static class ArchGuards\n{\n\t// Bays are authored EDGE BY EDGE, so every station lands either on a corner or inside one straight stretch.\n\t// Divided by arc length alone a bay spans a corner and its rail cuts the corner off.\n\tpublic static ArchBarrierSpec Spec( float height, ArchKit kit, ArchRunPath run, BarrierStyle style = BarrierStyle.PostAndRail )\n\t{\n\t\tvar spec = new ArchBarrierSpec\n\t\t{\n\t\t\tStyle = style,\n\t\t\tGround = BarrierGround.Level,\n\t\t\tHeight = height,\n\t\t\tPanelLength = ArchGuardStyles.Bay( style, kit ),\n\t\t\tPostSize = MathF.Max( 1f, kit.RoofGuardPost ),\n\t\t\tPanelThickness = kit.SolidGuardThickness,\n\t\t\tRails = 2\n\t\t};\n\n\t\tfor ( var edge = 0; edge \u003C run.Edges; edge\u002B\u002B )\n\t\t{\n\t\t\tvar division = ArchDivide.AtMost( (run.At( edge \u002B 1 ) - run.At( edge )).Length, spec.PanelLength );\n\n\t\t\tfor ( var bay = 0; bay \u003C division.Count; bay\u002B\u002B )\n\t\t\t{\n\t\t\t\tspec.Bays.Add( new ArchBarrierBay { Span = division.Step } );\n\t\t\t}\n\t\t}\n\n\t\treturn spec;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Geometry/ArchWallFaces.cs","FileName":"ArchWallFaces.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\n\n// Which way a wall faces out of its building, decided by probing both sides for a room rather than by the winding\n// the wall was authored in. Fixtures, approaches and anything else seated on an exterior face reads this.\npublic static class ArchWallFaces\n{\n\tpublic static bool TryOutward( ArchWall wall, ArchRoom room, ArchBuilding building, out Vector2 outward )\n\t{\n\t\tvar normal = wall.Normal;\n\t\tvar midpoint = wall.PointAt( wall.Length * 0.5f );\n\t\tvar probe = MathF.Max( 4f, wall.Thickness );\n\t\tvar positiveOccupied = Occupied( building, room.Floor, midpoint \u002B normal * probe );\n\t\tvar negativeOccupied = Occupied( building, room.Floor, midpoint - normal * probe );\n\n\t\tif ( positiveOccupied == negativeOccupied )\n\t\t{\n\t\t\toutward = default;\n\n\t\t\treturn false;\n\t\t}\n\n\t\toutward = positiveOccupied ? -normal : normal;\n\n\t\treturn true;\n\t}\n\n\tpublic static Vector2 Outward( ArchWall wall, ArchRoom room, ArchBuilding building )\n\t{\n\t\tif ( TryOutward( wall, room, building, out var outward ) )\n\t\t{\n\t\t\treturn outward;\n\t\t}\n\n\t\tvar normal = wall.Normal;\n\t\tvar midpoint = wall.PointAt( wall.Length * 0.5f );\n\n\t\treturn ArchFloorGen.Contains( ArchFloorGen.Footprint( room ), midpoint \u002B normal * 4f ) ? -normal : normal;\n\t}\n\n\tstatic bool Occupied( ArchBuilding building, int level, Vector2 point )\n\t{\n\t\tif ( building is null )\n\t\t{\n\t\t\treturn false;\n\t\t}\n\n\t\tforeach ( var room in building.Rooms )\n\t\t{\n\t\t\tif ( room.Floor == level \u0026\u0026 ArchFloorGen.Contains( ArchFloorGen.Footprint( room ), point ) )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Layers/ArchAssetInstanceManifest.cs","FileName":"ArchAssetInstanceManifest.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\n\nnamespace Sunless.Architecture;\n\npublic sealed class ArchAssetInstanceManifest : ArchKindManifest\u003CArchAssetInstance\u003E\n{\n\tpublic override ArchKind Kind =\u003E ArchKind.AssetInstance;\n\n\tpublic override string Slot =\u003E \u0022Instances\u0022;\n\n\tpublic override ArchLayerDomain Domain =\u003E ArchLayerDomain.Connections;\n\n\tpublic override string Label =\u003E \u0022Instance\u0022;\n\n\tpublic override string Glyph =\u003E \u0022link\u0022;\n\n\t// A placed copy stands at the plan root, so nothing hosts it and only a hostless ask reaches its list.\n\tpublic override IEnumerable\u003Cobject\u003E Filed( ArchPlan plan, object host )\n\t{\n\t\treturn plan is not null \u0026\u0026 host is null ? plan.Instances : Array.Empty\u003Cobject\u003E();\n\t}\n\n\tpublic override bool Unfile( ArchPlan plan, object payload )\n\t{\n\t\treturn plan is not null \u0026\u0026 payload is ArchAssetInstance instance \u0026\u0026 plan.Instances.Remove( instance );\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Layers/ArchDraft.cs","FileName":"ArchDraft.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"namespace Sunless.Architecture;\n\n// A placement in flight: an in-memory layer under an explicit parent, shown in the tree but never\n// serialized until Finish binds the part the subtool just committed. Temporary ids are negative so\n// they can never collide with the plan\u0027s own allocation.\npublic sealed class ArchDraft\n{\n\tstatic int nextTemporaryId = -1;\n\n\tpublic int Id { get; }\n\tpublic ArchKind Kind { get; }\n\tpublic ArchLayerRef? Parent { get; }\n\tpublic string Name { get; }\n\tpublic object Payload { get; }\n\tpublic bool Active { get; private set; } = true;\n\tpublic int ItemId { get; private set; }\n\n\t// The subtool that opened it. A draft outliving its owner is a row saying \u0022placing\u0022 for a tool nobody\n\t// is holding, and nothing else on the plan can tell that it should have gone.\n\tpublic object Owner { get; }\n\n\t// Whether a gesture is actually in flight. A placement tool holds its draft for its whole life, so a\n\t// draft shown on the strength of that alone stands there through every idle minute claiming to be busy.\n\tpublic bool Standing { get; private set; }\n\n\tpublic ArchDraft( ArchKind kind, ArchLayerRef? parent, string name, object payload, object owner = null )\n\t{\n\t\tId = nextTemporaryId--;\n\t\tKind = kind;\n\t\tParent = parent;\n\t\tName = name;\n\t\tPayload = payload;\n\t\tOwner = owner;\n\t}\n\n\tpublic void Show( bool standing ) =\u003E Standing = standing;\n\n\t// Discards the draft without touching the plan or the undo history.\n\tpublic void Cancel() =\u003E Active = false;\n\n\t// Binds the draft to the part that was just committed, then it leaves the tree.\n\tpublic void Finish( int itemId )\n\t{\n\t\tItemId = itemId;\n\t\tActive = false;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Output/ArchAudit.Gaps.cs","FileName":"ArchAudit.Gaps.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing HalfEdgeMesh;\nusing Sandbox;\n\nnamespace Sunless.Architecture;\npublic static partial class ArchAudit\n{\n\t// Sampled along its length, so a border half covered by the part next door still reports.\n\tstatic float? Unmet( Border border, List\u003CPiece\u003E pieces )\n\t{\n\t\tvar nearest = float.MaxValue;\n\n\t\tforeach ( var fraction in new[] { 0.25f, 0.5f, 0.75f } )\n\t\t{\n\t\t\tvar point = border.Along( fraction );\n\t\t\tvar gap = Reaches( point, border.Face, pieces );\n\n\t\t\tif ( gap \u003C= Closing )\n\t\t\t{\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tnearest = MathF.Min( nearest, gap );\n\t\t}\n\n\t\treturn nearest;\n\t}\n\n\t// How far this point is from the nearest surface other than the border\u0027s own. Inside the face\u0027s\n\t// own outline, or a border running past a wall\u0027s end would read as landing on it.\n\tstatic float Reaches( Vector3 point, Facet owner, List\u003CPiece\u003E pieces )\n\t{\n\t\tvar nearest = float.MaxValue;\n\n\t\tforeach ( var piece in pieces )\n\t\t{\n\t\t\tif ( Outside( piece.Bounds, point, MissReach ) )\n\t\t\t{\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tforeach ( var face in piece.Faces )\n\t\t\t{\n\t\t\t\tif ( ReferenceEquals( face, owner ) || face.Normal.Length \u003C 0.5f || face.Area \u003C MinArea )\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tvar gap = MathF.Abs( Vector3.Dot( point - face.Centre, face.Normal ) );\n\n\t\t\t\tif ( gap \u003E= nearest || gap \u003E MissReach || !Covers( face, point ) )\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tnearest = gap;\n\t\t\t}\n\t\t}\n\n\t\treturn nearest;\n\t}\n\n\t// Inside the outline OR on it. A mitre\u0027s two cut faces meet exactly, so the contact pass deletes\n\t// one of them as wholly covered - and the border it leaves behind lies on the survivor\u0027s own\n\t// EDGE, which a strict containment test calls open. That is a met mitre, not a hole.\n\tstatic bool Covers( Facet face, Vector3 point )\n\t{\n\t\tBasis( ArchMeshContactService.Canonical( face.Normal ), out var right, out var up );\n\n\t\tvar flat = Flatten( face.Corners, face.Centre, right, up );\n\t\tvar offset = point - face.Centre;\n\t\tvar flattened = new Vector2( Vector3.Dot( offset, right ), Vector3.Dot( offset, up ) );\n\n\t\treturn Contains( flat, flattened ) || Rims( flat, flattened );\n\t}\n\n\tstatic bool Rims( Vector2[] loop, Vector2 point )\n\t{\n\t\tfor ( var index = 0; index \u003C loop.Length; index\u002B\u002B )\n\t\t{\n\t\t\tvar from = loop[index];\n\t\t\tvar run = loop[(index \u002B 1) % loop.Length] - from;\n\t\t\tvar length = run.Length;\n\t\t\tvar along = length \u003C 0.01f ? 0f : Math.Clamp( Vector2.Dot( point - from, run / length ), 0f, length );\n\n\t\t\t// A hair, not a Closing: the border a deleted mitre leaves lies exactly ON the survivor\u0027s\n\t\t\t// edge. Anything wider and every edge in the mesh closes every border near it.\n\t\t\tif ( (point - (from \u002B (length \u003C 0.01f ? Vector2.Zero : run / length * along))).Length \u003C= 0.05f )\n\t\t\t{\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tstatic bool Outside( BBox bounds, Vector3 point, float reach )\n\t{\n\t\treturn point.x \u003C bounds.Mins.x - reach || point.x \u003E bounds.Maxs.x \u002B reach\n\t\t\t|| point.y \u003C bounds.Mins.y - reach || point.y \u003E bounds.Maxs.y \u002B reach\n\t\t\t|| point.z \u003C bounds.Mins.z - reach || point.z \u003E bounds.Maxs.z \u002B reach;\n\t}\n\n\tstatic List\u003CBorder\u003E Borders( List\u003CPiece\u003E pieces )\n\t{\n\t\tvar borders = new List\u003CBorder\u003E();\n\n\t\tforeach ( var piece in pieces )\n\t\t{\n\t\t\tvar walked = new Dictionary\u003C(long, long), (Facet Face, Vector3 From, Vector3 To)\u003E();\n\t\t\tvar counts = new Dictionary\u003C(long, long), int\u003E();\n\n\t\t\tforeach ( var face in piece.Faces )\n\t\t\t{\n\t\t\t\tfor ( var index = 0; index \u003C face.Corners.Length; index\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tvar one = face.Corners[index];\n\t\t\t\t\tvar two = face.Corners[(index \u002B 1) % face.Corners.Length];\n\t\t\t\t\tvar from = Key( one );\n\t\t\t\t\tvar to = Key( two );\n\n\t\t\t\t\tif ( from == to )\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar edge = from \u003C to ? (from, to) : (to, from);\n\n\t\t\t\t\tcounts[edge] = counts.GetValueOrDefault( edge ) \u002B 1;\n\t\t\t\t\twalked[edge] = (face, one, two);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tforeach ( var (edge, _) in counts.Where( entry =\u003E entry.Value == 1 ) )\n\t\t\t{\n\t\t\t\tvar (face, from, to) = walked[edge];\n\n\t\t\t\tborders.Add( new Border { Piece = piece, Face = face, From = from, To = to } );\n\t\t\t}\n\t\t}\n\n\t\treturn borders;\n\t}\n\n\tstatic (long, long) Span( Border border )\n\t{\n\t\tvar from = Key( border.From );\n\t\tvar to = Key( border.To );\n\n\t\treturn from \u003C to ? (from, to) : (to, from);\n\t}\n\n\tstatic void Degenerate( List\u003CPiece\u003E pieces, List\u003CArchFinding\u003E findings, Dictionary\u003Cstring, int\u003E totals )\n\t{\n\t\tvar slivers = 0;\n\t\tvar normals = 0;\n\n\t\tforeach ( var piece in pieces )\n\t\t{\n\t\t\tforeach ( var face in piece.Faces )\n\t\t\t{\n\t\t\t\tif ( face.Normal.Length \u003C 0.5f || !float.IsFinite( face.Normal.x ) )\n\t\t\t\t{\n\t\t\t\t\tnormals\u002B\u002B;\n\n\t\t\t\t\tfindings.Add( new ArchFinding\n\t\t\t\t\t{\n\t\t\t\t\t\tCheck = \u0022degenerate\u0022,\n\t\t\t\t\t\tWhere = piece.Name,\n\t\t\t\t\t\tWhat = \u0022face has no usable normal - its corners are collinear\u0022,\n\t\t\t\t\t\tAt = Say( face.Centre ),\n\t\t\t\t\t\tSeverity = 0.8f\n\t\t\t\t\t} );\n\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif ( face.Area \u003E= MinArea )\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tslivers\u002B\u002B;\n\n\t\t\t\tfindings.Add( new ArchFinding\n\t\t\t\t{\n\t\t\t\t\tCheck = \u0022degenerate\u0022,\n\t\t\t\t\tWhere = piece.Name,\n\t\t\t\t\tWhat = $\u0022sliver face, area {face.Area:0.###} sq in\u0022,\n\t\t\t\t\tAt = Say( face.Centre ),\n\t\t\t\t\tSeverity = 0.35f\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\ttotals[\u0022sliver faces\u0022] = slivers;\n\t\ttotals[\u0022bad normals\u0022] = normals;\n\t}\n\n\t// Overlapping faces on one plane - the z-fight the generators\u0027 constants avoid.\n\tstatic void Coplanar( List\u003CPiece\u003E pieces, List\u003CArchFinding\u003E findings, Dictionary\u003Cstring, int\u003E totals, List\u003Cstring\u003E truncated )\n\t{\n\t\tvar planes = new Dictionary\u003C(long, long, long, long), List\u003C(Piece Piece, Facet Face)\u003E\u003E();\n\n\t\tforeach ( var piece in pieces )\n\t\t{\n\t\t\tforeach ( var face in piece.Faces )\n\t\t\t{\n\t\t\t\tif ( face.Normal.Length \u003C 0.5f || face.Area \u003C MinArea )\n\t\t\t\t{\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Unsigned: back-to-back is the commonest z-fight, and those normals are opposite.\n\t\t\t\tvar key = ArchMeshContactService.PlaneKey( face.Normal, face.Centre );\n\n\t\t\t\tif ( !planes.TryGetValue( key, out var group ) )\n\t\t\t\t{\n\t\t\t\t\tgroup = new List\u003C(Piece, Facet)\u003E();\n\t\t\t\t\tplanes[key] = group;\n\t\t\t\t}\n\n\t\t\t\tgroup.Add( (piece, face) );\n\t\t\t}\n\t\t}\n\n\t\tvar overlaps = 0;\n\t\tvar budget = PairBudget;\n\n\t\tforeach ( var group in planes.Values.Where( group =\u003E group.Count \u003E 1 ) )\n\t\t{\n\t\t\tfor ( var a = 0; a \u003C group.Count; a\u002B\u002B )\n\t\t\t{\n\t\t\t\tfor ( var b = a \u002B 1; b \u003C group.Count; b\u002B\u002B )\n\t\t\t\t{\n\t\t\t\t\tif ( budget-- \u003C= 0 )\n\t\t\t\t\t{\n\t\t\t\t\t\ttruncated.Add( \u0022coplanar comparison budget reached - narrow the target for a complete answer.\u0022 );\n\t\t\t\t\t\ttotals[\u0022coplanar overlaps\u0022] = overlaps;\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar (pieceA, faceA) = group[a];\n\t\t\t\t\tvar (pieceB, faceB) = group[b];\n\n\t\t\t\t\tif ( !Overlapping( faceA, faceB ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\toverlaps\u002B\u002B;\n\n\t\t\t\t\tvar same = ReferenceEquals( pieceA, pieceB );\n\t\t\t\t\tvar facing = Vector3.Dot( faceA.Normal, faceB.Normal ) \u003C 0f ? \u0022back to back\u0022 : \u0022stacked\u0022;\n\n\t\t\t\t\tfindings.Add( new ArchFinding\n\t\t\t\t\t{\n\t\t\t\t\t\tCheck = \u0022coplanar\u0022,\n\t\t\t\t\t\tWhere = same ? pieceA.Name : $\u0022{pieceA.Name} vs {pieceB.Name}\u0022,\n\t\t\t\t\t\tWhat = $\u0022{facing} faces share a plane and overlap - these will z-fight\u0022,\n\t\t\t\t\t\tAt = Say( faceA.Centre ),\n\t\t\t\t\t\tSeverity = same ? 0.7f : 0.85f\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttotals[\u0022coplanar overlaps\u0022] = overlaps;\n\t}\n}\n"},{"Ident":"sunless.lib_architecture","Path":"Editor/Pillar/ArchPillarDrawer.cs","FileName":"ArchPillarDrawer.cs","PackageType":"library","CodeKind":"Editor","AssetVersionId":343545,"Code":"namespace Sunless.Architecture;\n\npublic sealed class ArchPillarDrawer : IArchDrawer\n{\n\tpublic string Draws =\u003E ArchDraws.Pillar;\n\n\tpublic void Draw( ArchDrawing drawing )\n\t{\n\t\tArchPillarGen.Build( drawing.Canvas, drawing.Drawn\u003CArchPillarPart\u003E(), drawing.Within\u003CArchRoom\u003E(),\n\t\t\tdrawing.Within\u003CArchBuilding\u003E(), drawing.Plan, drawing.Kit, drawing.Style );\n\t}\n}\n"}]}