Editor/Road/ArchRoadPreviews.cs

Editor preview rendering for road, bridge and tunnel architecture parts. Draws gizmo outlines, verges, crossings and delegates bridge/tunnel previews to shape resolvers and draw helpers.

File Access
namespace Sunless.Architecture;

public sealed class ArchRoadPreview : IArchPreview
{
	public ArchKind Kind => ArchKind.Road;

	public void Draw( ArchPreviewContext context )
	{
		if ( context.Payload is not ArchRoadPart road || context.Tool is not { } tool )
		{
			return;
		}

		var shapes = tool.Resolved( road.Id, () => new ArchRoadPreviewShapes( tool, road ) );

		if ( !shapes.Curve.IsUsable )
		{
			return;
		}

		Gizmo.Draw.Color = context.Selected ? Color.Yellow : ArchGhost.Line.WithAlpha( 0.7f );
		ArchGhost.Path( shapes.Outline, road.Closed );

		if ( !Detailing( tool, road ) )
		{
			return;
		}

		Gizmo.Draw.Color = (context.Selected ? Color.Yellow : Color.White).WithAlpha( 0.35f );
		ArchGhost.Verges( shapes.Curve, shapes.Frames, road.HalfWidth );

		if ( road.Pavements != RoadSide.None )
		{
			ArchGhost.Verges( shapes.Curve, shapes.Frames, road.HalfWidth + road.PavementWidth );
		}

		foreach ( var crossing in shapes.Crossings )
		{
			if ( tool.LayerHidden( crossing.Id ) || !shapes.Curve.Sample( crossing.Distance, out var frame ) )
			{
				continue;
			}

			var reach = road.HalfWidth + road.PavementWidth;

			Gizmo.Draw.Color = tool.Picked?.Targets( crossing ) == true ? Color.Yellow : Color.Green.WithAlpha( 0.8f );
			Gizmo.Draw.Line( frame.Side( -reach ), frame.Side( reach ) );
			Gizmo.Draw.LineSphere( frame.Position, MathF.Max( 8f, crossing.Width * 0.1f ) );
		}

		var previews = ArchPreviews.Load();

		foreach ( var bridge in road.Bridges )
		{
			if ( !tool.LayerHidden( bridge.Id ) )
			{
				previews.Draw( ArchKind.Bridge,
					new ArchPreviewContext( bridge, null, null, 0f, tool.Picked?.Targets( bridge ) == true, tool ) );
			}
		}

		foreach ( var tunnel in road.Tunnels )
		{
			if ( !tool.LayerHidden( tunnel.Id ) )
			{
				previews.Draw( ArchKind.Tunnel,
					new ArchPreviewContext( tunnel, null, null, 0f, tool.Picked?.Targets( tunnel ) == true, tool ) );
			}
		}
	}

	static bool Detailing( ArchTool tool, ArchRoadPart road )
	{
		if ( tool.GroundPoint( out var point ) && tool.RoadUnder( point ) == road )
		{
			return true;
		}

		return tool.Picked is { } picked
			&& (picked.Targets( road )
				|| road.Crossings.Any( picked.Targets )
				|| road.Bridges.Any( picked.Targets )
				|| road.Tunnels.Any( picked.Targets )
				|| road.Cuts.Any( picked.Targets ));
	}
}

public sealed class ArchBridgePreview : IArchPreview
{
	public ArchKind Kind => ArchKind.Bridge;

	public void Draw( ArchPreviewContext context )
	{
		if ( context.Payload is ArchBridgePart bridge
			&& context.Tool is { } tool
			&& tool.Plan.RoadCarrying( bridge ) is { } road )
		{
			var shape = tool.Resolved( bridge.Id, () =>
			{
				var curve = road.Curve();

				return ArchBridge.Resolve( road, bridge, tool.Plan, curve, ArchRoadGen.Frames( road, curve ), tool.Kit, new ArchGround( tool.Scene ) );
			} );

			ArchRoadPreviewDraw.Bridge( shape, !context.Selected );
		}
	}
}

public sealed class ArchTunnelPreview : IArchPreview
{
	public ArchKind Kind => ArchKind.Tunnel;

	public void Draw( ArchPreviewContext context )
	{
		if ( context.Payload is ArchTunnelPart tunnel
			&& context.Tool is { } tool
			&& tool.Plan.RoadHolding( tunnel ) is { } road )
		{
			var shape = tool.Resolved( tunnel.Id, () =>
			{
				var curve = road.Curve();

				return ArchTunnel.Resolve( road, tunnel, curve, ArchRoadGen.Frames( road, curve ), tool.Kit, new ArchGround( tool.Scene ) );
			} );

			ArchRoadPreviewDraw.Tunnel( shape, !context.Selected );
		}
	}
}

sealed class ArchRoadPreviewShapes
{
	readonly ArchTool tool;
	readonly ArchRoadPart road;
	List<Vector3> outline;
	List<ArchFrame> frames;
	List<ArchRoadCrossing> crossings;

	public ArchRoadPreviewShapes( ArchTool tool, ArchRoadPart road )
	{
		this.tool = tool;
		this.road = road;
		Curve = road.Curve();
	}

	public ArchCurve Curve { get; }

	public List<Vector3> Outline => outline ??= Curve.Walk( 24f ).Select( frame => frame.Position ).ToList();

	public List<ArchFrame> Frames => frames ??= ArchRoadGen.Frames( road, Curve );

	public List<ArchRoadCrossing> Crossings => crossings ??= ArchCrossover.Crossings( road, tool.Plan, Curve );

}

