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.
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;
}
}