Editor/InteriorLayoutBuilder/RoomLayoutData.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Sandbox;
namespace ReusableRoomLayout;
public sealed class RoomLayoutDocument
{
public const int MinimumFloor = -32;
public const int MaximumFloor = 128;
public int Version { get; set; } = 9;
public int NextId { get; set; } = 1;
public bool GeneratedGeometryEnabled { get; set; } = true;
public RoomLayoutSettings Settings { get; set; } = new();
public List<RoomLayoutRoom> Rooms { get; set; } = new();
public List<RoomLayoutDoor> Doors { get; set; } = new();
public List<RoomLayoutWindow> Windows { get; set; } = new();
public List<RoomLayoutCorridor> Corridors { get; set; } = new();
public List<RoomLayoutFloorCutout> FloorCutouts { get; set; } = new();
public int AllocateId()
{
NextId = Math.Max( NextId, HighestId() + 1 );
return NextId++;
}
public void NormalizeIds()
{
NextId = Math.Max( 1, HighestId() + 1 );
var migrateOpeningDimensions = Version < 6;
var migrateFloorSpacing = Version < 8;
if ( migrateFloorSpacing && Math.Abs( Settings.FloorSpacing - 160.0f ) < 0.001f )
{
Settings.FloorSpacing = 0.0f;
}
foreach ( var room in Rooms )
{
room.Floor = ClampFloor( room.Floor );
}
foreach ( var corridor in Corridors )
{
corridor.Floor = ClampFloor( corridor.Floor );
if ( corridor.BendPoints.Count > 0 )
{
corridor.ManualPath = true;
}
}
foreach ( var cutout in FloorCutouts )
{
cutout.Floor = ClampFloor( cutout.Floor );
}
foreach ( var door in Doors )
{
if ( door.Width <= 0.0f )
{
door.Width = Settings.DoorWidth;
}
if ( migrateOpeningDimensions || door.Height <= 0.0f )
{
door.Height = Settings.DoorHeight;
}
}
foreach ( var window in Windows )
{
if ( window.Width <= 0.0f )
{
window.Width = Settings.WindowWidth;
}
if ( migrateOpeningDimensions || window.Height <= 0.0f )
{
window.Height = Settings.WindowHeight;
}
if ( migrateOpeningDimensions || window.SillHeight < 0.0f )
{
window.SillHeight = Settings.WindowSillHeight;
}
}
Version = 9;
}
public RoomLayoutRoom FindRoom( int id )
{
return Rooms.FirstOrDefault( room => room.Id == id );
}
public RoomLayoutDoor FindDoor( int id )
{
return Doors.FirstOrDefault( door => door.Id == id );
}
public RoomLayoutWindow FindWindow( int id )
{
return Windows.FirstOrDefault( window => window.Id == id );
}
public int FloorFor( RoomLayoutRoom room )
{
return room is null ? 0 : ClampFloor( room.Floor );
}
public int FloorFor( RoomLayoutCorridor corridor )
{
return corridor is null ? 0 : ClampFloor( corridor.Floor );
}
public int FloorFor( RoomLayoutFloorCutout cutout )
{
return cutout is null ? 0 : ClampFloor( cutout.Floor );
}
public int FloorFor( RoomLayoutDoor door )
{
if ( door is null )
{
return 0;
}
if ( door.RoomId != 0 )
{
return FloorFor( FindRoom( door.RoomId ) );
}
if ( door.CorridorId != 0 )
{
return FloorFor( Corridors.FirstOrDefault( corridor => corridor.Id == door.CorridorId ) );
}
return 0;
}
public int FloorFor( RoomLayoutWindow window )
{
if ( window is null )
{
return 0;
}
if ( window.RoomId != 0 )
{
return FloorFor( FindRoom( window.RoomId ) );
}
if ( window.CorridorId != 0 )
{
return FloorFor( Corridors.FirstOrDefault( corridor => corridor.Id == window.CorridorId ) );
}
return 0;
}
public bool CorridorDoorsAreOnFloor( RoomLayoutCorridor corridor )
{
var startDoor = FindDoor( corridor?.StartDoorId ?? 0 );
var endDoor = FindDoor( corridor?.EndDoorId ?? 0 );
return startDoor is not null &&
endDoor is not null &&
FloorFor( startDoor ) == FloorFor( corridor ) &&
FloorFor( endDoor ) == FloorFor( corridor );
}
public IReadOnlyList<int> UsedFloors()
{
var floors = new HashSet<int>();
foreach ( var room in Rooms )
{
floors.Add( FloorFor( room ) );
}
foreach ( var corridor in Corridors )
{
floors.Add( FloorFor( corridor ) );
}
foreach ( var cutout in FloorCutouts )
{
floors.Add( FloorFor( cutout ) );
}
if ( floors.Count == 0 )
{
floors.Add( 0 );
}
return floors.OrderBy( floor => floor ).ToArray();
}
public void RemoveRoom( int roomId )
{
var removedDoorIds = Doors.Where( door => door.RoomId == roomId )
.Select( door => door.Id )
.ToHashSet();
Rooms.RemoveAll( room => room.Id == roomId );
Doors.RemoveAll( door => door.RoomId == roomId );
Windows.RemoveAll( window => window.RoomId == roomId );
Corridors.RemoveAll( corridor =>
removedDoorIds.Contains( corridor.StartDoorId ) ||
removedDoorIds.Contains( corridor.EndDoorId ) );
}
private int HighestId()
{
var highestRoom = Rooms.Count == 0 ? 0 : Rooms.Max( room => room.Id );
var highestDoor = Doors.Count == 0 ? 0 : Doors.Max( door => door.Id );
var highestWindow = Windows.Count == 0 ? 0 : Windows.Max( window => window.Id );
var highestCorridor = Corridors.Count == 0 ? 0 : Corridors.Max( corridor => corridor.Id );
var highestCutout = FloorCutouts.Count == 0 ? 0 : FloorCutouts.Max( cutout => cutout.Id );
return Math.Max( Math.Max( highestRoom, highestWindow ), Math.Max( Math.Max( highestDoor, highestCorridor ), highestCutout ) );
}
public static int ClampFloor( int floor )
{
return Math.Clamp( floor, MinimumFloor, MaximumFloor );
}
}
public sealed class RoomLayoutSettings
{
public float GridSize { get; set; } = 64.0f;
public float WallHeight { get; set; } = 128.0f;
public float WallThickness { get; set; } = 12.0f;
public float WallModuleLength { get; set; } = 64.0f;
public float WallCapHeight { get; set; } = 6.0f;
public float FloorSpacing { get; set; }
public float FloorThickness { get; set; } = 4.0f;
public float FloorModuleSize { get; set; } = 64.0f;
public float TextureWorldSize { get; set; } = 128.0f;
public float DefaultCorridorWidth { get; set; } = 64.0f;
public float DoorWidth { get; set; } = 64.0f;
public float DoorHeight { get; set; } = 96.0f;
public float DoorFrameThickness { get; set; } = 8.0f;
public float WindowWidth { get; set; } = 64.0f;
public float WindowHeight { get; set; } = 64.0f;
public float WindowSillHeight { get; set; } = 48.0f;
public float WindowFrameThickness { get; set; } = 2.0f;
public bool BaseboardsEnabled { get; set; } = true;
public bool ThresholdsEnabled { get; set; } = true;
public bool RoofEnabled { get; set; }
public float BaseboardHeight { get; set; } = 4.0f;
public float BaseboardDepth { get; set; } = 2.0f;
public float ThresholdDepth { get; set; } = 12.0f;
public float RoofThickness { get; set; } = 4.0f;
public string FloorMaterialPath { get; set; } = "";
public string WallMaterialPath { get; set; } = "";
public string OuterWallMaterialPath { get; set; } = "";
public string InnerWallMaterialPath { get; set; } = "";
public string WallCapMaterialPath { get; set; } = "";
public string DoorFrameMaterialPath { get; set; } = "";
public string WindowFrameMaterialPath { get; set; } = "";
public string BaseboardMaterialPath { get; set; } = "";
public string CorridorFloorMaterialPath { get; set; } = "";
public string ThresholdMaterialPath { get; set; } = "";
public string RoofMaterialPath { get; set; } = "";
public float FloorMaterialScale { get; set; }
public float WallMaterialScale { get; set; }
public float OuterWallMaterialScale { get; set; }
public float InnerWallMaterialScale { get; set; }
public float WallCapMaterialScale { get; set; }
public float DoorFrameMaterialScale { get; set; }
public float WindowFrameMaterialScale { get; set; }
public float BaseboardMaterialScale { get; set; }
public float CorridorFloorMaterialScale { get; set; }
public float ThresholdMaterialScale { get; set; }
public float RoofMaterialScale { get; set; }
[JsonIgnore]
public float DefaultFloorSpacing => MathF.Max( 1.0f, WallHeight ) +
MathF.Max( 0.0f, WallCapHeight ) +
MathF.Max( 0.5f, FloorThickness );
[JsonIgnore]
public float EffectiveFloorSpacing => FloorSpacing > 0.0f
? Math.Clamp( FloorSpacing, 1.0f, 8192.0f )
: Math.Clamp( DefaultFloorSpacing, 1.0f, 8192.0f );
}
public sealed class RoomLayoutRoom
{
public int Id { get; set; }
public int Floor { get; set; }
public string Name { get; set; } = "";
public RoomLayoutRect Bounds { get; set; }
public string FloorMaterialPath { get; set; } = "";
public string WallMaterialPath { get; set; } = "";
public string OuterWallMaterialPath { get; set; } = "";
public string InnerWallMaterialPath { get; set; } = "";
public string WallCapMaterialPath { get; set; } = "";
public string BaseboardMaterialPath { get; set; } = "";
public string ThresholdMaterialPath { get; set; } = "";
public string RoofMaterialPath { get; set; } = "";
public float FloorMaterialScale { get; set; }
public float WallMaterialScale { get; set; }
public float OuterWallMaterialScale { get; set; }
public float InnerWallMaterialScale { get; set; }
public float WallCapMaterialScale { get; set; }
public float BaseboardMaterialScale { get; set; }
public float ThresholdMaterialScale { get; set; }
public float RoofMaterialScale { get; set; }
}
public sealed class RoomLayoutDoor
{
public int Id { get; set; }
public int RoomId { get; set; }
public int CorridorId { get; set; }
public int CorridorSegmentIndex { get; set; }
public int CorridorSide { get; set; }
public RoomLayoutWallSide Side { get; set; }
public float Offset { get; set; }
public float Width { get; set; }
public float Height { get; set; } = -1.0f;
public string DoorFrameMaterialPath { get; set; } = "";
public float DoorFrameMaterialScale { get; set; }
}
public sealed class RoomLayoutWindow
{
public int Id { get; set; }
public int RoomId { get; set; }
public int CorridorId { get; set; }
public int CorridorSegmentIndex { get; set; }
public int CorridorSide { get; set; }
public RoomLayoutWallSide Side { get; set; }
public float Offset { get; set; }
public float Width { get; set; }
public float Height { get; set; } = -1.0f;
public float SillHeight { get; set; } = -1.0f;
public string WindowFrameMaterialPath { get; set; } = "";
public float WindowFrameMaterialScale { get; set; }
}
public sealed class RoomLayoutCorridor
{
public int Id { get; set; }
public int Floor { get; set; }
public int StartDoorId { get; set; }
public int EndDoorId { get; set; }
public float Width { get; set; }
public bool ManualPath { get; set; }
public string FloorMaterialPath { get; set; } = "";
public string WallMaterialPath { get; set; } = "";
public string OuterWallMaterialPath { get; set; } = "";
public string InnerWallMaterialPath { get; set; } = "";
public string WallCapMaterialPath { get; set; } = "";
public string BaseboardMaterialPath { get; set; } = "";
public string ThresholdMaterialPath { get; set; } = "";
public string RoofMaterialPath { get; set; } = "";
public float FloorMaterialScale { get; set; }
public float WallMaterialScale { get; set; }
public float OuterWallMaterialScale { get; set; }
public float InnerWallMaterialScale { get; set; }
public float WallCapMaterialScale { get; set; }
public float BaseboardMaterialScale { get; set; }
public float ThresholdMaterialScale { get; set; }
public float RoofMaterialScale { get; set; }
public List<RoomLayoutPoint> BendPoints { get; set; } = new();
}
public sealed class RoomLayoutFloorCutout
{
public int Id { get; set; }
public int Floor { get; set; }
public string Name { get; set; } = "";
public RoomLayoutRect Bounds { get; set; }
}
public enum RoomLayoutWallSide
{
North,
East,
South,
West
}
public struct RoomLayoutPoint
{
public float X { get; set; }
public float Y { get; set; }
public RoomLayoutPoint( float x, float y )
{
X = x;
Y = y;
}
[JsonIgnore]
public Vector2 Vector => new( X, Y );
public static RoomLayoutPoint FromVector( Vector2 point ) => new( point.x, point.y );
}
public struct RoomLayoutRect
{
public float X { get; set; }
public float Y { get; set; }
public float Width { get; set; }
public float Height { get; set; }
public RoomLayoutRect( float x, float y, float width, float height )
{
X = x;
Y = y;
Width = width;
Height = height;
}
[JsonIgnore]
public Vector2 Min => new( X, Y );
[JsonIgnore]
public Vector2 Max => new( X + Width, Y + Height );
[JsonIgnore]
public Vector2 Center => new( X + Width * 0.5f, Y + Height * 0.5f );
public bool Contains( Vector2 point )
{
return point.x >= X && point.x <= X + Width &&
point.y >= Y && point.y <= Y + Height;
}
public RoomLayoutRect MovedBy( Vector2 delta )
{
return new RoomLayoutRect( X + delta.x, Y + delta.y, Width, Height );
}
public static RoomLayoutRect FromPoints( Vector2 a, Vector2 b )
{
var minX = MathF.Min( a.x, b.x );
var minY = MathF.Min( a.y, b.y );
var maxX = MathF.Max( a.x, b.x );
var maxY = MathF.Max( a.y, b.y );
return new RoomLayoutRect( minX, minY, maxX - minX, maxY - minY );
}
}