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