Editor utility for bridge geometry generation. It builds mesh parts for girders, bents, footings, shafts, abutments, upstands and parapets using provided shape, kit and style data, and emits sweeps, beams, prisms and revolved sections into an ArchMesh.
using System;
using System.Collections.Generic;
using Sandbox;
namespace Sunless.Architecture;
// Over a bridged span the road's own strips come out deeper - a bridge is never a different width or camber.
public static class ArchBridgeGen
{
const int RoundSides = 12;
static float Embedment( ArchKit kit ) => ArchGround.Embedment( kit );
// Set back so the fascia carries a shadow line; its top is buried a bite into the slab.
public static void Girder( ArchMesh canvas, ArchBridgeShape shape, ArchKit kit, ArchStyle style )
{
var road = shape.Road;
var part = shape.Part;
var brush = style.Brush( ArchSurface.Bridge, part.Palette, road.Palette );
var bite = ArchContact.Bite( kit );
var ribs = Math.Clamp( part.Ribs, 1, 24 );
var rows = new List<Vector3[]>();
foreach ( var frame in shape.Frames )
{
var left = -ArchBridge.Beam( road, part, frame, false );
var right = ArchBridge.Beam( road, part, frame, true );
var top = ArchBridge.Soffit( road, part, frame ) + bite;
var row = new Vector3[ribs + 1];
for ( var rib = 0; rib <= ribs; rib++ )
{
row[rib] = frame.Side( MathX.Lerp( left, right, rib / (float)ribs ) ).WithZ( top );
}
rows.Add( row );
}
ArchMeshSweep.Solid( canvas, rows, shape.Frames, ArchBridge.Girder( part ) + bite, new[] { brush }, brush, false );
}
public static void Bents( ArchMesh canvas, ArchBridgeShape shape, ArchKit kit, ArchStyle style )
{
var deck = style.Brush( ArchSurface.Bridge, shape.Part.Palette, shape.Road.Palette );
var pier = style.Brush( ArchSurface.Pier, shape.Part.Palette, shape.Road.Palette );
foreach ( var bent in shape.Bents )
{
Bent( canvas, shape, bent, kit, deck, pier );
}
}
// The headstock is thicker than the pier; where the shafts stand is the resolved shape's business.
static void Bent( ArchMesh canvas, ArchBridgeShape shape, ArchBridgeBent bent, ArchKit kit, ArchBrush deck, ArchBrush pier )
{
var part = shape.Part;
var bite = ArchContact.Bite( kit );
var head = MathF.Max( 0f, part.Headstock );
var seat = bent.Bearing - head;
var thickness = MathF.Max( 8f, part.PierDepth ) + ArchContact.Proud( kit ) * 2f;
foreach ( var stand in bent.Stands )
{
if ( head > 0.5f )
{
canvas.Beam( Flat( bent.Frame, stand.From ), Flat( bent.Frame, stand.To ),
-thickness * 0.5f, thickness * 0.5f, seat, bent.Bearing + bite, deck );
}
var foot = stand.Ground - Embedment( kit );
if ( seat - foot < 1f )
{
continue;
}
Footing( canvas, part, bent.Frame, stand, kit, pier );
foreach ( var across in stand.Shafts )
{
Shaft( canvas, part, bent.Frame, across, stand.Width, foot, seat + bite, pier );
}
}
}
// Proud of grade so its edge reads; shafts are footed below its top, cast into it.
static void Footing( ArchMesh canvas, ArchBridgePart part, ArchFrame frame, ArchBridgeStand stand, ArchKit kit, ArchBrush brush )
{
var thickness = MathF.Max( 0f, part.Footing );
if ( thickness < 1f || stand.Shafts.Count == 0 )
{
return;
}
var spread = Math.Clamp( part.FootingSpread, 0f, 96f );
var depth = MathF.Max( 8f, part.PierDepth ) + spread * 2f;
var top = stand.Ground + ArchContact.Proud( kit );
var left = stand.Shafts.Min() - stand.Width * 0.5f - spread;
var right = stand.Shafts.Max() + stand.Width * 0.5f + spread;
canvas.Beam( Flat( frame, left ), Flat( frame, right ),
-depth * 0.5f, depth * 0.5f, top - thickness, top, brush );
}
// Battered - a pier of constant section reads as a post; the taper carries the deck visually.
static void Shaft( ArchMesh canvas, ArchBridgePart part, ArchFrame frame, float across, float width, float foot, float top, ArchBrush brush )
{
var batter = Math.Clamp( part.Batter, 0f, width * 0.4f );
var depth = MathF.Max( 8f, part.PierDepth );
if ( part.Pier == PierStyle.Columns )
{
canvas.Revolve( frame.Side( across ).WithZ( foot ), new List<Vector2>
{
new( width * 0.5f, 0f ),
new( width * 0.5f - batter, top - foot )
}, RoundSides, 1f, 1f, brush );
return;
}
var pinch = MathF.Min( batter, depth * 0.4f );
canvas.Prism(
ArchBridge.Section( frame, across, width, depth, foot ),
ArchBridge.Section( frame, across, width - batter * 2f, depth - pinch * 2f, top ),
brush );
}
// Takes the FULL deck width - the deck edge bears on it too; held to the girder, slab would overhang nothing.
public static void Abutments( ArchMesh canvas, ArchBridgeShape shape, ArchKit kit, ArchStyle style )
{
var brush = style.Brush( ArchSurface.Bridge, shape.Part.Palette, shape.Road.Palette );
Abutment( canvas, shape, shape.Near, true, kit, brush );
Abutment( canvas, shape, shape.Far, false, kit, brush );
ArchWing.Build( canvas, shape.Wings, MathF.Max( 8f, shape.Part.AbutmentDepth * 0.3f ), kit, brush );
}
static void Abutment( ArchMesh canvas, ArchBridgeShape shape, ArchBridgeBent bent, bool near, ArchKit kit, ArchBrush brush )
{
var road = shape.Road;
var part = shape.Part;
var frame = bent.Frame;
var bite = ArchContact.Bite( kit );
var foot = bent.Ground - Embedment( kit );
var top = ArchBridge.Soffit( road, part, frame ) + bite;
if ( top - foot < 1f )
{
return;
}
var left = ArchBridge.Edge( road, frame, false );
var right = ArchBridge.Edge( road, frame, true );
var depth = MathF.Max( 12f, part.AbutmentDepth );
// Grow AWAY from the deck - built off one direction the far abutment runs back under the span.
var from = near ? Flat( frame, -left ) : Flat( frame, right );
var to = near ? Flat( frame, right ) : Flat( frame, -left );
canvas.Beam( from, to, -bite, depth, foot, top, brush );
}
// The kerb the parapet bolts to, swept on the deck's own frames so it rakes with the road.
public static void Upstand( ArchMesh canvas, ArchBridgeShape shape, bool right, ArchKit kit, ArchStyle style )
{
var road = shape.Road;
var part = shape.Part;
var height = ArchBridge.Upstand( part );
if ( height < 0.5f )
{
return;
}
var brush = style.Brush( ArchSurface.Bridge, part.Palette, road.Palette );
var bite = ArchContact.Bite( kit );
var kerb = ArchBridge.Kerb( part );
var seats = new List<float>();
var rows = new List<Vector3[]>();
foreach ( var frame in shape.Frames )
{
var edge = ArchBridge.Edge( road, frame, right );
var seat = frame.Position.z + Lift( road, right );
var outer = right ? edge : -edge;
var inner = right ? edge - kerb : -edge + kerb;
seats.Add( seat - bite );
rows.Add( new[]
{
frame.Side( MathF.Min( inner, outer ) ).WithZ( seat + height ),
frame.Side( MathF.Max( inner, outer ) ).WithZ( seat + height )
} );
}
ArchMeshSweep.Solid( canvas, rows, shape.Frames, height + bite, new[] { brush }, brush, false,
soffit: row => seats[row] );
}
// Resolved over the ROAD's whole curve, not a sub-curve - the bay grid would restart at the abutment.
public static void Parapet( ArchMesh canvas, ArchBridgeShape shape, bool right, ArchCurve curve, ArchKit kit, ArchStyle style )
{
var road = shape.Road;
var part = shape.Part;
var stand = ArchBridge.Stand( road, part, right );
var lift = Lift( road, right ) + ArchBridge.Upstand( part );
var resolved = ArchBarrierShape.Resolve( curve, part.Barrier, right ? stand : -stand, lift, kit );
// Cut back to the STRUCTURE's span - the upstand lifting the rail only exists over the resolved span.
ArchBarrierGen.Build( canvas, resolved, part.Barrier, kit,
ArchBarrierBrushes.Of( style, part.Palette, road.Palette ), distance => !shape.Carries( distance ) );
}
// A verge sits a kerb's height above the carriageway it kerbs.
static float Lift( ArchRoadPart road, bool right )
{
return road.Carries( road.Pavements, right ) ? road.KerbHeight : 0f;
}
static Vector2 Flat( ArchFrame frame, float across )
{
var point = frame.Side( across );
return new Vector2( point.x, point.y );
}
}