Editor/Wall/ArchWallExtrude.cs
using System;
using System.Collections.Generic;
using Sandbox;
namespace Sunless.Architecture;
// One stretch of a wall that an extrude zone covers, in the wall's own stations. The ONE resolve behind every
// answer a zone gives: the wall generator dresses these, the pier affector stands a column on them, and the MCP
// report counts them - so a preview, a build and a report can never disagree about where a zone reaches.
public readonly record struct ArchExtrudeStretch( ArchCutPart Zone, float From, float To, float Bottom, float Top ) {
public float Along => (From + To) * 0.5f;
public float Width => To - From;
public float Rise => Top - Bottom;
}
// The proud work an extrude zone stands on a wall, expressed as ordinary wall modifiers so the generator emits
// them with the same pass it emits an authored quoin with. The zone is the AUTHORING: dragging a box over a
// corner, over the middle of an elevation or over its head is what a station and a width used to have to say.
public static class ArchWallExtrude {
// How near a wall's own end a zone has to reach before it counts as covering the corner. An inch, because the
// zone was dragged on the grid and the wall's end is on it too.
const float Reaching = 1f;
public static IEnumerable<ArchExtrudeStretch> Stretches(
ArchPlan plan,
ArchKit kit,
ArchBuilding building,
ArchRoom room,
ArchWall wall,
float edgeFrom,
float edgeTo,
float height ) {
foreach ( var (zone, volume) in ArchCut.Extrusions( plan, room.Floor, kit, room.BaseHeight, room.BaseHeight + height, building?.Id ?? 0, ArchCutAffects.Walls, wall.Id ) ) {
foreach ( var span in ArchFootprint.Inside( volume.Footprint, wall.Start, wall.End ) ) {
// A zone reaching a wall's own end runs out to the end the JOIN emits, so the blocks off two
// elevations meet in the corner and read as the single pier they are - which is the whole reason
// to drag one there.
var entered = span.From * wall.Length;
var left = span.To * wall.Length;
var from = entered <= Reaching ? edgeFrom : entered;
var to = left >= wall.Length - Reaching ? edgeTo : left;
if ( to - from < 0.5f ) {
continue;
}
var sample = wall.PointAt( (from + to) * 0.5f );
var bottom = Math.Clamp( volume.Floor.At( sample ) - room.BaseHeight, 0f, height );
var top = Math.Clamp( volume.Ceiling.At( sample ) - room.BaseHeight, 0f, height );
if ( top - bottom < 0.5f ) {
continue;
}
yield return new ArchExtrudeStretch( zone, from, to, bottom, top );
}
}
}
public static List<ArchWallModShape> Zones(
ArchPlan plan,
ArchKit kit,
ArchBuilding building,
ArchRoom room,
ArchWall wall,
float edgeFrom,
float edgeTo,
float height,
float thickness,
IReadOnlyList<ArchOpening> openings ) {
var stood = new List<ArchWallModShape>();
foreach ( var stretch in Stretches( plan, kit, building, room, wall, edgeFrom, edgeTo, height ) ) {
if ( !ArchExtrude.Dresses( stretch.Zone.Extrude ) ) {
continue;
}
var half = thickness * 0.5f;
var depth = ArchExtrude.Reach( stretch.Zone );
var reach = MathF.Max( half, half + depth - ArchLap.Bite( kit ) );
var spread = half > 0.01f ? reach / half : 1f;
// A corner block runs into the NEIGHBOUR's proud face, or the two elevations' blocks leave a notch between them.
var from = stretch.From <= edgeFrom + 0.01f ? edgeFrom * spread : stretch.From;
var to = stretch.To >= edgeTo - 0.01f ? wall.Length + (edgeTo - wall.Length) * spread : stretch.To;
if ( to - from < 0.5f ) {
continue;
}
// Parted around the openings by the walk that already owns that question, so a zone dragged across a
// whole elevation stands between its windows instead of driving a block through the glass.
foreach ( var patch in ArchWallGen.Panels( from, to, height, openings ) ) {
var low = MathF.Max( stretch.Bottom, patch.MinZ );
var high = MathF.Min( stretch.Top, patch.MaxZ );
if ( high - low < 0.5f || patch.MaxX - patch.MinX < 0.5f ) {
continue;
}
var shape = ArchWallModShape.Resolve(
Block( stretch.Zone, patch.MinX, patch.MaxX, low, high - low ),
patch.MinX, patch.MaxX, height, thickness, kit );
if ( shape.Stands ) {
stood.Add( shape );
}
}
}
return stood;
}
static ArchWallModPart Block( ArchCutPart cut, float from, float to, float seat, float rise ) {
var kind = ArchExtrude.Section( cut.Extrude );
return new ArchWallModPart {
Name = $"{cut.Extrude} Zone",
Kind = kind,
// A recess bites one elevation whatever the zone was told: on both it would be a hole through the
// wall, and a hole is Subtract's job.
Side = cut.ExtrudeBothFaces && !kind.Carves() ? ArchWallSide.Both : ArchWallSide.Outside,
Along = (from + to) * 0.5f,
Width = to - from,
Base = seat,
Height = rise,
Depth = ArchExtrude.Reach( cut ),
Course = cut.ExtrudeCourse,
Joint = cut.ExtrudeJoint,
Field = kind.Field(),
Palette = cut.Palette
};
}
}