From 1fa7dee5d6fb9575c5141b9f14dc56c8e2bbfd2f Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 12:43:36 +0200 Subject: [PATCH] fix(track): at a crossing at a wide angle the floor of the flangeway between a running rail and its guard rail is laid only where real crossings have it, as the cast floor of each corner up to 0.3 m past the rail of the other track and as separate spacer blocks every 0.5 m along the rest of the guard, and the floor takes its texture across its width at the scale of the head instead of one stretched column; the wheels of the other track roll over a guard rail across their whole tread instead of only over the head of their rail; and when the tracks cross at nearly a right angle the treads of the other track are laid as plain spans along the rail instead of lines running almost straight across it, which left fins standing up at every edge of a tread --- world/RailLayer.cpp | 25 ++++++++++----- world/RailLayout.h | 5 +++ world/RailPlan.cpp | 77 +++++++++++++++++++++++++++++++++++++-------- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/world/RailLayer.cpp b/world/RailLayer.cpp index 26479d94..a8a18312 100644 --- a/world/RailLayer.cpp +++ b/world/RailLayer.cpp @@ -169,15 +169,15 @@ using namespace track_rails; namespace { -gfx::vertex_array box_profile( float const Bottom, float const Top, float const Texture ) { +gfx::vertex_array box_profile( float const Bottom, float const Top, float const Texture, float const Spread = 0.f ) { return { - {{ 1.f, Bottom, 0.f }, { 1.f, 0.f, 0.f }, { Texture, 0.f }}, - {{ 1.f, Top, 0.f }, { 1.f, 0.f, 0.f }, { Texture, 0.f }}, - {{ 1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture, 0.f }}, - {{ -1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture, 0.f }}, - {{ -1.f, Top, 0.f }, { -1.f, 0.f, 0.f }, { Texture, 0.f }}, - {{ -1.f, Bottom, 0.f }, { -1.f, 0.f, 0.f }, { Texture, 0.f }} }; + {{ 1.f, Bottom, 0.f }, { 1.f, 0.f, 0.f }, { Texture + Spread, 0.f }}, + {{ 1.f, Top, 0.f }, { 1.f, 0.f, 0.f }, { Texture + Spread, 0.f }}, + {{ 1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture + Spread, 0.f }}, + {{ -1.f, Top, 0.f }, { 0.f, 1.f, 0.f }, { Texture - Spread, 0.f }}, + {{ -1.f, Top, 0.f }, { -1.f, 0.f, 0.f }, { Texture - Spread, 0.f }}, + {{ -1.f, Bottom, 0.f }, { -1.f, 0.f, 0.f }, { Texture - Spread, 0.f }} }; } } // namespace @@ -243,7 +243,16 @@ TTrack::rail_drawing TTrack::make_rail_drawing( gfx::geometrybank_handle const & break; } } - sides( box_profile( bottom, neck, webtexture ), drawing.fillers ); + glm::vec2 across { 1e9f, -1e9f }; + glm::vec2 band { 1e9f, -1e9f }; + for( std::size_t i = 0; i < outline.size() / 2; ++i ) { + if( outline[ i ].position.y > highest - 0.0005f ) { + across = { std::min( across.x, outline[ i ].position.x ), std::max( across.y, outline[ i ].position.x ) }; + band = { std::min( band.x, outline[ i ].texture.s ), std::max( band.y, outline[ i ].texture.s ) }; + } + } + auto const spread { across.y > across.x ? ( band.y - band.x ) / ( across.y - across.x ) : 0.f }; + sides( box_profile( bottom, neck, webtexture, spread ), drawing.fillers ); } return drawing; } diff --git a/world/RailLayout.h b/world/RailLayout.h index efbd0b2a..fae6d5ad 100644 --- a/world/RailLayout.h +++ b/world/RailLayout.h @@ -30,6 +30,10 @@ double constexpr middle_angle { 0.35 }; double constexpr wide_angle { 0.785 }; double constexpr guard_reach { 1.0 }; double constexpr guard_flare { 0.6 }; +double constexpr casting_reach { 0.3 }; +double constexpr square_slope { 3.0 }; +double constexpr spacer_length { 0.12 }; +double constexpr spacer_pitch { 0.5 }; // where the inner rails of the two paths of a switch cross, found from their gauge lines behind the blades struct switch_frog { @@ -122,6 +126,7 @@ struct guard_rail { glm::dvec2 tomiter { 0.0 }; bool through { true }; std::vector treads; + knots::line castings; }; struct rail_band { double at { 0.0 }; diff --git a/world/RailPlan.cpp b/world/RailPlan.cpp index f3e9b0eb..f9ebac10 100644 --- a/world/RailPlan.cpp +++ b/world/RailPlan.cpp @@ -142,20 +142,58 @@ track_rails::rail_cut corner_cut( track_rails::rail_crossing const &Crossing, do return cut; } -track_rails::rail_fill guard_fill( track_rails::guard_rail const &Guard, track_rails::rail_size const &Size ) { +std::vector guard_fills( track_rails::guard_rail const &Guard, track_rails::rail_size const &Size ) { auto const offset { Size.web - ( flangeway + Size.head ) }; auto const flare { std::min( guard_flare, 0.25 * ( Guard.to - Guard.from ) ) }; - track_rails::rail_fill fill; - fill.web = true; - fill.from = Guard.from; - fill.to = Guard.to; - fill.capfrom = true; - fill.capto = true; - fill.lift = ( Guard.through ? 0.0 : -0.001 ); - fill.inner = { { Guard.from, offset - ( Guard.flarefrom ? 0.02 : 0.0 ) }, { Guard.from + flare, offset }, { Guard.to - flare, offset }, { Guard.to, offset - ( Guard.flareto ? 0.02 : 0.0 ) } }; - fill.outer = { { Guard.from, Size.web }, { Guard.to, Size.web } }; - return fill; + track_rails::rail_fill whole; + whole.web = true; + whole.lift = ( Guard.through ? 0.0 : -0.001 ); + whole.inner = { { Guard.from, offset - ( Guard.flarefrom ? 0.02 : 0.0 ) }, { Guard.from + flare, offset }, { Guard.to - flare, offset }, { Guard.to, offset - ( Guard.flareto ? 0.02 : 0.0 ) } }; + whole.outer = { { Guard.from, Size.web }, { Guard.to, Size.web } }; + knots::line pieces; + for( auto const &casting : Guard.castings ) { + if( false == pieces.empty() && casting.x <= pieces.back().y ) { + pieces.back().y = std::max( pieces.back().y, casting.y ); + } + else { + pieces.push_back( casting ); + } + } + knots::line spacers; + auto const place = [&]( double const From, double const To, double const Step ) { + auto const count { static_cast( std::floor( ( To - From - 0.5 * spacer_length ) / spacer_pitch ) ) }; + for( int i = 1; i <= count; ++i ) { + auto const at { ( Step > 0.0 ? From : To ) + Step * i * spacer_pitch }; + spacers.emplace_back( at - 0.5 * spacer_length, at + 0.5 * spacer_length ); + } }; + auto const first { Guard.from + spacer_length }; + auto const last { Guard.to - spacer_length }; + if( pieces.empty() ) { + place( first - spacer_pitch, last, 1.0 ); + } + else { + place( first, pieces.front().x, -1.0 ); + for( std::size_t i = 1; i < pieces.size(); ++i ) { + auto const middle { 0.5 * ( pieces[ i - 1 ].y + pieces[ i ].x ) }; + place( pieces[ i - 1 ].y, middle, 1.0 ); + place( middle, pieces[ i ].x, -1.0 ); + } + place( pieces.back().y, last, 1.0 ); + } + pieces.insert( pieces.end(), spacers.begin(), spacers.end() ); + std::vector fills; + for( auto const &piece : pieces ) { + auto fill { whole }; + fill.from = std::max( piece.x, Guard.from ); + fill.to = std::min( piece.y, Guard.to ); + fill.capfrom = true; + fill.capto = true; + if( fill.to - fill.from > 0.01 ) { + fills.push_back( fill ); + } + } + return fills; } bool goes_through( TTrack const &Track, TTrack const &Other ) { @@ -217,6 +255,14 @@ track_rails::rail_tread wide_tread( track_rails::rail_crossing const &Crossing, auto const facing { std::abs( Crossing.facing ) > 0.02 ? Crossing.facing : std::copysign( 0.02, Crossing.facing ) }; auto const slope { Crossing.into / facing }; track_rails::rail_tread tread { 1e9, -1e9, Material }; + if( std::abs( slope ) > square_slope ) { + for( auto const edge : { 0.0, -Reach } ) { + auto const distance { Crossing.at + ( edge + Crossing.facing * ( Base + 0.5 * Head ) ) / Crossing.into }; + tread.from = std::min( tread.from, distance ); + tread.to = std::max( tread.to, distance ); + } + return tread; + } for( auto const across : { Base, Base + Head } ) { for( auto const edge : { tread_fade, -Reach - tread_fade } ) { auto const distance { Crossing.at + ( edge + Crossing.facing * across ) / Crossing.into }; @@ -317,6 +363,8 @@ void merge_guard( std::vector &Guards, track_rails::gua span.plan.cuts.insert( span.plan.cuts.end(), Guard.plan.cuts.begin(), Guard.plan.cuts.end() ); span.through = ( span.through && Guard.through ); span.treads.insert( span.treads.end(), Guard.treads.begin(), Guard.treads.end() ); + span.castings.insert( span.castings.end(), Guard.castings.begin(), Guard.castings.end() ); + knots::sort( span.castings ); return; } } @@ -405,7 +453,8 @@ rail_layout TTrack::rail_planner::lay_out() { for( int path = 0; path < 2; ++path ) { for( int side = 0; side < 2; ++side ) { for( auto const &guard : m_layout.guards[ path ][ side ] ) { - m_layout.fills[ path ][ side ].push_back( guard_fill( guard, m_size ) ); + auto const fills { guard_fills( guard, m_size ) }; + m_layout.fills[ path ][ side ].insert( m_layout.fills[ path ][ side ].end(), fills.begin(), fills.end() ); } } } @@ -470,9 +519,11 @@ void TTrack::rail_planner::cross_wide( rail_crossing const &Crossing, rail_size auto const reach { ( flangeway + m_size.head + guard_reach ) / std::abs( Crossing.into ) }; track_rails::guard_rail guard { Crossing.at - reach, Crossing.at + reach }; guard.through = goes_through( m_track, *Crossing.track ); + auto const casting { ( flangeway + Other.head + casting_reach ) / std::abs( Crossing.into ) }; + guard.castings.emplace_back( Crossing.at - casting, Crossing.at + casting ); guard.plan.cuts.push_back( corner_cut( Crossing, -flangeway, 1.0, m_size.head, Other.head ) ); auto const theirs { Crossing.track->rail_material( Crossing.otherpath ) }; - guard.treads.push_back( wide_tread( Crossing, theirs != null_handle ? theirs : m_track.rail_material( Crossing.path ), m_guardoffset, m_size.head, Other.head ) ); + guard.treads.push_back( wide_tread( Crossing, theirs != null_handle ? theirs : m_track.rail_material( Crossing.path ), m_guardoffset, m_size.head, rolled_band ) ); guard.treads.back().fade = 0.0; merge_guard( m_layout.guards[ Crossing.path ][ Crossing.side > 0 ], guard ); }