Editor/Services/ArchProbe.cs

Utility for probing architecture geometry. Computes gap distance, whether a direction is against nearby walls, ray-like reach to nearest wall face considering wall normals, thickness and length, and a helper to test if a mid-point offset lies inside a footprint region.

File Access
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// Flush things start INSIDE a wall's thickness - only counting walls strictly in front reports nothing.
public static class ArchProbe
{
	// Off the edge so even-odd is no coin flip; near enough a wall's thickness reads covered.
	public const float Step = 2f;

	public static float Gap( ArchKit kit ) => MathF.Max( Step, kit?.StairWallGap ?? 10f );

	public static bool Against( IEnumerable<ArchWall> walls, ArchKit kit, Vector2 from, Vector2 direction )
	{
		var gap = Gap( kit );

		return Reach( walls, kit, from, direction, gap ) <= gap;
	}

	// A negative answer means the probe started INSIDE a wall's thickness - flush, not nothing found.
	public static float Reach( IEnumerable<ArchWall> walls, ArchKit kit, Vector2 from, Vector2 direction, float limit )
	{
		var best = float.MaxValue;

		if ( walls is null )
		{
			return best;
		}

		foreach ( var wall in walls )
		{
			var length = wall.Length;

			if ( length < 1f )
			{
				continue;
			}

			// Only a wall square to the probe counts. Loose, and every side of everything reads walled.
			var facing = Vector2.Dot( wall.Normal, direction );

			if ( MathF.Abs( facing ) < 0.8f )
			{
				continue;
			}

			var half = (wall.Thickness > 0f ? wall.Thickness : kit.WallThickness) * 0.5f;
			var travel = Vector2.Dot( wall.Normal, wall.Start - from ) / facing;
			var face = travel - half;

			if ( face < -half - 1f || face > limit || face >= best )
			{
				continue;
			}

			var along = Vector2.Dot( from + direction * travel - wall.Start, wall.Direction );

			if ( along < -1f || along > length + 1f )
			{
				continue;
			}

			best = face;
		}

		return best;
	}

	// Footprints wind with the interior on the left, so outward is the right-hand normal.
	public static bool Faces( IReadOnlyList<IReadOnlyList<Vector2>> region, Vector2 from, Vector2 to, Vector2 outward )
	{
		return region is { Count: > 0 } && ArchFootprint.Encloses( region, (from + to) * 0.5f + outward * Step );
	}
}