Editor/Wall/ArchWallSurfaces.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public static partial class ArchWallGen {
// Lap the field, don't replace it: panel carries material, profile the shadow.
static void Clad( ArchMesh canvas, Panel panel, float half, ArchKit kit, WallCladding cladding, ArchBrush brush ) {
switch ( cladding ) {
case WallCladding.Weatherboard:
Courses( canvas, panel, half, kit, brush );
break;
case WallCladding.Profiled:
Ribs( canvas, panel, half, kit, brush );
break;
}
}
// Ribs index off x = 0, or the sheeting steps at every opening.
static void Ribs( ArchMesh canvas, Panel panel, float half, ArchKit kit, ArchBrush brush ) {
var pitch = MathF.Max( 2f, kit.SheetPitch );
var crest = Math.Clamp( kit.SheetCrest, 0.5f, pitch * 0.6f );
var proud = MathF.Max( 0.1f, kit.SheetProud );
var web = (pitch - crest) * 0.3f;
var valley = pitch - crest - web * 2f;
if ( panel.MaxZ - panel.MinZ < 0.05f || panel.MaxX - panel.MinX < 0.05f ) {
return;
}
var first = MathF.Floor( panel.MinX / pitch );
var last = MathF.Ceiling( panel.MaxX / pitch );
for ( var index = first; index < last; index++ ) {
var at = index * pitch;
var flat = -half;
var high = -half - proud;
Rib( canvas, panel, at, flat, at + valley, flat, brush );
Rib( canvas, panel, at + valley, flat, at + valley + web, high, brush );
Rib( canvas, panel, at + valley + web, high, at + valley + web + crest, high, brush );
Rib( canvas, panel, at + valley + web + crest, high, at + pitch, flat, brush );
}
}
static void Rib( ArchMesh canvas, Panel panel, float fromX, float fromY, float toX, float toY, ArchBrush brush ) {
var minX = MathF.Max( panel.MinX, fromX );
var maxX = MathF.Min( panel.MaxX, toX );
if ( maxX - minX < 0.01f ) {
return;
}
var span = toX - fromX;
float Depth( float x ) => span < 0.001f ? fromY : fromY + (x - fromX) / span * (toY - fromY);
var near = Depth( minX );
var far = Depth( maxX );
canvas.Quad(
new Vector3( minX, near, panel.MinZ ),
new Vector3( maxX, far, panel.MinZ ),
new Vector3( maxX, far, panel.MaxZ ),
new Vector3( minX, near, panel.MaxZ ),
brush );
}
// Courses index off z = 0, or the coursing would step at every opening.
static void Courses( ArchMesh canvas, Panel panel, float half, ArchKit kit, ArchBrush brush ) {
var course = MathF.Max( 2f, kit.SidingCourse );
var proud = MathF.Max( 0.1f, kit.SidingProud );
if ( panel.MaxX - panel.MinX < 0.05f ) {
return;
}
var first = MathF.Floor( panel.MinZ / course );
var last = MathF.Ceiling( panel.MaxZ / course );
for ( var index = first; index < last; index++ ) {
var boardBottom = index * course;
var bottom = MathF.Max( panel.MinZ, boardBottom );
var top = MathF.Min( panel.MaxZ, boardBottom + course );
if ( top - bottom < 0.05f ) {
continue;
}
// The face is a ramp; a clipped course samples it, not the full lap.
var nearProud = proud * (1f - (bottom - boardBottom) / course);
var farProud = proud * (1f - (top - boardBottom) / course);
canvas.Quad(
new Vector3( panel.MinX, -half - nearProud, bottom ),
new Vector3( panel.MaxX, -half - nearProud, bottom ),
new Vector3( panel.MaxX, -half - farProud, top ),
new Vector3( panel.MinX, -half - farProud, top ),
brush );
canvas.Quad(
new Vector3( panel.MinX, -half, bottom ),
new Vector3( panel.MaxX, -half, bottom ),
new Vector3( panel.MaxX, -half - nearProud, bottom ),
new Vector3( panel.MinX, -half - nearProud, bottom ),
brush );
EndCap( canvas, panel.MinX, half, bottom, top, nearProud, farProud, false, brush );
EndCap( canvas, panel.MaxX, half, bottom, top, nearProud, farProud, true, brush );
}
}
// The flush top edge collapses the section; a degenerate quad breaks the normal.
static void EndCap( ArchMesh canvas, float x, float half, float bottom, float top, float nearProud, float farProud, bool flip, ArchBrush brush ) {
var points = new List<Vector3>
{
new( x, -half, bottom ),
new( x, -half - nearProud, bottom ),
new( x, -half - farProud, top ),
new( x, -half, top )
};
for ( var index = points.Count - 1; index > 0; index-- ) {
if ( (points[index] - points[index - 1]).Length < 0.01f ) {
points.RemoveAt( index );
}
}
if ( (points[^1] - points[0]).Length < 0.01f ) {
points.RemoveAt( points.Count - 1 );
}
canvas.Polygon( points, brush, flip );
}
static void Wainscot( ArchMesh canvas, float from, float to, float half, ArchKit kit, IReadOnlyList<ArchOpening> openings, ArchBrush panelling, ArchBrush rail, float finish ) {
var top = kit.WainscotHeight + finish;
var skirt = MathF.Max( 0f, kit.BaseboardHeight ) + finish;
var cap = MathF.Max( 0.5f, kit.ChairRailHeight );
var proud = MathF.Max( 0.2f, kit.WainscotProud );
var member = MathF.Max( 1f, kit.WainscotStile );
var field = top - cap;
if ( field - skirt < member * 2f + 2f ) {
return;
}
var blocked = openings
.Where( opening => opening.SillHeight < field )
.Select( opening => Cased( opening, kit ) )
.OrderBy( gap => gap.Left )
.ToList();
var cursor = from;
foreach ( var gap in blocked ) {
if ( gap.Left - cursor > 6f ) {
Dado( canvas, cursor, gap.Left, half, skirt, field, top, proud, member, kit.WainscotBay, kit.ChairRailDepth, panelling, rail );
}
cursor = MathF.Max( cursor, gap.Right );
}
if ( to - cursor > 6f ) {
Dado( canvas, cursor, to, half, skirt, field, top, proud, member, kit.WainscotBay, kit.ChairRailDepth, panelling, rail );
}
}
static void Dado(
ArchMesh canvas,
float from,
float to,
float half,
float skirt,
float field,
float top,
float proud,
float member,
float bay,
float capDepth,
ArchBrush panelling,
ArchBrush rail ) {
var recess = proud * 0.4f;
canvas.Box( new Vector3( from, half, skirt ), new Vector3( to, half + recess, field ), panelling, BoxFaces.All & ~BoxFaces.Front );
canvas.Box( new Vector3( from, half, skirt ), new Vector3( to, half + proud, skirt + member ), panelling );
canvas.Box( new Vector3( from, half, field - member ), new Vector3( to, half + proud, field ), panelling );
foreach ( var offset in ArchDivide.AtMost( to - from, MathF.Max( 8f, bay ) ).Nodes ) {
var left = Math.Clamp( from + offset - member * 0.5f, from, to - member );
canvas.Box( new Vector3( left, half, skirt ), new Vector3( left + member, half + proud, field ), panelling );
}
canvas.Box( new Vector3( from, half, field ), new Vector3( to, half + capDepth, top ), rail );
}
}