Editor/Tool/Subtools/ArchCrossingSubtool.cs

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.

File Access
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 ) );
		} );
	}
}