Editor/Floor/ArchFloorGen.Slab.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Sandbox;
namespace Sunless.Architecture;
public static partial class ArchFloorGen {
public static List<Vector2> Footprint( ArchRoom room ) {
if ( room.HasFootprint ) {
return ArchFootprint.Wind( room.Footprint.ToList() );
}
return ArchFootprint.Wind( FromWalls( room ) );
}
static List<Vector2> FromWalls( ArchRoom room ) {
var points = new List<Vector2>();
foreach ( var wall in room.Walls ) {
if ( points.Count == 0 || (points[^1] - wall.Start).Length > 0.5f ) {
points.Add( wall.Start );
}
points.Add( wall.End );
}
if ( points.Count >= 2 && (points[0] - points[^1]).Length < 0.5f ) {
points.RemoveAt( points.Count - 1 );
}
return points;
}
public static bool Contains( IReadOnlyList<Vector2> polygon, Vector2 point ) => ArchFootprint.Contains( polygon, point );
public static void Slab(
ArchMesh canvas,
IReadOnlyList<Vector2> footprint,
IReadOnlyList<ArchFloorCutout> cutouts,
float bottom,
float top,
ArchBrush brush,
IReadOnlyList<ArchCarveVolume> recesses = null ) {
if ( footprint.Count < 3 || top - bottom < 0.05f ) {
return;
}
var shape = SlabShape( footprint, cutouts, bottom, top, recesses );
using var welding = canvas.Welding();
foreach ( var face in shape.Faces ) {
canvas.Polygon( face.Points, brush );
}
}
// A cutout is a hole in PLAN and takes the whole band; a recess carries its own band, so what is left
// over or under it stays standing. Both go through the one carve, or a coffer and a stairwell would
// disagree about what a hole is.
public static ArchCarveShape SlabShape(
IReadOnlyList<Vector2> footprint,
IReadOnlyList<ArchFloorCutout> cutouts,
float bottom,
float top,
IReadOnlyList<ArchCarveVolume> recesses = null ) {
var carve = ArchCarve.Prism( footprint, bottom, top );
foreach ( var cutout in cutouts ?? Array.Empty<ArchFloorCutout>() ) {
carve.Less( ArchCarveVolume.Over( cutout.Outline(), bottom - 1f, top + 1f ).Breaking( cutout.Break ) );
}
foreach ( var recess in recesses ?? Array.Empty<ArchCarveVolume>() ) {
carve.Less( recess );
}
return carve.Resolve();
}
readonly struct Cell {
public Vector2 Min { get; init; }
public Vector2 Max { get; init; }
}
static IEnumerable<Cell> Cells( IReadOnlyList<Vector2> footprint, IReadOnlyList<ArchFloorCutout> cutouts ) {
var xs = new List<float>();
var ys = new List<float>();
foreach ( var point in footprint ) {
xs.Add( point.x );
ys.Add( point.y );
}
foreach ( var cutout in cutouts ) {
xs.Add( cutout.Min.x );
xs.Add( cutout.Max.x );
ys.Add( cutout.Min.y );
ys.Add( cutout.Max.y );
// A turned flight's loop corners become grid lines, or its hole comes out square.
foreach ( var point in cutout.Loop ) {
xs.Add( point.x );
ys.Add( point.y );
}
}
var boundsMinX = footprint.Min( point => point.x );
var boundsMaxX = footprint.Max( point => point.x );
var boundsMinY = footprint.Min( point => point.y );
var boundsMaxY = footprint.Max( point => point.y );
xs = xs.Where( value => value > boundsMinX - 0.01f && value < boundsMaxX + 0.01f ).Distinct().OrderBy( value => value ).ToList();
ys = ys.Where( value => value > boundsMinY - 0.01f && value < boundsMaxY + 0.01f ).Distinct().OrderBy( value => value ).ToList();
var columns = xs.Count - 1;
var rows = ys.Count - 1;
var solid = new bool[columns * rows];
for ( var ix = 0; ix < columns; ix++ ) {
for ( var iy = 0; iy < rows; iy++ ) {
var min = new Vector2( xs[ix], ys[iy] );
var max = new Vector2( xs[ix + 1], ys[iy + 1] );
if ( max.x - min.x < 0.05f || max.y - min.y < 0.05f ) {
continue;
}
var centre = (min + max) * 0.5f;
solid[iy * columns + ix] = ArchFootprint.Covered( new[] { footprint }, cutouts, centre );
}
}
// Merged into the fewest boxes: shared faces are internal coplanar pairs.
for ( var iy = 0; iy < rows; iy++ ) {
for ( var ix = 0; ix < columns; ix++ ) {
if ( !solid[iy * columns + ix] ) {
continue;
}
var width = 1;
while ( ix + width < columns && solid[iy * columns + ix + width] ) {
width++;
}
var height = 1;
while ( iy + height < rows && Spans( solid, columns, ix, width, iy + height ) ) {
height++;
}
for ( var y = iy; y < iy + height; y++ ) {
for ( var x = ix; x < ix + width; x++ ) {
solid[y * columns + x] = false;
}
}
yield return new Cell {
Min = new Vector2( xs[ix], ys[iy] ),
Max = new Vector2( xs[ix + width], ys[iy + height] )
};
}
}
}
static bool Spans( bool[] solid, int columns, int ix, int width, int iy ) {
for ( var x = ix; x < ix + width; x++ ) {
if ( !solid[iy * columns + x] ) {
return false;
}
}
return true;
}
}