From 2a5dda150955b81e541eb835a0daf68ccd26fb53 Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 11:25:34 +0200 Subject: [PATCH] feat(track): at a crossing at a wide angle the tread of the other track is laid where its wheels roll over the mitred corners: the part of a guard rail under the running rail of the other track becomes a running surface in the shared tread material instead of matte, and running rails of different materials share the tread over the rolled band of the other track instead of over a square span; wide_tread draws the edges of the band from the crossing itself, since the gauge lines meet in one point at right angles, and a tread gives its own fade, widened for bands lying across the rail; treaded keeps the matte parts of a guard rail matte, lay_treads splits guard rails at their treads, and a piece of no triangles is not inserted --- world/RailLayer.cpp | 64 +++++++++++++++++++++++++++++++++------------ world/RailLayout.h | 20 +++++++------- world/RailPlan.cpp | 30 ++++++++++++++++++++- 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/world/RailLayer.cpp b/world/RailLayer.cpp index 4d6b07ed..26479d94 100644 --- a/world/RailLayer.cpp +++ b/world/RailLayer.cpp @@ -465,6 +465,7 @@ private: void lay_guards(); void lay_checks(); void lay_fills(); + void lay_treads( rail_piece const &Whole, std::vector const &Treads ); void lay_cuts( gfx::vertex_array const &Shape, std::vector Cuts, bool const Wheels, std::vector const &Treads, float const Wallu = -1.f ); void lay_grooved( gfx::vertex_array const &Outline ); grooved_profile split_grooved( gfx::vertex_array const &Outline ) const; @@ -581,10 +582,7 @@ void TTrack::rail_layer::lay_guards() { ( ending == ( miter.y < 0.0 ) ? uppers : lowers ).emplace_back( 1.0 / miter.y, -miter.x / miter.y - m_guardoffset ); } } - piece( - m_profile, - { .from = a, .to = b, .offsets = offsets, .extents = envelope( uppers, true, a, b ), .inner = envelope( lowers, false, a, b ), .capfrom = true, .capto = true, .rolled = false }, - { a, b, {}, {} } ); + lay_treads( { .from = a, .to = b, .offsets = offsets, .extents = envelope( uppers, true, a, b ), .inner = envelope( lowers, false, a, b ), .capfrom = true, .capto = true, .rolled = false }, guard.treads ); } if( i < cuts.size() && cuts[ i ].start > start ) { start = cuts[ i ].start; @@ -614,6 +612,34 @@ void TTrack::rail_layer::lay_checks() { } } +void TTrack::rail_layer::lay_treads( rail_piece const &Whole, std::vector const &Treads ) { + + std::vector splits { Whole.from, Whole.to }; + for( auto const &tread : Treads ) { + for( auto const edge : { tread.from, tread.to } ) { + if( edge > Whole.from + 0.01 && edge < Whole.to - 0.01 ) { + splits.push_back( edge ); + } + } + } + std::sort( splits.begin(), splits.end() ); + piece_ends const ends { Whole.from, Whole.to }; + for( std::size_t k = 1; k < splits.size(); ++k ) { + auto part { Whole }; + part.from = splits[ k - 1 ]; + part.to = splits[ k ]; + part.capfrom = ( k == 1 && Whole.capfrom ); + part.capto = ( k + 1 == splits.size() && Whole.capto ); + auto const *tread { tread_at( Treads, 0.5 * ( part.from + part.to ) ) }; + if( tread != nullptr && m_topfirst < m_toplast ) { + treaded( part, *tread, ends ); + } + else { + piece( m_profile, part, ends ); + } + } +} + void TTrack::rail_layer::lay_fills() { for( auto const &fill : ours( m_layout.fills ) ) { @@ -923,33 +949,34 @@ void TTrack::rail_layer::treaded( rail_piece Whole, rail_tread const &Tread, pie auto const colour { 0.5f * ( tread_colour( Tread.material, m_track.tread_band() ) + tread_colour( own, m_track.tread_band() ) ) }; auto const tint = [&]( material_handle const Material, glm::vec3 const &Mark ) { return paint( Material, marked ? Mark : colour ); }; - auto const owntint { tint( own, { 1.f, 0.f, 0.f } ) }; + auto const owntint { Whole.rolled ? tint( own, { 1.f, 0.f, 0.f } ) : null_handle }; auto const sharedtint { tint( Tread.material, { 0.f, 1.f, 0.f } ) }; auto const shared { marked ? tint( Tread.material, { 0.f, 0.f, 1.f } ) : Tread.material }; auto const ramp = [&]( knots::line const &Knots ) { return marked ? knots::line {} : Knots; }; + auto const fade { Tread.fade }; auto const centre { knots::middle( lower, upper ) }; - auto const beneath { knots::shifted( lower, -tread_fade ) }; - auto const beyond { knots::shifted( upper, tread_fade ) }; - auto const lowfade { knots::bound( knots::shifted( lower, tread_fade ), centre, true ) }; - auto const highfade { knots::bound( knots::shifted( upper, -tread_fade ), centre, false ) }; + auto const beneath { knots::shifted( lower, -fade ) }; + auto const beyond { knots::shifted( upper, fade ) }; + auto const lowfade { knots::bound( knots::shifted( lower, fade ), centre, true ) }; + auto const highfade { knots::bound( knots::shifted( upper, -fade ), centre, false ) }; auto splits { cross_splits( Whole, reach, { &lower, &upper, &lowfade, &highfade, &beneath, &beyond }, 0.001 ) }; - add_meetings( knots::shifted( lower, 2.0 * tread_fade ), upper, Whole.from, Whole.to, 0.001, splits ); + add_meetings( knots::shifted( lower, 2.0 * fade ), upper, Whole.from, Whole.to, 0.001, splits ); std::sort( splits.begin(), splits.end() ); lay_top( top, Whole, reach, splits, { - { null_handle, knots::bound( Whole.extents, beneath, true ), Whole.inner }, - { owntint, knots::bound( Whole.extents, lower, true ), knots::bound( Whole.inner, beneath, false ), false, ramp( lower ), tread_fade }, - { sharedtint, knots::bound( Whole.extents, lowfade, true ), knots::bound( Whole.inner, lower, false ), false, ramp( lower ), -tread_fade }, + { null_handle, knots::bound( Whole.extents, beneath, true ), Whole.inner, Whole.rolled }, + { owntint, knots::bound( Whole.extents, lower, true ), knots::bound( Whole.inner, beneath, false ), false, ramp( lower ), fade }, + { sharedtint, knots::bound( Whole.extents, lowfade, true ), knots::bound( Whole.inner, lower, false ), false, ramp( lower ), -fade }, { shared, knots::bound( Whole.extents, highfade, true ), knots::bound( Whole.inner, lowfade, false ), false == marked }, - { sharedtint, knots::bound( Whole.extents, upper, true ), knots::bound( Whole.inner, highfade, false ), false, ramp( upper ), tread_fade }, - { owntint, knots::bound( Whole.extents, beyond, true ), knots::bound( Whole.inner, upper, false ), false, ramp( upper ), -tread_fade }, - { null_handle, Whole.extents, knots::bound( Whole.inner, beyond, false ) } }, + { sharedtint, knots::bound( Whole.extents, upper, true ), knots::bound( Whole.inner, highfade, false ), false, ramp( upper ), fade }, + { owntint, knots::bound( Whole.extents, beyond, true ), knots::bound( Whole.inner, upper, false ), false, ramp( upper ), -fade }, + { null_handle, Whole.extents, knots::bound( Whole.inner, beyond, false ), Whole.rolled } }, [&]( double const Middle ) { auto const from { knots::sample( lower, Middle, 0.0 ) }; auto const to { knots::sample( upper, Middle, 0.0 ) }; - return std::vector { from - tread_fade, from, knots::sample( lowfade, Middle, 0.0 ), knots::sample( highfade, Middle, 0.0 ), to, to + tread_fade }; }, + return std::vector { from - fade, from, knots::sample( lowfade, Middle, 0.0 ), knots::sample( highfade, Middle, 0.0 ), to, to + fade }; }, Ends ); } @@ -1012,6 +1039,9 @@ void TTrack::rail_layer::piece( gfx::vertex_array const &Shape, rail_piece const } } } + if( triangles.empty() ) { + return; + } sink( Piece.material, false == Piece.rolled ).emplace_back( GfxRenderer->Insert( triangles, triangleuserdata, m_drawing.bank, GL_TRIANGLES ) ); } diff --git a/world/RailLayout.h b/world/RailLayout.h index 062bc557..efbd0b2a 100644 --- a/world/RailLayout.h +++ b/world/RailLayout.h @@ -101,6 +101,16 @@ struct rail_cut { struct rail_plan { std::vector cuts; }; +struct rail_tread { + double from { 0.0 }; + double to { 0.0 }; + material_handle material { 0 }; + int anchor { 0 }; + knots::line edge; + knots::line gauge; + bool below { false }; + double fade { tread_fade }; +}; // guard rails of a crossing at a wide angle: the rail profile laid inside the running rail across the flangeway, broken like the rails struct guard_rail { double from { 0.0 }; @@ -111,6 +121,7 @@ struct guard_rail { glm::dvec2 frommiter { 0.0 }; glm::dvec2 tomiter { 0.0 }; bool through { true }; + std::vector treads; }; struct rail_band { double at { 0.0 }; @@ -129,15 +140,6 @@ struct rail_fill { double lift { 0.0 }; bool web { false }; }; -struct rail_tread { - double from { 0.0 }; - double to { 0.0 }; - material_handle material { 0 }; - int anchor { 0 }; - knots::line edge; - knots::line gauge; - bool below { false }; -}; struct rail_layout { rail_plan plans[ 2 ][ 2 ]; // path, right side std::vector bands[ 2 ][ 2 ]; diff --git a/world/RailPlan.cpp b/world/RailPlan.cpp index 59fa3abc..2c17a0c3 100644 --- a/world/RailPlan.cpp +++ b/world/RailPlan.cpp @@ -212,6 +212,29 @@ void vee_fill( std::vector &Fills, double const At, doub Fills.push_back( fill ); } +track_rails::rail_tread wide_tread( track_rails::rail_crossing const &Crossing, material_handle const Material, double const Base, double const Head, double const Reach ) { + + 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 }; + 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 }; + tread.from = std::min( tread.from, distance - 0.01 ); + tread.to = std::max( tread.to, distance + 0.01 ); + } + } + auto const line = [&]( double const Edge ) { + auto const across = [&]( double const Distance ) { + return ( ( Distance - Crossing.at ) * Crossing.into - Edge ) / facing - Base; }; + return knots::line { { tread.from, across( tread.from ) }, { tread.to, across( tread.to ) } }; }; + tread.gauge = line( 0.0 ); + tread.edge = line( -Reach ); + tread.below = ( facing > 0.0 ); + tread.fade = tread_fade * std::sqrt( 1.0 + slope * slope ); + return tread; +} + std::vector tread_edge( TTrack const &Track, int const Path, int const Side, TTrack const &Other, int const Otherpath, int const Otherside, double const From, double const To, bool &Below, std::vector &Gauge ) { auto const &ours { rail_line( Track, Path, Side > 0, false ) }; @@ -293,6 +316,7 @@ void merge_guard( std::vector &Guards, track_rails::gua span.to = std::max( span.to, Guard.to ); 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() ); return; } } @@ -439,11 +463,15 @@ void TTrack::rail_planner::cross_grooved( rail_crossing const &Crossing, rail_si void TTrack::rail_planner::cross_wide( rail_crossing const &Crossing, rail_size const &Other ) { plan_of( Crossing ).cuts.push_back( corner_cut( Crossing, 0.0, -1.0, m_size.head, Other.head ) ); - add_tread( Crossing.path, Crossing.side, shared_span( Crossing, Other ), *Crossing.track, Crossing.otherpath ); + auto const material { m_track.shared_tread( Crossing.path, *Crossing.track, Crossing.otherpath ) }; + if( material != m_track.rail_material( Crossing.path ) ) { + m_layout.treads[ Crossing.path ][ Crossing.side > 0 ].push_back( wide_tread( Crossing, material, 0.0, m_size.head, rolled_band ) ); + } 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 ); guard.plan.cuts.push_back( corner_cut( Crossing, -flangeway, 1.0, m_size.head, Other.head ) ); + guard.treads.push_back( wide_tread( Crossing, material, m_guardoffset, m_size.head, Other.head ) ); merge_guard( m_layout.guards[ Crossing.path ][ Crossing.side > 0 ], guard ); }