Editor/InteriorLayoutBuilder/RoomLayoutGeometryBuilder.Corridors.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace ReusableRoomLayout;
public sealed partial class RoomLayoutGeometryBuilder
{
private static void BuildCorridor(
Scene scene,
GameObject frameParent,
RoomLayoutDocument document,
RoomLayoutCorridor corridor,
ICollection<RoomLayoutFloorSlab> floorSlabs,
ICollection<RoomLayoutFloorSlab> roofSlabs,
ICollection<RoomLayoutThresholdSlab> thresholdSlabs,
ICollection<RoomLayoutSolidSlab> solidSlabs )
{
if ( !document.CorridorDoorsAreOnFloor( corridor ) )
{
return;
}
if ( !TryGetDoorPoint( document, corridor.StartDoorId, out var start ) ||
!TryGetDoorPoint( document, corridor.EndDoorId, out var end ) )
{
return;
}
var settings = document.Settings;
var width = CorridorClearWidth( settings, corridor, start, end );
var floorMaterialPath = CorridorMaterialPath( settings, corridor, RoomLayoutSurface.CorridorFloor );
var outerWallMaterialPath = CorridorOuterWallMaterialPath( settings, corridor );
var innerWallMaterialPath = CorridorInnerWallMaterialPath( settings, corridor );
var capMaterialPath = CorridorMaterialPath( settings, corridor, RoomLayoutSurface.Cap );
var baseboardMaterialPath = CorridorMaterialPath( settings, corridor, RoomLayoutSurface.Baseboard );
var roofMaterialPath = CorridorMaterialPath( settings, corridor, RoomLayoutSurface.Roof );
var floorMaterialScale = CorridorMaterialScale( settings, corridor, RoomLayoutSurface.CorridorFloor );
var outerWallMaterialScale = CorridorOuterWallMaterialScale( settings, corridor );
var innerWallMaterialScale = CorridorInnerWallMaterialScale( settings, corridor );
var capMaterialScale = CorridorMaterialScale( settings, corridor, RoomLayoutSurface.Cap );
var baseboardMaterialScale = CorridorMaterialScale( settings, corridor, RoomLayoutSurface.Baseboard );
var roofMaterialScale = CorridorMaterialScale( settings, corridor, RoomLayoutSurface.Roof );
var footprintRects = new List<RoomLayoutRect>();
BuildDoorThreshold(
floorSlabs,
roofSlabs,
thresholdSlabs,
settings,
corridor.Id,
"Start",
start,
width,
floorMaterialPath,
floorMaterialScale,
roofMaterialPath,
roofMaterialScale,
ThresholdMaterialPath( document, corridor, start ),
ThresholdMaterialScale( document, corridor, start ) );
BuildDoorThreshold(
floorSlabs,
roofSlabs,
thresholdSlabs,
settings,
corridor.Id,
"End",
end,
width,
floorMaterialPath,
floorMaterialScale,
roofMaterialPath,
roofMaterialScale,
ThresholdMaterialPath( document, corridor, end ),
ThresholdMaterialScale( document, corridor, end ) );
var points = CorridorPath( settings, corridor, start, end, width );
for ( var i = 0; i < points.Count - 1; i++ )
{
BuildCorridorSegmentFloor(
floorSlabs,
roofSlabs,
settings,
footprintRects,
corridor.Id,
i,
points[i],
points[i + 1],
width,
floorMaterialPath,
floorMaterialScale,
roofMaterialPath,
roofMaterialScale );
}
BuildCorridorBendFloors( floorSlabs, roofSlabs, settings, footprintRects, corridor.Id, points, width, floorMaterialPath, floorMaterialScale, roofMaterialPath, roofMaterialScale );
BuildCorridorOpeningFloors( floorSlabs, roofSlabs, settings, document, corridor, points, width, floorMaterialPath, floorMaterialScale, roofMaterialPath, roofMaterialScale );
BuildCorridorBoundaryWalls(
scene,
frameParent,
document,
solidSlabs,
settings,
corridor,
points,
footprintRects,
start,
end,
width,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
baseboardMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale,
baseboardMaterialScale );
}
private static List<Vector2> CorridorPath(
RoomLayoutSettings settings,
RoomLayoutCorridor corridor,
DoorPoint start,
DoorPoint end,
float width )
{
var startPortal = CorridorPortalPoint( start );
var endPortal = CorridorPortalPoint( end );
var startApproach = CorridorApproachPoint( settings, start, width );
var endApproach = CorridorApproachPoint( settings, end, width );
var points = new List<Vector2>();
if ( corridor.ManualPath )
{
AddCorridorPoint( points, startPortal );
AddCorridorPoint( points, startApproach );
foreach ( var bend in corridor.BendPoints )
{
AddCorridorPoint( points, bend.Vector );
}
AddCorridorPoint( points, endApproach );
AddCorridorPoint( points, endPortal );
SimplifyCorridorPath( points );
return points;
}
AddCorridorPoint( points, startPortal );
AddCorridorPoint( points, startApproach );
foreach ( var bend in corridor.BendPoints )
{
AddCorridorPoint( points, bend.Vector );
}
if ( corridor.BendPoints.Count == 0 && !startApproach.x.AlmostEqual( endApproach.x ) && !startApproach.y.AlmostEqual( endApproach.y ) )
{
AddCorridorPoint( points, new Vector2( endApproach.x, startApproach.y ) );
}
AddCorridorPoint( points, endApproach );
AddCorridorPoint( points, endPortal );
SimplifyCorridorPath( points );
return points;
}
private static Vector2 CorridorPortalPoint( DoorPoint door )
{
return door.Point;
}
private static Vector2 CorridorApproachPoint( RoomLayoutSettings settings, DoorPoint door, float width )
{
return door.Point + door.Normal * (width * 0.5f + settings.WallThickness);
}
private static void AddCorridorPoint( List<Vector2> points, Vector2 point )
{
if ( points.Count == 0 || Vector2.DistanceBetween( points[^1], point ) > 0.5f )
{
points.Add( point );
}
}
private static void SimplifyCorridorPath( List<Vector2> points )
{
for ( var i = 1; i < points.Count - 1; )
{
if ( IsRedundantCorridorPoint( points[i - 1], points[i], points[i + 1] ) )
{
points.RemoveAt( i );
continue;
}
i++;
}
}
private static bool IsRedundantCorridorPoint( Vector2 a, Vector2 b, Vector2 c )
{
if ( a.x.AlmostEqual( b.x ) && b.x.AlmostEqual( c.x ) )
{
return IsBetween( b.y, a.y, c.y );
}
return a.y.AlmostEqual( b.y ) && b.y.AlmostEqual( c.y ) && IsBetween( b.x, a.x, c.x );
}
private static bool IsBetween( float value, float a, float b )
{
return value >= MathF.Min( a, b ) - 0.5f && value <= MathF.Max( a, b ) + 0.5f;
}
private static void BuildCorridorSegmentFloor(
ICollection<RoomLayoutFloorSlab> floorSlabs,
ICollection<RoomLayoutFloorSlab> roofSlabs,
RoomLayoutSettings settings,
ICollection<RoomLayoutRect> footprintRects,
int corridorId,
int segmentIndex,
Vector2 a,
Vector2 b,
float width,
string floorMaterialPath,
float floorMaterialScale,
string roofMaterialPath,
float roofMaterialScale )
{
if ( !TryGetCorridorSegmentRect( a, b, width, out var floorRect, out var horizontal ) )
{
return;
}
var structuralRect = CorridorSegmentStructuralRect( floorRect, settings, horizontal );
AddCorridorFloorSlab(
floorSlabs,
roofSlabs,
settings,
footprintRects,
$"Corridor {corridorId:00} Segment {segmentIndex:00} Floor",
floorRect,
structuralRect,
floorMaterialPath,
floorMaterialScale,
$"Corridor {corridorId:00} Segment {segmentIndex:00} Roof",
roofMaterialPath,
roofMaterialScale );
}
private static bool TryGetCorridorSegmentRect( Vector2 a, Vector2 b, float width, out RoomLayoutRect rect, out bool horizontal )
{
rect = default;
horizontal = false;
var delta = b - a;
if ( delta.Length < 1.0f )
{
return false;
}
horizontal = MathF.Abs( delta.x ) >= MathF.Abs( delta.y );
var length = horizontal ? MathF.Abs( delta.x ) : MathF.Abs( delta.y );
var center = (a + b) * 0.5f;
rect = horizontal
? new RoomLayoutRect( MathF.Min( a.x, b.x ), center.y - width * 0.5f, length, width )
: new RoomLayoutRect( center.x - width * 0.5f, MathF.Min( a.y, b.y ), width, length );
return rect.Width >= 1.0f && rect.Height >= 1.0f;
}
private static void BuildDoorThreshold(
ICollection<RoomLayoutFloorSlab> floorSlabs,
ICollection<RoomLayoutFloorSlab> roofSlabs,
ICollection<RoomLayoutThresholdSlab> thresholdSlabs,
RoomLayoutSettings settings,
int corridorId,
string suffix,
DoorPoint door,
float width,
string floorMaterialPath,
float floorMaterialScale,
string roofMaterialPath,
float roofMaterialScale,
string thresholdMaterialPath,
float thresholdMaterialScale )
{
var center = door.Point + door.Normal * (settings.WallThickness * 0.5f);
var verticalWall = MathF.Abs( door.Normal.y ) > MathF.Abs( door.Normal.x );
var floorRect = verticalWall
? new RoomLayoutRect( center.x - width * 0.5f, center.y - settings.WallThickness * 0.5f, width, settings.WallThickness )
: new RoomLayoutRect( center.x - settings.WallThickness * 0.5f, center.y - width * 0.5f, settings.WallThickness, width );
AddFloorSlab( floorSlabs, $"Corridor {corridorId:00} {suffix} Threshold Floor", floorRect, floorMaterialPath, floorMaterialScale );
if ( settings.RoofEnabled )
{
AddRoofSlab( roofSlabs, $"Corridor {corridorId:00} {suffix} Threshold Roof", floorRect, roofMaterialPath, roofMaterialScale );
}
if ( settings.ThresholdsEnabled )
{
var thresholdDepth = Math.Clamp( settings.ThresholdDepth, 0.5f, 512.0f );
var thresholdRect = verticalWall
? new RoomLayoutRect( center.x - width * 0.5f, center.y - thresholdDepth * 0.5f, width, thresholdDepth )
: new RoomLayoutRect( center.x - thresholdDepth * 0.5f, center.y - width * 0.5f, thresholdDepth, width );
AddThresholdSlab( thresholdSlabs, $"Corridor {corridorId:00} {suffix} Threshold", thresholdRect, thresholdMaterialPath, thresholdMaterialScale );
}
}
private static void BuildCorridorBendFloors(
ICollection<RoomLayoutFloorSlab> floorSlabs,
ICollection<RoomLayoutFloorSlab> roofSlabs,
RoomLayoutSettings settings,
ICollection<RoomLayoutRect> footprintRects,
int corridorId,
IReadOnlyList<Vector2> points,
float width,
string floorMaterialPath,
float floorMaterialScale,
string roofMaterialPath,
float roofMaterialScale )
{
for ( var i = 1; i < points.Count - 1; i++ )
{
var rect = new RoomLayoutRect( points[i].x - width * 0.5f, points[i].y - width * 0.5f, width, width );
AddCorridorFloorSlab(
floorSlabs,
roofSlabs,
settings,
footprintRects,
$"Corridor {corridorId:00} Bend {i:00} Floor",
rect,
StructuralFootprintRect( rect, settings ),
floorMaterialPath,
floorMaterialScale,
$"Corridor {corridorId:00} Bend {i:00} Roof",
roofMaterialPath,
roofMaterialScale );
}
}
private static void AddCorridorFloorSlab(
ICollection<RoomLayoutFloorSlab> floorSlabs,
ICollection<RoomLayoutFloorSlab> roofSlabs,
RoomLayoutSettings settings,
ICollection<RoomLayoutRect> footprintRects,
string name,
RoomLayoutRect rect,
RoomLayoutRect structuralRect,
string floorMaterialPath,
float floorMaterialScale,
string roofName,
string roofMaterialPath,
float roofMaterialScale )
{
AddFloorSlab( floorSlabs, name, structuralRect, floorMaterialPath, floorMaterialScale );
if ( settings.RoofEnabled )
{
AddRoofSlab( roofSlabs, roofName, structuralRect, roofMaterialPath, roofMaterialScale );
}
footprintRects.Add( rect );
}
private static RoomLayoutRect CorridorSegmentStructuralRect( RoomLayoutRect rect, RoomLayoutSettings settings, bool horizontal )
{
var overlap = MathF.Max( 0.0f, settings.WallThickness );
return horizontal
? new RoomLayoutRect( rect.X, rect.Y - overlap, rect.Width, rect.Height + overlap * 2.0f )
: new RoomLayoutRect( rect.X - overlap, rect.Y, rect.Width + overlap * 2.0f, rect.Height );
}
private static void BuildCorridorWallRun(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int segmentIndex,
float centerX,
float centerY,
float length,
bool horizontal,
int sideIndex,
string wallMaterialPath,
string capMaterialPath )
{
var size = horizontal
? new Vector3( length, settings.WallThickness, settings.WallHeight )
: new Vector3( settings.WallThickness, length, settings.WallHeight );
var capSize = horizontal
? new Vector3( length, settings.WallThickness, settings.WallCapHeight )
: new Vector3( settings.WallThickness, length, settings.WallCapHeight );
AddSolidSlab(
solidSlabs,
$"Corridor {corridorId:00} Segment {segmentIndex:00} Wall {sideIndex}",
new Vector3( centerX, centerY, settings.WallHeight * 0.5f ),
size,
RoomLayoutSurface.Wall,
materialPath: wallMaterialPath );
AddSolidSlab(
solidSlabs,
$"Corridor {corridorId:00} Segment {segmentIndex:00} Wall {sideIndex} Cap",
new Vector3( centerX, centerY, settings.WallHeight + settings.WallCapHeight * 0.5f ),
capSize,
RoomLayoutSurface.Cap,
materialPath: capMaterialPath );
}
private static void BuildCorridorBaseboardRun(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int segmentIndex,
float centerX,
float centerY,
float length,
bool horizontal,
int direction,
string materialPath,
float textureWorldSize )
{
if ( !settings.BaseboardsEnabled ||
length < 1.0f ||
settings.BaseboardHeight < 0.5f ||
settings.BaseboardDepth < 0.5f )
{
return;
}
var offset = -direction * settings.BaseboardDepth * 0.5f + direction * TrimEmbedForDepth( settings.BaseboardDepth );
var center = horizontal
? new Vector3( centerX, centerY + offset, settings.BaseboardHeight * 0.5f )
: new Vector3( centerX + offset, centerY, settings.BaseboardHeight * 0.5f );
var size = horizontal
? new Vector3( length, settings.BaseboardDepth, settings.BaseboardHeight )
: new Vector3( settings.BaseboardDepth, length, settings.BaseboardHeight );
AddSolidSlab(
solidSlabs,
$"Corridor {corridorId:00} Segment {segmentIndex:00} Baseboard",
center,
size,
RoomLayoutSurface.Baseboard,
hasCollider: false,
materialPath: materialPath,
textureWorldSize: textureWorldSize );
}
private static bool TryGetDoorPoint( RoomLayoutDocument document, int doorId, out DoorPoint point )
{
point = default;
var door = document.FindDoor( doorId );
if ( door is null )
{
return false;
}
var room = document.FindRoom( door.RoomId );
if ( room is null )
{
return false;
}
var bounds = room.Bounds;
point = door.Side switch
{
RoomLayoutWallSide.North => new DoorPoint(
new Vector2( bounds.X + door.Offset, bounds.Y + bounds.Height ),
new Vector2( 0.0f, 1.0f ),
door.Width,
door.RoomId ),
RoomLayoutWallSide.South => new DoorPoint(
new Vector2( bounds.X + door.Offset, bounds.Y ),
new Vector2( 0.0f, -1.0f ),
door.Width,
door.RoomId ),
RoomLayoutWallSide.East => new DoorPoint(
new Vector2( bounds.X + bounds.Width, bounds.Y + door.Offset ),
new Vector2( 1.0f, 0.0f ),
door.Width,
door.RoomId ),
_ => new DoorPoint(
new Vector2( bounds.X, bounds.Y + door.Offset ),
new Vector2( -1.0f, 0.0f ),
door.Width,
door.RoomId )
};
return true;
}
private static float CorridorClearWidth( RoomLayoutSettings settings, RoomLayoutCorridor corridor, DoorPoint start, DoorPoint end )
{
var width = corridor.Width <= 0.0f ? settings.DefaultCorridorWidth : corridor.Width;
width = MathF.Max( 1.0f, width );
if ( start.Width > 0.0f )
{
width = MathF.Min( width, start.Width );
}
if ( end.Width > 0.0f )
{
width = MathF.Min( width, end.Width );
}
return width;
}
private static float CorridorClearWidth( RoomLayoutDocument document, RoomLayoutCorridor corridor )
{
var width = corridor.Width <= 0.0f ? document.Settings.DefaultCorridorWidth : corridor.Width;
width = MathF.Max( 1.0f, width );
var startDoor = document.FindDoor( corridor.StartDoorId );
if ( startDoor is not null && startDoor.Width > 0.0f )
{
width = MathF.Min( width, startDoor.Width );
}
var endDoor = document.FindDoor( corridor.EndDoorId );
if ( endDoor is not null && endDoor.Width > 0.0f )
{
width = MathF.Min( width, endDoor.Width );
}
return width;
}
private readonly record struct DoorPoint( Vector2 Point, Vector2 Normal, float Width, int RoomId );
}