Editor/Tool/ArchToolOverlay.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;
namespace Sunless.Architecture;
public partial class ArchTool {
void DrawPlan() {
using ( Gizmo.Scope( "arch-plan" ) ) {
Gizmo.Draw.IgnoreDepth = true;
Gizmo.Draw.LineThickness = 2f;
DrawDebugFaces();
if ( InElevation ) {
DrawSection();
return;
}
if ( EditedBuilding() is { } building && !LayerHidden( building.Id ) ) {
DrawNeighbours( building );
if ( Placing ) {
DrawWorkPlane( building );
}
DrawStoreyBelow( building );
DrawBuilding( building );
Identify( building );
}
foreach ( var road in Plan.Roads() ) {
if ( LayerHidden( road.Id ) ) {
continue;
}
DrawRoad( road );
}
DrawUnits();
DrawPicked();
}
}
void DrawPicked() {
if ( Picked?.Item is not { } item || ArchKinds.Load().Claiming( item ) is not { } manifest ) {
return;
}
var id = ArchPlanStore.IdOf( item );
if ( id > 0 && LayerHidden( id ) ) {
return;
}
var band = ArchExtents.Load()
.Measure( manifest.Kind, item, new ArchExtentContext( Plan, Kit, Picked.Room, Picked.Building, this ) );
using ( Gizmo.Scope( "arch-picked" ) ) {
Gizmo.Draw.IgnoreDepth = true;
Gizmo.Draw.LineThickness = 3f;
Gizmo.Draw.Color = ArchGhost.Picked;
if ( band.Any ) {
Gizmo.Draw.LineBBox( band.Box );
return;
}
var lift = Picked.Lift( Plan, Kit );
var foot = manifest.Height( item, Picked.Room?.BaseHeight ?? 0f ) + lift;
foreach ( var loop in manifest.Loops( item ) ) {
ArchGhost.Prism( loop, foot, foot + PickedPost );
}
}
}
const float PickedPost = 12f;
void DrawUnits() {
var previews = ArchPreviews.Load();
foreach ( var unit in Plan.Units ) {
if ( unit is ArchBuilding or ArchRoadPart || LayerHidden( unit.Id ) ) {
continue;
}
previews.Draw( unit.Kind,
new ArchPreviewContext( unit, null, null, 0f, Picked?.Targets( unit ) == true, this ) );
}
}
public ArchBuilding EditedBuilding() {
return Picked?.Building ?? ActiveBuilding();
}
void Identify( ArchBuilding building ) {
var frame = Frame( building.Rooms );
if ( !Bounds( building, frame, out var min, out var max ) ) {
return;
}
var height = LevelHeight + ArchAsks.Lift( Plan, building, Kit );
var box = ArchFootprint.Turned( ArchFootprint.Rect( min, max ), frame.Pivot, frame.Degrees );
Gizmo.Draw.LineThickness = 3f;
Gizmo.Draw.Color = ArchGhost.Accent;
ArchGhost.Brackets( box, height );
Gizmo.Draw.LineThickness = 2f;
ArchGhost.Tag( new Vector3( box[3].x, box[3].y, height ), $"{building.Name} · L{Level}", ArchGhost.Accent );
}
(Vector2 Pivot, float Degrees) Frame( IEnumerable<ArchRoom> rooms ) {
var loops = rooms.Select( Outline ).Where( loop => loop.Count >= 3 ).ToList();
ArchFootprint.Bearing( loops, out var degrees );
return (ArchFootprint.Pivot( loops ), degrees);
}
void DrawNeighbours( ArchBuilding edited ) {
if ( Plan.Buildings.Count < 2 ) {
return;
}
var found = AimPoint( out var point );
Gizmo.Draw.LineThickness = 1f;
foreach ( var building in Plan.Buildings ) {
var frame = Frame( building.Rooms );
if ( building == edited || LayerHidden( building.Id ) || !Bounds( building, frame, out var min, out var max ) ) {
continue;
}
var at = ArchFootprint.Turned( new Vector2( point.x, point.y ), frame.Pivot, -frame.Degrees );
var over = found && at.x >= min.x && at.y >= min.y && at.x <= max.x && at.y <= max.y;
var box = ArchFootprint.Turned( ArchFootprint.Rect( min, max ), frame.Pivot, frame.Degrees );
Gizmo.Draw.Color = ArchGhost.Line.WithAlpha( over ? 0.55f : 0.12f );
DrawLoop( box, LevelHeight );
if ( over ) {
ArchGhost.Tag( new Vector3( box[3].x, box[3].y, LevelHeight ), building.Name, ArchGhost.Line );
}
}
Gizmo.Draw.LineThickness = 2f;
}
bool Bounds( ArchBuilding building, (Vector2 Pivot, float Degrees) frame, out Vector2 min, out Vector2 max ) {
var low = new Vector2( float.MaxValue, float.MaxValue );
var high = new Vector2( float.MinValue, float.MinValue );
var found = false;
foreach ( var room in building.Rooms ) {
foreach ( var corner in Outline( room ).Select( point => ArchFootprint.Turned( point, frame.Pivot, -frame.Degrees ) ) ) {
low = Vector2.Min( low, corner );
high = Vector2.Max( high, corner );
found = true;
}
}
min = low;
max = high;
return found && high.x - low.x > 1f && high.y - low.y > 1f;
}
void DrawStoreyBelow( ArchBuilding building ) {
if ( Level <= 0 ) {
return;
}
var height = LevelHeight + ArchAsks.Lift( Plan, building, Kit );
Gizmo.Draw.LineThickness = 3f;
Gizmo.Draw.Color = ArchGhost.Accent.WithAlpha( 0.45f );
foreach ( var room in building.Rooms ) {
if ( room.Floor != Level - 1 || LayerHidden( room.Id ) ) {
continue;
}
var loop = Outline( room );
if ( loop.Count >= 3 ) {
ArchGhost.Ring( loop, height );
}
}
Gizmo.Draw.LineThickness = 2f;
}
void DrawBuilding( ArchBuilding building ) {
var inside = HoveredRoom( building );
var lift = ArchAsks.Lift( Plan, building, Kit );
var previews = ArchPreviews.Load();
foreach ( var room in building.Rooms ) {
DrawRoom( room, inside, lift, building, previews );
}
foreach ( var cutout in building.Cutouts ) {
var sourceId = cutout.OwnerId != 0 ? cutout.OwnerId : cutout.Id;
if ( !Shown( cutout.Level, sourceId ) ) {
continue;
}
Gizmo.Draw.Color = Color.Red.WithAlpha( 0.8f );
ArchGhost.Ring( cutout.Outline(), FloorOf( cutout.Level ) + lift );
}
foreach ( var cut in building.Cuts ) {
if ( Shown( cut.Level, cut.Id ) ) {
DrawCut( cut, lift );
}
}
foreach ( var roof in building.Roofs ) {
if ( !Shown( roof.Level, roof.Id ) ) {
continue;
}
var selected = Picked?.Targets( roof ) == true;
Gizmo.Draw.Color = (selected ? Color.Yellow : Color.Orange).WithAlpha( 0.6f );
DrawLoop( roof.Outline(), roof.BaseHeight + lift );
}
foreach ( var fence in building.Fences ) {
if ( LayerHidden( fence.Id ) ) {
continue;
}
var picked = Picked?.Targets( fence ) == true;
Gizmo.Draw.Color = picked ? Color.Yellow : ArchGhost.Line.WithAlpha( 0.8f );
ArchGhost.Curve( Curve( fence ) );
if ( !picked ) {
continue;
}
if ( fence.Barrier.Style == BarrierStyle.Retaining ) {
ArchGhost.Retaining( Retaining( fence ) );
continue;
}
ArchGhost.Barrier( Barrier( fence ), fence.Barrier );
}
foreach ( var pipe in building.Downpipes ) {
if ( LayerHidden( pipe.Id ) ) {
continue;
}
var picked = Picked?.Targets( pipe ) == true;
Gizmo.Draw.Color = picked ? Color.Yellow : Color.Cyan.WithAlpha( 0.7f );
Gizmo.Draw.Line(
new Vector3( pipe.Outlet.x, pipe.Outlet.y, pipe.TopHeight + lift ),
new Vector3( pipe.Wall.x, pipe.Wall.y, pipe.BaseHeight + lift ) );
if ( picked ) {
Gizmo.Draw.LineSphere( new Vector3( pipe.Outlet.x, pipe.Outlet.y, pipe.TopHeight + lift ), 6f );
}
}
foreach ( var run in building.Pipes ) {
DrawPipeRun( run, building, lift, previews );
}
foreach ( var bracket in building.Brackets ) {
DrawBrackets( bracket, building, lift, previews );
}
}
void DrawPipeRun( ArchPipePart run, ArchBuilding building, float lift, ArchPreviews previews ) {
if ( LayerHidden( run.Id ) ) {
return;
}
previews.Draw( ArchKind.Pipe,
new ArchPreviewContext( run, null, building, lift, Picked?.Targets( run ) == true, this ) );
}
void DrawBrackets( ArchPipeBracketPart bracket, ArchBuilding building, float lift, ArchPreviews previews ) {
if ( LayerHidden( bracket.Id ) ) {
return;
}
previews.Draw( ArchKind.Bracket,
new ArchPreviewContext( bracket, null, building, lift, Picked?.Targets( bracket ) == true, this ) );
}
void DrawCut( ArchCutPart cut, float lift ) {
var picked = Picked?.Targets( cut ) == true;
Gizmo.Draw.Color = (picked ? Color.Yellow : Color.Red).WithAlpha( 0.7f );
foreach ( var segment in cut.Segments.Where( leg => leg.HasLoop || leg.Length > 1f ) ) {
var loop = segment.Outline();
var band = ArchCut.WorldBand( segment, lift );
ArchGhost.Prism( loop, band.x, band.y );
if ( !picked ) {
continue;
}
ArchFootprint.Bounds( loop, out var low, out var high );
ArchGhost.Brackets( loop, band.y );
ArchGhost.Note( new Vector3( high.x, high.y, band.y ),
$"{cut.Name} — {high.x - low.x:0} x {high.y - low.y:0}, {segment.TopHeight - segment.BaseHeight:0} deep" );
}
}
ArchRoom HoveredRoom( ArchBuilding building ) {
return AimPoint( out var point ) ? Contained( building, point ) : null;
}
void DrawRoad( ArchRoadPart road ) {
foreach ( var cut in road.Cuts ) {
if ( !LayerHidden( cut.Id ) ) {
DrawCut( cut, 0f );
}
}
ArchPreviews.Load().Draw( ArchKind.Road,
new ArchPreviewContext( road, null, null, 0f, Picked?.Targets( road ) == true, this ) );
}
bool Shown( int floor ) => floor == Level;
bool Shown( int floor, int itemId ) => OverlayShows( floor, Level, itemId, LayerHidden );
public static bool OverlayShows( int floor, int level, int itemId, Func<int, bool> hidden ) {
return floor == level && !(hidden?.Invoke( itemId ) ?? false);
}
const int GridMargin = 3;
void DrawWorkPlane( ArchBuilding building ) {
var frame = WorkFrame( building );
if ( !WorkBounds( building, frame, out var min, out var max ) || !Over( frame, min, max ) ) {
return;
}
var grid = MathF.Max( 8f, Kit.GridSize );
var height = LevelHeight + ArchAsks.Lift( Plan, building, Kit );
var world = ArchRemap.Spun( frame.Pivot, frame.Degrees );
Gizmo.Draw.LineThickness = 1f;
Gizmo.Draw.Color = ArchGhost.Line.WithAlpha( 0.12f );
for ( var x = min.x; x <= max.x + 0.01f; x += grid ) {
Rung( world, new Vector2( x, min.y ), new Vector2( x, max.y ), height );
}
for ( var y = min.y; y <= max.y + 0.01f; y += grid ) {
Rung( world, new Vector2( min.x, y ), new Vector2( max.x, y ), height );
}
Gizmo.Draw.LineThickness = 2f;
Gizmo.Draw.Color = ArchGhost.Line.WithAlpha( 0.4f );
var edge = ArchFootprint.Rect( min, max );
world.Loop( edge );
DrawLoop( edge, height );
}
static void Rung( ArchRemap world, Vector2 from, Vector2 to, float height ) {
var start = world.Of( from );
var end = world.Of( to );
Gizmo.Draw.Line( new Vector3( start.x, start.y, height ), new Vector3( end.x, end.y, height ) );
}
(Vector2 Pivot, float Degrees) WorkFrame( ArchBuilding building ) {
return Frame( building.Rooms.Where( room => room.Floor == Level || room.Floor == Level - 1 ) );
}
bool Over( (Vector2 Pivot, float Degrees) frame, Vector2 min, Vector2 max ) {
if ( !AimPoint( out var world ) ) {
return false;
}
var point = ArchFootprint.Turned( new Vector2( world.x, world.y ), frame.Pivot, -frame.Degrees );
return point.x >= min.x && point.y >= min.y
&& point.x <= max.x && point.y <= max.y;
}
// This storey and the one below, in the building's own frame
bool WorkBounds( ArchBuilding building, (Vector2 Pivot, float Degrees) frame, out Vector2 min, out Vector2 max ) {
min = default;
max = default;
var low = new Vector2( float.MaxValue, float.MaxValue );
var high = new Vector2( float.MinValue, float.MinValue );
var found = false;
foreach ( var room in building.Rooms ) {
if ( room.Floor != Level && room.Floor != Level - 1 ) {
continue;
}
foreach ( var corner in Outline( room ).Select( point => ArchFootprint.Turned( point, frame.Pivot, -frame.Degrees ) ) ) {
low = Vector2.Min( low, corner );
high = Vector2.Max( high, corner );
found = true;
}
}
if ( !found ) {
return false;
}
var grid = MathF.Max( 8f, Kit.GridSize );
var margin = grid * GridMargin;
min = new Vector2(
MathF.Floor( (low.x - margin) / grid ) * grid,
MathF.Floor( (low.y - margin) / grid ) * grid );
max = new Vector2(
MathF.Ceiling( (high.x + margin) / grid ) * grid,
MathF.Ceiling( (high.y + margin) / grid ) * grid );
return true;
}
bool Editing( ArchRoom room, ArchRoom inside ) {
return room == inside || room.Id == ActiveRoomId || Picked?.Targets( room ) == true || Picked?.Room == room;
}
void DrawRoom( ArchRoom room, ArchRoom inside, float lift, ArchBuilding building, ArchPreviews previews ) {
if ( !Shown( room.Floor, room.Id ) ) {
return;
}
var loop = Outline( room );
var floor = room.BaseHeight + lift;
if ( room == inside && loop.Count >= 3 ) {
Gizmo.Draw.LineThickness = 4f;
Gizmo.Draw.Color = ArchGhost.Line;
ArchGhost.Ring( loop, floor );
Gizmo.Draw.LineThickness = 2f;
}
if ( !Editing( room, inside ) ) {
Gizmo.Draw.Color = Color.White.WithAlpha( 0.3f );
DrawWalls( room, lift );
return;
}
var picked = Picked?.Targets( room ) == true;
if ( loop.Count >= 3 && (picked || room.Id == ActiveRoomId) ) {
Gizmo.Draw.Color = picked ? Color.Yellow.WithAlpha( 0.85f ) : ArchGhost.Accent.WithAlpha( 0.45f );
ArchGhost.Prism( loop, floor, floor + ArchFloorGen.WallHeight( room, Kit ) );
}
foreach ( var wall in room.Walls ) {
if ( LayerHidden( wall.Id ) ) {
continue;
}
Gizmo.Draw.Color = Picked?.Targets( wall ) == true ? Color.Yellow : Color.Cyan;
Gizmo.Draw.Line(
new Vector3( wall.Start.x, wall.Start.y, room.BaseHeight + lift ),
new Vector3( wall.End.x, wall.End.y, room.BaseHeight + lift ) );
foreach ( var opening in wall.Openings ) {
if ( LayerHidden( opening.Id ) || LayerHidden( opening.OwnerId ) ) {
continue;
}
var left = wall.PointAt( opening.Left );
var right = wall.PointAt( opening.Right );
var z = room.BaseHeight + lift + opening.SillHeight;
Gizmo.Draw.Color = Picked?.Targets( opening ) == true ? Color.Yellow : Color.Green;
Gizmo.Draw.Line( new Vector3( left.x, left.y, z ), new Vector3( right.x, right.y, z ) );
}
}
foreach ( var pillar in room.Pillars ) {
if ( LayerHidden( pillar.Id ) ) {
continue;
}
previews.Draw( ArchKind.Pillar,
new ArchPreviewContext( pillar, room, building, lift, Picked?.Targets( pillar ) == true ) );
}
foreach ( var stair in room.Stairs ) {
if ( LayerHidden( stair.Id ) ) {
continue;
}
Gizmo.Draw.Color = Picked?.Targets( stair ) == true ? Color.Yellow : Color.Magenta.WithAlpha( 0.6f );
Gizmo.Draw.LineSphere( new Vector3( stair.Origin.x, stair.Origin.y, stair.BaseHeight + lift ), 10f );
previews.Draw( ArchKind.Stair,
new ArchPreviewContext( stair, room, building, lift, Picked?.Targets( stair ) == true, this ) );
}
foreach ( var trim in room.Trims ) {
if ( LayerHidden( trim.Id ) ) {
continue;
}
Gizmo.Draw.Color = Picked?.Targets( trim ) == true ? Color.Yellow : Color.Blue.WithAlpha( 0.7f );
for ( var index = 0; index < trim.Path.Count - 1; index++ ) {
Gizmo.Draw.Line( trim.Path[index] + Vector3.Up * lift, trim.Path[index + 1] + Vector3.Up * lift );
}
}
var kinds = ArchKinds.Load();
foreach ( var porch in room.Porches ) {
if ( LayerHidden( porch.Id ) ) {
continue;
}
Gizmo.Draw.Color = Picked?.Targets( porch ) == true ? Color.Yellow : ArchGhost.Line;
var manifest = kinds.Claiming( porch );
foreach ( var outline in manifest?.Loops( porch ) ?? Array.Empty<List<Vector2>>() ) {
ArchGhost.Ring( outline, manifest.Height( porch, porch.BaseHeight ) + lift );
}
}
}
void DrawWalls( ArchRoom room, float lift ) {
foreach ( var wall in room.Walls ) {
if ( LayerHidden( wall.Id ) ) {
continue;
}
Gizmo.Draw.Line(
new Vector3( wall.Start.x, wall.Start.y, room.BaseHeight + lift ),
new Vector3( wall.End.x, wall.End.y, room.BaseHeight + lift ) );
}
}
public static void DrawRect( Vector2 min, Vector2 max, float height ) {
DrawLoop( ArchFootprint.Rect( min, max ), height );
}
public static void DrawLoop( IReadOnlyList<Vector2> loop, float height ) {
for ( var index = 0; index < loop.Count; index++ ) {
var from = loop[index];
var to = loop[(index + 1) % loop.Count];
Gizmo.Draw.Line( new Vector3( from.x, from.y, height ), new Vector3( to.x, to.y, height ) );
}
}
}