Editor utility that computes placement for approach/seat parts and finds the nearest exterior wall. Seat(...) snaps dimensions to a grid and returns an ArchApproachPart with origin, yaw, width, run, kerbs, rails and splay. NearestExteriorWall(...) iterates buildings/rooms/walls to find the closest outward-facing wall on a given floor and returns the wall plus out parameters for building, room, along, distance and outward vector.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public static class ArchApproachPlacement
{
public static ArchApproachPart Seat(
ArchWall wall,
ArchRoom room,
ArchBuilding building,
ArchKit kit,
float along,
ApproachKind kind,
float width,
float run,
bool kerbs,
bool rails,
float splay )
{
var grid = new ArchGridService();
var unit = grid.SubgridSize( 4 );
var maximumWidth = MathF.Max( unit, MathF.Floor( wall.Length / unit ) * unit );
var snappedWidth = Math.Clamp( grid.Subgrid( MathF.Max( unit, width ), 4 ), unit, maximumWidth );
var snappedAlong = grid.Subgrid( along, 4 );
var centreAlong = Math.Clamp( snappedAlong, snappedWidth * 0.5f, wall.Length - snappedWidth * 0.5f );
var thickness = wall.Thickness > 0f ? wall.Thickness : kit.WallThickness;
var outward = ArchWallFaces.Outward( wall, room, building );
return new ArchApproachPart
{
Kind = kind,
Origin = wall.PointAt( centreAlong ) + outward * (thickness * 0.5f + MathF.Max( 0f, kit.FoundationOversize )),
Yaw = MathF.Atan2( outward.y, outward.x ).RadianToDegree(),
Width = snappedWidth,
Run = run > 0f ? MathF.Max( unit, grid.Subgrid( run, 4 ) ) : 0f,
Kerbs = kerbs,
Rails = rails,
Splay = splay
};
}
public static ArchWall NearestExteriorWall(
IEnumerable<ArchBuilding> buildings,
int level,
Vector2 point,
out ArchBuilding building,
out ArchRoom room,
out float along,
out float distance,
out Vector2 outward )
{
ArchWall best = null;
building = null;
room = null;
along = 0f;
distance = float.MaxValue;
outward = default;
foreach ( var candidateBuilding in buildings )
{
foreach ( var candidateRoom in candidateBuilding.Rooms )
{
if ( candidateRoom.Floor != level )
{
continue;
}
foreach ( var candidateWall in candidateRoom.Walls )
{
var length = candidateWall.Length;
if ( length < 0.5f || !ArchWallFaces.TryOutward( candidateWall, candidateRoom, candidateBuilding, out var candidateOutward ) )
{
continue;
}
var projected = Math.Clamp( Vector2.Dot( point - candidateWall.Start, candidateWall.Direction ), 0f, length );
var gap = (point - candidateWall.PointAt( projected )).Length;
if ( gap >= distance )
{
continue;
}
best = candidateWall;
building = candidateBuilding;
room = candidateRoom;
along = projected;
distance = gap;
outward = candidateOutward;
}
}
}
return best;
}
}