Editor/InteriorLayoutBuilder/RoomLayoutGeometryBuilder.CorridorBoundarySections.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace ReusableRoomLayout;
public sealed partial class RoomLayoutGeometryBuilder
{
private static int BuildCorridorBoundaryWall(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutDocument document,
RoomLayoutSettings settings,
RoomLayoutCorridor corridor,
IReadOnlyList<Vector2> points,
int wallIndex,
CorridorBoundaryEdge edge,
DoorPoint start,
DoorPoint end,
float width,
string outerWallMaterialPath,
string innerWallMaterialPath,
string capMaterialPath,
string baseboardMaterialPath,
float outerWallMaterialScale,
float innerWallMaterialScale,
float capMaterialScale,
float baseboardMaterialScale )
{
if ( edge.Length < 1.0f )
{
return wallIndex;
}
var clippedEdges = new List<CorridorBoundaryEdge> { edge };
ClipCorridorDoorOpening( clippedEdges, start, width );
ClipCorridorDoorOpening( clippedEdges, end, width );
foreach ( var clippedEdge in clippedEdges )
{
var openings = CorridorBoundaryOpeningsForEdge( document, corridor, points, clippedEdge, width );
wallIndex = BuildCorridorBoundaryWallWithOpenings(
solidSlabs,
settings,
corridor.Id,
wallIndex,
clippedEdge,
openings,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
baseboardMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale,
baseboardMaterialScale );
}
return wallIndex;
}
private static int BuildCorridorBoundaryWallWithOpenings(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int wallIndex,
CorridorBoundaryEdge edge,
IReadOnlyList<CorridorBoundaryOpening> openings,
string outerWallMaterialPath,
string innerWallMaterialPath,
string capMaterialPath,
string baseboardMaterialPath,
float outerWallMaterialScale,
float innerWallMaterialScale,
float capMaterialScale,
float baseboardMaterialScale )
{
var cursor = edge.Start;
var cursorTouchesOpening = false;
foreach ( var opening in openings.OrderBy( opening => opening.Start ) )
{
if ( opening.End <= cursor + 0.5f )
{
continue;
}
var openingStart = Math.Clamp( opening.Start, edge.Start, edge.End );
var openingEnd = Math.Clamp( opening.End, edge.Start, edge.End );
openingStart = MathF.Max( openingStart, cursor );
if ( openingStart > cursor + 0.5f )
{
wallIndex = BuildCorridorBoundaryWallRun(
solidSlabs,
settings,
corridorId,
wallIndex,
edge.WithSpan( cursor, openingStart ),
cursorTouchesOpening,
true,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
baseboardMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale,
baseboardMaterialScale );
}
if ( openingEnd - openingStart >= 1.0f )
{
wallIndex = BuildCorridorBoundaryOpening(
solidSlabs,
settings,
corridorId,
wallIndex,
edge.WithSpan( openingStart, openingEnd ),
opening,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
baseboardMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale,
baseboardMaterialScale );
}
cursor = MathF.Max( cursor, openingEnd );
cursorTouchesOpening = true;
}
if ( edge.End > cursor + 0.5f )
{
wallIndex = BuildCorridorBoundaryWallRun(
solidSlabs,
settings,
corridorId,
wallIndex,
edge.WithSpan( cursor, edge.End ),
cursorTouchesOpening,
false,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
baseboardMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale,
baseboardMaterialScale );
}
return wallIndex;
}
private static int BuildCorridorBoundaryWallRun(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int wallIndex,
CorridorBoundaryEdge edge,
bool revealStartOuter,
bool revealEndOuter,
string outerWallMaterialPath,
string innerWallMaterialPath,
string capMaterialPath,
string baseboardMaterialPath,
float outerWallMaterialScale,
float innerWallMaterialScale,
float capMaterialScale,
float baseboardMaterialScale )
{
BuildCorridorBoundaryWallSection(
solidSlabs,
settings,
corridorId,
wallIndex,
edge,
0.0f,
settings.WallHeight,
true,
revealStartOuter,
revealEndOuter,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale );
BuildCorridorBoundaryBaseboardRun( solidSlabs, settings, corridorId, wallIndex, edge, baseboardMaterialPath, baseboardMaterialScale );
return wallIndex + 1;
}
private static int BuildCorridorBoundaryOpening(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int wallIndex,
CorridorBoundaryEdge edge,
CorridorBoundaryOpening opening,
string outerWallMaterialPath,
string innerWallMaterialPath,
string capMaterialPath,
string baseboardMaterialPath,
float outerWallMaterialScale,
float innerWallMaterialScale,
float capMaterialScale,
float baseboardMaterialScale )
{
if ( opening.Kind == CorridorBoundaryOpeningKind.Door )
{
var openingTop = EffectiveDoorHeight( settings, opening.Door );
if ( settings.WallHeight - openingTop > 0.5f )
{
BuildCorridorBoundaryWallSection(
solidSlabs,
settings,
corridorId,
wallIndex,
edge,
openingTop,
settings.WallHeight,
true,
true,
true,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale );
return wallIndex + 1;
}
return wallIndex;
}
var sillHeight = EffectiveWindowSillHeight( settings, opening.Window );
var openingHeight = EffectiveWindowHeight( settings, opening.Window, sillHeight );
var windowTop = MathF.Min( settings.WallHeight, sillHeight + openingHeight );
if ( sillHeight > 0.5f )
{
BuildCorridorBoundaryWallSection(
solidSlabs,
settings,
corridorId,
wallIndex,
edge,
0.0f,
sillHeight,
false,
true,
true,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale );
}
if ( settings.WallHeight - windowTop > 0.5f )
{
BuildCorridorBoundaryWallSection(
solidSlabs,
settings,
corridorId,
wallIndex,
edge,
windowTop,
settings.WallHeight,
true,
true,
true,
outerWallMaterialPath,
innerWallMaterialPath,
capMaterialPath,
outerWallMaterialScale,
innerWallMaterialScale,
capMaterialScale );
}
BuildCorridorBoundaryBaseboardRun( solidSlabs, settings, corridorId, wallIndex, edge, baseboardMaterialPath, baseboardMaterialScale );
return wallIndex + 1;
}
private static void BuildCorridorBoundaryWallSection(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int wallIndex,
CorridorBoundaryEdge edge,
float zMin,
float zMax,
bool includeTopCap,
bool revealStartOuter,
bool revealEndOuter,
string outerWallMaterialPath,
string innerWallMaterialPath,
string capMaterialPath,
float outerWallMaterialScale,
float innerWallMaterialScale,
float capMaterialScale )
{
var height = zMax - zMin;
if ( edge.Length < 1.0f || height < 0.5f )
{
return;
}
var wall = CorridorBoundaryBox( settings, edge, edge.Length, height, zMin + height * 0.5f );
AddSolidSlab(
solidSlabs,
$"Corridor {corridorId:00} Boundary {wallIndex:00} Wall",
wall.Center,
wall.Size,
RoomLayoutSurface.Wall,
materialPath: outerWallMaterialPath,
textureWorldSize: outerWallMaterialScale,
northMaterialPath: CorridorBoundaryFaceMaterialPath( edge, revealStartOuter, revealEndOuter, CardinalDirection.North, outerWallMaterialPath, innerWallMaterialPath ),
southMaterialPath: CorridorBoundaryFaceMaterialPath( edge, revealStartOuter, revealEndOuter, CardinalDirection.South, outerWallMaterialPath, innerWallMaterialPath ),
eastMaterialPath: CorridorBoundaryFaceMaterialPath( edge, revealStartOuter, revealEndOuter, CardinalDirection.East, outerWallMaterialPath, innerWallMaterialPath ),
westMaterialPath: CorridorBoundaryFaceMaterialPath( edge, revealStartOuter, revealEndOuter, CardinalDirection.West, outerWallMaterialPath, innerWallMaterialPath ),
northTextureWorldSize: CorridorBoundaryFaceMaterialScale( edge, revealStartOuter, revealEndOuter, CardinalDirection.North, outerWallMaterialScale, innerWallMaterialScale ),
southTextureWorldSize: CorridorBoundaryFaceMaterialScale( edge, revealStartOuter, revealEndOuter, CardinalDirection.South, outerWallMaterialScale, innerWallMaterialScale ),
eastTextureWorldSize: CorridorBoundaryFaceMaterialScale( edge, revealStartOuter, revealEndOuter, CardinalDirection.East, outerWallMaterialScale, innerWallMaterialScale ),
westTextureWorldSize: CorridorBoundaryFaceMaterialScale( edge, revealStartOuter, revealEndOuter, CardinalDirection.West, outerWallMaterialScale, innerWallMaterialScale ) );
if ( !includeTopCap )
{
return;
}
var cap = CorridorBoundaryBox( settings, edge, edge.Length, settings.WallCapHeight, settings.WallHeight + settings.WallCapHeight * 0.5f );
AddSolidSlab(
solidSlabs,
$"Corridor {corridorId:00} Boundary {wallIndex:00} Wall Cap",
cap.Center,
cap.Size,
RoomLayoutSurface.Cap,
materialPath: capMaterialPath,
textureWorldSize: capMaterialScale );
}
private static string CorridorBoundaryFaceMaterialPath(
CorridorBoundaryEdge edge,
bool revealStartOuter,
bool revealEndOuter,
CardinalDirection face,
string outerWallMaterialPath,
string innerWallMaterialPath )
{
return IsCorridorBoundaryOuterFace( edge, revealStartOuter, revealEndOuter, face )
? outerWallMaterialPath
: innerWallMaterialPath;
}
private static float CorridorBoundaryFaceMaterialScale(
CorridorBoundaryEdge edge,
bool revealStartOuter,
bool revealEndOuter,
CardinalDirection face,
float outerWallMaterialScale,
float innerWallMaterialScale )
{
return IsCorridorBoundaryOuterFace( edge, revealStartOuter, revealEndOuter, face )
? outerWallMaterialScale
: innerWallMaterialScale;
}
private static bool IsCorridorBoundaryOuterFace(
CorridorBoundaryEdge edge,
bool revealStartOuter,
bool revealEndOuter,
CardinalDirection face )
{
return face switch
{
CardinalDirection.North => edge.Horizontal
? edge.Direction > 0
: revealEndOuter,
CardinalDirection.South => edge.Horizontal
? edge.Direction < 0
: revealStartOuter,
CardinalDirection.East => edge.Horizontal
? revealEndOuter
: edge.Direction > 0,
_ => edge.Horizontal
? revealStartOuter
: edge.Direction < 0
};
}
private static void BuildCorridorBoundaryBaseboardRun(
ICollection<RoomLayoutSolidSlab> solidSlabs,
RoomLayoutSettings settings,
int corridorId,
int wallIndex,
CorridorBoundaryEdge edge,
string materialPath,
float textureWorldSize )
{
BuildCorridorBaseboardRun(
solidSlabs,
settings,
corridorId,
wallIndex,
edge.Horizontal ? edge.CenterAlong : edge.Fixed,
edge.Horizontal ? edge.Fixed : edge.CenterAlong,
edge.Length,
edge.Horizontal,
edge.Direction,
materialPath,
textureWorldSize );
}
private static WallBoxData CorridorBoundaryBox(
RoomLayoutSettings settings,
CorridorBoundaryEdge edge,
float length,
float height,
float centerZ )
{
var wallOffset = settings.WallThickness * 0.5f * edge.Direction;
return edge.Horizontal
? new WallBoxData(
new Vector3( edge.CenterAlong, edge.Fixed + wallOffset, centerZ ),
new Vector3( length, settings.WallThickness, height ) )
: new WallBoxData(
new Vector3( edge.Fixed + wallOffset, edge.CenterAlong, centerZ ),
new Vector3( settings.WallThickness, length, height ) );
}
private static void ClipCorridorDoorOpening( List<CorridorBoundaryEdge> edges, DoorPoint door, float width )
{
var clipped = new List<CorridorBoundaryEdge>();
foreach ( var edge in edges )
{
if ( !TryGetCorridorDoorOpeningSpan( edge, door, width, out var openStart, out var openEnd ) ||
openEnd <= edge.Start + 0.5f ||
openStart >= edge.End - 0.5f )
{
clipped.Add( edge );
continue;
}
var beforeEnd = MathF.Min( openStart, edge.End );
if ( beforeEnd - edge.Start >= 1.0f )
{
clipped.Add( edge.WithSpan( edge.Start, beforeEnd ) );
}
var afterStart = MathF.Max( openEnd, edge.Start );
if ( edge.End - afterStart >= 1.0f )
{
clipped.Add( edge.WithSpan( afterStart, edge.End ) );
}
}
edges.Clear();
edges.AddRange( clipped );
}
private static bool TryGetCorridorDoorOpeningSpan(
CorridorBoundaryEdge edge,
DoorPoint door,
float width,
out float openStart,
out float openEnd )
{
openStart = 0.0f;
openEnd = 0.0f;
var verticalDoorWall = MathF.Abs( door.Normal.y ) > MathF.Abs( door.Normal.x );
var halfWidth = width * 0.5f;
if ( verticalDoorWall )
{
if ( !edge.Horizontal ||
!edge.Fixed.AlmostEqual( door.Point.y ) ||
edge.Direction != -MathF.Sign( door.Normal.y ) )
{
return false;
}
openStart = door.Point.x - halfWidth;
openEnd = door.Point.x + halfWidth;
return true;
}
if ( edge.Horizontal ||
!edge.Fixed.AlmostEqual( door.Point.x ) ||
edge.Direction != -MathF.Sign( door.Normal.x ) )
{
return false;
}
openStart = door.Point.y - halfWidth;
openEnd = door.Point.y + halfWidth;
return true;
}
private enum CardinalDirection
{
North,
South,
East,
West
}
private readonly record struct CorridorBoundaryEdge( float Start, float End, float Fixed, bool Horizontal, int Direction )
{
public float Length => End - Start;
public float CenterAlong => (Start + End) * 0.5f;
public CorridorBoundaryEdge WithSpan( float start, float end ) => new( start, end, Fixed, Horizontal, Direction );
}
}