Procedural wall generator for the editor's Advanced Mapping Tools package. Splits a straight wall into panels (piers, sill and header sections around each opening), builds each panel as a box or a mitred prism, and wraps it in a generator that also exposes WallPartsSettings for length, height, thickness, opening count and sizes.
namespace AdvancedMappingTools;
public readonly record struct WallOpening( float Start, float End, float Sill, float Head );
public sealed record WallRequest(
string Id, string Name, float Length, float Height, float Thickness,
IReadOnlyList<WallOpening> Openings, float Variation, Material Material,
bool MiterStart = false, bool MiterEnd = false );
/// <summary>
/// A straight wall as separate panels: a pier between openings, one panel below
/// and one above each opening. Runs along +X with its middle on the X axis.
/// </summary>
public static class WallGenerator
{
public const string Category = "wall";
public static PartPlan Generate( WallRequest request, GenContext context )
{
var group = context.Group( request.Id, request.Name, "Wall", Category );
var cursor = 0.0f;
var index = 0;
void Panel( string name, string label, float x0, float x1, float z0, float z1 )
{
if ( x1 - x0 < 0.01f || z1 - z0 < 0.01f ) return;
var id = $"{request.Id}/{name}";
var stream = context.Stream( Category, id );
var miterStart = request.MiterStart && x0 <= 0.01f;
var miterEnd = request.MiterEnd && x1 >= request.Length - 0.01f;
// Two mitres only meet when both walls are as thick as planned.
var half = request.Thickness * 0.5f + (miterStart || miterEnd ? 0.0f : request.Variation * stream.Range( -0.25f, 0.25f ));
group.Add( context.Solid( id, label, "WallPanel", Category, request.Material, kit =>
{
if ( !miterStart && !miterEnd )
{
kit.Box( new Vector3( x0, -half, z0 ), new Vector3( x1, half, z1 ) );
return;
}
// A mitre: the outer face runs to the end, the inner face stops a wall thickness short.
var knee = MathF.Min( half * 2.0f, (x1 - x0) * (miterStart && miterEnd ? 0.5f : 1.0f) );
var shape = new List<Vector3>
{
new( x0, -half, 0.0f ),
new( x1, -half, 0.0f ),
new( miterEnd ? x1 - knee : x1, half, 0.0f ),
new( miterStart ? x0 + knee : x0, half, 0.0f )
};
kit.Prism( shape, z0, z1 );
} ) );
}
foreach ( var opening in request.Openings.OrderBy( x => x.Start ) )
{
Panel( $"pier{index}", $"Pier {index + 1}", cursor, opening.Start, 0.0f, request.Height );
Panel( $"below{index}", $"Below Opening {index + 1}", opening.Start, opening.End, 0.0f, opening.Sill );
Panel( $"above{index}", $"Above Opening {index + 1}", opening.Start, opening.End, opening.Head, request.Height );
cursor = opening.End;
index++;
}
Panel( $"pier{index}", $"Pier {index + 1}", cursor, request.Length, 0.0f, request.Height );
return group;
}
}
public sealed class WallPartsSettings : GeneratorSettings
{
public float Length { get; set; } = 256.0f;
[Property, Group( "Wall" ), Range( 8, 4096, slider: false ), WideMode]
public float Height { get; set; } = 128.0f;
[Property, Group( "Wall" ), Range( 0.5f, 256, slider: false ), WideMode]
public float Thickness { get; set; } = 8.0f;
[Property, Group( "Wall" ), Range( 0, 32, slider: false ), WideMode]
public int Openings { get; set; } = 1;
[Property, HideIf( nameof( Openings ), 0 ), Group( "Wall" ), Title( "Opening Width" ), Range( 4, 1024, slider: false ), WideMode]
public float OpeningWidth { get; set; } = 48.0f;
[Property, HideIf( nameof( Openings ), 0 ), Group( "Wall" ), Title( "Sill Height" ), Range( 0, 1024, slider: false ), WideMode]
public float SillHeight { get; set; } = 36.0f;
[Property, HideIf( nameof( Openings ), 0 ), Group( "Wall" ), Title( "Opening Height" ), Range( 4, 1024, slider: false ), WideMode]
public float OpeningHeight { get; set; } = 56.0f;
[Property, Group( "Materials" ), ResourceType( "vmat" ), WideMode]
public string Material { get; set; }
}
public sealed class WallPartsGenerator : IPartGenerator
{
public string Id => "wall";
public string Title => "Wall";
public Type SettingsType => typeof( WallPartsSettings );
public PartPlan Generate( GeneratorSettings settings, GenContext context )
{
var s = (WallPartsSettings)settings;
var root = context.Group( "root", "Wall", "Building", "wall" );
var openings = new List<WallOpening>();
for ( var i = 0; i < s.Openings; i++ )
{
var center = s.Length * (i + 0.5f) / s.Openings;
openings.Add( new WallOpening( center - s.OpeningWidth * 0.5f, center + s.OpeningWidth * 0.5f, s.SillHeight, s.SillHeight + s.OpeningHeight ) );
}
root.Add( WallGenerator.Generate( new WallRequest( "wall", "Wall", s.Length, s.Height, s.Thickness, openings, s.Variation, context.Material( s.Material ) ), context ) );
return root;
}
}