Editor subtool class for placing road crossings in the architecture tool. It handles clicking to add a crossing to a road, drawing hover visuals, and building UI options for crossing kind and width.
using Editor;
using Sandbox;
namespace Sunless.Architecture;
[Title( "Crossing" ), Icon( "directions_walk" ), Group( "03" )]
public sealed class ArchCrossingSubtool( ArchTool owner ) : ArchSubtool( owner )
{
public override ArchSurface[] Surfaces => new[] { ArchSurface.Pavement, ArchSurface.Kerb, ArchSurface.RoadLine };
CrossingKind kind = CrossingKind.Driveway;
float width = 132f;
protected override bool UsesDrag => false;
protected override string Title() => "Drop Crossing";
protected override string Advice() => "Click a road where the kerb should drop. The pavement is never broken - it rakes down to the dropped kerb and back up.";
protected override ArchKind? DraftKind => ArchKind.Crossing;
protected override void OnClick( Vector2 point )
{
if ( Reached( point, out var road, out var frame ) )
{
var crossing = new ArchRoadCrossing
{
Id = Owner.Plan.AllocateId(),
Name = $"Crossing{road.Crossings.Count + 1}",
Kind = kind,
Distance = frame.Distance,
Width = width,
Side = Vector2.Dot( point - frame.Flat, ArchBarrierGen.Across( frame ) ) > 0f ? RoadSide.Right : RoadSide.Left
};
road.Crossings.Add( crossing );
FinishDraft( crossing.Id );
Owner.Commit( "Add Crossing" );
}
}
protected override void DrawHover( Vector2 point )
{
base.DrawHover( point );
if ( !Reached( point, out var road, out var frame ) )
{
ArchGhost.Note( new Vector3( point.x, point.y, Owner.LevelHeight ), "click a road" );
return;
}
var reach = road.HalfWidth + road.PavementWidth;
var half = frame.Along * (width * 0.5f);
Gizmo.Draw.Color = ArchGhost.Accent;
Gizmo.Draw.Line( frame.Side( -reach ), frame.Side( reach ) );
ArchGhost.Note( frame.Position, $"{road.Name} {kind} {width / 12f:0.#} ft" );
// A crossing's width is measured ALONG the road.
Gizmo.Draw.Color = ArchGhost.Line;
Gizmo.Draw.Line( frame.Side( -reach ) - half, frame.Side( reach ) - half );
Gizmo.Draw.Line( frame.Side( -reach ) + half, frame.Side( reach ) + half );
}
// Verge not generous radius: the dropped kerb belongs to the pavement it breaks.
bool Reached( Vector2 point, out ArchRoadPart road, out ArchFrame frame )
{
road = Owner.RoadAt( point, candidate => candidate.HalfWidth + candidate.PavementWidth, out frame );
return road is not null;
}
protected override void BuildOptions( ToolSidebarWidget panel )
{
ArchSidebarSection.Show( panel, Scope( "placement" ), "Crossing", group =>
{
ArchRoadUi.Kinds( group, kind, value => { kind = value; Refresh(); } );
group.Add( ArchPartUi.Number( "Width", width, 132f, value => width = value ) );
} );
}
}