Editor/InteriorLayoutBuilder/RoomLayoutGeometryBuilder.Floors.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace ReusableRoomLayout;
public sealed partial class RoomLayoutGeometryBuilder
{
private const float ThresholdSurfaceOffset = 0.15f;
private const float FloorSideProbeOffset = 0.01f;
private static void BuildFloorGeometry(
Scene scene,
GameObject parent,
RoomLayoutSettings settings,
IReadOnlyCollection<RoomLayoutFloorSlab> floorSlabs )
{
if ( floorSlabs.Count == 0 )
{
return;
}
var surfaceCells = BuildFloorSurfaceCells( floorSlabs );
foreach ( var group in surfaceCells.GroupBy( cell => new MaterialSurfaceKey( cell.MaterialPath ?? "", cell.TextureWorldSize ) ) )
{
CreateFloorSurface( scene, parent, settings, group.Key.MaterialPath, group.Key.TextureWorldSize, group.ToArray(), surfaceCells );
}
foreach ( var slab in floorSlabs )
{
CreateFloorCollider( scene, parent, slab, settings.FloorThickness );
}
}
private static void BuildThresholdGeometry(
Scene scene,
GameObject parent,
RoomLayoutSettings settings,
IReadOnlyCollection<RoomLayoutThresholdSlab> thresholdSlabs )
{
if ( thresholdSlabs.Count == 0 )
{
return;
}
foreach ( var group in thresholdSlabs.GroupBy( slab => new MaterialSurfaceKey( slab.MaterialPath ?? "", slab.TextureWorldSize ) ) )
{
CreateThresholdSurface( scene, parent, settings, group.Key.MaterialPath, group.Key.TextureWorldSize, group.ToArray() );
}
}
private static void BuildRoofGeometry(
Scene scene,
GameObject parent,
RoomLayoutSettings settings,
IReadOnlyCollection<RoomLayoutFloorSlab> roofSlabs )
{
if ( roofSlabs.Count == 0 )
{
return;
}
var surfaceCells = BuildFloorSurfaceCells( roofSlabs, preferFirstSlab: true );
foreach ( var group in surfaceCells.GroupBy( cell => new MaterialSurfaceKey( cell.MaterialPath ?? "", cell.TextureWorldSize ) ) )
{
CreateRoofSurface( scene, parent, settings, group.Key.MaterialPath, group.Key.TextureWorldSize, group.ToArray(), surfaceCells );
}
foreach ( var slab in roofSlabs )
{
CreateRoofCollider( scene, parent, slab, settings );
}
}
private static void CreateThresholdSurface(
Scene scene,
GameObject parent,
RoomLayoutSettings settings,
string materialPath,
float textureWorldSize,
IReadOnlyCollection<RoomLayoutThresholdSlab> thresholdSlabs )
{
var gameObject = scene.CreateObject( true );
gameObject.Name = "Combined Threshold Surface";
gameObject.SetParent( parent, false );
var meshComponent = gameObject.Components.Create<MeshComponent>( false );
meshComponent.Mesh = BuildThresholdSurfaceMesh( thresholdSlabs, textureWorldSize, MaterialFor( settings, RoomLayoutSurface.Threshold, materialPath ) );
meshComponent.Color = Color.White;
meshComponent.SmoothingAngle = 0.0f;
meshComponent.Enabled = true;
meshComponent.RebuildMesh();
}
private static void CreateFloorSurface(
Scene scene,
GameObject parent,
RoomLayoutSettings settings,
string materialPath,
float textureWorldSize,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> surfaceCells,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> allSurfaceCells )
{
var gameObject = scene.CreateObject( true );
gameObject.Name = "Combined Floor Surface";
gameObject.SetParent( parent, false );
var meshComponent = gameObject.Components.Create<MeshComponent>( false );
meshComponent.Mesh = BuildFloorSurfaceMesh( surfaceCells, allSurfaceCells, textureWorldSize, settings.FloorThickness, MaterialFor( settings, RoomLayoutSurface.Floor, materialPath ) );
meshComponent.Color = Color.White;
meshComponent.SmoothingAngle = 0.0f;
meshComponent.Enabled = true;
meshComponent.RebuildMesh();
}
private static void CreateRoofSurface(
Scene scene,
GameObject parent,
RoomLayoutSettings settings,
string materialPath,
float textureWorldSize,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> surfaceCells,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> allSurfaceCells )
{
var gameObject = scene.CreateObject( true );
gameObject.Name = "Combined Roof Surface";
gameObject.SetParent( parent, false );
var meshComponent = gameObject.Components.Create<MeshComponent>( false );
meshComponent.Mesh = BuildRoofSurfaceMesh(
surfaceCells,
allSurfaceCells,
textureWorldSize,
RoofBottomZ( settings ),
settings.RoofThickness,
MaterialFor( settings, RoomLayoutSurface.Roof, materialPath ) );
meshComponent.Color = Color.White;
meshComponent.SmoothingAngle = 0.0f;
meshComponent.Enabled = true;
meshComponent.RebuildMesh();
}
private static PolygonMesh BuildFloorSurfaceMesh(
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> surfaceCells,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> allSurfaceCells,
float textureWorldSize,
float floorThickness,
Material material )
{
var mesh = new PolygonMesh();
var vertices = new Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle>();
var textureSize = MathF.Max( 1.0f, textureWorldSize );
var bottomZ = -MathF.Max( 0.5f, floorThickness );
foreach ( var cell in surfaceCells )
{
var rect = cell.Rect;
AddFloorTopFace(
mesh,
vertices,
material,
textureSize,
new Vector2( rect.X, rect.Y ),
new Vector2( rect.X + rect.Width, rect.Y + rect.Height ) );
AddFloorBottomFace(
mesh,
vertices,
material,
textureSize,
new Vector2( rect.X, rect.Y ),
new Vector2( rect.X + rect.Width, rect.Y + rect.Height ),
bottomZ );
AddFloorSideFaces(
mesh,
vertices,
material,
textureSize,
rect,
bottomZ,
0.0f,
allSurfaceCells );
}
mesh.ComputeFaceTextureParametersFromCoordinates();
mesh.SetSmoothingAngle( 0.0f );
return mesh;
}
private static PolygonMesh BuildRoofSurfaceMesh(
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> surfaceCells,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> allSurfaceCells,
float textureWorldSize,
float roofBottomZ,
float roofThickness,
Material material )
{
var mesh = new PolygonMesh();
var vertices = new Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle>();
var textureSize = MathF.Max( 1.0f, textureWorldSize );
var bottomZ = MathF.Max( 0.0f, roofBottomZ );
var topZ = bottomZ + MathF.Max( 0.5f, roofThickness );
foreach ( var cell in surfaceCells )
{
var rect = cell.Rect;
AddFloorBottomFace(
mesh,
vertices,
material,
textureSize,
new Vector2( rect.X, rect.Y ),
new Vector2( rect.X + rect.Width, rect.Y + rect.Height ),
bottomZ );
AddFloorTopFace(
mesh,
vertices,
material,
textureSize,
new Vector2( rect.X, rect.Y ),
new Vector2( rect.X + rect.Width, rect.Y + rect.Height ),
topZ );
AddFloorSideFaces(
mesh,
vertices,
material,
textureSize,
rect,
bottomZ,
topZ,
allSurfaceCells );
}
mesh.ComputeFaceTextureParametersFromCoordinates();
mesh.SetSmoothingAngle( 0.0f );
return mesh;
}
private static PolygonMesh BuildThresholdSurfaceMesh(
IReadOnlyCollection<RoomLayoutThresholdSlab> thresholdSlabs,
float textureWorldSize,
Material material )
{
var mesh = new PolygonMesh();
var vertices = new Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle>();
var textureSize = MathF.Max( 1.0f, textureWorldSize );
foreach ( var slab in thresholdSlabs )
{
var rect = slab.Rect;
AddFloorTopFace(
mesh,
vertices,
material,
textureSize,
new Vector2( rect.X, rect.Y ),
new Vector2( rect.X + rect.Width, rect.Y + rect.Height ),
ThresholdSurfaceOffset );
}
mesh.ComputeFaceTextureParametersFromCoordinates();
mesh.SetSmoothingAngle( 0.0f );
return mesh;
}
private static void AddFloorTopFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
Vector2 min,
Vector2 max,
float z = 0.0f )
{
var faceVertices = new[]
{
FloorVertex( mesh, vertices, min.x, min.y, z ),
FloorVertex( mesh, vertices, max.x, min.y, z ),
FloorVertex( mesh, vertices, max.x, max.y, z ),
FloorVertex( mesh, vertices, min.x, max.y, z )
};
var face = mesh.AddFace( faceVertices );
mesh.SetFaceMaterial( face, material );
mesh.SetFaceTextureCoords( face, new[]
{
new Vector2( min.x, min.y ) / textureWorldSize,
new Vector2( max.x, min.y ) / textureWorldSize,
new Vector2( max.x, max.y ) / textureWorldSize,
new Vector2( min.x, max.y ) / textureWorldSize
} );
}
private static void AddFloorBottomFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
Vector2 min,
Vector2 max,
float z )
{
var faceVertices = new[]
{
FloorVertex( mesh, vertices, min.x, min.y, z ),
FloorVertex( mesh, vertices, min.x, max.y, z ),
FloorVertex( mesh, vertices, max.x, max.y, z ),
FloorVertex( mesh, vertices, max.x, min.y, z )
};
var face = mesh.AddFace( faceVertices );
mesh.SetFaceMaterial( face, material );
mesh.SetFaceTextureCoords( face, new[]
{
new Vector2( min.x, min.y ) / textureWorldSize,
new Vector2( min.x, max.y ) / textureWorldSize,
new Vector2( max.x, max.y ) / textureWorldSize,
new Vector2( max.x, min.y ) / textureWorldSize
} );
}
private static void AddFloorSideFaces(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
RoomLayoutRect rect,
float bottomZ,
float topZ,
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> allSurfaceCells )
{
var min = new Vector2( rect.X, rect.Y );
var max = new Vector2( rect.X + rect.Width, rect.Y + rect.Height );
if ( !HasFloorNeighbor( allSurfaceCells, rect, -FloorSideProbeOffset, 0.0f ) )
{
AddFloorWestFace( mesh, vertices, material, textureWorldSize, min, max, bottomZ, topZ );
}
if ( !HasFloorNeighbor( allSurfaceCells, rect, FloorSideProbeOffset, 0.0f ) )
{
AddFloorEastFace( mesh, vertices, material, textureWorldSize, min, max, bottomZ, topZ );
}
if ( !HasFloorNeighbor( allSurfaceCells, rect, 0.0f, -FloorSideProbeOffset ) )
{
AddFloorSouthFace( mesh, vertices, material, textureWorldSize, min, max, bottomZ, topZ );
}
if ( !HasFloorNeighbor( allSurfaceCells, rect, 0.0f, FloorSideProbeOffset ) )
{
AddFloorNorthFace( mesh, vertices, material, textureWorldSize, min, max, bottomZ, topZ );
}
}
private static bool HasFloorNeighbor(
IReadOnlyCollection<RoomLayoutFloorSurfaceCell> allSurfaceCells,
RoomLayoutRect rect,
float offsetX,
float offsetY )
{
var probe = new Vector2( rect.Center.x + offsetX, rect.Center.y + offsetY );
if ( offsetX < 0.0f )
{
probe.x = rect.X + offsetX;
}
else if ( offsetX > 0.0f )
{
probe.x = rect.X + rect.Width + offsetX;
}
if ( offsetY < 0.0f )
{
probe.y = rect.Y + offsetY;
}
else if ( offsetY > 0.0f )
{
probe.y = rect.Y + rect.Height + offsetY;
}
return TryGetFloorCell( allSurfaceCells, probe, out _ );
}
private static void AddFloorWestFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
Vector2 min,
Vector2 max,
float zMin,
float zMax )
{
AddFloorVerticalFace( mesh, vertices, material, textureWorldSize, new[]
{
FloorVertex( mesh, vertices, min.x, min.y, zMin ),
FloorVertex( mesh, vertices, min.x, min.y, zMax ),
FloorVertex( mesh, vertices, min.x, max.y, zMax ),
FloorVertex( mesh, vertices, min.x, max.y, zMin )
}, BoxUvPlane.YZ );
}
private static void AddFloorEastFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
Vector2 min,
Vector2 max,
float zMin,
float zMax )
{
AddFloorVerticalFace( mesh, vertices, material, textureWorldSize, new[]
{
FloorVertex( mesh, vertices, max.x, min.y, zMin ),
FloorVertex( mesh, vertices, max.x, max.y, zMin ),
FloorVertex( mesh, vertices, max.x, max.y, zMax ),
FloorVertex( mesh, vertices, max.x, min.y, zMax )
}, BoxUvPlane.YZ );
}
private static void AddFloorSouthFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
Vector2 min,
Vector2 max,
float zMin,
float zMax )
{
AddFloorVerticalFace( mesh, vertices, material, textureWorldSize, new[]
{
FloorVertex( mesh, vertices, min.x, min.y, zMin ),
FloorVertex( mesh, vertices, max.x, min.y, zMin ),
FloorVertex( mesh, vertices, max.x, min.y, zMax ),
FloorVertex( mesh, vertices, min.x, min.y, zMax )
}, BoxUvPlane.XZ );
}
private static void AddFloorNorthFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
Vector2 min,
Vector2 max,
float zMin,
float zMax )
{
AddFloorVerticalFace( mesh, vertices, material, textureWorldSize, new[]
{
FloorVertex( mesh, vertices, min.x, max.y, zMin ),
FloorVertex( mesh, vertices, min.x, max.y, zMax ),
FloorVertex( mesh, vertices, max.x, max.y, zMax ),
FloorVertex( mesh, vertices, max.x, max.y, zMin )
}, BoxUvPlane.XZ );
}
private static void AddFloorVerticalFace(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
Material material,
float textureWorldSize,
HalfEdgeMesh.VertexHandle[] faceVertices,
BoxUvPlane uvPlane )
{
var face = mesh.AddFace( faceVertices );
mesh.SetFaceMaterial( face, material );
var uvs = new Vector2[faceVertices.Length];
for ( var i = 0; i < faceVertices.Length; i++ )
{
uvs[i] = ProjectBoxUv( mesh.GetVertexPosition( faceVertices[i] ), textureWorldSize, uvPlane );
}
mesh.SetFaceTextureCoords( face, uvs );
}
private static HalfEdgeMesh.VertexHandle FloorVertex(
PolygonMesh mesh,
Dictionary<FloorVertexKey, HalfEdgeMesh.VertexHandle> vertices,
float x,
float y,
float z )
{
var key = new FloorVertexKey( QuantizeCoordinate( x ), QuantizeCoordinate( y ), QuantizeCoordinate( z ) );
if ( vertices.TryGetValue( key, out var vertex ) )
{
return vertex;
}
vertex = mesh.AddVertex( new Vector3( x, y, z ) );
vertices[key] = vertex;
return vertex;
}
private static IReadOnlyList<RoomLayoutFloorSurfaceCell> BuildFloorSurfaceCells( IReadOnlyCollection<RoomLayoutFloorSlab> floorSlabs, bool preferFirstSlab = false )
{
var xs = new List<float>();
var ys = new List<float>();
foreach ( var slab in floorSlabs )
{
AddUniqueCoordinate( xs, slab.Rect.X );
AddUniqueCoordinate( xs, slab.Rect.X + slab.Rect.Width );
AddUniqueCoordinate( ys, slab.Rect.Y );
AddUniqueCoordinate( ys, slab.Rect.Y + slab.Rect.Height );
}
xs.Sort();
ys.Sort();
var cells = new List<RoomLayoutFloorSurfaceCell>();
for ( var x = 0; x < xs.Count - 1; x++ )
{
for ( var y = 0; y < ys.Count - 1; y++ )
{
var min = new Vector2( xs[x], ys[y] );
var max = new Vector2( xs[x + 1], ys[y + 1] );
if ( !TryGetFloorCellSlab( floorSlabs, (min + max) * 0.5f, preferFirstSlab, out var slab ) )
{
continue;
}
cells.Add( new RoomLayoutFloorSurfaceCell(
new RoomLayoutRect( min.x, min.y, max.x - min.x, max.y - min.y ),
slab.MaterialPath ?? "",
slab.TextureWorldSize ) );
}
}
return cells;
}
private static bool TryGetFloorCellSlab( IReadOnlyCollection<RoomLayoutFloorSlab> floorSlabs, Vector2 point, bool preferFirstSlab, out RoomLayoutFloorSlab coveringSlab )
{
var found = false;
coveringSlab = default;
foreach ( var slab in floorSlabs )
{
var rect = slab.Rect;
if ( point.x >= rect.X && point.x <= rect.X + rect.Width &&
point.y >= rect.Y && point.y <= rect.Y + rect.Height )
{
coveringSlab = slab;
found = true;
if ( preferFirstSlab )
{
return true;
}
}
}
return found;
}
private static bool TryGetFloorCell( IReadOnlyCollection<RoomLayoutFloorSurfaceCell> surfaceCells, Vector2 point, out RoomLayoutFloorSurfaceCell coveringCell )
{
foreach ( var cell in surfaceCells )
{
var rect = cell.Rect;
if ( point.x >= rect.X && point.x <= rect.X + rect.Width &&
point.y >= rect.Y && point.y <= rect.Y + rect.Height )
{
coveringCell = cell;
return true;
}
}
coveringCell = default;
return false;
}
private static void AddFloorSlab( ICollection<RoomLayoutFloorSlab> floorSlabs, string name, RoomLayoutRect rect, string materialPath, float textureWorldSize )
{
if ( rect.Width < 1.0f || rect.Height < 1.0f )
{
return;
}
floorSlabs.Add( new RoomLayoutFloorSlab( name, rect, materialPath ?? "", textureWorldSize ) );
}
private static void AddStructuralFloorSlab(
ICollection<RoomLayoutFloorSlab> floorSlabs,
string name,
RoomLayoutRect rect,
RoomLayoutSettings settings,
string materialPath,
float textureWorldSize )
{
AddFloorSlab( floorSlabs, name, StructuralFootprintRect( rect, settings ), materialPath, textureWorldSize );
}
private static void AddRoofSlab(
ICollection<RoomLayoutFloorSlab> roofSlabs,
string name,
RoomLayoutRect rect,
string materialPath,
float textureWorldSize )
{
AddFloorSlab( roofSlabs, name, rect, materialPath, textureWorldSize );
}
private static void AddStructuralRoofSlab(
ICollection<RoomLayoutFloorSlab> roofSlabs,
string name,
RoomLayoutRect rect,
RoomLayoutSettings settings,
string materialPath,
float textureWorldSize )
{
AddFloorSlab( roofSlabs, name, StructuralFootprintRect( rect, settings ), materialPath, textureWorldSize );
}
private static IReadOnlyList<RoomLayoutFloorSlab> ClipFloorSlabs(
IReadOnlyCollection<RoomLayoutFloorSlab> floorSlabs,
IReadOnlyCollection<RoomLayoutRect> cutouts )
{
if ( floorSlabs.Count == 0 || cutouts.Count == 0 )
{
return floorSlabs.ToArray();
}
var current = floorSlabs.ToList();
foreach ( var cutout in cutouts.Where( cutout => cutout.Width >= 1.0f && cutout.Height >= 1.0f ) )
{
var next = new List<RoomLayoutFloorSlab>();
foreach ( var slab in current )
{
next.AddRange( SubtractFloorCutout( slab, cutout ) );
}
current = next;
}
return current;
}
private static IReadOnlyList<RoomLayoutThresholdSlab> ClipThresholdSlabs(
IReadOnlyCollection<RoomLayoutThresholdSlab> thresholdSlabs,
IReadOnlyCollection<RoomLayoutRect> cutouts )
{
if ( thresholdSlabs.Count == 0 || cutouts.Count == 0 )
{
return thresholdSlabs.ToArray();
}
var current = thresholdSlabs.ToList();
foreach ( var cutout in cutouts.Where( cutout => cutout.Width >= 1.0f && cutout.Height >= 1.0f ) )
{
var next = new List<RoomLayoutThresholdSlab>();
foreach ( var slab in current )
{
next.AddRange( SubtractThresholdCutout( slab, cutout ) );
}
current = next;
}
return current;
}
private static IReadOnlyList<RoomLayoutFloorSlab> SubtractFloorCutout( RoomLayoutFloorSlab slab, RoomLayoutRect cutout )
{
if ( !RectsOverlap( slab.Rect, cutout ) )
{
return new[] { slab };
}
var fragments = new List<RoomLayoutFloorSlab>();
foreach ( var fragment in SubtractRect( slab.Rect, cutout ) )
{
fragments.Add( new RoomLayoutFloorSlab(
$"{slab.Name} Cut {fragments.Count:00}",
fragment,
slab.MaterialPath,
slab.TextureWorldSize ) );
}
return fragments;
}
private static IReadOnlyList<RoomLayoutThresholdSlab> SubtractThresholdCutout( RoomLayoutThresholdSlab slab, RoomLayoutRect cutout )
{
if ( !RectsOverlap( slab.Rect, cutout ) )
{
return new[] { slab };
}
var fragments = new List<RoomLayoutThresholdSlab>();
foreach ( var fragment in SubtractRect( slab.Rect, cutout ) )
{
fragments.Add( new RoomLayoutThresholdSlab(
$"{slab.Name} Cut {fragments.Count:00}",
fragment,
slab.MaterialPath,
slab.TextureWorldSize ) );
}
return fragments;
}
private static bool RectsOverlap( RoomLayoutRect a, RoomLayoutRect b )
{
return a.X < b.X + b.Width &&
a.X + a.Width > b.X &&
a.Y < b.Y + b.Height &&
a.Y + a.Height > b.Y;
}
private static IReadOnlyList<RoomLayoutRect> SubtractRect( RoomLayoutRect rect, RoomLayoutRect cutout )
{
var rectMaxX = rect.X + rect.Width;
var rectMaxY = rect.Y + rect.Height;
var cutoutMaxX = cutout.X + cutout.Width;
var cutoutMaxY = cutout.Y + cutout.Height;
var minX = MathF.Max( rect.X, cutout.X );
var minY = MathF.Max( rect.Y, cutout.Y );
var maxX = MathF.Min( rectMaxX, cutoutMaxX );
var maxY = MathF.Min( rectMaxY, cutoutMaxY );
if ( maxX <= minX || maxY <= minY )
{
return new[] { rect };
}
var fragments = new List<RoomLayoutRect>();
AddRectFragment( fragments, new RoomLayoutRect( rect.X, rect.Y, rect.Width, minY - rect.Y ) );
AddRectFragment( fragments, new RoomLayoutRect( rect.X, maxY, rect.Width, rectMaxY - maxY ) );
AddRectFragment( fragments, new RoomLayoutRect( rect.X, minY, minX - rect.X, maxY - minY ) );
AddRectFragment( fragments, new RoomLayoutRect( maxX, minY, rectMaxX - maxX, maxY - minY ) );
return fragments;
}
private static void AddRectFragment( ICollection<RoomLayoutRect> fragments, RoomLayoutRect rect )
{
if ( rect.Width >= 1.0f && rect.Height >= 1.0f )
{
fragments.Add( rect );
}
}
private static IReadOnlyList<RoomLayoutFloorSlab> ExposedRoofSlabs(
IReadOnlyCollection<RoomLayoutFloorSlab> roofSlabs,
IReadOnlyCollection<RoomLayoutRect> coverageRects )
{
if ( roofSlabs.Count == 0 || coverageRects.Count == 0 )
{
return roofSlabs.ToArray();
}
var xs = new List<float>();
var ys = new List<float>();
foreach ( var slab in roofSlabs )
{
AddUniqueCoordinate( xs, slab.Rect.X );
AddUniqueCoordinate( xs, slab.Rect.X + slab.Rect.Width );
AddUniqueCoordinate( ys, slab.Rect.Y );
AddUniqueCoordinate( ys, slab.Rect.Y + slab.Rect.Height );
}
foreach ( var rect in coverageRects )
{
AddUniqueCoordinate( xs, rect.X );
AddUniqueCoordinate( xs, rect.X + rect.Width );
AddUniqueCoordinate( ys, rect.Y );
AddUniqueCoordinate( ys, rect.Y + rect.Height );
}
xs.Sort();
ys.Sort();
var exposed = new List<RoomLayoutFloorSlab>();
var index = 0;
for ( var x = 0; x < xs.Count - 1; x++ )
{
for ( var y = 0; y < ys.Count - 1; y++ )
{
var min = new Vector2( xs[x], ys[y] );
var max = new Vector2( xs[x + 1], ys[y + 1] );
var center = (min + max) * 0.5f;
if ( IsCoveredByRect( coverageRects, center ) ||
!TryGetFloorCellSlab( roofSlabs, center, preferFirstSlab: false, out var slab ) )
{
continue;
}
exposed.Add( new RoomLayoutFloorSlab(
$"{slab.Name} {index:00}",
new RoomLayoutRect( min.x, min.y, max.x - min.x, max.y - min.y ),
slab.MaterialPath,
slab.TextureWorldSize ) );
index++;
}
}
return exposed;
}
private static IReadOnlyCollection<RoomLayoutRect> HigherFloorCoverageRects( RoomLayoutDocument document, int floor )
{
var coverage = new List<RoomLayoutFloorSlab>();
foreach ( var room in document.Rooms.Where( room => document.FloorFor( room ) > floor ) )
{
AddStructuralFloorSlab( coverage, "", room.Bounds, document.Settings, "", 0.0f );
}
foreach ( var corridor in document.Corridors.Where( corridor => document.FloorFor( corridor ) > floor && document.CorridorDoorsAreOnFloor( corridor ) ) )
{
AddCorridorCoverageSlabs( document, corridor, coverage );
}
return coverage.Select( slab => slab.Rect ).ToArray();
}
private static void AddCorridorCoverageSlabs(
RoomLayoutDocument document,
RoomLayoutCorridor corridor,
ICollection<RoomLayoutFloorSlab> floorSlabs )
{
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 roofSlabs = new List<RoomLayoutFloorSlab>();
var thresholdSlabs = new List<RoomLayoutThresholdSlab>();
var footprintRects = new List<RoomLayoutRect>();
BuildDoorThreshold(
floorSlabs,
roofSlabs,
thresholdSlabs,
settings,
corridor.Id,
"Start",
start,
width,
"",
0.0f,
"",
0.0f,
"",
0.0f );
BuildDoorThreshold(
floorSlabs,
roofSlabs,
thresholdSlabs,
settings,
corridor.Id,
"End",
end,
width,
"",
0.0f,
"",
0.0f,
"",
0.0f );
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,
"",
0.0f,
"",
0.0f );
}
BuildCorridorBendFloors( floorSlabs, roofSlabs, settings, footprintRects, corridor.Id, points, width, "", 0.0f, "", 0.0f );
BuildCorridorOpeningFloors( floorSlabs, roofSlabs, settings, document, corridor, points, width, "", 0.0f, "", 0.0f );
}
private static bool IsCoveredByRect( IReadOnlyCollection<RoomLayoutRect> rects, Vector2 point )
{
foreach ( var rect in rects )
{
if ( point.x >= rect.X && point.x <= rect.X + rect.Width &&
point.y >= rect.Y && point.y <= rect.Y + rect.Height )
{
return true;
}
}
return false;
}
private static RoomLayoutRect StructuralFootprintRect( RoomLayoutRect rect, RoomLayoutSettings settings )
{
var overlap = MathF.Max( 0.0f, settings.WallThickness );
return new RoomLayoutRect( rect.X - overlap, rect.Y - overlap, rect.Width + overlap * 2.0f, rect.Height + overlap * 2.0f );
}
private static void AddThresholdSlab( ICollection<RoomLayoutThresholdSlab> thresholdSlabs, string name, RoomLayoutRect rect, string materialPath, float textureWorldSize )
{
if ( rect.Width < 1.0f || rect.Height < 1.0f )
{
return;
}
thresholdSlabs.Add( new RoomLayoutThresholdSlab( name, rect, materialPath ?? "", textureWorldSize ) );
}
private static void CreateFloorCollider( Scene scene, GameObject parent, RoomLayoutFloorSlab slab, float floorThickness )
{
var colliderObject = scene.CreateObject( true );
colliderObject.Name = $"{slab.Name} Collider";
colliderObject.SetParent( parent, false );
colliderObject.LocalPosition = new Vector3( slab.Rect.Center.x, slab.Rect.Center.y, -floorThickness * 0.5f );
var collider = colliderObject.Components.Create<BoxCollider>();
collider.Scale = new Vector3( slab.Rect.Width, slab.Rect.Height, floorThickness );
collider.Static = true;
}
private static void CreateRoofCollider( Scene scene, GameObject parent, RoomLayoutFloorSlab slab, RoomLayoutSettings settings )
{
var roofThickness = MathF.Max( 0.5f, settings.RoofThickness );
var bottomZ = RoofBottomZ( settings );
var colliderObject = scene.CreateObject( true );
colliderObject.Name = $"{slab.Name} Collider";
colliderObject.SetParent( parent, false );
colliderObject.LocalPosition = new Vector3( slab.Rect.Center.x, slab.Rect.Center.y, bottomZ + roofThickness * 0.5f );
var collider = colliderObject.Components.Create<BoxCollider>();
collider.Scale = new Vector3( slab.Rect.Width, slab.Rect.Height, roofThickness );
collider.Static = true;
}
private static float RoofBottomZ( RoomLayoutSettings settings )
{
return MathF.Max( 0.0f, settings.WallHeight ) + MathF.Max( 0.0f, settings.WallCapHeight );
}
private readonly record struct RoomLayoutFloorSlab( string Name, RoomLayoutRect Rect, string MaterialPath, float TextureWorldSize );
private readonly record struct RoomLayoutFloorSurfaceCell( RoomLayoutRect Rect, string MaterialPath, float TextureWorldSize );
private readonly record struct RoomLayoutThresholdSlab( string Name, RoomLayoutRect Rect, string MaterialPath, float TextureWorldSize );
private readonly record struct MaterialSurfaceKey( string MaterialPath, float TextureWorldSize );
private readonly record struct FloorVertexKey( int X, int Y, int Z );
}