Editor/Road/ArchRoadPreviews.cs
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.AimPoint( 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] );
}
}
}