static class ArchRoadPreviewDraw
{
	public static void Bridge( ArchBridgeShape shape, bool faint )
	{
		if ( !shape.IsUsable )
		{
			return;
		}

		var road = shape.Road;
		var part = shape.Part;
		Gizmo.Draw.Color = faint ? ArchGhost.Line.WithAlpha( 0.22f ) : ArchGhost.Line;

		for ( var index = 0; index < shape.Frames.Count - 1; index++ )
		{
			var here = shape.Frames[index];
			var next = shape.Frames[index + 1];
			var soffit = ArchBridge.Soffit( road, part, here ) - ArchBridge.Girder( part );
			var beyond = ArchBridge.Soffit( road, part, next ) - ArchBridge.Girder( part );

			foreach ( var right in new[] { false, true } )
			{
				var side = right ? 1f : -1f;
				var edge = here.Side( ArchBridge.Edge( road, here, right ) * side );
				var ahead = next.Side( ArchBridge.Edge( road, next, right ) * side );

				Gizmo.Draw.Line( edge, ahead );
				Gizmo.Draw.Line( edge.WithZ( soffit ), ahead.WithZ( beyond ) );
			}
		}

		Gizmo.Draw.Color = faint ? ArchGhost.Accent.WithAlpha( 0.28f ) : ArchGhost.Accent;

		foreach ( var bent in shape.Bents )
		{
			foreach ( var stand in bent.Stands )
			{
				foreach ( var across in stand.Shafts )
				{
					Shaft(
						ArchBridge.Section( bent.Frame, across, stand.Width, MathF.Max( 8f, part.PierDepth ), stand.Ground ),
						ArchBridge.Section( bent.Frame, across, stand.Width, MathF.Max( 8f, part.PierDepth ), bent.Bearing ) );
				}
			}
		}

		Gizmo.Draw.Color = faint ? ArchGhost.Ground.WithAlpha( 0.14f ) : ArchGhost.Ground;

		foreach ( var wing in shape.Wings )
		{
			Gizmo.Draw.Line( new Vector3( wing.From.x, wing.From.y, wing.Buried ), new Vector3( wing.To.x, wing.To.y, wing.Ground ) );
			Gizmo.Draw.Line( new Vector3( wing.From.x, wing.From.y, wing.Head ), new Vector3( wing.To.x, wing.To.y, wing.Ground ) );
		}

		Gizmo.Draw.Color = ArchGhost.Line;
	}

	public static void Tunnel( ArchTunnelShape shape, bool faint )
	{
		if ( !shape.IsUsable )
		{
			return;
		}

		var ribs = shape.Inner[0].Length;

		Gizmo.Draw.Color = faint ? ArchGhost.Line.WithAlpha( 0.22f ) : ArchGhost.Line;

		for ( var index = 0; index < shape.Frames.Count; index++ )
		{
			if ( index == 0 || index == shape.Frames.Count - 1 || index % 4 == 0 )
			{
				Section( shape.Inner[index] );
			}

			if ( index == shape.Frames.Count - 1 )
			{
				continue;
			}

			foreach ( var rib in new[] { 0, ribs / 2, ribs - 1 } )
			{
				Gizmo.Draw.Line( shape.Inner[index][rib], shape.Inner[index + 1][rib] );
			}
		}

		Gizmo.Draw.Color = faint ? ArchGhost.Accent.WithAlpha( 0.28f ) : ArchGhost.Accent;

		foreach ( var mouth in new[] { shape.Near, shape.Far } )
		{
			var reach = ArchTunnel.Reach( shape.Part, shape.Section );
			var frame = mouth.Frame;

			Gizmo.Draw.Line( frame.Side( -reach ).WithZ( mouth.Foot ), frame.Side( -reach ).WithZ( mouth.Head ) );
			Gizmo.Draw.Line( frame.Side( reach ).WithZ( mouth.Foot ), frame.Side( reach ).WithZ( mouth.Head ) );
			Gizmo.Draw.Line( frame.Side( -reach ).WithZ( mouth.Head ), frame.Side( reach ).WithZ( mouth.Head ) );
		}

		Gizmo.Draw.Color = faint ? ArchGhost.Ground.WithAlpha( 0.14f ) : ArchGhost.Ground;

		foreach ( var mouth in new[] { shape.Near, shape.Far } )
		{
			foreach ( var wing in mouth.Wings ?? new List<ArchWingWall>() )
			{
				Gizmo.Draw.Line( new Vector3( wing.From.x, wing.From.y, wing.Buried ), new Vector3( wing.To.x, wing.To.y, wing.Ground ) );
				Gizmo.Draw.Line( new Vector3( wing.From.x, wing.From.y, wing.Head ), new Vector3( wing.To.x, wing.To.y, wing.Ground ) );
			}
		}

		Gizmo.Draw.Color = ArchGhost.Line;
	}

	static void Section( IReadOnlyList<Vector3> ring )
	{
		for ( var index = 0; index < ring.Count - 1; index++ )
		{
			Gizmo.Draw.Line( ring[index], ring[index + 1] );
		}
	}

	static void Shaft( IReadOnlyList<Vector3> foot, IReadOnlyList<Vector3> head )
	{
		for ( var index = 0; index < foot.Count; index++ )
		{
			var next = (index + 1) % foot.Count;

			Gizmo.Draw.Line( foot[index], foot[next] );
			Gizmo.Draw.Line( head[index], head[next] );
			Gizmo.Draw.Line( foot[index], head[index] );
		}
	}
}