Editor/Road/ArchRoadExtentProviders.cs

Three extent provider implementations for road-related architecture pieces in the editor. ArchRoadExtentProvider measures the spatial extent of a road part including kerb, barriers, and child bridges/tunnels. ArchBridgeExtentProvider measures a bridge part by resolving its shape against the road and adding key positions at ground heights. ArchTunnelExtentProvider measures a tunnel part by resolving its section and adding side and mouth extents with correct vertical offsets.

File Access
namespace Sunless.Architecture;

public sealed class ArchRoadExtentProvider : IArchExtentProvider
{
	public ArchKind Kind => ArchKind.Road;

	public ArchExtent Measure( object payload, ArchExtentContext context )
	{
		if ( payload is not ArchRoadPart road )
		{
			return new ArchExtent();
		}

		var extent = new ArchExtent();
		var curve = road.Curve();
		var reach = road.Reach();
		var stand = road.KerbHeight + (road.Barriers == RoadSide.None ? 0f : road.Barrier.Height + road.Barrier.PostOverhang);
		var frames = ArchRoadGen.Frames( road, curve );

		foreach ( var frame in frames )
		{
			extent.Add( frame.Side( -reach ) );
			extent.Add( frame.Side( reach ) );
			extent.Add( frame.Position - Vector3.Up * (road.Thickness + road.KerbHeight + road.Camber) );
			extent.Add( frame.Position + Vector3.Up * stand );
		}

		foreach ( var bridge in ArchLayerGate.Enabled( road.Bridges ) )
		{
			extent.Add( new ArchBridgeExtentProvider().Measure( bridge, context ) );
		}

		foreach ( var tunnel in road.Tunnels )
		{
			extent.Add( new ArchTunnelExtentProvider().Measure( tunnel, context ) );
		}

		return extent.Grown( 4f );
	}
}

public sealed class ArchBridgeExtentProvider : IArchExtentProvider
{
	public ArchKind Kind => ArchKind.Bridge;

	public ArchExtent Measure( object payload, ArchExtentContext context )
	{
		if ( payload is not ArchBridgePart bridge || context.Plan?.RoadCarrying( bridge ) is not { } road )
		{
			return new ArchExtent();
		}

		var extent = new ArchExtent();
		var curve = road.Curve();
		var frames = ArchRoadGen.Frames( road, curve );
		var ground = new ArchGround( context.Tool?.Scene );
		var shape = ArchBridge.Resolve( road, bridge, context.Plan, curve, frames, context.Kit ?? new ArchKit(), ground );

		if ( !shape.IsUsable )
		{
			return extent;
		}

		extent.Add( shape.Near.Frame.Position.WithZ( shape.Near.Ground ) );
		extent.Add( shape.Far.Frame.Position.WithZ( shape.Far.Ground ) );

		foreach ( var bent in shape.Bents )
		{
			extent.Add( bent.Frame.Position.WithZ( bent.Ground ) );
		}

		return extent;
	}
}

public sealed class ArchTunnelExtentProvider : IArchExtentProvider
{
	public ArchKind Kind => ArchKind.Tunnel;

	public ArchExtent Measure( object payload, ArchExtentContext context )
	{
		if ( payload is not ArchTunnelPart tunnel || context.Plan?.RoadHolding( tunnel ) is not { } road )
		{
			return new ArchExtent();
		}

		var extent = new ArchExtent();
		var curve = road.Curve();
		var kit = context.Kit ?? new ArchKit();
		var shape = ArchTunnel.Resolve( road, tunnel, curve, ArchRoadGen.Frames( road, curve ), kit,
			new ArchGround( context.Tool?.Scene ) );

		if ( !shape.IsUsable )
		{
			return extent;
		}

		var reach = ArchTunnel.Reach( tunnel, shape.Section );

		foreach ( var frame in shape.Frames )
		{
			extent.Add( frame.Side( -reach ).WithZ( frame.Position.z + shape.Section.Floor - tunnel.Invert ) );
			extent.Add( frame.Side( reach ).WithZ( frame.Position.z + shape.Section.Crown + tunnel.Lining ) );
		}

		foreach ( var mouth in new[] { shape.Near, shape.Far } )
		{
			extent.Add( mouth.Frame.Side( -reach ).WithZ( mouth.Foot ) );
			extent.Add( mouth.Frame.Side( reach ).WithZ( mouth.Head ) );
		}

		return extent;
	}
}