Editor/InteriorLayoutBuilder/RoomLayoutGeometryBuilder.CorridorOpenings.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace ReusableRoomLayout;
public sealed partial class RoomLayoutGeometryBuilder
{
private static WallBoxData CorridorInteriorTrimBox(
RoomLayoutSettings settings,
CorridorBoundaryEdge edge,
float length,
float depth,
float height,
float centerZ )
{
var embed = TrimEmbedForDepth( depth );
var inwardOffset = -edge.Direction * depth * 0.5f + edge.Direction * embed;
return edge.Horizontal
? new WallBoxData(
new Vector3( edge.CenterAlong, edge.Fixed + inwardOffset, centerZ ),
new Vector3( length, depth, height ) )
: new WallBoxData(
new Vector3( edge.Fixed + inwardOffset, edge.CenterAlong, centerZ ),
new Vector3( depth, length, height ) );
}
private static IReadOnlyList<CorridorBoundaryOpening> CorridorBoundaryOpeningsForEdge(
RoomLayoutDocument document,
RoomLayoutCorridor corridor,
IReadOnlyList<Vector2> points,
CorridorBoundaryEdge edge,
float corridorWidth )
{
var openings = new List<CorridorBoundaryOpening>();
foreach ( var door in document.Doors.Where( door => door.CorridorId == corridor.Id ) )
{
if ( TryGetCorridorOpeningSpan(
points,
door.CorridorSegmentIndex,
door.CorridorSide,
door.Offset,
door.Width,
edge,
corridorWidth,
out var start,
out var end ) )
{
openings.Add( new CorridorBoundaryOpening( CorridorBoundaryOpeningKind.Door, start, end, door, null ) );
}
}
foreach ( var window in document.Windows.Where( window => window.CorridorId == corridor.Id ) )
{
if ( TryGetCorridorOpeningSpan(
points,
window.CorridorSegmentIndex,
window.CorridorSide,
window.Offset,
window.Width,
edge,
corridorWidth,
out var start,
out var end ) )
{
openings.Add( new CorridorBoundaryOpening( CorridorBoundaryOpeningKind.Window, start, end, null, window ) );
}
}
return openings;
}
private static bool TryGetCorridorOpeningSpan(
IReadOnlyList<Vector2> points,
int segmentIndex,
int side,
float offset,
float openingWidth,
CorridorBoundaryEdge edge,
float corridorWidth,
out float openStart,
out float openEnd )
{
openStart = 0.0f;
openEnd = 0.0f;
if ( !TryGetCorridorOpeningEdge( points, segmentIndex, side, offset, openingWidth, corridorWidth, out var openingEdge ) ||
openingEdge.Horizontal != edge.Horizontal ||
openingEdge.Direction != edge.Direction ||
!openingEdge.Fixed.AlmostEqual( edge.Fixed ) )
{
return false;
}
openStart = MathF.Max( edge.Start, openingEdge.Start );
openEnd = MathF.Min( edge.End, openingEdge.End );
return openEnd - openStart >= 1.0f;
}
private static bool TryGetCorridorOpeningEdge(
IReadOnlyList<Vector2> points,
int segmentIndex,
int side,
float offset,
float openingWidth,
float corridorWidth,
out CorridorBoundaryEdge edge )
{
edge = default;
if ( segmentIndex < 0 || segmentIndex >= points.Count - 1 || openingWidth < 1.0f )
{
return false;
}
var start = points[segmentIndex];
var end = points[segmentIndex + 1];
var delta = end - start;
if ( delta.Length < 1.0f )
{
return false;
}
var horizontal = MathF.Abs( delta.x ) >= MathF.Abs( delta.y );
var minAlong = horizontal ? MathF.Min( start.x, end.x ) : MathF.Min( start.y, end.y );
var maxAlong = horizontal ? MathF.Max( start.x, end.x ) : MathF.Max( start.y, end.y );
var halfWidth = openingWidth * 0.5f;
var centerAlong = Math.Clamp( minAlong + offset, minAlong + halfWidth, MathF.Max( minAlong + halfWidth, maxAlong - halfWidth ) );
var direction = Math.Sign( side == 0 ? 1 : side );
var fixedCoordinate = (horizontal ? start.y : start.x) + direction * corridorWidth * 0.5f;
edge = new CorridorBoundaryEdge( centerAlong - halfWidth, centerAlong + halfWidth, fixedCoordinate, horizontal, direction );
return edge.Length >= 1.0f;
}
private static void BuildCorridorOpeningFrames(
Scene scene,
GameObject frameParent,
RoomLayoutSettings settings,
RoomLayoutDocument document,
RoomLayoutCorridor corridor,
IReadOnlyList<Vector2> points,
float corridorWidth )
{
foreach ( var door in document.Doors.Where( door => door.CorridorId == corridor.Id ) )
{
if ( TryGetCorridorOpeningEdge(
points,
door.CorridorSegmentIndex,
door.CorridorSide,
door.Offset,
door.Width,
corridorWidth,
out var edge ) )
{
BuildCorridorDoorFrame( scene, frameParent, settings, corridor.Id, door, edge );
}
}
foreach ( var window in document.Windows.Where( window => window.CorridorId == corridor.Id ) )
{
if ( TryGetCorridorOpeningEdge(
points,
window.CorridorSegmentIndex,
window.CorridorSide,
window.Offset,
window.Width,
corridorWidth,
out var edge ) )
{
BuildCorridorWindowFrame( scene, frameParent, settings, corridor.Id, window, edge );
}
}
}
private static void BuildCorridorOpeningFloors(
ICollection<RoomLayoutFloorSlab> floorSlabs,
ICollection<RoomLayoutFloorSlab> roofSlabs,
RoomLayoutSettings settings,
RoomLayoutDocument document,
RoomLayoutCorridor corridor,
IReadOnlyList<Vector2> points,
float corridorWidth,
string floorMaterialPath,
float floorMaterialScale,
string roofMaterialPath,
float roofMaterialScale )
{
foreach ( var door in document.Doors.Where( door => door.CorridorId == corridor.Id ) )
{
if ( !TryGetCorridorOpeningEdge(
points,
door.CorridorSegmentIndex,
door.CorridorSide,
door.Offset,
door.Width,
corridorWidth,
out var edge ) )
{
continue;
}
var rect = edge.Horizontal
? new RoomLayoutRect(
edge.Start,
edge.Direction > 0 ? edge.Fixed : edge.Fixed - settings.WallThickness,
edge.Length,
settings.WallThickness )
: new RoomLayoutRect(
edge.Direction > 0 ? edge.Fixed : edge.Fixed - settings.WallThickness,
edge.Start,
settings.WallThickness,
edge.Length );
AddFloorSlab( floorSlabs, $"Corridor {corridor.Id:00} Door {door.Id:00} Floor", rect, floorMaterialPath, floorMaterialScale );
if ( settings.RoofEnabled )
{
AddRoofSlab( roofSlabs, $"Corridor {corridor.Id:00} Door {door.Id:00} Roof", rect, roofMaterialPath, roofMaterialScale );
}
}
}
private static void BuildCorridorDoorFrame(
Scene scene,
GameObject frameParent,
RoomLayoutSettings settings,
int corridorId,
RoomLayoutDoor door,
CorridorBoundaryEdge edge )
{
if ( edge.Length < 1.0f )
{
return;
}
var jamb = Math.Clamp( settings.DoorFrameThickness, 1.0f, MathF.Max( 1.0f, edge.Length * 0.45f ) );
var height = EffectiveDoorHeight( settings, door );
if ( height < 0.5f )
{
return;
}
var railHeight = MathF.Min( height, Math.Clamp( settings.DoorFrameThickness, 1.0f, MathF.Max( 1.0f, height * 0.45f ) ) );
var leftEdge = edge.WithSpan( edge.Start, edge.Start + jamb );
var rightEdge = edge.WithSpan( edge.End - jamb, edge.End );
var left = CorridorBoundaryBox( settings, leftEdge, leftEdge.Length, height, height * 0.5f );
var right = CorridorBoundaryBox( settings, rightEdge, rightEdge.Length, height, height * 0.5f );
var header = CorridorBoundaryBox( settings, edge, edge.Length, railHeight, height - railHeight * 0.5f );
var materialPath = FirstMaterialPath( door.DoorFrameMaterialPath, settings.DoorFrameMaterialPath );
var textureWorldSize = MaterialScaleOrFallback( door.DoorFrameMaterialScale, MaterialScaleForSettings( settings, RoomLayoutSurface.DoorFrame ) );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Door {door.Id:00} Jamb A", left.Center, left.Size, settings, RoomLayoutSurface.DoorFrame, materialPath, textureWorldSize );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Door {door.Id:00} Jamb B", right.Center, right.Size, settings, RoomLayoutSurface.DoorFrame, materialPath, textureWorldSize );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Door {door.Id:00} Header", header.Center, header.Size, settings, RoomLayoutSurface.DoorFrame, materialPath, textureWorldSize );
}
private static void BuildCorridorWindowFrame(
Scene scene,
GameObject frameParent,
RoomLayoutSettings settings,
int corridorId,
RoomLayoutWindow window,
CorridorBoundaryEdge edge )
{
if ( edge.Length < 1.0f )
{
return;
}
var sillHeight = EffectiveWindowSillHeight( settings, window );
var openingHeight = EffectiveWindowHeight( settings, window, sillHeight );
var jamb = Math.Clamp( settings.WindowFrameThickness, 0.1f, MathF.Max( 0.1f, edge.Length * 0.25f ) );
var railHeight = Math.Clamp( settings.WindowFrameThickness, 0.1f, MathF.Max( 0.1f, openingHeight * 0.25f ) );
var depth = TrimDepth( settings, settings.WindowFrameThickness );
var materialPath = FirstMaterialPath( window.WindowFrameMaterialPath, settings.WindowFrameMaterialPath );
var textureWorldSize = MaterialScaleOrFallback( window.WindowFrameMaterialScale, MaterialScaleForSettings( settings, RoomLayoutSurface.WindowFrame ) );
var verticalCenter = sillHeight + openingHeight * 0.5f;
var sillCenter = sillHeight + railHeight * 0.5f;
var headerCenter = sillHeight + MathF.Max( railHeight * 0.5f, openingHeight - railHeight * 0.5f );
var leftEdge = edge.WithSpan( edge.Start, edge.Start + jamb );
var rightEdge = edge.WithSpan( edge.End - jamb, edge.End );
var left = CorridorInteriorTrimBox( settings, leftEdge, leftEdge.Length, depth, openingHeight, verticalCenter );
var right = CorridorInteriorTrimBox( settings, rightEdge, rightEdge.Length, depth, openingHeight, verticalCenter );
var sill = CorridorInteriorTrimBox( settings, edge, edge.Length, depth, railHeight, sillCenter );
var header = CorridorInteriorTrimBox( settings, edge, edge.Length, depth, railHeight, headerCenter );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Window {window.Id:00} Jamb A", left.Center, left.Size, settings, RoomLayoutSurface.WindowFrame, materialPath, textureWorldSize );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Window {window.Id:00} Jamb B", right.Center, right.Size, settings, RoomLayoutSurface.WindowFrame, materialPath, textureWorldSize );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Window {window.Id:00} Sill", sill.Center, sill.Size, settings, RoomLayoutSurface.WindowFrame, materialPath, textureWorldSize );
CreateBox( scene, frameParent, $"Corridor {corridorId:00} Window {window.Id:00} Header", header.Center, header.Size, settings, RoomLayoutSurface.WindowFrame, materialPath, textureWorldSize );
}
private enum CorridorBoundaryOpeningKind
{
Door,
Window
}
private readonly record struct CorridorBoundaryOpening(
CorridorBoundaryOpeningKind Kind,
float Start,
float End,
RoomLayoutDoor Door,
RoomLayoutWindow Window );
}