From 179748480996f2f92569c194d2c3f925a4e6066d Mon Sep 17 00:00:00 2001 From: maj00r Date: Wed, 14 Oct 2026 00:47:39 +0200 Subject: [PATCH] fix(track): the pieces of the top of a rail head are laid only where they have width on the head between its cut edges, split where the edges of the wheel treads cross those cut edges, and the shared spans reach 2 cm further for the fade, so no inverted piece is drawn inside the neighbouring rail and no fin stands out of the head; EU07_EDITOR_TREADS paints the pieces in plain colours: blue shared, green and red the fades, yellow the edge of the rolled band, magenta the matte part out of reach of the wheels --- world/TrackRails.cpp | 149 ++++++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 60 deletions(-) diff --git a/world/TrackRails.cpp b/world/TrackRails.cpp index 0295479a..da2486fc 100644 --- a/world/TrackRails.cpp +++ b/world/TrackRails.cpp @@ -1212,7 +1212,7 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { for( std::size_t i = 0; i < part.edge.size() && i < part.gauge.size(); ++i ) { auto const low { std::min( part.edge[ i ].y, part.gauge[ i ].y ) }; auto const high { std::max( part.edge[ i ].y, part.gauge[ i ].y ) }; - if( low < size.head && high > 0.0 ) { + if( low - tread_fade < size.head && high + tread_fade > 0.0 ) { from = std::min( from, part.edge[ i ].x ); to = std::max( to, part.edge[ i ].x ); } @@ -1295,12 +1295,12 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { acute( plan, crossing.at, toward, slope, wing_extension( *this, checks, crossing.path, crossing.side, crossing.at, toward, slope, *crossing.track, crossing.otherpath, crossing.otherside, crossing.otherat, crossing.aligned ? -toward : toward ), size ); auto const nose { toward < 0.0 ? plan.cuts.back().start : plan.cuts.back().end }; - auto const shared { crossing.at - toward * ( rolled_band + size.head ) / slope }; + auto const shared { crossing.at - toward * ( rolled_band + size.head + tread_fade ) / slope }; tread( crossing.path, crossing.side, { std::min( nose, shared ), std::max( nose, shared ) }, *crossing.track, crossing.otherpath, toward > 0.0 ? 1 : -1, crossing.otherside ); } else { obtuse( plan, crossing.at, crossing.into, size ); - auto const reach { ( rolled_band + size.head ) / std::max( std::tan( crossing.angle ), 0.01 ) + 0.2 }; + auto const reach { ( rolled_band + size.head + tread_fade ) / std::max( std::tan( crossing.angle ), 0.01 ) + 0.2 }; tread( crossing.path, crossing.side, { crossing.at - reach, crossing.at + reach }, *crossing.track, crossing.otherpath, 0, crossing.otherside ); } } @@ -1421,7 +1421,7 @@ TTrack::rail_layout TTrack::plan_rails( bool const Crossings ) { acute( plans[ path ][ frog.side[ path ] > 0 ], frog.at[ path ], -1.0, slope, wing_extension( *this, checks, path, frog.side[ path ], frog.at[ path ], -1.0, slope, *this, 1 - path, frog.side[ 1 - path ], frog.at[ 1 - path ], 1.0 ), size ); auto const nose { plans[ path ][ frog.side[ path ] > 0 ].cuts.back().start }; - auto const shared { frog.at[ path ] + ( rolled_band + size.head ) / slope }; + auto const shared { frog.at[ path ] + ( rolled_band + size.head + tread_fade ) / slope }; tread( path, frog.side[ path ], { std::min( nose, shared ), std::max( nose, shared ) }, *this, 1 - path, -1, frog.side[ 1 - path ] ); } } @@ -1506,6 +1506,14 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const &treads { Layout.treads[ Path ][ Side > 0 ] }; material_handle current { null_handle }; std::vector tints; + static auto const marked { std::getenv( "EU07_EDITOR_TREADS" ) != nullptr }; + auto const paint = [&]( material_handle const Material, glm::vec3 const &Colour ) { + auto const tinted { tint_material( Material, Colour ) }; + if( tinted != Material ) { + tints.push_back( tinted ); + } + return tinted; }; + std::vector const unramped; auto const sink = [&]( bool const Matte ) -> geometryhandle_sequence & { if( current == null_handle ) { return Matte ? Cuts : Geometry; @@ -1605,6 +1613,33 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i } auto const strip = [&]( std::size_t const First, std::size_t const Last ) { return gfx::vertex_array( Profile.begin() + First, Profile.begin() + Last + 1 ); }; + auto const shifted = []( std::vector Knots, double const Lateral ) { + for( auto &knot : Knots ) { + knot.y += Lateral; + } + return Knots; }; + auto const crossings = [&]( std::vector const &A, std::vector const &B, double const Start, double const End, double const Margin, std::vector &Splits ) { + if( A.empty() || B.empty() ) { + return; + } + std::vector stations; + for( auto const *knots : { &A, &B } ) { + for( auto const &knot : *knots ) { + stations.push_back( knot.x ); + } + } + std::sort( stations.begin(), stations.end() ); + for( std::size_t i = 1; i < stations.size(); ++i ) { + auto const before { level( A, stations[ i - 1 ] ) - level( B, stations[ i - 1 ] ) }; + auto const after { level( A, stations[ i ] ) - level( B, stations[ i ] ) }; + if( before * after < 0.0 ) { + auto const distance { stations[ i - 1 ] + ( stations[ i ] - stations[ i - 1 ] ) * before / ( before - after ) }; + if( distance > Start + Margin && distance < End - Margin ) { + Splits.push_back( distance ); + } + } + } + }; auto const running = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Inner, std::vector const &Rolled, bool const Capstart, bool const Capend ) { gfx::vertex_array vertices; if( segment.RenderLoftSpan( vertices, m_origin, Profile, Side, halfwidth, texturelength(), Start, End, Offsets, Extents, 0.f, Inner ) ) { @@ -1619,37 +1654,39 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i low = std::min( low, static_cast( Side * point.position.x - halfwidth ) ); high = std::max( high, static_cast( Side * point.position.x - halfwidth ) ); } - std::vector inner; - for( auto const &knot : Rolled ) { - inner.emplace_back( knot.x, knot.y - blend ); - } + auto const inner { shifted( Rolled, -blend ) }; std::vector splits { Start, End }; - for( auto const edge : { low, high, low + blend, high + blend } ) { - for( std::size_t i = 1; i < Rolled.size(); ++i ) { - auto const &a { Rolled[ i - 1 ] }; - auto const &b { Rolled[ i ] }; - if( ( a.y - edge ) * ( b.y - edge ) < 0.0 ) { - auto const distance { a.x + ( edge - a.y ) / ( b.y - a.y ) * ( b.x - a.x ) }; - if( distance > Start && distance < End ) { - splits.push_back( distance ); - } - } + std::vector const lowline { { Start, low }, { End, low } }; + std::vector const highline { { Start, high }, { End, high } }; + for( auto const *knots : { &Rolled, &inner } ) { + for( auto const *limit : { &lowline, &highline, &Extents, &Inner } ) { + crossings( *knots, *limit, Start, End, 0.0, splits ); } } std::sort( splits.begin(), splits.end() ); auto const shiny { bound( Extents, inner, true ) }; auto const blended { bound( Extents, Rolled, true ) }; for( std::size_t i = 1; i < splits.size(); ++i ) { - auto const reach { level( Rolled, 0.5 * ( splits[ i - 1 ] + splits[ i ] ) ) }; - if( reach - blend > low ) { + auto const middle { 0.5 * ( splits[ i - 1 ] + splits[ i ] ) }; + auto const reach { level( Rolled, middle ) }; + auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; + auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; + auto const shown = [&]( double const Lowest, double const Highest ) { + return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; + if( shown( -1e9, reach - blend ) ) { piece( splits[ i - 1 ], splits[ i ], Offsets, shiny, false, false, top, true, Inner ); } - if( reach > low && reach - blend < high ) { - piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), Rolled ); + auto const plain { current }; + auto const own { current == null_handle ? rail_material( Path ) : current }; + if( shown( reach - blend, reach ) ) { + current = ( marked ? paint( own, { 1.f, 1.f, 0.f } ) : plain ); + piece( splits[ i - 1 ], splits[ i ], Offsets, blended, false, false, top, false, bound( inner, Inner, false ), marked ? unramped : Rolled ); } - if( reach < high ) { + if( shown( reach, 1e9 ) ) { + current = ( marked ? paint( own, { 1.f, 0.f, 1.f } ) : plain ); piece( splits[ i - 1 ], splits[ i ], Offsets, Extents, false, false, top, false, bound( Rolled, Inner, false ) ); } + current = plain; } }; auto const treaded = [&]( double const Start, double const End, std::vector const &Offsets, std::vector const &Extents, std::vector const &Inner, bool const Capstart, bool const Capend, rail_tread const &Span ) { @@ -1671,19 +1708,13 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const &upper { Span.below ? Span.edge : Span.gauge }; auto const own { rail_material( Path ) }; auto const colour { 0.5f * ( tread_colour( Span.material, tread_band() ) + tread_colour( own, tread_band() ) ) }; - auto const tint = [&]( material_handle const Material ) { - auto const tinted { tint_material( Material, colour ) }; - if( tinted != Material ) { - tints.push_back( tinted ); - } - return tinted; }; - auto const owntint { tint( own ) }; - auto const sharedtint { tint( Span.material ) }; - auto const shifted = []( std::vector Knots, double const Lateral ) { - for( auto &knot : Knots ) { - knot.y += Lateral; - } - return Knots; }; + 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 sharedtint { tint( Span.material, { 0.f, 1.f, 0.f } ) }; + auto const shared { marked ? tint( Span.material, { 0.f, 0.f, 1.f } ) : Span.material }; + auto const ramp = [&]( std::vector const &Knots ) -> std::vector const & { + return marked ? unramped : Knots; }; std::vector centre; { std::vector stations; @@ -1702,20 +1733,14 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const lowfade { bound( shifted( lower, tread_fade ), centre, true ) }; auto const highfade { bound( shifted( upper, -tread_fade ), centre, false ) }; std::vector splits { Start, End }; + std::vector const lowline { { Start, low }, { End, low } }; + std::vector const highline { { Start, high }, { End, high } }; for( auto const *knots : { &lower, &upper, &lowfade, &highfade, &beneath, &beyond } ) { - for( auto const edge : { low, high } ) { - for( std::size_t i = 1; i < knots->size(); ++i ) { - auto const &a { ( *knots )[ i - 1 ] }; - auto const &b { ( *knots )[ i ] }; - if( ( a.y - edge ) * ( b.y - edge ) < 0.0 ) { - auto const distance { a.x + ( edge - a.y ) / ( b.y - a.y ) * ( b.x - a.x ) }; - if( distance > Start + 0.001 && distance < End - 0.001 ) { - splits.push_back( distance ); - } - } - } + for( auto const *limit : { &lowline, &highline, &Extents, &Inner } ) { + crossings( *knots, *limit, Start, End, 0.001, splits ); } } + crossings( shifted( lower, 2.0 * tread_fade ), upper, Start, End, 0.001, splits ); std::sort( splits.begin(), splits.end() ); for( std::size_t i = 1; i < splits.size(); ++i ) { auto const a { splits[ i - 1 ] }; @@ -1725,31 +1750,35 @@ bool TTrack::lay_rail( rail_layout const &Layout, rail_drawing const &Drawing, i auto const to { level( upper, middle ) }; auto const inner { level( lowfade, middle ) }; auto const outer { level( highfade, middle ) }; - if( from - tread_fade > low ) { + auto const least { std::max( low, Inner.empty() ? low : level( Inner, middle ) ) }; + auto const most { std::min( high, Extents.empty() ? high : level( Extents, middle ) ) }; + auto const shown = [&]( double const Lowest, double const Highest ) { + return Highest > Lowest + 1e-6 && Highest > least && Lowest < most; }; + if( shown( -1e9, from - tread_fade ) ) { current = null_handle; piece( a, b, Offsets, bound( Extents, beneath, true ), false, false, top, true, Inner ); } - if( from > low && from - tread_fade < high ) { + if( shown( from - tread_fade, from ) ) { current = owntint; - piece( a, b, Offsets, bound( Extents, lower, true ), false, false, top, false, bound( Inner, beneath, false ), lower, false, 0.f, -1.f, tread_fade ); + piece( a, b, Offsets, bound( Extents, lower, true ), false, false, top, false, bound( Inner, beneath, false ), ramp( lower ), false, 0.f, -1.f, tread_fade ); } - if( inner > low && from < high ) { + if( shown( from, inner ) ) { current = sharedtint; - piece( a, b, Offsets, bound( Extents, lowfade, true ), false, false, top, false, bound( Inner, lower, false ), lower, false, 0.f, -1.f, -tread_fade ); + piece( a, b, Offsets, bound( Extents, lowfade, true ), false, false, top, false, bound( Inner, lower, false ), ramp( lower ), false, 0.f, -1.f, -tread_fade ); } - if( outer > low && inner < high ) { - current = Span.material; - piece( a, b, Offsets, bound( Extents, highfade, true ), false, false, top, true, bound( Inner, lowfade, false ) ); + if( shown( inner, outer ) ) { + current = shared; + piece( a, b, Offsets, bound( Extents, highfade, true ), false, false, top, false == marked, bound( Inner, lowfade, false ) ); } - if( to > low && outer < high ) { + if( shown( outer, to ) ) { current = sharedtint; - piece( a, b, Offsets, bound( Extents, upper, true ), false, false, top, false, bound( Inner, highfade, false ), upper, false, 0.f, -1.f, tread_fade ); + piece( a, b, Offsets, bound( Extents, upper, true ), false, false, top, false, bound( Inner, highfade, false ), ramp( upper ), false, 0.f, -1.f, tread_fade ); } - if( to + tread_fade > low && to < high ) { + if( shown( to, to + tread_fade ) ) { current = owntint; - piece( a, b, Offsets, bound( Extents, beyond, true ), false, false, top, false, bound( Inner, upper, false ), upper, false, 0.f, -1.f, -tread_fade ); + piece( a, b, Offsets, bound( Extents, beyond, true ), false, false, top, false, bound( Inner, upper, false ), ramp( upper ), false, 0.f, -1.f, -tread_fade ); } - if( to + tread_fade < high ) { + if( shown( to + tread_fade, 1e9 ) ) { current = null_handle; piece( a, b, Offsets, Extents, false, false, top, true, bound( Inner, beyond, false ) ); }